目录页改为自适应方框样式,并在 TeX Live 2024 下编译验证

把每一个一级标题放进独立的方框,方框高度与字号随章节数目自动计算,
方框自动均分铺满整页;文字左对齐,左侧只留少量缩进。

新增 \settocstyle{box|solid|bar} 三种方框样式(未知值告警并回退 box),
新增 \toctheme{red|blue} 两种配色(默认海大红,与各章节标题页同色)。

本分支已用 TeX Live 2024 亲自编译验证:0 错误、0 溢出、23 页,
目录页 4 个跳转链接正常,且与本仓库其他分支渲染一致。
This commit is contained in:
2026-10-12 11:29:12 +08:00
parent e3f2384e99
commit 78e1415231
8 changed files with 369 additions and 1 deletions
+126
View File
@@ -31,6 +31,64 @@
3. 可以安装其他的IDE工具进行Tex文件的编写和修改,推荐使用<a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code</a>。 3. 可以安装其他的IDE工具进行Tex文件的编写和修改,推荐使用<a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code</a>。
4. 编译时,请选择XeLaTex,有时候,可能需要连续编译两次,以得到正确的页码和图表编号。 4. 编译时,请选择XeLaTex,有时候,可能需要连续编译两次,以得到正确的页码和图表编号。
# 分支与编译器版本
本仓库按 **TeX Live 年份**组织分支,规则很简单:**分支名是哪一年,就说明该分支可以用那一年的 TeX Live 直接编译通过**。
| 分支 | 对应 TeX Live | beamer 版本 | 说明 |
|------|---------------|-------------|------|
| `main` | 最新版 | 最新 | 始终指向最新的年份分支(当前等于 `2026`) |
| `2026` | TeX Live 2026 | 3.77 | 无需改动源码即可编译通过 |
| `2025` | TeX Live 2025 | 3.72 | 无需改动源码即可编译通过 |
| `2024` | TeX Live 2024 | 3.71 | 无需改动源码即可编译通过(PDF 体积说明见下) |
| `2023` | TeX Live 2023 | 3.69 | 适配 beamer 3.69 的 frametitle 变更,并修复 allcaps 选项 |
| `2022` | TeX Live 2022 | 3.66 | 保留早期 beamer 的写法(旧版 frametitle 补丁) |
### 两点说明
**一、各分支的 PDF 都是用对应年份的 TeX Live 编译出来的。**
也就是说,`2023` 分支里的 `slides.pdf` 出自 TeX Live 2023,
`2026` 分支里的出自 TeX Live 2026,以此类推。功能上各分支内容一致,
差别只在于适配各自 beamer 版本的源码写法。
**二、`2024` 分支的 PDF 体积明显偏大(约 9.7 MB,其他年份约 2.4 MB)。**
这是 TeX Live 2024 的 `xdvipdfmx` 一度不做跨页图像去重导致的,
从 TeX Live 2025 起恢复正常。**页面渲染内容与其他年份完全一致**,不影响使用。
## 我该用哪个分支
- **想用最新版本**:直接用 `main` 分支,并配合**最新版 TeX Live**。
- **本机已经装了某个特定年份的 TeX Live**:切到**同名的那个分支**即可。
```bash
# 例如本机是 TeX Live 2023,就用 2023 分支
git clone -b 2023 https://code.yangyq.net/laoyang/ouc-slide-latex-template.git
# 已经克隆过的仓库,可以这样切换
git checkout 2023
```
简单说:**编译器是哪一年的,就用哪一年的分支**,不需要自己去改模板代码。
## 为什么要这样分
beamer 的内部实现会随 TeX Live 版本变化,而模板中对内部宏的补丁(`\patchcmd`)会因此失效。最典型的一次是:
- **beamer 3.69(TeX Live 2023)** 起,`\beamer@@frametitle` 改用 `\beamer@insertframetitlecontinuation`,使得面向旧版写法的补丁匹配失败,报 `Patching original frame title failed`。
- 模板现已加入**版本自动判别**(依据 `\beamer@insertframetitlecontinuation` 是否存在,判断逻辑写在文件顶层),因此 `2023` 及之后的各年份分支**不需要再改动源码**。
- `2022` 分支保留原始写法,只保证在 TeX Live 2022(beamer 3.66)下编译通过。
## 维护约定
每当有新的 TeX Live 年份发布时:
1. 在新版本的 TeX Live 下,用 **XeLaTeX 编译当前的 `main`**;
2. 如果编译不通过,进行修复,注意保持向后兼容,不要破坏旧年份分支;
3. 修复通过后,新建一个**以该年份命名**的分支并提交;
4. 把该年份分支**快进合并到 `main`**,让 `main` 始终是最新版本。
> 附注:不同年份的 TeX Live 生成的 PDF 在体积上可能略有差异(例如 TeX Live 2024 的 xdvipdfmx 曾一度不做跨页图像去重,导致成品偏大约 4 倍),但**页面渲染内容是一致的**,不影响使用。
# 元素介绍 # 元素介绍
## 基本信息 ## 基本信息
Tex文件的开头,是基本信息部分,可以根据需要添加`\title`{标题}、`\subtitle`{子标题}、`\author`{作者}、`\date`{\today}、`\institute`{所属机构}。 Tex文件的开头,是基本信息部分,可以根据需要添加`\title`{标题}、`\subtitle`{子标题}、`\author`{作者}、`\date`{\today}、`\institute`{所属机构}。
@@ -50,6 +108,74 @@ Tex文件的开头,是基本信息部分,可以根据需要添加`\title`{
首页和结尾页,使用的是基本信息中的数据,而目录页的内容,将根据`\section`进行填充。 首页和结尾页,使用的是基本信息中的数据,而目录页的内容,将根据`\section`进行填充。
## 目录页样式(自适应方框)
目录页默认使用**自适应方框**样式:每一个一级标题(`\section`)被放进一个独立的方框里,**方框的高度、以及方框内文字的字号,都会根据一级标题的数目自动计算**,并且方框会自动均分、铺满整个目录页。章节少时方框又高又大、字号醒目;章节多时方框自动变矮、字号自动缩小,不需要手工调任何一个数字。
方框内的文字**左对齐**,左侧只留一点点缩进,读起来更像一份正常的目录。
![默认目录页样式](/figs/fig09.png)
### 配色:默认海大红,蓝色可选
目录页默认使用**海大红**(`oucred`)——和各章节标题页的颜色保持一致。
这是因为目录页上「目录」二字本身是**海大蓝**的,如果目录项再用蓝色,整页就分不出层次。用海大红正好和章节标题页呼应。
如果就喜欢蓝色,改一行即可切回海大蓝:
```
\toctheme{red} % 默认:海大红,与各章节标题页同色
\toctheme{blue} % 海大蓝,与目录页标题「目录」同色
```
![蓝色配色](/figs/fig13.png)
### 三种方框样式
在导言区用一行命令即可切换,样式与配色可以自由组合:
```
\settocstyle{box} % 默认:浅色底 + 圆角框 + 圆形序号
\settocstyle{solid} % 实心色底 + 白色文字
\settocstyle{bar} % 白底 + 左侧粗色条
```
![实心样式](/figs/fig10.png)
![左侧色条样式](/figs/fig11.png)
章节较多时(下面这张是 9 个一级标题),方框和字号会自动压缩:
![章节较多时自动压缩](/figs/fig12.png)
### 可调参数
所有参数都可以在导言区用 `\setlength` 覆盖,一般**不需要改**:
| 参数 | 默认值 | 含义 |
|------|--------|------|
| `\tocareamargin` | `52pt` | 目录区上下预留的高度(自动模式下从 `\textheight` 中扣除) |
| `\tocareaheight` | `0pt` | 直接指定目录区高度;`0pt` 表示自动 |
| `\tocboxgap` | `10pt` | 方框之间的理想间距(空间紧张时会自动压缩) |
| `\tocboxgapmin` | `2pt` | 方框之间允许的最小间距 |
| `\tocboxmaxheight` | `110pt` | 单个方框的最大高度 |
| `\tocboxleftpad` | `2.5pt` | 方框内文字距左边框的距离(只留一点点) |
| `\tocfontmax` | `26pt` | 方框内文字的最大字号 |
| `\tocfontwarn` | `8pt` | 字号低于该值时会给出警告 |
例如,希望方框之间更紧凑一些:
```
\setlength{\tocboxgap}{6pt}
```
### 几点说明
1. **目录页需要连续编译两次**:章节数目是从上一次编译生成的 `.toc` 文件里读出来的。第一次编译时 `.toc` 还是空的,目录页会是空白,再编译一次即可正常。
2. 方框的序号和标题是**超链接**,在 PDF 里点击会直接跳到对应章节。
3. 一级标题**太多时(大约 9 个以上)**,方框和字号会变得偏小,编译日志里会有 `oucslide Warning` 提示。这种情况下建议合并章节,或者把目录页改回普通的列表样式。
4. 如果确实想换回旧版那种简洁的列表目录,把 `\makeoutline` 里的 `\ouc@toc@body` 换回 `\tableofcontents[hideallsubsections]` 即可(旧的模板定义仍保留在 `beamerouterthemeOUCSlide.sty` 中)。
## 使用分节 ## 使用分节
本模板使用分节进行整体规划,章节共三级,对应的关键字分别是: 本模板使用分节进行整体规划,章节共三级,对应的关键字分别是:
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
View File
Binary file not shown.
+243 -1
View File
@@ -183,19 +183,261 @@
\setbeamertemplate{frametitle}[frame] \setbeamertemplate{frametitle}[frame]
} }
%% ======================================================================
%% 目录页:自适应方框样式
%% ----------------------------------------------------------------------
%% 把每一个一级标题(\section)放进一个方框里。方框的高度、以及方框内
%% 文字的字号,都会根据一级标题的数目自动计算,并自动均分填满整页。
%%
%% 可调参数(在导言区用 \setlength 覆盖即可):
%% \tocareamargin 目录区上下预留的高度(自动模式下 =
%% \textheight 减去页眉页脚,默认 52pt)
%% \tocareaheight 直接指定目录区高度;设为 0pt(默认)表示自动
%% \tocboxgap 方框之间的理想间距(空间紧张时会自动压缩)
%% \tocboxgapmin 方框之间允许的最小间距
%% \tocboxmaxheight 单个方框的最大高度
%% \tocfontmax 方框内文字的最大字号
%% \tocfontwarn 字号低于该值时会给出警告(提示章节太多)
%%
%% 方框样式(在导言区用 \settocstyle{...} 选择):
%% box 浅色底 + 圆角框 + 圆形序号(默认)
%% solid 实心色底 + 白色文字
%% bar 白底 + 左侧粗色条
%%
%% 配色主题(在导言区用 \toctheme{...} 选择):
%% red 海大红(默认),与各章节标题页的颜色一致
%% blue 海大蓝,与目录页上「目录」二字的颜色一致
%% ======================================================================
\newlength{\tocareamargin}
\setlength{\tocareamargin}{52pt}
\newlength{\tocareaheight}
\setlength{\tocareaheight}{\z@}
\newlength{\tocboxgap}
\setlength{\tocboxgap}{10pt}
\newlength{\tocboxgapmin}
\setlength{\tocboxgapmin}{2pt}
\newlength{\tocboxmaxheight}
\setlength{\tocboxmaxheight}{110pt}
\newlength{\tocboxleftpad}
\setlength{\tocboxleftpad}{2.5pt}
\newlength{\tocfontmax}
\setlength{\tocfontmax}{26pt}
\newlength{\tocfontwarn}
\setlength{\tocfontwarn}{8pt}
\makeatletter
\newcount\ouc@toc@count
\newlength\ouc@toc@avail
\newlength\ouc@toc@gap
\newlength\ouc@toc@height
\newlength\ouc@toc@font
\newlength\ouc@toc@base
\newlength\ouc@toc@pad
\newlength\ouc@toc@padx
\newlength\ouc@toc@badgesize
\newlength\ouc@toc@tmp
\def\ouc@toc@style{box}
\newcommand{\settocstyle}[1]{\def\ouc@toc@style{#1}}
%% 配色主题:red = 海大红(默认,与章节标题页同色)
%% blue = 海大蓝(与目录页标题「目录」同色)
\def\ouc@toc@theme{red}
\newcommand{\toctheme}[1]{%
\def\ouc@toc@theme{#1}%
\ifcsname ouc@toc@setcolors@#1\endcsname
\csname ouc@toc@setcolors@#1\endcsname
\else
\PackageWarning{oucslide}{未知的目录页配色 “#1”,
可选值为 red / blue,这里按 red 处理}%
\ouc@toc@setcolors@red
\fi
}
%% 红色主题:主色 oucred,浅底 oucred10
\def\ouc@toc@setcolors@red{%
\def\ouc@toc@main{oucred}%
\def\ouc@toc@light{oucred10}%
}
%% 蓝色主题:主色 oucblue,浅底 oucblue10
\def\ouc@toc@setcolors@blue{%
\def\ouc@toc@main{oucblue}%
\def\ouc@toc@light{oucblue10}%
}
\ouc@toc@setcolors@red
%% 左对齐时给文字留出的左内边距
\def\ouc@toc@indent{\dimexpr\tocboxleftpad\relax}
\long\def\ouc@toc@gobblevi#1#2#3#4#5#6{}
\long\def\ouc@toc@gobblevii#1#2#3#4#5#6#7{}
%% 方框内文字的字体(字号由 \ouc@toc@compute 算出)
\def\ouc@toc@fontsel{\fontsize{\ouc@toc@font}{\ouc@toc@base}\selectfont\bfseries}
%% 圆形序号徽标(#1 = 圆圈填充色,#2 = 数字)
\def\ouc@toc@badge#1#2{%
\tikz[baseline=(ouc@badge.base)]{%
\node[circle, fill=#1, text=white, inner sep=0pt,
minimum size=\ouc@toc@badgesize, font=\bfseries] (ouc@badge) {\strut#2};%
}%
}
%% 实心样式下用的空心序号(#1 = 描边色,#2 = 数字)
\def\ouc@toc@badgeopen#1#2{%
\tikz[baseline=(ouc@badge.base)]{%
\node[circle, draw=#1, line width=0.8pt, text=#1, inner sep=0pt,
minimum size=\ouc@toc@badgesize, font=\bfseries] (ouc@badge) {\strut#2};%
}%
}
%% ---------------------------------------------------------------- 计数
%% 读入上一次编译生成的 .toc 文件,数出一共有多少个一级标题
\def\ouc@toc@countsections{%
\global\ouc@toc@count\z@
\begingroup
\makeatletter
\def\beamer@sectionintoc##1##2##3##4##5{\global\advance\ouc@toc@count\@ne}%
\let\beamer@subsectionintoc\ouc@toc@gobblevi
\let\beamer@subsubsectionintoc\ouc@toc@gobblevii
\@input{\jobname.toc}%
\makeatother
\endgroup
}
%% ------------------------------------------------------------ 计算尺寸
\def\ouc@toc@compute{%
%% 1. 目录区可用高度
\ifdim\tocareaheight=\z@
\setlength{\ouc@toc@avail}{\dimexpr\textheight-\tocareamargin\relax}%
\else
\setlength{\ouc@toc@avail}{\tocareaheight}%
\fi
\ifnum\ouc@toc@count<\@ne \global\ouc@toc@count\@ne \fi
%% 2. 间距:理想值 \tocboxgap;空间紧张时压缩,但不小于 \tocboxgapmin
\setlength{\ouc@toc@gap}{\tocboxgap}%
\setlength{\ouc@toc@tmp}{%
\dimexpr\ouc@toc@avail/(\ouc@toc@count+1)*25/100\relax}%
\ifdim\ouc@toc@gap>\ouc@toc@tmp \setlength{\ouc@toc@gap}{\ouc@toc@tmp}\fi
\ifdim\ouc@toc@gap<\tocboxgapmin \setlength{\ouc@toc@gap}{\tocboxgapmin}\fi
%% 3. 方框高度:扣掉 N+1 个间距后,把目录区高度均分给 N 个方框
\setlength{\ouc@toc@height}{%
\dimexpr(\ouc@toc@avail-\ouc@toc@count\ouc@toc@gap-\ouc@toc@gap)
/\ouc@toc@count\relax}%
\ifdim\ouc@toc@height>\tocboxmaxheight
\setlength{\ouc@toc@height}{\tocboxmaxheight}\fi
\ifdim\ouc@toc@height<8pt \setlength{\ouc@toc@height}{8pt}\fi
%% 4. 字号:随方框高度等比缩放,同时保证文字不会撑破方框
%% 内边距也随方框高度缩小,避免方框过矮时被 tcolorbox 撑回去
\setlength{\ouc@toc@pad}{\dimexpr\ouc@toc@height*12/100\relax}%
\ifdim\ouc@toc@pad>2pt \setlength{\ouc@toc@pad}{2pt}\fi
\setlength{\ouc@toc@padx}{\dimexpr\ouc@toc@pad*3\relax}%
\ifdim\ouc@toc@padx<6pt \setlength{\ouc@toc@padx}{6pt}\fi
%% 序号徽标的直径:随字号缩放,下限 1.15em(免得章节多时圆圈看不清),
%% 但绝不能超过方框高度,否则会把矮方框撑破
\setlength{\ouc@toc@badgesize}{\dimexpr\ouc@toc@font*115/100\relax}%
\ifdim\ouc@toc@badgesize<1.15em \setlength{\ouc@toc@badgesize}{1.15em}\fi
\ifdim\ouc@toc@badgesize>2.2em \setlength{\ouc@toc@badgesize}{2.2em}\fi
\setlength{\ouc@toc@tmp}{\dimexpr\ouc@toc@height-2pt\relax}%
\ifdim\ouc@toc@badgesize>\ouc@toc@tmp \setlength{\ouc@toc@badgesize}{\ouc@toc@tmp}\fi
\ifdim\ouc@toc@badgesize<4pt \setlength{\ouc@toc@badgesize}{4pt}\fi
\setlength{\ouc@toc@font}{\dimexpr\ouc@toc@height*40/100\relax}%
\setlength{\ouc@toc@tmp}{%
\dimexpr(\ouc@toc@height-2\ouc@toc@pad-2pt)*5/6\relax}%
\ifdim\ouc@toc@font>\ouc@toc@tmp \setlength{\ouc@toc@font}{\ouc@toc@tmp}\fi
\ifdim\ouc@toc@font>\tocfontmax \setlength{\ouc@toc@font}{\tocfontmax}\fi
\ifdim\ouc@toc@font<\tocfontwarn
\PackageWarning{oucslide}{目录页有 \the\ouc@toc@count\space 个一级标题,
字号已被压缩到 \the\ouc@toc@font(低于 \the\tocfontwarn)。
如观感不佳,请减少 section 的数目,或调小 \string\tocareamargin}%
\fi
\setlength{\ouc@toc@base}{\dimexpr\ouc@toc@font*6/5\relax}%
}
%% -------------------------------------------------------------- 条目
%% #1 = 序号, #2 = 标题, #3 = 锚点页码
\def\ouc@toc@entry#1#2#3{%
\ifcsname ouc@toc@entry@\ouc@toc@style\endcsname
\csname ouc@toc@entry@\ouc@toc@style\endcsname{#1}{#2}{#3}%
\else
\PackageWarning{oucslide}{未知的目录页样式 “\ouc@toc@style”,
可选值为 box / solid / bar,这里按 box 处理}%
\ouc@toc@entry@box{#1}{#2}{#3}%
\fi
}
%% 样式 box:浅色底 + 圆角框 + 圆形序号,文字左对齐(左侧留白)
\def\ouc@toc@entry@box#1#2#3{%
\begin{tcolorbox}[width=\textwidth, height=\ouc@toc@height,
valign=center, halign=left, arc=3pt, boxrule=0.8pt,
colframe=\ouc@toc@main, colback=\ouc@toc@light, coltext=\ouc@toc@main,
left=\ouc@toc@indent, right=\ouc@toc@padx, top=\ouc@toc@pad, bottom=\ouc@toc@pad,
boxsep=0pt, before skip=0pt, after skip=0pt,
fontupper=\ouc@toc@fontsel]%
\ouc@toc@badge{\ouc@toc@main}{#1}%
\hspace{0.8em}\hyperlink{Navigation#3}{#2}%
\end{tcolorbox}%
}
%% 样式 solid:实心色底 + 白字,文字左对齐(左侧留白)
\def\ouc@toc@entry@solid#1#2#3{%
\begin{tcolorbox}[width=\textwidth, height=\ouc@toc@height,
valign=center, halign=left, arc=3pt, boxrule=0pt,
colframe=\ouc@toc@main, colback=\ouc@toc@main, coltext=white,
left=\ouc@toc@indent, right=\ouc@toc@padx, top=\ouc@toc@pad, bottom=\ouc@toc@pad,
boxsep=0pt, before skip=0pt, after skip=0pt,
fontupper=\ouc@toc@fontsel]%
\ouc@toc@badgeopen{white}{#1}%
\hspace{0.8em}\hyperlink{Navigation#3}{#2}%
\end{tcolorbox}%
}
%% 样式 bar:白底 + 左侧粗色条 + 圆形序号,文字左对齐(左侧留白)
\def\ouc@toc@entry@bar#1#2#3{%
\begin{tcolorbox}[width=\textwidth, height=\ouc@toc@height,
valign=center, halign=left, arc=0pt, boxrule=0.5pt,
leftrule=8pt, colframe=\ouc@toc@main, colback=white, coltext=black,
left=\ouc@toc@indent, right=\ouc@toc@padx, top=\ouc@toc@pad, bottom=\ouc@toc@pad,
boxsep=0pt, before skip=0pt, after skip=0pt,
fontupper=\ouc@toc@fontsel]%
\ouc@toc@badge{\ouc@toc@main}{#1}%
\hspace{0.8em}\hyperlink{Navigation#3}{#2}%
\end{tcolorbox}%
}
%% ---------------------------------------------------------- 目录页正文
%% 逐条读入 .toc,把每个一级标题渲染成一个方框;
%% 方框之间、方框与页面上下边缘之间各放一个 \vfill,
%% 由 beamer 自动等分剩余空间,从而刚好铺满整页。
\def\ouc@toc@body{%
\ouc@toc@countsections
\ouc@toc@compute
\begingroup
\makeatletter
\let\beamer@subsectionintoc\ouc@toc@gobblevi
\let\beamer@subsubsectionintoc\ouc@toc@gobblevii
\def\beamer@sectionintoc##1##2##3##4##5{%
\vfill\ouc@toc@entry{##5}{##2}{##3}%
}%
\@input{\jobname.toc}%
\makeatother
\vfill
\endgroup
}
%定义目录页生成命令 %定义目录页生成命令
\def\makeoutline{ \def\makeoutline{
{ {
\setbeamertemplate{frametitle}[outlineframe] \setbeamertemplate{frametitle}[outlineframe]
\begin{frame} \begin{frame}
\frametitle{目录} \frametitle{目录}
\tableofcontents[hideallsubsections] \ouc@toc@body
\end{frame} \end{frame}
} }
%再把模板改回去 %再把模板改回去
\setbeamertemplate{frametitle}[frame] \setbeamertemplate{frametitle}[frame]
} }
\makeatother
%定义最后一页生成命令 %定义最后一页生成命令
\def\makelast{ \def\makelast{