Technical Article

Print a Loaded PDF in Delphi with HotPDF and TPrinter

HotPDF druckt eine geladene PDF-Datei auf einem physischen Drucker, in einem beliebigen Windows-Gerätekontext (Device Context) oder in der Zwischenablage über drei Aufrufe: PrintLoadedDocument steuert einen VCL-TPrinter, RenderLoadedPageToDC zeichnet eine Seite direkt in einen beliebigen HDC und RenderLoadedPageToClipboard kopiert eine Seite als Bitmap oder als Vektor-Metadatei. HotPDF ist eine native VCL-PDF-Komponente für Delphi und C++Builder. Dieser erstklassige Druckpfad wurde in v2.331.0 eingeführt und in v2.348.0 verfeinert, sodass Sie keine TPrinter-Schleife um einen Bitmap-Renderer mehr manuell programmieren müssen. Alle drei Aufrufe zeichnen den Vektorinhalt der Seite direkt dort, wo es darauf ankommt, sodass der Ausdruck so scharf bleibt, wie der Drucker ihn rendern kann

Warum eine PDF-Seite direkt in den Drucker zeichnen statt in ein Bitmap?

Eine PDF-Seite ist ein Programm aus Vektor-Zeichenoperatoren — Pfaden, Texten und Bildern, die gegen das Grafikmodell ISO 32000-1 ausgeführt werden — und kein Pixelraster. Der naheliegende Weg, eine Seite zu drucken, besteht darin, sie in eine Bitmap zu rastern und diese Bitmap auf das Drucker-Canvas zu übertragen. Das funktioniert zwar, wirft aber das Einzige weg, worin ein Drucker wirklich gut ist. Ein moderner Laserdrucker löst 600 oder 1200 DPI auf. Eine mit 150 oder 300 DPI gerasterte und dann an die Papiergröße angepasste Bitmap wirkt unscharf, mit ausgefransten Textkanten und sichtbaren Pixeln. RenderLoadedPageToDC wählt den besseren Weg: Es spielt den Inhaltsstrom der Seite als GDI-Aufrufe direkt in den Ziel-Gerätekontext ab, sodass Vektortext und Linienzeichnungen den Drucker als Vektoren erreichen und nur einmal, vom Drucker selbst, in dessen eigener Auflösung gerastert werden

Wie druckt man eine geladene PDF in Delphi auf einem physischen Drucker?

HotPDF druckt ein geladenes Dokument mit PrintLoadedDocument(Printer, Options), das einen zugewiesenen VCL-TPrinter und einen THPDFPrintOptions-Datensatz erwartet, die ausgewählten Seiten druckt und die Anzahl der gesendeten Seiten zurückgibt. Setzen Sie zuerst Printer.PrinterIndex, um ein Gerät auszuwählen, füllen Sie den Options-Datensatz — die Klassenfunktion THPDFPrintOptions.Default liefert Ihnen sinnvolle Standardwerte — und rufen Sie die Methode auf. Die Bibliothek öffnet den Druckauftrag mit Printer.BeginDoc, durchläuft den Seitenbereich, fügt ein Printer.NewPage zwischen den Seiten ein und schließt den Auftrag mit Printer.EndDoc, sodass Sie den Lebenszyklus des Druckauftrags niemals manuell verwalten müssen

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 selects the default printer
      Opts := THPDFPrintOptions.Default;  // all pages, 300 DPI, FitPage, colour
      Opts.PageRange := '1,3-5';          // one-based; empty prints every page
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

Die Zeichenkette PageRange ist einsbasiert und liest sich wie ein Druckdialog: '1,3-5' druckt Seite 1 und die Seiten 3 bis 5, und eine leere Zeichenkette druckt das gesamte Dokument. Copies wird direkt an Printer.Copies übergeben. Da die VCL die Sortierung (Collate) über verschiedene Delphi-Versionen hinweg inkonsistent zur Verfügung stellt, setzt HotPDF die Kopienanzahl und überlässt die Sortierung der Standardeinstellung des Druckertreibers. Wenn Sie eine garantierte Sortierung benötigen, stellen Sie diese über den Treiber mittels TPrinterSetupDialog ein, anstatt sich auf das Flag zu verlassen

Wie bestimmen DPI und FitPage den Druckmaßstab?

