forked from EduCraft/curriculum-project-hub
c17af60bb0
Single entry `display(info, target, parts)` over an ordered parts array, the frozen contract the generated typst driver calls. Per-kind dispatch for segment/example/lemma/sop; student/teacher matrix derived internally from `target` (student hides example solution + lemma proof; teacher shows all). Content-field values are consumed as already-evaluated content (driver `include`s them ⇒ module body, ADR-0006), never imported/stringified. Optional fields (lemma proof, example source) handled gracefully; unknown kind/target degrade without crashing. Zero @preview deps (CI-robust); CJK + math styled. Smoke compiles to non-empty student/teacher PDFs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
2.1 KiB
Typst
86 lines
2.1 KiB
Typst
// Base document styling: CJK fonts, headings, math, paragraph layout, page.
|
|
//
|
|
// Zero external dependencies. Heading numbering uses builtin `numbering`
|
|
// strings (no @preview/numbly) so the package compiles with no network.
|
|
|
|
#import "fonts.typ"
|
|
|
|
/// Apply the base style to a document body. Used as `show: base-style`.
|
|
#let base-style(doc) = {
|
|
// Language / region drive CJK line-breaking and punctuation.
|
|
set text(lang: "zh", region: "cn", size: 12pt, font: fonts.serif)
|
|
set text(cjk-latin-spacing: auto)
|
|
|
|
// Heading numbering: 一、 / 1.1 / 1.1.1 — builtin numbering, no deps.
|
|
set heading(numbering: "一、")
|
|
show heading: set text(font: fonts.sans)
|
|
|
|
// Number display equations.
|
|
set math.equation(numbering: "(1)")
|
|
|
|
set par(
|
|
first-line-indent: (amount: 2em, all: true),
|
|
leading: 0.8em,
|
|
spacing: 1.5em,
|
|
justify: true,
|
|
)
|
|
|
|
set page(
|
|
margin: (x: 2.2cm, y: 2.4cm),
|
|
footer: context align(center)[
|
|
#counter(page).get().at(0) / #counter(page).final().at(0)
|
|
],
|
|
)
|
|
|
|
set table(inset: 5pt, stroke: 0.75pt)
|
|
|
|
// Inline quotes -> subtle highlight (matches prototype feel).
|
|
show quote.where(block: false): it => highlight(
|
|
it.body,
|
|
fill: gray.transparentize(60%),
|
|
top-edge: 1em,
|
|
bottom-edge: -0.2em,
|
|
extent: 0.35em,
|
|
radius: 0.25em,
|
|
)
|
|
|
|
show link: set text(fill: eastern)
|
|
show link: underline
|
|
|
|
doc
|
|
}
|
|
|
|
/// Cancel the active paragraph first-line indent (for tag lines that should
|
|
/// start flush-left). Defensive against both `length` and `dict` indent forms.
|
|
#let no-indent = context {
|
|
let fli = par.first-line-indent
|
|
if type(fli) == length {
|
|
h(-fli)
|
|
} else {
|
|
h(-fli.amount)
|
|
}
|
|
}
|
|
|
|
/// Document title block.
|
|
#let title-block() = {
|
|
set align(center)
|
|
set text(font: fonts.sans, size: 22pt, weight: "bold")
|
|
context document.title
|
|
}
|
|
|
|
/// Subtitle + authors block under the title.
|
|
#let subtitle-block(subtitle) = {
|
|
set align(center)
|
|
set text(size: 14pt)
|
|
v(1.2em, weak: true)
|
|
subtitle
|
|
context {
|
|
let authors = document.author
|
|
if authors != () {
|
|
v(0.8em, weak: true)
|
|
authors.join(h(1.5em))
|
|
}
|
|
}
|
|
v(1.2em, weak: true)
|
|
}
|