Files
ouc_latex_template/课程教学日历/README.md
T

417 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 课程教学日历 —— 中国海洋大学课程教学日历(理论课程)
学校下发的**通用空白表**「附件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`,**没有 `<w:b/>`**。
本模板照抄,不加粗。`\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」。换开源等价字体后**字宽逐字相同**,
所以列宽、折行数、行高都不受影响 —— 这一点已由上面的行高实测表验证。