Apache PDFBox

Apache PDFBox позволяет разбирать, создавать и преобразовывать PDF: извлекать текст и изображения, объединять и делить документы, накладывать штампы, заполнять формы, шифровать файлы, визуализировать страницы и исследовать внутренние объекты через PDFDebugger. Основные операции доступны из командной строки, а Java API даёт точный контроль над страницами, шрифтами, потоками содержимого, метаданными, вложениями и правами доступа.

Рабочий процесс обычно начинается с выбора одного из двух уровней. Для разовой пакетной обработки достаточно запуска JAR-файла с подкомандой и явными путями входа и выхода. Для сервера, корпоративного документооборота или генератора отчётов библиотеку подключают к Java-проекту и описывают операции кодом: загружают документ через Loader, получают страницы из PDDocument, меняют модель объектов и сохраняют результат в новый файл.

Единственное окно, с которым пользователь взаимодействует напрямую, — PDFDebugger. Слева оно показывает дерево каталога, страниц, ресурсов, аннотаций и структурных элементов, справа — выбранный объект, декодированный поток, шестнадцатеричное представление либо отрисованную страницу. Остальные инструменты управляются параметрами командной строки, поэтому результат зависит не от кнопок меню, а от правильно заданных файлов, диапазонов страниц, разрешения, паролей и разрешений.

Скачать Apache PDFBox

Оценка 9.7 Рекомендуем
  • Редактирование PDF
  • Русский интерфейс
  • Просто новичкам
Скачать бесплатно на Windows
Лучшая альтернатива
Apache PDFBox
Оценка 8.5
  • Нет обычного PDF-редактора
  • Нужна среда Java
  • OCR не встроен
Скачать Apache PDFBox
Загрузка начнётся после нажатия

Как устроена работа с Apache PDFBox

PDFBox оперирует не визуальными абзацами и картинками как текстовый редактор, а объектной моделью PDF. Документ представлен объектом PDDocument; страницы образуют PDPageTree; каждая страница хранит прямоугольники MediaBox, CropBox, BleedBox и TrimBox, ресурсы, один или несколько потоков содержимого и список аннотаций. Текст в потоке задаётся операторами выбора шрифта, матрицами позиционирования и командами показа строк. Изображение обычно находится в XObject, форма — в AcroForm, закладки — в DocumentOutline, а служебные сведения — в Info dictionary и XMP-пакете. Понимание этой структуры объясняет, почему библиотека хорошо автоматизирует точные изменения, но не предлагает привычного редактирования абзаца мышью.

В командной строке одна и та же поставка открывает набор подкоманд. Команда получает входной файл через параметр -i, а выходной — через -o либо специальный префикс. Параметры чувствительны к синтаксису: знак равенства, регистр имени, кавычки вокруг пути с пробелами и порядок повторяемых аргументов имеют значение. Перед обработкой производственного каталога полезно запустить выбранную подкоманду с --help, сохранить исходники отдельно и проверить результат независимым просмотрщиком.

В Java-проекте граница ответственности проходит иначе. Код сам выбирает источник, режим кэширования, обработку исключений и стратегию записи. PDDocument необходимо закрывать через try-with-resources; один экземпляр документа нельзя одновременно менять из нескольких потоков; большие задания лучше распараллеливать по отдельным файлам. При сохранении поверх исходника следует учитывать временные файлы, права на каталог и риск оставить повреждённый файл при аварийном завершении. Надёжный сценарий пишет во временное имя, проверяет результат и только затем заменяет оригинал.

Запуск командных инструментов

Для выполнения готовых операций используется автономный JAR с зависимостями. На компьютере должна быть доступна команда java; проверка java -version должна завершаться без ошибки. В путях Windows безопаснее использовать кавычки, особенно когда каталоги содержат пробелы или кириллицу. На Linux и macOS те же кавычки защищают пробелы и символы оболочки. Рабочую папку удобно организовать так, чтобы исходные PDF лежали в input, результаты — в output, а журнал — отдельно: это снижает вероятность перезаписи и упрощает повторный запуск.

java -jar pdfbox-app.jar --help
java -jar pdfbox-app.jar export:text --help
java -jar pdfbox-app.jar render --help

Общий список команд показывает операции шифрования, экспорта, импорта форм, наложения, отладки, объединения, разделения, рендеринга, печати, создания PDF из текста и изображений, а также декодирования потоков. Названия подкоманд не следует угадывать по старым примерам из интернета: в новых командах используются короткие глаголы и группы вроде export:text. Ошибка вида Unmatched argument обычно означает неправильное имя параметра, лишний пробел после знака равенства или устаревший синтаксис.

При пакетной обработке оболочка должна проверять код возврата. Наличие выходного файла само по себе не гарантирует успех: процесс мог записать неполный документ перед исключением. В сценарии PowerShell или Bash сначала создают временный результат, затем проверяют ненулевой размер и открытие PDF, после чего перемещают файл в итоговый каталог. Для журналирования полезно перенаправить стандартный вывод и поток ошибок в отдельные файлы, а имя входного документа добавлять к каждой записи.

Дерево страницы и декодированный поток Contents в PDFDebugger

PDFDebugger: чтение внутренней структуры

PDFDebugger запускается командой debug и принимает необязательный путь к документу. После открытия в левой панели появляется иерархия косвенных объектов. Узел Root ведёт к каталогу; Pages раскрывает дерево страниц; Resources содержит шрифты, XObject, цветовые пространства, графические состояния и шаблоны; Annots показывает аннотации; AcroForm — поля формы; Names — именованные назначения и вложенные файлы. Строка пути над деревом помогает понять, через какие словари и массивы достигнут выбранный объект.

Правая часть меняется в зависимости от типа узла. Для строки или числа она показывает значение, для словаря — дочерние записи, для потока — вкладки декодированного текста и шестнадцатеричных байтов, для изображения — визуализацию, для страницы — рендер. Выпадающий список фильтров позволяет смотреть данные после применения FlateDecode, ASCII85Decode и других фильтров либо исследовать сырой поток. Если содержимое повреждено, различие между декодированным и исходным представлением часто указывает на проблемный фильтр или неверную длину stream.

Практическая диагностика начинается с конкретного симптома. При пропавшем тексте раскрывают страницу, Contents и Resources/Font, затем сравнивают имя шрифта в операторе Tf с ресурсом шрифта и проверяют ToUnicode. При неверном изображении ищут XObject с Subtype Image, сверяют Width, Height, ColorSpace, BitsPerComponent, Filter и маску. При проблеме формы переходят к AcroForm/Fields, проверяют полное имя поля, значение V, значение по умолчанию DV, флаги Ff и поток внешнего вида AP. Такая проверка быстрее случайного изменения кода, потому что показывает реальные данные, записанные в файл.

Декодированный поток содержимого страницы с операторами текста в PDFDebugger

Поток страницы следует читать как программу рисования. Операторы BT и ET ограничивают текстовый объект, Tf выбирает шрифт и размер, Tm задаёт текстовую матрицу, Tj показывает строку, TJ показывает массив строк и поправок межсимвольного расстояния, q и Q сохраняют и восстанавливают графическое состояние, cm меняет матрицу преобразования, Do выводит XObject. Если строка выглядит как набор управляющих байтов, это не обязательно повреждение: код символа может ссылаться на глиф через собственную кодировку шрифта. Для восстановления Unicode нужна корректная таблица ToUnicode либо достоверное сопоставление кодов и глифов.

