Техническая статья

Экспорт и импорт XFDF в Delphi с помощью PDFium Component

Компонент PDFium Component для Delphi и Lazarus обменивается данными форм и аннотациями через XFDF — формат обмена XML, определенный стандартом ISO 19444-1. Метод TPdf.ExportXFDF сериализует значения всех полей форм и все поддерживаемые аннотации в загруженном документе в файл XFDF или поток TStream; метод TPdf.ImportXFDF считывает документ XFDF обратно, воссоздавая аннотации на их страницах и применяя значения полей. Этот двусторонний цикл обмена (round-trip) решает две задачи, которые рано или поздно возникают в любом приложении для работы с документами: когда рецензент делает пометки в PDF в Acrobat и отправляет вам комментарии для объединения, или когда правила соответствия требуют, чтобы данные форм хранились в отдельном, отслеживаемом файле, а не встраивались в сам PDF

Поддерживает ли PDFium импорт и экспорт XFDF?

Сама библиотека PDFium этого не умеет. Собственный C-API не содержит функций XFDF или FDF вообще — ни один из общедоступных заголовков не умеет ни читать, ни писать эти форматы, поэтому любое оборачивание функций (wrapping) здесь не поможет. В связи с этим PDFium Component реализует весь движок XFDF на Pascal в отдельном модуле, который создает и анализирует XML непосредственно поверх существующей модели данных аннотаций и полей форм TPdf. Модуль записи формирует XML вручную, а модуль чтения представляет собой написанный вручную синтаксический анализатор на основе конечного автомата. Это исключает зависимость от модулей Delphi XMLDoc или FPC DOM и обеспечивает одинаковое поведение в Delphi и Lazarus

Этот выбор архитектуры важен при оценке альтернатив. Если вы вызывали сырые функции FPDF_* из Pascal, XFDF станет препятствием: эта возможность просто отсутствует в DLL. Компонент преодолевает эту преграду, рассматривая XFDF как задачу чистой сериализации — собрать значения полей и записи аннотаций, которые библиотека уже умеет читать, записать их в виде стандартного XML и выполнить обратный процесс при импорте

Что содержит файл XFDF?

Стандарт ISO 19444-1 определяет XFDF как представление FDF в формате XML, и его содержимое делится на два блока верхнего уровня. Элемент <fields> содержит иерархические имена полей форм и их значения — все, что пользователь ввел, выбрал или отметил в AcroForm. Элемент <annots> содержит аннотации: TPdf.ExportXFDF выводит 18 подтипов (text, highlight, underline, strikeout, squiggly, line, circle, square, caret, polygon, polyline, stamp, ink, freetext, fileattachment, sound, link и redact). Аннотации элементов управления (Widget) намеренно отсутствуют в <annots>, поскольку их данные передаются в <fields>, а внутренние подтипы просмотрщика вроде Popup не входят в структуру XFDF

Для обеспечения точности двустороннего обмена запись TPdfAnnotation была расширена метаданными, которые ожидает XFDF: Name (уникальный идентификатор NM), Subject, ModificationDate, CreationDate, Icon, Opacity, конечные точки линий, вершины полигонов и полилиний, а также контуры рукописного ввода (ink). Каждое новое поле связано с логическим флагом-стражем Has*, поэтому код, создававший аннотации в прошлых версиях, продолжает успешно компилироваться и генерирует те же словари — неустановленные свойства в файл не записываются. Если вы уже создаете разметку программно, та же запись, которую вы используете для текстовых аннотаций с координатными точками QuadPoints, теперь содержит все необходимые для XFDF данные

Как экспортировать данные форм и аннотации в XFDF?

Один вызов обрабатывает весь документ. Загрузите PDF с включенным заполнением форм, вызовите ExportXFDF, и компонент обойдет каждую страницу, соберет значения полей и аннотации, а затем запишет XML. Возвращаемое значение — это количество записанных байтов UTF-8, что служит удобной базовой проверкой в логах

var
  Pdf: TPdf;
  Bytes: Integer;
begin
  Pdf := TPdf.Create(Self);
  Pdf.FormFill := True;            // needed so field values are live
  Pdf.FileName := 'expense-report.pdf';
  Pdf.Active := True;
  Bytes := Pdf.ExportXFDF('expense-report.xfdf');
  ShowMessage(Format('%d bytes of XFDF written', [Bytes]));
