作者:CuteXiaoKe
微信公众号:CuteXiaoKe

  ElementPropertyContainer 类有三个子类:StyleRootElement、和AbstractElement。在第一章里面我们已经简明地讨论了Style类。在上一章我们讨论了RootElement的子类CanvasDocument。在接下来的是三章我们将围绕AbstractElement类来讨论:

  • 我们将在本章讲述ILeafElement的实现类:TabLinkTextImage;
  • 在下一章我们会着重讲述BlockElement对象: DivLineSeparatorListListItemParagraph;
  • 在章节5我们讲述TableCell对象结束对BlockElement对象的探讨;

  注意在第二章我们已经讨论了AreaBreak对象,一直到第五章为止,我们会涵盖所有构件基础块的类;

  在上一章中,我们使用了一个txt文件来创建PDF文档。同样的在本章中我们使用一个CSV文件来当做数据输入,如图3.1所示:

图3.1 CSV文件当做数据输入源

  在上图我们可以清晰的看到,此 CSV 文件可以解释为包含由 6 个字段组成的记录的数据库表:

  1. IMDB电影编号-这些编号是基于英国小说家罗伯特·路易斯·史蒂文森写的《Jekyll and Hyde》所改编的电影;
  2. 年份-电影制作的年份
  3. 标题-电影标题名称
  4. 导演
  5. 产地
  6. 时长

  我们会使用工具类来解析这个文件,这个文件以UTF-8格式存储,读取并保存在二位列表List<List<String>>,代码如下:

public static final List<List<String>> convert(String src, String separator) throws IOException {List<List<String>> resultSet = new ArrayList<>();BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(src), "UTF8"));String line;List record;while ((line = br.readLine()) != null) {StringTokenizer tokenizer = new StringTokenizer(line, separator);record = new ArrayList<>();while (tokenizer.hasMoreTokens()) {record.add(tokenizer.nextToken());}resultSet.add(record);}return resultSet;}

  在本章,我们会使用Tab类来渲染二维列表到PDF中。

1. 使用Tab元素

  我们直接看下面一段代码:

List<List<String>> resultSet = CsvTo2DList.convert(SRC, "|");for (List<String> record : resultSet) {Paragraph p = new Paragraph();p.add(record.get(0).trim()).add(new Tab()).add(record.get(1).trim()).add(new Tab()).add(record.get(2).trim()).add(new Tab()).add(record.get(3).trim()).add(new Tab()).add(record.get(4).trim()).add(new Tab()).add(record.get(5).trim());document.add(p);}

  在第1行,我们使用CsvTo2DList工具类创建了二维列表resultSet。第2行开始,我们对这个结果进行循环,在循环体中,我们对每一行的数据进行分割,每一列中添加Tab对象;

  如图3.2展示了结果的PDF文件:

图3.2 默认的Tab位置

  在下列代码中我们添加了多余的线来展示默认的Tab位置。

PdfCanvas pdfCanvas = new PdfCanvas(pdf.addNewPage());
for (int i = 1; i <= 10; i++) {pdfCanvas.moveTo(document.getLeftMargin() + i * 50, 0);pdfCanvas.lineTo(document.getLeftMargin() + i * 50, 595);
}
pdfCanvas.stroke();

  默认情况下,在页面左边距开始,每个分割线是50用户单位(默认情况下为50pt,用户单位详见itext第3章)。前三列(“IMDB”,“Year”,“Title”)的Tab位置出现的很恰当,但是从"Director(s)"列开始就没有对齐了,我们尝试修复并得出如图3.3所示的结果:

图3.3 修复后的Tab位置

  以下是修复位置后的代码,其中我们使用了TabStop类:

float[] stops = new float[]{80, 120, 430, 640, 720};
List<TabStop> tabstops = new ArrayList();
PdfCanvas pdfCanvas = new PdfCanvas(pdf.addNewPage());
for (int i = 0; i < stops.length; i++) {tabstops.add(new TabStop(stops[i]));pdfCanvas.moveTo(document.getLeftMargin() + stops[i], 0);pdfCanvas.lineTo(document.getLeftMargin() + stops[i], 595);
}
pdfCanvas.stroke();

  在行1中我们定义了一个float数组,里面包含了5个Tab位置。行2定了一个TabStop列表,从行4开始我们开始循环float数组,行5是往TabStop列表中添加元素。同样我们会在第一页把Tab的线画出来,方便我们视觉上判断Tab是否正确。

  接下来的代码和之前的几乎一样:

        List<List<String>> resultSet = CsvTo2DList.convert(SRC, "|");for (List<String> record : resultSet) {Paragraph p = new Paragraph();p.addTabStops(tabstops);p.add(record.get(0).trim()).add(new Tab()).add(record.get(1).trim()).add(new Tab()).add(record.get(2).trim()).add(new Tab()).add(record.get(3).trim()).add(new Tab()).add(record.get(4).trim()).add(new Tab()).add(record.get(5).trim());document.add(p);}document.close();

  第4行是唯一的区别:我们使用addTabStops()方法来把List对象添加到Paragraph中。每一列的内容都是相对于制表符定义的位置对齐方式是左对齐的。

  如果我们修改一下对齐方式,如图3.4所示:

图3.4 不同的Tab对齐方式

  完成上图效果所需的代码如下:

