# PDF 格式规范(scholia-notes) **规范性文件。** 这份文档定义「Markdown 底稿 → Typst/PDF」的语义对应关系、 预算与禁止事项。工具(`zju_build_study_notes` 与 `zju_write_note`)按这份规范执行与校验; 人写底稿时也按这份规范写。冲突时以本文件为准。 > **适用范围**:本规范同时管两条链路——**填空式学习笔记**(`zju_build_study_notes`) > 与**阅读版讲义**(`zju_write_note` 自动排版的同名 PDF)。两者共用同一套标记语义、 > 配色与版心参数;区别只在阅读版不挖空:正文里的 `____` 仍画成灰色填空线, > 但练习块用 3 行书写横线、不挖空。冲突时以本文件为准。 ## 0. 一句话原则 > **正文是主角,框框是例外。** 排版的一切取舍都服从这一条:读者要的是**能读下去的文字**,不是一堆彩色方块。 一个框框唯一的正当理由是「**这条内容需要被编号、被后面的内容回指**」—— 不是为了「它看起来像个定理」,也不是为了「让页面显得丰满」。 ## 1. 源文件约定 | 项 | 约定 | | :--- | :--- | | 文件 | 一份笔记一个 `.md`(底稿),由 LLM 直接产出 | | 目的 | **省 token**:底稿里不写任何 Typst 语法、样式、颜色、编号 | | 公式 | **写 LaTeX**(`$…$` / `$$…$$`),工具负责转成 Typst 数学 | | 表格 | **写 GFM 表格**(`\| a \| b \|`),编译时转成 Typst `table` | | 列表 | 照常写 Markdown(`-` / `1.`),编译时转成 Typst 列表 | | 行内代码 | 照常写反引号(`` `[00:20]` ``),编译时原样进 Typst raw,**不转义** | | 底稿的读者 | 人和 LLM 都要能独立读懂并直接改 | | 排版实现 | 底稿是内容的唯一事实来源;`main.typ` 是它的一次排版实现 | > **为什么公式写 LaTeX 而不是 Typst**:LLM 写 LaTeX 的准确率远高于写 Typst 数学, > 而且 LaTeX 是通用写法、换别的工具也能用。Typst 数学的坑(`a_(ij)`、多字母标识符、 > `\overline`)由工具在编译前统一改写,改不动的会**点名报出来**,不静默吞掉。 > > **转换只做一次。** 转换点在 `zju_build_study_notes(mode="compile")`;`mode="draft"` 只搬运结构、**不改写公式**。 > 两处都转会把已经转好的 `frac(…)`/`cal(…)` 当成多字母变量逐字母拆成 `f r a c`/`c a l`, > 编译报 `unknown variable`。所以看到一份全是 LaTeX 的底稿是**正常的**,不要手动改成 Typst。 ### 1.2 正文的排版参数 字号、行距、段距、段首缩进、公式间距、版心与边注栏**不在本文件重复**: 它们是 [`typography-spec.md`](typography-spec.md) §2–§4 的内容,两条链路共用同一套值。 本文件只规定「底稿写什么、映射成什么装置」;底稿里**不写**任何 Typst 语法、样式、 颜色或手写编号。 三条与底稿写法直接相关的实现约定(具体写法与数值见 `typography-spec.md` §3.1): - 正文段首空两格,用行内空白顶首行,不用 `par.first-line-indent`; - 独占一行的公式写成 `$ … $`(两侧留空格),工具会自动补; - 块级公式的上下间距与 `par.spacing` 解耦,显式给出。 ## 2. 语义对应表 「装置」一列是 PDF 上真正出现的东西;「视觉」一列是它的**版面代价**。 | Markdown 构造 | PDF 装置 | 什么时候用 | 视觉 | | :--- | :--- | :--- | :--- | | `# 封面` + `- title: …` 等 | 封面(独立页) | 每份笔记一次 | — | | `## 章节` | 一级标题,自动编号 1 / 2 / 3 | 章节 | 轻 | | `### 小节` | 二级标题,自动编号 1.1 | 小节 | 轻 | | `#### 小小节` | 三级标题 | 很少用 | 轻 | | **普通段落** | **正文(近黑,无底色)** | **主体,绝大多数篇幅** | **无** | | `> 引用` | 引用块(左侧细线 + 灰色小字;多个 `> **字段**:…` 逐条换行) | 讲义抬头的来源 / ASR 说明 | 轻 | | `$…$` / `$$…$$` | 行内 / 块级公式 | 数学 | 中 | | `[直觉] …` | 淡蓝底色块 | 每节一次,讲「人话版」 | 中 | | `[定义] 术语 —— 正文` | 定义框(青绿,编号 Definition *n*) | **需要被回指时** | 重 | | `[定理] 标题 —— 正文` | 定理框(蓝,编号 Theorem *n*) | **需要被回指时** | 重 | | `[引理] …` / `[推论] …` | 引理框 / 推论框(蓝) | 同上 | 重 | | `[公理] …` / `[假设] …` / `[记号] …` | 公理 / 假设 / 记号框(青绿) | 同上 | 重 | | `[例] 标题 —— 正文` | 例框(琥珀) | 值得完整展示的例题 | 重 | | `[证明] …` | 证明段(斜体 `Proof.` + qed 方块) | 有证明 | 轻 | | `[练习] …` | 练习块(琥珀 + 3 行书写位) | 每节 ≤ 1 | 中 | | `[边注] …` | 右栏主动回忆问题(`?`) | 每节 ≤ 1 | 不占版面 | | `[边注!] …` | 右栏提醒(`!`) | 需要强调的提醒 | 不占版面 | | `[强调] …` | 行内淡色加粗 | 重点句子 | 轻 | | `[易错] …` | 行内紫色加粗 | 常见陷阱 | 轻 | | `[弱注] …` | 灰色小字 | 可跳读的补充 | 轻 | | `[分隔]` | 极浅横线 | 段落切分 | 轻 | | `---`(独占一行) | 极浅横线(与 `[分隔]` 同款) | 讲义原文里的水平分隔线;不要手动删 | 轻 | | `____` | 灰色填空线 | **正文里的挖空**(练习块里不画,见下) | 轻 | | `[缺口: …]` | 琥珀色待填空位 | 证明骨架的缺口 | 轻 | | 行尾 `` | 编号 + 交叉引用(`@def:x`) | 需要回指时 | — | | `` | 不渲染(转成 Typst 注释) | 写给自己看的提示 | — | ### 2.1 框框的决策规则 > **填空线只出现在正文里。** 练习块(`[练习]`)里已经有书写横线, > 再叠几条短下划线只会挤在题面里、还折行 —— 工具会**丢弃**练习块里的 `____`。 > 正文里显式写的挖空(如「关键处留空:____」)照常画成灰色填空线。 写到一个结论/定义/例子时,**按顺序问**: 1. **后面会引用它吗?** → 会 ⇒ 用框(这样才有编号可引)。 2. **它是这份笔记的骨架吗?**(整章靠它串起来)→ 是 ⇒ 用框。 3. **都不成立** → **写成普通段落**。 不成立时,用 `[强调]`(蓝)、`[易错]`(紫)或 `[弱注]`(灰)来标重点 —— 它们几乎不占版面,但照样有视觉层级。 > 反面例子:「每个定理都套一个框」——一页六个框,读者只看见六个框,看不见内容。 > 正确做法:全篇只框住**真正被引用**的那几个,其余写成段落。 ### 2.2 预算(每节) | 装置 | 上限 | 说明 | | :--- | :--- | :--- | | 正文段落 | 不限 | 主体 | | `[直觉]` | 1 | 整节只讲一次人话版 | | `[练习]` | 1 | 要给读者留书写位 | | `[边注]` + `[边注!]` | 合计 1~2 | 在右栏,不占正文版面 | | `[强调]` + `[易错]` + `[弱注]` | 合计 2~3 | 行内强调,最省的强调手段 | | `[分隔]` | 1 | 段落切分 | | **框(定义/定理/引理/推论/公理/假设/记号/例)** | **≤ 2** | 所有类型**合计**;只为编号与回指而存在 | `[证明]` 不计入框预算(它没有框)。 「节」按**最小**的那一级标题算:`## 二、分章详解` 下面挂 7 个 `### 2.x` 时,是每个 `2.x` 各自 ≤ 2,而不是整章合计 ≤ 2。工具按这个口径逐节点名超预算。 ### 2.3 全篇比例参考 工具在编译后会给出一个**方框 : 正文段落**的比值。 - 比值 **≤ 1 : 8** 比较稳(大约每八段话最多一个框)。 - 超过 **1 : 4** 基本可以判定「框框滥用」:把非关键的那个降级成普通段落 + `[强调]`。 ## 3. 禁止事项 | 不要 | 为什么 | 改写成 | | :--- | :--- | :--- | | 在底稿里写 Typst 语法(`#note[…]`、`= 标题`) | 底稿要保持「人能读、LLM 能改」的纯 Markdown | 用本文件的标记(`[直觉]` 等) | | 手写章节编号(`## 1.1 小节`、`## 一、…`) | 包装器会自动编号,并且会**剥掉**任何手写前缀(`1.` / `1.2` / `一、` / `(三)`),写了也白写 | 只写标题文字 | | 在底稿里写颜色、字号、缩进 | 样式由模板统一管,底稿不该管样式 | 删掉 | | 依赖工具不认识的环境(`tikz`、`picture` 等) | 常见环境会机械转换,其余只能点名 | 用 `pmatrix` / `bmatrix` / `cases` / `aligned`(工具会转),其余手工改 | | 把 `[定义]` 的正文塞进术语里 | 术语与正文要分开:`[定义] 术语 —— 正文` | 用 `——` 分隔 | | 用 `____` 表示非填空的意思 | `____` 会被渲染成一条填空线 | 改写或用 `[弱注]` | ## 4. 数学的写法 工具在编译前统一把 LaTeX 数学改成 Typst 数学。常见改写: | LaTeX | Typst | 说明 | | :--- | :--- | :--- | | `\frac{a}{b}` | `frac(a, b)` | 分式 | | `\sqrt{x}` / `\sqrt[3]{x}` | `sqrt(x)` / `root(3, x)` | | | `a_{ij}` | `a_(i j)` | 多字母下标必须拆开,否则 Typst 认不出 | | `x^{2}` | `x^(2)` | | | `\operatorname{rank}(A)` | `"rank"(A)` | 多字母算子要加引号 | | `\mathbb{R}` | `RR` | | | `\sum_{i=1}^n a_i` | `sum_(i = 1)^n a_i` | | | `\overline{A}` / `\underline{A}` | `overline(A)` / `underline(A)` | | | `\,dz` | `d z` | 微分约定:`dz` 在 Typst 里是未知变量 | | `\text{…}` / `\mathrm{…}` | `upright("…")` | | | `\begin{pmatrix}…\end{pmatrix}` | `mat(…)` | 矩阵;`bmatrix` / `vmatrix` / `cases` 同理 | | `\begin{aligned}…\end{aligned}` | `a &= b \ c &= d` | Typst 用 `&` 对齐、`\` 换行 | | `\begin{cases}…\end{cases}` | `cases(a & b, c & d)` | 分段函数 | | `\mathcal{L}` / `\mathfrak{g}` | `cal(L)` / `frak(g)` | 花体 | | `AC` / `BVP` | `A C` / `upright("BVP")` | 多字母:变量乘积拆开,缩写转直立文本 | | `e^{-st}` | `e^(-s t)` | 多字母上标同样逐字母拆开 | | `a \quad b` / `a \qquad b` | `a quad b` / `a wide b` | 1em / 2em 真间距;**不是**空格(Typst 会折叠空格,两条公式会粘成 `0∇×H`) | | `a\,b` / `a\;b` | `a thin b` / `a med b` | 细间距 / 中间距 | | 行内 `$1/r$` | `1 slash r` | 行内排平斜杠,不顶高行距;独占一行的 `$$…$$` 仍排上下分式 | | `> **来源**:…` | `#quoted[…]` | Typst 没有 `>` 引用语法;字段按行换行,不会粘成一句 | **转不动的会被点名**(例如 `\begin{tikz}` 这类环境),工具会把片段原样列出来, 不会静默改错。 ## 5. 校验清单 工具自动做的: - [x] 数学按 Typst 规则改写,改不动的点名 - [x] 框的**逐节计数**,超预算(> 2)时点名是第几节、超了几个 - [x] 全篇 **方框 : 正文段落** 的比值 - [x] 悬空的交叉引用(`@def:x` 找不到对应 ``)会被报出来 - [x] 讲义与笔记都检查**逐节方框数**(> 2 点名)、全篇**方框 : 正文**(> 1:4 告警) - [x] 编译错误一条不留(`error:` 即失败) **必须人做的**(工具替不了): - [ ] **看逐页 PNG**。框的内容块数量错位时**不会报错** —— `#theorem[标题][正文][多余内容]` 里的第二块会被当成来源标签渲染成小字, 退出码仍是 0。`grep error:` 抓不到这种错,只有看图才发现得了。 - [ ] 确认方框确实都是「值得被引用」的那几个。 ## 6. 底稿骨架(`mode=draft` 生成的形状) ```markdown # 封面 - title: 课程名 第 n 讲 主题 - author: 名字 - date: 2026-11-02 ## 一、章节名 这里是正文,原样从讲义搬来。普通段落就是正文,**绝大多数篇幅应该都是这种段落**。 ## 二、下一节 按内容需要,自己决定要不要加: [直觉] 用一句人话把这节在解决什么问题讲清楚。 [练习] 留给读者自己重做的一题。 ``` 骨架**不逐节插空占位**(2.2.2 起):`[直觉]/[练习]/[边注]` 加不加、加在哪里, 是内容判断,不该由搬运工具替你做。它也不替你插 `[定义]/[定理]/[证明]` —— 那是「后面会不会被回指」的判断,由你按 §2.1 的决策规则决定。 骨架只在文件头留一行 HTML 注释说明可选装置;它不渲染、也不计入正文段落。 公式**保持 LaTeX**(`$$…$$` / `$…$`),表格保持 `| a | b |`,列表保持 `1.`; 这些都在 `mode=compile` 时一次性转换。你补内容时也按同样的写法继续写 —— 不要把 公式改成 `frac(…)`、把表格改成 `#table(…)`,那会让底稿既不像 Markdown、又在二次 转换里被拆坏。`---` 也不用管:渲染器会把它排成细分隔线(见 §2 语义表)。 ### 6.1 阅读版讲义里怎么用这些标记 阅读版讲义(`zju_write_note` 自动生成的同名 PDF)用的是同一套标记与配色, 但**建议更克制**——它首先是给人连续读的: - 每节最多一个 `[直觉]`、一个 `[练习]`、一个 `[边注]`; - `[定义]` / `[定理]` / `[例]` / `[证明]` 只在「后面会被回指」或「整章靠它串起来」时用; - 其余写成普通段落,用 `[强调]` / `[易错]` / `[弱注]` 标重点; - 阅读版的练习块带 **3 行书写横线**(与参考图一致);题面里的 `____` 会被丢弃, 正文里显式写的挖空仍画成灰色填空线; - 标题不要手写编号:渲染器会自动编号,并把 `1.` / `1.2` / `一、` 这类前缀剥掉。 ## 7. 配色与字体 ### 7.1 单一来源 浅色主题(「淡彩极简」)是**本项目自己定义的**,唯一权威副本是 `zju_lecture/prelude.typ` 里的 `#let soft-palette`。另外两处都是它的**生成副本**, 不能手改: | 生成副本 | 为什么需要 | 谁同步 | | :--- | :--- | :--- | | `zju_lecture/handout_prelude.typ` 的 `#let palette` | 阅读版讲义的模板是**自包含**的(不 import 任何包),只能把色值写进文件 | `python tools/sync_palette.py` | | `zju_lecture/handout.py` 的 `_PALETTE` | Python 侧渲染 `> [!IMPORTANT]` 色块时要算淡彩底 | 同上 | 改配色的流程: ```bash # 1. 只改 zju_lecture/prelude.typ 的 soft-palette # 2. 同步三处 + 校验 python tools/sync_palette.py python tools/sync_palette.py --check # 有漂移时退出码 1 python tools/sync_skill_assets.py # prelude 的 skill 副本逐字节同步 python tools/build_skill_manifest.py # 重算 sha256 清单 ``` `tests/test_palette_sync.py` 守住三处一致、下面这组对比度,以及「默认字体栈里 没有商业字体」。 ### 7.2 角色与色值(浅色 / soft-palette) 浅色主题的完整色表、对比度与「四个强调色极差 ≤ 1.2」的口径见 [`typography-spec.md`](typography-spec.md) §7。本节只保留归属事实: - 浅色 `soft-palette` 是本项目自定义的,**不是**上游默认值; - 深色主题(slate)由上游 `scholia` 自带,本项目不覆盖; - 改色只改 `zju_lecture/prelude.typ`,再跑 `tools/sync_palette.py` 与 `tools/sync_skill_assets.py`。 ### 7.3 深色主题 深色主题(slate)由上游 `scholia` 自带,本项目**不覆盖**:`#show: use-palette` 只在 `theme: "light"` 时注入,深色下注入会把它覆盖回浅色。阅读版讲义目前只出浅色。 ### 7.4 字体:默认就是「可分发的」 字体角色、回退链与许可列见 [`typography-spec.md`](typography-spec.md) §1。 两条链路的默认字体栈都只包含可再分发字体(STIX Two / Libertinus / 随包 Notes Kai SC 与 Notes Serif SC / Noto / Source Han / DejaVu);商业字体 (Optima / Avenir Next / Menlo / Songti SC / SimSun / Microsoft YaHei / Consolas) 不在生效列表里。 对外分发前核对一次:`pdffonts out.pdf`(poppler)或 PDF 阅读器的「属性 → 字体」, 看到上述商业字体就换字体重编,别把专有字体子集传出去。