Режим Nice view с операторами Tm, Tw, Tj и TJ в PDFDebugger

Структурное дерево тегированного PDF и ParentTree в PDFDebugger

Рендер страницы в отладчике помогает отделить ошибку структуры от ошибки конкретного просмотрщика. Если PDFDebugger показывает страницу правильно, а другой клиент — нет, стоит проверить особенности прозрачности, цветового профиля, аннотаций и поддержки шрифтов в этом клиенте. Если ошибаются оба, исследуют контент, ресурсы и журналы PDFBox. Для сложных файлов полезно последовательно отключать части содержимого в копии документа или выделять одну страницу в отдельный PDF, чтобы сократить дерево и воспроизводить проблему на минимальном примере.

Отрисованная страница и дерево её объектов в PDFDebugger

Извлечение текста

Команда export:text сохраняет текст из PDF в обычный файл, HTML или Markdown. По умолчанию применяется UTF-8, а диапазон можно ограничить параметрами начальной и конечной страницы. Опция вывода в консоль удобна для конвейера, но для больших документов лучше указывать файл: так проще различить текст и диагностические сообщения. Режим сортировки пытается приблизить порядок к визуальному, однако не превращает сложную многоколоночную страницу в идеально структурированный документ.

java -jar pdfbox-app.jar export:text -i="input/report.pdf" -o="output/report.txt" -encoding=UTF-8
java -jar pdfbox-app.jar export:text -i="input/report.pdf" -o="output/report.md" -md -startPage=3 -endPage=15
java -jar pdfbox-app.jar export:text -i="input/report.pdf" -console -sort

PDF не обязан хранить символы в порядке чтения. Авторское приложение может сначала нарисовать правую колонку, затем левую, выводить каждую букву отдельно или использовать координаты с поворотом. Поэтому параметр сортировки улучшает часть документов, но иногда смешивает таблицы, подписи и боковые колонтитулы. Режим rotationMagic анализирует повёрнутый и наклонный текст, приводит ориентацию к нулю и извлекает блоки отдельно; это медленнее, зато полезно для сканов с текстовым слоем, чертежей и страниц, где подписи повернуты на 90 градусов.

Режим Markdown удобен при подготовке данных для поиска и языковых моделей, но результат остаётся эвристическим. Заголовок определяется по размеру, положению и стилю шрифта, таблица — по геометрии текста, а не по явной разметке. Перед загрузкой в базу знаний следует удалить повторяющиеся колонтитулы, проверить переносы слов, нормализовать пробелы и сохранить номер страницы в метаданных каждого фрагмента. Для юридических и бухгалтерских документов автоматическую выгрузку необходимо сверять с визуальным оригиналом.

Если выходной файл пуст, сначала выясняют, есть ли в PDF текстовые объекты. Страница, состоящая из JPEG или JBIG2, требует OCR, которого в PDFBox нет. Если текст выбирается в просмотрщике, но извлекается бессмыслицей, проверяют ToUnicode и кодировку шрифта в PDFDebugger. Если команда сообщает об ограничении прав, нужен пароль владельца либо документ без запрета на извлечение. Обходить защиту без полномочий нельзя; библиотека соблюдает флаги доступа и стандартную модель шифрования PDF.

В Java API базовый класс PDFTextStripper позволяет задать диапазон страниц, сортировку по координатам, разделители строк и слов, а также переопределить обработку текстовых позиций. Для извлечения областей применяют PDFTextStripperByArea: регистрируют прямоугольники в координатах страницы и получают текст отдельно по каждой зоне. Такой подход подходит для форм с неизменным макетом, но его нужно адаптировать к повороту страницы, CropBox и документам с разными размерами листа.

try (PDDocument doc = Loader.loadPDF(inputFile)) {
    PDFTextStripper stripper = new PDFTextStripper();
    stripper.setStartPage(1);
    stripper.setEndPage(doc.getNumberOfPages());
    stripper.setSortByPosition(true);
    String text = stripper.getText(doc);
}

Экспорт встроенных изображений

Подкоманда export:images обходит графические ресурсы и сохраняет изображения, встроенные в документ. Это отличается от рендеринга страницы: экспорт старается получить исходный XObject, тогда как рендеринг превращает весь лист вместе с текстом, векторами и аннотациями в один растр. Параметр префикса управляет именами файлов. Для JPEG и JPEG 2000 можно принудить прямое извлечение сжатого потока, если цветовое пространство и маски позволяют использовать его без преобразования.

java -jar pdfbox-app.jar export:images -i="input/catalog.pdf" -prefix="output/catalog-image"
java -jar pdfbox-app.jar export:images -i="input/catalog.pdf" -prefix="output/raw" -useDirectJPEG
java -jar pdfbox-app.jar export:images -i="input/catalog.pdf" -prefix="output/native" -noColorConvert

Прямое извлечение сохраняет качество и часто работает быстрее, но не всегда даёт картинку, совпадающую с тем, что видно на странице. PDF может применять отдельную маску прозрачности, цветовой ключ, ICC-профиль, декодирующий массив или преобразование DeviceN. В таком случае исходный JPEG представляет только часть визуального результата. Для публикации безопаснее сравнить экспортированный файл с рендером страницы; для архивного анализа полезно сохранить и исходный поток, и преобразованный вариант.

Одно изображение может использоваться на нескольких страницах или выводиться несколько раз с разными матрицами. Команда экспортирует ресурс, а не каждое визуальное появление. Вложенные Form XObject способны содержать собственные изображения, и обход должен учитывать рекурсию. При программном извлечении следует вести набор уже обработанных COSObject, иначе повторные ссылки создадут дубликаты, а циклическая структура приведёт к бесконечному обходу.

Поддержка JBIG2 и JPEG 2000 зависит от ImageIO-плагинов в classpath. Без них PDFBox может открыть документ, но пропустить изображение либо выдать предупреждение при рендеринге. Для TIFF-записи также требуется подходящий ImageIO-компонент. Ошибку No ImageIO reader устраняют не сменой расширения, а добавлением совместимого плагина и проверкой, что его JAR действительно виден тому процессу Java, который запускает команду или приложение.

Рендеринг страниц в PNG, JPEG и другие растровые форматы

Команда render создаёт отдельное изображение для каждой выбранной страницы. Ключевой параметр — DPI: он определяет число пикселей и напрямую влияет на память. Лист A4 при 300 DPI занимает примерно 2480×3508 пикселей; изображение ARGB в памяти требует около 35 МБ только для массива пикселей, без учёта PDF, шрифтов и промежуточных буферов. При параллельной обработке нескольких страниц потребление растёт пропорционально числу задач.

java -jar pdfbox-app.jar render -i="input/manual.pdf" -outputPrefix="output/page" -format=png -dpi=150
java -jar pdfbox-app.jar render -i="input/manual.pdf" -outputPrefix="output/thumb" -format=jpg -dpi=96 -quality=0.85
java -jar pdfbox-app.jar render -i="input/manual.pdf" -outputPrefix="output/crop" -format=png -page=4 -cropbox="0 0 420 300"

