Невозможно сделать водяной знак itext5 pdf неудаляемым в электронной почте VMware Workspace ONE Boxer

Я использую itext5 для создания PDF-файлов с нарисованными неудаляемыми водяными знаками следующим образом:

public class TestWatermark {

    public static String resourcesPath = "C:\\Users\\java\\Desktop\\TestWaterMark\\";
    public static String FILE_NAME = resourcesPath + "test.pdf";

    public static void main(String[] args) throws IOException {
        System.out.println("########## STARTED ADDING WATERMARK ###########");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            byte[] byteArray = Files.readAllBytes(Paths.get(FILE_NAME));
            String watermarkText = "confidential";
            String fontPath = resourcesPath + "myCustomFont.ttf";
            Font arabicFont = FontFactory.getFont(fontPath, BaseFont.IDENTITY_H, 16);

            BaseFont baseFont = arabicFont.getBaseFont();
            PdfReader reader = new PdfReader(byteArray);
            PdfStamper stamper = new PdfStamper(reader, baos);

            int numberOfPages = reader.getNumberOfPages();

            float height = baseFont.getAscentPoint(watermarkText, 24) + baseFont.getDescentPoint(watermarkText, 24);

            for (int i = 1; i <= numberOfPages; i++) {

                Rectangle pageSize = reader.getPageSizeWithRotation(i);
                PdfContentByte overContent = stamper.getOverContent(i);

                PdfPatternPainter bodyPainter = stamper.getOverContent(i).createPattern(pageSize.getWidth(),
                        pageSize.getHeight());
                BaseColor baseColor = new BaseColor(10, 10, 10);
                bodyPainter.setColorStroke(baseColor);
                bodyPainter.setColorFill(baseColor);
                bodyPainter.setLineWidth(0.85f);
                bodyPainter.setLineDash(0.2f, 0.2f, 0.2f);

                PdfGState state = new PdfGState();
                state.setFillOpacity(0.3f);
                overContent.saveState();
                overContent.setGState(state);

                for (float x = 70f; x < pageSize.getWidth(); x += height + 100) {
                    for (float y = 90; y < pageSize.getHeight(); y += height + 100) {

                        bodyPainter.beginText();
                        bodyPainter.setTextRenderingMode(PdfPatternPainter.TEXT_RENDER_MODE_FILL);
                        bodyPainter.setFontAndSize(baseFont, 13);
                        bodyPainter.showTextAlignedKerned(Element.ALIGN_MIDDLE, watermarkText, x, y, 45f);
                        bodyPainter.endText();

                        overContent.setColorFill(new PatternColor(bodyPainter));
                        overContent.rectangle(pageSize.getLeft(), pageSize.getBottom(), pageSize.getWidth(),
                                pageSize.getHeight());
                        overContent.fill();

                    }
                }

                overContent.restoreState();

            }

            stamper.close();
            reader.close();
            byteArray = baos.toByteArray();
            File outputFile = new File(resourcesPath + "output.pdf");
            if (outputFile.exists()) {
                outputFile.delete();
            }
            Files.write(outputFile.toPath(), byteArray);

            System.out.println("########## FINISHED ADDING WATERMARK ###########");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

приведенный выше код делает водяной знак недоступным для выбора и удаления в функции редактирования Adobe Pro. но проблема в том, что при открытии этого PDF-файла из электронного письма VMware Workspace ONE Boxer водяной знак не отображается!

Есть какие-нибудь советы о том, как решить эту проблему?

ОБНОВЛЕНИЕ: следующий код отлично работает в Boxer PDF Viewer, и водяной знак отображается нормально, но проблема в том, что этот водяной знак можно выбрать и удалить с помощью Adobe Pro:

public class TestWatermark2 {

    public static String resourcesPath = "C:\\Users\\java\\Desktop\\TestWaterMark\\";
    public static String FILE_NAME = resourcesPath + "test.pdf";

    public static void main(String[] args) throws IOException {
        System.out.println("########## STARTED ADDING WATERMARK ###########");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            byte[] byteArray = Files.readAllBytes(Paths.get(FILE_NAME));
            String watermarkText = "confidential";
            String fontPath = resourcesPath + "myCustomFont.ttf";
            Font arabicFont = FontFactory.getFont(fontPath, BaseFont.IDENTITY_H, 16);

            BaseFont baseFont = arabicFont.getBaseFont();
            PdfReader reader = new PdfReader(byteArray);
            PdfStamper stamper = new PdfStamper(reader, baos);
            Phrase watermarkPhrase = new Phrase(watermarkText, arabicFont);

            int numberOfPages = reader.getNumberOfPages();

            float height = baseFont.getAscentPoint(watermarkText, 24) + baseFont.getDescentPoint(watermarkText, 24);

            for (int i = 1; i <= numberOfPages; i++) {

                Rectangle pageSize = reader.getPageSizeWithRotation(i);
                PdfContentByte overContent = stamper.getOverContent(i);

                PdfGState state = new PdfGState();
                state.setFillOpacity(0.3f);
                overContent.saveState();
                overContent.setGState(state);

                for (float x = 70f; x < pageSize.getWidth(); x += height + 100) {
                    for (float y = 90; y < pageSize.getHeight(); y += height + 100) {
                        ColumnText.showTextAligned(overContent, Element.ALIGN_CENTER, watermarkPhrase, x, y, 45f,
                                PdfWriter.RUN_DIRECTION_RTL, ColumnText.DIGITS_AN2EN);
                    }
                }

                overContent.restoreState();

            }

            stamper.close();
            reader.close();
            byteArray = baos.toByteArray();
            File outputFile = new File(resourcesPath + "output.pdf");
            if (outputFile.exists()) {
                outputFile.delete();
            }
            Files.write(outputFile.toPath(), byteArray);

            System.out.println("########## FINISHED ADDING WATERMARK ###########");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ОБНОВЛЕНИЕ 2: Я попробовал решение mkl, и оно работает очень хорошо, но у него есть одна небольшая проблема: если текст водяного знака арабский, он отображается неверным LTR, как на прикрепленном изображении:

Его действительно удалят? Или эта программа просто не показывает водяной знак?

mkl 10.03.2024 10:32

@mkl Как точно узнать ответ? Согласно моему тесту, при пересылке одного и того же электронного письма в формате PDF из Boxer на мой Gmail PDF-файл пересылается с водяным знаком, а при загрузке файла на мобильный телефон он загружается с водяным знаком.

Mahmoud Saleh 10.03.2024 11:18

@mkl, И как предотвратить скрытие или удаление нарисованного водяного знака в приведенном выше коде?

Mahmoud Saleh 10.03.2024 11:22

@Mahmoud «Как узнать ответ наверняка? Судя по моему тесту, при пересылке одного и того же электронного письма в формате PDF из Boxer на мой Gmail PDF-файл пересылается с водяным знаком, а при загрузке файла на мобильный телефон он загружается с водяным знаком» - Для меня это звучит так, будто Boxer не удаляет его, а просто не отображает, возможно, потому, что он не поддерживает прозрачность (по крайней мере, не с непрозрачностью 0,01) или шаблоны. Существует множество процессоров PDF, которые не поддерживают всю спецификацию PDF.

mkl 10.03.2024 22:27

@Mahmoud «А как предотвратить скрытие или удаление нарисованного водяного знака в приведенном выше коде?» - Вы не можете. Конечно, вы можете создавать водяные знаки, которые становится все труднее удалить, особенно с помощью обычного программного обеспечения. Но до тех пор, пока водяной знак не полностью скроет содержимое и не удалит инструкции по рисованию этого содержимого, его невозможно отменить.

mkl 10.03.2024 22:39

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

mkl 10.03.2024 23:14

@mkl, проблема не связана ни с непрозрачностью, ни со шрифтом, потому что я безуспешно увеличивал непрозрачность, а также безуспешно менял шрифт, проблема именно с PdfPatternPainter, потому что при использовании ColumnText.showTextAligned он появляется в Boxer PDF Viewer с нет проблем, смотрите мой обновленный код

Mahmoud Saleh 11.03.2024 08:05

Хорошо, очевидно, что программа просмотра (должным образом) не поддерживает шаблоны. Вы можете сообщить в службу поддержки Boxer об этом вопросе.

mkl 11.03.2024 09:29

Кстати, вы можете переместить overContent.setColorFill ... overContent.fill(); после двойного цикла. В конце концов, этот цикл создает шаблон, поэтому, возможно, лучше использовать шаблон только после цикла. Однако я предполагаю, что это не имеет отношения к проблеме боксеров.

mkl 11.03.2024 09:38

И еще замечание сбоку: "приведенный выше код делает водяной знак невыбираемым и неудаляемым в функции редактирования Adobe Pro" - я не знаю, с какой версией Acrobat вы тестировали, но с версией 2023.008.20555 у меня есть здесь Acrobat может удалить водяной знак. Я просто использую инструмент «Редактировать PDF», выбираю объект покрытия страницы и удаляю его. Хорошо, мне придется сделать это примерно 35 раз, потому что вы столько раз заполняете избыточное содержимое шаблоном (см. Мой предыдущий комментарий), но после этого отметка исчезает.

mkl 11.03.2024 17:21

@mkl, вы правы, я попробовал, и его можно удалить, так что не существует 100% решения, как сделать водяной знак неудаляемым из файлов PDF?

Mahmoud Saleh 12.03.2024 09:23

Могут существовать и другие методы, которые в настоящее время недоступны для редактирования в Acrobat. Но Acrobat со временем также совершенствуется, поэтому то, что в настоящее время недоступно для редактирования, может стать редактируемым в следующем месяце.

mkl 12.03.2024 09:34

В настоящее время вы также можете поместить фактическое содержимое в шаблон вместе с водяным знаком: в настоящее время Adobe позволяет удалять только весь заполненный прямоугольник, поэтому, если фактическое содержимое также было в шаблоне, кто-то, удалив прямоугольник, также удалит содержимое. . Недостаток: текст в шаблонах нельзя скопировать и вставить. Более того, текст в шаблонах недоступен, программы чтения с экрана и т. д. не будут его читать.

mkl 12.03.2024 09:42

@mkl, не могли бы вы предоставить пример кода предлагаемого решения?

Mahmoud Saleh 12.03.2024 23:06

«Не могли бы вы предоставить пример кода для этого предлагаемого решения?» — см. мой ответ.

mkl 21.03.2024 11:42
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
15
184
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете использовать PDF-файл в качестве водяного знака вместо того, чтобы создавать его внутри своего кода.

Кроме того, рассмотрите возможность создания отдельного файла вместо преобразования исходного файла и повторного использования того же файла, а затем создайте новый с обновленными листами с водяным знаком.

Вот как я его использовал и, конечно, никогда не сталкивался с какими-либо проблемами. Вы можете прочитать образец, который я создал на моем Github: https://github.com/web20opensource/stamper/blob/master/README.md

Я бы сказал, что отличие от вашего кода в основном заключается в том, что водяной знак PDF создается отдельно в PDF-файле, а затем используется для штамповки PDF-файла. Вы хотите иметь водяной знак PDF-файла.

На ресурсе вы можете найти учебник Ларса Фогеля, который я использовал в то время (несколько лет назад), очень прозрачный, понятный и отличный ресурс.

И я использовал ту же версию библиотеки itext (itextpdf-5.4.2.jar), вероятно, сегодня произошли серьезные обновления, пожалуйста, проверьте сайт на наличие последней версии.

Я столкнулся с этой проблемой не такой же, но похожей, мне помогло использование холста.

PdfContentByte canvas = writer.getDirectContext();

//добавляем контент

canvas.addTemplate(writer.getImportedPage(reader, pageNum), 0,0);

//добавляем водяной знак

canvas.beginText();canvas.setFontandSize(BaseFont.createFont(),40);canvas.showTextAligned(Element.ALIGN_CENTER, "CONFIDENTIAL", 300, 400, 45);canvas.endText();

}

Ответ принят как подходящий

Узнав в комментариях, что исходный водяной знак действительно можно выбрать и удалить в текущей версии Adobe Acrobat, я упомянул там следующий вариант:

В настоящее время вы также можете поместить фактическое содержимое в шаблон вместе с водяным знаком: в настоящее время Adobe позволяет удалять только весь заполненный прямоугольник, поэтому, если фактическое содержимое также было в шаблоне, кто-то, удалив прямоугольник, также удалит содержимое. . Недостаток: текст в шаблонах нельзя скопировать и вставить. Более того, текст в шаблонах недоступен, программы чтения с экрана и т. д. не будут его читать.

В ответ вы запросили пример кода для предлагаемого решения. В этом ответе основное внимание уделяется быстрому и грязному примеру кода.

Ниже приводится краткое подтверждение концепции предлагаемого решения. В частности, возможно, потребуется настроить его для страниц, левый нижний угол которых (поле обрезки) не является началом координат (0, 0) или на которых применен поворот страницы.

Более того, в ходе тестирования кода выяснилось, что Acrobat внезапно позволяет редактировать содержимое шаблона, если на странице нет другого контента, кроме шаблона.

Таким образом, код также добавляет короткий псевдоконтент — строку пробелов. Интересно, что этого достаточно, чтобы снова сделать шаблон доступным для выбора только целиком, и в то же время, поскольку это всего лишь строка пробелов, Acrobat также не позволяет выбирать этот псевдоконтент...

Ниже приведены снимки экрана в инструменте редактирования Acrobat и файлы примеров без псевдоконтента и с ним соответственно:

Файл результатов без псевдоконтента Файл результатов с псевдоконтентом пример PDFпример PDF

Будьте осторожны: есть вероятность, что Acrobat со временем также позволит редактировать водяные знаки такого типа. Может быть, это и сейчас возможно, просто не так очевидно, как раньше.

ByteArrayOutputStream baos = new ByteArrayOutputStream();

PdfReader reader = new PdfReader(byteArray);
PdfStamper stamper = new PdfStamper(reader, baos);

BaseFont baseFont = BaseFont.createFont(BaseFont.HELVETICA_OBLIQUE, BaseFont.WINANSI, false);
String watermarkText = "confidential";

int numberOfPages = reader.getNumberOfPages();
for (int i = 1; i <= numberOfPages; i++) {
    Rectangle pageSize = reader.getPageSizeWithRotation(i);

    // get handle for existing page content
    PdfImportedPage pageContent = stamper.getImportedPage(reader, i);
    // store that content as form XObject
    stamper.getWriter().addToBody(pageContent.getFormXObject(stamper.getWriter().getCompressionLevel()), pageContent.getIndirectReference());
    pageContent.setCopied();
    // reset page content
    reader.getPageN(i).put(PdfName.CONTENTS, null);

    // create pattern with former page content
    PdfPatternPainter bodyPainter = stamper.getOverContent(i).createPattern(pageSize.getWidth(),
            pageSize.getHeight());
    bodyPainter.addTemplate(pageContent, 0, 0);

    // add watermark to pattern
    PdfGState state = new PdfGState();
    state.setFillOpacity(0.3f);
    bodyPainter.saveState();
    bodyPainter.setGState(state);
    for (float x = 70f; x < pageSize.getWidth(); x += 100) {
        for (float y = 90; y < pageSize.getHeight(); y += 100) {
            bodyPainter.beginText();
            bodyPainter.setTextRenderingMode(PdfPatternPainter.TEXT_RENDER_MODE_FILL);
            bodyPainter.setFontAndSize(baseFont, 13);
            bodyPainter.showTextAlignedKerned(Element.ALIGN_MIDDLE, watermarkText, x, y, 45f);
            bodyPainter.endText();
        }
    }
    bodyPainter.restoreState();

    // create new page content
    PdfContentByte canvas = stamper.getUnderContent(i);
    // add pseudo-content
    canvas.beginText();
    canvas.setFontAndSize(baseFont, 13);
    canvas.showTextAlignedKerned(Element.ALIGN_MIDDLE, "        ", 0, 0, 45f);
    canvas.endText();
    // fill with pattern holding former page content
    canvas.setColorFill(new PatternColor(bodyPainter));
    canvas.rectangle(pageSize.getLeft(), pageSize.getBottom(), pageSize.getWidth(),
            pageSize.getHeight());
    canvas.fill();
}

stamper.close();
reader.close();
byteArray = baos.toByteArray();

(Добавить водяной знак тест testWatermarkAllInPattern)

@KJ «всегда готов принять вызов, поэтому я хотел бы увидеть ваши результаты» - я добавил примеры снимков экрана и ссылки на файлы в свой ответ. Но не поймите меня неправильно: я не думаю, что этот подход обязательно обманет многие инструменты, но в настоящее время, похоже, он работает против Acrobat.

mkl 21.03.2024 10:36

@KJ «Первая попытка увидеть» — вы, конечно, всегда можете визуализировать как изображение и выполнять стандартные трюки. Качество явно с потерями. Что может или не может беспокоить человека, удаляющего метку. «Чтобы победить пороговую систему» ​​— Махмуд также может рассмотреть этот совет. Для этого легко изменить код в моем ответе.

mkl 21.03.2024 16:40

Конечно, вы не сможете победить целеустремленного пользователя, в частности того, кто либо разбирается во внутреннем устройстве PDF, либо у которого нет проблем с потерями качества, связанными с рендерингом в виде изображения. К счастью, ОП просто ищет решение, которое предотвратит простые попытки редактирования с использованием текущего инструмента Acrobat Edit.

mkl 21.03.2024 17:13

@mkl, я попробовал ваше решение, и оно работает нормально, но не могли бы вы помочь в случае с текстом водяного знака на арабском языке, поскольку текст добавляется слева направо и должен быть справа налево, посмотрите мой обновленный вопрос, я прикрепил изображение

Mahmoud Saleh 24.03.2024 19:29

@mkl, к вашему сведению, предоставленный вами пример кода невозможно удалить, но, к сожалению, все содержимое PDF скрыто в Boxer PDF Viewer

Mahmoud Saleh 24.03.2024 19:35

@mkl, не могли бы вы помочь с проблемой RTL?

Mahmoud Saleh 24.03.2024 19:46

«К вашему сведению, предоставленный вами пример кода не подлежит удалению, но, к сожалению, все содержимое PDF скрыто в Boxer PDF Viewer» — да, учитывая ваши предыдущие наблюдения, этого было слишком ожидаемо. Похоже, Boxer не поддерживает шаблоны. Это, очевидно, упрощает показ людям неправильного контента. Как следствие, я бы не стал использовать боксер в этой версии.

mkl 24.03.2024 20:47

«в случае с текстом водяного знака на арабском языке, поскольку текст добавляется слева направо и должен быть справа налево» - iText 5 имеет только ограниченную поддержку RTL. В частности, поддержка поддерживается только в нескольких контекстах, в частности в ячейках таблицы. Боюсь, однако, что я не знаю подробностей, поскольку мне никогда не требовалась поддержка RTL. Однако здесь должно быть множество вопросов и ответов по этой теме.

mkl 25.03.2024 00:15

Другие вопросы по теме