@airmark/quiver 0.32.1 → 0.33.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 (76) hide show
  1. package/README.md +54 -18
  2. package/package.json +8 -6
  3. package/quills/af4141/0.1.0/Quill.yaml +1 -1
  4. package/quills/afmc_moa/0.0.1/Quill.yaml +10 -17
  5. package/quills/classic_resume/0.0.1/Quill.yaml +361 -0
  6. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/LICENSE +21 -0
  7. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Bold.ttf +0 -0
  8. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-BoldItalic.ttf +0 -0
  9. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Italic.ttf +0 -0
  10. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Regular.ttf +0 -0
  11. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/OFL.txt +94 -0
  12. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/components.typ +181 -0
  13. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/config.typ +53 -0
  14. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/layout.typ +57 -0
  15. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/lib.typ +6 -0
  16. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/util.typ +32 -0
  17. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/typst.toml +26 -0
  18. package/quills/classic_resume/0.0.1/plate.typ +143 -0
  19. package/quills/cyber_ribbon_chart/0.0.1/Quill.yaml +320 -0
  20. package/quills/cyber_ribbon_chart/0.0.1/plate.typ +428 -0
  21. package/quills/daf1206/0.1.0/Quill.yaml +1 -2
  22. package/quills/daf4392/0.1.0/Quill.yaml +15 -1
  23. package/quills/daf4392/0.1.0/plate.typ +2 -2
  24. package/quills/usaf_letter/0.1.0/Quill.yaml +214 -0
  25. package/quills/usaf_letter/0.1.0/assets/dod_seal.png +0 -0
  26. package/quills/usaf_letter/0.1.0/assets/dow_seal.png +0 -0
  27. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/.gitignore +7 -0
  28. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/LICENSE +201 -0
  29. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/Cinzel/Cinzel-Regular.ttf +0 -0
  30. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/Cinzel/LICENSE +93 -0
  31. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/CopperplateCC-Bold.otf +0 -0
  32. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
  33. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/LICENSE.md +79 -0
  34. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/LiberationMono/LICENSE +102 -0
  35. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/LiberationMono/LiberationMono-Regular.ttf +0 -0
  36. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/GNU General Public License.txt +340 -0
  37. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-Med.otf +0 -0
  38. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-MedIta.otf +0 -0
  39. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-Reg.otf +0 -0
  40. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-RegIta.otf +0 -0
  41. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/backmatter.typ +32 -0
  42. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/config.typ +45 -0
  43. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/frontmatter.typ +226 -0
  44. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/lib.typ +35 -0
  45. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/mainmatter.typ +140 -0
  46. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/primitives.typ +544 -0
  47. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/utils.typ +334 -0
  48. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/typst.toml +22 -0
  49. package/quills/usaf_letter/0.1.0/plate.typ +124 -0
  50. package/quills/usaf_memo/0.2.0/Quill.yaml +1 -1
  51. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/LICENSE +201 -21
  52. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/typst.toml +1 -1
  53. package/quills/usaf_memo/0.3.0/Quill.yaml +75 -56
  54. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/LICENSE +201 -21
  55. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel-Regular.ttf +0 -0
  56. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/backmatter.typ +29 -17
  57. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/body.typ +97 -126
  58. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/config.typ +12 -45
  59. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +81 -87
  60. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +58 -66
  61. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/lib.typ +14 -36
  62. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/mainmatter.typ +47 -19
  63. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +178 -121
  64. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/utils.typ +41 -141
  65. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/typst.toml +3 -8
  66. package/quills/usaf_memo/0.3.0/plate.typ +89 -85
  67. package/templates/af4141.md +23 -0
  68. package/templates/appointment_letter.md +58 -0
  69. package/templates/daf1206.md +23 -0
  70. package/templates/daf4392.md +34 -0
  71. package/templates/pass_request.md +48 -0
  72. package/templates/rebuttal.md +63 -0
  73. package/templates/templates.json +58 -0
  74. package/templates/usaf_template.md +30 -0
  75. package/templates/ussf_template.md +30 -0
  76. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel[wght].ttf +0 -0
