Technisch artikel

PDF-kleurfilters voor slechtzienden in Delphi met PDFium

Een slechtziende lezer kan zwarte tekst op een witte pagina bij standaardcontrast niet ontcijferen, dus vraagt hij om een dark mode. Het naïeve antwoord is elke pixel van de gerenderde pagina inverteren. Het wordt in een week verscheept en breekt de volgende dag: gescande foto's komen terug als filmnegatieven, de gele markeerstiften van de lezer worden een onleesbaar blauw smeuïg, en iemand vraagt waarom de afdruk effen zwart uitkwam. De feature is oprecht het bouwen waard en oprecht makkelijk half goed te krijgen, en de kloof tussen de twee uitkomsten is één idee: elke kleurbeslissing hoort op een specifiek punt in de render-pipeline, en inversie is het verkeerde gereedschap toegepast op het verkeerde stadium. De code hier gebruikt PDFium Component, de op PDFium gebaseerde viewer voor Delphi, C++Builder en Lazarus, wiens render-API die stadia apart blootstelt

Filters zijn presentatiestatus, nooit documentstatus

Eén regel voorkomt de ergste categorie bugs hier: een leesmodus verandert hoe de bitmap geproduceerd of nabewerkt wordt, en verder niets. De PDF-bytes blijven onaangetast, elke modus is omkeerbaar door opnieuw te renderen, en "opslaan" schrijft nooit een gefilterde appearance terug in het bestand. Dit klinkt vanzelfsprekend tot een juridisch reviewer een contract afdrukt onder een actief filter en de geïnverteerde versie indient. Op dat moment verdient de vraag "gebruikt afdrukken de eigen appearance van het document of die van het scherm" een expliciet antwoord in uw specificatie, niet een toeval van het codepad. Houd de filterinstelling in viewer-status, pas die toe op het moment van renderen, en laat elk exportpad verklaren welke appearance het gebruikt

De regel verdient zichzelf tweemaal terug. Omkeerbaarheid is gratis, want wisselen van modus rendert opnieuw uit de ongewijzigde bron: er is geen undo-stack te onderhouden en geen manier waarop een reeks moduswijzigingen de pagina degradeert. Multi-vensterscenario's blijven om dezelfde reden coherent. Twee weergaven van één document kunnen verschillende modi draaien, omdat elke weergave zijn eigen presentatiestatus bezit terwijl het documentobject gedeeld blijft

Render eerst, transformeer daarna

Het ondersteunde patroon is bitmap-nabewerking na renderen: RenderPage produceert de pagina-raster, dan past een transform-pass die aan. De component levert drie transforms als in-place bitmapoperaties, InvertPdfBitmap, DuotonePdfBitmap en GrayscalePdfBitmap, wat de moduswissel een schone tweekoppelfunctie maakt:

Diagram van een Delphi PDF-viewer leesmoduspijplijn waarin één PDFium RenderPage-aanroep vier leesmodi voedt, elk een in-place bitmaptransformatie zoals InvertPdfBitmap of DuotonePdfBitmap
RenderPage produceert de raster één keer en de actieve leesmodus kiest één in-place bitmaptransformatie
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);  // donkere achtergrond, amberkleurige tekst
    rmGrayscale:    GrayscalePdfBitmap(Result);
  end;
  // rmNormal valt door: het document behoudt zijn eigen kleuren
end;

Uit dit ontwerp volgen twee dingen. Ten eerste is de transform-kosten evenredig met de bitmapgrootte, dus het werk hoort waar uw render-resultaten gecached worden: filter de gecachte bitmap eenmaal, niet bij elke paint. Ten tweede, omdat de transform op de afgewerkte raster draait, raakt hij tekst, vector-kunst, afbeeldingen en appearance van annotaties op dezelfde manier. Die uniformiteit is precies wat gewone inversie fout doet voor foto's. Het is de reden dat de duotone-transform een betere standaard is voor tekstintensieve documenten, omdat die luminantie afbeeldt op een gekozen donker-naar-licht-kleurramp in plaats van tinten te negeren; inversie blijft beschikbaar als expliciete keuze voor lezers die dat willen. Scherpere glyph-randen zijn een aparte hendel. De render-optie reNoSmoothText schakelt tekst-anti-aliasing uit op het moment van renderen en combineert goed met hoogcontrastmodus bij grote zoom

Twee grijstinten die het oneens zijn

De render-opties bevatten reGrayscale, wat eruitziet als een snelkoppeling voorbij de nabewerkingsstap. Het is niet dezelfde bewerking:

Diagram dat de PDFium reGrayscale-renderoptie, die afbeeldingen ontadert maar gekleurde koppen laat, afzet tegen de Delphi GrayscalePdfBitmap-nabewerking die de hele pagina omzet
De engineoptie ontdoet beeldinhoud van verzadiging terwijl de nabewerking elke pixel van de voltooide bitmap converteert
// Op engineniveau: grijswaarden toegepast tijdens rasterisatie
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);

// Nabewerking: render in kleur, converteer de afgewerkte bitmap
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);

