Seitenheftung setzt den Inhalt vorhandener Seiten auf einer gemeinsamen Leinwand zusammen, was sich vom Zusammenführen von Dateien unterscheidet. Die losLab PDF Library implementiert Form-XObject-Seitenheftung und zusammensetzbare Wasserzeichen-Vorlagen für Delphi: StitchPageOverlay, StitchPagesSideBySide und StitchPagesVertically platzieren erfasste Seiten in beliebigem Maßstab mit voller Vektortreue, und TPDFlibCompositeWatermark stapelt Text- und Bildwasserzeichen über einen Seitenbereich in einem einzigen Aufruf. Wenn Sie eigentlich ganze Seiten aus mehreren Dokumenten an eine Datei anhängen müssen, gehört dies zur Merge-Familie, die in dem Artikel über schnelles PDF-Zusammenführen auf Byte-Ebene beschrieben wird; in diesem Artikel geht es um das Kombinieren von Seiteninhalten innerhalb eines Koordinatenraums
Die Anforderungen, die hierher führen, sind alltäglich und hartnäckig. Eine Schule möchte zwei Prüfungsseiten auf ein einziges Querformat-Blatt aufteilen, um den Papierverbrauch zu halbieren. Ein Lokalisierungsprüfer möchte die englische Seite und ihre Übersetzung nebeneinander auf einer einzigen Seite sehen, die sich gemeinsam scrollen lassen. Ein Anwaltsteam möchte jeden Entwurf mit drei Schichten gleichzeitig stempeln: einem Logo in der Ecke, einem diagonalen VERTRAULICH in der Mitte und einer kleinen Fußzeile, und das nur auf den Seiten 1 bis 3. Alle drei Fälle basieren auf demselben zugrunde liegenden Problem, nämlich dem Platzieren von bereits gesetzten Seiteninhalten an einer anderen Stelle in einer anderen Größe, ohne sie zu einer Bitmap zu degradieren
Warum schlägt das Verketten von PDF-Inhaltsströmen fehl?
Natives Heften, also das Kopieren des /Contents-Stroms der Quellseite in die Zielseite, schlägt aus drei strukturellen Gründen fehl. Erstens kollidieren Koordinaten: Beide Inhaltsströme nehmen ihren eigenen Ursprung unten links in ihrer eigenen MediaBox an, sodass der eingefügte Inhalt an der falschen Stelle über dem vorhandenen Inhalt landet. Zweitens kollidieren Ressourcennamen: Jede Seite löst Operatoren wie /F1 Tf und /Im0 Do mit ihrem eigenen /Resources-Wörterbuch auf, und zwei Seiten binden routinemäßig denselben Namen an unterschiedliche Schriftarten oder Bilder, sodass eine davon stillschweigend mit der falschen Ressource gerendert wird. Drittens bietet eine rohe Stromkopie überhaupt keine Skalierung; das Beste, was Sie tun können, ist eine Translation (Verschiebung)
Form-XObjects lösen alle drei Probleme auf einmal, und deshalb baut die losLab PDF Library ihre Heftfunktionen darauf auf. ISO 32000-1 §8.10 definiert ein Form-XObject als einen in sich geschlossenen Inhaltsstrom mit einem eigenen /Resources-Wörterbuch und einem eigenen Koordinatenraum, der durch Aufrufen seines Namens unter der jeweils aktiven aktuellen Transformationsmatrix in eine Seite eingefügt wird. CapturePage verpackt eine gesamte vorhandene Seite, Inhalt plus Ressourcen, in ein /Subtype /Form-XObject, und die Heftfunktionen positionieren es dann mit einer cm-Matrix: Skalieren, Verschieben, Rotieren nach Belieben. Da die Quelle Vektor bleibt, bleibt Text Text und Pfade bleiben Pfade auf jeder Zoomstufe – dieselbe Eigenschaft, die Vektorgrafiken und Schattierungsmuster auflösungsunabhängig macht. Es wird nichts gerastert, und das isolierte Ressourcenwörterbuch bedeutet, dass die erfasste Seite nicht mit der Zielseite um den Namen /F1 streiten kann
Wie kombiniert man zwei PDF-Seiten auf einer einzigen Seite?
StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) ist die direkte Antwort: Es erfasst beide Quellseiten, erstellt eine neue breitere Seite und platziert die beiden Erfassungen links und rechts mit einem Zwischenraum dazwischen, jeweils skaliert auf TargetHeight. Die Funktion gibt die neue Seitennummer (1-basiert) oder 0 bei Fehlschlag zurück, und beide Quellseiten werden als Teil des Vorgangs erfasst und ausgeblendet. Der Fall mit zwei Prüfungsseiten pro Blatt ist eine Aufgabe von drei Zeilen
var
Lib: TPDFlib;
NewPage: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('exam.pdf', '');
// Pages 1 and 2 on one new wider page with an 18pt gutter,
// both scaled to a 595pt output height (A4 landscape)
NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
if NewPage > 0 then
Lib.SaveToFile('exam-2up.pdf');
finally
Lib.Free;
end;
end;
Für die vertikale Zusammensetzung stapelt StitchPagesVertically(PageRanges, Gap, TargetWidth) einen gesamten Seitenbereich auf einer einzigen hohen Seite im Poster-Stil. PageRanges akzeptiert die Standardbereichssyntax wie '1-3,5'. Jede Quellseite wird so skaliert, dass ihre Breite mit TargetWidth übereinstimmt, sodass der Höhenbeitrag jeder Seite ihrer eigenen Höhe multipliziert mit ihrem eigenen Skalierungsfaktor entspricht und die Ausgabeseitenhöhe die Summe dieser Werte plus jeweils ein Gap-Abstand zwischen jedem Paar ist. Dies ist das Format, das Sie für Dokumente zur kontinuierlichen Überprüfung (Continuous-Scroll) oder lange Belege wünschen, die aus Quellen im Letter-Format zusammengestellt wurden
Wie überlagert man eine Seite auf eine andere?
StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) zeichnet eine vorhandene Seite auf eine andere vorhandene Seite, anstatt eine neue zu erstellen. Die Quellseite wird als Form-XObject erfasst und auf dem Ziel im angegebenen Rechteck gezeichnet, mit einer Opacity (Deckkraft) von 0 bis 1 (1 is deckend). Breite und Höhe sind freie Parameter, sodass sich die Platzierung streckt, wenn Ihr Verhältnis vom eigenen Seitenverhältnis der Quellseite abweicht; übergeben Sie ein proportionales Paar, wenn Sie eine unverzerrte Miniaturansicht der Quelle wünschen. Eine typische Anwendung ist ein Dokument, dessen Freigabeblock als eigene Seite gepflegt und auf das Deckblatt gestempelt wird
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('report.pdf', '');
// Page 5 holds the approval block; place it on page 1
// in a 260 x 180 box at 45% opacity. Page 5 is consumed:
// it is captured and hidden, so pages 6..n become 5..n-1
if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
Lib.SaveToFile('report-approved.pdf');
finally
Lib.Free;
end;
end;
Was passiert nach einer Erfassung mit den Seitennummern?
CapturePage verbraucht die Quellseite: Ihr Inhalt wird in das XObject verschoben und die nun leere Seite wird ausgeblendet, sodass sich jede Seitennummerierung oberhalb der Quelle sofort um eins nach unten verschiebt. Dies ist die wichtigste Semantik in der gesamten Heft-API. Die losLab PDF Library handhabt die Arithmetik innerhalb jedes Heftaufrufs, StitchPageOverlay passt den Zielindex an, wenn das Ziel oberhalb der Quelle liegt, und StitchPagesSideBySide erfasst zuerst die Seite mit der höheren Nummer, sodass der Index der zweiten Erfassung weiterhin gültig ist. Ihr eigener umgebender Code muss jedoch die Verschiebung zwischen den Aufrufen berücksichtigen. Zwei Regeln halten Multi-Stitch-Code korrekt: Erfassen Sie alle benötigten Seitengrößen oder Seitennummern, bevor die erste Erfassung stattfindet, da sich diese danach ändern. Und wenn Sie selbst mehrere Seiten in einer Schleife erfassen, iterieren Sie in absteigender Seitenreihenfolge, damit das Ausblenden einer späteren Seite niemals den Index einer früheren Seite stört, die Sie noch nicht erfasst haben
Wie wendet man ein mehrschichtiges Wasserzeichen auf einen Seitenbereich an?
Die Wasserzeichen-Vorlagenschicht in PDFlibWatermark.pas verwandelt das Prinzip „einmal definieren, auf viele Seiten anwenden“ in Objekte. TPDFlibWatermarkBase enthält die Gemeinsamkeiten aller Wasserzeichen: eine Neun-Punkte-Position (wpTopLeft bis wpBottomRight, plus wpCustom, das rohe X/Y-Koordinaten verwendet), einen Angle (Winkel) in Grad, eine Opacity von 0 bis 1 und eine PageRange-Zeichenfolge, wobei leer alle Seiten bedeutet. TPDFlibTextWatermark fügt Text, eine Schriftart-ID (Standard ist Helvetica), Größe und eine RGB-Farbe hinzu; TPDFlibImageWatermark fügt eine Bild-ID von einem zuvor hinzugefügten Bild sowie eine Platzierungsbreite und -höhe hinzu. TPDFlibCompositeWatermark enthält eine Liste von Unterwasserzeichen und wendet sie rekursiv an, und TPDFlib.ApplyWatermark ist der Einstiegspunkt für einen einzigen Aufruf, der den eigenen Seitenbereich der Vorlage berücksichtigt und die Anzahl der gestempelten Seiten zurückgibt. Der dreischichtige Rechtsstempel sieht so aus
var
Lib: TPDFlib;
Logo: TPDFlibImageWatermark;
Diagonal, Footer: TPDFlibTextWatermark;
Stack: TPDFlibCompositeWatermark;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('draft.pdf', '');
Stack := TPDFlibCompositeWatermark.Create;
try
Logo := TPDFlibImageWatermark.Create;
Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
Logo.Position := wpTopRight;
Logo.Width := 90;
Logo.Height := 32;
Stack.Add(Logo);
Diagonal := TPDFlibTextWatermark.Create;
Diagonal.Text := 'CONFIDENTIAL';
Diagonal.Position := wpCenter;
Diagonal.Angle := 45;
Diagonal.FontSize := 60;
Diagonal.Opacity := 0.15;
Stack.Add(Diagonal);
Footer := TPDFlibTextWatermark.Create;
Footer.Text := 'Internal review copy';
Footer.Position := wpBottomCenter;
Footer.FontSize := 9;
Footer.Opacity := 0.6;
Stack.Add(Footer);
Stack.PageRange := '1-3'; // children inherit this range
Lib.ApplyWatermark(Stack);
Lib.SaveToFile('draft-marked.pdf');
finally
Stack.Free; // composite frees its children
end;
finally
Lib.Free;
end;
end;
Zwei Zusammensetzungsdetails sind erwähnenswert. Die Bereichsvererbung ist einseitig und zerstörungsfrei: Ein untergeordnetes Element mit einem leeren PageRange erbt während des Apply-Vorgangs vorübergehend den Bereich des Verbunds, während ein untergeordnetes Element, das seinen eigenen Bereich festlegt, diesen beibehält, sodass Sie die Fußzeile auf jeder Seite festheften können, während der diagonale Stempel nur den Entwurfsabschnitt abdeckt. Und das Eigentum folgt dem Verbund: TPDFlibCompositeWatermark.Destroy gibt seine untergeordneten Elemente frei, sodass Sie den Verbund einmal freigeben und sonst nichts, wie im obigen Beispiel
Grenzen, die man kennen sollte
Es gelten zwei ehrliche Einschränkungen, die sich beide aus der Funktionsweise des Mechanismus ergeben. Erstens verpackt eine Erfassung nur den Inhaltsstrom und die Ressourcen der Seite. Anmerkungen (Annotations), Links, Formularfelder und Kommentare existieren als separate Objekte im Seitenwörterbuch, nicht im Inhaltsstrom, und wandern daher nicht in das XObject; ein geheftetes Layout trägt den sichtbaren Seiteninhalt, aber nicht die interaktive Ebene seiner Quellen. Zweitens ist die Neun-Punkte-Textpositionierung eine Annäherung: Ohne eine vollständige Layout-Engine kann die Bibliothek die genaue gerenderte Breite einer Zeichenfolge nicht kennen, daher schätzt sie das Textfeld bei der Auflösung von Positionen wie wpCenter auf 40 % der Seitenbreite. Das ist genau genug, um ein kurzes Label zu zentrieren. Für eine pixelgenaue Platzierung langer Zeichenfolgen wechseln Sie jedoch zu wpCustom und berechnen die Koordinaten selbst
Die Skalierung ist hier keine praktische Einschränkung, da Erfassung und Heftung Objektverweise manipulieren, anstatt den Seiteninhalt neu zu codieren; wenn Ihre Quellen Hunderte von Megabytes groß sind, gilt vor dem Heften dieselbe Disziplin, die in der Anleitung zum Zusammenführen und Teilen großer Dateien mit direktem Zugriff beschrieben ist. Die hier gezeigten Heftfunktionen und Wasserzeichen-Vorlagenklassen sind in der aktuellen losLab PDF Library für Delphi, C# und VB.NET enthalten, zusammen mit den Low-Level-Primitiven CapturePage, DrawCapturedPage und DrawCapturedPageMatrix, wenn Sie ein Layout benötigen, das die drei vorgefertigten Heftaufrufe nicht abdecken