%====================================================================== % 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 都没有 (按 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