Для предпросмотра обычно хватает 96–150 DPI, для распознавания мелкого текста — 200–300 DPI, а дальнейшее увеличение оправдано только конкретным требованием. JPEG подходит для фотографических страниц, но создаёт артефакты вокруг текста и штрихкодов; PNG лучше сохраняет резкие границы и прозрачность. Параметр качества действует на форматы с потерями. Бинарный или серый режим уменьшает память, однако может потерять цветовые различия и сглаживание.

Subsampling снижает нагрузку на документы с огромными встроенными изображениями: декодер читает уменьшенную выборку, когда итоговый размер заметно меньше исходного. Это ускоряет миниатюры, но способно ухудшить тонкие линии и мелкие подписи. Для технических чертежей и QR-кодов нужно сравнивать результат с режимом без субдискретизации. Отдельная опция времени помогает понять, где находится узкое место: чтение, разбор, рендер или запись изображения.

Рендер страницы с разными вариантами позиционирования текста в PDFDebugger

В Java классе PDFRenderer можно получать BufferedImage, передавать собственный ImageType и разрешение, а также переопределять PageDrawer для нестандартной отрисовки. Это позволяет скрывать некоторые аннотации, перехватывать изображения, менять сглаживание или визуально отмечать области. Однако модификация PageDrawer требует знания графической модели PDF: порядок операторов, прозрачные группы, clipping path, blend mode и цветовые пространства влияют на итог не меньше координат.

Если на рендере появляются чёрные пятна, проверяют мягкие маски, ICC-профили и используемый JPEG-декодер. Если пропали элементы, ищут предупреждения о JBIG2/JPX, повреждённых шрифтах и неподдерживаемых цветах. Если результат отличается только при высокой нагрузке, исключают совместное использование одного PDDocument несколькими потоками. Документ не потокобезопасен; каждая параллельная задача должна иметь собственный экземпляр или работать строго последовательно.

Объединение и разделение PDF

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

java -jar pdfbox-app.jar merge -i="input/part-01.pdf" -i="input/part-02.pdf" -i="input/appendix.pdf" -o="output/full.pdf"

Наиболее частая проблема при объединении форм — совпадающие полные имена полей. Два поля customer.name могут начать отображать одно значение, потому что AcroForm связывает виджеты с общей моделью поля. До объединения имена нужно сделать уникальными либо сплющить формы, если дальнейшее редактирование не требуется. Сплющивание превращает текущее визуальное значение в содержание страницы, но уничтожает интерактивность; делать его следует только после заполнения и проверки внешнего вида.

Команда split делит документ по одному листу, фиксированными группами либо выделяет диапазон. Нумерация параметров начинается с единицы. Последняя часть может содержать меньше страниц, чем заданный шаг. Префикс выходного имени нужно направлять в отдельный каталог, иначе повторный запуск перезапишет предыдущие части. Для конфиденциальных файлов также проверяют, не сохраняются ли в каждой части общие вложения, метаданные или сценарии, которые не относятся к выбранным страницам.

java -jar pdfbox-app.jar split -i="input/book.pdf" -split=20 --outputPrefix="output/chapter"
java -jar pdfbox-app.jar split -i="input/book.pdf" -startPage=41 -endPage=60 --outputPrefix="output/selection"

В API PDFMergerUtility позволяет добавлять File, InputStream и другие источники, задавать выход и стратегию использования памяти. Splitter возвращает список PDDocument, каждый из которых необходимо закрыть после сохранения. Для больших файлов опасно сначала держать все части открытыми: лучше сохранять и закрывать их по мере получения либо реализовать потоковый сценарий вокруг диапазонов. После операции полезно пересчитать страницы, открыть первую и последнюю часть и проверить, что границы разделения соответствуют заданию.

Наложение подложек, водяных знаков и бланков

Подкоманда overlay размещает страницы одного PDF поверх или под содержимым другого. Можно задать общий шаблон, отдельные шаблоны для первой, последней, чётных и нечётных страниц, а также переопределение для конкретного номера. Позиция FOREGROUND подходит для печати отметки поверх документа; BACKGROUND — для фирменного бланка, сетки или фона. Параметр корректировки поворота помогает, когда исходные страницы имеют Rotate 90/180/270.

java -jar pdfbox-app.jar overlay -i="input/contracts.pdf" -default="assets/watermark.pdf" -position=FOREGROUND -o="output/contracts-marked.pdf"
java -jar pdfbox-app.jar overlay -i="input/report.pdf" -first="assets/cover-frame.pdf" -odd="assets/right.pdf" -even="assets/left.pdf" -o="output/report-branded.pdf"

Шаблон должен иметь подходящий размер страницы и прозрачный фон. Если его MediaBox отличается от исходного, содержимое может оказаться смещённым или обрезанным. Перед массовым запуском проверяют страницы разных размеров и ориентаций: договор может содержать A4, приложение A3 и отсканированную квитанцию нестандартного формата. Универсальный водяной знак лучше строить программно с вычислением центра и масштаба для каждого CropBox.

Наложение не является редактированием существующего текста. Оно добавляет новый поток содержимого или форму XObject, сохраняя исходные объекты. Белый прямоугольник поверх номера не удаляет номер: текст остаётся извлекаемым и может быть виден при отключении слоя либо изменении порядка рисования. Для необратимого удаления конфиденциальных данных нужна корректная редакция содержимого и аннотаций с последующей проверкой текстового извлечения, объектов, вложений и предыдущих инкрементальных сохранений.

Шифрование, пароли и разрешения

Команда encrypt устанавливает пользовательский и владельческий пароль, длину ключа и разрешения. Пользовательский пароль открывает документ с назначенными ограничениями; владельческий разрешает менять настройки защиты. Флаги управляют печатью, точной печатью, извлечением, изменением, аннотациями, заполнением форм, сборкой страниц и доступностью. Значения по умолчанию следует задавать осознанно: пароль без изменения флагов может оставить больше действий, чем предполагает политика.

java -jar pdfbox-app.jar encrypt -i="input/confidential.pdf" -o="output/confidential-protected.pdf" -O="owner-secret" -U="reader-secret" -keyLength=256 -canPrint=false -canExtractContent=false -canModify=false

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

Команда decrypt требует владельческий пароль при парольной защите. Для шифрования сертификатом используются X.509-сертификат, хранилище ключей и псевдоним. Операции с открытым ключом и цифровыми подписями требуют библиотек Bouncy Castle в classpath. Ошибка о провайдере или неизвестном алгоритме означает, что нужный JAR отсутствует, несовместим по версии либо не зарегистрирован в том процессе Java, который выполняет код.

Пароли нельзя помещать в команду на общем сервере без оценки риска: аргументы процесса могут быть видны другим пользователям и попадать в журнал CI. Безопаснее получать секрет из защищённого хранилища, ограничивать права учетной записи и удалять временные файлы. При обработке исключений не следует писать пароль в сообщение. Тестовый набор должен включать открытие с пользовательским и владельческим паролем, проверку прав и попытку выполнить запрещённую операцию.

Формы FDF, XFDF и AcroForm

