forked from EduCraft/curriculum-project-hub
fix(render): per-level heading numbering via numbly; config-driven, offline (WU-C)
Fixes the numbering bug (single pattern "一、" reused across levels → level-2
rendered "二、一、…"). display now takes a `config` dict; heading numbering uses
numbly per level — framework default ("{1:一}、","{1:1}.{2:1}","{1:1}.{2:1}.{3:1}")
→ 一、 / 1.1 / 1.1.1 — overridable via config.numbering.heading from the
engineering file (ADR-0009's file-resident override path, the layer whose absence
caused the bug). Verified: grep "二、一" = 0 across all smoke PDFs.
Adds @preview/numbly:0.1.0 (pure typst, zero transitive deps), VENDORED in-repo
at render/vendor/typst-packages/preview/numbly/0.1.0/ for network-free CI;
resolve via --package-cache-path render/vendor/typst-packages. parts contract +
student/teacher field visibility unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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}"))),
|
||||
)
|
||||
@@ -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
|
||||
|
||||
+20
-3
@@ -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)
|
||||
|
||||
+24
-6
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
))
|
||||
```
|
||||
@@ -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))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user