是否可以使用 PDFBox 按位置编辑 PDF 区域?

Tha*_*ias 6 java pdf pdfbox

上下文

目前,我有一个解决方案,可以循环遍历 PDF 并在其中绘制黑色矩形。

所以我已经有一个 PDRectangle 列表,代表我需要在 pdf 上填充/覆盖的正确区域,隐藏我想要的所有文本。

问题

问题 1:黑色矩形下方的文本很容易被其他工具复制、搜索或提取。

我通过展平我的pdf解决了这个问题(将其转换为图像,使其成为单层文档,并且黑色矩形不再被欺骗)。与此处描述的解决方案相同: Disable pdf-text search with pdfBox

这并不是真正的编辑,而更像是一种解决方法。这让我

问题 2:

我的最终 PDF 变成了图像文档,我失去了所有 pdf 属性,包括搜索、复制……而且这是一个慢得多的过程。我想保留所有 pdf 属性,而编辑区域无论如何都无法读取。

我想要完成什么

话虽这么说,我想知道是否有可能以及如何进行实际的编辑,将矩形区域涂黑,因为我已经拥有了我需要的所有位置,使用 PDFBox,保留 pdf 属性并且不允许编辑区域待读。

注意:我知道 PDFBox 使用旧的 ReplaceText 函数存在的问题,但这里我有我需要的位置,以确保我精确地空白我需要的区域。

另外,我接受其他免费图书馆的建议。

技术规格:

PDFBox 2.0.21
Java 11.0.6+10,采用OpenJDK
MacOS Catalina 10.15.4,16gb,x86_64

我的代码

这就是我绘制黑色矩形的方法:

private void draw(PDPage page, PDRectangle hitPdRectangle) throws IOException {

    PDPageContentStream content = new PDPageContentStream(pdDocument, page,
        PDPageContentStream.AppendMode.APPEND, false, false);
    content.setNonStrokingColor(0f);
    
    content.addRect(hitPdRectangle.getLowerLeftX(), 
        hitPdRectangle.getLowerLeftY()  -0.5f, 
        hitPdRectangle.getUpperRightX() - hitPdRectangle.getLowerLeftX(), 
        hitPdRectangle.getUpperRightY() - hitPdRectangle.getLowerLeftY());
    
    content.fill();
    content.close();
}
Run Code Online (Sandbox Code Playgroud)

这就是我将其转换为图像 PDF 的方法:

private PDDocument createNewRedactedPdf() throws IOException {
    PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);

    PDDocument redactedDocument = new PDDocument();

    for (int pageIndex = 0; pageIndex < pdDocument.getNumberOfPages(); pageIndex++) {
        BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 200);

        String formatName = "jpg";
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, formatName, baos);

        byte[] bimg = baos.toByteArray();

        PDPage page = pdDocument.getPage(pageIndex);
        float pageWidth  = page.getMediaBox().getWidth();
        float pageHeight = page.getMediaBox().getHeight();

        PDPage pageDraw = new PDPage(new PDRectangle(pageWidth, pageHeight));
        redactedDocument.addPage(pageDraw);
        String imgSuffixName = pageIndex + "." + formatName;
        PDImageXObject img = PDImageXObject.createFromByteArray(redactedDocument, bimg,
            pdDocument.getDocument().getDocumentID() + imgSuffixName);

        try (PDPageContentStream contentStream
                 = new PDPageContentStream(redactedDocument, pageDraw, PDPageContentStream.AppendMode.OVERWRITE, false)) {

            contentStream.drawImage(img, 0, 0, pageWidth, pageHeight);
        }
    }

    return redactedDocument;
}
Run Code Online (Sandbox Code Playgroud)

有什么想法吗?

mkl*_*mkl 6

您想要的真正的编辑功能可以基于 PDFBox 实现,但需要在其之上进行大量编码(类似于在 iText 之上实现的 pdfSweep 插件)。

特别是,您自己发现,在要编辑的区域上绘制黑色矩形是不够的,因为从查看器中提取文本或复制粘贴通常完全忽略文本是否可见或被某些东西覆盖。

因此,在代码中,您必须找到绘制文本的实际指令以编辑和删除它们。但您不能简单地删除它们而不进行替换,否则同一行上的其他文本可能会因您的编辑而移动。

但是您不能简单地用相同数量的空格或按已删除文本的宽度向右移动来替换它们:只需考虑一下您想要编辑仅包含“是”和“否”条目的列的表的情况。如果在编辑后,文本提取器返回三个空格(其中有“是”)和两个空格(其中有“否”),则任何查看这些结果的人都知道编辑区域中有什么。

您还必须清理实际文本绘制指令周围的指令。再次考虑用“是”/“否”信息进行编辑的列的示例,但这次为了更清楚,“是”用绿色绘制,“否”用红色绘制。如果您仅替换文本绘制指令,则拥有提取器(也提取颜色等属性)的人将立即知道经过编辑的信息。

如果是带标签的 PDF,则还必须检查标签属性。特别是有一个属性ActualText,它包含由标记指令表示的实际文本(特别是对于屏幕阅读器)。如果您只删除文本绘制指令,而保留标签及其属性,则使用屏幕阅读器阅读的任何人甚至可能不会意识到您试图编辑某些内容,因为他的屏幕阅读器会向他读取完整的原始文本。

因此,为了进行正确的编辑,您本质上必须解释所有当前指令,确定它们绘制的实际内容,并创建一组新的指令来绘制相同的内容,而无需不必要的额外指令,这些指令可能会泄露有关编辑内容的信息。

在这里我们只考虑了编辑文本;在 PDF 页面上编辑矢量和位图图形需要克服类似的挑战才能进行正确的编辑。

...

因此,实际编辑所需的代码超出了堆栈溢出答案的范围。尽管如此,上述内容可能会帮助实现编辑器的人不陷入过于幼稚的编辑代码的典型陷阱。