PDFBox экспортирует данные AcroForm в FDF или XFDF и импортирует их обратно. FDF использует PDF-подобный бинарный синтаксис, XFDF — XML, который удобнее проверять, хранить в системе контроля версий и передавать между сервисами. Импорт сопоставляет данные по полным именам полей. Если шаблон изменил иерархию или имя, значение не попадёт в нужное место даже при одинаковой подписи на странице.

java -jar pdfbox-app.jar export:xfdf -i="input/form-filled.pdf" -o="output/data.xfdf"
java -jar pdfbox-app.jar import:xfdf -i="input/form-template.pdf" --data="output/data.xfdf" -o="output/form-result.pdf"

Поле состоит не только из значения. Виджет задаёт прямоугольник и аннотационные свойства, словарь поля — имя, тип и флаги, а поток AP — внешний вид. Некоторые просмотрщики самостоятельно строят внешний вид из значения V, другие показывают только существующий AP. Поэтому после программного заполнения нужно обновить appearances либо применить корректный механизм генерации внешнего вида. Флаг NeedAppearances не гарантирует одинаковое отображение во всех клиентах и не заменяет проверку.

Для текстового поля важны шрифт и ресурсы по умолчанию. Если выбранный шрифт не содержит кириллицу, значение сохранится, но на странице появятся пустые квадраты. Нужно загрузить TrueType/OpenType-шрифт с нужными глифами, добавить его в ресурсы AcroForm и настроить default appearance. Размер шрифта ноль означает автоматический подбор, который разные просмотрщики реализуют неодинаково; для стабильной печати лучше рассчитывать размер и внешность на стороне приложения.

Сплющивание формы выполняют только после заполнения, расчёта и визуального контроля. Оно переносит внешний вид полей в страницу и удаляет интерактивные элементы. Подписи, JavaScript, зависимости расчётных полей и XFA требуют отдельного анализа. PDFBox хорошо работает с AcroForm, но не является полноценным движком динамических XFA-форм. Если документ открывается в специализированном клиенте с изменяемой разметкой, импорт обычного XFDF может быть недостаточен.

Создание PDF из текста и изображений

Команда fromtext превращает текстовый файл в PDF, позволяя задать кодировку, размер и межстрочный интервал, поля, ориентацию, размер страницы и шрифт. Для русского текста следует использовать TTF с кириллическими глифами; стандартные шрифты PDF не дают надёжной Unicode-поддержки. Команда делает простой поток строк, а не верстает HTML: она не понимает таблицы, CSS, плавающие блоки, автоматические оглавления и сложные сноски.

java -jar pdfbox-app.jar fromtext -i="input/note.txt" -o="output/note.pdf" -charset=UTF-8 -ttf="fonts/DejaVuSans.ttf" -fontSize=11 -pageSize=A4 -margins="48 48 56 56"

Если строка шире доступной области, нужно проверить поведение конкретной команды на тесте с длинными словами, URL и табуляцией. Для управляемой верстки лучше использовать API: измерять ширину через PDFont.getStringWidth, переносить по словам, учитывать leading и добавлять новую страницу при достижении нижнего поля. Высота строки зависит от выбранной метрики; формулы на основе bounding box и font descriptor могут давать разные результаты, поэтому макет проверяют на реальных шрифтах.

Команда fromimage создаёт страницы из одного или нескольких изображений. Можно выбрать размер листа, альбомную ориентацию, автоматическую ориентацию и масштабирование. Без resize исходное изображение может не совпасть с листом; с масштабированием нужно определить, допустимо ли изменение физического размера и разрешения. Для сканов обычно сохраняют пропорции и помещают изображение в границы страницы без растяжения.

java -jar pdfbox-app.jar fromimage -i="input/scan-01.jpg" -i="input/scan-02.jpg" -o="output/scans.pdf" -pageSize=A4 -autoOrientation -resize

Созданный из изображений PDF не получает текстовый слой. Поиск, копирование и доступность появятся только после отдельного OCR-процесса и добавления распознанного текста с правильными координатами. Также нужно решить, сохранять ли исходные JPEG без перекодирования. JPEGFactory позволяет внедрить совместимый JPEG-поток эффективно, а LosslessFactory создаёт без потерь, но может заметно увеличить файл. Выбор зависит от источника, требований к качеству и допустимого размера.

Java API: загрузка и сохранение документов

В коде документ загружается через класс Loader. Источником может быть File, byte[] или реализация RandomAccessRead. Для файла доступно буферизованное чтение; для массива байтов данные находятся в памяти; memory-mapped режим ускоряет произвольный доступ к крупному файлу, но имеет ограничение размера и особенности освобождения отображения на Windows. Стратегию выбирают по объёму, числу одновременных задач и политике временного хранения.

try (PDDocument document = Loader.loadPDF(inputFile)) {
    int pages = document.getNumberOfPages();
    document.getDocumentInformation().setTitle("Processed document");
    document.save(outputFile);
}

Try-with-resources закрывает COSDocument, потоки и временные ресурсы даже при исключении. Предупреждение о незакрытом документе означает реальную утечку, а не косметическую запись: на Windows файл может остаться заблокированным, а на сервере постепенно исчерпываются дескрипторы. Все PDDocument, созданные при разделении, клонировании или загрузке внешних шаблонов, закрывают отдельно.

Обычное сохранение переписывает документ. Инкрементальное добавляет изменения после предыдущей версии и сохраняет старые байты. Оно нужно для некоторых сценариев подписи и обновления, но не удаляет конфиденциальные данные из прошлых ревизий. Для очистки скрытой информации требуется полная перезапись и проверка того, что старые объекты действительно недоступны. Даже полная перезапись не заменяет анализ вложений, JavaScript, XMP и аннотаций.

При записи больших потоков используется RandomAccessStreamCache. Кэш в памяти быстрее на небольших файлах, но опасен при параллельной генерации многостраничных документов. ScratchFile позволяет переносить буферы на диск и ограничивать память. Каталог временных файлов должен иметь достаточное место, быстрый носитель и права на создание и удаление. В контейнере или серверной функции нужно учитывать квоту ephemeral storage и удалять остатки после аварии.

Создание страниц, текста и графики через API

Новый документ создают через PDDocument, добавляют PDPage выбранного размера и открывают PDPageContentStream. Поток содержимого формирует последовательность графических операторов. Перед текстом вызывают beginText, выбирают шрифт и размер, задают позицию через newLineAtOffset или матрицу, выводят строку и завершают endText. Для линий, прямоугольников и кривых используются операции пути, после которых вызывают stroke, fill или fillAndStroke.

try (PDDocument doc = new PDDocument()) {
    PDPage page = new PDPage(PDRectangle.A4);
    doc.addPage(page);
    PDFont font = PDType0Font.load(doc, new File("fonts/DejaVuSans.ttf"));
    try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
        cs.beginText();
        cs.setFont(font, 12);
        cs.newLineAtOffset(54, 790);
        cs.showText("Документ создан с Apache PDFBox");
        cs.endText();
    }
    doc.save(outputFile);
}