float[] stops = new float[]{80, 120, 580, 590, 720};
List tabstops = new ArrayList();
tabstops.add(new TabStop(stops[0], TabAlignment.CENTER));
tabstops.add(new TabStop(stops[1], TabAlignment.LEFT));
tabstops.add(new TabStop(stops[2], TabAlignment.RIGHT));
tabstops.add(new TabStop(stops[3], TabAlignment.LEFT));
TabStop anchor = new TabStop(stops[4], TabAlignment.ANCHOR);
anchor.setTabAnchor(' ');
tabstops.add(anchor);

  上面定义了5中tabstops(制表符/制表位):

  • 第一个tabstop会把年份集中在位置80上面,为此我们使用的是TabAlignment.CENTER
  • 第二个tabstop会确保标题会从位置120开始,为此我们使用的是TabAlignment.LEFT;
  • 第三个tabstop会确保导演会在位置580结束,为此我们使用的是TabAlignment.RIGHT;
  • 第四个tabstop会确保国家会在位置590开始;
  • 第五个tabstop会把对齐到空格出现的位置,为此我们使用的是TabAlignment.ANCHOR,并且使用setTabAnchor()方法来定义一个tab anchor。

  在CSV文件的时长字段里面并没有空格符,所以我们在试验的时候会把内容后面加上空格\,如下代码所示:

List<List<String>> resultSet = CsvTo2DList.convert(SRC, "|");for (List<String> record : resultSet) {Paragraph p = new Paragraph();p.addTabStops(tabstops);p.add(record.get(0).trim()).add(new Tab()).add(record.get(1).trim()).add(new Tab()).add(record.get(2).trim()).add(new Tab()).add(record.get(3).trim()).add(new Tab()).add(record.get(4).trim()).add(new Tab()).add(record.get(5).trim() + " \'");document.add(p);}document.close();

  接着,让我们来看看另一个变种的样例,如图3.5所示:

图3.5 有前导字符的制表符

  讲一讲如何添加前导字符的实例代码:

float[] stops = new float[]{80, 120, 580, 590, 720};
List tabstops = new ArrayList();
tabstops.add(new TabStop(stops[0], TabAlignment.CENTER, new DottedLine()));
tabstops.add(new TabStop(stops[1], TabAlignment.LEFT));
tabstops.add(new TabStop(stops[2], TabAlignment.RIGHT, new SolidLine(0.5f)));
tabstops.add(new TabStop(stops[3], TabAlignment.LEFT));
TabStop anchor = new TabStop(stops[4], TabAlignment.ANCHOR, new DashedLine());
anchor.setTabAnchor(' ');
tabstops.add(anchor);

  一个前导字符的定义使用了ILineDrawer接口。我们在IMDB电影编号年份之间添加了虚线,在标题导演之间添加实线,在国家时长之间添加了短划线。

  我们可以实现ILineDrawer 接口来画任何一种线。但是iText中已经附带了三种实现:SolidLineDottedLineDashedLine。这些实现类可以让你改变线的宽度和颜色。DottedLine类还允许您更改点之间的间距。在接下来的章节里面,我们也会使用这些类和LineSeparator类一起绘制线分隔符。
  乍一看,使用Tab对象似乎是以表格形式呈现内容的一种很好的方法,但是有一些严格的限制。

2. Tab功能的限制

  前面的例子我们通过使用制表位很好的展示了文字的内容。当然这里面有个前提就是我们使用了横向A4纸,并预留了足够的空间。遇到空间不足的情况咋办,例如如图3.6所示,我们在纵向A4纸上面添加内容:

图3.6 使用纵向A4纸渲染内容

  由上图可见,整体效果不错,除了第一行的"Country"和"Duration"挤在了一起。

  Tab 相关功能之前在 iText 7.0.0 中包含一些bug。 由于舍入错误,某些文本没有以看似随机的方式来选择对齐。 此问题已在 iText 7.0.1 中修复。

  iText 7.0.1 中修复的另一个错误与 SolidLine(实现) 类有关。 在 iText 7.0.0 中,SolidLine 的线宽被忽略。

  我们往下滚动文档,当没有足够的空间将标题导演放在一起时,我们会看到一个更严重的问题。 导演“Charles Lamont”将国家的值推到时长列,分钟数显示在第二行。如图3.7所示:

图3.7 纵向页面后数据溢出

  我们可以通过使用TableCell类以表格形式组织数据来解决这些问题。 这些对象将在接下来的第 5 章中讨论。 现在,我们将继续一些更多的 ILeafElement 实现类。

3. 添加链接

  在之前的样例中,我们展示的内容里面有IMDB电影编号。这个电影编号能让我们可以在IMDB官网(需fan墙)上面找到对应的影片信息。如下图3.8所示,我们不会显示电影编号,但是当我们点击电影标题的时候会跳转到IMDB相应的网址。

图3.8 加入链接到IMDB

  整体的代码如下:

List<List<String>> resultSet = CsvTo2DList.convert(SRC, "|");for (List<String> record : resultSet) {Paragraph p = new Paragraph();p.addTabStops(tabstops);PdfAction uri = PdfAction.createURI(String.format("http://www.imdb.com/title/tt%s", record.get(0)));Link link = new Link(record.get(2).trim(), uri);p.add(record.get(1).trim()).add(new Tab()).add(link).add(new Tab()).add(record.get(3).trim()).add(new Tab()).add(record.get(4).trim()).add(new Tab()).add(record.get(5).trim() + " \'");document.add(p);}document.close();

  在第 5-6 行,我们创建了一个链接到 URL 的 PdfAction 对象。 此 URL 由 https://www.imdb.com/title/tt/IMDB ID 组成。 在第 7 行,我们使用包含电影标题的 String 和 PdfAction 创建一个 Link 对象。 因此,您将能够在单击标题时跳转到相应的 IMDB 页面。

  PDF交互性的是通过annotations(注释)来实现的。注释不是真实内容的一部分。 它们是添加在内容之上的对象。上面的例子就是链接注释的使用方法。当然还有其他很多类型的注释,在本章中我们不会讲述。当然还有许多其他类型的Action(动作),目前我们只使用了URI动作,我们将在第6章用到更多的动作。

  Link 类扩展了 Text 类。此文档列出了一系列可用于 Link 和 Text 类的方法,用于更改字体、更改背景颜色、添加等。

4. 在Text中的额外方法

  在之前的章节中我们已经多次使用了Text类,但让我们仔细看看一些我们还没有讨论过的 Text 功能。

图3.9 TEXT额外的方法

  如上图3.9所示,并结合下述代码,一开始的文字是正常的,但是对于"Dr. Jekyll"字母,我们定义了文本上升,并且水平缩放了单词"and",最后我们歪曲了"Mr. Hyde."

Text t1 = new Text("The Strange Case of ");
Text t2 = new Text("Dr. Jekyll").setTextRise(5);
Text t3 = new Text(" and ").setHorizontalScaling(2);
Text t4 = new Text("Mr. Hyde").setSkew(10, 45);
document.add(new Paragraph(t1).add(t2).add(t3).add(t4));

  我们重点关注一下如下三个心方法:

  • 传递给setTextRise() 方法的参数是距离文本基线上方的用户单位数。 如果希望文本出现在基线下方,您也可以使用负值。
  • setHorizontalScaling() 方法的参数是我们要使用的水平缩放因子。 在这种情况下,单词“and”将呈现为正常宽度的两倍。
  • setSkew() 方法的参数以度为单位定义了两个角度。 第一个是文本与其基线之间的角度。 第二个是将用于倾斜字符的角度。 setSkew() 方法用于模仿斜体字体(参见第 1 章)。

  在后续涉及文本的每个示例中,我们将继续显式或隐式地使用 Text对象。 而本章的后半部分我们把目光看向 Image 类。

5. 引入图片

  接下来我们引入一张电影海报,如图3.10所示:

图3.10 电影海报添加到文档中

  添加图片的代码很简单,如下:

public static final String MARY = "src/main/resources/img/0117002.jpg";
public void createPdf(String dest) throws IOException {PdfDocument pdf = new PdfDocument(new PdfWriter(new FileOutputStream(dest)));Document document = new Document(pdf);Paragraph p = new Paragraph("Mary Reilly is a maid in the household of Dr. Jekyll: ");document.add(p);Image img = new Image(ImageDataFactory.create(MARY));document.add(img);document.close();
}

  在第1行是指向图片的路径。这个路径在第9行中ImageDataFactory对象中会使用,用来创建ImageData对象,并最终转换为Image对象。这个图片为JPEG类型。

  图像存储在页面内容流之外的名为图像 XObject 的对象中。 XObject 代表外部对象。 图像的字节存储在内容流之外的单独对象中。为了方便理解,我们添加两张相同的图片到文档,如图3.11所示:

图3.11 添加两张相同的图片

   最终会生成两个PDF文件:marry_reilly_V2.pdfmary_reilly_V3.pdf,还有之前生成的marry_reilly_V2.pdf,乍一看3个文件是一模一样的,但是比较一下这些文件的大小,我们就会发现一些端倪:

  • V2文件和V1文件有相同的大小。换句话说,包含两个图像的文件与包含单个图像的文件具有或多或少相同的文件大小。 这与我们之前所说的一致:文件作为外部对象仅在文档内部存储一次。 我们两次引用这个 XObject。
  • 标记为 V3 的文件看起来与标记为 V2 的文件相同,但其文件大小几乎是标记为 V2 的文件大小的两倍。 这个JPEG 的图像字节好像是被两次添加到 PDF 文档中。

  创建V2文件的代码如下:

Image img = new Image(ImageDataFactory.create(MARY));
document.add(img);
document.add(img);

  文档我们添加了同一个Image对象两次,但是图像字节存储在一个XObject中。

  创建V3文件的代码如下:

Image img1 = new Image(ImageDataFactory.create(MARY));
document.add(img1);
Image img2 = new Image(ImageDataFactory.create(MARY));
document.add(img2);

  这段代码我们创建了两个Image实例,并添加到同一个document中,最终导致在外部存储了两个图片的字节数据。

  iText 中的图像对象与 PDF 中的图像 XObject 之间存在直接关系。 创建并添加到文档中的每个新图像对象都会在 PDF 中生成一个单独的图像 XObject。 如果您为同一图像创建两个或多个 Image 对象,最后会得到一个臃肿的 PDF 文件,其中包含过多的冗余图像 XObject。 我们应该避免这种情况发生。

  之前这些例子中,我们添加的图片的时候没有设置位置,第一张图片是在我们的第一段下面添加的。 第二张图片添加在第一张图片的正下方。接下来,我们来在特定坐标添加Image

