Die Feature-Anfrage lautete schlicht: „Weiße Seiten sind schmerzhaft zu lesen — bitte einen Dark Mode ergänzen.“ Die erste Implementierung invertierte jedes Pixel der gerenderten Seite, wurde in einer Woche ausgeliefert und erzeugte binnen Tagen ein zweites Ticket: eingescannte Fotos sahen nun wie Filmnegative aus, die gelben Markierungen des Kunden waren zu einem unlesbaren blauen Fleck geworden, und ein Benutzer fragte, warum der Ausdruck schwarz herauskam. Unterstützung für Low-Vision-Anzeige in einem PDF-Viewer ist wirklich wertvoll und wirklich leicht halb richtig umzusetzen. Der Unterschied zwischen beiden Ergebnissen liegt darin zu verstehen, an welcher Stelle der Pipeline jede Farbentscheidung gehört. Die Implementierung unten verwendet PDFium Component, die PDFium-basierte Viewer-Komponente für Delphi, C++Builder und Lazarus, deren Rendering-API genau diese Entscheidungspunkte separat bereitstellt.
Filter sind Präsentationszustand, niemals Dokumentzustand
Die Architekturregel, die die schlimmste Fehlerkategorie verhindert: Ein Lesemodus ändert, wie die Bitmap erzeugt oder nachbearbeitet wird, und sonst nichts. Die PDF-Bytes bleiben unverändert, jeder Modus ist durch erneutes Rendern reversibel, und „Speichern“ persistiert nie ein gefiltertes Erscheinungsbild in die Datei. Das klingt offensichtlich, bis ein Legal Reviewer einen Vertrag unter aktivem Filter druckt und die invertierte Version ablegt — dann stellt sich heraus, dass die Frage „verwendet der Druck das dokumenteigene Erscheinungsbild oder das des Bildschirms“ eine explizite Antwort in Ihrer Spezifikation verdient, keinen Zufall im Codepfad. Halten Sie die Filtereinstellung im Viewer-Zustand, wenden Sie sie zur Renderzeit an, und lassen Sie jeden Exportpfad erklären, welches Erscheinungsbild er nutzt.
Die Regel zahlt sich doppelt aus. Reversibilität ist kostenlos — Moduswechsel rendern aus der unveränderten Quelle neu, also gibt es keinen Undo-Stack und keine Möglichkeit, die Seite durch eine Folge von Moduswechseln zu verschlechtern. Und Szenarien mit mehreren Fenstern bleiben kohärent: Zwei Ansichten desselben Dokuments können unterschiedliche Modi laufen lassen, weil jede Ansicht ihren Präsentationszustand besitzt, während das Dokumentobjekt geteilt bleibt.
Erst rendern, dann transformieren
Das unterstützte Muster ist Post-Render-Bitmap-Verarbeitung: RenderPage erzeugt den Seitenraster, danach passt ein Transformationsdurchlauf ihn an. Die Komponente liefert drei Transformationen — InvertPdfBitmap, DuotonePdfBitmap und GrayscalePdfBitmap — als In-place-Bitmap-Operationen, wodurch der Moduswechsel eine saubere zweistufige Funktion wird:
function TViewerForm.RenderWithMode(W, H: Integer): TBitmap;
begin
Result := Pdf.RenderPage(0, 0, W, H, ro0, [reAnnotations]);
case FReadingMode of
rmInverted: InvertPdfBitmap(Result);
rmHighContrast: DuotonePdfBitmap(Result, clBlack, $0000C8FF); // dark bg, amber text
rmGrayscale: GrayscalePdfBitmap(Result);
end;
// rmNormal falls through: the document keeps its own colors
end;
Zwei Folgen dieses Designs sollten sitzen. Die Transformationskosten sind proportional zur Bitmapgröße, also gehören sie dorthin, wo Ihre Renderresultate gecacht werden — filtern Sie die gecachte Bitmap einmal, nicht bei jedem Paint. Und weil die Transformation auf dem fertigen Raster läuft, wirkt sie gleichmäßig auf Text, Vektorgrafik, Bilder und Anmerkungs-Appearances; genau diese Gleichmäßigkeit macht reine Inversion bei Fotos falsch, weshalb die Duotone-Transformation — die Luminanz auf eine gewählte Dunkel-zu-Hell-Farbskala abbildet, statt Farbtöne zu negieren — der bessere Standard für textlastige Dokumente ist, während Inversion eine explizite Wahl bleibt. Leser, die schärfere Glyphenkanten wünschen, haben einen separaten Hebel: Die Renderoption reNoSmoothText deaktiviert Text-Antialiasing zur Renderzeit und passt bei hohem Zoom gut zu High-Contrast-Mode.
Zwei Graustufen, die sich widersprechen
Die Renderoptionen enthalten reGrayscale, was wie eine Abkürzung um den Nachbearbeitungsschritt wirkt. Es ist nicht dieselbe Operation:
// Engine-level: grayscale applied during rasterization
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);
// Post-process: render in color, convert the finished bitmap
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);
Die Engine-Option greift bei der Rasterausgabe von Bildinhalten, erreicht aber Vektorfüllungen oder Textfarben nicht. Eine Seite mit farbigen Überschriften kann deshalb mit grauen Fotos und hartnäckig blauen Überschriften zurückkommen. GrayscalePdfBitmap auf der fertigen Bitmap konvertiert alles, bedingungslos. Die Renderoption bleibt nützlich, wenn Sie gezielt Bilder entsättigen und Textfarbe als Signal erhalten möchten — manche Low-Vision-Benutzer bevorzugen genau das — aber wenn die Anforderung „Graustufenseite“ lautet, erfüllt die Nachbearbeitung diese Anforderung. Welchen Weg Sie wählen: Denken Sie daran, dass beide Überladungsstile von RenderPage existieren. Die Funktionsform gibt eine Bitmap zurück, die der Aufrufer besitzt und freigeben muss; das wird wichtig, sobald Filter die Zahl der gleichzeitig existierenden Render-Bitmaps erhöhen.
Hintergründe, Auswahlmarken und die PageColor-Falle
Nicht jede Komfortanpassung ist eine Transformation. Die weiße Seitenfläche durch einen warmen Ton zu ersetzen, reicht für blendempfindliche Leser oft aus und hat eine eigene Eigenschaft — mit einer Scope-Regel, die Leute erwischt:
// Affects the on-screen view only
PdfView.PageColor := $00D9EDF2; // warm paper tone behind page content
// RenderPage output ignores PageColor; pass the color explicitly
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);
PageColor ändert, was TPdfView anzeigt, aber Bitmaps, die über RenderPage erzeugt werden, bleiben weiß, solange der Parameter Color nichts anderes verlangt. Das praktische Symptom: Der Bildschirm zeigt die getönte Seite, der Benutzer exportiert oder druckt, und die Ausgabe fällt auf Weiß zurück — ordnen Sie das derselben Export-Policy-Entscheidung aus dem ersten Abschnitt zu.
Die übrigen Farbeigenschaften — HighlightColor für Suchtreffer, SelectionColor für Benutzerauswahl, ReadingWordColor für den Cursor des gesprochenen Wortes — definieren Overlay-Markierungen, und jede davon muss unter jedem angebotenen Filter erneut geprüft werden. Ein amberfarbener Lesecursor, der auf Weiß funktioniert, verschwindet nach Inversion; eine blassblaue Auswahl verschmilzt mit einem High-Contrast-Hintergrund. Pflegen Sie Overlay-Paletten pro Modus statt eines globalen Satzes, und testen Sie die Kombinationen bewusst: Filter plus Text-to-speech ist eine normale Konfiguration für die Benutzer, denen diese Funktion dient, kein Randfall. Die Overlay-Mechanik selbst wird im Artikel zum barrierefreien Reader behandelt.
Zahlen, Verifikation und die Druckfrage
WCAG 2.1 gibt dieser Funktion messbare Ziele: Erfolgskriterium 1.4.3 fordert ein Kontrastverhältnis von 4,5:1 für Fließtext, 1.4.6 erhöht es auf 7:1 für erweiterten Kontrast. Prüfen Sie Ihren High-Contrast-Mode stichprobenartig gegen diese Verhältnisse mit einem Kontrastanalysewerkzeug auf tatsächlich gerenderter Ausgabe — Text über Bildern und Text in Formularfeldern sind die Stellen, an denen Verhältnisse leise scheitern, obwohl der Fließtext besteht.
Für den Druck ist der belastbare Standard das dokumenteigene Erscheinungsbild, mit „drucken wie angezeigt“ als explizite Benutzerwahl; eine gedruckte Seite ist in mehr Workflows ein Nachweis, als Viewer-Autoren erwarten, und ein invertierter Vertragsausdruck ist ein Supportfall mit juristischem Beigeschmack. Da gefiltertes Rendering bei Moduswechseln Bitmaparbeit verdoppelt, ist die Cache-Strategie aus dem Artikel zu Render-Cache und Zoom-Performance die natürliche Ergänzung.
FAQ
Ändert Dark Mode die PDF-Datei?
Nicht in diesem Design — Transformationen laufen auf gerenderten Bitmaps, und die Dokumentbytes bleiben unverändert. Machen Sie dasselbe Versprechen in Ihrer UI-Kopie, denn Prüfer und Auditoren müssen ausdrücklich wissen, dass Anzeigeeinstellungen die Quelldatei nicht berühren.
Warum ist mein exportiertes Bild weiß, obwohl der Bildschirm eine getönte Seite zeigt?
Die Tönung kam von PageColor, das nur die Anzeige von TPdfView beeinflusst. Exporte laufen über RenderPage, das seinen eigenen Parameter Color verwendet — übergeben Sie die Tönung dort, oder akzeptieren Sie für Exporte das dokumenteigene Erscheinungsbild und sagen Sie das in der UI.
Welcher Modus sollte für Low-Vision-Benutzer der Standard sein?
Bieten Sie Auswahl statt einen Gewinner zu küren: hohen Kontrast für die meisten textlastigen Lesevorgänge, Inversion für Benutzer, die ausdrücklich hell auf dunkel wünschen, Graustufen zur Reduzierung von Farbrauschen und eine Hintergrundtönung gegen Blendung. Persistieren Sie die Wahl pro Benutzer, stellen Sie sie beim Start wieder her, und halten Sie einen Ein-Tasten-Weg zurück zu Normal bereit.
Beeinflussen die Filter die Rendering-Performance?
Die Transformationen sind lineare Durchläufe über die fertige Bitmap, daher skalieren ihre Kosten mit der Pixelzahl, nicht mit der Dokumentkomplexität, und bei Bildschirmauflösungen ist der Durchlauf deutlich billiger als das Rendern selbst. Die praktische Optimierung: die gefilterte Bitmap cachen und die Transformation nur neu ausführen, wenn Seite, Zoom oder Modus wechseln — nicht bei jeder Paint-Nachricht.
Die Renderoptionen, Bitmap-Transformationen und Ansichtsfarbeneigenschaften aus diesem Artikel werden mit PDFium Component für Delphi, C++Builder und Lazarus/FPC ausgeliefert, inklusive vollständigem Quellcode, damit die Transformationsimplementierungen geprüft oder erweitert werden können.