PDFium Component для Delphi и Lazarus обменивается данными форм и аннотациями через XFDF — формат обмена на XML, определённый в ISO 19444-1. TPdf.ExportXFDF сериализует значение каждого поля формы и каждую поддерживаемую аннотацию загруженного документа в файл XFDF или в TStream; TPdf.ImportXFDF читает документ XFDF обратно, заново создавая аннотации на их страницах и применяя значения полей. Один этот обмен покрывает два рабочих процесса, до которых рано или поздно доходит любое приложение для работы с документами: рецензент размечает PDF в Acrobat и присылает вам комментарии для слияния, либо правило комплаенса требует, чтобы данные формы жили в отдельном, сравнимом и проверяемом файле, а не были запечены в сам PDF
Поддерживает ли PDFium импорт и экспорт XFDF?
Сама библиотека PDFium — нет. В нативном C API вообще нет функций для XFDF или FDF: ни один её публичный заголовок не читает и не пишет ни тот, ни другой формат, поэтому никакой обёрткой туда не добраться. Поэтому PDFium Component реализует весь движок XFDF на Pascal — в отдельном модуле, который строит и разбирает XML напрямую поверх существующей модели данных аннотаций и полей формы TPdf. Писатель выдаёт XML вручную, а читатель представляет собой написанный вручную парсер на конечном автомате, так что возможность не добавляет зависимости ни от Delphi XMLDoc, ни от модулей DOM в FPC и ведёт себя одинаково в Delphi и Lazarus
Этот выбор архитектуры важен, когда вы оцениваете альтернативы. Если вы вызываете сырые функции FPDF_* из Pascal, XFDF для вас стена: этой возможности попросту нет в DLL. Компонент проходит сквозь стену, трактуя XFDF как чистую задачу сериализации: собрать значения полей и записи аннотаций, которые обёртка и так умеет читать, выписать их стандартным XML и обратить процесс при импорте
Что содержит файл XFDF?
ISO 19444-1 определяет XFDF как XML-представление FDF, и его полезная нагрузка делится на два блока верхнего уровня. Элемент <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, конечные точки линий, вершины многоугольников и ломаных и пути чернильных росчерков. Каждое новое поле идёт в паре с логическим сторожем Has*, поэтому код, строивший аннотации против прежних версий, продолжает компилироваться и продолжает выдавать те же словари — незаданное поле никогда не записывается. Если вы уже создаёте разметку программно, та же запись, которую вы используете для текстовых аннотаций разметки с quad points, теперь несёт всё, что нужно XFDF
Как экспортировать данные формы и аннотации в XFDF?
Один вызов делает весь документ. Загрузите PDF с включённым заполнением форм, вызовите ExportXFDF, и компонент обойдёт каждую страницу, соберёт значения полей и аннотации и запишет XML. Возвращаемое значение — число записанных байтов UTF-8, что даёт дешёвую проверку разумности в журналах
var
Pdf: TPdf;
Bytes: Integer;
begin
Pdf := TPdf.Create(Self);
Pdf.FormFill := True; // нужно, чтобы значения полей были живыми
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-ответ, в блоб базы данных или в запись подписанного архива
var
Buffer: TMemoryStream;
begin
Buffer := TMemoryStream.Create;
try
Pdf.ExportXFDF(Buffer); // тот же XML, без всякого файла
Buffer.Position := 0;
// отдайте поток веб-ответу, колонке с блобом или записи zip
finally
Buffer.Free;
end;
end;
Как ImportXFDF вливает комментарии обратно в документ?
Сторона импорта — то место, где замыкается рабочий процесс рецензирования. Коллега размечает договор в Acrobat, экспортирует комментарии в XFDF и присылает вам несколько килобайт XML вместо второй копии PDF. TPdf.ImportXFDF разбирает этот файл, создаёт каждую аннотацию на странице, названной её атрибутом page, и записывает значения полей в соответствующие аннотации widget. Функция возвращает суммарное число применённых полей и аннотаций, поэтому интерфейс может точно подтвердить, сколько всего пришло
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, а импортированные значения полей участвуют в обычной логике порядка табуляции, разобранной в статье навигация по полям формы
Почему для 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 перевозит данные аннотаций, а не отрисованный вид: потоки внешнего вида в формат не входят, поэтому принимающий просмотрщик заново порождает облик каждой аннотации из её свойств. Выделение или квадрат отрисуются верно везде; штамп с пользовательским внешним видом откатится к тому, что целевой просмотрщик рисует для штампа с таким именем. Во-вторых, у нижележащего API PDFium есть сеттеры для строк, но нет ни для чисел аннотаций, ни для геометрии путей, поэтому Opacity, конечные точки линий, вершины и чернильные росчерки доступны только на экспорт: компонент честно сохраняет их при записи XFDF, но не может записать их обратно в PDF при импорте. Значения полей, содержимое, цвета, прямоугольники, quad points, даты и метаданные идентичности проходят обмен в обе стороны
Для практического старта пример XfdfLab поставляется в наборах демонстраций для Delphi, C++Builder и Lazarus и вешает оба вызова на кнопки поверх любого открытого вами PDF. Поддержка XFDF включена в текущий выпуск PDFium Component наряду с API аннотаций и форм, на которых она построена