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

Posted CuteXiaoKe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iText7高级教程之构建基础块——5.使用AbstractElement对象(part 2)相关的知识,希望对你有一定的参考价值。

  介绍完本章后,iText 7中所有的基础构建块都已经涵盖并介绍了。我们将两个最常用的构建块放在最后:TableCell。 这些对象旨在以表格形式呈现内容。 许多开发人员使用 iText 将数据库查询的结果集转换为 PDF 格式的报告/页面。 他们创建了一个Table,其中每一行对应一个数据库记录,将每个字段值包装在一个 Cell 对象中。

  我们可以使用之前的“Jekyll and Hyde”例子的数据库轻松地创建一个与 PDF 类似的表格,但让我们先从几个简单的例子开始。

1. 第一个表格

  如图5.1是我们用iText7创建的第一个表格:

图5.1 第一个表格

  创建的代码很简单:

public void createPdf(String dest) throws IOException 
    PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
    Document document = new Document(pdf);
    Table table = new Table(new float[] 1, 1, 1);
    table.addCell(new Cell(1, 3).add("Cell with colspan 3"));
    table.addCell(new Cell(2, 1).add("Cell with rowspan 2"));
    table.addCell("row 1; cell 1");
    table.addCell("row 1; cell 2");
    table.addCell("row 2; cell 1");
    table.addCell("row 2; cell 2");
    document.add(table);
    document.close();

  我们在第4行创建了有3列的表格。通过传递一个包含三个float的数组,我们表明我们需要三列,但我们还没有定义宽度(1远远小于实际宽度,会自适应改变大小,具体解释后面会提及)。

  对于这个表格,就我们从行5-10添加了6个单元格:

  • 第一个单元格的行跨度为 1,列跨度为 3。
  • 第二个单元格的行跨度为 2,列跨度为 1。
  • 后续四个单元格的行跨度和列跨度为 1。

  对于前两个单元格,我因为我们想要定义一个特定的行跨度或列跨度,所以显式创建了一个Cell对象。对于接下来的四个单元格,我们直接使用String添加到Table中,也就是Cell对象是由 iText 在内部创建的,例如第 7 行是table.addCell(new Cell().add("row 1; cell 1"))的简写。
  你可能记得的在iText 5中的PdfPTablePdfPCell类不再存在。它们被TableCell取代,同时iText 7简化了表格的创建方式,文本模式复合模式的 iText 5 概念在初次使用 iText 的用户中引起了很多混淆。iText 7现在使用add()方法向Cell添加内容。

  浮点数组中的值是以用户单位表示的最小值。我们传递了 1 pt 的值。作为每列的宽度,很明显单元格的内容不适合宽度为 1/72 英寸(1pt)的列,因此 iText 自动扩展列以确保内容分布正确。在这种情况下,实际宽度由单元格的内容决定。当然我们有不同的方法来改变列的宽度。

2. 定义列宽度

  如图5.2展示第一个表格的变体:

图5.2 使用绝对宽度定义列宽度

  上图实现的代码和之前的几乎一模一样,唯一的改变是创建表格时传入构造器的值,现在为100:

Table table = new Table(new float[]200, 100, 100);

  到目前为止,我们使用了float数字来创建Table实例。还有一种构造器,传递的参数为UnitValue对象数组。

  UnitValue类有两个属性,unitType为类型,value为具体值。可以分为两大类:

  • 类型为UnitValue.POINT:绝对值类型;
  • 类型为UnitValue.PERCENT:相对值类型,在这我们可以用来定义相对宽度;

  上面的例子中,其实就是隐式使用类型为UnitValue.POINTUnitValue

  如图5.3所示,我们使用相对宽度来创建表格:

图5.3 使用相对宽度定义列宽度

  同样,我能也只改变了一行代码:

Table table = new Table(UnitValue.createPercentArray(new float[]1, 1, 1));

  我们使用现有的便利方法createPercentArray()创建了一个UnitValue对象数组,这些对象将每列的宽度定义为整个表格宽度的三分之一。 由于我们没有定义完整表格的宽度,因此每列的宽度由单元格的内容(最多的内容)决定。 在这种情况下,在计算表格的总宽度时,决定性的是“Cell with rowspan 2”单元格的内容。

3. 定义表格宽度

  我们也可以自定义表格的总宽度。 同样通过使用绝对宽度或相对宽度来完成。 如图 5.4 显示了两个表格,一个宽度为 450 个用户单位,另一个宽度为页面上可用宽度的 80%,不包括页边距。 请注意,我们还更改了表格的对齐方式。