PDType0Font подходит для Unicode и внедряет шрифт или его подмножество. Строка должна состоять из глифов, присутствующих в файле шрифта. Ошибка No glyph for U+… означает отсутствие символа, а не неправильную кодировку Java. Решение — выбрать шрифт с нужным диапазоном либо разделить строку на участки и применить запасные шрифты. Для эмодзи, сложных письменностей и цветных глифов возможности зависят от формата шрифта и механизма shaping; PDFBox не заменяет полноценный движок типографики.

При добавлении содержимого на существующую страницу важно выбрать AppendMode. APPEND рисует после старого содержимого и обычно оказывается сверху; PREPEND — перед ним и оказывается фоном. Параметр resetContext восстанавливает ожидаемое графическое состояние, если старый поток оставил изменённую матрицу, цвет или clipping path. Без сброса новый штамп может неожиданно масштабироваться, поворачиваться или быть невидимым из-за унаследованного clipping path.

Координаты PDF измеряются в пунктах, обычно от левого нижнего угла MediaBox, но CropBox и Rotate меняют воспринимаемую геометрию. Для размещения подписи в правом верхнем углу нельзя жёстко вычитать координаты из A4: нужно получить реальный прямоугольник страницы, учесть поворот и преобразовать координаты. Документы от сканеров часто имеют нестандартный origin, например отрицательный lower-left, что также влияет на формулы.

Шрифты, Unicode и порядок текста

Шрифт в PDF состоит из программы глифов, кодировки, метрик и необязательной таблицы ToUnicode. Для отображения достаточно знать, какой глиф нарисовать; для копирования и поиска нужно сопоставить код с Unicode. Поэтому визуально правильный документ может извлекаться как набор случайных символов. PDFBox использует доступные карты и эвристики, но не способен восстановить смысл, если авторский файл не содержит необходимой информации.

Стандартные 14 шрифтов удобны для латинских служебных надписей, однако для русского текста следует внедрять подходящий TrueType/OpenType-шрифт. Подмножество уменьшает размер: сохраняются только использованные глифы. Но если документ будет инкрементально дополняться новыми символами, существующее подмножество может их не содержать. В таком сценарии добавляют новый шрифтовой ресурс или заранее планируют набор символов.

PDFBox не выполняет полный Unicode shaping для всех письменностей автоматически. Арабский текст требует выбора форм глифов и направления справа налево, индийские письменности — перестановки и лигатур, а некоторые шрифты — сложных таблиц OpenType. Приложение может использовать HarfBuzz или другой shaping-движок, затем выводить рассчитанные глифы и позиции. Просто передать логическую строку в showText недостаточно для гарантированно правильной сложной письменности.

При измерении строки значение getStringWidth возвращается в тысячных долях единицы текста. Для ширины в пунктах его умножают на размер шрифта и делят на 1000. Кернинг и индивидуальные поправки могут требовать TJ или раздельного позиционирования. Выравнивание по ширине через word spacing работает не для всех составных шрифтов одинаково, поэтому стабильный макет строят на явных смещениях и проверяют в нескольких просмотрщиках.

Метаданные, XMP, закладки и вложения

DocumentInformation хранит простые поля Title, Author, Subject, Keywords, Creator, Producer и даты. XMP — XML-пакет с более богатой моделью, схемами Dublin Core, PDF и пользовательскими пространствами имён. Эти два источника могут противоречить друг другу. При обновлении метаданных в документообороте следует определить основной источник и синхронизировать ключевые поля, иначе поисковый индекс и свойства просмотрщика покажут разные значения.

Команда export:xmp выводит XMP документа или конкретной страницы в файл либо консоль. Это удобно для аудита без изменения PDF. Отсутствие XMP не является ошибкой; многие документы имеют только Info dictionary. При удалении персональных данных проверяют оба места, а также аннотации, вложения, имена слоёв, историю форм и пользовательские свойства. Простое обнуление автора не очищает остальные следы.

Закладки представлены деревом PDDocumentOutline и PDOutlineItem. Пункт содержит заголовок и действие или назначение. После объединения документов старые назначения могут ссылаться на страницы, которые изменили положение; корректное объединение требует клонировать элементы и пересчитать ссылки. Для создания оглавления задают destination на страницу и позицию, а затем открывают и закрывают узлы в соответствии с желаемым видом панели закладок.

Вложения могут находиться в Names/EmbeddedFiles или в аннотациях FileAttachment. Файловая спецификация содержит имя, Unicode-имя, описание и встроенный поток с размером, датами и MIME-подобным subtype. Извлечение должно обрабатывать оба расположения, нормализовать имя и защищаться от path traversal: значение вроде ../../file нельзя напрямую соединять с выходным каталогом. На сервере также ограничивают размер, число вложений и допустимые типы.

Аннотации включают ссылки, заметки, штампы, виджеты формы, вложения и мультимедийные конструкции. Рендер страницы может показывать их внешний вид, но поведение и интерактивность зависят от просмотрщика. При удалении аннотаций проверяют, не осталось ли их appearance-потоков как недостижимых объектов, и действительно ли задача требует удалить их или лишь скрыть при печати.

Цифровые подписи и проверка целостности

PDFBox предоставляет API для подготовки подписи, вычисления байтового диапазона и добавления CMS-контейнера. Криптографическая операция обычно выполняется через Bouncy Castle, аппаратный токен, HSM или внешний сервис. Подпись охватывает определённые байты документа; после добавления разрешённых изменений может сохраняться статус, зависящий от политики DocMDP, а произвольная перезапись делает подпись недействительной.

Нельзя считать документ подписанным только потому, что на странице нарисована картинка автографа. Визуальная область — appearance, а доказательство целостности находится в словаре подписи, ByteRange и CMS. Проверка включает криптографическую валидность, цепочку сертификатов, срок действия, отозванность, доверенный корень и метку времени. PDFBox помогает разобрать контейнер, но модель доверия и доступ к OCSP/CRL определяет приложение.

При внешнем подписании сначала резервируют пространство под подпись и получают контент для хэширования, затем передают хэш подписывающему сервису и встраивают результат. Недостаточный размер резервного поля приводит к ошибке на финальном шаге. Слишком большой резерв увеличивает файл, но обычно безопаснее небольшого. Повторная подпись выполняется инкрементально, чтобы не менять байты предыдущих подписей.

Диагностика ошибки Extra data detected in stream начинается с проверки точного ByteRange и размера CMS. Нельзя добавлять перевод строки, Base64-текст вместо DER-байтов или менять файл между расчётом хэша и встраиванием. В распределённом процессе нужен идентификатор версии документа и защита от параллельной модификации. После подписания результат проверяют независимым валидатором, а не только тем же кодом, который создавал подпись.

PDF/A и Preflight

Компонент Preflight проверяет соответствие профилям PDF/A и сообщает нарушения синтаксиса, шрифтов, цветовых пространств, метаданных, прозрачности и других требований. Успешное открытие документа не означает соответствие архивному стандарту: обычные просмотрщики намеренно терпимы к отклонениям. Валидатор, напротив, должен фиксировать несоответствия, даже если они не влияют на экран.

Проверка PDF/A отличается от преобразования. Preflight не исправляет автоматически отсутствующие шрифты, неверный OutputIntent или запрещённое действие. Для создания корректного PDF/A нужно внедрить шрифты, добавить ICC-профиль, сформировать XMP с идентификатором части и уровня, исключить запрещённые функции и затем валидировать результат. Исправление одного сообщения может открыть следующее, потому что требования взаимосвязаны.

