天道酬勤

<<iText in Action 2nd>>3.4节(Creating reusable content)读书笔记

前言

在这一节中我们会讨论两个可重用的对象:Image和PdfTemplate对象。在2.3节我们往文档中添加图片的时候其实就已经接触到Image对象。在一般情况下,图片的字节会被保存在pdf文件中分开的流中,页面如果想要包含这个图片只需引用即可,这种类型的对象也叫做XObject。XObject有很多的类型,不过Image和Form XObject是最重要的。

Image XObjects

当我们将图片添加到文档中时其实就已经和image XObject打交道了,我们知道通过Document.Add方法添加的图片都位于文本的下面,但如果我们希望图片在文本的上面那要如何实现呢?

将图片添加到最上层

这里直接上图和代码:

ImageCoverText

listing 3.23 ImageDirect.cs

string RESOURCE = ConfigurationManager.AppSettings["ResourceImage"];
Image img = Image.GetInstance(RESOURCE + "loa.jpg");

img.SetAbsolutePosition((PageSize.POSTCARD.Width - img.ScaledWidth) / 2, (PageSize.POSTCARD.Height - img.ScaledHeight) / 2);
writer.DirectContent.AddImage(img);
Paragraph p = new Paragraph("Foobar Film Festival", new Font(Font.FontFamily.HELVETICA, 22));
p.Alignment = Element.ALIGN_CENTER;
document.Add(p);

在代码中我们将包含了文本"Foobar Film Festival"的Paragraph添加到文档中,但是从图上可以看到,其文本被图片覆盖了,而且从图上可以看到我们还是可以选择这段文本的,如果copy出来就会看到文本,而且Adobe Reader还提供了一个选项:Look up "Foobar"(英文的Adobe有这个选项,中文测试没有)。

如果我们直接用记事本打开pdf文件,可以看到以下的pdf语法片段:

q
BT
30 386 Td
11.87 -33 Td
/F1 22 Tf
(Foobar Film Festival)Tj
-11.87 0 Td
ET
Q
q 232 0 0 362 25.5 27 cm /img0 Do Q

在第一个q/Q之间的负责打印文本"Foobar Film Festival",其之间的符号是修改CTM(current transformation matrix)。第二个q/Q就是添加图片,其中Do操作符是将图片以232*362 user space units大小在坐标x=25.5,y=27的地方被添加到文档中,而图片的内容是保存在内容流的外部。

在pdf内部,每一页都有一个包含了大量键值对的页面字典(page dictionary)。然后我们可以通过key在/Resources下找到对应的值:

/Resources<</XObject<</img0 1 0 R>> ... >>

以上就是XObjects的入口,也告诉我们/img0 可以在object 1中找到:包含了图片字节的流。这里其实还有大量其它类型的Resource,如页面引用的文件等。

在前几节中,我们学会了如何缩放,旋转图片,但是通过以上代码添加的图片还可以有更多的选择。

倾斜图片

以下就是具体的代码和效果图:

ImageSkew

在我们进行倾斜的操作时其中包含了大量的代数计算,具体的细节大家可以参考14.3节。

listing 3.24 ImageSkew.cs

 // Add the image to the upper layer
string resouce = ConfigurationManager.AppSettings["ResourceImage"] + "loa.jpg";
Image img = Image.GetInstance(resouce);
writer.DirectContent.AddImage(img, img.Width, 0, 0.35f * img.Height, 0.65f * img.Height, 30, 30);

以上代码中格外的参数最终生成的PDF语句如下:

q 232 0 126.7 235.3 30 30 cm /img0 Do Q

如果我们不希望将图片以XObject添加到文档中,那么可以将图片以内联的方法添加进去。

图片内联

图片在内容流中是以字节的方式添加进去的话那么图片就是内联的。

listing 3.25 ImageInline.cs

string resouce = ConfigurationManager.AppSettings["ResourceImage"] + "loa.jpg";
Image img = Image.GetInstance(resouce);
img.SetAbsolutePosition((PageSize.POSTCARD.Width - img.ScaledWidth) / 2, (PageSize.POSTCARD.Height - img.ScaledHeight) / 2);
writer.DirectContent.AddImage(img, true);

现在我们在看pdf的语句时你会在页面的内容流中看到图片的信息,其位于操作符BI(Begin image)和EI(End image)之间,以下为pdf的语句:

q 232 0 0 362 25.5 27 cm
BI
/CS /DeviceRGB
/BPC 8
/W 232
/H 362
/F /DCTDecode
ID
...
EI
Q

但是如果图片内联的话就不能被重用,所以这不是添加图片的最好方法,添加图片时最好使用image XObject。

还有一种类型的XObject就是form XObject,其完全的内容流被当作单个的图形对象。

