@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.
- package/README.md +63 -21
- package/package.json +9 -7
- package/quills/af4141/0.1.0/Quill.yaml +4 -13
- package/quills/af4141/0.1.0/example.md +53 -0
- package/quills/afmc_moa/0.0.1/Quill.yaml +39 -62
- package/quills/afmc_moa/0.0.1/example.md +114 -0
- package/quills/afmc_moa/0.0.1/packages/typst-afmc-moa/src/config.typ +1 -1
- package/quills/classic_resume/0.0.1/Quill.yaml +281 -0
- package/quills/classic_resume/0.0.1/example.md +111 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/LICENSE +21 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Bold.ttf +0 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-BoldItalic.ttf +0 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Italic.ttf +0 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/EBGaramond-Regular.ttf +0 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/fonts/EBGaramond/OFL.txt +94 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/components.typ +214 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/config.typ +61 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/layout.typ +86 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/lib.typ +7 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/util.typ +37 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/typst.toml +26 -0
- package/quills/classic_resume/0.0.1/plate.typ +143 -0
- package/quills/cyber_ribbon_chart/0.0.1/Quill.yaml +327 -0
- package/quills/cyber_ribbon_chart/0.0.1/example.md +55 -0
- package/quills/cyber_ribbon_chart/0.0.1/plate.typ +609 -0
- package/quills/daf1206/0.1.0/Quill.yaml +15 -18
- package/quills/daf1206/0.1.0/example.md +55 -0
- package/quills/daf4392/0.1.0/Quill.yaml +16 -7
- package/quills/daf4392/0.1.0/example.md +51 -0
- package/quills/daf4392/0.1.0/plate.typ +2 -2
- package/quills/usaf_letter/0.1.0/Quill.yaml +179 -0
- package/quills/usaf_letter/0.1.0/assets/dod_seal.png +0 -0
- package/quills/usaf_letter/0.1.0/assets/dow_seal.png +0 -0
- package/quills/usaf_letter/0.1.0/example.md +35 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/.gitignore +7 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/LICENSE +201 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/CopperplateCC-Bold.otf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/CopperplateCC/LICENSE.md +79 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/LiberationMono/LICENSE +102 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/LiberationMono/LiberationMono-Regular.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/GNU General Public License.txt +340 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-Med.otf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-MedIta.otf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-Reg.otf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/NimbusRomanNo9L/NimbusRomNo9L-RegIta.otf +0 -0
- 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
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/SpectralSC-Italic.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/SpectralSC/SpectralSC-Regular.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/backmatter.typ +32 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/config.typ +45 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/frontmatter.typ +226 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/lib.typ +35 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/mainmatter.typ +140 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/primitives.typ +544 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/src/utils.typ +334 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/typst.toml +22 -0
- package/quills/usaf_letter/0.1.0/plate.typ +121 -0
- package/quills/usaf_memo/0.2.0/Quill.yaml +8 -41
- package/quills/usaf_memo/0.2.0/example.md +45 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/LICENSE +201 -21
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/typst.toml +1 -1
- package/quills/usaf_memo/0.3.0/Quill.yaml +51 -69
- package/quills/usaf_memo/0.3.0/example.md +48 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/LICENSE +201 -21
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/OFL.txt +93 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/SpectralSC-Italic.ttf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/SpectralSC/SpectralSC-Regular.ttf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/backmatter.typ +29 -17
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/body.typ +97 -126
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/config.typ +12 -45
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +81 -87
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +58 -66
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/lib.typ +14 -36
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/mainmatter.typ +47 -19
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +158 -111
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/utils.typ +41 -141
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/typst.toml +3 -8
- package/quills/usaf_memo/0.3.0/plate.typ +78 -85
- package/templates/af4141.md +23 -0
- package/templates/appointment_letter.md +58 -0
- package/templates/daf1206.md +23 -0
- package/templates/daf4392.md +34 -0
- package/templates/deros_extension.md +40 -0
- package/templates/pass_request.md +48 -0
- package/templates/rebuttal.md +63 -0
- package/templates/templates.json +65 -0
- package/templates/usaf_template.md +30 -0
- package/templates/ussf_template.md +30 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel[wght].ttf +0 -0
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
|
@@ -1,58 +1,54 @@
|
|
|
1
|
-
//
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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(
|
|
37
|
+
assert(memo-for != none, message: "memo-for is required")
|
|
40
38
|
assert(
|
|
41
|
-
|
|
42
|
-
message: "
|
|
39
|
+
memo-style in ("usaf", "daf"),
|
|
40
|
+
message: "memo-style must be \"usaf\" or \"daf\"",
|
|
43
41
|
)
|
|
44
42
|
|
|
45
|
-
let
|
|
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`. `
|
|
48
|
-
// (a `str`), but `dissemination`
|
|
49
|
-
//
|
|
50
|
-
|
|
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 =
|
|
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
|
|
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
|
|
75
|
-
|
|
76
|
-
and type(
|
|
77
|
-
and
|
|
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
|
-
//
|
|
80
|
-
//
|
|
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(
|
|
84
|
-
lines.push([Controlled By: #
|
|
78
|
+
if not falsey(cui-controlled-by) {
|
|
79
|
+
lines.push([Controlled By: #cui-controlled-by])
|
|
85
80
|
}
|
|
86
|
-
if not falsey(
|
|
87
|
-
lines.push([CUI Category: #
|
|
81
|
+
if not falsey(cui-category) {
|
|
82
|
+
lines.push([CUI Category: #cui-category])
|
|
88
83
|
}
|
|
89
|
-
if not falsey(
|
|
90
|
-
lines.push([LDC: #upper(
|
|
84
|
+
if not falsey(cui-limited-dissemination) {
|
|
85
|
+
lines.push([LDC: #upper(cui-limited-dissemination)])
|
|
91
86
|
}
|
|
92
|
-
if not falsey(
|
|
93
|
-
lines.push([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:
|
|
104
|
-
show raw: set text(font: DEFAULT_MONO_FONTS)
|
|
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
|
|
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:
|
|
127
|
+
text(12pt, font: DEFAULT_BODY_FONTS, fill: classification-color)[#strong(classification-marking)],
|
|
134
128
|
)
|
|
135
129
|
}
|
|
136
130
|
},
|
|
137
131
|
footer: {
|
|
138
|
-
if
|
|
132
|
+
if classification-marking != none {
|
|
139
133
|
place(
|
|
140
134
|
bottom + center,
|
|
141
135
|
dy: -.375in,
|
|
142
|
-
text(12pt, font: DEFAULT_BODY_FONTS, fill:
|
|
136
|
+
text(12pt, font: DEFAULT_BODY_FONTS, fill: classification-color)[#strong(classification-marking)],
|
|
143
137
|
)
|
|
144
138
|
}
|
|
145
139
|
|
|
146
|
-
if not falsey(
|
|
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: "
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
185
|
+
box(height: reserved, place(bottom + right, dy: overhang, indicator-box)),
|
|
192
186
|
)
|
|
193
187
|
}
|
|
194
188
|
}
|
|
195
189
|
|
|
196
190
|
render-letterhead(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
letterhead-seal:
|
|
201
|
-
letterhead-seal-subtitle:
|
|
202
|
-
letterhead-emblem:
|
|
203
|
-
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
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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(
|
|
227
|
-
render-for-section(
|
|
228
|
-
if not falsey(
|
|
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
|
|
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
|
-
//
|
|
4
|
-
//
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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`).
|
|
25
|
-
//
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
54
|
-
let
|
|
55
|
-
let
|
|
49
|
+
let actual-date = date
|
|
50
|
+
let ind-from = first-or-value(from)
|
|
51
|
+
let ind-for = to
|
|
56
52
|
|
|
57
|
-
// An empty body
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
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
|
|
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
|
-
//
|
|
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("
|
|
82
|
-
let
|
|
83
|
-
let
|
|
84
|
-
let
|
|
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
|
-
|
|
87
|
-
let
|
|
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
|
|
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
|
-
// `
|
|
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
|
-
#[#
|
|
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),
|
|
99
|
+
#grid(columns: (auto, 1fr), ind-from, ind-date)
|
|
107
100
|
#blank-line()
|
|
108
|
-
#grid(columns: (auto, auto, 1fr), "MEMORANDUM 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), [#
|
|
110
|
+
#grid(columns: (auto, 1fr), [#indorsement-label, #box(ind-from)], ind-date)
|
|
118
111
|
#blank-line()
|
|
119
|
-
#grid(columns: (auto, auto, 1fr), "MEMORANDUM 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
|
|
147
|
+
if effective-action != none or not body-empty {
|
|
161
148
|
blank-line()
|
|
162
149
|
}
|
|
163
150
|
}
|
|
164
151
|
|
|
165
|
-
|
|
166
|
-
if effective_action != none {
|
|
152
|
+
if effective-action != none {
|
|
167
153
|
render-action-line(
|
|
168
|
-
|
|
169
|
-
approval-authority:
|
|
170
|
-
trailing-blank-line: not
|
|
154
|
+
effective-action,
|
|
155
|
+
approval-authority: approval-authority,
|
|
156
|
+
trailing-blank-line: not body-empty,
|
|
171
157
|
)
|
|
172
158
|
}
|
|
173
159
|
|
|
174
|
-
if not
|
|
160
|
+
if not body-empty {
|
|
175
161
|
context {
|
|
176
|
-
let memo-style = query(<usaf-memo-config>).first().value.at("
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
27
|
+
// signature-block: ("FIRST M. LAST, Maj, USAF", "Duty Title"),
|
|
50
28
|
// )[
|
|
51
29
|
// Indorsement content here.
|
|
52
30
|
// ]
|
|
@@ -1,27 +1,55 @@
|
|
|
1
|
-
//
|
|
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
|
-
///
|
|
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
|
-
///
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
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
|
-
///
|
|
18
|
-
///
|
|
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
|
-
///
|
|
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) =
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
|
|
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
|
}
|