4 Punkte von GN⁺ 2026-03-30 | 1 Kommentare | Auf WhatsApp teilen
  • Pretext ist eine reine JavaScript/TypeScript-Bibliothek, die Höhe und Zeilenumbruch von mehrzeiligem Text ohne DOM-Zugriff berechnet und sowohl Browser- als auch Server-Umgebungen unterstützt
  • Da keine DOM-Mess-APIs wie getBoundingClientRect verwendet werden, entfallen die Kosten für Layout-Reflow, während eine eigene, auf der Font-Engine basierende Messlogik für Genauigkeit sorgt
  • Über die APIs prepare() / layout() wird Text vorverarbeitet, und mithilfe zwischengespeicherter Breitenwerte erfolgt eine schnelle Höhenberechnung durch reine arithmetische Operationen
  • Unterstützt Emoji, bidirektionalen Text (bidi) und verschiedene Sprachen und liefert in Canvas, SVG, WebGL und Server-Rendering dieselben Ergebnisse
  • Eine hochperformante Text-Engine, die sich für präzise UI-Layouts wie virtualisiertes Scrollen, Prüfung von Text-Overflow und Floating-Text-Layout eignet

Überblick

  • Pretext ist eine reine JavaScript/TypeScript-Bibliothek für die Messung und das Layout von mehrzeiligem Text und unterstützt DOM, Canvas, SVG sowie Server-Side Rendering
  • Es verwendet keine DOM-Mess-APIs (getBoundingClientRect, offsetHeight usw.) und eliminiert damit die Kosten von Layout-Reflows
  • Durch eine eigene Messlogik auf Basis der Font-Engine des Browsers bietet es präzise und schnelle Performance
  • Unterstützt alle Sprachen, Emoji und bidirektionalen Text (bidi) und berücksichtigt auch Unterschiede zwischen Browsern

Installation und Demos

Hauptfunktionen

  • Pretext bietet zwei zentrale Nutzungsarten
  • 1. Absatzhöhe ohne DOM-Zugriff messen

    • prepare() verarbeitet Text vor, normalisiert Leerraum, trennt Segmente, wendet Glue-Regeln an und führt canvas-basierte Messungen durch; zurückgegeben wird ein opaker Handle (opaque handle)
    • layout() verwendet zwischengespeicherte Breitenwerte, um Höhe und Zeilenanzahl durch reine arithmetische Operationen zu berechnen
    • Bei identischem Text und identischen Einstellungen muss prepare() nicht wiederholt aufgerufen werden; bei Größenänderungen wird nur layout() erneut ausgeführt
    • Mit der Option { whiteSpace: 'pre-wrap' } bleiben Leerzeichen, Tabs(\t) und Zeilenumbrüche(\n) unverändert erhalten
    • Benchmark-Ergebnisse: prepare() ca. 19 ms (bei 500 Texten), layout() ca. 0,09 ms
    • Die zurückgegebenen Höhenwerte können für folgende UI-Funktionen genutzt werden
      • Exakte Höhenberechnung bei Virtualisierung und Occlusion Culling
      • JS-basierte Layout-Systeme (z. B. Masonry oder flexbox-ähnliche Strukturen)
      • KI-basierte Prüfung von Text-Overflow
      • Beibehaltung der Scroll-Position beim Nachladen von Text
  • 2. Manuelles Absatz-Layout aufbauen

    • Mit prepareWithSegments() werden Daten auf Segmentebene erzeugt
    • layoutWithLines() gibt bei fester Breite den Text und die Breiteninformationen jeder Zeile zurück
    • walkLineRanges() durchläuft Breite und Cursor-Bereiche jeder Zeile, ohne Textstrings zu erzeugen
      • Beispiel: binäre Layout-Anpassung per Suche, um bei mehreren Breiten eine passende Zeilenanzahl und Höhe zu finden
    • layoutNextLine() führt das Layout zeilenweise nacheinander aus, wenn jede Zeile eine andere Breite hat
      • Beispiel: Floating-Text-Layout, bei dem Text um ein Bild herumfließt
    • Dieser Ansatz lässt sich gleichermaßen in Canvas, SVG, WebGL und Server-Side Rendering einsetzen

API-Zusammenfassung

  • APIs für die grundlegende Messung

    • prepare(text, font, options?): analysiert und misst Text und gibt einen Handle für layout() zurück
    • layout(prepared, maxWidth, lineHeight): berechnet Texthöhe und Zeilenanzahl für gegebene Breite und Zeilenhöhe
  • APIs für manuelles Layout

    • prepareWithSegments(text, font, options?): gibt Daten auf Segmentebene zurück
    • layoutWithLines(prepared, maxWidth, lineHeight): enthält Text, Breite und Cursor-Informationen jeder Zeile
    • walkLineRanges(prepared, maxWidth, onLine): übergibt Breite und Cursor-Bereich jeder Zeile an einen Callback
    • layoutNextLine(prepared, start, maxWidth): führt das Layout in Form eines zeilenbasierten Iterators aus
    • Typdefinitionen für LayoutLine, LayoutLineRange, LayoutCursor sind enthalten
  • Weitere Utilities

    • clearCache(): internen Cache leeren
    • setLocale(locale?): Locale setzen und Cache leeren (hat keinen Einfluss auf bereits bestehenden Zustand)

