{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}

Pretext

用无 DOM 文本布局构建创意浏览器 demo。

Skill 元数据

来源可选 — 通过 hermes skills install official/creative/pretext 安装
路径optional-skills/creative/pretext
版本1.0.0
作者Hermes Agent
许可证MIT
平台linux, macos, windows
标签creative-coding, typography, pretext, ascii-art, canvas, generative, text-layout, kinetic-typography
相关 skillp5js, claude-design, excalidraw, architecture-diagram

参考:完整 SKILL.md

INFO

以下是 Hermes 在触发本 skill 时加载的完整 skill 定义。这是 agent 在 skill 激活时所看到的指令内容。

Pretext 创意 Demo

概述

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

听着像管线工程。其实不是。因为它快且几何化,它是一个创意原语:你可以让段落以 60fps 绕着一个移动精灵重排,做出关卡几何由真实单词构成的游戏,让 ASCII logo 穿行散文,把文字按精确的逐字素起始位置炸成粒子,或打包紧裹的多行 UI 而无需任何 getBoundingClientRect 抖动。

本 skill 的存在就是让 Hermes 用它做酷 demo——那种人们会发到 X 上的。社区 demo 库见 pretext.cool 和 chenglou.me/pretext。

何时使用

用户要求以下时使用:

  • 一个"pretext demo" / "酷的 pretext 东西" / "文本即 X"
  • 文字绕着移动形状流动(首屏、编辑版式、动画长文页)
  • 用真实单词或散文做 ASCII 艺术效果,而非等宽光栅
  • 游戏,其场地/障碍/砖块由文字构成(字母版俄罗斯方块、散文版打砖块)
  • 逐字形物理的动态排版(碎裂、散射、集群、流动)
  • 字体排印生成艺术,尤其带非拉丁文字或混合文字
  • 多行"紧裹"UI(仍能放下文本的最小容器宽度)
  • 任何需要在渲染之前知道换行的东西

不要用于:

  • CSS 已解决布局的静态 SVG/HTML 页面——直接用 CSS
  • 富文本编辑器、通用内联格式化引擎(pretext 刻意做窄)
  • 图片 → 文字(用 ascii-art / ascii-video skill)
  • 无文字角色的纯 canvas 生成艺术——用 p5js

创意标准

这是在浏览器里渲染的视觉艺术。Pretext 返回数字;你来画东西。

  • 不要交付一个 "hello world" demo。 hello-orb-flow.html 模板是起点。每个交付的 demo 必须加有意的颜色、动效、构图,以及一个用户没要求但会欣赏的视觉细节。
  • 暗背景、暖核心、考究的调色板。 经典黑底琥珀(CRT/终端)可行,但炭底冷白(编辑感)和去饱和粉彩(risograph)也行。选一个并坚持。
  • 比例字体才是重点。 Pretext 的整个调性就是"不等宽"——拥抱它。用 Iowan Old Style、Inter、JetBrains Mono、Helvetica Neue 或可变字体。绝不默认无衬线。
  • 真实源文本,不要 lorem ipsum。 语料要有意义。短宣言、诗、真实源码、一段捡到的文字、库自己的 README——绝不 lorem ipsum。
  • 首帧即卓越。 无加载态、无空白帧。demo 打开瞬间就必须看起来可交付。

技术栈

每个 demo 一个自包含 HTML 文件。无构建步骤。

层工具用途
核心@chenglou/pretext,经 esm.sh CDN文本测量 + 行布局
渲染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/pretext@0.0.6";
</script>

固定版本号。写作时是 @0.0.6——若 demo 行为不对,查 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 上病毒式传播的 demo。

值得知道的辅助函数

  • measureLineStats(prepared, maxWidth) → { lineCount, maxLineWidth }——最宽行,即多行紧裹宽度。
  • walkLineRanges(prepared, maxWidth, callback)——不分配字符串地迭代行。当你不需要字符、只要对字素做统计/物理时用。
  • @chenglou/pretext/rich-inline——同一系统,但用于混合字体/chip/@提及的段落。从子路径导入。

Demo 配方模式

社区库(见 references/patterns.md)聚成几个强模式。挑一个变奏——除非要求否则别发明新类别。

