Files
ouc_latex_template/试卷/ouckey.sty
T
laoyang 8aa979739a 字体统一:建公共字体目录,全部改用开源可商用字体 (#6)
七个模板原先分两套做法:幻灯片/fonts(49MB 鸿蒙 18 个字重)与
科技报告/font(92MB,含 5 款方正商业字体),其余模板直接调系统
SimSun/SimHei/KaiTi/FangSong/Times New Roman。换机器就编不出来,
方正系列也不允许随仓库分发。

统一到仓库根的 fonts/ + fonts.tex:
- OUC Serif(思源宋体 SC 子集)   —— 宋体
- OUC Sans(思源黑体 SC 子集)    —— 黑体
- OUC Kai(霞鹜文楷子集)          —— 楷体
- Zhuque Fangsong(朱雀仿宋)      —— 仿宋
- TeX Gyre Termes                  —— 西文 Times 等价(度量逐字相同)
- HarmonyOS Sans                   —— 幻灯片/科技报告原有字体

fonts.tex 是唯一允许声明字体的地方,7 个模板全部 \input 它。
字体体积 141MB -> 49MB(去掉重复),全库共用一套最小集合。

几处踩过的坑(均已写进源码注释):
- \newCJKfontfamily 与 ctex 默认 fontset 冲突会中断编译,改用
  \setCJKfamilyfont + \def
- \newfontfamily 缺粗体档会让 \bfseries 静默失效,四档必须全给
- UprightFont= 与主参数不能同时给,否则文件名被拼两次
- Path= 相对编译时工作目录,不是相对 .sty
- ×/□/○ 等符号按 Unicode 区段被判成西文,需 \xeCJKDeclareCharClass
- 中文斜体的 ItalicFont 与主字体不在同一目录,须在 ItalicFeatures
  里单独写 Path/Extension(否则报 OUCKai-Regular cannot be found)

顺带:
- 科技报告 report.tex 的字体演示正文改为新字体名(原文写着方正系列)
- 试卷归档封面新模板并入本库
- 科技报告 shell 命令框底色改海大蓝(#5 遗留)

验证(7 个模板 8 个 PDF 全量重编 + 逐行 A/B 对照):
- 错误 0,缺字 0
- 幻灯片 362 行逐行完全一致,零漂移
- 年度考核登记表 / 劳务发放说明表 / 试卷题型示例 / 参考答案 文本完全一致
- 课程执行大纲 仅 2 处断字连字符差异(throughout、processing),
  1 条 6.39pt Overfull 已论证不可见(距纸张右边缘仍有 20.7mm)
- 科技报告 内嵌字体方正归零,只剩 OUC Serif/Sans/Kai + Zhuque
- 全库正文 857 个非 ASCII 字符,四款中文字体(含粗体)覆盖 100%
2026-10-27 00:43:14 +08:00

386 lines
16 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 保持一致,全部取自公共字体包 ../fonts.tex)
%----------------------------------------------------------------------
\input{../fonts.tex}
% \sunfont 原指 SimSun,现统一指 Termes(见 oucexam.sty 的说明)
\let\sunfont\tnr
% 局部字体切换必须显式收尾,否则 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