<<iText in Action 2nd>>3.1节(Introducing the concept of direct content)读书笔记
前言
在第一节中我们学会了如何创建一个pdf文档,在2.2和2.3节时介绍了iText中的high-level对象的使用。接下来中我们会学习一种完全不同的添加内容模式:这通常也叫做low-level operations,因为我们是直接将pdf的语法添加到页面的内容流中。
Introducing the concept of direct content
好了先上图:
图的左边是通过先添加一个包裹了文本"Foobar Film Festival"的Paragraph到文档中,然后又将一张图片添加到文档中,不过图片的定位是通过SetAbsolutePosition方法实现的。在这里添加的顺序无关重要,因为通过Document.Add方法一般会将图片添加到图像层(image layer),而图像层是位于文本层(text layer)下面。图的右边使用了相同的Paragraph和Image对象,不过在图像层下面还添加了一个白色的矩形,在文本层的上面添加了文本"SOLD OUT"。
Direct content layers
以下就是具体的代码:
listing 3.1 FestivalOpening.cs
Paragraph p = new Paragraph("Foobar Film Festival", new Font(Font.FontFamily.HELVETICA, 22)); p.Alignment = Element.ALIGN_CENTER; document.Add(p); Image img = Image.GetInstance(Resource); img.SetAbsolutePosition((PageSize.POSTCARD.Width - img.ScaledWidth) / 2, ((PageSize.POSTCARD.Height - img.ScaledWidth) / 2)); document.Add(img); document.NewPage(); document.Add(p); document.Add(img); PdfContentByte over = writer.DirectContent; over.SaveState(); float sinus = (float)Math.Sin(Math.PI / 60); float cosinus = (float)Math.Cos(Math.PI / 60); BaseFont bf = BaseFont.CreateFont(); over.BeginText(); over.SetTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE); over.SetLineWidth(1.5f); over.SetRGBColorStroke(0XFF, 0X00, 0X00); over.SetRGBColorFill(0Xff, 0xFF, 0xFF); over.SetFontAndSize(bf, 36); over.SetTextMatrix(cosinus, sinus, -sinus, cosinus, 50, 324); over.ShowText("SOLD OUT"); over.EndText(); over.RestoreState(); PdfContentByte under = writer.DirectContentUnder; under.SaveState(); under.SetRGBColorFill(0xFF, 0xD7, 0x00); under.Rectangle(5, 5, PageSize.POSTCARD.Width - 10, PageSize.POSTCARD.Height - 10); under.Fill(); under.RestoreState();
但是How does this work?其实当我们往页面添加内容时--不管是通过Document.Add方法还是其他的方法--iText会将PDF的语法写入到PdfContentByte类的ByteBuffer对象中。当页面充满内容时,这些buffers就以特定的顺序添加到PDF文档中。我们可以将每一个Buffer当作一个图层来理解,iText也已下图中的顺序来处理图层。
当页面初始化时,以下两个PdfContentByte对象被创建,这两个对象是处理high-level类。
- 一个负责文本的PdfContentByte(上图中的layer3):如Chunk,Pharse,Paragraph等对象的文本
- 一个负责图像的PdfContentByte(上图中的layer2):如Chunk,Image的背景,PdfPcell的边框等。
我们是不能直接获取图中的layer 2和layer3:这两个layers由iText内部管理,但我们还有两个格外的选择:layer 1和layer 4。
- layer 4位于文本和图形层的上面:我们可以通过代码PdfWriter.DirectContent来获取。
- layer 1位于文本和图形层的下面:可以通过代码PdfWriter.DirectContentUnder来获取。
对iText而言,添加内容到这两个格外的层也叫做writing to the direct content或者low-level access,因为我们在PdfContentByte对象中使用了一些low-level的操作,就如果代码listing 3.1中一样。但我们还可以进行一些更多地操作:画图画线,将文本绝对定位,不过在此之前还需要知道一些图形状态(graphics state)的概念。
Graphics state and text state
图形状态
在listing 3.1代码中,我们通过Rectangle方法在已经存在的内容下面画了一个矩形。这个矩形就是一个图形化元素,接下来我们会在以下代码中添加五个矩形,具体的效果可以看图:
listing 3.2 GraphicsStateStack.cs
// state 1: canvas.SetRGBColorFill(0xFF, 0x45, 0X00); // fill a rectangle in state 1 canvas.Rectangle(10, 10, 60, 60); canvas.Fill(); canvas.SaveState(); // state 2; canvas.SetLineWidth(3); canvas.SetRGBColorFill(0x8B, 0x00, 0x00); // fill and stroke a rectangle in state 2 canvas.Rectangle(40, 20, 60, 60); canvas.FillStroke(); canvas.SaveState(); // state 3: canvas.ConcatCTM(1, 0, 0.1f, 1, 0, 0); canvas.SetRGBColorStroke(0xFF, 0x45, 0x00); canvas.SetRGBColorFill(0xFF, 0xD7, 0x00); // fill and stroke a rectangle in state 3 canvas.Rectangle(70, 30, 60, 60); canvas.FillStroke(); canvas.RestoreState(); // stroke a rectangle in state 2 canvas.Rectangle(100, 40, 60, 60); canvas.Stroke(); canvas.RestoreState(); // fill and stroke a rectangle in state 1 canvas.Rectangle(130, 50, 60, 60); canvas.FillStroke();
下面我们对代码listing 3.2进行一些解释:
首先state1将设置填充的颜色为橙色(#FF4500),然后Rectangle方法画了一个长为60 user uints的正方形,因为后面只调用了Fill方法,所以没有边框。state2设置线宽为3pt,填充颜色为黑红色(#8B0000),然后调用FillStroke方法填充并画边框。state3修改了current transformation matrix(CTM),这个方法就是将正方形倾斜。然后还将填充颜色修改为金色(#FFD700),画线的颜色修改为橙色(#FF4500)。这里要注意的是调用了RestoreState方法,所以state3就不起作用了,代码中只是使用state2,但代码只是调用了Stroke方法,导致只有边框但内部没有填充。最后又调用RestoreState方法,所以这里就使用state1,但因为使用的StrokeFill方法,默认的边框也被画出来,默认边框的颜色为默认直线的颜色,但边框只有1 user units。代码中要注意的是SaveState方法和RestoreState方法要对称,如果不对称iText会抛出异常。
文本状态
文本状态是图形状态的一个子集。大家可以将每个字符当作一个特殊的图形来理解。不过对文本来说,默认是没有边框的,所以在listing 3.1种我们调用了SetTextRenderingMode方法改变这一设置,因此最后的效果是:文本的填充颜色为白色,边框为红色。不过文本状态还有一些其他的方法如:SetFontAndSize方法,SetTextMatrix方法等,具体的细节大家可以参考书的14.4节。
A real-world database: three more tables
下图是film festival database的ERD图,其中的film_movietitle在第二节的时候有介绍,现在多了三个有festival前缀的表,这些表包含一些在foobar电影节(Foobar Film Festival)上每部电影的格外信息。具体的数据,大家可以用Sqlite Expert Profession工具查看。
CREATING A TIMETABLE
我们要画的Foobar Film Festival包含了三个影院:Cinema Paradiso, Googolplex,and The Majestic,接下来我们会创建下图的文档:
上图为最终的效果图,在这一节中我们只会画图形,后续的章节中会添加文本。上图的图形分为左右两块,左边为影院,右边为时间:从上午的9:30到晚上01:00。接下来我们使用一系列的图形操作符和操作命令来画这两个Grid。
DRAWING THE GRID
在listing 3.2种,我们使用Rectangle方法来画矩形。现在我们使用一系列的MoveTo(),LineTo()和ColsePath()方法构建一个路径(Path),最后调用Stroke方法将构建的路径画出来。
listing 3.3 MovieTimeTable.cs
directContent.SaveState();
directContent.SetLineWidth(1.2f);
float llx, lly, urx, ury;
llx = OFFSET_LEFT;
lly = OFFSET_BOTTOM;
urx = OFFSET_LEFT + WIDTH;
ury = OFFSET_BOTTOM + HEIGHT;
directContent.MoveTo(llx, lly);
directContent.LineTo(urx, lly);
directContent.LineTo(urx, ury);
directContent.LineTo(llx, ury);
directContent.ClosePath();
directContent.Stroke();
llx = OFFSET_LOCATION;
lly = OFFSET_BOTTOM;
urx = OFFSET_LOCATION + WIDTH_LOCATION;
ury = OFFSET_BOTTOM + HEIGHT;
directContent.MoveTo(llx, lly);
directContent.LineTo(urx, lly);
directContent.LineTo(urx, ury);
directContent.LineTo(llx, ury);
directContent.ClosePathStroke();
在上面的代码中,大家会认为比直接调用Rectangle方法复杂一点,不过我们可以使用这些直线构建其他的图形,而且我们还可以使用CurveTo方法来画曲线。以上代码中大家还可以发现我们可以将ClosePath方法和Stroke方法合并在一个方法ClosePathStroke中。iText里面还提供一些便利的方法来构建其他图形,如可以使用Arc方法画弧线,Ellipse方法画椭圆,Circle方法画圆。接下里是通过以下代码在右边的时间Grid中为每个时间点从上到下画虚线:
listing 3.4 MovieTimeTable.cs (continued)
protected void DrawTimeSlots(PdfContentByte directcontent) { directcontent.SaveState(); float x; for (int i = 1; i < TIMESLOTS; i++) { x = OFFSET_LEFT + (i * WIDTH_TIMESLOT); directcontent.MoveTo(x, OFFSET_BOTTOM); directcontent.LineTo(x, OFFSET_BOTTOM + HEIGHT); } directcontent.SetLineWidth(0.3f); directcontent.SetColorStroke(BaseColor.GRAY); directcontent.SetLineDash(3, 1); directcontent.Stroke(); directcontent.RestoreState(); }
在listing 3.3种我们在画完每个图形的时候就调用Stroke方法或者ClosePathStroke方法,但这并是不要的,我们可以将状态的改变延长到构建完所有的图形之后。如在listing 3.4中,当调用Stroke方法时这些0.3pt宽灰色的虚线才会画出来了。现在我们创建的DrawTimeTable方法和DrawTimeSlots方法已经将两个Grid画好了,现在要在Grid中添加一些屏幕(screenings)标志。
DRAWING TIME BLOCKS
就像在第二节的例子一样,我们使用PojoFactory类从数据库获取数据。除了 Movie, Director和 Country对象之外,PojoFactory还可以返回Category, Entry, and Screening POJOs类的集合:
listing 3.5 MovieTimeBlocks.cs
using (conn) { PdfContentByte over = writer.DirectContent; PdfContentByte under = writer.DirectContentUnder; conn.Open(); locations = PojoFactory.GetLocations(conn); List<DateTime> days = PojoFactory.GetDays(conn); List<Screening> screeings; foreach (DateTime day in days) { DrawTimeTable(under); DrawTimeSlots(over); screeings = PojoFactory.GetScreenings(conn, day); foreach (Screening screeing in screeings) { DrawBlock(screeing, under, over); } document.NewPage(); } }
我们可以重用listing 3.3中的DrawTimeTable方法在direct content layer下面画表格,DrawTimeSlots方法在direct content layer上面画曲线。以下的代码是画每个屏幕的:
listing 3.6 MovieTimeBlocks.cs (continued)
protected void DrawBlock(Screening screening, PdfContentByte under, PdfContentByte over) { under.SaveState(); BaseColor color = WebColors.GetRGBColor("#" + screening.Movie.Entry.Category.Color); under.SetColorFill(color); Rectangle rect = GetPosition(screening); under.Rectangle(rect.Left, rect.Bottom, rect.Width, rect.Height); under.Fill(); over.Rectangle(rect.Left, rect.Bottom, rect.Width, rect.Height); over.Stroke(); under.RestoreState(); }
代码不是很复杂,大家应该很好的理解,下图就是这一节的最终图:
总结
这一节主要介绍的是图形状态和文本状态,以及iText对不同层的处理,后续还用实例demo了一些图形的操作命令,不过目前打印的文档中只有图形没有文本,在接下来的章节中会在此基础上添加文本。最后是代码下载。
同步
此文章已同步到目录索引:iText in Action 2nd 读书笔记。