Teknisk artikkel

PDF-fargefiltre for svaksynte i Delphi med PDFium

En svaksynt leser kan ikke skille sort tekst fra en hvit side ved standardkontrast, så de ber om en mørk modus. Det naive svaret er å invertere hver eneste piksel i den rendrede siden. Det lanseres på en uke og bryter sammen dagen etter: skannede fotografier kommer tilbake og ligner filmnegativer, leserens gule overstrykningsmerker blir til en uleselig blå utflytende flekk, og noen spør hvorfor utskriften ble helt svart. Funksjonen er reelt verdt å bygge og reelt lett å få halvveis riktig, og forskjellen mellom de to utfallene er én idé: hver fargebeslutning hører hjemme på et bestemt sted i render-pipelinen, og inversjon er feil verktøy brukt på feil trinn. Koden her bruker PDFium Component, den PDFium-baserte viseren for Delphi, C++Builder og Lazarus, hvis render-API eksponerer disse trinnene hver for seg

Filtre er presentasjonstilstand, aldri dokumenttilstand

Én regel forhindrer den verste kategorien feil her: en lesemodus endrer bare hvordan bitmappen produseres eller etterbehandles, og ingenting annet. PDF-bytene forblir urørt, hver modus kan reverseres ved å rendre på nytt, og "lagre" skriver aldri et filtrert utseende tilbake til filen. Dette høres opplagt ut helt til en juridisk gjennomleser skriver ut en kontrakt under et aktivt filter og arkiverer den inverterte versjonen. På det tidspunktet viser spørsmålet "bruker utskrift dokumentets eget utseende eller skjermens" seg å fortjene et eksplisitt svar i spesifikasjonen din, ikke en tilfeldighet ved kodestien. Hold filterinnstillingen i visertilstanden, bruk den ved rendering, og la hver eksportsti erklære hvilket utseende den bruker

Regelen betaler seg selv to ganger. Reversibilitet får du gratis, fordi bytte av modus rendrer på nytt fra den uendrede kilden: det finnes ingen angre-stakk å vedlikeholde, og ingen måte en rekke modusbytter kan forringe siden på. Scenarier med flere vinduer forblir sammenhengende av samme grunn. To visninger av ett dokument kan kjøre forskjellige moduser, siden hver visning eier sin egen presentasjonstilstand mens dokumentobjektet forblir delt

Rendre først, transformere deretter

Det støttede mønsteret er bitmap-behandling etter rendering: RenderPage produserer sideraster, og deretter justerer et transformasjonstrinn det. Komponenten leverer tre transformasjoner som bitmap-operasjoner på plass, InvertPdfBitmap, DuotonePdfBitmap og GrayscalePdfBitmap, noe som gjør modusbyttet til en ren to-trinns funksjon:

Diagram over en Delphi PDF-viser lesemodus-rørledning der ett PDFium RenderPage-kall mater fire lesemoduser, hver en på-stedet bitmap-transformasjon som InvertPdfBitmap eller DuotonePdfBitmap
RenderPage produserer rasteren én gang, og den aktive lesemodusen velger én in-place bitmap-transformasjon
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);  // mørk bakgrunn, ravgul tekst
    rmGrayscale:    GrayscalePdfBitmap(Result);
  end;
  // rmNormal faller gjennom: dokumentet beholder sine egne farger
end;

To ting følger av dette designet. For det første er transformasjonskostnaden proporsjonal med bitmap-størrelsen, så arbeidet hører hjemme der render-resultatene dine caches: filtrer den bufrede bitmappen én gang, ikke ved hver tegning. For det andre, fordi transformasjonen kjører på det ferdige rasteret, treffer den tekst, vektorgrafikk, bilder og annotasjonsutseender på samme måte. Denne ensartetheten er nøyaktig det ren inversjon gjør feil for fotografier. Det er grunnen til at duotone-transformasjonen utgjør et bedre standardvalg for teksttunge dokumenter, siden den kartlegger lysstyrke til en valgt mørk-til-lys fargerampe i stedet for å negere fargetoner; inversjon forblir tilgjengelig som et eksplisitt valg for lesere som ønsker det. Skarpere glyfkanter er et eget grep. Render-innstillingen reNoSmoothText slår av tekst-antialiasing ved rendering og fungerer godt sammen med høykontrastmodus ved stor zoom

To gråtoner som er uenige

Render-innstillingene inkluderer reGrayscale, som ser ut som en snarvei forbi etterbehandlingstrinnet. Det er ikke den samme operasjonen:

Diagram som sammenligner PDFium reGrayscale render-alternativet, som avmetter bilder men etterlater fargede overskrifter, mot Delphi GrayscalePdfBitmap etterbehandlingen som konverterer hele siden
Motorvalget avmetter bildeinnhold mens etterbehandlingen konverterer hver piksel av det ferdige bitmapet
// Motornivå: gråtoner brukt under rasterisering
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);

// Etterbehandling: rendre i farge, konverter den ferdige bitmappen
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);

