Quarto 中文图号去空格
图2- 13 → 图2-13:现象、根因与 post-render 滤镜
经验来自《中国旱区农业技术发展报告2026》第2章。可拷贝源:本目录上一级
../lua/crossref-tight-num.lua。复用前提与挂载清单见 harness 长文。
书稿约定图号写成「图2-13」、表号写成「表2-1」,中间不要空格。Quarto 默认会在前缀和序号之间插入不间断空格,html 里变成「图2- 13」,docx 同样。title-delim: "" 去不掉这处空格。
发生情形
YAML 把章号写进前缀,而不是用 crossref.chapters:
crossref:
title-delim: ""
fig-title: "图2-"
fig-prefix: "图2-"
tbl-title: "表2-"
tbl-prefix: "表2-"正文用 见 @fig-bar-hitech-firms-all(@fig- 前必须有空格,否则交叉引用不解析)。期望「见图2-13」,实际常见:
| 位置 | 未修时 | 期望 |
|---|---|---|
正文 @fig- / @tbl- |
(见 图2-13 ) | (见图2-13) |
| 前缀与序号 | 图2- 13 / 表2- 1 | 图2-13 / 表2-1 |
| 图题 / 表题 | 图2- 13 … | 图2-13 … |
| html 源码 | 图2- 13 |
图2-13 |
第5章 图5- / 表5- 同一机制。英文图题里手写的 Figure 2-13 不受影响(那是 fig_cap 字符串,不是 Quarto 前缀)。
写成 见@fig-...(「见」与 @ 粘连)时,html 原样输出 @fig-...,根本不编号。Pandoc 把 @fig- 当 citation,前面必须是空白或西文标点;汉字「见」会粘进 token。所以源文件必须写成 见 @fig-id(@ 前、label 后留空格)。这些空格会原样进入 AST(Str "(见" + Space + Link 图2-6 + Space + Str ")"),成品就是「(见 图2-6 )」。不要为去空格去改源文件粘连 @;在 post-render 里,若交叉引用两侧是汉字或中文标点,再把 Space 丢掉。
根因
Quarto 1.8 内置 fig / tbl 在拼前缀时固定插入 nbspString()(pandoc.Str '\u{a0}'):
- 图题:
format.lua的titlePrefix() - 正文引用:
refs.lua的add_ref_prefix()
条件是 category.space_before_numbering ~= false。内置类型没有把该字段设为 false。
title-delim 只管序号与图题正文之间(默认 :,我们设成空字符串),后面还跟一个普通 Space。它不管前缀与序号之间的 nbsp。
官方 space-before-numbering: false 只出现在 crossref.custom 里,用来做「Figure S1」这类自定义 float。不要用 custom 覆盖保留键 fig / tbl:会破坏 knitr 的 #| label: fig-... / #| fig-cap。
修复方案
在 render 之后走一遍 Lua,把「图N- / 表N-」与紧随其后的序号拼成一个 Str。
滤镜:../lua/crossref-tight-num.lua。只处理匹配 图/表 + 数字 + - 的前缀,其它交叉引用不动。
必须 at: post-render
| 时机 | AST 里有什么 | 滤镜能否改掉空格 |
|---|---|---|
默认用户滤镜 / pre-quarto |
仍是 Cite(@fig-demo),图题还没有「图2-」 |
不能 |
post-quarto |
正文 Link 已是「图2-」+ nbsp +「13」;图题前缀尚未写入 | 只能修正文 |
post-render |
正文和图题/表题都已带前缀 | 都能修 |
图题前缀是 decorate_caption_with_crossref() 在 render 阶段才 prepend 的。只挂 post-quarto 时,会出现「正文已是图2-13、图题仍是图2- 13」。
挂载位置(三处都要)
第2/5章不在 _quarto.yml 的 project.render 列表里时,只写根配置、单独 quarto render report/chapter….qmd 可能吃不到滤镜。业务仓要把滤镜同时写在项目根、子目录 _metadata.yaml 和被渲的那份 YAML。
| 文件 | 路径 |
|---|---|
项目 _quarto.yml |
lua/crossref-tight-num.lua |
子目录 _metadata.yaml |
../lua/crossref-tight-num.lua |
| 章 YAML | 与该文件相对 lua/ 的路径 |
都带 at: post-render。从仓库根目录渲染。章内有行内 r 时不要用 --no-execute。
filters:
- path: ../lua/crossref-tight-num.lua
at: post-renderLua 实现要点
Pandoc Lua 的 pattern 按字节匹配。不要写 [图表]:那是字节集合,对不上 UTF-8 的「图」「表」。用 pandoc.text.sub 取首字符,或直接写 UTF-8 字节(U+56FE 图、U+8868 表)。
间隙节点可能是:
Str且内容为 U+00A0Space/SoftBreakRawInline的
同一 Str 里若已是「图2-」+ nbsp +「13」,用 gsub 收紧。滤镜可重复跑,已收紧的文本不再变化。
不要做的事
- 指望
title-delim: ""去掉「图2-」和「13」之间的空格。 - 用 custom float 重定义
key: fig/tbl来开space-before-numbering。 - 只改 html、不跑 docx:Word 也走同一套 AST,必须抽查。
- 为去空格改
fig_cap里手写的Figure 2-{num_fig}(本来就没有这处 nbsp)。
验收
小样(无 R)或整章均可。抽查:
- 正文:
(见图2-13),不要「见 图2-13 」;html 源码无图2- 。 - 图题、表题同样。
- docx:用 markdownify
docx-to-markdown(或 Word 打开)搜「图2- 13」应无命中。 见@fig-已全部改成见 @fig-,否则编号根本出不来。
第2章 html 预览与书稿 docx 在来源仓分别 quarto render … --to html / --to docx。其他项目按同样方式抽查即可。
复用清单
新章若仍用「图{章号}-」前缀:
- YAML:
fig-title/fig-prefix/tbl-*写成图N-/表N-,title-delim: ""。 - 正文:
见 @fig-...,勿粘连。 - 三处挂
crossref-tight-num.lua,at: post-render。 - 渲 html 与 docx 各抽查一次图号、表号。