关于java:如何将带有链接注释的覆盖文本添加到现有pdf?

How to add overlay text with link annotations to existing pdf?

我想在我的叠加文本中添加一个链接。我已经读过,使用 Anchor 仅适用于从头开始制作的文档,但不适用于现有的 pdf。我的代码正在为每个页面添加覆盖文本。我的目标是使该文本的一部分可点击。我不知道如何制作作为短语一部分的链接注释。

这是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
            int n = reader.getNumberOfPages();
            // step 4: we add content
            PdfImportedPage page;
            PdfCopy.PageStamp st
            for (int j = 0; j < n; )
            {
                ++j;
                page = writer.getImportedPage(reader, j);
                if (i == 1) {
                    stamp = writer.createPageStamp(page);
                    Rectangle mediabox = reader.getPageSize(j);
                    Rectangle crop = new Rectangle(mediabox);
                    writer.setCropBoxSize(crop);
                    // add overlay text
                    Paragraph p = new Paragraph();
                    p.setAlignment(Element.ALIGN_CENTER);
                    FONT_URL_OVERLAY.setColor(0, 191, 255);
                    // get current user
                    EPerson loggedin = context.getCurrentUser();
                    String eperson = null;
                    if (loggedin != null)
                    {
                        eperson = loggedin.getFullName();
                    }
                    else eperson ="Anonymous";
                    Phrase downloaded = new Phrase();
                    Chunk site = new Chunk("My Website",FONT_URL_OVERLAY);
                    site.setAction(new PdfAction("http://www.mywebsite.com"));
                    downloaded.add(new Chunk("Downloaded by [" + eperson +"] from", FONT_OVERLAY));
                    downloaded.add(site);
                    downloaded.add(new Chunk(" on", FONT_OVERLAY));
                    downloaded.add(new Chunk(new SimpleDateFormat("MMMM d, yyyy").format(new Date()), FONT_OVERLAY));
                    downloaded.add(new Chunk(" at", FONT_OVERLAY));
                    downloaded.add(new Chunk(new SimpleDateFormat("h:mm a z").format(new Date()), FONT_OVERLAY));
                    p.add(downloaded);
                    ColumnText.showTextAligned(stamp.getOverContent(), Element.ALIGN_CENTER, p,
                            crop.getLeft(10), crop.getHeight() / 2 + crop.getBottom(), 90);
                    stamp.alterContents();
                }
                writer.addPage(page);
            }

所以我的叠加层看起来像这样:

Downloaded by [Anonymous] from My Website on February 17, 2015 at 1:20 AM CST

如何将"我的网站"转换为链接注释?在 SO 中搜索,我找到了这篇文章,但我不知道如何将添加链接注释应用到我的覆盖文本的一部分。

提前致谢。

编辑:如何将带有链接注释的旋转覆盖文本添加到现有 pdf?
感谢 Bruno Lowagie 竭尽全力回答我的问题。虽然我最初询问如何将覆盖文本中的链接注释添加到现有 pdf,但他还在他的答案的评论部分中解决了我的问题,即如果覆盖文本被旋转,则正确设置坐标。


您正在使用 ColumnText.showAligned() 足以添加一行没有任何特殊功能的文本,但是如果您希望锚点起作用,则需要以不同的方式使用 ColumnText

这在 AddLinkAnnotation2 示例中显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    PdfContentByte canvas = stamper.getOverContent(1);
    Font bold = new Font(FontFamily.HELVETICA, 12, Font.BOLD);
    Chunk chunk = new Chunk("The Best iText Questions on StackOverflow", bold);
    chunk.setAnchor("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
    Phrase p = new Phrase("Download");
    p.add(chunk);
    p.add(" and discover more than 200 questions and answers.");
    ColumnText ct = new ColumnText(canvas);
    ct.setSimpleColumn(36, 700, 559, 750);
    ct.addText(p);
    ct.go();
    stamper.close();
    reader.close();
}

在这种情况下,我们为 ColumnText 对象定义一个矩形,我们将 Phrase 添加到列中,然后我们 go().

如果您检查结果,link_annotation2.pdf,您会注意到您可以单击粗体字。

没有计划在 ColumnText.showTextAligned() 中支持此功能。这是一种方便的方法,可以用作上面显示的少数几行的快捷方式,但有一些已知的限制:不换行,忽略交互性,...

更新 1:在评论部分,您提出了一个关于旋转内容和链接的附加问题。

旋转内容并不困难。有不止一种方法可以做到这一点。旋转链接并非易事,因为链接是一种注释,而注释不是内容的一部分。

我们先来看看AddLinkAnnotation3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 6);
    stamper.getWriter().setPageEvent(new AddAnnotation(stamper, transform));
    PdfContentByte canvas = stamper.getOverContent(1);
    Font bold = new Font(FontFamily.HELVETICA, 12, Font.BOLD);
    Chunk chunk = new Chunk("The Best iText Questions on StackOverflow", bold);
    chunk.setGenericTag("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
    Phrase p = new Phrase("Download");
    p.add(chunk);
    p.add(" and discover more than 200 questions and answers.");
    canvas.saveState();
    canvas.concatCTM(transform);
    ColumnText ct = new ColumnText(canvas);
    ct.setSimpleColumn(300, 0, 800, 400);
    ct.addText(p);
    ct.go();
    canvas.restoreState();
    stamper.close();
    reader.close();
}

