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

Is it possible to redact PDF areas with PDFBox by position?

背景

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

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

问题

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

我通过展平 pdf 解决了这个问题(将其转换为图像,使其成为单层文档,黑色矩形不再被欺骗)。与此处描述的解决方案相同: Disable pdf-text searching 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();
}

这就是我将其转换为图像 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;
}

有什么想法吗?

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

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

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

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

您还必须清理围绕实际文本绘制指令的指令。再次考虑要使用“是”/“否”信息编辑的列的示例,但这次为了更加清晰,“是”以绿色绘制,“否”以红色绘制。如果您只替换文本绘图说明,使用提取器的提取器也提取颜色等属性的人将立即知道编辑后的信息。

对于带标签的 PDF,还必须检查标签属性。特别是有一个属性 ActualText,它包含由标记指令表示的实际文本(特别是对于屏幕 readers)。如果您只删除文本绘图说明而保留标签及其属性,则使用屏幕 reader 阅读的任何人甚至可能都没有意识到您试图在他的屏幕 reader 阅读完整的原始文本时进行编辑给他。

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

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

...

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