Tehnički članak

PDF filteri boja za slabovide u Delphi-ju pomoću PDFium-a

Čitalac sa slabim vidom ne može da razazna crni tekst na beloj stranici pri podrazumevanom kontrastu, pa traži tamni režim. Naivno rešenje je invertovati svaki piksel iscrtane stranice. To se isporuči za nedelju dana i pokvari se već sledećeg dana: skenirane fotografije izgledaju kao negativi filma, žuta isticanja čitaoca se pretvaraju u nečitku plavu mrlju, a neko pita zašto je odštampana stranica ispala potpuno crna. Funkciju zaista vredi izgraditi i zaista je lako uraditi je polovično ispravno, a jaz između ta dva ishoda leži u jednoj ideji: svaka odluka o boji pripada određenoj tački u cevovodu iscrtavanja, a inverzija je pogrešna alatka primenjena u pogrešnoj fazi. Kod ovde koristi PDFium Component, čitač zasnovan na PDFium-u za Delphi, C++Builder i Lazarus, čiji API za iscrtavanje izlaže te faze odvojeno

Filteri su stanje prikaza, nikada stanje dokumenta

Jedno pravilo sprečava najgoru kategoriju grešaka ovde: režim čitanja menja način na koji se bitmapa proizvodi ili naknadno obrađuje, i ništa više. PDF bajtovi ostaju netaknuti, svaki režim je reverzibilan ponovnim iscrtavanjem, a „sačuvaj“ nikada ne upisuje filtrirani izgled nazad u datoteku. Ovo zvuči očigledno sve dok pravni pregledač ne odštampa ugovor pod aktivnim filterom i ne pošalje invertovanu verziju. U tom trenutku pitanje „da li štampanje koristi sopstveni izgled dokumenta ili izgled ekrana“ zaslužuje eksplicitan odgovor u vašoj specifikaciji, a ne slučajnost u kodu. Držite podešavanje filtera u stanju čitača, primenite ga u vreme iscrtavanja i učinite da svaka putanja izvoza deklariše koji izgled koristi

Ovo pravilo se isplati dvostruko. Reverzibilnost dolazi besplatno, jer prebacivanje režima ponovo iscrtava iz nepromenjenog izvora: nema steka za opoziv (undo) i nema načina da niz promena režima degradira stranicu. Scenariji sa više prozora ostaju koherentni iz istog razloga. Dva prikaza jednog dokumenta mogu pokretati različite režime, pošto svaki prikaz poseduje svoje stanje prezentacije dok objekat dokumenta ostaje deljen

Prvo iscrtaj, potom transformiši

Podržani šablon je obrada bitmape nakon iscrtavanja: RenderPage proizvodi raster stranice, a zatim ga prolaz transformacije prilagođava. Komponenta isporučuje tri transformacije kao operacije na bitmapi na licu mesta: InvertPdfBitmap, DuotonePdfBitmap i GrayscalePdfBitmap, što prebacivanje režima čini čistom dvostepenom funkcijom:

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;

Dve stvari prate ovaj dizajn. Prvo, cena transformacije je proporcionalna veličini bitmape, tako da posao pripada tamo gde se keširaju rezultati iscrtavanja: filtrirajte keširanu bitmapu jednom, a ne pri svakom iscrtavanju. Drugo, pošto se transformacija pokreće na završenom rasteru, ona utiče na tekst, vektorsku grafiku, slike i izglede anotacija na isti način. Ta uniformnost je upravo ono što obična inverzija pogreši kod fotografija. To je razlog zašto dvotonska (duotone) transformacija predstavlja bolji podrazumevani izbor za dokumente sa puno teksta, jer mapira osvetljenost na izabranu skalu boja od tamne do svetle umesto da negira tonove; inverzija ostaje dostupna kao eksplicitan izbor za čitaoce koji to žele. Oštrije ivice glifova su odvojena poluga. Opcija iscrtavanja reNoSmoothText isključuje anti-aliasing teksta u vreme iscrtavanja i dobro se slaže sa režimom visokog kontrasta pri velikom zumiranju

Dve grayskale koje se ne slažu

Opcije iscrtavanja uključuju reGrayscale, što izgleda kao prečica koja zaobilazi korak naknadne obrade. To nije 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);

