@airmark/quiver 0.32.2 → 0.34.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 (90) hide show
  1. package/README.md +63 -21
  2. package/package.json +9 -7
  3. package/quills/af4141/0.1.0/Quill.yaml +4 -13
  4. package/quills/af4141/0.1.0/example.md +53 -0
  5. package/quills/afmc_moa/0.0.1/Quill.yaml +39 -62
  6. package/quills/afmc_moa/0.0.1/example.md +114 -0
  7. package/quills/afmc_moa/0.0.1/packages/typst-afmc-moa/src/config.typ +1 -1
  8. package/quills/classic_resume/0.0.1/Quill.yaml +281 -0
  9. package/quills/classic_resume/0.0.1/example.md +111 -0
  10. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/LICENSE +21 -0
  11. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Bold.ttf +0 -0
  12. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-BoldItalic.ttf +0 -0
  13. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Italic.ttf +0 -0
  14. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Regular.ttf +0 -0
  15. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/OFL.txt +94 -0
  16. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/components.typ +214 -0
  17. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/config.typ +61 -0
  18. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/layout.typ +86 -0
  19. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/lib.typ +7 -0
  20. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/util.typ +37 -0
  21. package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/typst.toml +26 -0
  22. package/quills/classic_resume/0.0.1/plate.typ +143 -0
  23. package/quills/cyber_ribbon_chart/0.0.1/Quill.yaml +327 -0
  24. package/quills/cyber_ribbon_chart/0.0.1/example.md +55 -0
  25. package/quills/cyber_ribbon_chart/0.0.1/plate.typ +609 -0
  26. package/quills/daf1206/0.1.0/Quill.yaml +15 -18
  27. package/quills/daf1206/0.1.0/example.md +55 -0
  28. package/quills/daf4392/0.1.0/Quill.yaml +16 -7
  29. package/quills/daf4392/0.1.0/example.md +51 -0
  30. package/quills/daf4392/0.1.0/plate.typ +2 -2
  31. package/quills/usaf_letter/0.1.0/Quill.yaml +179 -0
  32. package/quills/usaf_letter/0.1.0/assets/dod_seal.png +0 -0
  33. package/quills/usaf_letter/0.1.0/assets/dow_seal.png +0 -0
  34. package/quills/usaf_letter/0.1.0/example.md +35 -0
  35. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/.gitignore +7 -0
  36. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/LICENSE +201 -0
  37. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/CopperplateCC-Bold.otf +0 -0
  38. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/LICENSE.md +79 -0
  39. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/LiberationMono/LICENSE +102 -0
  40. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/LiberationMono/LiberationMono-Regular.ttf +0 -0
  41. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/GNU General Public License.txt +340 -0
  42. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-Med.otf +0 -0
  43. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-MedIta.otf +0 -0
  44. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-Reg.otf +0 -0
  45. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-RegIta.otf +0 -0
  46. package/quills/{usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/LICENSE → usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/OFL.txt} +2 -2
  47. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/SpectralSC-Italic.ttf +0 -0
  48. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/SpectralSC-Regular.ttf +0 -0
  49. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/backmatter.typ +32 -0
  50. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/config.typ +45 -0
  51. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/frontmatter.typ +226 -0
  52. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/lib.typ +35 -0
  53. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/mainmatter.typ +140 -0
  54. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/primitives.typ +544 -0
  55. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/utils.typ +334 -0
  56. package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/typst.toml +22 -0
  57. package/quills/usaf_letter/0.1.0/plate.typ +121 -0
  58. package/quills/usaf_memo/0.2.0/Quill.yaml +8 -41
  59. package/quills/usaf_memo/0.2.0/example.md +45 -0
  60. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/LICENSE +201 -21
  61. package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/typst.toml +1 -1
  62. package/quills/usaf_memo/0.3.0/Quill.yaml +51 -69
  63. package/quills/usaf_memo/0.3.0/example.md +48 -0
  64. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/LICENSE +201 -21
  65. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/OFL.txt +93 -0
  66. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/SpectralSC-Italic.ttf +0 -0
  67. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/SpectralSC-Regular.ttf +0 -0
  68. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/backmatter.typ +29 -17
  69. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/body.typ +97 -126
  70. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/config.typ +12 -45
  71. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +81 -87
  72. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +58 -66
  73. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/lib.typ +14 -36
  74. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/mainmatter.typ +47 -19
  75. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +158 -111
  76. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/utils.typ +41 -141
  77. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/typst.toml +3 -8
  78. package/quills/usaf_memo/0.3.0/plate.typ +78 -85
  79. package/templates/af4141.md +23 -0
  80. package/templates/appointment_letter.md +58 -0
  81. package/templates/daf1206.md +23 -0
  82. package/templates/daf4392.md +34 -0
  83. package/templates/deros_extension.md +40 -0
  84. package/templates/pass_request.md +48 -0
  85. package/templates/rebuttal.md +63 -0
  86. package/templates/templates.json +65 -0
  87. package/templates/usaf_template.md +30 -0
  88. package/templates/ussf_template.md +30 -0
  89. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel[wght].ttf +0 -0
  90. package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
