Files
ouc_latex_template/特殊事项说明表/README.md
T
laoyang 0fd6021b03 新增特殊事项说明表 LaTeX 模板 #16
把报销用的《中国海洋大学特殊事项说明表》Word 版复刻为 LaTeX 模板,
新增 特殊事项说明表/ 目录,与库内其余模板同一套约定。

三处关键设计:

1. 版心取「表格宽度」而非 docx 版心。原表 169.54mm 比 docx 版心宽
   13.54mm,靠 tblInd 负值顶出去;照搬 docx 版心必然 Overfull。
   改把 geometry 的版心直接设成表格宽度,零 Overfull 且位置逐点相同,
   代价是标题/备注需额外偏移补回 docx 版心的中心。

2. 行距不用 \baselineskip。本表行距逐行不同(10.5pt 与 12pt 混排),
   而 \baselineskip 只在同一段落内管行距 —— 每行 \par 自成一段后它
   根本没被读到(实测所有行距被压成 13.51,整表矮 2.25bp)。
   改成逐行插 \kern:基线距 = 上盒深度 + \kern + 本盒高度,
   与字体度量彻底解耦。

3. R0 改成「上下两半 + \vfil」,删掉原件那 8 个手写空段。
   描述写长写短都不再影响签名区位置,比原件更稳。

顺带记录六个实测踩出来的坑(块首行胶被丢弃、负 \kern 不能挡、
多行段 \ht 参照末行基线、\prevgen 必须在 \par 之后且在盒内取、
\vbox to N 的深度另计等),详见 sty 内注释与 README。

验证:
- xelatex 0 error / 0 Overfull / 0 Underfull / 1 页
- 与 Word 导出 PDF 逐项对齐:33 个文字元素 dY 全部 0.00bp,
  dX ≤ 0.04bp;四条横线与页码 0.00bp
- 像素差分 3.03%,全部落在字形轮廓上,无整体错位
- 全库 12 个模板回归通过,既有 11 个 PDF 零字节变化

与原件仅 3 处有意差异:字体换本库 OUC Serif(可分发)、
标题小标宋换真粗体(商业字体不可分发)、第 3 条备注编号统一对齐
(原件是手打漏配编号,偏 0.36bp)。
2026-10-28 19:09:12 +08:00

