微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

pdfHTML/iText 7:在 <tfoot> 中打印 <table> 开始/结束行数

如何解决pdfHTML/iText 7:在 <tfoot> 中打印 <table> 开始/结束行数

对于一个项目,我目前正在编写一个使用 pdfHTML 3.0.3 和 iText 7.1.14 的文档生成器。该文档包含一个显示“项目”的表格。这些项目行可能永远不会真正适合一页,并且在大多数情况下会跨越许多页。

此表的第一列有一个项目编号,可能缺少项目编号(由于项目无效)。

我希望表格显示 <tfoot><table> 中的第一个和最后一个项目编号,在理想的解决方案中,第一个和最后一个项目将根据打印的内容动态确定当前布局的页面

示例:https://i.imgur.com/X4cQ4HB.png(FROM 应显示数字 1,TO 应显示数字 5)。

这似乎无法单独使用 HTML 和 CSS,因为它们不支持任何将页面用作上下文的计数器(CSS 计数器似乎使用全局上下文,而不是页面上下文)。

我认为可以根据 TableRenderer 编写渲染器,但我不知道从哪里开始。 iText 文档确实展示了如何创建自己的渲染器的示例,但我似乎找不到与此问题相关的示例。

解决方法

代码将使用 Java,但移植到 C# 应该是将某些方法名称更改为以大写字母开头的问题。我将根据以下表格的 HTML 视觉表示来回答:

HTML visual representation

HTML 代码:

<!DOCTYPE html>
<html>

<head>
  <style>
    table {
      border-collapse: collapse;
    }
    td {
      border: solid 1px black;
      page-break-inside: avoid;
    }
  </style>
</head>

<body>