@@ -1,58 +1,54 @@
1
- // frontmatter.typ: Frontmatter show rule for USAF memorandum
2
- //
3
- // This module implements the frontmatter (heading section) of a USAF memorandum
4
- // per AFH 33-337 Chapter 14 "The Heading Section". It handles:
5
- // - Page setup with proper margins
6
- // - Letterhead rendering
7
- // - Date, MEMORANDUM FOR, FROM, SUBJECT, and References placement
8
- // - Classification markings in headers/footers
1
+ // The memorandum's heading section, and the page setup the rest of the
2
+ // document inherits: AFH 33-337 Chapter 14 "The Heading Section".
9
3
 
10
4
  #import "primitives.typ": *
11
5
 
12
6
  #let frontmatter(
13
7
  subject: none,
14
- memo_for: none,
15
- memo_from: none,
8
+ memo-for: none,
9
+ memo-from: none,
16
10
  date: none,
11
+ // Fill-in widget printed on the date line for an omitted `date`, in place of
12
+ // today's date. An indorsement header restating the memo then leaves the date
13
+ // out.
14
+ date-field: none,
17
15
  references: none,
18
- letterhead_title: "DEPARTMENT OF THE AIR FORCE",
19
- letterhead_caption: "[YOUR SQUADRON/UNIT NAME]",
20
- letterhead_seal: none,
21
- letterhead_seal_subtitle: none, // optional line under seal (9pt bold caps); ignored if no seal
22
- letterhead_emblem: none, // optional image placed opposite the seal (right side)
23
- letterhead_emblem_height: 1in, // emblem fit-box height; reduce for shorter emblems
24
- letterhead_font: DEFAULT_LETTERHEAD_FONTS,
25
- body_font: DEFAULT_BODY_FONTS,
26
- font_size: 12pt,
27
- memo_for_cols: 3,
28
- classification_level: none,
16
+ letterhead-title: "DEPARTMENT OF THE AIR FORCE",
17
+ letterhead-caption: "[YOUR SQUADRON/UNIT NAME]",
18
+ letterhead-seal: none,
19
+ letterhead-seal-subtitle: none, // optional line under seal (9pt bold caps); ignored if no seal
20
+ letterhead-emblem: none, // optional image placed opposite the seal (right side)
21
+ letterhead-emblem-height: 1in, // emblem fit-box height; reduce for shorter emblems
22
+ letterhead-font: DEFAULT_LETTERHEAD_FONTS,
23
+ body-font: DEFAULT_BODY_FONTS,
24
+ font-size: 12pt,
25
+ memo-for-cols: 3,
26
+ classification-level: none,
29
27
  dissemination: none,
30
- cui_controlled_by: none,
31
- cui_category: none,
32
- cui_limited_dissemination: none,
33
- cui_poc: none,
34
- footer_tag_line: none,
35
- memo_style: "usaf",
28
+ cui-controlled-by: none,
29
+ cui-category: none,
30
+ cui-limited-dissemination: none,
31
+ cui-poc: none,
32
+ footer-tag-line: none,
33
+ memo-style: "usaf",
36
34
  it,
