Quarto 中文书稿 docx:图题、表题与交叉引用滤镜

可复用前提、必备 YAML,以及 scripts/quarto-docx 的挂载顺序

Quarto
docx
Lua
书稿
Windows
Author

胡华平

Published

September 29, 2026

摘要

中文书稿把 Quarto 渲成 Word 时,默认会得到出版社不接受的版式:网站项目的图号写成「图2- 13」,book 项目写成「图 3.1」;表题/图题被包进 1×1 外层表且左对齐;短列(年份/序号)被 Pandoc 按字符数压到折行。本包把 report-tech2026 已经跑通的 Word 参考文档 + Lua 滤镜 收到 harness scripts/quarto-docx/。同一目录里有两个安装剖面:网站用 图N- 收紧,book 用 图N-M 改写,并可选一页横向表。

核心结论:

  1. 这套滤镜不是通用英文论文模板。网站剖面假定前缀是「图{章号}-」「表{章号}-」。book 剖面假定前缀只是「图」「表」,由滤镜把 3.1 收成 图3-1。不要把网站的 图2- 写进 book 的 YAML。
  2. 软件底线是 Quarto 1.8+(已在 1.8.26 验证;1.10.18 于 2026-09-29 复测网站剖面,并于 2026-10-15 用 run-check-all.ps1 复测网站 + book 剖面通过)。不要给 Para / Table 写 .attr。
  3. 两个剖面的编号滤镜不要同时挂。题注拆壳与列宽都在 post-render。横向页滤镜只在 book 剖面,并且必须 at: pre-quarto。只写项目根 _quarto.yml 时,单独渲某一章可能吃不到滤镜。

可拷贝源与安装命令见 目录索引。


1. 这套包解决什么、不解决什么

1.1 解决

现象 滤镜 / 模板
「图2- 13」「(见 图2-13 )」(网站,前缀里已有章号) crossref-tight-num.lua
「图 3.1」「见表 3.1a」(book,前缀只有「图」「表」) crossref-book-num.lua
宽表需要单独一页横向 A4,且不能多出空白纵向页 docx-landscape.lua(pre-quarto,只认一层 landscape)
中英双行题注挤在一段 linebreak.lua(把 <br> 变成换行)
表题在表内、图题在图框内,样式还是 Image Caption、左对齐 docx-table-caption.lua + 模板样式「表题」「图题」
「年份」「2024」列过窄、表头看起来像小四 docx-table-layout.lua + 模板 Table firstRow

1.2 不解决

  • Windows 默认 png() + 宋体出空白图(那是 knitr dev = ragg_png,不是本包)
  • 纵轴题竖排、xlsx 源图、DEA / 年鉴数据
  • Word「插入题注」那种 SEQ 域;Quarto 的「表2-1」是静态字
  • 英文-only fig-title: "Figure" 的 IEEE/APA 论文版式

2. 可复用前提(不满足则不要整包拷贝)

下面是硬前提。缺任何一条,滤镜会静默少做事或拆错对象。

2.1 软件

项 要求 说明
Quarto 1.8+(已验证 1.8.26、1.10.18) 1.8 的 walker 给 Para/Table 赋 .attr 会报 Trying to set unavailable property attr。本包已经避开;1.10.18 上网站与 book 结构验收均通过(最近一次含 book:2026-10-15)。未在 1.7 验证。
Pandoc 随 Quarto 捆绑即可 不必另装
输出格式 至少要渲 docx crossref-tight-num.lua 对 html 同样有效;caption / layout 仅 docx
Word 能打开 reference-doc 验收时看样式廊是否有「表题」「图题」

2.2 网站剖面:交叉引用前缀是「图N-」「表N-」

网站滤镜用首字符「图」或「表」+ 一位及以上数字 + 连字符识别前缀,例如 图2-、表5-、图10-。这不是 book 的写法。book 见下一节。

YAML 必须类似:

crossref:
  title-delim: ""    # 只管序号与题注正文;去不掉前缀与序号之间的 nbsp
  fig-title: "图2-"
  fig-prefix: "图2-"
  tbl-title: "表2-"
  tbl-prefix: "表2-"
写法 本包是否处理
图2- / 表5- 是(设计目标)
图 2-(前缀内空格) 否
仅 Figure / Table(英文 Quarto 前缀) 否;手写在 fig_cap 里的 Figure 2-13 不受 nbsp 影响,也不走 tight-num
自定义 float 覆盖 key: fig / tbl 并开 space-before-numbering: false 不要;会破坏 knitr #\| label: fig-

title-delim: "" 仍建议保留,但它替代不了 crossref-tight-num.lua。