De optie op engineniveau geldt de raster-uitvoer van afbeeldingsinhoud maar bereikt geen vector-vullingen of tekstkleuren, dus een pagina met gekleurde kopteksten kan terugkomen met grijze foto's en hardnekkig blauwe kopteksten. GrayscalePdfBitmap op de afgewerkte bitmap converteert alles, onvoorwaardelijk. De render-optie verdient nog steeds zijn plaats wanneer u afbeeldingen gede-satureerd wilt terwijl tekstkleur als signaal behouden blijft, wat sommige slechtziende lezers expliciet prefereren. Maar als de eis luidt "grijswaardenpagina", is nabewerking de versie die daaraan voldoet. Welk pad u ook kiest, houd beide RenderPage-overload-stijlen in gedachten. De functievorm retourneert een bitmap die de aanroeper bezit en moet vrijgeven, en dat doet ertoe zodra filters het aantal gerenderde bitmaps in vlucht vermenigvuldigen

Achtergronden, selectiemarkeringen en de PageColor-val

Niet elke comfortaanpassing is een transform. De witte pagina-achtergrond vervangen door een warme tint is voor lichtgevoelige lezers vaak op zichzelf genoeg, en daar is een eigenschap voor. De eigenschap draagt een scope-regel die mensen vangt:

Diagram van de PageColor-bereikvalkuil in een Delphi PDF-viewer: de tint verschijnt op het scherm terwijl RenderPage-uitvoer wit blijft tenzij de kleur expliciet wordt doorgegeven
PageColor tint alleen de weergave op het scherm en RenderPage houdt een witte pagina aan tenzij de kleur expliciet wordt doorgegeven
// Beïnvloedt alleen de weergave op het scherm
PdfView.PageColor := $00D9EDF2;  // warme papiertint achter de pagina-inhoud

// RenderPage-uitvoer negeert PageColor; geef de kleur expliciet door
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);

PageColor verandert wat TPdfView toont, maar bitmaps via RenderPage geproduceerd behouden het standaard wit tenzij de parameter Color anders zegt. Het symptoom is betrouwbaar: het scherm toont de getinte pagina, de gebruiker exporteert of drukt af, en de uitvoer keert terug naar wit. Schrijf dat weg onder dezelfde export-beleidsbeslissing uit de eerste sectie

De overige kleureigenschappen definiëren overlay-markeringen: HighlightColor voor zoektreffers, SelectionColor voor tekstselectie door de gebruiker, ReadingWordColor voor de uitgesproken-woord-cursor. Elk ervan moet opnieuw gecontroleerd worden onder elk filter dat u aanbiedt. Een amber leescursor die op wit werkt verdwijnt na inversie; een bleekblauwe selectie lost op in een hoogcontrast-achtergrond. Onderhoud per-modus overlay-paletten in plaats van één globale set, en test de combinaties met opzet. Filters plus text-to-speech is een normale configuratie voor de lezers die deze feature dient, geen randgeval. De overlay-machinerie zelf wordt behandeld in het artikel over de toegankelijke lezer

Getallen, verificatie en de afdrukvraag

WCAG 2.1 maakt van deze feature iets wat u kunt meten. Succescriterium 1.4.3 vraagt een contrastverhouding van 4,5:1 voor looptekst, en 1.4.6 verhoogt die naar 7:1 voor verhoogd contrast. Spot-check uw hoogcontrastmodus tegen die verhoudingen met een contrastanalyzer op daadwerkelijke gerenderde uitvoer. Tekst over afbeeldingen en tekst in formuliervelden zijn waar de verhoudingen stilletjes falen zelfs wanneer de looptekst slaagt

Afdrukken verdient een eigen beslissing, en de verdedigbare standaard is de eigen appearance van het document, met "afdrukken zoals weergegeven" aangeboden als expliciete gebruikerskeuze. Een afgedrukte pagina is bewijs in meer workflows dan viewerauteurs geneigd zijn te verwachten, en een geïnverteerde afdruk van een contract is een supportincident met juridische bijsmaak. Nog één koppeling doet ertoe voor prestaties: gefilterd renderen verdubbelt het bitmapwerk bij elke moduswissel, dus pas geen transform toe op elk paint-bericht. Cache de gefilterde bitmap en draai de transform opnieuw alleen wanneer de pagina, zoom of modus daadwerkelijk verandert. De caching-strategie die dit goedkoop maakt staat in het artikel over render-cache en zoom-prestaties

Eén ding om in uw UI te beslissen in plaats van in uw code: welke modus de juiste standaard is. Er is niet één antwoord, dus bied de set aan en laat de lezer kiezen. Hoogcontrast past bij de meeste tekstintensieve leeswerk, inversie past bij lezers die expliciet licht-op-donker willen, grijstinten snijdt kleurruis weg, en een achtergrondtint handelt lichtgevoeligheid af. Persisteer de keuze per gebruiker, herstel die bij opstarten, en houd een pad van één toetsaanslag terug naar normaal, want een lezer die in een onleesbare modus landt heeft een snelle uitweg nodig

De render-opties, bitmap-transformen en view-kleureigenschappen die hier gebruikt worden, worden meegeleverd met PDFium Component voor Delphi, C++Builder en Lazarus/FPC, met volledige broncode zodat de transform-implementaties ge-audit of uitgebreid kunnen worden