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