PDFlibPas (losLab PDF Library) wypełnia szablony bloków PDF z Delphi: blok to nazwany prostokąt na stronie szablonu, o typie tekst, obraz albo PDF, a FillTextBlock, FillImageBlock i FillPDFBlock rysują w niego dane zmienne. Bloki są znajdowane jako adnotacje stron PDFlibBlock:Text, PDFlibBlock:Image i PDFlibBlock:PDF albo w słowniku page-piece pod /PieceInfo
Przypadek użycia to ten nudny, wielkotirażowy rodzaj. Projektant układa certyfikat, bilet na wydarzenie albo wezwanie do zapłaty raz, a program wpycha imię, numer miejsca, obraz podpisu albo wcześniej wyrenderowaną stronę kodu kreskowego w stałe sloty dla dziesięciu tysięcy rekordów. Pola formularza zrobią część tego, ale pole AcroForm to obiekt interaktywny z własnym strumieniem wyglądu i własnymi regułami, i zwykle i tak kończysz na jego spłaszczeniu. Blok jest prostszy: prostokąt plus garść podpowiedzi stylistycznych, skonsumowany raz i wyrzucony
Czym dokładnie jest blok PDF w PDFlibPas?
Blok PDFlibPas to adnotacja strony, której /Subtype to PDFlibBlock:Text, PDFlibBlock:Image albo PDFlibBlock:PDF, której /Rect to obszar placeholdera, a której napis /Contents trzyma name;key=value;key=value. Pierwszy segment to nazwa bloku, wszystko po nim to lista własności. TPDFlib.AddBlock zapisuje adnotację z /F 4 (Print) i bez strumienia wyglądu, więc niewypełniony blok nie zajmuje żadnych pikseli ani na ekranie, ani na papierze
Strona odczytu ma drugie źródło. Po wyliczeniu adnotacji bloków PDFlibPas schodzi do ścieżki w słowniku strony /PieceInfo << /PDFlib << /Blocks ... >> >> i jej wariantu /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>. Słowniki page-piece to standardowy PDF, zdefiniowany w ISO 32000-1 §14.5 jako miejsce, gdzie aplikacja przechowuje prywatne dane przyczepione do strony, i dokładnie tam zwykle mieszkają definicje bloków z zewnętrznych narzędzi do autoringu szablonów. Pojemnik /Blocks może być słownikiem kluczowanym nazwą bloku albo zwykłą tablicą; dla formy tablicowej PDFlibPas nazywa wpisy Block1, Block2 i tak dalej, chyba że blok niesie własne /Name
Wewnątrz bloku page-piece wartość /Subtype (albo /Type) Image mapuje się na blok obrazowy, PDF albo Page na blok PDF, a wszystko inne jest traktowane jako tekst. Każdy inny klucz poza /Type, /Subtype, /Name, /Rect i /Custom staje się własnością, a klucze zagnieżdżonego słownika /Custom też są spłaszczane do środka, więc wpis /Custom << /department (Finance) >> da się odczytać jako GetBlockProperty(Page, Index, 'department'). Indeksy bloków są numerowane od 1 na stronę, najpierw bloki adnotacji, za nimi bloki page-piece
Autoryng szablonu bloków przez AddBlock
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) dodaje blok do aktualnie wybranej strony i zwraca 1 przy sukcesie. Zwraca 0 dla pustej nazwy, zerowej albo ujemnej szerokości lub wysokości, i dla BlockType innego niż 'text', 'image' czy 'pdf' (bez rozróżniania wielkości liter). Properties jest doklejany do nazwy średnikiem, więc używa tej samej składni key=value;key=value co przechowywany napis
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// statyczna grafika: ramka, logo, stałe sformułowania ...
Lib.DrawText(72, 200, 'This certifies that');
Lib.AddBlock('text', 'recipient', 72, 230, 450, 44,
'fontname=Times-Bold;fontsize=30;fillcolor={cmyk 0 0.55 1 0.1};alignment=center');
Lib.AddBlock('text', 'course', 72, 290, 450, 24,
'fontname=Helvetica;fontsize=14;alignment=center;fitmethod=word');
Lib.AddBlock('image', 'signature', 360, 600, 160, 60, '');
Lib.AddBlock('pdf', 'seal', 72, 580, 100, 100, '');
if Lib.GetBlockCount(1) <> 4 then
raise Exception.Create('Template blocks missing');
Lib.SaveToFile('certificate-template.pdf');
finally
Lib.Free;
end;
end;
Parser własności jest celowo pobłażliwy. Klucze są dopasowywane bez rozróżniania wielkości liter, wartości są przycinane z białych znaków, ale poza tym zwracane dosłownie (wartość zapisana jako {top left} wraca z klamrami), a brakujący klucz zwraca pusty napis. Jedynym twardym limitem jest separator: wartość nie może zawierać średnika, bo średnik kończy segment
Jak FillTextBlock stosuje font, kolor i wyrównanie?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) czyta własności bloku, nakłada je na stan rysowania, dopasowuje tekst do prostokąta bloku przez FitTextBox, a potem przywraca font, rozmiar tekstu, kolor tekstu i wyrównanie wołającego w bloku finally. To przywracanie ma znaczenie w pętli scalania: czerwony blok nagłówka 30 pt nie może przecieknąć stylem do następnego wywołania DrawText na tej samej stronie
fontname(albofont): najpierw reużywa fontu już wczytanego w dokumencie pod tą nazwą, potem mapuje nazwy Standard 14, jakHelvetica-Bold,Times-RomanczyCourier, a na końcu próbujeAddTrueTypeFontz tą nazwąfontsize(albosize): rozmiar tekstu przekazywany dopasowywaczowi, który go zredukuje, gdy tekst się nie mieścifillcolor(albotextcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, gołe listy 3 albo 4 liczb, albo nazwy black, white, red, green, blue i gray; składowe RGB powyżej 1 są czytane jako 0–255alignment,alignipositionsą czytane razem: left, right, center (lub centre) i justify ustawiają wyrównanie poziome, top i bottom ustawiają rozmieszczenie pionowe, a domyślnie jest centrowanie pionowefitmethod:wordalbonofitsplitblokuje łamanie długich słów,scalealboexpandpozwala rozmiarowi urosnąć, by wypełnić pudełko
Ścieżka CMYK to powód, dla którego drukarnie się tym interesują. {cmyk 0 0.55 1 0.1} wywołuje SetTextColorCMYK, więc tekst zmienny jest emitowany w DeviceCMYK i trafia w kolor marki na maszynie bez odpływania w RGB. FillTextBlock zwraca 1, gdy tekst został narysowany; zwraca 0 dla pustego Text, brakującego bloku albo bloku, który nie jest blokiem tekstowym, i we wszystkich tych przypadkach blok zostaje na miejscu
Wypełnianie bloków obrazowych i PDF oraz bity Options
FillImageBlock(Page, BlockIndex, ImageID, Options) rysuje wcześniej dodany obraz, a FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importuje stronę innego wczytanego dokumentu jako Form XObject i rysuje ją, co trzyma treść wektorową, jak pieczęć, strona kodu QR albo papier listowy, w pełni wektorowo. Obie zachowują proporcje źródła i centrują wynik wewnątrz prostokąta bloku. Wszystkie trzy funkcje wypełniające dzielą te same bity Options: bit 1 (wartość 1) usuwa blok po udanym wypełnieniu, przez skasowanie adnotacji albo usunięcie wpisu z pojemnika /Blocks, a bit 2 (wartość 2) rozciąga treść obrazu i PDF do dokładnego prostokąta zamiast jej dopasowywać
function FillCertificate(const TemplateFile, SealFile, SignatureFile,
OutFile: WideString; Values: TStrings): Boolean;
var
Lib: TPDFlib;
SealID, SignatureID, I: Integer;
BlockName, BlockType: WideString;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(SealFile, '') <> 1 then Exit;
SealID := Lib.SelectedDocument; // prawdziwy identyfikator dokumentu
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// szablon jest teraz wybranym dokumentem
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// idź w tył: bit 1 usuwa każdy blok, co przenumerowuje dalsze
for I := Lib.GetBlockCount(1) downto 1 do
begin
BlockName := Lib.GetBlockName(1, I);
BlockType := Lib.GetBlockType(1, I);
if BlockType = 'text' then
Lib.FillTextBlock(1, I, Values.Values[BlockName], 1)
else if (BlockType = 'image') and (SignatureID <> 0) then
Lib.FillImageBlock(1, I, SignatureID, 1)
else if BlockType = 'pdf' then
Lib.FillPDFBlock(1, I, SealID, 1, 1);
end;
Result := Lib.SaveToFile(OutFile) = 1;
finally
Lib.Free;
end;
end;
Dwa detale tej pętli łatwo zepsuć. Pętla biegnie od najwyższego indeksu w dół, bo usunięcie bloku 2 zamienia stary blok 3 w blok 2; liczenie w górę z ustawionym bitem 1 przeskakuje co drugi blok. A dokument źródłowy dla FillPDFBlock jest wczytywany najpierw i jego identyfikator jest przechwytywany przed wczytaniem szablonu, bo identyfikatory obrazów należą do wybranego dokumentu, więc AddImageFromFile musi się wykonać, gdy wybrany jest szablon. Dla partii, gdzie pieczęć albo papier listowy zmienia się per rekord, zszywanie stron Form XObject i kompozycyjne szablony znaku wodnego w PDFlibPas używają tego samego mechanizmu przechwyć-i-narysuj na całych stronach
Dlaczego współrzędne bloków wychodzą odwrócone?
Prostokąty bloków wychodzą odwrócone, gdy kod miesza dwa układy współrzędne, z którymi PDFlibPas pracuje. Adnotacyjny /Rect jest przechowywany w przestrzeni użytkownika PDF, mierzonej od dołu strony (ISO 32000-1 §12.5.2), podczas gdy API rysowania TPDFlib mierzy od góry. AddBlock przyjmuje wejście liczone od góry i przechowuje dolną krawędź jako PageHeight - Top - Height; GetBlockRect i trzy funkcje wypełniające czytają prostokąt z powrotem i przeliczają go przez PageHeight - URy
W praktyce to znaczy: używaj GetBlockRect(Page, BlockIndex, Left, Top, Width, Height), gdy potrzebujesz geometrii slotu, na przykład żeby narysować ramkę debugową albo ustawić podpis obok bloku, i nie czytaj /Rect przez generyczne wywołanie adnotacji, żeby wpychać go prosto w DrawText. Blok page-piece z /Rect [40 700 240 730] na stronie letter 792 pt zgłasza Top = 62, a nie 700. Błąd, któremu to zapobiega, to klasyk: każda wypełniona wartość ląduje lustrzanie przy dolnym marginesie i nic nie zgłasza błędu
Pułapki warte wyciągnięcia wniosków
Najbardziej pouczający błąd w kodzie bloków to potknięcie o jeden znak. Adnotacje bloków są rozpoznawane testem prefiksu na subtypie, a 'PDFlibBlock:' ma 12 znaków. Wczesna wersja porównywała Copy(Subtype, 1, 13) z tym literałem 12-znakowym, co nigdy nie może być równe, więc GetBlockCount zwracał 0 dla każdego szablonu, a każde wypełnienie zgłaszało "no such block". Nic nie wywaliło się i nic nie zostało zalogowane. Wniosek uogólnia się na każdy silnik szablonów: sprawdzaj w teście liczbę bloków znanego szablonu, bo ciche wyliczenie z zerem trafień wygląda dokładnie jak pusta strona
Drugą pułapką są identyfikatory dokumentów. TPDFlib.LoadFromFile zwraca kod statusu, 1 dla sukcesu i 0 dla porażki, a nie identyfikator dokumentu, który właśnie wczytał. Podanie tej jedynki jako SourceDocumentID do FillPDFBlock zawodzi, bo 1 nie jest ważnym identyfikatorem dokumentu. Wywołaj SelectedDocument tuż po udanym wczytaniu, żeby dostać prawdziwy identyfikator, a SelectDocument, żeby wrócić do szablonu, jeśli źródło wczytałeś potem
I mniejsza: FillTextBlock z pustym napisem nic nie robi i trzyma blok. Dla mail merge z polami opcjonalnymi, jak brakująca druga linia adresu, to zwykle dokładnie to, czego chcesz, ale niewypełniona adnotacja zostaje wtedy w pliku wynikowym. Usuń ją jawnie przez DeleteAnnotation albo wypełnij pojedynczą spację z bitem 1, jeśli wynik musi być czysty. Wszystkie wywołania bloków zapisują i przywracają wybraną stronę, więc wypełnienie na stronie 3 nie zmienia tego, dokąd pójdzie następne wywołanie rysujące
Jakie są granice współpracy adnotacji bloków z innymi narzędziami?
Adnotacje bloków to prywatna konwencja, nie standardowa funkcja PDF. PDFlibBlock:Text i rodzeństwo nie figurują wśród typów adnotacji wypisanych w ISO 32000-1 §12.5.6.1, więc przeglądarka traktuje je jako nieznane adnotacje. Ponieważ flaga Invisible jest wyzerowana, a strumienia wyglądu nie ma, zgodne przeglądarki nic dla nich nie rysują, ale niektóre mogą je mimo wszystko wypisać w panelu komentarzy, a inne narzędzia PDF nie zrozumieją ani nie zachowają ich semantyki
- Wypełniaj z ustawionym bitem 1 wszystko, co opuszcza twój system, żeby końcowy plik zawierał wyłącznie zwykłą treść strony
- PDF/A nie pozwala na typy adnotacji spoza specyfikacji PDF (ISO 19005-1 §6.5.2), więc archiwizowany wynik nie może nieść niewypełnionych adnotacji bloków
- Bloki page-piece są bezpieczniejsze do trzymania w szablonie, bo
/PieceInfojest standardowe, a konsumenci ignorują dane, które nie są ich - Bloki to nie pola formularza: nie ma interaktywności, wartości pola w pliku ani eksportu do FDF czy XFDF; dane, które mają zostać edytowalne, prowadzą do wymiany danych formularzy FDF, XFDF i XFA w PDFlibPas
Jeśli szablon musi zadeklarować swoje pola zmienne w ustandaryzowany, sprawdzalny przez walidator sposób dla procesu drukarskiego, bloki to niewłaściwe narzędzie. Do tego jest PDF/VCR-1, a manifest placeholderów szablonów PDF/VCR-1 w PDFlibPas opisuje, jak te placeholdery mieszkają w drzewie struktury zamiast w adnotacjach
FAQ
Czy PDFlibPas wypełni szablony bloków stworzone przez inne narzędzia?
Tak, gdy narzędzie przechowuje bloki pod /PieceInfo /PDFlib /Blocks albo /PieceInfo /PDFlib /Private /Blocks, w formie słownikowej albo tablicowej, z /Rect z czterech liczb. GetBlockCount liczy je po wszystkich adnotacjach PDFlibBlock:*, a niestandardowe klucze są wystawiane przez GetBlockProperty
Czy FillTextBlock zmienia font wybrany przed wywołaniem?
Nie. FillTextBlock zapisuje wybrany font, rozmiar tekstu, kolor tekstu i wyrównanie, nakłada własności bloku fontname, fontsize, fillcolor i alignment, a potem przywraca pierwotny stan, nawet gdy dopasowanie zawiedzie
Jak wstawić wektorowe logo albo stronę kodu kreskowego do bloku?
Wczytaj źródłowy PDF, zapamiętaj SelectedDocument jako jego identyfikator, wybierz ponownie dokument szablonu, jeśli trzeba, i wywołaj FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1). Strona jest importowana jako Form XObject, więc zostaje wektorowa i jest dopasowywana do bloku z zachowanymi proporcjami, chyba że dodasz bit 2
Szablony bloków, import stron jako Form XObject i reszta narzędzi danych zmiennych są w PDFlibPas, losLab PDF Library for Delphi, która pokrywa tworzenie PDF, edycję i produkcję drukarską dla projektów Delphi i C++Builder