Format XFA (XML Forms Architecture) jest przestarzały. Standard ISO 32000-1 uwzględnia go w §12.7 z adnotacją, że został usunięty z wersji PDF 2.0, a współczesne przeglądarki stopniowo porzucają obsługę silników XFA. Nic z tego jednak nie opróżniło archiwów. Rządowe formularze rejestracyjne, wnioski ubezpieczeniowe i wyciągi bankowe były tworzone w formacie XFA przez niemal dwie dekady, a pliki te wciąż trafiają do skrzynek odbiorczych i systemów przetwarzania dokumentów. Gdy przeglądarka, która kiedyś je renderowała, przestaje to robić, formularz zamienia się w pustą stronę z komunikatem proszącym o otwarcie w innym programie. Trwałym rozwiązaniem jest spłaszczenie XFA do statycznej zawartości PDF, którą potrafi narysować dowolny czytnik.
Trudną częścią tego spłaszczania nie są same pola. Pola tekstowe i pola wyboru całkiem dobrze mapują się na widżety AcroForm. Trudność sprawia sformatowany tekst (rich text), który XFA przechowuje wewnątrz elementu rysunkowego w bloku <exData contentType="text/html">. Ten blok to podzbiór HTML ze stylami wbudowanymi (inline) i, często, kotwicami (hiperłączami). Przeniesienie go na stronę oznacza konieczność odtworzenia zarówno ostylowanego tekstu, jak i aktywnych hiperłączy, a hiperłącza to miejsce, w którym większość implementacji po cichu się poddaje.
Jak naprawdę wygląda tekst sformatowany XFA
Zawartość exData to krótki fragment kodu XHTML. Akapit to znacznik <p>; ostylowany ciąg znaków to <span> z własnym stylem inline CSS określającym grubość, pochylenie, kolor i rozmiar; a hiperłącze to <a href="..."> otaczający widoczny tekst. Pojedyncza linia może zawierać kilka następujących po sobie sekcji span, z których każda ma inny styl, a jedna z nich może być kotwicą. Stylizacja nie jest tutaj tylko dekoracją, którą można pominąć. Klauzula wyrenderowana pogrubioną czerwoną czcionką, ponieważ stanowi ostrzeżenie prawne, musi pozostać pogrubiona i czerwona po spłaszczeniu, inaczej spłaszczony dokument zniekształciłby oryginał.
Silnik spłaszczający nie może więc traktować tego bloku jako jednego ciągu znaków. Musi przejść przez strukturę wierszową, wyznaczyć efektywny styl każdego przebiegu poprzez nałożenie stylu inline CSS znacznika span na czcionkę bazową elementu rysunkowego, a następnie rozłożyć te przebiegi jeden po drugim wzdłuż linii. HotPDF modeluje każdy z tych rozmieszczonych fragmentów jako wewnętrzny rekord TXFARichRun. Rekord ten zawiera tekst przebiegu, jego ostateczny styl, zmierzony obszar (box) oraz, w przypadku kotwicy, adres Href, na który wskazuje.
Układanie przebiegów od lewej do prawej
Pozycjonowanie to moment, w którym sformatowany tekst przestaje być problemem składniowym (parsing), a staje się problemem zecerskim. Przebiegi współdzielą linię, więc każdy z nich zaczyna się w miejscu, w którym zakończył się poprzedni. Nie ma żadnych znaczników rejestrujących te pozycje; muszą one zostać zmierzone. Wewnętrzna procedura silnika LayoutRichText mierzy każdy przebieg za pomocą tych samych metryk czcionki, które zostaną użyte do jego narysowania, a następnie ustawia przesunięcie poziome przebiegu na bieżącą sumę szerokości wszystkich wcześniejszych przebiegów. Przebieg pierwszy zaczyna się na początku obszaru rysowania, drugi zaczyna się na szerokości pierwszego, trzeci na połączonej szerokości pierwszych dwóch i tak dalej wzdłuż linii.
Oto dlaczego dopasowanie czcionki pomiarowej jest tak istotne. Krok układania mierzy szerokości znaków (advances); osobny krok renderowania rysuje glify. Jeśli te dwa kroki będą miały różne informacje o czcionce, obszary obliczone podczas układania nie będą pokrywać się z glifami narysowanymi przez renderer. HotPDF dba o ich zgodność poprzez mapowanie ustalonego stylu każdego przebiegu na specyfikację czcionki (za pomocą wewnętrznego pomocnika RunStyleToFontSpec), która odpowiada domyślnym ustawieniom renderera, czyli Arial o rozmiarze 10 punktów. Zmierzona szerokość i narysowany tekst są wtedy zgodne, a obliczony obszar przebiegu rzeczywiście pokrywa znaki widziane przez czytelnika.
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
end;
Od przebiegu kotwicy do adnotacji Link w PDF
Hiperłącze w gotowym pliku PDF nie jest częścią zawartości strony. Jest to osobny obiekt — adnotacja Link, opisana w normie ISO 32000-1 §12.5.6.5. Adnotacja posiada wpis /Rect definiujący klikalny prostokąt na stronie oraz akcję uruchamianą po kliknięciu tego prostokąta. Dla linku zewnętrznego jest to akcja URI: wpis /S /URI z adresem docelowym w ciągu znaków /URI. Widoczny pod spodem tekst to zwykła zawartość strony; adnotacja to niewidoczna aktywna strefa nałożona na niego.
Ścieżka spłaszczania opiera się dokładnie na tym modelu. Gdy przebieg zawiera adres Href, HotPDF najpierw rysuje ostylowany tekst, a następnie tworzy adnotację Link nad obszarem tego przebiegu. Publicznym punktem wejścia dla tej adnotacji jest metoda strony AddURILink, która tworzy obiekt /Type /Annot /Subtype /Link z akcją /URI i zwraca słownik adnotacji. Jej prostokąt to zmierzony obszar przebiegu, przetłumaczony z lokalnych współrzędnych elementu rysunkowego na współrzędne strony. W rezultacie otrzymujemy link, który trafia dokładnie w tekst kotwicy i nigdzie indziej.
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Dlaczego aktywny obszar musi wynikać ze zmierzonych szerokości
Kuszące może być wyobrażenie, że link można zlokalizować poprzez wyszukanie widocznego tekstu na stronie i narysowanie prostokąta wokół znalezionego elementu. To jednak nie zadziała, a powód leży u podstaw sposobu przechowywania spłaszczonego tekstu. Ostylowane przebiegi są rysowane przy użyciu osadzonych czcionek podzbiorowych (subset fonts). Czcionka podzbiorowa zmienia numerację zachowanych glifów, przez co strumień zawartości strony przechowuje szesnastkowe kody CID, a nie pierwotne kody znaków. Bajty na stronie nie są literami czytanymi przez człowieka i nie można ich przeszukiwać jako tekstu. Wyszukanie podpisu kotwicy nic nie da, ponieważ ten podpis nie istnieje jako dosłowny tekst w żadnym miejscu strumienia.
Jedynym wiarygodnym zakotwiczeniem dla prostokąta jest geometria wyznaczona już w kroku układania. Przesunięcie i zmierzona szerokość każdego przebiegu zostały obliczone podczas przepływu linii, przed zmianą numeracji jakichkolwiek glifów, i określają one miejsce, w którym fizycznie pojawi się tekst. HotPDF pobiera zatem prostokąt linku bezpośrednio z ułożonego obszaru przebiegu, a nie z wyszukiwania tekstu. Ponieważ pomiar opierał się na czcionce renderującej, obszar jest poprawny niezależnie od tworzenia podzbiorów czcionek. Geometria przetrwa kodowanie, natomiast tekst nie. To jest kluczowy argument za pozycjonowaniem opartym na zmierzonych szerokościach i powód, dla którego próby dopasowania linków przez wyszukiwanie tekstu dają przesunięte lub znikające strefy kliknięcia.
Uruchamianie spłaszczania z poziomu kodu
W przypadku pliku PDF, który zawiera już pakiet XFA, punktem wejścia jest FlattenLoadedXFA. Ładujesz dokument, wywołujesz tę metodę i zapisujesz wynik. Parametr Editable decyduje o tym, co stanie się z polami formularza: przekaż True, aby zachować je jako widżety AcroForm z możliwością wypełniania, lub False, aby oznaczyć każdy widżet jako tylko do odczytu, zamrażając wynik. Bloki rysunkowe sformatowanego tekstu wraz z ich ostylowanymi przebiegami i adnotacjami linków są generowane w obu przypadkach. Funkcja zwraca liczbę wygenerowanych widżetów.
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
Po wywołaniu zawsze należy odczytać listę XFAFlattenWarnings. Lista jest czyszczona na początku każdego spłaszczania i gromadzi wiersze dla każdego elementu, którego silnik nie wyrenderował: nieobsługiwany typ pola, obraz, którego nie udało się zdekodować, czy blok exData bez poprawnych sekcji span. Żadna z tych sytuacji nie generuje wyjątku, więc pusta lista ostrzeżeń to dowód, że wszystko zostało zmapowane, a niepusta wskazuje, które elementy źródłowe należy sprawdzić. Gdy dysponujesz surowymi danymi XFA jako bajtami XDP zamiast załadowanego PDF, bliźniacza metoda ApplyXFAAsAcroForm przyjmuje te bajty bezpośrednio i korzysta z tej samej ścieżki kodu oraz obsługi ostrzeżeń. Z kolei komplementarna metoda AddXFAPacket działa w drugą stronę, osadzając pakiet XFA w budowanym dokumencie.
Potwierdzanie wyniku w czytniku
Otwórz spłaszczony plik w programie Acrobat lub dowolnej współczesnej przeglądarce i sprawdź dwie rzeczy. Po pierwsze, czy sformatowany tekst wyrenderował się z nienaruszoną stylizacją: pogrubione przebiegi są pogrubione, kolorowe zachowują swój kolor, a sekcje span układają się w prawidłowej kolejności na linii, zamiast nakładać się lub wykraczać poza obszar. Po drugie, czy hiperłącza są aktywne. Najedź kursorem na kotwicę, a pasek stanu powinien pokazać adres docelowy; kliknij go, a akcja URI powinna go otworzyć. Użyj inspektora adnotacji przeglądarki, aby potwierdzić, że każda z nich to prawdziwa adnotacja /Link, której /Rect ściśle otacza tekst kotwicy, leżąc nad zawartością będącą teraz zwykłymi narysowanymi glifami, a nie renderowanym przez formularz obiektem XFA. To połączenie — ostylowany statyczny tekst plus rzeczywiste adnotacje Link w odpowiednich prostokątach — sprawia, że spłaszczony dokument przetrwa silniki XFA, których już nie potrzebuje.
Spłaszczanie samych pól — pól tekstowych, pól wyboru i list wyboru otaczających ten sformatowany tekst — zostało omówione w naszym przewodniku po spłaszczaniu formularzy XFA do widżetów AcroForm. Szerszy opis ręcznego tworzenia i rozmieszczania adnotacji Link, poza tymi generowanymi przez ścieżkę spłaszczania, można znaleźć w artykule praca z adnotacjami PDF w HotPDF. Obie te funkcje bazują na tym samym modelu adnotacji i formularzy dostarczanym wraz z pakietem HotPDF Component dla Delphi i C++Builder.