Representación de texto: ¿qué tan complicado puede ser? ¡Resulta ser increíblemente desafiante! Que yo sepa, literalmente ningún sistema muestra el texto "perfectamente". En algún lugar mejor, en algún lugar peor.
Suponga que desea texto arbitrario con fuentes arbitrarias, colores y estilos, con soporte de ajuste de línea y resaltado de texto. De hecho, estos son los requisitos mínimos para la visualización correcta de texto complejo, una ventana de terminal, una página web, etc.
En general, digamos de inmediato: no hay respuestas correctas consecutivas, todo es mucho más importante de lo que piensas y todo afecta a todo lo demás.
Discutiremos temas que no están unidos en un solo concepto, estos son solo problemas que tuve que enfrentar durante varios años de trabajo en la representación de texto en Firefox. Por ejemplo, no discutiremos con demasiado detalle los problemas de segmentación de texto o la administración de varias bibliotecas de texto para una plataforma en particular, ya que no estoy demasiado interesado en esto.
1. Terminología
La naturaleza del texto es compleja, y el inglés transmite mal todos los matices. Para este documento, intentaré cumplir con los siguientes términos. Tenga en cuenta que estas palabras no son "correctas", simplemente las encuentro útiles para transmitir conceptos clave a hablantes nativos de inglés que no tienen experiencia en lingüística.
Personajes:
- Escalar (escalar): escalar Unicode, la "unidad más pequeña" en Unicode (también es un punto de código).
- Carácter: un grupo de grafemas Unicode extendido (EGC), la "unidad más grande" en Unicode (que podría consistir en varios escalares).
- Glifo (glifo): la unidad atómica de representación dada en una fuente. Suele tener un identificador único en la fuente.
- Ligadura: un glifo que consta de varios escalares y potencialmente incluso varios caracteres (los hablantes nativos pueden representar una ligadura como varios caracteres, pero para una fuente es solo un carácter).
- Emoji: glifo "a todo color".

Fuentes
- Fuente: un documento que asigna caracteres a glifos.
- Escritura / escritura (script): un conjunto de glifos que componen un determinado idioma (las fuentes, por regla general, implementan ciertos scripts).
- Fuente escrita a mano (escritura cursiva): cualquier fuente en la que los glifos se tocan y fluyen entre sí (por ejemplo, árabe).
- Color: valores RGB y alfa para las fuentes (no es necesario para algunos casos de uso, pero esto es interesante).
- Estilo: modificadores en negrita y cursiva para las fuentes (en implementaciones prácticas, sugerencias, alias y otras configuraciones generalmente también se proporcionan).
2. ¿El estilo, el diseño y la forma dependen el uno del otro?
Aquí hay un breve resumen para darle una idea de cómo funciona una tubería de representación de texto típica:
- Estilización (marcado de análisis, sistema de consulta para fuentes).
- Diseño (división de texto en líneas).
- Conformación, conformación (cálculo de glifos y sus posiciones).
- Rasterización de los glifos necesarios para el atlas de textura / caché).
- Composición (copia de glifos del atlas a la posición deseada).
Desafortunadamente, estos pasos no son tan simples como parecen.
La mayoría de las fuentes no producen todos los glifos posibles bajo demanda. Hay demasiados glifos, por lo que las fuentes generalmente solo implementan una letra específica. Los usuarios finales generalmente no lo saben o no les importa, por lo que un sistema confiable debería
conectarse en
cascada a otras fuentes si los caracteres no están disponibles.
Por ejemplo, aunque el marcado del siguiente texto no
implica múltiples fuentes, es necesario para una representación adecuada en cualquier sistema: hola