FitPage und DPI interagieren auf eine Weise, die man vor der Veröffentlichung verstehen sollte. Wenn FitPage auf True gesetzt ist — der Standardwert —, liest HotPDF die MediaBox jeder Seite aus, skaliert sie unter Beibehaltung des Seitenverhältnisses auf Printer.PageWidth and Printer.PageHeight und rendert sie in dem Maßstab, in dem die Seite das Papier ausfüllt. In diesem Modus wird Options.DPI praktisch durch den Skalierungsfaktor überschrieben, da die Ausgabe mit den physischen Seitenabmessungen des Druckers übereinstimmen muss. Wenn FitPage auf False steht, rendert HotPDF jede Seite im Verhältnis 1:1 mit genau dem in Options.DPI angegebenen Wert, wobei 72 PDF-Einheiten auf DPI-Gerätepixel abgebildet werden. Dies ist gewünscht, wenn die Quellseite und das Papier bereits dieselbe Größe haben und Sie eine exakte Auflösung benötigen. Nutzen Sie FitPage, wenn sich die Quellseitengröße und das Papierfach unterscheiden, und 1:1, wenn sie übereinstimmen und Sie eine deterministische Skalierung wünschen

Zwei in v2.348.0 hinzugefügte Optionen korrigieren Ausdrucke, die fast richtig aussehen. Jeder physische Drucker besitzt einen harten Rand — einen nicht bedruckbaren Bereich, den die Hardware nicht erreichen kann —, und standardmäßig landet der Seitenursprung innerhalb dieses Bereichs, sodass eine randlose Seite an zwei Kanten abgeschnitten wird. Setzen Sie IgnoreHardMargin auf True, fragt HotPDF die Werte PHYSICALOFFSETX und PHYSICALOFFSETY des Druckers ab und verschiebt den Geräteursprung so, dass dieser Versatz ausgeglichen wird und der Seitenursprung auf der tatsächlichen Papierecke landet. Die zweite Option, AutoRotate, ist standardmäßig aktiviert: Sie liest die MediaBox jeder Seite aus und dreht den Drucker ins Querformat, wenn eine Seite breiter als hoch ist, damit eine breite Seite unbeschnitten gedruckt wird, anstatt ins Hochformat gezwängt zu werden. AutoRotate tritt zurück, sobald Sie Orientation explizit setzen, da eine bewusste Ausrichtungsauswahl immer Vorrang hat

Wie rendert man eine PDF-Seite in einen beliebigen Gerätekontext?

RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) ist die grundlegende Funktion unter der Druckschleife und für sich genommen sehr nützlich. PrintLoadedDocument ruft sie einmal pro Seite auf dem Drucker-Canvas auf, aber das Ziel kann jeder Windows-HDC sein: ein Offline-Speicher-DC, das Canvas eines Steuerelements in einem benutzerdefinierten Viewer oder eine Druckvorschaufläche. PageIndex ist nullbasiert, W und H sind die Geräteeinheiten des Zielbereichs, und der Renderer durchläuft den Inhaltsstrom /Contents der Seite und sendet GDI-Aufrufe in Ihren DC. Wichtig zu wissen: Die Methode führt keine Hintergrundfüllung aus. Der Aufrufer besitzt die Fläche. Wenn Sie also weißes Papier hinter der Seite haben möchten, füllen Sie den DC vor dem Rendern selbst, was es Ihnen ermöglicht, eine Seite über ein bestehendes Canvas zu legen, falls Sie dies wünschen

// Draw page 1 into an off-screen 24-bit surface at 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 in at 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // caller paints the background
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface now holds the composited page, ready to blit or save
  finally
    Surface.Free;
  end;
end;

Wenn Sie nur ein fertiges TBitmap benötigen — ein Vorschaubild, einen Bildexport oder eine Druckvorschau, die Sie im Cache speichern möchten —, nutzen Sie stattdessen RenderLoadedPageToBitmap, das im Begleitartikel über das Rendern von PDF-Seiten in eine Bitmap behandelt wird. Der Unterschied liegt darin, wer die Fläche besitzt: Der Bitmap-Aufruf alloziert und liefert einen Pixelpuffer zurück, während RenderLoadedPageToDC in einen bereits vorhandenen Gerätekontext zeichnet, wie er Ihnen von einem Drucker-Canvas oder einem Paint-Handler übergeben wird. Beide teilen sich denselben Content-Stream-Interpreter und dieselbe Engine für eingebettete Glyphen, sodass der Text in beiden Fällen mit den exakt eingebetteten Umrissen ausgegeben wird

