main
  1#import "../utils/invisible-heading.typ": invisible-heading
  2#import "../utils/style.typ": , 
  3#import "@preview/numbly:0.1.0": numbly
  4
  5// 本科生英文目录
  6#let bachelor-outline-page-en(
  7  // documentclass 传入参数
  8  twoside: false,
  9  fonts: (:),
 10  // 其他参数
 11  depth: 3,
 12  title: "Contents",
 13  entry-numbering: ("Chapter {1}",),
 14  outlined: false,
 15  title-vspace: 14pt,
 16  title-text-args: auto,
 17  // 引用页码字体与字号
 18  reference-font: auto,
 19  reference-size: .,
 20  // 字体与字号
 21  font: auto,
 22  size: (., .),
 23  weight: ("bold", "bold", "regular"),
 24  // 目录样式
 25  above: (20pt, 14pt),
 26  below: (14pt, 14pt),
 27  indent: (0pt, 18pt, 28pt),
 28  fill: (repeat([.], gap: 0.15em),),
 29  gap: .3em,
 30) = {
 31  // 1.  默认参数
 32  fonts =  + fonts
 33  if title-text-args == auto {
 34    title-text-args = (font: fonts., size: ., weight: "bold")
 35  }
 36  if reference-font == auto {
 37    reference-font = fonts.
 38  }
 39  if font == auto {
 40    font = (fonts., fonts., fonts.)
 41  }
 42
 43  // 2.  正式渲染
 44  pagebreak(weak: true, to: if twoside { "odd" })
 45
 46  // 默认显示的字体
 47  set text(font: reference-font, size: reference-size)
 48
 49  {
 50    set align(center)
 51    text(..title-text-args, title)
 52    // 标记一个不可见的标题用于目录生成
 53    invisible-heading(level: 1, outlined: outlined, title)
 54  }
 55
 56  v(title-vspace)
 57
 58  // 目录样式
 59  set outline(indent: level => indent
 60    .slice(0, calc.min(level + 1, indent.len()))
 61    .sum())
 62  show outline.entry: entry => block(
 63    above: above.at(entry.level - 1, default: above.last()),
 64    below: below.at(entry.level - 1, default: below.last()),
 65    link(
 66      entry.element.location(),
 67      entry.indented(
 68        none,
 69        {
 70          text(
 71            font: font.at(entry.level - 1, default: font.last()),
 72            size: size.at(entry.level - 1, default: size.last()),
 73            weight: weight.at(entry.level - 1, default: weight.last()),
 74            {
 75              if (
 76                entry.prefix() not in (none, [])
 77                  and entry.element.numbering != none
 78              ) {
 79                let enentry-numbering-len = entry-numbering.len()
 80                let used-numbering = if (
 81                  entry.element.level <= enentry-numbering-len
 82                ) {
 83                  numbly(..entry-numbering)
 84                } else {
 85                  entry.element.numbering
 86                }
 87                numbering(
 88                  used-numbering,
 89                  ..counter(outline.target).at(entry.element.location()),
 90                )
 91                h(gap)
 92              }
 93              {
 94                let body = entry.body()
 95                assert(
 96                  body.fields().keys().contains("children"),
 97                  message: "论文标题应在最后添加 `#metadata((en: \"\"))`",
 98                )
 99                let meta = body.children.last()
100                assert(
101                  repr(meta).starts-with("metadata"),
102                  message: "论文标题应在最后添加 `#metadata((en: \"\"))`",
103                )
104                assert(
105                  meta.value.keys().contains("en"),
106                  message: "论文标题应在最后添加 `#metadata((en: \"\"))`",
107                )
108                meta.value.en
109              }
110            },
111          )
112          box(width: 1fr, inset: (x: .25em), fill.at(
113            entry.level - 1,
114            default: fill.last(),
115          ))
116          entry.page()
117        },
118        gap: 0pt,
119      ),
120    ),
121  )
122
123  // 显示目录
124  outline(title: none, depth: depth)
125}