मनी ب بسم 好. ¡Entonces nos acercamos peligrosamente al hecho de que el paso 1 (estilización) comienza a depender del paso 3 (conformación)!
(Alternativamente, puede adoptar el
enfoque Noto y usar una sola fuente Uber que contenga todos los caracteres. Aunque los usuarios no pueden configurar la fuente y no pueden proporcionar una interfaz de texto "nativa" a los usuarios en todas las plataformas. Pero supongamos que necesitan una fuente más confiable decisión).
Del mismo modo, para el diseño debe saber cuánto espacio ocupa cada parte del texto, ¡pero esto solo se conoce después de la configuración! ¿El paso 2 depende de los resultados del paso 3?
Pero para dar forma, debes conocer el diseño y el estilo, por lo que parece que estamos atascados. Que hacer
En primer lugar, la estilización aplica trucos. Aunque
realmente queremos obtener glifos completos, los
escalares son suficientes para estilizar. Si la fuente no admite la escritura adecuada, no alegará saber nada sobre escalares de esa escritura. Por lo tanto, puede encontrar fácilmente la "mejor" fuente de la siguiente manera:
Para cada símbolo (EGC) en nuestro texto, interrogamos a cada fuente en la lista (cascada) si conoce todos los escalares que componen este símbolo. Si es así, úsalos. Si llegamos al final de la lista sin resultado, obtenemos tofu (

, falta indicador de glifo).
¡Probablemente ya hayas visto ese indicador cuando te reuniste con emoji! Dado que algunos emojis son en realidad ligaduras de varios emojis más simples, una fuente puede indicar la compatibilidad con un personaje emitiendo solo componentes individuales. De esta manera

literalmente puede parecer

si la fuente es "demasiado antigua" para saber acerca de la nueva ligadura. Esto también puede suceder si tiene una implementación Unicode "demasiado antigua" que no conoce el nuevo carácter, lo que obliga al sistema de estilo a aceptar una coincidencia tan parcial.
Entonces, ahora sabemos exactamente qué fuentes usaremos, sin tener que referirnos al diseño o la forma (aunque la configuración puede cambiar nuestros colores, más sobre eso en las siguientes secciones). ¿Podemos lidiar de manera similar con la interdependencia del diseño y la forma? No! Cosas como los saltos de párrafo le brindan un salto de línea difícil, ¡pero la única forma de dar forma es a través del modelado iterativo!
Es necesario suponer que el texto se coloca en una línea y formar esta línea hasta que se agote el espacio. En este punto, puede realizar operaciones de composición tipográfica y averiguar dónde dividir el texto y comenzar la siguiente línea. Repita hasta que todo esté hecho.
3. El texto no es caracteres separados
A juzgar solo por el inglés, entonces podrías pensar que las ligaduras son una especie de absurdo extraño. Quiero decir, ¿a quién
le importa
realmente que "æ" se deletrea "ae"? Pero resulta que algunos idiomas consisten esencialmente en ligaduras. Por ejemplo, ड् ب بسم consta de los caracteres individuales «ب ب س م. En cualquier sistema avanzado de representación de texto (es decir, en cualquiera de los principales navegadores), estas dos líneas se verán
muy diferentes.
Y no: no se trata de la diferencia entre los escalares Unicode y los grupos de grafemas extendidos. Si le pide a un sistema Unicode confiable (por ejemplo, Swift) que le dé grupos de grafemas extendidos de esta línea, ¡dará estos cinco caracteres!
La forma del carácter depende de sus vecinos: el
texto no puede mostrar correctamente carácter tras carácter .
Es decir, debe usar una biblioteca de modelado. El estándar de la industria aquí es
HarfBuzz , y estas tareas son extremadamente difíciles de resolver por su cuenta. Entonces usa HarfBuzz.
3.1. Texto superpuesto
En las fuentes escritas a mano, los glifos a menudo se superponen para evitar costuras, y esto puede causar problemas.
Echemos otro vistazo a मनी م منش. ¿Se ve normal? Ahora aumenta:

Todavía parece hermoso, pero hagamos que el texto sea parcialmente transparente. Si estás en Safari o Edge, ¡el texto puede verse bien! Pero en Firefox o Chrome, la vista es terrible:

El problema es que Chrome y Firefox están tratando de hacer
trampa . Formaron correctamente el texto, pero tan pronto como se encuentran con estos glifos, todavía están tratando de dibujarlos por separado. Esto generalmente funciona bien, excepto cuando hay transparencia y superposición que produce tal atenuación.
Una implementación "correcta" llevará el texto a una superficie temporal
sin transparencia, y luego a la escena
con transparencia. Firefox y Chrome no hacen esto porque es costoso y generalmente no es necesario para los principales idiomas occidentales. Curiosamente, realmente entienden el problema, porque procesan específicamente un script para emoji (pero volveremos a esto más adelante).
3.2. El estilo puede cambiar la ligadura
Bien, este ejemplo lo estamos analizando
principalmente por curiosidad acerca de cómo se puede romper el marcado, aunque no conozco ningún escenario razonable en el que realmente pueda doler. Aquí hay dos textos con el mismo contenido pero con diferentes colores:

