iText7---Adding low-level content添加低层级内容

来源:互联网 发布:java short 范围 编辑:程序博客网 时间:2024/06/03 06:47

文章转载至:http://blog.csdn.net/ljheee/article/details/52877306   

文章翻译自:http://developers.itextpdf.com/content/itext-7-jump-start-tutorial/chapter-2-adding-low-level-content

        当我们在谈论iText文档低级的内容时,我们总是把PDF的语法写入PDF内容流。PDF定义了一系列的操作,比如m即表示moveTo()方法,l即表示lineTo()方法,s即为stroke()方法。通过组合使用这些操作和函数,你可以绘制路径和形状。

一起看看下面这个简单例子

-406 0 m

406 0 l

S

这个PDF语法意思是:移动到这个位置( X = -406 ; Y = 0 ),然后构造一个到( X = -406 ; Y = 0 )的路径,最终在这个内容上划一条线。如果我们使用iText的PDF语法创建这个PDF片段,代码是这样的:

[java] view plain copy
  1. canvas.moveTo(-4060)  
  2.             .lineTo(4060)  
  3.             .stroke();  

这看起来很简单。但是此处使用的canvas对象是什么呢?让我们通过几个例子一探究竟。

一、画布上的画线

        假设我们想创建一个PDF如图1:

图1.画X轴和Y轴 

        这个PDF显示了创建X轴和Y轴的实例。让我们一步一步来解释这个例子。

[java] view plain copy
  1. PdfDocument pdf = new PdfDocument(new PdfWriter(dest));  
  2. PageSize ps = PageSize.A4.rotate();  
  3. PdfPage page = pdf.addNewPage(ps);  
  4. PdfCanvas canvas = new PdfCanvas(page);  
  5. // Draw the axes  
  6. pdf.close();  

        第一大变化是,我们不再使用一个Document对象。正如在前面的章节中,我们创建了一个PdfWriter和PdfDocument对象,而不是创建一个默认或特定的页面大小的文件,我们创建了一个PageSize 与一个特定的PageSize。在这种情况下,我们使用A4页面横向。一旦我们有一个pdfpage实例,我们使用它来创建一个PdfCanvas 。我们将使用这个画布canvas 对象序列生成PDF运算符和操作数。一旦我们完成了绘画的任何路径和形状,我们要添加到页面中,然后关闭pdf。

        注意:之前我们关闭Document对象,是使用document.close();隐式的关闭了PdfDocument 对象。现在不使用Document对象了,需要关闭PdfDocument 对象。

        在PDF中,所有的测量都是在用户单位。默认情况下,一个用户单元对应一个点。这意味着有72个用户单位在一英寸。在PDF中,X轴向右为正方向,Y轴向上是正方向。          如果你使用PageSize对象创建的页面大小,坐标系统的原点位于页面的左下角。所有的坐标,我们都使用作为操作数的操作,如m或l使用这个坐标系统。我们可以通过改变当前变换矩阵来改变坐标系。

二、坐标系统和变换矩阵

        如果你学过《解析几何》课程,你知道可以通过应用变换矩阵在空间中移动物体。在PDF中,我不需要移动物体,只需要移动坐标系统,然后在新坐标系统中画图物体即可。假如我们要把坐标系统移到页面的正中间,使用concatMatrix()方法:

canvas.concatMatrix(1, 0, 0, 1, ps.getWidth() / 2, ps.getHeight() / 2);

这个方法的参数是一个变换矩阵的元素。这个矩阵由三行三列组成。

a   b   0

c   d   0

e   f   1

        矩阵第三列的值总是(0,0,1),因为我们使用的是一个二维坐标,a/b/c//d的值可用于缩放、旋转和倾斜坐标系。没有任何原因,我们被限制在一个坐标系统,其中的轴是正交的或在X方向的进展需要是相同的Y方向上的进展。但让我们保持简单,使用1,0,0,和1作为a,b,c和d的值。元素e和f定义了转换。我们获取页面大小ps,我们把它的宽度和高度除以2,得到e和f值。

