【问题标题】:In iText 7 java how do you update Link text after it's already been added to the document在 iText 7 java 中,如何在已将链接文本添加到文档后更新链接文本
【发布时间】:2021-05-25 22:42:08
【问题描述】:

我正在使用 iText7 为我的文档构建目录。在开始之前我知道所有的部分名称,但不知道页码是什么。我当前的过程是在第一页上创建一个表格,并使用通用文本“GO!”创建所有 Link 对象。然后,当我添加部分时,我通过链接对象添加并使用我在创建文档时计算出的页码更新文本。

但是,最后,为链接写出的是“GO!”,而不是我在创建文档其余部分时设置的更新页码值。

我在创建文档时确实将 immediateFlush 标志设置为 false。

public class UpdateLinkTest {

    PdfDocument pdfDocument = null;
    List<Link>links = null;

    Color hyperlinkColor = new DeviceRgb(0, 102, 204);

    public static void main(String[] args) throws Exception {
        List<String[]>notes = new ArrayList<>();

        notes.add(new String[] {"me", "title", "this is my text" });
        notes.add(new String[] {"me2", "title2", "this is my text 2" });

        new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
    }

    public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
        PdfWriter pdfWriter = new PdfWriter(selectedFile);
        pdfDocument = new PdfDocument(pdfWriter);
        Document document = new Document(pdfDocument, PageSize.A4, false);

        // add the table of contents table
        addSummaryTable(notes, document);

        // add a page break
        document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));

        // add the body of the document
        addNotesText(notes, document);

        document.close();
    }

    private void addSummaryTable(List<String[]> notes, Document document) {
        links = new ArrayList<>();

        Table table = new Table(3);
        float pageWidth = PageSize.A4.getWidth();
        table.setWidth(pageWidth-document.getLeftMargin()*2);

        // add header
        addCell("Author", table, true);
        addCell("Title", table, true);
        addCell("Page", table, true);

        int count = 0;
        for (String[] note : notes) {

            addCell(note[0], table, false);
            addCell(note[1], table, false);

            Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
            links.add(link);

            addCell(link, hyperlinkColor, table, false);

            count++;
        }
        document.add(table);
    }

    private void addNotesText(List<String[]> notes, Document document)
            throws Exception {

        int count = 0;
        for (String[] note : notes) {
            int numberOfPages = pdfDocument.getNumberOfPages();

            Link link = links.get(count);
            link.setText(""+(numberOfPages+1));

            Paragraph noteText = new Paragraph(note[2]);
            document.add(noteText);

            noteText.setDestination(++count+"");
            
            if (note != notes.get(notes.size()-1))
                document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
        }
    }

    private static void addCell(String text, Table table, boolean b) {
        Cell c1 = new Cell().add(new Paragraph(text));
        table.addCell(c1);
    }

    private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
        Cell c1 = new Cell().add(new Paragraph(text));
        text.setUnderline();
        text.setFontColor(backgroundColor);
        table.addCell(c1);
    }
}

【问题讨论】:

  • 您能否最小化您的代码示例,以便没有IJavaNote 接口定义和JavaNoteImpl 类定义的人可以轻松重现它?您的 fileOutputStream 也未使用 - 可能也可以从代码示例中删除。我可以想象可以使用没有表格等的更简单的代码示例来重现该问题。如果他们可以轻松运行您的代码,那么提出您的问题的人将更有可能为您提供帮助。我相信有一种很好的方法可以帮助您解决用例。
  • 我更新了示例。它现在更简单且完全独立(没有外部类)。希望有帮助

标签: itext


【解决方案1】:

与您现在的代码相比,需要做更多的工作,因为一旦将元素添加到文档中,对元素的更改就不会生效。立即刷新设置为 false 允许您重新布局元素,但这不会自动发生。您计算段落将放置在的当前页面 (int numberOfPages = pdfDocument.getNumberOfPages();) 的方式不是防弹的,因为在某些情况下可能会提前添加页面,即使内容不会立即放置在它们上。

有一种非常低级的方法可以实现您的目标,但是对于最新版本的 iText (7.1.15),还有一种更简单的方法,但仍然需要一些工作。基本上,您的用例与 CSS 中的 target-counter 概念非常相似,page 计数器是您案例中的目标。为了支持 pdfHTML 插件中的目标计数器,我们在 layout 模块中添加了新功能,这些功能也可以直接使用。

首先,我们要将Link 元素与它们将指向的相应Paragraph 元素联系起来。我们将在布局中使用ID 属性:

link.setProperty(Property.ID, String.valueOf(count));
noteText.setProperty(Property.ID, String.valueOf(count));

接下来,我们将为Link 元素和Paragraph 元素创建自定义渲染器。这些自定义渲染器将与TargetCounterHandler 交互,这是我在介绍中提到的layout 模块中的新功能。这个想法是,在布局操作期间,段落将记住放置它的页面,然后相应的链接元素(请记住,链接元素连接到段落元素)将在该链接元素的布局过程中询问TargetCounterHandler计划了相应的段落。所以在某种程度上,TargetCounterHandler 是一个连接器。

自定义渲染器代码:

private static class CustomParagraphRenderer extends ParagraphRenderer {
    public CustomParagraphRenderer(Paragraph modelElement) {
        super(modelElement);
    }

