@airmark/quiver 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +82 -33
  2. package/package.json +7 -5
  3. package/quills/af4141/0.1.0/Quill.yaml +21 -11
  4. package/quills/af4141/0.1.0/packages/typst-af4141/lib.typ +17 -3
  5. package/quills/af4141/0.1.0/plate.typ +11 -4
  6. package/quills/daf1206/0.1.0/Quill.yaml +3 -1
  7. package/quills/daf4392/0.1.0/Quill.yaml +14 -11
  8. package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/lib.typ +21 -4
  9. package/quills/usaf_memo/0.2.0/Quill.yaml +60 -44
  10. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/body.typ +37 -15
  11. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +5 -6
  12. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +5 -3
  13. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +87 -35
  14. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/utils.typ +47 -5
  15. package/quills/usaf_memo/0.2.0/plate.typ +15 -3
  16. package/quills/usaf_memo/0.3.0/Quill.yaml +321 -0
  17. package/quills/usaf_memo/0.3.0/assets/dod_seal.png +0 -0
  18. package/quills/usaf_memo/0.3.0/assets/dow_seal.png +0 -0
  19. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/.gitignore +7 -0
  20. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/LICENSE +21 -0
  21. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel[wght].ttf +0 -0
  22. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/LICENSE +93 -0
  23. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/CopperplateCC-Bold.otf +0 -0
  24. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
  25. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/LICENSE.md +79 -0
  26. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/LiberationMono/LICENSE +102 -0
  27. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/LiberationMono/LiberationMono-Regular.ttf +0 -0
  28. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/GNU General Public License.txt +340 -0
  29. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-Med.otf +0 -0
  30. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-MedIta.otf +0 -0
  31. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-Reg.otf +0 -0
  32. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-RegIta.otf +0 -0
  33. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/backmatter.typ +29 -0
  34. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/body.typ +364 -0
  35. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/config.typ +78 -0
  36. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +242 -0
  37. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +170 -0
  38. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/lib.typ +57 -0
  39. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/mainmatter.typ +27 -0
  40. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +462 -0
  41. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/utils.typ +419 -0
  42. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/typst.toml +27 -0
  43. package/quills/usaf_memo/0.3.0/plate.typ +120 -0
  44. package/quills/af4141/0.1.0/design/TASK.md +0 -20
  45. package/quills/af4141/0.1.0/design/af4141.pdf +0 -0
  46. package/quills/daf1206/0.1.0/DAFForm_1206_prepared.pdf +0 -0
  47. package/quills/usaf_memo/0.2.0/.quillignore +0 -31
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @airmark/quiver
2
2
 
