Un lector de baja visión no distingue el texto negro sobre una página blanca con el contraste predeterminado, así que pide un modo oscuro. La respuesta naïve es invertir cada píxel de la página renderizada. Se envía en una semana y se rompe al día siguiente: las fotos escaneadas vuelven pareciendo negativos de película, las marcas de resaltador amarillo del lector se convierten en un borón azul ilegible, y alguien pregunta por qué la impresión salió completamente negra. La característica vale la pena construirla genuinamente y es genuinamente fácil de hacer a medias, y la distancia entre los dos resultados es una idea: cada decisión de color pertenece a un punto concreto del pipeline de render, y la inversión es la herramienta equivocada aplicada en la etapa equivocada. El código aquí usa PDFium Component, el visor basado en PDFium para Delphi, C++Builder y Lazarus, cuya API de renderizado expone esas etapas por separado
Los filtros son estado de presentación, nunca estado de documento
Una regla previene la peor categoría de error aquí: un modo de lectura cambia cómo se produce o posprocesa el mapa de bits, y nada más. Los bytes del PDF permanecen intactos, cada modo es reversible al volver a renderizar, y "guardar" nunca escribe una apariencia filtrada de vuelta al archivo. Esto suena obvio hasta que un revisor legal imprime un contrato bajo un filtro activo y archiva la versión invertida. En ese punto la pregunta "¿usa la impresión la apariencia propia del documento o la de la pantalla?" resulta merecer una respuesta explícita en su especificación, no un accidente de la ruta de código. Mantenga el ajuste de filtro en estado del visor, aplíquelo en tiempo de render, y haga que cada ruta de exportación declare qué apariencia usa
La regla se paga dos veces sola. La reversibilidad sale gratis, porque cambiar de modos vuelve a renderizar desde la fuente sin cambiar: no hay pila de deshacer que mantener y ninguna forma de que una racha de cambios de modo degrade la página. Los escenarios multiventana se mantienen coherentes por la misma razón. Dos vistas de un documento pueden correr modos distintos, ya que cada vista posee su estado de presentación mientras el objeto documento permanece compartido
Renderice primero, transforme después
El patrón soportado es el procesamiento de mapa de bits pos-render: RenderPage produce el ráster de la página, luego un pase de transformación lo ajusta. El componente envía tres transformaciones como operaciones de mapa de bits in-situ, InvertPdfBitmap, DuotonePdfBitmap y GrayscalePdfBitmap, lo que hace del cambio de modo una función limpia de dos etapas:
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); // fondo oscuro, texto ámbar
rmGrayscale: GrayscalePdfBitmap(Result);
end;
// rmNormal se deja pasar: el documento conserva sus propios colores
end;
Dos cosas se siguen de este diseño. Primero, el costo de transformación es proporcional al tamaño del mapa de bits, así que el trabajo pertenece dondequiera que estén cacheados sus resultados de render: filtre el mapa de bits cacheado una vez, no en cada pintura. Segundo, porque la transformación corre sobre el ráster terminado, golpea al texto, el arte vectorial, las imágenes y las apariencias de anotación de la misma forma. Esa uniformidad es exactamente lo que la inversión simple hace mal con las fotografías. Es la razón por la que la transformación duotono hace un mejor predeterminado para documentos densos en texto, ya que mapea la luminancia a una rampa de color oscura-a-clara elegida en lugar de negar tonos; la inversión permanece disponible como una elección explícita para lectores que la quieran. Los bordes de glifo más nítidos son una palanca separada. La opción de render reNoSmoothText desactiva el antialiasing de texto en tiempo de render y se empareja bien con el modo de alto contraste en zoom grande
Dos escalas de grises que no se ponen de acuerdo
Las opciones de render incluyen reGrayscale, que parece un atajo más allá del paso de posprocesamiento. No es la misma operación:
// A nivel de motor: escala de grises aplicada durante la rasterización
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);
// Posprocesamiento: renderiza en color, convierte el mapa de bits terminado
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);
La opción a nivel de motor aplica a la salida de ráster del contenido de imagen pero no alcanza los rellenos vectoriales o los colores de texto, así que una página con encabezados de color puede volver con fotografías grises y encabezados tercamente azules. GrayscalePdfBitmap sobre el mapa de bits terminado convierte todo, incondicionalmente. La opción de render aún se gana su lugar cuando quiere imágenes desaturadas manteniendo el color del texto como señal, lo que algunos lectores de baja visión prefieren específicamente. Pero si el requisito lee "página en escala de grises", el posprocesamiento es la versión que lo satisface. Sea cual sea la ruta que elija, tenga en mente ambos estilos de sobrecarga de RenderPage. La forma funcional devuelve un mapa de bits que el llamador posee y debe liberar, y eso importa en cuanto los filtros multiplican el número de mapas de bits renderizados en vuelo
Fondos, marcas de selección y la trampa PageColor
No todos los ajustes de confort son una transformación. Reemplazar el fondo de página blanco con un tono cálido suele bastar por sí solo para lectores sensibles al deslumbramiento, y tiene una propiedad dedicada. La propiedad trae una regla de alcance que atrapa a la gente:
// Afecta solo a la vista en pantalla
PdfView.PageColor := $00D9EDF2; // tono de papel cálido detrás del contenido de la página
// La salida de RenderPage ignora PageColor; pasa el color explícitamente
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);
PageColor cambia lo que TPdfView muestra, pero los mapas de bits producidos a través de RenderPage mantienen el blanco predeterminado salvo que el parámetro Color diga lo contrario. El síntoma es fiable: la pantalla muestra la página teñida, el usuario exporta o imprime, y la salida vuelve al blanco. Archívelo bajo la misma decisión de política de exportación de la primera sección
Las propiedades de color restantes definen marcas de overlay: HighlightColor para resultados de búsqueda, SelectionColor para la selección de texto del usuario, ReadingWordColor para el cursor de palabra hablada. Cada una de ellas hay que volver a comprobarla bajo cada filtro que ofrezca. Un cursor de lectura ámbar que funciona sobre blanco desaparece tras la inversión; una selección azul pálido desaparece en un fondo de alto contraste. Mantenga paletas de overlay por modo en lugar de un único conjunto global, y pruebe las combinaciones a propósito. Filtros más texto-a-voz es una configuración normal para los lectores a los que sirve esta característica, no un caso límite. La maquinaria de overlay en sí se cubre en el artículo del lector accesible
Números, verificación y la pregunta de impresión
WCAG 2.1 convierte esta característica en algo que puede medir. El criterio de éxito 1.4.3 pide una razón de contraste 4,5:1 para el cuerpo de texto, y 1.4.6 la sube a 7:1 para contraste mejorado. Verifique su modo de alto contraste contra esas razones con un analizador de contraste corrido sobre salida renderizada real. El texto sobre imágenes y el texto en campos de formulario es donde las razones fallan silenciosamente incluso cuando el cuerpo de texto pasa
La impresión merece su propia decisión, y el predeterminado defendible es la apariencia propia del documento, con "imprimir como se muestra" ofrecido como una elección explícita del usuario. Una página impresa es evidencia en más flujos de trabajo de los que los autores de visores tienden a esperar, y una impresión invertida de un contrato es un incidente de soporte con condimento legal. Un emparejamiento más importa para el rendimiento: el render filtrado duplica el trabajo de mapa de bits en cada cambio de modo, así que no aplique una transformación en cada mensaje de pintura. Cachee el mapa de bits filtrado y vuelva a correr la transformación solo cuando la página, el zoom o el modo realmente cambien. La estrategia de cacheo que hace esto barato vive en el artículo de caché de render y rendimiento de zoom
Una cosa que conviene decidir en su interfaz en lugar de en su código: qué modo es el predeterminado correcto. No hay una respuesta única, así que ofrezca el conjunto y deje que el lector elija. El alto contraste se adapta a la mayor parte de la lectura densa en texto, la inversión encaja con lectores que específicamente quieren claro-sobre-oscuro, la escala de grises corta el ruido de color, y un tinte de fondo maneja la sensibilidad al deslumbramiento. Persista la elección por usuario, restáurela al iniciar, y mantenga un camino de una pulsación de vuelta a lo normal, ya que un lector que aterriza en un modo que no puede leer necesita una salida rápida
Las opciones de render, las transformaciones de mapa de bits y las propiedades de color de vista usadas aquí se envían con PDFium Component para Delphi, C++Builder y Lazarus/FPC, con código fuente completo para que las implementaciones de transformación puedan auditarse o extenderse