Files
ouc_latex_template/成绩构成说明/oucgrade.sty
T

352 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. 页面与版心
%
% 原 docx 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=30.28mm]},
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 的模板里都成立。
hline{1,Z}={0.5pt}, hline{2}={0.5pt},
vline{1,Z}={0.5pt},
row{1}={ht=8.5mm},
row{2-Z}={ht=7.5mm},
}%
}{%
\end{tblr}\par\endgroup}
% 表头格
\newcommand{\grdth}[1]{{\grdsmall\songti\bfseries #1}}
% 表身格
\newcommand{\grdtd}[1]{{\grdsmall\songti #1}}
%======================================================================
% 8. 落款与签字栏
%
% 原件**没有**签字栏(只有一段说明文字就结束了)。用户明确要求补上
% 「任课老师签字」和年月日 —— 这是成绩构成说明作为归档材料的必要项:
% 没有签字的说明文件在教务归档时不被认作有效文件。
%
% 版式(右侧靠齐,与公文落款习惯一致):
% 任课教师(签字):____________
% 20×× 年  月  日
%
% ⚠️⚠️ 踩过的两个坑(都表现成「内容不多却翻到下一页」):
%
% 坑① 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}{%
% ⚠️⚠️ 这一块踩了三次「内容不多却翻页」,最终解法是**不用 \vfill**:
%
% 尝试① \par\vspace{4mm} + minipage
% → 翻页。minipage 是不可分对象,紧跟 \par 后成为合法断页点。
% 尝试② \vfill 顶到底部
% → 不翻页了,但会一直顶到版心底线,年月日落到 y=774.9,
% 而版心底只有 771.6 —— 出界 3.3pt,打印被裁。
% 尝试③ \vfill + 块内 \vspace*{5mm} 留白
% → 又翻页了。\vfill 与块高加起来超过剩余空间时,
% TeX 不接受压缩 \vfill,只能整块搬迁。
%
% 最终解法:**固定间距 + 精确控制块高**,不依赖任何竖直胶。
% \vspace 用一个实测够用的定值,块高用 minipage 内容决定。
% 正文字数变了就调这个 3mm —— 文档只有一页,回归成本极低。
% 实测 3mm 时内容底 769.8,距版心底 771.6 余 1.8pt,刚好不出界。
\par\vspace{3mm}
\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}20××~年\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