PDFlibPas (losLab PDF Library) füllt PDF-Block-Templates aus Delphi: Ein Block ist ein benanntes Rechteck auf einer Template-Seite, typisiert als Text, Bild oder PDF, und FillTextBlock, FillImageBlock und FillPDFBlock zeichnen variable Daten hinein. Blöcke werden als PDFlibBlock:Text-, PDFlibBlock:Image- und PDFlibBlock:PDF-Seiten-Annotationen gefunden oder in einem Page-Piece-Dictionary unter /PieceInfo
Der Anwendungsfall ist die langweilige, massenhafte Sorte. Ein Designer legt ein Zertifikat, ein Event-Ticket oder eine Mahnung einmal an, und das Programm stempelt einen Namen, eine Sitzplatznummer, ein Unterschriftsbild oder eine vorgerenderte Barcode-Seite für zehntausend Datensätze in feste Slots. Formularfelder können einen Teil davon, aber ein AcroForm-Feld ist ein interaktives Objekt mit eigenem Appearance-Stream und eigenen Regeln, und am Ende flattet man es sowieso. Ein Block ist einfacher: ein Rechteck plus eine Handvoll Styling-Hinweise, einmal verbraucht und dann weggeworfen
Was genau ist ein PDF-Block in PDFlibPas?
Ein PDFlibPas-Block ist eine Seiten-Annotation, deren /Subtype PDFlibBlock:Text, PDFlibBlock:Image oder PDFlibBlock:PDF ist, deren /Rect der Platzhalterbereich ist und deren /Contents-String name;key=value;key=value enthält. Das erste Segment ist der Blockname, alles danach ist eine Eigenschaftsliste. TPDFlib.AddBlock schreibt die Annotation mit /F 4 (Print) und ohne Appearance-Stream, sodass ein ungefüllter Block weder auf dem Bildschirm noch auf Papier Pixel belegt
Die Leseseite hat eine zweite Quelle. Nach dem Aufzählen der Block-Annotationen greift PDFlibPas auf den Seiten-Dictionary-Pfad /PieceInfo << /PDFlib << /Blocks ... >> >> und seine Variante /PieceInfo << /PDFlib << /Private << /Blocks ... >> >> >> zurück. Page-Piece-Dictionarys sind Standard-PDF, definiert in ISO 32000-1 §14.5 als der Ort, an dem eine Anwendung private, an eine Seite angehängte Daten ablegt, und dort leben Blockdefinitionen aus externen Template-Authoring-Tools meist. Der /Blocks-Container kann ein nach Blockname verschlüsseltes Dictionary oder ein einfaches Array sein; in der Array-Form benennt PDFlibPas die Einträge Block1, Block2 und so weiter, sofern ein Block nicht sein eigenes /Name mitbringt
In einem Page-Piece-Block mappt der /Subtype- (oder /Type)-Wert Image auf einen Bildblock, PDF oder Page auf einen PDF-Block, und alles andere wird als Text behandelt. Jeder andere Schlüssel außer /Type, /Subtype, /Name, /Rect und /Custom wird eine Eigenschaft, und die Schlüssel eines verschachtelten /Custom-Dictionarys werden mit eingeebnet, sodass ein Eintrag /Custom << /department (Finance) >> als GetBlockProperty(Page, Index, 'department') lesbar ist. Blockindizes sind pro Seite 1-basiert, Annotationsblöcke zuerst, Page-Piece-Blöcke danach
Ein Block-Template mit AddBlock erstellen
TPDFlib.AddBlock(BlockType, BlockName, Left, Top, Width, Height, Properties) fügt der aktuell ausgewählten Seite einen Block hinzu und gibt bei Erfolg 1 zurück. Es gibt 0 zurück für einen leeren Namen, eine null oder negative Breite oder Höhe oder einen BlockType, der nicht 'text', 'image' oder 'pdf' ist (Groß-/Kleinschreibung egal). Properties wird mit einem Semikolon an den Namen angehängt, verwendet also dieselbe key=value;key=value-Syntax wie der gespeicherte String
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
// statisches Artwork: Rahmen, Logo, fester 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;
Der Eigenschaftsparser ist bewusst tolerant. Schlüssel werden case-insensitiv abgeglichen, Werte werden getrimmt, aber sonst wortwörtlich zurückgegeben (ein als {top left} geschriebener Wert kommt mit seinen geschweiften Klammern zurück), und ein fehlender Schlüssel liefert einen leeren String. Die eine harte Grenze ist das Trennzeichen: Ein Wert darf kein Semikolon enthalten, denn das Semikolon beendet das Segment
Wie wendet FillTextBlock Schrift, Farbe und Ausrichtung an?
TPDFlib.FillTextBlock(Page, BlockIndex, Text, Options) liest die Block-Eigenschaften, wendet sie auf den Zeichenzustand an, passt den Text mit FitTextBox in das Blockrechteck ein und stellt danach in einem finally-Block die Schrift, Textgröße, Textfarbe und Ausrichtung des Aufrufers wieder her. Diese Wiederherstellung zählt in einer Merge-Schleife: Ein roter 30-pt-Überschriftenblock darf seinen Stil nicht in den nächsten DrawText-Aufruf auf derselben Seite hineintragen
fontname(oderfont): verwendet zuerst eine bereits im Dokument unter diesem Namen geladene Schrift, mappt dann die Standard-14-Namen wieHelvetica-Bold,Times-RomanoderCourierund versucht zuletztAddTrueTypeFontmit dem Namenfontsize(odersize): die dem Fitter übergebene Textgröße, die er verkleinert, wenn der Text nicht passtfillcolor(odertextcolor,color):#RRGGBB,{rgb r g b},{cmyk c m y k},{gray g}, nackte Listen aus 3 oder 4 Zahlen oder die Namen black, white, red, green, blue und gray; RGB-Komponenten über 1 werden als 0–255 gelesenalignment,alignundpositionwerden gemeinsam gelesen: left, right, center (oder centre) und justify setzen die horizontale Ausrichtung, top und bottom die vertikale Platzierung, die Voreinstellung ist vertikal zentriertfitmethod:wordodernofitsplitverhindert, dass lange Wörter gebrochen werden,scaleoderexpandlässt die Größe wachsen, um die Box zu füllen
Der CMYK-Pfad ist der Grund, warum Druckereien aufhorchen. {cmyk 0 0.55 1 0.1} ruft SetTextColorCMYK auf, also wird der variable Text in DeviceCMYK emittiert und trifft die Markenfarbe in der Presse ohne RGB-Rundtrip. FillTextBlock gibt 1 zurück, wenn der Text gezeichnet wurde; es gibt 0 zurück für einen leeren Text, einen fehlenden Block oder einen Block, der kein Textblock ist, und in all diesen Fällen bleibt der Block an Ort und Stelle
Bild- und PDF-Blöcke füllen, und die Options-Bits
FillImageBlock(Page, BlockIndex, ImageID, Options) zeichnet ein zuvor hinzugefügtes Bild, und FillPDFBlock(Page, BlockIndex, SourceDocumentID, SourcePage, Options) importiert eine Seite eines anderen geladenen Dokuments als Form XObject und zeichnet sie, was Vektorinhalt wie ein Siegel, eine QR-Code-Seite oder ein Briefkopf vollständig als Vektor belässt. Beide wahren das Seitenverhältnis der Quelle und zentrieren das Ergebnis im Blockrechteck. Alle drei Fill-Funktionen teilen sich dieselben Options-Bits: Bit 1 (Wert 1) entfernt den Block nach einer erfolgreichen Füllung, entweder durch Löschen der Annotation oder durch Entfernen des Eintrags aus dem /Blocks-Container, und Bit 2 (Wert 2) streckt Bild- und PDF-Inhalt auf das exakte Rechteck, statt ihn einzupassen
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; // die echte Dokument-ID
if Lib.LoadFromFile(TemplateFile, '') <> 1 then Exit;
// das Template ist jetzt das ausgewählte Dokument
SignatureID := Lib.AddImageFromFile(SignatureFile, 0);
// rückwärts laufen: Bit 1 entfernt jeden Block und nummeriert spätere um
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;
Zwei Details in dieser Schleife sind leicht falsch zu machen. Die Schleife läuft vom höchsten Index abwärts, weil das Entfernen von Block 2 den alten Block 3 zu Block 2 macht; aufwärts zu zählen, überspringt mit gesetztem Bit 1 jeden zweiten Block. Und das Quelldokument für FillPDFBlock wird zuerst geladen und seine ID erfasst, bevor das Template geladen wird, denn Bild-IDs gehören zum ausgewählten Dokument, sodass AddImageFromFile laufen muss, während das Template ausgewählt ist. Für Batch-Läufe, bei denen Siegel oder Briefkopf pro Datensatz wechseln, verwenden das Form-XObject-Seitenzusammenfügen und die komponierbaren Wasserzeichen-Templates in PDFlibPas denselben Erfassen-und-Zeichnen-Mechanismus auf ganzen Seiten
Warum kommen die Block-Koordinaten gespiegelt heraus?
Blockrechtecke kommen gespiegelt heraus, wenn Code die beiden Koordinatensysteme vermischt, mit denen PDFlibPas arbeitet. Eine Annotation /Rect wird im PDF-User-Space gespeichert, gemessen vom Seitenunterrand (ISO 32000-1 §12.5.2), während die TPDFlib-Zeichen-API von oben misst. AddBlock nimmt auf Top bezogene Eingaben entgegen und speichert die Unterkante als PageHeight - Top - Height; GetBlockRect und die drei Fill-Funktionen lesen das Rechteck zurück und wandeln es mit PageHeight - URy
In der Praxis heißt das: Verwenden Sie GetBlockRect(Page, BlockIndex, Left, Top, Width, Height), wenn Sie die Slot-Geometrie brauchen, etwa um einen Debug-Rahmen zu zeichnen oder eine Beschriftung neben einen Block zu setzen, und lesen Sie /Rect nicht über einen generischen Annotation-Aufruf und füttern es nicht direkt in DrawText. Ein Page-Piece-Block mit /Rect [40 700 240 730] auf einer 792-pt-Letter-Seite meldet Top = 62, nicht 700. Der Bug, den das verhindert, ist der klassische: Jeder gefüllte Wert landet gespiegelt nahe der unteren Margine, und nichts meldet einen Fehler
Fallstricke, an denen man lernen kann
Der lehrreichste Bug im Block-Code war ein Ein-Zeichen-Ausrutscher. Block-Annotationen werden über einen Präfixtest am Subtyp erkannt, und 'PDFlibBlock:' ist 12 Zeichen lang. Eine frühe Version verglich Copy(Subtype, 1, 13) mit diesem 12-Zeichen-Literal, was nie gleich sein kann, sodass GetBlockCount für jedes Template 0 zurückgab und jede Füllung "no such block" meldete. Nichts stürzte ab und nichts wurde geloggt. Die Lehre lässt sich auf jede Template-Engine verallgemeinern: Prüfen Sie die Blockanzahl eines bekannten Templates in einem Test, denn eine stille Enumeration mit null Treffern sieht exakt so aus wie eine leere Seite
Die zweite Falle sind Dokument-IDs. TPDFlib.LoadFromFile gibt einen Statuscode zurück, 1 für Erfolg und 0 für Fehlschlag, nicht die ID des gerade geladenen Dokuments. Diese 1 als SourceDocumentID an FillPDFBlock zu übergeben scheitert, weil 1 keine gültige Dokument-ID ist. Rufen Sie SelectedDocument direkt nach einem erfolgreichen Laden auf, um die echte ID zu bekommen, und SelectDocument, um zum Template zurückzuwechseln, falls Sie danach die Quelle geladen haben
Eine kleinere: FillTextBlock mit einem leeren String tut nichts und behält den Block. Für einen Mail Merge mit optionalen Feldern, etwa einer fehlenden zweiten Adresszeile, ist das meist genau, was Sie wollen, aber die ungefüllte Annotation bleibt dann in der Ausgabedatei. Entfernen Sie sie explizit mit DeleteAnnotation oder füllen Sie ein einzelnes Leerzeichen mit Bit 1, wenn die Ausgabe sauber sein muss. Alle Block-Aufrufe sichern und stellen die ausgewählte Seite wieder her, sodass eine Füllung auf Seite 3 nicht ändert, wohin Ihr nächster Zeichenaufruf geht
Welche Interoperabilitätsgrenzen haben Block-Annotationen?
Block-Annotationen sind eine private Konvention, kein Standard-PDF-Feature. PDFlibBlock:Text und seine Geschwister gehören nicht zu den in ISO 32000-1 §12.5.6.1 aufgeführten Annotationstypen, also behandelt ein Viewer sie als unbekannte Annotationen. Weil das Invisible-Flag nicht gesetzt ist und es keinen Appearance-Stream gibt, zeichnen konforme Viewer nichts für sie, aber manche Viewer listen sie trotzdem in einem Kommentar-Panel auf, und andere PDF-Werkzeuge werden ihre Semantik weder verstehen noch bewahren
- Füllen Sie mit gesetztem Bit 1 für alles, was Ihr System verlässt, damit die finale Datei nur gewöhnlichen Seiteninhalt enthält
- PDF/A erlaubt keine Annotationstypen außerhalb der PDF-Spezifikation (ISO 19005-1 §6.5.2), eine archivierte Ausgabe darf also keine ungefüllten Block-Annotationen tragen
- Page-Piece-Blöcke sind sicherer in einem Template aufzuheben, denn
/PieceInfoist Standard, und Konsumenten ignorieren Daten, die ihnen nicht gehören - Blöcke sind keine Formularfelder: keine Interaktivität, kein Feldwert in der Datei und kein Export nach FDF oder XFDF; für Daten, die editierbar bleiben müssen, siehe FDF-, XFDF- und XFA-Formulardatenaustausch in PDFlibPas
Muss das Template seine variablen Felder für einen Print-Workflow auf standardisierte, von Validatoren prüfbare Weise deklarieren, sind Blöcke das falsche Werkzeug. Dafür ist PDF/VCR-1 da, und das PDF/VCR-1-Template-Platzhalter-Manifest in PDFlibPas zeigt, wie diese Platzhalter im Strukturbaum statt in Annotationen leben
FAQ
Kann PDFlibPas Block-Templates füllen, die andere Tools erstellt haben?
Ja, wenn das Tool Blöcke unter /PieceInfo /PDFlib /Blocks oder /PieceInfo /PDFlib /Private /Blocks ablegt, als Dictionary oder Array, mit einem /Rect aus vier Zahlen. GetBlockCount zählt diese nach allen PDFlibBlock:*-Annotationen, und eigene Schlüssel liegen über GetBlockProperty offen
Ändert FillTextBlock die Schrift, die ich vor dem Aufruf gewählt habe?
Nein. FillTextBlock sichert die gewählte Schrift, Textgröße, Textfarbe und Ausrichtung, wendet die fontname-, fontsize-, fillcolor- und alignment-Eigenschaften des Blocks an und stellt den Originalzustand danach wieder her, selbst wenn das Einpassen scheitert
Wie platziere ich ein Vektor-Logo oder eine Barcode-Seite in einem Block?
Laden Sie das Quell-PDF, merken Sie SelectedDocument als seine ID vor, wechseln Sie bei Bedarf wieder zum Template-Dokument und rufen Sie FillPDFBlock(Page, BlockIndex, SourceID, SourcePage, 1) auf. Die Seite wird als Form XObject importiert, bleibt also Vektor und wird unter Wahrung ihres Seitenverhältnisses in den Block eingepasst, außer Sie ergänzen Bit 2
Block-Templates, der Form-XObject-Seitenimport und der Rest des Werkzeugkastens für variable Daten sind in PDFlibPas, der losLab PDF Library for Delphi enthalten, die PDF-Erstellung, -Bearbeitung und Druckaufbereitung für Delphi- und C++Builder-Projekte abdeckt