6. 更改图像的位置和宽度

  在图3.12中的两个PDF看起来一模一样,但是他们的创建的方式却不一致.

图3.12 添加图片到一个绝对位置

  上面一个PDF创建的代码如下:

Image img = new Image(ImageDataFactory.create(MARY), 320, 750, 50);
document.add(img);

  上述代码中,在Image的构造函数定了图片的大小和位置。我们定义了坐标(x=320,y=750),然后定了宽度为50(默认情况下为50pt),同时为例保持图像的纵横比,图片的高度也会被调整。

  底下那个PDF创建的代码如下:

Image img = new Image(ImageDataFactory.create(MARY));
img.setFixedPosition(320, 750, UnitValue.createPointValue(50));
document.add(img);

  上述代码中我们使用setFixedPosition()方法来确定图片的位置和大小。注意我们使用了UnitValue的静态方法来定义了50 是一个以 pt 表示的值,当然还可以使用百分比来定义宽度。

  当然Image的构造函数方法和setFixedPosition()方法有很多重载方法,例如下面代码我们可以定义加入到第几页,如下代码:

Image img = new Image(ImageDataFactory.create(MARY));
img.setFixedPosition(2, 300, 750, UnitValue.createPointValue(50));
document.add(img);

  上述代码中,我们添加图片到第二页,这样就触发了第二个页面的创建,效果如图3.13所示:

图3.13 在特定的页面上添加图片

  如果你添加的位置在第200页面,那面前面199页面会被创建(如果没有的话)来确保添加成功,当然这种用法并不推荐,内容不可控。

7. 添加图片到现有PDF

  在之前的系列中我们或多或少的对现有文件进行了操作。在iText7中我们可以使用PdfReder实例来导入一个存在的文档并以此来创建一个新的PDF。在iText5中,使用的是PdfStamper对象添加内容到现有PDF。PdfStamper在iText7中已被废弃,使用PdfDocument实例(低级别内容)和Document(高级别内容)来添加内容。

  让我们首先来看看下述代码:

public void manipulatePdf(String src, String dest) throws IOException {PdfReader reader = new PdfReader(src);PdfWriter writer = new PdfWriter(dest);PdfDocument pdfDoc = new PdfDocument(reader, writer);Document document = new Document(pdfDoc);Image img = new Image(ImageDataFactory.create(MARY));img.setFixedPosition(1, 350, 750, UnitValue.createPointValue(50));document.add(img);document.close();
}

  上述代码使用了PdfReaderPdfWriter对象创建了一个PdfDocument实例,然后并使用这个实例来创建了一个Document,最后在页号1的页面上面添加一个特定坐标和大小的图片,整体效果如图3.14所示:

图3.14 添加图片到现有PDF

  接下来看看如何调整图片的大小。

8. 调整和旋转图片

  之前的例子中是以点为单位调整图宽度,接下来我们使用百分比来调整大小,如下代码:

Image img = new Image(ImageDataFactory.create(MARY));
img.setHorizontalAlignment(HorizontalAlignment.CENTER);
img.setWidthPercent(80);
document.add(img);

  对照图片3.15所示,使用了setHorizontalAlignment()方法是图片居中,使用了setWidthPercent()方法使图片宽度占据页面宽度的80%。

图3.15 以百分比定义图片宽度

