@airmark/quiver 0.35.6 → 0.35.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airmark/quiver",
3
- "version": "0.35.6",
3
+ "version": "0.35.7",
4
4
  "description": "Airmark Quiver — Air Force and DAF document Quills for Quillmark.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,9 +18,9 @@
18
18
  "node": ">=24"
19
19
  },
20
20
  "devDependencies": {
21
- "@quillmark/quiver": "0.33.0",
22
- "@quillmark/wasm": "^0.121.0",
23
- "quillkit": "0.13.0"
21
+ "@quillmark/quiver": "0.36.0",
22
+ "@quillmark/wasm": "^0.124.0",
23
+ "quillkit": "0.16.0"
24
24
  },
25
25
  "scripts": {
26
26
  "test": "quillkit test && node scripts/check-examples.mjs && node scripts/check-templates.mjs",
@@ -1,109 +1,103 @@
1
1
  ~~~
2
2
  $quill: classic_resume
3
3
  $kind: main
4
- name: Nibs Bin
4
+ name: Philip J. Fry
5
5
  contacts:
6
- - nibs@tonguetoquill.com
7
- - (333) 333-3333
8
- - github.com/nibsbin
9
- - linkedin.com/in/nibsbin/
6
+ - fry@planetexpress.example
7
+ - (212) 555-0199
8
+ - github.com/borb-sh
10
9
  - New New York
11
10
  paper: us-letter
12
11
  font_size: 11.5
13
12
  margin: 0.5
14
13
  ~~~
15
14
 
15
+ ~~~
16
+ $kind: summary
17
+ ~~~
18
+
19
+ Delivery boy with a thousand years of experience, most of it frozen. Brings 20th-century know-how to 31st-century problems and has saved Earth more than once. Wants to keep delivering anywhere, ideally with fewer space bees.
20
+
16
21
  ~~~
17
22
  $kind: certifications
18
23
  title: Certifications
19
24
  columns: 2
20
25
  items:
21
- - Offensive Security Certified Professional (OSCP)
22
- - GIAC Cyber Threat Intelligence (GCTI)
23
- - CompTIA CASP+, CySA+, Sec+, Net+, A+, Proj+
24
- - GIAC Machine Learning Engineer (GMLE)
26
+ - "Career Chip: Delivery Boy"
27
+ - Earth Army Basic Training
28
+ - First Aid, taught by Dr. Zoidberg
29
+ - Food Handler's Permit, New York
25
30
  ~~~
26
31
 
27
32
  ~~~
28
33
  $kind: skills
29
34
  columns: 2
30
35
  skills:
31
- - label: Programming
32
- items: Python, R, JS, C#, Rust, PowerShell, CI/CD
33
- - label: Data Science
34
- items: ML/statistics, TensorFlow, AI Engineering
35
- - label: IT & Cybersecurity
36
- items: AD DS, Splunk, Metasploit, Wireshark, Nessus
37
- - label: Cloud
38
- items: AWS EC2/S3, Helm, Docker, Serverless
36
+ - label: Logistics
37
+ items: Packages, pizza, interplanetary freight
38
+ - label: 20th-Century Culture
39
+ items: TV, arcade games, Star Trek trivia
40
+ - label: Music
41
+ items: Holophonor, opera composition
42
+ - label: Endurance
43
+ items: Cryogenic sleep, 100 cups of coffee
39
44
  ~~~
40
45
 
41
46
  ~~~
42
47
  $kind: experience
43
48
  jobs:
44
- - company: Planet Express Delivery Division
45
- dates: August 2024 – Present
46
- role: Interstellar Logistics Analyst
49
+ - company: Planet Express
50
+ dates: December 2999 – Present
51
+ role: Delivery Boy
47
52
  location: New New York
48
53
  details: |-
49
- - Analyzed dark matter fuel combustion following the Nibblonian incident, developing safeguards to protect remaining ship engine infrastructure.
54
+ - Deliver packages anywhere in the universe with Captain Turanga Leela and Bender Bending Rodríguez.
50
55
 
51
- - Building automated hazard detection pipelines using Bender's antenna arrays to monitor Omicronian threat signatures across the sector.
52
- - company: MomCorp Robotics Academy
53
- dates: May 2025 – July 2025
54
- role: Robot Firmware Trainee
55
- location: Mars Vegas
56
- details: "- Developed behavior-inhibitor firmware for MomCorp robots integrating real-time Slurm demand intelligence."
57
- - company: DOOP Starship Nimbus
58
- dates: January 2018 – June 2020
59
- role: Combat Engineer
60
- location: Vergon 6
61
- details: "- Administrated Nimbus shipboard systems, Kif's maintenance logs, and killbot defense automation for 30,000+ crew."
56
+ - Completed runs to robot-only Chapek 9 and a hive of giant space bees, and survived both.
57
+ - company: Earth Army
58
+ dates: November 3000
59
+ role: Private
60
+ location: Spheron 1
61
+ details: "- Enlisted for the military discount; deployed against the Spheroids under Zapp Brannigan."
62
+ - company: Panucci's Pizza
63
+ dates: June 1997 – December 1999
64
+ role: Delivery Boy
65
+ location: New York, NY
66
+ details: "- Delivered pizza across New York City until a prank order to Applied Cryogenics ran 1,000 years late."
62
67
  title: Experience
63
68
  ~~~
64
69
 
65
70
  ~~~
66
71
  $kind: education
67
72
  schools:
68
- - school: Carnegie Mellon University
69
- dates: December 2025
70
- degree: Master of Information Technology Strategy
71
- location: Pittsburgh, PA
72
- - school: United States Air Force Academy
73
- dates: May 2024
74
- degree: BS, Data Science
75
- location: Colorado Springs, CO
76
- details: |-
77
- - Distinguished Graduate (top 10%); Chinese language minor (L2+/R1 on DLPT).
78
-
79
- - Professor Bradley A. Warner Data Science Catalyst and Top Cadet in Computer Networks.
80
- - school: Community College of the Air Force
81
- dates: February 2029
82
- degree: AS, Information Systems Technology
83
- location: Online
73
+ - school: Mars University
74
+ dates: September 3000
75
+ degree: Undeclared, no degree
76
+ location: Mars
84
77
  ~~~
85
78
 
86
79
  ~~~
87
80
  $kind: other
88
- title: Cyber Competition
81
+ title: Planetary Defense
89
82
  entries:
90
- - heading: 1st in SANS Academy Cup 2024
91
- details: "- Competed as the Delogrand Web Exploit SME, solving SQLi, API, and HTTP packet crafting problems."
92
- - heading: 1st in NCX 2023
83
+ - heading: Saved Earth from the Brainspawn
84
+ details: "- Held the line as the only human immune to the Brainspawn, thanks to a missing Delta brainwave."
85
+ - heading: Ended the Omicronian Invasion
93
86
  details: |-
94
- - Developed strategies, defensive scripts, and exploits for the Cyber Combat event.
87
+ - Restaged the lost finale of *Single Female Lawyer* for Lrrr, ruler of the planet Omicron Persei 8.
95
88
 
96
- - Analyzed logs with Bash and Python for the Data Analysis event.
89
+ - Caused the crisis in the first place by knocking the original broadcast off the air in 1999.
90
+ - heading: Deflected a Giant Ball of Garbage
91
+ details: "- Helped knock it off course with a second ball of garbage, built on 20th-century expertise."
97
92
  ~~~
98
93
 
99
94
  ~~~
100
95
  $kind: projects
101
96
  title: Projects
102
97
  projects:
103
- - name: TongueToQuill
104
- link: tonguetoquill.com
105
- details: "- Rich markdown editor for perfectly formatted USAF and USSF documents with Claude MCP integration."
106
98
  - name: Quillmark
107
- link: github.com/nibsbin/quillmark
108
- details: "- Data-driven document engine for generating arbitrarily typesetted documents from markdown."
99
+ link: github.com/borb-sh/quillmark
100
+ details: "- Schema-driven document engine that delivers typeset documents from Markdown, this resume included."
101
+ - name: Holophonor Opera
102
+ details: "- Wrote and performed an opera about Leela on the holophonor, using the Robot Devil's hands."
109
103
  ~~~
@@ -80,6 +80,12 @@
80
80
  none
81
81
  }
