【问题标题】:What makes PdfStamper to remove images from pdf after cleanup() though it shouldn't?是什么让 PdfStamper 在 cleanup() 之后从 pdf 中删除图像,尽管它不应该?
【发布时间】:2019-05-17 12:30:53
【问题描述】:

首先,我对 java 和 iText 都比较陌生。

简而言之,我有一个程序,它从大源 pdf 文档中每 2 页复制一次,并为每对页面创建一个新文档。此外,该程序会从首页删除一些文本信息,并使用所有者密码保护新文档。

这是我的代码。我使用 iText 5.5.13。

    //...
    final Rectangle RECT_TOP= new Rectangle(25f, 788f, 288f, 812.5f);  
    final Rectangle RECT_BOT= new Rectangle(103.5f, 36.5f, 331f, 40f); 
    //...

    PdfDocument document = new Document(reader.getPageSizeWithRotation(1));
    File tempFile = File.createTempFile("temp", ".pdf");
    PdfCopy writer = new PdfCopy(
            document, //PdfDocument
            new FileOutputStream(tempFile.getAbsolutePath()));  

   document.open(); 

    writer.addPage( writer.getImportedPage(reader, i) );
    writer.addPage( writer.getImportedPage(reader, i + 1) );                                        

    writer.freeReader(reader);
    writer.close();
    document.close(); 

    PdfReader tmpReader = new PdfReader(tempFile.getAbsolutePath());
    PdfStamper st = new PdfStamper(tmpReader, new FileOutputStream(outFile));
    List<PdfCleanUpLocation> locations = new ArrayList<PdfCleanUpLocation>();
    locations.add(new PdfCleanUpLocation(1, RECT_TOP, BaseColor.WHITE));
    locations.add(new PdfCleanUpLocation(1, RECT_BOT, BaseColor.WHITE));

    new PdfCleanUpProcessor(locations, st).cleanUp();                                                                   
    st.setEncryption(   
        "".getBytes(),
        OWNER_PASSWORD.getBytes(),  
        PdfWriter.ALLOW_COPY | PdfWriter.ALLOW_PRINTING,
        PdfWriter.ENCRYPTION_AES_256 | PdfWriter.DO_NOT_ENCRYPT_METADATA);  

    st.getWriter().freeReader(tmpReader);
    st.close();
    tmpReader.close();
    tempFile.delete();

源 PDF 在我必须清理的每个页面上都有一个 QR 码作为图像()。区域 RECT_TOP 和 RECT_BOT 不以任何方式包含图像。

我在两个包含相同数据的 pdf 上测试了我的代码。其中一个是使用 BullZip PDF 打印机 (v PDF-1.5) 创建的,另一个是使用 Foxit PDF 打印机 (v PDF-1.7) 创建的。问题是 cleanUp 方法在 BullZip 创建的文档中删除了 QR 码 和传递给 PdfCleanUpProcessor 的矩形位置的数据,但是对于 foxit PDF,它可以正常工作,我真的需要它与 Bullzip 文档一起使用。

我试图通过编辑注释来操作临时 pdf 文件和 cleanUp() 的版本,但没有用。

我想了解在哪里查看和更改哪些内容(可能在 PdfCleanUpProcessor 类的某个地方?)以使其正常工作。 有人知道为什么会这样吗?

UPD。我设法创建了一些类似于我需要处理的 PDF,并且发现了另一个有趣的事情:Bullzip 能够自己创建“坏”和“好”文件。我检查了不同的打印机设置,包括权限,但仍然很难说它取决于什么。在显着的差异中,只有文件大小稍小,页边距略有不同。

无论如何,这里是我的test files

【问题讨论】:

  • 这是特定于相关 PDF 的问题;当您确定自己为两个外观相同的 PDF 时,它只会在一种情况下发生。因此,如果您希望获得帮助,您确实应该提供一个示例。 “我在两个包含相同数据的 pdf 上测试了代码。...对于 BullZip 创建的文档,cleanUp 方法从我传递给 PdfCleanUpProcessor 的页面上的位置删除图像和数据,对于 foxit PDF,它可以正常工作" - 这听起来你知道如何生成一个示例 PDF,使用 BullZip 创建类似于你的 PDF 的东西。
  • @mkl,对,我没想到。无论如何,问题已更新为新信息。
  • 如果是坏 PDF,二维码是内嵌图像(即图像数据立即嵌入到页面内容流中),如果是好的 PDF,它是从内容流。显然,iText 在正确解析图像时存在问题,导致在编辑期间将其删除。顺便说一句,不仅 iText 有问题,Adobe Reader 也有问题,但部分恢复了:Adobe Reader 以某种方式显示图像但不允许选择它,而它确实允许在良好的 PDF 中选择它,这表明它有问题它。稍后我会尝试详细检查。
  • @mkl,嗯,这就是东西。我检查了处理页面的 iText 代码,据我所知,在 PdfContentStreamProcessor 的 processContent 方法中发生了错误的事情。有一段内嵌图像的代码,但我不确定那里到底发生了什么,以及内嵌图像的代码是否可以针对我的情况进行更正。

标签: java pdf itext


【解决方案1】:

您确实在 iText 5 PdfCleanUpProcessor 中发现了一个错误:它会丢弃所有未部分编辑的内联图像。

bug详解

bug位于PdfCleanUpRenderListener方法renderImage

