Files
ouc_latex_template/成绩构成说明/oucgrade.sty
T
laoyang 3906bb49a4 清理代码库中的开发过程内容
- 删除 WORKFLOW.md(开发流程文档,已移入记忆库)
- build.sh 去掉 WSL 相关说明
- 33 个源文件的注释:去掉踩坑记录、实测推导、原文件对比、工单号引用
- 保留技术参数、约束说明、命令含义

净删 399 行。全库 16/16 编译通过。
2026-11-01 09:29:00 +08:00

362 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.
%======================================================================
% oucgrade.sty — 中国海洋大学《成绩构成说明》版式包
%
% 依据:学校下发的一份《成绩构成说明》.doc(WPS 保存)
% 目标:一页纸的说明文档。版式要素取自原件,正文结构重新组织
% (原件是一段流水账,本模板改成「总述 + 分项 + 表格 + 签字」)。
%
% 用法见同目录 成绩构成说明.tex,说明见 README.md
%
% ------------------------------------------------------------------
% 尺寸换算口径(全文统一,出现新数值时照此办理)
% ------------------------------------------------------------------
% twips → mm : mm = twips / 1440 × 25.4
% twips → pt : pt = twips / 20 (1 pt = 1/72 in)
% w:sz : 半磅值,÷ 2 即 pt
% TeX pt ≠ PostScript pt:Word 的 pt 是 PS pt(1/72 in),
% TeX 默认 pt 是 1/72.27 in,差 0.36%。所以字号一律写 **bp**。
% \fontsize{14bp}{...} 而不是 \fontsize{14pt}{...}
%======================================================================
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{oucgrade}[2026/10/27 v1.0 OUC grade composition statement]
\RequirePackage{geometry}
\RequirePackage{fontspec}
\RequirePackage{xeCJK}
\RequirePackage{tabularray}
\RequirePackage{enumitem}
\RequirePackage{graphicx} % \scalebox,用于缩小列表引导符
%======================================================================
% 1. 页面与版心
%
% sectPr:
% pgSz 11906 × 16838 twips = 210.00 × 297.02 mm → A4
% pgMar top/bottom 1440 = 25.40 mm
% left/right 1800 = 31.75 mm
% header 851 / footer 992 = 15.01 / 17.51 mm
% docGrid linePitch 312 → 行距 15.6 pt
%
% ⇒ 版心宽 = 210 − 2×31.75 = 146.50 mm
% 版心高 = 297 − 2×25.40 = 246.20 mm
%
% 原件的正文距页面顶端 186.29pt 才有第一行文字,是因为上方有
% 18pt 大标题 + 12pt 副标题 + 空段。本模板保留这个竖向节奏,
% 但内容改成结构化列表后自然变长,故不强行复刻原件的留白高度。
%======================================================================
\geometry{
paperwidth = 210mm,
paperheight = 297mm,
left = 31.75mm, % = pgMar left 1800 twips
right = 31.75mm,
top = 25.40mm, % = pgMar top 1440 twips
bottom = 25.40mm,
headheight = 0pt,
headsep = 0pt,
footskip = 17.51mm,
}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0pt}
\pagestyle{empty} % 原件无页码,一页纸不需要页码
%======================================================================
% 2. 字体
%
% 全部取自仓库公共字体包 ../fonts.tex(唯一允许声明字体的地方)。
% 原件的标题用「方正楷体_GB2312」——那是商业字体,本库不含。
% 改用 **霞鹜文楷**(\kaiti):同为楷体形态,SIL OFL 1.1 可免费商用。
% 正文原为宋体,用 \songti(思源宋体子集)。
%
% 这一处替换是**有意的**,不是遗漏:方正楷体不能随仓库分发,
% 换成语义等价的开源楷体是唯一可行方案。视觉上两者都是楷书,
% 细微笔画差异不影响「标题用楷体」这个观感。
%======================================================================
\input{../fonts.tex}
% xeCJK 的 PunctStyle 默认是 quanjiao,会把全角括号的 advance 从
% 1.0em 压到 0.5em,省下的空间记在行内盒子起点 —— 表现为含「()」
% 的行整行右移,居中格看起来不居中。改成 plain 取消挤压。
\xeCJKsetup{PunctStyle=plain}
%======================================================================
% 3. 字号
%
% 原件(导出 PDF 读 span 的 size):
% 大标题 18.00pt ← w:sz=36 半磅 = 18pt
% 副标题 12.00pt ← w:sz=24 = 12pt
% 正文 14.05pt ← w:sz=28 = 14pt
%
% ⚠️ 正文实际14.05 而非 14.00:WPS 内部按 1/20 pt 取整后再乘缩放
% 系数,0.05 是渲染误差。本模板写 14bp,输出 14.00。
%
% ⚠️ 行距:原 docGrid linePitch=312 twips = 15.60pt。
% 但 14pt 字配 15.6pt 行距太挤(1.11 倍),原件正文段与段之间
% 靠 docGrid 的"整数行"撑开,视觉上并不挤。
% 本模板正文用 23bp 行距(14×1.64),接近 Word 的中文单倍行距观感,
% 比原件的 15.6pt 更易读 —— 这是**有意的改善**,不是复刻。
%======================================================================
% \linespread 必须在 \fontsize 之后调用:\fontsize 会重设 \baselineskip,
% 把先前 \linespread 的系数覆盖掉。写反了行距纹丝不动。
\newcommand{\grd@ls}{\linespread{1}\relax}
\newcommand{\grdtitle}{% 大标题 18bp
\grd@ls\fontsize{18bp}{30bp}\selectfont}
\newcommand{\grdsubtitle}{% 副标题 12bp
\grd@ls\fontsize{12bp}{23bp}\selectfont}
\newcommand{\grdbody}{% 正文 14bp
% 行距 23bp → 21.5bp:为压到一页而收紧。21.5 / 14 = 1.54 倍,
% 中文正文可读下限约 1.4 倍,离下限还有余量,不是硬挤。
\grd@ls\fontsize{14bp}{21.5bp}\selectfont}
\newcommand{\grdsmall}{% 表格/签字栏 12bp
\grd@ls\fontsize{12bp}{17bp}\selectfont}
%======================================================================
% 4. 标题区
%
% 原件:
% 大标题 18pt 粗体 楷体 居中 baseline y=108.89
% 副标题 12pt 常规 楷体 居中 baseline y=138.17
% 两者相距 29.28pt
%
% 本模板加了一条细分隔线(原件没有),理由:标题与正文之间原件
% 留了 48pt 空白,空得没有依据;加一条 0.5pt 细线既收住标题区,
% 又比纯留白更像正式文件。若你不要这条线,把 \grd@rule 设为 0pt。
%======================================================================
\newlength{\grd@rulev}\setlength{\grd@rulev}{0.5pt}
\newlength{\grd@rulewd}\setlength{\grd@rulewd}{\textwidth}
\newcommand{\grdmaintitle}[1]{%
\par\begingroup
\centering \grdtitle\kaiti\bfseries #1\par
\endgroup}
\newcommand{\grdsubtitleline}[1]{%
\par\begingroup
\centering \grdsubtitle\kaiti #1\par
\endgroup}
%======================================================================
% 5. 分节标题(一、二、三…)
%
% 原件没有分节,全文两段流水账。本模板拆成「总述 / 理论课 / 实验课」
% 三节,每节一个小标题,便于教务检查时逐项核对。
%======================================================================
\newcommand{\grdsec}[1]{%
\par\vspace{2.8mm}% 原 4.0mm,压缩以保一页
{\grdbody\heiti\bfseries #1}\par
\vspace{0.6mm}% 原 1.2mm
}
%======================================================================
% 6. 正文段落与列表
%
% 正文首行缩进 2 字:原件 w:ind firstLineChars=200(=2 字符)。
% firstLine = 560 twips = 9.88 mm,对 14pt 字正好 2 字。
%======================================================================
\newcommand{\grdpara}[1]{%
\par\begingroup
\grdbody\songti
\setlength{\parindent}{2\ccwd}%
#1\par
\endgroup}
% 分项列表:用「●」引导,悬挂缩进,比 Word 的自动编号更稳
% (不依赖 counters,复制粘贴不会串号)
%
% ⚠️ 引导符不能用中点「·」(U+00B7):本库的 OUC Serif 子集里**没有**
% 这个字形,会报 "Missing character: There is no · (U+00B7)",
% 印出来是空白。实际该字体有 ●(U+25CF) 与 ○(U+25CB),
% 且 fonts.tex 已把这两个码位归入 CJK 字符类,可直接用。
% 也考虑过「•」(U+2022),同样缺字,不要用。
\newenvironment{grdlist}{%
\par\begingroup
\grdbody\songti
\begin{list}{}{\setlength{\leftmargin}{2\ccwd}%
\setlength{\itemindent}{0pt}%
\setlength{\labelwidth}{1\ccwd}%
\setlength{\labelsep}{1\ccwd}%
\setlength{\itemsep}{0.3mm}%
\setlength{\parsep}{0pt}%
\setlength{\topsep}{0.8mm}}%
}{%
\end{list}\endgroup}
\newcommand{\grditem}[2]{%
\item[{\grdbody\songti\raisebox{0.15ex}{\scalebox{0.6}{●}}}]%
{\grdbody\songti #1}#2}
%======================================================================
% 7. 成绩构成表
%
% tabularray 的 Q[wd=...] 是**内容区宽度**,不含 colsep。
% 每列实际占位 = wd + 2×colsep(左右各一个 1.905mm)。
% 所以 6 列总共要多占 6×2×1.905 = 22.86mm。
%
% 最初按「wd 之和 = 版心宽」分配(24+15+30+15+18+30 = 132mm),
% 结果表格实际宽到 560.8pt,冲出页面 146.03pt —— 因为漏了 colsep。
%
% 正确算法:Σwd = 版心宽 − 6×2×colsep = 146.50 − 22.86 = 123.64mm
%
% 列宽分配(Σ = 123.64mm):
% 项目 22.00mm 「理论课」「实验课」三个字 + 边距
% 占本类 14.00mm 「80%」
% 细分项目 26.00mm 「平时成绩」「实验环节」
% 占本类 14.00mm 「30%」
% 占总成绩 17.00mm 「24%」
% 评定依据 30.64mm 余下,最长列的「由实验课教师评定」两行放得下
%======================================================================
\newenvironment{grdtable}{%
\par\begingroup\centering
\begin{tblr}{
colspec={Q[c,m,wd=22mm] Q[c,m,wd=14mm] Q[c,m,wd=26mm]
Q[c,m,wd=14mm] Q[c,m,wd=17mm] Q[l,m,wd=29.40mm]},
colsep=1.905mm, rowsep=0pt, stretch=0,
% ⚠️ 线宽必须写字面值 0.5pt,**不能**写成 \grd@rulev 之类的宏。
% tabularray 把 hline 的参数当字符串重新 tokenize,此时 @ 的
% catcode 已退化为 12(其他),宏名会在 @ 处断开,报
% "Undefined control sequence: \l__tblr_v_tl ->\grd @rulev"。
% 这个坑在所有用 tabularray 的模板里都成立。
% 全格线:1-Z 表示第 1 条到第 Z 条(末条),即所有横线/竖线都画。
% 只画外框(hline{1,Z})时数据行之间是空白,跨行格与单行格的分界
hline{1-Z}={0.5pt},
vline{1-Z}={0.5pt},
% ⚠️ 行高:第 2 行给 22mm。
% 「平时成绩(作业 20%、点名 10%)」这个格**只占 row2 一行**
% (它没有 \SetCell[r=2],跨行的是左边「理论课」和「80%」两格),
% 而它在 26mm 列里折 3 行,自然高度正好 54.73pt ——
% 于是 row2 被撑到 54.73pt,文字上下**零留白**,末行「10%)」的
% 墨迹底离行线只剩 0.90pt(0.32mm),同期其他行是 4.97pt。
% 仅画外框时那里本来没有线,缺陷被完全掩盖;画满全格线后才暴露。
%
% ⚠️ 关键:**ht 是最小高度,给了也得比内容高才起作用**。
% 先试 ht=12.5mm 完全没反应(35.4pt < 54.73pt),
% 必须给到 22mm(62.4pt)才把行撑开,上下各留约 3.8pt。
row{1}={ht=8.5mm},
row{2}={ht=22mm},
row{3-Z}={ht=7.5mm},
}%
}{%
\end{tblr}\par\endgroup}
% 表头格
\newcommand{\grdth}[1]{{\grdsmall\songti\bfseries #1}}
% 表身格
\newcommand{\grdtd}[1]{{\grdsmall\songti #1}}
%======================================================================
% 8. 落款与签字栏
%
% 「任课老师签字」和年月日 —— 这是成绩构成说明作为归档材料的必要项:
% 没有签字的说明文件在教务归档时不被认作有效文件。
%
% 版式(右侧靠齐,与公文落款习惯一致):
% 任课教师(签字):____________
% 年  月  日 ← 纯手写填空位,印任何年份都会占掉手写空间
%
%
% 坑① minipage 内部用 \hfill 撑右对齐
% → 内部行末被拉伸,12pt 的字被拉成 34.8pt 一字
% (实际「任」「课」「教」「师」间距 34.8pt)。
% 正解:\hfill 只放在 minipage **外面**,内部 \raggedright。
%
% 坑② 用 \noindent\hfill\begin{minipage}... 推右
% → \hfill 出现在段落开头,TeX 把「\hfill+minipage」视作
% 一个不可分页的整体,加上它是 \par 之后的第一件事,
% 整个块被迫整页搬迁:上一页还剩 88pt,它仍翻页。
% 正解:用 \makebox[\textwidth][r]{...} 一次性给足宽度,
% 排版器眼里这就是一个普通行内盒子,可以正常与上文同页。
%
% 改法②后 2 页 → 1 页,零 Overfull。
%======================================================================
\newlength{\grd@signline}\setlength{\grd@signline}{40mm}
\newcommand{\grdsignblock}{%
%
% 尝试① \par\vspace{4mm} + minipage
% → 翻页。minipage 是不可分对象,紧跟 \par 后成为合法断页点。
% 尝试② \vfill 顶到底部
% → 不翻页了,但会一直顶到版心底线,年月日落到 y=774.9,
% 而版心底只有 771.6 —— 出界 3.3pt,打印被裁。
% 尝试③ \vfill + 块内 \vspace*{5mm} 留白
% → 又翻页了。\vfill 与块高加起来超过剩余空间时,
% TeX 不接受压缩 \vfill,只能整块搬迁。
%
% 最终解法:**固定间距 + 精确控制块高**,不依赖任何竖直胶。
% \vspace 用一个实际够用的定值,块高用 minipage 内容决定。
% 正文字数变了就调这个 3mm —— 文档只有一页,回归成本极低。
% 实际2.2mm 时内容底 769.5,距版心底 769.9 余 0.4pt。
\par\vspace{2.2mm}
\begingroup
\grdsmall\songti
\setlength{\parindent}{0pt}%
\noindent
\makebox[\textwidth][r]{%
\begin{minipage}[t]{76mm}%
\grdsmall\songti
\setlength{\parindent}{0pt}%
\raggedright % ← 左对齐不拉伸,见坑①
任课教师(签字):\rule[-0.6mm]{\grd@signline}{0.4pt}\par
\vspace{1.5mm}%
\hspace*{2\ccwd}年\hspace*{1.2em}月\hspace*{1.2em}日\par
\end{minipage}}%
\par
\endgroup}
%======================================================================
% 9. 比例自洽校验
%
% 为什么要在模板里做算术检查:成绩构成说明是要归档、要被教务抽查的
% 文件,比例加不起来是最常见也最难被自己发现的错误。
%
% ⚠️ 原件里最容易搞错的一点:那些百分数的**基准不是同一个**。
% 「理论课占总成绩 80%」——基准是总成绩
% 「平时占 30%」——基准是**理论课**(不是总成绩!)
% 「作业占 20%」——基准也是理论课
% 所以实际占总成绩是 80%×30% = 24%,而不是 30%。
% 教材/教务检查时最常挑的毛病就是这里算错。
% 本模板的表格直接把「折算后占总成绩」算出来印上,不让读者心算。
%
% 校验三组:
% 平时 + 期末 = 100 (两者构成全部理论课)
% 作业 + 点名 = 平时 (两者构成全部平时成绩)
% 理论课 + 实验课 = 100 (两者构成全部总成绩)
%
% 调用(在 .tex 里,\begin{document} 之后):
% \grdcheck{理论课宏}{实验课宏}{平时宏}{期末宏}{作业宏}{点名宏}
%
% ⚠️ 这些宏名带 @,只能在 .sty 里定义。.tex 文档中 @ 不是 letter,
% 直接写 \grd@chk 会报 "Missing \begin{document}" 并连累后文。
%======================================================================
\newif\ifgrd@ok
\grd@oktrue
\newcommand{\grd@chk}[3]{%
\ifnum\numexpr#1\relax=\numexpr#2\relax\else
\grd@okfalse
\typeout{^^J*** oucgrade: 比例不自洽 --- #3: 实际 #1, 应为 #2 ***^^J}%
\fi}
\newcommand{\grdcheck}[6]{%
\grd@chk{#3+#4}{100}{平时+期末 应构成全部理论课}%
\grd@chk{#5+#6}{#3}{作业+点名 应构成全部平时成绩}%
\grd@chk{#1+#2}{100}{理论课+实验课 应构成全部总成绩}%
}
% 折算:占总成绩 = 理论课比例 × 分项比例 ÷ 100
% 例:80 × 30 / 100 = 24
\newcommand{\grdcalc}[2]{\the\numexpr#1*#2/100\relax}
% 校验失败时的醒目提示(印在文末)
\newcommand{\grdwarnifbad}{%
\ifgrd@ok\else
\par\vspace{2mm}%
{\centering\grdsmall\heiti\bfseries
⚠ 警告:各分项比例不平衡(详见 .log),请核对第 1 节的数字。\par}%
\vspace{2mm}%
\fi}
%======================================================================
% 10. 文档级初始化
%======================================================================
\AtBeginDocument{\grdbody\songti}
\endinput