## 用户诉求
「评分标准最开始不是有个表格吗,里面有开课学院什么的,如果学院名字太长,
就和后面课程名称重合在一起了。看看怎么解决。我建议一行一个信息,
因为这个又不怕长,如果太长,一行都放不下,就直接换行。」
## 现象(已复现并量化)
`dept` 填超长值后,pymupdf 读出:
开课学院:(70.87~123.37) 值:(123.37~406.87)
而第二格「课程名称」本该从 216.0 开始 ⇒ **重叠 190.87bp**,
把「课程名称 / 试卷类型」整个压住。
⚠️ **编译完全不报错,也没有 Overfull**(rc=0 over=0)。
`\makebox` 内容溢出盒外时 TeX 不认为是坏行。这类 bug 只能靠
量 span 坐标发现,看日志发现不了。
## 根因
`\ouckey@inforow` 用三个 `\makebox[0.3333\linewidth][l]` 并排。
`\makebox` 是**不可断盒子**:
- 内容比盒宽短 → 左对齐(正常)
- 内容比盒宽长 → **直接溢出盒外,压到右边一格**(就是 bug)
- 盒内中文**不能换行**(LR 模式,不做段落断行)
⇒ 三格布局对短字段友好,对长字段必然重叠。
## 改动一:一行一项
\newcommand{\ouckey@inforow}[2]{%
\ouckey@ifempty{#2}{}{%
{\fontsize{10.5bp}{15bp}\selectfont
\par\noindent\textbf{#1:}#2\par}}}
调用点从「三参数一组」改成「两参数一行」,共 9 项:
课程名称 / 开课学院 / 学生学院 / 年级专业 / 试卷类型 / 考试方式 /
任课教师 / 命题人 / 满分。顺序按"读者关心程度"排。
行距收紧到 15bp(`\Fivhao` 基线距是 16bp)—— 抬头是查阅信息,
不需要正文行距;9 行省约 52bp,将近 3 行正文。
## 改动二:附带修掉一个旧版就有的判空 bug
**旧实现**:
\newcommand{\ouckey@ifempty}[1]{%
\if\relax\detokenize{#1}\relax ... }
调用 `\ouckey@ifempty{\ouckey@dept}{空}{非空}`。
`\detokenize` **不展开参数**,看到的是字符串 `\ouckey@dept`(宏名),
永远非空 ⇒ 判空永远失败。
后果:`dept = {}` 时抬头印出孤立的「开课学院:」,后面没有值。
这是旧版就有的 bug(三格布局下不明显),改成一行一项后暴露。
**修法**:在 `\define@key` 里就规范化空值,新增
\newcommand{\ouckey@setkey}[2]{%
\if\relax\detokenize{#2}\relax \def#1{}\else \def#1{#2}\fi}
13 个 key 全改走它。
⚠️ 试过「先 `\edef` 展开再 `\detokenize`」的写法,报
`! Incomplete \iffalse` —— `\if...\else...\fi` 被 `\expandafter`
链打散,TeX 匹配 `\fi` 时错位。这类宏层展开的修补太脆,放弃。
⚠️ `\ouckey@setkey` 必须定义在 `\define@key` **之前**,否则 undefined。
## 验证
### 三个场景
| 场景 | 结果 |
|---|---|
| 正常(学院名 12 字) | 9 项齐全,无重叠,无越界 |
| 长(含括号说明,约 40 字) | 单行排到 x=406.90,无重叠(下面没有格子可压) |
| 极长(约 70 字) | **自动换行成两行**,第二行缩进对齐,右缘正好 524.40 |
| 留空(dept/college/major 全空) | 三项**整项不印**,不留空行 |
### 编译
试卷题型示例 6 页 err=0 over=0
参考答案与评分标准 4 页 err=0 over=0
全库 `bash build.sh all` → **13/13 通过,0 失败**。
### 页数变化
答案卷 3 页 → 4 页。抬头从 3 行变 9 行,是「一行一项」的必然代价。
**已向用户确认,接受 4 页**(第 4 页只有说明文字末尾 2 行 + 签字行)。
## 示例文件未改
`参考答案与评分标准示例.tex` 测试后已还原,`git diff` 为空。
只改 `ouckey.sty` 一个文件。
试卷 —— 试卷与参考答案 LaTeX 模板
中国海洋大学期末考试试卷与参考答案及评分标准的 XeLaTeX 模板。 两份模板均不含真题,题目与分值都是示意,按教学大纲替换即可。
样张是《数字逻辑》2025 秋的一份完整虚构 A 卷:16 类题型(填空 / 选择 / 判断 / 化简 / 分析 / 设计 / 时序 / 计数器 / 状态机 / 存储器 / ADC-DAC / Verilog / 综合设计 …)+ 对应参考答案与评分标准(8 大题 / 满分 100)。 题目是按数字逻辑课程内容真编的、答案算过是对的,不是「略」或「×××」—— 这样既能看清版式,也能看出各题型的排版难度(比如卡诺图、时序波形图 都是 TikZ 内联画的)。
与
课程教学日历/、成绩构成说明/、试卷归档封面/是同一门课,四份一起看是自洽的。 唯一故意留空的是命题人 / 审核人 / 各题得分栏 —— 那是要签字担责的, 编名字上去容易被误当真实署名。
文件
| 文件 | 说明 |
|---|---|
oucexam.sty |
试卷版式包。纸张 195×270mm(学校专用试卷纸),含装订栏、成绩表、页脚 |
ouckey.sty |
答案与评分标准版式包。默认 A4;加 exampaper 选项则与试卷同纸同边距 |
试卷题型示例.tex |
试卷范例,16 类题型,6 页 |
参考答案与评分标准示例.tex |
答案范例,8 大题 / 满分 100,3 页 |
编译
xelatex 试卷题型示例.tex
xelatex 试卷题型示例.tex # 跑两遍,第二遍才能取到正确总页数(lastpage)
必须用 XeLaTeX(fontspec + xeCJK),pdflatex 编译不过。 两份 .tex 都在本目录内自包含,不依赖任何图片文件(示例配图是 TikZ 内联画的)。
试卷模板要点
纸张、页边距、行距、装订栏位置、页脚样式都已在 oucexam.sty 里按原卷实测值定好,
一般只改 \examsetup 那一块(下例是样张实际用的虚构值):
\examsetup{
year = {2025}, % 学年(会自动印成 2025—2026)
term = {秋}, % 秋 / 春
course = {数字逻辑},
college = {信息科学与工程学院},
type = {A}, % 卷头「试卷类型:A 卷」
foottype = {}, % 页脚课程名后是否再印类型字母,空=不印
proposer = {}, % 命题人
reviewer = {}, % 审核人
mode = {闭卷},
allowed = {无}, % 允许携带的物品
nsec = {16}, % 大题数,成绩表自动按 nsec+1 栏等分
}
proposer/reviewer样张里故意留空:命题人、审核人是要签字担责的, 编一个名字上去容易被误当真实署名。留空时模板自动不印该项、不留空横线。type与foottype互不影响:前者只管卷头,后者只管页脚。 原卷 A 卷页脚没印字母、B 卷印了 B,这是出卷人手工差异,不是规则。- 大题数变了必须同步改
nsec,否则成绩表格子会错位。
答案模板要点
\keysetup{
year = {2025}, term = {秋}, course = {数字逻辑}, type = {A},
dept = {计算机科学与技术学院}, college = {信息科学与工程学院},
major = {计算机科学与技术专业 \TNR{2023} 级},
teacher = {张远航}, examiner = {张远航},
mode = {闭卷、笔试}, total = {100},
}
如实现在试卷同一张纸上印,把 \usepackage{ouckey} 改成 \usepackage[exampaper]{ouckey}。
体例四条(学校没有统一模板,按国内高校通行做法拟定):
- 抬头信息齐全,留空的项自动不印、不留空横线
- 客观题答案集中列(
\objkeys{C,B,A,...},每行 6 个自动换行),附统一给分规则 - 主观题内嵌分步分值(
\pt{2}),阅卷时批到哪里看到哪里 - 每个大题末尾给【评分标准】
\scoring{...}
主观题内嵌分值是有意为之:阅卷是流水作业,分值必须和答案文字贴在一起, 集中写在末尾会让批卷人来回翻页。
文末 \keytotal{20,10,12,12,12,16,8,10} 是分值汇总表,用于阅卷前核对
「各大题相加 = 满分」。签名栏 \keysign 只留命题人一处。
书写注意
- 题干内部换行用
\\*,不要用裸\\。裸\\语义是"允许在此断页", 题干会被切到上下两页,学生看到半句话。 - 数字、英文用
\TNR{...}包起来(内部显式\normalfont收尾)。 裸用字体切换命令会让字体状态泄漏到后续段落。 \keyitem{题号}{分值}{答案正文}的参数里不要嵌tabular/tikzpicture—— 这些环境的&与\\会被当成参数分隔符,报File ended while scanning。 表格和图写在\keyitem之后。