Slabovidni čitatelj ne može razaznati crni tekst na bijeloj stranici pri zadanom kontrastu, pa traži tamni način rada. Naivan odgovor je invertirati svaki piksel iscrtane stranice. To se isporuči za tjedan dana i pokvari već sljedeći dan: skenirane fotografije izgledaju poput filmskih negativa, žute oznake isticanja čitatelja pretvaraju se u nečitljivu plavu mrlju, a netko pita zašto je ispis ispao potpuno crn. Ovu značajku doista vrijedi izgraditi i doista ju je lako napola ispravno izvesti, a jaz između ta dva ishoda leži u jednoj ideji: svaka odluka o boji pripada određenoj točki u cjevovodu iscrtavanja (render pipeline), a inverzija je pogrešan alat primijenjen u pogrešnoj fazi. Kod ovdje koristi PDFium komponentu (PDFium Component), preglednik temeljen na PDFiumu za Delphi, C++Builder i Lazarus, čiji API za iscrtavanje zasebno izlaže te faze.
Filtri su stanje prezentacije, a ne stanje dokumenta
Jedno pravilo sprječava najgoru kategoriju pogrešaka ovdje: način čitanja mijenja način na koji se bitmapa stvara ili naknadno obrađuje, i ništa drugo. Bajtovi PDF-a ostaju netaknuti, svaki je način rada reverzibilan ponovnim iscrtavanjem, a "spremi" nikada ne zapisuje filtrirani izgled natrag u datoteku. To zvuči očito sve dok pravni recenzent ne ispiše ugovor pod aktivnim filtrom i preda invertiranu verziju. U tom trenutku pitanje "koristi li ispis vlastiti izgled dokumenta ili onaj sa zaslona" zaslužuje jasan odgovor u vašoj specifikaciji, a ne da bude slučajnost u putanji koda. Držite postavku filtra u stanju preglednika, primijenite je u vrijeme iscrtavanja i učinite da svaka putanja izvoza deklarira koji izgled koristi.
To se pravilo isplati dvostruko. Reverzibilnost dolazi besplatno jer prebacivanje načina rada ponovno iscrtava stranicu iz nepromijenjenog izvora: nema stoga za poništavanje (undo) koji bi trebalo održavati i nema načina da niz promjena načina rada pokvari stranicu. Scenariji s više prozora ostaju koherentni iz istog razloga. Dva prikaza istog dokumenta mogu pokretati različite načine rada, budući da svaki prikaz posjeduje svoje stanje prezentacije dok objekt dokumenta ostaje zajednički.
Prvo iscrtaj, potom transformiraj
Podržani uzorak je obrada bitmape nakon iscrtavanja: RenderPage stvara raster stranice, a zatim ga prolaz transformacije prilagođava. Komponenta isporučuje tri transformacije kao operacije na bitmapi na licu mjesta: InvertPdfBitmap, DuotonePdfBitmap i GrayscalePdfBitmap, što prebacivanje načina rada čini čistom dvostupanjskom 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;
Dvije stvari proizlaze iz ovog dizajna. Prvo, trošak transformacije je proporcionalan veličini bitmape, pa taj posao pripada onom mjestu gdje se vaši rezultati iscrtavanja predmemoriraju: filtrirajte predmemoriranu bitmapu jednom, a ne pri svakom crtanju. Drugo, budući da transformacija radi na dovršenom rasteru, ona na isti način utječe na tekst, vektorsku grafiku, slike i izglede anotacija. Ta je ujednačenost upravo ono što obična inverzija pogrešno radi kod fotografija. To je razlog zašto duotonska transformacija (duotone) predstavlja bolji zadani izbor za dokumente s puno teksta, jer mapira svjetlinu na odabranu paletu boja od tamne do svijetle umjesto negiranja nijansi; inverzija ostaje dostupna kao izričit izbor za čitatelje koji to žele. Oštriji rubovi znakova (glyphs) su zaseban alat. Opcija iscrtavanja reNoSmoothText isključuje zaglađivanje teksta (anti-aliasing) u vrijeme iscrtavanja i dobro se nadopunjuje s načinom visokog kontrasta pri velikom zumiranju.
Dvije sive skale koje se ne podudaraju
Opcije iscrtavanja uključuju reGrayscale, što izgleda kao prečac koji 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 razini pokretača primjenjuje se na rasterski izlaz slikovnog sadržaja, ali ne dopire do vektorskih ispuna ili boja teksta, pa stranica s naslovima u boji može vratiti sive fotografije i tvrdoglavo plave naslove. GrayscalePdfBitmap na dovršenoj bitmapi pretvara sve, bezuvjetno. Opcija iscrtavanja i dalje ima svoje mjesto kada želite desaturirati slike zadržavajući boju teksta kao signal, što neki slabovidni čitatelji posebno preferiraju. Ali ako zahtjev glasi "stranica u sivoj skali", naknadna obrada je verzija koja to ispunjava. Koji god put odabrali, imajte na umu oba stila preopterećenja funkcije RenderPage. Oblik funkcije vraća bitmapu koju pozivatelj posjeduje i mora osloboditi, a to postaje važno čim filtri umnože broj iscrtanih bitmapa u izvođenju.
Pozadine, oznake odabira i zamka PageColor trap
Nije svaka prilagodba za ugodnije čitanje transformacija. Zamjena bijele pozadine stranice toplim tonom često je sama po sebi dovoljna za čitatelje osjetljive na odsjaj, a za to postoji namjensko svojstvo. To svojstvo nosi pravilo opsega koje često 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 mijenja ono što TPdfView prikazuje, ali bitmape stvorene putem RenderPage zadržavaju zadanu bijelu boju osim ako parametar Color ne navodi drugačije. Simptom je pouzdan: zaslon prikazuje toniranu stranicu, korisnik je izvozi ili ispisuje, a izlaz se vraća na bijelu boju. Svrstajte to pod istu odluku o politici izvoza iz prvog odjeljka.
Preostala svojstva boja definiraju oznake preklapanja: HighlightColor za pogotke pretraživanja, SelectionColor za korisnički odabir teksta, ReadingWordColor za kursor izgovorene riječi. Svako od njih mora se ponovno provjeriti pod svakim filtrom koji nudite. Kursor za čitanje boje jantara koji radi na bijeloj pozadini nestaje nakon inverzije; blijedoplavi odabir nestaje u pozadini visokog kontrasta. Održavajte palete preklapanja po svakom načinu rada radije nego jedan globalni skup i namjerno testirajte kombinacije. Filtri plus pretvaranje teksta u govor uobičajena su konfiguracija za čitatelje kojima je ova značajka namijenjena, a ne granični slučaj. Sam mehanizam preklapanja pokriven je u članku o pristupačnom čitatelju.
Brojevi, provjera i pitanje ispisa
WCAG 2.1 pretvara ovu značajku u nešto što možete izmjeriti. Kriterij uspješnosti 1.4.3 zahtijeva omjer kontrasta od 4.5:1 za običan tekst, a 1.4.6 ga podiže na 7:1 za pojačani kontrast. Nasumično provjerite svoj način visokog kontrasta u odnosu na te omjere pomoću analizatora kontrasta pokrenutog na stvarnom iscrtanom izlazu. Tekst preko slika i tekst u poljima obrazaca mjesta su na kojima omjeri tiho zakazuju čak i kada tekst tijela prolazi.
Ispis zaslužuje vlastitu odluku, a opravdan zadani izbor je izvorni izgled dokumenta, dok se "ispis kako je prikazano" nudi kao izričit izbor korisnika. Tiskani list papira je dokaz u više tijekova rada nego što autori preglednika obično očekuju, a invertirani ispis ugovora predstavlja incident za podršku s pravnim prizvukom. Još jedno uparivanje važno je za performanse: filtrirano iscrtavanje udvostručuje rad s bitmapama pri svakom prebacivanju načina rada, stoga nemojte primjenjivati transformaciju pri svakoj poruci o crtanju. Predmemorirajte filtriranu bitmapu i ponovno pokrenite transformaciju samo kada se stranica, zumiranje ili način rada stvarno promijene. Strategija predmemoriranja koja to čini jeftinim nalazi se u članku o predmemoriji iscrtavanja i performansama zumiranja.
Jedna stvar koju treba riješiti u korisničkom sučelju, a ne u kodu: koji je način rada ispravan zadani izbor. Nema jedinstvenog odgovora, pa ponudite skup i pustite čitatelja da odabere. Visoki kontrast odgovara većini čitanja s puno teksta, inverzija odgovara čitateljima koji izričito žele svijetlo na tamnom, siva skala smanjuje šum boja, a tonirana pozadina rješava osjetljivost na odsjaj. Trajno spremite izbor po korisniku, obnovite ga pri pokretanju i držite prečac od jedne tipke za povratak u normalno stanje, budući da čitatelj koji završi u načinu rada koji ne može čitati treba brz način za izlaz.
Opcije iscrtavanja, transformacije bitmape i svojstva boja prikaza koja se ovdje koriste dolaze s PDFium komponentom (PDFium Component) za Delphi, C++Builder i Lazarus/FPC, s punim izvornim kodom kako bi se implementacije transformacija mogle revidirati ili proširiti.