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

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

        文章翻译自: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);
// Draw the axes
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中画出那些轴:

//Draw X axis
canvas.moveTo(-(ps.getWidth() / 2 - 15), 0)
        .lineTo(ps.getWidth() / 2 - 15, 0)
        .stroke();
//Draw X axis arrow
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);
//Draw Y axis
canvas.moveTo(0, -(ps.getHeight() / 2 - 15))
        .lineTo(0, ps.getHeight() / 2 - 15)
        .stroke();
//Draw Y axis arrow
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();
//Draw X serif
for (int i = -((int) ps.getWidth() / 2 - 61);
    i < ((int) ps.getWidth() / 2 - 60); i += 40) {
    canvas.moveTo(i, 5).lineTo(i, -5);
}
//Draw Y serif
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.画网格

/*
 * This example is part of the iText 7 tutorial.
 */
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;
 
/**
 * Simple changing graphics state example.
 */
@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 {
 
        //Initialize PDF document
        PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
 
        PageSize ps = PageSize.A4.rotate();
        PdfPage page = pdf.addNewPage(ps);
 
        PdfCanvas canvas = new PdfCanvas(page);
        //Replace the origin of the coordinate system to the center of the 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);
 
        //Draw horizontal grid lines
        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);
        }
        //Draw vertical grid lines
        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();
 
        //Draw axes
        canvas.setLineWidth(3).setStrokeColor(grayColor);
        C02E01_Axes.drawAxes(canvas, ps);
 
        //Draw plot
        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();
 
        //Close document
        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对象。使用高层次的方法将分发文本在几行中,引入线自动断,如果内容不适合的页面宽度、高度,则页面中断。 

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

/*
 * This example is part of the iText 7 tutorial.
 */
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;
 
/**
 * Simple drawing text example.
 */
@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 {
 
        //Initialize PDF document
        PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
 
        //Add new page
        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...");
 
        //Replace the origin of the coordinate system to the top left corner
        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) {
            //Add text and move to the next line
            canvas.newlineShowText(s);
        }
        canvas.endText();
 
        //Close document
        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) {
    //Add text and move to the next line
    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的很多高级功能。这个问题将在下一章回答。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值