Files
ouc_latex_template/form2/ouclw.sty
T
laoyang e032aec228 新增劳务发放说明表 LaTeX 模板(form2/)#1
把实验室《劳务发放说明表》docx 复刻为 XeLaTeX 模板,与 exam/、form/ 同一套做法:
自包含子目录、版式逐项对齐原 docx、模板不含真人信息。

- form2/ouclw.sty      版式包:A4 + left=31.70mm + 表宽 150.32mm,
                       4 列 / 5 行、14pt 宋体、15.6pt 行距、0.5pt 边框、
                       1.905mm 单元格内边距,全部尺寸附 twips→mm 换算注释
- form2/劳务发放说明表.tex  主文件,填表只改 4 个变量
- form2/README.md      用法、换算表、四个坑、量化校验方法与实测数据
- README.md            目录表登记 form2/

文字改写:原文「聘请秦依康等2位研究生参与《观测数据驱动的渤海生态系统健康
评估关键技术研发》项目的研究工作。」改为「聘请xxx等x位研究生参与《xxx》
项目的研究工作。」,句式未动;其余内容一律 × 占位。

量化验收(pdftocairo -svg + pdftotext -bbox):
  表宽 150.137mm(目标 150.319,差 -0.18)
  表左  31.662mm(目标  31.700,差 -0.04)
  R0 11.172 / R1 5.668 / R2 49.646 / R3 22.159 / R4 11.172 mm 全部达标
  三个签字格标签基线、日期行基线完全重合
  编译零错误、零 Overfull / Underfull,输出 1 页

本次踩到并记录在 README 的四个坑:
1. ctex 默认 \linespread≈1.3 会把 \fontsize{14}{15.6} 放大成 20.28pt
2. 合并格 {c} 是 LR 模式,不折行而是撑宽整表;\hsize 也未重设
3. 合并格内容可用宽要补回 2*colsep*(n-1),否则长文本提前折行
4. \parbox[t] 与 tabularray 的 strut 叠加会每行多出 3.6pt
2026-10-26 16:18:58 +08:00

343 lines
16 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.
%======================================================================
% ouclw.sty — 中国海洋大学 劳务发放说明表 版式包
%
% 依据:实验室管理/劳务补贴/劳务发放说明表.docx(Word 2007+ 保存)
% 目标:版式与原 docx 逐项对齐。所有尺寸都由 docx 内部数值换算:
% twips → mm : mm = twips / 1440 * 25.4
% twips → pt : pt = twips / 20 (1 pt = 1/72 in)
% w:sz : 半磅值,÷ 2 即 pt
%
% 用法见同目录 劳务发放说明表.tex,说明见 README.md
%======================================================================
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{ouclw}[2026/10/26 v1.0 OUC labor payment form]
\RequirePackage{geometry}
\RequirePackage{fontspec}
\RequirePackage{xeCJK}
\RequirePackage{tabularray}
%======================================================================
% 1. 页面与版心
%
% 原 docx sectPr:
% pgSz 11906 × 16838 twips = 210.02 × 297.05 mm → A4
% pgMar top/bottom 1361 = 24.01 mm
% left/right 1797 = 31.70 mm
% header 851 / footer 992 = 15.01 / 17.51 mm
% docGrid linePitch 312 → 行距 15.6 pt
%
% 原表 tblW 8522 twips = 150.32 mm,比 docx 版心(146.68 mm)宽 3.71 mm。
% docx 的 tblPr 里【没有 jc】,tblInd = 0 ⇒ Word 是按左对齐处理的:
% 表从左边距 31.70 mm 起排,右端溢出到右侧页边距里 3.71 mm。
% 实测原表左右边距就是 pgMar 的 1797 twips = 31.70 mm,并不对称。
%
% ⇒ 本包照实复现这个“不对称”:
% left = 31.70 mm (= docx pgMar left)
% 表宽 = 150.32 mm
% ⇒ 右边界自然落在 210 − 31.70 − 150.32 = 27.98 mm
%
% ⚠️ 为什么不写成 left = right = 29.84 mm(把表居中):
% 那样表会整体左移 1.86 mm,与 Word 里看到的位置不符。
% 代价是 \textwidth 变成 150.32 mm 而非 docx 的 146.68 mm ——
% 本表除标题外没有通栏正文,标题是居中的,
% 居中后中心仍在 31.70 + 75.16 = 106.86 mm,
% 与 docx 的 31.70 + 73.34 = 105.04 mm 差 0.04 mm(取整误差级)。
%======================================================================
\geometry{
paperwidth = 210mm,
paperheight = 297mm,
left = 31.70mm, % = docx pgMar left 1797 twips
totalwidth = 150.32mm, % = 表宽,精确;geometry 由此算出 right=27.98mm
top = 24.01mm,
bottom = 24.01mm,
headheight = 0pt,
headsep = 0pt,
footskip = 17.51mm,
}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0pt}
\pagestyle{empty} % 原 docx 无页眉页脚,故不排页码
%======================================================================
% 2. 字体与字号
%
% 全文 宋体 14pt (w:sz=28) ← 注意是宋体,不是仿宋
% 大标题 宋体 14pt 加粗(w:b + w:sz=28),居中
% 行距 15.6pt (docGrid linePitch=312 twips)
%
% ⚠️ 本表字号只有 14pt 一档,没有 .docx 里那种混排小字。
%
%----------------------------------------------------------------------
% ⚠️⚠️⚠️【最要命的一条】ctex 默认 \linespread 约 1.3,会把
% \fontsize{14}{15.6} 的 15.6pt 行距放大成 20.28pt!
%
% 实测:
% \fontsize{14}{15.6}\selectfont → \baselineskip = 20.28pt ✗
% 加 \linespread{1} 之后 → \baselineskip = 15.60pt ✓
%
% 后果:所有行都高 30%,五行表从 99mm 涨到 129mm,行高全对不上
% (R0 35.66 vs 31.2、R3 69.3 vs 62.4)。而且 ht= 也压不住 ——
% 内容本身就那么高,tabularray 只能被动撑开。
%
% \fontsize 的第二个参数【本来是】用来设 \baselineskip 的,但
% \selectfont 时会再乘一次 \linespread,所以必须先把 \linespread
% 归 1。这一步不能省,也必须在 \AtBeginDocument 之后再设一次
% (ctex 在 begin document 时会重设)。
%======================================================================
\setmainfont{Times New Roman}
\setCJKmainfont{SimSun}[AutoFakeBold=2.5]
\newCJKfontfamily\songti{SimSun}[AutoFakeBold=2.5]
\newCJKfontfamily\heiti{SimHei}
\newCJKfontfamily\fangsong{FangSong}[AutoFakeBold=2.5]
\newcommand{\lwbodyfont}{%
\linespread{1}\relax
\fontsize{14}{15.6}\selectfont}
%----------------------------------------------------------------------
% ⚠️ xeCJK 陷阱:局部字体切换命令若紧邻 \par(或落在段落末尾),段末的
% 字体状态会“溢出”到后面段落。故局部切换一律用带 \normalfont 收尾的
% 包装,不要直接写 {\heiti ...\par}。
%----------------------------------------------------------------------
\newcommand{\LWFS}[1]{{\fangsong #1\normalfont}}
\newcommand{\LWHT}[1]{{\heiti #1\normalfont}}
%======================================================================
% 3. 列的宽度模型
%
% 原 docx tblGrid(4 个网格列,twips → mm):
% c1 1384 → 24.4122 c2 1418 → 25.0119
% c3 2883 → 50.8529 c4 2837 → 50.0415
% 合计 8522 twips = 150.3186 mm(与 tblW 一致)
%
% 各行合并跨度(docx gridSpan):
% R0/R1/R2/R4 c1 | c2-c4(span 3)
% R3 c1-c2(span 2)| c3 | c4
%
%----------------------------------------------------------------------
% ⚠️ 本表与年度考核表(form/)的关键差别:**可以用 colsep 真实还原内边距**。
%
% docx 的 tblCellMar 是 top/bottom = 0、left/right = 108 twips(1.905 mm)。
% 年度考核表里有个只有 1.41 mm 宽的 c10,比两倍内边距还窄,所以只能取
% colsep=0 并放弃内边距。本表最窄的列也有 24.41 mm,完全可以照实还原:
%
% colsep = 1.905mm
%
% tabularray 的 colsep 计在每列【左右两侧】,故 4 列共 +8×1.905 = 15.24 mm;
% 竖线 5 根(左右边界 + 3 内部)× 0.5pt = 2.5pt = 0.882 mm。
% 要让 Σwd + 15.24 + 0.882 = 150.3186,需
% Σwd = 134.197 mm
% 而 (gridCol − 2×1.905) 之和为 135.079 mm,两者差 0.882 mm 正好是竖线宽,
% 故每列再减 0.882/4 = 0.2204 mm:
%
% wd = gridCol(mm) − 3.81 − 0.2204
%
% ⚠️ 上式只是理论值。tabularray 的计宽与画宽之间有约 0.45 pt 的取整差,
% 实测需再微调(见 3.2 的实测标定值),否则一直报
% “Table width is too small, need 0.45pt more!” 外加一条 Overfull \hbox。
%
%----------------------------------------------------------------------
% ⚠️ 两条通用结论(在 form/ 里踩过,这里同样适用):
%
% (a) 合并单元格 cell{r}{c}={c=n}{c} 的单元类型 {c} 是【不折行的 LR 模式】。
% 格内文字若比所跨各列合计更宽,它不会在列内折行,而是【把这些列撑宽】,
% 整张表随之超出版心。⇒ 合并格里的文字一律包定宽 \parbox(见 \lwfit)。
% 单列 Q[c,m,wd=…] 没有这个问题:超出时只溢出、列宽不变。
%
% (b) 合并单元格里的 \hsize【没有】被重设成该跨度的宽度,仍是外层版心宽。
% ⇒ 凡是内容较长或含多段的跨列单元格,必须用固定宽 \parbox 包起来。
%======================================================================
\newcommand{\lwlwval}{0.5pt} % docx tblBorders sz=4 ⇒ 0.5pt(sz 单位 1/8pt)
\newcommand{\lwcolsep}{1.905mm} % docx tblCellMar left/right 108 twips
\newlength{\lwtblw}
\setlength{\lwtblw}{150.32mm} % 表宽(含 5 根竖线与 8 处内边距)
%----------------------------------------------------------------------
% 3.1 各列宽(= gridCol − 3.81mm − 0.2204mm 的理论值)
% 与 lwtable 环境里 colspec 的 wd 必须逐字一致。
%----------------------------------------------------------------------
\newcommand{\lwwa}{20.3828mm} % c1 24.4122 − 4.0304
\newcommand{\lwwb}{20.9825mm} % c2 25.0119 − 4.0304
\newcommand{\lwwc}{46.8235mm} % c3 50.8529 − 4.0304
\newcommand{\lwwd}{46.0121mm} % c4 50.0415 − 4.0304
%----------------------------------------------------------------------
% 3.2 各跨度的【内容可用宽】
%
% ⚠️⚠️ 这里有一个必踩的坑:合并单元格(cell{r}{c}={c=n}{c})合并后,
% n 列之间的 (n−1) 组内边距【会被回收进内容宽】。
% ⇒ 内容可用宽 = Σ 所跨各列 wd + 2·colsep·(n−1)
%
% 只有 1 列时(\lwwa…\lwwd)内容宽 = wd,因为左右两半 colsep 已被
% colspec 扣掉;跨 n 列时则不然,必须补回来。
%
% 漏算的后果非常隐蔽:\parbox 比真实格宽窄,短文本看不出问题,
% 长文本会【提前折行】——本表“批准单位负责人签字”就曾被折成
% “批准单位负责人签”+“字”两行,而 docx 里是一行。
%----------------------------------------------------------------------
% c1-c2 = 20.3828 + 20.9825 + 2×1.905 = 45.1753mm(“批准单位负责人签字”)
\newcommand{\lwspanA}{45.1753mm}
% c2-c4 = 20.9825 + 46.8235 + 46.0121 + 4×1.905 = 121.4381mm(长句等)
\newcommand{\lwspanB}{121.4381mm}
%======================================================================
% 4. 行高
%
% 原 docx 各行【没有 trHeight】,高度全由段数决定
% (每段 = 1 个行距 15.6 pt):
% R0 2 段 → 31.2 pt = 11.01 mm
% R1 1 段 → 15.6 pt = 5.50 mm
% R2 9 段 → 140.4 pt = 49.53 mm ← 长句 1 段 + 8 个空段
% R3 4 段 → 62.4 pt = 22.01 mm ← 标题 1 + 空 2 + “年 月 日” 1
% R4 2 段 → 31.2 pt = 11.01 mm
% 合计 280.8 pt = 99.06 mm
%
% ⚠️ tabularray 的 ht= 经实测同为“最小行高”,与 Word 的 atLeast 语义一致。
% 本表直接把这些值写成 ht=,行高即固定;内容更多的行会自然长高。
%======================================================================
%======================================================================
% 5. 表格环境
%
% ⚠️ 用 tblr 而非 longtblr:本表总高约 100 mm,一页装得下,
% 不需要断页,也就不需要清 caption 模板。
%
% ⚠️⚠️ colspec 与 row{} 必须字面写在这里,不能先存进宏再展开:
% tabularray 的列类型解析在宏展开之外进行,会报
% “Unexpandable command \xxx inside column type”。
% (所以 3.1 节那些 \lwwa… 只作文档用,不能拼进 colspec。)
%
% ⚠️⚠️⚠️ 跨度用 cell{r}{c}={c=n}{c} 声明,不要用行内 \SetCell[c=n]{c}:
% \SetCell 之后要补若干个空 &,补几个取决于该格跨几列,一旦数错一位,
% 同行后面所有单元格会整体错列到窄列,报出难以定位的 Overfull。
% ⚠️ 正确写法只有 {c=n}{c};={c=n}、={n}{c}、={span=n}{c} 都不认。
%
% docx 各行的 gridSpan:
% R0/R1/R2/R4 1,3 R3 2,1,1
%
% ⚠️ 选项里的宏名一律不带 @:tabularray 解析选项时 @ 不是字母。
%======================================================================
\newenvironment{lwtable}{%
\par\begingroup
\begin{tblr}[label=none]{
colspec={
Q[c,m,wd=20.3828mm] Q[c,m,wd=20.9825mm]
Q[c,m,wd=46.8235mm] Q[c,m,wd=46.0121mm]
},
colsep=1.905mm, rowsep=0pt, stretch=0,
hline{1-Z}={\lwlwval}, vline{1-Z}={\lwlwval},
row{1}={ht=11.01mm}, row{2}={ht=5.50mm}, row{3}={ht=49.53mm},
row{4}={ht=22.01mm}, row{5}={ht=11.01mm},
%---- R0/R1/R2/R4 c1 | c2-c4
cell{1}{2}={c=3}{c}, cell{2}{2}={c=3}{c}, cell{3}{2}={c=3}{c},
cell{5}{2}={c=3}{c},
%---- R3 c1-c2 | c3 | c4
cell{4}{1}={c=2}{},
}%
}{%
\end{tblr}\par\endgroup}
%======================================================================
% 6. 单元格内容包装(见 3 节结论 a / b)
%
% 统一入口,.tex 里只用下面几个宏,不要直接把长文字写进 & 之间。
%======================================================================
% \lwfit{跨度宽宏}{内容} —— 定宽、水平居中、可折行(用于合并格里的标签)
\newcommand{\lwfit}[2]{\parbox{#1}{\centering #2}}
% \lwcell{内容} —— c2-c4 内容栏,左对齐、可折行
%
% ⚠️⚠️ 这里【必须】保持 \parbox 默认的 [c],不要改成 [t]。
% 原因:tabularray 会给每个单元格垫一个 strut,[t] 生成的盒子顶部
% 与 strut 顶部相加,实测每行白白多出 3.6pt(R0 从 31.67 → 35.26)。
%
% docx 里 R0C1 / R2C1 / R4C1 都没有 <w:vAlign>(按 OOXML 语义是顶部对齐),
% 但它们靠【段落数把行高填满】实现“贴顶”:
% R0C1 2 段 = 31.2pt = 行高
% R2C1 9 段 = 140.4pt = 行高
% 内容高度已等于行高,[c] 居中的结果就是贴顶,等价。
% ⇒ 场合决定用法:段落数够 → \lwcellv{内容 + \lwblank{n}};
% 段落数不够(如 R1 只有 1 段)→ 也正是 vAlign=center。
\newcommand{\lwcell}[1]{\parbox{\lwspanB}{\raggedright #1}}
% \lwcellv{内容} —— 同上(保留别名,语义上表示“垂直居中”)
\newcommand{\lwcellv}[1]{\parbox[c]{\lwspanB}{\raggedright #1}}
% \lwstack{内容} —— c1-c2 合并格(通用,任意多段)
\newcommand{\lwstack}[1]{\parbox{\lwspanA}{\centering #1}}
%----------------------------------------------------------------------
% \lwsign{格宽}{签字标签} —— R3 的签字格
%
% docx 里 R3 每格都是 4 段:
% 标签 / 空 / 空 / “年 月 日”
% 即标签与日期之间有【2 个空行距 = 31.2pt】的签名留白,不是 1 行。
% 4 段 × 15.6pt = 62.4pt = 22.01mm,正好等于行高,故 vAlign 取哪个都一样。
%
% ⚠️ R3 第一格是合并格(c1-c2),另外两格是普通列。tabularray 对这两种格
% 的垂直居中算法【不一样】:合并格内容整体偏低 1.55 pt。
% 三个格分别裸写内容时,实测基线为
% 批准单位负责人签字 y = 280.65
% 用工单位负责人签字 y = 279.10
% 经办人签字 y = 279.10
% 肉眼勉强看得出参差。把所有格都包成【同一种】\parbox 后,
% 垂直居中的对象变成同一个盒子高度,三格自然对齐(实测 y 完全相同)。
% 故本表三个签字格一律走这个宏。
%----------------------------------------------------------------------
\newcommand{\lwsign}[2]{%
\parbox[c]{#1}{\centering #2\par\vspace*{31.2pt}\lwdate}}
%======================================================================
% 7. 签字栏的“年 月 日”
%
% docx 里是右对齐,字间用 3 个半角空格(Times New Roman):
% “年 月 日”,jc=right
% R3C1 另加 firstLine=700 twips / firstLineChars=250(2.5 字)缩进,
% 但既然整段右对齐,缩进只影响折行,不影响位置,故忽略。
%
% ⚠️ 用固定宽 \parbox 内的 \raggedleft,不要用 \hfill:
% \hfill 在跨越 \par 的段尾会撑满 \hsize,若 \hsize 与 parbox 宽
% 不一致就会冲出格子。
%======================================================================
\newcommand{\lwdate}{%
\par\noindent
\makebox[\linewidth][r]{年\hspace{1.5em}月\hspace{1.5em}日}}
% 空段落:\lwblank{n} ⇒ 追加 n 个空行
%
% ⚠️ 不要用 \par\null:实测在 tabularray 单元格里,9 个 \par\null 会排成
% 192.07pt,而 docx 里 9 个空段落只有 140.4pt。原因是 \null 是一个
% 有高度的盒子,每个都按“汉字 + 行距”算钱;Word 的空段落只贡献行距。
% ⇒ 行高统一交给 tabularray 的 ht=(实测 ht=49.53mm 排出 140.73pt,与
% docx 的 140.4pt 差 0.33pt,可忽略),本宏只用于确有需要的场合。
\newcommand{\lwblank}[1]{%
\par
\@tempcnta=0
\loop\ifnum\@tempcnta<#1\relax
\mbox{}\par\advance\@tempcnta by 1
\repeat}
%======================================================================
% 8. 表外标题
%
% 原 docx 第 1 段:宋体、加粗、sz=28(14pt)、jc=center
% 注意字号与正文相同,只靠加粗区分,不是常见的大标题。
%======================================================================
\newcommand{\lwtitle}{%
\par
\begingroup
\centering
{\songti\bfseries\fontsize{14}{15.6}\selectfont 劳务发放说明表\par}%
\endgroup}
%======================================================================
% 9. 表后空行
%
% 原 docx 表格之后还有一个 jc=center 的空段落。
%======================================================================
\newcommand{\lwtail}{\par\null\par}
\AtBeginDocument{\lwbodyfont}
\endinput