На современных JDK для Preflight могут потребоваться библиотеки activation и JAXB, которые больше не поставляются вместе с Java. Ошибка ClassNotFoundException на javax.activation или javax.xml.bind указывает на неполный classpath. Добавление случайной версии из старого проекта способно вызвать конфликт; зависимости нужно согласовать с используемой сборкой и запускать проверку в чистом окружении.

Для архивного конвейера сохраняют отчёт валидатора вместе с хэшем входного и выходного файла, временем, профилем проверки и версией конфигурации. Один валидатор не должен быть единственным доказательством: полезно сравнивать результат с veraPDF или другим независимым инструментом, особенно когда документ предназначен для длительного хранения. Разногласие требует анализа конкретного правила и объекта, а не выбора более удобного зелёного отчёта.

Печать документов

Подкоманда print использует Java Print Service и умеет перечислять принтеры, выбирать устройство, лоток, размер бумаги, ориентацию, дуплекс, рамку и режим без диалога. Параметр DPI включает промежуточный рендер в изображение, что помогает при сложной графике, но увеличивает память и может растрировать текст. Значение автоматического разрешения зависит от возможностей драйвера.

java -jar pdfbox-app.jar print -i="input/tickets.pdf" -listPrinters
java -jar pdfbox-app.jar print -i="input/tickets.pdf" -printerName="Office Printer" -silentPrint -duplex=SIMPLEX -orientation=PORTRAIT -noColorOpt

Для этикеток, штрихкодов и чеков отключение цветовых оптимизаций может предотвратить нежелательное преобразование чёрного. Размер MediaBox должен совпадать с выбранной бумагой; иначе драйвер применит масштабирование или обрезку. Лоток и mediaSize не всегда доступны одинаково на разных JDK и драйверах, поэтому сначала получают список поддерживаемых атрибутов принтера и тестируют одну страницу.

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

Декодирование потоков и анализ повреждённых PDF

Команда decode переписывает PDF с декодированными потоками, что облегчает чтение содержимого текстовым редактором и сравнение объектов. Параметр пропуска изображений не распаковывает тяжёлые графические потоки. Итоговый файл обычно значительно больше исходника и предназначен для диагностики, а не распространения. Декодирование не превращает бинарные шрифты и изображения в понятный текст и не исправляет логические ошибки автоматически.

При повреждённой cross-reference table PDFBox пытается восстановить ссылки, сканируя объекты. Успех зависит от того, насколько целы заголовки объектов, trailer и потоки. Восстановленный файл нужно сохранить под новым именем и проверить страницы, закладки, формы, вложения и подписи. Если подпись была в исходном документе, полная перезапись обычно изменит подписанные байты и нарушит её криптографический статус.

Для минимизации проблемы выделяют страницу или объект, на котором возникает исключение. PDFDebugger показывает номер косвенного объекта; журнал часто содержит offset и имя фильтра. Сопоставив их, можно понять, повреждён ли поток, длина, словарь ресурсов или шрифт. Правка байтов вручную допустима только на копии и при знании синтаксиса PDF: неверное значение Length или пропущенный endstream способно изменить границы последующих объектов.

Производительность и управление памятью

Главный фактор нагрузки — не размер файла на диске, а число и сложность объектов после декодирования. PDF на 5 МБ может содержать гигантский сжатый растр, а PDF на 200 МБ — множество уже сжатых страниц, которые обрабатываются последовательно. Перед запуском ограничивают максимальный размер входа, число страниц, разрешение рендера, глубину вложенных XObject и время выполнения.

Один PDDocument не потокобезопасен. Безопасная параллельность строится на модели один файл — один документ — один рабочий поток. Если нужно параллельно рендерить страницы одного файла, надёжнее открыть отдельный экземпляр на задачу либо реализовать контролируемую очередь и измерить стоимость повторного чтения. Совместное использование кэшей шрифтов и ImageIO-провайдеров также проверяют под нагрузкой.

Для крупных операций выбирают файловое чтение и дисковый scratch cache, а не загрузку всего файла в byte[]. Порог памяти должен учитывать остальные компоненты JVM и native-буферы. Параметр -Xmx не гарантирует, что процесс уложится в этот объём: BufferedImage, memory-mapped files, кодеки и JIT используют память вне heap. Мониторинг должен отслеживать RSS, GC pauses, число открытых файлов и объём временного каталога.

Субдискретизация изображений, ограничение DPI и последовательное закрытие страниц уменьшают нагрузку. Кэширование результата имеет смысл, если один и тот же PDF рендерится многократно с одинаковыми параметрами; ключ кэша должен включать хэш файла, номер страницы, DPI, цветовой режим, формат и версию конфигурации. Иначе после замены документа сервис может вернуть старую миниатюру.

При измерении производительности отделяют холодный запуск JVM от устойчивого режима. Первый файл включает загрузку классов, инициализацию шрифтов и ImageIO. Для сервиса полезно прогреть процесс тестовым документом. Бенчмарк должен содержать текстовые, сканированные, прозрачные, шрифтовые и повреждённые PDF, потому что среднее время по одному типу скрывает худшие случаи.

Совместимость и дополнительные компоненты

Для выполнения нужен Java 8 или новее. На более новых JDK основной код работает без графического рабочего стола, но печать и PDFDebugger требуют соответствующих AWT-компонентов и окружения. В headless-контейнере рендеринг возможен, а открытие окна — нет. Шрифты операционной системы влияют на подстановку отсутствующих шрифтов, поэтому один и тот же плохо сформированный PDF может выглядеть по-разному на сервере и рабочей станции.

Основная Maven-зависимость подтягивает FontBox и обязательные компоненты. XMPBox добавляют, когда приложение напрямую работает с XMP. Preflight подключают для PDF/A. Плагины JBIG2, JPEG 2000 и улучшенный JPEG-декодер устанавливают по необходимости. Лишние кодеки увеличивают поверхность зависимостей, поэтому состав classpath фиксируют, сканируют на уязвимости и обновляют контролируемо.

Для открытого ключа и подписей нужны bcprov, bcmail и bcpkix совместимой серии. Одновременное присутствие нескольких поколений Bouncy Castle вызывает NoSuchMethodError или конфликт провайдера. В fat JAR и сервере приложений следует проверить дерево зависимостей, исключить старые транзитивные версии и выполнить тест реального сертификата. Успешная компиляция не доказывает, что CMS-операция сработает во время выполнения.

Формат PDF допускает JavaScript, действия запуска, вложения и мультимедиа. PDFBox разбирает многие такие объекты, но не исполняет их как просмотрщик. Это полезно для серверной безопасности, однако не означает, что вход безопасен. Сервис должен считать любой PDF недоверенным, ограничивать ресурсы, запускать обработчик с минимальными правами и не открывать извлечённые вложения автоматически.

Типовые ошибки и способы устранения

Команда не запускается или показывает справку

Проверяют, что используется автономный JAR, а не только библиотечный pdfbox JAR без инструментов и зависимостей. Затем выполняют java -jar файл.jar --help. Если Java сообщает Unable to access jarfile, путь неверен или нет прав чтения. Если выводится справка подкоманды, один из обязательных параметров отсутствует. Пути с пробелами заключают в кавычки, повторяемые -i задают отдельно для каждого файла.