2.2a book 剖面:前缀只写「图」「表」

Quarto book 在前缀后插入不间断空格和 章.序号(可带字母后缀,如 3.1a),页面上是「图 3.1」。crossref-book-num.lua 把它收成「图3-1」「表3-1a」,并去掉中文语境里交叉引用两侧多余的空格。源文件仍要写成 见 @fig-id,@ 不能贴住前面的字。

crossref:
  title-delim: ""
  fig-title: "图"
  fig-prefix: "图"
  tbl-title: "表"
  tbl-prefix: "表"

不要在 book 里写 fig-title: "图2-"。那是网站剖面的手写章号,和 book 的自动 章.序号 叠在一起会变成「图2-3.1」一类无法改写的串。

题注拆壳同时接受两类编号:网站的 图2- / 图2- 13,以及 book 的 图3-1 或尚未改写的 图3.1 / 图3.1a。因此两个剖面共用 docx-table-caption.lua。

横向页不是编号的一部分。需要时只标记一次 class landscape:包住浮层和紧随的「注」「资料来源」,或只写在 chunk 的 classes 上。滤镜在 pre-quarto 去掉这个 class,避免 Quarto 自带的 landscape 处理再插入空的分节符。页脚关系默认 rId14(本包 reference-doc.docx 里页码页脚的 id)。业务仓参考文档若不同,设置元数据 docx-landscape-footer。纵向页的纸型和页边距仍由该项目自己的 reference-doc 决定;滤镜只在它切开的分节上写入 A4 尺寸。

2.3 题注文本:中文行 + <br> + 英文行(推荐,非识别所必需)

业务仓约定:

fig_cap <- glue("旱区……分布情况<br>Figure 2-{num_fig} Amounts distribution of …")
tbl_cap <- glue("……名单<br>Table 5-{num_tbl} Illustrative …")
  • 中文行由 Quarto 再在前面加上 图2-13 / 表5-1。
  • 英文行是字符串里手写的 Figure 2-N / Table 5-N,不是 fig-title。
  • <br> 必须配 linebreak.lua,否则 Word 里中英挤在一行。

拆壳滤镜识别题注的方式有两条,满足其一即可:

  1. 题注纯文本以网站编号 图{数字}- / 表{数字}- 开头,或以 book 编号 图3-1 / 图3.1 / 图3.1a 开头;或
  2. 段落 OpenXML 带 ImageCaption / TableCaption / BiaoTi / TuTi。

因此:可以只用中文题、不写英文行,拆壳仍会工作。双语是出版社版式,不是 Lua 的硬编码。

2.4 chunk 标签与正文引用

项 必备写法 反例
图 chunk #\| label: fig-… 且 #\| fig-cap: 去掉 label 以「躲开」浮层(交叉引用会断)
表 chunk #\| label: tbl-… 且 #\| tbl-cap: 同上
正文 见 @fig-id(@ 前、label 后都有空格) 见@fig-id(Pandoc 当 citation 粘住,根本不编号)

拆壳靠 fig- / tbl- identifier 判断图还是表。不要把图标成 tbl-。

2.5 Word 参考文档里的样式(本包已备好)

reference-doc.docx 必须含:

styleId Word 样式名 用途
BiaoTi 表题 表外题,居中
TuTi 图题 图下题,居中
Compact Compact 表单元格,五号 sz=21
Table 的 firstRow (表样式条件) 表头加粗 + sz=21

滤镜 custom-style 对的是样式名「表题」「图题」,不是 styleId。若业务仓另做 reference-doc,必须克隆这两套样式,否则 Word 会静默回落正文。

文档默认字号若是小四(sz=24),表头一旦丢掉 Compact 就会变大。docx-table-layout.lua 因此把表头保持为 Plain(Pandoc 才写成 Compact)。

2.6 滤镜顺序与挂载位置

网站:

flowchart LR
  A["linebreak.lua<br/>默认滤镜"] --> B["crossref-tight-num.lua<br/>post-render"]
  B --> C["docx-table-caption.lua<br/>post-render · 仅 docx"]
  C --> D["docx-table-layout.lua<br/>post-render · 仅 docx"]

book:

flowchart LR
  L["docx-landscape.lua<br/>pre-quarto · 仅 docx"] --> A["linebreak.lua<br/>默认滤镜"]
  A --> B["crossref-book-num.lua<br/>post-render"]
  B --> C["docx-table-caption.lua<br/>post-render · 仅 docx"]
  C --> D["docx-table-layout.lua<br/>post-render · 仅 docx"]

