zoukankan      html  css  js  c++  java
  • <<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 读书笔记。

  • 相关阅读:
    关于 iOS 证书,你必须了解的知识
    Spark踩坑记:共享变量
    Python 操作 MySQL 的正确姿势
    【黑客浅析】像黑客一样思考
    利用 Flask+Redis 维护 IP 代理池
    程序员的江湖:从黑木崖到回龙观
    [NM 状态机1] Application状态机详解
    Hadoop 2.0 编译问题小结
    Yarn上的几个问题整理
    Yarn中如何生成状态机图
  • 原文地址:https://www.cnblogs.com/julyluo/p/2562846.html
Copyright © 2011-2022 走看看