mcpField
P
pretext

pretext

github.com/nousresearch创意与媒体258.1k317 次安装今天更新
代码仓库

关于 pretext

pretext 是用于无 DOM 读取测量和排布多行文本的 TypeScript 库及创作技能,可在渲染前获取行与字素几何信息,用于浏览器演示和界面。

主要功能

  1. 01用 canvas 测量文本与行宽
  2. 02无需 DOM 重排计算多行布局
  3. 03提供逐字素位置
  4. 04支持逐行可变宽度布局
  5. 05提供 rich-inline 段落辅助工具
  6. 06将布局数据用于动画和游戏

适用场景

  1. 01让段落绕过移动图形排布
  2. 02构建以文本几何构成的游戏
  3. 03动画化或散开单个字形
  4. 04计算紧凑多行界面的尺寸

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nousresearch/hermes-agent --skill pretext

安装后在 Claude Code 中输入/pretext确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
patterns.md
templates/
donut-orbit.html
hello-orb-flow.html

详细介绍

Pretext 创意演示

概述

@chenglou/pretext 是 Cheng Lou(React 核心开发者、ReasonML、Midjourney)开发的一个仅 15KB、零依赖的 TypeScript 库,用于无 DOM 的多行文本测量与布局。它只做一件事:给定 (text, font, width),返回换行位置、每行宽度、每个字素的位置和总高度——全部通过 Canvas 测量,无需触发重排。

听起来像是基础设施工作,其实不然。由于它速度快且基于几何信息,它是一种创意基元:你可以让段落以 60fps 围绕移动的精灵图重新排版,用真实文字构建关卡几何结构的游戏,让 ASCII 徽标穿行于散文之中,将文本打散成粒子并精确设置每个字素的起始位置,或者打包紧贴多行文本的 UI,而无需反复调用 getBoundingClientRect。

这项技能旨在让 Hermes 用它制作酷炫的演示——那种人们会发到 X 上的作品。社区演示合集见 pretext.cool 和 chenglou.me/pretext。

何时使用

用户提出以下需求时使用:

  • “pretext 演示”/“酷炫的 pretext 效果”/“文字变成 X”
  • 让文本围绕移动形状流动(主视觉区域、编辑式布局、动画长篇页面)
  • 使用真实文字或散文而非等宽栅格制作 ASCII 艺术效果
  • 游戏中的游玩区域/障碍物/砖块由文本构成(字母俄罗斯方块、散文打砖块)
  • 带有逐字形物理效果的动态排印(碎裂、散射、群集、流动)
  • 排印生成艺术,尤其是非拉丁文字或混合文字
  • 多行“紧密包裹”UI(仍能容纳文本的最窄容器宽度)
  • 任何需要在渲染前确定换行位置的场景

以下情况不要使用:

  • CSS 已能解决布局的静态 SVG/HTML 页面——直接使用 CSS
  • 富文本编辑器、通用行内格式引擎(pretext 的定位本就很专一)
  • 图像转文本(使用 ascii-art/ascii-video 技能)
  • 不涉及文本的纯 Canvas 生成艺术——使用 p5js

创作标准

这是在浏览器中渲染的视觉艺术。Pretext 返回数字;由你来绘制作品。

  • 不要交付“hello world”演示。 hello-orb-flow.html 模板只是起点。每个交付的演示都必须加入精心设计的色彩、动态效果、构图,以及一个用户没要求但会欣赏的视觉细节。
  • 深色背景、温暖核心、考究的配色。 经典的黑底琥珀色(CRT/终端)很有效,炭灰底冷白色(编辑风格)和低饱和粉彩(孔版印刷风格)也不错。选定一种并贯彻到底。
  • 重点是比例字体。 Pretext 的整体风格就是“非等宽”——充分利用这一点。使用 Iowan Old Style、Inter、JetBrains Mono、Helvetica Neue 或可变字体。不要默认使用无衬线字体。
  • 使用真实素材/文本,而非 lorem ipsum。 文本内容应有意义。短篇宣言、诗歌、真实源代码、拾得文本、库自身的 README——不要用 lorem ipsum。
  • 首次呈现就要出色。 不要有加载状态或空白帧。演示打开后应立刻达到可交付品质。

技术栈

每个演示都是一个独立的 HTML 文件。无需构建步骤。

层级工具用途
核心通过 esm.sh CDN 使用 @chenglou/pretext文本测量 + 行布局
渲染HTML5 Canvas 2D字形渲染、逐帧构图
分段Intl.Segmenter(内置)对 emoji/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 获取最新版本。

两种用法

几乎所有场景都可以归结为以下两种形式。请同时掌握。

用例 1 — 测量,再用 CSS/DOM 渲染
const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);

仍然让浏览器绘制文本。Pretext 只会告诉你在给定宽度下文本框会有多高,无需读取 DOM。适用于:

  • 包含换行文本的行的虚拟化列表
  • 精确控制卡片高度的瀑布流
  • 开发时检查“这个标签放得下吗?”
  • 防止远程文本加载时发生布局偏移

确保 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 Logo、形状变换,以及让文本围绕其实际几何形状展开的可拖动线框物体
编辑式多栏排版每栏使用 layoutNextLineRange + 共享游标带有引文的动画杂志跨页
动态排版layoutWithLines + 随时间变化的逐行变换《星球大战》滚动字幕、波浪、弹跳、故障效果
多行自动收缩measureLineStats自动调整尺寸以适配最紧凑容器的引言卡片

可参见 templates/donut-orbit.html 和 templates/hello-orb-flow.html 中可运行的单文件入门示例。

