iText 7的 copyPagesTo对具有无效GoTo目标的链接注释抛出UnsupportedOperationException。

编程语言 2026-07-07

我在使用iText 7的 copyPagesTo 合并PDF页面。对于大多数文件它工作正常,但少量来自第三方的PDF在复制时会抛出 java.lang.UnsupportedOperationException,这会导致整个合并失败,而不仅仅是损坏的链接。

原因是一个 /Link 注释的GoTo目标不是一个有效的目标对象。在实际文件中,它是一个悬空的间接引用,如下所示:

<</A<</S/GoTo/D 7 0 R>>/Subtype/Link/Rect[...]>>

对象 7 0 R 实际上在文件中并不存在。对缺失对象的引用在规范中是合法的(解析为null),因此在其他地方PDF打开正常。qpdf --check 不报告错误,pdfinfo 仅给出一个警告:

Syntax Warning: Illegal annotation destination

但当iText复制页面时,DestinationResolverCopyFilter 会在该目标上调用 PdfDestination.makeDestination(...),并且 makeDestination 会对不是 PdfStringPdfNamePdfArray 的情况抛出异常。因此,一个损坏的超链接会导致整个复制失败。

最小可复现示例

This builds a one page PDF with a single link annotation that has an invalid /A /GoTo /D, then copies the page. I used a plain number for the destination because iText manages its own xref so you can't easily write a real dangling reference from code, but any value that isn't a String/Name/Array hits the exact same throw.

import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class Main {
    public static void main(String[] args) {
        byte[] pdf = buildPdfWithMalformedLinkDestination();

        try (PdfDocument source = new PdfDocument(new PdfReader(new ByteArrayInputStream(pdf)));
             PdfDocument target = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) {
            source.copyPagesTo(1, source.getNumberOfPages(), target); // throws
        }
    }

    private static byte[] buildPdfWithMalformedLinkDestination() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (PdfDocument doc = new PdfDocument(new PdfWriter(out))) {
            doc.addNewPage(PageSize.A4);

            PdfDictionary goTo = new PdfDictionary();
            goTo.put(PdfName.S, PdfName.GoTo);
            goTo.put(PdfName.D, new PdfNumber(1)); // not a [page /XYZ ...] array or a named dest

            PdfLinkAnnotation link = new PdfLinkAnnotation(new Rectangle(20, 20, 120, 20));
            link.put(PdfName.A, goTo);
            doc.getFirstPage().addAnnotation(link);
        }
        return out.toByteArray();
    }
}

Dependency:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext7-core</artifactId>
    <version>7.2.6</version>
    <type>pom</type>
</dependency>

实际结果

Exception in thread "main" java.lang.UnsupportedOperationException
    at com.itextpdf.kernel.pdf.navigation.PdfDestination.makeDestination(PdfDestination.java:89)
    at com.itextpdf.kernel.pdf.DestinationResolverCopyFilter.processLinkAnnotion(DestinationResolverCopyFilter.java:109)
    at com.itextpdf.kernel.pdf.DestinationResolverCopyFilter.shouldProcess(DestinationResolverCopyFilter.java:89)
    at com.itextpdf.kernel.pdf.PdfPage.copyAnnotations(PdfPage.java:1294)
    at com.itextpdf.kernel.pdf.PdfPage.copyTo(PdfPage.java:406)
    at com.itextpdf.kernel.pdf.PdfDocument.copyPagesTo(PdfDocument.java:1278)
    at Main.main(Main.java)

环境

iText itext7-core 7.2.6。我也检查过8.0.3,看起来也一样(makeDestination 仍然拒绝任何不是String/Name/Array的情况,且 copyPagesTo 仍然没有 ICopyFilter 的重载),因此这不是回归问题。Java 21,macOS,但似乎并非OS相关。

问题

是否存在一种受支持的方式,让 copyPagesTo 跳过坏的链接目标,而不是抛出异常,从而一个格式错误的注释不会中止复制一个本来有效的页面?我找不到公开的 copyPagesTo(..., ICopyFilter) 重载。ICopyFilter 似乎只能通过底层的 PdfObject.copyTo 访问。

我已经尝试过的办法

在调用 copyPagesTo 之前,我现在逐页遍历每页的 /Annots,并剥离任何 /Link 目标,其解析后的对象不是 PdfStringPdfNamePdfArray(包括 /Dest/A /D)。这有效并保留了有效链接,但如果有一种合适的方式来实现iText自己的复制过滤器,我宁愿不要重新实现它。

对于单个文档,我还只是返回原始字节,不再重新复制,这样就避免了整件事,但当我确实需要将若干PDF合并为一个时,这种方法就不起作用了。

解决方案

不,没有一种受支持的方式可以让 copyPagesTo() 忽略格式错误的GoTo目标。该方法始终使用iText的内部 DestinationResolverCopyFilter,并且没有公开的 copyPagesTo(..., ICopyFilter) 重载来定制该行为。

实际的解决办法正是你已经在做的:在复制之前对注释进行清理,通过移除或修正无效的 /Dest/A /D 条目。如果你认为 copyPagesTo() 应该在遇到无效链接时跳过而不是抛出异常,值得向iText团队报告,将其作为鲁棒性问题。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章