@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,16 +1,9 @@
|
|
|
1
|
-
//
|
|
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
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
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
|
-
|
|
129
|
-
let first_pass = {
|
|
130
|
-
// Collect pars with nesting level
|
|
121
|
+
let first-pass = {
|
|
131
122
|
show par: p => context {
|
|
132
|
-
let
|
|
133
|
-
let
|
|
134
|
-
let
|
|
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 (
|
|
138
|
-
let
|
|
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
|
-
|
|
144
|
-
kind: if
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
152
|
+
nest-level: -1,
|
|
162
153
|
kind: "table",
|
|
163
154
|
))
|
|
164
155
|
pars
|
|
165
156
|
})
|
|
166
157
|
t
|
|
167
158
|
}
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
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).
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
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
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
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(
|
|
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.
|
|
234
|
+
// item.nest-level — nesting depth (−1 for tables)
|
|
251
235
|
// item.kind — "par", "heading", "table", "continuation", or "quote"
|
|
252
236
|
context {
|
|
253
|
-
let
|
|
254
|
-
let
|
|
255
|
-
//
|
|
256
|
-
//
|
|
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
|
|
264
|
-
let
|
|
246
|
+
let par-count = items.filter(item => item.kind == "par").len()
|
|
247
|
+
let total-count = items.len()
|
|
265
248
|
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
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
|
|
259
|
+
let any-emitted = false
|
|
277
260
|
for item in items {
|
|
278
261
|
i += 1
|
|
279
262
|
let kind = item.kind
|
|
280
|
-
let
|
|
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 (
|
|
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
|
|
292
|
-
let
|
|
274
|
+
if heading-buffer != none {
|
|
275
|
+
let runs-in = (
|
|
293
276
|
kind not in ("heading", "quote")
|
|
294
|
-
and item.
|
|
295
|
-
and (
|
|
277
|
+
and item.nest-level == heading-level
|
|
278
|
+
and (heading-level == 0 or kind == "continuation")
|
|
296
279
|
)
|
|
297
|
-
if
|
|
298
|
-
|
|
280
|
+
if runs-in {
|
|
281
|
+
item-content = [#strong[#heading-buffer.] #item-content]
|
|
299
282
|
} else {
|
|
300
|
-
if
|
|
301
|
-
strong[#
|
|
302
|
-
|
|
283
|
+
if any-emitted { blank-line() }
|
|
284
|
+
strong[#heading-buffer.]
|
|
285
|
+
any-emitted = true
|
|
303
286
|
}
|
|
304
|
-
|
|
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
|
-
|
|
312
|
-
|
|
294
|
+
heading-buffer = item-content
|
|
295
|
+
heading-level = item.nest-level
|
|
313
296
|
continue
|
|
314
297
|
}
|
|
315
298
|
|
|
316
|
-
|
|
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
|
|
305
|
+
let final-par = {
|
|
324
306
|
if kind == "table" {
|
|
325
|
-
render-memo-table(
|
|
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
|
-
|
|
333
|
+
item-content
|
|
352
334
|
}
|
|
353
|
-
let offset = paragraph-text-offset(
|
|
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
|
|
360
|
-
|
|
341
|
+
if memo-style == "daf" and nest-level == 0 {
|
|
342
|
+
item-content
|
|
361
343
|
} else {
|
|
362
|
-
format-par(
|
|
344
|
+
format-par(item-content, nest-level, level-counts, indent-fn, continuation: true)
|
|
363
345
|
}
|
|
364
346
|
} else if memo-style == "daf" {
|
|
365
|
-
if
|
|
366
|
-
let par = format-par(
|
|
367
|
-
level-counts.insert(str(
|
|
368
|
-
level-counts = reset-levels-from(level-counts,
|
|
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)#
|
|
356
|
+
[#h(daf-paragraph.top-first-line-indent)#item-content]
|
|
375
357
|
}
|
|
376
|
-
} else if
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
level-counts
|
|
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
|
-
|
|
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
|
|
395
|
-
|
|
396
|
-
if i ==
|
|
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
|
|
409
|
-
//
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
//
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
|
|
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
|
|
446
|
-
if
|
|
447
|
-
block(sticky: true)[#strong[#
|
|
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
|
-
//
|
|
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, //
|
|
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
|
-
|
|
24
|
-
//
|
|
25
|
-
// Courier New
|
|
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
|
|
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
|
-
//
|
|
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),
|