Что заставляет PdfStamper удалять изображения из pdf после cleanup (), хотя этого не следует делать? - PullRequest
1 голос
/ 17 мая 2019

Во-первых, я относительно новичок в Java, а также в iText.

Вскоре у меня есть программа, которая копирует каждые 2 страницы из большого исходного PDF-документа и создает новый документ для каждой пары страниц.Кроме того, программа удаляет некоторую текстовую информацию с первой страницы и защищает новые документы с помощью пароля владельца.

Вот мой код.Я использую 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 удаляет QR-код и данные из местоположений прямоугольников, передаваемых в PdfCleanUpProcessor в документах, созданных в BullZip, но для фокситовых PDF-файлов он работает должным образом, и мне действительно нужно работать с документами Bullzip.

Я пытался манипулировать версией временного pdf-файла и cleanUp () с помощью отредактированных аннотаций, но безрезультатно.

Я хочу понять, где искать и что изменить (возможно, где-нибудь в классе PdfCleanUpProcessor?), Чтобы он работал правильно. Кто-нибудь знает, почему это происходит?

UPD.Мне удалось создать несколько PDF-файлов, похожих на те, которые мне нужно обработать, и я обнаружил еще одну интересную вещь: Bullzip способен создавать как «плохие», так и «хорошие» файлы самостоятельно.Я проверил различные настройки принтера, включая разрешения, но все еще трудно сказать, от чего это зависит.Среди заметных различий есть только немного меньший размер файла и немного другие поля страницы.

В любом случае, здесь мои тестовые файлы

1 Ответ

0 голосов
/ 28 мая 2019

Вы действительно нашли ошибку в iText 5 PdfCleanUpProcessor: Она удаляет все встроенные изображения, которые не подлежат частичной редакции.

Ошибкаподробно

Ошибка находится в методе 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() > 0), но не относящиеся к изображениям, не покрытым областями редактирования (areasToBeCleaned != null и areasToBeCleaned.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)

выглядит следующим образом:

screenshot

Применение такой редакции

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 test testRedactPdfWithInlineImages)

бези с патчем соответственно приводит к

screenshot screenshot

AsВы можете видеть, что первоначально оставались только частично отредактированные встроенные изображения, но с встроенными изображениями патча, полностью находящимися за пределами области редактирования, также остаются.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...