Drei Rasterizer können dasselbe PDF lesen und sich darüber uneins sein, was darin steht. Die eingebaute Engine in PDF Library for Delphi ist die, die ohne zusätzliche Dateien ausgeliefert wird und alles kompetent rendert, weshalb sie den Standardplatz verdient. Cairo bringt eine andere Transparenz- und Anti-Aliasing-Pipeline mit und ist meist die Wahl, wenn Soft Masks oder Blend-Modi anderswo falsch herauskommen. PDFium trägt den Rendering-Code von Chrome in sich, sodass eine Seite, die im Browser richtig aussieht, unter PDFium meist ebenfalls richtig aussieht, um den Preis einer stattlichen DLL und einer Bitbreite, auf deren Übereinstimmung es besteht. Keine der drei ist abstrakt betrachtet korrekt. Korrektheit gilt pro Dokument, und der einzig ehrliche Weg herauszufinden, welche Engine einen gegebenen Korpus bewältigt, ist, diesen Korpus durch jede von ihnen laufen zu lassen
Das ist das Argument dafür, die Engine als Laufzeitwahl statt als Build-Zeit-Entscheidung zu behandeln. PDF Library for Delphi, die PDF-Bibliothek für Delphi und C++Builder von losLab, stellt alle drei hinter eine einzige Rendering-Oberfläche, sodass die Entscheidung einen Integer kostet statt einer Code-Verzweigung. Der Rest läuft darauf hinaus, sicher zwischen ihnen zu wählen, zu bestätigen, welche Engines eine ausgelieferte Binärdatei tatsächlich enthält, und zu verhindern, dass der Rendering-Zustand den nächsten Job stillschweigend vergiftet
Drei Rasterizer hinter einer Aufrufoberfläche
Die Bibliothek nummeriert ihre Engines. Engine 1 ist der eingebaute Renderer, der Standard, mit GDI+-Glättungsoptionen unter Windows. Engine 2 ist Cairo und Engine 3 ist PDFium, beide zur Laufzeit über SelectRenderer ausgewählt. Die beiden externen Engines laden aus DLLs, deren Pfade Sie vor der Auswahl mit SetCairoFileName und SetPDFiumFileName angeben. Welche Engine auch aktiv ist, die Arbeit läuft über dieselben Aufrufe: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Ein Engine-Wechsel verschiebt eine Zahl; der Rest Ihres Rendering-Codes merkt nichts davon
Das Zielmodell reicht weit über Bitmaps hinaus. Die Renderer-Klasse zielt auch auf Metadateien (WMF, EMF, EMF+), EPS, direkte Gerätekontexte, Drucker und HTML5, wobei Cairo und PDFium nur dann als zusätzliche Ziele auftauchen, wenn sie einkompiliert wurden. Bei der Rasterausgabe gehen die drei Engines am sichtbarsten auseinander, deshalb verwenden die Beispiele hier genau diese
Nie annehmen, dass eine Engine existiert: beim Start sondieren
Cairo und PDFium sind Features bedingter Kompilierung, eine Binärdatei kann also ganz ohne sie gebaut sein. Passiert das, löst die Anfrage nach Engine 2 oder 3 nichts aus. SelectRenderer gibt schlicht einen anderen Wert als die angeforderte ID zurück, und Code, der den Rückgabewert ignoriert, rendert weiter mit der Engine, die ohnehin aktiv war. Die Verteidigung ist eine Sonde beim Start, die jede Engine bittet, sich zu identifizieren, und die Antwort festhält:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // Engine 1 ist immer vorhanden
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); // Standard vor der eigentlichen Arbeit wiederherstellen
end;
Lassen Sie diese Sonde einmal beim Start laufen und schreiben Sie ihr Ergebnis neben jeden Render-Job ins Protokoll. Die mit Abstand häufigste Frage, wenn ein Kunde einen Rendering-Unterschied meldet, lautet, welche Engines seine Installation tatsächlich hat, und eine einzeilige Antwort im Protokoll klärt das ohne Remote-Desktop-Sitzung. Ein nützlicher Nebeneffekt: Gibt SetPDFiumFileName selbst 0 zurück, wissen Sie bereits, dass die DLL das Problem ist (falscher Pfad, falsche Bitbreite, eine fehlende Abhängigkeit) und nicht eine ohne PDFium-Unterstützung kompilierte Binärdatei, denn der Pfadaufruf hat nichts aufgelöst, bevor SelectRenderer überhaupt lief
Zehn Ausgabeformate hinter einem Options-Integer
Der Parameter Options der Render-Aufrufe wählt die Ausgabekodierung: 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 vernünftige Standard für Vorschauen und archivierte Seitenbilder. JPEG (1), gepaart mit SetJPEGQuality, ist die bessere Wahl für fotografische Scans, bei denen die Dateigröße wichtiger ist als scharfe Kanten
Ein Format verbirgt eine Anforderung an den Zielstream. Der BMP-Pfad schreibt zuerst die Bilddaten und springt dann zu Offset 0x26 zurück, um die Auflösungsfelder im Header zu patchen. Richten Sie das auf einen reinen Vorwärtsstream, einen Kompressions-Wrapper oder einen Netzwerk-Socket, scheitert der Aufruf auf eine Weise, die wie ein Engine-Fehler aussieht, aber keiner ist. Ist ein nicht positionierbares Ziel unvermeidbar, rendern Sie stattdessen PNG oder leiten das BMP über einen Memory-Stream und kopieren es weiter, sobald es vollständig ist
Die DPI, die Sie übergeben, sind nicht die DPI, die Sie bekommen
Jeder Render-Aufruf nimmt ein DPI-Argument, aber die Auflösung, die Sie tatsächlich erhalten, ist dieser Wert multipliziert mit dem globalen Render-Maßstab. SetRenderScale beginnt bei 1.0, und sobald Sie ihn ändern, gilt der neue Faktor stillschweigend für jedes spätere Rendern auf dieser Instanz:
PDF.SetRenderScale(2.0); // jedes spätere Rendern wird verdoppelt
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // effektiv 300 DPI
PDF.SetRenderScale(1.0); // zurücksetzen, sonst kommen Ihre Thumbnails riesig an
Dieselbe Hartnäckigkeit gilt für SetRenderCropType und die JPEG-Qualitätseinstellung. In einem Dienst, der Thumbnails, Vorschauen und Bilder in Druckauflösung aus einer gemeinsamen Instanz erzeugt, stecken diese übrig gebliebenen Einstellungen in Wahrheit hinter dem gelegentlichen Ticket „Thumbnails sind plötzlich 40 MB groß“. Zwei saubere Auswege: den relevanten Zustand zu Beginn jeder Operation zurücksetzen oder jedem Ausgabeprofil eine eigene Instanz widmen, damit nichts zwischen ihnen durchsickert
Die Standard-Engine abstimmen, bevor man zu einer anderen greift
Ein überraschend großer Anteil der Anfragen „wir brauchen eine andere Engine“ entpuppt sich als verkleidetes Einstellungsproblem. Der eingebaute Renderer legt sein Glättungsverhalten über SetGDIPlusOptions und die weitere SetRenderOptions-Familie offen, und SetGDIPlusFileName lässt Sie ihn auf eine bestimmte GDI+-Laufzeit richten, wenn eine Deployment-Umgebung eine ungewöhnliche mitbringt. Ausgefranste Strichgrafik bei niedriger DPI, unscharfer Text in Thumbnails, Streifenbildung über Verläufe: All das reagiert auf diese Stellschrauben, und sie zu drehen kostet im Installer nichts. Cairo oder PDFium hinzuzufügen bedeutet dagegen, mehr DLLs auszuliefern, eine zweite oder dritte Bitbreitenvariante zu verfolgen und die Pflicht zu übernehmen, sie zu aktualisieren
Eine Qualitätsbeschwerde hat also eine natürliche Reihenfolge. Reproduzieren Sie sie zuerst bei genau den DPI und dem Maßstab des Kunden, denn in der Hälfte der Fälle verschwindet der Unterschied, sobald diese übereinstimmen. Probieren Sie als Nächstes die Glättungsoptionen der eingebauten Engine. Erst dann stellen Sie die Seite über die Engines hinweg nebeneinander, mit jeder anderen Variablen konstant gehalten: Rendern Sie sie über die Engines 1, 2 und 3 bei identischen DPI nach PNG und hängen Sie alle drei an. Meist stimmen zwei von dreien überein, und diese Mehrheit sagt Ihnen, ob der Ausreißer ein anders interpretiertes Dokument ist oder Ihre eigene Erwartung daneben liegt. Drei konkrete Bilder klären einen Streit über „rendert falsch“ weit schneller als ein Absatz voller Adjektive
Eine Fallback-Kette, die sich selbst erklärt
Sind Sondierung und Zustandsdisziplin erst eingerichtet, ist die Fallback-Kette selbst kurz. Die Fehlererkennung stützt sich auf LastRenderError, das den eigenen Meldungstext der Engine für das letzte Rendern hält und leer ist, wenn das Rendern erfolgreich war:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // zuerst die eingebaute Engine
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 als schwerer 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 tragen hier Gewicht. Die Kette hält fest, warum jeder Wechsel stattfand, denn eine Protokollzeile wie „diese Seite fällt seit Release 3.7 auf PDFium zurück“ ist ein Regressionssignal, das Sie im Monitoring als Trend sehen wollen, nicht verloren. Die Fallback-Reihenfolge selbst ist eine Richtlinie, die man pro Workload wählen sollte. Die eingebaute Engine wird ohne zusätzliche DLLs ausgeliefert, was sie in den meisten Installationen zum richtigen ersten Versuch macht, während Dokumente voller Transparenzgruppen oder ungewöhnlicher Shadings der übliche Grund sind, warum ein Team überhaupt eine alternative Engine einbindet. Keine Engine ist allgemein die schnellste, und genau das ist der Sinn der Wahl pro Aufruf: Benchmarken Sie jede gegen eine Stichprobe Ihrer echten Dokumente bei Ihren echten DPI, und wiederholen Sie diese Messung, wann immer sich die Engine-DLLs oder der Dokumentmix ändern. Der Korpus gewinnt die Diskussion jedes Mal
Jenseits einzelner Seiten: TIFF-Batches und Live-Gerätekontexte
Zwei Nachbarn der seitenbezogenen 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. RenderPageToDC zeichnet für Vorschausteuerelemente direkt auf einen Windows-Gerätekontext, gesteuert von einem eigenen Trio hartnäckiger Einstellungen (SetRenderDCOffset, SetRenderDCErasePage plus der Crop-Typ), die dieselbe Rücksetzdisziplin brauchen wie der Maßstabsfaktor. Bildschirmvorschau und Rendering auf dem Druckpfad bergen genug eigene Fallen, um einen eigenen, unten verlinkten Artikel zu rechtfertigen
Wie es weitergeht
Eine Gewohnheit, die sich mitzunehmen lohnt: Da SelectRenderer für jeden späteren Aufruf auf der Instanz gilt, lässt sich eine einzelne störrische Seite auf einer anderen Engine erneut versuchen, während der Rest des Dokuments beim Standard bleibt. Für das Zeichnen von Vorschauen, die Druckerauswahl und den Umgang mit DevMode fahren Sie mit dem Artikel zu Druckvorschau und Gerätekontext fort. Speisen Renderings eine Pipeline mit hohem Durchsatz über sehr große Dateien, passt der Handle-basierte Ansatz aus dem Direktzugriffs-Leitfaden natürlich zum seitenweisen Rendern über DARenderPageToFile
Engine-Paketierung, unterstützte Formate und Test-Builds sind auf der Produktseite von PDF Library for Delphi beschrieben