Motornivå-innstillingen gjelder for rasterutdataen til bildeinnhold, men når ikke vektorfyllinger eller tekstfarger, så en side med fargede overskrifter kan komme tilbake med grå fotografier og stahalsig blå overskrifter. GrayscalePdfBitmap på den ferdige bitmappen konverterer alt, betingelsesløst. Render-innstillingen fortjener likevel sin plass når du vil ha bilder avmettet mens tekstfargen beholdes som et signal, noe enkelte svaksynte lesere spesifikt foretrekker. Men hvis kravet lyder "gråtoneside", er etterbehandling versjonen som oppfyller det. Uansett hvilken vei du velger, hold begge RenderPage-overload-stilene i mente. Funksjonsformen returnerer en bitmap som kalleren eier og må frigi, og det betyr noe så snart filtre mangedobler antallet rendrede bitmaps i omløp

Bakgrunner, markeringer og PageColor-fellen

Ikke enhver komfortjustering er en transformasjon. Å erstatte den hvite sidebakgrunnen med en varm tone er ofte nok i seg selv for blendingsfølsomme lesere, og det finnes en dedikert egenskap for det. Egenskapen har en gyldighetsregel som fanger folk:

Diagram over PageColor-omfangsfellen i en Delphi PDF-viser: tonen vises på skjermen mens RenderPage-utdata forblir hvit med mindre fargen sendes eksplisitt
PageColor toner bare skjermvisningen, og RenderPage beholder en hvit side med mindre fargen gis eksplisitt
// Påvirker kun visningen på skjermen
PdfView.PageColor := $00D9EDF2;  // varm papirtone bak sideinnholdet

// RenderPage-utdata ignorerer PageColor; send fargen eksplisitt
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);

PageColor endrer hva TPdfView viser, men bitmaps produsert gjennom RenderPage beholder standardhvitt med mindre Color-parameteren sier noe annet. Symptomet er pålitelig: skjermen viser den tonede siden, brukeren eksporterer eller skriver ut, og utdataen går tilbake til hvitt. Plasser det under den samme eksportpolicy-beslutningen som i den første delen

De gjenværende fargeegenskapene definerer overleggsmarkeringer: HighlightColor for søketreff, SelectionColor for brukerens tekstmarkering, ReadingWordColor for markøren til det talte ordet. Hver eneste av dem må kontrolleres på nytt under hvert filter du tilbyr. En ravgul lesemarkør som fungerer på hvitt, forsvinner etter inversjon; en blek blå markering forsvinner inn i en høykontrastbakgrunn. Vedlikehold overleggspaletter per modus fremfor ett globalt sett, og test kombinasjonene med vilje. Filtre pluss tekst-til-tale er en normal konfigurasjon for leserne denne funksjonen betjener, ikke et grensetilfelle. Selve overleggsmekanismen er dekket i artikkelen om den tilgjengelige leseren

Tall, verifisering og spørsmålet om utskrift

WCAG 2.1 gjør denne funksjonen til noe du kan måle. Suksesskriterium 1.4.3 krever et kontrastforhold på 4,5:1 for brødtekst, og 1.4.6 hever det til 7:1 for forsterket kontrast. Stikkprøvekontroller høykontrastmodusen din mot disse forholdene med en kontrastanalysator kjørt på faktisk rendret utdata. Tekst over bilder og tekst i skjemafelter er der forholdene stille og rolig svikter, selv når brødteksten består

Utskrift fortjener sin egen beslutning, og det forsvarlige standardvalget er dokumentets eget utseende, med "skriv ut som vist" tilbudt som et eksplisitt brukervalg. En utskrevet side er bevismateriale i flere arbeidsflyter enn viserforfattere pleier å forvente, og en invertert utskrift av en kontrakt er en supporthendelse med juridisk snert. Enda en kombinasjon betyr noe for ytelsen: filtrert rendering dobler bitmap-arbeidet ved hvert modusbytte, så ikke bruk en transformasjon ved hver tegnemelding. Bufre den filtrerte bitmappen, og kjør transformasjonen på nytt bare når siden, zoomen eller modusen faktisk endres. Bufringsstrategien som gjør dette billig, finnes i artikkelen om render-cache og zoom-ytelse

Én ting bør avgjøres i brukergrensesnittet ditt fremfor i koden: hvilken modus som er det riktige standardvalget. Det finnes ikke ett enkelt svar, så tilby hele settet og la leseren velge. Høy kontrast passer det meste av teksttung lesing, inversjon passer lesere som spesifikt ønsker lyst på mørkt, gråtoner kutter fargestøy, og en bakgrunnstone håndterer blendingsfølsomhet. Lagre valget per bruker, gjenopprett det ved oppstart, og behold en vei tilbake til normal med ett tastetrykk, siden en leser som havner i en modus de ikke kan lese, trenger en rask vei ut

Render-innstillingene, bitmap-transformasjonene og visningens fargeegenskaper som brukes her, leveres med PDFium Component for Delphi, C++Builder og Lazarus/FPC, med full kildekode slik at transformasjonsimplementeringene kan revideres eller utvides