@@ -1,16 +1,9 @@
1
- // body.typ: Paragraph body rendering for USAF memorandum sections
2
- //
3
- // This module implements the visual rendering of AFH 33-337 compliant
4
- // paragraph bodies with proper numbering, nesting, and formatting.
1
+ // Paragraph numbering, nesting, and indentation for the memorandum body.
5
2
 
6
3
  #import "config.typ": *
7
4
  #import "utils.typ": *
8
5
  #import "primitives.typ": render-memo-table
9
6
 
10
- // =============================================================================
11
- // PARAGRAPH NUMBERING UTILITIES
12
- // =============================================================================
13
-
14
7
  /// Gets the numbering format for a specific paragraph level.
15
8
  ///
16
9
  /// - level (int): Paragraph nesting level (0-based)
@@ -52,7 +45,6 @@
52
45
  daf-paragraph.nested-first-level-indent + (level - 1) * daf-paragraph.nested-step
53
46
  }
54
47
 
55
- /// Resets counter entries from `start` upward to 1 in the level-counts dictionary.
56
48
  #let reset-levels-from(level-counts, start, max-levels) = {
57
49
  for child in range(start, max-levels) {
58
50
  level-counts.insert(str(child), 1)
@@ -101,15 +93,16 @@
101
93
  }
102
94
  }
103
95
 
104
- // =============================================================================
105
- // PARAGRAPH BODY RENDERING
106
- // =============================================================================
107
- // AFH 33-337 "The Text of the Official Memorandum" §1-12 specifies:
108
- // - Single-space text, double-space between paragraphs
109
- // - Number and letter each paragraph and subparagraph
110
- // - "A single paragraph is not numbered" (§2)
111
- // - First paragraph flush left, never indented
112
- // - Indent sub-paragraphs to align with first character of parent paragraph text
96
+ // AFH 33-337 "The Text of the Official Memorandum" §1-12: number and letter
97
+ // each paragraph and subparagraph as 1. / a. / (1) / (a); leave a top-level
98
+ // paragraph flush left and a lone paragraph unnumbered (§2); indent each
99
+ // subparagraph to align with the first character of its parent's text.
100
+ //
101
+ // Rendering takes two passes. The first lays the content out hidden, so that
102
+ // Typst's own show rules resolve, and buffers each paragraph, table, and block
103
+ // quote with the nesting level it was found at. The second emits the buffer,
104
+ // where those show rules are out of scope and numbering can be assigned in
105
+ // document order.
113
106
  #let render-body(content, memo-style: "usaf") = {
114
107
  let PAR_BUFFER = state("PAR_BUFFER")
115
108
  PAR_BUFFER.update(())
@@ -125,72 +118,63 @@
125
118
  let ITEM_FIRST_PAR = state("ITEM_FIRST_PAR")
126
119
  ITEM_FIRST_PAR.update(false)
127
120
 
