Artykuł techniczny

HotPDF: XFA, AcroForm i decyzje o spłaszczaniu w Delphi

Dwa formularze mogą nieść te same pola i zachowywać się zupełnie inaczej. AcroForm trzyma swoje pola jako zwykłe obiekty PDF siedzące na prawdziwej treści strony, więc każdy zgodny czytnik go narysuje. Dynamiczny formularz XFA trzyma jako PDF prawie nic: pola, układ, nawet geometria strony żyją w pakiecie XML, a widoczne strony są produkowane w chwili otwarcia przez silnik układu, który szeroko dostarczał tylko Adobe. Podaj ten plik przeglądarce webowej, silnikowi archiwizującemu albo ekstraktorowi tekstu, a nie dostaniesz formularza. Dostaniesz jedną szarą stronę z napisem "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." Każdy, kto przyjmował papierologię rządową albo ubezpieczeniową, rozpoznaje tę stronę na pierwszy rzut oka

Symbol zastępczy to nie uszkodzenie. To dokładnie to, co format nakazuje, gdy nie ma procesora XFA, a od 2026 roku to opisuje niemal każdą przeglądarkę poza desktopowym Acrobatem. Więc praktycznym ruchem jest przekonwertowanie dynamicznego formularza na zwykły AcroForm, zanim dotrze do czegokolwiek dalej w potoku. HotPDF, biblioteka PDF losLab dla Delphi i C++Builder, wykonuje tę konwersję w kodzie, odbudowując formularz XML jako natywne pola na natywnych stronach

HotPDF: Porównanie obok siebie AcroForm, którego strony, widżety i wartości żyją w samym PDF, i dynamicznego formularza XFA, który pokazuje stronę zastępczą bez silnika XFA
AcroForm trzyma strony, widżety i wartości wewnątrz PDF, więc każdy czytnik narysuje formularz, podczas gdy dynamiczny XFA chowa je za placeholderem Please-wait

Dlaczego oba modele nie mogą współistnieć

AcroForm jest zdefiniowany w ISO 32000-1 §12.7. Każde pole to obiekt PDF z adnotacją widżetu i strumieniem wyglądu, strona to prawdziwa treść PDF, a dane jeżdżą na niej. XFA odwraca to: formularz to dokument XML, pakiet XDP przechowywany we wpisie /XFA słownika AcroForm, a strony PDF formularza dynamicznego niosą symbol zastępczy "Please wait" i nic więcej, bo prawdziwa treść nigdy nie została zserializowana jako PDF. Czytnik przetwarza plik według jednego modelu albo drugiego. Zignoruj wpis /XFA, a zobaczysz pustą powłokę; uszanuj go bez silnika XFA, a zobaczysz ostrzeżenie. ISO 32000-2 zakończył tę debatę, porzucając XFA z PDF 2.0, co jest głównym powodem, dla którego "konwertuj, póki jeszcze można" zmieniło się z przypadku brzegowego w rutynową politykę przyjmowania

Zanim cokolwiek przekonwertujesz, sklasyfikuj to, bo nie każdy plik XFA pokazuje symbol zastępczy. Statyczne formularze XFA dostarczają wstępnie wyrenderowane strony PDF obok XML, więc wyświetlają się wszędzie i psują się dopiero przy wypełnianiu. Formularze dynamiczne dostarczają sam symbol zastępczy i są bezużyteczne aż do konwersji. Rzeczą, której trzeba zaufać, jest dokument, nigdy rozszerzenie ani nadawca. Plik, który renderuje prawdziwą treść w przeglądarce nie-Adobe, a mimo to wciąż niesie wpis /XFA, jest statyczny albo hybrydowy; plik, który pokazuje stronę ostrzeżenia, jest dynamiczny. Zapisz, do jakiego kubełka trafił każdy przyjmowany plik. Oba rodzaje psują się później na różne sposoby, a zgłoszenie o pustym zarchiwizowanym formularzu zamyka się w sekundy, gdy dziennik przyjęcia już mówi "dynamiczny XFA, przekonwertowany, zmapowano 47 pól, 2 ostrzeżenia"

