Artykuł techniczny

Eksport i import XFDF w Delphi za pomocą PDFium Component

Komponent PDFium Component dla Delphi i Lazarusa wymienia dane formularzy i adnotacje za pomocą formatu XFDF, czyli standardu wymiany XML zdefiniowanego przez ISO 19444-1. Metoda TPdf.ExportXFDF zapisuje każdą wartość pola formularza oraz każdą obsługiwaną adnotację w załadowanym dokumencie do pliku XFDF lub strumienia TStream; z kolei funkcja TPdf.ImportXFDF odczytuje dokument XFDF, odtwarzając adnotacje na właściwych stronach i wprowadzając wartości pól. Ten cykl obsługuje dwa procesy, których potrzebuje niemal każda aplikacja do obsługi dokumentów: recenzent nanosi poprawki na plik PDF w programie Acrobat i przesyła komentarze do scalenia, lub reguła zgodności wymaga, aby dane formularza były przechowywane w osobnym, łatwym do porównania i audytowania pliku, a nie wbudowywane bezpośrednio w plik PDF

Czy biblioteka PDFium obsługuje import i eksport XFDF?

Sama biblioteka PDFium tego nie obsługuje. Natywne API C nie posiada żadnych funkcji związanych z formatami XFDF lub FDF — nagłówki publiczne nie zawierają metod do odczytu ani zapisu tych formatów, więc żadna nakładka (wrapper) nie rozwiąże tego problemu. Komponent PDFium Component implementuje cały silnik XFDF bezpośrednio w Pascalu, w dedykowanym module budującym i analizującym kod XML bezpośrednio na bazie istniejącego modelu danych adnotacji i pól formularza klasy TPdf. Moduł zapisu tworzy XML ręcznie, a moduł odczytu to samodzielnie napisany parser automatu stanowego. Dzięki temu funkcja ta nie wymaga biblioteki Delphi XMLDoc ani modułów DOM środowiska FPC, działając identycznie w Delphi i Lazarusie

Ten wybór projektowy ma duże znaczenie przy ocenie alternatywnych rozwiązań. Jeśli wywołujesz surowe funkcje FPDF_* bezpośrednio z poziomu Pascala, brak obsługi XFDF staje się barierą: tej funkcjonalności po prostu nie ma w bibliotece DLL. Komponent omija to ograniczenie, traktując XFDF jako klasyczny problem serializacji — pobiera wartości pól oraz rekordy adnotacji, które wrapper potrafi już odczytać, zapisuje je jako standardowy kod XML, a przy imporcie odwraca cały proces

Co zawiera plik XFDF?

Standard ISO 19444-1 definiuje format XFDF jako reprezentację XML formatu FDF, a jego zawartość dzieli się na dwa główne bloki. Element <fields> przechowuje hierarchiczne nazwy pól formularza oraz ich wartości — wszystko to, co użytkownik wpisał, zaznaczył lub wybrał w strukturze AcroForm. Z kolei element <annots> przechowuje adnotacje: funkcja TPdf.ExportXFDF obsługuje 18 ich podtypów — tekstowe (text), wyróżnienia (highlight), podkreślenia (underline), przekreślenia (strikeout), faliste (squiggly), linie (line), okręgi (circle), prostokąty (square), znaczniki wstawienia (caret), wielokąty (polygon), łamane (polyline), pieczątki (stamp), rysunki odręczne (ink), notatki tekstowe (freetext), załączniki plików (fileattachment), dźwięki (sound), odnośniki (link) oraz redakcje (redact). Adnotacje widgetów są celowo nieobecne w sekcji <annots>, ponieważ ich data travels w sekcji <fields>, a podtypy wewnętrzne przeglądarki, takie jak Popup, nie wchodzą w skład schematu XFDF

