4 points par GN⁺ 2026-03-30 | 1 commentaires | Partager sur WhatsApp
  • 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

 
GN⁺ 2026-03-30
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

    • J’ai déjà fait quelque chose de similaire. Une version simple de 2 Ko appelée uWrap.js, réalisée sans IA
      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
    • Les moteurs de mise en page du texte sont réputés extrêmement difficiles
      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
    • D’après l’explication, il semble qu’en réalité il rende les segments dans un canvas pour les mesurer
      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
    • En pratique, ce n’est pas vraiment une réimplémentation directe de l’algorithme de rendu de texte 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
    • J’ai galéré à cause du calcul de la hauteur du texte en créant des sous-titres dynamiques pour des vidéos Remotion, et cette bibliothèque pourrait beaucoup aider
  • 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

    • Les accordéons responsives sont désormais possibles en CSS, mais on avait quand même besoin d’une API comme celle-ci
      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-size de CSS
    Voir l’article de Josh Comeau
    L’exemple des bulles de texte peut être reproduit de manière similaire avec text-wrap: balance | pretty

    • Mais balance ou pretty ne constituent pas une solution complète
      Souvent, on ne veut pas uniformiser la longueur des lignes
      Issue CSSWG associée : #191
    • text-wrap aide à équilibrer le nombre de mots par ligne, mais le problème de la marge droite demeure
  • pretext 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

    • La différence, c’est la simplicité. pretext n’est pas en wasm
    • Skia est un énorme moteur de rendu. Il fournit une API de rendu indépendante de l’appareil, comme Flutter
      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
    • Si l’auteur a raison, cela pourrait provoquer de grands changements pour les frameworks GUI web et les éditeurs de texte enrichi
  • 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

    • Cela montre bien que ce genre de projet finit par devenir une traque sans fin des cas limites
  • 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

    • Il existe déjà une proposition Font Metrics API
      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 virtuel
    Pour résoudre ce problème, il faudra peut-être une nouvelle API de type « Search » plutôt qu’une solution en JS

    • Il y a bien eu une Virtual Scroller API, mais elle n’a presque pas avancé
      Projets associés : Display Locking, documentation MDN
    • La recherche native n’explore que les nœuds présents dans le DOM
      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