// 教材模板 —— 定理/代码/目录 + 封面页 + 三线表 #import "@preview/bookly:3.1.0": * #import "@preview/great-theorems:0.1.2": * #import "@preview/codly:1.3.0": * #import "@preview/showybox:2.0.4": showybox // ── helpers ────────────────────────────────────────────────────────────── #let content-to-string(content) = { if content.has("text") { content.text } else if content.has("children") { content.children.map(content-to-string).join("") } else if content.has("body") { content-to-string(content.body) } else if content == [ ] { " " } else { "" } } // ── blockquote themes ──────────────────────────────────────────────────── #let blockquote-themes = ( ("课堂小实验", rgb("#F0FDF4"), rgb("#16A34A")), ("关键洞察", rgb("#FFFBEB"), rgb("#D97706")), ("工程警示", rgb("#FEF2F2"), rgb("#DC2626")), ("前沿瞭望", rgb("#F3E8FF"), rgb("#9333EA")), ("历史视角", rgb("#FEF3C7"), rgb("#B45309")), ("你知道吗", rgb("#ECFEFF"), rgb("#0891B2")), ) #let themed-blockquote(body) = { let body-str = content-to-string(body) let theme = blockquote-themes.find(t => body-str.contains(t.at(0))) let (fill-color, stroke-color) = if theme != none { (theme.at(1), theme.at(2)) } else { (rgb("#F1F5F9"), rgb("#64748B")) } block( fill: fill-color, stroke: (left: 4pt + stroke-color), inset: (x: 14pt, y: 10pt), radius: (right: 4pt), spacing: 1.2em, body, ) } #let primary = rgb("#1F4E79") #let secondary = rgb("#2C3E50") #let accent = rgb("#3467A8") #let light = rgb("#96A0AA") #let rule-blue = accent.lighten(46%) // Filled mathblocks: inset so fill does not hug text; radius slightly larger than codly (4pt) #let _mathblock-inset = (x: 14pt, y: 12pt) #let _mathblock-radius = 5pt // Theorem-like environments (great-theorems) #let theorem = mathblock( blocktitle: "Theorem", fill: rgb("#E8F0FE"), inset: _mathblock-inset, radius: _mathblock-radius, ) #let lemma = mathblock( blocktitle: "Lemma", fill: rgb("#F3E8FF"), inset: _mathblock-inset, radius: _mathblock-radius, ) #let proof = proofblock( inset: _mathblock-inset, radius: _mathblock-radius, ) #let example = mathblock( blocktitle: "Example", fill: rgb("#F0FDF4"), inset: _mathblock-inset, radius: _mathblock-radius, ) // Uncounted block: titles must use `title: ...`, not a positional string (see great-theorems README). #let exercise = mathblock( blocktitle: "Exercise", prefix: [], titlix: title => [*习题*~#title~。#h(0.25em)], fill: rgb("#FEF3C7"), inset: (x: 14pt, y: 13pt), radius: _mathblock-radius, ) #let cover-page(title, subtitle, date) = { page(margin: (top: 3cm, bottom: 3cm, left: 3cm, right: 3cm))[ #align(center + horizon)[ #v(2cm) #text(size: 28pt, weight: "bold", fill: primary, title) #if subtitle != none { v(0.8cm) text(size: 16pt, fill: accent, subtitle) } #if date != none { v(2cm) text(size: 12pt, fill: light, date) } ] ] } #let three-line-table(..args) = { let kwargs = args.named() let children = args.pos() let new_children = () let header_seen = false for child in children { if child.func() == table.header { new_children.push(table.hline(stroke: 1.25pt + rule-blue)) new_children.push(child) header_seen = true } else if header_seen and child.func() == table.hline { new_children.push(table.hline(stroke: 0.75pt + rule-blue)) header_seen = false } else { new_children.push(child) } } new_children.push(table.hline(stroke: 1.25pt + rule-blue)) kwargs.stroke = none kwargs.inset = (x: 8pt, y: 5pt) table(..new_children, ..kwargs) } // Reference-list block: smaller text, hanging indent, tighter rhythm. // Wrap a 参考文献 / References section's body via `#references[ ... ]`. #let references(body) = block({ set text(size: 9.5pt) set par( justify: true, leading: 0.7em, spacing: 0.6em, first-line-indent: 0pt, hanging-indent: 1.5em, ) body }) #let textbook( title: none, subtitle: none, date: none, body, ) = { if title != none { cover-page(title, subtitle, date) pagebreak() } set text( font: ( "Cambria", "PingFang SC", ), size: 11pt, lang: "zh", ) show raw: set text(font: ( "Courier New", "Menlo", "Monaco", "SimSun", )) // Long-form reading: most generous body rhythm in this family set par( justify: true, leading: 0.86em, spacing: 1.2em, first-line-indent: (amount: 2em, all: true), ) show: codly-init codly( stroke: 0.5pt + rule-blue, fill: rgb("#F8F9FA"), zebra-fill: none, lang-stroke: none, lang-fill: light.lighten(60%), radius: 4pt, ) show: great-theorems-init set heading(numbering: "1.1") // 按章编号:每章开始时重置图片/表格计数器 set figure(numbering: (..nums) => { let h = counter(heading).get() let ch = if h.len() > 0 { h.first() } else { 1 } numbering("1-1", ch, nums.pos().first()) }) // List rhythm: spacing ≥ leading (0.86em), slightly looser than body for long-form reading set list(indent: 1.2em, body-indent: 0.45em, spacing: 0.98em) set enum(indent: 1.2em, body-indent: 0.45em, spacing: 0.98em) // 每章从新页开始(第一章除外,因为前面已有 pagebreak) let first-chapter = state("first-chapter", true) show heading.where(level: 1): it => { counter(figure.where(kind: image)).update(0) counter(figure.where(kind: table)).update(0) context if not first-chapter.get() { pagebreak() } first-chapter.update(false) v(1.45em) text(size: 18pt, weight: "bold", fill: primary, it) v(0.55em) } show heading.where(level: 2): it => { v(1.1em) text(size: 14pt, weight: "bold", fill: accent, it) v(0.5em) } show heading.where(level: 3): it => { v(0.8em) text(size: 12pt, weight: "bold", fill: secondary, it) v(0.4em) } // Blockquote → themed block show quote.where(block: true): it => themed-blockquote(it.body) set page( paper: "a4", margin: (top: 2.5cm, bottom: 2.5cm, left: 2.5cm, right: 2.5cm), header: context { let h = query(selector(heading.where(level: 1))).find(it => it.location().page() == here().page()) if h != none { align(right)[ #text(size: 9pt, fill: light)[ #h.body ] ] } }, footer: context { align(center)[ #text(size: 9pt, fill: light)[ — #counter(page).display("1") — ] ] }, ) show link: it => text(fill: accent, it) outline(title: "目录", depth: 2) pagebreak() body }