Как изменить шрифт существующего PDTextField в pdfbox Java?

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

PDFont formFont = PDType0Font
        .load(doc,
              PDFGenerator.class.getResourceAsStream(
                      "/fonts/AbhayaLibre-Regular.ttf"),
              false);

PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
PDResources resources = acroForm.getDefaultResources();
String fontName = resources.add(formFont).getName();

Шрифт устанавливается в поле следующим образом:

PDTextField template = (PDTextField) acroForm.getField("main_text");
template.setMultiline(template.isMultiline());
template.setDefaultAppearance(template.getDefaultAppearance()
                                      .replaceAll("/\\w+", "/"
                                                           + fontName));
template.setValue("Ahoj světe");

Этот код работает, например, для шаблонов, созданных в LibreOffice. А вот для шаблонов, созданных в https://www.sejda.com/pdf-forms, вылетает с ошибкой кодировки

Исключение в потоке «основной» java.lang.IllegalArgumentException: U+011B («ecaron») недоступен в шрифте Helvetica, кодировка: WinAnsiEncoding в org.apache.pdfbox.pdmodel.font.PDType1Font.encode(PDType1Font.java:410) в org.apache.pdfbox.pdmodel.font.PDFont.encode(PDFont.java:337)

Я могу создать в программе новый PDTextField, установить для него шрифт и сделать его прямоугольным. Тогда программа работает корректно

PDFont formFont = PDType0Font
        .load(doc,
              PDFGenerator.class.getResourceAsStream(
                      "/fonts/AbhayaLibre-Regular.ttf"),
              false);


PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
PDResources resources = acroForm.getDefaultResources();
final String fontName = resources.add(formFont).getName();

PDPage page = doc.getPage(0);
PDTextField template = (PDTextField) acroForm.getField("main_text");
PDTextField implementation = new PDTextField(acroForm);

implementation.setPartialName(
        template.getPartialName() + "_generated");
implementation.setMultiline(template.isMultiline());
implementation.setDefaultAppearance(template.getDefaultAppearance()
                                            .replaceAll("/\\w+", "/"
                                                                 + fontName));
implementation.getWidgets().get(0).setRectangle(
        template.getWidgets().get(0).getRectangle());
implementation.getWidgets().get(0).setPage(page);
implementation.setValue("Ahoj světe");
page.getAnnotations().add(implementation.getWidgets().get(0));
acroForm.getFields().add(implementation);

template.setReadOnly(true);
template.setValue(null);

doc.save("1.pdf");

Я предполагаю, что такое поведение связано с тем, что DefaultAppearance где-то перекрывается со значением, установленным редактором PDF, но я не могу понять, где это искать. Верны ли мои рассуждения? Как правильно обрабатывать иностранные языки в теле формы pdfbox?

Поместите файлы на диск. https://drive.google.com/drive/folders/14Ko_55T9wyYaC6rj7uoH5AUYcTOgN7JC?usp=sharing *_new_font — файлы, созданные после

template.setDefaultAppearance(template.getDefaultAppearance()
                                      .replaceAll("/\\w+", "/"
                                                           + fontName));

исполнение.

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

System.out.println("Old field default appearance: "
                   + template.getDefaultAppearance());
System.out.println("Old widget default appearance: "
                   + template.getWidgets().get(0).getCOSObject()
                             .getString(COSName.DA));
String newAppearance = template.getDefaultAppearance()
                               .replaceAll("/\\w+", "/"
                                                    + fontName);
template.setDefaultAppearance(newAppearance);
System.out.println("Nef field default appearance: "
                   + template.getDefaultAppearance());
System.out.println("New widget default appearance: "
                   + template.getWidgets().get(0).getCOSObject()
                             .getString(COSName.DA));

этот код дает для сейги

Old field default appearance: /Helv 18 Tf 0.129 0.129 0.129 rg
Old widget default appearance: /Helv 18 Tf 0.129 0.129 0.129 rg
Nef field default appearance: /F3 18 Tf 0.129 0.129 0.129 rg
New widget default appearance: /Helv 18 Tf 0.129 0.129 0.129 rg

и бесплатно

Old field default appearance: 1 1 1 rg /He 10.006 Tf
Old widget default appearance: 1 1 1 rg /He 10.006 Tf
Nef field default appearance: 1 1 1 rg /F4 10.006 Tf
New widget default appearance: 1 1 1 rg /F4 10.006 Tf

Я могу удалить DA виджета, прежде чем менять внешний вид поля. Это решит мою проблему. Однако у меня остались вопросы:

  1. Почему внешний вид setDefault иногда меняет DA виджета, а иногда нет?
  2. Есть ли способ настроить внешний вид поля, чтобы другие объекты не могли его перезаписать?
  3. Какие объекты, кроме виджета, могут перезаписать внешний вид поля?

Я использую pdfbox 3.0.2

Можете ли вы поделиться обоими файлами после изменения шрифта поля, но до вызова setValue() (или вызова с чем-то безобидным)? Ошибка предполагает, что поле не изменилось. Возможно, проследите внешний вид по умолчанию до и после replaceAll().

Tilman Hausherr 02.04.2024 05:19
ссылка - файлы с примерами
Anastasiia Ivanova 02.04.2024 09:06

Я понял, что внешний вид поля по умолчанию меняется после setDefaultAppearance, тогда как внешний вид виджета по умолчанию не может быть изменен.

Anastasiia Ivanova 02.04.2024 09:09

Запись /DA в дочернем виджете в форме sejda не является стандартной. В спецификации ничего подобного не упоминается. Запись /DA предназначена только для уровня поля. Однако, как показывает ваш опыт и код, запись /DA учитывается нашим кодом, потому что люди ожидают этого. Проще всего было бы удалить его на уровне виджета перед заменой.

Tilman Hausherr 02.04.2024 09:39

Мне интересно, стоит ли нам что-то изменить в коде PDFBox, учитывая эту «незаконную» запись DA при вызове setDefaultAppearance(), или хотя бы вывести предупреждение. Несмотря на то, что вы здесь новичок, вы действительно провели исследование, и ваш код работал бы, если бы не эта странная запись.

Tilman Hausherr 02.04.2024 10:00

Это было бы очень полезно. Благодарю за ваш ответ!

Anastasiia Ivanova 02.04.2024 11:09

На самом деле DA на уровне виджетов недавно обсуждался в вопросе ассоциации PDF. Acrobat, очевидно, размещает DA повсюду.

mkl 02.04.2024 12:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
7
170
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Как прокомментировал Тилман Хаушерр, проблема возникает, когда виджет поля имеет собственный DA. Правильный способ решить эту проблему — удалить DA из виджета перед сменой шрифта.

String newAppearance = template.getDefaultAppearance()
                               .replaceAll("/\\w+", "/"
                                                    + fontName);
template.getWidgets().get(0).getCOSObject().removeItem(COSName.DA);
template.setDefaultAppearance(newAppearance);

Обратите внимание, что для поля может быть более одного виджета. Это случается не часто, но может случиться. Типичным вариантом использования было бы наличие имени и идентификатора на каждой странице.

Tilman Hausherr 02.04.2024 11:07

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