Files
ouc_latex_template/课程教学日历
laoyang 8984b93ee7 模板内容填充:全库 × 占位换成完整虚构示例,README 表述同步 #11
把 11 个模板里满篇的 × 换成一套连贯的虚构数据,让样张能看出排版效果。

虚构设定(跨模板统一):
- 数字逻辑 / 2025 秋 / 张远航 / 计算机科学与技术学院,教学日历、成绩构成
  说明、试卷归档封面、试卷四份互相对得上
- 简历:张远航,1985.03,教授博导,论文/专利/项目/获奖全套虚构
- 年度考核:同一人的 2025 年度考核表,年度总结四段写满
- 执行大纲:刻意不套用上面那套(原件就是《信号与线性系统分析》的课,
  强行改课程名会让章节体系、学时分配、思政案例全对不上)

假数据都留肉眼可辨标记:DOI 统一 10.9999/、专利号 ZL209999…、
软著 2024SR99999…、手机 138 0000 0000、邮箱 example.net。
签字/日期/评语栏仍然留空——编了就分不清该改哪儿。

修掉一个既有 bug(填充时才暴露):
年度考核登记表「出生年月」的值从来不显示,连字符都不在 PDF 里。
根因是 tabularray 的 cell{r}{c}={c=n} 里那个 c 是数据行的单元序号、
不是视觉列号,表头三处 c=2 要求该行吃 15 个视觉列而表只有 11 列,
超出部分静默丢弃。迷惑点:写 12 格丢值,写 9 格反而正常。
正解是表头不写 cell{},改用行内 \SetCell[c=n]{c},每行照常写满 11 格。
同时 R0 行原件本就装不下(前六字段 115.76mm,剩 42.20mm 要放
19.68mm 标签 + 17.22mm 值,但列宽被切成 15.31/1.38/25.51),
把标签折两行「出生/年月」解决,列宽一个没改。

README 同步:根 README 与 8 个模板 README 里「一律用 × 占位」的说法
已不成立,统一改为「示例内容全部虚构」,并说明哪些位置仍必须留空。

验证:12 个模板零 error,4 处基线警告未新增;干净副本重编页数一致;
PDF 正文 × 清零;真实信息零泄漏。
2026-10-27 12:50:02 +08:00
..

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

学校下发的通用空白表「附件3:中国海洋大学课程教学日历(理论课程)」的 XeLaTeX 模板。原文件是学校统一的 .doc 表(7 列 × 18 行的 16 周教学日历 + 课程考核说明 + 签字栏),本目录把它做成可直接编译的 LaTeX 版。

本模板不含任何真实课程数据。 原 .doc 是一份填过的实例,本模板只复刻 它的版式,一个字段都没带过来 —— 见下面「真实数据零泄漏」。

但样张不是满篇叉号:为了能看出排版效果,填了一份完整的虚构课程—— 《数字逻辑》2025 秋学期、计算机科学与技术学院、任课教师「张远航」、 86 人、4 学分 64 学时、开课号 080502101231,16 周周次与章节内容全部写满 (含每章作业编号)。这套设定与 成绩构成说明/、 试卷归档封面/、试卷/ 是同一门课, 四份文档放在一起看是自洽的。

与 试卷归档封面/、试卷归档目录/ 的关系:三件同属教务处下发的表格套件(附件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}{2025}                      % 学年
\newcommand{\calseason}{秋}                      % 季学期(春/秋)
\newcommand{\caldept}{计算机科学与技术学院(系)}  % 开课单位
\newcommand{\calcodenum}{080502101231}           % 开课号(12 位)
\newcommand{\calteacher}{张远航}                  % 任课教师
\newcommand{\calcredit}{4.0}                     % 课程学分
\newcommand{\calhour}{64}                        % 总学时
\newcommand{\calstudent}{86}                     % 学生人数
\newcommand{\calassess}{闭卷考试}                 % 考核方式

⚠️ 改课程名 / 教师名时注意字数:表头标题、日期列宽都是按 4 字课程名、 3 字教师名标定的。课程名超过 6 字可能让「中国海洋大学 ○○ 课程教学日历」 那行折行,教师名超过 4 字会挤压签字栏。改完翻到 PDF 看一眼再走。

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

教学日历表

每行格式:

\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 中零命中: 原件课程名、原件开课号、原件教师姓名、原件年份、原件周次日期、 原件章节名、原件学部名、原件考核方式。

⚠️ 为什么是「换掉」而不是「打叉」:这张表16 行格子、每行 3~4 个字段, 全打叉的话整张表是灰的,既看不出周次排布,也判断不出某行内容多了会不会 撑高行高(本表的行高是写死的 ht= 最小高,内容超了会顶出去)。 填满一遍才发现「有的周章节名写两行,有的写一行」正是原件的真实形态。

替换成你自己课程的内容时,注意不要把原件的真实信息再写回模板提交。 git diff 里出现真实课程名 / 教师名就等于泄了。

字体

全部取自仓库根目录的 fonts/,通过 fonts.tex 声明:

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

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