用户要求:「试卷的字体,需要改一下,全部都用思源宋体就可以了。」 pymupdf 提取两份成品 PDF 定位到四类需要统一的字体: 试卷题型示例(6 页) 参考答案与评分标准示例(3 页) OUCSerif-Regular 203 中文 OUCSerif-Regular 333 中文 TeXGyreTermes-Regular 133 西文 TeXGyreTermes-Regular 197 西文 TeXGyreTermes-Bold 85 题号分值 OUCSans-Regular 37 黑体小标题 OUCSerif-Bold 81 中文加粗 CMR10/CMMI10/CMSY10 49 数学公式 OUCKai-Regular 25 卷头与成绩表 TeXGyreTermes-Bold 21 数字 一、西文与数字:Termes → 思源宋体(oucexam.sty / ouckey.sty) 先在包里用 \newfontfamily 登记 \OUCLatinSerif / \OUCLatinSerifBold (四个档位全给,斜体指回 Regular —— 思源宋体没有真斜体),再把 \tnr / \tnrb / \sunfont 都 \let 过去。 ⚠️ 只做这一步**不够**,这点踩了一轮:模板里大量文字根本没走 \tnr, 而是走 \setmainfont(默认西文族)——\textbf{learning outcome} 的裸西文、 页脚的页码与总页数、任何 \selectfont 之后的裸 ASCII。 第一轮只 \let 时,PDF 里仍残留 22 个 TeXGyreTermes span: 页脚 6 个页码 + 6 个总页数 + 第 4 页的 A./B./C./D. 与 learning outcome 等。 ⇒ 必须连 \setmainfont / \setsansfont 一起指到 OUCSerif。 二、卷头与成绩表题号:霞鹜文楷 → 思源宋体(oucexam.sty) \kh 与 \scoretable 用 \kaiti。包里 \let\kaiti\songti 一次性收口, 不动 ../fonts.tex 的 \def\kaiti —— 别的模板还要用楷体。 三、答案卷黑体小标题:思源黑体 → 思源宋体(ouckey.sty) \heiti 共 37 处(卷头大标题与各节小标题)。同样 \let\heiti\songti。 四、数学公式:保持不动 $x$ / $\dfrac{1}{45}\sum f_i x_i$ 等 49 处仍走 Computer Modern。 思源宋体没有 MATH 表(实测只有 GSUB/GPOS),不能作数学字体; 强行塞进正文字体会破坏斜体变量、正体数字、运算符间距。用户已确认。 五、装订线虚线:连字符排字 → 用线画(oucexam.sty) 原卷那 6 段竖虚线是 18bp Times 连字符排出来的。换成思源宋体后连字符 每个宽 0.346 em(Termes 0.333),每串会长 4~7bp,破坏"复刻原卷长度"。 ⇒ 改为几何画的短横线段,长度锁死。实测原卷(720dpi 逐笔画测量): 单个连字符笔画 长 4.40bp、粗 1.10bp、x 中心 51.60bp 相邻笔画步距 6.00bp 新增 \ouc@dashseg{起点y}{笔画数},六段坐标与笔画数逐段照抄原卷 (含原卷被白底遮断成 13+3 两截的那一段)。 ⚠️ \newcommand 必须定义在 \ouc@dobindstrip 外面,否则每次 shipout 重新定义,第二页起报 already defined。 ⚠️ y 坐标不能写 \dimexpr#1bp+\i*6bp —— TikZ 坐标解析器不认小数 + \dimexpr 组合,报 Illegal unit of measure。要先 \pgfmathsetmacro。 六、直引号归入 CJK(ouckey.sty) 答案卷正文用 ASCII 直引号 "" 表示中文引号(32 处)。U+0022 在 Basic Latin,xeCJK 判为西文 ⇒ 引号走 \setmainfont,与两侧汉字不同字体, 并挤压出 Overfull(第 88 行 +8.07pt)。 \xeCJKDeclareCharClass{CJK}{"0022} 归位后解决。 ⚠️ 不在 fonts.tex 里改:那里全库共用,会让正文真正的英文引号(代码 片段 "hello world")也变全角。 七、Overfull 的修法:改源码断行,不调参数(ouckey.sty 留有完整记录) 换字体后 \keyitem{3}{4} 那段 Overfull 2.61pt,实际压出版心 (最右 527.01bp,版心止于 524.41)。试过并确认**全部无效**的五种: · \emergencystretch 1.5em —— 本行没有弹性胶可拉,无从施展 · \tolerance 3000 —— 不是不满意断点,是没有更早的合法断点 · PunctStyle=kaiming —— 能消 Overfull,但标点悬挂探出版心 6.5bp · PunctStyle=quanjiao —— 同样探出 8bp (xeCJK 这几个 PunctStyle 做的是"标点悬挂"不是"标点压缩", 指望它消 Overfull 必翻车) · \rightskip 0pt plus 6bp(含 \AtBeginDocument 版)—— 6bp 远小于 需要的 150bp,TeX 仍选"撑破"这个解 正解:第 124 行句号「。」后加 \\。分项给分的答案本来就该一句一行, 读者靠句号扫分点 —— 既解决排版,又让内容结构更清楚。 验证(两套环境各跑,逐项同数) WSL / TeX Live 2025 试卷 rc=0 err=0 over=0 6页 答案卷 rc=0 err=0 over=0 3页 Windows / TeX Live 2022 试卷 rc=0 err=0 over=0 6页 答案卷 rc=0 err=0 over=0 3页 pymupdf 提取字体分布: 试卷题型示例 OUCSerif-Regular 291 + OUCSerif-Bold 70 → 非宋体文字字体:无 参考答案示例 OUCSerif-Regular 235 + OUCSerif-Bold 53 + CMR10/CMMI10/CMSY10/CMMI7/CMEX10(数学,保留) → 非宋体文字字体:无 几何核对(逐 span 比对,均与基线一致或更优): · 成绩表 17 栏逐字符坐标与基线**完全相同**(硬编码坐标,不受字体影响) · 装订线 96 笔全部绘出:x 49.40~53.60(宽 4.40bp)、步距 6.00bp, 六段位置与原卷实测一致 · 三页最右墨迹均为 524.41bp,与版心严丝合缝,无越界 · 试卷 6 页最右最大 523.33bp(第 4 页 \mtpair 的 44% 栏宽设计, 基线同位置同样数值,非本次引入) 涉及文件: 试卷/oucexam.sty 默认族、西文字族、\kaiti、装订线虚线 试卷/ouckey.sty 默认族、西文字族、\heiti、直引号、踩坑记录 试卷/参考答案与评分标准示例.tex 第 124 行句号后加断行
515 lines
24 KiB
TeX
515 lines
24 KiB
TeX
%======================================================================
|
||
% ouckey.sty — 中国海洋大学 期末考试 参考答案及评分标准 版式包
|
||
%
|
||
% 设计依据:学校无统一模板,本包按国内高校通行体例提炼,要点四条——
|
||
% ① 抬头须有:学年学期、课程名、试卷类型、学院/年级/专业、
|
||
% 开课学院、任课教师、考试方式、(可选)满分与考试日期;
|
||
% ② 客观题(填空/选择/判断)答案集中列出,附统一给分规则;
|
||
% ③ 主观题(名词解释/简答/论述/分析/计算/作图…)答案内嵌
|
||
% "分步给分"标记,每个得分点后直接标(X 分);
|
||
% ④ 每个大题末尾给【评分标准】,写清"答错/多选/漏选/未答"如何计分。
|
||
%
|
||
% 与 oucexam.sty(试卷版式)配套使用,字号字体同名同义:
|
||
% \Fivhao 五号正文 / \Sihao 小四 / \Xiaowu 小五
|
||
% \TNR{} Times / \TNRB{} Times Bold / \SUN{} 宋体 / \heiti 黑体
|
||
%
|
||
% 纸张:默认 A4(阅卷、归档、打印都按 A4 走)。
|
||
% 需要与试卷同尺寸(195mm×270mm 专用试卷纸)时加选项 exampaper:
|
||
% \usepackage[exampaper]{ouckey}
|
||
%
|
||
% 用法见同目录 参考答案与评分标准示例.tex
|
||
%======================================================================
|
||
\NeedsTeXFormat{LaTeX2e}
|
||
\ProvidesPackage{ouckey}[2026/10/22 v1.0 OUC answer key & marking scheme]
|
||
|
||
% 本包大量使用 \ouckey@xxx 私有名,@ 必须当字母
|
||
\makeatletter
|
||
|
||
\RequirePackage{fontspec}
|
||
\RequirePackage{xeCJK}
|
||
\RequirePackage{graphicx}
|
||
\RequirePackage{lastpage}
|
||
\RequirePackage{keyval}
|
||
\RequirePackage{array}
|
||
\RequirePackage{tikz} % 作图题的参考答案要配图
|
||
\usetikzlibrary{calc,positioning}
|
||
|
||
%----------------------------------------------------------------------
|
||
% 纸张选项
|
||
% exampaper → 195mm × 270mm(与试卷同纸,便于和试卷一起装订归档)
|
||
% 默认 → A4(阅卷现场用,字大好认)
|
||
%----------------------------------------------------------------------
|
||
\newif\ifouckey@exampaper \ouckey@exampaperfalse
|
||
\DeclareOption{exampaper}{\ouckey@exampapertrue}
|
||
\ProcessOptions\relax
|
||
|
||
\ifouckey@exampaper
|
||
\RequirePackage[paperwidth=195mm,paperheight=270mm,
|
||
left=82.2bp,textwidth=436.55bp,
|
||
top=46bp,textheight=650bp,
|
||
headheight=0pt,headsep=0pt,footskip=30bp]{geometry}
|
||
\else
|
||
\RequirePackage[paperwidth=210mm,paperheight=297mm,
|
||
left=25mm,right=25mm,top=25mm,bottom=25mm,
|
||
headheight=0pt,headsep=0pt,footskip=28bp]{geometry}
|
||
\fi
|
||
|
||
\setlength{\parindent}{0pt}
|
||
\setlength{\parskip}{0pt}
|
||
\pagestyle{empty}
|
||
|
||
%----------------------------------------------------------------------
|
||
% 字体(#20:与 oucexam.sty 同步,全部统一为思源宋体)
|
||
% 全部取自公共字体包 ../fonts.tex,再在包内重定向。
|
||
%
|
||
% ⚠️ 必须和 oucexam.sty 做**完全相同的处理**,否则两份文件观感不一致:
|
||
% oucexam 是试卷、ouckey 是答案卷,学校通常一起印发。
|
||
%
|
||
% ⚠️⚠️ 只重定向 \tnr / \tnrb 不够 —— 模板里大量文字没走它们:
|
||
% · \textbf{...} 的裸西文 → 走 \setmainfont
|
||
% · 「(4 分)」里的数字 → 走 \setmainfont
|
||
% · \thepage / \pageref 页码 → 走 \setmainfont
|
||
% ⇒ 必须连 \setmainfont 一起改,否则 PDF 里会残留 Termes span。
|
||
% 实测第一轮只做 \let 时,答案卷残留 197 个 Termes span。
|
||
%
|
||
% ⚠️ 数学公式($x$ / $\dfrac{}{}$ / $\sum$)**不在统一范围内**,
|
||
% 仍走 Computer Modern。原因见 oucexam.sty 的说明:思源宋体没有
|
||
% MATH 表,不能作数学字体。用户已确认保持不动。
|
||
%----------------------------------------------------------------------
|
||
\input{../fonts.tex}
|
||
|
||
%----------------------------------------------------------------------
|
||
% 西文字族:思源宋体(与 oucexam.sty 同一套定义,复制而非 \input,
|
||
% 因为两个 .sty 是并列关系,谁先加载不确定,不能互相依赖)
|
||
%----------------------------------------------------------------------
|
||
\newfontfamily\OUCLatinSerif{OUCSerif}[
|
||
Extension = .otf,
|
||
Path = ../fonts/ouc-serif/,
|
||
UprightFont = *-Regular,
|
||
BoldFont = *-Bold,
|
||
ItalicFont = *-Regular,
|
||
BoldItalicFont = *-Bold,
|
||
]
|
||
\newfontfamily\OUCLatinSerifBold{OUCSerif}[
|
||
Extension = .otf,
|
||
Path = ../fonts/ouc-serif/,
|
||
UprightFont = *-Bold,
|
||
BoldFont = *-Bold,
|
||
ItalicFont = *-Bold,
|
||
BoldItalicFont = *-Bold,
|
||
]
|
||
|
||
% 默认西文字体 → 思源宋体(这一步不能省,见上面的说明)
|
||
\setmainfont{OUCSerif}[
|
||
Extension = .otf,
|
||
Path = ../fonts/ouc-serif/,
|
||
UprightFont = *-Regular,
|
||
BoldFont = *-Bold,
|
||
ItalicFont = *-Regular,
|
||
BoldItalicFont = *-Bold,
|
||
]
|
||
\setsansfont{OUCSerif}[
|
||
Extension = .otf,
|
||
Path = ../fonts/ouc-serif/,
|
||
UprightFont = *-Regular,
|
||
BoldFont = *-Bold,
|
||
ItalicFont = *-Regular,
|
||
BoldItalicFont = *-Bold,
|
||
]
|
||
|
||
% \sunfont 原指 SimSun,现统一指思源宋体
|
||
\let\sunfont\OUCLatinSerif
|
||
\let\tnr\OUCLatinSerif
|
||
\let\tnrb\OUCLatinSerifBold
|
||
|
||
% 答案卷里的黑体小标题(\heiti,共 37 处)一并换成思源宋体,#20 要求统一。
|
||
% ⚠️ 用 \let 而非改 \def\heiti —— 后者会破坏 ../fonts.tex,影响别的模板。
|
||
% ⚠️ 若将来要恢复黑体标题,注释掉下面这行即可。
|
||
\let\heiti\songti
|
||
|
||
% 局部字体切换必须显式收尾,否则 xeCJK 字体会"溢出"到后一段
|
||
\newcommand{\TNR}[1]{{\tnr #1\normalfont}}
|
||
\newcommand{\TNRB}[1]{{\tnrb #1\normalfont}}
|
||
\newcommand{\SUN}[1]{{\sunfont #1\normalfont}}
|
||
|
||
% 中西文间距 2.70bp(原卷实测值,不随字号缩放)。
|
||
\xeCJKsetup{CJKecglue={\hskip 2.70bp\relax},PunctStyle=plain}
|
||
|
||
%----------------------------------------------------------------------
|
||
% 直引号 " (U+0022) 归入 CJK 字符类(#20 补充修正)
|
||
%
|
||
% ⚠️ 问题:本文件正文用 ASCII 直引号 "" 表示中文引号(共 32 处),
|
||
% 而 xeCJK 按 Unicode 区段分类 —— U+0022 在 Basic Latin,被判成**西文**,
|
||
% 于是引号走 \setmainfont(原为 Termes),与两侧汉字不同字体、不同宽度。
|
||
% 换字体后表现为一行里出现 TeXGyreTermes-Regular 的 ",且因为西文引号
|
||
% 比中文引号窄、字距算法不同,挤压出 Overfull(实测第 88 行 +8.07pt)。
|
||
%
|
||
% ⚠️ 为什么不在 fonts.tex 里改:那里是**全库共用**的,把 U+0022 全局归 CJK
|
||
% 会让正文里真正的英文引号(如 code 片段 "hello world")也变成全角,
|
||
% 破坏那些地方。这是试卷的局部需求 —— 试卷正文基本是中文,直引号就是
|
||
% 中文引号的意思。
|
||
% ⚠️ 必须在 \input{../fonts.tex} **之后**声明,否则被随后加载的默认分类覆盖。
|
||
%----------------------------------------------------------------------
|
||
\xeCJKDeclareCharClass{CJK}{"0022} % " 直引号(当中文引号用)
|
||
|
||
%----------------------------------------------------------------------
|
||
% 【#20 踩坑记录】换字体引起的 Overfull 怎么修(结论:改源码断行,别调参数)
|
||
%
|
||
% 现象:字体统一为思源宋体后,参考答案与评分标准示例.tex 第 123-125 行
|
||
% (\keyitem{3}{4})出现 Overfull 2.61pt,渲染后压出版心右边界
|
||
% (最右墨迹 527.01bp,版心止于 524.41bp)。
|
||
% 基线同一行本来就有 0.21pt 轻微 Overfull,换字体后西文变宽
|
||
% (数字 +6%、小写字母 +13~20%),把 0.21 放大到 2.61。
|
||
%
|
||
% 根因:该行末尾是「…正迁移与负迁移(1 分),」。
|
||
% · 「,」按中文标点禁则不能出现在行首 ⇒ 不许在「)」与「,」之间断
|
||
% · \pt 定义里有 `~`(\nobreakspace)⇒ 「(1 分)」内部也不可断
|
||
% ⇒ 「(1 分),」约 16bp,是不可断尾部,必须整体塞进本行。
|
||
% 而本行没有弹性胶可拉(汉字定宽、唯一的西文空格已满),
|
||
% 往前找更早的合法断点(句号「。」之后)的话,第一行只填 65%,
|
||
% 欠宽 150bp 无处拉伸 ⇒ badness 爆掉 ⇒ TeX 宁可撑破 2.61bp。
|
||
%
|
||
% ⚠️⚠️ 试过且**全部无效**的方案(别再花时间重复,每种都实测过):
|
||
% ① \emergencystretch 1.5em —— 无效。只追加"可拉伸量",
|
||
% 本行没有胶可拉,给多少额度都无从施展。
|
||
% ② \tolerance 3000 —— 无效。不是不满意断点,是没有更早的**合法**断点。
|
||
% ③ PunctStyle=kaiming —— 能消 Overfull,但句末标点悬挂探出版心
|
||
% 6.5bp(第 3 页右缘 530.93 > 524.41),比不修更难看。
|
||
% ④ PunctStyle=quanjiao —— 同样探出 8bp(532.5),一样不可接受。
|
||
% 根子在于 xeCJK 的这几个 PunctStyle 做的是"行末标点**悬挂**",
|
||
% 不是"标点**压缩**"—— 中文排版里这是两回事,指望它消 Overfull 必翻车。
|
||
% ⑤ \rightskip 0pt plus 6bp(含 \AtBeginDocument 版)—— 无效。
|
||
% 6bp 远小于需要的 150bp,TeX 仍选"撑破"这个解。
|
||
% 注意:preamble 里直接 \setlength 还会被 ctex/xeCJK 初始化重置,
|
||
% 必须 \AtBeginDocument 才可能生效 —— 这类"设了没反应"的常见原因。
|
||
%
|
||
% ✅ 正解:**在源码的自然断点处手动断行**。
|
||
% 第 124 行末尾句号「。」之后本来就是语义上的句子边界,加 \\ 即可:
|
||
% \keyitem{3}{4}{%
|
||
% \textbf{学习迁移}:指一种学习对另一种学习的影响\pt{2}。\\
|
||
% 按方向分正迁移与负迁移\pt{1},其发生依赖于共同要素与概括水平\pt{1}。}
|
||
% 已实测:Overfull 归零,三页最右墨迹均为 524.41bp,与版心严丝合缝。
|
||
%
|
||
% 为什么这是对的做法而不是"绕过问题":
|
||
% 分项给分的答案本来就该一句一行,读者(阅卷人)靠句号扫分点。
|
||
% 手动断行既解决排版,又让内容结构更清楚 —— 不是妥协,是双赢。
|
||
% ⚠️ 若将来修改了这段文字导致又不齐,先看这里:多半还是在句号处补 \\。
|
||
%----------------------------------------------------------------------
|
||
|
||
\newcommand{\Fivhao}{\fontsize{10.5bp}{16bp}\selectfont}
|
||
\newcommand{\Sihao}{\fontsize{12bp}{18bp}\selectfont}
|
||
\newcommand{\Xiaowu}{\fontsize{9bp}{13bp}\selectfont}
|
||
\AtBeginDocument{\Fivhao\selectfont}
|
||
|
||
%======================================================================
|
||
% 1. 抬头信息
|
||
% \keysetup{ ... } 里填;留空的项自动不印(不留空横线)
|
||
%======================================================================
|
||
\def\ouckey@year{}\def\ouckey@term{}\def\ouckey@course{}
|
||
\def\ouckey@type{}\def\ouckey@college{}\def\ouckey@grade{}
|
||
\def\ouckey@major{}\def\ouckey@dept{}\def\ouckey@teacher{}
|
||
\def\ouckey@examiner{}\def\ouckey@mode{}\def\ouckey@date{}
|
||
\def\ouckey@total{}
|
||
\define@key{ouckey}{year}{\def\ouckey@year{#1}}
|
||
\define@key{ouckey}{term}{\def\ouckey@term{#1}}
|
||
\define@key{ouckey}{course}{\def\ouckey@course{#1}}
|
||
\define@key{ouckey}{type}{\def\ouckey@type{#1}}
|
||
\define@key{ouckey}{college}{\def\ouckey@college{#1}}
|
||
\define@key{ouckey}{grade}{\def\ouckey@grade{#1}}
|
||
\define@key{ouckey}{major}{\def\ouckey@major{#1}}
|
||
\define@key{ouckey}{dept}{\def\ouckey@dept{#1}}
|
||
\define@key{ouckey}{teacher}{\def\ouckey@teacher{#1}}
|
||
\define@key{ouckey}{examiner}{\def\ouckey@examiner{#1}}
|
||
\define@key{ouckey}{mode}{\def\ouckey@mode{#1}}
|
||
\define@key{ouckey}{date}{\def\ouckey@date{#1}}
|
||
\define@key{ouckey}{total}{\def\ouckey@total{#1}}
|
||
\newcommand{\keysetup}[1]{\setkeys{ouckey}{#1}}
|
||
|
||
% 判空:\ifx 比不了多 token 参数,用 \detokenize 展成字符串再比
|
||
\newcommand{\ouckey@ifempty}[1]{%
|
||
\if\relax\detokenize{#1}\relax
|
||
\expandafter\@firstoftwo
|
||
\else
|
||
\expandafter\@secondoftwo
|
||
\fi}
|
||
|
||
% 一格信息:标签加粗,值用宋体;值为空则整格不出
|
||
\newcommand{\ouckey@cell}[2]{%
|
||
\ouckey@ifempty{#2}{}{\textbf{#1:}#2}}
|
||
% 一行三格,等宽
|
||
\newcommand{\ouckey@inforow}[6]{%
|
||
\par\noindent
|
||
\makebox[0.3333\linewidth][l]{\ouckey@cell{#1}{#2}}%
|
||
\makebox[0.3333\linewidth][l]{\ouckey@cell{#3}{#4}}%
|
||
\makebox[0.3333\linewidth][l]{\ouckey@cell{#5}{#6}}\par}
|
||
|
||
%----------------------------------------------------------------------
|
||
% 抬头:标题 + 信息栏 + 一条分隔线
|
||
% 标题 = 学年学期 + 课程名 + 试卷类型 +「参考答案及评分标准」
|
||
%----------------------------------------------------------------------
|
||
\newcommand{\makekeyhead}{%
|
||
\par\noindent
|
||
\begin{minipage}{\linewidth}\centering
|
||
{\fontsize{16bp}{24bp}\selectfont\heiti
|
||
中国海洋大学%
|
||
\ouckey@ifempty{\ouckey@year}{}{\quad\TNR{\ouckey@year}—\TNR{\the\numexpr\ouckey@year+1\relax}~学年}%
|
||
\ouckey@ifempty{\ouckey@term}{}{\ouckey@term 学期}\par}
|
||
\vspace*{6bp}
|
||
{\fontsize{15bp}{22bp}\selectfont\heiti
|
||
《\ouckey@course》期末考试%
|
||
\ouckey@ifempty{\ouckey@type}{}{(\ouckey@type~卷)}%
|
||
参考答案及评分标准\par}
|
||
\end{minipage}\par
|
||
\vspace*{10bp}
|
||
\hrule height 1.2bp
|
||
\vspace*{8bp}
|
||
\ouckey@inforow{开课学院}{\ouckey@dept}%
|
||
{课程名称}{\ouckey@course}%
|
||
{试卷类型}{\ouckey@ifempty{\ouckey@type}{}{\ouckey@type~卷}}
|
||
\ouckey@inforow{学生学院}{\ouckey@college}%
|
||
{年级专业}{\ouckey@major}%
|
||
{考试方式}{\ouckey@mode}
|
||
\ouckey@inforow{任课教师}{\ouckey@teacher}%
|
||
{命题人}{\ouckey@examiner}%
|
||
{满分}{\ouckey@ifempty{\ouckey@total}{}{\TNR{\ouckey@total}~分}}
|
||
% ★ 抬头【不印考试日期】。
|
||
% date 这个键仍保留(老文件写了 date= 不会报 unknown key),
|
||
% 但不再渲染,对话框里也不显示,避免抬头行数随日期有无而浮动。
|
||
\vspace*{6bp}
|
||
\hrule height 0.6bp
|
||
\vspace*{10bp}}
|
||
|
||
%======================================================================
|
||
% 2. 分值标记
|
||
% \pt{2} → 「(2 分)」——嵌在答案文字中间,标"这一点值 2 分"
|
||
% \ptline{2}{要点} → 要点靠左、分值靠右对齐(长要点更清爽)
|
||
%======================================================================
|
||
% 分值标注「(X 分)」。
|
||
% ⚠️ 里面用 `~`(\nobreakspace)是**有意为之** —— 不让「X」和「分」
|
||
% 分处两行,否则会看到「(1」在行末、「分)」在下一行,很难看。
|
||
% 代价是「(X 分)」成了不可断的整体,极端行宽下会撑破版心。
|
||
% ⇒ 这个代价接受,解决办法是在**源码的句子边界手动断行**,
|
||
% 不是在这里放开可断。详见上面「#20 踩坑记录」一节。
|
||
\newcommand{\pt}[1]{{\small(\TNR{#1}~分)}}
|
||
\newcommand{\ptline}[2]{%
|
||
\par\noindent #2\unskip\par\nobreak
|
||
\vspace*{-1.2\baselineskip}%
|
||
\par\noindent\hfill{\small(\TNR{#1}~分)}\par\vspace*{2bp}}
|
||
|
||
% 阿拉伯数字 → 中文(1..16)★必须在使用之前定义
|
||
\newcommand{\ouckey@cnnum}[1]{%
|
||
\ifcase\numexpr#1\relax\or 一\or 二\or 三\or 四\or 五\or 六%
|
||
\or 七\or 八\or 九\or 十\or 十一\or 十二%
|
||
\or 十三\or 十四\or 十五\or 十六\fi}
|
||
|
||
%======================================================================
|
||
% 3. 大题标题
|
||
% \keysection{一}{单项选择题}{10}{2}
|
||
% → 一、单项选择题(本题共 10 题,每小题 2 分,共 20 分)
|
||
% 小计自动算,不用手填。
|
||
% \keysectionfree{二}{填空题}{共 10 空,每空 1 分}
|
||
% → 二、填空题(共 10 空,每空 1 分) —— 分值不整齐时用这个
|
||
%======================================================================
|
||
\newcommand{\keysection}[4]{%
|
||
\par\addvspace{10bp}%
|
||
{\Sihao\heiti
|
||
#1、#2(本题共~\TNR{#3}~题,每小题~\TNR{#4}~分,共~\TNR{\the\numexpr#3*#4\relax}~分)\par}%
|
||
\addvspace{5bp}}
|
||
\newcommand{\keysectionfree}[3]{%
|
||
\par\addvspace{10bp}%
|
||
{\Sihao\heiti #1、#2(#3)\par}%
|
||
\addvspace{5bp}}
|
||
|
||
%======================================================================
|
||
% 4. 客观题答案
|
||
% \objkeys{C,B,A,B,C,A,C,A,C,A} → 每行 6 个,自动换行
|
||
% \objkeys[5]{...} → 每行 5 个
|
||
% 答案串逗号分隔;题号自动递增。
|
||
%
|
||
% ★ 这个宏反复改过很多次,下面几条是实测结论,动之前先看清:
|
||
%
|
||
% 【不能用 \par 分行】
|
||
% 在 \@for 循环体里写 "\hbox to \textwidth{...}\par\noindent",
|
||
% 整段放进文档正文是好的,但一旦包进宏体就失效——
|
||
% 十几项全排在同一个基线(pdftotext -bbox 的 yMin 完全相同),
|
||
% 后续直接溢出纸边。\@for 先把循环体抓成参数,\par 的展开时机不对。
|
||
%
|
||
% 【不能用 \unvbox + \prevdepth 思路】
|
||
% \unvbox 执行完寄存器即被清空,紧跟的 \ifdim\ht...>0pt 恒为假;
|
||
% 而且 \unvbox 后 TeX 把 \prevdepth 设成 -1000pt 抑制行间胶水,
|
||
% 各行会叠在一起(实测间距只有一个行高)。
|
||
%
|
||
% 【不能用 \makebox[<变宽>]】
|
||
% 在 \@for 循环里 \makebox[\dimexpr\textwidth/\perrow\relax] 或
|
||
% \makebox[\cw] 会**静默吞掉内容**(PDF 里整行消失,且不报错,
|
||
% 只在 log 里留 Undefined control sequence / Missing number)。
|
||
% → 用 \hbox to <定宽> 代替,定宽预先算进 \dimen 寄存器。
|
||
%
|
||
% 可靠做法(本实现):
|
||
% 逐项往 \ouckey@cellbox 塞一个 "\hbox to \ouckey@cellw" 单元格;
|
||
% 攒满 \\ouckey@perrow 个就整理成一行,追加进 \ouckey@allbox(\vbox);
|
||
% 行与行之间自己补胶水 = \baselineskip - \ht(行) - \dp(行);
|
||
% 最后 \unvbox 一次性铺出。全程不碰 \par、不碰 \makebox。
|
||
%======================================================================
|
||
\newbox\ouckey@cellbox
|
||
\newbox\ouckey@linebox
|
||
\newbox\ouckey@allbox
|
||
\newcount\ouckey@objn
|
||
\newcount\ouckey@objrow
|
||
\newcount\ouckey@perrow
|
||
\newcount\ouckey@rowsdone
|
||
\newdimen\ouckey@cellw
|
||
\newdimen\ouckey@textw
|
||
\newdimen\ouckey@gap
|
||
|
||
\newcommand{\ouckey@flushline}{%
|
||
\setbox\ouckey@linebox\hbox to \ouckey@textw{\unhbox\ouckey@linebox\hss}%
|
||
\ouckey@gap=\dimexpr\baselineskip-\ht\ouckey@linebox
|
||
-\dp\ouckey@linebox\relax
|
||
\ifnum\ouckey@rowsdone>0
|
||
\setbox\ouckey@allbox\vbox{\unvbox\ouckey@allbox
|
||
\vskip\ouckey@gap \box\ouckey@linebox}%
|
||
\else
|
||
\setbox\ouckey@allbox\vbox{\unvbox\ouckey@allbox
|
||
\box\ouckey@linebox}%
|
||
\fi
|
||
\advance\ouckey@rowsdone by 1
|
||
\setbox\ouckey@linebox\hbox{}}
|
||
|
||
\newcommand{\ouckey@addcell}[1]{%
|
||
\advance\ouckey@objn by 1
|
||
\setbox\ouckey@cellbox\hbox to \ouckey@cellw{%
|
||
\TNR{\the\ouckey@objn}.#1\hss}%
|
||
\setbox\ouckey@linebox\hbox{\unhbox\ouckey@linebox
|
||
\box\ouckey@cellbox}%
|
||
\ouckey@objrow=\ouckey@objn
|
||
\divide\ouckey@objrow by \ouckey@perrow
|
||
\multiply\ouckey@objrow by \ouckey@perrow
|
||
\ifnum\ouckey@objn=\ouckey@objrow \ouckey@flushline\fi}
|
||
|
||
\newcommand{\objkeys}[2][6]{%
|
||
\par\addvspace{4bp}%
|
||
\begingroup
|
||
\ouckey@textw=\textwidth
|
||
\ouckey@perrow=#1
|
||
\ouckey@cellw=\dimexpr\ouckey@textw/\ouckey@perrow\relax
|
||
\ouckey@objn=0
|
||
\ouckey@rowsdone=0
|
||
\setbox\ouckey@linebox\hbox{}%
|
||
\@for\ouckey@item:=#2\do{\expandafter\ouckey@addcell\expandafter{\ouckey@item}}%
|
||
\ouckey@flushline
|
||
\noindent\unvbox\ouckey@allbox
|
||
\par
|
||
\endgroup
|
||
\addvspace{4bp}}
|
||
|
||
%======================================================================
|
||
% 5. 主观题
|
||
% \keyitem{1}{8}{答案正文……} → 「1.(8 分)答案正文……」
|
||
% \keyitemstar{2}{答案} → 只出题号+答案,不印分值
|
||
%
|
||
% ★ 注意:参数正文里可以正常用 \\ 换行与 \pt{..};
|
||
% 但**不要在里面嵌 tabular/tikzpicture 环境**——那些环境的 & 与 \\
|
||
% 会被当成参数分隔符,导致 "File ended while scanning"。
|
||
% 表格/图请写在 \keyitem 之后(先写完文字,再另起环境)。
|
||
%======================================================================
|
||
\newcommand{\keyitem}[3]{%
|
||
\par\addvspace{5bp}\noindent
|
||
{\bfseries \TNR{#1}.}\pt{#2}\hspace*{3bp}#3\relax\par}
|
||
\newcommand{\keyitemstar}[2]{%
|
||
\par\addvspace{5bp}\noindent
|
||
{\bfseries \TNR{#1}.}\hspace*{3bp}#2\relax\par}
|
||
|
||
%======================================================================
|
||
% 6. 【评分标准】块
|
||
% 左侧一条 2bp 竖线 + 灰底标题,视觉上和"答案正文"分开,但不抢眼
|
||
% \scoring{内容}
|
||
% \scoring*{内容} → 不带左侧竖线(行内短说明用)
|
||
%======================================================================
|
||
\newcommand{\scoring}{%
|
||
\@ifstar{\ouckey@scoring@plain}{\ouckey@scoring@rule}}
|
||
\newcommand{\ouckey@scoring@rule}[1]{%
|
||
\par\addvspace{4bp}%
|
||
\noindent
|
||
\begin{minipage}{\linewidth}%
|
||
\hspace*{2bp}%
|
||
\vrule width 2bp \hspace*{6bp}%
|
||
\begin{minipage}{\dimexpr\linewidth-10bp\relax}%
|
||
{\bfseries【评分标准】}#1%
|
||
\end{minipage}%
|
||
\end{minipage}%
|
||
\par\addvspace{4bp}}
|
||
\newcommand{\ouckey@scoring@plain}[1]{%
|
||
\par\addvspace{2bp}\noindent{\bfseries【评分标准】}#1\par\addvspace{2bp}}
|
||
|
||
% 备注 / 说明(比评分标准低一级,用于"其他解法同等给分"这类话)
|
||
\newcommand{\keynote}[1]{%
|
||
\par\addvspace{3bp}\noindent
|
||
{\small\bfseries 说明:}#1\par\addvspace{3bp}}
|
||
|
||
%======================================================================
|
||
% 7. 分值汇总表
|
||
% \keytotal{20,20,15,10,15,20}
|
||
% → 自动画"题号 / 一 二 三 … / 合计"三行表,合计自动求和
|
||
% 用于阅卷前核对"各大题分值相加是否等于满分"。
|
||
%======================================================================
|
||
\newcount\ouckey@sum
|
||
\newcount\ouckey@cnt
|
||
%----------------------------------------------------------------------
|
||
% ★ 这里踩过三个坑,都记下:
|
||
%
|
||
% 坑1 \edef\head{\head&...} 自引用 → 无限展开,parameter stack 爆掉。
|
||
% 坑2 \g@addto@macro / \toks 存 token 序列 → 存进去的是 \ouckey@cur
|
||
% 这个"宏名",等表格排版时循环早走完,16 格全变成最后一个值。
|
||
% 坑3 本想用 \xdef 拼,但在里面包 \TNR{...}(内含 \normalfont,
|
||
% 不可展开)→ 又撞栈。
|
||
%
|
||
% 正解:\xdef 拼装,且**只拼纯字符**,字体命令留到表格里再加。
|
||
% 数字用 Times 是题号行的既有风格,直接靠正文西文字体即可。
|
||
%----------------------------------------------------------------------
|
||
\newcommand{\keytotal}[1]{%
|
||
\par\addvspace{12bp}%
|
||
\ouckey@sum=0 \ouckey@cnt=0
|
||
\def\ouckey@head{}\def\ouckey@body{}%
|
||
\@for\ouckey@v:=#1\do{%
|
||
\edef\ouckey@cur{\ouckey@v}%
|
||
\advance\ouckey@cnt by 1
|
||
\advance\ouckey@sum by \ouckey@cur\relax
|
||
\edef\ouckey@cn{\ouckey@cnnum{\the\ouckey@cnt}}%
|
||
\xdef\ouckey@head{\ouckey@head&\ouckey@cn}%
|
||
\xdef\ouckey@body{\ouckey@body&\ouckey@cur}}%
|
||
\noindent
|
||
\begin{tabular}{|c|*{\the\ouckey@cnt}{c|}|c|}
|
||
\hline
|
||
\textbf{题号}\ouckey@head&\textbf{合计}\\
|
||
\hline
|
||
\textbf{满分}\ouckey@body&\the\ouckey@sum\\
|
||
\hline
|
||
\end{tabular}%
|
||
\par\addvspace{12bp}}
|
||
|
||
|
||
%======================================================================
|
||
% 8. 结尾签署栏
|
||
% ★ 只保留命题人一行。审核人、系(教研室)主任、日期都不印——
|
||
% 这三个位置是纸质归档时由相应人员手签,答案稿本身不预印。
|
||
% 换标签就直接改下面这行文字;想整栏不要,删掉 tex 里的 \keysign 即可。
|
||
%======================================================================
|
||
\newcommand{\keysign}{%
|
||
\par\addvspace{18bp}%
|
||
\noindent
|
||
\makebox[0.5\linewidth][l]{命题人(签字):\hrulefill}\par}
|
||
|
||
%======================================================================
|
||
% 9. 页脚:第 X 页 共 Y 页(居中)
|
||
%======================================================================
|
||
\def\ps@ouckeypage{%
|
||
\def\@oddfoot{\hfill{\small 第\,\thepage\,页\quad 共\,\pageref{LastPage}\,页}\hfill}%
|
||
\let\@evenfoot\@oddfoot
|
||
\def\@oddhead{}\let\@evenhead\@oddhead}
|
||
\pagestyle{ouckeypage}
|
||
|
||
% 包结尾必须恢复,否则会污染文档类里 @ 的处理
|
||
\makeatother
|
||
|
||
\endinput |