Files
ouc_latex_template/exam/ouckey.sty
T
laoyang 47158f2add add exam: 试卷与参考答案及评分标准模板
- exam/oucexam.sty    试卷版式包(195x270mm 专用试卷纸)
- exam/ouckey.sty     答案与评分标准版式包(默认 A4,可选 exampaper)
- exam/试卷题型示例.tex        16 类题型,6 页
- exam/参考答案与评分标准示例.tex  8 大题 / 满分 100,3 页
- exam/README.md      用法与书写注意事项
- .gitignore          忽略 LaTeX 编译中间文件
2026-10-22 16:27:54 +08:00

392 lines
17 KiB
TeX
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.
%======================================================================
% 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}
%----------------------------------------------------------------------
% 字体(与 oucexam.sty 保持一致)
%----------------------------------------------------------------------
\setmainfont{Times New Roman}
\setCJKmainfont{SimSun}[AutoFakeBold=2.86]
% ctexart 已定义 \songti / \heiti,重名会报 "Control sequence already defined"
% 只在未定义时才建(\provideCJKfontfamily 并不存在,必须自己判)
\ifdefined\songti\else\newCJKfontfamily\songti{SimSun}[AutoFakeBold=2.86]\fi
\ifdefined\heiti\else\newCJKfontfamily\heiti{SimHei}\fi
\newCJKfontfamily\kaiti{KaiTi}
\newfontfamily\tnr{Times New Roman}
\newfontfamily\tnrb{Times New Roman Bold}
\newfontfamily\sunfont{SimSun}
% 局部字体切换必须显式收尾,否则 xeCJK 字体会"溢出"到后一段
\newcommand{\TNR}[1]{{\tnr #1\normalfont}}
\newcommand{\TNRB}[1]{{\tnrb #1\normalfont}}
\newcommand{\SUN}[1]{{\sunfont #1\normalfont}}
\xeCJKsetup{CJKecglue={\hskip 2.70bp\relax},PunctStyle=plain}
\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}{要点} → 要点靠左、分值靠右对齐(长要点更清爽)
%======================================================================
\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