The PdfTemplate object

单词form在这个上下文中有点模糊,因为我们讨论的不是一般提到的要填充的表单。为了避免此困扰,iText将form XObject定位为对象PdfTemplate。

PDFTEMPLATE: ANOTHER NAME FOR FORM XOBJECT

PdfTemplate是一个可以包含任意序列的图形对象而且是自我描述的Pdf内容流。PdfTemplate对象继承于PdfContentByte对象,因此继承了其父类的所有方法。PdfTemplate一个格外的有自定义尺寸的模板层(template layer),我们可以使用其完成一些不同的任务。

假设你想将数据中所有电影的封面全部在一页中打印出来,为了更好的好看,我们还希望画一些长条让电影的方面看起来就像镜框一样,具体的效果看下图:

TemplateWithStrip

数据库中有120部电影,所以在一行中容纳12部电影,我们需要画10行,但我们只需创建一次就好了。

listing 3.26 MoviePosters.cs

// Create the XObject
PdfTemplate celluloid = canvas.CreateTemplate(595, 84.2f);
celluloid.Rectangle(8, 8, 579, 68);

for (float f = 8.25f; f < 581; f += 6.5f)
{
    celluloid.RoundRectangle(f, 8.5f, 6, 3, 1.5f);
    celluloid.RoundRectangle(f, 72.5f, 6, 3, 1.5f);
}

celluloid.SetGrayFill(0.1f);
celluloid.EoFill();
// Write the XObject to the OutputStream
writer.ReleaseTemplate(celluloid);

代码中通过特定的层来创建PdfTemplate,并且传入自定义的长宽,这里我们定义的长度和页面一样,但高度只有页面的十分之一。然后再里面画了一个大的矩形,矩形里面有很多一系列小的圆矩形。下面的代码比较的特殊:

你可能认为的是调用Fill方法,但是这个方法也会将里面的圆矩形也填充,因此使用EoFill方法来实现,图像就会使用奇偶规则(even odd rule)。如果大家对这种规则不熟悉的话没有关系,具体的解释会在14.2节中展开。

对于XObject流,最重要的要知道他们是被存储在单独的对象中:

1 0 obj
<</Length 153/Filter/FlateDecode>>stream
8 8 579 68 re
...
581.75 72.5 m
584.75 72.5 l
585.58 72.5 586.25 73.17 586.25 74 c
586.25 74 l
586.25 74.83 585.58 75.5 584.75 75.5 c
581.75 75.5 l
580.92 75.5 580.25 74.83 580.25 74 c
580.25 74 l
580.25 73.17 580.92 72.5 581.75 72.5 c
0.1 g
f*
endstream

完整的流比以上的片段要长很多哦,如果你仔细观察文件,你会发现在文件中XObject在逻辑上和物理上都(逻辑上:对象的数字是1,物理上:对象在第十五个字节被添加)是第一个被添加到文档中的。但这不是iText中的标准行为,一般情况下PdfTemplate对象会被一直保存在内存中直到我们调用Document.Add方法,或者显示的调用Writer.ReleaseTemplate方法。这样的行为是有目的的:我们会在第五节的时候发现将XObject保存在内存中的好处。

ADDING PDFTEMPLATE OBJECTS

因为是可重用的内容,因此我们不需要重复的将那一大串的PDF语句添加10次到页面的内容流中,相反它是按照以下方式被引用的:

q 1 0 0 1 0 0 cm /Xf1 Do Q
q 1 0 0 1 0 84.2 cm /Xf1 Do Q
q 1 0 0 1 0 168.4 cm /Xf1 Do Q
q 1 0 0 1 0 252.6 cm /Xf1 Do Q
q 1 0 0 1 0 336.8 cm /Xf1 Do Q
q 1 0 0 1 0 421 cm /Xf1 Do Q
q 1 0 0 1 0 505.2 cm /Xf1 Do Q
q 1 0 0 1 0 589.4 cm /Xf1 Do Q
q 1 0 0 1 0 673.6 cm /Xf1 Do Q
q 1 0 0 1 0 757.8 cm /Xf1 Do Q

以上的pdf语句的片段也很好解释:form XObject /xf1 按照原有大小在坐标(0,y)(其中y是从0到757.8之间的数值,并且有一个84.2递增)被添加进去。

以下的代码是两次将PdfTemplate对象按照特定的x,y坐标添加进去。

listing 3.27 MoviePosters.cs(continued)

// Add the XObject 10 times
for (int i = 0; i < 10; i++)
{
    canvas.AddTemplate(celluloid, 0, i * 84.2f);
}

List<Movie> movies = PojoFactory.GetMovies(conn);
Image img;
float x = 11.5f;
float y = 769.7f;

