Tehnični članek

Barvni filtri PDF za slabovidne v Delphi s PDFium

Slaboviden bralec ne more razločiti črnega besedila na beli strani pri privzetem kontrastu, zato potrebuje temen način. Preprost odgovor je inverzija vsake slikovne pike na izrisani strani. To sicer lahko implementirate v enem tednu, a se zalomi že naslednji dan: skenirane fotografije postanejo podobne filmskim negativom, rumeni poudarki bralnika se spremenijo v nečitljiv moder madež, nekdo pa vpraša, zakaj je natisnjena stran popolnoma črna. To funkcijo je vsekakor vredno zgraditi in jo je razmeroma enostavno narediti napol delujočo, razlika med dobrim in slabim rezultatom pa je v naslednjem: vsaka odločitev o barvah sodi na določeno točko v cevovodu izrisa (render pipeline), inverzija pa je napačno orodje na napačni stopnji. Tukajšnja koda uporablja komponento PDFium Component (pregledovalnik na osnovi PDFium za Delphi, C++Builder in Lazarus), katere vmesnik API za izris ločeno izpostavlja te stopnje.

Eno pravilo preprečuje najhujšo vrsto napak na tem področju: bralni način spremeni le to, kako se bitna slika ustvari ali naknadno obdela, in nič drugega. Bajti dokumenta PDF ostanejo nedotaknjeni, vsak način je mogoče razveljaviti s ponovnim izrisom, ukaz »shrani« pa filtriranega videza dokumenta nikoli ne zapiše nazaj v datoteko. To se zdi samoumevno, dokler pravni pregledovalec ne natisne pogodbe z aktivnim filtrom in vloži obrnjene različice. Takrat se izkaže, da vprašanje »ali tiskanje uporablja lasten videz dokumenta ali tistega z zaslona« zahteva jasen odgovor v vaši specifikaciji in ne naključne izbire v kodi. Nastavitev filtra ohranite v stanju pregledovalnika, jo uveljavite ob izrisu in poskrbite, da vsaka izvozna pot jasno določi, kateri videz uporablja.

To pravilo se obrestuje dvakrat. Razveljavitev je brezplačna, saj preklop med načini ponovno izriše stran iz nespremenjenega vira: ni treba vzdrževati sklada za razveljavitev (undo stack) in večkratni preklopi ne morejo poslabšati kakovosti strani. Scenariji z več okni ostanejo usklajeni iz istega razloga. Dva pogleda istega dokumenta lahko uporabljata različne načine, saj si vsak pogled lasti svoje predstavitveno stanje, medtem ko je sam objekt dokumenta skupen.

Najprej izris, nato transformacija

Podprt vzorec je naknadna obdelava bitne slike po izrisu: metoda RenderPage ustvari raster strani, nato pa ga prehod za transformacijo prilagodi. Komponenta ponuja tri transformacije kot neposredne operacije nad bitno sliko: InvertPdfBitmap, DuotonePdfBitmap in GrayscalePdfBitmap, kart naredi preklop med načini za čisto dvostopenjsko funkcijo:

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;

Iz te zasnove sledita dve ugotovitvi. Prvič, strošek transformacije je sorazmeren z velikostjo bitne slike, zato delo sodi tja, kjer so shranjeni vaši rezultati izrisa: filtrirajte predpomnjeno bitno sliko enkrat in ne ob vsakem risanju. Drugič, ker transformacija teče nad končnim rastrom, na enak način vpliva na besedilo, vektorsko grafiko, slike in videz anotacij. Ta enotnost je natanko tisto, kar navadna inverzija pokvari pri fotografijah. Zato je duotonska transformacija (duotone) boljša privzeta izbira za dokumente z veliko besedila, saj preslika svetilnost na izbrano barvno lestvico od temne do svetle namesto negiranja odtenkov; inverzija pa ostaja na voljo kot izrecna možnost za bralce, ki jo želijo. Bolj ostri robovi znakov so ločena nastavitev. Možnost izrisa reNoSmoothText izklopi glajenje besedila (anti-aliasing) ob izrisu in se dobro ujema z načinom visokega kontrasta pri veliki povečavi.

Dve sivi lestvici, ki se ne ujemata

Možnosti izrisa vključujejo reGrayscale, kar je videti kot bližnjica mimo koraka naknadne obdelave. Vendar to ni ista operacija:

// 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);