在这个例子中,我们定义了一个 30 度的变换 (Math.PI / 6):

1
AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 6);

我们在渲染列时使用这个转换:

1
2
3
4
canvas.saveState();
canvas.concatCTM(transform);
// render column
canvas.restoreState();

这会旋转内容,但我们还没有添加任何注释。相反,我们定义了一个页面事件:

1
stamper.getWriter().setPageEvent(new AddAnnotation(stamper, transform));

我们引入了一个通用标签:

1
chunk.setGenericTag("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");

为了添加注解,我们在页面事件实现中使用了一些魔法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AddAnnotation extends PdfPageEventHelper {
    protected PdfStamper stamper;
    protected AffineTransform transform;

    public AddAnnotation(PdfStamper stamper, AffineTransform transform) {
        this.stamper = stamper;
        this.transform = transform;
    }

    @Override
    public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
        float[] pts = {rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()};
        transform.transform(pts, 0, pts, 0, 2);
        float[] dstPts = {pts[0], pts[1], pts[2], pts[3]};
        rect = new Rectangle(dstPts[0], dstPts[1], dstPts[2], dstPts[3]);
        PdfAnnotation annot = PdfAnnotation.createLink(writer, rect, PdfAnnotation.HIGHLIGHT_INVERT, new PdfAction(text));
        stamper.addAnnotation(annot, 1);
    }
}

我们创建了一个注解,但在我们这样做之前,我们对矩形进行了转换。这可以确保文本与需要可点击的文本相匹配,但是......这可能不是您所期望的:

screen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AddAnnotation extends PdfPageEventHelper {
    protected PdfStamper stamper;
    protected AffineTransform transform;

    public AddAnnotation(PdfStamper stamper, AffineTransform transform) {
        this.stamper = stamper;
        this.transform = transform;
    }

    @Override
    public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
        float[] pts = {rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()};
        transform.transform(pts, 0, pts, 0, 2);
        float[] dstPts = {pts[0], pts[1], pts[2], pts[3]};
        rect = new Rectangle(dstPts[0], dstPts[1], dstPts[2], dstPts[3]);
        PdfAnnotation annot = PdfAnnotation.createLink(writer, rect, PdfAnnotation.HIGHLIGHT_INVERT, new PdfAction(text));
        annot.setBorder(new PdfBorderArray(0, 0, 0));
        stamper.addAnnotation(annot, 1);
    }

}

如您所见,我添加了一行来移除边框(默认情况下,边框在那里,除非您重新定义 PdfBorderArray)。

其余代码也几乎相同。我们现在定义 Math.PI / 2(90 度)的angular。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    AffineTransform transform = AffineTransform.getRotateInstance(Math.PI / 2);
    stamper.getWriter().setPageEvent(new AddAnnotation(stamper, transform));
    PdfContentByte canvas = stamper.getOverContent(1);
    Font bold = new Font(FontFamily.HELVETICA, 12, Font.BOLD);
    Chunk chunk = new Chunk("The Best iText Questions on StackOverflow", bold);
    chunk.setGenericTag("http://pages.itextpdf.com/ebook-stackoverflow-questions.html");
    Phrase p = new Phrase("Download");
    p.add(chunk);
    p.add(" and discover more than 200 questions and answers.");
    canvas.saveState();
    canvas.concatCTM(transform);
    ColumnText ct = new ColumnText(canvas);
    ct.setSimpleColumn(36, -559, 806, -36);
    ct.addText(p);
    ct.go();
    canvas.restoreState();
    stamper.close();
    reader.close();
}

注意页面的左下角是轴心点,因此我们需要调整我们添加列的坐标,否则你会旋转页面可见区域之外的所有内容。

更新 2:

在另一条评论中,您询问在旋转坐标系中添加文本时需要使用的坐标。

我画了这幅画:
enter

1
ct.setSimpleColumn(36, -559, 806, -36);

这是在可见区域之外(它低于实际页面尺寸),但是当我将所有内容旋转 90 度时,它会旋转到可见区域中。

如果你看我的图,你可以看到坐标为 (0, 0), (0, -595), (842, -598) 和 (842, 0) 的页面旋转了 90 度,因此得到与坐标为 (0, 0)、(595, 0)、(595, 842) 和 (0, 842) 的页面重合。这就是我们在高中时都学过的数学类型;-)

您在 crop.getLeft(10), crop.getHeight() / 2 + crop.getBottom() 位置添加文本。如果你知道文本会旋转 90 度,你应该使用 crop.getHeight() / 2 + crop.getBottom(), -crop.getLeft().

了解原因的最好方法是画一幅画。