Un cititor cu deficiențe de vedere nu poate distinge text negru pe o pagină albă la contrastul implicit, așa că cere un mod întunecat. Răspunsul naiv este să inversați fiecare pixel al paginii randate. Se livrează într-o săptămână și se defectează a doua zi: fotografiile scanate revin arătând ca niște negative de film, marcajele galbene ale cititorului cu marker se transformă într-o pată albastră ilizibilă, iar cineva întreabă de ce documentul tipărit a ieșit complet negru. Funcționalitatea chiar merită construită și chiar este ușor să fie realizată doar pe jumătate corect, iar diferența dintre cele două rezultate ține de o singură idee: fiecare decizie de culoare aparține unui punct anume din pipeline-ul de randare, iar inversarea este instrumentul greșit aplicat la etapa greșită. Codul de aici folosește PDFium Component, vizualizatorul bazat pe PDFium pentru Delphi, C++Builder și Lazarus, al cărui API de randare expune acele etape separat
Filtrele sunt stare de prezentare, niciodată stare a documentului
O singură regulă previne cea mai gravă categorie de erori aici: un mod de citire schimbă modul în care este produs sau post-procesat bitmap-ul, și nimic altceva. Octeții PDF rămân neatinși, fiecare mod este reversibil prin re-randare, iar „salvarea” nu scrie niciodată un aspect filtrat înapoi în fișier. Asta pare evident până când un jurist tipărește un contract sub un filtru activ și îl depune în varianta inversată. În acel moment, întrebarea „tipărirea folosește aspectul propriu al documentului sau pe cel al ecranului” se dovedește a merita un răspuns explicit în specificația dvs., nu un accident al căii de cod. Păstrați setarea filtrului în starea vizualizatorului, aplicați-o la momentul randării și faceți ca fiecare cale de export să declare ce aspect folosește
Regula se răsplătește de două ori. Reversibilitatea vine gratuit, deoarece schimbarea modurilor re-randează din sursa neschimbată: nu există nicio stivă de anulare de întreținut și nicio cale prin care o succesiune de schimbări de mod să degradeze pagina. Scenariile cu ferestre multiple rămân coerente din același motiv. Două vizualizări ale aceluiași document pot rula moduri diferite, deoarece fiecare vizualizare deține propria stare de prezentare, în timp ce obiectul document rămâne partajat
Mai întâi randare, apoi transformare
Modelul acceptat este procesarea bitmap-ului după randare: RenderPage produce rasterul paginii, apoi o trecere de transformare îl ajustează. Componenta livrează trei transformări ca operații de bitmap in-place, InvertPdfBitmap, DuotonePdfBitmap și GrayscalePdfBitmap, ceea ce face din schimbarea modului o funcție curată în două etape:
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); // fundal întunecat, text chihlimbariu
rmGrayscale: GrayscalePdfBitmap(Result);
end;
// rmNormal trece prin: documentul își păstrează propriile culori
end;
Din acest design decurg două lucruri. Primul, costul transformării este proporțional cu dimensiunea bitmap-ului, așa că munca aparține locului unde sunt stocate în cache rezultatele randării: filtrați bitmap-ul din cache o singură dată, nu la fiecare desenare. Al doilea, deoarece transformarea rulează pe rasterul finit, ea afectează la fel textul, grafica vectorială, imaginile și aspectele adnotărilor. Această uniformitate este exact ceea ce inversarea simplă face greșit pentru fotografii. Acesta este motivul pentru care transformarea duotonă reprezintă o valoare implicită mai bună pentru documentele bogate în text, deoarece mapează luminanța pe o rampă de culori aleasă, de la închis la deschis, în loc să inverseze nuanțele; inversarea rămâne disponibilă ca opțiune explicită pentru cititorii care o doresc. Marginile mai clare ale glifelor sunt o pârghie separată. Opțiunea de randare reNoSmoothText dezactivează anti-aliasingul textului la momentul randării și se combină bine cu modul de contrast ridicat la zoom mare
Două grayscale-uri care nu concordă
Opțiunile de randare includ reGrayscale, care pare o scurtătură ce ocolește pasul de post-procesare. Nu este aceeași operație:
// La nivel de motor: grayscale aplicat în timpul rasterizării
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);
// Post-procesare: randare color, apoi conversia bitmap-ului finit
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);
Opțiunea la nivel de motor se aplică ieșirii raster a conținutului de tip imagine, dar nu ajunge la umplerile vectoriale sau la culorile textului, așa că o pagină cu titluri colorate poate reveni cu fotografii gri și titluri încăpățânat de albastre. GrayscalePdfBitmap aplicat pe bitmap-ul finit convertește totul, necondiționat. Opțiunea de randare își câștigă totuși locul atunci când doriți imagini desaturate păstrând în același timp culoarea textului ca semnal, ceea ce unii cititori cu deficiențe de vedere preferă în mod specific. Dar dacă cerința spune „pagină grayscale”, post-procesarea este versiunea care o satisface. Indiferent de calea aleasă, țineți minte ambele stiluri de supraîncărcare (overload) ale RenderPage. Forma de funcție returnează un bitmap pe care apelantul îl deține și trebuie să îl elibereze, iar acest lucru contează de îndată ce filtrele multiplică numărul de bitmap-uri randate aflate simultan în uz
Fundaluri, marcaje de selecție și capcana PageColor
Nu orice ajustare de confort este o transformare. Înlocuirea fundalului alb al paginii cu o nuanță caldă este adesea suficientă de una singură pentru cititorii sensibili la strălucire, iar pentru asta există o proprietate dedicată. Proprietatea vine cu o regulă de scop care încurcă lumea:
// Afectează doar vizualizarea pe ecran
PdfView.PageColor := $00D9EDF2; // nuanță caldă de hârtie în spatele conținutului paginii
// Rezultatul RenderPage ignoră PageColor; transmiteți culoarea explicit
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);
PageColor schimbă ceea ce afișează TPdfView, dar bitmap-urile produse prin RenderPage păstrează albul implicit, dacă parametrul Color nu spune altceva. Simptomul este constant: ecranul arată pagina colorată, utilizatorul exportă sau tipărește, iar rezultatul revine la alb. Clasați asta sub aceeași decizie de politică de export din prima secțiune
Proprietățile de culoare rămase definesc marcaje suprapuse (overlay): HighlightColor pentru rezultatele căutării, SelectionColor pentru selecția de text a utilizatorului, ReadingWordColor pentru cursorul cuvântului rostit. Fiecare dintre ele trebuie reverificată sub fiecare filtru pe care îl oferiți. Un cursor de citire chihlimbariu care funcționează pe alb dispare după inversare; o selecție albastru pal se pierde pe un fundal cu contrast ridicat. Mențineți palete de overlay separate pentru fiecare mod, în loc de un singur set global, și testați intenționat combinațiile. Filtrele plus conversia text-vorbire reprezintă o configurație normală pentru cititorii cărora le este destinată această funcționalitate, nu un caz limită. Mecanismul de overlay în sine este tratat în articolul despre cititorul accesibil
Cifre, verificare și problema tipăririi
WCAG 2.1 transformă această funcționalitate în ceva măsurabil. Criteriul de succes 1.4.3 cere un raport de contrast de 4,5:1 pentru textul de bază, iar 1.4.6 îl ridică la 7:1 pentru contrast îmbunătățit. Verificați prin sondaj modul de contrast ridicat față de aceste rapoarte, folosind un analizor de contrast rulat pe rezultatul efectiv randat. Textul suprapus peste imagini și textul din câmpurile de formular sunt locurile unde rapoartele eșuează în tăcere, chiar și atunci când textul de bază trece testul
Tipărirea merită propria decizie, iar valoarea implicită apărabilă este aspectul propriu al documentului, cu „tipărire așa cum este afișat” oferită ca opțiune explicită a utilizatorului. O pagină tipărită este probă în mai multe fluxuri de lucru decât tind să se aștepte autorii de vizualizatoare, iar un document tipărit inversat al unui contract este un incident de suport cu tentă juridică. Mai contează o combinație pentru performanță: randarea filtrată dublează munca de bitmap la fiecare schimbare de mod, așa că nu aplicați o transformare la fiecare mesaj de desenare. Stocați în cache bitmap-ul filtrat și rulați din nou transformarea doar atunci când pagina, zoom-ul sau modul se schimbă efectiv. Strategia de cache care face acest lucru ieftin se află în articolul despre cache-ul de randare și performanța la zoom
Un lucru de stabilit în interfața dvs., nu în cod: care mod este valoarea implicită corectă. Nu există un singur răspuns, așa că oferiți întregul set și lăsați cititorul să aleagă. Contrastul ridicat se potrivește majorității lecturilor bogate în text, inversarea li se potrivește cititorilor care doresc în mod specific text deschis pe fundal întunecat, grayscale-ul reduce zgomotul de culoare, iar o nuanță de fundal rezolvă sensibilitatea la strălucire. Persistați alegerea per utilizator, restaurați-o la pornire și păstrați o cale de revenire la normal printr-o singură apăsare de tastă, deoarece un cititor care ajunge într-un mod pe care nu îl poate citi are nevoie de o ieșire rapidă
Opțiunile de randare, transformările de bitmap și proprietățile de culoare ale vizualizării folosite aici se livrează cu PDFium Component pentru Delphi, C++Builder și Lazarus/FPC, cu cod sursă complet, astfel încât implementările transformărilor să poată fi auditate sau extinse