Aby zachować wierność zapisu i odczytu, rekord TPdfAnnotation został rozszerzony o metadane wymagane przez XFDF: Name (unikalny identyfikator NM), Subject, ModificationDate, CreationDate, Icon, Opacity, punkty końcowe linii, wierzchołki wielokątów i linii łamanych oraz ścieżki gestów rysunku odręcznego. Każde nowe pole łączy się z wartownikiem logicznym (boole'owskim) typu Has*, dzięki czemu kod tworzący adnotacje w starszych wersjach kompiluje się bez zmian i generuje te same słowniki — nieustawione pola nie są zapisywane. Jeśli generujesz znaczniki programowo, ten sam rekord, którego używasz do tworzenia adnotacji zaznaczania tekstu z punktami quad points, zawiera teraz wszystko to, czego wymaga format XFDF

Jak wyeksportować dane formularza i adnotacje do formatu XFDF?

Cały proces na poziomie dokumentu wymaga jednego wywołania. Wczytaj plik PDF z włączoną obsługą wypełniania formularzy, wywołaj metodę ExportXFDF, a komponent przejdzie przez każdą stronę, zbierze wartości pól i adnotacje, a następnie wygeneruje kod XML. Zwracaną wartością jest liczba zapisanych bajtów UTF-8, co ułatwia prostą weryfikację poprawności w logach

var
  Pdf: TPdf;
  Bytes: Integer;
begin
  Pdf := TPdf.Create(Self);
  Pdf.FormFill := True;            // wymagane do sprawnego odczytu wartości pól
  Pdf.FileName := 'expense-report.pdf';
  Pdf.Active := True;
  Bytes := Pdf.ExportXFDF('expense-report.xfdf');
  ShowMessage(Format('%d bytes of XFDF written', [Bytes]));
end;

Oba kierunki obsługują przeciążenie dla strumienia TStream, więc nie ma potrzeby tworzenia plików tymczasowych na dysku. Zapis do strumienia TMemoryStream to naturalne rozwiązanie, gdy plik XFDF ma zostać przekazany jako odpowiedź protokołu HTTP, pole blob w bazie danych lub podpisana pozycja w archiwum

var
  Buffer: TMemoryStream;
begin
  Buffer := TMemoryStream.Create;
  try
    Pdf.ExportXFDF(Buffer);        // ten sam XML, bez użycia pliku na dysku
    Buffer.Position := 0;
    // przekaż strumień do odpowiedzi sieciowej, kolumny blob lub archiwum zip
  finally
    Buffer.Free;
  end;
end;

Jak metoda ImportXFDF scala komentarze z dokumentem?

Import to etap, na którym zamyka się proces recenzji dokumentu. Współpracownik dodaje komentarze w programie Acrobat, eksportuje je do pliku XFDF i przesyła Ci kilkukilobajtowy plik XML zamiast kolejnej kopii całego pliku PDF. Funkcja TPdf.ImportXFDF analizuje ten plik, tworzy każdą adnotację na stronie wskazanej przez atrybut page i zapisuje wartości pól w odpowiednich adnotacjach widgetów. Funkcja zwraca łączną liczbę zaimportowanych pól i adnotacji, dzięki czemu interfejs użytkownika może potwierdzić liczbę wprowadzonych zmian

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

Parser to napisany ręcznie skaner znaczników zaprojektowany z myślą o tolerancji na błędy: nieznane elementy są pomijane, instrukcje przetwarzania, definicje DOCTYPE oraz komentarze są ignorowane, nieznane nazwy flag są odrzucane, a wartości atrybutów są dekodowane (unescaped) z uwzględnieniem pełnego zestawu encji XML i numerycznych referencji znaków, w tym par surogatowych UTF-16. Pliki XFDF utworzone w programie Acrobat, przeglądarce sieciowej czy innym narzędziu są bez problemu scalane z dokumentem. Po wprowadzeniu adnotacji cykl recenzji może być kontynuowany z użyciem mechanizmów odpowiedzi i statusów opisanych w artykule o budowaniu systemu recenzji adnotacji za pomocą PDFium Component, a zaimportowane wartości pól biorą udział w standardowym przejściu tabulacji opisanym w rozdziale o nawigacji po polach formularza

Dlaczego separator dziesiętny ma znaczenie dla formatu XFDF?

Każda adnotacja w pliku XFDF jest pozycjonowana za pomocą współrzędnych tekstowych — np. rect="70.5,540,200,560" — a standard ISO 19444-1 wymaga użycia kropki jako separatora dziesiętnego. Domyślne formatowanie liczb zmiennoprzecinkowych w Delphi zależy od lokalnych ustawień systemu operacyjnego Windows, więc w systemie z językiem niemieckim lub francuskim funkcja FloatToStr zamieni 70.5 na 70,5, co uszkodzi rozdzielaną przecinkami listę współrzędnych, czyniąc ją nieczytelną dla innych programów. Komponent PDFium Component normalizuje każdą zapisywaną liczbę: lokalny separator dziesiętny jest zastępowany kropką, a końcowe zera są odrzucane. Dzięki temu wyeksportowany plik jest identyczny niezależnie od tego, czy został utworzony na maszynie z ustawieniami en-US czy de-DE. Jeśli zdarzyło Ci się debugować narzędzie do PDF, które działało w biurze, lecz ulegało awarii u klienta w Europie, to napotkałeś właśnie ten problem — warto wiedzieć, że komponent rozwiązuje go automatycznie

Jakie są ograniczenia zapisu i odczytu?

Należy otwarcie wspomnieć o dwóch ograniczeniach. Po pierwsze, format XFDF przenosi dane adnotacji, a nie ich wyrenderowany wygląd — strumienie wyglądu (appearance streams) nie wchodzą w skład tego formatu, więc program docelowy generuje wygląd adnotacji na podstawie jej właściwości. Wyróżnienie lub prostokąt wyrenderują się poprawnie wszędzie, lecz pieczątka z niestandardowym wyglądem powróci do domyślnego kształtu rysowanego dla danej nazwy przez program docelowy. Po drugie, bazowe API biblioteki PDFium posiada metody zapisu dla ciągów tekstowych, lecz nie udostępnia ich dla wartości liczbowych czy geometrii ścieżek adnotacji, stąd Opacity, punkty końcowe linii, wierzchołki oraz gesty rysunku odręcznego są dostępne wyłącznie przy eksporcie: komponent poprawnie zapisuje je do pliku XFDF, lecz nie pozwala na ich ponowne zapisanie w pliku PDF podczas importu. Wartości pól, treści, kolory, prostokąty rects, punkty quad points, daty oraz metadane tożsamości są w pełni obsługiwane w obu kierunkach

Aby szybko rozpocząć pracę, w pakietach demonstracyjnych dla Delphi, C++Buildera i Lazarusa udostępniono przykład XfdfLab, który przypisuje oba wywołania do przycisków na dowolnym otwartym pliku PDF. Obsługa XFDF wchodzi w skład bieżącego wydania PDFium Component, wraz z interfejsami API adnotacji i formularzy, na których bazuje