3
- A [Quillmark](https://github.com/nibsbin/quillmark) Source Quiver of Air Force
4
- and DAF official-document Quills, sourced from
3
+ A [Quillmark](https://github.com/nibsbin/quillmark) source quiver of Air Force
4
+ and DAF official-document quills, sourced from
5
5
  [tonguetoquill-collection](https://github.com/nibsbin/tonguetoquill-collection)
6
6
  and aligned to the current Quillmark spec.
7
7
 
@@ -9,7 +9,8 @@ and aligned to the current Quillmark spec.
9
9
 
10
10
  | Quill | Version | Description |
11
11
  |--------------|---------|-----------------------------------------------------------------------------------|
12
- | `usaf_memo` | 0.2.0 | USAF / DAF Official Memorandum (AFH 33-337) |
12
+ | `usaf_memo` | 0.3.0 | USAF / DAF Official Memorandum (AFH 33-337) |
13
+ | `usaf_memo` | 0.2.0 | Previous release, retained so documents pinned to `@0.2` keep resolving |
13
14
  | `af4141` | 0.1.0 | AF Form 4141 — Individual's Record of Duties and Experience (Ground Environment) |
14
15
  | `daf1206` | 0.1.0 | DAF Form 1206 — Nomination for Award |
15
16
  | `daf4392` | 0.1.0 | DAF Form 4392 — Pre-Departure Safety Briefing (Page 2) |
@@ -20,30 +21,33 @@ and aligned to the current Quillmark spec.
20
21
  npm install @airmark/quiver @quillmark/quiver @quillmark/wasm
21
22
  ```
22
23
 
23
- This package ships only the Source Quiver assets (`Quiver.yaml` + `quills/`)
24
- and exposes no JavaScript API. All loading is performed by `@quillmark/quiver`,
25
- either from the on-disk package directory or from a packed archive fetched
26
- over HTTP.
24
+ This package ships only the source-quiver assets (`Quiver.yaml` + `quills/`) and
25
+ exposes no JavaScript API. Loading is `@quillmark/quiver`'s, rendering is
26
+ `@quillmark/wasm`'s.
27
27
 
28
28
  ## Usage
29
29
 
30
- ### Load from the exported directory
30
+ ### Load from the installed directory (Node)
31
31
 
32
- Resolve the package's `Quiver.yaml` via `import.meta.resolve` and hand it to
33
- `@quillmark/quiver`:
32
+ Resolve this package's `Quiver.yaml` from your own module — your dependencies are
33
+ reachable from you, not from the loader's install location — and hand its
34
+ directory to `fromDir`:
34
35
 
35
36
  ```ts
36
- import { fileURLToPath } from 'node:url';
37
- import path from 'node:path';
38
- import { Quillmark, Document } from '@quillmark/wasm';
39
- import { Quiver } from '@quillmark/quiver/node';
37
+ import { createRequire } from 'node:module';
38
+ import { dirname } from 'node:path';
39
+ import { Engine, init } from '@quillmark/wasm';
40
+ import { fromDir } from '@quillmark/quiver/node';
40
41
 
41
- const quiverYaml = fileURLToPath(import.meta.resolve('@airmark/quiver/Quiver.yaml'));
42
- const quiver = await Quiver.fromDir(path.dirname(quiverYaml));
42
+ // `init` is the only door to `Document`: there is no static export to reach early.
43
+ const { Document } = await init();
44
+
45
+ const root = dirname(createRequire(import.meta.url).resolve('@airmark/quiver/Quiver.yaml'));
46
+ const quiver = await fromDir(root);
47
+ const engine = new Engine();
43
48
 
44
- const engine = new Quillmark();
45
49
  const doc = Document.fromMarkdown(`~~~
46
- $quill: usaf_memo@0.2
50
+ $quill: usaf_memo@0.3
47
51
  $kind: main
48
52
  memo_for: ["ORG/SYMBOL"]
49
53
  memo_from: ["ORG/SYMBOL", "Organization Name", "123 Street Ave", "City ST 12345-6789"]
@@ -53,19 +57,31 @@ signature_block: ["FIRST M. LAST, Rank, USAF", "Duty Title"]
53
57
 
54
58
  Body of the memo.`);
55
59
 
56
- const quill = await quiver.getQuill(doc.quillRef, { engine });
57
- const { artifacts } = quill.render(doc, { format: 'pdf' });
60
+ const quill = await quiver.getQuill(doc.quillRef);
61
+ const { artifacts } = await engine.render(quill, doc, { format: 'pdf' });
58
62
  ```
59
63
 
60
- ### Load a packed Quiver via HTTP
64
+ A quill from `getQuill` is borrowed: every caller asking for that ref gets the
65
+ same instance, so it is not yours to `free()`.
66
+
67
+ ### Load over HTTP (browser)
68
+
69
+ A browser cannot read the source layout, so pack it at deploy time and serve the
70
+ output as static files:
71
+
72
+ ```ts
73
+ import { build } from '@quillmark/quiver/node'; // build step
74
+ await build(root, './public/quivers/airmark');
75
+ ```
61
76
 
62
- Publish a packed Quiver archive (e.g. as a CDN or GitHub release asset) and
63
- load it in the browser or any fetch-capable runtime through `@quillmark/quiver`.
64
- See the upstream `@quillmark/quiver` docs for the packed-format and HTTP API.
77
+ ```ts
78
+ import { Quiver } from '@quillmark/quiver'; // browser runtime
79
+ const quiver = await Quiver.fromBuiltUrl('/quivers/airmark/');
80
+ ```
65
81
 
66
82
  ## Layout
67
83
 
68
- This package is a [Source Quiver](https://www.npmjs.com/package/@quillmark/quiver)
84
+ This package is a [source quiver](https://www.npmjs.com/package/@quillmark/quiver)
69
85
  conforming to the canonical layout:
70
86
 
71
87
  ```
@@ -79,20 +95,53 @@ quills/
79
95
  packages/
80
96
  ```
81
97
 
82
- ## Testing
98
+ ## Working on a quill
83
99
 
84
- `quiver.test.js` at the repo root runs the
85
- [`@quillmark/quiver/testing`](https://www.npmjs.com/package/@quillmark/quiver)
86
- suite under the built-in [`node:test`](https://nodejs.org/api/test.html) runner,
87
- validating each `(quill, version)` pair through the full load +
88
- `engine.quill(tree)` compile pipeline:
100
+ Two verbs, and they answer different questions.
101
+
102
+ Both verbs are [`quillkit`](https://www.npmjs.com/package/quillkit)'s, the quill
103
+ author's toolchain. It resolves `@quillmark/quiver` and `@quillmark/wasm` out of
104
+ this collection's own `node_modules`, so the versions pinned here are the format
105
+ the quiver is packed in and the wasm the gate renders through.
106
+
107
+ **Does it work?** `quillkit test` loads the collection with `fromDir`, compiles
108
+ every quill, and renders each one's example document — the blueprint seeded from
109
+ the `example:` values in `Quill.yaml`. It is the gate CI runs, so a validation
110
+ failure surfaces here rather than on a consumer's build:
89
111
 
90
112
  ```bash
91
113
  npm install
92
114
  npm test
93
115
  ```
94
116
 
117
+ The gate renders nothing the schema did not write, so the coverage is the
118
+ `example:` block: a field with no example is a field no render exercises.
119
+
120
+ **What is it like to use?** `npm run dev` is `quillkit studio`: it packs this
121
+ quiver, serves the studio client over it, and repacks on every save — pick a
122
+ quill, edit the seeded document, watch it paint, read the diagnostics. The
123
+ client is quillkit's own, so there is nothing to install for it and nothing to
124
+ keep in step.
125
+
126
+ Reload to pick up a repack. Editing a quill into an invalid state is not a
127
+ failure of the loop: it packs, and the client says what is wrong with that quill
128
+ and where, while the others stay usable.
129
+
130
+ The two verbs answer to different authorities. `quillkit test` renders through
131
+ the wasm pinned here and is what CI blocks on; the client renders through the
132
+ wasm it was built against, and nothing at runtime reconciles the two. The gate
133
+ is authoritative, studio is advisory.
134
+
135
+ `npm run site` writes the arrangement a deploy serves into `site/` — the client
136
+ at the root, a built quiver at `quiver/` beneath it — without serving it. CI
137
+ uploads that on every run, so a pull request — a fork's included — is reviewed
138
+ by downloading the artifact and serving the directory.
139
+
140
+ `main` is deployed to GitHub Pages by `.github/workflows/studio.yml`, which runs
141
+ that same command and keeps the deploy here: nothing outside this repository
142
+ holds `pages: write`.
143
+
95
144
  ## License
96
145
 
97
- Apache-2.0. Individual Quills carry their own licensing terms; see the
98
- `packages/` directory inside each Quill for upstream font and template licenses.
146
+ Apache-2.0. Individual quills carry their own licensing terms; see the
147
+ `packages/` directory inside each quill for upstream font and template licenses.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airmark/quiver",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "Airmark Quiver — Air Force and DAF document Quills for Quillmark.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,12 +16,14 @@
16
16
  "node": ">=24"
17
17
  },
18
18
  "devDependencies": {
19
- "@quillmark/quiver": "0.15.0",
20
- "@quillmark/wasm": "^0.91.0"
19
+ "@quillmark/quiver": "0.21.0",
20
+ "@quillmark/wasm": "^0.104.0",
21
+ "quillkit": "0.2.2"
21
22
  },
22
23
  "scripts": {
23
- "test": "quiver test",
24
- "preview": "quiver preview"
24
+ "test": "quillkit test",
25
+ "site": "quillkit site --out site",
26
+ "dev": "quillkit studio"
25
27
  },
26
28
  "keywords": [
27
29
  "quillmark",
@@ -2,9 +2,11 @@ quill:
2
2
  name: af4141
3
3
  version: 0.1.0
4
4
  backend: typst
5
- plate_file: plate.typ
6
5
  description: "AF Form 4141 — Individual's Record of Duties and Experience, Ground Environment Personnel"
7
6
 
7
+ typst:
8
+ plate_file: plate.typ
9
+
8
10
  main:
9
11
  body:
10
12
  enabled: false
@@ -36,58 +38,66 @@ card_kinds:
36
38
  description: Each card represents one row in the Record of Experience table. Rows fill page 1 (up to 16 rows) then page 2 (up to 21 rows), for a maximum of 37 rows.
37
39
  ui:
38
40
  title: Record of Experience
41
+ groups: [record]
39
42
  body:
40
43
  enabled: false
41
44
  fields:
42
45
  date:
43
- type: datetime
46
+ type: date
44
47
  default: ""
48
+ example: "2026-03-14"
45
49
  ui:
46
- group: Record
50
+ group: record
47
51
  description: Date of the action (column A).
48
52
 
49
53
  action:
50
54
  type: string
51
55
  default: ""
56
+ example: Upgraded to 5-level; assumed ADO duties
52
57
  ui:
53
- group: Record
58
+ group: record
54
59
  description: One type of action per line (column B).
55
60
 
56
61
  written_grade:
57
62
  type: string
58
63
  default: ""
64
+ example: "3"
59
65
  ui:
60
- group: Record
66
+ group: record
61
67
  compact: true
62
68
  description: Written grade, if applicable (column C).
63
69
 
64
70
  written_grade_date:
65
- type: datetime
71
+ type: date
66
72
  default: ""
73
+ example: "2026-02-28"
67
74
  ui:
68
- group: Record
75
+ group: record
69
76
  compact: true
70
77
  description: Date of written grade (column D). Omit or leave blank when there is no written grade.
71
78
 
72
79
  positional_grade:
73
80
  type: string
74
81
  default: ""
82
+ example: "2"
75
83
  ui:
76
- group: Record
84
+ group: record
77
85
  compact: true
78
86
  description: Positional grade, if applicable (column E).
79
87
 
80
88
  positional_grade_date:
81
- type: datetime
89
+ type: date
82
90
  default: ""
91
+ example: "2026-03-14"
83
92
  ui:
84
- group: Record
93
+ group: record
85
94
  compact: true
86
95
  description: Date of positional grade (column F). Omit or leave blank when there is no positional grade.
87
96
 
88
97
  auth_or_remarks:
89
98
  type: string
90
99
  default: ""
100
+ example: "J.A.D."
91
101
  ui:
92
- group: Record
102
+ group: record
93
103
  description: Authentication or remarks entry (column G).
@@ -11,7 +11,15 @@
11
11
  /// True for short/narrow fields with brief content (grades, ranks, dates).
12
12
  #let should-shrink-to-fit(display, width, height) = {
13
13
  let aspect = width / height
14
- let char-count = display.len()
14
+ // A date field's content is a single `text` element, and the string inside it
15
+ // is what decides the shrink, as a plain value's own does. Any other content
16
+ // (a styled label, a sequence) has no such string and counts as long, so only
17
+ // the dimension conditions apply.
18
+ let char-count = if type(display) == str {
19
+ display.len()
20
+ } else {
21
+ display.at("text", default: "0" * 99).len()
22
+ }
15
23
  char-count <= 10 or height < 20pt or aspect > 4.0
16
24
  }
17
25
 
@@ -81,8 +89,14 @@
81
89
  /// - field (dictionary): raw field entry from the schema
82
90
  #let render-field(field-type, value, width, height, field) = {
83
91
  if field-type == "text" {
84
- if value != none and str(value) != "" {
85
- render-text-field(str(value), width, height, 1.5pt, 1pt)
92
+ if value != none {
93
+ // A date field arrives as content carrying its click-to-edit region, so it
94
+ // is placed as it stands; a plain string is placed only when non-empty.
95
+ if type(value) == content {
96
+ render-text-field(value, width, height, 1.5pt, 1pt)
97
+ } else if str(value) != "" {
98
+ render-text-field(str(value), width, height, 1.5pt, 1pt)
99
+ }
86
100
  }
87
101
  } else if field-type == "checkbox" {
88
102
  if value == true {
@@ -3,13 +3,20 @@
3
3
 
4
4
  #set text(font: "NimbusRomNo9L")
5
5
 
6
- // `type: date` fields arrive as Typst `datetime`; PDF overlay expects strings.
7
- // Missing / blank dates: helper uses `none`; empty strings may also appear from input.
6
+ // A `type: date` field lowers to a click-to-edit value object
7
+ // `(value: datetime, display: closure)`; `display` is a stored closure on a dict
8
+ // rather than a method, so it is called through parentheses, and the content it
9
+ // returns carries the region that makes the glyphs clickable. A native datetime
10
+ // is still accepted. Missing / blank dates arrive as `none`; empty strings may
11
+ // also appear from input.
8
12
  #let form-cell(v) = {
13
+ let pattern = "[month padding:none]/[day padding:none]/[year]"
9
14
  if v == none { "" }
10
15
  else if type(v) == str and v == "" { "" }
11
- else if type(v) == datetime {
12
- v.display("[month padding:none]/[day padding:none]/[year]")
16
+ else if type(v) == dictionary {
17
+ (v.display)(pattern)
18
+ } else if type(v) == datetime {
19
+ v.display(pattern)
13
20
  } else {
14
21
  str(v)
15
22
  }
@@ -2,9 +2,11 @@ quill:
2
2
  name: daf1206
3
3
  version: 0.1.0
4
4
  backend: typst
5
- plate_file: plate.typ
6
5
  description: "DAF Form 1206 — Nomination for Award"
7
6
 
7
+ typst:
8
+ plate_file: plate.typ
9
+
8
10
  main:
9
11
  body:
10
12
  example: |
@@ -2,21 +2,23 @@ quill:
2
2
  name: daf4392
3
3
  version: 0.1.0
4
4
  backend: typst
5
- plate_file: plate.typ
6
5
  description: "DAF Form 4392 - Pre-Departure Safety Briefing (Page 2)"
7
6
 
7
+ typst:
8
+ plate_file: plate.typ
9
+
8
10
  main:
9
11
  body:
10
12
  enabled: false
11
13
  fields:
12
14
  transportation_mode:
13
- type: string
14
- enum: [pmv, airplane, bus, train, motorcycle, other]
15
+ type: enum
16
+ values: [pmv, airplane, bus, train, motorcycle, other]
15
17
  default: pmv
16
18
  description: "Matches the check box."
17
19
 
18
20
  departure_date:
19
- type: datetime
21
+ type: date
20
22
  default: ""
21
23
  example: "2026-06-01"
22
24
  ui:
@@ -59,7 +61,7 @@ main:
59
61
  description: "Unit / Organization."
60
62
 
61
63
  briefed_date:
62
- type: datetime
64
+ type: date
63
65
  default: ""
64
66
  example: "2026-05-22"
65
67
  ui:
@@ -96,41 +98,42 @@ card_kinds:
96
98
  description: "Each card represents one row in the travel itinerary table. Up to 10 rows are supported."
97
99
  ui:
98
100
  title: Proposed Travel Itinerary
101
+ groups: [itinerary_entry]
99
102
  body:
100
103
  enabled: false
101
104
  fields:
102
105
  date:
103
- type: datetime
106
+ type: date
104
107
  default: ""
105
108
  ui:
106
- group: Itinerary Entry
109
+ group: itinerary_entry
107
110
  compact: true
108
111
  description: "Date for the itinerary row."
109
112
  departure_point:
110
113
  type: string
111
114
  default: ""
112
115
  ui:
113
- group: Itinerary Entry
116
+ group: itinerary_entry
114
117
  compact: true
115
118
  description: "Point of departure."
116
119
  arrival_point:
117
120
  type: string
118
121
  default: ""
119
122
  ui:
120
- group: Itinerary Entry
123
+ group: itinerary_entry
121
124
  compact: true
122
125
  description: "Point of arrival."
123
126
  rest_length:
124
127
  type: string
125
128
  default: ""
126
129
  ui:
127
- group: Itinerary Entry
130
+ group: itinerary_entry
128
131
  compact: true
129
132
  description: "Duration of stay at arrival point"
130
133
  mileage:
131
134
  type: string
132
135
  default: ""
133
136
  ui:
134
- group: Itinerary Entry
137
+ group: itinerary_entry
135
138
  compact: true
136
139
  description: "Approximate mileage for this leg (leave blank if not applicable, e.g., flights)."
@@ -31,11 +31,22 @@
31
31
  }
32
32
 
33
33
  /// Format a date value for display as `DD Mon YYYY`.
34
+ ///
35
+ /// Dispatches on the shape a date can arrive in:
36
+ /// - dict: a Quillmark `date` field lowers to a click-to-edit value object
37
+ /// `(value: datetime, display: closure)`. `display` is a stored closure on a
38
+ /// dict rather than a method, so it is called through parentheses, and the
39
+ /// content it returns carries the region that makes the glyphs clickable.
40
+ /// - datetime: a native Typst datetime, from a plate building its own value.
41
+ /// - str: shown as-is.
34
42
  #let format-date(value) = {
43
+ let pattern = "[day padding:zero] [month repr:short] [year]"
35
44
  if value == none {
36
45
  none
46
+ } else if type(value) == dictionary {
47
+ (value.display)(pattern)
37
48
  } else if type(value) == datetime {
38
- value.display("[day padding:zero] [month repr:short] [year]")
49
+ value.display(pattern)
39
50
  } else if type(value) == str and value == "" {
40
51
  ""
41
52
  } else {
@@ -45,11 +56,17 @@
45
56
 
46
57
  /// Should this field shrink text to a single line rather than word-wrap?
47
58
  /// True for short/narrow fields with brief content (grades, ranks, dates).
48
- /// `display` may be a string or content; char-count is skipped for content.
49
59
  #let should-shrink-to-fit(display, width, height) = {
50
60
  let aspect = width / height
51
- // content has no .len(); treat as "long" so only the dimension conditions apply
52
- let char-count = if type(display) == str { display.len() } else { 99 }
61
+ // A date field's content is a single `text` element, and the string inside it
62
+ // is what decides the shrink, as a plain value's own does. Any other content
63
+ // (a styled label, a sequence) has no such string and counts as long, so only
64
+ // the dimension conditions apply.
65
+ let char-count = if type(display) == str {
66
+ display.len()
67
+ } else {
68
+ display.at("text", default: "0" * 99).len()
69
+ }
53
70
  char-count <= 10 or height < 20pt or aspect > 4.0
54
71
  }
55
72