pretext
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePretext Creative Demos
Pretext创意演示
Overview
概述
@chenglou/pretext(text, font, width)That sounds like plumbing. It is not. Because it is fast and geometric, it is a creative primitive: you can reflow paragraphs around a moving sprite at 60fps, build games whose level geometry is made of real words, drive ASCII logos through prose, shatter text into particles with exact per-grapheme starting positions, or pack shrink-wrapped multiline UI without any thrash.
getBoundingClientRectThis skill exists so Hermes can make cool demos with it — the kind people post to X. See and for the community demo corpus.
pretext.coolchenglou.me/pretext@chenglou/pretext(text, font, width)这听起来像是底层工具,但实际上并非如此。由于它速度快且基于几何计算,它是一个创意基础组件:你可以以60fps的帧率让段落围绕移动的精灵重新排版,构建以真实文字为关卡几何结构的游戏,让ASCII标志在散文中穿梭,将文本分解为带有精确初始位置的粒子,或者构建无需频繁调用的多行自适应UI。
getBoundingClientRect本技能旨在让Hermes使用它制作酷炫的演示——就是那种人们会分享到X平台的作品。可查看和获取社区演示合集。
pretext.coolchenglou.me/pretextWhen to Use
使用场景
Use when the user asks for:
- A "pretext demo" / "cool pretext thing" / "text-as-X"
- Text flowing around a moving shape (hero sections, editorial layouts, animated long-form pages)
- ASCII-art effects using real words or prose, not monospace rasters
- Games where the playfield / obstacles / bricks are made of text (Tetris-from-letters, Breakout-of-prose)
- Kinetic typography with per-glyph physics (shatter, scatter, flock, flow)
- Typographic generative art, especially with non-Latin scripts or mixed scripts
- Multiline "shrink-wrap" UI (smallest container width that still fits the text)
- Anything that would require knowing line breaks before rendering
Don't use for:
- Static SVG/HTML pages where CSS already solves layout — just use CSS
- Rich text editors, general inline formatting engines (pretext is intentionally narrow)
- Image → text (use /
ascii-artskills)ascii-video - Pure canvas generative art with no text role — use
p5js
当用户提出以下需求时使用:
- 制作“pretext演示”/“酷炫的pretext作品”/“文本化X效果”
- 文本围绕移动形状流动(英雄区、编辑排版、动画长页面)
- 使用真实文字或散文而非等宽光栅的ASCII艺术效果
- 以文本作为游戏场地/障碍物/砖块的游戏(字母俄罗斯方块、散文打砖块)
- 带有每个字形物理效果的动态排版(破碎、分散、聚集、流动)
- 排版生成艺术,尤其是非拉丁脚本或混合脚本的情况
- 多行“自适应”UI(能容纳文本的最小容器宽度)
- 任何需要在渲染前确定换行位置的场景
请勿用于:
- CSS已能解决布局的静态SVG/HTML页面——直接使用CSS即可
- 富文本编辑器、通用内联格式引擎(pretext的定位非常专注)
- 图片转文本(使用/
ascii-art技能)ascii-video - 无文本角色的纯Canvas生成艺术——使用
p5js
Creative Standard
创意标准
This is visual art rendered in a browser. Pretext returns numbers; you draw the thing.
- Don't ship a "hello world" demo. The template is the starting point. Every delivered demo must add intentional color, motion, composition, and one visual detail the user didn't ask for but will appreciate.
hello-orb-flow.html - Dark backgrounds, warm cores, considered palette. Classic amber-on-black (CRT / terminal) works, but so do cold-white-on-charcoal (editorial) and desaturated pastels (risograph). Pick one and commit.
- Proportional fonts are the point. Pretext's whole vibe is "not monospaced" — lean into it. Use Iowan Old Style, Inter, JetBrains Mono, Helvetica Neue, or a variable font. Never default sans.
- Real source/text, not lorem ipsum. The corpus should mean something. Short manifestos, poetry, real source code, a found text, the library's own README — never .
lorem ipsum - First-paint excellence. No loading states, no blank frames. The demo must look shippable the instant it opens.
这是在浏览器中渲染的视觉艺术。Pretext返回数值;你负责绘制最终效果。
- 不要交付“hello world”级别的演示。模板只是起点。每个交付的演示都必须添加精心设计的颜色、动效、构图,以及一个用户没要求但会欣赏的视觉细节。
hello-orb-flow.html - 深色背景、暖色调核心、精心搭配的调色板。经典的黑底琥珀色(CRT/终端风格)很适用,但冷白深灰(编辑风格)和低饱和度马卡龙色(Risograph风格)也不错。选定一种风格并贯彻到底。
- 比例字体是关键。Pretext的核心特色就是“非等宽”——要充分利用这一点。使用Iowan Old Style、Inter、JetBrains Mono、Helvetica Neue或可变字体。不要使用默认无衬线字体。
- 使用真实内容/文本,而非Lorem Ipsum。内容要有意义。比如简短宣言、诗歌、真实源代码、收集到的文本、库自身的README——绝不要用。
lorem ipsum - 首屏效果出色。不要加载状态,不要空白帧。演示必须在打开瞬间看起来就已完成。
Stack
技术栈
Single self-contained HTML file per demo. No build step.
| Layer | Tool | Purpose |
|---|---|---|
| Core | | Text measurement + line layout |
| Render | HTML5 Canvas 2D | Glyph rendering, per-frame composition |
| Segmentation | | Grapheme splitting for emoji / CJK / combining marks |
| Interaction | Raw DOM events | Mouse / touch / wheel — no framework |
html
<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/pretext@0.0.6";
</script>Pin the version. at time of writing — check npm for the latest if demo behavior is off.
@0.0.6每个演示都是独立的单HTML文件。无需构建步骤。
| 层级 | 工具 | 用途 |
|---|---|---|
| 核心 | | 文本测量 + 行布局 |
| 渲染 | HTML5 Canvas 2D | 字形渲染、逐帧构图 |
| 分割 | | 拆分 emoji / 中日韩文字 / 组合标记的字形 |
| 交互 | 原生DOM事件 | 鼠标/触摸/滚轮——无需框架 |
html
<script type="module">
import {
prepare, layout, // 场景1:简单高度测量
prepareWithSegments, layoutWithLines, // 场景2a:固定宽度行
layoutNextLineRange, materializeLineRange, // 场景2b:流式/可变宽度
measureLineStats, walkLineRanges, // 无需字符串分配的统计
} from "https://esm.sh/@chenglou/pretext@0.0.6";
</script>固定版本号。撰写本文时版本为——如果演示行为异常,请查看npm获取最新版本。
@0.0.6The Two Use Cases
两种核心使用场景
Almost everything reduces to one of these two shapes. Learn both.
几乎所有应用都可以归为以下两种场景。请掌握这两种用法。
Use-case 1 — measure, then render with CSS/DOM
场景1 — 测量,然后用CSS/DOM渲染
js
const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);You still let the browser draw the text. Pretext just tells you how tall the box will be at a given width, without a DOM read. Use for:
- Virtualized lists where rows contain wrapping text
- Masonry with precise card heights
- "Does this label fit?" dev-time checks
- Preventing layout shift when remote text loads
Keep and exactly in sync with your CSS. The canvas format (e.g. , ) must match the rendered CSS, or measurements drift.
fontletterSpacingctx.font"16px Inter""500 17px 'JetBrains Mono'"js
const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);你仍然让浏览器绘制文本。Pretext只是告诉你在给定宽度下容器的高度,无需读取DOM。适用于:
- 包含换行文本的虚拟化列表
- 带有精确卡片高度的瀑布流布局
- 开发时检查“这个标签是否能容纳?”
- 远程文本加载时防止布局偏移
确保和与你的CSS完全同步。canvas的格式(如、)必须与渲染的CSS匹配,否则测量结果会出现偏差。
fontletterSpacingctx.font"16px Inter""500 17px 'JetBrains Mono'"Use-case 2 — measure and render yourself
场景2 — 自行测量并渲染
js
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);
}This is where the creative work lives. You own the drawing, so you can:
- Render to canvas, SVG, WebGL, or any coordinate system
- Substitute per-glyph transforms (rotation, jitter, scale, opacity)
- Use line metadata (width, grapheme positions) as geometry
For variable-width-per-line flow (text around a shape, text in a donut band, text in a non-rectangular column):
js
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;
}This is the most important pattern in the whole library. It's what unlocks "text flowing around a dragged sprite" — the demo that went viral on X.
js
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或任何坐标系
- 替换每个字形的变换(旋转、抖动、缩放、透明度)
- 将行元数据(宽度、字形位置)作为几何图形使用
对于每行宽度可变的排版(围绕形状的文本、环形文本、非矩形列中的文本):
js
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
let y = 0;
while (true) {
const lineWidth = widthAtY(y); // 你的自定义函数:该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平台走红。
Helpers worth knowing
值得了解的辅助函数
- →
measureLineStats(prepared, maxWidth)— the widest line, i.e. multiline shrink-wrap width.{ lineCount, maxLineWidth } - — iterate lines without allocating strings. Use for stats/physics over graphemes when you don't need the characters.
walkLineRanges(prepared, maxWidth, callback) - — the same system but for paragraphs mixing fonts / chips / mentions. Import from the subpath.
@chenglou/pretext/rich-inline
- →
measureLineStats(prepared, maxWidth)—— 最宽行的宽度,即多行自适应宽度。{ lineCount, maxLineWidth } - —— 遍历行而不分配字符串。当你不需要字符时,可用于字形的统计/物理计算。
walkLineRanges(prepared, maxWidth, callback) - —— 同一系统,但适用于混合字体/标签/提及的段落。从子路径导入。
@chenglou/pretext/rich-inline
Demo Recipe Patterns
演示模板模式
The community corpus (see ) clusters into a handful of strong patterns. Pick one and riff — don't invent a new category unless asked.
references/patterns.md| Pattern | Key API | Example idea |
|---|---|---|
| Reflow around obstacle | | Editorial paragraph that parts around a dragged cursor sprite |
| Text-as-geometry game | | Breakout where each brick is a measured word |
| Shatter / particles | | Sentence that explodes into letters on click |
| ASCII obstacle typography | | Bitmap ASCII logo, shape morphs, and draggable wire objects that make text open around their actual geometry |
| Editorial multi-column | | Animated magazine spread with pull quotes |
| Kinetic type | | Star Wars crawl, wave, bounce, glitch |
| Multiline shrink-wrap | | Quote card that auto-sizes to its tightest container |
See and for working single-file starters.
templates/donut-orbit.htmltemplates/hello-orb-flow.html社区合集(见)集中了几种常用模式。选择一种进行改编——除非用户要求,否则不要创建新类别。
references/patterns.md| 模式 | 核心API | 示例思路 |
|---|---|---|
| 绕障碍物重排 | | 围绕拖动的光标精灵分开的编辑段落 |
| 文本化几何游戏 | | 每个砖块都是已测量文字的打砖块游戏 |
| 破碎/粒子效果 | | 点击时分解为字母的句子 |
| ASCII障碍物排版 | | 位图ASCII标志、形状变形、可拖动的线框物体,文本会围绕其实际几何结构展开 |
| 编辑式多列布局 | 每列使用 | 带有拉引号的动画杂志跨页 |
| 动态排版 | | 星球大战滚动字幕、波浪、弹跳、故障效果 |
| 多行自适应 | | 自动调整到最紧凑容器的引用卡片 |
可查看和获取可用的单文件启动模板。
templates/donut-orbit.htmltemplates/hello-orb-flow.htmlWorkflow
工作流程
- Pick a pattern from the table above based on the user's brief.
- Start from a template:
- — text reflowing around a moving orb (reflow-around-obstacle pattern)
templates/hello-orb-flow.html - — advanced example: measured ASCII logo obstacles, draggable wire sphere/cube, morphing shape fields, selectable DOM text, and dev-only controls
templates/donut-orbit.html - to a new
write_filein.htmlor the user's workspace./tmp/
- Swap the corpus for something intentional to the brief. Real prose, 10-100 sentences, no lorem.
- Tune the aesthetic — font, palette, composition, interaction. This is the work; don't skip it.
- Verify locally:
sh
cd <dir-with-html> && python3 -m http.server 8765 # then open http://localhost:8765/<file>.html - Check the console — pretext will throw if is called with a bad font string;
prepareWithSegmentsis available in every modern browser.Intl.Segmenter - Show the user the file path, not just the code — they want to open it.
- 根据用户需求选择模式:从上方表格中挑选合适的模式。
- 从模板开始:
- —— 围绕移动球体重排的文本(绕障碍物重排模式)
templates/hello-orb-flow.html - —— 高级示例:已测量的ASCII标志障碍物、可拖动的线框球体/立方体、变形形状区域、可选DOM文本,以及仅开发者可见的控制项
templates/donut-orbit.html - 使用将模板写入
write_file或用户工作区的新/tmp/文件。.html
- 替换内容:根据需求替换为有意义的内容。真实散文,10-100句,不要用Lorem Ipsum。
- 调整美学风格——字体、调色板、构图、交互。这是核心工作,不要跳过。
- 本地验证:
sh
cd <包含html的目录> && python3 -m http.server 8765 # 然后打开 http://localhost:8765/<file>.html - 检查控制台——如果调用时传入无效字体字符串,pretext会抛出错误;
prepareWithSegments在所有现代浏览器中都可用。Intl.Segmenter - 向用户展示文件路径,而不只是代码——他们需要打开文件查看效果。
Performance Notes
性能注意事项
- /
prepare()is the expensive call. Do it once per text+font pair. Cache the handle.prepareWithSegments() - On resize, only rerun /
layout()— never re-prepare.layoutWithLines() - For per-frame animations where text doesn't change but geometry does, in a tight loop is cheap enough to do every frame at 60fps for normal-length paragraphs.
layoutNextLineRange - When rendering ASCII masks per frame, keep a cell buffer (/typed arrays), derive measured per-row obstacle spans from the cells or projected geometry, merge spans, then feed those spans into
Uint8Arraybefore drawing text.layoutNextLineRange - Keep visual animation and layout animation coupled. If a sphere morphs into a cube, tween both the rendered cell buffer and the obstacle spans with the same value; otherwise the demo looks painted-on instead of physically reflowed.
- For fades, prefer layer opacity over changing glyph intensity or obstacle scale. Put transient ASCII sprites on their own canvas and fade the canvas with CSS/GSAP opacity so geometry does not appear to shrink.
- Canvas setting is surprisingly slow; set it once per frame if font doesn't vary, not per
ctx.fontcall.fillText
- /
prepare()是耗时操作。每个文本+字体组合只调用一次。缓存处理后的句柄。prepareWithSegments() - 调整大小时,只需重新运行/
layout()——绝不要重新调用prepare。layoutWithLines() - 对于文本不变但几何结构变化的逐帧动画,在紧凑循环中调用的开销足够低,可以对普通长度的段落实现60fps的帧率。
layoutNextLineRange - 逐帧渲染ASCII遮罩时,保留一个单元格缓冲区(/类型数组),从单元格或投影几何结构导出已测量的每行障碍物跨度,合并跨度,然后在绘制文本前将这些跨度传入
Uint8Array。layoutNextLineRange - 保持视觉动画和布局动画同步。如果球体变形为立方体,使用相同的数值对渲染的单元格缓冲区和障碍物跨度进行补间;否则演示会看起来像是贴上去的,而非物理重排。
- 实现淡入淡出效果时,优先使用图层透明度,而非改变字形强度或障碍物缩放。将临时ASCII精灵放在单独的canvas上,用CSS/GSAP调整canvas的透明度,这样几何结构不会看起来缩小。
- Canvas的设置意外地慢;如果字体不变,每帧只设置一次,不要每次
ctx.font都设置。fillText
Common Pitfalls
常见陷阱
-
Drifting CSS/canvas font strings.measured, but CSS says
ctx.font = "16px Inter". Fine if Inter loads. If Inter 404s, CSS falls back to sans-serif and measurements drift by 5-20%. Alwaysfont-family: Inter, sans-serif; font-size: 16pxthe font or use a web-safe family.preload -
Re-preparing inside the animation loop. Onlyis cheap. Re-calling
layout*every frame will tank perf. Keep the prepared handle in module scope.prepare -
Forgettingfor grapheme splits. Emoji, combining marks, CJK —
Intl.Segmentergives you two chars. Use"é".split("")when sampling individual visible glyphs.new Intl.Segmenter(undefined, { granularity: "grapheme" }) -
chips without
break: 'never'. InextraWidth, if you userich-inlinefor an atomic chip/mention, you must also supplybreak: 'never'for the pill padding — otherwise chip chrome overflows the container.extraWidth -
Usingfrom
@chenglou/pretextwith TypeScript-only entry. Useunpkg— it compiles the TS exports to browser-ready ESM automatically.esm.shwill 404 or serve raw TS.unpkg -
Monospace fallbacks silently erasing the whole point. Users seeing monospace-looking output often have a CSSthat fell through to
font-family. Verify the actual rendered font via DevTools.monospace -
Skipping rows vs adjusting width when flowing around a shape. If the corridor on this row is too narrow to fit a line, skip the row () rather than passing a tiny maxWidth to
y += lineHeight; continue;— pretext will return one-grapheme lines that look broken.layoutNextLineRange -
Shipping a cold demo. The default first-paint looks tutorial-grade. Add: vignette, subtle scanline, idle auto-motion, one carefully chosen interactive response (drag, hover, scroll, click). Without these, "cool pretext demo" lands as "intern repro of the README."
-
CSS/Canvas字体字符串不一致。测量时使用,但CSS设置为
ctx.font = "16px Inter"。如果Inter加载成功没问题,但如果Inter加载失败,CSS会回退到无衬线字体,测量结果会偏差5-20%。务必预加载字体或使用网页安全字体族。font-family: Inter, sans-serif; font-size: 16px -
在动画循环中重新调用prepare。只有是轻量操作。每帧重新调用
layout*会严重影响性能。将处理后的句柄保存在模块作用域中。prepare -
忘记使用拆分字形。Emoji、组合标记、中日韩文字——
Intl.Segmenter会得到两个字符。当采样单个可见字形时,使用"é".split("")。new Intl.Segmenter(undefined, { granularity: "grapheme" }) -
使用标签时未设置
break: 'never'。在extraWidth中,如果对原子标签/提及使用rich-inline,必须同时提供break: 'never'用于标签内边距——否则标签的边框会溢出容器。extraWidth -
从导入
unpkg时使用仅TypeScript入口。使用@chenglou/pretext——它会自动将TS导出编译为浏览器可用的ESM。esm.sh会返回404或原始TS代码。unpkg -
等宽字体回退无声地抹杀核心特色。用户看到等宽样式的输出通常是因为CSS的回退到了
font-family。通过开发者工具验证实际渲染的字体。monospace -
围绕形状排版时跳过行vs调整宽度。如果该行的走廊太窄无法容纳一行文本,跳过该行(),而非向
y += lineHeight; continue;传入极小的maxWidth——pretext会返回单个字形的行,看起来像是断裂的。layoutNextLineRange -
交付缺乏温度的演示。默认首屏效果看起来像教程级别。添加:暗角、微妙扫描线、空闲自动动效、一个精心设计的交互响应(拖动、悬停、滚动、点击)。没有这些,“酷炫的pretext演示”会变成“实习生复刻的README”。
Verification Checklist
验证清单
- Demo is a single self-contained file — opens by double-click or
.htmlpython3 -m http.server - imported via
@chenglou/pretextwith pinned versionesm.sh - Corpus is real prose, not lorem ipsum, and matches the demo's concept
- Font string passed to matches the CSS font exactly
prepare - /
prepare()called once, not per frameprepareWithSegments() - Dark background + considered palette — not the default white canvas
- At least one interactive response (drag / hover / scroll / click) or idle auto-motion
- Tested locally with and confirmed no console errors
python3 -m http.server - 60fps on a mid-tier laptop (or graceful degradation documented)
- One "extra mile" detail the user didn't ask for
- 演示是独立的单文件——可通过双击或
.html打开python3 -m http.server - 通过
@chenglou/pretext导入并固定版本esm.sh - 内容是真实散文,而非Lorem Ipsum,且与演示概念匹配
- 传入的字体字符串与CSS字体完全匹配
prepare - /
prepare()只调用一次,而非每帧调用prepareWithSegments() - 深色背景 + 精心搭配的调色板——不是默认白色Canvas
- 至少有一个交互响应(拖动/悬停/滚动/点击)或空闲自动动效
- 通过本地测试,确认无控制台错误
python3 -m http.server - 在中端笔记本上达到60fps(或记录了优雅降级方案)
- 包含一个用户未要求但超出预期的细节
Reference: Community Demos
参考:社区演示
Clone these for inspiration / patterns (all MIT-ish, linked from pretext.cool):
- Pretext Breaker — breakout with word-bricks —
github.com/rinesh/pretext-breaker - Tetris × Pretext —
github.com/shinichimochizuki/tetris-pretext - Dragon animation —
github.com/qtakmalay/PreTextExperiments - Somnai editorial engine —
github.com/somnai-dreams/pretext-demos - Bad Apple!! ASCII —
github.com/frmlinn/bad-apple-pretext - Drag-sprite reflow —
github.com/dokobot/pretext-demo - Alarmy editorial clock —
github.com/SmisLee/alarmy-pretext-demo
Official playground: chenglou.me/pretext — accordion, bubbles, dynamic-layout, editorial-engine, justification-comparison, masonry, markdown-chat, rich-note.
克隆这些项目获取灵感/模式(均为MIT类许可,链接来自pretext.cool):
- Pretext Breaker —— 以单词为砖块的打砖块游戏 ——
github.com/rinesh/pretext-breaker - Tetris × Pretext ——
github.com/shinichimochizuki/tetris-pretext - Dragon animation ——
github.com/qtakmalay/PreTextExperiments - Somnai editorial engine ——
github.com/somnai-dreams/pretext-demos - Bad Apple!! ASCII ——
github.com/frmlinn/bad-apple-pretext - Drag-sprite reflow ——
github.com/dokobot/pretext-demo - Alarmy editorial clock ——
github.com/SmisLee/alarmy-pretext-demo
官方 playground:chenglou.me/pretext —— 手风琴、气泡、动态布局、编辑引擎、对齐方式对比、瀑布流、Markdown聊天、富文本笔记。