Műszaki cikk

Gyengénlátóknak szánt PDF színszűrők Delphi-ben PDFium-mal

Egy gyengénlátó olvasó nem tudja kivenni a fekete szöveget egy fehér oldalon alapértelmezett kontraszt mellett, ezért sötét módot kér. A naiv válasz az, hogy megfordítod a renderelt oldal minden pixelét. Egy hét alatt kiszáll, és másnap elromlik: a beszkennelt fényképek filmnegatívnak néznek ki, az olvasó sárga kiemelőjelei olvashatatlan kék elkenődéssé válnak, és valaki megkérdezi, miért lett a nyomtatás tömör fekete. A funkció valóban megéri felépíteni, és valóban könnyű félig elrontani, és a két kimenetel közötti szakadék egyetlen gondolat: minden színdöntés a render-folyamat egy adott pontjához tartozik, és az invertálás a rossz eszköz, rossz szakaszba alkalmazva. Az itteni kód a PDFium Component-et használja, a PDFium-alapú nézegetőt Delphihez, C++Builderhez és Lazarushoz, amelynek renderelési API-ja külön kínálja fel ezeket a szakaszokat

A szűrők megjelenítési állapotok, sosem dokumentumállapotok

Egy szabály megelőzi itt a hibák legrosszabb kategóriáját: egy olvasási mód azt változtatja meg, hogyan készül vagy kerül utófeldolgozásra a bitkép, és semmi mást. A PDF-bájtok érintetlenek maradnak, minden mód visszafordítható újrarenderléssel, és a „mentés" sosem ír vissza egy szűrt megjelenést a fájlba. Ez magától értetődőnek hangzik, amíg egy jogi átnéző ki nem nyomtat egy szerződést egy aktív szűrő alatt, és be nem iktatja az invertált változatot. Ezen a ponton kiderül, hogy a „a nyomtatás a dokumentum saját megjelenését használja, vagy a képernyőét" kérdés egy explicit választ érdemel a specifikációdban, nem egy kódútvonal-baleset. Tartsd a szűrőbeállítást a nézegető állapotában, alkalmazd renderelési időben, és minden exportútvonalnak deklarálnia kell, melyik megjelenést használja

A szabály kétszer is megtérül. A visszafordíthatóság ingyen jár, mert a módváltás a változatlan forrásból renderel újra: nincs karbantartandó visszavonási verem, és nincs mód arra, hogy egy sor módváltás lerontsa az oldalt. A több ablakos forgatókönyvek ugyanezért maradnak koherensek. Egy dokumentum két nézete futtathat különböző módokat, mert minden nézet a saját megjelenítési állapotát birtokolja, miközben a dokumentumobjektum megosztott marad

Először renderelj, aztán alakíts át

A támogatott minta a renderelés utáni bitkép-feldolgozás: a RenderPage előállítja az oldalraszter-t, majd egy átalakítási lépés módosítja azt. A komponens három átalakítást szállít helyben végzett bitkép-műveletként, az InvertPdfBitmap-et, a DuotonePdfBitmap-et és a GrayscalePdfBitmap-et, ami tiszta kétlépcsős függvénnyé teszi a módváltást:

Diagram egy Delphi PDF megjelenítő olvasási mód folyamatáról: egyetlen PDFium RenderPage hívás táplál négy olvasási módot, mindegyik helybeni bitképtranszformáció, mint InvertPdfBitmap vagy DuotonePdfBitmap
A RenderPage egyszer állítja elő a rasztert, és az aktív olvasási mód egy helyben való bittérkép-transzformációt választ
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);  // sötét háttér, borostyán szöveg
    rmGrayscale:    GrayscalePdfBitmap(Result);
  end;
  // az rmNormal átesik: a dokumentum megtartja a saját színeit
end;