37
35
  ) = {
38
36
  assert(subject != none, message: "subject is required")
39
- assert(memo_for != none, message: "memo_for is required")
37
+ assert(memo-for != none, message: "memo-for is required")
40
38
  assert(
41
- memo_style in ("usaf", "daf"),
42
- message: "memo_style must be \"usaf\" or \"daf\"",
39
+ memo-style in ("usaf", "daf"),
40
+ message: "memo-style must be \"usaf\" or \"daf\"",
43
41
  )
44
42
 
45
- let actual_date = if date == none { datetime.today() } else { date }
43
+ let actual-date = if date != none { date } else if date-field != none { date-field } else { datetime.today() }
46
44
 
47
- // The banner is `LEVEL` or `LEVEL//SUFFIX`. `classification_level` is an enum
48
- // (a `str`), but `dissemination` is a `plaintext` field and so arrives as
49
- // content, which `str + str` cannot absorb — the marking is assembled as
50
- // content instead, keeping the suffix's glyphs click-navigable on every page
51
- // the banner repeats on.
52
- let classification_marking = if classification_level == none or type(classification_level) != str {
45
+ // The banner is `LEVEL` or `LEVEL//SUFFIX`. `classification-level` is an enum
46
+ // (a `str`), but `dissemination` may arrive as content, which `str + str`
47
+ // cannot absorb — so the marking is assembled as content instead.
48
+ let classification-marking = if classification-level == none or type(classification-level) != str {
53
49
  none
54
50
  } else {
55
- let base = classification_level.trim()
51
+ let base = classification-level.trim()
56
52
  if base == "" {
57
53
  none
58
54
  } else if falsey(dissemination) {
@@ -66,42 +62,40 @@
66
62
  [#base#separator#box(upper(dissemination))]
67
63
  }
68
64
  }
69
- let classification_color = get-classification-level-color(classification_level)
65
+ let classification-color = get-classification-level-color(classification-level)
70
66
 
71
67
  // Build the CUI designation indicator block (DoDM 5200.48, Table 1), shown
72
68
  // only for CUI when at least one indicator field is set. Rendered as a
73
69
  // bottom-right page-1 float (see placement below).
74
- let cui_indicator = if (
75
- classification_level != none
76
- and type(classification_level) == str
77
- and classification_level.trim().starts-with("CUI")
70
+ let cui-indicator = if (
71
+ classification-level != none
72
+ and type(classification-level) == str
73
+ and classification-level.trim().starts-with("CUI")
78
74
  ) {
79
- // Each indicator is a `plaintext` field, so it arrives as content when set
80
- // and as `""` when blank — `falsey` is the shape-agnostic presence test
81
- // that the former `type(..) == str and ..trim() != ""` guard replaces.
75
+ // An indicator may arrive as content or as a `str`; `falsey` is the
76
+ // presence test that reads both shapes.
82
77
  let lines = ()
83
- if not falsey(cui_controlled_by) {
84
- lines.push([Controlled By: #cui_controlled_by])
78
+ if not falsey(cui-controlled-by) {
79
+ lines.push([Controlled By: #cui-controlled-by])
85
80
  }
86
- if not falsey(cui_category) {
87
- lines.push([CUI Category: #cui_category])
81
+ if not falsey(cui-category) {
82
+ lines.push([CUI Category: #cui-category])
88
83
  }
89
- if not falsey(cui_limited_dissemination) {
90
- lines.push([LDC: #upper(cui_limited_dissemination)])
84
+ if not falsey(cui-limited-dissemination) {
85
+ lines.push([LDC: #upper(cui-limited-dissemination)])
91
86
  }
92
- if not falsey(cui_poc) {
93
- lines.push([POC: #cui_poc])
87
+ if not falsey(cui-poc) {
88
+ lines.push([POC: #cui-poc])
94
89
  }
95
90
  if lines.len() > 0 { lines.join(linebreak()) } else { none }
96
91
  } else {
97
92
  none
98
93
  }
99
94
 
100
- // Document-wide typography settings (inlined from configure())
101
95
  set par(leading: spacing.line, spacing: spacing.line, justify: false)
102
96
  set block(above: spacing.line, below: 0em, spacing: 0em)
103
- set text(font: body_font, size: font_size, fallback: true)
104
- show raw: set text(font: DEFAULT_MONO_FONTS) // Static monospace face for inline code and code blocks
97
+ set text(font: body-font, size: font-size, fallback: true)
98
+ show raw: set text(font: DEFAULT_MONO_FONTS)
105
99
 
106
100
  set page(
107
101
  paper: "us-letter",
@@ -126,29 +120,29 @@
126
120
  )
127
121
  }
128
122
 
129
- if classification_marking != none {
123
+ if classification-marking != none {
130
124
  place(
131
125
  top + center,
132
126
  dy: 0.375in,
133
- text(12pt, font: DEFAULT_BODY_FONTS, fill: classification_color)[#strong(classification_marking)],
127
+ text(12pt, font: DEFAULT_BODY_FONTS, fill: classification-color)[#strong(classification-marking)],
134
128
  )
135
129
  }
136
130
  },
137
131
  footer: {
138
- if classification_marking != none {
132
+ if classification-marking != none {
139
133
  place(
140
134
  bottom + center,
141
135
  dy: -.375in,
142
- text(12pt, font: DEFAULT_BODY_FONTS, fill: classification_color)[#strong(classification_marking)],
136
+ text(12pt, font: DEFAULT_BODY_FONTS, fill: classification-color)[#strong(classification-marking)],
143
137
  )
144
138
  }
145
139
 
146
- if not falsey(footer_tag_line) {
140
+ if not falsey(footer-tag-line) {
147
141
  place(
148
142
  bottom + center,
149
143
  dy: -0.625in,
150
144
  align(center)[
151
- #text(fill: LETTERHEAD_COLOR, font: "cinzel", size: 15pt)[#footer_tag_line]
145
+ #text(fill: LETTERHEAD_COLOR, font: "Spectral SC", size: 15pt)[#footer-tag-line]
152
146
  ],
153
147
  )
154
148
  }
@@ -160,25 +154,25 @@
160
154
  // it (1) reserves flow space, raising page 1's effective bottom margin so body
161
155
  // text never overlaps it, and (2) stays pinned to page 1 — as the first flow
162
156
  // content it can never be bumped to page 2.
163
- if cui_indicator != none {
157
+ if cui-indicator != none {
164
158
  context {
165
159
  // The box shrink-wraps to its widest line; `set align(left)` keeps the
166
160
  // text flush-left within it, overriding the `align(right)` the placement
167
161
  // below imposes.
168
- let indicator_box = box({
162
+ let indicator-box = box({
169
163
  set text(font: DEFAULT_BODY_FONTS, size: 10pt)
170
164
  set par(leading: 0.4em, spacing: 0pt)
171
165
  set align(left)
172
- cui_indicator
166
+ cui-indicator
173
167
  })
174
168
  // Reserve only the part of the block inside the text area (`reserved`):
175
169
  // float a box of that height, then `place` the full block inside it pushed
176
170
  // down by `overhang` so the surplus overflows into the edge band. (A bare
177
- // `box(height: reserved, indicator_box)` overflows *upward* into the body
171
+ // `box(height: reserved, indicator-box)` overflows *upward* into the body
178
172
  // instead.) The inner `place` adds no size, so the box stays `reserved`
179
173
  // tall and the block's bottom lands 0.5in from the page edge.
180
174
  let overhang = spacing.margin - 0.5in
181
- let reserved = measure(indicator_box).height - overhang
175
+ let reserved = measure(indicator-box).height - overhang
182
176
  place(
183
177
  bottom + right,
184
178
  float: true,
@@ -188,44 +182,44 @@
188
182
  // Minimum gap to the body's last line; the actual gap is larger when the
189
183
  // next paragraph can't fit above the block and breaks to the next page.
190
184
  clearance: spacing.line,
191
- box(height: reserved, place(bottom + right, dy: overhang, indicator_box)),
185
+ box(height: reserved, place(bottom + right, dy: overhang, indicator-box)),
192
186
  )
193
187
  }
194
188
  }
195
189
 
196
190
  render-letterhead(
197
- letterhead_title,
198
- letterhead_caption,
199
- letterhead_font,
200
- letterhead-seal: letterhead_seal,
201
- letterhead-seal-subtitle: letterhead_seal_subtitle,
202
- letterhead-emblem: letterhead_emblem,
203
- letterhead-emblem-height: letterhead_emblem_height,
191
+ letterhead-title,
192
+ letterhead-caption,
193
+ letterhead-font,
194
+ letterhead-seal: letterhead-seal,
195
+ letterhead-seal-subtitle: letterhead-seal-subtitle,
196
+ letterhead-emblem: letterhead-emblem,
197
+ letterhead-emblem-height: letterhead-emblem-height,
204
198
  )
205
199
 
206
200
  // AFH 33-337 "Date": "Place the date 1 inch from the right edge, 1.75 inches from the top"
207
201
  // Since we have a 1-inch top margin, we need (1.75in - margin) vertical space
208
202
  v(1.75in - spacing.margin)
209
203
 
210
- // Measure and cache body line stride once for body line-count heuristics.
204
+ // Measure one line's stride once, under the typography just set, for the
205
+ // blank-line spacing the sections below and the body are laid out on.
211
206
  context {
212
207
  let one-line = measure(par(spacing: 0pt)[x]).height
213
- let line-stride = measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line
214
- LINE_STRIDE.update(line-stride)
208
+ LINE_STRIDE.update(measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line)
215
209
  }
216
210
 
217
211
  [#metadata((
218
212
  subject: subject,
219
- original_date: actual_date,
220
- original_from: first-or-value(memo_from),
221
- body_font: body_font,
222
- font_size: font_size,
223
- memo_style: memo_style,
213
+ original-date: if date == none and date-field != none { none } else { actual-date },
214
+ original-from: first-or-value(memo-from),
215
+ body-font: body-font,
216
+ font-size: font-size,
217
+ memo-style: memo-style,
224
218
  )) <usaf-memo-config>]
225
219
 
226
- render-date-section(actual_date, memo-style: memo_style)
227
- render-for-section(memo_for, memo_for_cols)
228
- if not falsey(memo_from) { render-from-section(memo_from) }
220
+ render-date-section(actual-date, memo-style: memo-style)
221
+ render-for-section(memo-for, memo-for-cols)
222
+ if not falsey(memo-from) { render-from-section(memo-from) }
229
223
  // Blank entries are dropped first so a stub `- ` left under `references:`
230
224
  // cannot pass as the lone reference and render an empty `()` after the subject.
231
225
  let refs = compact-references(references)
@@ -1,14 +1,8 @@
1
- // indorsement.typ: Indorsement rendering for USAF memorandum
1
+ // An indorsement forwards a memorandum onward with the endorser's own
2
+ // commentary and signature, per AFH 33-337 Chapter 14.
2
3
  //
3
- // This module implements indorsements (endorsements) per AFH 33-337 Chapter 14.
4
- // Indorsements are used to forward memorandums with additional commentary.
5
- // They follow the format: "1st Ind", "2d Ind", "3d Ind", etc.
6
- // Each indorsement includes its own body text and signature block.
7
- //
8
- // Note: When using #show: indorsement.with(...), the indorsement wraps the
9
- // entire remainder of the document. This works for a single indorsement at
10
- // the end of a file. For multiple indorsements, use the function call syntax:
11
- // #indorsement(...)[Body text...]
4
+ // Call it as a function, `#indorsement(..)[Body]`. As a show rule it wraps the
5
+ // whole remainder of the document, which leaves room for exactly one.
12
6
 
13
7
  #import "primitives.typ": *
14
8
  #import "body.typ": *
@@ -16,14 +10,17 @@
16
10
  #let indorsement(
17
11
  from: none,
18
12
  to: none,
19
- signature_block: none,
20
- signature_blank_lines: 4,
21
- signing_field: none,
13
+ signature-block: none,
14
+ // An indorsement has a closing section of its own, so it takes an authority
15
+ // line on the same terms as the memorandum.
16
+ authority-line: none,
17
+ signature-blank-lines: 4,
18
+ signing-field: none,
22
19
  date: none,
23
20
  // Fill-in widget for an omitted `date`, anchored in the date slot of the
24
- // indorsement header (see `date-placeholder-slot`). `none` leaves the slot
25
- // blank.
26
- date_field: none,
21
+ // indorsement header (see `date-placeholder-slot`). Without one the slot is
22
+ // ruled for a handwritten date.
23
+ date-field: none,
27
24
  // Format of indorsement: "standard" (same page), "informal" (no header), or "separate_page" (starts on new page)
28
25
  format: "standard",
29
26
  // Decision action. `none` (default) displays no action line at all.
@@ -35,10 +32,9 @@
35
32
  // in the chain) Approves / Disapproves, every coordinating official before
36
33
  // it Concurs / Nonconcurs. The caller owns this because only it can see
37
34
  // whether further indorsements follow.
38
- approval_authority: false,
35
+ approval-authority: false,
39
36
  content,
40
- ) = {
41
- // Validate format parameter
37
+ ) = [#{
42
38
  assert(
43
39
  format in ("standard", "informal", "separate_page"),
44
40
  message: "format must be \"standard\", \"informal\", or \"separate_page\"",
@@ -50,62 +46,59 @@
50
46
  }
51
47
 
52
48
 
53
- let actual_date = date
54
- let ind_from = first-or-value(from)
55
- let ind_for = to
49
+ let actual-date = date
50
+ let ind-from = first-or-value(from)
51
+ let ind-for = to
56
52
 
57
- // An empty body (e.g. a KIND with only an action selected and no markdown
58
- // body) collapses to zero rendered layout via render-body's filter. To
59
- // make the "empty body takes no layout space" guarantee end-to-end, also
60
- // suppress the spacing the surrounding code reserves *for* the body:
61
- // the header→body gap (when no action is present) and the action→body
62
- // trailing gap. Without this, an empty body still leaves an extra
63
- // blank-line stride above the signature, pushing it off the AFH 33-337
64
- // "fifth line below the last line of text" anchor.
65
- // The plate calls indorsement with `[]` when the markdown body is empty
66
- // or whitespace-only; comparing against `[]` reliably detects that.
67
- let body_empty = content == []
53
+ // An empty body renders as zero layout through render-body's filter, so the
54
+ // spacing reserved *for* the body is suppressed too: the header→body gap
55
+ // (when no action is present) and the action→body trailing gap. Left in,
56
+ // they add a blank-line stride above the signature and push it off AFH
57
+ // 33-337's "fifth line below the last line of text" anchor.
58
+ //
59
+ // A caller with no body passes `[]`.
60
+ let body-empty = content == []
68
61
 
69
- let effective_action = if action == none or type(action) != str or action.trim() == "" {
62
+ let effective-action = if action == none or type(action) != str or action.trim() == "" {
70
63
  none
71
64
  } else {
72
65
  action
73
66
  }
74
67
 
75
68
  if format != "informal" {
76
- // Step the counter BEFORE the context block to avoid read-then-update loop
69
+ // Stepped outside the `context` below, which reads it: a step inside would
70
+ // read and update the same counter.
77
71
  counters.indorsement.step()
78
72
 
79
73
  context {
80
74
  let config = query(<usaf-memo-config>).first().value
81
- let memo-style = config.at("memo_style", default: "usaf")
82
- let original_subject = config.subject
83
- let original_date = config.original_date
84
- let original_from = config.original_from
75
+ let memo-style = config.at("memo-style", default: "usaf")
76
+ let original-subject = config.subject
77
+ let original-date = config.original-date
78
+ let original-from = config.original-from
85
79
 
86
- // Read the counter value (already stepped above)
87
- let indorsement_number = counters.indorsement.get().at(0, default: 1)
88
- let indorsement_label = format-indorsement-number(indorsement_number)
80
+ let indorsement-number = counters.indorsement.get().at(0, default: 1)
81
+ let indorsement-label = format-indorsement-number(indorsement-number)
89
82
 
90
- let ind_date = align(right)[#if actual_date != none { display-date(actual_date, memo-style: memo-style) } else { date-placeholder-slot(date_field) }]
83
+ let ind-date = align(right)[#if actual-date != none { display-date(actual-date, memo-style: memo-style) } else { date-placeholder-slot(field: date-field) }]
91
84
 
92
85
  // Separate-page header body: restates the original memo's identity (FROM,
93
86
  // date, subject) on its own line, since the indorsement no longer shares a
94
87
  // page with the action document. Rendered as a non-breakable, sticky unit
95
88
  // so it travels to the next page *with* the content it heads rather than
96
89
  // being stranded at the bottom of a page.
97
- // `original_from` / `original_subject` are content fields, and a content
90
+ // `original-from` / `original-subject` are content fields, and a content
98
91
  // field lowers to a markup block whose newlines read as spaces in the
99
92
  // enclosing paragraph — mid-sentence that lands a stray space before the
100
93
  // following comma. `box` gives each its own paragraph context, where
101
94
  // Typst trims the edge spaces (the same treatment the inline reference
102
95
  // gets in `render-subject-section`), and keeps the phrase unbroken.
103
96
  let separate-page-body = block(breakable: false, sticky: true)[
104
- #[#indorsement_label to #box(original_from), #display-date(original_date, memo-style: memo-style), #box(original_subject)]
97
+ #[#indorsement-label to #box(original-from), #if original-date != none [#display-date(original-date, memo-style: memo-style), ]#box(original-subject)]
105
98
  #blank-line()
106
- #grid(columns: (auto, 1fr), ind_from, ind_date)
99
+ #grid(columns: (auto, 1fr), ind-from, ind-date)
107
100
  #blank-line()
108
- #grid(columns: (auto, auto, 1fr), "MEMORANDUM FOR", " ", ind_for)
101
+ #grid(columns: (auto, auto, 1fr), "MEMORANDUM FOR", " ", ind-for)
109
102
  ]
110
103
 
111
104
  // Standard header: terse "Nth Ind, FROM date" line, used when the
@@ -114,9 +107,9 @@
114
107
  // across a page boundary and never detach from the body/signature below.
115
108
  let standard-header = block(breakable: false, sticky: true)[
116
109
  #blank-line()
117
- #grid(columns: (auto, 1fr), [#indorsement_label, #box(ind_from)], ind_date)
110
+ #grid(columns: (auto, 1fr), [#indorsement-label, #box(ind-from)], ind-date)
118
111
  #blank-line()
119
- #grid(columns: (auto, auto, 1fr), "MEMORANDUM FOR", " ", ind_for)
112
+ #grid(columns: (auto, auto, 1fr), "MEMORANDUM FOR", " ", ind-for)
120
113
  ]
121
114
 
122
115
  if format == "separate_page" {
@@ -137,13 +130,7 @@
137
130
  // resolved position is at the top of its page, it was pushed; emit the
138
131
  // separate-page body (no extra pagebreak — we are already at page top).
139
132
  // Otherwise the header flows in place with the standard form.
140
- let stride = {
141
- let s = LINE_STRIDE.get()
142
- if s == none {
143
- let one-line = measure(par(spacing: 0pt)[x]).height
144
- measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line
145
- } else { s }
146
- }
133
+ let stride = line-stride()
147
134
  // here().position().y is the resolved flow position of this header. On a
148
135
  // continuation page the first content sits at the top margin; allow one
149
136
  // line stride of tolerance for baseline/rounding.
@@ -157,26 +144,31 @@
157
144
  }
158
145
  // Header→content gap. Skipped when there is neither an action line nor
159
146
  // body to follow — render-signature-block supplies its own 4-line gap.
160
- if effective_action != none or not body_empty {
147
+ if effective-action != none or not body-empty {
161
148
  blank-line()
162
149
  }
163
150
  }
164
151
 
165
- // Show action line only when an action decision is set (not `none`)
166
- if effective_action != none {
152
+ if effective-action != none {
167
153
  render-action-line(
168
- effective_action,
169
- approval-authority: approval_authority,
170
- trailing-blank-line: not body_empty,
154
+ effective-action,
155
+ approval-authority: approval-authority,
156
+ trailing-blank-line: not body-empty,
171
157
  )
172
158
  }
173
159
 
174
- if not body_empty {
160
+ if not body-empty {
175
161
  context {
176
- let memo-style = query(<usaf-memo-config>).first().value.at("memo_style", default: "usaf")
162
+ let memo-style = query(<usaf-memo-config>).first().value.at("memo-style", default: "usaf")
177
163
  render-body(content, memo-style: memo-style)
178
164
  }
179
165
  }
180
166
 
181
- render-signature-block(signature_block, signature-blank-lines: signature_blank_lines, signing-field: signing_field)
182
- }
167
+ render-signature-block(
168
+ signature-block,
169
+ closing-line: format-authority-line(authority-line),
170
+ signature-blank-lines: signature-blank-lines,
171
+ signing-field: signing-field,
172
+ )
173
+ // Labelled so `mainmatter` can split the closing off the body; `split-closing`.
174
+ }<usaf-memo-closing>]
@@ -1,52 +1,30 @@
1
- // lib.typ: Public API for USAF memorandum template
1
+ // Public API of the USAF memorandum template, which typesets per AFH 33-337
2
+ // "The Tongue and Quill" Chapter 14, "The Official Memorandum".
2
3
  //
3
- // This module provides a composable API for creating United States Air Force
4
- // memorandums that comply with AFH 33-337 "The Tongue and Quill" Chapter 14
5
- // "The Official Memorandum" formatting standards.
4
+ // The four sections compose in document order: `frontmatter` and `mainmatter`
5
+ // as show rules, `backmatter` and `indorsement` as functions, so any number of
6
+ // indorsements may follow the memo they endorse.
6
7
  //
7
- // AFH 33-337 Chapter 14 specifies exact requirements for:
8
- // - Margins: 1 inch on all sides (§4)
9
- // - Font: 12pt Times New Roman (§5)
10
- // - Date placement: 1.75 inches from top, 1 inch from right (Date section)
11
- // - Heading elements: MEMORANDUM FOR, FROM, SUBJECT with 2-line spacing
12
- // - Paragraph numbering: Hierarchical 1., a., (1), (a) format (§2)
13
- // - Signature block: 4.5 inches from left, never orphaned (Signature Block section)
14
- // - Backmatter: Attachments, cc:, distribution with specific spacing
15
- //
16
- // Key features:
17
- // - Composable show rules for frontmatter and mainmatter
18
- // - Function-based backmatter and indorsements for correct ordering
19
- // - No global state - configuration flows through metadata
20
- // - Reusable primitives for common rendering tasks
21
- // - AFH 33-337 compliant formatting throughout
22
- //
23
- // Basic usage:
24
- //
25
- // #import "@preview/tonguetoquill-usaf-memo:3.0.0": frontmatter, mainmatter, backmatter, indorsement
8
+ // #import "@preview/tonguetoquill-usaf-memo:5.0.0": (
9
+ // backmatter, frontmatter, indorsement, mainmatter,
10
+ // )
26
11
  //
27
12
  // #show: frontmatter.with(
28
13
  // subject: "Your Subject Here",
29
- // memo_for: ("OFFICE/SYMBOL",),
30
- // memo_from: ("YOUR/SYMBOL",),
31
- // memo_style: "usaf", // "usaf" (default) or "daf"
14
+ // memo-for: ("OFFICE/SYMBOL",),
15
+ // memo-from: ("YOUR/SYMBOL",),
16
+ // memo-style: "usaf", // "usaf" (default) or "daf"
32
17
  // )
33
- //
34
18
  // #show: mainmatter
35
19
  //
36
- // Your memo body content here.
37
- // (USAF style auto-numbers per AFH 33-337; DAF style uses unnumbered
38
- // top-level paragraphs with fixed 0.5in first-line indentation)
20
+ // Body paragraphs.
39
21
  //
40
- // #backmatter(
41
- // signature_block: ("NAME, Rank, USAF", "Title"),
42
- // attachments: (...),
43
- // cc: (...),
44
- // )
22
+ // #backmatter(signature-block: ("FIRST M. LAST, Maj, USAF", "Duty Title"))
45
23
  //
46
24
  // #indorsement(
47
25
  // from: "ORG/SYMBOL",
48
26
  // to: "RECIPIENT/SYMBOL",
49
- // signature_block: ("NAME, Rank, USAF", "Title"),
27
+ // signature-block: ("FIRST M. LAST, Maj, USAF", "Duty Title"),
50
28
  // )[
51
29
  // Indorsement content here.
52
30
  // ]
@@ -1,27 +1,55 @@
1
- // mainmatter.typ: Mainmatter show rule for USAF memorandum
2
- //
3
- // This module implements the mainmatter (body text) of a USAF memorandum per
4
- // AFH 33-337 Chapter 14 "The Text of the Official Memorandum" (§1-12).
1
+ // The memorandum body: AFH 33-337 "The Text of the Official Memorandum" §1-12.
5
2
 
6
3
  #import "body.typ": *
7
4
 
8
- /// Mainmatter show rule for USAF memorandum body content.
5
+ /// Splits content at the first closing section: the part `render-body` numbers,
6
+ /// and the part that reaches the page untouched.
9
7
  ///
10
- /// AFH 33-337 "The Text of the Official Memorandum" §1-12 requirements:
11
- /// - Begin text on second line below subject/references
12
- /// - Single-space text, double-space between paragraphs
13
- /// - Number and letter each paragraph/subparagraph
14
- /// - "A single paragraph is not numbered" (§2)
15
- /// - First paragraph flush left, never indented
8
+ /// `render-body` rebuilds what it is given from a buffer of paragraphs, tables,
9
+ /// and block quotes. A closing section through it loses the placement it is
10
+ /// made of, and its surviving lines take body paragraph numbers. Applied as a
11
+ /// show rule, `mainmatter` is handed those sections along with the body;
12
+ /// `backmatter` and `indorsement` label their output to mark the boundary.
16
13
  ///
17
- /// Applies AFH 33-337 paragraph numbering and formatting to the main body
18
- /// of the memorandum. Automatically detects single vs. multiple paragraphs
19
- /// to comply with AFH 33-337 numbering requirements.
14
+ /// Everything from the first marker on stays together, prose a caller wrote
15
+ /// between two closing sections included.
20
16
  ///
21
- /// - content (content): The body content to render
17
+ /// A marker is found on `it` itself, on a direct child, or inside the `styled`
18
+ /// element that a `set` or `show` rule after `#show: mainmatter` wraps the
19
+ /// remainder in; both halves come back under those styles. A marker a caller
20
+ /// nested in a container of their own — a `block`, a `grid` cell — is not
21
+ /// found. A closing section built in a code block or a loop is not nested:
22
+ /// joining content extends the sequence on the left in place, so its marker
23
+ /// stays a direct child.
24
+ ///
25
+ /// - it (content): Content handed to `mainmatter`
26
+ /// -> array: the body content and the closing content
27
+ #let split-closing(it) = {
28
+ if it.at("label", default: none) == <usaf-memo-closing> { return ([], it) }
29
+ // `styled` is not a name in scope; its two fields identify it.
30
+ if it.has("child") and it.has("styles") {
31
+ let (body, closing) = split-closing(it.child)
32
+ let restyle = part => (it.func())(part, it.styles)
33
+ return (restyle(body), restyle(closing))
34
+ }
35
+ if not it.has("children") { return (it, []) }
36
+ let children = it.children
37
+ let boundary = children.position(child => child.at("label", default: none) == <usaf-memo-closing>)
38
+ if boundary == none { return (it, []) }
39
+ // `sum` over `join`: an empty half is `none` from a join and `[]` from this.
40
+ (children.slice(0, boundary).sum(default: []), children.slice(boundary).sum(default: []))
41
+ }
42
+
43
+ /// Show rule for the memorandum body, applying AFH 33-337 paragraph numbering
44
+ /// in the style the frontmatter recorded.
45
+ ///
46
+ /// - it (content): Body content
22
47
  /// -> content
23
- #let mainmatter(it) = context {
24
- let config = query(<usaf-memo-config>).first().value
25
- let memo-style = config.at("memo_style", default: "usaf")
26
- render-body(it, memo-style: memo-style)
48
+ #let mainmatter(it) = {
49
+ let (body, closing) = split-closing(it)
50
+ context {
51
+ let config = query(<usaf-memo-config>).first().value
52
+ render-body(body, memo-style: config.at("memo-style", default: "usaf"))
53
+ }
54
+ closing
27
55
  }