- Pretext est une bibliothèque JavaScript/TypeScript pure qui calcule la hauteur et la répartition sur les lignes d’un texte multiligne sans accès au DOM, avec prise en charge des environnements navigateur et serveur
- Comme elle n’utilise pas d’API de mesure du DOM telles que getBoundingClientRect, elle élimine le coût de reflow de mise en page et garantit la précision grâce à une logique de mesure interne basée sur le moteur de polices
- Via les API prepare() / layout(), elle prétraite le texte puis effectue un calcul rapide de la hauteur par opérations arithmétiques pures en s’appuyant sur des données de largeur mises en cache
- Elle prend en charge les emoji, le texte bidirectionnel mixte (bidi) et diverses langues, tout en fournissant les mêmes résultats avec Canvas, SVG, WebGL et le rendu côté serveur
- Il s’agit d’un moteur de texte haute performance utilisable pour implémenter des mises en page UI précises comme le scroll virtualisé, la validation du débordement de texte et le placement de texte flottant
Vue d’ensemble
- Pretext est une bibliothèque JavaScript/TypeScript pure dédiée à la mesure et à la mise en page de texte multiligne, compatible avec le DOM, Canvas, SVG et même le rendu côté serveur
- Elle n’utilise pas les API de mesure du DOM (
getBoundingClientRect, offsetHeight, etc.), ce qui supprime le coût de reflow de mise en page
- Elle offre précision et rapidité grâce à une logique de mesure interne fondée sur le moteur de polices du navigateur
- Elle prend en charge toutes les langues, les emoji et le texte bidirectionnel mixte (bidi), tout en gérant les différences entre navigateurs
Installation et démos
Fonctionnalités principales
- Pretext propose deux grands modes d’utilisation
-
1. Mesure de la hauteur d’un paragraphe sans accès au DOM
prepare() prétraite le texte, normalise les espaces, sépare les segments, applique les règles de glue et effectue une mesure basée sur canvas pour renvoyer un handle opaque
layout() utilise les largeurs mises en cache pour calculer la hauteur et le nombre de lignes par opérations arithmétiques pures
- Pour un même texte et une même configuration, il n’est pas nécessaire de rappeler
prepare() ; lors d’un redimensionnement, seul layout() doit être relancé
- L’option
{ whiteSpace: 'pre-wrap' } permet de conserver tels quels les espaces, tabulations (\t) et retours à la ligne (\n)
- Résultats de benchmark :
prepare() environ 19 ms (sur 500 textes), layout() environ 0,09 ms
- La hauteur renvoyée peut être utilisée pour des fonctionnalités UI telles que :
- calcul précis des hauteurs dans la virtualisation et la gestion d’occlusion
- systèmes de mise en page pilotés en JS (par ex. masonry ou structures de type flexbox)
- validation du débordement de texte basée sur l’IA
- maintien de la position de défilement lors du chargement du texte
-
2. Construction manuelle de la mise en page d’un paragraphe
prepareWithSegments() génère des données au niveau des segments
layoutWithLines() renvoie, pour une largeur fixe, le texte de chaque ligne ainsi que ses informations de largeur
walkLineRanges() parcourt la largeur et la plage de curseurs de chaque ligne sans construire la chaîne de texte
- Exemple : il est possible de faire un ajustement de mise en page par recherche binaire en testant plusieurs largeurs afin de trouver un nombre de lignes et une hauteur appropriés
layoutNextLine() effectue la mise en page ligne par ligne lorsque la largeur varie d’une ligne à l’autre
- Exemple : placement de texte flottant pour faire couler le texte autour d’une image
- Cette approche s’applique de la même manière à Canvas, SVG, WebGL et au rendu côté serveur
Résumé de l’API
-
API de mesure de base
prepare(text, font, options?) : analyse et mesure le texte, puis renvoie un handle à transmettre à layout()
layout(prepared, maxWidth, lineHeight) : calcule la hauteur du texte et le nombre de lignes selon la largeur et la hauteur de ligne données
-
API de mise en page manuelle
prepareWithSegments(text, font, options?) : renvoie des données au niveau des segments
layoutWithLines(prepared, maxWidth, lineHeight) : inclut le texte, la largeur et les informations de curseur pour chaque ligne
walkLineRanges(prepared, maxWidth, onLine) : transmet via callback la largeur et la plage de curseurs de chaque ligne
layoutNextLine(prepared, start, maxWidth) : effectue la mise en page sous forme d’itérateur ligne par ligne
- Inclut les définitions de types
LayoutLine, LayoutLineRange, LayoutCursor
-
Autres utilitaires
clearCache() : réinitialise le cache interne
setLocale(locale?) : définit la locale et réinitialise le cache (sans effet sur l’état existant)
Limitations et points d’attention
- Pretext n’est pas un moteur de rendu de polices complet
- Propriétés CSS ciblées par défaut :
white-space: normal
word-break: normal
overflow-wrap: break-word
line-break: auto
- Avec
{ whiteSpace: 'pre-wrap' }, les espaces, tabulations et retours à la ligne sont conservés, avec application de tab-size: 8
- Sur macOS, la police
system-ui n’est pas adaptée à la précision de layout() ; il est donc recommandé d’utiliser un nom de police explicite
- En raison de
overflow-wrap: break-word, un retour à la ligne à l’intérieur d’un mot est possible sur des largeurs très étroites, mais la coupure se fait uniquement au niveau des graphèmes
Développement
- Pour l’environnement de développement et les commandes, voir
DEVELOPMENT.md
Contribution et contexte
- Reprend les idées du projet text-layout de Sebastian Markbage
- L’architecture a été développée à partir du shaping basé sur canvas
measureText, du traitement bidi de pdf.js et d’une conception de retour à la ligne en streaming
1 commentaires
Réactions sur Hacker News
Ce projet est vraiment impressionnant
Il résout le problème du calcul efficace de la hauteur d’un texte multiligne sans réellement le rendre dans la page web
Il met en cache la largeur et la hauteur de segments découpés mot par mot, puis implémente directement l’algorithme de retour à la ligne du navigateur
C’est un travail très difficile à cause de la gestion des césures, des emojis, du chinois et des différences de rendu entre navigateurs, Safari compris
Il utilise le jeu de données corpora et la page de test de précision pour comparer avec de vrais navigateurs
Sur du texte ASCII, mon code prend 80 ms, contre 2200 ms pour pretext
Je n’ai pas encore testé la précision, mais je compte le faire ce soir
Des PR d’amélioration des performances sont déjà ouvertes dans l’issue #18
J’en ai moi-même bavé autrefois en essayant de rendre du texte multiligne dans un canvas
Ce projet est bien plus utile parce qu’il est directement connecté au DOM
Exemple : démo Scrawl
Cela peut être plus lent qu’une API native, et rien ne garantit qu’il utilise exactement la même logique que le rendu non-canvas du navigateur
La méthode consiste à rendre dans un canvas puis à mesurer, et cela reste surtout une API d’analyse de la mise en page du texte
C’est vraiment une fonctionnalité attendue depuis très longtemps
Depuis des années, il était difficile d’implémenter correctement des choses comme des accordéons responsives
L’évolution du web a toujours suivi ce schéma : ① besoin complexe → ② hacks JS/CSS → ③ standardisation
Cette fois, j’ai l’impression qu’on est dans une vraie étape 2, pas un simple hack
Si on lit RESEARCH.md, l’étude va jusqu’aux différences de mesure des emojis selon les navigateurs
La maintenance sera sans doute difficile, mais cela pourrait être un grand tournant pour l’évolution du web
Ce qui est intéressant cette fois, c’est que l’IA a été activement utilisée dans le développement. Il semble que la majeure partie ait été réalisée avec l’agent Cursor
Selon l’auteur de la bibliothèque, Claude Code et Codex ont reçu des données de référence ground truth du navigateur pour effectuer des mesures itératives sur plusieurs semaines
Voir ce tweet
Autoresearch semble aussi avoir été utilisé en partie
J’ai particulièrement aimé l’exemple de reflow basé sur des formes
J’aurais voulu l’appliquer à Ensō (enso.sonnet.io), mais je me suis retenu pour préserver la simplicité
L’exemple d’accordéon peut aussi être implémenté avec le
interpolate-sizede CSSVoir l’article de Josh Comeau
L’exemple des bulles de texte peut être reproduit de manière similaire avec
text-wrap: balance | prettybalanceouprettyne constituent pas une solution complèteSouvent, on ne veut pas uniformiser la longueur des lignes
Issue CSSWG associée : #191
text-wrapaide à équilibrer le nombre de mots par ligne, mais le problème de la marge droite demeurepretext n’utilise pas directement canvas.measureText : on lui passe le texte et ses attributs via une API JS, et il calcule automatiquement la mise en page
Avant, il fallait soit utiliser measureText à la main, soit porter harfbuzz dans le navigateur
Cela ressemble moins à une percée technique qu’à une bonne combinaison d’éléments existants
Je me demande quand même quelle est la différence avec Skia-wasm / Canvaskit
Ce qui distingue pretext, c’est l’implémentation du rendu de glyphes en pur TypeScript avec l’aide de l’IA
Cela fait penser à la différence entre implémenter ffmpeg directement en C et l’appeler depuis Dart
Ce genre d’approche montre de nouvelles possibilités pour le FOSS côté client
L’an dernier, j’ai construit en HTML un système de composition pour brochures imprimées, et j’ai dû recalculer en boucle les limites des boîtes via la Selection API pour gérer les retours à la ligne et éviter les lignes orphelines
Ça fonctionne encore bien aujourd’hui, mais il y a un hack off-by-one dont j’ignore toujours la raison
La génération itérative de lignes de pretext est vraiment une fonctionnalité bienvenue
Sous Fedora + Firefox, toutes les démos apparaissent cassées
Exemple : capture d’écran
Une fonctionnalité comme celle-ci devrait en réalité être fournie comme API standard du navigateur
Je me demande comment soumettre une demande de fonctionnalité au W3C, et s’il existe un système de vote communautaire ou autre
Mais les éditeurs de navigateurs n’en font pas une priorité
En ce moment, Chrome semble davantage concentré sur les API liées à l’IA
Le moteur Sciter possède déjà la fonctionnalité Graphics.Text
C’est un élément de rendu de texte basé sur canvas auquel on peut appliquer directement les styles CSS
La recherche de texte du navigateur (
Ctrl+F) ne fonctionne pas correctement dans les listes à défilement virtuelPour résoudre ce problème, il faudra peut-être une nouvelle API de type « Search » plutôt qu’une solution en JS
Projets associés : Display Locking, documentation MDN
Les éléments hors écran d’une liste virtualisée n’étant pas dans le DOM, ils sont introuvables
Pour résoudre cela, il faudrait un nouveau contrat navigateur intégrant la sélection, le focus, la position de défilement et la navigation entre correspondances
Mais il est peu probable que les sites l’utilisent de manière cohérente