PDFlibPas (losLab PDF Library) plní šablony PDF bloků z Delphi: blok je pojmenovaný obdélník na stránce šablony, typovaný jako text, obrázek nebo PDF, a FillTextBlock, FillImageBlock a FillPDFBlock do něj kreslí variabilní data. Bloky se hledají jako anotace stránky PDFlibBlock:Text, PDFlibBlock:Image a PDFlibBlock:PDF, nebo ve slovníku page-piece pod /PieceInfo
Use case je ten nudný, vysokosvazkový druh. Designér vysází certifikát, vstupenku na akci nebo výzvu k úhradě jednou a program na deset tisíc záznamů pečetí do pevných slotů jméno, číslo sedadla, obrázek podpisu nebo předrenderovanou stránku s čárovým kódem. Formulářová pole zvládnou část toho, ale AcroForm pole je interaktivní objekt s vlastním appearance streamem a vlastními pravidly a stejně s ním obvykle skončíte u flattening. Blok je jednodušší: obdélník plus hrst stylizačních nápověd, spotřebuje se jednou a zahodí
Co přesně je PDF blok v PDFlibPas?
Blok PDFlibPas je anotace stránky, jejíž /Subtype je PDFlibBlock:Text, PDFlibBlock:Image nebo PDFlibBlock:PDF, jejíž /Rect je oblast placeholderu a jejíž řetězec /Contents drží name;key=value;key=value. První segment je jméno bloku, všechno za ním je seznam vlastností. TPDFlib.AddBlock zapisuje anotaci s /F 4 (Print) a bez appearance streamu, takže nevyplněný blok nezabírá žádné pixely na obrazovce ani na papíře
Strana čtení má druhý zdroj. Po sečtení blokových anotací sáhne PDFlibPas po cestě slovníku stránky /PieceInfo << /PDFlib << /Blocks ... >> >> a její variantě /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>. Slovníky page-piece jsou standardní PDF, definované v ISO 32000-1 §14.5 jako místo, kam aplikace ukládá privátní data připojená ke stránce, a právě tam obvykle žijí definice bloků z externích nástrojů na autoring šablon. Kontejner /Blocks může být slovník klíčovaný jmény bloků nebo prosté pole; pro formu pole pojmenovává PDFlibPas položky Block1, Block2 a tak dále, pokud blok nenese vlastní /Name
Uvnitř bloku page-piece se hodnota /Subtype (nebo /Type) Image mapuje na blok obrázku, PDF nebo Page na blok PDF a cokoli jiného se bere jako text. Každý další klíč kromě /Type, /Subtype, /Name, /Rect a /Custom se stane vlastností a klíče vnořeného slovníku /Custom se zploští také, takže položka /Custom << /department (Finance) >> se čte jako GetBlockProperty(Page, Index, 'department'). Indexy bloků se počítají od 1 na stránku, nejdřív bloky anotací, za nimi bloky page-piece
Autoring šablony bloků přes AddBlock
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) přidá blok na aktuálně vybranou stránku a vrací 1 při úspěchu. Vrací 0 pro prázdné jméno, nulovou nebo zápornou šířku či výšku, nebo BlockType jiný než 'text', 'image' nebo 'pdf' (nerozlišuje velikost písmen). Properties se připojí ke jménu středníkem, takže používá tutéž syntaxi key=value;key=value jako uložený řetězec
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// statická grafika: rámeček, logo, pevné texty ...
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 vlastností je záměrně shovívavý. Klíče se porovnávají bez ohledu na velikost písmen, hodnoty se ořezou, ale jinak se vracejí slovo od slova (hodnota zapsaná jako {top left} se vrátí se svými složenými závorkami) a chybějící klíč vrátí prázdný řetězec. Jediný tvrdý limit je oddělovač: hodnota nesmí obsahovat středník, protože středník segment ukončuje
Jak FillTextBlock aplikuje font, barvu a zarovnání?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) přečte vlastnosti bloku, aplikuje je na kreslicí stav, vmístí text do obdélníku bloku přes FitTextBox a potom v bloku finally obnoví font volajícího, velikost textu, barvu textu a zarovnání. To obnovení záleží ve smyčce merge: červený nadpisový blok 30 pt nemůže prosakovat svým stylem do dalšího volání DrawText, které na téže stránce uděláte
fontname(nebofont): nejdřív znovu použije font už načtený v dokumentu pod tímto jménem, pak mapuje jména Standard 14 jakoHelvetica-Bold,Times-RomanneboCouriera nakonec zkusíAddTrueTypeFonts tímto jménemfontsize(nebosize): velikost textu předaná fitteru, který ji zmenší, když se text nevejdefillcolor(nebotextcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, holé seznamy 3 nebo 4 čísel, nebo jména black, white, red, green, blue a gray; složky RGB nad 1 se čtou jako 0–255alignment,alignapositionse čtou společně: left, right, center (nebo centre) a justify nastavují vodorovné zarovnání, top a bottom nastavují svislé umístění a výchozí stav je svisle na středfitmethod:wordnebonofitsplitzabrání lámání dlouhých slov,scaleneboexpandnechá velikost narůst, aby vyplnila box
CMYK cesta je důvod, proč se o to zajímají tiskárny. {cmyk 0 0.55 1 0.1} zavolá SetTextColorCMYK, takže variabilní text se vysílá v DeviceCMYK a trefí firemní barvu na tisku bez okliky přes RGB. FillTextBlock vrací 1, když se text nakreslil; vrací 0 pro prázdný Text, chybějící blok nebo blok, který není textovým blokem, a ve všech těch případech zůstává blok na místě
Plnění bloků obrázku a PDF a bity Options
FillImageBlock(Page, BlockIndex, ImageID, Options) nakreslí dříve přidaný obrázek a FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importuje stránku jiného načteného dokumentu jako Form XObject a nakreslí ji, což udrží vektorový obsah jako razítko, stránku s QR kódem nebo hlavičku dopisu plně vektorovou. Obojí zachovává poměr stran zdroje a vycentruje výsledek uvnitř obdélníku bloku. Všechny tři fill funkce sdílejí stejné bity Options: bit 1 (hodnota 1) odstraní blok po úspěšném plnění, buď smazáním anotace, nebo odebráním položky z kontejneru /Blocks, a bit 2 (hodnota 2) natáhne obsah obrázku a PDF na přesný obdélník místo vymístění
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; // skutečné ID dokumentu
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// šablona je teď vybraným dokumentem
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// jděte pozpátku: bit 1 odstraňuje každý blok, čímž přečísluje pozdější
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;
Dva detaily v té smyčce se snadno pokazí. Smyčka jede od nejvyššího indexu dolů, protože odstranění bloku 2 promění starý blok 3 v blok 2; počítání nahoru s nastaveným bitem 1 přeskočí každý druhý blok. A zdrojový dokument pro FillPDFBlock se načítá první a jeho ID se chytí před načtením šablony, protože ID obrázků patří vybranému dokumentu, takže AddImageFromFile musí běžet, když je vybraná šablona. Pro dávkové běhy, kde se razítko nebo hlavička mění po záznamech, používá stitching stránek Form XObject a skládací šablony vodoznaků v PDFlibPas tentýž mechanismus chycení a nakreslení na celé stránky
Proč vyjdou souřadnice bloků převrácené?
Obdélníky bloků vyjdou převrácené, když kód promíchá dva souřadné systémy, se kterými PDFlibPas pracuje. Anotace /Rect se ukládá v PDF user space, měřená od spodku stránky (ISO 32000-1 §12.5.2), zatímco kreslicí API TPDFlib měří odshora. AddBlock bere vstup odshora a ukládá dolní hranu jako PageHeight - Top - Height; GetBlockRect a tři fill funkce čtou obdélník zpět a převádějí ho přes PageHeight - URy
V praxi to znamená: použijte GetBlockRect(Page, BlockIndex, Left, Top, Width, Height), když potřebujete geometrii slotu, třeba na nakreslení debug rámečku nebo umístění popisku vedle bloku, a nečtěte /Rect přes generické volání anotací, abyste ho nasypali přímo do DrawText. Page-piece blok s /Rect [40 700 240 730] na letter stránce 792 pt hlásí Top = 62, ne 700. Bug, kterému to předchází, je ten klasický, kdy každá vyplněná hodnota dopadne zrcadlově u dolního okraje a nic nehlásí chybu
Pasti, ze kterých se dá učit
Nejpoučnější bug v blokovém kódu byl přešlap o jeden znak. Blokové anotace se poznávají testem prefixu subtype a 'PDFlibBlock:' je dlouhý 12 znaků. Raná verze porovnávala Copy(Subtype, 1, 13) proti tomuto 12znakovému literálu, což se nikdy nemůže rovnat, takže GetBlockCount vracel 0 pro každou šablonu a každé plnění hlásilo „no such block". Nic nespadlo a nic se nelogovalo. Lekce se zobecňuje na jakýkoli template engine: otestujte počet bloků známé šablony v testu, protože tiché vyčíslení bez zásahů vypadá přesně jako prázdná stránka
Druhá past jsou ID dokumentů. TPDFlib.LoadFromFile vrací status kód, 1 pro úspěch a 0 pro selhání, ne ID dokumentu, který právě načetl. Předání té jedničky jako SourceDocumentID do FillPDFBlock selže, protože 1 není platné ID dokumentu. Zavolejte SelectedDocument hned po úspěšném načtení, abyste dostali skutečné ID, a SelectDocument pro přepnutí zpět na šablonu, pokud jste zdroj načetli poté
Menší z nich: FillTextBlock s prázdným řetězcem nedělá nic a blok ponechá. Pro mail merge s volitelnými poli, třeba chybějícím druhým řádkem adresy, je to obvykle to, co chcete, ale nevyplněná anotace pak zůstává ve výstupním souboru. Odstraňte ji explicitně přes DeleteAnnotation, nebo vyplňte jednu mezeru s bitem 1, když musí být výstup čistý. Všechna bloková volání ukládají a obnovují vybranou stránku, takže plnění na stránce 3 nezmění, kam míří vaše další kreslicí volání
Jaké jsou limity interoperability blokových anotací?
Blokové anotace jsou soukromá konvence, ne standardní funkce PDF. PDFlibBlock:Text a jeho sourozenci nejsou mezi typy anotací uvedenými v ISO 32000-1 §12.5.6.1, takže prohlížeč je bere jako neznámé anotace. Protože je příznak Invisible vypnutý a neexistuje žádný appearance stream, korektní prohlížeče pro ně nenakreslí nic, ale některé prohlížeče je mohou pořád vypisovat v panelu komentářů a další PDF nástroje ani nepochopí, ani nezachovají jejich sémantiku
- Cokoli, co opouští váš systém, plňte s nastaveným bitem 1, aby výsledný soubor obsahoval jen obyčejný obsah stránky
- PDF/A nepovoluje typy anotací mimo specifikaci PDF (ISO 19005-1 §6.5.2), takže archivovaný výstup nesmí nést nevyplněné blokové anotace
- Bloky page-piece jsou v šabloně bezpečnější ponechat, protože
/PieceInfoje standardní a konzumenti ignorují data, která nevlastní - Bloky nejsou formulářová pole: žádná interaktivita, žádná hodnota pole v souboru a žádný export do FDF nebo XFDF; pro data, která musí zůstat editovatelná, viz výměna formulářových dat FDF, XFDF a XFA v PDFlibPas
Pokud musí šablona deklarovat svá variabilní pole standardizovaným, validátorem kontrolovatelným způsobem pro tiskový workflow, bloky jsou špatný nástroj. Pro to je PDF/VCR-1 a manifest placeholderů šablon PDF/VCR-1 v PDFlibPas popisuje, jak tyto placeholdery žijí ve stromu struktury místo v anotacích
FAQ
Dokáže PDFlibPas plnit blokové šablony vytvořené jinými nástroji?
Ano, když nástroj ukládá bloky pod /PieceInfo /PDFlib /Blocks nebo /PieceInfo /PDFlib /Private /Blocks, ve formě slovníku nebo pole, s /Rect o čtyřech číslech. GetBlockCount je počítá po všech anotacích PDFlibBlock:* a vlastní klíče se vystavují přes GetBlockProperty
Mění FillTextBlock font, který jsem před voláním vybral?
Ne. FillTextBlock uloží vybraný font, velikost textu, barvu textu a zarovnání, aplikuje vlastnosti bloku fontname, fontsize, fillcolor a alignment a potom obnoví původní stav, i když vymístění selže
Jak umístím vektorové logo nebo stránku s čárovým kódem do bloku?
Načtěte zdrojové PDF, uložte SelectedDocument jako jeho ID, v případě potřeby znovu vyberte dokument šablony a zavolejte FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1). Stránka se importuje jako Form XObject, takže zůstává vektorová a vymístí se do bloku se zachovaným poměrem stran, pokud nepřidáte bit 2
Blokové šablony, import stránek jako Form XObject a zbytek toolkitu pro variabilní data dodává PDFlibPas, losLab PDF Library for Delphi, který pokrývá tvorbu PDF, editaci a tiskovou produkci pro projekty v Delphi a C++Builderu