    @Override
    public IRenderer getNextRenderer() {
        return new CustomParagraphRenderer((Paragraph) modelElement);
    }

    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        LayoutResult result = super.layout(layoutContext);
        TargetCounterHandler.addPageByID(this);
        return result;
    }
}
private static class CustomLinkRenderer extends LinkRenderer {
    public CustomLinkRenderer(Link link) {
        super(link);
    }

    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
        if (targetPageNumber != null) {
            setText(String.valueOf(targetPageNumber));
        }
        return super.layout(layoutContext);
    }

    @Override
    public IRenderer getNextRenderer() {
        return new CustomLinkRenderer((Link) getModelElement());
    }
}

不要忘记将自定义渲染器分配给他们的元素:

link.setNextRenderer(new CustomLinkRenderer(link));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));

现在,我们需要做的另一件事relayout。您已经将immediateFlush 设置为false,这是relayout 工作所必需的。需要重新布局,因为在第一个布局循环中,我们不会知道段落的所有位置,但是当我们知道这些位置时,我们已经在页面上放置了链接。因此,我们需要第二次传递来使用有关段落所在页码的信息,并将该信息设置为链接。

Relayout 非常简单 - 一旦您放置了所有内容,您只需要调用一个专用方法即可:

// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();

需要注意的是,现在您还需要对DocumentRenderer 进行子类化,因为还有一个额外的操作需要在后台执行 - 将目标计数器处理程序传播到根渲染器,我们将用于第二个布局操作:

// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
    public CustomDocumentRenderer(Document document, boolean immediateFlush) {
        super(document, immediateFlush);
    }

    @Override
    public IRenderer getNextRenderer() {
        CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
        renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
        return renderer;
    }
}
document.setRenderer(new CustomDocumentRenderer(document, false));

现在我们完成了。这是我们的视觉结果:

完整代码如下:

public class UpdateLinkTest {

    PdfDocument pdfDocument = null;

    Color hyperlinkColor = new DeviceRgb(0, 102, 204);

    public static void main(String[] args) throws Exception {
        List<String[]> notes = new ArrayList<>();

        notes.add(new String[] {"me", "title", "this is my text" });
        notes.add(new String[] {"me2", "title2", "this is my text 2" });

        new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
    }

    public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
        PdfWriter pdfWriter = new PdfWriter(selectedFile);
        pdfDocument = new PdfDocument(pdfWriter);
        Document document = new Document(pdfDocument, PageSize.A4, false);

        document.setRenderer(new CustomDocumentRenderer(document, false));

        // add the table of contents table
        addSummaryTable(notes, document);

        // add a page break
        document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));

        // add the body of the document
        addNotesText(notes, document);

        // For now we have to prepare the handler for relayout manually, this is going to be improved
        // in future iText versions
        ((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
        document.relayout();

        document.close();
    }

    private void addSummaryTable(List<String[]> notes, Document document) {
        Table table = new Table(3);
        float pageWidth = PageSize.A4.getWidth();
        table.setWidth(pageWidth-document.getLeftMargin()*2);

        // add header
        addCell("Author", table, true);
        addCell("Title", table, true);
        addCell("Page", table, true);

        int count = 0;
        for (String[] note : notes) {
            addCell(note[0], table, false);
            addCell(note[1], table, false);

            Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
            link.setProperty(Property.ID, String.valueOf(count));
            link.setNextRenderer(new CustomLinkRenderer(link));

            addCell(link, hyperlinkColor, table, false);

            count++;
        }
        document.add(table);
    }

    private void addNotesText(List<String[]> notes, Document document) {

        int count = 0;
        for (String[] note : notes) {
            Paragraph noteText = new Paragraph(note[2]);
            noteText.setProperty(Property.ID, String.valueOf(count));
            noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
            document.add(noteText);

            noteText.setDestination(++count+"");

            if (note != notes.get(notes.size()-1))
                document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
        }
    }

    private static void addCell(String text, Table table, boolean b) {
        Cell c1 = new Cell().add(new Paragraph(text));
        table.addCell(c1);
    }

    private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
        Cell c1 = new Cell().add(new Paragraph(text));
        text.setUnderline();
        text.setFontColor(backgroundColor);
        table.addCell(c1);
    }

    private static class CustomLinkRenderer extends LinkRenderer {
        public CustomLinkRenderer(Link link) {
            super(link);
        }

        @Override
        public LayoutResult layout(LayoutContext layoutContext) {
            Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
            if (targetPageNumber != null) {
                setText(String.valueOf(targetPageNumber));
            }
            return super.layout(layoutContext);
        }

        @Override
        public IRenderer getNextRenderer() {
            return new CustomLinkRenderer((Link) getModelElement());
        }
    }

    private static class CustomParagraphRenderer extends ParagraphRenderer {
        public CustomParagraphRenderer(Paragraph modelElement) {
            super(modelElement);
        }

        @Override
        public IRenderer getNextRenderer() {
            return new CustomParagraphRenderer((Paragraph) modelElement);
        }

        @Override
        public LayoutResult layout(LayoutContext layoutContext) {
            LayoutResult result = super.layout(layoutContext);
            TargetCounterHandler.addPageByID(this);
            return result;
        }
    }

    // For now we have to create a custom renderer for the root document to propagate the
    // target counter handler to the renderer that will be used on the second layout process
    // This is going to be improved in future iText versions
    private static class CustomDocumentRenderer extends DocumentRenderer {
        public CustomDocumentRenderer(Document document, boolean immediateFlush) {
            super(document, immediateFlush);
        }

        @Override
        public IRenderer getNextRenderer() {
            CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
            renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
            return renderer;
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-27
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    相关资源
    最近更新 更多