Извлечённый текст пустой или бессмысленный

Пустой результат на скане является ожидаемым: сначала нужен OCR. Для текстового PDF открывают Contents и Font в PDFDebugger, проверяют ToUnicode и возможность выделения текста в другом просмотрщике. Неправильный порядок исправляют параметром sort или собственным анализом координат. Запрет на извлечение снимается только владельцем документа с соответствующим паролем.

Не отображается изображение или страница отличается от оригинала

Читают журнал на сообщения JBIG2, JPX, ICC и ImageIO, добавляют нужный плагин, затем повторяют тест на одной странице. Проверяют мягкую маску, цветовое пространство и прозрачную группу. Если проблема возникает только при прямом экспорте, используют преобразованный экспорт или рендер страницы. Если ошибка плавающая, исключают параллельный доступ к PDDocument.

Кириллица превращается в квадраты

Выбранный шрифт не содержит нужных глифов либо не внедрён. Загружают TTF/OTF через PDType0Font, проверяют лицензию шрифта и наличие символов. В форме дополнительно настраивают default resources и appearance. Замена кодировки UTF-8 на Windows-1251 не исправит отсутствие глифа: Java-строка уже хранит Unicode, проблема находится в шрифтовом ресурсе.

Форма заполнена, но значение не видно

Проверяют V и AP выбранного поля. Значение могло записаться, а внешний вид остался старым. Генерируют appearances, добавляют шрифт в ресурсы формы и открывают результат в нескольких просмотрщиках. Для checkbox и radio button значение должно совпадать с именем доступного appearance state; произвольное true не всегда соответствует состоянию On.

OutOfMemoryError при рендеринге

Снижают DPI, выбирают RGB или GRAY вместо ARGB, включают subsampling для огромных изображений и уменьшают число параллельных страниц. Переходят с byte[] на файл, настраивают scratch cache и проверяют временный диск. Увеличение Xmx применяют после оценки, а не вместо неё. Если одна страница стабильно потребляет чрезмерно много памяти, исследуют размеры её XObject и масок.

После обработки файл не удаляется на Windows

Остался открытый PDDocument, InputStream, OutputStream или memory-mapped reader. Все ресурсы переводят на try-with-resources и проверяют пути исключений. Не следует полагаться на сборщик мусора для закрытия. При memory mapping учитывают особенности unmap и используют буферизованный файловый reader, если немедленное удаление критично.

NoClassDefFoundError или NoSuchMethodError

Первая ошибка указывает на отсутствующий класс, вторая — на конфликт версий. Анализируют Maven dependency tree и фактический classpath процесса. Для изображений проверяют ImageIO-плагины, для подписей — Bouncy Castle, для Preflight на новых JDK — activation/JAXB. В fat JAR убеждаются, что service descriptors ImageIO не потеряны при упаковке.

Практические рабочие процессы

Архив текстов для полнотекстового поиска

  1. Проверить хэш входного файла, число страниц, шифрование и разрешение на извлечение.
  2. Запустить export:text в UTF-8 с отладочным временем; для сложной ориентации включить rotationMagic.
  3. Сохранить номер страницы и координатные фрагменты через Java API, если поиск должен открывать точное место.
  4. Удалить повторяющиеся колонтитулы, нормализовать переносы и не смешивать текст разных документов.
  5. Сравнить несколько случайных страниц с визуальным оригиналом и отдельно отправить сканы в OCR.

В этом сценарии PDFBox отвечает за надёжное чтение структуры и текста, а не за семантическое распознавание таблиц. Индексатор должен хранить ссылку на исходный документ, его хэш и параметры извлечения. При повторной обработке новый результат не следует безусловно смешивать со старым: изменение версии шрифта, сортировки или алгоритма может поменять порядок строк и номера фрагментов.

Создание пакета документов из шаблонов

  1. Сформировать данные клиента и валидировать обязательные поля до открытия шаблона.
  2. Заполнить AcroForm, обновить appearances и проверить кириллицу на используемом шрифте.
  3. Добавить индивидуальные приложения через merge, предварительно переименовав конфликтующие поля.
  4. Наложить номер дела и отметку через append content либо overlay с учётом размеров страниц.
  5. При необходимости сплющить формы, добавить закладки и установить метаданные.
  6. Записать временный файл, отрендерить контрольные страницы, затем зашифровать или подписать.

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

Генерация миниатюр для веб-каталога

  1. Ограничить размер файла, число страниц и время обработки до передачи в PDFRenderer.
  2. Открыть документ отдельным PDDocument для каждой задачи и выбрать 120–150 DPI.
  3. Включить subsampling для страниц с фотографиями, но отключать его для штрихкодов и тонких схем.
  4. Сохранить PNG для страниц с текстом или JPEG с контролируемым качеством для фотографий.
  5. Кэшировать по хэшу документа, номеру страницы и параметрам рендера.
  6. Возвращать нейтральную заглушку при повреждении, не раскрывая внутренний путь сервера.

Рендерер должен работать в изолированном процессе или контейнере с лимитами памяти и диска. Уменьшенная копия не должна заменять оригинал при проверке качества. Если первая страница содержит прозрачность или обложку нестандартного размера, центрирование миниатюры выполняют после рендера, а не изменением CropBox исходного PDF.

Проверка входящих PDF перед загрузкой

  1. Определить, открывается ли файл, сколько в нём страниц и зашифрован ли он.
  2. Перечислить вложения, действия, JavaScript, аннотации и необычные мультимедийные объекты.
  3. Ограничить размеры декодированных изображений и глубину вложенных ресурсов.
  4. Отрендерить выборочные страницы и извлечь текст для антивирусного и контентного анализа.
  5. При архивных требованиях запустить Preflight и сохранить отчёт.
  6. Не считать успешный парсинг доказательством безопасности или соответствия бизнес-правилам.

PDFDebugger полезен для ручного разбора отклонённых файлов: аналитик видит объект, путь и фильтр, вызвавший проблему. В автоматическом сервисе те же сведения записываются в структурированный журнал без содержимого конфиденциальных строк. Сообщение пользователю должно быть понятным — например, не поддерживается изображение JPEG 2000 — но не раскрывать стек, абсолютные пути и секретные параметры.

Сравнение Apache PDFBox с аналогами

ПрограммаЛучше подходит дляГлавное ограничение
Apache PDFBoxJava-автоматизации, разбора структуры, рендера и пакетных операцийНет визуального редактора страниц
PDF CommanderРучного редактирования, сборки и оформления PDF без программированияНе является Java-библиотекой для серверной интеграции
iText CoreСложной генерации и обработки PDF в Java/.NET с развитой экосистемойУсловия лицензирования требуют отдельной оценки проекта
OpenPDFСоздания PDF в Java через API, близкое к классической модели iTextМеньше инструментов низкоуровневой диагностики
qpdfСтруктурных преобразований, шифрования, линейзации и ремонта из CLI/C++Не предназначен для верстки текста и рендера страниц
Apache FOPГенерации отчётов из XSL-FO и XMLНе подходит для произвольного редактирования существующего PDF

Для пользовательского исправления текста, перестановки объектов и работы мышью практичнее PDF Commander. Для Java-сервиса, который читает существующие PDF, извлекает данные, строит изображения страниц и меняет объектную модель, сильнее подходит Apache PDFBox. iText выбирают, когда нужны его специализированные модули и приемлемы лицензионные условия. OpenPDF удобен для генерации через знакомый Java API, qpdf — для быстрых структурных операций, а Apache FOP — когда исходные данные уже представлены как XML и документ строится по XSL-FO.

Безопасность обработки недоверенных документов

PDF следует рассматривать как сложный контейнер, а не безопасную картинку. Сжатые потоки способны многократно увеличиваться, дерево объектов — быть глубоко вложенным, изображения — требовать огромной памяти, а вложения — содержать исполняемые файлы. Даже если PDFBox не исполняет JavaScript, парсер и кодеки обрабатывают данные злоумышленника. Сервис запускают с минимальными правами, без доступа к секретам и внутренней сети, с лимитом CPU, памяти, временного диска и времени.

Имя входного и извлечённого файла нельзя использовать как путь без нормализации. Выход должен оставаться внутри выделенного каталога, а символические ссылки и последовательности .. запрещаются. Расширение не доказывает тип содержимого: сигнатуру проверяют отдельно. Вложения не открывают и не передают в другие парсеры без собственных ограничений.

Журналы не должны содержать пароли, закрытые ключи, полные персональные данные и декодированные потоки. Для расследования достаточно хэша, размера, номера страницы, номера объекта, типа фильтра и класса исключения. Исходный файл сохраняют только по утверждённой политике. Ошибки, вызывающие чрезмерное потребление ресурсов, добавляют в тестовый корпус после удаления чувствительного содержимого.

Зависимости ImageIO и криптографические библиотеки обновляют вместе с основной системой, потому что уязвимость может находиться не в PDFBox, а в декодере. Состав сборки фиксируют lock-файлом или SBOM, проверяют подписи артефактов и воспроизводимость. Загрузка JAR с случайного каталога недопустима для серверной среды: имя файла и совпадающая версия не подтверждают происхождение.

Тестирование и контроль результата

Автоматические тесты для PDF должны проверять не только отсутствие исключения. Минимальный набор включает число страниц, размеры и поворот листов, наличие ожидаемого текста, полей, закладок, метаданных и вложений. Для защищённого файла дополнительно открывают документ с разными паролями и проверяют флаги разрешений. Для подписанного — валидируют ByteRange и криптографический контейнер. Такой набор ловит ошибки, которые визуальный просмотр одной первой страницы не обнаруживает.

Сравнение PDF по байтам редко полезно: дата создания, идентификатор документа, порядок косвенных объектов, сжатие и подмножества шрифтов могут меняться при одинаковом внешнем результате. Структурный тест извлекает нужные свойства через API и сравнивает их семантически. Визуальный тест рендерит страницы с фиксированным DPI и сравнивает изображения с допустимым порогом, учитывая небольшие различия сглаживания между JDK и операционными системами.

Для генератора документов создают золотой набор из коротких файлов, каждый из которых проверяет отдельную функцию: кириллицу, длинные строки, таблицу, прозрачное PNG, JPEG, форму, закладку, вложение, альбомную страницу, нестандартный CropBox и шифрование. Большой универсальный PDF сложнее диагностировать: при изменении картинки неясно, какой компонент виноват. Небольшие образцы дают точное сообщение и быстро выполняются в CI.

Регрессионный набор должен содержать реальные проблемные документы, но перед хранением их очищают от персональных и коммерческих данных. Для каждого фиксируют ожидаемый исход: успешное извлечение, контролируемый отказ, предупреждение о недоступном кодеке или ограничение ресурсов. Исправление не считается завершённым, пока тест воспроизводит старую ошибку на прежней сборке и проходит на новой.

После обновления Java, ImageIO-плагина или криптографической библиотеки повторяют рендер, экспорт изображений, подпись и Preflight. Даже без изменения кода PDFBox результат может поменяться из-за декодера JPEG, набора системных шрифтов или провайдера безопасности. В контейнере полезно фиксировать список установленных шрифтов и локаль, иначе документ с отсутствующим встроенным шрифтом будет выглядеть по-разному в средах разработчика и производства.

Контроль производительности выполняют на заранее определённых классах документов: обычный текстовый отчёт, скан с JBIG2, каталог с JPEG 2000, чертёж с векторами, форма и повреждённый файл. Измеряют медиану и высокий процентиль времени, пиковую память, временный диск и число открытых дескрипторов. Ограничение считают частью функции: обработчик обязан завершить слишком тяжёлый файл предсказуемой ошибкой, а не доводить процесс до общей нехватки памяти.

Для командного конвейера полезен манифест результата. В нём сохраняют хэш исходника, выбранную команду без секретов, диапазон страниц, DPI, формат, код возврата, хэш выхода и предупреждения. Манифест позволяет воспроизвести преобразование и понять, почему два файла различаются. Пароли и закрытые ключи в него не включают; вместо этого записывают идентификатор секрета или политики.

Ручная приёмка начинается с пограничных страниц: первой, последней, страницы перед и после объединения, начала и конца диапазона, листов с отличающимся размером и поворотом. Затем проверяют поиск по кириллице, копирование текста, печать, форму в двух просмотрщиках и отсутствие лишних вложений. Для водяного знака убеждаются, что он виден на светлом и тёмном фоне и не перекрывает обязательные реквизиты.

Визуальное совпадение не доказывает сохранность содержания. Белая маска может скрывать текст, который остаётся извлекаемым; сплющенная форма может выглядеть правильно, но потерять значение поля для системы; объединение может сохранить страницы, но сломать закладки. Поэтому финальный тест сочетает рендер, извлечение, анализ объектной модели и проверку в независимом приложении.

Когда Apache PDFBox подходит лучше всего

PDFBox особенно полезен, когда операция должна повторяться без ручного участия и быть встроена в Java-процесс: формирование договоров, разбор входящих счетов, создание миниатюр, слияние приложений, заполнение AcroForm, добавление штампов, проверка PDF/A и извлечение метаданных. Объектная модель даёт доступ к низкому уровню PDF, а PDFDebugger позволяет увидеть, что именно записал генератор или прислал контрагент.

Инструмент не заменяет визуальный редактор, OCR-систему, HTML/CSS-движок и универсальный валидатор доступности. Когда задача состоит в ручном исправлении абзацев, перемещении объектов или распознавании сканов, разумнее использовать специализированное приложение и подключать PDFBox для последующей автоматизации. Когда нужен полноценный веб-макет из HTML, лучше выбрать движок, который изначально понимает CSS, а PDFBox оставить для объединения, подписи, анализа или постобработки результата.

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

Финальная проверка должна сочетать несколько представлений. PDF открывают и рендерят, из него повторно извлекают текст, пересчитывают страницы, исследуют формы и подписи, а для архивного профиля запускают независимый валидатор. Такой подход использует сильную сторону Apache PDFBox — точное программное управление PDF — и одновременно учитывает ограничения формата, шрифтов, кодеков и разных просмотрщиков.