Ein sehbehinderter Leser kann schwarzen Text auf weißer Seite bei Standardkontrast nicht erkennen und bittet deshalb um einen Dunkelmodus. Die naive Antwort ist, jedes Pixel der gerenderten Seite zu invertieren. Das ist in einer Woche ausgeliefert und am nächsten Tag kaputt: Gescannte Fotos kommen wie Filmnegative zurück, die gelben Textmarker-Markierungen des Lesers werden zu einem unleserlichen blauen Schmier, und jemand fragt, warum der Ausdruck komplett schwarz herauskam. Das Feature ist es wirklich wert, gebaut zu werden, und wirklich leicht halb richtig hinzubekommen, und die Lücke zwischen den beiden Ergebnissen ist eine einzige Idee: Jede Farbentscheidung gehört an eine bestimmte Stelle der Render-Pipeline, und Invertierung ist das falsche Werkzeug an der falschen Stufe. Der Code hier verwendet die PDFium Component, den PDFium-basierten Viewer für Delphi, C++Builder und Lazarus, dessen Rendering-API diese Stufen getrennt zugänglich macht
Filter sind Darstellungszustand, nie Dokumentzustand
Eine Regel verhindert hier die schlimmste Kategorie von Fehlern: Ein Lesemodus ändert, wie die Bitmap erzeugt oder nachbearbeitet wird, und sonst nichts. Die PDF-Bytes bleiben unangetastet, jeder Modus ist durch erneutes Rendern umkehrbar, und „Speichern“ schreibt nie ein gefiltertes Erscheinungsbild in die Datei zurück. Das klingt offensichtlich, bis ein juristischer Prüfer einen Vertrag unter aktivem Filter druckt und die invertierte Fassung zu den Akten legt. Spätestens dann verdient die Frage „verwendet das Drucken das eigene Erscheinungsbild des Dokuments oder das des Bildschirms“ eine explizite Antwort in Ihrer Spezifikation statt eines Zufalls im Codepfad. Halten Sie die Filtereinstellung im Viewer-Zustand, wenden Sie sie beim Rendern an und lassen Sie jeden Exportpfad erklären, welches Erscheinungsbild er verwendet
Die Regel zahlt sich doppelt aus. Umkehrbarkeit gibt es gratis, weil der Moduswechsel aus der unveränderten Quelle neu rendert: Es gibt keinen Undo-Stack zu pflegen und keine Möglichkeit, dass eine Folge von Moduswechseln die Seite verschlechtert. Szenarien mit mehreren Fenstern bleiben aus demselben Grund kohärent. Zwei Ansichten eines Dokuments können unterschiedliche Modi fahren, da jede Ansicht ihren Darstellungszustand besitzt, während das Dokumentobjekt geteilt bleibt
Erst rendern, dann transformieren
Das unterstützte Muster ist Bitmap-Verarbeitung nach dem Rendern: RenderPage erzeugt das Seitenraster, dann passt ein Transformationsdurchlauf es an. Die Komponente liefert drei Transformationen als In-Place-Bitmap-Operationen mit, InvertPdfBitmap, DuotonePdfBitmap und GrayscalePdfBitmap, was den Moduswechsel zu einer sauberen zweistufigen Funktion macht:
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); // dunkler Hintergrund, Bernsteintext
rmGrayscale: GrayscalePdfBitmap(Result);
end;
// rmNormal fällt durch: Das Dokument behält seine eigenen Farben
end;
Aus diesem Design folgen zwei Dinge. Erstens sind die Transformationskosten proportional zur Bitmap-Größe, also gehört die Arbeit dorthin, wo Ihre Render-Ergebnisse zwischengespeichert werden: Filtern Sie die gecachte Bitmap einmal, nicht bei jedem Zeichnen. Zweitens trifft die Transformation, weil sie auf dem fertigen Raster läuft, Text, Vektorgrafik, Bilder und Annotations-Erscheinungsbilder auf dieselbe Weise. Genau diese Gleichförmigkeit macht die schlichte Invertierung bei Fotos falsch. Sie ist der Grund, warum die Duotone-Transformation für textlastige Dokumente der bessere Standard ist, da sie die Luminanz auf eine gewählte Farbrampe von dunkel nach hell abbildet, statt Farbtöne zu negieren; die Invertierung bleibt als explizite Wahl für Leser verfügbar, die sie wünschen. Schärfere Glyphenkanten sind ein eigener Hebel. Die Render-Option reNoSmoothText schaltet das Text-Anti-Aliasing beim Rendern ab und passt gut zum Hochkontrastmodus bei großem Zoom
Zwei Graustufen, die sich widersprechen
Die Render-Optionen umfassen reGrayscale, was wie eine Abkürzung am Nachbearbeitungsschritt vorbei aussieht. Es ist nicht dieselbe Operation:
// Engine-Ebene: Graustufen während der Rasterisierung angewendet
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);
// Nachbearbeitung: in Farbe rendern, die fertige Bitmap konvertieren
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);
Die Option auf Engine-Ebene wirkt auf die Rasterausgabe von Bildinhalten, erreicht aber weder Vektorfüllungen noch Textfarben, sodass eine Seite mit farbigen Überschriften mit grauen Fotos und hartnäckig blauen Überschriften zurückkommen kann. GrayscalePdfBitmap auf der fertigen Bitmap konvertiert alles, bedingungslos. Die Render-Option verdient ihren Platz weiterhin, wenn Sie Bilder entsättigt haben wollen und die Textfarbe als Signal behalten, was manche sehbehinderten Leser ausdrücklich bevorzugen. Lautet die Anforderung aber „Graustufenseite“, ist die Nachbearbeitung die Fassung, die sie erfüllt. Welchen Weg Sie auch wählen, behalten Sie beide Überladungsstile von RenderPage im Blick. Die Funktionsform gibt eine Bitmap zurück, die dem Aufrufer gehört und von ihm freigegeben werden muss, und das zählt, sobald Filter die Zahl der gerenderten Bitmaps im Umlauf vervielfachen
Hintergründe, Auswahlmarkierungen und die PageColor-Falle
Nicht jede Komfortanpassung ist eine Transformation. Den weißen Seitenhintergrund durch einen warmen Ton zu ersetzen reicht für blendempfindliche Leser oft schon allein, und dafür gibt es eine eigene Eigenschaft. Die Eigenschaft trägt eine Geltungsregel, in der sich die Leute verfangen:
// Wirkt nur auf die Bildschirmansicht
PdfView.PageColor := $00D9EDF2; // warmer Papierton hinter dem Seiteninhalt
// Die RenderPage-Ausgabe ignoriert PageColor; die Farbe explizit übergeben
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);
PageColor ändert, was TPdfView anzeigt, aber über RenderPage erzeugte Bitmaps behalten das Standardweiß, sofern der Parameter Color nichts anderes sagt. Das Symptom ist zuverlässig: Der Bildschirm zeigt die getönte Seite, der Benutzer exportiert oder druckt, und die Ausgabe fällt auf Weiß zurück. Verbuchen Sie das unter derselben Exportrichtlinien-Entscheidung aus dem ersten Abschnitt
Die übrigen Farbeigenschaften definieren Overlay-Markierungen: HighlightColor für Suchtreffer, SelectionColor für die Textauswahl des Benutzers, ReadingWordColor für den Cursor des gesprochenen Wortes. Jede davon muss unter jedem Filter, den Sie anbieten, erneut geprüft werden. Ein bernsteinfarbener Lesecursor, der auf Weiß funktioniert, verschwindet nach der Invertierung; eine blassblaue Auswahl geht in einem Hochkontrast-Hintergrund unter. Pflegen Sie Overlay-Paletten pro Modus statt eines globalen Satzes, und testen Sie die Kombinationen gezielt. Filter plus Sprachausgabe ist für die Leser, denen dieses Feature dient, eine normale Konfiguration, kein Randfall. Die Overlay-Mechanik selbst behandelt der Artikel zum barrierefreien Reader
Zahlen, Verifikation und die Druckfrage
WCAG 2.1 macht dieses Feature zu etwas Messbarem. Erfolgskriterium 1.4.3 verlangt ein Kontrastverhältnis von 4,5:1 für Fließtext, und 1.4.6 hebt es für erhöhten Kontrast auf 7:1 an. Prüfen Sie Ihren Hochkontrastmodus stichprobenartig gegen diese Verhältnisse mit einem Kontrastanalysator, der auf tatsächlich gerenderter Ausgabe läuft. Text über Bildern und Text in Formularfeldern sind die Stellen, an denen die Verhältnisse stillschweigend scheitern, selbst wenn der Fließtext besteht
Das Drucken verdient eine eigene Entscheidung, und der vertretbare Standard ist das eigene Erscheinungsbild des Dokuments, mit „wie angezeigt drucken“ als expliziter Benutzerwahl. Eine gedruckte Seite ist in mehr Arbeitsabläufen Beweismittel, als Viewer-Autoren gemeinhin erwarten, und ein invertierter Ausdruck eines Vertrags ist ein Support-Vorfall mit juristischem Beigeschmack. Eine weitere Paarung zählt für die Performance: Gefiltertes Rendern verdoppelt die Bitmap-Arbeit bei jedem Moduswechsel, also wenden Sie keine Transformation bei jeder Paint-Nachricht an. Cachen Sie die gefilterte Bitmap und führen Sie die Transformation nur dann erneut aus, wenn sich Seite, Zoom oder Modus tatsächlich ändern. Die Caching-Strategie, die das günstig macht, steht im Artikel zu Render-Cache und Zoom-Performance
Eines gehört in Ihre UI statt in Ihren Code: welcher Modus der richtige Standard ist. Es gibt keine einzige Antwort, also bieten Sie den Satz an und lassen Sie den Leser wählen. Hochkontrast passt zu den meisten textlastigen Lektüren, Invertierung zu Lesern, die ausdrücklich hell auf dunkel wollen, Graustufen reduzieren Farbrauschen, und eine Hintergrundtönung kümmert sich um Blendempfindlichkeit. Speichern Sie die Wahl pro Benutzer, stellen Sie sie beim Start wieder her und halten Sie einen Weg mit einem Tastendruck zurück zu Normal offen, denn ein Leser, der in einem Modus landet, den er nicht lesen kann, braucht einen schnellen Ausweg
Die hier verwendeten Render-Optionen, Bitmap-Transformationen und Ansichts-Farbeigenschaften werden mit der PDFium Component für Delphi, C++Builder und Lazarus/FPC ausgeliefert, mit vollständigem Quellcode, sodass die Transformationsimplementierungen geprüft oder erweitert werden können