模式关键 API示例点子
绕障碍重排layoutNextLineRange + 逐行宽度函数编辑段落从拖拽光标精灵两侧分开
文字即几何游戏layoutWithLines + 逐行碰撞矩形每块砖是一个被测单词的打砖块
碎裂/粒子walkLineRanges → 逐字素 (x,y) → 物理点击时炸成字母的句子
ASCII 障碍字体layoutNextLineRange + 测得的逐行障碍跨度位图 ASCII logo、形状变形、可拖拽线框物体,让文字按其真实几何打开
编辑多栏逐栏 layoutNextLineRange + 共享光标带 pull quote 的动画杂志跨页
动态字体layoutWithLines + 随时间逐行变换星球大战字幕滚动、波浪、弹跳、glitch
多行紧裹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() 是昂贵调用。每个 文本+字体 对只做一次。缓存 handle。
  • 缩放时只重跑 layout() / layoutWithLines()——绝不重新 prepare。
  • 对文字不变但几何变的逐帧动画,紧循环里的 layoutNextLineRange 对正常长度段落每帧 60fps 都够便宜。
  • 逐帧渲染 ASCII 遮罩时,保留一个单元格缓冲(Uint8Array/类型数组),从单元格或投影几何推导测得的逐行障碍跨度,合并跨度,再在画文字前喂给 layoutNextLineRange。
  • 让视觉动画和布局动画耦合。若球变成立方体,用同一个值补间渲染的单元格缓冲和障碍跨度;否则 demo 看起来像贴上去的,而非物理重排。
  • 淡入淡出时,优先层透明度而非改字形强度或障碍尺度。把临时 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. 在动画循环里重新 prepare。 只有 layout* 便宜。每帧重调 prepare 会拖垮性能。把 prepared handle 留在模块作用域。

  3. 字素切分忘了 Intl.Segmenter。 Emoji、组合符、CJK——"é".split("") 给你两个字符。采样单个可见字形时用 new Intl.Segmenter(undefined, { granularity: "grapheme" })。

  4. rich-inline 里 break: 'never' 的 chip 没给 extraWidth。 若你对原子 chip/提及用 break: 'never',还必须为胶囊内边距给 extraWidth——否则 chip 金属边溢出容器。

  5. 从 unpkg 用 @chenglou/pretext 且入口是纯 TypeScript。 用 esm.sh——它自动把 TS 导出编成浏览器可用的 ESM。unpkg 会 404 或发原始 TS。

  6. 等宽回退悄悄抹掉整个意义。 看到等宽外观输出的用户,CSS font-family 常回退到了 monospace。用 DevTools 验证实际渲染字体。

  7. 绕形状流动时,跳行 vs 调宽度。 若这行走廊太窄放不下一行,跳过该行(y += lineHeight; continue;),而不是给 layoutNextLineRange 传一个极小 maxWidth——pretext 会返回一字素行,看着像坏了。

  8. 交付一个冷冰冰的 demo。 默认首帧看着像教程级。加:暗角、微妙扫描线、闲置自动动效、一个精心选的交互响应(拖、悬停、滚、点)。没有这些,"酷 pretext demo" 就落成"实习生复刻 README"。

验证清单

  •  demo 是单个自包含 .html 文件——双击或 python -m http.server 可开
  •  @chenglou/pretext 经 esm.sh 导入且版本固定
  •  语料是真实散文,不是 lorem ipsum,且匹配 demo 概念
  •  传给 prepare 的字体串与 CSS 字体严格一致
  •  prepare() / prepareWithSegments() 调一次,不是每帧
  •  暗背景 + 考究调色板——不是默认白 canvas
  •  至少一个交互响应(拖/悬停/滚/点)或闲置自动动效
  •  本地用 python -m http.server 测过,确认无控制台错误
  •  中端笔记本上 60fps(或记录了优雅降级)
  •  一个用户没要求的"多走一步"细节

参考:社区 Demo

clone 这些找灵感/模式(均为 MIT 类,链接自 pretext.cool):

  • Pretext Breaker——单词砖块打砖块——github.com/rinesh/pretext-breaker
  • 俄罗斯方块 × 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

官方 playground:chenglou.me/pretext——手风琴、气泡、动态布局、编辑引擎、对齐对比、瀑布流、markdown 聊天、富笔记。