Opcija na nivou motora se primenjuje na rasterski izlaz slikovnog sadržaja, ali ne dopire do vektorskih ispuna ili boja teksta, tako da stranica sa obojenim naslovima može da se vrati sa sivim fotografijama i tvrdoglavo plavim naslovima. GrayscalePdfBitmap na završenoj bitmapi pretvara sve, bezuslovno. Opcija iscrtavanja i dalje nalazi svoje mesto kada želite da slike budu desaturisane dok zadržavate boju teksta kao signal, što neki čitaoci sa slabim vidom posebno preferiraju. Ali ako zahtev glasi „stranica u sivoj skali“, naknadna obrada je verzija koja to ispunjava. Koji god put da izaberete, imajte na umu oba stila preopterećenja funkcije RenderPage. Forma funkcije vraća bitmapu koju pozivalac poseduje i mora je osloboditi, a to postaje važno čim filteri umnože broj iscrtanih bitmapa u toku

Pozadine, oznake selekcije i zamka PageColor

Nije svako prilagođavanje komfora transformacija. Zamena bele pozadine stranice toplim tonom često je sama po sebi dovoljna za čitaoce osetljive na odsjaj, i to ima namensko svojstvo. Ovo svojstvo nosi pravilo opsega koje zbunjuje ljude:

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

PageColor menja ono što TPdfView prikazuje, ali bitmape proizvedene kroz RenderPage zadržavaju podrazumevanu belu boju osim ako parametar Color ne kaže drugačije. Simptom je pouzdan: ekran prikazuje toniranu stranicu, korisnik izvozi ili štampa, a izlaz se vraća na belu. Odložite to pod istu odluku o politici izvoza iz prvog odeljka

Preostala svojstva boje definišu prekrivne oznake: HighlightColor za pogotke pretrage, SelectionColor za korisničku selekciju teksta, ReadingWordColor za kursor koji prati izgovorenu reč. Svako od njih mora biti ponovo provereno pod svakim filterom koji nudite. Ćilibar kursor za čitanje koji radi na beloj pozadini nestaje nakon inverzije; svetloplava selekcija nestaje u pozadini visokog kontrasta. Održavajte palete prekrivanja po režimu umesto jednog globalnog skupa, i namerno testirajte kombinacije. Filteri plus tekst u govor su normalna konfiguracija za čitaoce kojima ova funkcija služi, a ne granični slučaj. Sama mašinerija prekrivanja pokrivena je u članku o pristupačnom čitaču

Brojevi, verifikacija i pitanje štampanja

WCAG 2.1 pretvara ovu funkciju u nešto što možete izmeriti. Kriterijum uspeha 1.4.3 traži odnos kontrasta 4.5:1 za običan tekst, a 1.4.6 ga podiže na 7:1 za poboljšani kontrast. Spot-proverite svoj režim visokog kontrasta u odnosu na ove odnose pomoću analizatora kontrasta pokrenutog na stvarnom iscrtanom izlazu. Tekst preko slika i tekst u poljima obrazaca su mesta gde odnosi tiho padaju čak i kada tekst u telu dokumenta prolazi

Štampanje zaslužuje sopstvenu odluku, a odbranjiv podrazumevani izbor jeste sopstveni izgled dokumenta, sa opcijom „štampaj kako je prikazano“ ponuđenom kao eksplicitan izbor korisnika. Odštampana stranica je dokaz u više tokova rada nego što autori čitača obično očekuju, a invertovani otisak ugovora je podrška sa pravnim ukusom. Još jedno uparivanje je važno za performanse: filtrirano iscrtavanje duplira rad sa bitmapom pri svakoj promeni režima, tako da nemojte primenjivati transformaciju na svaku poruku iscrtavanja. Keširajte filtriranu bitmapu i ponovo pokrenite transformaciju samo kada se stranica, zumiranje ili režim zaista promene. Strategija keširanja koja ovo čini jeftinim živi u članku o keširanju iscrtavanja i performansama zumiranja

Jedna stvar koju treba rešiti u vašem korisničkom interfejsu, a ne u kodu: koji režim je ispravan podrazumeveni režim. Ne postoji jedinstven odgovor, pa ponudite skup i pustite čitaoca da izabere. Visok kontrast odgovara većini čitanja sa puno teksta, inverzija odgovara čitaocima koji žele svetlo na tamnom, siva skala smanjuje šum boja, a toniranje pozadine rešava osetljivost na odsjaj. Sačuvajte izbor po korisniku, vratite ga pri pokretanju i obezbedite putanju od jednog tastera nazad do normale, jer čitaocu koji završi u režimu koji ne može da pročita treba brz izlaz

Opcije iscrtavanja, transformacije bitmapa i svojstva boja prikaza korišćena ovde dolaze sa PDFium Component za Delphi, C++Builder i Lazarus/FPC, sa punim izvornim kodom tako da se implementacije transformacija mogu revidirati ili proširiti