Files
ouc_latex_template/会议预算表/oucmeeting.sty
T
laoyang 1c67b7ded1 新增《会议预算表》LaTeX 模板 #24
源文件:微信收到的 会议预算表.doc(51712 字节,**OLE2 老式 .doc**,
D0CF11E0 签名)。python-docx 读不了,处理链路:
.doc --(LibreOffice headless)--> .docx --(python-docx)--> 结构

## 新增文件

   会议预算表/oucmeeting.sty   版式包(含全部 28 行合并规则)
   会议预算表/会议预算表.tex   空表正文(只有内容,无 \SetCell)
   会议预算表/README.md        用法 + 踩坑记录

## 版式(实测)

   页面    A4 210×297mm
   边距    top 38.01 / bottom 35.01 / left 28.01 / right 26.00 mm
   版心    155.99 × 223.98 mm
   字号    标题 22pt,表格与说明 9pt
   表格    28 行 × 7 列,边框 0.5pt,行高 5.6mm

四个边距都不常见,是原表为把 28 行塞进一页压出来的。照抄不"优化"。

⚠️ 原表宽 169.12mm(tblGrid 9588 twips),**比分宽出版心 13.12mm**,
原 docx 靠 fixed 布局硬撑、实际压进页边距。本模板等比缩到版心内。

## 三个踩了很久的坑(全部写进代码注释 + README)

### 1. 合并必须用矩阵式语法,不能写 \SetCell

在这套 7 列 Q[c,m,wd=…] + colsep=1.0mm 配置下,内容里一写
\SetCell[c=n] 就报:

   ! Missing number, treated as zero.
   ! Illegal unit of measure (pt inserted.)
   \__dim_eval_end:

**所有错误都定位到 \end{tblr}**,完全看不出是哪个格。
最小复现(已验证):
   \begin{tblr}{colspec={Q[c,m,wd=21mm]...7列..., colsep=0.8mm}}
     \SetCell[c=2]{c} A & \SetCell[c=5]{l} B \\
   \end{tblr}   % 必崩

✅ 正解:在 tblr 选项里用 cell{行}{列}={c=n}{对齐},被合并格留空。

这个坑吃掉最多时间 —— 因为报错行号指向 \end{tblr},
试过 \shortstack、\rotatebox、\newline 各种改,全都不对,
最后靠「同一合并规则换成 cell{} 语法就 0 错」的最小复现才定位到。

### 2. \SetCell[r=n] 与 \SetCell[c=m] 不能同现

纵向合并和横向合并放同一行,报同样那串错。
本表完全不用纵向合并 —— 「其他费用」改成 \rotatebox{90} 竖排,
第 1 列下面 4 行留空,视觉等价。

### 3. colspec / hline / vline / ht 参数不能用宏

tabularray 用自带展开器解析这些选项,遇到用户宏直接
`! Undefined control sequence.`。全部写字面值。

## 页高预算(关键,别乱改行高)

   28 × 5.6mm = 156.8mm
   标题区(附件1 + 22pt 标题 + 日期行)≈ 30mm
   说明 3 条(第 2 条占两行)≈ 35mm
   合计 ≈ 222mm  ≤ 223.98mm  ✅ 刚好一页

⚠️ 行高 5.6mm 已在下限附近。改回 6.0mm 会让 28 行多出 11.2mm,
第 3 条说明立刻被挤到第 2 页(实测过)。

## 验证

   1 页   error 0   Overfull 0   Underfull 0
   表格 155.52mm / 版心 155.99mm  → 界内
   内容 50 项逐项核对,全部存在
   (唯一"缺失"项是「不得坐支现金」因跨行被 \n 断开,
     去空白后匹配通过 —— 不是真缺失)

全库 bash build.sh all → **15/15 通过**(新增第 15 项,已加入 verify_all.sh)

双端一致:
   WSL TeX Live 2025     1 页 err=0 over=0 under=0
   Windows TeX Live 2022  1 页 err=0 over=0 under=0

