Другое

Исправление иероглифов в полях форм PDFBox с подмножественными шрифтами

Узнайте, почему поля форм Apache PDFBox отображают иероглифы с подмножественными шрифтами TrueType и найдите проверенные решения для исправления проблем с отображением символов в PDF-формах.

Почему поля формы отображают бессмысленные символы при использовании подмножественных шрифтов TrueType в Apache PDFBox?

Я использую Apache PDFBox для создания PDF с полями формы и встраивания пользовательского шрифта TrueType (FreeSans.ttf) с включенным подмножествением. Однако при установке значения в текстовое поле вместо фактического текста отображаются бессмысленные символы.

Окружение:

  • Версии Apache PDFBox: 2.x, 3.x
  • Затронутые шрифты: Любые шрифты, загруженные из внешнего источника, кроме 14 стандартных шрифтов

Пример кода:

java
package org.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;

import java.io.InputStream;


public class Main {

    public static void main(String[] args) {
        try (PDDocument document = new PDDocument()) {

            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);

            PDAcroForm acroForm = new PDAcroForm(document);
            document.getDocumentCatalog().setAcroForm(acroForm);

            // Загрузка внешнего шрифта TrueType с включенным подмножеством
            PDType0Font font;
            try (InputStream fontStream = Main.class.getResourceAsStream("/FreeSans.ttf")) {
                if (fontStream == null) {
                    throw new RuntimeException("FreeSans.ttf не найден в ресурсах");
                }
                font = PDType0Font.load(document, fontStream, true);
            }

            // Добавление шрифта в ресурсы формы
            PDResources formResources = new PDResources();
            String fontResourceName = formResources.add(font).getName();
            acroForm.setDefaultResources(formResources);
            acroForm.setDefaultAppearance("/" + fontResourceName + " 12 Tf 0 0 0 rg");

            // Создание текстового поля со значением
            PDTextField textField = new PDTextField(acroForm);
            textField.setPartialName("testField");
            textField.setDefaultAppearance("/" + fontResourceName + " 12 Tf 0 0 0 rg");

            // Установка прямоугольника виджета и добавление на страницу
            textField.getWidgets().get(0).setRectangle(new PDRectangle(100, 700, 200, 20));
            textField.getWidgets().get(0).setPage(page);
            page.getAnnotations().add(textField.getWidgets().get(0));

            acroForm.getFields().add(textField);

            textField.setValue("Hello, this is a filled form field!");
            
            acroForm.setNeedAppearances(false);
            acroForm.refreshAppearances();
            
            document.save("output.pdf");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Вывод в консоль:

WARNING: Using fallback font LiberationSans for CID-keyed TrueType font FreeSans
WARNING: widget of field testField has no rectangle, no appearance stream created

Что может вызывать эту проблему, и как правильно отображать текст в полях формы при использовании подмножественных шрифтов TrueType с Apache PDFBox?

Содержание

Основное понимание проблемы

Основная проблема связана с тем, как PDFBox обрабатывает кодировку шрифтов и генерацию потока внешнего вида для полей форм. При использовании подмножеств TrueType шрифтов, PDFBox преобразует каждый кодовую точку Unicode в имя и ищет это имя в карте, сгенерированной на основе инвертированной кодировки шрифта. Этот процесс может нарушаться при включенном подмножестве, что приводит к неправильному отображению символов.

Сообщения об ошибках в выводе консоли выявляют конкретные проблемы:

  • “Using fallback font LiberationSans for CID-keyed TrueType font FreeSans” - PDFBox переходит к использованию другого шрифта, так как не может обработать кодировку исходного шрифта
  • “widget of field testField has no rectangle, no appearance stream created” - Генерация потока внешнего вида не удалась, что критично для отображения полей форм

Подмножества шрифтов и CID-ключевые шрифты

При встраивании шрифта PDFBox может встроить весь шрифт или создать его подмножество, включающее только глифы, используемые в документе. Однако с CID-ключевыми TrueType шрифтами этот процесс создания подмножества вызывает проблемы:

Создание подмножеств шрифтов может повредить карту кодировки, от которой зависит PDFBox для отображения текста в полях форм. Процесс создания подмножеств изменяет внутреннюю структуру шрифта, что может нарушить сопоставление Unicode с глифами, от которого зависят поля форм для правильного отображения текста.

Согласно документации PDFBox, метод addToSubset и связанные функции создания подмножеств предназначены для общего отображения текста в PDF, но они не работают хорошо с полями AcroForm, поскольку поля требуют другого подхода к обработке кодировки шрифтов и потоков внешнего вида.

Проблемы генерации потока внешнего вида

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

Проблема возникает потому, что:

  1. PDFBox создает потоки внешнего вида, отображая текст с указанным шрифтом
  2. При использовании подмножеств шрифтов, сопоставление кодировки становится неполным или неправильным
  3. Поток внешнего вида содержит неверные ссылки на глифы
  4. Когда просмотрщик PDF отображает поле, он показывает неправильные символы

Как отмечено в обсуждении на Stack Overflow, это известная проблема, которую разработчики PDFBox признали.

Решения и обходные пути

Вот несколько эффективных решений для исправления проблемы с бессмысленными символами:

1. Отключите создание подмножеств для полей форм

Наиболее надежное решение - отключить создание подмножеств при загрузке шрифтов для использования в AcroForm. Согласно документации исходного кода PDFBox, конструктор с тремя параметрами в PDType0Font.load явно указывает не использовать создание подмножеств для шрифтов, используемых в AcroForm.

java
// Загрузка внешнего TrueType шрифта с ОТКЛЮЧЕННЫМ созданием подмножеств
PDType0Font font = PDType0Font.load(document, fontStream, false);

2. Включите NeedAppearances

Установите setNeedAppearances(true), чтобы заставить PDFBox правильно генерировать потоки внешнего вида:

java
acroForm.setNeedAppearances(true);

3. Настройте сопоставление шрифтов

Правильное сопоставление шрифтов может решить многие проблемы, связанные со шрифтами. Как упоминается в FAQ PDFBox, вы можете настроить сопоставление шрифтов через FontMapperImpl.java или использовать файл PDFBox_External_Fonts.properties.

4. Используйте стандартные 14 шрифтов, когда это возможно

Для производственных форм рассмотрите возможность использования стандартных 14 PDF шрифтов (Helvetica, Times, Courier и т.д.), которые нatively поддерживаются просмотрщиками PDF и не требуют встраивания.

Лучшие практики для шрифтов полей форм

  • Избегайте создания подмножеств для шрифтов, используемых в полях AcroForm
  • Тщательно тестируйте с разными типами шрифтов и наборами символов
  • Используйте стандартные PDF шрифты по возможности для лучшей совместимости
  • Устанавливайте правильные прямоугольники полей для обеспечения генерации потоков внешнего вида
  • Настраивайте резервные шрифты для случаев, когда основной шрифт нельзя использовать

Пример исправленного кода

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

java
package org.example;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;

import java.io.InputStream;

public class Main {

    public static void main(String[] args) {
        try (PDDocument document = new PDDocument()) {

            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);

            PDAcroForm acroForm = new PDAcroForm(document);
            document.getDocumentCatalog().setAcroForm(acroForm);

            // Загрузка внешнего TrueType шрифта с ОТКЛЮЧЕННЫМ созданием подмножеств
            PDType0Font font;
            try (InputStream fontStream = Main.class.getResourceAsStream("/FreeSans.ttf")) {
                if (fontStream == null) {
                    throw new RuntimeException("FreeSans.ttf не найден в ресурсах");
                }
                // ИСПРАВЛЕНИЕ: Установите создание подмножеств в false для использования в AcroForm
                font = PDType0Font.load(document, fontStream, false);
            }

            // Добавление шрифта в ресурсы формы
            PDResources formResources = new PDResources();
            String fontResourceName = formResources.add(font).getName();
            acroForm.setDefaultResources(formResources);
            acroForm.setDefaultAppearance("/" + fontResourceName + " 12 Tf 0 0 0 rg");

            // Создание текстового поля со значением
            PDTextField textField = new PDTextField(acroForm);
            textField.setPartialName("testField");
            textField.setDefaultAppearance("/" + fontResourceName + " 12 Tf 0 0 0 rg");

            // Установка прямоугольника виджета и добавление на страницу
            textField.getWidgets().get(0).setRectangle(new PDRectangle(100, 700, 200, 20));
            textField.getWidgets().get(0).setPage(page);
            page.getAnnotations().add(textField.getWidgets().get(0));

            acroForm.getFields().add(textField);

            textField.setValue("Привет, это заполненное поле формы!");
            
            // ИСПРАВЛЕНИЕ: Включите needAppearances для правильного отображения полей формы
            acroForm.setNeedAppearances(true);
            acroForm.refreshAppearances();
            
            document.save("output.pdf");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Основные исправления в этом исправленном коде:

  1. Изменено PDType0Font.load(document, fontStream, true) на PDType0Font.load(document, fontStream, false) для отключения создания подмножеств
  2. Изменено acroForm.setNeedAppearances(false) на acroForm.setNeedAppearances(true) для включения правильной генерации потоков внешнего вида

Источники

  1. Apache PDFBox FAQ - Сопоставление шрифтов
  2. Stack Overflow - Проблема с интервалом текста TrueType при заполнении формы Apache PDFBox
  3. API PDFBox 2.0.1 - PDTrueTypeFont
  4. Документация по обработке шрифтов Apache PDFBox
  5. PDFBOX-3332 - Проблема с интервалом текста TrueType при заполнении формы

Заключение

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

Авторы
Проверено модерацией
Модерация