main
1#import "@preview/i-figured:0.2.4"
2#import "@preview/numbly:0.1.0": numbly
3#import "@preview/hydra:0.6.2": hydra
4#import "../utils/style.typ": 字体, 字号
5#import "../utils/unpairs.typ": unpairs
6
7#let mainmatter(
8 // documentclass 传入参数
9 twoside: false,
10 fonts: (:),
11 info: (:),
12 // 其他参数
13 leading: 1.5 * 15.6pt - 0.7em,
14 spacing: 1.5 * 15.6pt - 0.7em,
15 justify: true,
16 first-line-indent: (amount: 2em, all: true),
17 numbering: numbly("第{1:一}章", "{1}.{2} "),
18 // 正文字体与字号参数
19 text-args: auto,
20 // 标题字体与字号
21 heading-font: auto,
22 heading-size: (字号.小三, 字号.四号, 字号.小四),
23 heading-weight: ("regular",),
24 heading-above: (2 * 15.6pt - 0.7em, 2 * 15.6pt - 0.7em),
25 heading-below: (2 * 15.6pt - 0.7em, 1.5 * 15.6pt - 0.7em),
26 heading-pagebreak: (true, false),
27 heading-align: (center, auto),
28 // 页眉
29 header-render: auto,
30 header-vspace: 0em,
31 display-header: false,
32 skip-on-first-level: true,
33 stroke-width: 0.5pt,
34 reset-footnote: true,
35 // caption 的分隔符
36 separator: " ",
37 // caption 样式
38 caption-style: strong,
39 caption-text-size: 字号.五号,
40 // figure 计数
41 show-figure: i-figured.show-figure,
42 // equation 计数
43 show-equation: i-figured.show-equation,
44 ..args,
45 it,
46) = {
47 // 0. 标志前言结束
48 set page(footer: none)
49 pagebreak(weak: true, to: if twoside { "odd" })
50
51 // 1. 默认参数
52 fonts = 字体 + fonts
53 info = (
54 (
55 title: ("基于 Typst 的", "厦门大学本科毕业论文模板"),
56 title-en: "An XMU Undergraduate Thesis Template\nPowered by Typst",
57 grade: "20XX",
58 student-id: "1234567890",
59 author: "张三",
60 department: "某学院",
61 major: "某专业",
62 supervisor: ("李四", "教授"),
63 submit-date: datetime.today(),
64 )
65 + info
66 )
67 // 1.1 字体与字号
68 if (text-args == auto) {
69 text-args = (font: fonts.宋体, size: 字号.小四)
70 }
71 if (heading-font == auto) {
72 heading-font = (fonts.黑体,)
73 }
74 // 1.2 处理 heading- 开头的其他参数
75 let heading-text-args-lists = args
76 .named()
77 .pairs()
78 .filter(pair => pair.at(0).starts-with("heading-"))
79 .map(pair => (pair.at(0).slice("heading-".len()), pair.at(1)))
80
81 // 2. 辅助函数
82 let array-at(arr, pos) = {
83 arr.at(calc.min(pos, arr.len()) - 1)
84 }
85
86 // 3. 设置基本样式
87 // 3.1 文本和段落样式
88 set text(..text-args)
89 set par(
90 leading: leading,
91 spacing: spacing,
92 justify: justify,
93 first-line-indent: first-line-indent,
94 )
95 show raw: set text(font: fonts.等宽)
96 // 3.2 脚注样式
97 set footnote(numbering: "①")
98 show footnote.entry: set text(font: fonts.宋体, size: 字号.小五)
99 // 3.3 设置 figure 的编号
100 show heading: i-figured.reset-counters
101 show figure: show-figure
102 // 3.4 设置 equation 的编号
103 show math.equation.where(block: true): show-equation
104 // 3.5 表格表头置顶 + 不用冒号用空格分割 + 样式
105 show figure.where(kind: table): set figure.caption(position: top)
106 set figure.caption(separator: separator)
107 show figure.caption: caption-style
108 show figure.caption: set text(font: fonts.宋体, size: caption-text-size)
109
110 // 4. 处理标题
111 // 4.1 设置标题的 Numbering
112 set heading(numbering: numbering)
113 // 4.2 设置字体字号
114 show heading: it => {
115 set text(
116 font: array-at(heading-font, it.level),
117 size: array-at(heading-size, it.level),
118 weight: array-at(heading-weight, it.level),
119 ..unpairs(heading-text-args-lists.map(pair => (
120 pair.at(0),
121 array-at(pair.at(1), it.level),
122 ))),
123 )
124 set block(
125 above: array-at(heading-above, it.level),
126 below: array-at(heading-below, it.level),
127 )
128 if (
129 it.level == 1 and it.numbering != none and counter(heading).display(it.numbering) != none
130 ) {
131 block({
132 counter(heading).display(it.numbering)
133 h(2em)
134 it.body
135 // 使 i-figured 正常更新
136 set text(size: 0pt)
137 set block(above: 0pt, below: 0pt)
138 it
139 })
140 } else {
141 it
142 }
143 }
144 // 4.3 标题居中与自动换页
145 show heading: it => {
146 if array-at(heading-pagebreak, it.level) {
147 // 如果打上了 no-auto-pagebreak 标签,则不自动换页
148 if "label" not in it.fields() or str(it.label) != "no-auto-pagebreak" {
149 pagebreak(weak: true, to: if twoside { "odd" })
150 block() // 用于使页顶处的 heading 的 above 属性生效
151 }
152 }
153 if array-at(heading-align, it.level) != auto {
154 set align(array-at(heading-align, it.level))
155 it
156 } else {
157 it
158 }
159 }
160
161 // 5. 页眉配置
162 set page(header: {
163 // 重置 footnote 计数器
164 if reset-footnote {
165 counter(footnote).update(0)
166 }
167 context {
168 set text(font: fonts.宋体, size: 字号.小五)
169 align(
170 center,
171 if calc.odd(here().page()) {
172 hydra(skip-starting: false, use-last: true, 1)
173 } else {
174 info.title.join()
175 },
176 )
177 }
178 // 分隔线
179 place(bottom, dy: 0.35em, line(length: 100%, stroke: 0.5pt))
180 })
181
182 set page(
183 numbering: "1",
184 footer: context {
185 set text(font: fonts.宋体, size: 字号.小五)
186 align(center, counter(page).display("1"))
187 },
188 )
189 counter(page).update(1)
190 it
191}