82
82
 
83
+ let tag-line = if falsey(footer-tag-line) {
84
+ none
85
+ } else {
86
+ align(center, text(fill: LETTERHEAD_COLOR, font: "Spectral SC", size: 15pt)[#footer-tag-line])
87
+ }
88
+
83
89
  set par(leading: spacing.line, spacing: spacing.line, justify: false)
84
90
  set block(above: spacing.line, below: 0em, spacing: 0em)
85
91
  set text(font: body-font, size: font-size, fallback: true)
@@ -126,14 +132,11 @@
126
132
  )
127
133
  }
128
134
 
129
- if not falsey(footer-tag-line) {
130
- place(
131
- bottom + center,
132
- dy: -0.625in,
133
- align(center)[
134
- #text(fill: LETTERHEAD_COLOR, font: "Spectral SC", size: 15pt)[#footer-tag-line]
135
- ],
136
- )
135
+ // Page 1's tag line rides the indicator block's float when there is one.
136
+ if tag-line != none {
137
+ context if cui-indicator == none or here().page() > 1 {
138
+ place(bottom + center, dy: -0.625in, tag-line)
139
+ }
137
140
  }
138
141
  },
139
142
  )
@@ -142,7 +145,8 @@
142
145
  // corner, dropped into the 0.5in page-edge band. Emitted as a bottom float so
143
146
  // it (1) reserves flow space, raising page 1's effective bottom margin so body
144
147
  // text never overlaps it, and (2) stays pinned to page 1 — as the first flow
145
- // content it can never be bumped to page 2.
148
+ // content it can never be bumped to page 2. Page 1's tag line stands above the
149
+ // block in the same float, since both claim the band.
146
150
  if cui-indicator != none {
147
151
  context {
148
152
  // The box shrink-wraps to its widest line; `set align(left)` keeps the
@@ -154,24 +158,35 @@
154
158
  set align(left)
155
159
  cui-indicator
156
160
  })
157
- // Reserve only the part of the block inside the text area (`reserved`):
158
- // float a box of that height, then `place` the full block inside it pushed
159
- // down by `overhang` so the surplus overflows into the edge band. (A bare
161
+ let block-height = measure(indicator-box).height
162
+ let tag-gap = 0.125in
163
+ let stack-height = if tag-line == none {
164
+ block-height
165
+ } else {
166
+ block-height + tag-gap + measure(tag-line, width: page.width - 2 * spacing.margin).height
167
+ }
168
+ // Reserve only the part of the stack inside the text area (`reserved`):
169
+ // float a box of that height, then `place` the stack inside it pushed down
170
+ // by `overhang` so the surplus overflows into the edge band. (A bare
160
171
  // `box(height: reserved, indicator-box)` overflows *upward* into the body
161
- // instead.) The inner `place` adds no size, so the box stays `reserved`
172
+ // instead.) The inner `place`s add no size, so the box stays `reserved`
162
173
  // tall and the block's bottom lands 0.5in from the page edge.
