PDFlibPas (losLab PDF Library) fyller PDF-blockmallar från Delphi: ett block är en namngiven rektangel på en mallsida, typad som text, bild eller PDF, och FillTextBlock, FillImageBlock och FillPDFBlock ritar variabel data i den. Block hittas som sidannoteringar av typen PDFlibBlock:Text, PDFlibBlock:Image och PDFlibBlock:PDF, eller i en page-piece-ordbok under /PieceInfo
Användningsfallet är den tråkiga, högvolymiga sorten. En formgivare lägger ut ett certifikat, en eventbiljett eller en påminnelse en gång, och programmet stämplar ett namn, ett platsnummer, en signaturbild eller en förrenderad streckkodssida i fasta luckor för tiotusen poster. Formulärfält kan göra en del av det, men ett AcroForm-fält är ett interaktivt objekt med sin egen utseendeström och sina egna regler, och du hamnar oftast i att platta ut det ändå. Ett block är enklare: en rektangel plus ett fåtal formgivningsanvisningar, konsumerat en gång och sedan kastat
Vad är egentligen ett PDF-block i PDFlibPas?
Ett PDFlibPas-block är en sidannotering vars /Subtype är PDFlibBlock:Text, PDFlibBlock:Image eller PDFlibBlock:PDF, vars /Rect är platshållarytan, och vars /Contents-sträng håller name;key=value;key=value. Det första segmentet är blocknamnet, allt efter det är en egenskapslista. TPDFlib.AddBlock skriver annoteringen med /F 4 (Print) och ingen utseendeström, så ett ofyllt block upptar inga pixlar på skärm eller papper
Lässidan har en andra källa. Efter uppräkning av blockannoteringar faller PDFlibPas tillbaka på sidordboksvägen /PieceInfo << /PDFlib << /Blocks ... >> >> och dess variant /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>. Page-piece-ordböcker är standard-PDF, definierade i ISO 32000-1 §14.5 som platsen där ett program lagrar privat data fäst vid en sida, och där brukar blockdefinitioner från externa mallverktyg leva. /Blocks-behållaren får vara en ordbok nycklad på blocknamn eller en vanlig array; för arrayformen namnger PDFlibPas posterna Block1, Block2 och så vidare, om inte ett block bär sitt eget /Name
Inuti ett page-piece-block mappas /Subtype- (eller /Type)-värdet Image till ett bildblock, PDF eller Page till ett PDF-block, och allt annat behandlas som text. Varje annan nyckel utöver /Type, /Subtype, /Name, /Rect och /Custom blir en egenskap, och nycklarna i en nästlad /Custom-ordbok plattas också ut, så att en post /Custom << /department (Finance) >> är läsbar som GetBlockProperty(Page, Index, 'department'). Blockindex är 1-baserade per sida, annoteringsblock först, page-piece-block efter dem
Att skriva en blockmall med AddBlock
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) lägger till ett block på den valda sidan och returnerar 1 vid framgång. Den returnerar 0 för ett tomt namn, en bredd eller höjd som är noll eller negativ, eller en BlockType annan än 'text', 'image' eller 'pdf' (skiftlägesokänsligt). Properties fogas till namnet med ett semikolon, så den använder samma key=value;key=value-syntax som den lagrade strängen
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// statisk grafik: ram, logotyp, fast text ...
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;
Egenskapsparsaren är medvetet lös. Nycklar matchas skiftlägesokänsligt, värden trimmas men returneras i övrigt ordagrant (ett värde skrivet som {top left} kommer tillbaka med sina klamrar), och en saknad nyckel returnerar en tom sträng. Den enda hårda gränsen är avgränsaren: ett värde får inte innehålla semikolon, eftersom semikolonet avslutar segmentet
Hur tillämpar FillTextBlock typsnitt, färg och justering?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) läser blockegenskaperna, tillämpar dem på rittillståndet, anpassar texten till blockrektangeln med FitTextBox, och återställer sedan anroparens typsnitt, textstorlek, textfärg och justering i ett finally-block. Den återställningen spelar roll i en sammanslagningsloop: ett rött 30 pt-rubrikblock får inte läcka sin stil in i nästa DrawText-anrop du gör på samma sida
fontname(ellerfont): återanvänder först ett typsnitt redan laddat i dokumentet med det namnet, mappar sedan Standard 14-namnen somHelvetica-Bold,Times-RomanellerCourier, och försöker till sistAddTrueTypeFontmed namnetfontsize(ellersize): textstorleken som skickas till anpassaren, som krymper den om texten inte får platsfillcolor(ellertextcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, blotta listor med 3 eller 4 tal, eller namnen black, white, red, green, blue och gray; RGB-komponenter över 1 läses som 0–255alignment,alignochpositionläses tillsammans: left, right, center (eller centre) och justify sätter horisontell justering, top och bottom sätter vertikal placering, och standardläget är vertikalt centreratfitmethod:wordellernofitsplithindrar långa ord från att brytas,scaleellerexpandlåter storleken växa för att fylla rutan
CMYK-vägen är skälet att tryckerier bryr sig. {cmyk 0 0.55 1 0.1} anropar SetTextColorCMYK, så den variabla texten sänds ut i DeviceCMYK och träffar varumärkesfärgen på pressen utan en omväg över RGB. FillTextBlock returnerar 1 när texten ritades; den returnerar 0 för en tom Text, ett saknat block, eller ett block som inte är ett textblock, och i alla de fallen lämnas blocket kvar
Att fylla bild- och PDF-block, och Options-bitarna
FillImageBlock(Page, BlockIndex, ImageID, Options) ritar en tidigare tillagd bild, och FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importerar en sida ur ett annat laddat dokument som ett Form XObject och ritar den, vilket håller vektorinnehåll som ett sigill, en QR-kodsida eller ett brevhuvud fullt vektoriellt. Båda bevarar källans bildförhållande och centrerar resultatet inne i blockrektangeln. Alla tre fyllnadsfunktionerna delar samma Options-bitar: bit 1 (värde 1) tar bort blocket efter en lyckad fyllning, antingen genom att radera annoteringen eller genom att ta bort posten ur /Blocks-behållaren, och bit 2 (värde 2) sträcker bild- och PDF-innehåll till exakt rektangeln i stället för att anpassa det
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; // det riktiga dokument-ID:t
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// mallen är nu det valda dokumentet
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// gå baklänges: bit 1 tar bort varje block, vilket omnumrerar senare
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;
Två detaljer i den loopen är lätta att få fel. Loopen går från högsta index nedåt eftersom att ta bort block 2 gör gamla block 3 till block 2; att räkna uppåt med bit 1 satt hoppar över vartannat block. Och källdokumentet för FillPDFBlock laddas först och dess ID fångas innan mallen laddas, eftersom bild-ID:n tillhör det valda dokumentet, så AddImageFromFile måste köras medan mallen är vald. För batchkörningar där sigillet eller brevhuvudet byter per post använder Form XObject-sidsammanfogningen och sammansättningsbara vattenstämpelmallar i PDFlibPas samma fånga-och-rita-mekanism på hela sidor
Varför kommer blockkoordinaterna ut spegelvända?
Blockrektanglar kommer ut spegelvända när kod blander de två koordinatsystem PDFlibPas har att göra med. En annoteringens /Rect lagras i PDF-användarutrymme, mätt från sidans botten (ISO 32000-1 §12.5.2), medan rit-API:et TPDFlib mäter från toppen. AddBlock tar toppbaserad indata och lagrar undre kanten som PageHeight - Top - Height; GetBlockRect och de tre fyllnadsfunktionerna läser tillbaka rektangeln och konverterar den med PageHeight - URy
I praktiken betyder det: använd GetBlockRect(Page, BlockIndex, Left, Top, Width, Height) när du behöver luckans geometri, till exempel för att rita en avlusningsram eller placera en bildtext bredvid ett block, och läs inte /Rect via ett generiskt annoteringsanrop och mata in den rakt i DrawText. Ett page-piece-block med /Rect [40 700 240 730] på en 792 pt Lettersida rapporterar Top = 62, inte 700. Felet detta förhindrar är det klassiska där varje ifyllt värde hamnar speglat nära bottenmarginalen utan att något rapporteras
Fallgropar värda att lära av
Det mest lärorika felet i blockkoden var en en-teckens förskrivning. Blockannoteringar känns igen med ett prefixtest på subtypen, och 'PDFlibBlock:' är 12 tecken långt. En tidig version jämförde Copy(Subtype, 1, 13) mot den 12-teckensliteralen, vilket aldrig kan bli lika, så GetBlockCount returnerade 0 för varje mall och varje fyllning rapporterade "no such block". Inget krashade och inget loggades. Lärdomen generaliseras till vilken mallmotor som helst: gör en kontroll av en känd malls blockantal i ett test, för tyst uppräkning med noll träffar ser exakt ut som en tom sida
Den andra fällan är dokument-ID:n. TPDFlib.LoadFromFile returnerar en statuskod, 1 för framgång och 0 för misslyckande, inte ID:t på dokumentet den just laddade. Att skicka den 1:an som SourceDocumentID till FillPDFBlock misslyckas eftersom 1 inte är ett giltigt dokument-ID. Anropa SelectedDocument direkt efter en lyckad laddning för att få det riktiga ID:t, och SelectDocument för att växla tillbaka till mallen om du laddade källan efteråt
En mindre: FillTextBlock med en tom sträng gör ingenting och behåller blocket. För ett mail merge med valfria fält, som en saknad andra adressrad, är det oftast vad du vill, men den ofyllda annoteringen blir då kvar i utdatafilen. Ta bort den explicit med DeleteAnnotation eller fyll ett enskilt mellanslag med bit 1 om utdata måste vara rent. Alla blockanrop sparar och återställer den valda sidan, så en fyllning på sida 3 ändrar inte vart nästa ritningsanrop går
Vilka är interoperabilitetsgränserna för blockannoteringar?
Blockannoteringar är en privat konvention, inte en standardfunktion i PDF. PDFlibBlock:Text och dess syskon finns inte bland annoteringstyperna listade i ISO 32000-1 §12.5.6.1, så en visare behandlar dem som okända annoteringar. Eftersom Invisible-flaggan är nollställd och det inte finns någon utseendeström ritar konforma visare ingenting för dem, men vissa visare kan ändå lista dem i en kommentarspanel, och andra PDF-verktyg varken förstår eller bevarar deras semantik
- Fyll med bit 1 satt för allt som lämnar ditt system, så att den slutliga filen bara innehåller vanligt sidinnehåll
- PDF/A tillåter inte annoteringstyper utanför PDF-specifikationen (ISO 19005-1 §6.5.2), så ett arkiverat utdata får inte bära ofyllda blockannoteringar
- Page-piece-block är säkrare att behålla i en mall, eftersom
/PieceInfoär standard och konsumenter ignorerar data de inte äger - Block är inte formulärfält: ingen interaktivitet, inget fältvärde i filen och ingen export till FDF eller XFDF; för data som måste förbli redigerbar, se FDF-, XFDF- och XFA-formulärdatautbyte i PDFlibPas
Om mallen måste deklarera sina variabla fält på ett standardiserat, validatorerbart sätt för ett tryckeriarbetsflöde är block fel verktyg. Det är det PDF/VCR-1 är till för, och PDF/VCR-1-mallens platshållarmanifest i PDFlibPas täcker hur de platshållarna lever i strukturträdet i stället för i annoteringar
FAQ
Kan PDFlibPas fylla blockmallar skapade av andra verktyg?
Ja, när verktyget lagrar block under /PieceInfo /PDFlib /Blocks eller /PieceInfo /PDFlib /Private /Blocks, i ordboks- eller arrayform, med en /Rect av fyra tal. GetBlockCount räknar de efter eventuella PDFlibBlock:*-annoteringar, och anpassade nycklar exponeras via GetBlockProperty
Ändrar FillTextBlock typsnittet jag valde före anropet?
Nej. FillTextBlock sparar det valda typsnittet, textstorleken, textfärgen och justeringen, tillämpar blockets egenskaper fontname, fontsize, fillcolor och alignment, och återställer originaltillståndet efteråt, även om anpassningen misslyckas
Hur placerar jag en vektorlogotyp eller streckkodssida i ett block?
Ladda käll-PDF:en, lagra SelectedDocument som dess ID, välj malldokumentet igen vid behov, och anropa FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1). Sidan importeras som ett Form XObject, så den förblir vektoriell och anpassas till blocket med bevarat bildförhållande om du inte lägger till bit 2
Blockmallar, Form XObject-sidimport och resten av verktygslådan för variabel data ingår i PDFlibPas, losLab PDF Library for Delphi, som täcker PDF-skapande, redigering och tryckproduktion för Delphi- och C++Builder-projekt