图5.4 定义表格宽度

  让我们比较一下两者的代码,上半图片的实现代码如下:

Table table = new Table(UnitValue.createPercentArray(new float[]2, 1, 1));
table.setWidth(450);
table.setHorizontalAlignment(HorizontalAlignment.CENTER);

  下半图片的实现代码如下:

Table table = new Table(UnitValue.createPercentArray(new float[]2, 1, 1));
table.setWidthPercent(80);
table.setHorizontalAlignment(HorizontalAlignment.CENTER);

  和之前一样,我们同样也定义了列宽度的相对宽度,也就是第一列的宽度等于第二、三列宽度之和。第一段代码,我们使用setWidth()方法来定义绝对宽度:450用户单位。第二段代码,我们使用setWidthPercent()方法来告诉iText添加表格的时候使用80%的可用宽度。
  假设你不管有意或无意地定义了一个太窄而无法展示所有内容的宽度(例如450用户单位改成20或者更小),在这种情况下,iText 将忽略使用setWidth()setWidthPercent()方法传递的宽度。发生这种情况时不会引发异常,但您会在日志文件中看到以下消息:

WARN c.i.layout.renderer.TableWidths - Table width is more than expected due to min width of cell(s).

  让我们再举一个示例来显示使用绝对宽度与使用相对宽度之间的区别。

  如图 5.5中,我们有一个占可用宽度 80% 的表格。但是我们宽度定义使用绝对宽度。

图5.5 定义表格宽度(额外的例子)

  整体实现的代码如下:

Table table = new Table(new float[]2, 1, 1);
table.setWidthPercent(80);
table.setHorizontalAlignment(HorizontalAlignment.CENTER);

  乍一看,我们可能认为使用new float[]2, 1, 1 等价于使用 UnitValue.createPercentArray(new float[]2, 1, 1),但仔细观察,你会注意到第一列的宽度不是第二列和第三列的两倍,因为new float[]2, 1, 1中的 float 数组的值是以用户单位表示的最小值,它们不是使用 UnitValue数组时的相对值类型。在这种情况下,iText 会根据单元格的内容来定义不同列的宽度,试图使结果尽可能令人赏心悦目。

  我们现在已经使用setHorizo​​ntalAlignment()方法将表格居中了几次,接下来让我们来看看一般是如何进行对齐表格和内容的。

4. 选择正确的对齐配置

  如图5.6所示,我们同样改变了单元格内容的对齐方式。

图5.6 单元格内容对齐

  我们有多种方式改变Cell内容的对齐方式,代码如下:

Table table = new Table(UnitValue.createPercentArray(new float[]2, 1, 1));
table.setWidthPercent(80);
table.setHorizontalAlignment(HorizontalAlignment.CENTER);
table.setTextAlignment(TextAlignment.CENTER);
table.addCell(new Cell(1, 3).add("Cell with colspan 3"));
table.addCell(new Cell(2, 1).add("Cell with rowspan 2")
    .setTextAlignment(TextAlignment.RIGHT));
table.addCell("row 1; cell 1");
table.addCell("row 1; cell 2");
table.addCell("row 2; cell 1");
table.addCell("row 2; cell 2");
Cell cell = new Cell()
    .add(new Paragraph("Left").setTextAlignment(TextAlignment.LEFT))
    .add(new Paragraph("Center"))
    .add(new Paragraph("Right").setTextAlignment(TextAlignment.RIGHT));
table.addCell(cell);
cell = new Cell().add("Middle")
    .setVerticalAlignment(VerticalAlignment.MIDDLE);
table.addCell(cell);
cell = new Cell().add("Bottom")
    .setVerticalAlignment(VerticalAlignment.BOTTOM);