163
174
  let overhang = spacing.margin - 0.5in
164
- let reserved = measure(indicator-box).height - overhang
175
+ let reserved = stack-height - overhang
165
176
  place(
166
- bottom + right,
177
+ bottom,
167
178
  float: true,
168
- // Slide the right edge into the page-edge band, 0.5in from the border;
169
- // the inner place right-aligns the block to that edge.
170
- dx: spacing.margin - 0.5in,
171
179
  // Minimum gap to the body's last line; the actual gap is larger when the
172
180
  // next paragraph can't fit above the block and breaks to the next page.
173
181
  clearance: spacing.line,
174
- box(height: reserved, place(bottom + right, dy: overhang, indicator-box)),
182
+ box(width: 100%, height: reserved, {
183
+ // Slide the block's right edge into the page-edge band, 0.5in from
184
+ // the border.
185
+ place(bottom + right, dx: spacing.margin - 0.5in, dy: overhang, indicator-box)
186
+ if tag-line != none {
187
+ place(bottom + center, dy: overhang - block-height - tag-gap, tag-line)
188
+ }
189
+ }),
175
190
  )
176
191
  }
177
192
  }
@@ -76,7 +76,7 @@
76
76
  NESTED.update(n => n + 1)
77
77
  context {
78
78
  let last = BLOCKS.get() == BLOCKS.final()
79
- block(above: par.spacing, sticky: last and keeps-signature(it), it)
79
+ block(above: par.spacing, below: par.spacing, sticky: last and keeps-signature(it), it)
80
80
  }
81
81
  NESTED.update(n => n - 1)
82
82
  }
@@ -197,7 +197,10 @@
197
197
  // spaces to the right of page center"
198
198
  // AFH 33-337 "Do not place the signature element on a continuation page by itself"
199
199
  // AFH 33-337 long-name example: "Signature block adjusted to the left" when a
200
- // long name would otherwise exceed the right margin.
200
+ // long name would otherwise exceed the right margin. Only the name line moves
201
+ // the block; any other line that runs long wraps, its overflow beginning "under
202
+ // the third character of the line above". The handbook gives no measurement,
203
+ // so the indent is the width of the line's own first two characters.
201
204
  //
202
205
  // A closing line may open the section above the block, and AFH 33-337 gives it
203
206
  // one geometry across the two documents that have one: on the second line below
@@ -215,6 +218,17 @@
215
218
  /// -> content | none
216
219
  #let format-authority-line(value) = if falsey(value) { none } else { upper(value) }
217
220
 
221
+ // A signature line arrives as `str` or as content; the indent measures its text.
222
+ #let plain-text(it) = {
223
+ if type(it) == str { return it }
224
+ if type(it) != content { return "" }
225
+ if it.has("text") { return plain-text(it.text) }
226
+ if it.has("children") { return it.children.map(plain-text).join(default: "") }
227
+ if it.has("body") { return plain-text(it.body) }
228
+ if it.has("child") { return plain-text(it.child) }
229
+ ""
230
+ }
231
+
218
232
  #let render-signature-block(
219
233
  signature-lines,
220
234
  closing-line: none,
@@ -234,11 +248,11 @@
234
248
  // anchor. pad() is relative to the text area, hence (4.5in - margin).
235
249
  let default-pad = 4.5in - spacing.margin
236
250
  context {
237
- // Measure each line at its rendered settings to detect long-name overflow.
238
- // The closing line shares the anchor, so it joins the measurement: the
239
- // wider of the two decides the shift and they stay aligned.
251
+ // Measure the name line at its rendered settings to detect long-name
252
+ // overflow. The closing line shares the anchor, so it joins the
253
+ // measurement: the wider of the two decides the shift and they stay aligned.
240
254
  let body-width = page.width - 2 * spacing.margin
241
- let anchored-lines = signature-lines
255
+ let anchored-lines = signature-lines.slice(0, calc.min(1, signature-lines.len()))
242
256
  if closing-line != none { anchored-lines.push(closing-line) }
243
257
  let widest = 0pt
244
258
  for line in anchored-lines {
@@ -305,9 +319,9 @@
305
319
  #pad(left: left-pad)[
306
320
  #text(hyphenate: false)[
307
321
  #for line in signature-lines {
308
- // AFH 33-337: "indent the next line to begin under the third character
309
- // of the line above" — 2-character indent ≈ 1em in Times New Roman 12pt
310
- par(hanging-indent: .5em, line)
322
+ let clusters = plain-text(line).clusters()
323
+ let lead = clusters.slice(0, calc.min(2, clusters.len())).join()
324
+ par(hanging-indent: measure(lead).width, line)
311
325
  }
312
326
  // The page the backmatter compares its own against. Must stay
313
327
  // inside the unbreakable block: a marker outside one travels with
@@ -393,7 +407,11 @@
393
407
  /// - it (content): The table element to style and render
394
408
  /// -> content
395
409
  #let render-memo-table(it) = {
396
- show table.cell.where(y: 0): set text(weight: "bold")
410
+ // A headless table's first row is a body row, which Typst also numbers 0.
411
+ show table: t => if t.children.any(c => c.func() == table.header) {
412
+ show table.cell.where(y: 0): set text(weight: "bold")
413
+ t
414
+ } else { t }
397
415
  set table(
398
416
  stroke: 0.5pt + black,
399
417
  inset: (x: 0.5em, y: 0.4em),
@@ -145,6 +145,20 @@
145
145
  })
146
146
  t
147
147
  }
