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

Posted CuteXiaoKe

tags:

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

  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 "以上是关于iText7高级教程之构建基础块——3.使用ILeafElement实现类的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

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

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