把学校下发的通用空白表「附件3:中国海洋大学课程教学日历(理论课程)」 做成 XeLaTeX 模板,与 试卷归档封面/、试卷归档目录/ 同一套做法。 版式按原 .doc 逐项还原并实测标定: - 表宽 174.625mm(tblW 9900 twips),tblInd 为负(−13.97mm), 表格比版心更靠左,可见左边界 15.875mm - 7 列宽度取 gridCol − 2×colsep − 线宽 - 边框 0.5pt(w:sz=4),内边距 1.905mm(108 twips) - 字号 14 / 12 / 10.5bp 三档,全部用 bp(Word 的 pt 是 PostScript pt) - 行高按「行数 × 23pt」显式给 ht(原件 w:line=460 exact) 实测校验(pymupdf 读两版 PDF): - 8 条竖线 x 偏差 ≤ 0.004mm - 纵向锚点:附件号 / 标题 / 表格顶线 Δ = 0.00pt,其余 ≤ 0.03pt - 行高 18 行中 17 行 Δ ≤ 0.4pt - 字号 14.00 / 12.00 / 10.50 精确 - 零警告(无 Overfull / Underfull / 缺字) 真实数据零泄漏:原 .doc 是填过的实例,模板把所有字段换成占位符 (××××、000000000000、×、20××),已 grep 确认课程名、开课号、 教师姓名、年份、16 周日期与章节名均零命中。 README 记录五类坑:bp 单位、固定行距在 TeX 里不等于 baselineskip、 linespread 必须在 fontsize 之后、SetCell 的 c 列不折行、leftskip 与 geometry 叠加;以及原件五处不一致(第一周行距 15.6pt、周次列对齐 前后不同、表头折行数、表头不加粗、签字栏用空格顶位)。 注:本模板 3 页,原件 2 页 —— 因不复制第一周行距的疏漏,该行多 22pt。
15 KiB
课程教学日历 —— 中国海洋大学课程教学日历(理论课程)
学校下发的通用空白表「附件3:中国海洋大学课程教学日历(理论课程)」的
XeLaTeX 模板。原文件是学校统一的 .doc 表(7 列 × 18 行的 16 周教学日历 +
课程考核说明 + 签字栏),本目录把它做成可直接编译的 LaTeX 版。
本模板不含任何真实课程数据。 课程名、开课号、教师、学分、学时、 人数、学期、周次日期、章节名、作业编号全部是虚构占位符(
××××、000000000000、×)。原.doc是一份填过的实例,本模板只复刻 它的版式,一个字段都没带过来 —— 见下面「真实数据零泄漏」。
与
试卷归档封面/、试卷归档目录/的关系:三件同属教务处下发的表格套件(附件1 封面、附件2 归档目录、 附件3 教学日历),版式上都照原.doc1:1 还原,做法同一套 (版心设成表格可见范围 +caloutside环境放回表格外的段落),互不依赖。
文件
| 文件 | 说明 |
|---|---|
oucal.sty |
版式包。A4、表宽 174.625mm、7 列、18 行显式行高,每项尺寸都附换算过程 |
课程教学日历.tex |
正文入口(课程信息与 16 周内容写在这里) |
课程教学日历.pdf |
成品样张 |
编译
xelatex 课程教学日历.tex
必须用 XeLaTeX(fontspec + xeCJK),pdflatex 编译不过。 自包含,不依赖任何图片。实测 3 页、零警告(无 Overfull / Underfull / 缺字)。
⚠️ 页数与原件不同:原件 2 页,本模板 3 页。原因见「原件的不一致之处」第 1 条 —— 原件第一周那一行的行距缺省(15.6pt),本模板统一按 23pt 排,多出 22pt, 正好把表格顶到第 3 页。这是有意取舍,不是 bug。
用法
课程信息
改 .tex 开头的宏:
\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}{××考试} % 考核方式
\newcommand{\calgrade}{20××} % 签字栏年份
教学日历表
每行格式:
\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 页的横线:
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 | 376.08 | 376.08 | 0.00 |
| 签字栏「日」x | 397.08 | 396.04 | −1.04 |
行高(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 立即生效。
\linespread{0.67826}\calfontC % ❌ 无效,行距还是 23pt
\calfontC\linespread{0.67826}\selectfont % ✅ 15.6pt
(本包最终没用这条 —— 见「原件的不一致之处」第 1 条。但改行距时会踩。)
4. \SetCell[c=7]{c} 里的 c 是自然宽度,不折行
跨列的格必须显式给宽,否则整句不折行、直接冲出表外报 Overfull:
\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
就直接生效:
【开课单位】…\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 行不一致
原件第 110 周(第一周第十周)是 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,没有 <w:b/>。
本模板照抄,不加粗。\calhead 保留了 \bfseries 的能力,想加粗自己去打开。
5. 签字栏第二行用空格顶位,不是缩进
「院(系)负责人签字:」有 w:ind firstLine=4515(79.64mm),而
「××××年 月 日」没有 ind,是靠空格顶到位置的。故两行起点不同
(321.00 与 352.50pt)。本模板按实测值分别给 \hspace*。
真实数据零泄漏
原 .doc 是一份填过的实例(真实课程名、开课号、教师姓名、人数、
年份、16 周真实日期与章节名)。本模板把每一个字段都换成了占位符。
已 grep 确认以下关键词在 oucal.sty 与 课程教学日历.tex 中零命中:
数字逻辑 080502101231 杨永全 2021 2022 逻辑代数
卡诺图 触发器 时序线路 组合线路 信息科学与工程学部 闭卷考试
内容里的「××××基础」「×××设计」等也都是通用占位(原件的真实章节名是 「逻辑代数基础」「组合线路设计」等,已全部抽象掉)。替换成你自己课程的 内容时,注意不要把这些真实信息再写回模板提交。
字体
全部取自仓库根目录的 fonts/,通过 fonts.tex 声明:
| 用途 | 字体 | 授权 |
|---|---|---|
| 正文宋体 | OUC Serif(思源宋体 SC 子集) | OFL 1.1 |
| 西文/数字 | TeX Gyre Termes(Times 度量兼容) | GUST Font License |
原件用的是「宋体 + Times New Roman」。换开源等价字体后字宽逐字相同, 所以列宽、折行数、行高都不受影响 —— 这一点已由上面的行高实测表验证。