@airmark/quiver 0.35.6 → 0.35.8
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 +4 -4
- package/quills/af4141/0.1.0/Quill.yaml +39 -49
- package/quills/af4141/0.1.0/plate.typ +7 -7
- package/quills/afmc_moa/0.0.1/Quill.yaml +126 -153
- package/quills/afmc_moa/0.0.1/plate.typ +11 -11
- package/quills/classic_resume/0.1.0/Quill.yaml +34 -40
- package/quills/classic_resume/0.1.0/example.md +53 -59
- package/quills/classic_resume/0.1.0/plate.typ +11 -11
- package/quills/cyber_career_plan/0.1.0/Quill.yaml +258 -287
- package/quills/cyber_career_plan/0.1.0/plate.typ +92 -70
- package/quills/daf1206/0.1.0/plate.typ +18 -18
- package/quills/daf4392/0.1.0/Quill.yaml +33 -38
- package/quills/daf4392/0.1.0/plate.typ +16 -16
- package/quills/usaf_letter/0.1.0/Quill.yaml +143 -158
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/frontmatter.typ +34 -19
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/mainmatter.typ +1 -1
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/primitives.typ +27 -9
- package/quills/usaf_letter/0.1.0/plate.typ +26 -28
- package/quills/usaf_memo/0.2.0/Quill.yaml +217 -247
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/body.typ +15 -1
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +34 -19
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +5 -1
- package/quills/usaf_memo/0.2.0/plate.typ +28 -28
- package/quills/usaf_memo/0.3.0/Quill.yaml +219 -245
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/body.typ +47 -15
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +34 -19
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +27 -9
- package/quills/usaf_memo/0.3.0/plate.typ +35 -36
- package/templates/classic_resume.md +53 -59
- package/templates/cyber_career_plan.md +56 -0
- package/templates/templates.json +7 -0
|
@@ -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
|
|
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 =
|
|
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
|
|
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):
|
|
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
|
|
70
|
-
let number-text = numbering(get-paragraph-numbering-format(level),
|
|
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: -
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
dy: -0.625in,
|
|
144
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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`
|
|
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 =
|
|
186
|
+
let reserved = stack-height - overhang
|
|
176
187
|
place(
|
|
177
|
-
bottom
|
|
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(
|
|
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
|
|
238
|
-
// The closing line shares the anchor, so it joins 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
|
-
|
|
309
|
-
|
|
310
|
-
par(hanging-indent: .
|
|
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
|
-
|
|
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,23 +1,21 @@
|
|
|
1
|
-
#import "@local/quillmark-helper:0.
|
|
2
|
-
data, display, form-field, signature-field,
|
|
3
|
-
)
|
|
1
|
+
#import "@local/quillmark-helper:0.2.0" as qm
|
|
4
2
|
#import "@local/tonguetoquill-usaf-memo:5.0.0": (
|
|
5
3
|
backmatter, date-pattern, frontmatter, indorsement, mainmatter,
|
|
6
4
|
)
|
|
7
5
|
|
|
8
6
|
// A memo has no "no style" state, so the blank takes the package's default.
|
|
9
7
|
// Resolved once here: `frontmatter` and the date's pattern must agree.
|
|
10
|
-
#let memo_style = if data.memo_style != "" { data.memo_style } else { "usaf" }
|
|
8
|
+
#let memo_style = if qm.data.memo_style != "" { qm.data.memo_style } else { "usaf" }
|
|
11
9
|
|
|
12
10
|
// The letterhead arrives as one ordered array of lines. Element 0 is the title,
|
|
13
11
|
// set larger; the rest are caption lines beneath it. A single element renders
|
|
14
12
|
// the title alone — the package skips a falsey caption. An empty array renders
|
|
15
13
|
// no letterhead text at all, silently: the block is `place`d, so an empty title
|
|
16
14
|
// leaves no glyphs and takes no space from the flow, and the seal stands alone.
|
|
17
|
-
#let letterhead_lines = data.letterhead_title
|
|
15
|
+
#let letterhead_lines = qm.data.letterhead_title
|
|
18
16
|
|
|
19
17
|
// Body text size, in points. Also the height of a blank date's fill-in widget.
|
|
20
|
-
#let body_font_size = data.font_size * 1pt
|
|
18
|
+
#let body_font_size = qm.data.font_size * 1pt
|
|
21
19
|
|
|
22
20
|
// A blank date's fill-in slot: an empty AcroForm text box the signer types the
|
|
23
21
|
// date into, set in the body face, the value flush right in the date line's
|
|
@@ -34,7 +32,7 @@
|
|
|
34
32
|
#let date_slot(name, field) = context box(
|
|
35
33
|
width: date_slot_width,
|
|
36
34
|
height: measure[0].height,
|
|
37
|
-
place(horizon + left, form-field(
|
|
35
|
+
place(horizon + left, qm.form-field(
|
|
38
36
|
name,
|
|
39
37
|
type: "text",
|
|
40
38
|
width: date_slot_width,
|
|
@@ -49,10 +47,10 @@
|
|
|
49
47
|
#show: frontmatter.with(
|
|
50
48
|
letterhead-title: letterhead_lines.at(0, default: ""),
|
|
51
49
|
letterhead-caption: if letterhead_lines.len() > 1 { letterhead_lines.slice(1) } else { () },
|
|
52
|
-
letterhead-seal-subtitle: data.letterhead_seal_subtitle,
|
|
50
|
+
letterhead-seal-subtitle: qm.data.letterhead_seal_subtitle,
|
|
53
51
|
// A memo has no seal-less state, so the blank takes the default, DoW.
|
|
54
52
|
letterhead-seal: image(
|
|
55
|
-
if data.letterhead_seal == "dod" {
|
|
53
|
+
if qm.data.letterhead_seal == "dod" {
|
|
56
54
|
"assets/dod_seal.png"
|
|
57
55
|
} else {
|
|
58
56
|
"assets/dow_seal.png"
|
|
@@ -61,44 +59,44 @@
|
|
|
61
59
|
|
|
62
60
|
// Date. `data.date` is the native `datetime` and would render identically,
|
|
63
61
|
// but its ink would be born inside the package and carry no schema address.
|
|
64
|
-
// `
|
|
62
|
+
// `qm.ink` places the field's *content* projection instead: the glyphs are
|
|
65
63
|
// born in the generated helper, so the memo date stays click-to-edit however
|
|
66
64
|
// deep the package formats it.
|
|
67
65
|
//
|
|
68
66
|
// A blank date is left for the signer: a memo is dated when it is signed,
|
|
69
67
|
// which is generally not when it is rendered, so the slot is fillable rather
|
|
70
68
|
// than stamped with the compile date.
|
|
71
|
-
date:
|
|
69
|
+
date: qm.ink(qm.data, "date", date-pattern(memo-style: memo_style)),
|
|
72
70
|
date-field: date_slot("Date", "date"),
|
|
73
71
|
|
|
74
|
-
memo-for: data.memo_for,
|
|
72
|
+
memo-for: qm.data.memo_for,
|
|
75
73
|
|
|
76
74
|
// A memo with no FROM line is a Memorandum for Record.
|
|
77
|
-
..if data.memo_from.len() > 0 { (memo-from: data.memo_from) },
|
|
75
|
+
..if qm.data.memo_from.len() > 0 { (memo-from: qm.data.memo_from) },
|
|
78
76
|
|
|
79
|
-
subject: data.subject,
|
|
77
|
+
subject: qm.data.subject,
|
|
80
78
|
|
|
81
|
-
..if data.references.len() > 0 { (references: data.references) },
|
|
79
|
+
..if qm.data.references.len() > 0 { (references: qm.data.references) },
|
|
82
80
|
|
|
83
|
-
footer-tag-line: data.tag_line,
|
|
81
|
+
footer-tag-line: qm.data.tag_line,
|
|
84
82
|
|
|
85
83
|
// The blank reads as no banner, which is what the package's own
|
|
86
84
|
// `classification-level: none` default means.
|
|
87
|
-
classification-level: data.classification.value,
|
|
85
|
+
classification-level: qm.data.classification.value,
|
|
88
86
|
|
|
89
|
-
dissemination: data.dissemination,
|
|
87
|
+
dissemination: qm.data.dissemination,
|
|
90
88
|
|
|
91
89
|
// CUI designation indicator block fields (DoDM 5200.48). `classification`
|
|
92
90
|
// declares a `CUI` variant, so these four exist only where the discriminant
|
|
93
91
|
// reads CUI, and the branch is what makes reading them total: inside it every
|
|
94
92
|
// declared field of that world is present, outside it none is. The package's
|
|
95
93
|
// own `cui_*: none` defaults cover the worlds that omit them.
|
|
96
|
-
..if data.classification.value == "CUI" {
|
|
94
|
+
..if qm.data.classification.value == "CUI" {
|
|
97
95
|
(
|
|
98
|
-
cui-controlled-by: data.classification.controlled_by,
|
|
99
|
-
cui-category: data.classification.category,
|
|
100
|
-
cui-limited-dissemination: data.classification.limited_dissemination,
|
|
101
|
-
cui-poc: data.classification.poc,
|
|
96
|
+
cui-controlled-by: qm.data.classification.controlled_by,
|
|
97
|
+
cui-category: qm.data.classification.category,
|
|
98
|
+
cui-limited-dissemination: qm.data.classification.limited_dissemination,
|
|
99
|
+
cui-poc: qm.data.classification.poc,
|
|
102
100
|
)
|
|
103
101
|
},
|
|
104
102
|
|
|
@@ -115,26 +113,26 @@
|
|
|
115
113
|
// the rebuilt glyphs keep their spans, which is what the backend reads regions
|
|
116
114
|
// from.
|
|
117
115
|
#mainmatter[
|
|
118
|
-
#data.at("$body")
|
|
116
|
+
#qm.data.at("$body")
|
|
119
117
|
]
|
|
120
118
|
|
|
121
119
|
#backmatter(
|
|
122
|
-
authority-line: data.authority_line,
|
|
123
|
-
signature-block: data.signature_block,
|
|
120
|
+
authority-line: qm.data.authority_line,
|
|
121
|
+
signature-block: qm.data.signature_block,
|
|
124
122
|
// The widget sits at the bottom of AFH 33-337's four blank lines and is
|
|
125
123
|
// sized to two and a half of them, so the line and a half above it stays
|
|
126
124
|
// clear of the body text without moving the block off the fifth line.
|
|
127
|
-
signing-field: signature-field(
|
|
125
|
+
signing-field: qm.signature-field(
|
|
128
126
|
"Signature",
|
|
129
127
|
field: "signature_block",
|
|
130
128
|
height: body_font_size * 2.5,
|
|
131
129
|
),
|
|
132
130
|
|
|
133
|
-
..if data.cc.len() > 0 { (cc: data.cc) },
|
|
131
|
+
..if qm.data.cc.len() > 0 { (cc: qm.data.cc) },
|
|
134
132
|
|
|
135
|
-
..if data.distribution.len() > 0 { (distribution: data.distribution) },
|
|
133
|
+
..if qm.data.distribution.len() > 0 { (distribution: qm.data.distribution) },
|
|
136
134
|
|
|
137
|
-
..if data.attachments.len() > 0 { (attachments: data.attachments) },
|
|
135
|
+
..if qm.data.attachments.len() > 0 { (attachments: qm.data.attachments) },
|
|
138
136
|
)
|
|
139
137
|
|
|
140
138
|
// The action line's wording follows the indorsement's place in the chain, not
|
|
@@ -149,7 +147,7 @@
|
|
|
149
147
|
// expression ends at a line break stops there, and the next line's leading
|
|
150
148
|
// `.at(…)` would be read as text.
|
|
151
149
|
#let last_indorsement_index = (
|
|
152
|
-
data
|
|
150
|
+
qm.data
|
|
153
151
|
.at("$cards")
|
|
154
152
|
.enumerate()
|
|
155
153
|
.filter(entry => entry.at(1).at("$kind", default: none) == "indorsement")
|
|
@@ -157,7 +155,7 @@
|
|
|
157
155
|
.at(-1, default: -1)
|
|
158
156
|
)
|
|
159
157
|
|
|
160
|
-
#for (i, card) in data.at("$cards").enumerate() {
|
|
158
|
+
#for (i, card) in qm.data.at("$cards").enumerate() {
|
|
161
159
|
if card.at("$kind", default: none) == "indorsement" {
|
|
162
160
|
// The quillmark helper leaves an unset/whitespace-only markdown body as
|
|
163
161
|
// the empty string `""`; only non-empty bodies are eval'd into content.
|
|
@@ -169,11 +167,12 @@
|
|
|
169
167
|
// it (distinct from the originating memo's date). The signing date is
|
|
170
168
|
// generally unknown at compile time, so a blank or omitted date leaves the
|
|
171
169
|
// date slot fillable rather than stamping the compile date into it.
|
|
172
|
-
// `
|
|
170
|
+
// `qm.ink` reads the card's own address, so a per-card call yields a per-card region
|
|
173
171
|
// even though every iteration shares one `card` loop variable, and it
|
|
174
172
|
// returns `none` for a blank date — the fill-in case below.
|
|
175
|
-
let resolved_date =
|
|
176
|
-
card
|
|
173
|
+
let resolved_date = qm.ink(
|
|
174
|
+
card,
|
|
175
|
+
"date",
|
|
177
176
|
date-pattern(memo-style: memo_style),
|
|
178
177
|
)
|
|
179
178
|
// The card's `$path` prefix composes its canonical schema addresses
|
|
@@ -187,7 +186,7 @@
|
|
|
187
186
|
to: card.at("for", default: ""),
|
|
188
187
|
authority-line: card.authority_line,
|
|
189
188
|
signature-block: card.signature_block,
|
|
190
|
-
signing-field: signature-field(
|
|
189
|
+
signing-field: qm.signature-field(
|
|
191
190
|
"Ind_" + str(i) + "_Signature",
|
|
192
191
|
field: card.at("$path") + "signature_block",
|
|
193
192
|
height: body_font_size * 2.5,
|
|
@@ -1,109 +1,103 @@
|
|
|
1
1
|
~~~
|
|
2
2
|
$quill: classic_resume@0.1
|
|
3
3
|
$kind: main
|
|
4
|
-
name:
|
|
4
|
+
name: Philip J. Fry
|
|
5
5
|
contacts:
|
|
6
|
-
-
|
|
7
|
-
- (
|
|
8
|
-
- github.com/
|
|
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
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
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:
|
|
32
|
-
items:
|
|
33
|
-
- label:
|
|
34
|
-
items:
|
|
35
|
-
- label:
|
|
36
|
-
items:
|
|
37
|
-
- label:
|
|
38
|
-
items:
|
|
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
|
|
45
|
-
dates:
|
|
46
|
-
role:
|
|
49
|
+
- company: Planet Express
|
|
50
|
+
dates: December 2999 – Present
|
|
51
|
+
role: Delivery Boy
|
|
47
52
|
location: New New York
|
|
48
53
|
details: |-
|
|
49
|
-
-
|
|
54
|
+
- Deliver packages anywhere in the universe with Captain Turanga Leela and Bender Bending Rodríguez.
|
|
50
55
|
|
|
51
|
-
-
|
|
52
|
-
- company:
|
|
53
|
-
dates:
|
|
54
|
-
role:
|
|
55
|
-
location:
|
|
56
|
-
details: "-
|
|
57
|
-
- company:
|
|
58
|
-
dates:
|
|
59
|
-
role:
|
|
60
|
-
location:
|
|
61
|
-
details: "-
|
|
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:
|
|
69
|
-
dates:
|
|
70
|
-
degree:
|
|
71
|
-
location:
|
|
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:
|
|
81
|
+
title: Planetary Defense
|
|
89
82
|
entries:
|
|
90
|
-
- heading:
|
|
91
|
-
details: "-
|
|
92
|
-
- heading:
|
|
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
|
-
-
|
|
87
|
+
- Restaged the lost finale of *Single Female Lawyer* for Lrrr, ruler of the planet Omicron Persei 8.
|
|
95
88
|
|
|
96
|
-
-
|
|
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/
|
|
108
|
-
details: "-
|
|
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
|
~~~
|