- 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
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
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
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
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
Es rendert auf Canvas und misst dann; letztlich ist es eher eine API zur Analyse von Text-Layout
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
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-sizeumsetzenSiehe Josh Comeaus Artikel
Das Textblasen-Beispiel lässt sich ähnlich mit
text-wrap: balance | prettyumsetzenbalanceoderprettyist das Problem nicht vollständig gelöstOft will man gerade nicht, dass die Zeilenlängen gleichmäßig werden
Relevantes CSSWG-Issue: #191
text-wraphilft dabei, die Wortzahl pro Zeile anzugleichen, aber das Problem des rechten Leerraums bleibt bestehenpretext 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
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
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
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
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
Verwandte Projekte: Display Locking, MDN-Dokumentation
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