iText 7 在特定位置添加注解

iText 7 Add Annotation at a specific position

我使用 iText7 将 HTML 转换为 PDF,并且需要为 HTML 中的特定文本添加文本标记注释。我正在按照 link and then I am using the annotation examples given here.

中的说明使用 CustomTagWorkers

通过用 link 注释替换 qr 标签,我能够成功添加 Link 注释。但是我的要求是添加文本标记注释。 Text Markup 注释只能通过给出我在代码中不知道的页面(矩形对象)的具体坐标来绘制。我试着给 Rectangle(0, 0) 希望 iText 会渲染它来代替标签。但是我无法将文本标记注释添加到段落对象,该对象是 public IPropertyContainer getElementResult() { 的 return 对象。

这是我的全部代码:

/**
 * Converts an HTML file to a PDF document, introducing a custom tag to create a
 * QR Code involving a custom TagWorker and a custom CssApplier.
 */
public class C05E04_QRCode2 {

    /**
     * The path to the resulting PDF file.
     */
    public static final String DEST = "C:\Samples\itext-annotation\qrcode.pdf";

    /**
     * The path to the source HTML file.
     */
    public static final String SRC = "C:\Samples\itext-annotation\qrcode.html";

    /**
     * The main method of this example.
     *
     * @param args no arguments are needed to run this example.
     * @throws IOException signals that an I/O exception has occurred.
     */
    public static void main(String[] args) throws IOException {
        File file = new File(DEST);
        file.getParentFile().mkdirs();

        C05E04_QRCode2 app = new C05E04_QRCode2();
        System.out.println("pdf");
        app.createPdf(SRC, DEST);
    }

    /**
     * Creates the PDF file.
     *
     * @param src  the path to the source HTML file
     * @param dest the path to the resulting PDF
     * @throws IOException signals that an I/O exception has occurred.
     */
    public void createPdf(String src, String dest) throws IOException {
        ConverterProperties properties = new ConverterProperties();
        properties.setCssApplierFactory(new QRCodeTagCssApplierFactory())
                .setTagWorkerFactory(new QRCodeTagWorkerFactory());
        HtmlConverter.convertToPdf(new File(src), new File(dest), properties);
    }

    /**
     * A factory for creating QRCodeTagCssApplier objects.
     */
    class QRCodeTagCssApplierFactory extends DefaultCssApplierFactory {

        /**
         * Gets the custom css applier.
         *
         * @param tag the tag
         * @return the custom css applier
         */
        /*
         * (non-Javadoc)
         * 
         * @see com.itextpdf.html2pdf.css.apply.impl.DefaultCssApplierFactory#
         * getCustomCssApplier(com.itextpdf.html2pdf.html.node.IElementNode)
         */
        @Override
        public ICssApplier getCustomCssApplier(IElementNode tag) {
            if (tag.name().equals("qr")) {
                return new BlockCssApplier();
            }
            return null;
        }
    }

    /**
     * A factory for creating QRCodeTagWorker objects.
     */
    class QRCodeTagWorkerFactory extends DefaultTagWorkerFactory {
        
        /**
         * Gets the custom tag worker.
         *
         * @param tag the tag
         * @param context the context
         * @return the custom tag worker
         */
        @Override
        public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
            if (tag.name().equals("qr")) {
                return new QRCodeTagWorker(tag, context);
            }
            return null;
        }
    }

    /**
     * The custom ITagWorker implementation for the qr-tag.
     */
    static class QRCodeTagWorker implements ITagWorker {
        
        /** The p. */
        private Paragraph p;
        
        /** The annotation. */
        private PdfLinkAnnotation linkAnnotation;

        /**
         * Instantiates a new QR code tag worker.
         *
         * @param element the element
         * @param context the context
         */
        public QRCodeTagWorker(IElementNode element, ProcessorContext context) {
            
        }

        /**
         * Process content.
         *
         * @param content the content
         * @param context the context
         * @return true, if successful
         */
        @Override
        public boolean processContent(String content, ProcessorContext context) {
            return true;
        }

        /**
         * Process tag child.
         *
         * @param childTagWorker the child tag worker
         * @param context the context
         * @return true, if successful
         */
        @Override
        public boolean processTagChild(ITagWorker childTagWorker, ProcessorContext context) {
            return false;
        }

        /**
         * Process end.
         *
         * @param element the element
         * @param context the context
         */
        @Override
        public void processEnd(IElementNode element, ProcessorContext context) { 
            
            //Link Annotation
            linkAnnotation = new PdfLinkAnnotation(new Rectangle(0, 0)).setAction(PdfAction.createURI(
                    "https://kb.itextpdf.com/"));
            Link link = new Link("here", linkAnnotation);
            p = new Paragraph("The example of link annotation. Click ").add(link.setUnderline())
                    .add(" to learn more...");
            
            
            //Line Annotation

            float[] floatArray = new float[] { 169, 790, 105, 790, 169, 800, 105, 800 };

            PdfAnnotation lineAnnotation = PdfTextMarkupAnnotation.createHighLight(new Rectangle(0, 0), floatArray);
            lineAnnotation.setTitle(new PdfString("You are here:"));
            lineAnnotation.setContents("Cambridge Innovation Center");
            lineAnnotation.setColor(ColorConstants.YELLOW); 
            
            //Text Markup Annotation
            PdfAnnotation ann = PdfTextMarkupAnnotation.createHighLight(
                    new Rectangle(105, 790, 64, 10),
                    new float[]{169, 790, 105, 790, 169, 800, 105, 800})
                .setColor(Color.YELLOW)
                .setTitle(new PdfString("Hello!"))
                .setContents(new PdfString("I'm a popup."))
                .setTitle(new PdfString("iText"))
                .setOpen(true)
                .setRectangle(new PdfArray(new float[]{100, 600, 200, 100}));           

        }

        /**
         * Gets the element result.
         *
         * @return the element result
         */
        @Override
        public IPropertyContainer getElementResult() {
            return p;
        }
    }

}