三、图形状态

        当前的转换矩阵是页面中图形状态的一部分。在图形状态中定义的其他值还有线条宽度,笔触颜色(线条),填充颜色(形状)等。在另一个教程中,我们将深入,详细地描述了图形状态的每个值及细节区别。现在,知道默认的线宽为1个用户单元、默认的笔触颜色是黑色的就足够了。让我们在图2.1中画出那些轴:

[java] view plain copy
  1. //Draw X axis  
  2. canvas.moveTo(-(ps.getWidth() / 2 - 15), 0)  
  3.         .lineTo(ps.getWidth() / 2 - 150)  
  4.         .stroke();  
  5. //Draw X axis arrow  
  6. canvas.setLineJoinStyle(PdfCanvasConstants.LineJoinStyle.ROUND)  
  7.         .moveTo(ps.getWidth() / 2 - 25, -10)  
  8.         .lineTo(ps.getWidth() / 2 - 150)  
  9.         .lineTo(ps.getWidth() / 2 - 2510).stroke()  
  10.         .setLineJoinStyle(PdfCanvasConstants.LineJoinStyle.MITER);  
  11. //Draw Y axis  
  12. canvas.moveTo(0, -(ps.getHeight() / 2 - 15))  
  13.         .lineTo(0, ps.getHeight() / 2 - 15)  
  14.         .stroke();  
  15. //Draw Y axis arrow  
  16. canvas.saveState()  
  17.         .setLineJoinStyle(PdfCanvasConstants.LineJoinStyle.ROUND)  
  18.         .moveTo(-10, ps.getHeight() / 2 - 25)  
  19.         .lineTo(0, ps.getHeight() / 2 - 15)  
  20.         .lineTo(10, ps.getHeight() / 2 - 25).stroke()  
  21.         .restoreState();  
  22. //Draw X serif  
  23. for (int i = -((int) ps.getWidth() / 2 - 61);  
  24.     i < ((int) ps.getWidth() / 2 - 60); i += 40) {  
  25.     canvas.moveTo(i, 5).lineTo(i, -5);  
  26. }  
  27. //Draw Y serif  
  28. for (int j = -((int) ps.getHeight() / 2 - 57);  
  29.     j < ((int) ps.getHeight() / 2 - 56); j += 40) {  
  30.     canvas.moveTo(5, j).lineTo(-5, j);  
  31. }  
  32. canvas.stroke();  

这个代码片段包含了几个不同部分:

(1)第2-4行和12-14应该不陌生了。我们移动到一个坐标,构建一个线到另一个坐标,然后画出这个线。

(2)6-10行画两条线相互连接。有几种连接的方式:miter 、bevel[角斜]、round[圆角]。我们想要圆角的,所以把默认连接值miter 改为round。我们通过一次moveTo()和两次lineTo() 方法调用,构造了路径,即坐标箭头,并且将这一行的连接值更改为默认值。虽然图形状态现在返回到它的原始值,但这不是返回到以前的图形状态的最佳方式。

(3)16-21行,展示更好的实践应用,无论什么时候要改变图形状态时。首先我们使用saveState()方法保存当前图形状态,然后改变状态,接着画出线条或图形,最后使用restoreState()方法还原初始图形状态。所有的变化,我们应用后saveState()将撤消。更有趣的是,如果你改变多个值(线宽度,颜色,…)或当它很难计算的反向变化(返回到原来的坐标系统)。

(4)23-31行,我们构造的小衬线被画在坐标轴上,每40个用户单位画一个小衬线。我们没立即画出,只有当我们已经构建了完整的路径,我们才调用stroke()方法画出。

        总是有不同种方式画线或图形。这导致我们很难解释不同方式生成PDF文件速度的优点和缺点,对文件大小的影响,和在一个PDF阅读器渲染文件的速度。这也是需要在另一个教程中进一步讨论。

        注意:也有具体的规则,需要考虑。比如对saveState()和restoreState()序列需要平衡。一个saveState()对应一个restoreState()。


        现在让我们采用本章第一个例子,通过改变线条粗度,引入虚线图案,使用不同的颜色,得到显示效果如图2:

图2.画网格

