Masz firmowy szablon faktury albo zarchiwizowany kontrakt wygenerowany lata temu w oprogramowaniu, którego nikt już nie potrafi znaleźć, a wymaganie brzmi: uczynić go interaktywnym. Dodaj w rogu pole podpisu, kilka pól tekstowych, zamień płaską listę kontrolną na prawdziwe checkboxy. Problem w tym, że nie tworzysz tego PDF-a od zera. On już istnieje, ma strony, strumienie treści i fonty, nad którymi nie panujesz, i musisz dosztukować widgety AcroForm do tego grafu obiektów bez jego przebudowy. To inny problem niż tworzenie formularza w świeżym dokumencie, a pułapka wychodzi dopiero wtedy, gdy otwierasz wynik w przeglądarce i pola, które właśnie zapisałeś, w ogóle nie pojawiają się na stronie
HotPDF to natywny komponent PDF VCL dla Delphi i C++Builder, a od v2.247.0 udostępnia specjalną rodzinę metod właśnie do tego celu: budowania wszystkich sześciu standardowych typów pól bezpośrednio w dokumencie wczytanym przez LoadFromFile. Ten artykuł pokazuje, co robią te metody, jaką strukturę ISO 32000-1 tworzą i której jednej flagi nie wolno pominąć, bo bez niej całe ćwiczenie po cichu kończy się plikiem wyglądającym na pusty
Dlaczego tworzenie pól w wczytanym dokumencie ma własną ścieżkę kodu
Gdy budujesz PDF od zera, HotPDF kontroluje cały model obiektów. Każda strona jest zapisywalnym opakowaniem THPDFPage, a dodanie pola tekstowego przez AddTextField podłącza nowy widget do obiektu adnotacji strony, samego obiektu strony i kolekcji pól formularza, a potem generuje appearance stream z zasobów fontów dokumentu. Appearance stream to widoczna powierzchnia widgetu, czyli ramka, obramowanie i domyślny tekst, narysowane jako operatory PDF, które przeglądarka renderuje dosłownie
Wczytany dokument nie daje ci żadnego z tego rusztowania. Strony przychodzą jako surowe słowniki, nie ma zapisywalnego opakowania THPDFPage, na którym można zawiesić widget, a co ważniejsze nie ma też gotowego potoku zasobów fontów do malowania appearance streamów. Ścieżka dla wczytanego dokumentu idzie więc inną drogą. Zapisuje słowniki pól bezpośrednio do sparsowanego grafu obiektów i adresuje strony indeksem zerowym, a nie obiektem strony. Typy pól i bity flag są dokładnie takie same jak w ścieżce od zera, więc pole Text pozostaje polem Text w obu przypadkach, ale zmienia się warstwa pośrednia, a przede wszystkim sposób rysowania powierzchni widgetu
Flaga /NeedAppearances nie jest tutaj opcjonalna
To właśnie ten pojedynczy fakt decyduje, czy wynik twojej pracy będzie w ogóle widoczny. Ponieważ ścieżka pracy z załadowanym dokumentem nie generuje strumieni wyglądu, nowo dodany widget trafia do przeglądarki bez wpisu /AP wpisu: pole bez opisanego wyglądu. Wiele przeglądarek, proszonych o wyrenderowanie widgetu bez wyglądu i bez instrukcji, by go zbudować, nie rysuje niczego. Pole jest w pliku, jest strukturalnie poprawne, dostępne dla narzędzia do wypełniania formularzy i całkowicie niewidoczne dla człowieka
Wyjście awaryjne definiuje ISO 32000-1 §12.7.3: słownik AcroForm zawiera wartość logiczną /NeedAppearances, a gdy ma ona wartość true zgodny czytnik musi sam zbudować brakujące strumienie wyglądu z ciągu /DA (domyślny wygląd) oraz wartości każdego pola. HotPDF ustawia to za ciebie. Gdy po raz pierwszy dodajesz dowolne pole do załadowanego dokumentu, EnsureLoadedAcroForm zostaje uruchomione: jeśli katalog nie ma /AcroForm, tworzy je, jeśli nie ma tablicy /Fields, tworzy ją, i wymusza ustawienie /NeedAppearances true. Nie wywołujesz tego bezpośrednio, ale świadomość jego istnienia wyjaśnia zachowanie. Wyjaśnia też zastrzeżenie wdrożeniowe, które warto powiedzieć wprost: garść minimalnych lub niezgodnych przeglądarek ignoruje /NeedAppearances i nadal niczego nie renderuje. Dla typowych czytników flaga działa, ale jeśli twoi odbiorcy używają nietypowego osadzonego renderera, przetestuj go, zanim cokolwiek obiecasz
Dodawanie sześciu typów pól
Każda metoda ma ten sam kształt. Przekazujesz zerowy indeks strony, cztery narożniki prostokąta widgetu we współrzędnych przestrzeni użytkownika PDF, nazwę pola i wszystkie dodatkowe argumenty wymagane przez dany typ. Prostokąt jest X1, Y1, X2, Y2 przy czym początkiem układu PDF jest lewy dolny róg strony, więc większe wartości Y leżą wyżej. To konwencja współrzędnych formatu pliku, a nie ekranowa konwencja lewego górnego rogu, i pomylenie tych dwóch rzeczy jest drugim najczęstszym błędem po zapomnieniu o fladze. Każde wywołanie zwraca zerowy indeks nowego pola albo -1 jeśli indeks strony jest poza zakresem lub nie udało się rozwiązać obiektu strony
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract.pdf') <= 0 then Exit;
// Text field: name, initial value, max length (0 = unlimited)
Idx := Pdf.AddLoadedTextField(0, 72, 680, 320, 700, 'FullName', '', 0);
// CheckBox: export value, initial checked state
Pdf.AddLoadedCheckBox(0, 72, 640, 90, 658, 'AgreeTerms', 'Yes', False);
// Signature field: just a name and a rectangle
Pdf.AddLoadedSignatureField(0, 360, 72, 540, 132, 'ApproverSig');
if Idx >= 0 then
Pdf.SaveLoadedDocument('contract-interactive.pdf');
finally
Pdf.Free;
end;
end;
Trzeci i czwarty argument typu string dla pola tekstowego to nazwa pola i jego początkowa /V; liczba całkowita to /MaxLen, zapisywane tylko wtedy, gdy jest większe od zera. HotPDF nadaje każdemu edytowalnemu polu domyślny ciąg wyglądu /Helv 12 Tf 0 0 0 rg, który przeglądarka respektująca /NeedAppearances odczytuje, aby ustalić krój pisma i kolor używany do rysowania wartości. Pole wyboru przyjmuje wartość eksportu, czyli ciąg wysyłany przy zaznaczonym polu, oraz wartość logiczną określającą stan początkowy. Wewnętrznie zapisuje odpowiadające wpisy nazw /V, /AS i /DV, tak aby stan on/off był spójny już w chwili otwarcia pliku. Pusta wartość eksportu domyślnie przyjmuje Yes, czyli konwencjonalną nazwę checkboxa oznaczającą stan "on"
Pola wyboru i bity /Ff
ComboBox i ListBox są obydwa polami wyboru, typ pola /Ch w ISO 32000-1 §12.7.4. Różnica między listą rozwijaną a przewijaną listą to jeden bit w liczbie flag pola /Ff: bit 18, flaga Combo, o wartości $40000. HotPDF ustawia ten bit dla AddLoadedComboBox i pozostawia go wyzerowany dla AddLoadedListBox; poza tym oba są identyczne i oba przyjmują swoje opcje jako otwartą tablicę ciągów zapisywaną do wpisu /Opt
// Dropdown (Combo flag set internally) with an initial selection
Pdf.AddLoadedComboBox(0, 72, 600, 300, 620, 'Country', 'Canada',
['United States', 'Canada', 'Mexico']);
// Scrolling list, no initial value
Pdf.AddLoadedListBox(0, 72, 520, 300, 590, 'Priority', '',
['Low', 'Normal', 'High']);
// Push button with a caption drawn through /MK
Pdf.AddLoadedPushButton(0, 360, 600, 480, 626, 'SubmitBtn', 'Submit');
Dwie uwagi o liście opcji. HotPDF zapisuje każdy wpis /Opt jako zwykły ciąg, w którym wartość eksportu i wyświetlana etykieta są tym samym tekstem. ISO 32000-1 §12.7.4.4 dopuszcza też dwuelementową postać [export display], gdy wartość wysyłana ma różnić się od tekstu widocznego dla użytkownika. Metody tworzenia na załadowanym dokumencie używają prostszej postaci z pojedynczym stringiem, więc jeśli potrzebujesz różnych wartości eksportu i wyświetlania, ustawisz je samodzielnie w wynikowym słowniku. Wartość przekazana jako bieżący wybór pola powinna też należeć do podanych opcji, ponieważ przeglądarka dopasowuje ją do listy
Push button to drugi przypadek sterowany flagą: typ pola /Btn z bitem 17, flagą PushButton, o wartości $10000. To właśnie ten bit odróżnia klikalny przycisk od checkboxa, który również jest polem /Btn, ale bez niego. Przekazany podpis jest zapisywany do słownika charakterystyki wyglądu /MK jako zwykły podpis /CA. Warto uczciwie zaznaczyć zakres tej funkcji: przycisk jest tworzony ze swoją etykietą i prostokątem, ale metoda tworzenia na załadowanym dokumencie nie dołącza akcji, więc sam z siebie jest to przycisk, który wygląda poprawnie i nic nie robi po kliknięciu. Podpięcie akcji submit, reset lub JavaScript to osobna sprawa. Po stronie tworzenia od zera przepływ pole plus akcja opisuje artykuł o budowaniu pól AcroForm i akcji w Delphi, który najlepiej pokazuje, co ścieżka pracy z załadowanym dokumentem celowo pomija
Słownik wspólny dla każdego pola
Pod spodem wszystkie sześć metod opiera się na jednym wspólnym konstruktorze, który buduje adnotację widgetu i rejestruje ją w dwóch miejscach. Zapisuje /Type /Annot oraz /Subtype /Widget, tablicę /Rect utworzoną z twoich czterech współrzędnych, flagi adnotacji /F 4 ustawiające bit Print, aby pole było widoczne na wydruku i na ekranie, nazwę pola /T, typ pola /FT, flagi /Ff, oraz odwołanie zwrotne /P do obiektu strony. Następnie dopisuje nowe pole do tablicy /Fields w AcroForm i do tablicy /Annots tej strony, rozwiązując po drodze referencje pośrednie, aby rozszerzyć prawdziwe tablice, a nie osierocić widget
Ta podwójna rejestracja ma znaczenie, bo widget obecny tylko na jednej z tych dwóch list jest uszkodzony w subtelny sposób. Pole obecne w /Fields, ale nieobecne w /Annots strony, jest znane formularzowi, lecz nigdy nie zostaje narysowane. Odwrotna sytuacja sprawia, że jest narysowane, ale nieznane logice formularza. HotPDF utrzymuje obie listy w synchronizacji przy każdym dodaniu, i to jest ten rodzaj księgowości, który w przeciwnym razie musiałbyś ręcznie wykonać dokładnie według specyfikacji
Kilka uczciwych ograniczeń
Ustal oczekiwania, zanim zbudujesz na tym cały workflow. Zachowanie polegające na spłaszczeniu w przeglądarce i regeneracji zależy od tego, czy czytnik respektuje /NeedAppearances, co obejmuje Acrobat, nowoczesne silniki PDF w przeglądarkach i popularne czytniki desktopowe, ale nie jest twardą gwarancją dla każdego renderera w terenie. Jeśli musisz wygenerować plik, którego pola będą wszędzie renderowane identycznie, także w przeglądarkach ignorujących tę flagę, wchodzisz już w obszar strumieni wyglądu i lepszym wyborem jest ścieżka tworzenia od zera, która rysuje /AP za ciebie. Pole podpisu również jest tworzone jako pusty widget podpisu gotowy do podpisania; umieszczenie pola nie jest tym samym co złożenie podpisu kryptograficznego
Jeśli zamiast dodawać chcesz zmieniać to, co już istnieje, pokrewną operacją jest flattening formularza, w którym interaktywne pola są zapiekane z powrotem w statycznej zawartości strony, tak aby wartości stały się trwałe i nieedytowalne. Ten pełny obieg, łącznie z tym, jak obsługiwane są formularze z XFA, opisuje artykuł o spłaszczaniu pól XFA i AcroForm w Delphi. Dodawanie pól i spłaszczanie pól to dwa końce tego samego cyklu życia: ten artykuł pokazuje, jak wprowadzić interaktywność do dokumentu, który jej nie miał, a flattening pokazuje, jak ją potem usunąć, gdy formularz spełni już swoje zadanie
Interfejs formularzy dla załadowanych dokumentów pokazany tutaj jest częścią standardowego HotPDF Component dla Delphi i C++Builder, obok pełnej referencji flag pól, obsługi wyglądu i reszty modelu AcroForm