<table>
  <colgroup>
    <col width="30%">
    <col width="70%">
  </colgroup>
  <tbody>
  <tr>
    <td>1</td>
    <td>Lorem ipsum dolor sit amet,consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Varius quam quisque id diam. Malesuada proin libero nunc consequat interdum varius. Tristique sollicitudin nibh sit amet commodo nulla. Ac tortor dignissim convallis aenean et tortor at risus. Odio ut sem nulla pharetra diam sit amet nisl. Purus faucibus ornare suspendisse sed nisi lacus. Interdum posuere lorem ipsum dolor sit amet consectetur. Elementum facilisis leo vel fringilla est ullamcorper eget. Ac turpis egestas sed tempus urna et pharetra. Urna porttitor rhoncus dolor purus non enim praesent. Mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare. Ipsum consequat nisl vel pretium lectus quam id. Eget nunc scelerisque viverra mauris in aliquam sem fringilla. At urna condimentum mattis pellentesque.

    </td>
  </tr>
  <tr>
    <td>2</td>
    <td>Iaculis at erat pellentesque adipiscing commodo. Sollicitudin ac orci phasellus egestas tellus rutrum. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper. A iaculis at erat pellentesque adipiscing commodo elit at. Nisl rhoncus mattis rhoncus urna neque viverra. Urna cursus eget nunc scelerisque viverra mauris in. Nunc aliquet bibendum enim facilisis gravida. Malesuada bibendum arcu vitae elementum curabitur vitae nunc. Elementum facilisis leo vel fringilla est ullamcorper eget nulla. Quis hendrerit dolor magna eget est lorem.

    </td>
  </tr>
  <tr>
    <td>3</td>
    <td>Eget velit aliquet sagittis id consectetur purus ut faucibus. Tortor condimentum lacinia quis vel eros. Elementum nibh tellus molestie nunc non blandit. Magna eget est lorem ipsum dolor sit amet. Gravida arcu ac tortor dignissim. Commodo viverra maecenas accumsan lacus vel. Vel fringilla est ullamcorper eget nulla facilisi etiam. Tellus in hac habitasse platea dictumst vestibulum. Lectus urna duis convallis convallis. Tincidunt ornare massa eget egestas purus viverra accumsan in nisl. Elementum tempus egestas sed sed risus pretium quam. Aenean pharetra magna ac placerat vestibulum lectus mauris ultrices. Ultrices vitae auctor eu augue ut lectus arcu. Placerat duis ultricies lacus sed turpis tincidunt. Tellus cras adipiscing enim eu turpis egestas pretium aenean. Tincidunt arcu non sodales neque sodales. Posuere ac ut consequat semper viverra nam libero justo laoreet. Turpis egestas integer eget aliquet nibh praesent tristique. Facilisis leo vel fringilla est ullamcorper eget nulla facilisi.

    </td>
  </tr>
  <tr>
    <td>4</td>
    <td>Sem integer vitae justo eget magna fermentum iaculis eu. Dolor sit amet consectetur adipiscing elit ut aliquam purus. Erat imperdiet sed euismod nisi. Scelerisque fermentum dui faucibus in ornare quam. Ipsum dolor sit amet consectetur adipiscing elit duis tristique sollicitudin. Semper quis lectus nulla at. Netus et malesuada fames ac turpis. Ornare suspendisse sed nisi lacus sed viverra tellus in. At urna condimentum mattis pellentesque id. Sit amet justo donec enim diam vulputate ut pharetra sit. Eget egestas purus viverra accumsan. In metus vulputate eu scelerisque felis imperdiet proin fermentum. Fermentum leo vel orci porta non pulvinar. Ut enim blandit volutpat maecenas. Ac tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Bibendum ut tristique et egestas. In massa tempor nec feugiat nisl pretium fusce. Vitae ultricies leo integer malesuada nunc vel. Porttitor massa id neque aliquam. Elementum curabitur vitae nunc sed velit dignissim sodales ut.

      </td>
  </tr>
  <tr>
    <td>5</td>
    <td>Risus ultricies tristique nulla aliquet enim tortor. Bibendum enim facilisis gravida neque convallis a cras semper. Sit amet consectetur adipiscing elit ut aliquam purus sit amet. Nec nam aliquam sem et. Nullam eget felis eget nunc lobortis mattis. In tellus integer feugiat scelerisque varius morbi enim nunc faucibus. Vitae tempus quam pellentesque nec nam. Elit sed vulputate mi sit. Scelerisque fermentum dui faucibus in ornare quam. Lacus viverra vitae congue eu consequat ac felis donec. Sed velit dignissim sodales ut eu sem integer vitae justo. Enim nunc faucibus a pellentesque sit amet porttitor eget. Est ultricies integer quis auctor elit. Massa sed elementum tempus egestas sed sed risus pretium quam. Lectus magna fringilla urna porttitor rhoncus dolor. Viverra maecenas accumsan lacus vel facilisis volutpat est. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Eget lorem dolor sed viverra ipsum nunc. Eget arcu dictum varius duis at consectetur lorem donec. A diam sollicitudin tempor id eu nisl.

    </td>
  </tr>
  <tr>
    <td>6</td>
    <td>Lacinia at quis risus sed vulputate odio ut enim blandit. Tincidunt lobortis feugiat vivamus at augue eget. Duis convallis convallis tellus id interdum velit laoreet. Vitae turpis massa sed elementum. Quam vulputate dignissim suspendisse in est. Id faucibus nisl tincidunt eget nullam non nisi est sit. Enim neque volutpat ac tincidunt vitae semper quis lectus nulla. Purus in mollis nunc sed id semper risus in hendrerit. Faucibus nisl tincidunt eget nullam. Non enim praesent elementum facilisis leo vel fringilla. Nec ultrices dui sapien eget. Eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis. Quis enim lobortis scelerisque fermentum dui faucibus in ornare quam. Est velit egestas dui id ornare. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Congue eu consequat ac felis donec.

    </td>
  </tr>
  <tr>
    <td>7</td>
    <td>Pulvinar sapien et ligula ullamcorper malesuada proin. Ac turpis egestas sed tempus urna. Nunc faucibus a pellentesque sit. Elit ut aliquam purus sit amet luctus. Etiam sit amet nisl purus in mollis nunc sed. At volutpat diam ut venenatis tellus in. Non pulvinar neque laoreet suspendisse interdum consectetur. Quam nulla porttitor massa id neque aliquam vestibulum morbi. Id volutpat lacus laoreet non curabitur gravida arcu ac. Facilisis sed odio morbi quis commodo odio aenean. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Placerat orci nulla pellentesque dignissim. Sit amet mattis vulputate enim. Neque ornare aenean euismod elementum nisi quis. Proin libero nunc consequat interdum varius sit amet mattis vulputate. Eget egestas purus viverra accumsan in nisl nisi scelerisque eu. Penatibus et magnis dis parturient montes nascetur.

    </td>
  </tr>
  <tr>
    <td>8</td>
    <td>Tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Consectetur lorem donec massa sapien faucibus et molestie. Cursus mattis molestie a iaculis at erat pellentesque adipiscing. Dui nunc mattis enim ut tellus elementum sagittis. Congue eu consequat ac felis donec et odio. Purus viverra accumsan in nisl nisi scelerisque eu. Lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis. Tempor nec feugiat nisl pretium fusce id velit ut tortor. Ut faucibus pulvinar elementum integer enim. Egestas quis ipsum suspendisse ultrices gravida dictum fusce. Eu tincidunt tortor aliquam nulla facilisi cras fermentum. Rutrum tellus pellentesque eu tincidunt. Scelerisque eleifend donec pretium vulputate sapien nec.

    </td>
  </tr>
  <tr>
    <td>9</td>
    <td>Integer feugiat scelerisque varius morbi. Posuere sollicitudin aliquam ultrices sagittis orci a. Habitant morbi tristique senectus et netus et malesuada fames ac. Sed faucibus turpis in eu mi bibendum neque. Tortor id aliquet lectus proin. Enim sit amet venenatis urna cursus eget nunc scelerisque. Cras adipiscing enim eu turpis egestas pretium aenean pharetra. Volutpat diam ut venenatis tellus in metus vulputate. Senectus et netus et malesuada. Gravida cum sociis natoque penatibus et. Ut tristique et egestas quis ipsum suspendisse. At tempor commodo ullamcorper a. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Massa ultricies mi quis hendrerit dolor magna.

    </td>
  </tr>
  <tr>
    <td>10</td>
    <td>In eu mi bibendum neque egestas congue quisque egestas diam. Hendrerit gravida rutrum quisque non tellus. Posuere morbi leo urna molestie at. Turpis egestas pretium aenean pharetra magna ac placerat. Vel pharetra vel turpis nunc eget lorem dolor. Lorem sed risus ultricies tristique nulla aliquet enim tortor. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Consectetur adipiscing elit pellentesque habitant morbi tristique senectus et. Proin libero nunc consequat interdum varius sit amet mattis vulputate. Elementum pulvinar etiam non quam lacus. Egestas egestas fringilla phasellus faucibus. Vel pretium lectus quam id leo in vitae turpis. Bibendum ut tristique et egestas. Morbi non arcu risus quis varius. Morbi tristique senectus et netus. Sed id semper risus in hendrerit gravida rutrum quisque. Luctus venenatis lectus magna fringilla urna. Sed turpis tincidunt id aliquet.

    </td>
  </tr>
  <tr>
    <td>11</td>
    <td>Integer feugiat scelerisque varius morbi. Posuere sollicitudin aliquam ultrices sagittis orci a. Habitant morbi tristique senectus et netus et malesuada fames ac. Sed faucibus turpis in eu mi bibendum neque. Tortor id aliquet lectus proin. Enim sit amet venenatis urna cursus eget nunc scelerisque. Cras adipiscing enim eu turpis egestas pretium aenean pharetra. Volutpat diam ut venenatis tellus in metus vulputate. Senectus et netus et malesuada. Gravida cum sociis natoque penatibus et. Ut tristique et egestas quis ipsum suspendisse. At tempor commodo ullamcorper a. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Massa ultricies mi quis hendrerit dolor magna.

    </td>
  </tr>
  <tr>
    <td>12</td>
    <td>Tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Consectetur lorem donec massa sapien faucibus et molestie. Cursus mattis molestie a iaculis at erat pellentesque adipiscing. Dui nunc mattis enim ut tellus elementum sagittis. Congue eu consequat ac felis donec et odio. Purus viverra accumsan in nisl nisi scelerisque eu. Lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis. Tempor nec feugiat nisl pretium fusce id velit ut tortor. Ut faucibus pulvinar elementum integer enim. Egestas quis ipsum suspendisse ultrices gravida dictum fusce. Eu tincidunt tortor aliquam nulla facilisi cras fermentum. Rutrum tellus pellentesque eu tincidunt. Scelerisque eleifend donec pretium vulputate sapien nec.

    </td>
  </tr>
  </tbody>
  <tfoot>
  <tr><td colspan="2">from <span id="from">dummy</span> to <span id="to">dummy</span></td></tr>
  </tfoot>
