# 课程教学日历 —— 中国海洋大学课程教学日历(理论课程) 学校下发的**通用空白表**「附件3:中国海洋大学课程教学日历(理论课程)」的 XeLaTeX 模板。原文件是学校统一的 `.doc` 表(7 列 × 18 行的 16 周教学日历 + 课程考核说明 + 签字栏),本目录把它做成可直接编译的 LaTeX 版。 > **本模板不含任何真实课程数据。** 课程名、开课号、教师、学分、学时、 > 人数、学期、周次日期、章节名、作业编号**全部是虚构占位符**(`××××`、 > `000000000000`、`×`)。原 `.doc` 是一份**填过的实例**,本模板只复刻 > 它的版式,一个字段都没带过来 —— 见下面「真实数据零泄漏」。 > 与 [`试卷归档封面/`](../试卷归档封面/)、[`试卷归档目录/`](../试卷归档目录/) > 的关系:三件同属教务处下发的表格套件(附件1 封面、附件2 归档目录、 > 附件3 教学日历),版式上都照原 `.doc` 1:1 还原,做法同一套 > (版心设成表格可见范围 + `caloutside` 环境放回表格外的段落),互不依赖。 ## 文件 | 文件 | 说明 | |---|---| | `oucal.sty` | 版式包。A4、表宽 174.625mm、7 列、18 行显式行高,每项尺寸都附换算过程 | | `课程教学日历.tex` | 正文入口(课程信息与 16 周内容写在这里) | | `课程教学日历.pdf` | 成品样张 | ## 编译 ```bash xelatex 课程教学日历.tex ``` 必须用 **XeLaTeX**(fontspec + xeCJK),pdflatex 编译不过。 自包含,不依赖任何图片。实测 **3 页**、零警告(无 Overfull / Underfull / 缺字)。 > ⚠️ 页数与原件不同:原件 2 页,本模板 3 页。原因见「原件的不一致之处」第 1 条 —— > 原件第一周那一行的行距缺省(15.6pt),本模板统一按 23pt 排,多出 22pt, > 正好把表格顶到第 3 页。这是**有意取舍**,不是 bug。 ## 用法 ### 课程信息 改 `.tex` 开头的宏: ```latex \newcommand{\calcno}{附件3} % 附件编号 \newcommand{\calcname}{××××} % 课程名称 \newcommand{\calyear}{20××} % 学年 \newcommand{\calseason}{×} % 季学期(春/秋) \newcommand{\caldept}{××××学院(系)} % 开课单位 \newcommand{\calcodenum}{000000000000} % 开课号(12 位) \newcommand{\calteacher}{×××} % 任课教师 \newcommand{\calcredit}{×.×} % 课程学分 \newcommand{\calhour}{00} % 总学时 \newcommand{\calstudent}{000} % 学生人数 \newcommand{\calassess}{××考试} % 考核方式 ``` 签字栏的「年 月 日」**没有对应的宏**,因为那是手写填写的空位, 模板中保持印刷好的「年」「月」「日」三个字,中间留白给老师签日期。 ### 教学日历表 每行格式: ```latex \calcell{第一周} & \calcell{×月×日至×月×日} & \calcell{×} & \calcell{课程基本情况介绍\par 引例\par 补充知识} & \calcell{多媒体讲课} & & \\ ``` 可用的单元格宏: | 宏 | 用途 | 说明 | |---|---|---| | `\calcell{...}` | 左对齐格 | 正文常用 | | `\calctr{...}` | 居中格 | 表头、第 11~16 周的周次列 | | `\calhead{...}` | 表头(**不加粗**,见下) | 原件表头无 `w:b` | ## ⚠️ 改表之前必读:行高是写死的 `oucal.sty` §5 里 18 个 `row{n}={ht=...}` 是**按每行的实际行数**算出来的: ``` ht = (内容行数 − 1) × 23pt ``` 原件所有表格段落都是 `w:line=460 exact`(23pt 固定行距),且每个单元格 多带一个空段落,所以**最少 2 行**。于是: | 内容行数 | ht | 原件实测行高 | |---|---|---| | 2 行 | 46.00pt | 46.5pt | | 3 行 | 69.00pt | 69.5pt | | 4 行 | 92.00pt | 92.5pt | | 5 行(表头) | 115.50pt | 115.5pt | 差的那 0.5pt 是边框线宽,由 tabularray 自己加。 **ht 是最小高度**(内容多了会顶高、少了不会压扁)。所以: - **加长**某周的教学内容 → 折行数变多 → 该行会被内容顶高,`ht` 不用改,但整表会变长 - **缩短**某周的教学内容 → 折行数变少 → `ht` 就会留下空白,必须手动改小 - **改字号或列宽** → 全部折行数都可能变 → 18 个 `ht` 都要重新标定 重新标定的办法(不靠猜):编译后导出 PDF,读第 1 页的横线: ```python import pymupdf d = pymupdf.open("课程教学日历.pdf") ys = {} for dr in d[0].get_drawings(): r = dr["rect"] if abs(r.y1 - r.y0) < 2 and r.width > 20: # 横线 k = round(r.y0, 1) ys[k] = ys.get(k, 0) + r.width rows = sorted(k for k, w in ys.items() if w > 300) # 只留跨多列的 print([round(rows[i] - rows[i-1], 2) for i in range(1, len(rows))]) ``` 输出就是逐行行高,与上表对照即可。 ## 版式参数 全部照原 `.doc` 换算,未做主观调整: | 项目 | 值 | 来源 | |---|---|---| | 纸张 | A4 210×297mm | `pgSz` 11906×16838 twips | | 页边距 | 上下 25.40mm、左右 31.75mm | `pgMar` 1440 / 1800 twips | | 表宽 | 174.625mm | `tblW` 9900 twips | | 表格缩进 | −13.97mm(**负**,比版心更靠左) | `tblInd` −792 twips | | 表格可见左边界 | 15.875mm | 31.75 − 13.97 − 1.905(cellMar) | | 列宽 | 15.875 / 25.559 / 18.150 / 47.078 / 24.095 / 21.590 / 22.278 mm | `gridCol` 900/1449/1029/2669/1366/1224/1263 twips | | 单元格内边距 | 左右 1.905mm、上下 0 | `tblCellMar` 108 twips | | 边框 | 0.5pt 实线 | `w:sz=4`(1/8pt 单位) | | 行距 | **23.00pt 固定** | `w:line=460` `lineRule=exact` | | 行高 | 全表无 `trHeight`,由内容撑开 | 实测 = 行数×23 + 0.5pt | | 附件号/标题 | 14pt 粗 | `w:sz=28` + `w:b` | | 学年学期行 | 12pt 粗 | `w:sz=24` + `w:b` | | 正文/表格/签字栏 | 10.5pt | Normal 样式 `w:sz=21` | ### 列宽为什么是 `gridCol − 2×colsep − 线宽` `wd = gridCol − 2×1.905mm − 0.1764mm`。tabularray 把边框线宽算进列宽里 (相邻竖线中心距 = `wd + 2×colsep + 线宽`)。按「竖线根数分摊」算每列会窄 0.154mm,整表窄 1.07mm —— 这点在 `试卷归档目录/` 里实测验证过,直接沿用。 ### `totalwidth` 为什么是 174.80mm 而不是 174.625mm Σwd + 内边距 + 8 条竖线 = 174.8013mm。若取 174.625 会报 `Table width is too small` —— tabularray 的盒子还要多占约 0.4945pt (左竖线画在边界内侧、右竖线外缘又占半个线宽)。 ## 实测校验 用 pymupdf 读两版 PDF(原件由 WPS COM 导出,136 个文字 span + 21 条横线 + 8 条竖线), 逐位对比: ### 竖线(8 条,单位 mm) | # | 原件 | 本模板 | Δ | |---|---|---|---| | 1 | 15.875 | 15.875 | 0.000 | | 2 | 31.750 | 31.750 | 0.000 | | 3 | 57.309 | 57.309 | 0.000 | | 4 | 75.458 | 75.454 | −0.004 | | 5 | 122.560 | 122.556 | −0.004 | | 6 | 146.629 | 146.625 | −0.004 | | 7 | 168.218 | 168.214 | −0.004 | | 8 | 190.500 | 190.500 | 0.000 | **最大 |Δ| = 0.004mm。** ### 纵向锚点(基线 y,单位 pt) | 锚点 | 原件 | 本模板 | Δ | |---|---|---|---| | 「附件3:」 | 92.69 | 92.69 | **0.00** | | 标题 | 123.89 | 123.89 | **0.00** | | 「学年 学期」 | 150.41 | 150.42 | +0.01 | | 课程信息第 1 行 | 191.81 | 191.82 | +0.01 | | 课程信息第 2 行 | 214.85 | 214.82 | −0.03 | | 表格顶线 | 242.60 | 242.60 | **0.00** | | 「院(系)负责人签字」x | 321.00 | 320.99 | −0.01 | | 签字栏「年」x | 355.08 | 355.09 | +0.01 | | 签字栏「月」x | 376.08 | 376.09 | +0.01 | | 签字栏「日」x | 397.08 | 397.10 | +0.02 | ### 行高(21 条横线,单位 pt) 17/18 行 **Δ ≤ 0.4pt**;唯一例外是第 2 行(第一周)多 22.0pt, 原因见「原件的不一致之处」第 1 条。 ### 字号 导出 PDF 读 span 的 `size`:**14.00 / 12.00 / 10.50** 三档,与 Word 的 28/24/21(半磅)÷2 逐位相同。 ## 五个坑(改之前先看) ### 1. `\fontsize` 必须用 `bp`,不能用默认的 `pt` Word 的 pt 是 PostScript point(1/72 英寸),TeX 默认的 pt 是 TeX point (1/72.27 英寸),**大 0.36%**。写 `\fontsize{10.5}{23}` 实测排出来 10.45、 23pt 的 `\baselineskip` 实测 22.9167。必须写 `10.5bp` / `23bp`。 这条与 `试卷归档目录/` 的坑 1 是同一个。 ### 2. Word 的「固定行距」在 TeX 里不是 `\fontsize` 的第二参数 原件的 `w:line=460 exact` 意思是**每一行都占满 23pt,首行也不例外** (既不算字体的 ascent,也不算 descent,硬压成 23)。而 TeX 的 `\fontsize{10.5bp}{23bp}` 只设了 `\baselineskip = 23pt`: | n 行格子 | Word | TeX 裸排 | |---|---|---| | 1 行 | 23.00pt | 11.00pt(ascent + descent) | | 5 行 | 115.00pt | 99.00 + 约 8pt = 107pt | 初版没有处理这一条,编译出来表头只有 102.2pt(应 115.5),第七列第 5 行 「节」直接压到表格底线以下。 **试过但都不行的三个方案**: | 方案 | 实测结果 | |---|---| | `rowsep=6.6pt`(单元格上下 padding) | 1 行格 23.0 ✓、2 行格 **39.3** ✗(斜率 16.4 而非 23) | | `\rule[-4.35pt]{0pt}{23pt}` 段首段尾各一 | 表头 150pt ✗(`\lineskip` 介入,每行多 1pt,首行基线跑到框外) | | `\vrule height 18.65pt depth 4.35pt` | 与 `\rule` 同 | 原因是 **xeCJK 的 `\CJKglue` 与 `\lineskip`** 会介入自动折行,行间距不是 线性的,没法用单一 padding 同时满足 1/2/4/5 行。 **最终用的**:直接给 `row{n}={ht=...}`,把「行高」这件事从盒模型手里拿走。 `ht` 是最小值,配上内容刚好 = 目标值,结果确定、可复现。 ### 3. `\linespread` 必须在 `\fontsize` **之后**调用 `\fontsize` 会重设 `\baselineskip`,把先前设的系数覆盖掉;反之 `\linespread` 改的是系数,对已生成的 `\baselineskip` 立即生效。 ```latex \linespread{0.67826}\calfontC % ❌ 无效,行距还是 23pt \calfontC\linespread{0.67826}\selectfont % ✅ 15.6pt ``` (本包最终没用这条 —— 见「原件的不一致之处」第 1 条。但改行距时会踩。) ### 4. `\SetCell[c=7]{c}` 里的 `c` 是自然宽度,不折行 跨列的格必须显式给宽,否则整句不折行、直接冲出表外报 Overfull: ```latex \SetCell[c=7]{c} % ❌ Overfull 197.8pt too wide \SetCell[c=7]{m,wd=170.638mm} % ✅ ``` `wd = 表宽 − 2×colsep − 取整余量 = 174.625 − 3.810 − 0.177 = 170.638mm`。 ### 5. `\leftskip` 与 `geometry` 的左边距会**叠加**,不是覆盖 本包把版心设成表格可见范围(`left=15.787mm`),表格外的段落要靠 `\leftskip` 放回原版心的 31.75mm。但 `\leftskip = 15.875mm` 排出来是 **31.662mm**(15.787 + 15.875),而不是 31.75mm。 必须取 `\leftskip = 31.75 − 15.787 = 15.963mm`。差 0.088mm 看着小, 但它让所有「按原版心再做负缩进」的段落全部错位 —— 初版 【开课单位】一行实测偏右 16.3pt。 ### 6.(附)负的 `\vspace*` 在 `\par` 处会被丢弃 想压掉两段之间 2.11pt 的余差,写 `\vspace*{-2.11pt}` 或 `\calskip{-0.734mm}` 都无效(TeX 在 `\par` 处丢弃负的垂直胶, 不能"压缩"已存在的间距)。 **正解**:让两行落在**同一段落**里,用 `\newline` 换行,`\baselineskip` 就直接生效: ```latex 【开课单位】…\newline 【课程学分】…\par ``` 注意 `\newline` 之后的行是"段中行",**不吃 `\parindent`**,只认 `\leftskip`, 所以要在段落开头把 `\leftskip` 整体前移。 ## 原件的不一致之处 ### 1. 第一周(第 2 行)的行距是 15.6pt,其余 17 行是 23pt 原 `.doc` 里只有这一行的段落没写 `w:spacing`,随 `docGrid` (`linePitch=312` twips = 15.6pt)排。于是该行高 47.3pt,而同为 3 行的 第五周(第 6 行)是 69.5pt —— 同样内容量,差 22.2pt。 **本模板不复制这个不一致**,全表统一 23pt。后果是该行比原件高 22.0pt, 表格总高 1132.3pt(原件 1114.2pt),最终排出 **3 页**(原件 2 页)。 要压回 2 页的话:把第一周那一行的 3 个段落合并成自动折行(`\par` 分段 不行,`\linespread` 只对段内行生效),再把 `row{2}` 的 `ht` 改成 24.30pt。 但那等于把疏漏重新引入,故未采用。 ### 2. 周次列的对齐前 10 行、后 6 行不一致 原件第 1~10 周(第一周~第十周)是 `jc=left`,第 11~16 周是 `jc=center`: | 周次 | 原件 x | 本模板 x | |---|---|---| | 第一周~第十周 | 50.40pt | 50.65pt | | 第十一周~第十六周 | 51.72pt | 51.75pt | 差 1.32pt,肉眼几乎看不出,但确实是原件的行为。**本模板照抄这个不一致** (`.tex` 里前 10 行用 `\calcell`、后 6 行用 `\calctr`),因为改成统一反而 会与原件对不上。若你想统一,把后 6 行的 `\calctr` 全换 `\calcell` 即可。 ### 3. 表头第 6、7 格是「2 个段落又各自折行」 原件里这两格各是 2 个段落,但 PDF 显示 4 行与 5 行 —— 说明每个段落 在格内**又自动折行**了一次。本模板把整句写进一格交给自动折行, 实测折行数与原稿一致(c6 折 4 行、c7 折 5 行),表头行高 115.5pt = 5×23pt 就是这么来的。 ### 4. 表头**不加粗** 视觉上表头显得醒目,但原 `.doc` 的表头格只有 `jc=center`,**没有 ``**。 本模板照抄,不加粗。`\calhead` 保留了 `\bfseries` 的能力,想加粗自己去打开。 ### 5. 签字栏的「年 月 日」是手写填空位,不要填年份 原件第 2 页底部两段: | 段落 | 定位方式 | 起点 x | |---|---|---| | 「院(系)负责人签字:」 | `w:ind firstLine=4515`(79.64mm) | 321.00 | | 「×××× 年 月 日」 | **无** `ind`,靠空格顶位 | 331.44 | 原件是被填过的实例,年份位印着 4 个半角数字(x=331.44~352.50)。 **本模板不写年份** —— 空白模板应该留白给老师手写。所以直接从「年」开始排: ```latex \hspace*{93.517mm}\calfontC 年\hspace*{10.55pt}月\hspace*{10.55pt}日\par ``` 三个字的实测锚点(10.45pt 宋体),本模板与原件逐字对齐: | 字 | 原件 x | 本模板 x | Δ | |---|---|---|---| | 年 | 355.08 | 355.09 | +0.01 | | 月 | 376.08 | 376.09 | +0.01 | | 日 | 397.08 | 397.10 | +0.02 | `\hspace*{93.517mm}` 的来历:目标 x = 355.09pt = 125.23mm, `caloutside` 环境的 `\leftskip` 是 15.963mm,geometry 左边距 15.787mm, 两者叠加后排版起点在 31.75mm 处,故 `125.23 − 31.75 = 93.48mm`。取 93.48mm 实测落在 354.98(偏左 0.10pt), 加 0.037mm 微调到 **93.517mm**,实测 355.09(偏右 0.01pt)。 ⚠️ 三字之间的空档是 **10.55pt**,不是 21pt。因为中文字形本身占 10.5pt, 「年」起点到「月」起点 = 字形宽 10.5 + 空档 10.55 ≈ 21.0pt。 #### ❌ 曾经踩的坑:用负 `\hspace` 塞年份占位符 → 三字重叠 第一版模板里有 `\calgrade`(`20××`)占位,写法是: ```latex \hspace*{92.604mm}\calfontC \calgrade~年\hspace{-8.35mm}月\hspace{3.34mm}日 ``` 实测:「月」落在 376.08,但 `20×× 年` 一直延伸到 x=399.74, 「月」正好压在「年」上 —— **三个字叠成一个墨团**。 原因:**负 `\hspace` 只能压缩它后面盒子之间的间距,不能把已经排出去的 字符收回来**。减少间距减到负数 = 重叠,而不是"往回挪"。 最终解法不是修排版技巧,而是**去掉年份占位符** —— 那本来就不该有。 现在这一行只有三个中文字,不存在宽度变化,位置永远稳定。 ### 6. `\calgrade` 已删除 曾经有过 `\newcommand{\calgrade}{20××}`,后因「签字栏不该印年份」而删除。 如果你需要**预填**年份(比如批量生成带年份的表),自己加回来: ```latex \hspace*{81.52mm}{\calfontC\makebox[34.0pt][r]{2026}年}% \hspace*{10.55pt}\calfontC 月\hspace*{10.55pt}日\par ``` 要点:年份必须放进**定宽 `\makebox`**,让盒宽吸收内容长度变化, 否则换 `2026` 与 `20××` 时「月」「日」会跟着漂移。 实测该写法落到:年 354.95 / 月 375.96 / 日 396.97(目标 355.08 / 376.08 / 397.08)。 ## 真实数据零泄漏 原 `.doc` 是一份**填过的实例**(真实课程名、开课号、教师姓名、人数、 年份、16 周真实日期与章节名)。本模板把每一个字段都换成了占位符。 已 grep 确认以下关键词在 `oucal.sty` 与 `课程教学日历.tex` 中**零命中**: ``` 数字逻辑 080502101231 杨永全 2021 2022 逻辑代数 卡诺图 触发器 时序线路 组合线路 信息科学与工程学部 闭卷考试 ``` 内容里的「××××基础」「×××设计」等也都是通用占位(原件的真实章节名是 「逻辑代数基础」「组合线路设计」等,已全部抽象掉)。**替换成你自己课程的 内容时,注意不要把这些真实信息再写回模板提交。** ## 字体 全部取自仓库根目录的 [`fonts/`](../fonts/),通过 [`fonts.tex`](../fonts.tex) 声明: | 用途 | 字体 | 授权 | |---|---|---| | 正文宋体 | OUC Serif(思源宋体 SC 子集) | OFL 1.1 | | 西文/数字 | TeX Gyre Termes(Times 度量兼容) | GUST Font License | 原件用的是「宋体 + Times New Roman」。换开源等价字体后**字宽逐字相同**, 所以列宽、折行数、行高都不受影响 —— 这一点已由上面的行高实测表验证。