Я использую 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 Как точно узнать ответ? Согласно моему тесту, при пересылке одного и того же электронного письма в формате PDF из Boxer на мой Gmail PDF-файл пересылается с водяным знаком, а при загрузке файла на мобильный телефон он загружается с водяным знаком.
@mkl, И как предотвратить скрытие или удаление нарисованного водяного знака в приведенном выше коде?
@Mahmoud «Как узнать ответ наверняка? Судя по моему тесту, при пересылке одного и того же электронного письма в формате PDF из Boxer на мой Gmail PDF-файл пересылается с водяным знаком, а при загрузке файла на мобильный телефон он загружается с водяным знаком» - Для меня это звучит так, будто Boxer не удаляет его, а просто не отображает, возможно, потому, что он не поддерживает прозрачность (по крайней мере, не с непрозрачностью 0,01) или шаблоны. Существует множество процессоров PDF, которые не поддерживают всю спецификацию PDF.
@Mahmoud «А как предотвратить скрытие или удаление нарисованного водяного знака в приведенном выше коде?» - Вы не можете. Конечно, вы можете создавать водяные знаки, которые становится все труднее удалить, особенно с помощью обычного программного обеспечения. Но до тех пор, пока водяной знак не полностью скроет содержимое и не удалит инструкции по рисованию этого содержимого, его невозможно отменить.
Ах, я просто понимаю, что вы используете собственный шрифт, но не встраиваете его. Возможно, ваш собственный шрифт просто не зарегистрирован в программе просмотра PDF-файлов Boxer. Как следствие, Boxer не сможет отображать ваш водяной знак.
@mkl, проблема не связана ни с непрозрачностью, ни со шрифтом, потому что я безуспешно увеличивал непрозрачность, а также безуспешно менял шрифт, проблема именно с PdfPatternPainter, потому что при использовании ColumnText.showTextAligned он появляется в Boxer PDF Viewer с нет проблем, смотрите мой обновленный код
Хорошо, очевидно, что программа просмотра (должным образом) не поддерживает шаблоны. Вы можете сообщить в службу поддержки Boxer об этом вопросе.
Кстати, вы можете переместить overContent.setColorFill ... overContent.fill(); после двойного цикла. В конце концов, этот цикл создает шаблон, поэтому, возможно, лучше использовать шаблон только после цикла. Однако я предполагаю, что это не имеет отношения к проблеме боксеров.
И еще замечание сбоку: "приведенный выше код делает водяной знак невыбираемым и неудаляемым в функции редактирования Adobe Pro" - я не знаю, с какой версией Acrobat вы тестировали, но с версией 2023.008.20555 у меня есть здесь Acrobat может удалить водяной знак. Я просто использую инструмент «Редактировать PDF», выбираю объект покрытия страницы и удаляю его. Хорошо, мне придется сделать это примерно 35 раз, потому что вы столько раз заполняете избыточное содержимое шаблоном (см. Мой предыдущий комментарий), но после этого отметка исчезает.
@mkl, вы правы, я попробовал, и его можно удалить, так что не существует 100% решения, как сделать водяной знак неудаляемым из файлов PDF?
Могут существовать и другие методы, которые в настоящее время недоступны для редактирования в Acrobat. Но Acrobat со временем также совершенствуется, поэтому то, что в настоящее время недоступно для редактирования, может стать редактируемым в следующем месяце.
В настоящее время вы также можете поместить фактическое содержимое в шаблон вместе с водяным знаком: в настоящее время Adobe позволяет удалять только весь заполненный прямоугольник, поэтому, если фактическое содержимое также было в шаблоне, кто-то, удалив прямоугольник, также удалит содержимое. . Недостаток: текст в шаблонах нельзя скопировать и вставить. Более того, текст в шаблонах недоступен, программы чтения с экрана и т. д. не будут его читать.
@mkl, не могли бы вы предоставить пример кода предлагаемого решения?
«Не могли бы вы предоставить пример кода для этого предлагаемого решения?» — см. мой ответ.




Вы можете использовать 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 и файлы примеров без псевдоконтента и с ним соответственно:
Будьте осторожны: есть вероятность, что 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.
@KJ «Первая попытка увидеть» — вы, конечно, всегда можете визуализировать как изображение и выполнять стандартные трюки. Качество явно с потерями. Что может или не может беспокоить человека, удаляющего метку. «Чтобы победить пороговую систему» — Махмуд также может рассмотреть этот совет. Для этого легко изменить код в моем ответе.
Конечно, вы не сможете победить целеустремленного пользователя, в частности того, кто либо разбирается во внутреннем устройстве PDF, либо у которого нет проблем с потерями качества, связанными с рендерингом в виде изображения. К счастью, ОП просто ищет решение, которое предотвратит простые попытки редактирования с использованием текущего инструмента Acrobat Edit.
@mkl, я попробовал ваше решение, и оно работает нормально, но не могли бы вы помочь в случае с текстом водяного знака на арабском языке, поскольку текст добавляется слева направо и должен быть справа налево, посмотрите мой обновленный вопрос, я прикрепил изображение
@mkl, к вашему сведению, предоставленный вами пример кода невозможно удалить, но, к сожалению, все содержимое PDF скрыто в Boxer PDF Viewer
@mkl, не могли бы вы помочь с проблемой RTL?
«К вашему сведению, предоставленный вами пример кода не подлежит удалению, но, к сожалению, все содержимое PDF скрыто в Boxer PDF Viewer» — да, учитывая ваши предыдущие наблюдения, этого было слишком ожидаемо. Похоже, Boxer не поддерживает шаблоны. Это, очевидно, упрощает показ людям неправильного контента. Как следствие, я бы не стал использовать боксер в этой версии.
«в случае с текстом водяного знака на арабском языке, поскольку текст добавляется слева направо и должен быть справа налево» - iText 5 имеет только ограниченную поддержку RTL. В частности, поддержка поддерживается только в нескольких контекстах, в частности в ячейках таблицы. Боюсь, однако, что я не знаю подробностей, поскольку мне никогда не требовалась поддержка RTL. Однако здесь должно быть множество вопросов и ответов по этой теме.
Его действительно удалят? Или эта программа просто не показывает водяной знак?