</table>

</body>
</html>

请注意,我们在此处使用 page-break-inside: avoid; CSS 声明是为了使单元格不会跨页面被打断(这会使我们更难以确定要在页脚中显示的范围)。

另请注意,我们的 <tfoot> 元素中有一个特殊的构造:

  <tfoot>
  <tr><td colspan="2">from <span id="from">dummy</span> to <span id="to">dummy</span></td></tr>
  </tfoot>

我们标记了 <span> 占位符,这些占位符将使用我们自定义的 id 填充范围编号。如果您不能完全控制用于生成 PDF 的 HTML,那么您可以执行一些预处理步骤将所需的 <tfoot> 内容注入到您的 HTML 中。

我们需要在 HTML 到 PDF 的转换中自定义一些呈现行为,起点是自定义传递给 ConverterProperties 的标签工作器工厂:

ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setTagWorkerFactory(new CustomTagWorkerFactory());
HtmlConverter.convertToPdf(new File(sourceHtml),new File(targetPDF),converterProperties);

在我们的标签工作者工厂中,我们需要对上述 <span> 占位符以及通用 <table> 元素进行自定义处理(请注意,如果您的 HTML 中有多个表格,那么您可能需要区分您在标签工作器中处理哪个表,并且只为需要在页脚中添加范围的表添加自定义)。

