4 ポイント 投稿者 GN⁺ 2026-03-30 | 1件のコメント | WhatsAppで共有
  • Pretextは、DOM へアクセスせずに複数行テキストの高さと行配置を計算する純粋な JavaScript/TypeScript ライブラリで、ブラウザとサーバー環境の両方をサポート
  • getBoundingClientRect のような DOM 計測 API を使わないため、レイアウトのリフローコストを排除し、フォントエンジンベースの独自計測ロジックで精度を確保
  • prepare() / layout() API によりテキストを前処理し、キャッシュされた幅データを使って純粋な算術演算で高速な高さ計算を実行
  • 絵文字、双方向テキスト(bidi)、さまざまな言語をサポートし、Canvas・SVG・WebGL・サーバーレンダリングでも同一の結果を提供
  • 仮想化スクロール、テキストオーバーフロー検証、フローティングテキスト配置など、精密な UI レイアウト実装に活用できる高性能テキストエンジン

概要

  • Pretextは、複数行テキストの計測とレイアウトのための純粋な JavaScript/TypeScript ライブラリで、DOM、Canvas、SVG、さらにサーバーサイドレンダリングまで対応
  • DOM 計測 APIgetBoundingClientRectoffsetHeight など)を使わないため、レイアウトのリフローコストを排除
  • ブラウザのフォントエンジンを基準にした独自計測ロジックにより、正確かつ高速な性能を提供
  • **あらゆる言語、絵文字、双方向テキスト(bidi)**をサポートし、ブラウザごとの差異にも対応

インストールとデモ

主な機能

  • Pretext は 2 つの主要な利用方法を提供
  • 1. DOM アクセスなしで段落の高さを計測

    • prepare() はテキストを前処理し、空白の正規化・セグメント分離・glue ルール適用・canvas ベースの計測を行って、**不透明ハンドル(opaque handle)**を返す
    • layout() はキャッシュ済みの幅データを使い、純粋な算術演算で高さと行数を計算
    • 同じテキストと設定では prepare() を繰り返し呼び出す必要はなく、リサイズ時には layout() のみ再実行
    • { whiteSpace: 'pre-wrap' } オプションで空白、タブ(\t)、改行(\n)をそのまま保持
    • ベンチマーク結果: prepare() は約 19ms(500 テキスト基準)、layout() は約 0.09ms
    • 返された高さの値は、次のような UI 機能に活用可能
      • 仮想化およびオクルージョン処理での正確な高さ計算
      • JS ベースのレイアウトシステム(例: masonry、flexbox 類似構造)
      • AI ベースのテキストオーバーフロー検証
      • テキスト読み込み時のスクロール位置維持
  • 2. 手動で段落レイアウトを構成

    • prepareWithSegments() でセグメント単位のデータを生成
    • layoutWithLines() は固定幅で各行のテキストと幅情報を返す
    • walkLineRanges() はテキスト文字列を作らずに、各行の幅とカーソル範囲を走査
      • 例: 複数の幅を試して適切な行数と高さを見つける二分探索型レイアウト調整が可能
    • layoutNextLine()行ごとに幅が異なる場合に、1 行ずつ順次レイアウト
      • 例: 画像の周囲にテキストを回り込ませるフローティングテキスト配置
    • この方式はCanvas、SVG、WebGL、サーバーサイドレンダリングにも同様に適用可能

API 要約

  • 基本計測用 API

    • prepare(text, font, options?): テキストを解析・計測し、layout() に渡すハンドルを返す
    • layout(prepared, maxWidth, lineHeight): 与えられた幅と行高に応じたテキストの高さと行数を計算
  • 手動レイアウト用 API

    • prepareWithSegments(text, font, options?): セグメント単位のデータを返す
    • layoutWithLines(prepared, maxWidth, lineHeight): 各行のテキスト、幅、カーソル情報を含む
    • walkLineRanges(prepared, maxWidth, onLine): 各行の幅とカーソル範囲をコールバックで渡す
    • layoutNextLine(prepared, start, maxWidth): 行単位イテレーター形式でレイアウトを実行
    • LayoutLine, LayoutLineRange, LayoutCursor の型定義を含む
  • その他のユーティリティ

    • clearCache(): 内部キャッシュを初期化
    • setLocale(locale?): ロケールを設定してキャッシュを初期化(既存状態には影響なし)

制約と注意事項

  • Pretext は完全なフォントレンダリングエンジンではない
  • 基本対象の CSS プロパティ
    • white-space: normal
    • word-break: normal
    • overflow-wrap: break-word
    • line-break: auto
  • { whiteSpace: 'pre-wrap' } 使用時は空白、タブ、改行を保持し、tab-size: 8 を適用
  • macOS で system-ui フォントは layout() の精度に不向きなため、明示的なフォント名の使用を推奨
  • overflow-wrap: break-word により、非常に狭い幅では単語内部でも改行される可能性があるが、**文字単位(grapheme)**でのみ分割

開発関連

  • 開発環境およびコマンドは DEVELOPMENT.md を参照

貢献と背景

  • Sebastian Markbagetext-layout プロジェクトのアイデアを継承
  • canvas measureText ベースのシェーピングpdf.js の bidi 処理ストリーミング改行処理の設計を受け継いで発展させた構造