Két dolog következik ebből a tervezésből. Először is, az átalakítás költsége arányos a bitkép méretével, így a munka oda tartozik, ahol a render-eredményeidet gyorsítótárazod: szűrd meg a gyorsítótárazott bitképet egyszer, ne minden festésnél. Másodszor, mivel az átalakítás a kész raszteren fut, ugyanúgy éri a szöveget, a vektorgrafikát, a képeket és a jegyzet-megjelenéseket. Ez az egyformaság pontosan az, amit a sima invertálás elront a fényképeknél. Ezért ad jobb alapértelmezést a duotone-átalakítás szövegben gazdag dokumentumokhoz, mivel a fényerőt egy választott sötét-világos színrámpára képezi le a színárnyalatok negálása helyett; az invertálás explicit választásként elérhető marad azoknak az olvasóknak, akik ezt akarják. Az élesebb glyph-élek egy külön kar. A reNoSmoothText render-opció renderelési időben kikapcsolja a szöveg élsimítását, és jól párosul a nagykontrasztos móddal nagy zoomnál

Két szürkeárnyalat, amelyek nem értenek egyet

A render-opciók között szerepel a reGrayscale, amely egy rövidítésnek tűnik az utófeldolgozási lépés helyett. Ez nem ugyanaz a művelet:

Diagram: a PDFium reGrayscale render opció, amely kiszürkíti a képeket, de hagyja a színes címsorokat, szemben a Delphi GrayscalePdfBitmap utófeldolgozással, amely a teljes oldalt konvertálja
A motorbeállítás szürkíti a képtartalmat, míg az utófeldolgozás a kész bittérkép minden pixelét átalakítja
// Motorszinten: a szürkeárnyalat a raszterizálás közben van alkalmazva
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);

// Utófeldolgozás: színesben renderel, majd átalakítja a kész bitképet
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);

A motorszintű opció a képtartalom raszterkimenetére vonatkozik, de nem éri el a vektorkitöltéseket vagy a szövegszíneket, így egy színes címsorokkal rendelkező oldal szürke fényképekkel és makacsul kék címsorokkal térhet vissza. A GrayscalePdfBitmap a kész bitképen mindent átalakít, feltétel nélkül. A render-opció akkor is megtartja a helyét, amikor a képeket telítetlenítve akarod, miközben a szövegszínt jelzésként megtartod, amit néhány gyengénlátó olvasó kifejezetten előnyben részesít. De ha a követelmény úgy szól, hogy „szürkeárnyalatos oldal", az utófeldolgozás az a verzió, amely ezt kielégíti. Bármelyik utat is választod, tartsd szem előtt mindkét RenderPage túlterhelési stílust. A függvényforma egy olyan bitképet ad vissza, amelyet a hívó birtokol, és fel kell szabadítania, és ez akkor számít, amint a szűrők megsokszorozzák a repülésben lévő renderelt bitképek számát

Hátterek, kijelölési jelek és a PageColor csapda

Nem minden kényelmi beállítás egy átalakítás. A fehér oldalháttér meleg tónusra cserélése önmagában gyakran elég a fényérzékeny olvasóknak, és van hozzá egy dedikált tulajdonság. A tulajdonság egy hatókör-szabályt hordoz, amely sokakat megfog:

Diagram a PageColor hatókörcsapdáról Delphi PDF megjelenítőben: az árnyalat a képernyőn látszik, miközben a RenderPage kimenete fehér marad, hacsak a színt nem adják át explicit módon
A PageColor csak a képernyőn megjelenő nézetet színezi, a RenderPage pedig fehér oldalt tart, hacsak a színt explicit módon nem adják át
// Csak a képernyőn megjelenő nézetet érinti
PdfView.PageColor := $00D9EDF2;  // meleg papírtónus az oldaltartalom mögött

// a RenderPage kimenete figyelmen kívül hagyja a PageColor-t; add meg a színt explicit módon
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);