private static final class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
    @Override
    public ITagWorker getCustomTagWorker(IElementNode tag,ProcessorContext context) {
        if (tag.name().equals("span") && ("from".equals(tag.getAttribute("id")) || "to".equals(tag.getAttribute("id")))) {
            return new ElementCounterTagWorker(tag,context);
        } else if (tag.name().equals("table")) {
            return new CustomTableTagWorker(tag,context);
        }
        return super.getCustomTagWorker(tag,context);
    }
}

自定义表格标签工作者非常基础——它只是为表格的页脚设置一个自定义渲染器(渲染器本身将在下面定义):

private static class CustomTableTagWorker extends TableTagWorker {
    public CustomTableTagWorker(IElementNode element,ProcessorContext context) {
        super(element,context);
    }

    @Override
    public IPropertyContainer getElementResult() {
        IPropertyContainer table = super.getElementResult();
        ((Table)table).getFooter().setNextRenderer(new CustomTableFooterRenderer(((Table) table).getFooter()));
        return table;
    }
}

<span> 元素的标签工作者也旨在为我们的 <span> 占位符定义自定义渲染器,但由于从 HTML 元素到 iText 布局元素的映射方式,实现有点困难执行。请注意,我们还将占位符的类型(来自 id 属性)保存到自定义渲染器:

private static class ElementCounterTagWorker extends SpanTagWorker {
    private IElementNode element;
    public ElementCounterTagWorker(IElementNode element,context);
        this.element = element;
    }

    @Override
    public List<IPropertyContainer> getAllElements() {
        List<IPropertyContainer> elements = super.getAllElements();
        for (IPropertyContainer elem : elements) {
            if (elem instanceof Text) {
                ((Text) elem).setNextRenderer(new TextCounterRenderer((Text) elem,element.getAttribute("id")));
            }
        }
        return elements;
    }
}