public void renderImage(ImageRenderInfo renderInfo) {
    List<Rectangle> areasToBeCleaned = getImageAreasToBeCleaned(renderInfo);

    if (areasToBeCleaned == null) {
        chunks.add(new PdfCleanUpContentChunk.Image(false, null));
    } else if ( areasToBeCleaned.size() > 0) {
        try {
            PdfImageObject pdfImage = renderInfo.getImage();
            byte[] imageBytes = processImage(pdfImage.getImageAsBytes(), areasToBeCleaned);

            if (renderInfo.getRef() == null && pdfImage != null) { // true => inline image
                PdfDictionary dict = pdfImage.getDictionary();
                PdfObject imageMask = dict.get(PdfName.IMAGEMASK);
                Image image = Image.getInstance(imageBytes);

                if (imageMask == null) {
                    imageMask = dict.get(PdfName.IM);
                }

                if (imageMask != null && imageMask.equals(PdfBoolean.PDFTRUE)) {
                    image.makeMask();
                }

                PdfContentByte canvas = getContext().getCanvas();
                canvas.addImage(image, 1, 0, 0, 1, 0, 0, true);
            } else if (pdfImage != null && imageBytes != pdfImage.getImageAsBytes()) {
                chunks.add(new PdfCleanUpContentChunk.Image(true, imageBytes));
            }
        } catch (UnsupportedPdfException pdfException) {
            chunks.add(new PdfCleanUpContentChunk.Image(false, null));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

这里需要对内嵌图像进行特殊处理:对于非内嵌图像,绘制图像 XObject 的实际指令在其他地方处理,renderImage 只需要检查图像是否必须编辑并提供编辑图像版本。但是,对于内嵌图像,此方法还必须将图像添加到结果内容流中(除非它被完全删除)。

如您所见,对于部分被编校区域(areasToBeCleaned.size() &gt; 0)覆盖的图像,块中的内嵌图像有特殊处理,但对于未被编校区域(areasToBeCleaned != nullareasToBeCleaned.size() == 0)覆盖的图像则没有。

如何解决

您可以通过在新的 else 子句中添加类似的特殊处理来解决此问题:

public void renderImage(ImageRenderInfo renderInfo) {
    List<Rectangle> areasToBeCleaned = getImageAreasToBeCleaned(renderInfo);

    if (areasToBeCleaned == null) {
        chunks.add(new PdfCleanUpContentChunk.Image(false, null));
    } else if ( areasToBeCleaned.size() > 0) {
        try {
            PdfImageObject pdfImage = renderInfo.getImage();
            byte[] imageBytes = processImage(pdfImage.getImageAsBytes(), areasToBeCleaned);

            if (renderInfo.getRef() == null && pdfImage != null) { // true => inline image
                PdfDictionary dict = pdfImage.getDictionary();
                PdfObject imageMask = dict.get(PdfName.IMAGEMASK);
                Image image = Image.getInstance(imageBytes);

                if (imageMask == null) {
                    imageMask = dict.get(PdfName.IM);
                }

                if (imageMask != null && imageMask.equals(PdfBoolean.PDFTRUE)) {
                    image.makeMask();
                }

                PdfContentByte canvas = getContext().getCanvas();
                canvas.addImage(image, 1, 0, 0, 1, 0, 0, true);
            } else if (pdfImage != null && imageBytes != pdfImage.getImageAsBytes()) {
                chunks.add(new PdfCleanUpContentChunk.Image(true, imageBytes));
            }
        } catch (UnsupportedPdfException pdfException) {
            chunks.add(new PdfCleanUpContentChunk.Image(false, null));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    } else { // add inline images not subject to redaction to the content
        try {
            PdfImageObject pdfImage = renderInfo.getImage();
            if (renderInfo.getRef() == null && pdfImage != null) { // true => inline image
                PdfDictionary dict = pdfImage.getDictionary();
                PdfObject imageMask = dict.get(PdfName.IMAGEMASK);
                Image image = Image.getInstance(pdfImage.getImageAsBytes());

                if (imageMask == null) {
                    imageMask = dict.get(PdfName.IM);
                }

                if (imageMask != null && imageMask.equals(PdfBoolean.PDFTRUE)) {
                    image.makeMask();
                }

                PdfContentByte canvas = getContext().getCanvas();
                canvas.addImage(image, 1, 0, 0, 1, 0, 0, true);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

示例

为了说明问题和修复的效果,我创建了一个包含五个内嵌图像的简单 PDF:

Document document = new Document(new Rectangle(500, 500));
PdfWriter writer = PdfWriter.getInstance(document, baos);
document.open();
PdfContentByte canvas = writer.getDirectContent();
for (int i = 0; i < 5; i++) {
    canvas.addImage(image, 50, 0, 0, 50, i * 100 + 25, i * 100 + 25, true);
}
document.close();

(RedactWithImageIssue 辅助方法createPdfWithInlineImages)

看起来像这样:

这样应用编辑

PdfReader reader = new PdfReader(pdf);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(RESULT));
List<com.itextpdf.text.pdf.pdfcleanup.PdfCleanUpLocation> locations = new ArrayList<>();
locations.add(new com.itextpdf.text.pdf.pdfcleanup.PdfCleanUpLocation(1, new Rectangle(150, 150, 350, 350), BaseColor.RED));
new PdfCleanUpProcessor(locations, stamper).cleanUp();
stamper.close();

(RedactWithImageIssue 测试testRedactPdfWithInlineImages)

没有和有补丁分别导致

如您所见,最初只保留了部分编辑的内联图像,但补丁内联图像也完全位于编辑区域之外。

【讨论】:

    猜你喜欢
    • 2020-08-23
    • 1970-01-01
    • 2011-09-21
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-12
    • 1970-01-01
    相关资源
    最近更新 更多