table.addCell(cell);
document.add(table);

  我们再次使用setHorizo​​ntalAlignment()方法来定义表格本身的水平对齐方式(第 3 行)。改方法参数为枚举,所有的值为Horizo​​ntalAlignment.LEFT——左对齐,默认值、Horizo​​ntalAlignment.CENTER——居中对齐,在本例中使用和 Horizo​​ntalAlignment.RIGHT

  此外,我们使用setTextAlignment()法更改添加到此表的Cell内容的默认对齐方式。默认情况下,此内容左对齐(TextAlignment.LEFT);我们将对齐方式更改为TextAlignment.CENTER(第 4 行)。最后的结果显示,“Cell with colspan 3”将位于我们添加的第一个单元格的中心(第 5 行)。

  我们将第二个单元格的“Cell with rowspan 2”的对齐方式更改为TextAlignment.RIGHT。进一步,我们在Cell级别使用setTextAlignment()方法(第 6-7 行)。通过在不指定对齐方式的情况下再添加四个单元格来完成此行跨度中的两(第8-11行)。对齐是从表中继承的,所以内容居中。

  从第 12 行开始,我们定义了一个Cell,定义了内容级别的对齐方式:

  • 行13:添加为左对齐的Paragraph
  • 行14:我们没有为段落定义对齐方式。对齐是从Cell继承的。Cell级别也没有定义对齐方式,因此对齐方式是从Table继承的。所以内容居中。
  • 行15:添加为右对齐的Paragraph

  接下来的两个单元格演示了垂直对齐和setVerticalAlignment()方法。默认情况下,内容与顶部对齐 (VerticalAlignment.TOP)。在第 17-18 行,我们创建了一个对齐设置为中间的 Cell(垂直方向:VerticalAlignment.MIDDLE)。在第 20-21 行,内容是底部对齐的(VerticalAlignment.BOTTOM)。

  如图 5.6所示,一行的高度会自动适应该行中单元格的高度。单元格的高度取决于其内容,但我们可以改变它.

5. 更改单元高度

  我来看看一下代码:

Paragraph p =
    new Paragraph("The Strange Case of\\nDr. Jekyll\\nand\\nMr. Hyde")
        .setBorder(new DashedBorder(0.3f));

  上述String包含换行符\\n,这将导致这个Paragraph由多行组成。我们还定义了 0.3 个用户单位的虚线边框。然后会把同样的Paragraph添加到Table中7次。

图5.7 完整单元格和裁剪单元格

  由于虚线边框,很容易区分Paragraph的边界和Cell的实线边框。 如图 5.7中,我们看到前两个单元格:一个显示全文; 在另一个中,文本被剪裁。

  让我们分段看一下代码:

Table table = new Table(UnitValue.createPercentArray(new float[]1));
table.setWidthPercent(100);
table.addCell(p);
Cell cell = new Cell().setHeight(45).add(p);
table.addCell(cell);

  表格第二行的内容被裁剪,是因为我们限制了单元格高度为45pt(行4),而第一行的高度我们没有定义(行3)。在这种情况下是 iText 以Paragraph的全部内容计算适合Cell的高度。 45 pt 的高度不足以渲染Paragraph对象中的所有行,因此文本将被剪裁。
  当定义一个不足以呈现内容的固定高度时,不会抛出异常。 但是,你将在日志文件中看到以下消息:

c.i.layout.renderer.BlockRenderer - Element content was clipped because some height properties are set.

  接下来的代码片段如下:

    cell = new Cell().setMinHeight(45).add(p);
    table.addCell(cell);
    cell = new Cell().setMinHeight(135).add(p);
    table.addCell(cell);
    cell = new Cell().setMaxHeight(45).add(p);
    table.addCell(cell);
    cell = new Cell().setMaxHeight(135).add(p);
    table.addCell(cell);

  结果如图5.8所示:

图5.8 单元格的最小和最高高度

  我们可以看到有4行:

  1. 第一行的最小高度为 45 pt。 当然我们的内容大于45pt,因此该行高于 45 pt。
  2. 第二行的最小高度为 135 pt。 这绰绰有余,因此我们可以在段落的底部边框和单元格的底部边框之间看到一些额外的空间。
  3. 第三行的最大高度为 45 pt。 内容被剪裁,我们得到与使用setHeight()方法设置高度时相同的警告日志。
  4. 第四行的最大高度为 135 pt。 同样也绰绰有余,因此文本不会被剪裁,但段落下方也不会添加额外的空格。

  最后是内容别旋转以后高度以后改变,如图5.9

图5.9 内容旋转的单元格

  旋转Cell内容是使用setRotationAngle()方法,角度需要以弧度表示。代码如下所示:

cell = new Cell().add(p).setRotationAngle(Math.PI / 6);
table.addCell(cell);

  我们引入Paragraph边框来查看Paragraph占用的空间——带有虚线边框的矩形,和单元格占用的空间——带有实线边框的矩形。 虚线边框和实线边框之间的空间称为填充。 默认情况下,使用 2 pt 的填充。

  在下一个示例中,我们将更改一些单元格的填充,还将讨论单元格边距的概念。

6. 单元格颜色和填充

  如图5.10所示,我们修改了表格的背景颜色为橙色,并且我们为某些单元格定义了不同的背景颜色。 此外,我们在不同的地方更改了填充。

