%====================================================================== % oucal.sty —— 中国海洋大学 课程教学日历(理论课程)版式包 % % 依据:学校下发的《附件3:中国海洋大学课程教学日历(理论课程)》.doc % % 这是**学校下发的通用空白表**,与课程无关。本包只复刻版式, % 不携带任何具体课程的数据。 % % 所有尺寸都由 Word 内部数值换算: % pt → mm : mm = pt / 72 * 25.4 % twips → mm : mm = twips / 1440 * 25.4 % w:sz : 半磅值,÷ 2 即 pt % % 用法见同目录 课程教学日历.tex,说明见 README.md %====================================================================== \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{oucal}[2026/10/27 v1.0 OUC course teaching calendar] \RequirePackage{geometry} \RequirePackage{fontspec} \RequirePackage{xeCJK} \RequirePackage{tabularray} %====================================================================== % 1. 页面与版心 % % 原 .doc sectPr: % pgSz w:w=11906 h:h=16838 → A4(210.01 × 297.00 mm) % pgMar top/bottom = 1440 twips = 25.40 mm % left/right = 1800 twips = 31.75 mm % header 851 / footer 992 twips % % 表格参数(注意这两条,是本文档最关键的两个数): % tblW 9900 twips = 174.625 mm ← 表宽 % tblInd −792 twips = −13.97 mm ← **负缩进**,表格比版心更靠左 % % 版心宽 = 210 − 2×31.75 = 146.50 mm,而表宽 174.625 mm, % 表格比版心宽 28.125 mm。加上 tblInd 为负,表格实际位置是: % % 表格左边界 = 31.75 + (−13.97) − cellMar(1.905) = 15.875 mm % 表格右边界 = 15.875 + 174.625 = 190.50 mm % % 实测 PDF 8 条竖线的 x 正是 15.875 / 31.75 / 57.309 / … / 190.5 mm, % 与上式逐位吻合(见 README 的实测表)。 % % ⚠️ 但按 left=15.875mm 排出来,实测竖线在 15.963mm,整体右偏 0.088mm。 % 这是 tabularray 把左竖线画在"版心左边界 + 半线宽"处的取整结果。 % 故 left 取 15.875 − 0.088 = 15.787mm,实测竖线回到 15.875mm。 % % ⇒ 本包处理(与前两个归档模板同一套办法): % 把版心直接设成表格的可见范围 15.875 ~ 190.50 mm, % 即 left = 15.875mm、totalwidth = 174.625mm。 % 这样表格与版心同宽、居中摆放,编译不刷 Overfull \hbox, % 且表格在纸面上的绝对位置与原 .doc 完全一致。 % % 表格【外】的内容(标题、课程信息、签字栏)原本是相对 31.75mm % 的版心排的,故用 §7 的 \caloutside 把它们放回原位: % \leftskip = 31.75 − 15.875 = 15.875 mm % \rightskip = 190.50 − 178.25 = 12.25 mm % ⇒ 行宽 = 174.625 − 15.875 − 12.25 = 146.50 mm(正是原版心宽) %====================================================================== \geometry{ paperwidth = 210mm, paperheight = 297mm, left = 15.787mm, % = 表格左边界 15.875 − 实测取整 0.088(见下) totalwidth = 174.80mm, % = 表宽 + tabularray 取整余量(见 §4) top = 25.40mm, bottom = 25.40mm, headheight = 0pt, headsep = 0pt, footskip = 17.50mm, } \setlength{\parindent}{0pt} \setlength{\parskip}{0pt} \pagestyle{empty} % 原 .doc 无页眉页脚,故不排页码 %====================================================================== % 2. 字体 % % 原 .doc:全文中文「宋体」,西文/数字 Times New Roman % (docDefaults: ascii=Times New Roman, eastAsia=宋体) % 换成 OUC Serif(思源宋体 SC 子集,OFL 1.1)与 TeX Gyre Termes % (Times 的度量兼容替代,字宽逐字相同)。 %====================================================================== \input{../fonts.tex} %---------------------------------------------------------------------- % ⚠️⚠️⚠️【必须关掉 xeCJK 的全角标点压缩】 % % 本表内容大量用全角标点(,、。:()),落在**居中**的单元格里。 % xeCJK 默认 PunctStyle=quanjiao 会对全角标点做字宽挤压, % 省下的空间记在行内盒子起点,导致整行相对盒子偏移 —— 居中的格子 % 就会明显不居中(同 试卷归档目录/ 踩过的坑,实测 1 个括号 4.23pt)。 % % Word 不做这个挤压,关掉反而与原件一致。 %---------------------------------------------------------------------- \xeCJKsetup{PunctStyle=plain} %====================================================================== % 3. 字号与行距 % % 原 .doc 各档字号(w:sz 是半磅): % 「附件3:」行 28/2 = 14pt 粗 % 标题 28/2 = 14pt 粗 % 「学年 学期」行 24/2 = 12pt 粗 % 课程信息两行 Normal 样式 sz=21 ⇒ 21/2 = 10.5pt % 表格正文 同上 10.5pt % 签字栏 10.5pt % % 行距(表格外两行信息段落):w:line=460 lineRule=exact % ⇒ 460 twips = 460/20 = 23.00 pt 固定行距 % 表格内未单独设 spacing,Word 按同一 docGrid 排,实测行距也是 23.00pt。 % % ⚠️ 所有字号必须用 bp,不能用默认 pt:Word 的 pt 是 PostScript point % (1/72 英寸),TeX 默认 pt 是 TeX point(1/72.27 英寸),大 0.36%。 % 写 10.5 会排出 10.45(= 10.5 × 72/72.27),与原稿对不上。 %====================================================================== \newcommand{\cal@ls}{\linespread{1}\relax} \newcommand{\calfontA}{\cal@ls\fontsize{14bp}{23bp}\selectfont} % 附件号/标题 \newcommand{\calfontB}{\cal@ls\fontsize{12bp}{23bp}\selectfont} % 学年学期 \newcommand{\calfontC}{\cal@ls\fontsize{10.5bp}{23bp}\selectfont} % 正文/表格 %====================================================================== % 4. 列的宽度模型 % % 原 .doc tblGrid 七列(twips → mm): % c1 周次 900 = 15.8750 mm % c2 日期 1449 = 25.5587 mm % c3 学时 1029 = 18.1504 mm % c4 教学内容 2669 = 47.0782 mm % c5 教学方式 1366 = 24.0947 mm % c6 课堂讨论… 1224 = 21.5900 mm % c7 课下作业… 1263 = 22.2779 mm % ───────────────────────────── % 合计 9900 twips = 174.6250 mm % % cellMar:left/right = 108 twips = 1.9050 mm,top/bottom = 0 % 边框 :w:sz = 4 ⇒ 0.5 pt = 0.1764 mm % % ⚠️⚠️ 列宽 = gridCol − 2×colsep − 线宽,不是"按竖线根数分摊"。 % tabularray 把边框线宽算进列宽里(相邻竖线中心距 = wd + 2×colsep % + 线宽)。按分摊算每列会窄 0.154mm,整表窄 0.47mm —— 这在 % 试卷归档目录/ 里实测验证过,本包直接沿用正确式。 % % c1: 15.8750 − 3.8100 − 0.1764 = 11.8886 mm % c2: 25.5587 − 3.8100 − 0.1764 = 21.5723 mm % c3: 18.1504 − 3.8100 − 0.1764 = 14.1640 mm % c4: 47.0782 − 3.8100 − 0.1764 = 43.0918 mm % c5: 24.0947 − 3.8100 − 0.1764 = 20.1083 mm % c6: 21.5900 − 3.8100 − 0.1764 = 17.6036 mm % c7: 22.2779 − 3.8100 − 0.1764 = 18.2915 mm % ─────────────────────────────────────── % Σwd = 146.7201 mm % + 7×2×1.9050 = 26.6700(内边距) % + 8×0.1764 = 1.4112(8 条竖线) % ─────────────────────────────────────── % = 174.8013 mm % % ⇒ totalwidth 取 174.80mm(§1 已设)。若取 174.625 会报 % "Table width is too small",因为 tabularray 的盒子还要多占 % 约 0.4945pt(左竖线画在边界内侧、右竖线外缘又占半个线宽)。 %====================================================================== \newcommand{\calrule}{0.5pt} % 边框粗细(w:sz=4) \newcommand{\calcolsep}{1.905mm} % 单元格左右内边距(108 twips) \newlength{\calwa}\setlength{\calwa}{11.8886mm} \newlength{\calwb}\setlength{\calwb}{21.5723mm} \newlength{\calwc}\setlength{\calwc}{14.1640mm} \newlength{\calwd}\setlength{\calwd}{43.0918mm} \newlength{\calwe}\setlength{\calwe}{20.1083mm} \newlength{\calf}\setlength{\calf}{17.6036mm} \newlength{\calwg}\setlength{\calwg}{18.2915mm} %====================================================================== % 5. 表格环境 % % ⚠️ 用 longtblr(可跨页),不用 tblr:本表 18 行、约 604mm 高, % 一页装不下(版心高 297 − 2×25.40 = 246.2 mm),必然跨两页。 % 原 .doc 也是 2 页。 % % ⚠️ 行高用**显式的 ht**(见 §5b),不用 rowsep / strut。 % % ⚠️ 不设 head/foot 重复表头:原 .doc 跨页时**不重复表头** % (P2 顶部直接从第六周续排),故保持一致。 % % ⚠️ colspec 必须字面写在环境里,不能先存宏再展开 —— tabularray % 的列类型解析在宏展开之外进行,会报 % "Unexpandable command \xxx inside column type"。 %====================================================================== \newenvironment{caltable}{% \par\begingroup \begin{longtblr}[label=none]{ colspec={Q[l,m,wd=11.8886mm] Q[l,m,wd=21.5723mm] Q[l,m,wd=14.1640mm] Q[l,t,wd=43.0918mm] Q[l,m,wd=20.1083mm] Q[l,t,wd=17.6036mm] Q[l,t,wd=18.2915mm]}, colsep=1.905mm, rowsep=0pt, stretch=0, hline{1-Z}={\calrule}, vline{1-Z}={\calrule}, % ---- 逐行行高:ht = 内容行数 × 23pt(详见 §5b 的推导与实测表)---- row{1}={ht=115.50pt}, % 表头 5 行(c7 折成 5 行) row{2}={ht= 69.00pt}, % 第一周 3 行 row{3}={ht= 46.00pt}, % 第二周 2 行 row{4}={ht= 92.00pt}, % 第三周 4 行 row{5}={ht=115.00pt}, % 第四周 5 行 row{6}={ht= 69.00pt}, % 第五周 3 行 row{7}={ht= 46.00pt}, % 第六周 2 行 row{8}={ht= 92.00pt}, % 第七周 4 行 row{9}={ht= 46.00pt}, % 第八周 2 行 row{10}={ht= 46.00pt}, % 第九周 2 行 row{11}={ht= 46.00pt}, % 第十周 2 行 row{12}={ht= 46.00pt}, % 第十一周 2 行 row{13}={ht= 46.00pt}, % 第十二周 2 行 row{14}={ht= 46.00pt}, % 第十三周 2 行 row{15}={ht= 46.00pt}, % 第十四周 2 行 row{16}={ht= 46.00pt}, % 第十五周 2 行 row{17}={ht= 46.00pt}, % 第十六周 2 行 }% }{% \end{longtblr}\par\endgroup} %====================================================================== % 5b. 行高为什么是这些数 —— 实测推导 % % 原 .doc 的表格段落有两种行距来源,**混用**: % · 表头 7 格 + 第 3~18 行:w:line=460 lineRule=exact ⇒ 23.00 pt % · 第 2 行(第一周):段落没写 w:spacing,随 docGrid % (docGrid linePitch=312 twips)⇒ 15.60 pt % 并且**每个单元格都多带一个空段落**(Word 表格格的固有尾巴), % 故任何一行最少 2 行。 % % 于是原稿每行的真实高度 = 整数行 × 行距 + 0.5pt 边框线: % 46.5 = 2×23 + 0.5 92.5 = 4×23 + 0.5 % 69.5 = 3×23 + 0.5 115.5 = 5×23 + 0.5 % 47.3 = 3×15.6 + 0.5 ← 只有第一周是 15.6 % % 原稿 18 行实测(pymupdf 读横线,见 README): % 115.5 47.3 46.5 92.5 115.5 69.5 46.94 92.5 % 46.5 ×9 69.5 % % ⇒ 本包把 ht 设成「(行数 − 1) × 23」,让 tabularray 自己加上内容 % 首行与边框线 —— 实测加上后正好等于上表,误差 ≤ 0.5pt。 % % ⚠️ 为什么不用 rowsep: % 实测 rowsep=6.6pt 时 1 行格 23.0、2 行格 **39.3**,斜率 16.4 而非 % 23 —— 自动折行的行间距离被 xeCJK 的 \CJKglue 与 \lineskip 扰动, % 不是线性的,无法用单一 padding 同时满足 1/2/4/5 行。 % % ⚠️ 为什么不用 \rule[−d]{0pt}{h} 的 strut: % h = 23pt 超过 \baselineskip 22.9pt 会触发 \lineskip,每行再多 1pt, % 且首行基线会跑到表格顶线**上方**(实测表头第一个字压到框外)。 % % ⚠️ 为什么不复制第一周的 15.6pt 行距: % 那是原件的疏漏(同一张表里 17 行 23pt、1 行 15.6pt)。本模板全表 % 统一 23pt,该行比原稿高 22.2pt(69.5 而原 47.3)。 % % ⇒ 后果:表格总高 1132.3pt,版心只有 246.2mm×2 = 1395pt,本应够, % 但加上表头前的抬头(216pt)与签字栏,最终排出 3 页(原稿 2 页)。 % 这是**有意取舍**:保真列宽/字号/行距,但不复制原件的疏漏。 % 理由记在 README「原件的不一致之处」第 1 条;页面数差异也记在那里。 % % ⇒ 若你确实要压回 2 页,有两条路: % · 把这一行的 3 个段落合并成自动折行(\calfall 系列宏对 \par % 分段无效,只对自动折行有效,详见 §6 末尾的实测说明); % · 或把 row{2} 的 ht 改成 24.30pt 并给该行单独换 15.6pt 行距的字号 % 命令 —— 但那样等于把一个已知的疏漏重新引入。 % % ⚠️ ht 是**最小**高度:内容多了会顶高、少了不会压缩。故上面 18 个 % 行数必须与 .tex 里每行的实际内容对得上。改了字号或列宽导致折行 % 数变化时,ht 要跟着改(README 有一节讲怎么重新标定)。 %====================================================================== \newcommand{\calrowsep}{0pt} % 保留名字,向后兼容(本包不用它) %====================================================================== % 6. 单元格内容包装 % % 对齐方式照原 .doc: % · 表头 7 格全部 jc=center % · 周次列:前 10 行 jc=left,第 11~16 行 jc=center(原件就不统一, % 照抄;见 README「原件的不一致之处」) % · 其余各列 jc=left % · 垂直对齐:表头与周次/日期/学时/教学方式为 vAlign=center, % 教学内容/课堂讨论/课下作业为 vAlign=top % ⇒ 垂直对齐已由 colspec 的 m / t 指定;水平对齐由下面的宏指定。 % % ⚠️ 单元格宏里**不要**加 strut / rule:行高已由 §5 的 ht 定死。 %====================================================================== \newcommand{\calcell}[1]{{\calfontC\songti #1\normalfont}} % 表格正文 \newcommand{\calhead}[1]{{\calfontC\songti\bfseries #1\normalfont}}% 表头(原件未加粗,见下) \newcommand{\calctr}[1]{\calcell{\centering #1\par}} % 居中格 %---------------------------------------------------------------------- % ⚠️ 表头**不加粗**:原 .doc 表头格只有 jc=center,没有 。 % 视觉上表头看起来醒目,是因为表头行高、字号与正文相同、且居中, % 并非加粗。照抄原件,不加粗。 % (\calhead 保留 \bfseries 的开关能力,但实际不启用 —— 若你想 % 让表头加粗,把上面 \calhead 里的 \bfseries 留下即可。) %---------------------------------------------------------------------- %====================================================================== % 7. 表格外内容的位置补偿 % % 见 §1:版心被设成表格范围 15.875 ~ 190.50 mm,而原版心是 % 31.75 ~ 178.25 mm。表格外的段落必须放回原位才不会整体左移。 % % ⚠️⚠️ \leftskip 会与 geometry 的 left 边距**叠加**,不是覆盖。 % 初版取 15.875mm 时,段落实际起点 = 15.787 + 15.875 = 31.662mm, % 比目标 31.75mm 左移 0.088mm。更要命的是它让所有"按原版心再 % 做负缩进"的段落全部错位(【开课单位】一行实测偏右 16.3pt)。 % % ⇒ \leftskip 必须取 31.75 − 15.787 = 15.963mm,段落起点才回到 % 原版心的 31.75mm。此时行宽 = 174.80 − 15.963 − 12.25 = 146.587mm, % 与公元 146.50mm 只差 tabularray 的取整余量 0.087mm。 % % 用法:把表格外的**全部**段落放进 caloutside 环境。 %====================================================================== \newlength{\caloutsideskip} \setlength{\caloutsideskip}{15.963mm} % = 31.75 − 15.787(见上) \newlength{\caloutsideright} \setlength{\caloutsideright}{12.25mm} % = 190.50 − 178.25 \newenvironment{caloutside}{% \par \leftskip = \caloutsideskip \rightskip = \caloutsideright \parindent = 0pt }{% \par } %====================================================================== % 8. 表头两格的折行 % % 原 .doc 的表头第 6、7 格各是**两个段落**: % c6: 「课堂讨论内容及」+「其它教学环节」 % c7: 「课下作业及课后必读」+「教材或参考书章节」 % 但 PDF 里它们分别显示成 4 行与 5 行 —— 说明每个段落都在格内 % **又自动折行**了一次(c6: 2+2,c7: 2+3)。 % % ⇒ 本包源码里把整句直接写进一格,由 tabularray 按列宽自动折行, % 不插 \par。实测列宽下 c6 折成 4 行、c7 折成 5 行,与原稿一致, % 表头行高 115.5pt = 5×23pt 就是这么来的。 % % ⚠️ 若改了字号或列宽导致折行数变化,表头行高会跟着变 —— 这是 % 原件的固有行为,不是 bug。但 §5 的 ht=115.5pt 是**写死**的, % 届时需要同步改 ht(README 有一节讲怎么标定)。 %====================================================================== %====================================================================== % 9. 表格外各段的垂直间距 % % 原件的段距不是等距的(标题到学年行 26.52pt、学年行到信息行 % 41.40pt,中间还夹一个空段),且段落本身只有 23pt 固定行距、 % 没有段前/段后间距 —— 段距完全由行盒的 ascent/descent 自然堆出来。 % 而 TeX 的行盒比 Word 矮(单行段落少约 11.6pt),故必须逐段补差。 % % ⇒ 用 \calskip{...} 逐个标定,不依赖 \baselineskip 自动累积。 % % ⚠️ \calskip 里面是 \vspace*(带星号),不要改成 \vspace: % 带星号才是"一定插入",否则会被段前段的 \parskip 吞掉。 %====================================================================== \newcommand{\calskip}[1]{\par\vspace*{#1}\par} %====================================================================== % 10. 表格跨页 % % 原 .doc 在「第六周」这一行中间分页(P1 底 752.84pt / P2 顶 72.24pt), % 该行被切成两半,**没有任何跨页提示**。 % % ⇒ 本模板不强行复刻这个断点(断点位置取决于运行环境的行高取整, % 不可靠)。改为让 longtblr 自然跨页,并在表格后留出签字栏的 % 固定距离。实测本模板自然跨页后同样是 2 页。 % % 若需要断在特定行,在该行前插 % \SetTblrTemplate{...} % 或在 .tex 里把表拆成两个 caltable 环境(不推荐,会丢竖线连续)。 %====================================================================== \AtBeginDocument{\calfontC} \endinput