148
+ // A `qm-table` wrapper's `align` places its table from outside it, so the
149
+ // placement is buffered with the table, and the emission spans the text
150
+ // width for it to place the table across.
151
+ show align: a => if a.body.func() == table {
152
+ PAR_BUFFER.update(pars => {
153
+ pars.push((
154
+ content: a,
155
+ nest_level: -1,
156
+ kind: "table",
157
+ ))
158
+ pars
159
+ })
160
+ none
161
+ } else { a }
148
162
  {
149
163
  show heading: h => {
150
164
  IS_HEADING.update(true)
@@ -268,7 +282,7 @@
268
282
  }
269
283
  let final_par = {
270
284
  if kind == "table" {
271
- render-memo-table(item_content)
285
+ block(width: 100%, render-memo-table(item_content))
272
286
  } else if kind == "continuation" {
273
287
  // Continuation block within a multi-block list item:
274
288
  // indent to align with preceding numbered paragraph's text, no new number.
@@ -92,6 +92,12 @@
92
92
  none
93
93
  }
94
94
 
95
+ let tag_line = if falsey(footer_tag_line) {
96
+ none
97
+ } else {
98
+ align(center, text(fill: LETTERHEAD_COLOR, font: "cinzel", size: 15pt)[#footer_tag_line])
99
+ }
100
+
95
101
  // Document-wide typography settings (inlined from configure())
96
102
  set par(leading: spacing.line, spacing: spacing.line, justify: false)
97
103
  set block(above: spacing.line, below: 0em, spacing: 0em)
@@ -138,14 +144,11 @@
138
144
  )
139
145
  }
140
146
 
141
- if not falsey(footer_tag_line) {
142
- place(
143
- bottom + center,
144
- dy: -0.625in,
145
- align(center)[
146
- #text(fill: LETTERHEAD_COLOR, font: "cinzel", size: 15pt)[#footer_tag_line]
147
- ],
148
- )
147
+ // Page 1's tag line rides the indicator block's float when there is one.
148
+ if tag_line != none {
149
+ context if cui_indicator == none or here().page() > 1 {
150
+ place(bottom + center, dy: -0.625in, tag_line)
151
+ }
149
152
  }
150
153
  },
151
154
  )
@@ -154,7 +157,8 @@
154
157
  // corner, dropped into the 0.5in page-edge band. Emitted as a bottom float so
155
158
  // it (1) reserves flow space, raising page 1's effective bottom margin so body
156
159
  // text never overlaps it, and (2) stays pinned to page 1 — as the first flow
157
- // content it can never be bumped to page 2.
160
+ // content it can never be bumped to page 2. Page 1's tag line stands above the
161
+ // block in the same float, since both claim the band.
158
162
  if cui_indicator != none {
159
163
  context {
160
164
  // The box shrink-wraps to its widest line; `set align(left)` keeps the
@@ -166,24 +170,35 @@
166
170
  set align(left)
167
171
  cui_indicator
168
172
  })
169
- // Reserve only the part of the block inside the text area (`reserved`):
170
- // float a box of that height, then `place` the full block inside it pushed
171
- // down by `overhang` so the surplus overflows into the edge band. (A bare
173
+ let block_height = measure(indicator_box).height
174
+ let tag_gap = 0.125in
175
+ let stack_height = if tag_line == none {
176
+ block_height
177
+ } else {
178
+ block_height + tag_gap + measure(tag_line, width: page.width - 2 * spacing.margin).height
179
+ }
180
+ // Reserve only the part of the stack inside the text area (`reserved`):
181
+ // float a box of that height, then `place` the stack inside it pushed down
182
+ // by `overhang` so the surplus overflows into the edge band. (A bare
172
183
  // `box(height: reserved, indicator_box)` overflows *upward* into the body
173
- // instead.) The inner `place` adds no size, so the box stays `reserved`
184
+ // instead.) The inner `place`s add no size, so the box stays `reserved`
174
185
  // tall and the block's bottom lands 0.5in from the page edge.
175
186
  let overhang = spacing.margin - 0.5in
176
- let reserved = measure(indicator_box).height - overhang
187
+ let reserved = stack_height - overhang
177
188
  place(
178
- bottom + right,
189
+ bottom,
179
190
  float: true,
180
- // Slide the right edge into the page-edge band, 0.5in from the border;
181
- // the inner place right-aligns the block to that edge.
182
- dx: spacing.margin - 0.5in,
183
191
  // Minimum gap to the body's last line; the actual gap is larger when the
184
192
  // next paragraph can't fit above the block and breaks to the next page.
185
193
  clearance: spacing.line,
186
- box(height: reserved, place(bottom + right, dy: overhang, indicator_box)),
194
+ box(width: 100%, height: reserved, {
195
+ // Slide the block's right edge into the page-edge band, 0.5in from
196
+ // the border.
197
+ place(bottom + right, dx: spacing.margin - 0.5in, dy: overhang, indicator_box)
198
+ if tag_line != none {
199
+ place(bottom + center, dy: overhang - block_height - tag_gap, tag_line)
200
+ }
201
+ }),
187
202
  )
188
203
  }
189
204
  }
@@ -358,7 +358,11 @@
358
358
  #let render-memo-table(it) = {
359
359
  // AFH 33-337 does not specify table formatting, so we follow the general
360
360
  // aesthetic principles of the standard: bold headers for clarity.
361
- show table.cell.where(y: 0): set text(weight: "bold")
361
+ // A headless table's first row is a body row, which Typst also numbers 0.
362
+ show table: t => if t.children.any(c => c.func() == table.header) {
363
+ show table.cell.where(y: 0): set text(weight: "bold")
364
+ t
365
+ } else { t }
362
366
  set table(
363
367
  stroke: 0.5pt + black,
364
368
  inset: (x: 0.5em, y: 0.4em),
@@ -12,10 +12,20 @@
12
12
  paragraph-config.numbering-formats.at(level, default: "i.")
13
13
  }
