- Pretext es una biblioteca pura de JavaScript/TypeScript que calcula la altura y la disposición de líneas de texto multilínea sin acceder al DOM, con soporte tanto para navegador como para entorno de servidor
- Al no usar APIs de medición del DOM como getBoundingClientRect, elimina el costo del reflow de layout y garantiza precisión mediante una lógica propia de medición basada en el motor de fuentes
- A través de las APIs prepare() / layout(), preprocesa el texto y realiza un cálculo rápido de altura con operaciones puramente aritméticas usando datos de ancho en caché
- Soporta emoji, texto de dirección mixta (bidi) y varios idiomas, y ofrece los mismos resultados también en Canvas·SVG·WebGL·renderizado del lado del servidor
- Es un motor de texto de alto rendimiento útil para implementar layouts de UI precisos, como scroll con virtualización, validación de desbordamiento de texto y colocación de texto flotante
Descripción general
- Pretext es una biblioteca pura de JavaScript/TypeScript para la medición y maquetación de texto multilínea, con soporte para DOM, Canvas, SVG y también renderizado del lado del servidor
- No usa APIs de medición del DOM (
getBoundingClientRect, offsetHeight, etc.), por lo que elimina el costo del reflow de layout
- Ofrece precisión y alto rendimiento mediante una lógica propia de medición basada en el motor de fuentes del navegador
- Soporta todos los idiomas, emoji y texto de dirección mixta (bidi), y también maneja diferencias entre navegadores
Instalación y demo
Funciones principales
- Pretext ofrece dos formas principales de uso
-
1. Medición de la altura de párrafos sin acceder al DOM
prepare() preprocesa el texto, normaliza espacios, separa segmentos, aplica reglas de glue y realiza mediciones basadas en canvas para devolver un opaque handle
layout() usa datos de ancho en caché para calcular la altura y la cantidad de líneas con operaciones puramente aritméticas
- Con el mismo texto y configuración, no es necesario volver a llamar a
prepare(), y al redimensionar solo hace falta ejecutar de nuevo layout()
- Con la opción
{ whiteSpace: 'pre-wrap' }, conserva tal cual los espacios, tabulaciones (\t) y saltos de línea (\n)
- Resultado de benchmark:
prepare() aprox. 19 ms (sobre 500 textos), layout() aprox. 0.09 ms
- El valor de altura devuelto puede aprovecharse en funciones de UI como las siguientes
- Cálculo preciso de altura en virtualización y occlusion culling
- Sistemas de layout basados en JS (por ejemplo, masonry o estructuras similares a flexbox)
- Validación de desbordamiento de texto basada en IA
- Mantener la posición de scroll al cargar texto
-
2. Construcción manual del layout de párrafos
prepareWithSegments() genera datos por segmento
layoutWithLines() devuelve el texto y la información de ancho de cada línea en un ancho fijo
walkLineRanges() recorre el ancho y el rango de cursor de cada línea sin crear cadenas de texto
- Ejemplo: permite hacer ajuste de layout tipo búsqueda binaria para probar varios anchos y encontrar una cantidad adecuada de líneas y altura
layoutNextLine() realiza el layout línea por línea cuando cada línea tiene un ancho distinto
- Ejemplo: colocación de texto flotante para hacer fluir texto alrededor de imágenes
- Este enfoque también puede aplicarse igual en Canvas, SVG, WebGL y renderizado del lado del servidor
Resumen de la API
-
API para medición básica
prepare(text, font, options?): analiza y mide el texto, y devuelve un handle para pasar a layout()
layout(prepared, maxWidth, lineHeight): calcula la altura del texto y la cantidad de líneas según el ancho y alto de línea dados
-
API para layout manual
prepareWithSegments(text, font, options?): devuelve datos a nivel de segmento
layoutWithLines(prepared, maxWidth, lineHeight): incluye texto, ancho e información de cursor de cada línea
walkLineRanges(prepared, maxWidth, onLine): pasa por callback el ancho y el rango de cursor de cada línea
layoutNextLine(prepared, start, maxWidth): realiza el layout en forma de iterador por línea
- Incluye definiciones de tipos
LayoutLine, LayoutLineRange, LayoutCursor
-
Otras utilidades
clearCache(): reinicia la caché interna
setLocale(locale?): establece la configuración regional y reinicia la caché (sin afectar el estado existente)
Limitaciones y consideraciones
- Pretext no es un motor completo de renderizado tipográfico
- Propiedades CSS objetivo por defecto
white-space: normal
word-break: normal
overflow-wrap: break-word
line-break: auto
- Al usar
{ whiteSpace: 'pre-wrap' }, conserva espacios, tabulaciones y saltos de línea, y aplica tab-size: 8
- En macOS, la fuente
system-ui no es adecuada para la precisión de layout(), por lo que se recomienda usar un nombre de fuente explícito
- Debido a
overflow-wrap: break-word, en anchos muy estrechos puede haber saltos dentro de una palabra, aunque la división se hace solo a nivel de grapheme
Desarrollo
- Consulta
DEVELOPMENT.md para el entorno y los comandos de desarrollo
Contribuciones y contexto
- Retoma ideas del proyecto text-layout de Sebastian Markbage
- La arquitectura evolucionó a partir del shaping basado en canvas
measureText, el manejo bidi de pdf.js y el diseño de line breaking en streaming
1 comentarios
Comentarios de Hacker News
Este proyecto es realmente impresionante
Resuelve el problema de calcular de forma eficiente la altura de texto con saltos de línea sin renderizarlo realmente en una página web
Almacena en caché el ancho y la altura de segmentos divididos por palabras, e implementa directamente el algoritmo de salto de línea del navegador
Es una tarea muy difícil por el manejo de caracteres diversos como guiones, emoji y chino, además de las diferencias de renderizado entre navegadores, incluido Safari
Usa el dataset corpora y la página de pruebas de precisión para comparar con navegadores reales
Con texto ASCII, mi código tarda 80 ms y pretext 2200 ms
Aún no he probado la precisión, pero planeo hacerlo esta noche
Ya hay PR abiertas de mejora de rendimiento en issue #18
Yo también sufrí antes intentando renderizar texto multilínea en canvas
Este proyecto es mucho más útil porque está conectado directamente con el DOM
Ejemplo: demo de Scrawl
Podría ser más lento que una API nativa, y no se puede garantizar que use la misma lógica que el renderizado no-canvas del navegador
Funciona renderizando en canvas y luego midiendo, y más bien ofrece una API para analizar la maquetación de texto
Esta es una función que de verdad se esperaba desde hace mucho tiempo
Desde antes era difícil implementar bien cosas como acordeones responsivos
El patrón de evolución de la web siempre ha sido ① aparecen requisitos complejos → ② hacks con JS/CSS → ③ estandarización
Esta vez, siento que es una etapa 2 bien hecha, no solo un hack
En RESEARCH.md incluso estudiaron en detalle las diferencias de medición de emoji entre navegadores
Será difícil de mantener, pero parece un gran punto de inflexión para la evolución de la web
Esta vez resulta interesante que la IA se usó activamente en el desarrollo. Parece que la mayor parte se implementó usando el agente de Cursor
Según el autor de la librería, hicieron que Claude Code y Codex aprendieran datos de ground truth del navegador e iteraron mediciones durante varias semanas
Ver este tuit relacionado
Parece que también se usó Autoresearch en parte
Me gustó especialmente el ejemplo de reflow basado en shapes
Quise aplicarlo a Ensō(enso.sonnet.io), pero me contuve para mantener la simplicidad
El ejemplo de acordeón también se puede implementar con
interpolate-sizede CSSVer el artículo de Josh Comeau
El ejemplo de burbujas de texto se puede implementar de forma similar con
text-wrap: balance | prettybalanceoprettyno lo resuelven por completoMuchas veces no quieres igualar la longitud de las líneas
Issue relacionado de CSSWG: #191
text-wrapayuda a ajustar la cantidad de palabras por línea, pero el problema del margen derecho sigue ahípretext no usa canvas.measureText directamente, sino que si pasas el texto y los atributos por una API de JS, calcula automáticamente la maquetación
Antes había que usar measureText directamente o portar harfbuzz al navegador
Más que un avance técnico revolucionario, parece el resultado de combinar bien piezas existentes
Aun así, me da curiosidad la diferencia frente a Skia-wasm / Canvaskit
pretext se diferencia en que implementó renderizado de glifos en TypeScript puro con ayuda de IA
Se siente como la diferencia entre implementar ffmpeg directamente en C y llamarlo desde Dart
Este tipo de intento muestra nuevas posibilidades para el FOSS del lado del cliente
El año pasado hice un sistema de composición para folletos impresos con HTML, y para manejar saltos de línea y evitar líneas huérfanas calculaba repetidamente los límites de cajas con la Selection API
Todavía funciona bien, pero tiene un hack off-by-one cuyo motivo no entiendo
La función de generación iterativa de líneas de pretext es realmente bienvenida
En Fedora + Firefox todos los demos se ven rotos
Ejemplo: captura de pantalla
Este tipo de función en realidad debería existir como API estándar del navegador
Me pregunto cómo se hace una solicitud de funcionalidad al W3C y si existe algo como votación de la comunidad
Pero los fabricantes de navegadores no la priorizan
Actualmente Chrome está más enfocado en APIs relacionadas con IA
El motor Sciter ya tiene la función Graphics.Text
Es un elemento de renderizado de texto basado en canvas al que se le pueden aplicar directamente estilos CSS
La búsqueda de texto del navegador (Ctrl+F) no funciona bien en listas con scroll virtual
Para resolver este problema, quizá haga falta una nueva API de “Search” en lugar de JS
Proyectos relacionados: Display Locking, documentación de MDN
Los elementos fuera de pantalla de una lista virtualizada no están en el DOM, por lo que no se pueden buscar
Para resolverlo haría falta un nuevo contrato del navegador que integre selección, foco, posición de scroll y navegación entre coincidencias
Pero es poco probable que los sitios lo usen de forma consistente