注意如果你添加的图片不能适配的话,iText会自动调整图片宽度到可用宽度的100%。

  调整图像大小不会改变图像的原始质量。 图像中的像素数保持不变,也就是图像分辨率Resolution; iText 不会更改图像中的单个像素。当然这不意味当你改变图像大小的时候,它的像素密度不会改变。例如一个图像的分辨率为720*720px,并且将图像渲染到720*720pt,那么像素密度为72ppi/dpi(一英寸72pt,720/(720/72)=72。如果将渲染到72*72pt,那么像素密度为720ppi/dpi(720/(72/72)=720).

图像分辨率Resolution: 数码图像的分辨率就是指这幅图的像素多少,究竟有多少个像素;
数码打印分辨率或屏幕分辨率ppi:表示每英寸有多少个像素点(数码世界);
打印机分辨率或物理世界dpi:表示每英寸有多少个打印点(物理世界),一个像素点可以由一个或多个打印点来表示;
像素密度dpi和ppi通用:像素密度指的是显示物理世界中的显示器、电视机、扫描仪等在宽或者搞或者对角线方向上,一个单位长度的像素数量或者点数量,在这里一个像素就是一个打印点。

  到目前为止,我们添加是直接往文档里面添加Image对象的,你当然可以添加Image对象到BlockElement对象中。如下代码所示:

Paragraph p = new Paragraph("Mary Reilly is a maid in the household of Dr. Jekyll: ");
Image img = new Image(ImageDataFactory.create(MARY));
p.add(img);
document.add(p);

  结果如图3.16所示:

图3.16 添加图片到一个段落

  上图我们可以看到行距会自动调整了,但是图像看起来很大。Mary Reilly 海报尺寸为 182 x 268 像素。 在这种情况下,iText 将在用户单位中使用相同的大小。 因此,图 3.16 中显示的图像尺寸为 182 x 268 pt。iText 可能会根据上下文自动缩放图像, 之前已经提到了图像不适合页面宽度的情况, 而在第 5 章中,我们将看到图像在表格的上下文中是如何表现的。

  scale()方法可以让我们缩放一个图像。在下面的例子中,我们在X和Y轴方向上把图片缩放到50%:

Paragraph p = new Paragraph("Mary Reilly is a maid in the household of Dr. Jekyll: ");
Image img = new Image(ImageDataFactory.create(MARY));
img.scale(0.5f, 0.5f);
img.setRotationAngle(-Math.PI / 6);
p.add(img);
document.add(p);

  上面代码中我们还把图片旋转了-30度,如图3.17所示:

图3.17 旋转和缩放图像

  以下是更改Image对象尺寸的最常用方法:

  • scale()方法:接收两个参数。1. X轴方向上的缩放因子;2.Y轴方向上的缩放因子。例如,X轴方向上面为1f,Y轴上面为0.5f,那么图像将与最初一样宽,但高度将减少到原始高度的 50%。
  • scaleAbsolute()方法:接收两个参数。1.绝对宽度(用户单位/pt) 2.绝对高度(用户单位/pt)。例如绝对高度和宽度都是72f,那么最终这张图片将被渲染成1 inch x 1inch。
  • scaleToFit()方法:接收两个参数。上面scaleAbsolute()会让图片的失去原始的纵横比。scaleToFit()方法第一个参数定义了图片的最大宽度,第二个参数定义了图片的最大高度。图像将在保留纵横比的情况下进行缩放。 这意味着生成的图像可能比预期的要小。

  至今我们加入的图片都是JPEG格式的,iText同样支持其他格式的图片。

9. iText支持的图像类型

  iText 支持以下图像格式:JPEG、JPEG2000、BMP、PNG、GIF、JBIG2、TIFF 和 WMF。 iText 还支持原始图像数据(如果您提供像素或 CCITT 字节)。 如果认为PDF 是一种图像格式(实际上并非如此),你甚至可以像导入图像一样导入 PDF 页面。

  我们接下来重点关注一下除JPEG以外的其他图片类型。

9.1 原始图像数据

  当我们使用ImageDataFactory累时,iText检测提供的图像。它会检查是那种类型的图像,并为该特定图像类型创建一个ImageDataFactory对象。大多数情况下,我们会导入一张已经存在的图片,但我们可以动态的创建原始图像数据,如下图3.18,我们看到了从黄色到蓝色的渐变图像:

图3.18 原始图像

  黄色的 RGB 代码是#FFFF00;蓝色的 RGB 代码是#0000FF。如果创建一个显示从黄色到蓝色渐变的 RGB 图像,我们可以创建一个 256 像素(256 像素宽和 1 像素高)的图像。然后我们可以从 0 (0x00) 到 255 (0xFF) 循环创建从 [Red = 255, Green = 255, Blue = 0] 到 [Red = 0, Green = 0, Blue = 255] 的像素。该图像的总字节大小将是像素数乘以描述每个像素颜色所需的值数。来看下面代码是如何完成的:

byte data[] = new byte[256 * 3];
for (int i = 0; i < 256; i++) {data[i * 3] = (byte) (255 - i);data[i * 3 + 1] = (byte) (255 - i);data[i * 3 + 2] = (byte) i;
}
ImageData raw = ImageDataFactory.create(256, 1, 3, 8, data, null);
Image img = new Image(raw);
img.scaleAbsolute(256, 10);
document.add(img);

  在上面代码段中,使用 ImageDataFactory静态方法创建了有 256 x 1 像素(第一、第二个参数)的图像的ImageData。每个像素使用 3 个颜色空间分量(第三个参数指定)。每个分量使用 8 位/分量 (bpc) 表示(也就是1个字节)。 create() 方法的第四个参数是字节数组data[]。第五个参数是我们可以用来定义透明度的数组。在我们的简单示例中,我们不需要此参数。接着使用 ImageData 创建一个新图像,并在 Y 方向缩放该图像。如果我们不缩放图像,我们只会看到一条非常细的线,高 1 个用户单位。

9.2 分量的有效值值有哪些?

  • 1分量:意味着你使用一种值来定义表示一个像素。通常将其称为灰度值。如果使用1 bit来表示,那么实际就是黑/白(有颜色/无颜色)。如果使用8 bit来表示,则可以定义强度在 0(黑色)和 255(白色)之间变化的灰度值;
  • 3分量: RGB颜色空间,红色(Red),绿色(Green)和蓝色(Blue);
  • 4分量: CMYK颜色空间,青色(Cyan),品红(Magenta)、黄色(Yellow)、黑色(blacK);

  一般情况下,我们不需要用上上述参数,只需要传递一个图片地址或者byte[]图片字节数据即可,iText底层会进行识别并处理。

  下面是一批图像文件,让我们来看看把他们添加到一个Document会发生什么:

public static final String TEST1 = "src/main/resources/img/test/map.jp2";
public static final String TEST2 = "src/main/resources/img/test/butterfly.bmp";
public static final String TEST3 = "src/main/resources/img/test/hitchcock.png";
public static final String TEST4 = "src/main/resources/img/test/info.png";
public static final String TEST5 = "src/main/resources/img/test/hitchcock.gif";
public static final String TEST6 = "src/main/resources/img/test/amb.jb2";
public static final String TEST7 = "src/main/resources/img/test/marbles.tif";

  首先来看看.JP2后缀的JPEG2000格式的图片。

9.3 JPEG / JPEG2000

  添加 JPEG2000 图像的代码看起来与添加 JPEG 图像的代码没有任何不同。

Image img1 = new Image(ImageDataFactory.create(TEST1));
document.add(img1);

  效果如图3.19所示:

图3.19 JPEG2000

  PDF 原生支持 JPEG 和 JPEG200,而 PNG 则不支持。

9.4 BMP / PNG / GIF

  PDF 支持 GIF(GIF为LZW拜纳姆),但每当 iText 遇到 BMP 文件、PNG 文件或 GIF 文件时,该文件都会转换为包含定义像素的字节的原始图像。 然后将这些像素压缩并存储在 PDF 中。

  图3.20展示了一幅BMP图片(蝴蝶),两个PNG文件(第一个Hitchcock和一个信息符号)和一个GIF(被添加了两次,第二个Hitchcock)。

Hitchcock为恐怖电影大师——阿尔弗雷德·希区柯克。

图3.20 BMP,PNG,GIF

  上图左半部分实现的代码如下:

// BMP
Image img2 = new Image(ImageDataFactory.create(TEST2));
img2.setMarginBottom(10);
document.add(img2);
// PNG
Image img3 = new Image(ImageDataFactory.create(TEST3));
img3.setMarginBottom(10);
document.add(img3);
// Transparent PNG
Image img4 = new Image(ImageDataFactory.create(TEST4));
img4.setBorderLeft(new SolidBorder(6));
document.add(img4);

  我们使用setMarginBottom()方法来设置img2img3的下边距为10用户单位。img3有点特殊,info.png是部分透明的,所以我们引入了厚度为6用户单位的左边框。我们之所以能看到边框,是因为图像是透明的。如果图像不透明,则该边界将不可见,因为它会被图像覆盖。PDF 不支持透明图像,支持的方式可能和你预料的不大一样。当将带有透明部分的图像添加到 PDF 时,iText 将添加两个图像(两大部分):

  • 不透明图像:例如,一个图像,其中透明部分用黑色像素代替;
  • 图片掩膜(可以理解为图片底片):这个是1分量类的图片,代表着透明度;

  一个PDF查看器会使用上述两个图片来组成透明图像;

  如果掩膜是1bpc,那么称之为硬掩膜(hard mask),不透明图像的像素在此掩膜下是可见或者不可见的。如果掩膜是大于1bpc的,那么称之为软掩膜(soft mask),不透明图像的像素在此掩膜下是部分透明的。

  对于背景颜色也是如此。在上图右侧第一幅Hitchcock图片中我们定义了一个灰色背景,代码如下:

Image img5 = new Image(ImageDataFactory.create(TEST5));
img5.setBackgroundColor(Color.LIGHT_GRAY);
document.add(img5);

  我们可以看到这个背景颜色,因为hitchcock.gif是具有透明度的GIF文件。而第二个hitchcock是以一种完全不同的方式添加。

9.5 AWT images

  如果你是一位java开发者,那么你肯定对AWT的image类java.awt.Image,iText同样也支持这些图片:

java.awt.Image awtImage =Toolkit.getDefaultToolkit().createImage(TEST5);
Image awt =new Image(ImageDataFactory.create(awtImage, java.awt.Color.yellow));
awt.setMarginTop(10);
document.add(awt);

  在行1-2我们把hitchcock.gif读入到java.awt.Image对象中。在第 4 行从 ImageDataFactory 中得到一个 ImageData 对象。第一个参数是 AWT 图像,第二个参数定义了透明部分(如果有的话)需要使用的颜色。 还可以添加一个布尔值作为第三个参数。 如果该参数为真,则图像将转换为黑白图像。

9.6 JBIG2 / TIFF

  如图3.21所示我们添加了JBIG2 和 TIFF格式的图片:

图3.21 JBIG2, TIFF

  代码比较简单:

// JBIG2
Image img6 = new Image(ImageDataFactory.create(TEST6));
document.add(img6);
// TIFF
Image img7 = new Image(ImageDataFactory.create(TEST7));
document.add(img7);

  不过讲完整的JBIG2和TIFF图片添加到PDF没有想象中那么简单,一个JBIG2或TIFF图片会包含不同的图片页。在这种情况下,我们需要遍历页面并将每个页面提取为单独的图像。另外比较典型的就是GIF图片,包含了不同的帧。

9.7 动画 GIF / 页面图像(Animated GIFs / Paged images)

  让我们看看如下新的图片文件:

public static final String TEST1 ="src/main/resources/img/test/animated_fox_dog.gif";
public static final String TEST2 = "src/main/resources/img/test/amb.jb2";
public static final String TEST3 = "src/main/resources/img/test/marbles.tif";

  图 3.22 显示了动画 GIF 的不同帧,其中显示了狐狸跳过狗的动画。

图3.22 来自动画 GIF 的帧

  PDF 不支持动画 GIF,因此无法将动画原样添加到文档中。 我们只能将每一帧作为单独的图像添加到文档中。如下述代码所示:

URL url1 = UrlUtil.toURL(TEST1);
List list = ImageDataFactory.createGifFrames(url1);
for (ImageData data : list) {img = new Image(data);document.add(img);
}

  首先是把文件路径转换为URL对象。然后,创建一个包含动画 GIF 中每一帧的 ImageDataImageData对象列表。 最后,我们将每一帧作为单独的图像添加到文档中。

  从 JBIG2 和 TIFF 文件读取不同页面的代码更复杂。代码如下:

// JBIG2
URL url2 = UrlUtil.toURL(TEST2);
IRandomAccessSource ras2 =new RandomAccessSourceFactory().createSource(url2);
RandomAccessFileOrArray raf2 = new RandomAccessFileOrArray(ras2);
int pages2 = Jbig2ImageData.getNumberOfPages(raf2);
for (int i = 1; i <= pages2; i++) {img = new Image(ImageDataFactory.createJbig2(url2, i));document.add(img);
}
// TIFF
URL url3 = UrlUtil.toURL(TEST3);
IRandomAccessSource ras3 =new RandomAccessSourceFactory().createSource(url3);
RandomAccessFileOrArray raf3 = new RandomAccessFileOrArray(ras3);
int pages3 = TiffImageData.getNumberOfPages(raf3);
for (int i = 1; i <= pages3; i++) {img = new Image(ImageDataFactory.createTiff(url3, true, i, true));document.add(img);
}
document.close();

  我们首先需要读取出JBIG2或者TIFF文件的页数。所以我们需要创建一个RandomAccessFileOrArray对象。使用此对象,我们可以向 Jbig2ImageDataTiffImageData 类询问 JBIG2 或 TIFF 文件中的页数。 然后我们可以遍历该文件中的页数,并使用 createJbig2()createTiff() 方法来获取创建 Image所需的ImageData 对象。

  到目前为止,我们创建的所有 Image 对象都生成了一个存储在 PDF 文档中的图像 XObject。 在下一个示例中,我们将创建一个不同类型的 XObject。

9.8 WMF / PDF

  到目前为止,我们使用的所有图像类型都是光栅图像。 光栅图像由网格中彼此相邻的某种颜色的像素组成。让我们来看看下述的文件:

public static final String WMF = "src/main/resources/img/test/butterfly.wmf";
public static final String SRC = "src/main/resources/pdfs/jekyll_hyde.pdf";

  WMF 是一种矢量图像格式。 矢量图像没有像素。 它们由基本的几何形状组成,例如直线和曲线。 这些直线和曲线以数学方程式表示,这意味着您可以轻松缩放它们而不会损失任何质量。

  矢量图像的上下文中不存在分辨率的概念。 仅当将图像渲染到设备时,分辨率才会起作用。 设备(打印机、屏幕)的分辨率将决定在查看矢量图像时所感知的分辨率。

  PDF 可以包含光栅图像,每个光栅图像都有自己的分辨率,但 PDF 本身没有分辨率。 PDF 的内容也由使用 PDF 语法定义的几何形状组成。

  如图3.23所示,看到一个代表蝴蝶的 WMF 文件和来自现有 PDF 文件的页面,这些文件是使用 Image 对象添加到 Document 的。

图3.23 WMF, PDF

  如果你查看此 PDF 文件,您将找不到任何图像 XObject; 相反,您会发现两个form XObjects(表单XObject)。 表单 XObject 使用与图像 XObject 相同的机制,只是表单 XObject 不包含像素。 它是页面内容外部的 PDF 语法片段。

  如果我们想使用 Image 类将 WMF 文件添加到 Document,我们需要首先创建一个 PdfFormXObjectWmfImageData 对象将帮助我们创建创建此表单 XObject 所需的 ImageData。 我们可以使用该 xObject1 创建一个 Image 实例。代码如下:

PdfFormXObject xObject1 =new PdfFormXObject(new WmfImageData(WMF), pdf);
Image img1 = new Image(xObject1);
document.add(img1);

  如果我们想像导入图像一样导入一个现有PDF的某一页,我们需要执行类似的操作。代码如下所示:

PdfReader reader = new PdfReader(SRC);
PdfDocument existing = new PdfDocument(reader);
PdfPage page = existing.getPage(1);
PdfFormXObject xObject2 = page.copyAsFormXObject(pdf);
Image img2 = new Image(xObject2);
img2.scaleToFit(400, 400);
document.add(img2);

  我们首先创建一个 PdfReader 对象(第 1 行)和一个基于该readerPdfDocument(第 2 行)。 我们从该现有文档(第 3 行)中获取一个 PdfPage,并将该页面复制为 PdfXFormObject。 我们可以使用该 xObject2 创建一个 Image 实例。

  现有页面的内容将被添加,就好像它是矢量图像一样。 原始页面中可能存在的所有交互功能,例如链接、表单字段和其他注释,都将丢失。

10. 总结

  在本章,我们介绍了实现 ILeafElement 接口的构建块。这些元素是原子构建块; 它们不是由其他元素组成的。

  • Tab(制表符):是一个元素,允许您在其他两个构建块之间放置一些空间,或者使用空白,或者通过引入前导。 你还可以使用 Tab 元素来对齐元素。
  • Text(文本):是一个包含使用单一字体、单一字体大小、单一字体颜色的文本片段的元素。 它是原子文本构建块。
  • Link(链接):是一个Text元素,我们可以为其定义一个PdfAction,例如:当我们单击文本时打开网站的操作。 我们将在第 6 章讨论更多链接和操作的示例。
  • Image(图片):是可用于创建图像 XObject 的元素,以便可以在 PDF 中使用光栅图像。 为方便起见,我们还可以将 PdfFormXObject 包装在 Image 对象中,这样 XObject 和表单 XObject可以使用相同的函数功能。

  当然我们还没有说完这些对象。 我们将在接下来的章节中继续使用它们,从下一章开始我们将会讨论 DivLineSeparatorParagraphListListItem 对象。

iText7高级教程之构建基础块源码下载-CSDN

本章代码资源下载地址:

  1. 关注我的微信公众号CuteXiaoKe,点击代码资源-iText官网代码即可
  2. 或者直接点击微信文章

iText7高级教程之构建基础块——3.使用ILeafElement实现类相关推荐

  1. iText7高级教程之构建基础块——2.添加内容到Canvas或Document

    作者:CuteXiaoKe 微信公众号:CuteXiaoKe 在这一章中,我们通过添加BlockElement和Image对象添加到RootElement实例的方式来创建PDF文档.RootEleme ...

  2. iText7高级教程之构建基础块——1.引入字体

    作者:CuteXiaoKe 微信公众号:CuteXiaoKe 本章我们开始讲述一些使用不同字体展示标题和作者的例子,在这里会引入一些类,例如FontProgram和PdfFont. 本章内容偏长,请耐 ...

  3. iText7高级教程之构建基础块——1.引入字体实践

    本章的例子,请参考我翻译的博文:iText7高级教程之构建基础块--1.引入字体,里面有详细的解释,有什么不懂得也可以评论或者私信我! 例子1:创建不嵌入的三种字体的文档 三种不同的字体来创建带有标题 ...

  4. iText7高级教程之构建基础块——4.使用AbstractElement对象(part 1)

    作者:CuteXiaoKe 微信公众号:CuteXiaoKe   在之前的章节中,我们讨论了实现AbstractElement类的5个类.在章节2中,我们讨论了AreaBreak类.在章节3中我们讨论 ...

  5. iText7高级教程之构建基础块——7.处理事件,设置阅读器首选项和打印属性

    作者:CuteXiaoKe 微信公众号:CuteXiaoKe   整个教程从关于字体的第一章开始.在随后的章节中,我们讨论了每个元素的默认行为,这些元素为:Paragraph.Text.Image等. ...

  6. Arduino 高级教程 01:基础篇

    我与 Arduino,以及为什么要写这个系列的文章 Arduino 这个已经火了好多年了,早就不是什么新鲜的技术.如果有人还不清楚 Arduino 是个什么东西,对不起,请自行搜索,随便翻开哪个维基百 ...

  7. DOS批处理高级教程(一) 批处理基础

    前言 批处理主要是用于脚本的编写, 是为了减少重复劳动力而建立的一个工具;. DOS批处理已经慢慢淡出大家的视线,在windowswindows中我们完全可以写shellshell, python s ...

  8. iText7高级教程之html2pdf——6.在pdfHTML中使用字体

    作者:CuteXiaoKe 微信公众号:CuteXiaoKe   到目前为止,我们还没有花太多的精力来研究将HTML转换为PDF时使用的字体.我们知道Helvetica是iText在没有指定字体时使用 ...

  9. iText7高级教程之html2pdf——1.从Hello HTML开始

    作者:CuteXiaoKe 微信公众号:CuteXiaoKe   在本章,我们通过不同的方法把一个简单的HTML文件转换为PDF文件.HTML文件的内容包含一个"TEST"标题,一 ...

最新文章

  1. TensorFlow用法
  2. 求e的近似值java_7-78 求e的近似值 (15 分)
  3. Eclipse 设置
  4. 【剑指offer-Java版】39二叉树的深度
  5. lanmp/lamp/lnmp/lnamp一键安装
  6. 什么是SSID/ESSID/BSSID
  7. centos7 dotnet command not found
  8. chap6_2 Parallax mapping in OGRE
  9. JS中浅拷贝和深拷贝的使用,深拷贝实现方法总结
  10. 人人都可以开发高可用高伸缩应用——论Azure Service Fabric的意义
  11. PHP 保留 n 位小数
  12. python数学实验与建模司守奎pdf_数学建模算法与程序司守奎.pdf
  13. 安装win7系统时,硬盘模式为IDE模式时安装成功,硬盘模式为AHCI模式时重启蓝屏或一直启动的问题,以及IDE模式与AHCI模式的区别!
  14. 如何使用PowerShell批量删除注册表项
  15. html js创建表格,javascript创建表格方式详解
  16. g6的minimap中的配置_Minimap 缩略图
  17. VMware winserver2016安装
  18. 二、伊森商城 环境 虚拟机配置 p3
  19. rust怎么发送求救信号_海上遇险怎么发求救信号?
  20. 心系冬奥 翰墨传情 |当代书画名家为奥运加油书画推介展【宋碧梅篇】

热门文章

  1. C# @Page指令中的AutoEventWireup,CodeBehind,Inherits
  2. JS cookie的设置、获取和删除(非常详细)
  3. 最先进的微型计算机,获得《微型计算机》称赞,GPD P2 MAX堪称对便携本的终极追求...
  4. springboot毕设项目java学习平台m55rv(java+VUE+Mybatis+Maven+Mysql)
  5. npm(一):从npm CLI说起
  6. AVR-GCC(atmel studio)程序存储器API库函数
  7. mysql精品课程网_7天搞定MySQL!华为云新推精品课程了解一下
  8. Android studio 40 播放网络歌曲
  9. 健客行-结石之1种水果竟能溶解体内结石
  10. GPS与AGPS定位服务