## 说明

空表,金额/卡号/日期/人名一律留空。不含任何真实数据。
2026-10-30 16:50:32 +08:00

257 lines
12 KiB
TeX
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.
%======================================================================
% oucmeeting.sty —— 中国海洋大学 会议预算表 版式包
%
% 由学校下发的 .doc 复刻而来(源文件:会议预算表.doc,OLE2 老格式,
% 先经 LibreOffice 转 docx 再解析 OOXML)。尺寸全部从原文件量出。
%
% ⚠️ 编译: xelatex 会议预算表.tex
% 单次编译即可,无交叉引用。
% 依赖 ctex + geometry + fontspec + xeCJK + tabularray
% 与 课程大纲/、课程执行大纲/ 同源同风格。
%======================================================================
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{oucmeeting}[2026/10/30 v1.0 OUC meeting budget form]
\RequirePackage{geometry}
\RequirePackage{fontspec}
\RequirePackage{xeCJK}
\RequirePackage{tabularray}
\RequirePackage{graphicx} % \rotatebox —— 「其他费用」竖排用
%======================================================================
% 1. 页面
%
% 原 docx sectPr 实测(11906 x 16838 twips):
% 宽 210.01 mm 高 297.00 mm ← 标准 A4
% top 2155 twips = 38.01 mm
% bottom 1985 twips = 35.01 mm
% left 1588 twips = 28.01 mm
% right 1474 twips = 26.00 mm
% header 0 twips = 0.00 mm (无页眉)
% footer 992 twips = 17.50 mm
%
% 四个边距都不常见(38/35/28/26),是原表为把 28 行表格塞进一页
% 压出来的。照抄,不"优化"——这是复刻件,不是重新设计。
%
% ⚠️ 版心宽 = 210 - 28.01 - 26.00 = 155.99 mm
% 而原表宽 169.12 mm,**超宽 13.12 mm**。
% 原 docx 用 tblLayout=fixed 硬撑,实际会压进右侧页边距。
% ⇒ 本包把表格等比缩到 155.99 mm(系数 0.9224),
% 列宽按比例重算,见第 3 节。这是唯一一处主动偏离原文件的地方。
%======================================================================
\geometry{
paperwidth = 210mm,
paperheight = 297mm,
left = 28.01mm,
right = 26.00mm,
top = 38.01mm,
bottom = 35.01mm,
headheight = 0pt,
headsep = 0pt,
footskip = 17.50mm,
}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0pt}
\pagestyle{empty} % 原 docx 无页眉页脚页码
%======================================================================
% 2. 字体
%
% 原 docx 用宋体 / 黑体。仓库规矩(#17):不用商业字体,
% 全部取自公共字体包 ../fonts.tex。
%
% ⚠️ 原表字号分布实测:
% 22pt ×1 —— 标题「中国海洋大学会议预算表」
% 9pt ×47 —— 表格内全部文字 + 三条说明
% 无 12pt 段落,与课程大纲那套不同,别照抄那边的字号命令。
%======================================================================
\input{../fonts.tex}
\newcommand{\oucMeetFont}[2]{\fontsize{#1pt}{#2pt}\selectfont}
\newcommand{\Mtw}{\oucMeetFont{22}{30}} % 标题 22pt
\newcommand{\Mn}{\oucMeetFont{9}{12}} % 表格与说明 9pt
\AtBeginDocument{\Mn}
%======================================================================
% 3. 表格列宽
%
% 原 docx 网格(tblGrid,单位 twips):
% [424, 2054, 790, 1262, 2056, 209, 2793] 合计 9588
% 换算 mm(×25.4/1440):
% [7.48, 36.23, 13.93, 22.26, 36.27, 3.69, 49.27] 合计 169.12
%
% 等比缩到版心 155.99 mm(系数 0.9224):
% [6.9, 33.4, 12.9, 20.5, 33.4, 3.4, 45.4] 合计 155.9
%
% ⚠️ 第 6 列只有 3.4mm,是原文件里「其他费用」竖排标题那一列的残留
% (原 209 twips,几乎只是一条分隔线宽)。保留它是因为第 20~24 行
% 的「其他费用」合并单元格需要它占位,删掉会破坏网格。
%======================================================================
% ⚠️ 列定义**不能**放进宏再用 \OucMeetColspec 引用 —— tabularray 的
% colspec 解析器要求字面量,报
% Package tabularray Error: Unexpandable command ... inside Column type
% ⇒ 直接写在下方的 colspec={...} 里(实测唯一可行写法)。
%----------------------------------------------------------------------
% 行高(#24 修正)
%
% ⚠️⚠️ 原 docx 的 28 行里**只有 R26 显式设了 trHeight**(567 exact = 10mm),
% 其余 27 行都没设 —— 行高由内容自然撑开,9pt 字约 4.5~5mm。
%
% 第一版我给所有行硬设 ht=7.4mm(自己拍的数),结果:
% 28 × 7.4 = 207.2mm,加标题区约 30mm + 说明约 25mm = 262mm
% 而版心只有 224mm ⇒ **必然溢出一页**,说明文字被推到第 3 页。
%
% ⇒ 改为 row{2-Z} 不设 ht,让内容撑开(与原 docx 一致);
% 表头行(R6/R14 分节标题)和签署行(R26/R27)单独给最小高度,
% 保证印章框有空间。
%
% ⚠️ 但 9pt + 0 上下内边距时行高只有 4.2mm,全表 28×4.2=118mm,
% 偏挤且不像正式表格。折中取 ht=6.0mm(≈ 一点留白),
% 28×6.0=168mm,加标题 30 + 说明 25 = 223mm,正好卡在 224mm 内。
% ⚠️ 若将来内容变多导致溢出,优先降这个值,不要动页边距。
% (注:这个 6.0mm 是写在下方 row{1-Z}={ht=6.0mm} 里的字面量,
% 不做成宏 —— 原因见第 4 节开头的说明。)
%======================================================================
% 4. 主表环境
%
% ⚠️⚠️ 合并单元格用**矩阵式语法**,绝对不能写在内容里用 \SetCell
%
% 现状(tabularray 2022A + 本表的 7 列 Q[c,m,wd=…] + colsep=1mm):
% 只要在单元格内容里写 \SetCell[c=n],编译立刻报
% ! Missing number, treated as zero.
% ! Illegal unit of measure (pt inserted.)
% \__dim_eval_end:
% 且**全部错误都定位到 \end{tblr}**,根本看不出是哪个格。
% 最小复现(已验证):
% \begin{tblr}{colspec={Q[c,m,wd=21mm] ...7列..., colsep=0.8mm}}
% \SetCell[c=2]{c} A & \SetCell[c=5]{l} B \\
% \end{tblr}
% 必崩;把同样的合并改写成下面的 cell{} 语法就 0 错。
%
% ✅ 正确写法:在 tblr 的选项里用
% cell{行}{列}={c=n}{对齐}
% 被合并掉的格在内容里**留空**(写 `& &`),不要写 {}。
%
% ⚠️ 另有一个坑(#23):纵向合并 \SetCell[r=n] 与横向 \SetCell[c=m]
% 不能出现在同一行,同样报上面那串错。本表因此完全不用纵向合并,
% 「其他费用」改成竖排文字 + 该列留空(视觉上等价)。
%======================================================================
\newenvironment{oucmeettable}{%
\par\begingroup\Mn
\begin{tblr}[label=none]{
colspec={
Q[c,m,wd=6.10mm] Q[c,m,wd=29.65mm] Q[c,m,wd=11.43mm] Q[c,m,wd=18.18mm]
Q[c,m,wd=29.65mm] Q[c,m,wd=3.00mm] Q[c,m,wd=40.10mm]
},
colsep=1.0mm, rowsep=0pt, stretch=0,
hline{1-Z}={0.5pt}, vline{1-Z}={0.5pt},
row{1-Z}={ht=5.6mm, font=\Mn, valign=m},
% ---- 合并规则(行号从 1 开始)----
% R1~R6 基本信息
cell{1}{1}={c=2}{c}, cell{1}{3}={c=5}{l},
cell{2}{1}={c=2}{c}, cell{2}{3}={c=2}{l}, cell{2}{5}={c=2}{c}, cell{2}{7}={c=1}{l},
cell{3}{1}={c=2}{c}, cell{3}{3}={c=5}{l},
cell{4}{1}={c=2}{c}, cell{4}{3}={c=5}{l},
cell{5}{1}={c=2}{c}, cell{5}{3}={c=5}{l},
cell{6}{1}={c=2}{c}, cell{6}{3}={c=5}{l},
% R7 收入预算标题(跨 7 列)
cell{7}{1}={c=7}{c},
% R8~R14 收入表
cell{8}{1}={c=2}{c}, cell{8}{3}={c=2}{c}, cell{8}{5}={c=2}{c}, cell{8}{7}={c=1}{c},
cell{9}{1}={c=2}{l}, cell{9}{3}={c=2}{l}, cell{9}{5}={c=2}{l}, cell{9}{7}={c=1}{l},
cell{10}{1}={c=2}{l}, cell{10}{3}={c=2}{l}, cell{10}{5}={c=2}{l}, cell{10}{7}={c=1}{l},
cell{11}{1}={c=2}{l}, cell{11}{3}={c=2}{l}, cell{11}{5}={c=2}{l}, cell{11}{7}={c=1}{l},
cell{12}{1}={c=2}{l}, cell{12}{3}={c=2}{l}, cell{12}{5}={c=2}{l}, cell{12}{7}={c=1}{l},
cell{13}{1}={c=2}{l}, cell{13}{3}={c=2}{l}, cell{13}{5}={c=2}{l}, cell{13}{7}={c=1}{l},
cell{14}{1}={c=2}{c}, cell{14}{3}={c=2}{l}, cell{14}{5}={c=2}{l}, cell{14}{7}={c=1}{l},
% R15 支出预算标题(跨 7 列)
cell{15}{1}={c=7}{c},
% R16~R20 支出表(3+2+2)
cell{16}{1}={c=3}{c}, cell{16}{4}={c=2}{c}, cell{16}{6}={c=2}{c},
cell{17}{1}={c=3}{l}, cell{17}{4}={c=2}{l}, cell{17}{6}={c=2}{l},
cell{18}{1}={c=3}{l}, cell{18}{4}={c=2}{l}, cell{18}{6}={c=2}{l},
cell{19}{1}={c=3}{l}, cell{19}{4}={c=2}{l}, cell{19}{6}={c=2}{l},
cell{20}{1}={c=3}{l}, cell{20}{4}={c=2}{l}, cell{20}{6}={c=2}{l},
% R21~R25 其他费用区(1+2+2+2),第 1 列竖排文字、下面 4 行留空
cell{21}{2}={c=2}{l}, cell{21}{4}={c=2}{l}, cell{21}{6}={c=2}{l},
cell{22}{2}={c=2}{l}, cell{22}{4}={c=2}{l}, cell{22}{6}={c=2}{l},
cell{23}{2}={c=2}{l}, cell{23}{4}={c=2}{l}, cell{23}{6}={c=2}{l},
cell{24}{2}={c=2}{l}, cell{24}{4}={c=2}{l}, cell{24}{6}={c=2}{l},
cell{25}{2}={c=2}{l}, cell{25}{4}={c=2}{l}, cell{25}{6}={c=2}{l},
% R26 合计(3+2+2)
cell{26}{1}={c=3}{c}, cell{26}{4}={c=2}{l}, cell{26}{6}={c=2}{l},
% R27~R28 意见与签字(3+4)
cell{27}{1}={c=3}{l}, cell{27}{4}={c=4}{l},
cell{28}{1}={c=3}{l}, cell{28}{4}={c=4}{l},
}%
}{%
\end{tblr}\par\endgroup}
%======================================================================
% 5. 标题与页首
%======================================================================
% 左上角「附件1」小字(原 docx 无字号设置,随正文 9pt)
\newcommand{\oucmeetattach}[1]{%
\par\noindent{\Mn #1}\par\vspace*{4bp}}
% 标题:22pt 居中
\newcommand{\oucmeettitle}[1]{%
\par\begingroup\Mtw\heiti\centering #1\par\endgroup
\vspace*{8bp}}
% 填表日期 / 金额单位 那一行(左右分列,中间留白)
% 原 docx 用一串空格撑开,这里用 \hfill 更稳
\newcommand{\oucmeetdateline}[1]{%
\par\noindent{\Mn #1}\par\vspace*{4bp}}
%======================================================================
% 6. 表尾说明
%
% ⚠️ 悬挂缩进不能用 \leftmargin=2em —— 实测报
% Overfull \hbox (26.71pt too wide)
% 因为「1、」「2、」是**全角字符 + 顿号**,9pt 下实测宽约 2.4em,
% 比 2em 宽,标签凸出到左边界外。⇒ \labelwidth 要给足 2.5em。
%
% ⚠️⚠️ \itemindent 不能设负值!
% 第一版写 \itemindent=-2.5em 想让「1、」顶格,结果整段被拉到
% x=65.95,而版心左界是 79.40 —— **说明文字跑到版心外左侧 13.45bp**,
% 且只报一个 5.1pt 的 Overfull(因为 \hbox 宽度的计算没把左溢算进去)。
% ⇒ 正确做法:\leftmargin 与 \labelwidth 相等,\itemindent=0,
% 标签自然落在左边界,「1、」后面直接接正文。
% \labelwidth 要 ≥ 标签实际宽度(「1、」9pt 全角约 2.4em),给 2.5em。
% 见下方 \oucmeetnote 的说明。环境只负责字号与上间距,不再用 list。
\newenvironment{oucmeetnotes}{%
\par\vspace*{2bp}\begingroup\Mn
}{\par\endgroup}
%----------------------------------------------------------------------
% 单条说明
%
% ⚠️ 为什么不用 list 环境:list 的 \leftmargin / \itemindent 组合极易
% 把内容推出边界,而且**不报 Overfull**(踩过一次,文字跑到版心
% 左外 13.45bp,日志里只有个无关的 5.1pt)。
%
% ⚠️ 也不要用 \hangindent 悬挂缩进。「2、」后面直接接正文,续行不需要
% 缩进;相反,\hangindent 会让续行可用宽度变窄,第 2 条
% (50 字,两行)因此溢出 5.1pt。
% ⇒ 就是普通的 \noindent + 段落,什么都不设。
%
% ⚠️⚠️ \tolerance / \emergencystretch 必须在**段落开始前**设置。
% 写成 `\noindent` 之后再改无效(\noindent 已开段落,实测 5.1pt
% 的 Overfull 纹丝不动)。
% ⇒ 这里用 \sloppy,它在内部改 \tolerance 和 \emergencystretch,
% 且必须在 \par 之前调用 —— 下面 #1#2 之前就调了,有效。
%
% ⚠️ 为什么会溢出:第 2 条说明正好 50 字,9pt 下版心一行放 49 字,
% 第 50 字「现」挤到第二行后,第一行欠宽极小、第二行只有「金;」
% 两个字,TeX 判定这比"溢出 5.1pt"更难看。\sloppy 允许更松的断行,
% 两害相权取其轻。这是内容长度与版心宽度的巧合冲突,不是排版错误。
%----------------------------------------------------------------------
\newcommand{\oucmeetnote}[2]{%
\par\noindent\sloppy #1#2\par
\vspace*{1bp}}
\endinput