// Loop over the movies and add images
foreach (Movie movie in movies)
{
    string RESOURCE = ConfigurationManager.AppSettings["ResourcePosters"];
    img = Image.GetInstance(string.Format(RESOURCE, movie.IMDB));
    img.ScaleToFit(1000, 60);
    img.SetAbsolutePosition(x + (45 - img.ScaledWidth) / 2, y);
    canvas.AddImage(img);
    x += 48;
    if (x > 578)
    {
        x = 11.5f;
        y -= 84.2f;
    }
}

下图展示的是通过AddTemplate方法的其他版本:

PdfTemplateCTM

在上图中,电影的线条被添加了四次,但是被转动,缩放,倾斜和旋转。

listing 3.28 MoviePosters.cs (continued)

// Add the template using a different CTM
canvas.AddTemplate(celluloid, 0.8f, 0, 0.35f, 0.65f, 0, 600);
// Wrap the XObject in an Image object
Image tmpImage = Image.GetInstance(celluloid);
tmpImage.SetAbsolutePosition(0, 480);
document.Add(tmpImage);

// Perform transformations on the image
tmpImage.RotationDegrees = 30;
tmpImage.ScalePercent(80);
tmpImage.SetAbsolutePosition(30, 500);
document.Add(tmpImage);
// More transformations
tmpImage.Rotation = ((float) Math.PI/2);
tmpImage.SetAbsolutePosition(200, 300);
document.Add(tmpImage);

方法AddTemplate中格外的参数是CTM的设置,我们可以使用其完成一些更加复杂的转换。但如果我们只需要移动,缩放或者旋转这个模板,我们可以将PdfTemplate包裹在Iamge中从而改善代码的可读性。

WRAPPING A PDFTEMPLATE INSIDE AN IMAGE

通过AddTemplate方法转换的语法如下:

q 0.8 0 0.35 0.65 0 600 cm /Xf1 Do Q

将其包裹在Image中设置的语法如下:

q 1 0 0 1 0 480 cm /Xf1 Do Q
q 0.69282 0.4 -0.4 0.69282 63.68 500 cm /Xf1 Do Q
q 0 0.8 -0.8 0 267.36 300 cm /Xf1 Do Q

大家可以看到其还是被当作XObject对象处理而不是被转换为image XObject。通过这种包裹的方法可以避免对一些CTM的计算,应该算是一种封装吧。最后我们通过PdfTemplate将前几节中创建的Film Festival的页面大小减少。

ADAPTING THE TIMETABLE EXAMPLE

在3.1节中,我们在文档的每一页中都画了两个Grid。但将此两个Grid添加到PdfTemplate应该是个重用的好例子。

listing 3.29 MovieTemplates.cs

PdfContentByte over = writer.DirectContent;
PdfContentByte under = writer.DirectContentUnder;

locations = PojoFactory.GetLocations(conn);
PdfTemplate t_under = under.CreateTemplate(PageSize.A4.Height, PageSize.A4.Width);
DrawTimeTable(t_under);

PdfTemplate t_over = over.CreateTemplate(PageSize.A4.Height, PageSize.A4.Width);
DrawTimeSlots(t_over);

DrawInfo(t_over);

List<DateTime> days = PojoFactory.GetDays(conn);
List<Screening> screenings;

int d = 1;
foreach (DateTime day in days)
{
    over.AddTemplate(t_over, 0, 0);
    under.AddTemplate(t_under, 0, 0);
    DrawDateInfo(day, d++, over);
    screenings = PojoFactory.GetScreenings(conn, day);
    foreach (Screening screening in screenings)
    {
        DrawBlock(screening, under, over);
        DrawMovieInfo(screening, over);
    }

    document.NewPage();
}

因为MovieTemplates继续MovieCalander,所以DrawTimeTable和DrawTimeSlots方法可以被重用。最后大家可以比较一下文件的大小:通过MovieCalendar生成的Pdf的大小是18.82kb,而通过MovieTemplates生成的pdf的大小是14.29kb,这也意味着我们通过XObject的使用可以节省大概25%的空间。

总结:

对XObject的使用主要目的是减少文件的大小,iText对Image对象有了内置的支持。对于PdfTemplate则需要自己创建,但我们可以设置PdfTemplate的CTM来实现很复杂的功能,如果只是简单的旋转,移动和缩放则可以将其包裹在Image中,然后调用Image对应的方法即可。在接下来的章节中我们会学到iText中最重要的俩个类:PdfPTable和PdfPCell,我们还会学到在PdfPCell对象的内部使用了ColumnText对象来处理每个单元格的文本。最后就是代码下载

同步

此文章已同步到目录索引:iText in Action 2nd 读书笔记。

posted @ 2012-06-30 23:47  JulyLuo  阅读(2467)  评论(0编辑  收藏  举报