Einschränkungen und Hinweise

  • Pretext ist keine vollständige Font-Rendering-Engine
  • Standardmäßig anvisierte CSS-Eigenschaften
    • white-space: normal
    • word-break: normal
    • overflow-wrap: break-word
    • line-break: auto
  • Bei Verwendung von { whiteSpace: 'pre-wrap' } bleiben Leerzeichen, Tabs und Zeilenumbrüche erhalten; dabei gilt tab-size: 8
  • Unter macOS ist die Schriftart system-ui für die Genauigkeit von layout() ungeeignet; daher wird die Verwendung eines expliziten Font-Namens empfohlen
  • Wegen overflow-wrap: break-word sind bei sehr schmalen Breiten auch Umbrüche innerhalb von Wörtern möglich, allerdings nur auf Ebene von Graphemen

Entwicklung

  • Hinweise zur Entwicklungsumgebung und zu Befehlen stehen in DEVELOPMENT.md

Beiträge und Hintergrund

  • Baut auf Ideen aus dem Projekt text-layout von Sebastian Markbage auf
  • Übernimmt und entwickelt die Architektur von canvas-measureText-basiertem Shaping, bidi-Verarbeitung aus pdf.js und streamingbasiertem Line Breaking weiter

1 Kommentare

 
GN⁺ 2026-03-30
Hacker-News-Kommentare
  • Dieses Projekt ist wirklich beeindruckend
    Es löst das Problem, die Höhe von umgebrochenem Text effizient zu berechnen, ohne den Text tatsächlich auf der Webseite zu rendern
    Es cached Breite und Höhe von in Wortsegmente aufgeteilten Abschnitten und implementiert den Zeilenumbruch-Algorithmus des Browsers selbst
    Wegen Silbentrennung, Emojis, Chinesisch und unterschiedlicher Browser-Rendering-Unterschiede (einschließlich Safari) ist das eine sehr schwierige Aufgabe
    Für Vergleichstests mit echten Browsern werden das corpora-Dataset und die accuracy-Testseite verwendet

    • Ich habe auch einmal etwas Ähnliches gebaut. Eine einfache 2KB-Version namens uWrap.js, implementiert ohne AI
      Bei ASCII-Text braucht mein Code 80ms, pretext 2200ms
      Die Genauigkeit habe ich noch nicht getestet, aber das will ich heute Nacht tun
      Unter Issue #18 gibt es bereits offene PRs zur Performance-Verbesserung
    • Text-Layout-Engines sind wirklich berüchtigt schwierig
      Ich habe früher selbst damit gekämpft, mehrzeiligen Text auf einem Canvas zu rendern
      Dieses Projekt ist viel nützlicher, weil es direkt mit dem DOM verbunden ist
      Beispiel: Scrawl-Demo
    • Der Beschreibung nach scheint es in Wirklichkeit ein Ansatz zu sein, bei dem Segmente auf einem Canvas gerendert und vermessen werden
      Das könnte langsamer sein als native APIs, und es ist nicht garantiert, dass dabei dieselbe Logik wie beim Nicht-Canvas-Rendering des Browsers verwendet wird
    • Genau genommen wurde damit nicht wirklich der Text-Rendering-Algorithmus des Browsers nachimplementiert
      Es rendert auf Canvas und misst dann; letztlich ist es eher eine API zur Analyse von Text-Layout
    • Beim Erstellen dynamischer Untertitel für Remotion-Videos hatte ich große Mühe mit der Berechnung der Texthöhe; diese Bibliothek dürfte dabei sehr helfen
  • Das ist wirklich eine lange erwartete Funktion
    Schon lange war es schwierig, Dinge wie responsive Akkordeons sauber zu implementieren
    Das Muster der Web-Entwicklung war immer: ① komplexe Anforderungen tauchen auf → ② JS/CSS-Hacks → ③ Standardisierung
    Diesmal fühlt es sich nicht wie ein Hack an, sondern wie eine richtige Stufe 2
    In RESEARCH.md wurde sogar detailliert untersucht, wie sich die Emoji-Messung je nach Browser unterscheidet
    Die Wartung dürfte schwierig sein, aber es könnte ein großer Wendepunkt für die Weiterentwicklung des Webs werden

    • Responsive Akkordeons sind inzwischen mit CSS möglich, aber so eine API wurde trotzdem gebraucht
      Interessant ist diesmal, dass AI im Entwicklungsprozess aktiv genutzt wurde. Offenbar wurde der Großteil mit dem Cursor-Agenten umgesetzt
  • Laut dem Autor der Bibliothek haben Claude Code und Codex über mehrere Wochen hinweg wiederholt Messungen durchgeführt, nachdem sie mit Ground-Truth-Daten des Browsers gefüttert worden waren
    Siehe den zugehörigen Tweet
    Offenbar wurde auch teilweise Autoresearch verwendet

  • Das Beispiel für formbasierten Reflow hat mir besonders gut gefallen
    Ich wollte es bei Ensō (enso.sonnet.io) einsetzen, habe es aber gelassen, um die Einfachheit zu bewahren
    Das Akkordeon-Beispiel lässt sich auch mit CSS-interpolate-size umsetzen
    Siehe Josh Comeaus Artikel
    Das Textblasen-Beispiel lässt sich ähnlich mit text-wrap: balance | pretty umsetzen

    • Aber mit balance oder pretty ist das Problem nicht vollständig gelöst
      Oft will man gerade nicht, dass die Zeilenlängen gleichmäßig werden
      Relevantes CSSWG-Issue: #191
    • text-wrap hilft dabei, die Wortzahl pro Zeile anzugleichen, aber das Problem des rechten Leerraums bleibt bestehen
  • pretext verwendet canvas.measureText nicht direkt, sondern berechnet das Layout automatisch, wenn Text und Attribute an eine JS-API übergeben werden
    Früher musste man measureText direkt verwenden oder harfbuzz in den Browser portieren
    Es wirkt weniger wie ein technischer Durchbruch als vielmehr wie eine gute Kombination vorhandener Bausteine
    Trotzdem würde mich der Unterschied zu Skia-wasm / Canvaskit interessieren

    • Der Unterschied ist schlicht die Einfachheit. pretext ist kein wasm
    • Skia ist eine riesige Rendering-Engine. Es bietet wie Flutter eine geräteunabhängige Rendering-API
      pretext unterscheidet sich dadurch, dass per AI ein reines TypeScript-basiertes Glyphen-Rendering implementiert wurde
      Es fühlt sich an wie der Unterschied zwischen einer direkten ffmpeg-Implementierung in C und dem Aufruf aus Dart
      Solche Versuche zeigen neue Möglichkeiten für clientseitiges FOSS
    • Falls der Autor recht hat, wird das Web-GUI-Frameworks und Rich-Text-Editoren stark verändern
  • Letztes Jahr habe ich ein Satzsystem für Druckbroschüren in HTML gebaut und zur Vermeidung von Zeilenumbrüchen und Hurenkindern wiederholt Box-Grenzen über die Selection API berechnet
    Es funktioniert immer noch gut, aber es gibt einen off-by-one-Hack, dessen Ursache ich nicht kenne
    Die iterative Zeilenerzeugung von pretext ist dafür wirklich eine willkommene Funktion

  • Unter Fedora + Firefox sehen alle Demos kaputt aus
    Beispiel: Screenshot

    • Das zeigt, dass solche Projekte am Ende aus einem endlosen Hinterherjagen von Edge Cases bestehen
  • So eine Funktion sollte eigentlich als standardisierte Browser-API bereitgestellt werden
    Ich frage mich, wie man beim W3C eine Feature-Anfrage stellt und ob so etwas wie Community-Abstimmungen möglich ist

    • Es gibt bereits einen Vorschlag für die Font Metrics API
      Aber die Browser-Vendoren priorisieren das nicht
      Chrome konzentriert sich derzeit stärker auf AI-bezogene APIs
  • In der Sciter-Engine gibt es bereits die Funktion Graphics.Text
    Das ist ein Canvas-basiertes Text-Rendering-Element, auf das sich CSS-Stile direkt anwenden lassen

  • Die Textsuche im Browser (Strg+F) funktioniert in virtualisierten Scroll-Listen nicht richtig
    Um solche Probleme zu lösen, braucht es womöglich eine neue „Search“-API statt JavaScript

    • Es gab die Virtual Scroller API, aber es gibt kaum Fortschritte
      Verwandte Projekte: Display Locking, MDN-Dokumentation
    • Die native Suche durchsucht nur Knoten, die im DOM vorhanden sind
      Offscreen-Elemente in virtualisierten Listen existieren nicht im DOM und können daher nicht gefunden werden
      Um das zu lösen, bräuchte es einen neuen Browser-Vertrag, der Auswahl, Fokus, Scroll-Position und Match-Navigation integriert
      Allerdings ist es unwahrscheinlich, dass Websites das konsistent verwenden würden