Un lettore ipovedente non riesce a distinguere il testo nero su pagina bianca al contrasto predefinito, e quindi chiede una modalità scura. La risposta ingenua è invertire ogni pixel della pagina renderizzata. Viene rilasciata in una settimana e si rompe il giorno dopo: le fotografie scansionate tornano indietro con l'aspetto di negativi fotografici, i segni di evidenziatore giallo del lettore diventano una macchia blu illeggibile, e qualcuno chiede perché la stampa sia uscita tutta nera. La funzionalità merita davvero di essere costruita ed è davvero facile realizzarla a metà, e la distanza fra i due esiti sta in una sola idea: ogni decisione sul colore appartiene a un punto preciso della pipeline di rendering, e l'inversione è lo strumento sbagliato applicato nella fase sbagliata. Il codice qui usa PDFium Component, il viewer basato su PDFium per Delphi, C++Builder e Lazarus, la cui API di rendering espone quelle fasi separatamente
I filtri sono stato di presentazione, mai stato del documento
Una regola previene qui la peggiore categoria di bug: una modalità di lettura cambia il modo in cui la bitmap viene prodotta o post-elaborata, e nient'altro. I byte del PDF restano intatti, ogni modalità è reversibile con un nuovo rendering, e il comando di salvataggio non riscrive mai un aspetto filtrato dentro il file. Sembra ovvio finché un revisore legale non stampa un contratto con un filtro attivo e non archivia la versione invertita. A quel punto la domanda se la stampa usi l'aspetto proprio del documento oppure quello dello schermo si rivela meritevole di una risposta esplicita nella vostra specifica, non di un caso fortuito del percorso di codice. Tenete l'impostazione del filtro nello stato del viewer, applicatela al momento del rendering e fate dichiarare a ogni percorso di esportazione quale aspetto utilizza
La regola si ripaga due volte. La reversibilità arriva gratis, perché cambiare modalità significa renderizzare di nuovo dalla sorgente immutata: non c'è alcuno stack di undo da mantenere e nessun modo perché una sequenza di cambi di modalità degradi la pagina. Gli scenari multi-finestra restano coerenti per la stessa ragione. Due viste di uno stesso documento possono usare modalità diverse, dato che ciascuna vista possiede il proprio stato di presentazione mentre l'oggetto documento resta condiviso
Prima il rendering, poi la trasformazione
Il pattern supportato è la post-elaborazione della bitmap: RenderPage produce il raster della pagina, poi una passata di trasformazione lo adatta. Il componente fornisce tre trasformazioni come operazioni bitmap in place, InvertPdfBitmap, DuotonePdfBitmap e GrayscalePdfBitmap, il che rende il cambio di modalità una funzione pulita a due stadi:
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); // sfondo scuro, testo ambra
rmGrayscale: GrayscalePdfBitmap(Result);
end;
// rmNormal prosegue: il documento mantiene i propri colori
end;
Da questo disegno discendono due cose. Primo, il costo della trasformazione è proporzionale alla dimensione della bitmap, quindi il lavoro va dove sono memorizzati in cache i risultati del rendering: filtrate la bitmap in cache una volta sola, non a ogni ridisegno. Secondo, poiché la trasformazione agisce sul raster finito, colpisce allo stesso modo testo, grafica vettoriale, immagini e aspetti delle annotazioni. Quella uniformità è esattamente ciò che la semplice inversione sbaglia con le fotografie. È la ragione per cui la trasformazione duotone costituisce un default migliore per i documenti ricchi di testo, dato che mappa la luminanza su una rampa di colore scelta dallo scuro al chiaro invece di negare le tinte; l'inversione resta disponibile come scelta esplicita per i lettori che la desiderano. I bordi più netti dei glifi sono una leva a parte. L'opzione di rendering reNoSmoothText disattiva l'anti-aliasing del testo al momento del rendering e si abbina bene alla modalità ad alto contrasto agli ingrandimenti elevati
Due scale di grigi che non concordano
Le opzioni di rendering includono reGrayscale, che sembra una scorciatoia per saltare il passo di post-elaborazione. Non è la stessa operazione:
// A livello di motore: scala di grigi applicata durante la rasterizzazione
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);
// Post-elaborazione: rendering a colori, conversione della bitmap finita
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);
L'opzione a livello di motore si applica al risultato raster del contenuto immagine ma non raggiunge i riempimenti vettoriali né i colori del testo, quindi una pagina con titoli colorati può tornare indietro con fotografie grigie e titoli ostinatamente blu. GrayscalePdfBitmap sulla bitmap finita converte tutto, senza condizioni. L'opzione di rendering si guadagna comunque il proprio posto quando volete immagini desaturate mantenendo il colore del testo come segnale, cosa che alcuni lettori ipovedenti preferiscono espressamente. Ma se il requisito dice pagina in scala di grigi, la post-elaborazione è la versione che lo soddisfa. Qualunque percorso scegliate, tenete a mente entrambi gli stili di overload di RenderPage. La forma a funzione restituisce una bitmap che il chiamante possiede e deve liberare, e questo conta non appena i filtri moltiplicano il numero di bitmap renderizzate in circolazione
Sfondi, segni di selezione e la trappola di PageColor
Non ogni regolazione di comfort è una trasformazione. Sostituire lo sfondo bianco della pagina con un tono caldo basta spesso da solo per i lettori sensibili al riverbero, e ha una proprietà dedicata. La proprietà porta con sé una regola di ambito che coglie in fallo:
// Interessa soltanto la vista a schermo
PdfView.PageColor := $00D9EDF2; // tono carta caldo dietro il contenuto della pagina
// Il risultato di RenderPage ignora PageColor; passate il colore esplicitamente
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);
PageColor cambia ciò che TPdfView visualizza, ma le bitmap prodotte tramite RenderPage mantengono il bianco predefinito a meno che il parametro Color non dica altrimenti. Il sintomo è affidabile: lo schermo mostra la pagina tinta, l'utente esporta o stampa, e il risultato torna bianco. Archiviate la cosa sotto la stessa decisione di politica di esportazione della prima sezione
Le restanti proprietà di colore definiscono i segni di overlay: HighlightColor per le occorrenze di ricerca, SelectionColor per la selezione di testo da parte dell'utente, ReadingWordColor per il cursore della parola pronunciata. Ognuna di esse va ricontrollata sotto ogni filtro che offrite. Un cursore di lettura ambra che funziona sul bianco svanisce dopo l'inversione; una selezione azzurro pallido sparisce dentro uno sfondo ad alto contrasto. Mantenete palette di overlay per modalità invece di un unico insieme globale, e collaudate le combinazioni di proposito. Filtri più sintesi vocale è una configurazione normale per i lettori che questa funzionalità serve, non un caso limite. Il meccanismo degli overlay in sé è trattato nell'articolo sul reader accessibile
Numeri, verifica e la questione della stampa
WCAG 2.1 trasforma questa funzionalità in qualcosa di misurabile. Il criterio di successo 1.4.3 richiede un rapporto di contrasto di 4,5:1 per il testo del corpo, e l'1.4.6 lo alza a 7:1 per il contrasto migliorato. Verificate a campione la vostra modalità ad alto contrasto rispetto a quei rapporti con un analizzatore di contrasto eseguito sul risultato realmente renderizzato. Il testo sopra le immagini e il testo nei campi modulo sono i punti in cui i rapporti falliscono silenziosamente anche quando il testo del corpo passa
La stampa merita una decisione a sé, e il default difendibile è l'aspetto proprio del documento, con la stampa come visualizzato offerta come scelta esplicita dell'utente. Una pagina stampata è una prova in più flussi di lavoro di quanto gli autori di viewer tendano ad aspettarsi, e una stampa invertita di un contratto è un incidente di supporto dalle sfumature legali. Un altro abbinamento conta per le prestazioni: il rendering filtrato raddoppia il lavoro sulla bitmap a ogni cambio di modalità, quindi non applicate una trasformazione a ogni messaggio di ridisegno. Mettete in cache la bitmap filtrata e rieseguite la trasformazione solo quando pagina, zoom o modalità cambiano davvero. La strategia di caching che rende tutto questo economico si trova nell'articolo sulla cache di rendering e sulle prestazioni dello zoom
Una cosa da risolvere nella vostra interfaccia piuttosto che nel codice: quale modalità sia il default giusto. Non esiste una risposta unica, quindi offrite l'insieme e lasciate scegliere al lettore. L'alto contrasto si adatta alla maggior parte della lettura ricca di testo, l'inversione va bene per i lettori che vogliono espressamente il chiaro su scuro, la scala di grigi taglia il rumore cromatico e una tinta di sfondo affronta la sensibilità al riverbero. Rendete persistente la scelta per utente, ripristinatela all'avvio e tenete un percorso di ritorno alla normalità con un solo tasto, perché un lettore che finisce in una modalità che non riesce a leggere ha bisogno di una via di uscita rapida
Le opzioni di rendering, le trasformazioni bitmap e le proprietà di colore della vista usate qui sono incluse in PDFium Component per Delphi, C++Builder e Lazarus/FPC, con sorgenti completi affinché le implementazioni delle trasformazioni possano essere verificate o estese