14
14
 
15
+ /// The number of the paragraph last emitted at a level. `level-counts` holds
16
+ /// the number the next one takes, so this is one less, and 1 before any.
17
+ ///
18
+ /// - level (int): Paragraph nesting level (0-based)
19
+ /// - level-counts (dictionary): Maps level index strings to the next number at each level
20
+ /// -> int
21
+ #let emitted-number(level, level-counts) = {
22
+ calc.max(1, level-counts.at(str(level), default: 1) - 1)
23
+ }
24
+
15
25
  /// Calculates indentation for USAF-style paragraphs from explicit counter values.
16
26
  ///
17
27
  /// - level (int): Paragraph nesting level (0-based)
18
- /// - level-counts (dictionary): Maps level index strings to their current counter values
28
+ /// - level-counts (dictionary): Maps level index strings to the next number at each level
19
29
  /// -> length
20
30
  #let calculate-indent-from-counts(level, level-counts) = {
21
31
  if level == 0 {
@@ -23,7 +33,7 @@
23
33
  }
24
34
  let total-indent = 0pt
25
35
  for ancestor-level in range(level) {
26
- let ancestor-value = level-counts.at(str(ancestor-level), default: 1)
36
+ let ancestor-value = emitted-number(ancestor-level, level-counts)
27
37
  let ancestor-format = get-paragraph-numbering-format(ancestor-level)
28
38
  let ancestor-number = numbering(ancestor-format, ancestor-value)
29
39
  total-indent += measure([#ancestor-number#" "]).width
@@ -56,18 +66,18 @@
56
66
  /// the width of the number label that precedes it.
57
67
  ///
58
68
  /// This is where a later block of the same paragraph — a continuation, a block
59
- /// quote — lines up, so the two callers share one measurement rather than each
60
- /// spelling it. A top-level paragraph carries no indent and its label is the
69
+ /// quote, a table — lines up, so the callers share one measurement rather than
70
+ /// each spelling it. A top-level paragraph carries no indent and its label is the
61
71
  /// left margin itself, so the offset is zero.
62
72
  ///
63
73
  /// - level (int): Nesting level (0-based)
64
- /// - level-counts (dictionary): Current counter values per level
74
+ /// - level-counts (dictionary): Maps level index strings to the next number at each level
65
75
  /// - indent-fn (function): `(level, level-counts) -> length`
66
76
  /// -> length
67
77
  #let paragraph-text-offset(level, level-counts, indent-fn) = {
68
78
  if level <= 0 { return 0pt }
69
- let current-value = level-counts.at(str(level), default: 1)
70
- let number-text = numbering(get-paragraph-numbering-format(level), current-value)
79
+ let number = emitted-number(level, level-counts)
80
+ let number-text = numbering(get-paragraph-numbering-format(level), number)
71
81
  indent-fn(level, level-counts) + measure([#number-text#" "]).width
72
82
  }
73
83
 
@@ -145,17 +155,36 @@
145
155
  p
146
156
  }
147
157
  // Tables are buffered whole; nothing inside one takes a paragraph number.
158
+ // The nesting level rides along, as a block quote's does, so a table
159
+ // inside a list item lines up with that item's text.
148
160
  show table: t => context {
161
+ let nest-level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
149
162
  PAR_BUFFER.update(pars => {
150
163
  pars.push((
151
164
  content: t,
152
- nest-level: -1,
165
+ nest-level: nest-level,
153
166
  kind: "table",
154
167
  ))
155
168
  pars
156
169
  })
157
170
  t
158
171
  }
172
+ // A `qm-table` wrapper's `align` places its table from outside it, so the
173
+ // placement is buffered with the table, and the emission spans the width
174
+ // left beside its indent for it to place the table across.
175
+ show align: a => if a.body.func() == table {
176
+ context {
177
+ let nest-level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
178
+ PAR_BUFFER.update(pars => {
179
+ pars.push((
180
+ content: a,
181
+ nest-level: nest-level,
182
+ kind: "table",
183
+ ))
184
+ pars
185
+ })
186
+ }
187
+ } else { a }
159
188
  // AFH 33-337 numbers paragraphs and letters subparagraphs, and a body
160
189
  // sometimes has to hold lines that are neither: a roster of names, a quoted
161
190
  // passage, an address. The block quote is where an author says so — its
@@ -231,7 +260,7 @@
231
260
  //
232
261
  // PAR_BUFFER item dictionary layout:
233
262
  // item.content — the paragraph body, table element, or block-quote body
234
- // item.nest-level — nesting depth (−1 for tables)
263
+ // item.nest-level — nesting depth (0-based)
235
264
  // item.kind — "par", "heading", "table", "continuation", or "quote"
236
265
  context {
237
266
  let heading-buffer = none
@@ -265,15 +294,14 @@
265
294
  // A buffered heading runs into this element only when the two belong to
266
295
  // the same item: a later block of this list item ("continuation"), or,
267
296
  // at top level, the next paragraph. The first block of the *next* item,
268
- // a table (nest-level −1, so the level test alone excludes it), a block
297
+ // a table (a block, with no line for a heading to run into), a block
269
298
  // quote (verbatim: a heading prepended to it would be ink the author did
270
299
  // not put inside the quote), and another heading all fail the test, and
271
300
  // each would otherwise carry the heading's text somewhere it was not
272
- // authored. Those emit the heading on its own line, the treatment a
273
- // heading before a table takes.
301
+ // authored. Those emit the heading on its own line.
274
302
  if heading-buffer != none {
275
303
  let runs-in = (
276
- kind not in ("heading", "quote")
304
+ kind not in ("heading", "quote", "table")
277
305
  and item.nest-level == heading-level
278
306
  and (heading-level == 0 or kind == "continuation")
279
307
  )
@@ -304,7 +332,12 @@
304
332
  }
305
333
  let final-par = {
306
334
  if kind == "table" {
307
- render-memo-table(item-content)
335
+ // Hangs under the text of the paragraph it sits in, as a block quote
336
+ // does. Its placement and `widths` measure against the width left
337
+ // beside the offset, so it stays inside the text column.
338
+ let placed = block(width: 100%, render-memo-table(item-content))
339
+ let offset = paragraph-text-offset(nest-level, level-counts, indent-fn)
340
+ if offset == 0pt { placed } else { pad(left: offset, placed) }
308
341
  } else if kind == "quote" {
309
342
  // A block quote is the body's unlabeled block: no number, no letter,
310
343
  // no bullet — the author's lines as written. It is placed, not
@@ -337,7 +370,6 @@
337
370
  } else if kind == "continuation" {
338
371
  // Continuation block within a multi-block list item:
339
372
  // indent to align with preceding numbered paragraph's text, no new number.
340
- // level-counts still holds the value of the preceding numbered paragraph.
341
373
  if memo-style == "daf" and nest-level == 0 {
342
374
  item-content
343
375
  } else {
@@ -92,6 +92,12 @@
92
92
  none
93
93
  }
94
94
 
95
+ let tag-line = if falsey(footer-tag-line) {
96
+ none
97
+ } else {
98
+ align(center, text(fill: LETTERHEAD_COLOR, font: "Spectral SC", size: 15pt)[#footer-tag-line])
99
+ }
100
+
95
101
  set par(leading: spacing.line, spacing: spacing.line, justify: false)
96
102
  set block(above: spacing.line, below: 0em, spacing: 0em)
97
103
  set text(font: body-font, size: font-size, fallback: true)
@@ -137,14 +143,11 @@
137
143
  )
138
144
  }
139
145
 
140
- if not falsey(footer-tag-line) {
141
- place(
142
- bottom + center,
143
- dy: -0.625in,
144
- align(center)[
145
- #text(fill: LETTERHEAD_COLOR, font: "Spectral SC", size: 15pt)[#footer-tag-line]
146
- ],
147
- )
146
+ // Page 1's tag line rides the indicator block's float when there is one.
147
+ if tag-line != none {
148
+ context if cui-indicator == none or here().page() > 1 {
149
+ place(bottom + center, dy: -0.625in, tag-line)
150
+ }
148
151
  }
149
152
  },
150
153
  )
@@ -153,7 +156,8 @@
153
156
  // corner, dropped into the 0.5in page-edge band. Emitted as a bottom float so
154
157
  // it (1) reserves flow space, raising page 1's effective bottom margin so body
155
158
  // text never overlaps it, and (2) stays pinned to page 1 — as the first flow
156
- // content it can never be bumped to page 2.
159
+ // content it can never be bumped to page 2. Page 1's tag line stands above the
160
+ // block in the same float, since both claim the band.
157
161
  if cui-indicator != none {
158
162
  context {
159
163
  // The box shrink-wraps to its widest line; `set align(left)` keeps the
@@ -165,24 +169,35 @@
165
169
  set align(left)
166
170
  cui-indicator
167
171
  })
168
- // Reserve only the part of the block inside the text area (`reserved`):
169
- // float a box of that height, then `place` the full block inside it pushed
170
- // down by `overhang` so the surplus overflows into the edge band. (A bare
172
+ let block-height = measure(indicator-box).height
173
+ let tag-gap = 0.125in
174
+ let stack-height = if tag-line == none {
175
+ block-height
176
+ } else {
177
+ block-height + tag-gap + measure(tag-line, width: page.width - 2 * spacing.margin).height
178
+ }
179
+ // Reserve only the part of the stack inside the text area (`reserved`):
180
+ // float a box of that height, then `place` the stack inside it pushed down
181
+ // by `overhang` so the surplus overflows into the edge band. (A bare
171
182
  // `box(height: reserved, indicator-box)` overflows *upward* into the body
172
- // instead.) The inner `place` adds no size, so the box stays `reserved`
183
+ // instead.) The inner `place`s add no size, so the box stays `reserved`
173
184
  // tall and the block's bottom lands 0.5in from the page edge.
174
185
  let overhang = spacing.margin - 0.5in
175
- let reserved = measure(indicator-box).height - overhang
186
+ let reserved = stack-height - overhang
176
187
  place(
177
- bottom + right,
188
+ bottom,
178
189
  float: true,
179
- // Slide the right edge into the page-edge band, 0.5in from the border;
180
- // the inner place right-aligns the block to that edge.
181
- dx: spacing.margin - 0.5in,
182
190
  // Minimum gap to the body's last line; the actual gap is larger when the
183
191
  // next paragraph can't fit above the block and breaks to the next page.
184
192
  clearance: spacing.line,
185
- box(height: reserved, place(bottom + right, dy: overhang, indicator-box)),
193
+ box(width: 100%, height: reserved, {
194
+ // Slide the block's right edge into the page-edge band, 0.5in from
195
+ // the border.
196
+ place(bottom + right, dx: spacing.margin - 0.5in, dy: overhang, indicator-box)
197
+ if tag-line != none {
198
+ place(bottom + center, dy: overhang - block-height - tag-gap, tag-line)
199
+ }
200
+ }),
186
201
  )
187
202
  }
188
203
  }
@@ -197,7 +197,10 @@
197
197
  // spaces to the right of page center"
198
198
  // AFH 33-337 "Do not place the signature element on a continuation page by itself"
199
199
  // AFH 33-337 long-name example: "Signature block adjusted to the left" when a
200
- // long name would otherwise exceed the right margin.
200
+ // long name would otherwise exceed the right margin. Only the name line moves
201
+ // the block; any other line that runs long wraps, its overflow beginning "under
202
+ // the third character of the line above". The handbook gives no measurement,
203
+ // so the indent is the width of the line's own first two characters.
201
204
  //
202
205
  // A closing line may open the section above the block, and AFH 33-337 gives it
203
206
  // one geometry across the two documents that have one: on the second line below
@@ -215,6 +218,17 @@
215
218
  /// -> content | none
216
219
  #let format-authority-line(value) = if falsey(value) { none } else { upper(value) }
217
220
 
221
+ // A signature line arrives as `str` or as content; the indent measures its text.
222
+ #let plain-text(it) = {
223
+ if type(it) == str { return it }
224
+ if type(it) != content { return "" }
225
+ if it.has("text") { return plain-text(it.text) }
226
+ if it.has("children") { return it.children.map(plain-text).join(default: "") }
227
+ if it.has("body") { return plain-text(it.body) }
228
+ if it.has("child") { return plain-text(it.child) }
229
+ ""
230
+ }
231
+
218
232
  #let render-signature-block(
219
233
  signature-lines,
220
234
  closing-line: none,
@@ -234,11 +248,11 @@
234
248
  // anchor. pad() is relative to the text area, hence (4.5in - margin).
235
249
  let default-pad = 4.5in - spacing.margin
236
250
  context {
237
- // Measure each line at its rendered settings to detect long-name overflow.
238
- // The closing line shares the anchor, so it joins the measurement: the
239
- // wider of the two decides the shift and they stay aligned.
251
+ // Measure the name line at its rendered settings to detect long-name
252
+ // overflow. The closing line shares the anchor, so it joins the
253
+ // measurement: the wider of the two decides the shift and they stay aligned.
240
254
  let body-width = page.width - 2 * spacing.margin
241
- let anchored-lines = signature-lines
255
+ let anchored-lines = signature-lines.slice(0, calc.min(1, signature-lines.len()))
242
256
  if closing-line != none { anchored-lines.push(closing-line) }
243
257
  let widest = 0pt
244
258
  for line in anchored-lines {
@@ -305,9 +319,9 @@
305
319
  #pad(left: left-pad)[
306
320
  #text(hyphenate: false)[
307
321
  #for line in signature-lines {
308
- // AFH 33-337: "indent the next line to begin under the third character
309
- // of the line above" — 2-character indent ≈ 1em in Times New Roman 12pt
310
- par(hanging-indent: .5em, line)
322
+ let clusters = plain-text(line).clusters()
323
+ let lead = clusters.slice(0, calc.min(2, clusters.len())).join()
324
+ par(hanging-indent: measure(lead).width, line)
311
325
  }
312
326
  // The page the backmatter compares its own against. Must stay
313
327
  // inside the unbreakable block: a marker outside one travels with
@@ -393,7 +407,11 @@
393
407
  /// - it (content): The table element to style and render
394
408
  /// -> content
395
409
  #let render-memo-table(it) = {
396
- show table.cell.where(y: 0): set text(weight: "bold")
410
+ // A headless table's first row is a body row, which Typst also numbers 0.
411
+ show table: t => if t.children.any(c => c.func() == table.header) {
412
+ show table.cell.where(y: 0): set text(weight: "bold")
413
+ t
414
+ } else { t }
397
415
  set table(
398
416
  stroke: 0.5pt + black,
399
417
  inset: (x: 0.5em, y: 0.4em),
@@ -1,109 +1,103 @@
1
1
  ~~~
2
2
  $quill: classic_resume@0.1
3
3
  $kind: main
4
- name: Nibs Bin
4
+ name: Philip J. Fry
5
5
  contacts:
6
- - nibs@tonguetoquill.com
7
- - (333) 333-3333
8
- - github.com/nibsbin
9
- - linkedin.com/in/nibsbin/
6
+ - fry@planetexpress.example
7
+ - (212) 555-0199
8
+ - github.com/borb-sh
10
9
  - New New York
11
10
  paper: us-letter
12
11
  font_size: 11.5
13
12
  margin: 0.5
14
13
  ~~~
15
14
 
15
+ ~~~
16
+ $kind: summary
17
+ ~~~
18
+
19
+ Delivery boy with a thousand years of experience, most of it frozen. Brings 20th-century know-how to 31st-century problems and has saved Earth more than once. Wants to keep delivering anywhere, ideally with fewer space bees.
20
+
16
21
  ~~~
17
22
  $kind: certifications
18
23
  title: Certifications
19
24
  columns: 2
20
25
  items:
21
- - Offensive Security Certified Professional (OSCP)
22
- - GIAC Cyber Threat Intelligence (GCTI)
23
- - CompTIA CASP+, CySA+, Sec+, Net+, A+, Proj+
24
- - GIAC Machine Learning Engineer (GMLE)
26
+ - "Career Chip: Delivery Boy"
27
+ - Earth Army Basic Training
28
+ - First Aid, taught by Dr. Zoidberg
29
+ - Food Handler's Permit, New York
25
30
  ~~~
26
31
 
27
32
  ~~~
28
33
  $kind: skills
29
34
  columns: 2
30
35
  skills:
31
- - label: Programming
32
- items: Python, R, JS, C#, Rust, PowerShell, CI/CD
33
- - label: Data Science
34
- items: ML/statistics, TensorFlow, AI Engineering
35
- - label: IT & Cybersecurity
36
- items: AD DS, Splunk, Metasploit, Wireshark, Nessus
37
- - label: Cloud
38
- items: AWS EC2/S3, Helm, Docker, Serverless
36
+ - label: Logistics
37
+ items: Packages, pizza, interplanetary freight
38
+ - label: 20th-Century Culture
39
+ items: TV, arcade games, Star Trek trivia
40
+ - label: Music
41
+ items: Holophonor, opera composition
42
+ - label: Endurance
43
+ items: Cryogenic sleep, 100 cups of coffee
39
44
  ~~~
40
45
 
41
46
  ~~~
42
47
  $kind: experience
43
48
  jobs:
44
- - company: Planet Express Delivery Division
45
- dates: August 2024 – Present
46
- role: Interstellar Logistics Analyst
49
+ - company: Planet Express
50
+ dates: December 2999 – Present
51
+ role: Delivery Boy
47
52
  location: New New York
48
53
  details: |-
49
- - Analyzed dark matter fuel combustion following the Nibblonian incident, developing safeguards to protect remaining ship engine infrastructure.
54
+ - Deliver packages anywhere in the universe with Captain Turanga Leela and Bender Bending Rodríguez.
50
55
 
51
- - Building automated hazard detection pipelines using Bender's antenna arrays to monitor Omicronian threat signatures across the sector.
52
- - company: MomCorp Robotics Academy
53
- dates: May 2025 – July 2025
54
- role: Robot Firmware Trainee
55
- location: Mars Vegas
56
- details: "- Developed behavior-inhibitor firmware for MomCorp robots integrating real-time Slurm demand intelligence."
57
- - company: DOOP Starship Nimbus
58
- dates: January 2018 – June 2020
59
- role: Combat Engineer
60
- location: Vergon 6
61
- details: "- Administrated Nimbus shipboard systems, Kif's maintenance logs, and killbot defense automation for 30,000+ crew."
56
+ - Completed runs to robot-only Chapek 9 and a hive of giant space bees, and survived both.
57
+ - company: Earth Army
58
+ dates: November 3000
59
+ role: Private
60
+ location: Spheron 1
61
+ details: "- Enlisted for the military discount; deployed against the Spheroids under Zapp Brannigan."
62
+ - company: Panucci's Pizza
63
+ dates: June 1997 – December 1999
64
+ role: Delivery Boy
65
+ location: New York, NY
66
+ details: "- Delivered pizza across New York City until a prank order to Applied Cryogenics ran 1,000 years late."
62
67
  title: Experience
63
68
  ~~~
64
69
 
65
70
  ~~~
66
71
  $kind: education
67
72
  schools:
68
- - school: Carnegie Mellon University
69
- dates: December 2025
70
- degree: Master of Information Technology Strategy
71
- location: Pittsburgh, PA
72
- - school: United States Air Force Academy
73
- dates: May 2024
74
- degree: BS, Data Science
75
- location: Colorado Springs, CO
76
- details: |-
77
- - Distinguished Graduate (top 10%); Chinese language minor (L2+/R1 on DLPT).
78
-
79
- - Professor Bradley A. Warner Data Science Catalyst and Top Cadet in Computer Networks.
80
- - school: Community College of the Air Force
81
- dates: February 2029
82
- degree: AS, Information Systems Technology
83
- location: Online
73
+ - school: Mars University
74
+ dates: September 3000
75
+ degree: Undeclared, no degree
76
+ location: Mars
84
77
  ~~~
85
78
 
86
79
  ~~~
87
80
  $kind: other
88
- title: Cyber Competition
81
+ title: Planetary Defense
89
82
  entries:
90
- - heading: 1st in SANS Academy Cup 2024
91
- details: "- Competed as the Delogrand Web Exploit SME, solving SQLi, API, and HTTP packet crafting problems."
92
- - heading: 1st in NCX 2023
83
+ - heading: Saved Earth from the Brainspawn
84
+ details: "- Held the line as the only human immune to the Brainspawn, thanks to a missing Delta brainwave."
85
+ - heading: Ended the Omicronian Invasion
93
86
  details: |-
94
- - Developed strategies, defensive scripts, and exploits for the Cyber Combat event.
87
+ - Restaged the lost finale of *Single Female Lawyer* for Lrrr, ruler of the planet Omicron Persei 8.
95
88
 
96
- - Analyzed logs with Bash and Python for the Data Analysis event.
89
+ - Caused the crisis in the first place by knocking the original broadcast off the air in 1999.
90
+ - heading: Deflected a Giant Ball of Garbage
91
+ details: "- Helped knock it off course with a second ball of garbage, built on 20th-century expertise."
97
92
  ~~~
98
93
 
99
94
  ~~~
100
95
  $kind: projects
101
96
  title: Projects
102
97
  projects:
103
- - name: TongueToQuill
104
- link: tonguetoquill.com
105
- details: "- Rich markdown editor for perfectly formatted USAF and USSF documents with Claude MCP integration."
106
98
  - name: Quillmark
107
- link: github.com/nibsbin/quillmark
108
- details: "- Data-driven document engine for generating arbitrarily typesetted documents from markdown."
99
+ link: github.com/borb-sh/quillmark
100
+ details: "- Schema-driven document engine that delivers typeset documents from Markdown, this resume included."
101
+ - name: Holophonor Opera
102
+ details: "- Wrote and performed an opera about Leela on the holophonor, using the Robot Devil's hands."
109
103
  ~~~