A PageColor megváltoztatja, mit jelenít meg a TPdfView, de a RenderPage-en keresztül előállított bitképek megtartják az alapértelmezett fehéret, hacsak a Color paraméter mást nem mond. A tünet megbízható: a képernyő a tónusozott oldalt mutatja, a felhasználó exportál vagy nyomtat, és a kimenet visszaáll fehérre. Ezt is ugyanaz alá az exportpolitikai döntés alá kell iktatni, amiről az első szakaszban volt szó

A megmaradt színtulajdonságok overlay-jeleket határoznak meg: a HighlightColor a keresési találatokhoz, a SelectionColor a felhasználói szövegkijelöléshez, a ReadingWordColor a kimondott-szó kurzorhoz. Mindegyiket újra kell ellenőrizni minden szűrő alatt, amit felkínálsz. Egy borostyánszínű olvasási kurzor, amely fehéren működik, eltűnik invertálás után; egy halványkék kijelölés belevész egy nagykontrasztos háttérbe. Tarts fenn módonkénti overlay-palettákat egyetlen globális készlet helyett, és szándékosan teszteld a kombinációkat. A szűrők plusz a szövegfelolvasás normál konfiguráció azoknak az olvasóknak, akiket ez a funkció szolgál, nem egy szélsőséges eset. Magát az overlay-gépezetet az akadálymentes felolvasó cikk tárgyalja

Számok, ellenőrzés és a nyomtatási kérdés

A WCAG 2.1 mérhetővé teszi ezt a funkciót. Az 1.4.3 sikerkritérium 4,5:1 kontrasztarányt kér a törzsszövegtől, az 1.4.6 pedig 7:1-re emeli a fokozott kontraszthoz. Ellenőrizd le pontszerűen a nagykontrasztos módodat ezekkel az arányokkal egy kontraszt-elemzővel, amelyet tényleges renderelt kimeneten futtatsz. A képek fölötti szöveg és az űrlapmezőkben lévő szöveg az, ahol az arányok csendben elbuknak, még akkor is, amikor a törzsszöveg megfelel

A nyomtatás megérdemli a saját döntését, és a védhető alapértelmezett a dokumentum saját megjelenése, a „nyomtatás úgy, ahogy megjelenik" pedig explicit felhasználói választásként kínálva. Egy nyomtatott oldal több munkafolyamatban bizonyíték, mint amennyire a nézegető szerzői gondolnák, és egy szerződés invertált nyomata jogi ízesítésű support-incidens. Még egy párosítás számít a teljesítmény szempontjából: a szűrt renderelés megduplázza a bitkép-munkát minden módváltásnál, ezért ne alkalmazz átalakítást minden festési üzenetnél. Gyorsítótárazd a szűrt bitképet, és csak akkor futtasd újra az átalakítást, amikor az oldal, a zoom vagy a mód ténylegesen megváltozik. A gyorsítótárazási stratégia, amely ezt olcsóvá teszi, a renderelési gyorsítótár és zoom-teljesítmény cikkben él

Egy dolgot a felhasználói felületedben kell lerendezni, nem a kódodban: melyik mód a helyes alapértelmezett. Nincs egyetlen válasz, ezért kínáld fel a készletet, és hagyd, hogy az olvasó válasszon. A nagykontraszt a legtöbb szövegben gazdag olvasáshoz illik, az invertálás azoknak az olvasóknak felel meg, akik kifejezetten világosat sötéten akarnak, a szürkeárnyalat csökkenti a színzajt, egy háttértónus pedig kezeli a fényérzékenységet. Tartsd meg a választást felhasználónként, állítsd vissza induláskor, és tarts fenn egy egy billentyűleütéses utat vissza a normálhoz, mert egy olyan olvasónak, aki egy olyan módban landol, amit nem tud elolvasni, gyors kiútra van szüksége

Az itt használt render-opciók, bitkép-átalakítások és nézet színtulajdonságok a Delphihez, C++Builderhez és Lazarus/FPC-hez készült PDFium Component részeként érkeznek, teljes forráskóddal, hogy az átalakítás-implementációk auditálhatók vagy bővíthetők legyenek