pretext について
pretextは、キャッシュしたフォントメトリクスを使い、DOMのリフローを起こさずに複数行テキストを計測・レイアウトするJavaScript/TypeScriptライブラリです。行や高さの計算が必要なレンダリングやサーバーサイド処理に向いています。
主な機能
- 01複数行の高さと行数の計測
- 02行単位のレイアウトとセグメント参照
- 03シュリンクラップ幅の計算
- 04空白、改行、文字間隔の設定
- 05DOM、Canvas、SVG、SSRへの出力
活用シーン
- 01DOM計測なしで段落の高さを算出
- 02浮動画像を回り込むテキストの配置
- 03多言語テキストや絵文字の描画
- 04高負荷な処理ループでのリフロー回避
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ akillness/ jeo-skills --skill pretextインストール後、Claude Code で/pretextを入力して読み込まれたか確認します。
同梱ファイルSKILL.toon
詳細
pretext — 高速な複数行テキストの測定とレイアウト
DOM のリフローは発生しません。キャッシュ済みのフォントメトリクスに基づく純粋な算術処理です。
pretext は、getBoundingClientRect、offsetHeight、その他の DOM リフロー処理を発生させずに、高速かつ正確に、複数行テキストを包括的に測定してレイアウトする、純粋な JavaScript/TypeScript ライブラリです。
インストール
プラグイン(Claude Code)
claude plugin marketplace add chenglou/pretext
npm
npm install @chenglou/pretext
スキル(任意のプラットフォーム)
npx skills add https://github.com/akillness/jeo-skills --skill pretext
使用する場面
- DOM で測定せずに、段落の高さと行数を計算する
- 浮動画像や可変幅コンテナの周囲にテキストを流し込むため、手動で行レイアウトを構築する
- 絵文字、CJK 文字、RTL テキスト、多言語の混在コンテンツを含むリッチテキストを扱う
- 正確なメトリクスで、測定済みテキストをDOM、Canvas、SVGに描画する
- ソフトハイフネーション、文字間隔、ロケールに応じた単語分割を実装する
- パフォーマンスが重要な描画ループでレイアウトのリフローを回避する
- サーバーサイドでテキストを測定する(SSR に対応)
使用しない場面
- CSS のテキストエンジンと完全に同じ動作が必要な場合 → DOM の測定を直接使用してください
- OpenType の機能(合字、カーニングテーブル)への対応が必要な場合 → 専用のフォントシェーピングライブラリを使用してください
- PDF のテキストレイアウトが必要な場合 → PDF 生成ライブラリを使用してください
- 対象環境が
Intl.Segmenterと Canvas 2D に対応していない場合
コア API
ユースケース 1:高さの測定
prepare() の呼び出し後は DOM に触れず、純粋な算術処理で段落の高さと行数を計算します。
import { prepare, layout } from '@chenglou/pretext'
// One-time analysis + font measurement (cached)
const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter')
// Pure arithmetic — no DOM interaction
const { height, lineCount } = layout(prepared, 320, 20)
// width: 320px container, lineHeight: 20px
ユースケース 2:手動での行レイアウト
カスタム描画(例:浮動画像の周囲にテキストを配置する場合)のために、個々の行にアクセスします。
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const prepared = prepareWithSegments('Text here', '18px "Helvetica Neue"')
const { lines } = layoutWithLines(prepared, 320, 26)
for (const line of lines) {
// line.text, line.y, line.width, line.segments
}
ユースケース 3:幅をテキストに合わせる
テキストが折り返されない最小のコンテナ幅を求めます。
import { prepare, shrinkWrap } from '@chenglou/pretext'
const prepared = prepare('Hello world', '14px system-ui')
const { width } = shrinkWrap(prepared, 26)
オプションのリファレンス
| オプション | 型 | 値 | 説明 |
|---|---|---|---|
whiteSpace | 文字列 | 'normal' | 'pre-wrap' | 'pre-wrap' は空白を保持します(textarea 形式) |
wordBreak | 文字列 | 'normal' | 'keep-all' | 'keep-all' は CJK テキストの単語途中での改行を防ぎます |
letterSpacing | 数値 | ピクセル | CSS の letter-spacing に相当 |
const prepared = prepare(text, font, {
whiteSpace: 'pre-wrap',
wordBreak: 'keep-all',
letterSpacing: 1.5,
})
要件
Intl.SegmenterAPI(ロケールに応じた単語/書記素の分割)- Canvas 2D のテキスト測定(
CanvasRenderingContext2D.measureText) - 最新のブラウザーと Node.js 16+ で動作します(Node では canvas パッケージが必要です)
出力先
| 出力先 | 備考 |
|---|---|
| DOM | lines と絶対位置指定、または transform: translateY を使用します |
| Canvas | lines[i].y と ctx.fillText を使用します |
| SVG | lines[i].y と <text y=...> 要素を使用します |
| SSR | 高さはサーバー側で計算し、位置はクライアント側でハイドレーションします |
運用ルール
- 固有の(テキスト、フォント)ペアごとに
prepare()/prepareWithSegments()を一度呼び出します。ここが処理コストの高いステップです - 同じテキストを異なる幅で測定する場合は、
preparedオブジェクトをキャッシュします layout()/layoutWithLines()は副作用のない純粋な算術処理なので、自由に呼び出せます- ブラウザー以外の環境で初期化する前に、必ず
Intl.Segmenterが利用可能か確認します - 固定幅のコンテナで測定する前に、
shrinkWrapを使って最小幅を求めます - リッチな描画のために行ごとのセグメントへアクセスする必要がある場合は、
prepareよりprepareWithSegmentsを優先します
使用例
import { prepare, layout, prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
// Basic height check
const p = prepare('Hello, 世界! 🌏', '16px sans-serif')
console.log(layout(p, 200, 20)) // { height: 20, lineCount: 1 }
// Variable-width layout (text around floated image)
const ps = prepareWithSegments('Long article text...', '16px Georgia')
const { lines } = layoutWithLines(ps, (lineIndex) => {
return lineIndex < 5 ? 200 : 320 // narrower for first 5 lines
}, 24)
出典:chenglou/pretext — MIT ライセンス
コメント
Markdown 対応