// cph-render — curriculum lesson render package. // // Single public entry: `display(info, target, parts)`. // // A lesson is an ORDERED array of typed parts. Each part is a dict with a // `kind` plus that kind's content/scalar fields. Content field VALUES are // already-evaluated typst content (the driver produces them via `include`) — // never modules or strings; we render them directly. // // kind -> fields (MVP): // segment : textbook (content, required) // example : problem (content, required), solution (content, required), // source (string, optional) // lemma : stmt (content, required), proof (content, optional) // sop : sop (content, required) // // target -> show/hide (the (kind x target) render matrix; defaults per // ADR-0005/0008, derived internally — caller passes no flags): // student : example problem only (hide solution); lemma stmt only (hide // proof); segment textbook; sop shown. // teacher : everything shown. // unknown : rendered conservatively == student (show only required-public // fields). Never crashes. The "no render rule => warning" // diagnostic is the Rust side's job, not ours. #import "src/style.typ": base-style, default-heading-numbering, title-block, subtitle-block #import "src/elements/segment.typ": display-segment #import "src/elements/example.typ": display-example #import "src/elements/lemma.typ": display-lemma #import "src/elements/sop.typ": display-sop #import "src/elements/common.typ": example-counter, lemma-counter /// Resolve a target string to the set of show/hide booleans. /// Unknown targets fall back to the conservative (student-like) profile. #let _flags-for(target) = { if target == "teacher" { (show-solution: true, show-proof: true, subtitle: [教师版讲义]) } else if target == "student" { (show-solution: false, show-proof: false, subtitle: [学生版讲义]) } else { // Unknown target: conservative — show only required-public fields. (show-solution: false, show-proof: false, subtitle: [讲义]) } } /// Render one part according to its `kind`. Unknown kinds render an inline /// note instead of crashing. #let _render-part(part, flags) = { let kind = part.at("kind", default: none) if kind == "segment" { display-segment(part) } else if kind == "example" { display-example(part, show-solution: flags.show-solution) } else if kind == "lemma" { display-lemma(part, show-proof: flags.show-proof) } else if kind == "sop" { display-sop(part) } else { // Don't crash on an unrecognised kind; surface it visibly instead. block( width: 100%, inset: 0.6em, fill: red.lighten(85%), stroke: red.darken(20%) + 0.6pt, radius: 0.4em, text(fill: red.darken(30%), weight: "bold")[ [未知部件类型 / unknown kind: #repr(kind)] ], ) } } /// THE ENTRY POINT. /// /// - `info`: dict, e.g. (title: "…", author: "…"). `author` may be absent. /// - `target`: string. MVP: "student" | "teacher". Unknown => conservative. /// - `parts`: ordered array of part dicts (see file header). /// - `config`: dict carrying the target's build/presentation overrides /// (ADR-0009). Open/forward-compatible — read keys with /// `.at(.., default: ..)`. Recognised keys (MVP): /// `config.numbering.heading`: array of per-level numbly pattern /// strings, e.g. `("{1:一}、", "{1:1}.{2:1}")`. Optional; when /// absent the framework default (correct per-level scheme) is /// used. Future presentation knobs slot in without touching /// this signature. #let display(info: (:), target: "student", parts: (), config: (:)) = { let title = info.at("title", default: []) let author = info.at("author", default: none) // Resolve presentation config -> framework defaults, file may override. // `config.numbering.heading` (array of numbly patterns) overrides the // framework default per-level numbering when present. let numbering-cfg = config.at("numbering", default: (:)) let heading-numbering = numbering-cfg.at( "heading", default: default-heading-numbering, ) // `document` author wants a string/array; normalise the optional field. set document( title: title, author: if author == none { () } else { author }, ) show: base-style.with(heading-numbering: heading-numbering) // Reset shared counters so each rendered lesson numbers from 1. example-counter.update(0) lemma-counter.update(0) let flags = _flags-for(target) title-block() subtitle-block(flags.subtitle) // Render strictly in array order. for part in parts { _render-part(part, flags) } }