@airmark/quiver 0.32.2 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -18
- package/package.json +8 -6
- package/quills/af4141/0.1.0/Quill.yaml +1 -1
- package/quills/afmc_moa/0.0.1/Quill.yaml +10 -17
- package/quills/classic_resume/0.0.1/Quill.yaml +361 -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 +181 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/config.typ +53 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/layout.typ +57 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/lib.typ +6 -0
- package/quills/classic_resume/0.0.1/packages/ttq-classic-resume/src/util.typ +32 -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 +320 -0
- package/quills/cyber_ribbon_chart/0.0.1/plate.typ +428 -0
- package/quills/daf1206/0.1.0/Quill.yaml +1 -2
- package/quills/daf4392/0.1.0/Quill.yaml +15 -1
- package/quills/daf4392/0.1.0/plate.typ +2 -2
- package/quills/usaf_letter/0.1.0/Quill.yaml +214 -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/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/Cinzel/Cinzel-Regular.ttf +0 -0
- package/quills/usaf_letter/0.1.0/packages/tonguetoquill-usaf-letter/fonts/Cinzel/LICENSE +93 -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/CopperplateCC-Heavy.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_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 +124 -0
- package/quills/usaf_memo/0.2.0/Quill.yaml +1 -1
- 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 +62 -43
- 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/Cinzel/Cinzel-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 +81 -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/pass_request.md +48 -0
- package/templates/rebuttal.md +63 -0
- package/templates/templates.json +58 -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
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// This module implements the visual rendering functions that produce AFH 33-337
|
|
4
|
-
// compliant formatting for all sections of a USAF memorandum. Each function
|
|
5
|
-
// corresponds to specific placement and formatting requirements from Chapter 14.
|
|
1
|
+
// One rendering function per section of the memorandum, each placing its
|
|
2
|
+
// section where AFH 33-337 Chapter 14 puts it.
|
|
6
3
|
|
|
7
4
|
#import "config.typ": *
|
|
8
5
|
#import "utils.typ": *
|
|
9
6
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// AFH 33-337 §1: "Use printed letterhead, computer-generated letterhead, or plain bond paper"
|
|
14
|
-
// Letterhead placement is not explicitly specified in AFH 33-337, but follows
|
|
15
|
-
// standard USAF memo formatting conventions
|
|
16
|
-
|
|
7
|
+
// AFH 33-337 does not specify letterhead placement; the geometry below follows
|
|
8
|
+
// standard USAF memo convention.
|
|
17
9
|
#let render-letterhead(
|
|
18
10
|
title,
|
|
19
11
|
caption,
|
|
@@ -24,8 +16,8 @@
|
|
|
24
16
|
letterhead-emblem-height: 1in, // emblem fit-box height; reduce for shorter emblems
|
|
25
17
|
) = {
|
|
26
18
|
font = ensure-array(font)
|
|
27
|
-
// `upper`
|
|
28
|
-
// survives
|
|
19
|
+
// `upper` takes content as readily as `str`, so the letterhead's uppercasing
|
|
20
|
+
// survives a title or caption handed over as markup.
|
|
29
21
|
title = upper(join-lines(title))
|
|
30
22
|
caption = upper(join-lines(caption))
|
|
31
23
|
|
|
@@ -78,8 +70,8 @@
|
|
|
78
70
|
#fit-box(width: corner-width, height: band-height)[#letterhead-seal]
|
|
79
71
|
]
|
|
80
72
|
} else {
|
|
81
|
-
// Isolate seal column from document `
|
|
82
|
-
// must not scale with body text (see frontmatter `set text(size:
|
|
73
|
+
// Isolate seal column from document `font-size`: stack `em` spacing and subtitle
|
|
74
|
+
// must not scale with body text (see frontmatter `set text(size: font-size)`).
|
|
83
75
|
// Subtitle is wrapped in `box` so it stays on one line and may extend past
|
|
84
76
|
// the seal's 2in column rather than wrapping.
|
|
85
77
|
block[
|
|
@@ -115,15 +107,6 @@
|
|
|
115
107
|
}
|
|
116
108
|
}
|
|
117
109
|
|
|
118
|
-
// =============================================================================
|
|
119
|
-
// HEADER SECTIONS
|
|
120
|
-
// =============================================================================
|
|
121
|
-
// AFH 33-337 "The Heading Section" specifies exact placement and format for:
|
|
122
|
-
// - Date: 1 inch from right edge, 1.75 inches from top
|
|
123
|
-
// - MEMORANDUM FOR: Second line below date
|
|
124
|
-
// - FROM: Second line below MEMORANDUM FOR
|
|
125
|
-
// - SUBJECT: Second line below FROM
|
|
126
|
-
|
|
127
110
|
// AFH 33-337 "Date": "Place the date 1 inch from the right edge, 1.75 inches from the top"
|
|
128
111
|
#let render-date-section(date, memo-style: "usaf") = {
|
|
129
112
|
align(right)[#display-date(date, memo-style: memo-style)]
|
|
@@ -160,12 +143,10 @@
|
|
|
160
143
|
|
|
161
144
|
/// Whether a `references` entry carries no citation text.
|
|
162
145
|
///
|
|
163
|
-
/// Blank entries reach the template routinely
|
|
164
|
-
///
|
|
165
|
-
///
|
|
166
|
-
///
|
|
167
|
-
/// satisfies the "exactly one reference" test below and renders as an empty
|
|
168
|
-
/// `()` after the subject.
|
|
146
|
+
/// Blank entries reach the template routinely — a stub entry left under
|
|
147
|
+
/// `references:` for the user to fill in. Such an entry must not count as a
|
|
148
|
+
/// reference: a lone blank one would otherwise satisfy the "exactly one
|
|
149
|
+
/// reference" test below and render as an empty `()` after the subject.
|
|
169
150
|
///
|
|
170
151
|
/// - entry (any): A single `references` element
|
|
171
152
|
/// -> bool
|
|
@@ -205,25 +186,48 @@
|
|
|
205
186
|
blank-line()
|
|
206
187
|
grid(
|
|
207
188
|
columns: (auto, auto, 1fr),
|
|
208
|
-
//
|
|
209
|
-
// lettered "(a) (b) (c)" per AFH 33-337.
|
|
189
|
+
// Spread the entries as enum items lettered "(a) (b) (c)" per AFH 33-337.
|
|
210
190
|
"References:", " ", enum(..references, numbering: "(a) ", body-indent: 0pt),
|
|
211
191
|
)
|
|
212
192
|
}
|
|
213
193
|
}
|
|
214
194
|
|
|
215
|
-
// =============================================================================
|
|
216
|
-
// SIGNATURE BLOCK
|
|
217
|
-
// =============================================================================
|
|
218
195
|
// AFH 33-337 "Signature Block": "Start the signature block on the fifth line below
|
|
219
196
|
// the last line of text and 4.5 inches from the left edge of the page or three
|
|
220
197
|
// spaces to the right of page center"
|
|
221
198
|
// AFH 33-337 "Do not place the signature element on a continuation page by itself"
|
|
222
199
|
// AFH 33-337 long-name example: "Signature block adjusted to the left" when a
|
|
223
200
|
// long name would otherwise exceed the right margin.
|
|
224
|
-
|
|
225
|
-
|
|
201
|
+
//
|
|
202
|
+
// A closing line may open the section above the block, and AFH 33-337 gives it
|
|
203
|
+
// one geometry across the two documents that have one: on the second line below
|
|
204
|
+
// the text at the block's own anchor, with the signature then five lines below
|
|
205
|
+
// the line rather than below the body. The memorandum's occupant is the
|
|
206
|
+
// authority line, "FOR THE COMMANDER"; the personal letter's is the
|
|
207
|
+
// complimentary close, "Sincerely", which the handbook bars the authority line
|
|
208
|
+
// from. Hence the neutral parameter, and hence case belongs to the caller — the
|
|
209
|
+
// authority line is uppercased, "Sincerely" is not.
|
|
210
|
+
|
|
211
|
+
/// The memorandum's occupant of that slot, uppercased per AFH 33-337. Blank is
|
|
212
|
+
/// no line: the handbook forbids one where the commander signs.
|
|
213
|
+
///
|
|
214
|
+
/// - value (str | content | none): The authority line as authored
|
|
215
|
+
/// -> content | none
|
|
216
|
+
#let format-authority-line(value) = if falsey(value) { none } else { upper(value) }
|
|
217
|
+
|
|
218
|
+
#let render-signature-block(
|
|
219
|
+
signature-lines,
|
|
220
|
+
closing-line: none,
|
|
221
|
+
signature-blank-lines: 4,
|
|
222
|
+
signing-field: none,
|
|
223
|
+
// Lines of breaking height reserved below the block for the backmatter
|
|
224
|
+
// lead-in and its continuation note. Reclaimed immediately after, so it moves
|
|
225
|
+
// where the block may break and nothing else.
|
|
226
|
+
reserved-lines: 0,
|
|
227
|
+
) = {
|
|
226
228
|
signature-lines = ensure-array(signature-lines)
|
|
229
|
+
// Blank is no line, whichever occupant the caller passes.
|
|
230
|
+
if falsey(closing-line) { closing-line = none }
|
|
227
231
|
// AFH 33-337 allows two equivalent anchors: 4.5in from the left edge, or three
|
|
228
232
|
// spaces right of page center. On 8.5in stock these coincide (page center =
|
|
229
233
|
// 4.25in; three TNR-12pt spaces ≈ 0.25in), so we use 4.5in as the canonical
|
|
@@ -231,19 +235,17 @@
|
|
|
231
235
|
let default-pad = 4.5in - spacing.margin
|
|
232
236
|
context {
|
|
233
237
|
// Measure each line at its rendered settings to detect long-name overflow.
|
|
238
|
+
// The closing line shares the anchor, so it joins the measurement: the
|
|
239
|
+
// wider of the two decides the shift and they stay aligned.
|
|
234
240
|
let body-width = page.width - 2 * spacing.margin
|
|
241
|
+
let anchored-lines = signature-lines
|
|
242
|
+
if closing-line != none { anchored-lines.push(closing-line) }
|
|
235
243
|
let widest = 0pt
|
|
236
|
-
for line in
|
|
244
|
+
for line in anchored-lines {
|
|
237
245
|
let w = measure(text(hyphenate: false, line)).width
|
|
238
246
|
if w > widest { widest = w }
|
|
239
247
|
}
|
|
240
|
-
let stride =
|
|
241
|
-
let s = LINE_STRIDE.get()
|
|
242
|
-
if s == none {
|
|
243
|
-
let one-line = measure(par(spacing: 0pt)[x]).height
|
|
244
|
-
measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line
|
|
245
|
-
} else { s }
|
|
246
|
-
}
|
|
248
|
+
let stride = line-stride()
|
|
247
249
|
// If the widest line would overflow the right margin at the standard
|
|
248
250
|
// anchor, shift the block left just enough to fit. Clamp at 0 so the
|
|
249
251
|
// block never crosses the left margin.
|
|
@@ -262,8 +264,15 @@
|
|
|
262
264
|
// and the block one indivisible unit. The total space above the block is
|
|
263
265
|
// identical either way — `block.above` still contributes the same 0.5em —
|
|
264
266
|
// but a gap left outside can be consumed at the foot of one page while
|
|
265
|
-
// the block starts at the top margin of the next
|
|
266
|
-
//
|
|
267
|
+
// the block starts at the top margin of the next.
|
|
268
|
+
//
|
|
269
|
+
// The closing line takes one of those lines above it and the gap then
|
|
270
|
+
// measures from the line, not from the text. Inside the block, so no page
|
|
271
|
+
// break can put it on a different page from the signature.
|
|
272
|
+
#if closing-line != none {
|
|
273
|
+
v(stride)
|
|
274
|
+
pad(left: left-pad, text(hyphenate: false, closing-line))
|
|
275
|
+
}
|
|
267
276
|
#if signing-field != none {
|
|
268
277
|
// The signing field covers those blank lines — where a signature is
|
|
269
278
|
// actually written — so it is placed over the gap. It is placed BEFORE
|
|
@@ -272,7 +281,8 @@
|
|
|
272
281
|
// paints it down over the printed signature block. Anchored here it
|
|
273
282
|
// still travels with the block onto whatever page the block lands on,
|
|
274
283
|
// since the gap is inside the unbreakable block rather than ahead of
|
|
275
|
-
// it.
|
|
284
|
+
// it. The current position is below the closing line when there is
|
|
285
|
+
// one, which is where the signature is written.
|
|
276
286
|
//
|
|
277
287
|
// The widget keeps its own size (the helper's default is 50pt tall and
|
|
278
288
|
// it positions itself, so an `align` around it does nothing) and is
|
|
@@ -299,31 +309,26 @@
|
|
|
299
309
|
// of the line above" — 2-character indent ≈ 1em in Times New Roman 12pt
|
|
300
310
|
par(hanging-indent: .5em, line)
|
|
301
311
|
}
|
|
312
|
+
// The page the backmatter compares its own against. Must stay
|
|
313
|
+
// inside the unbreakable block: a marker outside one travels with
|
|
314
|
+
// the section that moves.
|
|
315
|
+
#metadata(none)<usaf-memo-flow-anchor>
|
|
316
|
+
#v(stride * reserved-lines)
|
|
302
317
|
]
|
|
303
318
|
]
|
|
304
319
|
]
|
|
305
320
|
]
|
|
321
|
+
// The reservation counted toward breaking height only; the backmatter
|
|
322
|
+
// emits its own lead-in below.
|
|
323
|
+
v(stride * -reserved-lines)
|
|
306
324
|
}
|
|
307
325
|
}
|
|
308
326
|
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
// Two option pairs are supported, reflecting the two roles an indorsement
|
|
316
|
-
// plays in a coordination chain: coordinating officials Concur / Nonconcur,
|
|
317
|
-
// while the final approval authority Approves / Disapproves.
|
|
318
|
-
//
|
|
319
|
-
// Which pair prints is not the endorser's to choose — it follows from the
|
|
320
|
-
// role, so the caller supplies the role via `approval-authority` and the same
|
|
321
|
-
// three `action` values (affirm, reject, or leave open) carry over both pairs.
|
|
322
|
-
//
|
|
323
|
-
// The "undecided" form renders both options plain, for printing a memo the
|
|
324
|
-
// endorser marks by hand when signing.
|
|
325
|
-
//
|
|
326
|
-
// Empty/none suppression is handled by the caller before this is invoked.
|
|
327
|
+
// The indorsement decision line is an "either / or" pair. Which pair prints
|
|
328
|
+
// follows from the indorsement's role in the coordination chain rather than
|
|
329
|
+
// from the endorser's choice, so the caller supplies the role and the same
|
|
330
|
+
// three `action` values carry over both pairs. The caller also suppresses the
|
|
331
|
+
// line entirely; reaching here means one is wanted.
|
|
327
332
|
|
|
328
333
|
// The option pair for each role, ordered (affirmative, negative).
|
|
329
334
|
#let APPROVAL_OPTIONS = ("Approve", "Disapprove")
|
|
@@ -378,24 +383,16 @@
|
|
|
378
383
|
}
|
|
379
384
|
}
|
|
380
385
|
|
|
381
|
-
|
|
382
|
-
// TABLE RENDERING
|
|
383
|
-
// =============================================================================
|
|
384
|
-
// AFH 33-337 does not specify table formatting, so we follow the general
|
|
385
|
-
// aesthetic principles of the standard: plain black borders, no decorative
|
|
386
|
-
// fills, and the body font inherited throughout.
|
|
387
|
-
|
|
388
|
-
/// Renders a table with USAF memorandum–consistent formatting.
|
|
386
|
+
/// Renders a table in the memorandum's style.
|
|
389
387
|
///
|
|
390
|
-
///
|
|
391
|
-
///
|
|
392
|
-
///
|
|
388
|
+
/// AFH 33-337 does not specify table formatting, so this follows the general
|
|
389
|
+
/// aesthetic of the standard: plain black borders, a bold header row, no
|
|
390
|
+
/// decorative fills, and the body font and size inherited from the surrounding
|
|
391
|
+
/// text.
|
|
393
392
|
///
|
|
394
393
|
/// - it (content): The table element to style and render
|
|
395
394
|
/// -> content
|
|
396
395
|
#let render-memo-table(it) = {
|
|
397
|
-
// AFH 33-337 does not specify table formatting, so we follow the general
|
|
398
|
-
// aesthetic principles of the standard: bold headers for clarity.
|
|
399
396
|
show table.cell.where(y: 0): set text(weight: "bold")
|
|
400
397
|
set table(
|
|
401
398
|
stroke: 0.5pt + black,
|
|
@@ -404,23 +401,18 @@
|
|
|
404
401
|
it
|
|
405
402
|
}
|
|
406
403
|
|
|
407
|
-
// =============================================================================
|
|
408
|
-
// BACKMATTER SECTIONS
|
|
409
|
-
// =============================================================================
|
|
410
|
-
// AFH 33-337 "Attachment or Attachments": "Place 'Attachment:' (for a single attachment)
|
|
411
|
-
// or '# Attachments:' (for two or more attachments) at the left margin, on the third
|
|
412
|
-
// line below the signature element"
|
|
413
|
-
// AFH 33-337 "Courtesy Copy Element": "place 'cc:' flush with the left margin, on the
|
|
414
|
-
// second line below the attachment element"
|
|
415
|
-
|
|
416
404
|
#let render-backmatter-section(
|
|
417
405
|
content,
|
|
418
406
|
section-label,
|
|
419
407
|
numbering-style: none,
|
|
420
408
|
continuation-label: none,
|
|
409
|
+
// Lines of breaking height reserved below this section for the lead-in and
|
|
410
|
+
// continuation note of the section that follows it. Reclaimed immediately
|
|
411
|
+
// after, so it moves where a break may fall and nothing else.
|
|
412
|
+
reserved-lines: 0,
|
|
421
413
|
) = {
|
|
422
414
|
let formatted-content = {
|
|
423
|
-
//
|
|
415
|
+
// `text()` keeps the label from being laid out as a paragraph of its own.
|
|
424
416
|
text()[#section-label]
|
|
425
417
|
linebreak()
|
|
426
418
|
if numbering-style != none {
|
|
@@ -431,30 +423,66 @@
|
|
|
431
423
|
}
|
|
432
424
|
}
|
|
433
425
|
|
|
426
|
+
// Attachments pass continuation-label ("… (listed on next page):" per AFH 33-337).
|
|
427
|
+
// cc: and DISTRIBUTION: use a neutral default — "listed" applies to attachment lists only.
|
|
428
|
+
let continuation-text = if continuation-label != none {
|
|
429
|
+
text()[#continuation-label]
|
|
430
|
+
} else {
|
|
431
|
+
text()[#(section-label + " (continued on next page)")]
|
|
432
|
+
}
|
|
433
|
+
|
|
434
434
|
context {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
435
|
+
// `breakable: false` lets Typst's own breaker move the section to the next
|
|
436
|
+
// page as a unit; an explicit pagebreak here would feed this context's
|
|
437
|
+
// layout query back into its own input and the two would chase each other
|
|
438
|
+
// until layout gave up.
|
|
439
|
+
//
|
|
440
|
+
// The note is decided by observing where that breaker put the block. The
|
|
441
|
+
// nearest anchor above is the signature block or the preceding section — an
|
|
442
|
+
// element with real height that stays on the page this section departs.
|
|
443
|
+
//
|
|
444
|
+
// Do not reduce this to a `here()` reading. `here()` sits in the section and
|
|
445
|
+
// travels with it, so once the section has moved it reports the top of the
|
|
446
|
+
// page it moved to and every section measures as fitting. The room the note
|
|
447
|
+
// needs is reserved inside the anchor's own unbreakable block, so the
|
|
448
|
+
// decision can never move its own input.
|
|
449
|
+
let above = query(selector(<usaf-memo-flow-anchor>).before(here()))
|
|
450
|
+
let below = query(selector(<usaf-memo-flow-anchor>).after(here()))
|
|
451
|
+
if above.len() > 0 and below.len() > 0 {
|
|
452
|
+
if below.first().location().page() > above.last().location().page() {
|
|
453
|
+
continuation-text
|
|
443
454
|
}
|
|
444
|
-
continuation-text
|
|
445
|
-
pagebreak()
|
|
446
455
|
}
|
|
447
|
-
|
|
456
|
+
// A section taller than a page cannot move as a unit, so it splits where
|
|
457
|
+
// the page ends rather than overflowing it, and anchors its last page too:
|
|
458
|
+
// the section below measures from where this one ends.
|
|
459
|
+
let reserve = line-stride() * reserved-lines
|
|
460
|
+
layout(region => {
|
|
461
|
+
let oversized = measure(formatted-content, width: region.width).height + reserve > region.height
|
|
462
|
+
block(breakable: oversized)[
|
|
463
|
+
#metadata(none)<usaf-memo-flow-anchor>
|
|
464
|
+
#formatted-content
|
|
465
|
+
#if oversized [#metadata(none)<usaf-memo-flow-anchor>]
|
|
466
|
+
#v(reserve)
|
|
467
|
+
]
|
|
468
|
+
})
|
|
469
|
+
v(-reserve)
|
|
448
470
|
}
|
|
449
471
|
}
|
|
450
472
|
|
|
451
473
|
#let calculate-backmatter-spacing(is-first-section) = {
|
|
452
474
|
context {
|
|
453
|
-
let
|
|
454
|
-
blank-lines(
|
|
475
|
+
let line-count = if is-first-section { 2 } else { 1 }
|
|
476
|
+
blank-lines(line-count)
|
|
455
477
|
}
|
|
456
478
|
}
|
|
457
479
|
|
|
480
|
+
// AFH 33-337 "Attachment or Attachments": "Place 'Attachment:' (for a single attachment)
|
|
481
|
+
// or '# Attachments:' (for two or more attachments) at the left margin, on the third
|
|
482
|
+
// line below the signature element"
|
|
483
|
+
// AFH 33-337 "Courtesy Copy Element": "place 'cc:' flush with the left margin, on the
|
|
484
|
+
// second line below the attachment element"
|
|
485
|
+
|
|
458
486
|
#let render-backmatter-sections(
|
|
459
487
|
attachments: none,
|
|
460
488
|
cc: none,
|
|
@@ -471,27 +499,46 @@
|
|
|
471
499
|
pagebreak(weak: true)
|
|
472
500
|
}
|
|
473
501
|
|
|
502
|
+
// Collected first because a section's reservation depends on whether another
|
|
503
|
+
// follows it: with nothing below, no note can be owed.
|
|
504
|
+
let sections = ()
|
|
505
|
+
|
|
474
506
|
if attachments != none and attachments.len() > 0 {
|
|
475
|
-
calculate-backmatter-spacing(true)
|
|
476
507
|
let attachment-count = attachments.len()
|
|
477
508
|
let section-label = if attachment-count == 1 { "Attachment:" } else { str(attachment-count) + " Attachments:" }
|
|
478
509
|
let continuation-label = (
|
|
479
510
|
(if attachment-count == 1 { "Attachment" } else { str(attachment-count) + " Attachments" })
|
|
480
511
|
+ " (listed on next page):"
|
|
481
512
|
)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
513
|
+
sections.push((
|
|
514
|
+
content: attachments,
|
|
515
|
+
label: section-label,
|
|
516
|
+
// AFH 33-337: a single attachment is not numbered; numbering applies to two or more.
|
|
517
|
+
numbering-style: if attachment-count == 1 { none } else { "1." },
|
|
518
|
+
continuation-label: continuation-label,
|
|
519
|
+
))
|
|
485
520
|
}
|
|
486
521
|
|
|
487
522
|
if cc != none and cc.len() > 0 {
|
|
488
|
-
|
|
489
|
-
render-backmatter-section(cc, "cc:")
|
|
523
|
+
sections.push((content: cc, label: "cc:", numbering-style: none, continuation-label: none))
|
|
490
524
|
}
|
|
491
525
|
|
|
492
526
|
if distribution != none and distribution.len() > 0 {
|
|
493
|
-
|
|
494
|
-
|
|
527
|
+
sections.push((content: distribution, label: "DISTRIBUTION:", numbering-style: none, continuation-label: none))
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
for (index, section) in sections.enumerate() {
|
|
531
|
+
calculate-backmatter-spacing(index == 0)
|
|
532
|
+
// The next section's one blank line of lead-in, plus one for its note. The
|
|
533
|
+
// signature block reserves 3: the first section's lead-in is two lines.
|
|
534
|
+
let reserved-lines = if index + 1 < sections.len() { 2 } else { 0 }
|
|
535
|
+
render-backmatter-section(
|
|
536
|
+
section.content,
|
|
537
|
+
section.label,
|
|
538
|
+
numbering-style: section.numbering-style,
|
|
539
|
+
continuation-label: section.continuation-label,
|
|
540
|
+
reserved-lines: reserved-lines,
|
|
541
|
+
)
|
|
495
542
|
}
|
|
496
543
|
}
|
|
497
544
|
|