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}