Možnost na ravni pogona velja za rastrski izhod slikovne vsebine, ne doseže pa vektorskih polnil ali barv besedila, zato lahko stran z barvnimi naslovi vrne sive fotografije in trmasto modre naslove. Metoda GrayscalePdfBitmap na končni bitni sliki pretvori vse, brez pogoja. Možnost izrisa obdrži svojo vlogo takrat, ko želite razbarvati slike, hkrati pa ohraniti barvo besedila kot signal, kar nekateri slabovidni bralci posebej želijo. Če pa zahteva pravi »siva stran«, je naknadna obdelava tista različica, ki jo izpolni. Ne glede na izbrano pot imejte v mislih oba sloga preobremenitve RenderPage. Oblika funkcije vrne bitno sliko, ki si jo lasti klicatelj in jo mora sam sprostiti, kar postane pomembno takoj, ko filtri povečajo število bitnih slik v pomnilniku.

Ozadja, označbe izbora in past PageColor

Ni vsaka prilagoditev za udobnejše branje transformacija. Zamenjava belega ozadja strani s toplejšim tonom je za bralce, občutljive na bleščanje, pogosto dovolj že sama po sebi in ima namensko lastnost. Ta lastnost pa prinaša pravilo o dosegu, ki lahko preseneti:

// 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);

Lastnost PageColor spremeni tisto, kar prikazuje TPdfView, bitne slike, ustvarjene prek RenderPage, pa ohranijo privzeto belo barvo, razen če parameter Color določa drugače. Simptom je zanesljiv: zaslon prikazuje obarvano stran, ko jo uporabnik izvozi ali natisne, pa se izhod vrne na belo. To dodajte k isti odločitvi o politiki izvoza iz prvega razdelka.

Preostale barvne lastnosti določajo prekrivne označbe: HighlightColor za iskalne zadetke, SelectionColor za izbiro besedila uporabnika, ReadingWordColor za kazalec izgovorjene besede. Vsako od njih je treba ponovno preveriti pod vsakim filtrom, ki ga ponujate. Oranžen bralni kazalec, ki deluje na beli barvi, po inverziji izgine; svetlo modra izbira izgine v ozadju z visokim kontrastom. Vzdržujte prekrivne palete za posamezne načine namesto ene globalne in te kombinacije načrtno testirajte. Kombinacija filtrov in sinteze govora je običajna konfiguracija za bralce, ki jim je ta funkcija namenjena, in ne robni primer. Sam mehanizem prekrivanja je opisan v članku o dostopnem bralniku.

Številke, preverjanje in vprašanje o tiskanju

Smernice WCAG 2.1 to funkcijo spreminjajo v nekaj, kar lahko izmerite. Kriterij uspešnosti 1.4.3 zahteva kontrastno razmerje 4.5:1 za navadno besedilo, kriterij 1.4.6 pa to mejo dvigne na 7:1 za izboljšan kontrast. Naključno preverite svoj način visokega kontrasta glede na ta razmerja z analizatorjem kontrasta, ki ga zaženete na dejanskem izrisanem izhodu. Besedilo čez slike in besedilo v poljih obrazcev sta mesti, kjer razmerja tiho padejo, čeprav navadno besedilo ustreza kriterijem.

Tiskanje si zasluži lastno odločitev, pri čemer je najbolj varna privzeta možnost lasten videz dokumenta, možnost »tiskaj, kot je prikazano« pa je na voljo kot izrecna izbira uporabnika. Natisnjena stran je dokaz v več delovnih procesih, kot običajno predvidevajo avtorji pregledovalnikov, obrnjen tisk pogodbe pa hitro postane podporni zahtevek s pravnim pridihom. Za zmogljivost je pomembna še ena povezava: filtriran izris podvoji delo z bitno sliko ob vsakem preklopu načina, zato ne izvajajte transformacije ob vsakem risanju. Predpomnite filtrirano bitno sliko in ponovno zaženite transformacijo le, ko se dejansko spremenijo stran, povečava ali način. Strategija predpomnjenja, ki to olajša, je opisana v članku o predpomnilniku izrisa in zmogljivosti povečave.

Nekaj, kar je bolje rešiti v uporabniškem vmesniku kot v kodi, je izbira privzetega načina. Enega samega odgovora ni, zato ponudite celoten nabor in pustite bralcu, da izbere sam. Visok kontrast ustreza večini branja z veliko besedila, inverzija ustreza bralcem, ki želijo svetlo na temnem, siva lestvica zmanjša barvni šum, obarvanost ozadja pa pomaga pri občutljivosti na bleščanje. Shranite izbiro za vsakega uporabnika, jo obnovite ob zagonu in zagotovite hiter prehod nazaj v običajni način z enim samim pritiskom na tipko, saj bralec, ki se znajde v nečitljivem načinu, potrebuje hitro pot ven.

Možnosti izrisa, transformacije bitnih slik in barvne lastnosti pogleda, ki jih uporabljamo tukaj, so del paketa PDFium Component za Delphi, C++Builder in Lazarus/FPC, skupaj s celotno izvorno kodo, tako da je mogoče implementacije transformacij revidirati ali razširiti.