Wie fügt man eine PDF-Seite in Word oder einen Bildeditor ein?

RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) legt eine Seite in der Windows-Zwischenablage ab, damit sie in Word, PowerPoint, eine E-Mail oder einen Bildeditor eingefügt werden kann. Das Flag AsMetafile wählt das Format, und diese Wahl ist wichtiger, als es zunächst aussieht. Übergeben Sie False, legt HotPDF eine mit Ihrer DPI-Auflösung gerenderte 24-Bit-Gerätebitmap (CF_BITMAP) ab — ein festes Pixelraster, das überall eingefügt werden kann, aber verpixelt, wenn der Empfänger es vergrößert. Übergeben Sie True, wird eine Enhanced Metafile (CF_ENHMETAFILE) abgelegt, in der Text und Vektorgrafiken als GDI-Zeichenbefehle mitreisen. Das eingefügte Ergebnis bleibt bei jedem Zoom gestochen scharf und passt sich der Größe an, die das Zieldokument ihm gibt

// Copy page 2 as a scalable vector metafile for pasting into Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copy the same page as a fixed 300-DPI bitmap instead
Pdf.RenderLoadedPageToClipboard(1, 300, False);

Die Faustregel ist einfach. Wenn ein Benutzer die Seite in ein Dokument einfügt und die Größe ändern möchte — z. B. eine Abbildung in einem Bericht oder ein Absatz in einem Vertragsentwurf —, nutzen Sie das Metafile, da Vektortext sauber gedruckt und gezoomt wird. Wenn das Ziel nur Rasterdaten versteht oder Sie eine exakte Momentaufnahme in einer bestimmten Auflösung wünschen, nutzen Sie das Bitmap. Das Extrahieren der Wörter aus einer Seite (statt eines Bildes davon) ist eine ganz andere Aufgabe, die von der Text-Extraktions-API übernommen wird, welche die Zeichen und deren Positionen statt eines gerenderten Bildes zurückgibt

Wo der Druckpfad an seine Grenzen stößt

Alles auf dieser Seite ist Windows-spezifisch. PrintLoadedDocument, RenderLoadedPageToDC und RenderLoadedPageToClipboard bauen auf TPrinter, dem GDI-Gerätekontextmodell, der Windows-Zwischenablage und Enhanced Metafiles auf — alles Win32- und Win64-Konstrukte. Auf anderen Plattformen können Sie mit HotPDF weiterhin PDFs erstellen, bearbeiten und lesen, aber diese GDI-gestützte Druck- und Zwischenablageschnittstelle steht dort nicht zur Verfügung. Der Renderer zielt zudem auf die gängige Dokumenten-Teilmenge aus Text, Pfaden und Bildern ab, sodass farbintensive Design-Proofs und ICC-gesteuerte Farben eher als getreue Vorschau und nicht als farbverbindlicher Proof gedruckt werden — dieselbe Grenze, die auch der Bitmap-Renderer besitzt

Für das Stapeldrucken über viele Dateien hinweg laden und drucken Sie jedes Dokument nacheinander und behalten Sie den Speicherbedarf im Auge: PrintLoadedDocument rendert zwar eine Seite nach der anderen, aber der geladene Dokumentengraph selbst bleibt für die Dauer des gesamten Auftrags im Speicher. Wenn Sie eine Druckwarteschlange über Hunderte von großen Dateien hinweg betreiben, lässt sich das streamingbasierte Direct File API für große PDF-Workflows gut mit dem Druckpfad kombinieren — teilen, mischen oder wählen Sie zuerst die benötigten Seiten auf der Festplatte aus und laden Sie dann nur das schlanke Ergebnis zum Drucken. So werden PrintLoadedDocument, RenderLoadedPageToDC und RenderLoadedPageToClipboard als Teil der HotPDF-Komponente für Delphi und C++Builder ausgeliefert — eine native VCL-Bibliothek ohne externe DLL-Abhängigkeiten, die die PDF-Erstellung, -Bearbeitung, -Textextraktion, das Rendering und nun auch das Drucken direkt auf TPrinter, in jeden Gerätekontext oder die Zwischenablage abdeckt