HTML 文件:

 <html>    
    <head>
        <meta charset="UTF-8">
        <title>QRCode Example</title>
        <link rel="stylesheet" type="text/css" href="css/qrcode.css"/>
    </head>
    <body>
    <span> The example of <qr> text markup </qr> annotation. </span>
    </body>
    </html>

CSS 文件:

qr {
    border: solid 1px red;
    height: 200px;
    width: 200px;
}

最终输出图

最简单实用的方法是让您的 <qr> 标签表现为内联块。这将确保我们不会将标签中的文本换行到下一行(在这种情况下注释很难定义),而且我们将有一个自然的分组元素,我们将能够从中获取坐标。

我稍微修改了输入 HTML,去掉 <qr> 标签以支持 <annot> 并添加上面提到的 display: inline-block 行为:

<html>
<head>
  <style>
    annot {
      display: inline-block;
    }
  </style>
</head>
<body>
<span> The example of <annot> text markup </annot> annotation. </span>
</body>
</html>

我们以与原始 post 类似的方式继续定义自定义标签工人工厂和自定义标签工人:

private static class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
    @Override
    public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
        if ("annot".equals(tag.name())) {
            return new AnnotTagWorker(tag, context);
        }
        return super.getCustomTagWorker(tag, context);
    }
}

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

    @Override
    public IPropertyContainer getElementResult() {
        IPropertyContainer baseResult = super.getElementResult();
        if (baseResult instanceof Paragraph) {
            ((Paragraph) baseResult).setNextRenderer(new AnnotTagRenderer((Paragraph) baseResult));
        }
        return baseResult;
    }
}

现在内容丰富的部分将在AnnotTagRenderer中实现。请注意我们如何将渲染器设置为 AnnotTagWorker 中的结果元素。 reredrer 实现知道与我们在 PDF 页面上的 <annot> 标签相对应的文本块的物理坐标。我们现在可以在 draw() 方法中从 this.getOccupiedArea() 中获取坐标,这对我们来说是主要技巧 - 我们需要做的剩下的就是创建正确的注释对象并将其添加到页面中。这是实现:

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

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

    @Override
    public void draw(DrawContext drawContext) {
        super.draw(drawContext);

        Rectangle occupiedArea = this.getOccupiedAreaBBox();
        float[] quadPoints = new float[] {occupiedArea.getLeft(), occupiedArea.getTop(), occupiedArea.getRight(), occupiedArea.getTop(),
                occupiedArea.getLeft(), occupiedArea.getBottom(), occupiedArea.getRight(), occupiedArea.getBottom()};
        PdfAnnotation ann = PdfTextMarkupAnnotation.createHighLight(
                        new Rectangle(occupiedArea), quadPoints)
                .setColor(ColorConstants.YELLOW)
                .setTitle(new PdfString("Hello!"))
                .setContents(new PdfString("I'm a popup."))
                .setTitle(new PdfString("iText"));

        drawContext.getDocument().getPage(this.getOccupiedArea().getPageNumber()).addAnnotation(ann);
    }
}

视觉效果如下:

最后,不要忘记将自定义标签工作者工厂插入到转换器属性中:

ConverterProperties properties = new ConverterProperties().setTagWorkerFactory(new CustomTagWorkerFactory());
HtmlConverter.convertToPdf(new File(sourceHTML), new File(targetPDF), properties);