[java] view plain copy
  1. /* 
  2.  * This example is part of the iText 7 tutorial. 
  3.  */  
  4. package tutorial.chapter02;  
  5.    
  6. import com.itextpdf.kernel.color.Color;  
  7. import com.itextpdf.kernel.color.DeviceCmyk;  
  8. import com.itextpdf.kernel.geom.PageSize;  
  9. import com.itextpdf.kernel.pdf.PdfDocument;  
  10. import com.itextpdf.kernel.pdf.PdfPage;  
  11. import com.itextpdf.kernel.pdf.PdfWriter;  
  12. import com.itextpdf.kernel.pdf.canvas.PdfCanvas;  
  13. import com.itextpdf.test.annotations.WrapToTest;  
  14.    
  15. import java.io.File;  
  16. import java.io.IOException;  
  17.    
  18. /** 
  19.  * Simple changing graphics state example. 
  20.  */  
  21. @WrapToTest  
  22. public class C02E02_GridLines {  
  23.    
  24.     public static final String DEST = "results/chapter02/grid_lines.pdf";  
  25.    
  26.     public static void main(String args[]) throws IOException {  
  27.         File file = new File(DEST);  
  28.         file.getParentFile().mkdirs();  
  29.         new C02E02_GridLines().createPdf(DEST);  
  30.     }  
  31.    
  32.     public void createPdf(String dest) throws IOException {  
  33.    
  34.         //Initialize PDF document  
  35.         PdfDocument pdf = new PdfDocument(new PdfWriter(dest));  
  36.    
  37.         PageSize ps = PageSize.A4.rotate();  
  38.         PdfPage page = pdf.addNewPage(ps);  
  39.    
  40.         PdfCanvas canvas = new PdfCanvas(page);  
  41.         //Replace the origin of the coordinate system to the center of the page  
  42.         canvas.concatMatrix(1001, ps.getWidth() / 2, ps.getHeight() / 2);  
  43.    
  44.         Color grayColor = new DeviceCmyk(0.f, 0.f, 0.f, 0.875f);  
  45.         Color greenColor = new DeviceCmyk(1.f, 0.f, 1.f, 0.176f);  
  46.         Color blueColor = new DeviceCmyk(1.f, 0.156f, 0.f, 0.118f);  
  47.    
  48.         canvas.setLineWidth(0.5f).setStrokeColor(blueColor);  
  49.    
  50.         //Draw horizontal grid lines  
  51.         for (int i = -((int) ps.getHeight() / 2 - 57); i < ((int) ps.getHeight() / 2 - 56); i += 40) {  
  52.             canvas.moveTo(-(ps.getWidth() / 2 - 15), i)  
  53.                     .lineTo(ps.getWidth() / 2 - 15, i);  
  54.         }  
  55.         //Draw vertical grid lines  
  56.         for (int j = -((int) ps.getWidth() / 2 - 61); j < ((int) ps.getWidth() / 2 - 60); j += 40) {  
  57.             canvas.moveTo(j, -(ps.getHeight() / 2 - 15))  
  58.                     .lineTo(j, ps.getHeight() / 2 - 15);  
  59.         }  
  60.         canvas.stroke();  
  61.    
  62.         //Draw axes  
  63.         canvas.setLineWidth(3).setStrokeColor(grayColor);  
  64.         C02E01_Axes.drawAxes(canvas, ps);  
  65.    
  66.         //Draw plot  
  67.         canvas.setLineWidth(2).setStrokeColor(greenColor)  
  68.                 .setLineDash(10108)  
  69.                 .moveTo(-(ps.getWidth() / 2 - 15), -(ps.getHeight() / 2 - 15))  
  70.                 .lineTo(ps.getWidth() / 2 - 15, ps.getHeight() / 2 - 15).stroke();  
  71.    
  72.         //Close document  
  73.         pdf.close();  
  74.    
  75.     }  
  76. }  

        在这个例子中,我们首先定义了一些颜色对象:

        Color grayColor = new DeviceCmyk(0.f, 0.f, 0.f, 0.875f);

        Color greenColor = new DeviceCmyk(1.f, 0.f, 1.f, 0.176f);

        Color blueColor = new DeviceCmyk(1.f, 0.156f, 0.f, 0.118f);

        PDF规范(iso-32000)定义了许多不同的色彩空间,每一种已在iText一个单独的类中实现。最常用的颜色空间是DeviceGray(一个由一个单一的强度参数定义的颜色)、DeviceRgb(由三个参数:红色,绿色,和蓝色来决定)和DeviceCmyk(定义的四个参数:青、马真塔、黄色和黑色)。在我们的例子中,我们使用了三个CMYK颜色。

        注意:我们并不是使用java.awt.Color这个类,是使用iText中的Color类,在com.itextpdf.kernel.color包中。

