PDFlibPas (losLab PDF Library) remplit des modèles de blocs PDF depuis Delphi : un bloc est un rectangle nommé sur une page de modèle, typé texte, image ou PDF, et FillTextBlock, FillImageBlock et FillPDFBlock y dessinent des données variables. Les blocs se présentent comme des annotations de page PDFlibBlock:Text, PDFlibBlock:Image et PDFlibBlock:PDF, ou dans un dictionnaire page-piece sous /PieceInfo
Le cas dusage est du genre ennuyeux et à gros volumes. Un graphiste met en page un certificat, un billet dévénement ou une lettre de relance une fois, et le programme tamponne un nom, un numéro de siège, une image de signature ou une page de code-barres pré-rendue dans des emplacements fixes pour dix mille enregistrements. Les champs de formulaire savent en faire une partie, mais un champ AcroForm est un objet interactif avec son propre flux dapparence et ses propres règles, et vous finissez de toute façon généralement par laplatir. Un bloc est plus simple : un rectangle plus une poignée dindications de style, consommé une fois puis jeté
Quest-ce quun bloc PDF dans PDFlibPas, exactement ?
Un bloc PDFlibPas est une annotation de page dont le /Subtype vaut PDFlibBlock:Text, PDFlibBlock:Image ou PDFlibBlock:PDF, dont le /Rect est la zone réservée, et dont la chaîne /Contents contient name;key=value;key=value. Le premier segment est le nom du bloc, tout ce qui suit est une liste de propriétés. TPDFlib.AddBlock écrit lannotation avec /F 4 (Print) et sans flux dapparence, si bien quun bloc non rempli noccupe aucun pixel à lécran ni sur papier
La lecture a une seconde source. Après lénumération des annotations de blocs, PDFlibPas se rabat sur le chemin de dictionnaire de page /PieceInfo << /PDFlib << /Blocks ... >> >> et sa variante /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >>. Les dictionnaires page-piece sont du PDF standard, définis dans ISO 32000-1 §14.5 comme lendroit où une application stocke des données privées attachées à une page, et cest là que vivent dordinaire les définitions de blocs des outils externes dauteur de modèles. Le conteneur /Blocks peut être un dictionnaire indexé par nom de bloc ou un simple tableau ; pour la forme tableau, PDFlibPas nomme les entrées Block1, Block2 et ainsi de suite sauf si un bloc porte son propre /Name
Dans un bloc page-piece, la valeur /Subtype (ou /Type) Image correspond à un bloc image, PDF ou Page à un bloc PDF, et tout le reste est traité comme texte. Toute autre clé excepté /Type, /Subtype, /Name, /Rect et /Custom devient une propriété, et les clés dun dictionnaire /Custom imbriqué sont aplaties également, si bien quune entrée /Custom << /department (Finance) >> se lit via GetBlockProperty(Page, Index, 'department'). Les indices de blocs partent de 1 par page, blocs dannotations dabord, blocs page-piece ensuite
Écrire un modèle de blocs avec AddBlock
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) ajoute un bloc à la page actuellement sélectionnée et renvoie 1 en cas de succès. Il renvoie 0 pour un nom vide, une largeur ou hauteur nulle ou négative, ou un BlockType autre que 'text', 'image' ou 'pdf' (insensible à la casse). Properties est ajouté au nom avec un point-virgule, si bien quil emploie la même syntaxe key=value;key=value que la chaîne stockée
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// graphisme statique : bordure, logo, libellés fixes ...
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;
Lanalyseur de propriétés est délibérément souple. Les clés se comparent sans tenir compte de la casse, les valeurs sont élaguées mais sinon renvoyées verbatim (une valeur écrite {top left} revient avec ses accolades), et une clé absente renvoie une chaîne vide. La seule limite dure est le séparateur : une valeur ne peut pas contenir de point-virgule, parce que le point-virgule termine le segment
Comment FillTextBlock applique-t-il police, couleur et alignement ?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) lit les propriétés du bloc, les applique à létat de dessin, fait tenir le texte dans le rectangle du bloc avec FitTextBox, puis restaure la police, la taille de texte, la couleur de texte et lalignement de lappelant dans un bloc finally. Cette restauration compte dans une boucle de fusion : un bloc de titre rouge de 30 pt ne peut pas laisser fuir son style dans le prochain appel DrawText que vous faites sur la même page
fontname(oufont) : réutilise dabord une police déjà chargée dans le document sous ce nom, puis mappe les noms Standard 14 tels queHelvetica-Bold,Times-RomanouCourier, et tente enfinAddTrueTypeFontavec le nomfontsize(ousize) : la taille de texte passée à lajusteur, qui la réduit si le texte ne tient pasfillcolor(outextcolor,color) :#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, listes brutes de 3 ou 4 nombres, ou les noms black, white, red, green, blue et gray ; les composantes RGB supérieures à 1 se lisent comme 0–255alignment,alignetpositionse lisent ensemble : left, right, center (ou centre) et justify fixent lalignement horizontal, top et bottom fixent le placement vertical, et le défaut est centré verticalementfitmethod:wordounofitsplitempêche de couper les mots longs,scaleouexpandlaisse la taille grandir pour remplir la boîte
Le chemin CMYK est la raison pour laquelle les imprimeurs sy intéressent. {cmyk 0 0.55 1 0.1} appelle SetTextColorCMYK, si bien que le texte variable est émis en DeviceCMYK et atteint la couleur de marque sur presse sans aller-retour RGB. FillTextBlock renvoie 1 quand le texte a été dessiné ; il renvoie 0 pour un Text vide, un bloc absent ou un bloc qui nest pas un bloc texte, et dans tous ces cas le bloc reste en place
Remplir les blocs image et PDF, et les bits Options
FillImageBlock(Page, BlockIndex, ImageID, Options) dessine une image ajoutée précédemment, et FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importe une page dun autre document chargé comme Form XObject et la dessine, ce qui garde entièrement vectoriel un contenu tel quun sceau, une page de code QR ou un en-tête de lettre. Les deux préservent le rapport de proportions de la source et centrent le résultat dans le rectangle du bloc. Les trois fonctions de remplissage partagent les mêmes bits Options : le bit 1 (valeur 1) retire le bloc après un remplissage réussi, en supprimant lannotation ou en retirant lentrée du conteneur /Blocks, et le bit 2 (valeur 2) étire le contenu image et PDF au rectangle exact au lieu de lajuster
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; // le vrai ID de document
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// le modèle est maintenant le document sélectionné
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// parcourir à rebours : le bit 1 retire chaque bloc et renumérote les suivants
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;
Deux détails de cette boucle sont faciles à rater. La boucle part de lindice le plus haut vers le bas parce que retirer le bloc 2 transforme lancien bloc 3 en bloc 2 ; compter vers le haut avec le bit 1 posé saute un bloc sur deux. Et le document source de FillPDFBlock est chargé en premier et son ID capturé avant que le modèle soit chargé, parce que les IDs dimages appartiennent au document sélectionné, donc AddImageFromFile doit tourner pendant que le modèle est sélectionné. Pour des lots où le sceau ou len-tête change par enregistrement, lassemblage de pages Form XObject et les modèles de filigranes composables de PDFlibPas emploient le même mécanisme de capture et de dessin sur des pages entières
Pourquoi les coordonnées de blocs sortent-elles inversées ?
Les rectangles de blocs sortent inversés quand le code mélange les deux systèmes de coordonnées que PDFlibPas manipule. Un /Rect dannotation est stocké en espace utilisateur PDF, mesuré depuis le bas de la page (ISO 32000-1 §12.5.2), tandis que lAPI de dessin TPDFlib mesure depuis le haut. AddBlock prend une entrée basée sur le haut et stocke le bord bas comme PageHeight - Top - Height ; GetBlockRect et les trois fonctions de remplissage relisent le rectangle et le convertissent avec PageHeight - URy
En pratique cela signifie : utilisez GetBlockRect(Page, BlockIndex, Left, Top, Width, Height) quand vous avez besoin de la géométrie de lemplacement, par exemple pour dessiner un cadre de débogage ou placer une légende à côté dun bloc, et ne lisez pas /Rect via un appel dannotation générique pour lentraîner directement dans DrawText. Un bloc page-piece avec /Rect [40 700 240 730] sur une page letter de 792 pt rapporte Top = 62, pas 700. Le bug que cela prévient est le classique où chaque valeur remplie atterrit en miroir près de la marge basse sans quaucune erreur ne surgisse
Des pièges dont il vaut la peine dapprendre
Le bug le plus instructif du code des blocs était un décalage dun caractère. Les annotations de blocs sont reconnues par un test de préfixe sur le sous-type, et 'PDFlibBlock:' compte 12 caractères. Une première version comparait Copy(Subtype, 1, 13) à ce littéral de 12 caractères, qui ne peut jamais être égal, donc GetBlockCount renvoyait 0 pour chaque modèle et chaque remplissage rapportait "no such block". Rien ne plantait et rien nétait journalisé. La leçon se généralise à tout moteur de modèles : affirmez le nombre de blocs dun modèle connu dans un test, parce quune énumération silencieuse à zéro correspondance ressemble exactement à une page vide
Le second piège est celui des IDs de documents. TPDFlib.LoadFromFile renvoie un code de statut, 1 pour succès et 0 pour échec, pas lID du document quil vient de charger. Passer ce 1 comme SourceDocumentID à FillPDFBlock échoue parce que 1 nest pas un ID de document valide. Appelez SelectedDocument juste après un chargement réussi pour obtenir le vrai ID, et SelectDocument pour revenir au modèle si vous avez chargé la source ensuite
Un plus petit : FillTextBlock avec une chaîne vide ne fait rien et garde le bloc. Pour un publipostage avec des champs optionnels, comme une seconde ligne dadresse manquante, cest dordinaire ce que vous voulez, mais lannotation non remplie reste alors dans le fichier de sortie. Retirez-la explicitement avec DeleteAnnotation ou remplissez une espace unique avec le bit 1 si la sortie doit être propre. Tous les appels de blocs sauvegardent et restaurent la page sélectionnée, si bien quun remplissage en page 3 ne change pas où va votre prochain appel de dessin
Quelles sont les limites dinteropérabilité des annotations de blocs ?
Les annotations de blocs sont une convention privée, pas une fonction PDF standard. PDFlibBlock:Text et ses frères ne figurent pas parmi les types dannotations listés dans ISO 32000-1 §12.5.6.1, donc une visionneuse les traite comme annotations inconnues. Comme le drapeau Invisible est à zéro et quil ny a pas de flux dapparence, les visionneuses conformes ne dessinent rien pour elles, mais certaines visionneuses peuvent toujours les lister dans un panneau de commentaires, et dautres outils PDF ne comprendront ni ne préserveront leur sémantique
- Remplissez avec le bit 1 posé pour tout ce qui quitte votre système, afin que le fichier final ne contienne que du contenu de page ordinaire
- PDF/A nautorise pas de types dannotations hors spécification PDF (ISO 19005-1 §6.5.2), donc une sortie archivée ne doit pas porter dannotations de blocs non remplis
- Les blocs page-piece sont plus sûrs à garder dans un modèle, puisque
/PieceInfoest standard et que les consommateurs ignorent les données quils ne possèdent pas - Les blocs ne sont pas des champs de formulaire : pas dinteractivité, pas de valeur de champ dans le fichier et pas dexport vers FDF ou XFDF ; pour des données qui doivent rester éditables, voyez léchange de données de formulaires FDF, XFDF et XFA dans PDFlibPas
Si le modèle doit déclarer ses champs variables de façon standardisée et vérifiable par un validateur pour un workflow dimpression, les blocs sont le mauvais outil. Cest à cela que sert PDF/VCR-1, et le manifeste demplacements de modèles PDF/VCR-1 dans PDFlibPas couvre la façon dont ces emplacements vivent dans larbre de structure plutôt que dans des annotations
FAQ
PDFlibPas peut-il remplir des modèles de blocs créés par dautres outils ?
Oui, quand loutil stocke les blocs sous /PieceInfo /PDFlib /Blocks ou /PieceInfo /PDFlib /Private /Blocks, sous forme de dictionnaire ou de tableau, avec un /Rect de quatre nombres. GetBlockCount les compte après toute annotation PDFlibBlock:*, et les clés personnalisées sexposent via GetBlockProperty
FillTextBlock change-t-il la police que jai sélectionnée avant lappel ?
Non. FillTextBlock sauvegarde la police sélectionnée, la taille de texte, la couleur de texte et lalignement, applique les propriétés fontname, fontsize, fillcolor et alignment du bloc, puis restaure létat dorigine, même si lajustement échoue
Comment placer un logo vectoriel ou une page de code-barres dans un bloc ?
Chargez le PDF source, stockez SelectedDocument comme son ID, resélectionnez le document modèle si besoin, et appelez FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1). La page est importée comme Form XObject, donc elle reste vectorielle et est ajustée au bloc avec son rapport de proportions préservé sauf si vous ajoutez le bit 2
Les modèles de blocs, limport de pages Form XObject et le reste de la boîte à outils de données variables sont livrés dans PDFlibPas, la losLab PDF Library for Delphi, qui couvre la création, lédition et la production dimpression PDF pour des projets Delphi et C++Builder