Я работаю с шаблоном 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 виджета, прежде чем менять внешний вид поля. Это решит мою проблему. Однако у меня остались вопросы:
Я использую pdfbox 3.0.2
Я понял, что внешний вид поля по умолчанию меняется после setDefaultAppearance, тогда как внешний вид виджета по умолчанию не может быть изменен.
Запись /DA в дочернем виджете в форме sejda не является стандартной. В спецификации ничего подобного не упоминается. Запись /DA предназначена только для уровня поля. Однако, как показывает ваш опыт и код, запись /DA учитывается нашим кодом, потому что люди ожидают этого. Проще всего было бы удалить его на уровне виджета перед заменой.
Мне интересно, стоит ли нам что-то изменить в коде PDFBox, учитывая эту «незаконную» запись DA при вызове setDefaultAppearance()
, или хотя бы вывести предупреждение. Несмотря на то, что вы здесь новичок, вы действительно провели исследование, и ваш код работал бы, если бы не эта странная запись.
Это было бы очень полезно. Благодарю за ваш ответ!
На самом деле DA на уровне виджетов недавно обсуждался в вопросе ассоциации PDF. Acrobat, очевидно, размещает DA повсюду.
Как прокомментировал Тилман Хаушерр, проблема возникает, когда виджет поля имеет собственный DA. Правильный способ решить эту проблему — удалить DA из виджета перед сменой шрифта.
String newAppearance = template.getDefaultAppearance()
.replaceAll("/\\w+", "/"
+ fontName);
template.getWidgets().get(0).getCOSObject().removeItem(COSName.DA);
template.setDefaultAppearance(newAppearance);
Обратите внимание, что для поля может быть более одного виджета. Это случается не часто, но может случиться. Типичным вариантом использования было бы наличие имени и идентификатора на каждой странице.
Можете ли вы поделиться обоими файлами после изменения шрифта поля, но до вызова
setValue()
(или вызова с чем-то безобидным)? Ошибка предполагает, что поле не изменилось. Возможно, проследите внешний вид по умолчанию до и послеreplaceAll()
.