我们想要创建蓝色网格线:

[java] view plain copy
  1. canvas.setLineWidth(0.5f).setStrokeColor(blueColor);  
  2. for (int i = -((int) ps.getHeight() / 2 - 57);  
  3.     i < ((int) ps.getHeight() / 2 - 56); i += 40) {  
  4.     canvas.moveTo(-(ps.getWidth() / 2 - 15), i)  
  5.             .lineTo(ps.getWidth() / 2 - 15, i);  
  6. }  
  7. for (int j = -((int) ps.getWidth() / 2 - 61);  
  8.     j < ((int) ps.getWidth() / 2 - 60); j += 40) {  
  9.     canvas.moveTo(j, -(ps.getHeight() / 2 - 15))  
  10.             .lineTo(j, ps.getHeight() / 2 - 15);  
  11. }  
  12. canvas.stroke();  

        第1行,我们设置了线粗、0.5个用户单位、蓝色;2-10行构造了网格路径,然后画出网格。

        我们重用代码,从前面的例子中绘制的坐标轴,但我们让他们前面的一条线,改变线条宽度和颜色。

              canvas.setLineWidth(3).setStrokeColor(grayColor);

画完坐标轴,再画绿色的虚线。

[java] view plain copy
  1. canvas.setLineWidth(2).setStrokeColor(greenColor)  
  2.         .setLineDash(10108)  
  3.         .moveTo(-(ps.getWidth() / 2 - 15), -(ps.getHeight() / 2 - 15))  
  4.         .lineTo(ps.getWidth() / 2 - 15, ps.getHeight() / 2 - 15).stroke();  

        有许多可能的变化来定义一条虚线。上例中,我们使用三个参数定义一条虚线。小虚线的长度是10个用户单位,间距的长度是10个用户单位,阶段是8个用户单位。

        小结:随意尝试用一些,在pdfcanvas类可用的其他方法。你可以构建曲线用curveto()方法,与矩形用rectangle()法,等等。用stroke()方法画路径,用fill()方法填充。                 PdfCanvas类提供很多java版的PDF操作方法。它还有了大量方便的类来构造具体的路径,如椭圆或圆。

        在我们的下一个示例中,我们将查看一个子集的图形状态,这将允许我们在绝对位置添加文本。

四、文本状态

        在图3中,我们看到了星球大战第五集的开幕式:帝国反击战。

图3.在绝对位置添加文本

        要创建这样一个PDF,最好方式是使用一系列具有不同中心段落标题、对齐方式的Paragraph段落对象;文本左对齐,并且添加这些段落到一个Document对象。使用高层次的方法将分发文本在几行中,引入线自动断,如果内容不适合的页面宽度、高度,则页面中断。 

        所有这一切都不会发生,当我们添加文本使用低级别的方法时。我们需要把内容分成小块文本,如:

