diff --git a/.gitattributes b/.gitattributes
index 544a6ec..9e7dfff 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,3 @@
-compile.bat text eol=crlf
-install_typst.ps1 text eol=crlf
\ No newline at end of file
+compile.bat text eol=crlf
+install_typst.ps1 text eol=crlf
+**/*.otf filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3b846fb..812f488 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout master
uses: actions/checkout@v3
- with:
+ with:
fetch-depth: 0
- name: Setup Pages
uses: actions/configure-pages@v2
@@ -35,7 +35,7 @@ jobs:
# run: 'sudo apt-get update && sudo apt install -y ripgrep wget curl && (curl -s https://api.github.com/repos/typst/typst/releases | rg "browser_download_url.*typst-x86_64-unknown-linux-gnu.tar.gz" | head -1 | cut -d : -f 2,3 | tr -d \" | wget -qi -) && tar xf typst-x86_64-unknown-linux-gnu.tar.gz && sudo mv typst-x86_64-unknown-linux-gnu/typst /usr/local/bin/typst'
run: 'cargo install --git https://github.com/typst/typst typst-cli'
- name: Build PDF
- run: 'mkdir build && typst compile --font-path fonts thesis.typ build/thesis.pdf'
+ run: 'mkdir build && typst compile --root=. --font-path=fonts template/thesis.typ build/thesis.pdf'
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
@@ -64,7 +64,7 @@ jobs:
with:
name: github-pages
- name: Extract artifact
- run: |
+ run: |
mkdir -p $RUNNER_TEMP/gitee_pages
tar -C $RUNNER_TEMP/gitee_pages -xf artifact.tar
- name: Deploy to Gitee Pages
@@ -77,7 +77,7 @@ jobs:
git config --global user.name "Howard Lau"
git config --global user.email "howardlau1999@hotmail.com"
-
+
cd $RUNNER_TEMP/gitee_pages
git init
git checkout -b gh-pages
diff --git a/.github/workflows/gitee.yml b/.github/workflows/gitee.yml
deleted file mode 100644
index 738f9e6..0000000
--- a/.github/workflows/gitee.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: Sync To Gitee
-
-on:
- push:
- branches:
- - master
-
-concurrency:
- group: "sync-to-gitee"
- cancel-in-progress: true
-
-jobs:
- sync:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - uses: howardlau1999/repository-mirroring-action@main
- with:
- target_repo_url:
- git@gitee.com:howardlau/sysu-thesis-typst.git
- ssh_private_key:
- ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a7e151e..14940c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-thesis.pdf
\ No newline at end of file
+**/*.pdf
+.vscode
diff --git a/README.md b/README.md
index c8cf3cc..7e71fa4 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,9 @@
-# sysu-thesis-typst
-
-[Gitee 仓库镜像](https://gitee.com/howardlau/sysu-thesis-typst)(自动同步 [GitHub 主仓库](https://github.com/howardlau1999/sysu-thesis-typst),仅供只读访问)
-
-中山大学学位论文 Typst 模板,项目基于 [PKUTHSS-Typst](https://github.com/lucifer1004/pkuthss-typst) 重构。
+# 基于 Typst 的中山大学学位论文模板
+[![GitLab 仓库](https://img.shields.io/badge/gitlab-%23181717.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://gitlab.com/sysu-gitlab/thesis-template/better-thesis)
+[![GitHub 仓库](https://img.shields.io/badge/gitlab-%23181717.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://github.com/sysu/better-thesis)
当前还未完全符合学位论文格式要求,欢迎同学们贡献代码!模板交流 QQ 群:797942860([点此直接加入](https://jq.qq.com/?_wv=1027&k=m58va1kd))
-输出成品文件预览(每 6 小时自动更新):[thesis.pdf](https://liuhaohua.com/sysu-thesis-typst/thesis.pdf)
-
**Q:我不会 LaTeX,可以用这个模板写论文吗?**
**A:完全可以!Typst 是一个比 LaTeX 更简单的排版语言,同时安装更加方便,编译更加快速!**
@@ -16,6 +12,8 @@
**由于 Typst 还处于初期的快速开发阶段,本项目需要使用从源码编译的 Typst 版本才能正常生成 PDF。仓库提供了一键安装脚本,按照使用说明运行即可。**
+
+
### Windows 用户
1. [下载本仓库](https://github.com/howardlau1999/sysu-thesis-typst/archive/refs/heads/master.zip),或者使用 `git clone https://github.com/howardlau1999/sysu-thesis-typst` 命令克隆本仓库。
@@ -51,19 +49,4 @@ cargo install --git https://ghproxy.com/https://github.com/typst/typst.git
4. 执行 `make` 命令,即可生成 `thesis.pdf` 文件。
## 项目结构
-
-### 主要文件
-
-- `info.typ` 文件中包含论文的基本信息,包括作者、学位、导师、学位论文题目等。
-- `custom.typ` 文件中包含论文的自定义设置,包括行距,字体等。
-- `thesis.typ` 文件是论文的主体,包含论文的各个章节。在添加或者删除章节文件后,需要同步修改这个文件中的 `#include` 命令。
-- `template.typ` 文件是论文的模板,包含论文的各个部分的格式。一般不需要修改。
-
-### 文件夹
-
-- `chapters` 文件夹包含了论文的各个章节文件,你可以自由地增加或删除章节文件,按你自己需要的方式组织文件。在添加或删除文件后,需要同步修改 `thesis.typ` 文件中的 `#include` 命令。
-- `templates` 文件夹包含了论文的各个部分的模板文件,你可以自由地增加或删除模板文件,按你自己需要的方式组织文件。在添加或删除文件后,需要同步修改 `template.typ` 文件中的 `#include` 命令。欢迎贡献新的模板文件。
-- `functions` 文件夹包含了模板使用到的各种自定义辅助函数,如果你想贡献代码,可以在这个文件夹中添加新的辅助函数。
-- `fonts` 文件夹包含了模板使用到的字体文件,你可以按需要添加或者删除字体文件。在添加或删除文件后,可以运行 `typst --font-path fonts fonts` 查看 Typst 检测到的字体文件。
-- `bibs` 文件夹包含了论文的参考文献文件,你可以按需要添加或者删除参考文献文件,目录结构没有特殊需求。
-- `images` 文件夹包含了论文中使用到的图片文件,你可以按需要添加或者删除图片文件,目录结构没有特殊需求。`vi` 文件夹包含了校徽的矢量图文件。
\ No newline at end of file
+详见 `template\thesis.typ`
diff --git a/assets/vi/sysu_logo.svg b/assets/vi/sysu_logo.svg
new file mode 100644
index 0000000..1c14f8e
--- /dev/null
+++ b/assets/vi/sysu_logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/compile.bat b/compile.bat
index 667108f..975c031 100644
--- a/compile.bat
+++ b/compile.bat
@@ -1,8 +1,7 @@
-@echo off
+@echo off
chcp 65001>nul
-set Path=%cd%\;%Path%
set TYPST_FONT_PATHS=%cd%\fonts
echo 编译中……
-typst compile --font-path fonts thesis.typ
+typst compile --root=%cd% --font-path=fonts template/thesis.typ
echo 命令运行完毕,按任意键退出
-pause>nul
\ No newline at end of file
+pause>nul
diff --git a/custom.typ b/custom.typ
deleted file mode 100644
index b5878e7..0000000
--- a/custom.typ
+++ /dev/null
@@ -1,11 +0,0 @@
-#let 行距 = 1em
-
-#let 字体 = (
- 仿宋: ("Times New Roman", "Adobe Fangsong Std", "FangSong"),
- 宋体: ("Times New Roman", "Source Han Serif SC", "SimSun"),
- 黑体: ("Times New Roman", "Arial", "Adobe Heiti Std", "SimHei"),
- 楷体: ("Times New Roman", "Adobe Kaiti Std R", "KaiTi"),
- 代码: ("Inconsolata", "Source Han Sans HW SC", "Times New Roman", "SimSun"),
-)
-
-#let 强调色 = cmyk(100%, 0%, 100%, 60%)
\ No newline at end of file
diff --git a/fonts/AdobeFonts/AdobeFangSongStd-Regular.otf b/fonts/AdobeFonts/AdobeFangSongStd-Regular.otf
index 5f700e1..54bfa28 100644
Binary files a/fonts/AdobeFonts/AdobeFangSongStd-Regular.otf and b/fonts/AdobeFonts/AdobeFangSongStd-Regular.otf differ
diff --git a/fonts/AdobeFonts/AdobeHeitiStd-Regular.otf b/fonts/AdobeFonts/AdobeHeitiStd-Regular.otf
index 7c4d336..c326cc0 100644
Binary files a/fonts/AdobeFonts/AdobeHeitiStd-Regular.otf and b/fonts/AdobeFonts/AdobeHeitiStd-Regular.otf differ
diff --git a/fonts/AdobeFonts/AdobeKaitiStd-Regular.otf b/fonts/AdobeFonts/AdobeKaitiStd-Regular.otf
index 5f63bf7..8d168fb 100644
Binary files a/fonts/AdobeFonts/AdobeKaitiStd-Regular.otf and b/fonts/AdobeFonts/AdobeKaitiStd-Regular.otf differ
diff --git a/fonts/Arial/Arial.otf b/fonts/Arial/Arial.otf
index 1669b0d..f7ebdfe 100644
Binary files a/fonts/Arial/Arial.otf and b/fonts/Arial/Arial.otf differ
diff --git a/fonts/Inconsolata/Inconsolata-Bold.otf b/fonts/Inconsolata/Inconsolata-Bold.otf
index 572d5d1..39558ab 100644
Binary files a/fonts/Inconsolata/Inconsolata-Bold.otf and b/fonts/Inconsolata/Inconsolata-Bold.otf differ
diff --git a/fonts/Inconsolata/Inconsolata-Regular.otf b/fonts/Inconsolata/Inconsolata-Regular.otf
index 312475d..9469b11 100644
Binary files a/fonts/Inconsolata/Inconsolata-Regular.otf and b/fonts/Inconsolata/Inconsolata-Regular.otf differ
diff --git a/fonts/Inconsolata/Inconsolata-SemiBold.otf b/fonts/Inconsolata/Inconsolata-SemiBold.otf
index 7903932..d12e9c8 100644
Binary files a/fonts/Inconsolata/Inconsolata-SemiBold.otf and b/fonts/Inconsolata/Inconsolata-SemiBold.otf differ
diff --git a/fonts/SourceHan/SourceHanSansHWSC-Bold.otf b/fonts/SourceHan/SourceHanSansHWSC-Bold.otf
index b158cbb..b3a66c2 100644
Binary files a/fonts/SourceHan/SourceHanSansHWSC-Bold.otf and b/fonts/SourceHan/SourceHanSansHWSC-Bold.otf differ
diff --git a/fonts/SourceHan/SourceHanSansHWSC-Regular.otf b/fonts/SourceHan/SourceHanSansHWSC-Regular.otf
index f46ee46..de11fed 100644
Binary files a/fonts/SourceHan/SourceHanSansHWSC-Regular.otf and b/fonts/SourceHan/SourceHanSansHWSC-Regular.otf differ
diff --git a/fonts/SourceHan/SourceHanSerifSC-Bold.otf b/fonts/SourceHan/SourceHanSerifSC-Bold.otf
index a7be084..6fee57d 100644
Binary files a/fonts/SourceHan/SourceHanSerifSC-Bold.otf and b/fonts/SourceHan/SourceHanSerifSC-Bold.otf differ
diff --git a/fonts/SourceHan/SourceHanSerifSC-Regular.otf b/fonts/SourceHan/SourceHanSerifSC-Regular.otf
index 0dd496a..186d841 100644
Binary files a/fonts/SourceHan/SourceHanSerifSC-Regular.otf and b/fonts/SourceHan/SourceHanSerifSC-Regular.otf differ
diff --git a/functions/helpers.typ b/functions/helpers.typ
index c8b587a..6735ff4 100644
--- a/functions/helpers.typ
+++ b/functions/helpers.typ
@@ -5,7 +5,7 @@
} else {
let ret = ()
let len = lists.fold(
- lists.first().len(),
+ lists.first().len(),
(a, b) => if a > b.len() { b.len() } else { a }
)
diff --git a/images/vi/sysu-banner.svg b/images/vi/sysu-banner.svg
deleted file mode 100644
index 17be9c7..0000000
--- a/images/vi/sysu-banner.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
\ No newline at end of file
diff --git a/images/vi/sysu-emblem.svg b/images/vi/sysu-emblem.svg
deleted file mode 100644
index fc54d34..0000000
--- a/images/vi/sysu-emblem.svg
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
\ No newline at end of file
diff --git a/layouts/appendix.typ b/layouts/appendix.typ
new file mode 100644
index 0000000..ee33f60
--- /dev/null
+++ b/layouts/appendix.typ
@@ -0,0 +1,24 @@
+#import "@preview/i-figured:0.2.4"
+#import "../utils/custom-numbering.typ": custom-numbering
+
+// 后记,重置 heading 计数器
+#let appendix(
+ numbering: custom-numbering.with(first-level: "", depth: 4, "1.1 "),
+ // figure 计数
+ show-figure: i-figured.show-figure.with(numbering: "1.1"),
+ // equation 计数
+ show-equation: i-figured.show-equation.with(numbering: "(1.1)"),
+ // 重置计数
+ reset-counter: false,
+ it,
+) = {
+ set heading(numbering: numbering)
+ if reset-counter {
+ counter(heading).update(0)
+ }
+ // 设置 figure 的编号
+ show figure: show-figure
+ // 设置 equation 的编号
+ show math.equation.where(block: true): show-equation
+ it
+}
\ No newline at end of file
diff --git a/layouts/doc.typ b/layouts/doc.typ
new file mode 100644
index 0000000..011cf8d
--- /dev/null
+++ b/layouts/doc.typ
@@ -0,0 +1,34 @@
+// 文稿设置,可以进行一些像页面边距这类的全局设置
+#let doc(
+ // documentclass 传入参数
+ info: (:),
+ // 其他参数
+ fallback: false, // 字体缺失时使用 fallback,不显示豆腐块
+ lang: "zh",
+ margin: (x: 89pt),
+ it,
+) = {
+ // 1. 默认参数
+ info = (
+ title: ("基于 Typst 的", "中山大学学位论文模板"),
+ author: "张三",
+ ) + info
+
+ // 2. 对参数进行处理
+ // 2.1 如果是字符串,则使用换行符将标题分隔为列表
+ if type(info.title) == str {
+ info.title = info.title.split("\n")
+ }
+
+ // 3. 基本的样式设置
+ set text(fallback: fallback, lang: lang)
+ set page(margin: margin)
+
+ // 4. PDF 元信息
+ set document(
+ title: (("",)+ info.title).sum(),
+ author: info.author,
+ )
+
+ it
+}
diff --git a/layouts/mainmatter.typ b/layouts/mainmatter.typ
new file mode 100644
index 0000000..b53da8e
--- /dev/null
+++ b/layouts/mainmatter.typ
@@ -0,0 +1,175 @@
+#import "@preview/anti-matter:0.0.2": anti-front-end
+#import "@preview/i-figured:0.2.4"
+#import "../utils/style.typ": 字号, 字体
+#import "../utils/custom-numbering.typ": custom-numbering
+#import "../utils/custom-heading.typ": heading-display, active-heading, current-heading
+#import "../utils/indent.typ": fake-par
+#import "../utils/unpairs.typ": unpairs
+
+#let mainmatter(
+ // documentclass 传入参数
+ twoside: false,
+ fonts: (:),
+ // 其他参数
+ leading: 1.5 * 15.6pt - 0.7em,
+ spacing: 1.5 * 15.6pt - 0.7em,
+ justify: true,
+ first-line-indent: 2em,
+ numbering: custom-numbering.with(first-level: "第一章 ", depth: 4, "1.1 "),
+ // 正文字体与字号参数
+ text-args: auto,
+ // 标题字体与字号
+ heading-font: (字体.黑体, 字体.黑体, 字体.宋体),
+ heading-size: (字号.三号, 字号.四号, 字号.小四),
+ heading-weight: ("regular", "regular", "bold"),
+ heading-above: (2 * 15.6pt - 0.7em, 2 * 15.6pt - 0.7em),
+ heading-below: (2 * 15.6pt - 0.7em, 1.5 * 15.6pt - 0.7em),
+ heading-pagebreak: (true, false),
+ heading-align: (center, left, auto),
+ // 页眉
+ header-render: auto,
+ header-vspace: 0em,
+ display-header: false,
+ skip-on-first-level: true,
+ stroke-width: 0.5pt,
+ reset-footnote: true,
+ // caption 的 separator
+ separator: " ",
+ // caption 样式
+ caption-style: strong,
+ caption-size: 字号.五号,
+ // figure 计数
+ show-figure: i-figured.show-figure,
+ // equation 计数
+ show-equation: i-figured.show-equation,
+ ..args,
+ it,
+) = {
+ // 0. 标志前言结束
+ anti-front-end()
+
+ // 1. 默认参数
+ fonts = 字体 + fonts
+ if (text-args == auto) {
+ text-args = (font: fonts.宋体, size: 字号.小四)
+ }
+ // 1.1 字体与字号
+ if (heading-font == auto) {
+ heading-font = (fonts.黑体,)
+ }
+ // 1.2 处理 heading- 开头的其他参数
+ let heading-text-args-lists = args.named().pairs()
+ .filter((pair) => pair.at(0).starts-with("heading-"))
+ .map((pair) => (pair.at(0).slice("heading-".len()), pair.at(1)))
+
+ // 2. 辅助函数
+ let array-at(arr, pos) = {
+ arr.at(calc.min(pos, arr.len()) - 1)
+ }
+
+ // 3. 设置基本样式
+ // 3.1 文本和段落样式
+ set text(..text-args)
+ set par(
+ leading: leading,
+ justify: justify,
+ first-line-indent: first-line-indent
+ )
+ show par: set block(spacing: spacing)
+ // 3.2 脚注样式
+ show footnote.entry: set text(font: fonts.宋体, size: 字号.五号)
+ // 3.3 设置 figure 的编号
+ show heading: i-figured.reset-counters
+ show figure: show-figure
+ // 3.4 设置 equation 的编号和假段落首行缩进
+ show math.equation.where(block: true): show-equation
+ // 3.5 表格表头置顶 + 不用冒号用空格分割 + 样式
+ show figure.where(
+ kind: table
+ ): set figure.caption(position: top)
+ set figure.caption(separator: separator)
+ show figure.caption: caption-style
+ show figure.caption: set text(font: fonts.宋体, size: 字号.五号)
+ // 3.6 优化列表显示
+ // 术语列表 terms 不应该缩进
+ show terms: set par(first-line-indent: 0pt)
+
+ // 4. 处理标题
+ // 4.1 设置标题的 Numbering
+ set heading(numbering: numbering)
+ // 4.2 设置字体字号并加入假段落模拟首行缩进
+ show heading: it => {
+ set text(
+ font: array-at(heading-font, it.level),
+ size: array-at(heading-size, it.level),
+ weight: array-at(heading-weight, it.level),
+ ..unpairs(heading-text-args-lists
+ .map((pair) => (pair.at(0), array-at(pair.at(1), it.level))))
+ )
+ set block(
+ above: array-at(heading-above, it.level),
+ below: array-at(heading-below, it.level),
+ )
+ it
+ fake-par
+ }
+ // 4.3 标题居中与自动换页
+ show heading: it => {
+ if (array-at(heading-pagebreak, it.level)) {
+ // 如果打上了 no-auto-pagebreak 标签,则不自动换页
+ if ("label" not in it.fields() or str(it.label) != "no-auto-pagebreak") {
+ pagebreak(weak: true)
+ }
+ }
+ if (array-at(heading-align, it.level) != auto) {
+ set align(array-at(heading-align, it.level))
+ it
+ } else {
+ pad(left: 2em, it)
+ }
+ }
+
+ // 5. 处理页眉
+ set page(..(if display-header {
+ (
+ header: {
+ // 重置 footnote 计数器
+ if reset-footnote {
+ counter(footnote).update(0)
+ }
+ locate(loc => {
+ // 5.1 获取当前页面的一级标题
+ let cur-heading = current-heading(level: 1, loc)
+ // 5.2 如果当前页面没有一级标题,则渲染页眉
+ if not skip-on-first-level or cur-heading == none {
+ if header-render == auto {
+ // 一级标题和二级标题
+ let first-level-heading = if not twoside or calc.rem(loc.page(), 2) == 0 { heading-display(active-heading(level: 1, loc)) } else { "" }
+ let second-level-heading = if not twoside or calc.rem(loc.page(), 2) == 2 { heading-display(active-heading(level: 2, prev: false, loc)) } else { "" }
+ set text(font: fonts.宋体, size: 字号.五号)
+ stack(
+ first-level-heading + h(1fr) + second-level-heading,
+ v(0.25em),
+ if first-level-heading != "" or second-level-heading != "" { line(length: 100%, stroke: stroke-width + black) },
+ )
+ } else {
+ header-render(loc)
+ }
+ v(header-vspace)
+ }
+ })
+ }
+ )
+ } else {
+ (
+ header: {
+ // 重置 footnote 计数器
+ if reset-footnote {
+ counter(footnote).update(0)
+ }
+ }
+ )
+ }))
+
+ it
+}
diff --git a/layouts/preface.typ b/layouts/preface.typ
new file mode 100644
index 0000000..974855e
--- /dev/null
+++ b/layouts/preface.typ
@@ -0,0 +1,18 @@
+#import "@preview/anti-matter:0.0.2": anti-matter
+
+// 前言,重置页面计数器
+#let preface(
+ // documentclass 传入的参数
+ twoside: false,
+ // 其他参数
+ spec: (front: "I", inner: "1", back: "I"),
+ ..args,
+ it,
+) = {
+ // 分页
+ if (twoside) {
+ pagebreak() + " "
+ }
+ counter(page).update(0)
+ anti-matter(spec: spec, ..args, it)
+}
\ No newline at end of file
diff --git a/lib.typ b/lib.typ
new file mode 100644
index 0000000..cc7b5b1
--- /dev/null
+++ b/lib.typ
@@ -0,0 +1,298 @@
+// 中山大学学位论文 Typst 模板 sysu-thesis-typst
+// 基于 [南京大学学位论文模板](https://github.com/nju-lug/modern-nju-thesis) 重构中
+// Repo: https://gitlab.com/sysu-gitlab/thesis-template/better-thesis
+
+#import "layouts/doc.typ": doc
+#import "layouts/preface.typ": preface
+#import "layouts/mainmatter.typ": mainmatter
+#import "layouts/appendix.typ": appendix
+#import "pages/fonts-display-page.typ": fonts-display-page
+#import "pages/bachelor-cover.typ": bachelor-cover
+// #import "pages/master-cover.typ": master-cover
+#import "pages/bachelor-title-page.typ": bachelor-title-page
+#import "pages/bachelor-decl-page.typ": bachelor-decl-page
+// #import "pages/master-decl-page.typ": master-decl-page
+#import "pages/bachelor-abstract.typ": bachelor-abstract
+// #import "pages/master-abstract.typ": master-abstract
+#import "pages/bachelor-abstract-en.typ": bachelor-abstract-en
+// #import "pages/master-abstract-en.typ": master-abstract-en
+#import "pages/bachelor-outline-page.typ": bachelor-outline-page
+#import "pages/list-of-figures.typ": list-of-figures
+#import "pages/list-of-tables.typ": list-of-tables
+#import "pages/notation.typ": notation
+#import "pages/acknowledgement.typ": acknowledgement
+#import "utils/bilingual-bibliography.typ": bilingual-bibliography
+#import "utils/custom-numbering.typ": custom-numbering
+#import "utils/custom-heading.typ": heading-display, active-heading, current-heading
+#import "utils/style.typ": 字体, 字号
+#import "@preview/anti-matter:0.0.2": anti-inner-end as mainmatter-end
+#import "@preview/i-figured:0.2.4": show-figure, show-equation
+
+// 使用函数闭包特性,通过 `documentclass` 函数类进行全局信息配置,然后暴露出拥有了全局配置的、具体的 `layouts` 和 `templates` 内部函数。
+#let documentclass(
+ doctype: "bachelor", // "bachelor" | "master" | "doctor" | "postdoc",文档类型,默认为本科生 bachelor
+ degree: "academic", // "academic" | "professional",学位类型,默认为学术型 academic
+ nl-cover: false, // TODO: 是否使用国家图书馆封面,默认关闭
+ twoside: false, // 双面模式,会加入空白页,便于打印
+ anonymous: false, // 盲审模式
+ bibliography: none, // 原来的参考文献函数
+ fonts: (:), // 字体,应传入「宋体」、「黑体」、「楷体」、「仿宋」、「等宽」
+ info: (:),
+) = {
+ // 默认参数
+ fonts = 字体 + fonts
+ info = (
+ title: ("基于 Typst 的", "中山大学学位论文模板"),
+ title-en: "A Typst Template for SYSU thesis",
+ grade: "20XX",
+ student-id: "1234567890",
+ author: "张三",
+ author-en: "Zhang San",
+ department: "某学院",
+ department-en: "XX Department",
+ major: "某专业",
+ major-en: "XX Major",
+ field: "某方向",
+ field-en: "XX Field",
+ supervisor: ("李四", "教授"),
+ supervisor-en: "Professor Li Si",
+ supervisor-ii: (),
+ supervisor-ii-en: "",
+ submit-date: datetime.today(),
+ // 以下为研究生项
+ defend-date: datetime.today(),
+ confer-date: datetime.today(),
+ bottom-date: datetime.today(),
+ chairman: "某某某 教授",
+ reviewer: ("某某某 教授", "某某某 教授"),
+ clc: "O643.12",
+ udc: "544.4",
+ secret-level: "公开",
+ supervisor-contact: "南京大学 江苏省南京市栖霞区仙林大道163号",
+ email: "xyz@smail.nju.edu.cn",
+ school-code: "10284",
+ degree: auto,
+ degree-en: auto,
+ ) + info
+
+ (
+ // 将传入参数再导出
+ doctype: doctype,
+ degree: degree,
+ nl-cover: nl-cover,
+ twoside: twoside,
+ anonymous: anonymous,
+ fonts: fonts,
+ info: info,
+ // 页面布局
+ doc: (..args) => {
+ doc(
+ ..args,
+ info: info + args.named().at("info", default: (:)),
+ )
+ },
+ preface: (..args) => {
+ preface(
+ twoside: twoside,
+ ..args,
+ )
+ },
+ mainmatter: (..args) => {
+ if doctype == "master" or doctype == "doctor" {
+ mainmatter(
+ twoside: twoside,
+ display-header: true,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ } else {
+ mainmatter(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ }
+ },
+ mainmatter-end: (..args) => {
+ mainmatter-end(
+ ..args,
+ )
+ },
+ appendix: (..args) => {
+ appendix(
+ ..args,
+ )
+ },
+
+ // 字体展示页
+ fonts-display-page: (..args) => {
+ fonts-display-page(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ },
+
+ // 封面页,通过 type 分发到不同函数
+ cover: (..args) => {
+ if doctype == "master" or doctype == "doctor" {
+ master-cover(
+ doctype: doctype,
+ degree: degree,
+ nl-cover: nl-cover,
+ anonymous: anonymous,
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ } else if doctype == "postdoc" {
+ panic("postdoc has not yet been implemented.")
+ } else {
+ bachelor-cover(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ }
+ },
+
+ // 扉页,通过 type 分发到不同函数
+ title-page: (..args) => {
+ if doctype == "master" or doctype == "doctor" {
+ panic("master or doctor has not yet been implemented.")
+ } else if doctype == "postdoc" {
+ panic("postdoc has not yet been implemented.")
+ } else {
+ bachelor-title-page(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ }
+ },
+
+ // 声明页,通过 type 分发到不同函数
+ decl-page: (..args) => {
+ if doctype == "master" or doctype == "doctor" {
+ master-decl-page(
+ anonymous: anonymous,
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ } else if doctype == "postdoc" {
+ panic("postdoc has not yet been implemented.")
+ } else {
+ bachelor-decl-page(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ }
+ },
+
+ // 中文摘要页,通过 type 分发到不同函数
+ abstract: (..args) => {
+ if doctype == "master" or doctype == "doctor" {
+ master-abstract(
+ doctype: doctype,
+ degree: degree,
+ anonymous: anonymous,
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ } else if doctype == "postdoc" {
+ panic("postdoc has not yet been implemented.")
+ } else {
+ bachelor-abstract(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ }
+ },
+
+ // 英文摘要页,通过 type 分发到不同函数
+ abstract-en: (..args) => {
+ if doctype == "master" or doctype == "doctor" {
+ master-abstract-en(
+ doctype: doctype,
+ degree: degree,
+ anonymous: anonymous,
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ } else if doctype == "postdoc" {
+ panic("postdoc has not yet been implemented.")
+ } else {
+ bachelor-abstract-en(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ info: info + args.named().at("info", default: (:)),
+ )
+ }
+ },
+
+ // 目录页
+ outline-page: (..args) => {
+ bachelor-outline-page(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ },
+
+ // 插图目录页
+ list-of-figures: (..args) => {
+ list-of-figures(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ },
+
+ // 表格目录页
+ list-of-tables: (..args) => {
+ list-of-tables(
+ twoside: twoside,
+ ..args,
+ fonts: fonts + args.named().at("fonts", default: (:)),
+ )
+ },
+
+ // 符号表页
+ notation: (..args) => {
+ notation(
+ twoside: twoside,
+ ..args,
+ )
+ },
+
+ // 参考文献页
+ bilingual-bibliography: (..args) => {
+ bilingual-bibliography(
+ bibliography: bibliography,
+ ..args,
+ )
+ },
+
+ // 致谢页
+ acknowledgement: (..args) => {
+ acknowledgement(
+ anonymous: anonymous,
+ twoside: twoside,
+ ..args,
+ )
+ },
+ )
+}
diff --git a/others/bachelor-proposal.typ b/others/bachelor-proposal.typ
new file mode 100644
index 0000000..c861e5e
--- /dev/null
+++ b/others/bachelor-proposal.typ
@@ -0,0 +1,88 @@
+#import "style.typ": 字体, 字号
+
+#let table-stroke = 0.5pt
+
+#set page(numbering: "1")
+
+#align(center, text(
+ font: 字体.黑体,
+ size: 字号.三号,
+ weight: "bold",
+ "南京大学本科毕业论文(设计)开题报告"
+))
+
+#set text(font: 字体.宋体, size: 字号.五号)
+#set underline(offset: 0.1em)
+
+#align(center)[
+ 填表人签名#underline(" ")填表时间:#underline(" ")年#underline(" ")月#underline(" ")日
+]
+
+#v(-0.7em)
+
+#{
+ set text(size: 字号.小四)
+ table(
+ columns: (100pt, 1fr, 100pt, 1fr),
+ stroke: table-stroke,
+ align: center + horizon,
+ [学生姓名], [], [学号], [],
+ [院系专业], [], [手机号], [],
+ [指导教师姓名一 \ (必填)], [], [职称], [],
+ [导师所在院系], [], [是否校内], [],
+ [指导教师姓名二 \ (选填)], [], [职称], [],
+ [导师所在院系], [], [是否校内], [],
+ [毕设类型], table.cell(colspan: 3)[#sym.ballot.x 毕业论文 #h(1fr) #sym.ballot 毕业设计(含毕业作品) #h(1fr)],
+ [论文题目], table.cell(colspan: 3)[],
+ )
+}
+
+#v(-1.37em)
+
+#table(
+ columns: 1fr,
+ stroke: table-stroke,
+ inset: 10pt,
+)[
+ *一、研究背景及意义*(附参考文献)
+
+ #v(45em)
+][
+ *二、国内外研究现状*(文献综述,附参考文献,不少于800字)
+
+ #v(45em)
+][
+ *三、主要研究或解决的问题和拟采用的方法*
+
+ #v(45em)
+][
+ *四、工作进度计划*(每两周为一个单位)
+
+ #v(20em)
+][
+ *指导教师意见*(不少于50个字)
+
+ #v(20em)
+
+ #set align(right)
+
+ *签名:* #h(6em)
+
+ #v(1em)
+
+ #strong(" 年 月 日")
+][
+ *院系意见:*
+
+ *(自动写同意)*
+
+ #v(1fr)
+
+ #set align(right)
+
+ *院系负责人签名:* #h(6em)
+
+ #v(1em)
+
+ #strong(" 年 月 日")
+]
\ No newline at end of file
diff --git a/others/master-proposal-logo.png b/others/master-proposal-logo.png
new file mode 100644
index 0000000..216f87e
Binary files /dev/null and b/others/master-proposal-logo.png differ
diff --git a/others/master-proposal.typ b/others/master-proposal.typ
new file mode 100644
index 0000000..60c5c5b
--- /dev/null
+++ b/others/master-proposal.typ
@@ -0,0 +1,151 @@
+#import "style.typ": 字体, 字号
+
+#let table-stroke = 0.5pt
+
+#set page(numbering: "1")
+#set par(leading: 1em)
+
+// 封面
+#{
+ set align(center)
+
+ v(5em)
+
+ image("master-proposal-logo.png", width: 7cm)
+
+ v(2em)
+
+ par(text(
+ font: 字体.黑体,
+ size: 字号.一号,
+ weight: "bold",
+ "某某学院\n硕士研究生论文开题报告",
+ ))
+
+ v(8em)
+
+ let info-inset = 0pt
+
+ let info-key(body) = {
+ rect(
+ width: 100%,
+ inset: info-inset,
+ stroke: none,
+ text(font: 字体.黑体, size: 字号.三号, weight: "bold", body),
+ )
+ }
+
+ let info-value(body) = {
+ set align(center)
+ rect(
+ width: 100%,
+ inset: info-inset,
+ stroke: (bottom: table-stroke + black),
+ text(
+ font: 字体.黑体,
+ size: 字号.三号,
+ bottom-edge: "descender",
+ body,
+ ),
+ )
+ }
+
+ grid(
+ columns: (100pt, 150pt),
+ column-gutter: -5pt,
+ row-gutter: 15pt,
+ info-key[论 文 题 目], info-value[题目],
+ info-key[作 者 姓 名], info-value[张三],
+ info-key[作 者 学 号], info-value[1234567890],
+ info-key[专 业 名 称], info-value[某专业],
+ info-key[研 究 方 向], info-value[某方向],
+ info-key[指 导 教 师], info-value[李四],
+ )
+
+ v(10em)
+
+ text(
+ font: 字体.黑体,
+ size: 字号.三号,
+ weight: "bold",
+ "二0 年 月 日",
+ )
+}
+
+#pagebreak(weak: true)
+
+// 主体
+#align(center, text(
+ font: 字体.黑体,
+ size: 字号.三号,
+ weight: "bold",
+ "开题报告",
+))
+
+#set text(font: 字体.宋体, size: 字号.五号)
+#set underline(offset: 0.1em)
+
+#table(
+ columns: (2em, 1fr),
+ align: (center + horizon, auto),
+ stroke: table-stroke,
+ inset: 10pt,
+)[
+ 题 \ \ 目
+][
+ #v(5em)
+][
+ 题 \ \ 目 \ \ 来 \ \ 源 \ \ 及 \ \ 类 \ \ 型
+][
+ #v(25em)
+][
+ 研 \ \ 究 \ \ 背 \ \ 景 \ \ 及 \ \ 意 \ \ 义
+][
+ #v(1fr)
+][
+ 国 \ \ 内 \ \ 外 \ \ 现 \ \ 状 \ \ 及 \ \ 分 \ \ 析
+][
+ #v(1fr)
+][
+ 研 \ \ 究 \ \ 目 \ \ 标 \ \ 、 \ \ 研 \ \ 究 \ \ 内 \ \ 容 \ \ 和 \ \ 拟 \ \ 解 \ \ 决 \ \ 的 \ \ 关 \ \ 键 \ \ 问 \ \ 题
+][
+ #v(1fr)
+][
+ 研 \ \ 究 \ \ 方 \ \ 法 \ \ 、 \ \ 设 \ \ 计 \ \ 及 \ \ 试 \ \ 验 \ \ 方 \ \ 案 \ \ 、 \ \ 可 \ \ 行 \ \ 性 \ \ 分 \ \ 析
+][
+ #v(1fr)
+][
+ 计 \ \ 划 \ \ 进 \ \ 度 \ \ 和 \ \ 质 \ \ 量 \ \ 保 \ \ 证
+][
+ #v(1fr)
+][
+ 预 \ \ 期 \ \ 成 \ \ 果 \ \ 与 \ \ 创 \ \ 新 \ \ 点
+][
+ #v(1fr)
+][
+ 参 \ \ 考 \ \ 文 \ \ 献 \ \ ︵ \ \ 不 \ \ 少 \ \ 于 \ \ 20 \ \ 篇 \ \ ︶
+][
+ #v(1fr)
+][
+ 导 \ \ 师 \ \ 意 \ \ 见
+][
+ #v(28em)
+][
+ 考 \ \ 核 \ \ 小 \ \ 组 \ \ 意 \ \ 见 \ \ 及 \ \ 结 \ \ 论
+][
+ #v(20em)
+
+ 是否进入论文写作:#h(1em) 是 #sym.ballot #h(2em) 否 #sym.ballot
+
+ // 是否进入论文写作:#h(1em) 是 #sym.ballot.x #h(2em) 否 #sym.ballot
+
+ #v(5em)
+
+ 签字:#underline(" " * 10) #h(1em) #underline(" " * 10) #h(1em) #underline(" " * 10) #h(1em)
+
+ #v(3em)
+
+ 日期:#h(4em) 年 #h(2em) 月 #h(2em) 日
+]
+
+*注:需向导师提供电子版开题报告,可打印粘贴。*
\ No newline at end of file
diff --git a/others/style.typ b/others/style.typ
new file mode 100644
index 0000000..aa5ce7b
--- /dev/null
+++ b/others/style.typ
@@ -0,0 +1,35 @@
+#let 字号 = (
+ 初号: 42pt,
+ 小初: 36pt,
+ 一号: 26pt,
+ 小一: 24pt,
+ 二号: 22pt,
+ 小二: 18pt,
+ 三号: 16pt,
+ 小三: 15pt,
+ 四号: 14pt,
+ 中四: 13pt,
+ 小四: 12pt,
+ 五号: 10.5pt,
+ 小五: 9pt,
+ 六号: 7.5pt,
+ 小六: 6.5pt,
+ 七号: 5.5pt,
+ 小七: 5pt,
+)
+
+#let 字体 = (
+ // 宋体,属于「有衬线字体」,一般可以等同于英文中的 Serif Font
+ // 这一行分别是「新罗马体(有衬线英文字体)」、「思源宋体(简体)」、「思源宋体」、「宋体(Windows)」、「宋体(MacOS)」
+ 宋体: ("Times New Roman", "Source Han Serif SC", "Source Han Serif", "Noto Serif CJK SC", "SimSun", "Songti SC", "STSongti"),
+ // 黑体,属于「无衬线字体」,一般可以等同于英文中的 Sans Serif Font
+ // 这一行分别是「Arial(无衬线英文字体)」、「思源黑体(简体)」、「思源黑体」、「黑体(Windows)」、「黑体(MacOS)」
+ 黑体: ("Arial", "Source Han Sans SC", "Source Han Sans", "Noto Sans CJK SC", "SimHei", "Heiti SC", "STHeiti"),
+ // 楷体
+ 楷体: ("Times New Roman", "KaiTi", "Kaiti SC", "STKaiti", "FZKai-Z03S"),
+ // 仿宋
+ 仿宋: ("Times New Roman", "FangSong", "FangSong SC", "STFangSong", "FZFangSong-Z02S"),
+ // 等宽字体,用于代码块环境,一般可以等同于英文中的 Monospaced Font
+ // 这一行分别是「Courier New(Windows 等宽英文字体)」、「思源等宽黑体(简体)」、「思源等宽黑体」、「黑体(Windows)」、「黑体(MacOS)」
+ 等宽: ("Courier New", "Menlo", "IBM Plex Mono", "Source Han Sans HW SC", "Source Han Sans HW", "Noto Sans Mono CJK SC", "SimHei", "Heiti SC", "STHeiti"),
+)
\ No newline at end of file
diff --git a/pages/acknowledgement.typ b/pages/acknowledgement.typ
new file mode 100644
index 0000000..28f2ba2
--- /dev/null
+++ b/pages/acknowledgement.typ
@@ -0,0 +1,19 @@
+// 致谢页
+#let acknowledgement(
+ // documentclass 传入参数
+ anonymous: false,
+ twoside: false,
+ // 其他参数
+ title: "致谢",
+ outlined: true,
+ body,
+) = {
+ if (not anonymous) {
+ pagebreak(weak: true, to: if twoside { "odd" })
+ [
+ #heading(level: 1, numbering: none, outlined: outlined, title)
+
+ #body
+ ]
+ }
+}
\ No newline at end of file
diff --git a/pages/bachelor-abstract-en.typ b/pages/bachelor-abstract-en.typ
new file mode 100644
index 0000000..671d9a8
--- /dev/null
+++ b/pages/bachelor-abstract-en.typ
@@ -0,0 +1,37 @@
+#import "../utils/style.typ": 字号, 字体
+#import "../utils/invisible-heading.typ": invisible-heading
+
+// 本科生英文摘要页
+#let bachelor-abstract-en(
+ // documentclass 传入的参数
+ anonymous: false,
+ twoside: false,
+ fonts: (:),
+ info: (:),
+ // 其他参数
+ keywords: (),
+ outline-title: "ABSTRACT",
+ outlined: false,
+ body,
+) = {
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ [
+ #set text(size: 字号.小四)
+
+ // 标记一个不可见的标题用于目录生成
+ #invisible-heading(level: 1, outlined: outlined, outline-title)
+
+ #align(center)[
+ #set text(size: 字号.三号, weight: "bold")
+ [ABSTRACT]
+ #v(1em)
+ ]
+
+ #body
+
+ #v(1em)
+
+ *Keywords:* #keywords.join(", ")
+ ]
+}
diff --git a/pages/bachelor-abstract.typ b/pages/bachelor-abstract.typ
new file mode 100644
index 0000000..811ba86
--- /dev/null
+++ b/pages/bachelor-abstract.typ
@@ -0,0 +1,40 @@
+#import "../utils/style.typ": 字号, 字体
+#import "../utils/invisible-heading.typ": invisible-heading
+
+// 本科生中文摘要页
+#let bachelor-abstract(
+ // documentclass 传入的参数
+ twoside: false,
+ fonts: (:),
+ info: (:),
+ // 其他参数
+ keywords: (),
+ outline-title: "中文摘要",
+ outlined: false,
+ body,
+) = {
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ [
+ #set text(font: fonts.宋体, size: 字号.小四)
+
+ // 标记一个不可见的标题用于目录生成
+ #invisible-heading(level: 1, outlined: outlined, outline-title)
+
+ #align(center)[
+ #set text(size: 字号.三号, font: fonts.黑体)
+ 【摘#h(1em)要】
+ #v(1em)
+ ]
+
+ #[
+ #set par(first-line-indent: 2em)
+
+ #body
+ ]
+
+ #v(1em)
+
+ *关键词:*#keywords.join(",")
+ ]
+}
diff --git a/pages/bachelor-cover.typ b/pages/bachelor-cover.typ
new file mode 100644
index 0000000..fc7f355
--- /dev/null
+++ b/pages/bachelor-cover.typ
@@ -0,0 +1,175 @@
+#import "../utils/datetime-display.typ": datetime-display
+#import "../utils/style.typ": 字号, 字体, sysucolor
+
+// 本科生封面
+#let bachelor-cover(
+ // documentclass 传入的参数
+ twoside: false,
+ fonts: (:),
+ info: (:),
+ // 其他参数
+ stoke-width: 0.5pt,
+ min-title-lines: 2,
+ info-inset: (x: 0pt, bottom: 1pt),
+ info-key-width: 72pt,
+ info-key-font: "黑体",
+ info-value-font: "宋体",
+ column-gutter: -3pt,
+ row-gutter: 11.5pt,
+ bold-info-keys: ("title",),
+ bold-level: "bold",
+) = {
+ // 1. 默认参数
+ fonts = 字体 + fonts
+ info = (
+ title: ("基于 Typst 的", "中山大学学位论文模板"),
+ grade: "20XX",
+ student-id: "1234567890",
+ author: "张三",
+ department: "某学院",
+ major: "某专业",
+ supervisor: ("李四", "教授"),
+ submit-date: datetime.today(),
+ ) + info
+
+ // 2. 对参数进行处理
+ // 2.1 如果是字符串,则使用换行符将标题分隔为列表
+ if type(info.title) == str {
+ info.title = info.title.split("\n")
+ }
+ // 2.2 根据 min-title-lines 填充标题
+ info.title = info.title + range(min-title-lines - info.title.len()).map((it) => " ")
+ // 2.3 处理提交日期
+ if type(info.submit-date) == datetime {
+ info.submit-date = datetime-display(info.submit-date)
+ }
+
+ // 3. 内置辅助函数
+ let info-key(
+ font: fonts.at(info-key-font, default: "黑体"),
+ size: 字号.小三,
+ body,
+ ) = {
+ rect(
+ width: 100%,
+ inset: info-inset,
+ stroke: none,
+ text(
+ font: font,
+ size: size,
+ body,
+ ),
+ )
+ }
+
+ let info-value(
+ font: fonts.at(info-value-font, default: "宋体"),
+ size: 字号.小三,
+ key,
+ body,
+ ) = {
+ set align(center)
+ rect(
+ width: 100%,
+ inset: info-inset,
+ stroke: (bottom: stoke-width + black),
+ text(
+ font: font,
+ size: size,
+ weight: if (key in bold-info-keys) { bold-level } else { "regular" },
+ bottom-edge: "descender",
+ body,
+ ),
+ )
+ }
+
+ let info-long-value(
+ font: fonts.at(info-value-font, default: "宋体"),
+ size: 字号.小三,
+ key,
+ body,
+ ) = {
+ grid.cell(colspan: 3,
+ info-value(
+ font: font,
+ size: size,
+ key,
+ body,
+ )
+ )
+ }
+
+ let info-short-value(
+ font: fonts.at(info-value-font, default: "宋体"),
+ size: 字号.小三,
+ key,
+ body
+ ) = {
+ info-value(
+ font: font,
+ size: size,
+ key,
+ body,
+ )
+ }
+
+
+ // 4. 正式渲染
+
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ // 居中对齐
+ set align(center)
+
+ // 封面校徽
+ // 使用校方官方 VI 系统的 logo,来源:https://home3.sysu.edu.cn/sysuvi/index.html
+ image("../assets/vi/sysu_logo.svg", width: 3cm)
+
+ text(size: 字号.小初, font: 字体.宋体, weight: "bold", fill: sysucolor.green)[本科生毕业论文(设计)]
+ v(-2em)
+ line(length: 200%, stroke: 0.12cm + sysucolor.green);
+ v(-0.8em)
+ line(length: 200%, stroke: 0.05cm + sysucolor.green);
+ v(1.5cm)
+
+ // 论文题目
+ h(0.7cm)
+ block(width: 100%, grid(
+ columns: (25%, 1fr, 75%, 1fr),
+ column-gutter: column-gutter,
+ row-gutter: row-gutter,
+ info-key(size: 字号.二号, "题目:"),
+ ..info.title.map((s) =>
+ info-long-value(size: 字号.二号, font: 字体.黑体, "title", s)
+ ).intersperse(info-key(size: 字号.二号, " ")),
+ ))
+ v(2.7cm)
+
+ // 学生与指导老师信息
+ set align(center + bottom)
+ block(width: 75%, grid(
+ columns: (info-key-width, 1fr, info-key-width, 1fr),
+ column-gutter: column-gutter,
+ row-gutter: row-gutter,
+ info-key("姓 名"),
+ info-long-value("author", info.author),
+ info-key("学 号"),
+ info-long-value("student-id", info.student-id),
+ info-key("院 系"),
+ info-long-value("department", info.department),
+ info-key("专 业"),
+ info-long-value("major", info.major),
+ info-key("指导教师"),
+ info-long-value("supervisor", text[ #info.supervisor.at(0) #info.supervisor.at(1) ]),
+ // info-short-value("supervisor", info.supervisor.at(0)),
+ // info-key("职 称"),
+ // info-short-value("supervisor", info.supervisor.at(1)),
+ ..(if info.supervisor-ii != () {(
+ info-key("第二导师"),
+ info-long-value("supervisor", text[ #info.supervisor-ii.at(0) #info.supervisor-ii.at(1) ]),
+ )} else {()}),
+ ))
+ v(2em)
+
+ text(font: 字体.黑体, size: 字号.小四)[#info.submit-date]
+}
diff --git a/pages/bachelor-decl-page.typ b/pages/bachelor-decl-page.typ
new file mode 100644
index 0000000..8d364ad
--- /dev/null
+++ b/pages/bachelor-decl-page.typ
@@ -0,0 +1,32 @@
+#import "../utils/indent.typ": indent
+#import "../utils/style.typ": 字号, 字体
+
+// 本科生声明页
+#let bachelor-decl-page(
+ anonymous: false,
+ twoside: false,
+ fonts: (:),
+ info: (:),
+) = {
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ align(center, text(font: 字体.黑体, size: 字号.三号)[学术诚信声明])
+
+ set text(font: 字体.宋体, size: 字号.小四)
+
+ par(justify: true, first-line-indent: 2em)[
+ 本人郑重声明:所呈交的毕业论文(设计),是本人在导师的指导下,独立进行研究工作所取得的成果。除文中已经注明引用的内容外,本论文(设计)不包含任何其他个人或集体已经发表或撰写过的作品成果。对本论文(设计)的研究做出重要贡献的个人和集体,均已在文中以明确方式标明。本论文(设计)的知识产权归属于培养单位。本人完全意识到本声明的法律结果由本人承担。
+ ]
+ v(2em)
+
+ align(right + top,
+ box(
+ grid(
+ columns: (auto, auto),
+ gutter: 2em,
+ "作者签名:", "",
+ "日" + h(2em) + "期:", h(2.5em) + text("年") + h(2em) + text("月") + h(2em) + text("日")
+ )
+ )
+ )
+}
diff --git a/pages/bachelor-outline-page.typ b/pages/bachelor-outline-page.typ
new file mode 100644
index 0000000..abac887
--- /dev/null
+++ b/pages/bachelor-outline-page.typ
@@ -0,0 +1,79 @@
+#import "@preview/outrageous:0.1.0"
+#import "../utils/invisible-heading.typ": invisible-heading
+#import "../utils/style.typ": 字号, 字体
+
+// 本科生目录生成
+#let bachelor-outline-page(
+ // documentclass 传入参数
+ twoside: false,
+ fonts: (:),
+ // 其他参数
+ depth: 4,
+ title: "目 录",
+ outlined: false,
+ title-vspace: 0pt,
+ title-text-args: auto,
+ // 引用页数的字体,这里用于显示 Times New Roman
+ reference-font: auto,
+ reference-size: 字号.小四,
+ // 字体与字号
+ font: auto,
+ size: (字号.小四, 字号.小四),
+ // 垂直间距
+ vspace: (25pt, 14pt),
+ indent: (0pt, 18pt, 28pt),
+ // 全都显示点号
+ fill: (auto,),
+ ..args,
+) = {
+ // 1. 默认参数
+ fonts = 字体 + fonts
+ if (title-text-args == auto) {
+ title-text-args = (font: fonts.黑体, size: 字号.三号, weight: "bold")
+ }
+ // 引用页数的字体,这里用于显示 Times New Roman
+ if (reference-font == auto) {
+ reference-font = fonts.宋体
+ }
+ // 字体与字号
+ if (font == auto) {
+ font = (fonts.宋体, fonts.宋体)
+ }
+
+ // 2. 正式渲染
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ // 默认显示的字体
+ set text(font: reference-font, size: reference-size)
+
+ {
+ set align(center)
+ text(..title-text-args, title)
+ // 标记一个不可见的标题用于目录生成
+ invisible-heading(level: 1, outlined: outlined, title)
+ }
+
+ v(title-vspace)
+
+ show outline.entry: outrageous.show-entry.with(
+ // 保留 Typst 基础样式
+ ..outrageous.presets.typst,
+ body-transform: (level, it) => {
+ // 设置字体和字号
+ set text(
+ font: font.at(calc.min(level, font.len()) - 1),
+ size: size.at(calc.min(level, size.len()) - 1),
+ )
+ // 计算缩进
+ let indent-list = indent + range(level - indent.len()).map((it) => indent.last())
+ let indent-length = indent-list.slice(0, count: level).sum()
+ h(indent-length) + it
+ },
+ vspace: vspace,
+ fill: fill,
+ ..args,
+ )
+
+ // 显示目录
+ outline(title: none, depth: depth)
+}
diff --git a/pages/bachelor-title-page.typ b/pages/bachelor-title-page.typ
new file mode 100644
index 0000000..280aa8b
--- /dev/null
+++ b/pages/bachelor-title-page.typ
@@ -0,0 +1,159 @@
+#import "../utils/datetime-display.typ": datetime-display
+#import "../utils/style.typ": 字号, 字体
+
+// 本科生论文扉页
+#let bachelor-title-page(
+ // documentclass 传入的参数
+ twoside: false,
+ fonts: (:),
+ info: (:),
+ // 其他参数
+ stoke-width: 0.5pt,
+ min-title-lines: 2,
+ info-inset: (x: 0pt, bottom: 1pt),
+ info-key-width: 72pt,
+ info-key-font: "黑体",
+ info-value-font: "宋体",
+ column-gutter: -3pt,
+ row-gutter: 11.5pt,
+) = {
+ // 1. 默认参数
+ fonts = 字体 + fonts
+ info = (
+ title: ("基于 Typst 的", "中山大学学位论文模板"),
+ title-en: "A Typst Template for SYSU thesis",
+ grade: "20XX",
+ student-id: "1234567890",
+ author: "张三",
+ department: "某学院",
+ major: "某专业",
+ supervisor: ("李四", "教授"),
+ submit-date: datetime.today(),
+ ) + info
+
+ // 2. 对参数进行处理
+ // 2.1 如果是字符串,则使用换行符将标题分隔为列表
+ if type(info.title) == str {
+ info.title = info.title.split("\n")
+ }
+ if type(info.title-en) == str {
+ info.title-en = info.title-en.split("\n")
+ }
+ // 2.2 根据 min-title-lines 填充标题
+ info.title = info.title + range(min-title-lines - info.title.len()).map((it) => " ")
+ // 2.3 处理提交日期
+ if type(info.submit-date) == datetime {
+ info.submit-date = datetime-display(info.submit-date)
+ }
+
+ // 3. 内置辅助函数
+ let info-key(
+ font: fonts.at(info-key-font, default: "黑体"),
+ size: 字号.三号,
+ body,
+ ) = {
+ rect(
+ width: 100%,
+ inset: info-inset,
+ stroke: none,
+ text(
+ font: font,
+ size: size,
+ body,
+ ),
+ )
+ }
+
+ let info-value(
+ font: fonts.at(info-value-font, default: "宋体"),
+ size: 字号.三号,
+ key,
+ body,
+ ) = {
+ set align(center)
+ rect(
+ width: 100%,
+ inset: info-inset,
+ stroke: (bottom: stoke-width + black),
+ text(
+ font: font,
+ size: size,
+ bottom-edge: "descender",
+ body,
+ ),
+ )
+ }
+
+ let info-long-value(
+ font: fonts.at(info-value-font, default: "宋体"),
+ size: 字号.三号,
+ key,
+ body,
+ ) = {
+ grid.cell(colspan: 3,
+ info-value(
+ font: font,
+ size: size,
+ key,
+ body,
+ )
+ )
+ }
+
+ let info-short-value(
+ font: fonts.at(info-value-font, default: "宋体"),
+ size: 字号.三号,
+ key,
+ body
+ ) = {
+ info-value(
+ font: font,
+ size: size,
+ key,
+ body,
+ )
+ }
+
+ // 4. 正式渲染
+ pagebreak(weak: true, to: if twoside { "odd" })
+ v(30pt)
+
+ set align(center + horizon)
+ for part in info.title {
+ text(size: 字号.二号, font: 字体.黑体, weight: "bold")[ #part ]
+ linebreak()
+ }
+ v(2em)
+ for part-en in info.title-en {
+ text(size: 字号.二号, weight: "bold")[ #part-en ]
+ linebreak()
+ }
+
+ // 学生与指导老师信息
+ set align(center + bottom)
+ block(width: 75%, grid(
+ columns: (info-key-width, 1fr, info-key-width, 1fr),
+ column-gutter: column-gutter,
+ row-gutter: row-gutter,
+ info-key("姓 名"),
+ info-long-value("author", info.author),
+ info-key("学 号"),
+ info-long-value("student-id", info.student-id),
+ info-key("院 系"),
+ info-long-value("department", info.department),
+ info-key("专 业"),
+ info-long-value("major", info.major),
+ info-key("指导教师"),
+ info-long-value("supervisor", text[ #info.supervisor.at(0) #info.supervisor.at(1) ]),
+ // info-short-value("supervisor", info.supervisor.at(0)),
+ // info-key("职 称"),
+ // info-short-value("supervisor", info.supervisor.at(1)),
+ ..(if info.supervisor-ii != () {(
+ info-key("第二导师"),
+ info-long-value("supervisor", text[ #info.supervisor-ii.at(0) #info.supervisor-ii.at(1) ]),
+ )} else {()}),
+ ))
+ v(2em)
+
+ text(font: 字体.黑体, size: 字号.四号)[#info.submit-date]
+}
diff --git a/pages/fonts-display-page.typ b/pages/fonts-display-page.typ
new file mode 100644
index 0000000..5b53249
--- /dev/null
+++ b/pages/fonts-display-page.typ
@@ -0,0 +1,54 @@
+#import "../utils/style.typ": 字号, 字体
+#import "../utils/hline.typ": hline
+
+// 字体显示测试页
+#let fonts-display-page(
+ twoside: false,
+ fonts: (:),
+ size: 字号.小四,
+ lang: "zh",
+) = {
+ // 1. 默认参数
+ fonts = 字体 + fonts
+
+ // 2. 辅助函数
+ let display-font(cjk-name, latin-name) = [
+ #set text(font: fonts.at(cjk-name))
+
+ #cjk-name (#latin-name CJK Regular): 落霞与孤鹜齐飞,秋水共长天一色。
+
+ #cjk-name (#latin-name Latin Regular): The fanfare of birds announces the morning.
+
+ *#cjk-name (#latin-name CJK Bold): 落霞与孤鹜齐飞,秋水共长天一色。*
+
+ *#cjk-name (#latin-name Latin Bold): The fanfare of birds announces the morning.*
+ ]
+
+ // 3. 正式渲染
+ pagebreak(weak: true, to: if twoside { "odd" })
+ set text(size: size, lang: lang)
+
+ [
+ *Fonts Display Page:*
+
+ #hline()
+
+ #display-font("宋体", "SongTi")
+
+ #hline()
+
+ #display-font("黑体", "HeiTi")
+
+ #hline()
+
+ #display-font("楷体", "KaiTi")
+
+ #hline()
+
+ #display-font("仿宋", "FangSong")
+
+ #hline()
+
+ #display-font("等宽", "Mono")
+ ]
+}
\ No newline at end of file
diff --git a/pages/list-of-figures.typ b/pages/list-of-figures.typ
new file mode 100644
index 0000000..2eb17cc
--- /dev/null
+++ b/pages/list-of-figures.typ
@@ -0,0 +1,74 @@
+#import "@preview/i-figured:0.2.4"
+#import "@preview/outrageous:0.1.0"
+#import "../utils/invisible-heading.typ": invisible-heading
+#import "../utils/style.typ": 字号, 字体
+
+// 表格目录生成
+#let list-of-figures(
+ // documentclass 传入参数
+ twoside: false,
+ fonts: (:),
+ // 其他参数
+ title: "插图目录",
+ outlined: false,
+ title-vspace: 32pt,
+ title-text-args: auto,
+ // caption 的 separator
+ separator: " ",
+ // 字体与字号
+ font: auto,
+ size: 字号.小四,
+ // 垂直间距
+ vspace: 14pt,
+ // 是否显示点号
+ fill: auto,
+ ..args,
+) = {
+ // 1. 默认参数
+ fonts = 字体 + fonts
+ if (title-text-args == auto) {
+ title-text-args = (font: fonts.黑体, size: 字号.三号, weight: "bold")
+ }
+ // 字体与字号
+ if (font == auto) {
+ font = fonts.宋体
+ }
+
+ // 2. 正式渲染
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ // 默认显示的字体
+ set text(font: font, size: size)
+
+ {
+ set align(center)
+ text(..title-text-args, title)
+ // 标记一个不可见的标题用于目录生成
+ invisible-heading(level: 1, outlined: outlined, title)
+ }
+
+ v(title-vspace)
+
+ show outline.entry: outrageous.show-entry.with(
+ // 保留 Typst 基础样式
+ ..outrageous.presets.typst,
+ body-transform: (level, it) => {
+ // 因为好像没找到 separator 的参数,所以这里就手动寻找替换了
+ if (it.has("children") and it.children.at(3, default: none) == [#": "]) {
+ it.children.slice(0, 3).sum() + separator + it.children.slice(4).sum()
+ } else {
+ it
+ }
+ },
+ vspace: (vspace,),
+ fill: (fill,),
+ )
+
+ // 显示目录
+ i-figured.outline(target-kind: image, title: none)
+
+ // 手动分页
+ if (twoside) {
+ pagebreak() + " "
+ }
+}
diff --git a/pages/list-of-tables.typ b/pages/list-of-tables.typ
new file mode 100644
index 0000000..949e47f
--- /dev/null
+++ b/pages/list-of-tables.typ
@@ -0,0 +1,74 @@
+#import "@preview/i-figured:0.1.0"
+#import "@preview/outrageous:0.1.0"
+#import "../utils/invisible-heading.typ": invisible-heading
+#import "../utils/style.typ": 字号, 字体
+
+// 表格目录生成
+#let list-of-tables(
+ // documentclass 传入参数
+ twoside: false,
+ fonts: (:),
+ // 其他参数
+ title: "表格目录",
+ outlined: false,
+ title-vspace: 32pt,
+ title-text-args: auto,
+ // caption 的 separator
+ separator: " ",
+ // 字体与字号
+ font: auto,
+ size: 字号.小四,
+ // 垂直间距
+ vspace: 14pt,
+ // 是否显示点号
+ fill: auto,
+ ..args,
+) = {
+ // 1. 默认参数
+ fonts = 字体 + fonts
+ if (title-text-args == auto) {
+ title-text-args = (font: fonts.黑体, size: 字号.三号, weight: "bold")
+ }
+ // 字体与字号
+ if (font == auto) {
+ font = fonts.宋体
+ }
+
+ // 2. 正式渲染
+ pagebreak(weak: true, to: if twoside { "odd" })
+
+ // 默认显示的字体
+ set text(font: font, size: size)
+
+ {
+ set align(center)
+ text(..title-text-args, title)
+ // 标记一个不可见的标题用于目录生成
+ invisible-heading(level: 1, outlined: outlined, title)
+ }
+
+ v(title-vspace)
+
+ show outline.entry: outrageous.show-entry.with(
+ // 保留 Typst 基础样式
+ ..outrageous.presets.typst,
+ body-transform: (level, it) => {
+ // 因为好像没找到 separator 的参数,所以这里就手动寻找替换了
+ if (it.has("children") and it.children.at(3, default: none) == [#": "]) {
+ it.children.slice(0, 3).sum() + separator + it.children.slice(4).sum()
+ } else {
+ it
+ }
+ },
+ vspace: (vspace,),
+ fill: (fill,),
+ )
+
+ // 显示目录
+ i-figured.outline(target-kind: table, title: none)
+
+ // 手动分页
+ if (twoside) {
+ pagebreak() + " "
+ }
+}
diff --git a/pages/notation.typ b/pages/notation.typ
new file mode 100644
index 0000000..2b8ac40
--- /dev/null
+++ b/pages/notation.typ
@@ -0,0 +1,35 @@
+#let notation(
+ twoside: false,
+ title: "符号表",
+ outlined: true,
+ width: 350pt,
+ columns: (60pt, 1fr),
+ row-gutter: 16pt,
+ ..args,
+ body,
+) = {
+ heading(
+ level: 1,
+ numbering: none,
+ outlined: outlined,
+ title
+ )
+
+ align(center, block(width: width,
+ align(start, grid(
+ columns: columns,
+ row-gutter: row-gutter,
+ ..args,
+ // 解析 terms 内部结构以渲染到表格里
+ ..body.children
+ .filter(it => it.func() == terms.item)
+ .map(it => (it.term, it.description))
+ .flatten()
+ ))
+ ))
+
+ // 手动分页
+ if (twoside) {
+ pagebreak() + " "
+ }
+}
\ No newline at end of file
diff --git a/template.typ b/template.typ
index 97653c8..a5a3fb0 100644
--- a/template.typ
+++ b/template.typ
@@ -241,7 +241,7 @@
}
} else if el.func() == heading {
// Handle headings
- if el.level == 1 {
+ if el.level == 1 {
link(el_loc, chinesenumbering(..counter(heading).at(el_loc), location: el_loc))
} else {
link(el_loc, [
@@ -266,7 +266,7 @@
set align(left + top)
set text(字号.小四)
- include "templates/declaration.typ"
+ include "templates/declaration.typ"
locate(loc => {
if alwaysstartodd {
@@ -275,7 +275,7 @@
})
include "templates/abstract-cn.typ"
-
+
pagebreak()
locate(loc => {
@@ -319,7 +319,7 @@
if not blind {
par(justify: true, first-line-indent: 2em, leading: 行距)[
#heading(numbering: none, "致谢")
-
+
#include "chapters/acknowledgement.typ"
]
diff --git a/template/images/sysu_logo.svg b/template/images/sysu_logo.svg
new file mode 100644
index 0000000..1c14f8e
--- /dev/null
+++ b/template/images/sysu_logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/template/ref.bib b/template/ref.bib
new file mode 100644
index 0000000..ac49843
--- /dev/null
+++ b/template/ref.bib
@@ -0,0 +1,94 @@
+@book{蒋有绪1998,
+ title={中国森林群落分类及其群落学特征},
+ author={蒋有绪 and 郭泉水 and 马娟 and others},
+ year={1998},
+ publisher={科学出版社},
+ address={北京},
+ pages={11-12},
+}
+
+@inproceedings{中国力学学会1990,
+ title={第3届全国实验流体力学学术会议论文集},
+ author={中国力学学会},
+ year={1990},
+ address={天津},
+ publisher={**出版社},
+ pages={20-24}
+}
+
+@techreport{WHO1970,
+ title={World Health Organization. Factors Regulating the Immune Response: Report of WHO Scientific Group},
+ year={1970},
+ institution={WHO},
+ address={Geneva}
+}
+
+@phdthesis{张志祥1998,
+ author = {张志祥},
+ title = {间断动力系统的随机扰动及其在守恒律方程中的应用},
+ school = {北京大学数学学院},
+ address = {北京},
+ year = {1998},
+ pages = {50-55}
+}
+
+@patent{河北绿洲2001,
+ title={一种荒漠化地区生态植被综合培育种植方法:中国,01129210.5},
+ author={河北绿洲生态环境科技有限公司},
+ year={2001},
+ type={patent},
+ number={01129210.5},
+ note={2001-10-24[2002-05-28]},
+ url={http://211.152.9.47/sipoasp/zlijs/hyjs-yxnew. asp?recid=01129210.5&leixin}
+}
+
+@standard{GB/T2659-1986,
+ title = {世界各国和地区名称代码},
+ author = {国家标准局信息分类编码研究所},
+ year = {1986},
+ howpublished = {全国文献工作标准化技术委员会. 文献工作国家标准汇编:3.北京:中国标准出版社,1988:59-92.},
+ note = {GB/T 2659-1986}
+}
+
+
+@article{李炳穆2000,
+ title={理想的图书馆员和信息专家的素质与形象},
+ author={李炳穆},
+ journal={图书情报工作},
+ year={2000},
+ volume={2000},
+ number={2},
+ pages={5-8}
+}
+
+@article{丁文祥2000,
+ title={数字革命与竞争国际化},
+ author={丁文祥},
+ journal={中国青年报},
+ year={2000},
+ month={11-20},
+ number={15}
+}
+
+@article{江向东1999,
+ title={互联网环境下的信息处理与图书管理系统解决方案},
+ author={江向东},
+ journal={情报学报},
+ year={1999},
+ volume={18},
+ number={2},
+ pages={4},
+ note={2000-01-18},
+ url={http://www.chinainfo.gov.cn/periodical/gbxb/gbxb99/gbxb990203}
+}
+
+@article{CHRISTINE1998,
+ title={Plant physiology:plant biology in the Genome Era},
+ author={M CHRISTINE},
+ journal={Science},
+ year={1998},
+ volume={281},
+ pages={331-332},
+ note={1998-09-23},
+ url={http://www.sciencemag.org/cgi/collection/anatmorp}
+}
\ No newline at end of file
diff --git a/template/thesis.typ b/template/thesis.typ
new file mode 100644
index 0000000..8ae10b1
--- /dev/null
+++ b/template/thesis.typ
@@ -0,0 +1,240 @@
+// TODO: 待正式发布时替换成可用的版本
+// 建议在正式编辑论文时,采用 typst.app 中的已发布版本模板
+// #import "@preview/morden-sysu-thesis:0.1.0": documentclass
+
+// 仅供开发调试使用
+#import "../lib.typ": documentclass
+
+// 你首先应该安装 https://gitlab.com/sysu-gitlab/thesis-template/better-thesis/-/tree/main/fonts 里的所有字体,
+// 如果是 Web App 上编辑,你应该手动上传这些字体文件,否则不能正常使用「楷体」和「仿宋」,导致显示错误。
+
+#let (
+ // 布局函数
+ twoside, doc, preface, mainmatter, mainmatter-end, appendix,
+ // 页面函数
+ fonts-display-page, cover, title-page, decl-page, abstract, abstract-en, bilingual-bibliography,
+ outline-page, list-of-figures, list-of-tables, notation, acknowledgement,
+) = documentclass(
+ // doctype: "bachelor", // "bachelor" | "master" | "doctor" | "postdoc", 文档类型,默认为本科生 bachelor
+ // degree: "academic", // "academic" | "professional", 学位类型,默认为学术型 academic
+ // anonymous: true, // 盲审模式
+ twoside: false, // 双面模式,会加入空白页,便于打印
+ // 可自定义字体,先英文字体后中文字体,应传入「宋体」、「黑体」、「楷体」、「仿宋」、「等宽」
+ // fonts: (楷体: ("Times New Roman", "FZKai-Z03S")),
+ info: (
+ title: ("基于 Typst 的", "中山大学学位论文模板"),
+ title-en: "A Typst Template for SYSU thesis",
+ grade: "20XX",
+ student-id: "1234567890",
+ author: "张三",
+ author-en: "Ming Xing",
+ department: "某学院",
+ department-en: "School of Chemistry and Chemical Engineering",
+ major: "某专业",
+ major-en: "Chemistry",
+ supervisor: ("李四", "教授"),
+ supervisor-en: "Professor My Supervisor",
+ // supervisor-ii: ("王五", "副教授"),
+ // supervisor-ii-en: "Professor My Supervisor",
+ submit-date: datetime.today(),
+ ),
+ // 参考文献源
+ bibliography: bibliography.with("ref.bib"),
+)
+
+// 文稿设置
+#show: doc
+
+// 字体展示测试页
+#fonts-display-page()
+
+// 封面页
+#cover()
+
+// 扉页
+#title-page()
+
+// 声明页
+#decl-page()
+
+
+// 前言
+#show: preface
+
+// 中文摘要
+#abstract(
+ keywords: ("我", "就是", "测试用", "关键词")
+)[
+ 中文摘要
+]
+
+// 英文摘要
+#abstract-en(
+ keywords: ("Dummy", "Keywords", "Here", "It Is")
+)[
+ English abstract
+]
+
+
+// 目录
+#outline-page()
+
+// 插图目录
+// #list-of-figures()
+
+// 表格目录
+// #list-of-tables()
+
+// 正文
+#show: mainmatter
+
+// 符号表
+// #notation[
+// / DFT: 密度泛函理论 (Density functional theory)
+// / DMRG: 密度矩阵重正化群密度矩阵重正化群密度矩阵重正化群 (Density-Matrix Reformation-Group)
+// ]
+
+= 导 论
+
+== 列表
+
+=== 无序列表
+
+- 无序列表项一
+- 无序列表项二
+ - 无序子列表项一
+ - 无序子列表项二
+
+=== 有序列表
+
++ 有序列表项一
++ 有序列表项二
+ + 有序子列表项一
+ + 有序子列表项二
+
+=== 术语列表
+
+/ 术语一: 术语解释
+/ 术语二: 术语解释
+
+== 图表
+
+引用@tbl:timing,引用@tbl:timing-tlt,以及@fig:sysu-logo。引用图表时,表格和图片分别需要加上 `tbl:`和`fig:` 前缀才能正常显示编号。
+
+#align(center, (stack(dir: ltr)[
+ #figure(
+ table(
+ align: center + horizon,
+ columns: 4,
+ [t], [1], [2], [3],
+ [y], [0.3s], [0.4s], [0.8s],
+ ),
+ caption: [常规表],
+ )
+][
+ #h(50pt)
+][
+ #figure(
+ table(
+ columns: 4,
+ stroke: none,
+ table.hline(),
+ [t], [1], [2], [3],
+ table.hline(stroke: .5pt),
+ [y], [0.3s], [0.4s], [0.8s],
+ table.hline(),
+ ),
+ caption: [三线表],
+ )
+]))
+
+#figure(
+ image("images/sysu_logo.svg", width: 20%),
+ caption: [图片测试],
+)
+
+
+== 数学公式
+
+可以像 Markdown 一样写行内公式 $x + y$,以及带编号的行间公式:
+
+$ phi.alt := (1 + sqrt(5)) / 2 $
+
+引用数学公式需要加上 `eqt:` 前缀,则由@eqt:ratio,我们有:
+
+$ F_n = floor(1 / sqrt(5) phi.alt^n) $
+
+我们也可以通过 `<->` 标签来标识该行间公式不需要编号
+
+$ y = integral_1^2 x^2 dif x $ <->
+
+而后续数学公式仍然能正常编号。
+
+$ F_n = floor(1 / sqrt(5) phi.alt^n) $
+
+== 参考文献
+
+可以像这样引用参考文献:图书#[@蒋有绪1998]和会议#[@中国力学学会1990]。
+
+== 代码块
+
+代码块支持语法高亮。引用时需要加上 `lst:` @lst:code
+
+#figure(
+ ```py
+ def add(x, y):
+ return x + y
+ ```,
+ caption:[代码块],
+)
+
+
+= 正 文
+
+== 正文子标题
+
+=== 正文子子标题
+
+正文内容
+
+
+// 手动分页
+#if twoside {
+ pagebreak() + " "
+}
+
+// 中英双语参考文献
+// 默认使用 gb-7714-2015-numeric 样式
+#bilingual-bibliography(full: true)
+
+// 致谢
+#acknowledgement[
+ 感谢 NJU-LUG,感谢 NJUThesis LaTeX 模板。
+]
+
+// 手动分页
+#if twoside {
+ pagebreak() + " "
+}
+
+
+// 附录
+#show: appendix
+
+= 附录
+
+== 附录子标题
+
+=== 附录子子标题
+
+附录内容,这里也可以加入图片,例如@fig:appendix-img。
+
+#figure(
+ image("images/sysu_logo.svg", width: 20%),
+ caption: [图片测试],
+)
+
+
+// 正文结束标志,不可缺少
+// 这里放在附录后面,使得页码能正确计数
+#mainmatter-end()
diff --git a/templates/abstract-cn.typ b/templates/abstract-cn.typ
deleted file mode 100644
index c49d672..0000000
--- a/templates/abstract-cn.typ
+++ /dev/null
@@ -1,16 +0,0 @@
-#import "../functions/style.typ": *
-#import "../custom.typ": *
-#import "../info.typ": *
-
-#par(justify: true, first-line-indent: 2em, leading: 行距)[
- #heading(numbering: none, outlined: false, "摘要")
-
- #include "../chapters/abstract-cn.typ"
- #import "../chapters/abstract-cn.typ": 中文关键词
-
- #linebreak()
- #set par(first-line-indent: 0em)
- #text("关键词:", weight: "bold")
- #中文关键词.join(",")
- #v(2em)
-]
\ No newline at end of file
diff --git a/templates/abstract-en.typ b/templates/abstract-en.typ
deleted file mode 100644
index c2694eb..0000000
--- a/templates/abstract-en.typ
+++ /dev/null
@@ -1,27 +0,0 @@
-#import "../functions/style.typ": *
-#import "../custom.typ": *
-#import "../info.typ": *
-
-#par(justify: true, first-line-indent: 2em, leading: 行距)[
- // #[
- // #set text(字号.小二)
- // #set align(center)
- // #strong(论文英文题目)
- // ]
- // #[
- // #set align(center)
- // #英文作者名 \(#英文专业\) \
- // Directed by #英文导师名
- // ]
- #heading(numbering: none, outlined: false, "ABSTRACT")
-
- #include "../chapters/abstract-en.typ"
- #import "../chapters/abstract-en.typ": 英文关键词
-
- #linebreak()
- #set par(first-line-indent: 0em)
- #text("Keywords:", weight: "bold")
- #h(0.5em, weak: true)
- #英文关键词.join(", ")
- #v(2em)
-]
\ No newline at end of file
diff --git a/templates/cover-no-emblem.typ b/templates/cover-no-emblem.typ
deleted file mode 100644
index d981329..0000000
--- a/templates/cover-no-emblem.typ
+++ /dev/null
@@ -1,55 +0,0 @@
-#import "../functions/style.typ": *
-#import "../functions/underline.typ": *
-#import "../functions/hline.typ": *
-#import "../info.typ": *
-#import "../custom.typ": *
-
-#v(30pt)
-
-#set align(center + horizon)
-#strong(论文中文题目)
-
-#v(2em)
-#strong(论文英文题目)
-
-
-#v(60pt)
-#set text(字号.三号)
-
-#let fieldname(name) = [
- #set align(right + top)
- #strong(name)
- #h(0.25em)
-]
-
-#let fieldvalue(value) = [
- #set align(center + horizon)
- #set text(font: 字体.宋体)
- #grid(
- rows: (auto, auto),
- row-gutter: 0.2em,
- value,
- line(length: 100%)
- )
-]
-
-#grid(
- columns: (80pt, 280pt),
- row-gutter: 1em,
- fieldname(text("姓") + h(2em) + text("名")),
- fieldvalue(中文作者名),
- fieldname(text("学") + h(2em) + text("号")),
- fieldvalue(学号),
- fieldname(text("学") + h(2em) + text("院")),
- fieldvalue(学院),
- fieldname(text("专") + h(2em) + text("业")),
- fieldvalue(专业),
- fieldname("研究方向"),
- fieldvalue(方向),
- fieldname("指导教师"),
- fieldvalue(中文导师名),
-)
-
-#v(2em)
-#set text(字号.小二)
-#text(日期)
\ No newline at end of file
diff --git a/templates/cover.typ b/templates/cover.typ
deleted file mode 100644
index fac5ea7..0000000
--- a/templates/cover.typ
+++ /dev/null
@@ -1,74 +0,0 @@
-#import "../functions/style.typ": *
-#import "../functions/underline.typ": *
-#import "../functions/hline.typ": *
-#import "../info.typ": *
-#import "../custom.typ": *
-
-#box(
- grid(
- columns: (auto, auto),
- gutter: 0.4em,
- image("../images/vi/sysu-emblem.svg", height: 3em, fit: "contain")
- )
-)
-#linebreak()
-#v(1em)
-#text("本科生毕业论文(设计)", font: 字体.宋体, size: 字号.小初, weight: "semibold", fill: 强调色)
-#hline(thickness: 3pt)
-#v(-1em)
-#hline(thickness: 1.2pt)
-
-#set text(字号.二号)
-#v(60pt)
-#grid(
- columns: (80pt, 300pt),
- [
- #set align(right + top)
- 题目:
- ],
- [
- #set align(center + horizon)
- #chineseunderline(论文中文题目, width: 300pt, bold: true)
- ]
-)
-
-#v(60pt)
-#set text(字号.三号)
-
-#let fieldname(name) = [
- #set align(right + top)
- #strong(name)
- #h(0.25em)
-]
-
-#let fieldvalue(value) = [
- #set align(center + horizon)
- #set text(font: 字体.宋体)
- #grid(
- rows: (auto, auto),
- row-gutter: 0.2em,
- value,
- line(length: 100%)
- )
-]
-
-#grid(
- columns: (80pt, 280pt),
- row-gutter: 1em,
- fieldname(text("姓") + h(2em) + text("名")),
- fieldvalue(中文作者名),
- fieldname(text("学") + h(2em) + text("号")),
- fieldvalue(学号),
- fieldname(text("学") + h(2em) + text("院")),
- fieldvalue(学院),
- fieldname(text("专") + h(2em) + text("业")),
- fieldvalue(专业),
- fieldname("研究方向"),
- fieldvalue(方向),
- fieldname("指导教师"),
- fieldvalue(中文导师名),
-)
-
-#v(2em)
-#set text(字号.小二)
-#text(日期)
\ No newline at end of file
diff --git a/templates/declaration.typ b/templates/declaration.typ
deleted file mode 100644
index 76ca468..0000000
--- a/templates/declaration.typ
+++ /dev/null
@@ -1,20 +0,0 @@
-#import "../functions/style.typ": *
-#import "../custom.typ": *
-
-#heading(numbering: none, outlined: false, "学术诚信声明")
-#par(justify: true, first-line-indent: 2em, leading: 行距)[
- 本人郑重声明:所呈交的毕业论文(设计),是本人在导师的指导下,独立进行研究工作所取得的成果。除文中已经注明引用的内容外,本论文(设计)不包含任何其他个人或集体已经发表或撰写过的作品成果。对本论文(设计)的研究做出重要贡献的个人和集体,均已在文中以明确方式标明。本论文(设计)的知识产权归属于培养单位。本人完全意识到本声明的法律结果由本人承担。
-]
-
-
-#v(3em)
-#align(right + top,
- box(
- grid(
- columns: (auto, auto),
- gutter: 2em,
- "作者签名:", "",
- "日" + h(2em) + "期:", h(2.5em) + text("年") + h(2em) + text("月") + h(2em) + text("日")
- )
- )
-)
\ No newline at end of file
diff --git a/thumbnail.png b/thumbnail.png
new file mode 100644
index 0000000..aab4141
Binary files /dev/null and b/thumbnail.png differ
diff --git a/typst.toml b/typst.toml
new file mode 100644
index 0000000..b532b79
--- /dev/null
+++ b/typst.toml
@@ -0,0 +1,15 @@
+[package]
+name = "morden-sysu-thesis"
+version = "0.1.0"
+entrypoint = "lib.typ"
+repository = "https://gitlab.com/sysu-gitlab/thesis-template/better-thesis"
+authors = ["howardlau1999 <@howardlau1999>", "Sunny Huang <@huangjj27>"]
+license = "MIT"
+description = "中山大学学位论文 Typst 模板,A Typst template for SYSU thesis"
+keywords = ["SYSU", "thesis", "typst"]
+categories = ["thesis"]
+
+[template]
+path = "template"
+entrypoint = "thesis.typ"
+thumbnail = "thumbnail.png"
diff --git a/utils/bilingual-bibliography.typ b/utils/bilingual-bibliography.typ
new file mode 100644
index 0000000..e3049c0
--- /dev/null
+++ b/utils/bilingual-bibliography.typ
@@ -0,0 +1,131 @@
+// Authors: csimide, OrangeX4
+// Tested only on GB-7714-2015-Numeric
+#let bilingual-bibliography(
+ bibliography: none,
+ title: "参考文献",
+ full: false,
+ style: "gb-7714-2015-numeric",
+ mapping: (:),
+) = {
+ assert(bibliography != none, message: "请传入带有 source 的 bibliography 函数。")
+
+ // Please fill in the remaining mapping table here
+ mapping = (
+ //"等": "et al",
+ "卷": "Vol.",
+ "册": "Bk.",
+ // "译": "tran",
+ // "等译": "et al. tran",
+ // 注: 请见下方译者数量判断部分。
+ ) + mapping
+
+ let to-string(content) = {
+ if content.has("text") {
+ content.text
+ } else if content.has("children") {
+ content.children.map(to-string).join("")
+ } else if content.has("child") {
+ to-string(content.child)
+ } else if content.has("body") {
+ to-string(content.body)
+ } else if content == [ ] {
+ " "
+ }
+ }
+
+ show grid.cell.where(x: 1): it => {
+ // 后续的操作是对 string 进行的。
+ let ittext = to-string(it)
+ // 判断是否为中文文献:去除特定词组后,仍有至少两个连续汉字。
+ let pureittext = ittext.replace(regex("[等卷册和版本章期页篇译间者(不详)]"), "")
+ if pureittext.find(regex("\p{sc=Hani}{2,}")) != none {
+ ittext
+ } else {
+ // 若不是中文文献,进行替换
+ // 第xxx卷、第xxx册的情况:变为 Vol. XXX 或 Bk. XXX。
+ let reptext = ittext
+ reptext = reptext.replace(
+ regex("(第\s?)?\d+\s?[卷册]"),
+ itt => {
+ if itt.text.contains("卷") {
+ "Vol. "
+ } else {
+ "Bk. "
+ }
+ itt.text.find(regex("\d+"))
+ },
+ )
+
+ // 第xxx版/第xxx本的情况:变为 1st ed 格式。
+ reptext = reptext.replace(
+ regex("(第\s?)?\d+\s?[版本]"),
+ itt => {
+ let num = itt.text.find(regex("\d+"))
+ num
+ if num.clusters().len() == 2 and num.clusters().first() == "1" {
+ "th"
+ } else {
+ (
+ "1": "st",
+ "2": "nd",
+ "3": "rd",
+ ).at(num.clusters().last(), default: "th")
+ }
+ " ed"
+ },
+ )
+
+ // 译者数量判断:单数时需要用 trans,复数时需要用 tran 。
+ // 注:
+ // 1. 目前判断译者数量的方法非常草率,有逗号就是多个作者。但是在部分 GB/T 7714-2015 方言中,姓名中可以含有逗号。
+ // 2. 在 GB/T 7714-2015 原文中有 `等译`(P15 10.1.3 小节 示例 1-[1]),但未给出相应的英文缩写翻译。
+ // 3. CSL 社区库内的 GB/T 7714-2015 会使用 `等, 译` 和 `et al., tran` 的写法。为了保证统一性,这里会译作不带逗号的版本,即 `et al. tran`
+ // 如果工作不正常,可以考虑换为简单关键词替换,即注释这段情况,取消 13 行 mapping 内 `译` 条目的注释。
+ reptext = reptext.replace(
+ regex("\].+?译"),
+ itt => {
+ // 我想让上面这一行匹配变成非贪婪的,但加问号后没啥效果?
+ if itt.text.replace(regex(",\s?译"), "").find(",") != none {
+ itt.text.replace(regex(",?\s?译"), " tran")
+ } else {
+ itt.text.replace(regex(",?\s?译"), " trans")
+ }
+ },
+ )
+
+ // `等` 特殊处理:`等`后方接内容也需要译作 `et al.`,如 `等译` 需要翻译为 `et al. trans`
+ reptext = reptext.replace(
+ regex("等."),
+ itt => {
+ "et al."
+ // 如果原文就是 `等.`,则仅需简单替换,不需要额外处理
+ // 如果原文 `等` 后没有跟随英文标点,则需要补充一个空格
+ if not itt.text.last() in (".", ",", ";", ":", "[", "]", "/", "\\", "<", ">", "?", "(", ")", " ", "\"", "'") {
+ " "
+ }
+ // 原文有英文句号时不需要重复句号,否则需要将匹配到的最后一个字符吐回来
+ if not itt.text.last() == "." {
+ itt.text.last()
+ }
+ },
+ )
+
+ // 其他情况:直接替换
+ reptext = reptext.replace(
+ regex("\p{sc=Hani}+"),
+ itt => {
+ mapping.at(itt.text, default: itt.text)
+ // 注意:若替换功能工作良好,应该不会出现 `default` 情形
+ },
+ )
+ reptext
+ }
+ }
+
+ set text(lang: "zh")
+ bibliography(
+ title: title,
+ full: full,
+ style: style,
+ )
+}
diff --git a/utils/custom-heading.typ b/utils/custom-heading.typ
new file mode 100644
index 0000000..02eb87f
--- /dev/null
+++ b/utils/custom-heading.typ
@@ -0,0 +1,50 @@
+// 展示一个标题
+#let heading-display(it) = {
+ if it != none {
+ if it.has("numbering") and it.numbering != none {
+ numbering(it.numbering, ..counter(heading).at(it.location()))
+ [ ]
+ }
+ it.body
+ } else {
+ ""
+ }
+}
+
+// 获取当前激活的 heading,参数 prev 用于标志优先使用之前页面的 heading
+#let active-heading(level: 1, prev: true, loc) = {
+ // 之前页面的标题
+ let prev-headings = query(selector(heading.where(level: level)).before(loc), loc)
+ // 当前页面的标题
+ let cur-headings = query(selector(heading.where(level: level)).after(loc), loc)
+ .filter(it => it.location().page() == loc.page())
+ if prev-headings.len() == 0 and cur-headings.len() == 0 {
+ return none
+ } else {
+ if prev {
+ if prev-headings.len() != 0 {
+ return prev-headings.last()
+ } else {
+ return cur-headings.first()
+ }
+ } else {
+ if cur-headings.len() != 0 {
+ return cur-headings.first()
+ } else {
+ return prev-headings.last()
+ }
+ }
+ }
+}
+
+// 获取当前页面的标题
+#let current-heading(level: 1, loc) = {
+ // 当前页面的标题
+ let cur-headings = query(selector(heading.where(level: level)).after(loc), loc)
+ .filter(it => it.location().page() == loc.page())
+ if cur-headings.len() != 0 {
+ return cur-headings.first()
+ } else {
+ return none
+ }
+}
\ No newline at end of file
diff --git a/utils/custom-numbering.typ b/utils/custom-numbering.typ
new file mode 100644
index 0000000..a5c8847
--- /dev/null
+++ b/utils/custom-numbering.typ
@@ -0,0 +1,30 @@
+// 一个简单的自定义 Numbering
+// 用法也简单,可以特殊设置一级等标题的样式,以及一个缺省值
+#let custom-numbering(base: 1, depth: 5, first-level: auto, second-level: auto, third-level: auto, format, ..args) = {
+ if (args.pos().len() > depth) {
+ return
+ }
+ if (first-level != auto and args.pos().len() == 1) {
+ if (first-level != "") {
+ numbering(first-level, ..args)
+ }
+ return
+ }
+ if (second-level != auto and args.pos().len() == 2) {
+ if (second-level != "") {
+ numbering(second-level, ..args)
+ }
+ return
+ }
+ if (third-level != auto and args.pos().len() == 3) {
+ if (third-level != "") {
+ numbering(third-level, ..args)
+ }
+ return
+ }
+ // default
+ if (args.pos().len() >= base) {
+ numbering(format, ..(args.pos().slice(base - 1)))
+ return
+ }
+}
\ No newline at end of file
diff --git a/utils/custom-tablex.typ b/utils/custom-tablex.typ
new file mode 100644
index 0000000..29fe03b
--- /dev/null
+++ b/utils/custom-tablex.typ
@@ -0,0 +1 @@
+#import "@preview/tablex:0.0.8": *
\ No newline at end of file
diff --git a/utils/datetime-display.typ b/utils/datetime-display.typ
new file mode 100644
index 0000000..2012033
--- /dev/null
+++ b/utils/datetime-display.typ
@@ -0,0 +1,9 @@
+// 显示中文日期
+#let datetime-display(date) = {
+ date.display("[year] 年 [month] 月 [day] 日")
+}
+
+// 显示英文日期
+#let datetime-en-display(date) = {
+ date.display("[month repr:short] [day], [year]")
+}
\ No newline at end of file
diff --git a/utils/double-underline.typ b/utils/double-underline.typ
new file mode 100644
index 0000000..74e1dd5
--- /dev/null
+++ b/utils/double-underline.typ
@@ -0,0 +1,11 @@
+// 双下划线
+#let double-underline(body) = style(styles => {
+ let size = measure(body, styles)
+ stack(
+ body,
+ v(3pt),
+ line(length: size.width),
+ v(2pt),
+ line(length: size.width),
+ )
+})
\ No newline at end of file
diff --git a/utils/hline.typ b/utils/hline.typ
new file mode 100644
index 0000000..afd563c
--- /dev/null
+++ b/utils/hline.typ
@@ -0,0 +1,4 @@
+// 一条水平横线
+#let hline() = {
+ line(length: 100%)
+}
\ No newline at end of file
diff --git a/utils/indent.typ b/utils/indent.typ
new file mode 100644
index 0000000..427bc09
--- /dev/null
+++ b/utils/indent.typ
@@ -0,0 +1,6 @@
+// 中文缩进
+#let indent = h(2em)
+
+// 假段落,附着于 heading 之后可以实现首行缩进
+#let empty-par = par[#box()]
+#let fake-par = context empty-par + v(-measure(empty-par + empty-par).height)
\ No newline at end of file
diff --git a/utils/invisible-heading.typ b/utils/invisible-heading.typ
new file mode 100644
index 0000000..f16fa80
--- /dev/null
+++ b/utils/invisible-heading.typ
@@ -0,0 +1,5 @@
+// 用于创建一个不可见的标题,用于给 outline 加上短标题
+#let invisible-heading(..args) = {
+ set text(size: 0pt, fill: white)
+ heading(numbering: none, ..args)
+}
\ No newline at end of file
diff --git a/utils/justify-text.typ b/utils/justify-text.typ
new file mode 100644
index 0000000..318027d
--- /dev/null
+++ b/utils/justify-text.typ
@@ -0,0 +1,9 @@
+// 双端对其一段小文本,常用于表格的中文 key
+#let justify-text(with-tail: false, tail: ":", body) = {
+ if with-tail and tail != "" {
+ stack(dir: ltr, stack(dir: ltr, spacing: 1fr, ..body.split("").filter(it => it != "")), tail)
+ } else {
+ stack(dir: ltr, spacing: 1fr, ..body.split("").filter(it => it != ""))
+ }
+}
+
diff --git a/utils/style.typ b/utils/style.typ
new file mode 100644
index 0000000..294df91
--- /dev/null
+++ b/utils/style.typ
@@ -0,0 +1,43 @@
+#let 字号 = (
+ 初号: 42pt,
+ 小初: 36pt,
+ 一号: 26pt,
+ 小一: 24pt,
+ 二号: 22pt,
+ 小二: 18pt,
+ 三号: 16pt,
+ 小三: 15pt,
+ 四号: 14pt,
+ 中四: 13pt,
+ 小四: 12pt,
+ 五号: 10.5pt,
+ 小五: 9pt,
+ 六号: 7.5pt,
+ 小六: 6.5pt,
+ 七号: 5.5pt,
+ 小七: 5pt,
+)
+
+#let 字体 = (
+ // 宋体,属于「有衬线字体」,一般可以等同于英文中的 Serif Font
+ // 这一行分别是「新罗马体(有衬线英文字体)」、「思源宋体(简体)」、「思源宋体」、「宋体(Windows)」、「宋体(MacOS)」
+ 宋体: ("Times New Roman", "Source Han Serif SC", "Source Han Serif", "Noto Serif CJK SC", "SimSun", "Songti SC", "STSongti"),
+ // 黑体,属于「无衬线字体」,一般可以等同于英文中的 Sans Serif Font
+ // 这一行分别是「Arial(无衬线英文字体)」、「思源黑体(简体)」、「思源黑体」、「黑体(Windows)」、「黑体(MacOS)」
+ 黑体: ("Arial", "Source Han Sans HW SC", "Source Han Sans", "Noto Sans CJK SC", "SimHei", "Heiti SC", "STHeiti"),
+ // 楷体
+ 楷体: ("Times New Roman", "KaiTi", "Kaiti SC", "STKaiti", "FZKai-Z03S", "Noto Serif CJK SC"),
+ // 仿宋
+ 仿宋: ("Times New Roman", "FangSong", "FangSong SC", "STFangSong", "FZFangSong-Z02S", "Noto Serif CJK SC"),
+ // 等宽字体,用于代码块环境,一般可以等同于英文中的 Monospaced Font
+ // 这一行分别是「Courier New(Windows 等宽英文字体)」、「思源等宽黑体(简体)」、「思源等宽黑体」、「黑体(Windows)」、「黑体(MacOS)」
+ 等宽: ("Courier New", "Menlo", "IBM Plex Mono", "Source Han Sans HW SC", "Source Han Sans HW", "Noto Sans Mono CJK SC", "SimHei", "Heiti SC", "STHeiti"),
+)
+
+// 定义中山大学规范颜色
+// 参考 http://home3.sysu.edu.cn/sysuvi/content/zaisheng/b/C-1-5.jpg
+#let sysucolor = (
+ green: cmyk(100%, 0%, 100%, 60%),
+ red: cmyk(30%, 100%, 100%, 50%),
+ black: cmyk(0%, 0%, 0%, 100%),
+)
diff --git a/utils/unpairs.typ b/utils/unpairs.typ
new file mode 100644
index 0000000..6931e95
--- /dev/null
+++ b/utils/unpairs.typ
@@ -0,0 +1,8 @@
+// 将 pairs 数组转成 dict 字典
+#let unpairs(pairs) = {
+ let dict = (:)
+ for pair in pairs {
+ dict.insert(..pair)
+ }
+ dict
+}
\ No newline at end of file