128
- // The first pass parses paragraphs, list items, etc. into standardized arrays
129
- let first_pass = {
130
- // Collect pars with nesting level
121
+ let first-pass = {
131
122
  show par: p => context {
132
- let nest_level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
133
- let is_heading = IS_HEADING.get()
134
- let is_first_par = ITEM_FIRST_PAR.get()
123
+ let nest-level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
124
+ let is-heading = IS_HEADING.get()
125
+ let is-first-par = ITEM_FIRST_PAR.get()
135
126
 
136
127
  // Determine if this is a continuation block within a multi-block list item.
137
- // A continuation is a non-first paragraph inside a list item (nest_level > 0).
138
- let is_continuation = nest_level > 0 and not is_first_par
128
+ // A continuation is a non-first paragraph inside a list item (nest-level > 0).
129
+ let is-continuation = nest-level > 0 and not is-first-par
139
130
 
140
131
  PAR_BUFFER.update(pars => {
141
132
  pars.push((
142
133
  content: text([#p.body]),
143
- nest_level: nest_level,
144
- kind: if is_heading { "heading" } else if is_continuation { "continuation" } else { "par" },
134
+ nest-level: nest-level,
135
+ kind: if is-heading { "heading" } else if is-continuation { "continuation" } else { "par" },
145
136
  ))
146
137
  pars
147
138
  })
148
139
 
149
140
  // After the first paragraph of a list item, mark subsequent ones as continuations
150
- if nest_level > 0 and is_first_par {
141
+ if nest-level > 0 and is-first-par {
151
142
  ITEM_FIRST_PAR.update(false)
152
143
  }
153
144
 
154
145
  p
155
146
  }
156
- // Collect tables — captured as-is without paragraph numbering
147
+ // Tables are buffered whole; nothing inside one takes a paragraph number.
157
148
  show table: t => context {
158
149
  PAR_BUFFER.update(pars => {
159
150
  pars.push((
160
151
  content: t,
161
- nest_level: -1,
152
+ nest-level: -1,
162
153
  kind: "table",
163
154
  ))
164
155
  pars
165
156
  })
166
157
  t
167
158
  }
168
- // Collect block quotes — captured as-is, with paragraph processing kept
169
- // out of them. AFH 33-337 numbers paragraphs and letters subparagraphs, and
170
- // a body sometimes has to hold lines that are neither: a roster of names, a
171
- // quoted passage, an address. The block quote is where an author says so —
172
- // its content is emitted verbatim, so nothing inside it takes a number, a
173
- // letter, or a bullet.
159
+ // AFH 33-337 numbers paragraphs and letters subparagraphs, and a body
160
+ // sometimes has to hold lines that are neither: a roster of names, a quoted
161
+ // passage, an address. The block quote is where an author says so — its
162
+ // content reaches the page verbatim, taking no number, letter, or bullet.
174
163
  //
175
164
  // Returning `none` rather than the quote is what makes that true: the
176
165
  // quote's own paragraphs are never laid out here, so the `show par` above
177
166
  // never sees them and cannot number them. The buffered body is emitted in
178
167
  // the second pass, where these show rules are out of scope. Typst's own
179
- // block-quote framing (padding, attribution) is dropped with the element;
180
- // what reaches the page is the author's lines and nothing else.
168
+ // block-quote framing (padding, attribution) is dropped with the element.
181
169
  //
182
170
  // The nesting level rides along so a quote inside a list item lines up with
183
- // that item's text (see the emission below). @quillmark/wasm 0.109.0 is
184
- // what makes that level trustworthy: before it, a container inside a list
185
- // item opened at column 0 and terminated the list, so a quote's siblings
186
- // came back as top-level paragraphs (#123). It now nests, and this quill
187
- // stopped declining the construct with it.
171
+ // that item's text (see the emission below).
188
172
  show quote.where(block: true): q => context {
189
- let nest_level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
173
+ let nest-level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
190
174
  PAR_BUFFER.update(pars => {
191
175
  pars.push((
192
176
  content: q.body,
193
- nest_level: nest_level,
177
+ nest-level: nest-level,
194
178
  kind: "quote",
195
179
  ))
196
180
  pars
@@ -204,9 +188,9 @@
204
188
  IS_HEADING.update(false)
205
189
  }
206
190
 
207
- // Convert list/enum items to pars
208
- // Note: No context wrapper here - state updates don't need it and cause
209
- // layout convergence issues with many list items
191
+ // List and enum items become paragraphs carrying a nesting level. No
192
+ // `context` wrapper: state updates do not need one, and one here fails
193
+ // to converge on a body with many list items.
210
194
  show enum.item: it => {
211
195
  NEST_DOWN.step()
212
196
  ITEM_FIRST_PAR.update(true)
@@ -221,9 +205,9 @@
221
205
  }
222
206
 
223
207
  {
224
- // Typst bug bandaid:
225
- // `show par` will not collect wrappers unless there is content outside
226
- // Add zero width space to always have content outside of wrapper
208
+ // `show par` does not capture an element that fills its paragraph
209
+ // whole, so a zero-width space follows each wrapper to keep some
210
+ // content outside it.
227
211
  show strong: it => {
228
212
  [#it#sym.zws]
229
213
  }
@@ -241,31 +225,30 @@
241
225
  }
242
226
  }
243
227
  // Use place() to prevent hidden content from affecting layout flow
244
- place(hide(first_pass))
228
+ place(hide(first-pass))
245
229
 
246
230
  // Second pass: consume par buffer
247
231
  //
248
232
  // PAR_BUFFER item dictionary layout:
249
233
  // item.content — the paragraph body, table element, or block-quote body
250
- // item.nest_level — nesting depth (−1 for tables)
234
+ // item.nest-level — nesting depth (−1 for tables)
251
235
  // item.kind — "par", "heading", "table", "continuation", or "quote"
252
236
  context {
253
- let heading_buffer = none
254
- let heading_level = 0
255
- // Filter out zero-width paragraphs so that an empty body (e.g. empty
256
- // string from a KIND with no text) emits nothing and collapses to zero
257
- // vertical space. Tables are always kept regardless of measured width.
237
+ let heading-buffer = none
238
+ let heading-level = 0
239
+ // Zero-width paragraphs are dropped, so an empty body emits nothing and
240
+ // collapses to zero vertical space. A table is kept whatever it measures.
258
241
  let items = PAR_BUFFER.get().filter(item =>
259
242
  item.kind == "table" or measure(item.content).width > 0pt
260
243
  )
261
244
  if items.len() == 0 { return }
262
245
  // Only top-level paragraphs count for AFH 33-337 §2 numbering purposes
263
- let par_count = items.filter(item => item.kind == "par").len()
264
- let total_count = items.len()
246
+ let par-count = items.filter(item => item.kind == "par").len()
247
+ let total-count = items.len()
265
248
 
266
- // Track paragraph numbers per level manually to avoid nested-context
267
- // counter propagation issues. Dictionary maps level index (as string)
268
- // to the current counter value at that level.
249
+ // Numbers are tracked in a dictionary keyed by level index (as a string)
250
+ // rather than in counters, whose updates do not propagate out of the
251
+ // nested contexts this loop runs in.
269
252
  let max-levels = paragraph-config.numbering-formats.len()
270
253
  let level-counts = (:)
271
254
  for lvl in range(max-levels) {
@@ -273,56 +256,55 @@
273
256
  }
274
257
 
275
258
  let i = 0
276
- let any_emitted = false
259
+ let any-emitted = false
277
260
  for item in items {
278
261
  i += 1
279
262
  let kind = item.kind
280
- let item_content = item.content
263
+ let item-content = item.content
281
264
 
282
265
  // A buffered heading runs into this element only when the two belong to
283
266
  // the same item: a later block of this list item ("continuation"), or,
284
267
  // at top level, the next paragraph. The first block of the *next* item,
285
- // a table (nest_level −1, so the level test alone excludes it), a block
268
+ // a table (nest-level −1, so the level test alone excludes it), a block
286
269
  // quote (verbatim: a heading prepended to it would be ink the author did
287
270
  // not put inside the quote), and another heading all fail the test, and
288
271
  // each would otherwise carry the heading's text somewhere it was not
289
272
  // authored. Those emit the heading on its own line, the treatment a
290
273
  // heading before a table takes.
291
- if heading_buffer != none {
292
- let runs_in = (
274
+ if heading-buffer != none {
275
+ let runs-in = (
293
276
  kind not in ("heading", "quote")
294
- and item.nest_level == heading_level
295
- and (heading_level == 0 or kind == "continuation")
277
+ and item.nest-level == heading-level
278
+ and (heading-level == 0 or kind == "continuation")
296
279
  )
297
- if runs_in {
298
- item_content = [#strong[#heading_buffer.] #item_content]
280
+ if runs-in {
281
+ item-content = [#strong[#heading-buffer.] #item-content]
299
282
  } else {
300
- if any_emitted { blank-line() }
301
- strong[#heading_buffer.]
302
- any_emitted = true
283
+ if any-emitted { blank-line() }
284
+ strong[#heading-buffer.]
285
+ any-emitted = true
303
286
  }
304
- heading_buffer = none
287
+ heading-buffer = none
305
288
  }
306
289
 
307
290
  // Buffer this heading for the next element to run into. The level it was
308
291
  // captured at rides along: the run-in test needs it, and the buffer is
309
292
  // read one iteration later, by which time `item` is the *next* element.
310
293
  if kind == "heading" {
311
- heading_buffer = item_content
312
- heading_level = item.nest_level
294
+ heading-buffer = item-content
295
+ heading-level = item.nest-level
313
296
  continue
314
297
  }
315
298
 
316
- // Format based on element kind
317
- let nest_level = item.nest_level
299
+ let nest-level = item.nest-level
318
300
  let indent-fn = if memo-style == "daf" {
319
301
  (level, _counts) => calculate-daf-indent(level)
320
302
  } else {
321
303
  (level, counts) => calculate-indent-from-counts(level, counts)
322
304
  }
323
- let final_par = {
305
+ let final-par = {
324
306
  if kind == "table" {
325
- render-memo-table(item_content)
307
+ render-memo-table(item-content)
326
308
  } else if kind == "quote" {
327
309
  // A block quote is the body's unlabeled block: no number, no letter,
328
310
  // no bullet — the author's lines as written. It is placed, not
@@ -348,40 +330,39 @@
348
330
  // body puts between its own paragraphs — keeps that break visible.
349
331
  let quoted = {
350
332
  set par(spacing: spacing.line + line-stride())
351
- item_content
333
+ item-content
352
334
  }
353
- let offset = paragraph-text-offset(nest_level, level-counts, indent-fn)
335
+ let offset = paragraph-text-offset(nest-level, level-counts, indent-fn)
354
336
  if offset == 0pt { quoted } else { pad(left: offset, quoted) }
355
337
  } else if kind == "continuation" {
356
338
  // Continuation block within a multi-block list item:
357
339
  // indent to align with preceding numbered paragraph's text, no new number.
358
340
  // level-counts still holds the value of the preceding numbered paragraph.
359
- if memo-style == "daf" and nest_level == 0 {
360
- item_content
341
+ if memo-style == "daf" and nest-level == 0 {
342
+ item-content
361
343
  } else {
362
- format-par(item_content, nest_level, level-counts, indent-fn, continuation: true)
344
+ format-par(item-content, nest-level, level-counts, indent-fn, continuation: true)
363
345
  }
364
346
  } else if memo-style == "daf" {
365
- if nest_level > 0 {
366
- let par = format-par(item_content, nest_level, level-counts, indent-fn)
367
- level-counts.insert(str(nest_level), level-counts.at(str(nest_level), default: 1) + 1)
368
- level-counts = reset-levels-from(level-counts, nest_level + 1, max-levels)
347
+ if nest-level > 0 {
348
+ let par = format-par(item-content, nest-level, level-counts, indent-fn)
349
+ level-counts.insert(str(nest-level), level-counts.at(str(nest-level), default: 1) + 1)
350
+ level-counts = reset-levels-from(level-counts, nest-level + 1, max-levels)
369
351
  par
370
352
  } else {
371
353
  // DAF top-level paragraphs are unnumbered and first-line indented.
372
354
  // Reset nested counters so each new top-level paragraph restarts children.
373
355
  level-counts = reset-levels-from(level-counts, 0, max-levels)
374
- [#h(daf-paragraph.top-first-line-indent)#item_content]
356
+ [#h(daf-paragraph.top-first-line-indent)#item-content]
375
357
  }
376
- } else if par_count > 1 {
377
- // Apply paragraph numbering per AFH 33-337 §2
378
- let par = format-par(item_content, nest_level, level-counts, indent-fn)
379
- level-counts.insert(str(nest_level), level-counts.at(str(nest_level)) + 1)
380
- level-counts = reset-levels-from(level-counts, nest_level + 1, max-levels)
358
+ } else if par-count > 1 {
359
+ let par = format-par(item-content, nest-level, level-counts, indent-fn)
360
+ level-counts.insert(str(nest-level), level-counts.at(str(nest-level)) + 1)
361
+ level-counts = reset-levels-from(level-counts, nest-level + 1, max-levels)
381
362
  par
382
363
  } else {
383
364
  // AFH 33-337 §2: "A single paragraph is not numbered"
384
- item_content
365
+ item-content
385
366
  }
386
367
  }
387
368
 
@@ -391,9 +372,9 @@
391
372
  // `context { for … }` block, where it does not combine with the
392
373
  // preceding header section's block-spacing the same way as a
393
374
  // top-level blank-line() call.
394
- if any_emitted { blank-line() }
395
- any_emitted = true
396
- if i == total_count {
375
+ if any-emitted { blank-line() }
376
+ any-emitted = true
377
+ if i == total-count {
397
378
  // AFH 33-337 "Signature Block": "Do not place the signature element on a
398
379
  // continuation page by itself." The signature block that follows has no
399
380
  // keep-with-previous of its own — Typst has no such property — so the
@@ -405,8 +386,8 @@
405
386
  // ordinary mid-paragraph break into a wholesale jump. A third of the
406
387
  // text block covers what a memorandum actually ends on — a closing
407
388
  // paragraph, a short table — while leaving the long block to be divided
408
- // by the break as before, which leaves its own tail on the continuation
409
- // page for the signature to sit under.
389
+ // by the break, which leaves its own tail on the continuation page for
390
+ // the signature to sit under.
410
391
  //
411
392
  // The budget is a fixed fraction rather than the space actually left
412
393
  // below this element, which is the question one would rather ask. A
@@ -415,26 +396,16 @@
415
396
  // the top of the page, so every element measures as fitting and
416
397
  // relocates — and from the top of its new page the same test agrees, so
417
398
  // the wrong answer is a fixed point that later passes never revisit.
418
- //
419
- // (The superseded rule was sticky under four lines, reading §11 — "avoid
420
- // dividing a paragraph of less than four lines between two pages" — as
421
- // if it governed the signature. It does not; it governs how a paragraph
422
- // may be divided. All the four-line gate did was leave every body ending
423
- // in a longer paragraph, or in a table, free to strand its signature.)
424
- let available_width = page.width - spacing.margin * 2
425
- let relocation_budget = (page.height - spacing.margin * 2) / 3
426
- let element_height = measure(final_par, width: available_width).height
427
- block(sticky: element_height <= relocation_budget)[#final_par]
399
+ let available-width = page.width - spacing.margin * 2
400
+ let relocation-budget = (page.height - spacing.margin * 2) / 3
401
+ let element-height = measure(final-par, width: available-width).height
402
+ block(sticky: element-height <= relocation-budget)[#final-par]
428
403
  } else {
429
- // Wrap every non-last emission in a plain block so the document-wide
430
- // `set block(above: spacing.line)` rule contributes the same 0.5em
431
- // gap above every paragraph, including the first (matching the
432
- // single-paragraph case which always passes through the
433
- // `i == total_count` branch) and middle paragraphs (so the gap
434
- // between the 1st and 2nd doesn't shrink relative to the gap
435
- // between the 2nd-to-last and last). Bare emission would skip
436
- // `block.above` entirely and visibly compress the spacing.
437
- block[#final_par]
404
+ // A plain block, so that the document-wide `set block(above:
405
+ // spacing.line)` contributes the same 0.5em above every paragraph as it
406
+ // does above the last one. A bare emission skips `block.above` and
407
+ // visibly compresses the gap.
408
+ block[#final-par]
438
409
  }
439
410
  }
440
411
 
@@ -442,9 +413,9 @@
442
413
  // dies with the loop unless it is drained here. Sticky for the same reason
443
414
  // the last item is: a standalone heading is one line, so it keeps with the
444
415
  // signature block rather than opening a page alone.
445
- if heading_buffer != none {
446
- if any_emitted { blank-line() }
447
- block(sticky: true)[#strong[#heading_buffer.]]
416
+ if heading-buffer != none {
417
+ if any-emitted { blank-line() }
418
+ block(sticky: true)[#strong[#heading-buffer.]]
448
419
  }
449
420
  }
450
421
  }
@@ -1,42 +1,24 @@
1
- // config.typ: Configuration constants and defaults for USAF memorandum template
2
- //
3
- // This module defines core configuration values that implement AFH 33-337 Chapter 14
4
- // formatting requirements for official USAF memorandums.
5
-
6
- // =============================================================================
7
- // SPACING CONSTANTS
8
- // =============================================================================
9
- // AFH 33-337 specifies precise spacing requirements throughout Chapter 14
1
+ // Spacing, typography, and color constants for the memorandum layout.
10
2
 
11
3
  #let spacing = (
12
- line: .5em, // Internal line spacing for readability (`par.leading`; gap between line boxes)
4
+ line: .5em, // `par.leading`: the gap between line boxes
13
5
  tab: 0.5in, // Tab stop for multi-column recipient alignment
14
6
  margin: 1in, // AFH 33-337 §4: "Use 1-inch margins on the left, right and bottom"
15
7
  )
16
8
 
17
- // =============================================================================
18
- // TYPOGRAPHY DEFAULTS
19
- // =============================================================================
20
- // AFH 33-337 §5: "Use 12 point Times New Roman font for text"
21
-
22
9
  #let DEFAULT_LETTERHEAD_FONTS = ("Copperplate CC", "NimbusRomNo9L")
23
- #let DEFAULT_BODY_FONTS = ("NimbusRomNo9L",) // AFH 33-337 §5: Times New Roman required; NimbusRomNo9L is a metric-compatible clone
24
- // Static monospace face for raw/code text. Liberation Mono is metric-compatible with
25
- // Courier New; only the regular weight is bundled (no bold or italic variants).
10
+ // AFH 33-337 §5: "Use 12 point Times New Roman font for text". NimbusRomNo9L is
11
+ // a metric-compatible clone of it; Liberation Mono, which sets raw text, is one
12
+ // of Courier New and carries its regular weight alone.
13
+ #let DEFAULT_BODY_FONTS = ("NimbusRomNo9L",)
26
14
  #let DEFAULT_MONO_FONTS = ("Liberation Mono",)
27
- #let LETTERHEAD_COLOR = rgb("#355e93") // Faded USAF blue for letterhead
28
-
29
- // =============================================================================
30
- // PARAGRAPH CONFIGURATION
31
- // =============================================================================
32
- // AFH 33-337 "The Text of the Official Memorandum" §2:
33
- // "Number and letter each paragraph and subparagraph"
34
- // Hierarchical numbering: 1., a., (1), (a), etc.
15
+ #let LETTERHEAD_COLOR = rgb("#355e93") // Faded USAF blue
35
16
 
17
+ // AFH 33-337 "The Text of the Official Memorandum" §2: "Number and letter each
18
+ // paragraph and subparagraph". The spec gives four levels; deeper nesting falls
19
+ // back to an underlined bare number.
36
20
  #let paragraph-config = (
37
21
  counter-prefix: "par-counter-",
38
- // AFH 33-337 §2: Hierarchical paragraph numbering format
39
- // Level 0: 1., 2., 3. | Level 1: a., b., c. | Level 2: (1), (2), (3) | Level 3: (a), (b), (c)
40
22
  numbering-formats: ("1.", "a.", "(1)", "(a)", n => underline(str(n)), n => underline(str(n))),
41
23
  )
42
24
 
@@ -48,27 +30,12 @@
48
30
  nested-step: 0.5in,
49
31
  )
50
32
 
51
- // =============================================================================
52
- // COUNTERS
53
- // =============================================================================
54
-
55
33
  #let counters = (
56
34
  indorsement: counter("indorsement"),
57
35
  )
58
36
 
59
- // =============================================================================
60
- // CLASSIFICATION COLORS
61
- // =============================================================================
62
- // AFH 33-337 §3: "Follow AFI 31-401, Information Security Program Management,
63
- // applicable executive orders and DoD guidance for the necessary markings on
64
- // classified correspondence."
65
- // Color values follow DoD/CAPCO standard classification marking colors:
66
- // - TOP SECRET: orange (#FF671F)
67
- // - SECRET: red (#C8102E)
68
- // - CONFIDENTIAL: black (per project style guide)
69
- // - CUI: black (DoDM 5200.48; per project style guide)
70
- // - UNCLASSIFIED: green (#007A33)
71
-
37
+ // DoD/CAPCO standard marking colors, except CONFIDENTIAL and CUI, which this
38
+ // template sets black.
72
39
  #let CLASSIFICATION_COLORS = (
73
40
  "TOP SECRET": rgb(255, 103, 31),
74
41
  "SECRET": rgb(200, 16, 46),