[java] view plain copy
  1. /* 
  2.  * This example is part of the iText 7 tutorial. 
  3.  */  
  4. package tutorial.chapter02;  
  5.    
  6. import com.itextpdf.io.font.FontConstants;  
  7. import com.itextpdf.kernel.font.PdfFontFactory;  
  8. import com.itextpdf.kernel.geom.PageSize;  
  9. import com.itextpdf.kernel.pdf.PdfDocument;  
  10. import com.itextpdf.kernel.pdf.PdfPage;  
  11. import com.itextpdf.kernel.pdf.PdfWriter;  
  12. import com.itextpdf.kernel.pdf.canvas.PdfCanvas;  
  13. import com.itextpdf.test.annotations.WrapToTest;  
  14.    
  15. import java.io.File;  
  16. import java.io.IOException;  
  17. import java.util.ArrayList;  
  18. import java.util.List;  
  19.    
  20. /** 
  21.  * Simple drawing text example. 
  22.  */  
  23. @WrapToTest  
  24. public class C02E03_StarWars {  
  25.    
  26.     public static final String DEST = "results/chapter02/star_wars.pdf";  
  27.    
  28.     public static void main(String args[]) throws IOException {  
  29.         File file = new File(DEST);  
  30.         file.getParentFile().mkdirs();  
  31.         new C02E03_StarWars().createPdf(DEST);  
  32.     }  
  33.    
  34.     public void createPdf(String dest) throws IOException {  
  35.    
  36.         //Initialize PDF document  
  37.         PdfDocument pdf = new PdfDocument(new PdfWriter(dest));  
  38.    
  39.         //Add new page  
  40.         PageSize ps = PageSize.A4;  
  41.         PdfPage page = pdf.addNewPage(ps);  
  42.    
  43.         PdfCanvas canvas = new PdfCanvas(page);  
  44.    
  45.         List<String> text = new ArrayList();  
  46.         text.add("         Episode V         ");  
  47.         text.add("  THE EMPIRE STRIKES BACK  ");  
  48.         text.add("It is a dark time for the");  
  49.         text.add("Rebellion. Although the Death");  
  50.         text.add("Star has been destroyed,");  
  51.         text.add("Imperial troops have driven the");  
  52.         text.add("Rebel forces from their hidden");  
  53.         text.add("base and pursued them across");  
  54.         text.add("the galaxy.");  
  55.         text.add("Evading the dreaded Imperial");  
  56.         text.add("Starfleet, a group of freedom");  
  57.         text.add("fighters led by Luke Skywalker");  
  58.         text.add("has established a new secret");  
  59.         text.add("base on the remote ice world");  
  60.         text.add("of Hoth...");  
  61.    
  62.         //Replace the origin of the coordinate system to the top left corner  
  63.         canvas.concatMatrix(10010, ps.getHeight());  
  64.         canvas.beginText()  
  65.                 .setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 14)  
  66.                 .setLeading(14 * 1.2f)  
  67.                 .moveText(70, -40);  
  68.         for (String s : text) {  
  69.             //Add text and move to the next line  
  70.             canvas.newlineShowText(s);  
  71.         }  
  72.         canvas.endText();  
  73.    
  74.         //Close document  
  75.         pdf.close();  
  76.    
  77.     }  
  78. }  
其中:

[java] view plain copy
  1. List<String> text = new ArrayList();  
  2. text.add("         Episode V         ");  
  3. text.add("  THE EMPIRE STRIKES BACK  ");  
  4. text.add("It is a dark time for the");  
  5. text.add("Rebellion. Although the Death");  
  6. text.add("Star has been destroyed,");  
  7. text.add("Imperial troops have driven the");  
  8. text.add("Rebel forces from their hidden");  
  9. text.add("base and pursued them across");  
  10. text.add("the galaxy.");  
  11. text.add("Evading the dreaded Imperial");  
  12. text.add("Starfleet, a group of freedom");  
  13. text.add("fighters led by Luke Skywalker");  
  14. text.add("has established a new secret");  
  15. text.add("base on the remote ice world");  
  16. text.add("of Hoth...");  

        为了方便起见,我们改变了坐标系,使它的原点位于左上角,而不是左下角。然后我们创建一个文本对象用beginText()方法,然后改变文本的状态:

[java] view plain copy
  1. canvas.concatMatrix(10010, ps.getHeight());  
  2. canvas.beginText()  
  3.     .setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 14)  
  4.     .setLeading(14 * 1.2f)  
  5.     .moveText(70, -40);  

        我们创建一个PdfFont,用Courier粗体显示文本,然后改变文本状态,是所有文字字号都是14。setLeading设置的是:两个后续的文本行的基线之间的距离。最后,我们改变了文本矩阵,使光标移动70、40个用户单位。

        接下来,我们循环将List text列表中的每一个字符串,显示并移动在一个新行。然后用endText()方法关闭文本。由beginText()/endText()方法限制,不能在这两个方法之外显示文本,也不能在其中嵌套。

[java] view plain copy
  1. for (String s : text) {  
  2.     //Add text and move to the next line  
  3.     canvas.newlineShowText(s);  
  4. }  
  5. canvas.endText();  

        如果继续延伸这个例子,做如何的改变,才能显示图4效果呢?

图4.在绝对位置添加倾斜、彩色文本

