|
| 1 | +#import "lib/tools.typ": * |
| 2 | +#import "lib/outline.typ": * |
| 3 | +#import "lib/choice.typ": * |
| 4 | +#import "lib/question.typ": answer, fillin, fillinn, paren, parenn, question, score, solution, text-figure, |
| 5 | + |
| 6 | +#let setup( |
| 7 | + mode: HANDOUTS, |
| 8 | + paper: a4, |
| 9 | + page-numbering: auto, |
| 10 | + page-align: center, |
| 11 | + gap: 1in, |
| 12 | + show-gap-line: false, |
| 13 | + footer-is-separate: true, |
| 14 | + outline-page-numbering: "⚜ I ⚜", |
| 15 | + font-size: 11pt, |
| 16 | + font: source-han, |
| 17 | + font-math: source-han, |
| 18 | + line-height: 2em, |
| 19 | + par-spacing: 2em, |
| 20 | + first-line-indent: 0em, |
| 21 | + heading-numbering: auto, |
| 22 | + heading-hanging-indent: auto, |
| 23 | + h1-size: auto, |
| 24 | + heading-font: hei-ti, |
| 25 | + heading-color: luma(0%), |
| 26 | + heading-top: 10pt, |
| 27 | + heading-bottom: 15pt, |
| 28 | + enum-numbering: "(1.1.i.a)", |
| 29 | + enum-spacing: 2em, |
| 30 | + enum-indent: 0pt, |
| 31 | + watermark: none, |
| 32 | + watermark-color: rgb("#f666"), |
| 33 | + watermark-font: source-han, |
| 34 | + watermark-size: 88pt, |
| 35 | + watermark-rotate: -45deg, |
| 36 | + show-answer: false, |
| 37 | + answer-color: blue, |
| 38 | + show-seal-line: true, |
| 39 | + seal-line-student-info: ( |
| 40 | + 姓名: underline[~~~~~~~~~~~~~], |
| 41 | + 准考证号: inline-square(14), |
| 42 | + 考场号: inline-square(2), |
| 43 | + 座位号: inline-square(2), |
| 44 | + ), |
| 45 | + seal-line-type: "dashed", |
| 46 | + seal-line-supplement: "弥封线内不得答题", |
| 47 | + doc, |
| 48 | +) = { |
| 49 | + assert(mode in (HANDOUTS, EXAM), message: "mode must be HANDOUTS or EXAM") |
| 50 | + mode-state.update(mode) |
| 51 | + let _footer(label) = context { |
| 52 | + assert( |
| 53 | + type(label) in (str, function, none) or label == auto, |
| 54 | + message: "expected str or function or none or auto, found " + str(type(label)), |
| 55 | + ) |
| 56 | + if label == none { return } |
| 57 | + let _label = label |
| 58 | + if label == auto { |
| 59 | + if mode == EXAM { |
| 60 | + _label = zh-arabic(suffix: "试题") |
| 61 | + } else { |
| 62 | + _label = "1 ✏ 1" |
| 63 | + } |
| 64 | + } |
| 65 | + // 如果传进来的label包含两个1,两个1中间不能是连续空格、包含数字 |
| 66 | + // 支持双:阿拉伯数字、小写、大写罗马,带圈数字页码 |
| 67 | + let reg-1 = "^[\D]*1[\D]*[^\d\s]+[\D]*1[\D]*$" |
| 68 | + let reg-i = reg-1.replace("1", "i") |
| 69 | + let reg-I = reg-1.replace("1", "I") |
| 70 | + let reg-circled-number = reg-1.replace("1", "①") |
| 71 | + let reg-circled-number2 = reg-1.replace("1", "⓵") |
| 72 | + let reg = reg-1 + "|" + reg-i + "|" + reg-I + "|" + reg-circled-number + "|" + reg-circled-number2 |
| 73 | + |
| 74 | + let current = counter(page).get() |
| 75 | + if (type(_label) == str and regex(reg) in _label) or (type(_label) == function) { |
| 76 | + current += counter(page).final() |
| 77 | + } |
| 78 | + |
| 79 | + let _numbering = numbering(_label, ..current) |
| 80 | + |
| 81 | + // 处于分栏下且左右页脚分离 |
| 82 | + if page.columns == 2 and footer-is-separate { |
| 83 | + current.at(0) += 1 |
| 84 | + grid( |
| 85 | + columns: (1fr, 1fr), |
| 86 | + align: center + horizon, |
| 87 | + // 左页码 |
| 88 | + _numbering, |
| 89 | + // 右页码 |
| 90 | + numbering(_label, ..current), |
| 91 | + ) |
| 92 | + counter(page).step() |
| 93 | + return |
| 94 | + } |
| 95 | + |
| 96 | + // 页面的页脚是未分离, 则让奇数页在右侧,偶数页在左侧 |
| 97 | + let position = page-align |
| 98 | + if not footer-is-separate { |
| 99 | + if calc.odd(current.first()) { |
| 100 | + position = right |
| 101 | + } else { |
| 102 | + position = left |
| 103 | + } |
| 104 | + } |
| 105 | + align(position, _numbering) |
| 106 | + } |
| 107 | + let _header( |
| 108 | + student-info: seal-line-student-info, |
| 109 | + line-type: seal-line-type, |
| 110 | + supplement: seal-line-supplement, |
| 111 | + ) = context { |
| 112 | + if mode != EXAM or not show-seal-line { return } |
| 113 | + // 根据页码决定是否显示弥封线 |
| 114 | + // 如果当前页面有<title>,则显示弥封线,并在该章节最后一页的右侧也设置弥封线 |
| 115 | + let chapter-location = for value in query(<title>) { |
| 116 | + counter(page).at(value.location()) |
| 117 | + } |
| 118 | + |
| 119 | + if chapter-location == none or chapter-location.len() == 0 { return } |
| 120 | + let current = counter(page).get().first() |
| 121 | + let last = counter(page).final() |
| 122 | + |
| 123 | + // 获取上一章最后一页的页码,给最后一页加上弥封线 |
| 124 | + let chapter-last-page-location = chapter-location.map(item => item - 1) + last |
| 125 | + if page.columns == 2 and footer-is-separate { |
| 126 | + chapter-last-page-location = chapter-location.map(item => item - 2) + (last.first() - 1,) |
| 127 | + } |
| 128 | + |
| 129 | + // 去除第一章,因为第一章前面没有章节了 |
| 130 | + let _ = chapter-last-page-location.remove(0) |
| 131 | + |
| 132 | + let _margin-y = page.margin * 2 |
| 133 | + let _width = page.height - _margin-y |
| 134 | + if page.flipped { _width = page.width - _margin-y } |
| 135 | + block(width: _width)[ |
| 136 | + // 判断当前是在当前章节第一页还是章节最后一页 |
| 137 | + //当前章节第一页弥封线 |
| 138 | + #if chapter-location.contains(current) { |
| 139 | + place( |
| 140 | + dx: -_width - 1em, |
| 141 | + dy: -2em, |
| 142 | + )[ |
| 143 | + #rotate(-90deg, origin: right + bottom)[ |
| 144 | + #_create-seal(dash: line-type, info: student-info, supplement: supplement) |
| 145 | + ] |
| 146 | + ] |
| 147 | + return |
| 148 | + } |
| 149 | + |
| 150 | + #if (chapter-last-page-location).contains(current) { |
| 151 | + _width = if page.flipped { |
| 152 | + page.height |
| 153 | + } else { page.width } |
| 154 | + // 章节最后页的弥封线 |
| 155 | + place(dx: _width - page.margin - 2em, dy: 2em)[ |
| 156 | + #rotate(90deg, origin: left + top, _create-seal(dash: line-type, supplement: supplement)) |
| 157 | + ] |
| 158 | + } |
| 159 | + ] |
| 160 | + } |
| 161 | + let _background() = { |
| 162 | + if paper.columns == 2 and show-gap-line { |
| 163 | + line(angle: 90deg, length: 100% - paper.margin * 2, stroke: .5pt) |
| 164 | + } |
| 165 | + } |
| 166 | + let _foreground() = { |
| 167 | + if watermark == none { return } |
| 168 | + set text(size: watermark-size, watermark-color) |
| 169 | + set par(leading: .5em) |
| 170 | + place(horizon)[ |
| 171 | + #grid( |
| 172 | + columns: paper.columns * (1fr,), |
| 173 | + ..paper.columns * (rotate(watermark-rotate, watermark),), |
| 174 | + ) |
| 175 | + ] |
| 176 | + } |
| 177 | + set page( |
| 178 | + ..paper, |
| 179 | + header: _header(), |
| 180 | + footer: _footer(page-numbering), |
| 181 | + background: _background(), |
| 182 | + foreground: _foreground(), |
| 183 | + ) |
| 184 | + set columns(gutter: gap) |
| 185 | + |
| 186 | + set outline( |
| 187 | + target: if mode == EXAM { <chapter> } else { heading }, |
| 188 | + title: text(size: 15pt)[目#h(1em)录], |
| 189 | + ) |
| 190 | + show outline: it => { |
| 191 | + set page(header: none, footer: _footer(outline-page-numbering)) |
| 192 | + align(center, it) |
| 193 | + pagebreak(weak: true) |
| 194 | + counter(page).update(1) // 正文页码从1开始 |
| 195 | + } |
| 196 | + |
| 197 | + set par(leading: line-height, spacing: par-spacing, first-line-indent: (amount: first-line-indent, all: true)) |
| 198 | + set text(font: font, size: font-size) |
| 199 | + |
| 200 | + if heading-numbering == auto { |
| 201 | + if mode == EXAM { |
| 202 | + heading-numbering = "一、" |
| 203 | + heading-hanging-indent = 2.3em |
| 204 | + } else { heading-numbering = "1.1.1" } |
| 205 | + } |
| 206 | + set heading(numbering: heading-numbering, hanging-indent: heading-hanging-indent) |
| 207 | + show heading: it => { |
| 208 | + v(heading-top) |
| 209 | + text(heading-color, font: heading-font, it) |
| 210 | + v(heading-bottom) |
| 211 | + } |
| 212 | + |
| 213 | + show heading.where(level: 1): it => { |
| 214 | + let size = h1-size |
| 215 | + if size == auto { |
| 216 | + if mode == HANDOUTS { size = text.size } else { size = 10.5pt } |
| 217 | + } |
| 218 | + text(size: size, it) |
| 219 | + } |
| 220 | + set enum(numbering: enum-numbering, spacing: enum-spacing, indent: enum-indent) |
| 221 | + set table(stroke: .5pt, align: center) |
| 222 | + set table.cell(align: horizon) |
| 223 | + |
| 224 | + // 分段函数样式 |
| 225 | + set math.cases(gap: 1em) |
| 226 | + // 显示方程编号 |
| 227 | + set math.equation(numbering: "(1)", supplement: [Eq -]) if mode == HANDOUTS |
| 228 | + show math.equation: it => { |
| 229 | + // features: 一些特殊符号的设置,如空集符号设置更加漂亮 |
| 230 | + set text(font: font-math, features: ("cv01",)) |
| 231 | + // 1. 行内样式默认块级显示样式; 2. 添加数学符号和中文之间间距 |
| 232 | + let space = h(.25em, weak: true) |
| 233 | + space + math.display(it) + space |
| 234 | + } |
| 235 | + |
| 236 | + if show-answer { |
| 237 | + answer-state.update(true) |
| 238 | + answer-color-state.update(answer-color) |
| 239 | + } |
| 240 | + |
| 241 | + doc |
| 242 | +} |
| 243 | + |
0 commit comments