- Pretext é uma biblioteca pura em JavaScript/TypeScript para calcular a altura e a distribuição de linhas de texto multilinha sem acesso ao DOM, com suporte tanto para navegador quanto para ambiente de servidor
- Como não usa APIs de medição do DOM como getBoundingClientRect, elimina o custo de reflow de layout e garante precisão com uma lógica própria de medição baseada no motor de fontes
- Por meio das APIs prepare() / layout(), pré-processa o texto e realiza cálculo rápido de altura com operações puramente aritméticas usando dados de largura em cache
- Suporta emoji, texto com direções mistas (bidi) e vários idiomas, além de fornecer os mesmos resultados em Canvas, SVG, WebGL e renderização no servidor
- É um motor de texto de alto desempenho que pode ser usado para implementar layouts de UI precisos, como scroll virtualizado, validação de overflow de texto e posicionamento de texto flutuante
Visão geral
- Pretext é uma biblioteca pura em JavaScript/TypeScript para medição e layout de texto multilinha, com suporte a DOM, Canvas, SVG e renderização no servidor
- Não usa APIs de medição do DOM (
getBoundingClientRect, offsetHeight etc.), portanto elimina o custo de reflow de layout
- Oferece desempenho preciso e rápido por meio de uma lógica própria de medição baseada no motor de fontes do navegador
- Suporta todos os idiomas, emoji e texto com direções mistas (bidi), além de lidar com diferenças entre navegadores
Instalação e demo
Principais recursos
- O Pretext oferece duas formas principais de uso
-
1. Medição da altura de parágrafos sem acesso ao DOM
prepare() pré-processa o texto, faz normalização de espaços, separação de segmentos, aplicação de regras de glue e medição baseada em canvas, e retorna um handle opaco (opaque handle)
layout() usa dados de largura em cache para calcular altura e número de linhas com operações puramente aritméticas
- Para o mesmo texto e configuração, não é necessário chamar
prepare() repetidamente; em redimensionamentos, basta executar layout() novamente
- Com a opção
{ whiteSpace: 'pre-wrap' }, preserva espaços, tabs (\t) e quebras de linha (\n)
- Resultado de benchmark:
prepare() cerca de 19ms (com base em 500 textos), layout() cerca de 0.09ms
- O valor de altura retornado pode ser usado em recursos de UI como:
- Cálculo preciso de altura em virtualização e oclusão
- Sistemas de layout baseados em JS (ex.: masonry, estruturas semelhantes a flexbox)
- Validação de overflow de texto baseada em IA
- Manutenção da posição de scroll ao carregar texto
-
2. Composição manual do layout de parágrafos
prepareWithSegments() gera dados por segmento
layoutWithLines() retorna, em largura fixa, o texto e as informações de largura de cada linha
walkLineRanges() percorre a largura e o intervalo de cursor de cada linha sem criar strings de texto
- Ex.: permite ajuste de layout com busca binária para testar várias larguras e encontrar a quantidade de linhas e altura adequadas
layoutNextLine() faz o layout sequencialmente, uma linha por vez, quando cada linha tem largura diferente
- Ex.: posicionamento de texto flutuante para fazer o texto contornar imagens
- Essa abordagem também pode ser aplicada da mesma forma em Canvas, SVG, WebGL e renderização no servidor
Resumo da API
-
API para medição básica
prepare(text, font, options?): analisa e mede o texto, retornando um handle para passar a layout()
layout(prepared, maxWidth, lineHeight): calcula a altura do texto e o número de linhas de acordo com a largura e a altura de linha fornecidas
-
API para layout manual
prepareWithSegments(text, font, options?): retorna dados por segmento
layoutWithLines(prepared, maxWidth, lineHeight): inclui texto, largura e informações de cursor de cada linha
walkLineRanges(prepared, maxWidth, onLine): passa para o callback a largura e o intervalo de cursor de cada linha
layoutNextLine(prepared, start, maxWidth): executa o layout em forma de iterador por linha
- Inclui definições de tipo
LayoutLine, LayoutLineRange, LayoutCursor
-
Outros utilitários
clearCache(): limpa o cache interno
setLocale(locale?): define a localidade e limpa o cache (sem afetar o estado existente)
Limitações e observações
- O Pretext não é um motor completo de renderização de fontes
- Propriedades CSS alvo padrão
white-space: normal
word-break: normal
overflow-wrap: break-word
line-break: auto
- Ao usar
{ whiteSpace: 'pre-wrap' }, preserva espaços, tabs e quebras de linha, aplicando tab-size: 8
- No macOS, a fonte
system-ui não é adequada para a precisão do layout(), portanto recomenda-se usar um nome de fonte explícito
- Por causa de
overflow-wrap: break-word, em larguras muito estreitas pode haver quebra dentro das palavras, mas a divisão ocorre apenas com base em unidades de caractere (grapheme)
Desenvolvimento
- Consulte
DEVELOPMENT.md para ambiente e comandos de desenvolvimento
Contribuição e contexto
- Dá continuidade às ideias do projeto text-layout de Sebastian Markbage
- A estrutura evoluiu herdando e desenvolvendo o design de shaping baseado em canvas
measureText, tratamento bidi do pdf.js e quebra de linha em streaming
1 comentários
Comentários do Hacker News
Este projeto é realmente impressionante
Resolve o problema de calcular com eficiência a altura de texto com quebra de linha sem realmente renderizar o texto na página web
Faz cache da largura e altura de segmentos divididos por palavra e implementa diretamente o algoritmo de quebra de linha do navegador
É um trabalho muito difícil por causa do tratamento de hífen, emoji, chinês e outros caracteres, além das diferenças de renderização entre navegadores (incluindo Safari)
Usa o dataset corpora e a página de teste de accuracy para testar em comparação com navegadores reais
No meu código, com texto ASCII, leva 80 ms; o pretext leva 2200 ms
Ainda não testei a precisão, mas pretendo fazer isso hoje à noite
Já existem PRs de melhoria de desempenho abertas na issue #18
Eu também sofri no passado tentando renderizar texto multilinha em canvas
Este projeto é muito mais útil porque está ligado diretamente ao DOM
Exemplo: demo do Scrawl
Pode ser mais lento que APIs nativas e não há garantia de que use a mesma lógica da renderização não-canvas do navegador
É uma abordagem de renderizar no canvas e medir depois, fornecendo basicamente uma API para análise de layout de texto
Isto é realmente uma funcionalidade esperada há muito tempo
Faz tempo que era difícil implementar direito coisas como acordeões responsivos
O padrão de evolução da web sempre foi ① surgem requisitos complexos → ② hacks em JS/CSS → ③ padronização
Desta vez, parece um estágio 2 bem feito, não um hack
Pelo RESEARCH.md, até as diferenças de medição de emoji entre navegadores foram estudadas em detalhe
A manutenção deve ser difícil, mas isso parece um grande ponto de virada para a evolução da web
Desta vez, é interessante que a IA foi usada ativamente no processo de desenvolvimento. Parece que a maior parte foi implementada com o agente do Cursor
Segundo o autor da biblioteca, ele fez Claude Code e Codex aprenderem dados de ground truth dos navegadores e repetiu medições ao longo de várias semanas
Veja o tweet relacionado
Parece que Autoresearch também foi usado em parte
Gostei especialmente do exemplo de reflow baseado em shape
Queria aplicar isso no Ensō (enso.sonnet.io), mas me contive para manter a simplicidade
O exemplo de acordeão também pode ser implementado com
interpolate-sizedo CSSVeja o artigo do Josh Comeau
O exemplo de bolhas de texto pode ser feito de forma parecida com
text-wrap: balance | prettybalanceouprettynão resolvem completamenteMuitas vezes você não quer simplesmente deixar o comprimento das linhas uniforme
Issue relacionada do CSSWG: #191
text-wrapajuda a ajustar o número de palavras por linha, mas o problema da margem direita continuaO pretext não usa canvas.measureText diretamente; você passa o texto e os atributos para uma API JS, e ele calcula o layout automaticamente
Antes, era preciso usar measureText diretamente ou portar o harfbuzz para o navegador
Parece mais o resultado de combinar bem elementos já existentes do que um avanço técnico radical
Ainda assim, fico curioso sobre a diferença em relação a Skia-wasm / Canvaskit
O diferencial do pretext é ter implementado renderização de glifos em TypeScript puro com ajuda de IA
Parece a diferença entre implementar diretamente o ffmpeg em C e apenas chamá-lo a partir de Dart
Esse tipo de tentativa mostra novas possibilidades para FOSS client-side
No ano passado criei um sistema de composição tipográfica para folhetos impressos em HTML, e calculei repetidamente os limites das caixas com a Selection API para lidar com quebra de linha e evitar linhas órfãs
Ainda funciona bem hoje, mas existe um hack de off-by-one cujo motivo eu não entendo
A funcionalidade iterativa de geração de linhas do pretext é realmente muito bem-vinda
No ambiente Fedora + Firefox, todas as demos parecem quebradas
Ex.: captura de tela
Esse tipo de recurso na verdade deveria ser oferecido como API padrão do navegador
Fico curioso sobre como fazer uma solicitação de funcionalidade ao W3C, e se algo como votação da comunidade seria possível
Mas os fornecedores de navegadores não estão priorizando isso
No momento, o Chrome está mais focado em APIs relacionadas a IA
O engine Sciter já tem a funcionalidade Graphics.Text
É um elemento de renderização de texto baseado em canvas ao qual se pode aplicar estilos CSS diretamente
A busca de texto no navegador (Ctrl+F) não funciona direito em listas com rolagem virtual
Para resolver esse tipo de problema, talvez seja necessária uma nova API de “Search”, e não JS
Projetos relacionados: Display Locking, documentação da MDN
Como os itens fora da tela de listas virtualizadas não estão no DOM, eles não podem ser encontrados
Para resolver isso, seria necessário um novo contrato do navegador que integrasse seleção, foco, posição de rolagem e navegação entre correspondências
Mas é improvável que os sites adotem isso de forma consistente