end;

Оба направления имеют TStream унаследованный метод, поэтому ничего не заставляет использовать временный файл на диске. Экспорт в TMemoryStream является естественным решением, когда XFDF направляется в HTTP-ответ, blob-поле базы данных или запись подписанного архива

var
  Buffer: TMemoryStream;
begin
  Buffer := TMemoryStream.Create;
  try
    Pdf.ExportXFDF(Buffer);        // same XML, no file involved
    Buffer.Position := 0;
    // hand the stream to a web response, blob column, or zip entry
  finally
    Buffer.Free;
  end;
end;

Как ImportXFDF объединяет комментарии с документом?

Импорт завершает рабочий процесс рецензирования. Коллега делает пометки в договоре в Acrobat, экспортирует комментарии в XFDF и отправляет вам файл XML размером в несколько килобайт вместо второй копии PDF. Метод TPdf.ImportXFDF анализирует этот файл, создает каждую аннотацию на странице, указанной в ее атрибуте page, и записывает значения полей в соответствующие управляющие элементы. Функция возвращает общее количество примененных полей и аннотаций, чтобы интерфейс мог точно подтвердить объем импорта

var
  Applied: Integer;
begin
  Applied := Pdf.ImportXFDF('review-comments.xfdf');
  StatusBar.SimpleText :=
    Format('%d fields and annotations merged', [Applied]);
end;

Парсер представляет собой написанный вручную сканер тегов, ориентированный на устойчивость к ошибкам: неизвестные элементы пропускаются, инструкции по обработке, объявления DOCTYPE и комментарии игнорируются, нераспознанные флаги отбрасываются, а значения атрибутов декодируются с поддержкой всех XML-сущностей и числовых ссылок на символы, включая суррогатные пары UTF-16. Файлы XFDF, созданные в Acrobat, веб-просмотрщике или других инструментах, объединяются без лишних проблем. Как только аннотации попадают в документ, процесс рецензирования продолжается с помощью механизмов статусов и ответов, описанных в статье о создании системы рецензирования аннотаций в PDFium Component, а импортированные значения полей участвуют в логике перехода по клавише Tab, о чем рассказано в навигации по полям форм

Почему десятичные разделители важны для XFDF?

Каждая аннотация в XFDF позиционируется строками координат вида rect="70.5,540,200,560", и стандарт ISO 19444-1 требует использования точки в качестве десятичного разделителя. Форматирование чисел по умолчанию в Delphi следует системным настройкам Windows, поэтому во французской или немецкой локали обычный вызов FloatToStr превратит 70.5 в 70,5, что сделает список координат нечитаемым для других процессоров. PDFium Component нормализует каждое записываемое число: десятичный разделитель локали заменяется точкой, а конечные нули отбрасываются. Поэтому экспортируемый файл получается побайтово идентичным независимо от того, создан он на машине с en-US или de-DE локалью. Если вам когда-либо приходилось отлаживать программу работы с PDF, которая отлично функционировала в офисе, но сбоила у европейских клиентов — вы сталкивались именно с этим классом багов, и полезно знать, что компонент решает эту проблему за вас

Каковы ограничения двустороннего обмена?

Стоит честно обозначить два ограничения. Во-первых, XFDF переносит данные аннотаций, но не их готовый внешний вид — потоки внешнего вида (appearance streams) не входят в формат, поэтому принимающий просмотрщик воссоздает вид аннотации на основе ее свойств. Выделение текста или прямоугольник отрисуются корректно везде; штамп с нестандартным оформлением откатится к тому стилю, который рисует целевой просмотрщик для этого имени штампа. Во-вторых, лежащий в основе API PDFium содержит сеттеры для строк, но не имеет методов записи чисел аннотаций или геометрии путей, поэтому свойства Opacity, конечные точки линий, вершины и рукописный ввод экспортируются только в одну сторону: компонент сохраняет их при записи XFDF, но не может записать обратно в PDF при импорте. Значения полей, содержимое, цвета, координаты rect, QuadPoints, даты и метаданные автора корректно передаются в обоих направлениях

Для практического ознакомления пример XfdfLab поставляется в комплекте демонстрационных программ для Delphi, C++Builder и Lazarus, связывая оба вызова с кнопками поверх любого открытого PDF. Поддержка XFDF включена в текущий релиз PDFium Component, наряду с API аннотаций и форм, на которых она базируется