图5.10 带填充单元格

  让我们实现上图的代码:

Table table = new Table(
    UnitValue.createPercentArray(new float[]2, 1, 1));
table.setBackgroundColor(Color.ORANGE);
table.setWidthPercent(80);
table.setHorizontalAlignment(HorizontalAlignment.CENTER);
table.addCell(
    new Cell(1, 3).add("Cell with colspan 3")
        .setPadding(10).setBackgroundColor(Color.GREEN));
table.addCell(new Cell(2, 1).add("Cell with rowspan 2")
    .setPaddingLeft(30)
    .setFontColor(Color.WHITE).setBackgroundColor(Color.BLUE));
table.addCell(new Cell().add("row 1; cell 1")
    .setFontColor(Color.WHITE).setBackgroundColor(Color.RED));
table.addCell(new Cell().add("row 1; cell 2"));
table.addCell(new Cell().add("row 2; cell 1")
    .setFontColor(Color.WHITE).setBackgroundColor(Color.RED));
table.addCell(new Cell().add("row 2; cell 2").setPadding(10)
    .setFontColor(Color.WHITE).setBackgroundColor(Color.RED));
document.add(table);

  行3我们把这个表格设置为橙色,然后我们添加6个单元格到这个表格:

  1. 行6-9:具有绿色背景和 10 单位用户大小填充的单元格。 填充指的是绿色矩形的边框和段落的边界之间的空间。
  2. 行9-11:一个带有白色文本、蓝色背景和 30 个用户单位的左侧填充的单元格。 文本不会从做左侧开始,因为左边框和文本之间有 30 个用户单位的空间。
  3. 行12-13:具有白色文本、红色背景和填充默认值的单元格。 文本不会粘在边框上,因为 iText 使用 2 个用户单位的默认填充。
  4. 行14:具有默认属性的单元格。 这个背景是橙色的,因为继承表格的背景颜色。
  5. 行15-16:一个带有白色文本和红色背景的单元格。
  6. 行17-18:一个带有白色文本、红色背景和 10 个用户单位的填充的单元格。

  其实所有操作都与在 html 中使用 CSS 定义颜色和填充时发生的情况非常相似。上述Java 代码等效的 HTML/CSS代码如下所示:

<table
    style="background: orange; text-align: center; width: 80%"
    border="solid black 0.5pt" align="center" cellspacing="0">
<tr>
    <td style="padding: 10pt; margin: 5pt; background: green;"
        colspan="3">Cell with colspan 3</td>
</tr>
<tr>
    <td style="color: white; background: blue;
        margin-top: 5pt; margin-bottom: 30pt; padding-left: 30pt"
        rowspan="2">Cell with rowspan 2</td>
    <td style="color: white; background: red">row 1; cell 1</td>
    <td>row 1; cell 2</td>
</tr>
<tr>
    <td style="color: white; background: red; margin: 10pt;">
        row 2; cell 1</td>
    <td style="color: white; background: red; padding: 10pt;">
        row 2; cell 2</td>
</tr>

  在浏览器中打开的效果如图5.11所示:

图5.11 浏览器中的 HTML 表格

  当然我们可以使用pdfHTML插件来把这个HTML转换成PDF,得到的结果会和图5.10一样。

  如果你对HTML很熟悉的话,你现在可能会提出疑问:“HTML中定义的边距呢?怎么不见了?”

  事实上,在研究 HTML 时,你会看到 CSS 属性,例如margin: 5pt,margin-top: 5pt等等。 我们之所以在浏览器中看不到任何这些边距,是因为 HTML 中的单元格没有考虑边距。 浏览器只是忽略这些值。 由于 HTML 和 CSS 中的这种行为,iText的设计会忽略Cell对象的边距属性。 这是默认行为,当然iText中也是可以改变的。

7. 单元格边距

  实现边距的效果如图5.12所示:

图5.12 具有填充和边距的单元格

  基于5.11的代码我们重新调整一下,把margin添加进去:

 
Table table = new Table(
    UnitValue.createPercentArray(new float[]2, 1, 1));
table.setBackgroundColor(Color.ORANGE);
table.setWidthPercent(80);
table.setHorizontalAlignment(HorizontalAlignment.CENTER);
table.addCell(
    new MarginCell(1, 3).add("Cell with colspan 3")
        .setPadding(10).setMargin(5).以上是关于iText7高级教程之构建基础块——5.使用AbstractElement对象(part 2)的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

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

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