
Hello World
你好,世界
排版系统压力测试样张(typography stress test)
这不是一篇给人读的文章。它把文章渲染链路上容易出问题的情况集中堆到一页,用来暴露布局缺陷。
渲染链路:
remark-math→rehypeArticleImage→rehypeKatex→rehypeCodeblock;样式是@tailwindcss/typography加prose.css覆盖;阅读列宽--container-prose: 720px。每节先用一个引用块说明本节点在测什么,后面是极端样例。已知高风险点是第 4 节表格和第 5 节代码块——
prose.css里.prose table与.prose pre都没有 overflow 包裹容器。
1. 溢出与断行
测什么:阅读列宽是硬约束。任何「没有断行机会」或「断行机会被算错」的内容,都会撑破 720px 或制造整页横向滚动。
1.1 无空格超长英文单词——单个 token 111 字符,检验 overflow-wrap / word-break 的实际取值:
pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicro
1.2 超长 URL——整串没有任何可断行字符,只能靠 overflow-wrap: anywhere 兜底:
1.3 SHA-256 样式长十六进制串——64 字符连续,数字与字母混排:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
1.4 长数字串——无千分位分隔符,部分等宽字体下完全不换行:
123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
1.5 连续中文长段——CJK 无空格,依赖 line-break: normal 的逐字断行。这是一段刻意写得很长的中文,用来确认中文在 720px 阅读列里能正常逐字折行而不会因为标点、数字或者拉丁字母的介入而整体溢出或者出现整行只有一两个字的丑陋断行。排版系统在处理中文时最容易出问题的地方是标点挤压与行首行尾禁则,其次是中英文之间的自动间距,再其次是长数字与长英文单词混在中文里时断行机会的判定。这一段还需要继续拉长,目的是让它至少占满若干行,从而让行高、行距、段间距的异常被肉眼和测量同时看见,而不是被一行内消化掉。
1.6 CJK 与拉丁字母无空格混排——检验自动间距与断行机会的判定:
中文English日本語Русский中文English日本語Русский中文English日本語Русский中文English日本語Русский中文English日本語Русский中文English日本語Русский
1.7 软连字符(­ 实体,渲染为 U+00AD)——浏览器只在必要时才断开:
pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis
1.8 无空格长标点串——标点通常被允许在任意位置断开,这里检验是否真的会断:
————————————————————————————————————————————————————————————————————————————————————————————————————————
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2. 多语言与双向文本
测什么:bidi 算法、字素簇(grapheme cluster)宽度计算、组合附加符对行高的撑高效果,以及全角/半角标点的混排。
2.1 四种站点语言同段混排:
中文内容 Chinese content Русский текст 日本語のテキスト が同じ段落に混在しています。Смешанный абзац с китайским, английским и японским текстом.
2.2 阿拉伯语与希伯来语 RTL 嵌入(混在 LTR 段落中):
阿拉伯语:النص العربي هنا مع أرقام 12345 وكلمات Latin. 希伯来语:טקסט עברי כאן עם מספרים 67890 ומילים Latin.
2.3 RTL 整段(段落方向由内容决定,检验是否出现行尾标点错位):
النص العربي الطويل يجب أن يظهر من اليمين إلى اليسار بشكل صحيح,وعلامات الترقيم يجب أن تبقى في مكانها الصحيح داخل الجملة.
2.4 emoji ZWJ 序列(单个字素簇由多个码位组成,宽度易算错):
家庭:👨👩👧👦 情侣:👩❤️👨 彩虹旗:🏳️🌈 女宇航员:👩🚀 携带病毒:👨🦰
2.5 肤色修饰符与变体选择符:
👍 👍🏽 👍🏿 ❤ ❤️ ☺ ☺️ ⚠ ⚠️
2.6 组合附加符(Zalgo 式堆叠)——4 个组合标记叠在一个基字符上,极易撑高行盒:
Z̸̷̶̵a̸̷̶̵l̸̷̶̵g̸̷̶̵o̸̷̶̵ 与 N̸̷̶̵o̸̷̶̵r̸̷̶̵m̸̷̶̵a̸̷̶̵l̸̷̶̵
2.7 全角与半角标点混排:
全角:你好,世界!这是「引号」和(括号)。 半角:你好,世界!这是”引号”和(括号). 混合:你好, 世界!这是”引号”和(括号).
2.8 全角空格(U+3000,不参与常规空白折叠):
中文 全角空格 分隔 测试
2.9 日语振假名(<ruby> 注音,影响行高):
漢字と振り仮名のテスト。
3. 标题层级
测什么:
prose.css只显式定义了h2/h3的字号与间距,并给h2、h3、h4加了scroll-margin-top。h5、h6完全落到 typography 默认值上,是层级断档处。
3.1 这一节内部先用 h3(跳过 h2 之后直接 h3 是正常的)
3.1.1 从 h3 直接跳到 h4,层级连续
3.1.2 这里是 h5——prose.css 未定义,落回 typography 默认
3.1.3 这里是 h6——同上,并且它是可用的最深层级
3.1.4 层级跳级测试(下面是 h2)
3.2 从 h4 直接跳回 h2(跳级,检验视觉层级是否还能区分)
3.2.1 从 h2 直接跳到 h4(跳级)
3.2.2 从 h4 跳到 h5
3.3 超长标题——标题里放一个 111 字符的不可断行 token:
3.3.1 超长标题测试pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicro
3.4 标题内含行内标记:
3.4.1 标题里有 行内代码、链接、 公式与 emoji 🎌
3.5 连续标题之间没有正文:
3.5.1 第一个连续标题
3.5.2 第二个连续标题
3.5.3 第三个连续标题
3.6 标题紧贴上一节末尾:
3.6.1 这个标题的上面直接是加粗段落,没有空行以外的缓冲
4. 表格
测什么:
.prose table { width: 100% }没有任何横向滚动包裹容器。列数一多,要么被压到不可读,要么直接撑破 720px 阅读列并制造整页横向滚动。这是本样张最可能直接暴露缺陷的一节。
4.1 二十列超宽表(刻意把列头写成中等长度):
| 列一 | 列二 | 列三 | 列四 | 列五 | 列六 | 列七 | 列八 | 列九 | 列十 | 列十一 | 列十二 | 列十三 | 列十四 | 列十五 | 列十六 | 列十七 | 列十八 | 列十九 | 列二十 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 | 值 |
4.2 单列表格:
| 唯一的一列 |
|---|
| 只有一列 |
| 第二行 |
4.3 只有表头没有数据行:
| 表头甲 | 表头乙 |
|---|
4.4 空单元格与纯度极低的单元格:
| 全空 | 半空 | |
|---|---|---|
| 有值 | ||
| 有值 | 单字 | |
4.5 单元格内放不可断行长 token:
| 场景 | 内容 |
|---|---|
| 长单词 | pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis |
| 长 URL | https://example.com/a/very/long/path/without/any/break/opportunity/at/all?query=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
| 长数字 | 1234567890123456789012345678901234567890 |
4.6 单元格内的行内标记(代码、链接、公式、删除线):
| 类型 | 样例 |
|---|---|
| 行内代码 | const x: Array<Map<string, number>> = [] |
| 链接 | 一个很长的链接文字用来测试表格单元格里的换行行为 |
| 公式 | 与 |
| 删除线 | |
| 加粗斜体 | 粗 斜 粗斜 |
4.7 单元格内强制换行(<br>):
| 步骤 | 说明 |
|---|---|
| 第一步 | 先做这个 然后做那个 最后收尾 |
| 第二步 | 单行说明 |
4.8 列对齐语法(左、居中、右):
| 左对齐 | 居中 | 右对齐 | 默认 |
|---|---|---|---|
| a | b | c | d |
| 长长的内容会怎样 | 长长的内容会怎样 | 长长的内容会怎样 | 长长的内容会怎样 |
| 1 | 22 | 333 | 4444 |
4.9 空表头:
| 甲 | 乙 | 丙 |
| 丁 | 戊 | 己 |
4.10 表格嵌在引用块里:
引用内的表头 第二列 引用内的单元格 内容 第二行 内容
4.11 表格嵌在列表项里:
-
列表项里的表格:
列表内表头 第二列 值 值 -
同级第二个列表项
5. 代码块
测什么:
rehypeCodeblock会把每个pre包成.code-block卡片(带语言标签与复制按钮),shikiConfig同时开了wrap: true(软换行),而prose.css又给.prose pre写了overflow-x: auto(横向滚动)。这两条规则语义冲突,超长行到底是换行还是滚动,这一节直接给出答案。
5.1 超长单行代码(300 字符以上,无自然断点):
const result = someVeryLongFunctionName(argumentNumberOne, argumentNumberTwo, argumentNumberThree, argumentNumberFour, argumentNumberFive, argumentNumberSix, argumentNumberSeven, argumentNumberEight)?.anotherMethod()?.yetAnotherMethod() ?? fallbackValue;5.2 无语言标记的代码块(语言标签应回退为 TEXT):
这是一个没有写语言标记的代码块。
第二行。
缩进的一行。5.3 未知语言(Shiki 无法高亮,检验是否仍然安全渲染):
const x = 1;
unknown language highlighting fallback5.4 空代码块:
5.5 代码块紧跟标题与列表(检验上下边距折叠):
紧跟在标题下面的代码块:
pnpm install- 列表项一:
json
{ "key": "value" } - 列表项二
5.6 四反引号包裹三反引号(嵌套围栏):
```javascript
const nested = true;
```5.7 内容含 HTML 结束标签、反引号与实体(检验转义与复制按钮的 data-code 属性):
<script>alert("xss")</script>
<code>backtick ` inside</code>
& < > " 😀
</code>5.8 内容含大量引号、反斜杠与换行(复制按钮把原始文本塞进 data-code 属性,是转义高危点):
"双引号" '单引号' `反引号` \反斜杠\ \\双反斜杠\\
属性注入尝试: " onmouseover="alert(1)
换行后的第二行
制表符开头的一行
行尾有尾随空格 5.9 多语言高亮冒烟(逐语言一行,确认 Shiki 双主题不报错):
interface Article<T extends Record<string, unknown>> { id: string; data: T }def fib(n: int) -> int:
return n if n < 2 else fib(n - 1) + fib(n - 2)fn main() { let v: Vec<i32> = (0..10).collect(); println!("{:?}", v); }.prose :where(h2, h3, h4) { scroll-margin-top: var(--spacing-2xl); }key: value
nested:
- item5.10 代码块后紧跟表格与引用:
code| 紧跟代码块的表格 | 第二列 |
|---|---|
| 值 | 值 |
紧跟表格的引用块
6. 行内代码与转义
测什么:行内代码的 padding/圆角在行首行尾是否吃字,HTML 实体在代码内外是否被区别对待,以及硬换行的两种写法是否都生效。
6.1 单反引号行内代码:const x = 1
6.2 双反引号包裹含反引号的内容:这里有 ` 一个反引号
6.3 行内代码含尖括号与与号(实体在代码内不应被解码):<div class="a">&</div> 与 <T extends object>
6.4 行内代码很长(接近阅读列宽):
这个是-一个-非常-长的-行内-代码-片段-用来-测试-行内-代码-在-接近-阅读-列-宽-度-时-是否-会-溢出-或者-被-正常-换-行-处理
6.5 行首与行尾的行内代码:行首代码 中间是普通文字 行尾代码
6.6 反斜杠转义:*不是斜体* 与 _不是下划线_ 与 [不是链接] 与 `不是代码`
6.7 HTML 实体:& < > ” ’ 间隔 © ® ™ — …
6.8 数字实体:😀 😀 中文
6.9 硬换行(行尾两个空格):
这一行末尾有两个空格,
所以这一行应该另起一行。
6.10 硬换行(行尾反斜杠):
这一行末尾有反斜杠
所以这一行也应该另起一行。
7. 数学公式
测什么:
remark-math加rehype-katex。高风险点四处:(a) 块级公式超宽时.katex-display的横向滚动是否生效;(b) 公式与中文之间没有空格时的间距;(c)$被误判为数学定界符(货币场景);(d) 非法 LaTeX 是否降级为红色错误 span 而不炸构建。
7.1 行内公式:质能方程 与欧拉恒等式 。
7.2 行内公式紧贴中文(无空格):这个公式紧贴中文,右边也紧贴。
7.3 块级公式:
7.4 相邻的两个块级公式(检验块间距是否稳定):
7.5 超宽块级公式(宽度远超 720px,检验 .katex-display { overflow-x: auto }):
7.6 超宽矩阵:
7.7 公式内使用 \text{} 放中文:
7.8 公式在表格、列表、引用块里:
| 位置 | 公式 |
|---|---|
| 表格内行内 | |
| 表格内块级 |
-
列表内的行内公式:
-
列表内的块级公式:
引用块内的行内公式 ,以及下面这个块级公式:
7.9 $ 被误判为数学定界符(货币场景,经典坑):
从 200,再从 150。
转义之后:$100 涨到 $200。
7.10 单个 $ 不成对:
单独一个 后面没有内容。
7.11 非法 LaTeX(检验 KaTeX 降级为红色错误 span 而非中断构建):
\frac{1}{ 与 与 \begin{matrix} 未闭合
8. 图片
测什么:
rehypeArticleImage只在「段落里恰好一张图、没有其他内容、alt 非空」时才包成figure;远程图额外加loading="lazy"并保留在p里;title会被提升为figcaption。这一节把每个分支都走一遍。
8.1 本地图 + 有 alt(应包成 figure,无 figcaption,可点开 Lightbox):

8.2 本地图 + 有 alt + 有 title(应包成 figure,title 变成 figcaption):

8.3 本地图 + 空 alt(不转 figure、不加 Lightbox,保持裸 p > img):
8.4 图片加载失败(两条,分别走本地与远程分支;两者都应触发 ArticleImageEnhancer 把 src 换成 /images/image-broken.svg 占位图):
8.4.1 本地分支——路径必须真实存在才能过 CI 的离线死链检查(check:links 用 lychee --offline,本地路径找不到文件直接 fail),所以这里用「文件存在但不是图片」制造 MIME 解码失败:
8.4.2 远程分支——.invalid 是 RFC 2606 保留 TLD,永不解析;lychee --offline 会跳过全部 http(s) 链接,所以不会拖垮 CI:

8.5 远程图(应保留在 p 内并加 loading="lazy",不转 figure):
8.6 图片与文字同段(段落里有非图片内容,不应转 figure):
这是一段普通文字,后面直接跟一张图
,图片之后还有文字。
8.7 同段两张图片(图片数量不等于 1,不应转 figure):

8.8 图片被链接包裹(顶层子节点是 a 而不是 img,不应转 figure):
8.9 同一段落组里的多张独立图片(Lightbox 应按 content-images 分组串起来):


9. 列表
测什么:深层嵌套时的缩进与标记对齐、宽松列表与紧凑列表的间距差、以及把代码块/表格/引用塞进列表项时的缩进正确性。
9.1 六层嵌套无序列表:
- 第一层
- 第二层
- 第三层
- 第四层
- 第五层
- 第六层——检验六层之后标记是否还在可视区内,以及文本列是否被挤到过窄
- 第五层
- 第四层
- 第三层
- 第二层
9.2 深层嵌套有序列表(检验多位数字的对齐):
- 第一层
- 第二层
- 第三层
- 第四层
- 第五层
- 第六层
- 第七层
- 第八层
- 第七层
- 第六层
- 第五层
- 第四层
- 第三层
- 第二层
9.3 有序列表起始值不是 1:
- 从五开始
- 第六项
- 第七项
9.4 大序号(检验三位数序号是否挤压内容):
- 第九十八项
- 第九十九项
- 第一百项
- 第一百零一项
9.5 无序与有序混嵌:
- 外层无序
- 内层有序第一项
- 内层有序第二项
- 再内层无序
- 再再内层有序
- 再内层无序
- 外层第二项
9.6 任务列表(GFM):
- 已完成的任务
- 未完成的任务
- 另一个已完成的任务,文字比较长,用来验证勾选框与文字的基线是否对齐,以及长文本换行时第二行是否缩进到文字列
- 嵌套的任务项
- 嵌套的已完成项
9.7 紧凑列表与宽松列表的间距对比:
紧凑:
- 一
- 二
- 三
宽松:
-
一
-
二
-
三
9.8 列表项内含代码块:
-
第一步,先装依赖:
bashpnpm install -
第二步,再起服务:
bashpnpm dev -
第三步,结束
9.9 列表项内含引用与表格:
-
含引用块的列表项:
这是列表项内部的引用块,检验左边框是否与列表标记正确错开。
-
含表格的列表项:
表头 表头 值 值
9.10 列表项内含不可断行长词:
- pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis
- 第二项正常
9.11 列表项内含多段落:
-
第一段落在列表项内部。
第二段落也在同一个列表项内部,应当与第一段保持相同的缩进。
-
第二个列表项
10. 引用块与其他块级元素
测什么:多层引用的左边框叠加、引用块内部放重块级元素、原始 HTML 是否被
rehypeRaw正常解析,以及连续hr的间距折叠。
10.1 四层嵌套引用:
第一层引用
第二层引用
第三层引用
第四层引用——检验四层左边框叠加后正文列还剩多少宽度
10.2 引用块内放代码块、列表与表格:
引用块里的列表:
- 引用内第一项
- 引用内第二项
引用块里的代码:
pythonprint("hello from blockquote")引用块里的表格:
表头 表头 值 值
10.3 引用块内不可断行长词:
引用块里的超长 token:pneumonoultramicroscopicsilicovolcanoconiosispneumonoultramicroscopicsilicovolcanoconiosis
10.4 连续三条分隔线:
10.5 <details> / <summary> 折叠块:
点开查看折叠内容(原始 HTML,由 rehypeRaw 解析)
折叠区域内部的加粗文字、行内代码,以及一个列表:
- 折叠内的列表项
- 第二项
10.6 行内语义标签(<kbd> / <mark> / <abbr> / <sub> / <sup> / <s> / <u> / <small>):
按 Ctrl + Shift + P 打开命令面板;这段是 高亮标记;这个缩写 CSS 带提示;化学式 H2O 与 CO2;上标 x2 与 an+1;这是 删除线标签、下划线标签 与 小号文字。
10.7 上标与下标连续堆叠(检验行高是否被撑开):
x12345 与 y12345
10.8 描述列表(<dl> / <dt> / <dd>,Markdown 无原生语法):
- 术语一
- 术语一的解释说明文字。
- 术语二
- 术语二的解释说明文字,写得稍微长一些以便观察缩进与行距。
10.9 原始 HTML 块级元素(<div> / <figure> 手写):
这是一个手写的 <div>,带有内联 style。检验内联样式是否被保留、以及它是否被限制在阅读列内。
10.10 HTML 注释(应当被丢弃,不出现在输出里):
10.11 表格后紧跟分隔线再紧跟标题:
| 表头 | 表头 |
|---|---|
| 值 | 值 |
10.11.1 分隔线之后的四级标题
11. 链接与脚注
测什么:超长链接文本与超长 URL 的断行、自动链接、锚点链接、链接内的行内代码,以及 GFM 脚注的编号与回跳。
11.1 超长链接文本:
这是一段刻意写得非常长的链接文字,用来验证链接在 720px 阅读列里换行时下划线的表现,以及第二行是否仍然保持正确的行高与下划线偏移
11.2 超长 URL 作为链接目标(链接文字也很长):
11.3 自动链接:
https://example.com/auto/link/with/a/fairly/long/path/that/should/wrap/somewhere
11.4 邮件链接:
11.5 纯文本形式的裸 URL(不可点击,检验断行):
https://example.com/bare-url-not-a-link
11.6 锚点链接:
11.7 链接内包含行内代码与加粗:
11.8 链接中嵌入图片(同时测图片与链接分支):
11.9 GFM 脚注:
这里引用一个脚注1,再引用一个更长的脚注2,以及一个不存在的脚注引用[^note-missing]。
未在正文中被引用的脚注定义也应出现在列表里3。
12. 本仓库特有的风险点
测什么:这些不是通用 Markdown 边界,而是本项目渲染链路里已经存在的、值得被内容触发的具体风险。
12.1 Smartypants 会把裸 ASCII 标点转成排印符号(Astro 默认开启 remark-smartypants)。下面是刻意保留的裸 ASCII 序列,用来观察实际替换结果:
直引号 “double quoted” 与 ‘single quoted’;破折号 a — b 与 a --- b;省略号 wait…;反引号 double backtick。
12.2 复制按钮的文本来源:CodeBlockEnhancer 优先取 code.textContent,回退到 data-code 属性。下面这个代码块同时包含标签、实体与超长行,点复制后应当得到与源码一致的文本:
<div class="a">&</div><span data-x="1">text</span>12.3 超长内容与磨砂面板:文章页正文位于带 backdrop-filter 的磨砂前景里。下面是刻意拉长的大段文字,用来观察在磨砂层内滚动长内容时的合成开销与是否出现采样断裂:
第 12.3 节的长段落之一。这一段的作用是提供足够的滚动高度,让阅读进度条与磨砂前景同时处于活动状态。排版系统在长文档下的常见问题是:滚动高度计算错误导致进度条提前到达 100%,或者固定定位元素在 backdrop-filter 制造的包含块里定位错乱。这一段需要足够长,所以下面继续填充内容,重复若干次以确保页面高度足够。排版系统在长文档下的常见问题是:滚动高度计算错误导致进度条提前到达 100%,或者固定定位元素在 backdrop-filter 制造的包含块里定位错乱。
第 12.3 节的长段落之二。这一段继续提供滚动高度。排版系统在长文档下的常见问题是:滚动高度计算错误导致进度条提前到达 100%,或者固定定位元素在 backdrop-filter 制造的包含块里定位错乱。这一段需要足够长,所以下面继续填充内容,重复若干次以确保页面高度足够。
第 12.3 节的长段落之三。这一段继续提供滚动高度。排版系统在长文档下的常见问题是:滚动高度计算错误导致进度条提前到达 100%,或者固定定位元素在 backdrop-filter 制造的包含块里定位错乱。这一段需要足够长,所以下面继续填充内容,重复若干次以确保页面高度足够。
12.4 Lightbox 分组与标题:下面三张图同属一个段落组,点开任意一张应当能在组内前后切换,并且第二张应带说明文字:


12.5 层级锚点跳转:prose.css 给 h2、h3、h4 设置了 scroll-margin-top,但 h5、h6 没有。点击下面两个链接观察跳转后标题是否被顶栏遮挡:
12.6 收尾标记:样张到此结束。如果以上所有小节都渲染正常,说明排版系统在溢出、断行、bidi、表格、代码块、公式、图片、列表、引用、脚注与原始 HTML 这些维度上都已经有基本韧性;反过来,任何一处出现横向滚动、内容被裁切、行高错乱或者控制台报错,就是这次压力测试要交付的结论。