Konwersja wczytanego dokumentu XFA na pola natywne

Konwersja działa na dokumencie już wczytanym do pamięci. FlattenLoadedXFA parsuje szablon XFA i jego pakiety danych, układa formularz i odbudowuje go jako pola AcroForm na prawdziwych stronach 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 = pola pozostają edytowalne
    Warnings := Pdf.XFAFlattenWarnings;
    for I := 0 to Warnings.Count - 1 do
      Log('XFA flatten warning: ' + Warnings[I]); // niezmapowane elementy
    Pdf.SaveLoadedDocument('native_acroform.pdf');
    Log(Format('Mapped %d fields', [MappedCount]));
  finally
    Pdf.Free;
  end;
end;

Wartość zwracana i lista ostrzeżeń to wyjście, nie szum diagnostyczny, więc zachowaj oba. Konwersja z natury traci informacje: skrypty XFA, pola obliczane i zachowanie dynamicznych podformularzy nie mają odpowiednika w AcroForm, a XFAFlattenWarnings nazywa każdy element szablonu, który się nie zmapował. Zarchiwizuj przekonwertowany plik bez listy ostrzeżeń, a kiedyś będziesz się gapić na puste pole sumy w zarchiwizowanej kopii bez żadnego zapisu, dlaczego tak jest. Flaga Editable kontroluje, czy nowe pola pozostają wypełnialne. Przekaż True, gdy ludzie będą dalej pracować z formularzem, a zablokuj wartości, gdy celem jest zamrożony rekord

Sprawdzanie konwersji jest częściowo wizualne, częściowo strukturalne, i potrzebujesz obu połówek. Połówka strukturalna jest łatwa: potwierdź, że liczba pól zgadza się z MappedCount. Połówka wizualna to ta, która łapie prawdziwe uszkodzenia. Otwórz formularz źródłowy w desktopowym Acrobacie, wciąż jedynej przeglądarce uruchamiającej silnik XFA, obok przekonwertowanego pliku w zwykłym czytniku, i porównaj wartości oraz układ na co najmniej jednej wypełnionej próbce na szablon. Data, którą silnik XFA wyświetlił jako 2026-06-11, może wylądować w kopii AcroForm jako surowa, niesformatowana wartość, i tylko twoje oczy to złapią

Przepływ klasyfikacji przyjęcia dokumentów XFA w Delphi: pliki renderujące prawdziwą zawartość poza Acrobatem są statyczne lub hybrydowe, podczas gdy pliki pokazujące stronę Proszę czekać są dynamiczne i muszą zostać skonwertowane
Formularze hybrydowe dowodzą siebie, renderując prawdziwą treść w czytnikach spoza Adobe, podczas gdy formularze dynamiczne zdradzają się samą stroną placeholdera

Gdy wejściem jest pakiet XDP

Nie każde zadanie zaczyna się od wypełnionego PDF. Czasem dostajesz sam pakiet XDP, wyeksportowany z narzędzia do projektowania formularzy albo przekazany przez system partnera. ApplyXFAAsAcroForm pomija krok wczytywania i aplikuje pakiet wprost do bieżącego dokumentu:

Potok HotPDF spłaszczający wczytany dynamiczny dokument XFA w edytowalne pola AcroForm w Delphi, wynosząc niezmapowane skrypty i pola wyliczane przez XFAFlattenWarnings
FlattenLoadedXFA parsuje i przełada pakiety XDP w edytowalne pola AcroForm, a XFAFlattenWarnings odnotowuje każdy element, którego nie udało się zmapować
XDPBytes := TFile.ReadAllBytes('benefit-claim.xdp');
MappedCount := Pdf.ApplyXFAAsAcroForm(XDPBytes, True);

