PDFlibPas (losLab PDF Library) udfylder PDF-blokskabeloner fra Delphi: en blok er et navngivet rektangel på en skabelonside, typet som tekst, billede eller PDF, og FillTextBlock, FillImageBlock og FillPDFBlock tegner variable data ind i den. Blokke findes som PDFlibBlock:Text-, PDFlibBlock:Image- og PDFlibBlock:PDF-sideannotationer eller i en page-piece-dictionary under /PieceInfo
Brugsscenariet er den kedelige, high-volume slags. En designer lægger et certifikat, en billet eller en rykker ud én gang, og programmet stempler et navn, et sædenummer, et signaturbillede eller en prærenderet stregkodeside ind i faste slots for titusindvis af records. Formularfelter kan klare en del af det, men et AcroForm-felt er et interaktivt objekt med sin egen appearance stream og sine egne regler, og du ender normalt med at flade den ud alligevel. En blok er enklere: et rektangel plus en håndfuld styling-hint, brugt én gang og så smidt væk
Hvad er præcis en PDF-blok i PDFlibPas?
En PDFlibPas-blok er en sideannotation, hvis /Subtype er PDFlibBlock:Text, PDFlibBlock:Image eller PDFlibBlock:PDF, hvis /Rect er placeholder-området, og hvis /Contents-streng rummer name;key=value;key=value. Første segment er bloknavnet, alt efter det er en egenskabsliste. TPDFlib.AddBlock skriver annotationen med /F 4 (Print) og ingen appearance stream, så en uudfyldt blok optager ingen pixels på skærm eller papir
Læsesiden har en anden kilde. Efter at have opremset blokannotationer falder PDFlibPas tilbage til side-dictionary-stien /PieceInfo << /PDFlib << /Blocks ... >> >> og dens variant /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>. Page-piece-dictionaries er standard-PDF, defineret i ISO 32000-1 §14.5 som stedet, hvor en applikation gemmer private data knyttet til en side, og dér lever blokdefinitioner fra eksterne skabelonværktøjer typisk. /Blocks-containeren kan være en dictionary nøglet efter bloknavn eller et rent array; i array-formen navngiver PDFlibPas indgangene Block1, Block2 og så videre, medmindre en blok bærer sit eget /Name
Inde i en page-piece-blok mapper /Subtype-værdien (eller /Type) Image til en billedblok, PDF eller Page mapper til en PDF-blok, og alt andet behandles som tekst. Alle andre nøgler undtagen /Type, /Subtype, /Name, /Rect og /Custom bliver til egenskaber, og nøglerne i en indlejret /Custom-dictionary flattes også ind, så en /Custom << /department (Finance) >>-indgang kan læses som GetBlockProperty(Page, Index, 'department'). Blokindekser er 1-baserede pr. side, annotationsblokke først, page-piece-blokke efter dem
At bygge en blokskabelon med AddBlock
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) tilføjer en blok til den aktuelt valgte side og returnerer 1 ved succes. Den returnerer 0 for et tomt navn, en nul eller negativ bredde eller højde, eller en BlockType anderledes end 'text', 'image' eller 'pdf' (case-insensitiv). Properties føjes til navnet med et semikolon, så den bruger samme key=value;key=value-syntaks som den gemte streng
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// statisk grafik: kant, logo, fast tekst ...
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;
Egenskabsparseren er bevidst afslappet. Nøgler matches case-insensitivt, værdier trimmes, men returneres ellers ordret (en værdi skrevet som {top left} kommer tilbage med sine tuborgklammer), og en manglende nøgle returnerer en tom streng. Den ene hårde grænse er separatoren: en værdi kan ikke indeholde et semikolon, fordi semikolonet afslutter segmentet
Hvordan anvender FillTextBlock font, farve og justering?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) læser blokens egenskaber, anvender dem på tegnetilstanden, får teksten til at passe ind i blokrektanglet med FitTextBox og gendanner derefter kalderens font, tekststørrelse, tekstfarve og justering i en finally-blok. Den gendannelse betyder noget i en merge-løkke: en rød 30 pt-overskriftsblok kan ikke lække sin stil videre ind i det næste DrawText-kald, du laver på samme side
fontname(ellerfont): genbruger først en font, der allerede er indlæst i dokumentet med det navn, mapper derefter Standard 14-navnene somHelvetica-Bold,Times-RomanellerCourier, og prøver til sidstAddTrueTypeFontmed navnetfontsize(ellersize): tekststørrelsen givet videre til fitteren, som formindsker den, hvis teksten ikke passerfillcolor(ellertextcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, bare lister med 3 eller 4 tal, eller navnene black, white, red, green, blue og gray; RGB-komponenter over 1 læses som 0–255alignment,alignogpositionlæses sammen: left, right, center (eller centre) og justify sætter vandret justering, top og bottom sætter lodret placering, og default er lodret centreretfitmethod:wordellernofitsplitforhindrer lange ord i at blive brudt,scaleellerexpandlader størrelsen vokse til at udfylde boksen
CMYK-stien er grunden til, at trykkerier bryder sig om det. {cmyk 0 0.55 1 0.1} kalder SetTextColorCMYK, så den variable tekst udsendes i DeviceCMYK og rammer brandfarven på pressen uden en RGB-omvej. FillTextBlock returnerer 1, når teksten blev tegnet; den returnerer 0 for en tom Text, en manglende blok eller en blok, der ikke er en tekstblok, og i alle de tilfælde efterlades blokken, hvor den er
Udfyldning af billed- og PDF-blokke samt Options-bits
FillImageBlock(Page, BlockIndex, ImageID, Options) tegner et tidligere tilføjet billede, og FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importerer en side fra et andet indlæst dokument som et Form XObject og tegner den, hvilket holder vektorindhold som et segl, en QR-kodeside eller et brevhoved fuldt vektorielt. Begge bevarer kildens aspektforhold og centrerer resultatet inde i blokrektanglet. Alle tre udfyldningsfunktioner deler de samme Options-bits: bit 1 (værdi 1) fjerner blokken efter en vellykket udfyldning, enten ved at slette annotationen eller fjerne indgangen fra /Blocks-containeren, og bit 2 (værdi 2) strækker billed- og PDF-indhold til det eksakte rektangel i stedet for at passe det ind
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 rigtige dokument-ID
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// skabelonen er nu det valgte dokument
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// gå baglæns: bit 1 fjerner hver blok, hvilket renummererer de senere
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;
To detaljer i den løkke er lette at få forkert. Løkken kører fra det højeste indeks og ned, fordi fjernelse af blok 2 gør den gamle blok 3 til blok 2; at tælle opad med bit 1 sat springer hver anden blok over. Og kildedokumentet til FillPDFBlock indlæses først, og dets ID fanges, før skabelonen indlæses, fordi billed-ID'er tilhører det valgte dokument, så AddImageFromFile skal køre, mens skabelonen er valgt. Til batchkørsler, hvor seglet eller brevhovedet skifter pr. record, bruger Form XObject-sidesammenføjningen og de sammensætbare vandmærkeskabeloner i PDFlibPas samme fang-og-tegn-mekanisme på hele sider
Hvorfor kommer blokkoordinaterne ud vendt om?
Blokrektangler kommer ud vendt om, når kode blander de to koordinatsystemer, PDFlibPas omgås. En annotations /Rect gemmes i PDF user space, målt fra sidens bund (ISO 32000-1 §12.5.2), mens TPDFlib-tegne-API'en måler fra toppen. AddBlock tager topbaseret input og gemmer den nederste kant som PageHeight - Top - Height; GetBlockRect og de tre udfyldningsfunktioner læser rektanglet tilbage og konverterer det med PageHeight - URy
I praksis betyder det: brug GetBlockRect(Page, BlockIndex, Left, Top, Width, Height), når du skal bruge slot-geometrien, for eksempel til at tegne en debug-ramme eller placere en tekst ved siden af en blok, og læs ikke /Rect gennem et generisk annotationskald og fodr det direkte ind i DrawText. En page-piece-blok med /Rect [40 700 240 730] på en 792 pt letter-side rapporterer Top = 62, ikke 700. Fejlen, det forhindrer, er den klassiske, hvor hver udfyldt værdi lander spejlet nær bundmargenen uden nogen fejl
Faldgruber værd at lære af
Den mest lærerige fejl i blokkoden var en fejl på ét enkelt tegn. Blokannotationer genkendes af en præfiks-test på subtypen, og 'PDFlibBlock:' er 12 tegn lang. En tidlig version sammenlignede Copy(Subtype, 1, 13) med det 12 tegn lange literal, som aldrig kan være lig, så GetBlockCount returnerede 0 for hver skabelon, og hver udfyldning rapporterede "no such block". Intet crashede, og intet blev logget. Lektionen generaliserer til enhver skabelonmotor: assert blokantallet for en kendt skabelon i en test, fordi lydløs nul-match-opremsning ser præcis ud som en tom side
Den anden faldgrube er dokument-ID'er. TPDFlib.LoadFromFile returnerer en statuskode, 1 for succes og 0 for fiasko, ikke ID'et på dokumentet, den lige indlæste. At give dén 1 med som SourceDocumentID til FillPDFBlock fejler, fordi 1 ikke er et gyldigt dokument-ID. Kald SelectedDocument lige efter en vellykket indlæsning for at få det rigtige ID, og SelectDocument for at skifte tilbage til skabelonen, hvis du indlæste kilden bagefter
En mindre en: FillTextBlock med en tom streng gør intet og beholder blokken. Til et mail merge med valgfrie felter, som en manglende anden adresselinje, er det som regel det, du vil, men den uudfyldte annotation bliver så i outputfilen. Fjern den eksplicit med DeleteAnnotation eller udfyld et enkelt mellemrum med bit 1, hvis outputtet skal være rent. Alle blokkald gemmer og gendanner den valgte side, så en udfyldning på side 3 ikke ændrer, hvor dit næste tegnekald lander
Hvad er grænserne for blokannotationers interoperabilitet?
Blokannotationer er en privat konvention, ikke en standard-PDF-funktion. PDFlibBlock:Text og søskendene er ikke blandt annotationstyperne opført i ISO 32000-1 §12.5.6.1, så en viewer behandler dem som ukendte annotationer. Fordi Invisible-flagget ikke er sat, og der ikke er nogen appearance stream, tegner konforme viewers intet for dem, men nogle viewers kan stadig vise dem i et kommentarpanel, og andre PDF-værktøjer vil hverken forstå eller bevare deres semantik
- Udfyld med bit 1 sat til alt, der forlader dit system, så den endelige fil kun indeholder almindeligt sideindhold
- PDF/A tillader ikke annotationstyper uden for PDF-specifikationen (ISO 19005-1 §6.5.2), så et arkiveret output må ikke bære uudfyldte blokannotationer
- Page-piece-blokke er sikrere at beholde i en skabelon, da
/PieceInfoer standard, og forbrugere ignorerer data, de ikke ejer - Blokke er ikke formularfelter: ingen interaktivitet, ingen feltværdi i filen og ingen eksport til FDF eller XFDF; til data, der skal forblive redigerbare, se FDF-, XFDF- og XFA-formulardataudveksling i PDFlibPas
Skal skabelonen erklære sine variable felter på en standardiseret, validator-tjekbar måde til et print-workflow, er blokke det forkerte værktøj. Dét er PDF/VCR-1 til, og PDF/VCR-1-skabelonens placeholder-manifest i PDFlibPas dækker, hvordan de placeholders lever i strukturtræet i stedet for i annotationer
FAQ
Kan PDFlibPas udfylde blokskabeloner skabt af andre værktøjer?
Ja, når værktøjet gemmer blokke under /PieceInfo /PDFlib /Blocks eller /PieceInfo /PDFlib /Private /Blocks, i dictionary- eller array-form, med en /Rect af fire tal. GetBlockCount tæller dem efter eventuelle PDFlibBlock:*-annotationer, og custom-nøgler eksponeres gennem GetBlockProperty
Ændrer FillTextBlock den font, jeg valgte før kaldet?
Nej. FillTextBlock gemmer den valgte font, tekststørrelse, tekstfarve og justering, anvender blokkens fontname-, fontsize-, fillcolor- og alignment-egenskaber og gendanner den oprindelige tilstand bagefter, selv hvis tilpasningen fejler
Hvordan placerer jeg et vektorlogo eller en stregkodeside i en blok?
Indlæs kilde-PDF'en, gem SelectedDocument som dens ID, vælg skabelondokumentet igen om nødvendigt, og kald FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1). Siden importeres som et Form XObject, så den forbliver vektor og passes ind i blokken med bevaret aspektforhold, medmindre du tilføjer bit 2
Blokkskabeloner, Form XObject-sideimport og resten af værktøjskassen til variable data følger med i PDFlibPas, losLab PDF Library for Delphi, som dækker PDF-oprettelse, redigering og printproduktion til Delphi- og C++Builder-projekter