1件のコメント

 
GN⁺ 2026-03-30
Hacker News の意見
  • このプロジェクトは本当に 印象的 Web ページで実際にテキストをレンダリングしなくても、改行されたテキストの高さを効率的に計算する問題を解決している 単語単位に分割されたセグメントの 幅と高さをキャッシュ し、ブラウザの改行アルゴリズムを自前で実装している ハイフン、絵文字、中国語など多様な文字処理や、ブラウザごとのレンダリング差異(Safari を含む)のため非常に難しい作業だ 実際のブラウザとの比較テストには corpora データセットaccuracy テストページ を使っている

    • 自分も似たものを作ったことがある。uWrap.js という 2KB のシンプル版で、AI なしで実装した ASCII テキスト基準では自分のコードは 80ms、pretext は 2200ms かかる 精度はまだテストしていないが、今夜試す予定 Issue #18 には性能改善の PR がすでにいくつか出ている
    • テキストレイアウトエンジンは本当に 悪名高く難しい 自分も以前 canvas でマルチラインテキストをレンダリングしようとして苦労した このプロジェクトは DOM と直接つながっているので、ずっと実用的だ 例: Scrawl デモ
    • 説明を見る限り、実際にはセグメントを canvas にレンダリングして測定 する方式のようだ ネイティブ API より遅い可能性があり、ブラウザの非 canvas レンダリングと同じロジックを使っている保証もない
    • 実質的にはブラウザの テキストレンダリングアルゴリズムを自前で実装したわけではない canvas にレンダリングしてから測定する方式で、単にテキストレイアウト解析用の API を提供している程度だ
    • Remotion の動画向け 動的字幕 を作るとき、テキスト高さの計算で苦労したので、このライブラリはかなり役立ちそう
  • これは本当に 長い間待ち望まれていた機能 以前からレスポンシブなアコーディオンのようなものをきちんと実装するのは難しかった Web の発展パターンはいつも ①複雑な要求の登場 → ②JS/CSS ハック → ③標準化 だった 今回はハックではなく、きちんとした第 2 段階だと思う RESEARCH.md を見ると、ブラウザごとの絵文字測定の違いまで細かく研究している 保守は大変だろうが、Web の発展における大きな 転換点 になりそう

    • レスポンシブなアコーディオンは今では CSS で可能だが、それでもこうした API は必要だった 今回は AI が開発プロセスで積極活用 された点が興味深い。Cursor エージェントを使って大半が実装されたようだ
  • ライブラリ作者によれば、Claude Code と Codex にブラウザの ground truth データを学習させ、数週間にわたって反復測定したとのこと 関連ツイート 参照 Autoresearch も一部使われたようだ

  • shape ベースのリフローの例が特に気に入った Ensō(enso.sonnet.io) に適用してみたかったが、シンプルさを保つために踏みとどまった アコーディオンの例 は CSS の interpolate-size でも実装できる Josh Comeau の記事 参照 テキストバブルの例text-wrap: balance | pretty で似たように実装できる

    • ただし balancepretty では完全な解決にはならない 行長を均等にしたくない場合も多い 関連する CSSWG Issue: #191
    • text-wrap は 1 行あたりの単語数をそろえる助けにはなるが、右側の余白の問題は依然として残る
  • pretext は canvas.measureText を直接使うのではなく、テキストと属性を JS API に渡すと自動でレイアウトを計算してくれる 以前は measureText を直接使うか、harfbuzz をブラウザへ持ち込む必要があった 技術的ブレークスルーというより、既存要素をうまく組み合わせた結果のように見える ただ Skia-wasm / Canvaskit との違いは気になる

    • 違いはシンプルさだ。pretext は wasm ではない
    • Skia は巨大なレンダリングエンジンだ。Flutter のように デバイス非依存のレンダリング API を提供する pretext は AI によって純粋な Typescript ベースのグリフレンダリングを実装した点が異なる ffmpeg を C で直接実装するのと、Dart から呼び出すのとの違いのように感じる こうした試みは クライアントサイド FOSS の新しい可能性 を示している
    • もし作者の言うことが正しいなら、これは Web GUI フレームワークとリッチテキストエディタ に大きな変化をもたらすだろう
  • 去年、HTML で印刷用パンフレットの組版システムを作ったが、改行とウィドウ・オーファン防止のために Selection API でボックス境界を繰り返し計算していた 今でもうまく動いているが、理由の分からない off-by-one ハックがある pretext の 反復的な行生成機能 は本当にうれしい機能だ

  • Fedora + Firefox 環境ではデモがすべて壊れて見える 例: スクリーンショット

    • こういう種類のプロジェクトは結局 終わりのないエッジケース追跡 の連続だと分かる
  • こうした機能は本来 ブラウザ標準 API として提供 されるべきだ W3C に機能要望を出すにはどうすればいいのか、コミュニティ投票のようなものが可能なのか気になる

    • すでに Font Metrics API 提案 が存在する だがブラウザベンダーは優先順位を置いていない 現在の Chrome は AI 関連 API により注力している
  • Sciter エンジンにはすでに Graphics.Text 機能がある CSS スタイルをそのまま適用できる canvas ベースのテキストレンダリング要素

  • ブラウザの テキスト検索(Ctrl+F) は仮想スクロールリストでは 正常に 動作しない この問題を解決するには、JS ではなく新しい "Search" API が必要かもしれない

    • Virtual Scroller API はあったが、ほとんど進展していない 関連プロジェクト: Display Locking, MDN 文書
    • ネイティブ検索は DOM に存在するノードだけを探索する 仮想化されたリストのオフスクリーン項目は DOM にないため検索できない これを解決するには、選択、フォーカス、スクロール位置、マッチ探索 まで統合された新しいブラウザ契約が必要だ だがサイト側が一貫して利用する可能性は低い