%====================================================================== % 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{} %---------------------------------------------------------------------- % key 赋值:空值规范成空串(#22) % % ⚠️ 所有 key 一律走这里,不要退回 `\def\ouckey@xxx{#1}`。 % 原因见文件下方「判空」一节的完整说明:那样 dept={} 会存成 % 「一个空组」,后续 \detokenize 展开后仍非空,判空失败, % 抬头会印出孤立的「开课学院:」而没有值。 % % ⚠️ 必须定义在 \define@key 之**前**,否则展开时 undefined。 %---------------------------------------------------------------------- \newcommand{\ouckey@setkey}[2]{% \if\relax\detokenize{#2}\relax \def#1{}% \else \def#1{#2}% \fi} % 所有 key 一律走 \ouckey@setkey \define@key{ouckey}{year}{\ouckey@setkey{\ouckey@year}{#1}} \define@key{ouckey}{term}{\ouckey@setkey{\ouckey@term}{#1}} \define@key{ouckey}{course}{\ouckey@setkey{\ouckey@course}{#1}} \define@key{ouckey}{type}{\ouckey@setkey{\ouckey@type}{#1}} \define@key{ouckey}{college}{\ouckey@setkey{\ouckey@college}{#1}} \define@key{ouckey}{grade}{\ouckey@setkey{\ouckey@grade}{#1}} \define@key{ouckey}{major}{\ouckey@setkey{\ouckey@major}{#1}} \define@key{ouckey}{dept}{\ouckey@setkey{\ouckey@dept}{#1}} \define@key{ouckey}{teacher}{\ouckey@setkey{\ouckey@teacher}{#1}} \define@key{ouckey}{examiner}{\ouckey@setkey{\ouckey@examiner}{#1}} \define@key{ouckey}{mode}{\ouckey@setkey{\ouckey@mode}{#1}} \define@key{ouckey}{date}{\ouckey@setkey{\ouckey@date}{#1}} \define@key{ouckey}{total}{\ouckey@setkey{\ouckey@total}{#1}} \newcommand{\keysetup}[1]{\setkeys{ouckey}{#1}} %---------------------------------------------------------------------- % 判空(#22 修正:原实现有真 bug) % % ⚠️⚠️ 旧实现的坑 % 旧代码是 % \newcommand{\ouckey@ifempty}[1]{% % \if\relax\detokenize{#1}\relax ... } % 调用处写 `\ouckey@ifempty{\ouckey@dept}{空}{非空}`。 % **\detokenize 不展开参数** —— 它看到的是字符串 `\ouckey@dept` % (宏名本身),永远非空 ⇒ 判空永远失败。 % 后果:`dept = {}` 时抬头仍印出孤立的「开课学院:」,后面没有值。 % 这是旧版就有的 bug(三格布局下不明显),#22 改成一行一项后暴露。 % % 为什么不在宏里修 % 试过「先 \edef 展开再 \detokenize」的版本,会报 % ! Incomplete \iffalse; all text was ignored after line 49. % 因为 \if...\else...\fi 被 \expandafter 链打散,TeX 匹配 \fi 时错位。 % 这类宏层展开的修补很脆,不值得。 % % ✅ 正确做法:**在 \define@key 里就把空值规范化**(见文件上方 % \ouckey@setkey)。空值存成真正的空串,判空就能工作。 % % ⚠️ 这里只留判空宏本身。它拿到的是**已经规范化过的值** % (`\ouckey@dept` 展开后就是纯文本),所以 \detokenize 有效。 %---------------------------------------------------------------------- \newcommand{\ouckey@ifempty}[1]{% \if\relax\detokenize{#1}\relax \expandafter\@firstoftwo \else \expandafter\@secondoftwo \fi} %---------------------------------------------------------------------- % 一项信息:标签加粗 + 值,**独占一行**(#22) % % ⚠️⚠️ 为什么从"一行三格"改成"一行一项" % % 旧实现是三个 \makebox[0.3333\linewidth][l] 并排: % \makebox[固定宽][l]{内容} % \makebox 是**不可断的盒子**,行为是: % · 内容比盒宽短 → 左对齐,右侧留白(正常) % · 内容比盒宽长 → **直接溢出盒外,压到右边一格上**(就是 bug) % · 盒内中文**不能换行**(LR 模式,不做段落断行) % 所以三格布局对短字段友好,对长字段必然重叠。 % % 实测:把 dept 填成 % 计算机科学与技术学院(人工智能与大数据技术方向实验班) % 后,pymupdf 读出 % 开课学院:(70.87~123.37) 值:(123.37~406.87) % 而第二格「课程名称」本该从 216.0 开始 ⇒ **重叠 190.87bp**, % 把「课程名称 / 试卷类型」整个压住。 % % ⚠️ **编译完全不报错,也没有 Overfull 警告**(rc=0 over=0)。 % \makebox 内容溢出盒外时 TeX 不认为是"坏行"。 % ⇒ 这类 bug 只能靠看渲染结果或量 span 坐标发现,看日志发现不了。 % % 现在的做法:每项 `\par\noindent\textbf{标签:}值\par`,普通段落。 % · 超长自动换行(中文可在任意汉字间断行) % · 不存在并排,也就不存在互相压住 % · 抬头是"查阅"性质,一项一行比三格更容易扫读 % 代价:抬头从 3 行变 9 行,多占约 6 行。答案卷 A4、内容不满页, % 多 6 行不影响版面。 % % ⚠️ 留空的项仍**整项不印**(也不留空行),逻辑不变。 %---------------------------------------------------------------------- \newcommand{\ouckey@inforow}[2]{% \ouckey@ifempty{#2}{}{% % 行距收紧到 15bp(\Fivhao 的基线距是 16bp)。抬头是"查阅"信息, % 不需要正文那种行距;9 行 × 省 5.8bp ≈ 省 52bp,将近 3 行正文。 {\fontsize{10.5bp}{15bp}\selectfont \par\noindent\textbf{#1:}#2\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} % 每项一行(#22)。顺序按"读者关心程度"排: % 先课程、后学院、再人与考试形式,最后分值。 % 留空的自动跳过,所以实际行数随填写项变化。 \ouckey@inforow{课程名称}{\ouckey@course} \ouckey@inforow{开课学院}{\ouckey@dept} \ouckey@inforow{学生学院}{\ouckey@college} \ouckey@inforow{年级专业}{\ouckey@major} \ouckey@inforow{试卷类型}{\ouckey@ifempty{\ouckey@type}{}{\ouckey@type~卷}} \ouckey@inforow{考试方式}{\ouckey@mode} \ouckey@inforow{任课教师}{\ouckey@teacher} \ouckey@inforow{命题人}{\ouckey@examiner} \ouckey@inforow{满分}{\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