Jeder PDF-Rasterizer hat eine Datei, die ihn blamiert. In einem Supportfall war es ein Stapel Versorgungsrechnungen, bei dem soft-maskierte Logos als schwarze Rechtecke herauskamen — aber nur beim Kunden, weil dessen Build über eine andere Engine renderte als die Testumgebung. Solche Fehler lassen sich selten in Anwendungscode beheben; die Seite ist gültig, die Engine interpretiert sie nur anders. Behebbar ist die Architektur: Machen Sie die Rendering-Engine zu einer Laufzeitentscheidung, prüfen Sie, welche Engines das ausgelieferte Binary tatsächlich enthält, und protokollieren Sie, welche Engine jedes Bild erzeugt hat. PDFlibPas, losLabs Delphi- und C++Builder-PDF-Bibliothek, ist für genau dieses Muster gebaut — eine Rendering-Aufrufoberfläche, drei auswählbare Engines.
Drei Rasterizer hinter einer Aufrufoberfläche
Die Bibliothek nummeriert ihre Engines: 1 ist der eingebaute Renderer (Standard, unter Windows mit GDI+-Glättungsoptionen), 2 ist Cairo und 3 ist PDFium, zur Laufzeit über SelectRenderer gewählt. Die externen Engines werden aus DLLs geladen, deren Pfade Sie mit SetCairoFileName und SetPDFiumFileName angeben. Unabhängig von der Engine erledigen dieselben Aufrufe die Arbeit — RenderPageToFile, RenderPageToStream, RenderDocumentToFile —, sodass eine Fallback-Strategie einen Integer ändert, nicht den Rendering-Code.
Unter der Haube ist das Zielmodell breiter als Bitmaps: Die Renderer-Klasse unterstützt Metafiles (WMF, EMF, EMF+), EPS, direkte Device Contexts, Drucker und HTML5-Ausgabe, wobei Cairo und PDFium als zusätzliche Ziele erscheinen, wenn sie einkompiliert sind. In diesem Artikel geht es um Rasterausgabe, wo Engine-Unterschiede am sichtbarsten sind.
Niemals annehmen, dass eine Engine existiert: beim Start prüfen
Cairo- und PDFium-Support sind bedingte Kompilierungsfeatures. Ein Binary ohne sie wirft nicht, wenn Sie Engine 2 oder 3 wählen — SelectRenderer gibt einfach etwas anderes als die angeforderte ID zurück, und wenn Sie den Rückgabewert ignorieren, rendern Sie weiter mit dem, was vorher aktiv war. Das verlässliche Muster ist eine Startprüfung:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // engine 1 is always present
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // restore the default before real work
end;
Loggen Sie das Probe-Ergebnis bei jedem Job. Wenn ein Kunde einen Rendering-Unterschied meldet, lautet die erste Diagnosefrage „welche Engines hat Ihre Installation“, und eine Ein-Zeilen-Antwort im Log schlägt eine Remote-Desktop-Sitzung.
Zehn Ausgabeformate hinter einem Options-Integer
Der Options-Parameter der Render-Aufrufe wählt die Ausgabecodierung: 0 ist BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ und 9 HTML5. PNG (5) ist der sinnvolle Standard für Vorschauen und archivierte Seitenbilder; JPEG (1) zusammen mit SetJPEGQuality gewinnt bei fotografischen Scans, wenn Größe zählt.
Ein Format verbirgt eine Stream-Anforderung. Der BMP-Pfad patcht die Auflösungsfelder des Headers, nachdem die Bilddaten geschrieben wurden, indem er zurück zu Offset 0x26 in der Ausgabe seekt. Rendern Sie BMP in einen forward-only Stream — einen Kompressionswrapper, einen Netzwerkstream —, schlägt der Aufruf auf eine Weise fehl, die enginebezogen aussieht, es aber nicht ist. Wenn ein nicht seekbarer Zielstream unvermeidlich ist, rendern Sie stattdessen PNG oder puffern Sie BMP-Ausgabe über einen Memory-Stream.
Die DPI, die Sie übergeben, ist nicht die DPI, die Sie bekommen
Alle Render-Aufrufe nehmen ein DPI-Argument, aber die effektive Auflösung ist dieser Wert multipliziert mit dem globalen Rendermaßstab. SetRenderScale steht standardmäßig auf 1.0; einmal geändert, gilt es still für jedes spätere Rendering auf dieser Instanz:
PDF.SetRenderScale(2.0); // every later render is doubled
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // effectively 300 DPI
PDF.SetRenderScale(1.0); // reset, or your thumbnails arrive huge
Dasselbe Persistenzverhalten gilt für SetRenderCropType und die JPEG-Qualität. In einem Dienst, der Thumbnails, Vorschauen und druckauflösende Bilder aus einer geteilten Instanz rendert, sind diese hängen bleibenden Einstellungen die Wurzel von Tickets wie „Thumbnails sind plötzlich 40 MB“. Setzen Sie den Zustand am Anfang jeder Operation zurück oder widmen Sie jedem Ausgabeprofil eine eigene Instanz.
Die Standard-Engine tunen, bevor man nach einer anderen greift
Ein überraschend großer Anteil der „wir brauchen eine andere Engine“-Anfragen sind in Wahrheit Qualitätsparameter-Anfragen. Der eingebaute Renderer legt sein Glättungsverhalten über SetGDIPlusOptions und die breitere SetRenderOptions-Familie offen, und eine bestimmte GDI+-Runtime kann über SetGDIPlusFileName gewählt werden, wenn eine Deployment-Umgebung eine ungewöhnliche mitbringt. Gezackte Linien bei niedriger DPI, unscharfer Text in Thumbnails und Banding in Verläufen reagieren auf diese Knöpfe — und sie zu justieren kostet zur Auslieferungszeit nichts, während Cairo oder PDFium im Installer zusätzliche DLLs, Bitness-Varianten und eine Updatepflicht bedeuten.
Die praktische Sequenz bei einer Qualitätsbeschwerde lautet daher: zuerst bei der exakten DPI und den Scale-Einstellungen des Kunden reproduzieren, dann Glättungsoptionen der Built-in-Engine probieren und erst danach die Seite bei sonst gleichen Parametern über Engines A/B testen. Rendern Sie dieselbe Seite mit identischer DPI über Engines 1, 2 und 3 nach PNG und hängen Sie alle drei Bilder an das Issue. Meist stimmen zwei von drei überein, was zeigt, ob der Ausreißer die Dokumentinterpretation oder die Basiserwartung ist — Belege, die „rendert falsch“-Streitigkeiten viel schneller klären als adjektivbasierte Fehlermeldungen.
Eine Fallback-Kette, die sich selbst erklärt
Mit Probing und Zustandsdisziplin ist die Fallback-Kette selbst kurz. Fehlererkennung nutzt LastRenderError, das den Meldungstext der Engine für das letzte Rendering trägt:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // built-in first
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium as the heavy fallback
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
Zwei Designpunkte sind bewusst. Die Kette loggt den Grund für jeden Wechsel, weil „diese Seite fällt seit Release 3.7 auf PDFium zurück“ ein Regressionssignal ist, das Sie im Monitoring sehen wollen, nicht begraben. Und die Fallback-Reihenfolge ist eine Policy, die Sie pro Workload wählen sollten: Die eingebaute Engine kommt ohne zusätzliche DLLs aus und ist deshalb in den meisten Installationen der richtige erste Versuch, während Dokumente mit vielen Transparenzgruppen oder ungewöhnlichen Shading-Patterns der klassische Grund sind, überhaupt eine alternative Engine einzubinden. Messen Sie an Ihrem eigenen Korpus; das Korpus gewinnt immer das Argument.
Über Einzelseiten hinaus: TIFF-Batches und Live-Device-Contexts
Zwei Nachbarn der Pro-Seite-Aufrufe runden den Werkzeugkasten ab. RenderAsMultipageTIFFToFile rendert einen Seitenbereichsausdruck direkt in ein mehrseitiges TIFF — die natürliche Form für Archivübergaben an Dokumentenmanagementsysteme, die älter sind als PDF. Und RenderPageToDC zeichnet direkt auf einen Windows-Device-Context für Vorschaucontrols, gesteuert durch ein eigenes Trio persistenter Einstellungen (SetRenderDCOffset, SetRenderDCErasePage plus Crop Type), die dieselbe Reset-Disziplin verdienen wie der Scale-Faktor. Bildschirmvorschau und Druckpfad-Rendering haben genug eigene Fallen, dass sie unten einen eigenen Artikel bekommen.
Rendering-Fragen, beantwortet
Warum gibt SelectRenderer(3) auf meinem Rechner 0 zurück? Entweder wurde das ausgelieferte Binary ohne PDFium-Support kompiliert, oder SetPDFiumFileName zeigt nicht auf eine ladbare DLL — falscher Pfad, falsche Bitness oder fehlende Abhängigkeiten. Das Probe-at-startup-Muster trennt beides: Wenn schon der Filename-Aufruf 0 zurückgab, ist die DLL das Problem.
Welche Engine ist am schnellsten? Über Dokumenttypen hinweg gibt es keine stabile Antwort, genau deshalb lässt die Bibliothek pro Aufruf wählen. Benchmarken Sie jede Engine gegen eine Stichprobe Ihrer echten Dokumente bei Ihrer echten DPI und wiederholen Sie das, wenn sich Engine-DLLs oder Dokumentmix ändern.
Können verschiedene Seiten eines Dokuments verschiedene Engines verwenden? Ja. SelectRenderer wirkt auf nachfolgende Aufrufe der Instanz, sodass eine Fallback-Kette eine einzelne störrische Seite auf einer anderen Engine wiederholen kann, während der Rest des Dokuments auf dem Standard bleibt.
Weiter erkunden
Für Vorschauzeichnung, Druckerauswahl und DevMode-Behandlung geht es weiter mit dem Artikel zu Print Preview und Device Context. Wenn Ihre Renderings eine Hochvolumen-Pipeline über sehr große Dateien speisen, passt der handlebasierte Ansatz in dem Direct-Access-Leitfaden gut zu seitenweisem Rendering über DARenderPageToFile.
Engine-Paketierung, unterstützte Formate und Testbuilds sind auf der PDFlibPas-Produktseite beschrieben.