Ta sama grupa wywołań działa też w drugą stronę, dla rzadszego przypadku, gdy trzeba wyemitować XFA, a nie go skonsumować. AddXFAPacket dołącza pojedyncze nazwane pakiety, takie jak 'xdp' albo 'config'. SetXFADocument instaluje kompletny jednostrumieniowy ładunek jednym wywołaniem. ClearXFAPackets czyści rejestrację, żeby dało się zacząć od nowa, a AddXFASignaturePacket osadza materiał XAdES dla przepływów, które podpisują dane formularza XML bezpośrednio. Produkowanie XFA w 2026 roku to niszowa potrzeba, niemal zawsze wymuszona przez jednego przestarzałego konsumenta, który nie akceptuje niczego innego, ale gdy kontrakt tego wymaga, te wywołania sprowadzają to do wyboru konfiguracji zamiast osobnego narzędzia

Inne znaczenie słowa "spłaszczanie"

Słowo "spłaszczanie" (flatten) potyka mnóstwo rozmów, bo nazywa zupełnie inną operację: wypalanie wyglądów pól AcroForm w strumień treści strony, aż nie zostanie żaden obiekt interaktywny. HotPDF nie ma dziś API do tego, i lepiej wiedzieć to teraz niż w połowie projektu. Zamiast tego biblioteka daje ci blokowanie na poziomie pola w chwili jego tworzenia, wsparte uprawnieniami dokumentu:

// Zablokuj wartość przy tworzeniu pola: pole tekstowe tylko do odczytu
Pdf.CurrentPage.AddTextField('CaseNumber', 'BC-2026-0117',
  Rect(50, 700, 220, 720), 0, [ffReadOnly]);

// Pas i szelki: ogranicz wypełnianie formularza w całym dokumencie
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.OwnerPassword := 'records-owner';
Pdf.ProtectOptions := [prPrint, prInformationCopy, prExtractContent];
// uprawnienie wypełniania wstrzymane: prFillAnnotations nie ma w zbiorze

Bądź jasny co do tego, co to daje, a czego nie. Pole tylko do odczytu wciąż jest obiektem formularza. Pojawia się w panelu pól przeglądarki, jego wartość jest czytelna przez API formularza, a narzędzie przepisujące plik może ponownie wyczyścić flagę tylko do odczytu. Flagi uprawnień podnoszą poprzeczkę, ale zależą od tego, czy przeglądarka zdecyduje się je uszanować, ograniczenie, które ISO 32000-1 stwierdza wprost. Gdy regulator nalega, żeby zarchiwizowany rekord w ogóle nie zawierał obiektów formularza, uczciwą odpowiedzią z HotPDF dziś jest odbudowanie dokumentu: odczytaj wartości, a potem narysuj je jako zwykłą treść TextOut na świeżej stronie, zamiast przebierać flagi tylko do odczytu za spłaszczanie. Jedną rzeczą do zapamiętania na ścieżce uprawnień jest to, że CryptKeyLength musi być ustawione przed BeginDoc; reszta jest w naszym artykule o szyfrowaniu AES-256 i uprawnieniach

Co XFA oznacza dla zgodności archiwalnej

PDF/A i PDF/X oba od razu odrzucają XFA. Potok zasilający archiwum ISO 19005 musi więc najpierw konwertować, a kolejność nie podlega negocjacji: wczytaj, FlattenLoadedXFA, zapisz, a potem uruchom generowanie albo walidację archiwalną na wyniku AcroForm. Nie traktuj konwersji jako dowodu zgodności. Naprawia ona model formularza i zostawia czcionki, kolor i metadane dokładnie takimi, jakie były, więc zwaliduj wyjście za pomocą veraPDF, zanim mu zaufasz. Gdy formularz jest już po stronie AcroForm, jego zachowanie dostaje własny zestaw kontroli. Wyzwalacze JavaScript, akcje wysyłania i skrypty walidacyjne są omówione w artykule o polach i akcjach AcroForm w HotPDF

API rejestracji, konwersji i obsługi formularzy XFA pokazane tutaj są dostępne w HotPDF Delphi Component dla Delphi i C++Builder, którego dokumentacja śledzi zestaw funkcji XFA w miarę jego rozwoju w kolejnych wydaniach