Technical Article

Farebné filtre PDF pre slabozrakých v Delphi pomocou PDFium

Slabozraký čitateľ nedokáže prečítať čierny text na bielej stránke pri predvolenom kontraste, preto požaduje tmavý režim. Jednoduchou odpoveďou je invertovať každý pixel vykreslenej stránky. Takáto funkcia sa dá spustiť za týždeň, no hneď na druhý deň zlyhá: naskenované fotografie začnú vyzerať ako filmové negatívy, žlté zvýraznenia čitateľa sa zmenia na nečitateľnú modrú škvrnu a niekto sa opýta, prečo je výtlačok úplne čierny. Túto funkciu sa naozaj oplatí vytvoriť a je skutočne ľahké urobiť ju napoly správne. Rozdiel medzi týmito dvoma výsledkami spočíva v jedinej myšlienke: každé rozhodnutie o farbe patrí do konkrétneho bodu vykresľovacieho reťazca (render pipeline) a inverzia je nesprávny nástroj použitý v nesprávnom štádiu. Kód v tomto článku používa komponent PDFium Component, prehliadač založený na knižnici PDFium pre Delphi, C++Builder a Lazarus, ktorého vykresľovacie API sprístupňuje tieto fázy samostatne.

Filtre sú prezentačným stavom, nikdy nie stavom dokumentu

Jedno pravidlo predchádza najhoršej kategórii chýb: režim čítania mení spôsob, akým sa vytvára alebo dodatočne spracováva bitová mapa, a nič iné. Samotné bajty PDF zostávajú nedotknuté, každý režim je reverzibilný opätovným vykreslením a uloženie („save“) nikdy nezapíše filtrovaný vzhľad späť do súboru. Znie to samozrejme, kým právny posudzovateľ nevytlačí zmluvu pod aktívnym filtrom a neodošle invertovanú verziu. V tom momente sa ukáže, že otázka „používa tlač vlastný vzhľad dokumentu alebo ten z obrazovky“ si zaslúži explicitnú odpoveď vo vašej špecifikácii, a nemá byť len náhodným výsledkom chodu kódu. Uchovávajte nastavenie filtra v stave prehliadača, aplikujte ho pri vykresľovaní a zabezpečte, aby každá cesta exportu jasne deklarovala, ktorý vzhľad používa.

Toto pravidlo sa vyplatí hneď dvakrát. Reverzibilita je zadarmo, pretože prepínanie režimov znova vykresľuje stránku z nezmeneného zdroja: netreba udržiavať históriu krokov späť (undo stack) a séria zmien režimov nijako neznižuje kvalitu stránky. Scenáre s viacerými oknami zostávajú z rovnakého dôvodu koherentné. Dva pohľady na jeden dokument môžu používať odlišné režimy, keďže každý pohľad vlastní svoj prezentačný stav, zatiaľ čo objekt dokumentu zostáva zdieľaný.

Najprv vykresliť, potom transformovať

Podporovaným modelom je spracovanie bitovej mapy po vykreslení: metóda RenderPage vygeneruje raster stránky a následne ho upraví transformačný prechod. Komponent obsahuje tri transformácie ako in-place operácie nad bitovou mapou: InvertPdfBitmap, DuotonePdfBitmap a GrayscalePdfBitmap, vďaka čomu je prepínanie režimov čistou dvojfázovou funkciou:

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;

Z tohto návrhu vyvstávajú dve veci. Po prvé, náročnosť transformácie je priamo úmerná veľkosti bitovej mapy, takže táto práca patrí tam, kde sa ukladajú výsledky vykresľovania: filtrujte uloženú bitovú mapu iba raz, nie pri každom prekreslení. Po druhé, keďže transformácia beží na hotovom rastri, ovplyvňuje text, vektorovú grafiku, obrázky aj vzhľad anotácií rovnakým spôsobom. Táto jednotnosť je presne to, čo pri bežnej inverzii zlyháva pri fotografiách. Preto je duotónová transformácia (duotone) lepšou predvolenou voľbou pre textové dokumenty, keďže mapuje jas na vybranú farebnú škálu od tmavej po svetlú namiesto negovania odtieňov. Inverzia zostáva k dispozícii ako explicitná voľba pre čitateľov, ktorí ju vyžadujú. Ostrejšie hrany znakov sú samostatným nástrojom. Možnosť vykresľovania reNoSmoothText vypína vyhladzovanie textu (anti-aliasing) počas vykresľovania a dobre sa kombinuje s režimom vysokého kontrastu pri veľkom priblížení.

Dva odlišné stupne sivej

Možnosti vykresľovania zahŕňajú položku reGrayscale, ktorá vyzerá ako skratka obchádzajúca krok dodatočného spracovania. Nie je to však rovnaká operácia:

// 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žnosť na úrovni jadra sa vzťahuje na rastrový výstup obrázkov, ale neovplyvňuje vektorové výplne ani farby textu, takže stránka s farebnými nadpismi sa môže vrátiť so sivými fotografiami a tvrdohlavo modrými nadpismi. Metóda GrayscalePdfBitmap na hotovej bitovej mape skonvertuje všetko a bezpodmienečne. Možnosť vykresľovania na úrovni jadra má stále svoje miesto, ak chcete znížiť sýtosť obrázkov, ale ponechať farbu textu ako vizuálny signál, čo niektorí slabozrakí čitatelia vyslovene preferujú. Ak je však požiadavkou „sivá stránka“, verzia s dodatočným spracovaním (post-processing) je tá, ktorá ju splní. Nech už si vyberiete ktorúkoľvek cestu, majte na pamäti oba štýly preťaženia metódy RenderPage. Funkčná forma vracia bitovú mapu, ktorú volajúci vlastní a musí uvoľniť, čo je dôležité hneď, ako filtre zvýšia počet vykreslených bitových máp v pamäti.

Pozadia, výbery a pasca s PageColor

Nie každá úprava pre pohodlie čítania je transformáciou. Nahradenie bieleho pozadia stránky teplým tónom často samo o sebe stačí pre čitateľov citlivých na oslnenie, pričom na to slúži samostatná vlastnosť. Táto vlastnosť však obsahuje pravidlo rozsahu pôsobnosti, na ktorom sa mnohí pomýlia:

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

Vlastnosť PageColor mení to, čo zobrazuje TPdfView, ale bitové mapy vytvorené cez RenderPage si zachovávajú predvolenú bielu farbu, pokiaľ parameter Color neurčuje inak. Príznak je spoľahlivý: obrazovka ukazuje tónovanú stránku, používateľ ju exportuje alebo vytlačí a výstup sa vráti k bielej. To patrí pod rovnaké rozhodnutie o pravidlách exportu z prvej časti. Zostávajúce vlastnosti farieb definujú prekryvné značky: HighlightColor pre výsledky vyhľadávania, SelectionColor pre výber textu používateľom, ReadingWordColor pre kurzor hovoreného slova. Každú z nich je potrebné znova skontrolovať pod každým filtrom, ktorý ponúkate. Jantárový čítací kurzor, ktorý funguje na bielej, po inverzii zmizne; svetlomodrý výber sa stratí v vysokokontrastnom pozadí. Udržiavajte palety prekrytí pre každý režim samostatne, namiesto jednej globálnej sady, a tieto kombinácie cielene otestujte. Kombinácia filtrov a syntézy reči je pre čitateľov, ktorým táto funkcia slúži, bežnou konfiguráciou, nie okrajovým prípadom. Samotný mechanizmus prekrytia je popísaný v článku o prístupnej čítačke.

Čísla, overenie a otázka tlače

Norma WCAG 2.1 mení túto funkciu na niečo merateľné. Kritérium úspešnosti 1.4.3 vyžaduje kontrastný pomer 4,5:1 pre bežný text a kritérium 1.4.6 ho zvyšuje na 7:1 pre zvýšený kontrast. Náhodne skontrolujte svoj vysokokontrastný režim voči týmto pomerom pomocou analyzátora kontrastu spusteného na skutočnom vykreslenom výstupe. Text cez obrázky a text vo formulárových poliach sú miesta, kde tieto pomery potichu zlyhávajú, aj keď bežný text prejde. Tlač si vyžaduje vlastné rozhodnutie a obhájiteľným predvoleným nastavením je pôvodný vzhľad dokumentu, pričom možnosť „tlačiť tak, ako je zobrazené“ sa ponúka ako explicitná voľba používateľom. Vytlačená stránka slúži ako dôkaz vo viacerých procesoch, než autori prehliadačov zvyčajne očakávajú, a invertovaný výtlačok zmluvy je zárodkom podpory s právnou príchuťou. Pre výkon je dôležité ešte jedno prepojenie: filtrované vykresľovanie zdvojnásobuje prácu s bitovou mapou pri každej zmene režimu, preto neaplikujte transformáciu pri každej správe o vykreslení (paint message). Uložte filtrovanú bitovú mapu do vyrovnávacej pamäte a transformáciu znova spustite iba vtedy, keď sa skutočne zmení stránka, priblíženie alebo režim. Stratégia ukladania, vďaka ktorej je to nenáročné na výkon, sa nachádza v článku o vyrovnávacej pamäti a výkone približovania.

Jedna vec, ktorú by ste mali vyriešiť skôr vo vašom UI ako v kóde: ktorý režim je správnym predvoleným nastavením. Neexistuje jediná odpoveď, preto ponúknite celú sadu a nechajte čitateľa vybrať si. Vysoký kontrast vyhovuje väčšine textového čítania, inverzia je vhodná pre tých, ktorí chcú vyslovene svetlé na tmavom, stupne sivej potláčajú farebný šum a tónovanie pozadia pomáha pri citlivosti na oslnenie. Uložte túto voľbu pre každého používateľa, obnovte ju pri štarte a zachovajte rýchlu klávesovú skratku pre návrat do normálneho režimu, pretože čitateľ, ktorý sa ocitne v režime, ktorý nevie prečítať, potrebuje rýchlu cestu von.

Možnosti vykresľovania, transformácie bitových máp a vlastnosti farieb zobrazenia použité v tomto článku sú súčasťou PDFium Component pre Delphi, C++Builder a Lazarus/FPC, a to vrátane plných zdrojových kódov, takže implementácie transformácií môžete sami auditovať alebo rozširovať.