改变背景色是比较容易的:

[java] view plain copy
  1. canvas.rectangle(00, ps.getWidth(), ps.getHeight())  
  2.         .setColor(Color.BLACK, true)  
  3.         .fill();  

        我们创建一个矩形,其左下角有坐标X = 0,Y = 0,其中宽度和高度和页面大小的宽高一样。我们使用setFillColor(Color.BLACK)设置填充色,也可以使用更通用的方法setColor()。布尔表示,是否我们要改变画线的颜色(假)或填充颜色(真)。最后,我们填充的矩形的路径,使用填充颜色作为颜料。

        现在来看最关键的一部分:我们如何添加文本?

[java] view plain copy
  1. canvas.concatMatrix(10010, ps.getHeight());  
  2. Color yellowColor = new DeviceCmyk(0.f, 0.0537f, 0.769f, 0.051f);  
  3. float lineHeight = 5;  
  4. float yOffset = -40;  
  5. canvas.beginText()  
  6.     .setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 1)  
  7.     .setColor(yellowColor, true);  
  8. for (int j = 0; j < text.size(); j++) {  
  9.     String line = text.get(j);  
  10.     float xOffset = ps.getWidth() / 2 - 45 - 8 * j;  
  11.     float fontSizeCoeff = 6 + j;  
  12.     float lineSpacing = (lineHeight + j) * j / 1.5f;  
  13.     int stringWidth = line.length();  
  14.     for (int i = 0; i < stringWidth; i++) {  
  15.         float angle = (maxStringWidth / 2 - i) / 2f;  
  16.         float charXOffset = (4 + (float) j / 2) * i;  
  17.         canvas.setTextMatrix(fontSizeCoeff, 0,  
  18.                 angle, fontSizeCoeff / 1.5f,  
  19.                 xOffset + charXOffset, yOffset - lineSpacing)  
  20.             .showText(String.valueOf(line.charAt(i)));  
  21.     }  
  22. }  
  23. canvas.endText();  

        再次,第1行我们改变了坐标系统的原点到页面的顶部,第2行给文本设置cmyk颜色值,我们初始化了线的高度值,和Y方向的偏移量。我们开始写文本,使用Courier Bold作为文字字体、字号1;我们可以通过改变文本矩阵,伸缩文字到一个可读大小。我们没有定义leading间距,因为没有使用newlineShowText()方法。相反我们要计算每个文本开始的单个字符,一个字符一个字符的画出来。

        注意:在一个字体中的每一个字形都被定义为一个路径。默认情况下,使一个文本符号的路径填充。这就是为什么我们设置填充颜色来改变文本的颜色。

        我们开始循环,读取List text列表中的每一个字符串,我们需要大量的数学来定义文本矩阵的不同元素,这些元素将被用来定位每一个字形。我们定义了每条线的偏移量。字号为1,但是会乘以一个系数fontSizeCoeff,即文字数组行号索引。我们还定义了线条将从哪里开始相对于yOffset。

        我们计算了每一行字符的数量,循环每一个字符。我们定义了一个角度,取决于这个字符在这个文本行中的位置。字符偏移量charOffset取决于字行索引和字符在行中的位置。

        现在准备设置文本矩阵。参数a,b定义了比例因子,使用他俩可以改变字体大小。参数c是斜交因子。最后由参数确定了字符坐标。现在使用showText()显示文字。一旦所有字符都循环完成,使用endText()关闭文本。

        如果你认为这个例子是相当复杂的,你是绝对正确的。我用它只是为了表明iText允许你创建的内容,以你任何想要的方式。如果可能的话在PDF,可能用iText。但放心,即将到来的例子将更容易理解。

总结

        在这一章中,我们已经尝试使用了PDF运算符和操作数和相应的iText的方法。我们已经了解了图形状态的概念,保持跟踪的属性,如当前的转换矩阵,线宽,颜色等。文本状态是覆盖与文本相关的所有属性的图形状态的一个子集,如文本矩阵、文本的字体和大小,以及许多其他我们还没有讨论过的属性。在另一个教程中,我们会得到更多的细节。

        有人也许想知道为什么开发者需要访问底层API,才知道iText的很多高级功能。这个问题将在下一章回答。

原创粉丝点击