跳到主要內容

Pretext

在使用 @chenglou/pretext 構建創意瀏覽器演示時使用 — 這是一種無需 DOM 的文本佈局方案,適用於 ASCII 藝術、圍繞障礙物的排版流、以文本為幾何形狀的遊戲、動態排版以及由文本驅動的生成藝術。默認生成單文件 HTML 演示。

技能元數據

來源捆綁(默認安裝)
路徑skills/creative/pretext
版本1.0.0
作者Hermes Agent
許可證MIT
平臺linux, macos, windows
標籤creative-coding, typography, pretext, ascii-art, canvas, generative, text-layout, kinetic-typography
相關技能p5js, claude-design, excalidraw, architecture-diagram

參考:完整 SKILL.md

信息

以下是 Hermes 在觸發此技能時加載的完整技能定義。這是技能激活時代理所看到的指令。

Pretext 創意演示

概述

@chenglou/pretext 是由 Cheng Lou(React 核心成員、ReasonML、Midjourney)開發的一個 15KB、零依賴的 TypeScript 庫,用於無需 DOM 的多行文本測量和佈局。它只做一件事:給定 (text, font, width),返回換行位置、每行寬度、每個字素(grapheme)的位置以及總高度 — 全部通過 canvas 測量完成,無需重排(reflow)。

這聽起來像是底層基礎設施。其實不然。因為它快速且基於幾何計算,所以是一個創意原語:你可以讓段落以 60fps 的速度圍繞移動的角色重新排版,構建關卡幾何形狀由真實單詞組成的遊戲,通過散文驅動 ASCII 徽標,將文本粉碎為具有精確每個字素起始位置的粒子,或者打包收縮包裹的多行 UI 而無需任何 getBoundingClientRect 抖動。

此技能的存在是為了讓 Hermes 能夠用它製作酷炫的演示 — 即人們發佈到 X(原 Twitter)的那種類型。請參閱 pretext.coolchenglou.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 / 終端)可行,冷白色炭灰底(編輯風格)和低飽和度 pastel 色(risograph 印刷風格)也可行。選擇一種並堅持到底。
  • 比例字體是重點。 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/pretext@0.0.6";
</script>

鎖定版本。撰寫時為 @0.0.6 — 如果演示行為異常,請檢查 npm 獲取最新版本。

兩種用例

幾乎所有情況都可歸結為以下兩種模式之一。掌握兩者。

用例 1 — 測量,然後使用 CSS/DOM 渲染

const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);

你仍然讓瀏覽器繪製文本。Pretext 僅告知你在給定寬度下盒子的高度,無需讀取 DOM。適用場景:

  • 包含換行文本的虛擬化列表
  • 需要精確卡片高度的瀑布流佈局
  • 開發時檢查“此標籤是否適配?”
  • 防止遠程文本加載時的佈局偏移

保持 fontletterSpacing 與你的 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 + 隨時間變化的每行變換星球大戰滾動字幕、波浪、彈跳、故障藝術效果
多行收縮包裹measureLineStats自動調整至最緊湊容器的引用卡片

參見 templates/donut-orbit.htmltemplates/hello-orb-flow.html 獲取可用的單文件起步模板。

工作流

  1. 從上述表格中選擇一種模式,基於用戶的需求簡報。
  2. 從模板開始
    • templates/hello-orb-flow.html — 文本圍繞移動球體重排(圍繞障礙物重排模式)
    • templates/donut-orbit.html — 高級示例:已測量的 ASCII 標誌障礙物、可拖動的線框球體/立方體、變形形狀場、可選中的 DOM 文本以及僅限開發的控件
    • 使用 write_file 將內容寫入 /tmp/ 或用戶工作區中的新 .html 文件。
  3. 替換語料庫,使用符合需求簡報有意選取的內容。使用真實散文,10-100 個句子,不要使用 Lorem Ipsum。
  4. 調整美學風格 — 字體、調色板、構圖、交互。這是核心工作,不要跳過。
  5. 本地驗證
    cd <dir-with-html> && python3 -m http.server 8765
    # then open http://localhost:8765/<file>.html
  6. 檢查控制檯 — 如果 prepareWithSegments 被傳入錯誤的 font 字符串,pretext 將拋出錯誤;Intl.Segmenter 在所有現代瀏覽器中均可用。
  7. 向用戶展示文件路徑,而不僅僅是代碼 — 他們想要打開它。

性能說明

  • prepare() / prepareWithSegments() 是開銷較大的調用。對於每個文本+字體組合,僅執行 一次。緩存該句柄。
  • 在調整大小時,僅重新運行 layout() / layoutWithLines() — 切勿重新準備(re-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. 在動畫循環中重複準備(prepare)。 只有 layout* 操作是低開銷的。每幀重新調用 prepare 會嚴重損害性能。請將準備好的句柄保持在模塊作用域內。

  3. 忘記使用 Intl.Segmenter 進行字素(grapheme)分割。 對於 Emoji、組合標記、CJK 字符——"é".split("") 會得到兩個字符。在採樣單個可見字形時,請使用 new Intl.Segmenter(undefined, { granularity: "grapheme" })

  4. 使用 break: 'never' 的芯片(chip)未設置 extraWidthrich-inline 中,如果對原子芯片/提及(mention)使用 break: 'never',還必須為藥丸狀內邊距提供 extraWidth——否則芯片的裝飾部分會溢出容器。

  5. unpkg 引入僅包含 TypeScript 入口的 @chenglou/pretext 請使用 esm.sh——它會自動將 TS 導出編譯為瀏覽器可用的 ESM。unpkg 會返回 404 或提供原始 TS 文件。

  6. 等寬字體回退 silently 抹殺了整體效果。 用戶看到類似等寬字體的輸出,通常是因為 CSS font-family 回退到了 monospace。請通過開發者工具驗證實際渲染的字體。

  7. 環繞形狀排版時,跳過行 vs 調整寬度。 如果當前行的通道太窄而無法容納一行文本,請跳過該行y += lineHeight; continue;),而不是向 layoutNextLineRange 傳遞極小的 maxWidth——否則 pretext 會返回看起來破碎的單字素行。

  8. 發佈未經打磨的演示。 默認的首屏繪製效果看起來像教程級別。請添加:暗角效果、細微的掃描線、空閒自動運動、一個精心選擇的交互響應(拖拽、懸停、滾動、點擊)。如果沒有這些,“酷炫的 pretext 演示”會被視為“README 的實習生復現版”。

驗證清單

  • 演示是一個獨立的 .html 文件——可通過雙擊或 python3 -m http.server 打開
  • 通過 esm.sh 引入 @chenglou/pretext 並固定版本
  • 語料庫是真實的散文,而非 Lorem Ipsum,且與演示概念相符
  • 傳遞給 prepare 的字體字符串與 CSS 字體完全匹配
  • prepare() / prepareWithSegments() 僅調用一次,而非每幀調用
  • 深色背景 + 經過考究的調色板——而非默認的白色畫布
  • 至少有一個交互響應(拖拽 / 懸停 / 滾動 / 點擊)或空閒自動運動
  • 已在本地使用 python3 -m http.server 測試並確認無控制檯錯誤
  • 在中端筆記本電腦上達到 60fps(或已記錄優雅降級方案)
  • 一個用戶未要求的“額外用心”細節

參考:社區演示

克隆這些項目以獲取靈感/模式(均為類 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

官方遊樂場:chenglou.me/pretext —— accordion, bubbles, dynamic-layout, editorial-engine, justification-comparison, masonry, markdown-chat, rich-note.