Files
ouc_latex_template/课程教学日历

课程教学日历 —— 中国海洋大学课程教学日历(理论课程)

学校下发的通用空白表「附件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 成品样张

编译

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}{××考试}                  % 考核方式

签字栏的「年 月 日」没有对应的宏,因为那是手写填写的空位, 模板中保持印刷好的「年」「月」「日」三个字,中间留白给老师签日期。

教学日历表

每行格式:

\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 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 立即生效。

\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. 签字栏的「年 月 日」是手写填空位,不要填年份

原件第 2 页底部两段:

段落 定位方式 起点 x
「院(系)负责人签字:」 w:ind firstLine=4515(79.64mm) 321.00
「×××× 年 月 日」 无 ind,靠空格顶位 331.44

原件是被填过的实例,年份位印着 4 个半角数字(x=331.44~352.50)。 本模板不写年份 —— 空白模板应该留白给老师手写。所以直接从「年」开始排:

\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××)占位,写法是:

\hspace*{92.604mm}\calfontC \calgrade~年\hspace{-8.35mm}月\hspace{3.34mm}日

实测:「月」落在 376.08,但 20×× 年 一直延伸到 x=399.74, 「月」正好压在「年」上 —— 三个字叠成一个墨团。

原因:负 \hspace 只能压缩它后面盒子之间的间距,不能把已经排出去的 字符收回来。减少间距减到负数 = 重叠,而不是"往回挪"。

最终解法不是修排版技巧,而是去掉年份占位符 —— 那本来就不该有。 现在这一行只有三个中文字,不存在宽度变化,位置永远稳定。

6. \calgrade 已删除

曾经有过 \newcommand{\calgrade}{20××},后因「签字栏不该印年份」而删除。 如果你需要预填年份(比如批量生成带年份的表),自己加回来:

\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.tex 声明:

用途 字体 授权
正文宋体 OUC Serif(思源宋体 SC 子集) OFL 1.1
西文/数字 TeX Gyre Termes(Times 度量兼容) GUST Font License

原件用的是「宋体 + Times New Roman」。换开源等价字体后字宽逐字相同, 所以列宽、折行数、行高都不受影响 —— 这一点已由上面的行高实测表验证。