工作流

  1. 根据用户的需求,从上表中选择一种模式。
  2. 从模板开始:
    • templates/hello-orb-flow.html — 文本围绕移动球体重新排布(绕开障碍物重排模式)
    • templates/donut-orbit.html — 进阶示例:经测量的 ASCII Logo 障碍物、可拖动的线框球体/立方体、变形的形状场、可选择的 DOM 文本,以及仅供开发使用的控制项
    • 使用 write_file 在 ~/.hermes/cache/scratch/ 或用户的工作区中新建 .html 文件。
  3. 替换语料,使其符合需求。使用真实文本,包含 10–100 个句子,不要用 lorem。
  4. 调整美感——字体、配色、构图、交互。这才是核心工作;不要跳过。
  5. 在本地验证:
    cd <dir-with-html> && python -m http.server 8765
    # then open http://localhost:8765/<file>.html
    
  6. 检查控制台——如果调用 prepareWithSegments 时使用了无效的字体字符串,pretext 会抛出错误;所有现代浏览器都支持 Intl.Segmenter。
  7. 向用户展示文件路径,不要只展示代码——他们需要打开文件。

性能说明

  • prepare() / prepareWithSegments() 是开销较大的调用。每组文本和字体只调用一次。缓存句柄。
  • 调整大小时,只需重新运行 layout() / layoutWithLines() — 不要重新 prepare。
  • 对于文本不变、几何形状会变化的逐帧动画,在紧密循环中调用 layoutNextLineRange 的开销足够低,正常长度的段落可在每帧 60fps 下运行。
  • 每帧渲染 ASCII 遮罩时,保留一个单元格缓冲区(Uint8Array/类型化数组),根据单元格或投影几何形状推导出每行测量后的障碍物跨度,合并这些跨度,然后在绘制文本前将其传给 layoutNextLineRange。
  • 让视觉动画与布局动画保持同步。如果球体变形成立方体,使用相同的值对渲染单元格缓冲区和障碍物跨度进行补间;否则,示例看起来就像是把图案贴上去,而不是根据形状重新排版。
  • 淡入淡出时,优先调整图层不透明度,而不是改变字形强度或障碍物缩放比例。将临时 ASCII 精灵放在单独的 canvas 上,再用 CSS/GSAP 调整 canvas 的不透明度,这样几何形状就不会看起来在缩小。
  • 设置 Canvas ctx.font 的速度出奇地慢;如果字体不变,每帧只设置一次,不要在每次 fillText 调用时都设置。

常见问题

  1. CSS 与 canvas 的字体字符串不一致。 测量时使用 ctx.font = "16px Inter",但 CSS 设置为 font-family: Inter, sans-serif; font-size: 16px。只要 Inter 能加载,这样就没问题。如果 Inter 返回 404,CSS 就会回退到 sans-serif,导致测量结果偏差 5-20%。务必 preload 字体或使用 Web 安全字体族。

  2. 在动画循环中重复准备。 只有 layout* 的开销较低。每帧重新调用 prepare 会严重拖慢性能。将已准备好的句柄保存在模块作用域中。

  3. 拆分字素时忘记使用 Intl.Segmenter。 Emoji、组合标记、CJK 字符 — "é".split("") 会得到两个字符。采样单个可见字形时,请使用 new Intl.Segmenter(undefined, { granularity: "grapheme" })。

  4. 使用 break: 'never' 的标签却没有提供 extraWidth。 在 rich-inline 中,如果对不可拆分的标签/提及使用 break: 'never',还必须提供用于胶囊内边距的 extraWidth,否则标签装饰会溢出容器。

  5. 通过 unpkg 使用 @chenglou/pretext,但它的入口仅支持 TypeScript。 请使用 esm.sh — 它会自动将 TS 导出编译成适用于浏览器的 ESM。unpkg 会返回 404 或提供原始 TS。

  6. 单调字体回退会悄无声息地抹去整个演示的意义。 用户看到看似等宽字体的输出,通常是因为 CSS 中的 font-family 最终回退到了 monospace。请通过 DevTools 验证实际渲染的字体。

  7. 围绕形状排版时,跳过行比调整宽度更合适。 如果当前行的通道太窄,无法容纳一行文本,就应跳过该行(y += lineHeight; continue;),而不是向 layoutNextLineRange 传入极小的 maxWidth — pretext 会返回只有一个字素的行,显示效果会很糟糕。

  8. 发布未经打磨的演示。 默认的首次渲染看起来像教程示例。请添加:暗角、细微扫描线、闲置时自动动效,以及一个精心设计的交互响应(拖动、悬停、滚动、点击)。缺少这些的话,“很酷的 pretext 演示”就会沦为“实习生照着 README 复现的作品”。

验证清单

  • 演示是一个独立的 .html 文件 — 双击即可打开,或使用 python -m http.server
  • 通过 esm.sh 导入 @chenglou/pretext,并固定版本
  • 使用真实文本语料,而非 lorem ipsum,且与演示概念相符
  • 传给 prepare 的字体字符串与 CSS 字体完全一致
  • prepare() / prepareWithSegments() 只调用一次,不会每帧调用
  • 深色背景 + 精心设计的配色 — 而非默认的白色 canvas
  • 至少包含一种交互响应(拖动 / 悬停 / 滚动 / 点击)或闲置时自动动效
  • 使用 python -m http.server 在本地测试,并确认控制台没有错误
  • 中端笔记本上达到 60fps(或记录优雅降级的说明)
  • 包含一项用户未要求的“额外惊喜”细节

参考:社区演示

克隆这些项目,获取灵感或参考实现模式(均为类似 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。

评论

支持 Markdown