Así es como se ven en Safari:

Así es como se ven en Chrome (cuando se usa su
nueva implementación de maqueta ):

Y aquí están en Firefox:

En resumen:
- Safari es inadecuado
- Chrome analiza los glifos pero deja caer muchos colores
- Firefox analiza simultáneamente glifos y muestra colores
Creo que todos deberían estar en Firefox, ¿verdad? Pero si te acercas, veremos que está haciendo algo muy extraño:

¡Simplemente dividió esta ligadura en cuatro partes iguales con diferentes colores!
El problema es que realmente no hay una respuesta razonable sobre lo que
debe hacerse aquí. Dividimos la ligadura en diferentes estilos, y dado que la ligadura es, en cierto sentido, una "unidad" de renderizado, tiene sentido simplemente negarse a soportar tal separación (como la mayoría lo hace).
Por alguna razón,
alguien en Firefox estaba realmente entusiasmado por hacer una implementación más elegante . Su enfoque es dibujar una ligadura varias veces con máscaras óptimas y colores diferentes, ¡lo que funciona sorprendentemente bien!
Es lógico tratar de admitir estas "ligaduras parciales": ¡solo el modelado puede saber si se mostrará una ligadura en particular, y esto depende de las fuentes del sistema, por lo que la ligadura puede aparecer donde nadie la esperaba! Un ejemplo clásico en inglés es una ligadura æ de una fuente instalada por el usuario en el borde de un hipervínculo.
También es bastante extraño que el inglés pueda cambiar en el medio de una palabra, pero ¿no pueden las fuentes escritas a mano?
Ni siquiera pregunte por el código que rompe líneas con ligaduras parciales.
4. Emoji rompe el color y el estilo
Si muestra emojis como lo hace el sistema nativo, debe ignorar la configuración de color del texto (con la excepción de la transparencia):

Por lo general, los emojis tienen sus propios colores nativos, y este color puede incluso tener un significado semántico, como es el caso de los modificadores del color de la piel. Además: ¡pueden tener varios colores!
Hasta donde puedo decir, no había tal problema antes de los emoji, por lo que las diferentes plataformas tienen diferentes enfoques para resolver. Algunos muestran emojis como una imagen sólida (Apple), otros como una
serie de capas monocromas (Microsoft).
El último enfoque no es malo, ya que se integra bien con las canalizaciones de representación de texto existentes, "simplemente" dividiendo el glifo en una serie de glifos monocromos con los que todos están acostumbrados a trabajar.
Sin embargo, esto significa que cuando dibuja un glifo "único", su estilo puede cambiar
repetidamente . También significa que el glifo "uno" puede superponerse, lo que lleva a los problemas de transparencia mencionados en la sección anterior. Aún así, los navegadores
realmente combinan correctamente la transparencia de las capas en emojis.
Esta discrepancia puede explicarse de tres maneras:
- Ya está buscando glifos de colores para procesarlos de una manera especial, por lo que es fácil para ellos elegir una ruta de diseño especial.
- Las fuentes escritas a mano con poca transparencia se ven un poco feas, pero los emojis se descomponen por completo y se convierten en un conjunto de caracteres ilegible, por lo que el trabajo adicional está justificado.
- Los desarrolladores occidentales se preocupan más por los emojis que por idiomas como el árabe y el marathi.
Elige la opción a tu gusto.
Y, sin embargo, ¿cómo resaltar un emoticón en cursiva o en negrita? ¿Ignorar estos estilos? ¿Deben ser sintetizados? Quien sabe ...
Además, ¿estos emojis no parecen extrañamente pequeños?
Sí, por alguna razón, un grupo de sistemas aumenta secretamente el tamaño de fuente para los emojis para que se vean mejor.
5. Alisar es el infierno
Los caracteres en el texto son muy pequeños y detallados. Es muy importante que el texto sea fácil de leer. Suena como una tarea de suavizado! Demonios, 480p es realmente de baja resolución. Más suavizado !!!

