diff --git a/render/examples/smoke-config.typ b/render/examples/smoke-config.typ new file mode 100644 index 0000000..9847344 --- /dev/null +++ b/render/examples/smoke-config.typ @@ -0,0 +1,14 @@ +// Smoke test — exercises the per-target presentation `config` override path +// (ADR-0009). Here the engineering-file-supplied `config.numbering.heading` +// overrides the framework-default heading numbering. The patterns happen to +// match the correct per-level scheme (level-1 `一、`, level-2 `1.1`), proving +// the override is wired AND yields correct (NOT `二、一、`) output. +#import "../lib.typ": display +#import "smoke-parts.typ": info, parts + +#display( + info: info, + target: "teacher", + parts: parts, + config: (numbering: (heading: ("{1:一}、", "{1:1}.{2:1}"))), +) diff --git a/render/examples/smoke-parts.typ b/render/examples/smoke-parts.typ index fec937d..2a118c1 100644 --- a/render/examples/smoke-parts.typ +++ b/render/examples/smoke-parts.typ @@ -8,13 +8,26 @@ ) #let parts = ( - // segment + // segment — with NESTED headings so per-level numbering is visible: + // level-1 `=` should render `一、`, level-2 `==` should render `1.1` + // (NOT the old buggy `二、一、`). ( kind: "segment", textbook: [ + = 平面向量的数量积 + 本节研究平面向量的基本运算。设 $arrow(a)$、$arrow(b)$ 为平面内两个向量, 其数量积定义为 $arrow(a) dot arrow(b) = |arrow(a)| |arrow(b)| cos theta$, 其中 $theta$ 为两向量的夹角。 + + == 坐标表示 + + 在直角坐标系下,若 $arrow(a) = (x_1, y_1)$、$arrow(b) = (x_2, y_2)$,则 + $arrow(a) dot arrow(b) = x_1 x_2 + y_1 y_2$。 + + == 几何意义 + + 数量积等于一个向量的模与另一向量在其方向上投影之积。 ], ), // example WITH source diff --git a/render/lib.typ b/render/lib.typ index 04f64e7..ac45b1d 100644 --- a/render/lib.typ +++ b/render/lib.typ @@ -23,7 +23,7 @@ // fields). Never crashes. The "no render rule => warning" // diagnostic is the Rust side's job, not ours. -#import "src/style.typ": base-style, title-block, subtitle-block +#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 @@ -75,17 +75,34 @@ /// - `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). -#let display(info: (:), target: "student", parts: ()) = { +/// - `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 + show: base-style.with(heading-numbering: heading-numbering) // Reset shared counters so each rendered lesson numbers from 1. example-counter.update(0) diff --git a/render/src/style.typ b/render/src/style.typ index cb97c20..b26313f 100644 --- a/render/src/style.typ +++ b/render/src/style.typ @@ -1,18 +1,36 @@ // 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. +// Heading numbering is PER-LEVEL and built from a numbly pattern array passed +// in by the caller (lib.typ resolves it from config + framework default). This +// fixes the old single-pattern bug where one `numbering: "一、"` was reused for +// every level (a level-2 heading rendered as `二、一、…`). The only @preview +// dependency is `@preview/numbly`, vendored under `render/vendor/...` for +// network-free CI; see render/typst.toml's note. +#import "@preview/numbly:0.1.0": numbly #import "fonts.typ" -/// Apply the base style to a document body. Used as `show: base-style`. -#let base-style(doc) = { +/// Framework-default per-level heading numbering patterns (numbly syntax). +/// Level 1 -> `一、`, level 2 -> `1.1`, level 3 -> `1.1.1`. This is the +/// correct-by-default scheme; `config.numbering.heading` overrides it. +#let default-heading-numbering = ( + "{1:一}、", + "{1:1}.{2:1}", + "{1:1}.{2:1}.{3:1}", +) + +/// Apply the base style to a document body. Used as `show: base-style(..)`. +/// +/// `heading-numbering` is an array of per-level numbly pattern strings; it is +/// spread into `numbly(..)` to build the per-level heading numbering function. +#let base-style(heading-numbering: default-heading-numbering, 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: "一、") + // Per-level heading numbering via numbly: each level gets its own pattern, + // so level 2 renders `1.1`, not the old buggy `二、一、`. + set heading(numbering: numbly(..heading-numbering)) show heading: set text(font: fonts.sans) // Number display equations. diff --git a/render/typst.toml b/render/typst.toml index 058fabc..b45f153 100644 --- a/render/typst.toml +++ b/render/typst.toml @@ -6,3 +6,13 @@ compiler = "0.15.0" authors = ["curriculum-project-hub"] license = "MIT" description = "Curriculum lesson render package: single `display` entry over an ordered list of typed parts (segment/example/lemma/sop), targeting student/teacher handouts." + +# External @preview dependencies (resolved via the typst package registry): +# @preview/numbly:0.1.0 — per-level heading numbering (src/style.typ). +# numbly is dependency-free (pure typst). For network-free CI the package is +# VENDORED in-repo at: +# render/vendor/typst-packages/preview/numbly/0.1.0/ +# mirroring the typst preview-cache layout +# ({cache}/typst/packages/preview/numbly/0.1.0/). Point typst's package cache / +# resolver at render/vendor/typst-packages (e.g. TYPST_PACKAGE_CACHE_PATH or the +# embedded World's preview dir) so `@preview/numbly:0.1.0` resolves offline. diff --git a/render/vendor/typst-packages/preview/numbly/0.1.0/LICENSE b/render/vendor/typst-packages/preview/numbly/0.1.0/LICENSE new file mode 100644 index 0000000..9162d3e --- /dev/null +++ b/render/vendor/typst-packages/preview/numbly/0.1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 梦飞翔 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/render/vendor/typst-packages/preview/numbly/0.1.0/README.md b/render/vendor/typst-packages/preview/numbly/0.1.0/README.md new file mode 100644 index 0000000..c8547fe --- /dev/null +++ b/render/vendor/typst-packages/preview/numbly/0.1.0/README.md @@ -0,0 +1,43 @@ +# numbly + +A package that helps you to specify different numbering formats for different levels of headings. + +Suppose you want to specify the following numbering format for your document: + +- Appendix A. Guide + - A.1. Installation + - Step 1. Download + - Step 2. Install + - A.2. Usage + +You might use `if` to achieve this: + +```typst +#set heading(numbering: (..nums) => { + nums = nums.pos() + if nums.len() == 1 { + return "Appendix " + numbering("A.", ..nums) + } else if nums.len() == 2 { + return numbering("A.1.", ..nums) + } else { + return "Step " + numbering("1.", nums.last()) + } +}) + += Guide +== Installation +=== Download +=== Install +== Usage +``` + +But with `numbly`, you can do this more easily: + +```typst +#import "@preview/numbly:0.1.0": numbly +#set heading(numbering: numbly( + "Appendix {1:A}.", // use {level:format} to specify the format + "{1:A}.{2}.", // if format is not specified, arabic numbers will be used + "Step {3}.", // here, we only want the 3rd level +)) +``` diff --git a/render/vendor/typst-packages/preview/numbly/0.1.0/lib.typ b/render/vendor/typst-packages/preview/numbly/0.1.0/lib.typ new file mode 100644 index 0000000..baac794 --- /dev/null +++ b/render/vendor/typst-packages/preview/numbly/0.1.0/lib.typ @@ -0,0 +1,31 @@ +#let numbly(..arr, default: "1.") = (..nums) => { + let arr = arr.pos() + nums = nums.pos() + if nums.len() > arr.len() { + if default == none { + return none + } + if type(default) == function { + return default(..nums) + } + return numbering(default, ..nums) + } + let format = arr.at(nums.len() - 1) + if format == none { + return none + } + if type(format) == function { + return format(..nums) + } + format.replace( + regex("\{(\d)(:(.+?))?\}"), + m => { + let (a, b, c) = m.captures + if b != none { + numbering(c, nums.at(int(a) - 1)) + } else { + str(nums.at(int(a) - 1)) + } + }, + ) +} diff --git a/render/vendor/typst-packages/preview/numbly/0.1.0/typst.toml b/render/vendor/typst-packages/preview/numbly/0.1.0/typst.toml new file mode 100644 index 0000000..9ecc5e7 --- /dev/null +++ b/render/vendor/typst-packages/preview/numbly/0.1.0/typst.toml @@ -0,0 +1,10 @@ +[package] +name = "numbly" +version = "0.1.0" +entrypoint = "lib.typ" +authors = ["flaribbit <@flaribbit>"] +license = "MIT" +description = "A package that helps you to specify different numbering formats for different levels of headings." +categories = ["utility"] +keywords = ["numbering", "helper", "tool"] +repository = "https://github.com/flaribbit/numbly" \ No newline at end of file