- 删除 WORKFLOW.md(开发流程文档,已移入记忆库) - build.sh 去掉 WSL 相关说明 - 33 个源文件的注释:去掉踩坑记录、实测推导、原文件对比、工单号引用 - 保留技术参数、约束说明、命令含义 净删 399 行。全库 16/16 编译通过。
362 lines
16 KiB
TeX
362 lines
16 KiB
TeX
%======================================================================
|
||
% 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 |