Entonces, hay dos tipos principales:
- Alisado en escala de grises

- Suavizado de subpíxeles

El suavizado en escala de grises es un enfoque "natural". La idea básica es que los píxeles parcialmente recubiertos obtienen una transparencia parcial. Durante la composición, esto hará que el píxel obtenga el tono apropiado, mejorando el detalle general.
El término "sombras de gris" se utiliza para el color unidimensional, al igual que nuestra transparencia unidimensional (de lo contrario, los glifos se muestran en un color sólido). Además, en una situación típica de texto negro sobre fondo blanco, el suavizado literalmente muestra sombras grises a lo largo de los bordes.
El suavizado de subpíxeles es un truco que abusa de la colocación normal de píxeles en los monitores. Es mucho más complicado, por lo que si está realmente interesado, tendrá que leer documentación más detallada, aquí hay una breve descripción del concepto de alto nivel.
Los píxeles en su monitor son en realidad tres pequeñas columnas de rojo, verde y azul. Si quieres ponerte rojo, dices "blanco negro negro". Del mismo modo, si desea obtener un color azul, especifique "negro negro blanco". En otras palabras, si juegas con flores, ¡puedes
triplicar la resolución horizontal y obtener muchos más detalles!
Se podría pensar que tal "arco iris" sería muy feo, pero en la práctica el sistema funciona bastante bien (aunque algunos no están de acuerdo con esto). El cerebro humano ama reconocer patrones y suavizarlos. Sin embargo, si toma una captura de pantalla del texto con suavizado de subpíxeles, verá claramente todos los colores adicionales si cambia el tamaño de la imagen o simplemente la mira en el monitor con un diseño de subpíxeles diferente. Es por eso que las capturas de pantalla con texto a menudo se ven muy extrañas y malas.
(En general, este sistema también significa que el color del icono puede cambiar accidentalmente su tamaño y posición percibidos, lo que es realmente molesto).
Por lo tanto, el suavizado de subpíxeles es un hack realmente limpio que puede mejorar significativamente la inteligibilidad del texto, ¡genial! Pero, desafortunadamente, ¡esta también es una gran astilla en el culo!
Tenga en cuenta que los
cambios de glifo de subpíxeles se producen en cualquier sistema de suavizado. Siempre desea que sus glifos rasterizados se ajusten a píxeles completos, pero la rasterización en sí está diseñada para un desplazamiento específico de subpíxeles (un valor entre 0 y 1).
Para entender esto, imagine un cuadrado negro 1x1 con suavizado en escala de grises:
- Si su desplazamiento de subpíxeles es 0, solo sale un píxel negro durante la rasterización.
- Si el desplazamiento del subpíxel es 0.5, cuando está rasterizado, dos píxeles salen al 50% de gris.
5.1. Las compensaciones de subpíxeles rompen la caché de glifos
Rasterizar los glifos requiere una cantidad increíble de cálculo, por lo que es mucho mejor almacenarlos en caché en un atlas de texturas. ¿Pero cómo almacenar en caché las texturas con desplazamientos de subpíxeles? ¡Cada desplazamiento tiene su propia rasterización única!
Aquí debe encontrar un compromiso entre calidad y rendimiento, y esto se puede hacer optimizando las compensaciones de subpíxeles. Para el texto en inglés, un equilibrio razonable sería la falta de precisión vertical de subpíxeles con el desplazamiento horizontal vinculado a un cuarto de un entero. Esto deja solo cuatro posiciones de subpíxeles, lo que aún mejora en gran medida la calidad al tiempo que mantiene un tamaño de caché razonable.
5.2. Los subpíxeles de suavizado no pueden ser compuestos
Una buena característica del suavizado en tonos de gris es que puedes jugar libremente con él y se degrada con gracia. Por ejemplo, si convierte una textura con texto (escala, rotación o transformación), puede volverse un poco borrosa, pero en general se verá normal.
Si haces lo mismo con el suavizado de subpíxeles, se verá horrible. Toda su idea es manipular los píxeles en la pantalla. Si los píxeles de la pantalla no coinciden con los píxeles de su textura, ¡los bordes rojo y azul serán claramente visibles!
Puede pensar que esto está "arreglado" simplemente por una nueva rasterización de glifos en una nueva ubicación. Y, de hecho, si la conversión es estática, esto podría funcionar. Pero si la transformación es una
animación , empeorará aún más.
Este es realmente un error muy común del navegador: si no encuentra que la animación está sucediendo con el texto, los caracteres se contraerán , ya que cada glifo salta entre diferentes enlaces de subpíxeles con sugerencias sobre cada cuadro.Como resultado, los navegadores contienen varias heurísticas para detectar tales animaciones con el fin de forzar el suavizado de subpíxeles para esta parte de la página (e idealmente incluso para el posicionamiento de subpíxeles). Es bastante difícil implementarlo de manera confiable, porque una animación puede ser activada por un JS complejo arbitrariamente sin darle al navegador "pistas" claras.Además, el suavizado de subpíxeles es difícil de usar en presencia de transparencia parcial. De hecho, aquí configuramos nuestros canales R, G y B para codificar tres valores de transparencia (uno para cada subpíxel), pero el texto en sí también tiene un color y un fondo, por lo que la información se pierde fácilmente.Cuando usamos anti-aliasing en escala de grises, tenemos un canal alfa dedicado, por lo que no se pierde nada. Por lo tanto, los navegadores usualmente usan tonos de gris para trabajar con objetos translúcidos.... Excepto Firefox. Una vez más, en esta extraña organización, alguien realmente se dejó llevar e hizo algo complicado: un componente alfa. Resulta que realmente puede componer correctamente el texto con suavizado de subpíxeles, pero esto requiere tres canales de transparencia adicionales para R, G y B. No es sorprendente que dicho suavizado duplique el consumo de memoria.Afortunadamente, con los años, el suavizado de subpíxeles se ha vuelto menos relevante:- Las pantallas de retina no lo necesitan en absoluto.
- El diseño de subpíxeles en los teléfonos bloquea este truco (sin un trabajo serio).
- En las versiones más recientes de MacOS, el texto de subpíxeles está deshabilitado de forma predeterminada en el nivel del sistema operativo.
- Chrome parece ser más agresivo al deshabilitar el suavizado de subpíxeles (no estoy seguro si esta es la política exacta).
- El nuevo backend gráfico de Firefox (webrender) abandonó el componente Alpha por simplicidad.
6. Esotérico
Esta parte es solo una colección de pequeñas cosas que no merecen mucha discusión.6.1. Las fuentes pueden contener SVG
Eso apesta. Estas fuentes son proporcionadas principalmente por Adobe, porque hace algún tiempo entraron en el SVG bastante bien. A veces puede simplemente ignorar partes de SVG (creo que la fuente Source Code Pro técnicamente contiene algunos glifos SVG, pero en la práctica en realidad no los usan los sitios web), pero en general tendrá que implementar el soporte SVG para admitir formalmente todas las fuentes.¿Y has oído hablar de las fuentes animadas SVG ? No? Bueno
Creo que están rotos o no se implementan en todas partes (Firefox los apoyó accidentalmente por un tiempo debido a algún tipo de desarrollador entusiasta).6.2. Los personajes pueden ser muy grandes
Si desea satisfacer ingenuamente la solicitud de un usuario de una fuente muy grande (o un nivel de zoom muy grande), entonces encontrará problemas extremos de administración de memoria para un atlas de glifos de este tamaño, ya que cada carácter puede ser más grande que toda la pantalla. Hay varias formas de manejar esto:- Negarse a dibujar un glifo (usuario triste).
- Rasterice el glifo en un tamaño más pequeño y aumente la escala durante la composición (esto es fácil, pero las formas se vuelven borrosas a lo largo de los bordes).
- Rasterice el glifo directamente en la superficie después de la composición (difícil, potencialmente costoso).
6.3. La selección no es un marco, pero el texto va en todas las direcciones.
La gente generalmente sabe que la dirección principal del texto puede ser de izquierda a derecha (inglés), de derecha a izquierda (árabe) o de arriba a abajo (japonés).Entonces, aquí hay un texto divertido para ti:Hola a todos بسم الله لا bip bip !!
Si selecciona texto en el escritorio con el mouse de izquierda a derecha, entonces la selección se vuelve intermitente y de alguna manera se tuerce extrañamente en el medio. Esto se debe a que mezclamos texto en una línea de izquierda a derecha y de derecha a izquierda, lo que sucede todo el tiempo.Primero, la selección a la derecha aumenta la selección, pero luego la disminuye hasta que de repente comienza a aumentar nuevamente. Esto es realmente correcto: la selección simplemente permanece continua en la línea real . Por lo tanto, puede copiar correctamente un fragmento de texto.Debe tener esto en cuenta en su código para resaltar el texto, así como en el algoritmo de salto de línea para el diseño.Pero eso no es todo.
Espero que no tengas que lidiar con esas cosas.6.4. ¿Cómo escribir lo que es imposible escribir?
Cuando no hay caracteres en la fuente, sería bueno informar al usuario al respecto. El glifo "tofu" está destinado a esto. Simplemente puede dibujar un tofu (rectángulo) vacío y limitarse a eso. Pero si desea proporcionar información realmente útil, puede escribir el valor del carácter que falta para simplificar la depuración.Pero espera, ¿usamos texto para explicar que no podemos generar el texto? Hm.
Puede decir que debe haber una fuente básica en el sistema que siempre muestre los caracteres 0-9 y AF, pero esto es una suposición para los débiles. Si el usuario realmente destruyó sus herramientas con sus herramientas, entonces Firefox ofrece una salida: ¡una micro fuente!Dentro de Firefox hay una pequeña matriz codificada de pixel art de un bit con un pequeño atlas de exactamente estos 16 caracteres. Entonces, al dibujar tofu, puede reenviar estos caracteres sin preocuparse por las fuentes.
6.5. El estilo es parte de la fuente (a menos que no lo sea)
Las fuentes de alta calidad vienen inicialmente con estilos como cursiva y negrita , ya que no existe una forma algorítmica simple para mostrar estos efectos de manera hermosa.Sin embargo, algunas fuentes vienen sin estos estilos, por lo que aún necesita una forma algorítmica simple para hacer estos efectos.La detección y el procesamiento exactos de los estilos dependen en gran medida del sistema y están fuera de mi área de especialización, por lo que no puedo explicarlo bien. Simplemente profundizaría en el código de manejo de fuentes en Webrender .En cualquier caso, necesita un respaldo sintético . Afortunadamente, la implementación es bastante simple:cursiva sintética: inclina cada glifo.Negrita sintética: dibuje cada glifo varias veces con un ligero desplazamiento en la dirección del texto.Honestamente, ¡estos enfoques funcionan bastante bien! Pero los usuarios pueden notar que todo parece "incorrecto". Por lo tanto, puede hacerlo mejor si hace un esfuerzo.6.6. Sin representación de texto perfecta
Cada plataforma ha tenido sus errores, optimizaciones y peculiaridades durante tanto tiempo que se han convertido en estética. Por lo tanto, incluso si cree firmemente que ciertas cosas son ideales o importantes, siempre habrá un gran grupo de usuarios con diferentes preferencias. Un sistema robusto de visualización de texto admite estas diversas preferencias (al elegir valores predeterminados razonables).Sus configuraciones deben tener en cuenta el sistema del usuario, las fuentes específicas, las aplicaciones específicas y los textos específicos. También debe intentar hacer coincidir el "aspecto" nativo de cada plataforma (tales peculiaridades).Esto incluye:- Capacidad para deshabilitar el suavizado de subpíxeles (algunos realmente lo odian).
- La capacidad de desactivar cualquier suavizado (sí, la gente hace esto).
- Una tonelada de propiedades específicas de plataforma / formato, como sugerencias, suavizado, variaciones, gamma, etc.
También significa que las bibliotecas de texto nativas deben usarse para que coincida con la estética de cada sistema (Core Text, DirectWrite y FreeType en sus respectivas plataformas).7. Enlaces adicionales
Aquí hay algunos artículos más sobre la pesadilla de renderizado de texto: