%====================================================================== % ouckhtb.sty — 中国海洋大学 教职工年度考核登记表 版式包 % % 依据:2025 年学校下发的 Word 表(WPS 保存的 .docx) % 目标:版式与逐项对齐。所有尺寸都由 docx 内部数值换算而来: % twips → mm : mm = twips / 1440 * 25.4 % twips → pt : pt = twips / 20 (1pt = 1/72in) % w:sz : 半磅值,÷2 即 pt % % 用法见同目录 年度考核登记表.tex,参数说明见 README.md %====================================================================== \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{ouckhtb}[2026/10/23 v1.0 OUC staff annual review form] \RequirePackage{geometry} \RequirePackage{fontspec} \RequirePackage{xeCJK} \RequirePackage{tabularray} %====================================================================== % 1. 页面与版心 % % sectPr: % pgSz 11907 × 16840 twips = 210.03 × 297.04 mm → A4 % pgMar top/bottom 1418 = 25.01 mm % left/right 1701 = 30.00 mm % header 851 / footer 992 = 15.01 / 17.50 mm % % 原表宽 tblW 8976 twips = 158.33 mm,比 docx 版心(150 mm)宽 8.33 mm, % Word 靠 tblPr/jc=center 把它居中压进左右页边距。 % % ⚠️ 本包【不】照搬 docx 的 30 mm 页边距,而是取 % left = right = (210 − 158.33)/2 = 25.835 mm % 使 \textwidth 恰为 158.33 mm。这样: % · 表格天然居中,无需 leftskip / \centerline 之类的偏移技巧 % · 表格宽度 = 版心宽度,编译时不会刷 Overfull \hbox % · 表格在纸面上的绝对位置与 Word 完全相同(左右各 25.835 mm) % 正文(表外标题、脚注)仍是水平居中,视觉上与 30 mm 版心一致。 %====================================================================== \geometry{ paperwidth = 210mm, paperheight = 297mm, totalwidth = 158.33mm, % = 表宽,精确;不要写 left/right(见下) hmarginratio = 1:1, % 左右等边距,表格自然居中 top = 25.01mm, bottom = 25.01mm, headheight = 0pt, headsep = 0pt, footskip = 17.50mm, } \setlength{\parindent}{0pt} \setlength{\parskip}{0pt} \pagestyle{plain} %====================================================================== % 2. 字体与字号 % % 正文 仿宋 14pt (w:sz=28) % 表外大标题 黑体 22pt (w:sz=44),docx 另设 w:spacing=-14 % ⇒ -14/20 = -0.7pt,相对 22pt 即 -3.2% % “2025 年度”一行 仿宋 16pt (w:sz=32) % R1C6 的括注 仿宋 12pt (w:sz=24),只有“研究所、实验室、科室” % 页脚页码 9pt (footer 样式 w:sz=18) % % FangSong / SimHei / SimSun + AutoFakeBold,换台机器(尤其 Linux/macOS) % 就编不出来。现已换成随仓库分发的开源字体,粗体用字体自带真粗体。 %====================================================================== \input{../fonts.tex} % 正文字号;行距 15.6pt 对应 docx docGrid/linePitch=312 twips \newcommand{\khtbbodyfont}{\fontsize{14}{15.6}\selectfont} % 括注小字:docx 里“(对考核档次为……需说明原因)”是 12pt (w:sz=24) \newcommand{\khtbsmallnote}{\fontsize{12}{14}\selectfont} % R0C6“系(研究所、实验室、科室)”在 docx 里是混排: % “系” w:sz=28(14pt),“(研究所、实验室、科室)” w:sz=24(12pt) % 用下面这个宏按原样排,别整句都用 14pt —— 否则折行位置会与 Word 不同。 \newcommand{\khtbxi}{\fangsong 系{\fontsize{12}{14}\selectfont (研究所、实验室、科室)\normalfont}} %---------------------------------------------------------------------- % ⚠️ xeCJK 陷阱:局部字体切换命令若紧邻 \par(或落在段落末尾),段末的 % 字体状态会“溢出”到后面段落,使后续 ASCII 字符被排成别的字体。 % 故局部切换一律走下面两个包装(内部用 \normalfont 收尾)。 %---------------------------------------------------------------------- \newcommand{\KHTBFS}[1]{{\fangsong #1\normalfont}} \newcommand{\KHTBHT}[1]{{\heiti #1\normalfont}} %====================================================================== % 3. 列的宽度模型(本包最关键的一节,改之前请先读完) % % tblGrid(11 个网格列,twips → mm): % c1 1087→19.17 c2 283→ 4.99 c3 1221→21.54 % c4 1047→18.47 c5 284→ 5.01 c6 1105→19.49 % c7 952→16.79 c8 600→10.58 c9 870→15.35 % c10 80→ 1.41 c11 1447→25.52 % 合计 8976 twips = 158.33mm(与 tblW 一致) % % 各行的合并跨度(docx gridSpan,每行之和均为 11): % R0 2,1,1,2,1,1,1,2 (8 个单元) % R1 2,3,2,4 (4 个单元) % R2 2,1,2,2,3,1 (6 个单元) % R3~R8 1,10 (2 个单元:标签 + 内容栏) % %---------------------------------------------------------------------- % ⚠️⚠️ 四条实际结论(tabularray 2022A),决定了下面的写法: % % (a) tabularray 的 wd 是“内容宽”,不含任何内边距;表宽 = Σwd + 竖线宽。 % 而 Word 的 gridCol 是“含内边距的整格宽”。两者语义相反。 % % tabularray 计宽时按 11 根竖线算(左边界那根不算),即 Σwd + 11×0.5pt % = Σwd + 5.5pt。要让它恰好等于表宽 158.33mm,需 % Σwd = 158.3267mm − 5.5pt = 158.3267 − 1.9329 = 156.3938mm % 而 ΣgridCol = 158.3267mm,故每列要减 (158.3267−156.3938)/11 % = 0.1757mm。(gridCol − 0.1757) 即下面 3.1 节的取值。 % % ⚠️ 曾取 0.1917mm(按 12 根竖线 6pt 算),表宽少 0.5pt, % longtblr 会一直报 “Table width is too small, need 0.50008pt more!” % 外加一条 0.5pt 的 Overfull \hbox。差得极小但很碍眼,故校正。 % % (b) Word 的 tblCellMar 左右各 108 twips(1.905mm)。本包【不】复现这个 % 内边距,而取 colsep=0、内容可用宽 = 整格宽。原因:c10 只有 1.41mm, % 比两倍内边距(3.81mm)还窄,统一 colsep 必然把该列算成负宽而报错。 % 代价:单元格文字可以贴到竖线上。本表所有单元格都是水平居中,且 % 窄列标签(如“出生年月”)在 Word 里本来就要折成两行,观感一致。 % % (c) ⚠️ 合并单元格(\SetCell[c=n])里的 \hsize【没有】被重设成该跨度的 % 宽度,它仍是外层版心宽。span=10 的格子里 \linewidth=47.8pt % 但 \hsize=450.5pt,于是长文本按 450pt 折行、彻底冲出格子 % ⇒ 凡是内容较长或含多段的跨列单元格,必须用固定宽的 \parbox 包起来。 % % (d) ⚠️⚠️⚠️【最要命的一条】跨列合并格 cell{r}{c}={c=n}{c} 的单元类型 % {c} 是【不折行的 LR 模式】。格内文字若比所跨各列的合计宽,它不会在 % 列内折行,而是【把这些列撑宽】,整张表随之超出版心。 % “系(研究所、实验室、科室)”共 12 字(14pt ≈ 168pt), % 塞进 c6-c7(合计 35.90mm ≈ 101.8pt)后,c6-c7 被撑到 126.5pt, % 整表由 448.3pt 涨到 558.9pt —— 正是那 111.6pt 的 Overfull \hbox。 % 而【单列】Q[c,m,wd=…] 没有这个问题:内容超出时只溢出、列宽不变 % (实际在 10.39mm 的 c8 里写 4 个汉字,表宽仍为 448.3pt)。 % ⇒ 结论:**凡是对齐到合并/窄格里、又必须折行的文字,一律包 \parbox**。 % 本包为此给每个跨度备了常量与 \khtbfit 包装,见下面 3.2 节。 %====================================================================== \newcommand{\khtblwval}{0.5pt} % docx tblBorders sz=4 ⇒ 0.5pt(sz 单位 1/8pt) \newcommand{\khtbcellmar}{0} % 不再使用内边距,保留名字便于将来调整 \newlength{\khtbtblw} \setlength{\khtbtblw}{158.33mm} % 表宽(含 12 根竖线) %---------------------------------------------------------------------- % 3.1 各列宽(= gridCol − 0.1917mm),单位 mm,四位小数足够。 % 与 khtbtable 环境里 colspec 的 wd 值必须逐字一致。 %---------------------------------------------------------------------- \newcommand{\khtbwa}{18.9661mm} % c1 \newcommand{\khtbwb}{4.7844mm} % c2 \newcommand{\khtbwc}{21.3297mm} % c3 \newcommand{\khtbwd}{18.2605mm} % c4 \newcommand{\khtbwe}{4.8020mm} % c5 \newcommand{\khtbwf}{19.2836mm} % c6 \newcommand{\khtbwg}{16.5848mm} % c7 \newcommand{\khtbwh}{10.3759mm} % c8 \newcommand{\khtbwi}{15.1384mm} % c9 \newcommand{\khtbwj}{1.2037mm} % c10 \newcommand{\khtbwk}{25.3162mm} % c11(多承担了 0.0198mm 的取整余量) %---------------------------------------------------------------------- % 3.2 各跨度的可用宽(= 所跨各列 wd 之和) %---------------------------------------------------------------------- \newcommand{\khtbspanA}{23.7505mm} % c1-c2 职工号 / 单 位 / 政治面貌 \newcommand{\khtbspanB}{44.3922mm} % c3-c5 单位名(学部 / 学院) \newcommand{\khtbspanC}{35.8684mm} % c6-c7 系(研究所、实验室、科室) \newcommand{\khtbspanD}{52.0342mm} % c8-c11 系名 / 学院名 \newcommand{\khtbspanE}{23.0625mm} % c4-c5 现专业技术职务 \newcommand{\khtbspanF}{26.7180mm} % c8-c10 所聘岗位 \newcommand{\khtbspanG}{26.5199mm} % c10-c11 出生年月值 \newcommand{\khtbspanH}{24.0856mm} % c5-c6 姓名值 / 性别值 % 右侧内容栏(c2-c11,跨 10 列)的可用宽 = 十列 wd 之和。 % ⚠️ 不要再用“实际\linewidth”得到的那个 379.65pt:它是带竖线、且被 % xeCJK 行首调整过的结果,换算成 mm 后比真实格宽窄 7mm,会让签字横线 % 和勾选框提前收尾、右侧留出一大段空白。 \newcommand{\khtbcellw}{137.0792mm} %====================================================================== % 4. 行高 % % trHeight + hRule=atLeast(twips → mm): % R0 412→7.27 R1 460→8.11 R2 939→16.56 % R3 1261→22.24 R4 1252→22.08 R5 1009→17.80 % R6 556→ 9.81 R7 1258→22.19 R8 1258→22.19 % % ⚠️ tabularray 的 ht= 经实际同为“最小行高”(ht=0pt 与 ht=5mm 同高, % ht=22mm 才撑开),与 Word 的 atLeast 语义一致 —— 内容多时行会自动 % 长高,与 Word 表现相同,无需额外处理。 %====================================================================== %====================================================================== % 5. 表格环境 % % ⚠️⚠️ 必须用 longtblr 而不是 tblr: % 本表内容使总高远超一页可用高度(也是 2 页)。而 tblr 是 % 一整块不可分割的盒子 —— 装进 \setbox 后报 Overfull \vbox (140pt), % 第 1 页只剩标题、表格整体被推到第 2 页。longtblr 才支持按行断页。 % % ⚠️ longtblr 默认会给每页续接处印“表 0: (Continued)”。本表是公文表格, % 绝不能出现这种字样,故下面用 \DefTblrTemplate 把 % caption / capcont / conthead-text / contfoot-text 四个模板全部清空。 % (只写 caption={} 不够,续页标题仍会出来。) % % ⚠️⚠️ colspec 与 row{} 必须字面写在这里,不能先存进宏再展开: % tabularray 的列类型解析在宏展开之外进行,会报 % “Unexpandable command \xxx inside column type”。 % (所以上面 3.1 节的 \khtbwa… 只作文档用,不能拼进 colspec。) % % ⚠️⚠️⚠️ stretch=0 必须写:longtblr(与 tblr 不同)默认把各列按 \textwidth % 等比拉伸, 11 列 wd 合计 450.46pt 的表被拉成 467.16pt(+16.7pt), % 表格右边界从 523.7pt 越到 540.4pt,压在页边距外。 % 加上 stretch=0 后实际宽 448.3pt,与版心严格一致。 % % ⚠️⚠️ 【易错点】单元格合并(跨度)全部写死在本环境里,用 % cell{行}{列}={c=n}{c} % 声明,而【不要】在数据行里用 \SetCell[c=n]{c}。原因: % · \SetCell 之后要补若干个空 &,补几个取决于该格跨几列;一旦 % 某一个数错一位,同行后面所有单元格会整体错列到窄列(如 1.2mm % 的 c10)里去,报出【每一行都超宽】的 Overfull,从行号上完全 % · cell{r}{c}={c=n}{c} 把跨度与数据行分离,每行老老实实写满 % 11 个单元格,行列对应一目了然。 % ⚠️ 正确写法只有 {c=n}{c};={c=n}、={n}{c}、={span=n}{c} 都不认。 % {c=n} 的那个 n 是【列数】,不是“跨度”关键字。 % 因为本包只服务这一张表,跨度直接写死在下面,.tex 里不用再管。 % % docx 各行的 gridSpan(改表时对着数): % R0 2,1,1,2,1,1,1,2 R1 2,3,2,4 R2 2,1,2,2,3,1 R3~R8 1,10 % % ⚠️ 出现在下面选项里的宏名一律不带 @:tabularray 解析选项时 @ 不是 % 字母,\khtb@lw 会被拆成 \khtb + @lw → Undefined control sequence。 %====================================================================== \DefTblrTemplate{caption}{default}{} \DefTblrTemplate{capcont}{default}{} \DefTblrTemplate{conthead-text}{default}{} \DefTblrTemplate{contfoot-text}{default}{} \newenvironment{khtbtable}{% \par\begingroup \begin{longtblr}[label=none]{ colspec={ Q[c,m,wd=18.9661mm] Q[c,m,wd=4.7844mm] Q[c,m,wd=21.3297mm] Q[c,m,wd=18.2605mm] Q[c,m,wd=4.8020mm] Q[c,m,wd=19.2836mm] Q[c,m,wd=16.5848mm] Q[c,m,wd=10.3759mm] Q[c,m,wd=15.1384mm] Q[c,m,wd=1.2037mm] Q[c,m,wd=25.3162mm] }, colsep=0pt, rowsep=0pt, stretch=0, hline{1-Z}={\khtblwval}, vline{1-Z}={\khtblwval}, row{1}={ht=7.27mm}, row{2}={ht=8.11mm}, row{3}={ht=16.56mm}, row{4}={ht=22.24mm}, row{5}={ht=22.08mm}, row{6}={ht=17.80mm}, row{7}={ht=9.81mm}, row{8}={ht=22.19mm}, row{9}={ht=22.19mm}, }% }{% \end{longtblr}\par\endgroup} %====================================================================== % 6. 单元格内容包装 % % ⚠️⚠️【跨列合并一律在数据行里用行内 \SetCell,不要用表头的 cell{r}{c}】 % % % 原先表头写的是: % cell{1}{1}={c=2}{c}, cell{1}{5}={c=2}{c}, cell{1}{10}={c=2}{c}, % 数据行按 11 列老老实实写满 12 格,结果 **「出生年月」的值整格消失**, % 日志零 error 零 warning,光看日志根本发现不了。 % % 根因:tabularray 的 cell{r}{c}={c=n} 里那个 c 是【数据行的单元序号】, % 不是视觉列号。三处 c=2 加起来让该行要吃掉 15 个视觉列,而表只有 11 列, % 超出的部分被静默丢弃 —— 被丢的正好是最后一个值。 % 同一行写 12 格 → 丢值;写 10 格/9 格 → 反而显示(因为空缺让它 % 有余量)。所以「格子数对不对」在这里完全不能当判断依据。 % % 正解:表头不写 cell{},合并直接标在被合并的那个格子上: % \SetCell[c=2]{c} 职工号 & & 0000000 & ... % 每行照常写满 11 格,跨度由行内标记自己声明,不再有隐式吞格。 % 改写后零 error,11 列全值齐在。 %====================================================================== % \khtbfit{跨度宽宏}{内容} —— 定宽、水平居中、可折行 % 例:\khtbfit{\khtbspanC}{系(研究所、实验室、科室)} \newcommand{\khtbfit}[2]{\parbox{#1}{\centering #2}} % \khtbcell{内容} —— 整条右侧内容栏(c2-c11),垂直居中 \newcommand{\khtbcell}[1]{\parbox{\khtbcellw}{#1}} % \khtbcellb{内容} —— 同上,内容贴底(用于签字栏) \newcommand{\khtbcellb}[1]{\parbox[b]{\khtbcellw}{#1}} %====================================================================== % 7. 竖排栏目标签 % % 只有 R3C0 的“年度总结”在 docx 里是【一个字一个段落】显式堆出来的 % (R3C0 有 16 个 ,“年”“度”“总”“结”各占一段)。 % 其余窄列标签(R4“系(所、实验室、科室)考评意见”、R5“师德考核结果”、 % R6“单位年度考核建议档次”、R7“年度考核结果”)在 docx 里都是 % 【一个普通段落】,靠列窄自然折行,所以包个 \khtbfit 直接写文字即可。 %====================================================================== \newcommand{\khtbvert}[1]{% \begingroup \khtbbodyfont \def\khtbvfirst{}% \@tfor\khtbvch:=#1\do{% \khtbvfirst\khtbvch\par \def\khtbvfirst{}}% \endgroup} % \khtbstack{本人,意见} —— 按 docx 的【分段】竖排,每项各占一行 % R8 首列在 docx 里是 3 个段落(空、“本人”、“意见”),故“本人”和 % “意见”各成一行;若直接写“本人意见”四字,窄列会折成“本人意/见”, % 与 Word 不同。 \newcommand{\khtbstack}[1]{% \begingroup \khtbbodyfont \def\khtbSfirst{}% \@for\khtbSitem:=#1\do{% \khtbSfirst\khtbSitem\par \def\khtbSfirst{}}% \endgroup} %====================================================================== % 8. 签字 / 日期 / 勾选框 % % ⚠️ 这几个宏全部按 \linewidth 定长,因此【必须】放在定宽 \parbox 里用 % (\khtbcell / \khtbcellb),否则 \linewidth 会等于版心宽而冲出格子。 % % ⚠️ 不要用 \hrulefill:它按 \hsize 撑满整行。在 \parbox 里 \hsize 与 % \linewidth 一致时勉强可用,但本包历史上出过“横线冲出格子、右边界从 % 522px 涨到 557px”的事故,故一律改用显式长度的 \rule。 %====================================================================== % 签字行:标签 + 一条一直画到行末的横线 % \khtbsign{本人签名:} % % ⚠️ 参数是【必选】的,不要写成 \newcommand{\khtbsign}[1][默认]{...}。 % 那样调用 \khtbsign{负责人签名:} 时,{...} 不是可选参数形式 [...] % 而不会被吃掉,结果是:先印出默认的“本人签名:”,随后那个参数组 % 在 \par 之后再单独排成一行 —— 一格里凭空多出两行签字,且标签还是错的。 \newbox\khtbsignbox \newcommand{\khtbsign}[1]{% \par\noindent \sbox\khtbsignbox{#1}% \usebox\khtbsignbox \raisebox{-0.4ex}{\rule{\dimexpr\linewidth-\wd\khtbsignbox\relax}{0.4pt}}% \par} % 日期行:年 / 月 / 日 靠右,中间留手写空隙 \newcommand{\khtbdate}{% \par\noindent \makebox[\linewidth][r]{年\hspace{1.6em}月\hspace{1.6em}日}% \par} % 填空横线:\khtbunderline{6em} ⇒ 一条 6em 长的横线 % docx 里“考核结果为____”处是若干带 w:u 的空格,这里换成规则横线。 % ⚠️ 横线长度必须用参数 #1,不能写 \linewidth —— makebox 内部 \linewidth % 并未改变,仍是外层 parbox 的宽度(389pt),会画出一条贯穿整栏的线。 \newcommand{\khtbunderline}[1]{% \makebox[#1][c]{\rule[-0.4ex]{#1}{0.4pt}}} % 档次勾选框:每项前一个空心方框 % \khtbchoices{优秀,合格,基本合格,不合格} % % ⚠️ 项间用 \hss 做弹性间距,而不是固定 \hspace{1.5em}: % 5 项(优秀/合格/基本合格/不合格/不确定档次)在 14pt 下按固定 1.5em % 排要 395pt,超过右侧内容栏的 389pt,会把整张表撑宽。 % 用 \hbox to\linewidth + 5 个 \hss(首、尾、中间 3 处等分), % 剩余空间自动均分,总宽永不超过栏宽。 % % ⚠️ 用 Wingdings 的 ¨(char 0x00A8)画未勾选的方框。 % 这里改用 U+25A1 “□”:实际仿宋、黑体都有该字形,宋体没有, % 故正文默认仿宋;若把正文改成宋体,需另指定方框字体。 \newcommand{\khtbchoices}[1]{% \par \begingroup \khtbbodyfont \noindent \hbox to\linewidth{% \hss \def\khtbsep{\hss}% \def\khtbfirst{}% \@for\khtbit:=#1\do{% \khtbfirst\mbox{□\,\khtbit}% \def\khtbfirst{\khtbsep}}% \hss}% \par \endgroup} % 单位负责人签字 + 公章 + 日期:三行(docx 首行缩进 2 字) \newcommand{\khtbseal}[1][2\ccwd]{% \par\noindent\hspace*{#1}单位负责人签字:\par \noindent\hspace*{#1}(公章):\par \noindent\hspace*{#1}年\hspace{1.6em}月\hspace{1.6em}日\par} %====================================================================== % 9. 表外标题 % % 第 1 段:黑体 22pt、居中、行距 exact 480twips=24pt、字距 -0.7pt % 第 2 段:仿宋 16pt。docx 里是 firstLine 640twips(2 字)缩进 % 且未设 jc,实际渲染为“左缩两字”而非居中 —— 更像原表排版的疏漏。 % 本模板改为居中(更符合版式意图);若要还原原样,把下面 \centering % 换成 \hspace*{2\ccwd} 即可。 %====================================================================== \newcommand{\khtbyearval}{××××} \newcommand{\khtbyear}[1]{\renewcommand{\khtbyearval}{#1}} \newcommand{\khtbtitle}{% \begingroup \centering {\heiti\fontsize{22}{24}\selectfont \addfontfeature{LetterSpace=-3.2}% 中国海洋大学教职工年度考核登记表\par}% \vspace{2pt}% {\fangsong\fontsize{16}{20}\selectfont \khtbyearval\,年度\par}% \endgroup \vspace{5pt}} %====================================================================== % 10. 表外脚注 % % 第 4 段:仿宋 14pt、居中 % 内容“注:此表存入本人档案(A4纸正反面打印) 人事处制表” %====================================================================== \newcommand{\khtbfootnote}{% \par\vspace{2pt}% {\khtbbodyfont \noindent\makebox[\linewidth][c]{% 注:此表存入本人档案(A4 纸正反面打印)\quad 人事处制表}\par}} %====================================================================== % 11. 年度总结正文区 % % \khtbsummary{正文} 多段用 \par 或空行分隔;首行缩进 2 字 % \khtbsummarysign 末尾“本人签名 / 年 月 日”(docx 缩进 13 字) %====================================================================== \newcommand{\khtbsummary}[1]{% \begingroup \khtbbodyfont \setlength{\parindent}{2\ccwd}% \setlength{\parskip}{0pt}% #1\par \endgroup} \newcommand{\khtbsummarysign}{% \par\vspace{1.5em}% \begingroup \setlength{\parindent}{0pt}% \noindent\hspace*{13\ccwd}% \sbox\khtbsignbox{本人签名:}% \usebox\khtbsignbox \raisebox{-0.4ex}% {\rule{\dimexpr\linewidth-13\ccwd-\wd\khtbsignbox\relax}{0.4pt}}% \par \vspace{0.2em}% \noindent\hspace*{13\ccwd}年\hspace{1.6em}月\hspace{1.6em}日\par \endgroup} \AtBeginDocument{\khtbbodyfont} \endinput