pretext について
pretextは、DOMの読み取りなしで複数行テキストを測定・配置するTypeScriptライブラリ兼クリエイティブスキルです。描画前に行や書記素の幾何情報を得て、ブラウザデモやUIに活用できます。
主な機能
- 01canvasによるテキストと行幅の測定
- 02DOM再レイアウトなしの複数行配置
- 03書記素ごとの位置情報
- 04行ごとに幅を変える配置
- 05rich-inline段落ヘルパー
- 06配置データをアニメーションやゲームに活用
活用シーン
- 01動く図形を避ける段落の配置
- 02テキスト形状を使ったゲームの制作
- 03個々の字形のアニメーションや飛散
- 04複数行UIの最小寸法の算出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nousresearch/ hermes-agent --skill pretextインストール後、Claude Code で/pretextを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/patterns.mdtemplates/donut-orbit.htmlhello-orb-flow.html詳細
Pretext のクリエイティブデモ
概要
@chenglou/pretext は、Cheng Lou(React コア、ReasonML、Midjourney)による、依存関係のない 15KB の TypeScript ライブラリで、DOM を使わない複数行テキストの測定とレイアウトを行います。機能は 1 つです。(text, font, width) を指定すると、改行位置、行ごとの幅、書記素ごとの位置、合計の高さを返します。すべてキャンバスで測定し、リフローは発生しません。
一見すると、地味な裏方仕事に思えるかもしれません。しかし、そうではありません。高速で幾何学的に扱えるため、クリエイティブな基本要素になります。60fps で動くスプライトの周囲に段落をリフローしたり、実際の単語でレベルの地形を作るゲームを構築したり、散文の中に ASCII ロゴを動かしたり、書記素ごとの正確な開始位置からテキストを粒子に砕いたり、getBoundingClientRect を何度も呼び出さずに、複数行の UI をぴったり収まる幅にしたりできます。
このスキルは、Hermes がこれを使ってクールなデモを作れるようにするものです。人々が X に投稿するようなデモです。コミュニティのデモ集は pretext.cool と chenglou.me/pretext をご覧ください。
使用する場面
次のような依頼に使用してください。
- 「pretext のデモ」/「クールな pretext のやつ」/「テキストを X にする」
- 動く図形の周囲を流れるテキスト(ヒーローセクション、エディトリアルレイアウト、アニメーション付きの長文ページ)
- 実際の単語や散文を使った ASCII アート風の効果。等幅フォントのラスター画像ではありません
- プレイフィールド/障害物/ブロックがテキストでできたゲーム(文字のテトリス、散文のブレイクアウト)
- 書体ごとの物理挙動を使ったキネティックタイポグラフィ(粉砕、散乱、群れ、流れ)
- タイポグラフィを使ったジェネレーティブアート。特に、非ラテン文字や複数の文字体系が混在する場合
- 複数行の「シュリンクラップ」UI(テキストが収まる最小のコンテナー幅)
- 描画前に改行位置を知る必要があるもの
次の用途には使用しないでください。
- CSS でレイアウトを解決できる静的な SVG/HTML ページ。CSS を使ってください
- リッチテキストエディターや汎用インライン書式エンジン(pretext は意図的に用途を絞っています)
- 画像 → テキスト(
ascii-art/ascii-videoスキルを使ってください) - テキストが関与しない純粋なキャンバスのジェネレーティブアート。
p5jsを使ってください
クリエイティブの基準
これはブラウザー上に描画するビジュアルアートです。Pretext が返すのは数値です。描画するのはあなたです。
- 「Hello World」デモを公開しないでください。
hello-orb-flow.htmlのテンプレートはあくまで出発点です。納品するデモにはすべて、意図的な色使い、動き、構図に加え、ユーザーが求めてはいないものの喜ばれるビジュアル上の工夫を 1 つ加えてください。 - 暗い背景、温かみのある中心、考え抜かれた配色。 定番の黒地にアンバー(CRT/ターミナル)もよいですが、チャコール地に冷たい白(エディトリアル)や、彩度を落としたパステル(リソグラフ)も使えます。どれかを選んだら、最後まで一貫させてください。
- プロポーショナルフォントこそがポイントです。 Pretext の持ち味は「等幅ではない」ことです。それを活かしてください。Iowan Old Style、Inter、JetBrains Mono、Helvetica Neue、またはバリアブルフォントを使ってください。デフォルトのサンセリフ体は使わないでください。
- 実際の出典テキストを使い、ダミーテキストは使わないでください。 コーパスには意味があるべきです。短いマニフェスト、詩、実際のソースコード、見つけた文章、ライブラリ自身の README などを使い、
lorem ipsumは使わないでください。 - 最初の描画から完成度を高くしてください。 読み込み中の状態や空白のフレームを表示しないでください。デモは開いた瞬間から公開できる品質である必要があります。
技術スタック
デモごとに、自己完結した HTML ファイルを 1 つ用意します。ビルド工程はありません。
| 層 | ツール | 目的 |
|---|---|---|
| コア | esm.sh CDN 経由の @chenglou/pretext | テキストの測定と行レイアウト |
| レンダリング | HTML5 Canvas 2D | 書記素の描画、フレームごとの合成 |
| 分割 | Intl.Segmenter(組み込み) | 絵文字/CJK/結合文字に対応した書記素分割 |
| インタラクション | 生の DOM イベント | マウス/タッチ/ホイール。フレームワークは不要 |
<script type="module">
import {
prepare, layout, // use-case 1: simple height
prepareWithSegments, layoutWithLines, // use-case 2a: fixed-width lines
layoutNextLineRange, materializeLineRange, // use-case 2b: streaming / variable width
measureLineStats, walkLineRanges, // stats without string allocation
} from "https://esm.sh/@chenglou/[email protected]";
</script>
バージョンを固定してください。執筆時点では @0.0.6 です。デモの動作がおかしい場合は、最新バージョンを npm で確認してください。
2 つのユースケース
ほとんどすべての用途は、この 2 つの形のどちらかに当てはまります。両方を覚えてください。
ユースケース 1 — 計測してから、CSS/DOM で描画する
const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);
テキストの描画は引き続きブラウザーに任せます。Pretext は DOM を読み取らずに、指定した幅でボックスがどれくらいの高さになるかを教えてくれます。次のような用途に使います。
- 折り返されるテキストを含む行を扱う仮想化リスト
- カードの高さを正確にそろえる Masonry
- 「このラベルは収まるか?」を開発時に確認する
- リモートのテキストの読み込み時にレイアウトシフトを防ぐ
font と letterSpacing は CSS と完全に同期させてください。 Canvas の ctx.font 形式(例:"16px Inter"、"500 17px 'JetBrains Mono'")は、描画される CSS と一致させる必要があります。一致しないと計測にずれが生じます。
ユースケース 2 — 計測して、自分で描画する
const prepared = prepareWithSegments(text, FONT);
const { lines } = layoutWithLines(prepared, 320, 26);
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i].text, 0, i * 26);
}
ここから創造的な作業が始まります。描画を自分で行うため、次のことができます。
- Canvas、SVG、WebGL、または任意の座標系に描画する
- グリフごとの変換(回転、ジッター、拡大縮小、不透明度)を適用する
- 行のメタデータ(幅、書記素の位置)をジオメトリとして使う
行ごとに幅が異なるフロー(図形の周囲に回り込むテキスト、ドーナツ状の帯の中のテキスト、矩形ではない段組み内のテキスト)には、次を使います。
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
let y = 0;
while (true) {
const lineWidth = widthAtY(y); // your function: how wide is the corridor at this y?
const range = layoutNextLineRange(prepared, cursor, lineWidth);
if (!range) break;
const line = materializeLineRange(prepared, range);
ctx.fillText(line.text, leftEdgeAtY(y), y);
cursor = range.end;
y += lineHeight;
}
これは、このライブラリ全体で最も重要なパターンです。「ドラッグしたスプライトの周囲にテキストを回り込ませる」を実現するもので、X で話題になったデモにも使われています。
知っておくと便利なヘルパー
measureLineStats(prepared, maxWidth)→{ lineCount, maxLineWidth }— 最も幅の広い行、つまり複数行テキストを縮めて囲む際の幅です。walkLineRanges(prepared, maxWidth, callback)— 文字列を割り当てずに行を反復処理します。文字が不要で、書記素に関する統計や物理演算を行う場合に使います。@chenglou/pretext/rich-inline— フォント、チップ、メンションが混在する段落向けの同じシステムです。サブパスからインポートします。
デモのレシピパターン
コミュニティのコーパス(references/patterns.md を参照)には、いくつかの有力なパターンがまとめられています。どれかを選んで発展させましょう。依頼がない限り、新しいカテゴリを作らないでください。
| パターン | 主な API | アイデアの例 |
|---|---|---|
| 障害物の周囲でリフロー | layoutNextLineRange + 行ごとの幅関数 | ドラッグしたカーソルスプライトを避けて折り返すエディトリアル段落 |
| ジオメトリとしてのテキストゲーム | layoutWithLines + 行ごとの衝突矩形 | 計測した単語が各ブリックになるブロック崩し |
| 粉砕/パーティクル | walkLineRanges → 書記素ごとの (x,y) → 物理演算 | クリックすると文字に分かれて爆発する文 |
| ASCII 障害物タイポグラフィ | layoutNextLineRange + 行ごとに計測した障害物の範囲 | ビットマップ ASCII ロゴ、形状の変形、実際のジオメトリに合わせてテキストを開かせるドラッグ可能なワイヤーオブジェクト |
| エディトリアルな複数段組み | layoutNextLineRange を段ごとに使用 + 共通カーソル | 引用文を配したアニメーション雑誌見開き |
| キネティックタイポグラフィ | layoutWithLines + 時間経過に伴う行ごとの変換 | Star Wars のクロール、波、バウンス、グリッチ |
| 複数行テキストの縮約 | measureLineStats | 最も小さいコンテナーに合わせて自動サイズ調整する引用カード |
動作する単一ファイルのスターターについては、templates/donut-orbit.html と templates/hello-orb-flow.html を参照してください。
ワークフロー
- ユーザーの依頼に基づいて、上の表からパターンを選びます。
- テンプレートから始めます。
templates/hello-orb-flow.html— 動く球体の周囲でテキストがリフローする例(障害物の周囲でリフローするパターン)templates/donut-orbit.html— 発展的な例:計測済み ASCII ロゴの障害物、ドラッグ可能なワイヤー球体/立方体、変形する形状フィールド、選択可能な DOM テキスト、開発時専用コントロールwrite_fileを使い、~/.hermes/cache/scratch/またはユーザーのワークスペースに新しい.htmlファイルを作成します。
- 依頼内容に合った、意図のあるコンテンツに差し替えます。実際の文章を 10〜100 文使用し、lorem は使わないでください。
- フォント、配色、構図、インタラクションを調整します。ここが作業の肝です。省略しないでください。
- ローカルで確認します。
cd <dir-with-html> && python -m http.server 8765 # then open http://localhost:8765/<file>.html - コンソールを確認します。
prepareWithSegmentsに不正なフォント文字列を渡すと pretext はエラーをスローします。Intl.Segmenterはすべての最新ブラウザーで利用できます。 - コードだけでなく、ユーザーにファイルのパスを伝えます。ユーザーはファイルを開きたいからです。
パフォーマンスに関する注意点
prepare()/prepareWithSegments()は処理コストの高い呼び出しです。テキストとフォントの組み合わせごとに一度だけ実行してください。ハンドルはキャッシュします。- リサイズ時は
layout()/layoutWithLines()のみを再実行し、再準備はしないでください。 - テキストは変わらずジオメトリだけが変化するフレーム単位のアニメーションでは、通常の長さの段落なら、
layoutNextLineRangeをタイトなループで毎秒60フレーム実行しても十分軽量です。 - フレームごとに ASCII マスクを描画する場合は、セルバッファー(
Uint8Array/型付き配列)を保持し、セルまたは投影したジオメトリから行ごとの測定済み障害物範囲を算出して範囲をマージし、テキストを描画する前にその範囲をlayoutNextLineRangeに渡します。 - ビジュアルアニメーションとレイアウトアニメーションを連動させてください。球体が立方体に変形する場合は、同じ値を使って、描画するセルバッファーと障害物範囲の両方をトゥイーンします。そうしないと、デモが物理的にリフローしているのではなく、表面に描き足しているように見えます。
- フェードには、グリフの強度や障害物のスケールを変えるのではなく、レイヤーの不透明度を使うのがおすすめです。一時的な ASCII スプライトは独自のキャンバスに配置し、CSS/GSAP の不透明度でキャンバスをフェードさせると、ジオメトリが縮んで見えることはありません。
- Canvas の
ctx.fontの設定は意外に時間がかかります。フォントが変わらない場合は、fillTextの呼び出しごとではなく、フレームごとに一度だけ設定してください。
よくある落とし穴
-
CSS とキャンバスのフォント文字列がずれる。
ctx.font = "16px Inter"で測定していても、CSS がfont-family: Inter, sans-serif; font-size: 16pxなら問題ありません。ただし、Inter が読み込まれている場合に限ります。Inter が 404 になると、CSS は sans-serif にフォールバックし、測定値が 5~20%ずれます。フォントを必ずpreloadするか、ウェブセーフなフォントファミリーを使用してください。 -
アニメーションループ内で再準備する。 軽量なのは
layout*だけです。フレームごとにprepareを再呼び出しすると、パフォーマンスが大幅に低下します。準備済みハンドルはモジュールスコープに保持してください。 -
グラフェム分割に
Intl.Segmenterを使い忘れる。 絵文字、結合文字、CJK では、"é".split("")は2文字を返します。個々の表示グリフをサンプリングするときは、new Intl.Segmenter(undefined, { granularity: "grapheme" })を使用してください。 -
extraWidthを指定せずにbreak: 'never'のチップを使う。rich-inlineでアトミックなチップ/メンションにbreak: 'never'を使う場合、ピルのパディング分のextraWidthも指定する必要があります。そうしないと、チップの装飾がコンテナーからはみ出します。 -
TypeScript 専用エントリーポイントの
@chenglou/pretextをunpkgから使う。esm.shを使ってください。TypeScript のエクスポートがブラウザー対応の ESM に自動でコンパイルされます。unpkgでは 404 になるか、生の TS が配信されます。 -
等幅フォントへのフォールバックで、肝心な点が知らないうちに失われる。 等幅に見える出力が表示される場合、CSS の
font-familyがmonospaceにフォールバックしていることがよくあります。DevTools で実際に描画されているフォントを確認してください。 -
形状を避けてテキストを流し込む際に、幅を調整せず行をスキップする。 その行の通路が行全体を収めるには狭すぎる場合、
layoutNextLineRangeに極端に小さな maxWidth を渡すのではなく、行をスキップしてください(y += lineHeight; continue;)。pretext は1つのグラフェムだけの行を返し、表示が崩れてしまいます。 -
作り込みが足りないデモを公開する。 デフォルトの初回描画はチュートリアル程度の見栄えです。ビネット、控えめな走査線、アイドル時の自動モーション、慎重に選んだインタラクション(ドラッグ、ホバー、スクロール、クリック)を加えてください。これらがないと、「かっこいい pretext のデモ」が「README をそのまま再現したインターンの成果物」に見えてしまいます。
検証チェックリスト
- デモは自己完結した単一の
.htmlファイルで、ダブルクリックまたはpython -m http.serverで開ける -
@chenglou/pretextはバージョンを固定してesm.sh経由でインポートしている - 使用する文章は Lorem Ipsum ではなく実際の文章で、デモのコンセプトに合っている
-
prepareに渡すフォント文字列が CSS のフォントと完全に一致している -
prepare()/prepareWithSegments()はフレームごとではなく、一度だけ呼び出している - 暗い背景とよく考えられた配色を使っており、デフォルトの白いキャンバスではない
- 少なくとも1つのインタラクション(ドラッグ/ホバー/スクロール/クリック)またはアイドル時の自動モーションがある
-
python -m http.serverを使ってローカルでテストし、コンソールエラーがないことを確認している - ミドルレンジのノート PC で毎秒60フレームを維持できる(または、適切なフォールバックについて説明している)
- ユーザーに頼まれていない「ひと手間」の工夫がある
参考:コミュニティのデモ
インスピレーションやパターンの参考に、これらをクローンしてください(すべてMITに近いライセンスで、pretext.coolからリンクされています)。
- Pretext Breaker — 単語ブロックのブレイクアウト —
github.com/rinesh/pretext-breaker - Tetris × Pretext —
github.com/shinichimochizuki/tetris-pretext - ドラゴンのアニメーション —
github.com/qtakmalay/PreTextExperiments - Somnaiのエディトリアルエンジン —
github.com/somnai-dreams/pretext-demos - Bad Apple!! ASCII —
github.com/frmlinn/bad-apple-pretext - ドラッグ可能なスプライトのリフロー —
github.com/dokobot/pretext-demo - Alarmyのエディトリアル時計 —
github.com/SmisLee/alarmy-pretext-demo
公式プレイグラウンド:chenglou.me/pretext — accordion、bubbles、dynamic-layout、editorial-engine、justification-comparison、masonry、markdown-chat、rich-note。