Технічна стаття

Зведення форм XFA до AcroForm у Delphi за допомогою HotPDF

Дві форми можуть нести однакові поля й поводитися абсолютно по-різному. AcroForm тримає свої поля як звичайні об'єкти PDF, що лежать поверх справжнього вмісту сторінки, тож будь-який сумісний читач малює її. Динамічна форма XFA тримає як PDF майже нічого: поля, розмітка, навіть геометрія сторінки живуть у пакеті XML, а видимі сторінки породжує в момент відкриття рушій розмітки, який широко постачала лише Adobe. Згодуйте такий файл вебпереглядачу, архівному рендереру чи екстрактору тексту — і форми ви не отримаєте. Ви отримаєте одну сіру сторінку з написом "Please wait... If this message is not eventually replaced by the proper contents of the document, your PDF viewer may not be able to display this type of document." Будь-хто, хто приймав державні чи страхові документи, впізнає цю сторінку з першого погляду

Цей заповнювач — не пошкодження. Це саме те, що формат приписує, коли немає процесора XFA, а станом на 2026 рік це стосується майже кожного переглядача поза настільним Acrobat. Тож практичний хід — перетворити динамічну форму на звичайний AcroForm ще до того, як вона потрапить кудись далі за конвеєром. HotPDF, бібліотека PDF від losLab для Delphi та C++Builder, виконує це перетворення прямо в коді, перебудовуючи XML-форму як нативні поля на нативних сторінках

HotPDF: порівняння поруч AcroForm, чиї сторінки, віджети та значення живуть у самому PDF, і динамічної XFA-форми, що без XFA-рушія показує лише сторінку-заповнювач
AcroForm тримає сторінки, віджети та значення всередині PDF, тож будь-який читач малює форму, тоді як динамічний XFA ховає їх за заглушкою Please-wait

Чому дві моделі не можуть співіснувати

AcroForm визначено в ISO 32000-1 §12.7. Кожне поле — це об'єкт PDF із анотацією-віджетом і потоком зовнішнього вигляду, сторінка — це справжній вміст PDF, а дані їдуть поверх нього. XFA перевертає це: форма — це XML-документ, пакет XDP, збережений у записі /XFA словника AcroForm, а сторінки PDF динамічної форми несуть лише заповнювач "Please wait" і нічого більше, бо справжній вміст ніколи не серіалізувався як PDF. Читач обробляє файл за однією моделлю чи за іншою. Ігноруйте запис /XFA — і побачите порожню оболонку; поважайте його без рушія XFA — і побачите попередження. ISO 32000-2 закрив цю дискусію, вилучивши XFA з PDF 2.0, і саме тому "конвертувати, поки ще можемо" перетворилося з крайнього випадку на рутинну політику прийому вхідних даних

Перш ніж щось конвертувати, класифікуйте це, бо не кожен файл XFA показує заповнювач. Статичні форми XFA постачають попередньо відрендерені сторінки PDF поряд з XML, тож вони відображаються всюди й поводяться неправильно лише під час заповнення. Динамічні форми постачають лише заповнювач і непридатні до використання, поки не конвертовані. Довіряйте лише документу, ніколи розширенню чи відправнику. Файл, що показує справжній вміст у переглядачі не від Adobe, але й досі несе запис /XFA, — статичний або гібридний; файл, що показує сторінку попередження, — динамічний. Записуйте, до якої категорії потрапив кожен вхідний файл. Ці два види потім ламаються по-різному, і тікет про порожню архівну форму закривається за секунди, коли журнал прийому вже містить запис "dynamic XFA, converted, 47 fields mapped, 2 warnings"

Перетворення завантаженого документа XFA на нативні поля

Перетворення виконується над документом, що вже в пам'яті. FlattenLoadedXFA розбирає шаблон XFA та його пакети даних, компонує форму й перебудовує її як поля AcroForm на справжніх сторінках PDF:

var
  Pdf: THotPDF;
  MappedCount, I: Integer;
  Warnings: TStrings;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('dynamic_xfa.pdf');
    MappedCount := Pdf.FlattenLoadedXFA(True);   // True = поля лишаються редагованими
    Warnings := Pdf.XFAFlattenWarnings;
    for I := 0 to Warnings.Count - 1 do
      Log('XFA flatten warning: ' + Warnings[I]); // незіставлені елементи
    Pdf.SaveLoadedDocument('native_acroform.pdf');
    Log(Format('Mapped %d fields', [MappedCount]));
  finally
    Pdf.Free;
  end;
end;

Значення, що повертається, і список попереджень — це вивід, а не шум налагодження, тож зберігайте обидва. Перетворення за своєю природою втрачає інформацію: скриптинг XFA, обчислювані поля й поведінка динамічних підформ не мають відповідника в AcroForm, і XFAFlattenWarnings називає кожен елемент шаблону, що не зіставився. Заархівуйте конвертований файл без його списку попереджень — і колись ви витріщатиметеся на порожній бокс підсумків в архівній копії, не маючи запису про причину. Прапорець Editable контролює, чи лишаються нові поля заповнюваними. Передавайте True, коли люди продовжуватимуть працювати з формою потім, і фіксуйте значення, коли мета — заморожений запис