现在最有趣的部分是表格的渲染器定义和我们的跨度占位符。这个想法是在 span 元素的布局期间,该元素在表页脚渲染器(它是链中的父级之一)中注册自己。然后表页脚渲染器将在绘制内容之前替换 span 元素的内容,此时已经定义了确切的布局位置。

以下是我们的 <span> 占位符中文本的自定义渲染器的定义方式(请注意,您必须同时覆盖 getNextRenderer()createCopy()

private static class TextCounterRenderer extends TextRenderer {
    String type;

    public TextCounterRenderer(Text textElement,String type) {
        super(textElement);
        this.type = type;
    }

    protected TextCounterRenderer(TextCounterRenderer other) {
        super(other);
        this.type = other.type;
    }

    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        IRenderer tableRenderer = parent;
        while (!(tableRenderer instanceof TableRenderer)) {
            tableRenderer = tableRenderer.getParent();
        }
        ((CustomTableFooterRenderer)tableRenderer).register(this,layoutContext);
        return super.layout(layoutContext);
    }

    @Override
    public IRenderer getNextRenderer() {
        return new TextCounterRenderer((Text) getModelElement(),type);
    }

    @Override
    protected TextRenderer createCopy(GlyphLine gl,PdfFont font) {
        TextCounterRenderer copy = new TextCounterRenderer(this);
        copy.setProcessedGlyphLineAndFont(gl,font);
        return copy;
    }
}

最后,我们的表页脚渲染器的实现。这个想法是跟踪所有注册的文本渲染器(我们用于插入范围的占位符),然后在我们即将绘制表格页脚之前,分析我们将要绘制的表格的内容以找到范围将在此页面上绘制的行数,并将这些范围注入到我们的占位符中。

private static class CustomTableFooterRenderer extends TableRenderer {
    private Set<Pair<TextCounterRenderer,LayoutContext>> textCounters = new HashSet<>();

    public CustomTableFooterRenderer(Table modelElement) {
        super((Table)modelElement);
    }

    public CustomTableFooterRenderer(Table modelElement,RowRange rowRange) {
        super(modelElement,rowRange);
    }

    public void register(TextCounterRenderer renderer,LayoutContext context) {
        Pair<TextCounterRenderer,LayoutContext> textNode = new Pair<>(renderer,context);
        if (!textCounters.contains(textNode)) {
            textCounters.add(textNode);
        }
    }

    @Override
    public void draw(DrawContext drawContext) {
        for (Pair<TextCounterRenderer,LayoutContext> counter : textCounters) {
            IRenderer counterParent = counter.getKey().getParent();
            while (counterParent != null && counterParent != this) {
                counterParent = counterParent.getParent();
            }
            if (counterParent == this) {
                TableRenderer tableRenderer = (TableRenderer) this.getParent();
                int columnCount = 2;
                IRenderer firstRowFirstCellRenderer = tableRenderer.getChildRenderers().get(0);
                IRenderer lastRowFirstCellRenderer = tableRenderer.getChildRenderers().get(tableRenderer.getChildRenderers().size() - columnCount);
                if ("from".equals(((TextCounterRenderer)counter.getKey()).type)) {
                    counter.getKey().setText(firstRowFirstCellRenderer.toString());
                } else {
                    counter.getKey().setText(lastRowFirstCellRenderer.toString());
                }
                counter.getKey().layout(counter.getValue());
            }
        }
        super.draw(drawContext);
    }

    @Override
    public IRenderer getNextRenderer() {
        return new CustomTableFooterRenderer((Table) modelElement,rowRange);
    }
}

总而言之,我们在 PDF 中得到以下结果:

page 1 result

page 2 result

请注意,没有 100% 保证此解决方案适用于所有未来的 iText 版本,因为它取决于一些实现细节,但它应该适用于 iText 7.1.15 行,并且它提供了一个关于在哪里寻找的好主意iText 中的通用布局自定义,适用于 HTML 到 PDF 转换上下文和纯布局模块使用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。