在上篇2.1节中,我向大家介绍了很多high-level类的使用。里面用到了ERD图中几乎所有的字段,但有个一没有用到:imdb。这个字段存储的是电影在网站imdb.com上的ID,缩写为IMDB(Internet Movie Database),因此这一节中我们会在文档中加入一些超链接。如果你下载了本书的源代码,里面会有一个resources文件夹,此文件夹中有一个poster文件夹:里面包含了每个imdb对应的图片(如Superman Return(超人归来)的ID是0348150,那么里面就会有一个0348150.jpg图片文件)。因此这一节我们首先会学会在文档中创建不同类型的链接,然后使用Chapter和Section对象来获取书签,最后学会如何往文档中添加图片。
现在web网站上充满了超链接,我们无法想像一个web页面中没有a标签。但在PDF文档中呢?
在iText中有很多不同的方法往pdf文中添加链接,在这一节中我们会设置Anchor类的Reference和Name属性,对应与Chunk类的SetRemoteGoto方法和SetLocalDestination方法。
在listing 2.22中,有三个Anchor对象被创建,第一个Anchor将country name作为其文本呈现,而且通过设置Name属性将其作为内部文档的一个书签,和HTML中的<a name="US">一样。第三个Anchor对象的文本是:"Go back to the first page",其会指向文档内部名称为US的书签,和HTML中的<a href="#US">一样。
listing 2.22 MovieLinks1.cs
Anchor imdb; // loop over the countries while (reader .Read ()) { Paragraph country = new Paragraph(); // the name of the country will be a destination Anchor dest = new Anchor(reader.GetString(1), FilmFonts.BOLD); dest.Name = reader.GetString(0); country.Add(dest); country.Add(string.Format(": {0} movies", reader.GetInt32(2))); document.Add(country); string country_id = reader.GetString(0); // loop over the movies foreach (Movie movie in PojoFactory.GetMovies(conn, country_id)) { imdb = new Anchor(movie.Title); // the movie title will be an external link imdb.Reference = string.Format("http://www.imdb.com/title/tt{0}", movie.IMDB); document.Add(imdb); document.Add(Chunk.NEWLINE); } document.NewPage(); } // Create an internal link to the first page Anchor toUS = new Anchor("Go back to the first page."); toUS.Reference = "#US"; document.Add(toUS);
第二个Anchor对象引用的是一个外部的链接,在以上代码中指向IMDB 站点的指定页面,如 http://www.imdb.com/title/tt0348150/ 引用的就是电影[超人归来]的电影信息页面。
不过还有其它的方法来获取相同的效果。
listing 2.23 MovieLinks2.cs
// Create a local destination at the top of the page. Paragraph p = new Paragraph(); Chunk top = new Chunk("Country List", FilmFonts.BOLD); top.SetLocalDestination("top"); p.Add(top); document.Add(p); // create an external link Chunk imdb = new Chunk("Internet Movie Database", FilmFonts.ITALIC); imdb.SetAnchor(new Uri("http://www.imdb.com")); p = new Paragraph("Click on a country, and you'll get a list of movies, containing likes to the "); p.Add(imdb); p.Add("."); document.Add(p); // Create a remote goto p = new Paragraph("This list can be found in a "); Chunk page1 = new Chunk("separate document"); page1.SetRemoteGoto("movie_links_1.pdf", 1); p.Add(page1); p.Add("."); document.Add(p); document.Add(Chunk.NEWLINE); // Create a database connection and statement string connStr = ConfigurationManager.AppSettings["SQLiteConnStr"]; SQLiteConnection conn = new SQLiteConnection(connStr); using (conn) { SQLiteCommand cmd = conn.CreateCommand(); cmd.CommandText = "SELECT DISTINCT mc.country_id, c.country, count(*) AS c " + "FROM film_country c, film_movie_country mc " + "WHERE c.id = mc.country_id " + "GROUP BY mc.country_id, country ORDER BY c DESC"; cmd.CommandType = System.Data.CommandType.Text; conn.Open(); SQLiteDataReader reader = cmd.ExecuteReader(); while (reader .Read ()) { Paragraph country = new Paragraph(reader.GetString(1)); country.Add(": "); Chunk link = new Chunk(string.Format("{0} moives", reader.GetInt32(2))); link.SetRemoteGoto("movie_links_1.pdf", reader.GetString(0)); country.Add(link); document.Add(country); } document.Add(Chunk.NEWLINE); // Create local goto to top p = new Paragraph("Go to"); top = new Chunk("top"); top.SetLocalGoto("top"); p.Add(top); p.Add("."); document.Add(p); }
以上代码使用的是Chunk对象来向一些链接。我们可以设置以下的一些属性:
我们可以使用列出了32个countries的movie_links_2.pdf文档作为movie_links_1.pdf文档一个可以点击的目录(Table Of contents坚持为TOC)。接下来的例子我们会创建一个不同类型的TOC:Adobe Reader的书签。要注意的是书签(bookmarks)在pdf的文档中被引用为outlines。
listing 2.24 MovieHistory.cs
title = new Paragraph(EPOCH[epoch], FONT[0]); chapter = new Chapter(title, epoch + 1); ……… title = new Paragraph(string.Format("The year {0}", movie.Year), FONT[1]); section = chapter.AddSection(title); section.BookmarkTitle = movie.Year.ToString(); section.Indentation = 30; section.BookmarkOpen = false; section.NumberStyle = Section.NUMBERSTYLE_DOTTED_WITHOUT_FINAL_DOT; section.Add(new Paragraph(string.Format("Movies from the year {0}:", movie.Year))); …… title = new Paragraph(movie.Title, FONT[2]); subsection = section.AddSection(title); subsection.Indentation = 20; subsection.NumberDepth = 1;
以上为代码和生成的pdf文档截图。如果滚动pdf的书签面板,你会发现最上层的书签一共有7个带数字的实体:Forties, Fifties, Sixties, Seventies, Eighties, Nineties, and Twenty-first century。这些都是通过Chapter对象实现的,在pdf中每一个Chapter对象都包含了一个或多个Section对象。在以上代码中就是年份(years)属于时代(forties, fifties等)。
在代码中,Chapter相关联的数字是通过构造器中传入进去的,默认情况下在数字后面有一个点,不过可以通过NumberStyle来修改。Seciton对象都是通过AddSection来并传入Section的标题传入进去。此标题会呈现在Pdf文档和对应的书签上,如果希望使用不同的书签标题可以使用BookmarkTitle属性来修改。代码中Section还通过Indentation属性要修改缩进,不过这个只会影响Pdf文档中的缩进,不会对书签有影响。最后我们看下subsection书签的数字,它不是这样标记 5.4.1,5.4.2而是1.,2.。因为在代码中设置了属性NumberDepth=1。
这里要注意的是Section类实现的不是IElement接口,是ILargeElement接口。iText一般会尽快的将Pdf的语句写入到输出流中,这样就可以方便释放内存。但对于类似Section对象,只有在其被加入到Document类是iText才会作Pdf语法的转换工作,这也意味Section会一直保存在内存中直到iText完成转换工作。不过有以下几种方法解决Section的问题:
目前为止我们已经讨论了类图中几乎所有的对象,除了以下两个对象:Rectangle和Image。
在第一节的时候我们使用了Rectangle对象来定义页面大小,不过大部分情况下我们不会通过Document.Add方法将Rectangle添加进去。在第三节时会有更好的方法画图。不过为了完整性,以下是添加Rectangle的代码:
listing 2.25 MoviePosters1.cs
Rectangle rect = new Rectangle(0, 806, 36, 842); rect.BackgroundColor = BaseColor.RED; document.Add(rect);
listing 2.26 MoviePosters1.cs (continued)
document.Add(new Paragraph(item.Title)); // Add an image document.Add(Image.GetInstance(string.Format(RESOURCE, item.IMDB)));
以上的代码就会将Image对象添加到文档中,iText使用不同的图片类处理不同的图片类型:Jpeg,PngImage,GifImage,TiffImage等。所有的这些类会在第十节有详细的讨论。我们可以创建这些不同的类处理Image,不过更方便的方法就是让Image类检测图片的类型并自动选择合适的类来处理。
上图的左边就是listing 2.26生成的pdf文档,仔细观察的话你会发现文本"The Breakfast club"从第四页上移到了上一页。这是默认的行为:iText会尽可能多的为每一页添加内容。不过目前看起来就不太正确,但通过以下代码就可以修正:
listing 2.27 MoviePosters2.cs
PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)).StrictImageSequence = true;
以上代码生成的Pdf文档在图的右侧,属性StrictImageSequence会让iText严格按照添加的顺序来添加内容。
上图中图片打印在页面左边,电影信息的文本打印在图片的边上。这是通过设置Alignment属性完成的,其属性包含以下几个:
但如果使用了SetAbsolutePosition()方法,上面所有的设置都不会起作用。SetAbsolutePosition方法接受一个坐标(x,y),这个坐标是用来定位图片的左下角,图片也不会跟随其它的对象被打印。
如果你仔细留意了类图的话你会发现Image类是继承Rectangle类,因此可以设置边框,长度和颜色等等:
listing 2.28 MoviePosters3.cs
// Create an image Image img = Image.GetInstance(string.Format(RESOURCE, item.IMDB)); img.Alignment = Image.LEFT_ALIGN | Image.TEXTWRAP; img.Border = Image.BOX; img.BorderWidth = 10; img.BorderColor = BaseColor.WHITE; img.ScaleToFit(1000, 72);
Image.BOX值实际上就是Rectangle.RIGHT_BORDER|Rectangle.LEFT_BORDER|Rectangle.TOP_BORDER|Rectangle.BOTTOM_BORDER,也就是说图片在每一边都需要一个border。
在listing 2.28中,我们使用了ScaleToFit()方法,并传入一个美特斯邦威不走寻常路的宽度1000pt,和一个正常的高度72pt。这就可以保证所有的图都有1英寸高,但长度就依赖与图片的高宽比(aspect ratio of the image)。以下是可以改变图片尺寸的一些方法:
缩放图片功能大家可能为认为iText改变了图片一些质量,但实际上iText不会修改图片的像素。当我们从文件中创建一个Image的实例时,我们有时候不太清楚图片的尺寸,但我们从以下几种属性中获取图片的长和宽。
scaled width/height和plain width/height具体的不同会在下一个列子中说明。
图片的旋转方向为逆时针。以下为具体的代码:
listing 2.29 RiverPhoenix.cs
Paragraph p = new Paragraph(); Image imdb = Image.GetInstance(string.Format(rescourec, imdbId)); imdb.ScaleToFit(1000, 72); imdb.RotationDegrees = -30; p.Add(new Chunk(imdb, 0, -15, true));
如果我们查看电影stand by me的图片,你会发现其实100 像素*140像素,也就是通过Width和Height获取的值。然后我们调用方法ScaleToFit(1000, 72),也就是说图片以高宽比不变的形式固定在1000像素*72像素的矩形中,这样图片的大小就会变成51.42857(72/140*100)*72,也就是通过PlainWidth和PlainWidth获取的值。但在下图中你会发现由于图片的旋转其要占据更多的空间,右下角到左上角之间的水平距离就变成了80.53845,右上角到左下角的垂直距离就变成了88.068115,也就是通过ScaledWidth和ScaledHeight获取的值。
在代码listing 2.29中,Image并不是直接添加到Document中,而是先被Chunk包裹,然后此Chunk又被添加到Paragraph中,最终添加到Document的是Paragraph对象。通过将Image包裹在Chunk类中,我们将Image当作普通Chunk文本。在Chunk的构造器中,定义了x和y方向的偏移量。listing 2.29中的负数会导致图片在基准线下添加15pt。最后一个参数设置的是chunk的行高是否适应图片。如果最后一个参数不为true的话,图片就有可能和其他的文本重叠。
通过Achor对象的使用,我们创建了文档的内部书签和外部链接。Chapter和Section对象可以用来创建文档的书签,但接下来我们还会学到文档书签的其他创建方法和一些也实现了ILargeElement接口的对象,最后我们学习了Image的大量常用方法和属性。目前为止大家处理的都是high-level的对象,在下一节中会讨论一些PDF底层创建的东东,最后代码在这里下载。
此文章已同步到目录索引:iText in Action 2nd 读书笔记。