本文主要是介绍iText7---Adding low-level content添加低层级内容,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章转载至: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片段,代码是这样的:
- canvas.moveTo(-406, 0)
- .lineTo(406, 0)
- .stroke();
这看起来很简单。但是此处使用的canvas对象是什么呢?让我们通过几个例子一探究竟。
一、画布上的画线
假设我们想创建一个PDF如图1:
图1.画X轴和Y轴
这个PDF显示了创建X轴和Y轴的实例。让我们一步一步来解释这个例子。
- PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
- PageSize ps = PageSize.A4.rotate();
- PdfPage page = pdf.addNewPage(ps);
- PdfCanvas canvas = new PdfCanvas(page);
-
- 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中画出那些轴:
-
- canvas.moveTo(-(ps.getWidth() / 2 - 15), 0)
- .lineTo(ps.getWidth() / 2 - 15, 0)
- .stroke();
-
- canvas.setLineJoinStyle(PdfCanvasConstants.LineJoinStyle.ROUND)
- .moveTo(ps.getWidth() / 2 - 25, -10)
- .lineTo(ps.getWidth() / 2 - 15, 0)
- .lineTo(ps.getWidth() / 2 - 25, 10).stroke()
- .setLineJoinStyle(PdfCanvasConstants.LineJoinStyle.MITER);
-
- canvas.moveTo(0, -(ps.getHeight() / 2 - 15))
- .lineTo(0, ps.getHeight() / 2 - 15)
- .stroke();
-
- canvas.saveState()
- .setLineJoinStyle(PdfCanvasConstants.LineJoinStyle.ROUND)
- .moveTo(-10, ps.getHeight() / 2 - 25)
- .lineTo(0, ps.getHeight() / 2 - 15)
- .lineTo(10, ps.getHeight() / 2 - 25).stroke()
- .restoreState();
-
- for (int i = -((int) ps.getWidth() / 2 - 61);
- i < ((int) ps.getWidth() / 2 - 60); i += 40) {
- canvas.moveTo(i, 5).lineTo(i, -5);
- }
-
- for (int j = -((int) ps.getHeight() / 2 - 57);
- j < ((int) ps.getHeight() / 2 - 56); j += 40) {
- canvas.moveTo(5, j).lineTo(-5, j);
- }
- 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.画网格
-
-
-
- package tutorial.chapter02;
-
- import com.itextpdf.kernel.color.Color;
- import com.itextpdf.kernel.color.DeviceCmyk;
- import com.itextpdf.kernel.geom.PageSize;
- import com.itextpdf.kernel.pdf.PdfDocument;
- import com.itextpdf.kernel.pdf.PdfPage;
- import com.itextpdf.kernel.pdf.PdfWriter;
- import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
- import com.itextpdf.test.annotations.WrapToTest;
-
- import java.io.File;
- import java.io.IOException;
-
-
-
-
- @WrapToTest
- public class C02E02_GridLines {
-
- public static final String DEST = "results/chapter02/grid_lines.pdf";
-
- public static void main(String args[]) throws IOException {
- File file = new File(DEST);
- file.getParentFile().mkdirs();
- new C02E02_GridLines().createPdf(DEST);
- }
-
- public void createPdf(String dest) throws IOException {
-
-
- PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
-
- PageSize ps = PageSize.A4.rotate();
- PdfPage page = pdf.addNewPage(ps);
-
- PdfCanvas canvas = new PdfCanvas(page);
-
- canvas.concatMatrix(1, 0, 0, 1, ps.getWidth() / 2, ps.getHeight() / 2);
-
- 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);
-
- canvas.setLineWidth(0.5f).setStrokeColor(blueColor);
-
-
- for (int i = -((int) ps.getHeight() / 2 - 57); i < ((int) ps.getHeight() / 2 - 56); i += 40) {
- canvas.moveTo(-(ps.getWidth() / 2 - 15), i)
- .lineTo(ps.getWidth() / 2 - 15, i);
- }
-
- for (int j = -((int) ps.getWidth() / 2 - 61); j < ((int) ps.getWidth() / 2 - 60); j += 40) {
- canvas.moveTo(j, -(ps.getHeight() / 2 - 15))
- .lineTo(j, ps.getHeight() / 2 - 15);
- }
- canvas.stroke();
-
-
- canvas.setLineWidth(3).setStrokeColor(grayColor);
- C02E01_Axes.drawAxes(canvas, ps);
-
-
- canvas.setLineWidth(2).setStrokeColor(greenColor)
- .setLineDash(10, 10, 8)
- .moveTo(-(ps.getWidth() / 2 - 15), -(ps.getHeight() / 2 - 15))
- .lineTo(ps.getWidth() / 2 - 15, ps.getHeight() / 2 - 15).stroke();
-
-
- pdf.close();
-
- }
- }
在这个例子中,我们首先定义了一些颜色对象:
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包中。
我们想要创建蓝色网格线:
- canvas.setLineWidth(0.5f).setStrokeColor(blueColor);
- for (int i = -((int) ps.getHeight() / 2 - 57);
- i < ((int) ps.getHeight() / 2 - 56); i += 40) {
- canvas.moveTo(-(ps.getWidth() / 2 - 15), i)
- .lineTo(ps.getWidth() / 2 - 15, i);
- }
- for (int j = -((int) ps.getWidth() / 2 - 61);
- j < ((int) ps.getWidth() / 2 - 60); j += 40) {
- canvas.moveTo(j, -(ps.getHeight() / 2 - 15))
- .lineTo(j, ps.getHeight() / 2 - 15);
- }
- canvas.stroke();
第1行,我们设置了线粗、0.5个用户单位、蓝色;2-10行构造了网格路径,然后画出网格。
我们重用代码,从前面的例子中绘制的坐标轴,但我们让他们前面的一条线,改变线条宽度和颜色。
canvas.setLineWidth(3).setStrokeColor(grayColor);
画完坐标轴,再画绿色的虚线。
- canvas.setLineWidth(2).setStrokeColor(greenColor)
- .setLineDash(10, 10, 8)
- .moveTo(-(ps.getWidth() / 2 - 15), -(ps.getHeight() / 2 - 15))
- .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对象。使用高层次的方法将分发文本在几行中,引入线自动断,如果内容不适合的页面宽度、高度,则页面中断。
所有这一切都不会发生,当我们添加文本使用低级别的方法时。我们需要把内容分成小块文本,如:
-
-
-
- package tutorial.chapter02;
-
- import com.itextpdf.io.font.FontConstants;
- import com.itextpdf.kernel.font.PdfFontFactory;
- import com.itextpdf.kernel.geom.PageSize;
- import com.itextpdf.kernel.pdf.PdfDocument;
- import com.itextpdf.kernel.pdf.PdfPage;
- import com.itextpdf.kernel.pdf.PdfWriter;
- import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
- import com.itextpdf.test.annotations.WrapToTest;
-
- import java.io.File;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
-
-
-
- @WrapToTest
- public class C02E03_StarWars {
-
- public static final String DEST = "results/chapter02/star_wars.pdf";
-
- public static void main(String args[]) throws IOException {
- File file = new File(DEST);
- file.getParentFile().mkdirs();
- new C02E03_StarWars().createPdf(DEST);
- }
-
- public void createPdf(String dest) throws IOException {
-
-
- PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
-
-
- PageSize ps = PageSize.A4;
- PdfPage page = pdf.addNewPage(ps);
-
- PdfCanvas canvas = new PdfCanvas(page);
-
- List<String> text = new ArrayList();
- text.add(" Episode V ");
- text.add(" THE EMPIRE STRIKES BACK ");
- text.add("It is a dark time for the");
- text.add("Rebellion. Although the Death");
- text.add("Star has been destroyed,");
- text.add("Imperial troops have driven the");
- text.add("Rebel forces from their hidden");
- text.add("base and pursued them across");
- text.add("the galaxy.");
- text.add("Evading the dreaded Imperial");
- text.add("Starfleet, a group of freedom");
- text.add("fighters led by Luke Skywalker");
- text.add("has established a new secret");
- text.add("base on the remote ice world");
- text.add("of Hoth...");
-
-
- canvas.concatMatrix(1, 0, 0, 1, 0, ps.getHeight());
- canvas.beginText()
- .setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 14)
- .setLeading(14 * 1.2f)
- .moveText(70, -40);
- for (String s : text) {
-
- canvas.newlineShowText(s);
- }
- canvas.endText();
-
-
- pdf.close();
-
- }
- }
其中:
- List<String> text = new ArrayList();
- text.add(" Episode V ");
- text.add(" THE EMPIRE STRIKES BACK ");
- text.add("It is a dark time for the");
- text.add("Rebellion. Although the Death");
- text.add("Star has been destroyed,");
- text.add("Imperial troops have driven the");
- text.add("Rebel forces from their hidden");
- text.add("base and pursued them across");
- text.add("the galaxy.");
- text.add("Evading the dreaded Imperial");
- text.add("Starfleet, a group of freedom");
- text.add("fighters led by Luke Skywalker");
- text.add("has established a new secret");
- text.add("base on the remote ice world");
- text.add("of Hoth...");
为了方便起见,我们改变了坐标系,使它的原点位于左上角,而不是左下角。然后我们创建一个文本对象用beginText()方法,然后改变文本的状态:
- canvas.concatMatrix(1, 0, 0, 1, 0, ps.getHeight());
- canvas.beginText()
- .setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 14)
- .setLeading(14 * 1.2f)
- .moveText(70, -40);
我们创建一个PdfFont,用Courier粗体显示文本,然后改变文本状态,是所有文字字号都是14。setLeading设置的是:两个后续的文本行的基线之间的距离。最后,我们改变了文本矩阵,使光标移动70、40个用户单位。
接下来,我们循环将List text列表中的每一个字符串,显示并移动在一个新行。然后用endText()方法关闭文本。由beginText()/endText()方法限制,不能在这两个方法之外显示文本,也不能在其中嵌套。
- for (String s : text) {
-
- canvas.newlineShowText(s);
- }
- canvas.endText();
如果继续延伸这个例子,做如何的改变,才能显示图4效果呢?
图4.在绝对位置添加倾斜、彩色文本
改变背景色是比较容易的:
- canvas.rectangle(0, 0, ps.getWidth(), ps.getHeight())
- .setColor(Color.BLACK, true)
- .fill();
我们创建一个矩形,其左下角有坐标X = 0,Y = 0,其中宽度和高度和页面大小的宽高一样。我们使用setFillColor(Color.BLACK)设置填充色,也可以使用更通用的方法setColor()。布尔表示,是否我们要改变画线的颜色(假)或填充颜色(真)。最后,我们填充的矩形的路径,使用填充颜色作为颜料。
现在来看最关键的一部分:我们如何添加文本?
- canvas.concatMatrix(1, 0, 0, 1, 0, ps.getHeight());
- Color yellowColor = new DeviceCmyk(0.f, 0.0537f, 0.769f, 0.051f);
- float lineHeight = 5;
- float yOffset = -40;
- canvas.beginText()
- .setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 1)
- .setColor(yellowColor, true);
- for (int j = 0; j < text.size(); j++) {
- String line = text.get(j);
- float xOffset = ps.getWidth() / 2 - 45 - 8 * j;
- float fontSizeCoeff = 6 + j;
- float lineSpacing = (lineHeight + j) * j / 1.5f;
- int stringWidth = line.length();
- for (int i = 0; i < stringWidth; i++) {
- float angle = (maxStringWidth / 2 - i) / 2f;
- float charXOffset = (4 + (float) j / 2) * i;
- canvas.setTextMatrix(fontSizeCoeff, 0,
- angle, fontSizeCoeff / 1.5f,
- xOffset + charXOffset, yOffset - lineSpacing)
- .showText(String.valueOf(line.charAt(i)));
- }
- }
- 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的很多高级功能。这个问题将在下一章回答。
这篇关于iText7---Adding low-level content添加低层级内容的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!