247 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 特殊事项说明表 — 中国海洋大学 特殊事项说明表(LaTeX 版)
把报销用的《中国海洋大学特殊事项说明表》Word 版复刻为 LaTeX 模板。
版式与原 `.docx` **逐点对齐**(见文末「实测偏差」),
**不含任何真人信息**:事项描述是虚构的报销事由,三处日期留空待手写。
## 文件
| 文件 | 说明 |
| :-- | :-- |
| `特殊事项说明表.tex` | 主文件。填表只改第 1 节(4 个变量) |
| `ouccsx.sty` | 版式包。页面/字体/行高/全部尺寸写在这里,附换算注释 |
## 编译
```bash
xelatex 特殊事项说明表.tex
```
单页,跑一遍即可(无交叉引用)。
需要 **XeLaTeX**,并安装 `ctex`、`geometry`、`fontspec`、`xeCJK`、`fancyhdr`。
实测环境:TeX Live 2022。
## 改内容
```latex
\newcommand{\sxdesc}{% % 事项描述(必填)
因项目研讨需要,前往菏泽进行项目研讨与系统演示,因携带大件演示设备,
公共交通多有不便,因此采用自驾方式前往。}
\newcommand{\sxdateA}{} % 项目负责人签字处日期
\newcommand{\sxdateB}{} % 单位负责人签字处日期
\newcommand{\sxdateC}{} % 科技管理部门签字处日期
```
就这四个。**表内其余文字全是学校规定原文,不要改** ——
「兹证明该事项真实有效,本人愿意为此承担责任。」「学院对本单位科研
经费使用承担监督和管理责任。」以及备注三条,都在 `ouccsx.sty` 里
装配好了,改了就与原件不一致。
### 关于 `\sxdesc`
描述**可以写多段**,空行或 `\\` 分段都行,表格会自动让出空间:
- 写短 → 空白落在「描述区」与「签名区」之间,签名区位置不动
- 写长 → 空白被吃掉,签名区仍然不动
> 这是本模板比原 docx 更稳的地方。原件靠 R0 里的 **8 个空段** 给手写
> 留白,描述一写长就把签名区顶到下一行(Word 里也一样)。本模板删掉
> 那 8 个空段,改成「上下两半 + 中间 `\vfil`」,两头都不破版。
⚠️ 描述特别长时签名区会挤出行外 —— 这是内容超量的正常表现(Word 里
写太多同样会把格子撑高),把描述删短即可,不是 bug。
### 关于三处日期
留空则印成「  年   月   日」,签字时手写。
想直接打印就填年份:`\newcommand{\sxdateA}{2026}` 会印在「年」前面。
> 默认留空的理由同 `成绩构成说明`:印了年份就占掉手写空间,
> 而且报销常常是事后补开,日期往往就是签字那天。
## 版式数据来源
全部尺寸从原 `.docx` 的 OOXML 里量出来,换算关系:
- 长度:`mm = twips ÷ 1440 × 25.4`,`pt = twips ÷ 20`
- 字号:`w:sz` 是半磅值,`sz=21` ⇒ 10.5 pt
- 行距:`w:line` 是 1/20 pt,`lineRule="exact"` 时即精确值
| 项目 | docx 原值 | 换算 |
| :-- | :-- | :-- |
| 纸张 | `pgSz 11906×16838` | A4 210×297 mm |
| 页边距(上下) | `pgMar top/bottom 2155/1985` | 38.01 / 35.00 mm |
| 页边距(左右) | `pgMar left/right 1588/1474` | 28.01 / 26.00 mm |
| 表宽 | `tblW 9613` | 169.54 mm = 480.65 bp |
| 表左缩进 | `tblInd −546` | −9.63 mm(把表顶出版心) |
| 单元格内边距 | `tblCellMar 108` | 5.4 pt(左右),上下为 0 |
| 边框 | `tblBorders sz=4` | 0.5 pt |
| 字号 | 标题 `sz44`/正文 `sz21`/签字日期 `sz24`/页码 `sz28` | 22 / 10.5 / 12 / 14 pt |
| 行距 | 标题 `line500`/备注 `line300`/表内未设 | 25 / 15 pt/Word 单倍(字号×1.3) |
表格结构:**单列 3 行**(`tblGrid` 只有一个 `gridCol`,每行一个 `tc`),
即三行整宽方框上下叠。没有合并单元格、没有多列,所以没用 `tabularray`
—— 手工 `\vbox` 叠三块反而更可控(行高精确、内线不重复、内容可用 `\vfil` 撑)。
### 行高为什么用实测值而不是 `trHeight`
`trHeight` 是 5091 / 1140 / 634 twips,`hRule="atLeast"`。
`atLeast` 是「最小」,实际高度由内容撑开:
| 行 | `trHeight` | 实际被内容撑到 | 段落数 |
| :-- | :-- | :-- | :-- |
| R0 事项描述 | 254.55 pt | **284.00 bp** | 19 |
| R1 单位负责人 | 57 pt | **58.35 bp** | 4 |
| R2 科技管理 | 31.70 pt | **56.80 bp** | 4 |
照 `trHeight` 会让整表矮 40 bp。故取实测撑开后的高度。
## 六个必须知道的坑
这一节的每一条都是实测踩出来的,改 `ouccsx.sty` 之前务必看完。
### 1. 版心要取「表格宽度」,不是 docx 版心
原表宽 169.54 mm,比 docx 版心(156 mm)宽 13.54 mm,
靠 `tblInd = −546 twips` 向左顶出版心。
若把 `geometry` 设成 docx 版心(`left = 28.01mm`),表格必然超出版心
13.54 mm,报一条 `Overfull \hbox`,而且改内容时位置会漂。
⇒ 本包把版心**直接设成表格宽度**:`left = 16.47mm`、`totalwidth = 169.54mm`。
表格正好填满版心,零 Overfull,与原件逐点相同。
代价:标题若用 `\centering` 会落在 287.03 bp,而原件在 300.41 bp(居中于
docx 版心),偏左 13.4 bp。故标题另给 `\sxtitleindent` + `\sxtitlewidth`
把中心校回去 —— **不要**把标题改成 `\centering`。
### 2. `\baselineskip` 管不了本表的行距
本表行距逐行不同(15.24 / 15.48 / 14.04 / 13.56 / 13.68 bp),
因为 10.5 pt 与 12 pt 的字混排,Word 按「**下一行**字号 × 1.3」算。
而 `\baselineskip` **只在同一段落内部**管行距。一旦每行 `\par` 自成一段,
段与段之间是另一套规则:
```
段间基线距 = 上段末行深度 + \parskip + 本段首行字高
```
这里 `\parskip = 0`、首行字高由字体 ascent 定死,`\baselineskip` 根本没被读到。
实测第一版所有行距被压成 13.51,12 pt 的签名行也没变高,整表矮 2.25 bp。
⇒ 正解:不依赖 `\baselineskip`,逐行显式插胶:
```
基线距 = 上盒深度 + \kern + 本盒高度
⇒ \kern = 目标距 − 上盒深度 − 本盒高度
```
这就是 `\sx@bl` / `\sx@blp` 的全部内容。与字体度量彻底解耦:换字体、
换字号、描述写长写短,行距一个 bp 都不变。
唯一的例外是「段内折行的长句」—— 那是一个段落里的多行,规则不同,
`\baselineskip` 在此**有效**,故 `\sx@blp` 仍给它。
### 3. 块首行必须用 `\kern`,不能用 `\vskip`
TeX 会**丢弃 `\vbox` 开头的胶**。用 `\vskip` 给的顶距会静默消失
(实测「事项描述:」给 10.53 只落到 5.38,等于只剩字高)。
`\kern` 不受这条约束,且照样计入盒高。
同理,块**末尾**不能用 `\vskip` 补空白(胶也被丢弃),
要用 `\sx@bl` 的可选参数 `#1` 直接撑末盒的深度。
### 4. 负的 `\kern` 不能被挡
相邻两行的目标距可能小于「上盒深度 + 本盒高度」。例:年月日行是 12 pt 字,
目标距 14.04,而 `2.99 + 12.06 = 15.05`,差值是 **−1.01**。
最初加了 `\ifdim\sx@tmp>\z@` 守卫,负值被静默吞掉 → 该行退回自然距 15.05,
整块错位 10 bp。负 `\kern` 在 TeX 里完全合法(盒间轻微重叠),不该挡。
### 5. 多行段落:`\ht` 参照的是**末行**基线
`\vbox` 的 `\ht = 首行高度 + (n−1)·行距`,它的参考点是**末行基线**。
所以「上一行基线 → 本段首行基线」的距离不是 `pd + kern + ht`,
而是 `pd + kern + ht − (n−1)·行距`。漏掉这一项,多行段自己被推下一整行,
其后所有行跟着错(实测 0.48 而非 14.04)。
⇒ 用 `\prevgraf` 取段内行数,把 `(n−1)·行距` 加回 `kern`。`\dp` 不用改 ——
`\vbox` 的 `\dp` 就是末行的真实深度。
⚠️⚠️ `\prevgraf` 有两道坎,两个都踩过:
- 必须在 `\vbox` **内部**取 —— 盒一关闭就恢复成外层值(实测把 2 读成 0)
- 必须在 `\par` **之后**取 —— 它是在段落断行那一步才被写入的(`\par` 前读到仍为 0)
### 6. `\vbox to N` 设的是**高度**,深度另计
若最后一项是带深度的 `\box`,`\vbox to N` 的 `\dp` 就是那个深度,
**总高变成 `N + 深度`**。本表末行深 3.11,于是 R0 变成 287.11 而非 284.00,
四条横线全部下移。
⇒ 末尾补一条 `\vskip\z@`:让末项变成胶(胶无深度),`\dp` 归零,
总高才正好等于 `N`。见 `\sx@cellvf`。
## 与原件的有意差异
| 项 | docx | 本模板 | 说明 |
| :-- | :-- | :-- | :-- |
| 字体 | 宋体 SimSun | OUC Serif(思源宋体子集) | 原用系统字体。改仓库公共字体包 `../fonts/`,换机器/换系统都能编,且允许分发 |
| 标题字体 | 方正小标宋简体 | OUC Serif **真粗体** | 小标宋是商业字体,本库不分发。按 `课程执行大纲/` 的既定做法换真粗体:小标宋本就是宋体的标题变体,真粗体比伪粗更接近,笔画不糊 |
| 第 3 条备注编号 | 手打的「   3.  」,无 `numPr` | 与前两条统一排齐 | 唯一的位置差异:原件该条比前两条偏 0.36 bp。原件是漏配自动编号后手打的,属笔误;本模板统一对齐 |
| R0 描述区留白 | 8 个空段 | 上下两半 + 中间 `\vfil` | 见上文「关于 `\sxdesc`」。描述长短不再影响签名区位置 |
除此之外**没有**任何版式差异。
## 实测偏差
不用肉眼看 PDF,直接用 `pymupdf` 取矢量坐标,与 Word 2007 导出的 PDF 逐项相减:
```python
span['origin'] # 真实基线坐标(bbox 顶端受字形上伸部影响,误差约 2 bp)
page.get_drawings() # 每条线的精确端点
```
结果(33 个文字元素 + 6 条线,`dY`/`dX` 为「本模板 − 原件」,单位 bp):
| 元素 | dY | dX |
| :-- | :-- | :-- |
| 标题 | 0.00 | −0.01 |
| 四条表格横线 | 0.00 | — |
| 事项描述 / 描述首行 / 次行 | 0.00 | +0.03 |
| 兹证明 | 0.00 | +0.04 |
| 项目负责人(签名) | 0.00 | +0.03 |
| 年月日(三处,各 3 字) | 0.00 | +0.03 |
| 项目负责人是 / 责任。/ 非科研经费 | 0.00 | +0.03 |
| 本人证明 / 单位负责人 / 单位公章 | 0.00 | +0.03 |
| 学院对本单位 | 0.00 | +0.03 |
| 科技管理 / 负责人签字 / 单位公章 | 0.00 | +0.03 |
| 备注四条 | −0.01 | −0.42 ~ −0.82 |
| 页码 | 0.00 | 0.00 |
**33 个文字元素的 dY 全部 0.00**(< 0.005 mm),dX ≤ 0.04 bp,
唯备注区 dX 偏 0.42~0.82 bp(约 0.15~0.29 mm,肉眼不可辨)——
那是 OUC Serif 与 SimSun 的字宽差累积,不是定位误差。
像素级差分(150 dpi)总差异 3.03%,且每条差异带都精确落在有文字的行上,
**没有整体错位的偏移带** —— 3% 全是字形轮廓不同造成的。
## 校验方法
量矢量而不是量像素:
```bash
pdftocairo -svg 特殊事项说明表.pdf out.svg # 线条精确坐标
pdftotext -bbox 特殊事项说明表.pdf out.xml # 字块坐标
```
或用 `pymupdf`(本模板就是这么核的):`page.get_drawings()` 取线,
`get_text('rawdict')` 取 `span['origin']` 作基线。
⚠️ 注意 `bp` 与 `pt` 的不同:PDF 用户单位是 **bp**(1/72 in),
LaTeX 的 `pt` 是 1/72.27 in,差 0.375%。A4 上累积到 2 pt,
超过一个字符宽。**本包凡与 PDF 对齐的几何量一律用 `bp`**。