天道酬勤

<<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 读书笔记。

posted @ 2012-06-25 22:31  JulyLuo  阅读(3003)  评论(1编辑  收藏  举报