Перевірка перетворення частково візуальна, частково структурна, і потрібні обидві половини. Структурна половина проста: підтвердьте, що кількість полів збігається з MappedCount. Візуальна половина — та, що ловить справжні пошкодження. Відкрийте вихідну форму в настільному Acrobat, який досі лишається єдиним переглядачем із запущеним рушієм XFA, поряд із конвертованим файлом у звичайному читачі, і порівняйте значення та розмітку принаймні на одному заповненому зразку на шаблон. Дата, яку рушій XFA показував як 2026-06-11, у копії AcroForm може приземлитися як сире, неформатоване значення, і лише ваші очі це помітять

Класифікаційний потік XFA-документів у Delphi: файли, що рендерять справжній вміст поза Acrobat, — статичні або гібридні, а файли зі сторінкою «Please-wait» — динамічні й потребують конвертації
Гібридні форми доводять себе, рендерячи справжній вміст у не-Adobe переглядачах, тоді як динамічні форми викривають себе однією сторінкою-заглушкою

Коли вхідні дані — пакет XDP

Не кожне завдання починається із заповненого PDF. Іноді ви отримуєте пакет XDP окремо, експортований з інструмента проєктування форм або переданий партнерською системою. ApplyXFAAsAcroForm пропускає крок завантаження й застосовує пакет прямо до поточного документа:

Конвеєр HotPDF, що сплощує завантажений динамічний XFA-документ у редаговані поля AcroForm у Delphi, виносячи непромаплені сценарії та обчислювані поля через XFAFlattenWarnings
FlattenLoadedXFA розбирає та переливає пакети XDP у редаговні AcroForm-поля, а XFAFlattenWarnings фіксує кожен елемент, який не вдалося відобразити
XDPBytes := TFile.ReadAllBytes('benefit-claim.xdp');
MappedCount := Pdf.ApplyXFAAsAcroForm(XDPBytes, True);

Та сама група викликів працює й у зворотному напрямку — для рідкіснішого випадку, коли треба видавати XFA, а не споживати його. AddXFAPacket приєднує окремі іменовані пакети на кшталт 'xdp' чи 'config'. SetXFADocument встановлює повне однопотокове навантаження одним викликом. ClearXFAPackets очищає реєстрацію, щоб можна було почати заново, а AddXFASignaturePacket вбудовує матеріал XAdES для робочих процесів, що підписують XML-дані форми напряму. Створення XFA у 2026 році — нішева потреба, майже завжди продиктована якимось застарілим споживачем, що відмовляється від будь-чого іншого, але коли контракт її називає, ці виклики зводять її до вибору конфігурації замість окремого інструмента

Інше значення слова "flatten"

Слово "flatten" збиває з пантелику чимало розмов, бо воно позначає зовсім іншу операцію: випалювання зовнішнього вигляду полів AcroForm у потік вмісту сторінки, доки не лишиться жодного інтерактивного об'єкта. HotPDF наразі не має API для цього, і краще дізнатися про це зараз, а не на середині проєкту. Замість цього бібліотека дає блокування на рівні поля в момент його створення, підкріплене дозволами документа:

// Заблокувати значення в момент створення поля: текстове поле лише для читання
Pdf.CurrentPage.AddTextField('CaseNumber', 'BC-2026-0117',
  Rect(50, 700, 220, 720), 0, [ffReadOnly]);

// Про всяк випадок: обмежити заповнення форми на рівні всього документа
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.OwnerPassword := 'records-owner';
Pdf.ProtectOptions := [prPrint, prInformationCopy, prExtractContent];
// дозвіл на заповнення утримано: prFillAnnotations відсутній у наборі

Будьте чіткими щодо того, що це дає, а що ні. Поле лише для читання все ще залишається об'єктом форми. Воно з'являється в панелі полів переглядача, його значення читається через API форми, а інструмент, що переписує файл, може знову зняти прапорець лише для читання. Прапорці дозволів піднімають планку, але залежать від того, чи вирішить переглядач їх поважати, — це обмеження ISO 32000-1 формулює прямо. Коли регулятор наполягає, що архівний запис узагалі не має містити об'єктів форми, чесна відповідь із HotPDF сьогодні — перебудувати документ: зчитати значення, а потім намалювати їх як звичайний вміст TextOut на новій сторінці, замість того щоб видавати прапорці лише для читання за сплощення. Одна річ, яку варто пам'ятати на шляху з дозволами: CryptKeyLength має бути встановлено до BeginDoc; решта — у нашій статті про шифрування AES-256 і дозволи

Що XFA означає для архівної відповідності

І PDF/A, і PDF/X повністю відхиляють XFA. Тож конвеєр, що живить архів ISO 19005, мусить спершу конвертувати, і порядок тут не підлягає обговоренню: завантажити, FlattenLoadedXFA, зберегти, а потім запустити архівну генерацію чи перевірку над результатом AcroForm. Не сприймайте конвертацію як доказ відповідності. Вона виправляє модель форми, залишаючи шрифти, колір і метадані точно такими, якими вони були, тож перевіряйте вивід через veraPDF, перш ніж йому довіряти. Щойно форма опиняється на боці AcroForm, її поведінка отримує власний набір контролів. Тригери JavaScript, дії надсилання й скрипти перевірки розглянуто в статті про поля та дії AcroForm у HotPDF

Показані тут API реєстрації, конвертації та роботи з формами XFA постачаються з HotPDF Delphi Component для Delphi та C++Builder, документація якого відстежує набір функцій XFA у міру його розвитку в останніх релізах