滤镜 at: 原因
docx-landscape.lua pre-quarto(仅 book,可省略) 先去掉 class landscape,再写一对完整分节符;晚于 Quarto 自带处理就会叠出空白页
linebreak.lua 默认(不要 post-render) 尽早把 <br> 变成 LineBreak
crossref-tight-num.lua 或 crossref-book-num.lua post-render 图号在 render 阶段才写入;两个编号滤镜只挂其中一个
docx-table-caption.lua post-render 须在图号已经写成「表5-1」或「表3-1」之后拆壳
docx-table-layout.lua post-render 须在拆壳之后改内层数据表

三处都要写(路径按该文件相对 lua/ 调整):

  1. 项目 _quarto.yml
  2. 子目录 _metadata.yaml(若单章 render 会读它)
  3. 实际被 quarto render path/to/chapter.qmd 的那份 YAML

只写根配置、章文件又不在 project.render 列表里时,单独渲该章会漏滤镜。

从仓库根渲染。章内有行内 r 时不要 --no-execute。


3. 源文件最小协议(拷贝滤镜后仍要遵守)

#| label: fig-bar-example
#| fig-cap: !expr fig_cap
#| label: tbl-example
#| tbl-cap: !expr tbl_cap

正文:……(见 @fig-bar-example )。

不要:

  • 用 custom float 重定义 fig / tbl
  • 渲完用 Python 改 document.xml 当主方案
  • 靠每张表手写 tbl-colwidths 代替 layout 滤镜
  • 改成 officedown / flextable 另写一套交叉引用(本包不覆盖)

4. 如何装进其他项目

推荐(Windows):

cd D:\github\harness
.\scripts\powershell\Install-Harness.ps1 `
  -InstallProfile quarto-docx `
  -ProjectRoot "D:\path\to\your-repo"

book 项目改用 -InstallProfile quarto-docx-book。它复制 crossref-book-num.lua、docx-landscape.lua 和 _quarto.harness-docx-book-filters.yml,不复制网站编号滤镜。两个剖面都包含换行、拆壳、列宽和参考文档。

然后把对应的滤镜片段合并进该书 YAML,并设置:

format:
  docx:
    reference-doc: template/reference-doc.docx

仍可手工复制 scripts/quarto-docx/ 下的 lua/ 与 reference-doc.docx。


5. 验收

Harness 标准样例(仓库内,Windows):

cd scripts\quarto-docx\test
.\scripts\run-check-all.ps1

自动 render mod-acceptance.qmd,再用 verify_docx.py 断言网站剖面的 1、2、4、6(交叉引用收紧、题注样式在表外、题注内换行、callout 仍为表)。同一脚本接着渲染 test/book/,用 verify_book.py 断言 图N-M、一对 A4 分节,以及「注」「资料来源」落在这一页横向节里。3、5(无残留 ImageCaption、列宽/五号表头)仍建议 Word 目视。详见 资产效果展示与标准验收。

5.1 版本复测记录

滤镜源未改。复测前用 sync-mirror.ps1 把 canonical lua/ 与 reference-doc.docx 同步进 test/,再跑 run-check-all.ps1。

日期 Quarto 结果
2026-09-08 1.8.26 文档记载的验证版本(当时尚未单列本次 L0/L1 流水)
2026-09-29 1.10.18 本机由 1.8.26 升级后复测。L0:<br> → LineBreak。L1:docx 与 html 均渲染成功;verify_docx.py 七项通过(正文交叉引用无多余空格、题注前缀收紧、图题 TuTi 与表题 BiaoTi 均在 w:tbl 外、题注内有 w:br、表内含「年份」「2024」、专栏标题仍在表内)。列宽与五号表头未在 Word 中目视。

本地流水写在 scripts/quarto-docx/test/TEST-LOG.md(已 gitignore,不提交)。规程见 quarto-docx-test-guide.md。

业务仓亦可手动:渲一章 --to html 与 --to docx 各一次。

  1. 正文「(见图2-13)」,不要「见 图2-13 」;html 无 图2-&nbsp;。
  2. 表题在表外,pStyle=BiaoTi,居中;图题在图下,pStyle=TuTi,居中。
  3. 无题注仍落在 ImageCaption。
  4. 中英双行中间有 w:br(若用了 <br>)。
  5. 「年份」列可单行放下「2024」;表头加粗且为五号(Compact / sz=21)。
  6. 专栏/callout 不要被拆(它们是 1 列表、没有「表N-」+ 内层数据表)。

原理展开:图号去空格笔记、表题图题出框笔记。


6. 与业务仓同步

源副本记录在 SOURCE.txt。业务仓若继续改 Lua,请把 diff 回灌本目录,避免两套滤镜分叉。