@airmark/quiver 0.1.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/Quiver.yaml +2 -0
- package/README.md +92 -0
- package/index.d.ts +16 -0
- package/index.js +28 -0
- package/package.json +44 -0
- package/quills/af4141/0.1.0/Quill.yaml +95 -0
- package/quills/af4141/0.1.0/assets/Figtree-Bold.ttf +0 -0
- package/quills/af4141/0.1.0/assets/Figtree-Italic.ttf +0 -0
- package/quills/af4141/0.1.0/assets/Figtree-Regular.ttf +0 -0
- package/quills/af4141/0.1.0/assets/GNU General Public License.txt +340 -0
- package/quills/af4141/0.1.0/assets/NimbusRomNo9L-Med.otf +0 -0
- package/quills/af4141/0.1.0/assets/NimbusRomNo9L-MedIta.otf +0 -0
- package/quills/af4141/0.1.0/assets/NimbusRomNo9L-Reg.otf +0 -0
- package/quills/af4141/0.1.0/assets/NimbusRomNo9L-RegIta.otf +0 -0
- package/quills/af4141/0.1.0/design/TASK.md +20 -0
- package/quills/af4141/0.1.0/design/af4141.pdf +0 -0
- package/quills/af4141/0.1.0/example.md +86 -0
- package/quills/af4141/0.1.0/packages/typst-af4141/FIELDS.json +3170 -0
- package/quills/af4141/0.1.0/packages/typst-af4141/form.typ +538 -0
- package/quills/af4141/0.1.0/packages/typst-af4141/lib.typ +270 -0
- package/quills/af4141/0.1.0/packages/typst-af4141/page1.png +0 -0
- package/quills/af4141/0.1.0/packages/typst-af4141/page2.png +0 -0
- package/quills/af4141/0.1.0/packages/typst-af4141/typst.toml +7 -0
- package/quills/af4141/0.1.0/plate.typ +67 -0
- package/quills/daf1206/0.1.0/DAFForm_1206_prepared.pdf +0 -0
- package/quills/daf1206/0.1.0/Quill.yaml +55 -0
- package/quills/daf1206/0.1.0/assets/arimo-v35-latin-700.ttf +0 -0
- package/quills/daf1206/0.1.0/assets/arimo-v35-latin-700italic.ttf +0 -0
- package/quills/daf1206/0.1.0/assets/arimo-v35-latin-italic.ttf +0 -0
- package/quills/daf1206/0.1.0/assets/arimo-v35-latin-regular.ttf +0 -0
- package/quills/daf1206/0.1.0/example.md +23 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/FIELDS.json +158 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/form.typ +36 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/lib.typ +297 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/page1.svg +826 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/page2.svg +497 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/typst.toml +7 -0
- package/quills/daf1206/0.1.0/plate.typ +47 -0
- package/quills/daf4392/0.1.0/Quill.yaml +139 -0
- package/quills/daf4392/0.1.0/assets/arimo-v35-latin-700.ttf +0 -0
- package/quills/daf4392/0.1.0/assets/arimo-v35-latin-700italic.ttf +0 -0
- package/quills/daf4392/0.1.0/assets/arimo-v35-latin-italic.ttf +0 -0
- package/quills/daf4392/0.1.0/assets/arimo-v35-latin-regular.ttf +0 -0
- package/quills/daf4392/0.1.0/assets/page1.png +0 -0
- package/quills/daf4392/0.1.0/example.md +34 -0
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/FIELDS.json +9 -0
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/form.typ +14 -0
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/lib.typ +227 -0
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/typst.toml +7 -0
- package/quills/daf4392/0.1.0/plate.typ +86 -0
- package/quills/usaf_memo/0.2.0/.quillignore +31 -0
- package/quills/usaf_memo/0.2.0/Quill.yaml +255 -0
- package/quills/usaf_memo/0.2.0/assets/dod_seal.png +0 -0
- package/quills/usaf_memo/0.2.0/assets/dow_seal.png +0 -0
- package/quills/usaf_memo/0.2.0/example.md +56 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/.gitignore +7 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/LICENSE +21 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/Cinzel-Regular.ttf +0 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/Cinzel/LICENSE +93 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/CopperplateCC-Heavy.otf +0 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/CopperplateCC/LICENSE.md +79 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/GNU General Public License.txt +340 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-Med.otf +0 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-MedIta.otf +0 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-Reg.otf +0 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/fonts/NimbusRomanNo9L/NimbusRomNo9L-RegIta.otf +0 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/backmatter.typ +28 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/body.typ +344 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/config.typ +73 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +137 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +111 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/lib.typ +57 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/mainmatter.typ +32 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +321 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/utils.typ +356 -0
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/typst.toml +27 -0
- package/quills/usaf_memo/0.2.0/plate.typ +85 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// lib.typ: Public API for USAF memorandum template
|
|
2
|
+
//
|
|
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.
|
|
6
|
+
//
|
|
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
|
|
26
|
+
//
|
|
27
|
+
// #show: frontmatter.with(
|
|
28
|
+
// subject: "Your Subject Here",
|
|
29
|
+
// memo_for: ("OFFICE/SYMBOL",),
|
|
30
|
+
// memo_from: ("YOUR/SYMBOL",),
|
|
31
|
+
// memo_style: "usaf", // "usaf" (default) or "daf"
|
|
32
|
+
// )
|
|
33
|
+
//
|
|
34
|
+
// #show: mainmatter
|
|
35
|
+
//
|
|
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)
|
|
39
|
+
//
|
|
40
|
+
// #backmatter(
|
|
41
|
+
// signature_block: ("NAME, Rank, USAF", "Title"),
|
|
42
|
+
// attachments: (...),
|
|
43
|
+
// cc: (...),
|
|
44
|
+
// )
|
|
45
|
+
//
|
|
46
|
+
// #indorsement(
|
|
47
|
+
// from: "ORG/SYMBOL",
|
|
48
|
+
// to: "RECIPIENT/SYMBOL",
|
|
49
|
+
// signature_block: ("NAME, Rank, USAF", "Title"),
|
|
50
|
+
// )[
|
|
51
|
+
// Indorsement content here.
|
|
52
|
+
// ]
|
|
53
|
+
|
|
54
|
+
#import "frontmatter.typ": frontmatter
|
|
55
|
+
#import "mainmatter.typ": mainmatter
|
|
56
|
+
#import "backmatter.typ": backmatter
|
|
57
|
+
#import "indorsement.typ": indorsement
|
|
@@ -0,0 +1,32 @@
|
|
|
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).
|
|
5
|
+
|
|
6
|
+
#import "body.typ": *
|
|
7
|
+
|
|
8
|
+
/// Mainmatter show rule for USAF memorandum body content.
|
|
9
|
+
///
|
|
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
|
|
16
|
+
///
|
|
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.
|
|
20
|
+
///
|
|
21
|
+
/// When `auto_numbering` is false (set in frontmatter), base-level paragraphs
|
|
22
|
+
/// render flush left without numbering. Only explicitly numbered or bulleted
|
|
23
|
+
/// items enter the numbering hierarchy.
|
|
24
|
+
///
|
|
25
|
+
/// - content (content): The body content to render
|
|
26
|
+
/// -> content
|
|
27
|
+
#let mainmatter(it) = context {
|
|
28
|
+
let config = query(metadata).last().value
|
|
29
|
+
let auto-numbering = config.at("auto_numbering", default: true)
|
|
30
|
+
let memo-style = config.at("memo_style", default: "usaf")
|
|
31
|
+
render-body(it, auto-numbering: auto-numbering, memo-style: memo-style)
|
|
32
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// primitives.typ: Reusable rendering primitives for USAF memorandum sections
|
|
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.
|
|
6
|
+
|
|
7
|
+
#import "config.typ": *
|
|
8
|
+
#import "utils.typ": *
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// LETTERHEAD RENDERING
|
|
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
|
+
|
|
17
|
+
#let render-letterhead(
|
|
18
|
+
title,
|
|
19
|
+
caption,
|
|
20
|
+
font,
|
|
21
|
+
letterhead-seal: none,
|
|
22
|
+
letterhead-seal-subtitle: none,
|
|
23
|
+
) = {
|
|
24
|
+
font = ensure-array(font)
|
|
25
|
+
title = ensure-string(title)
|
|
26
|
+
caption = ensure-string(caption)
|
|
27
|
+
title = upper(title)
|
|
28
|
+
caption = upper(caption)
|
|
29
|
+
|
|
30
|
+
place(
|
|
31
|
+
dy: 0.625in - spacing.margin,
|
|
32
|
+
box(
|
|
33
|
+
width: 100%,
|
|
34
|
+
fill: none,
|
|
35
|
+
stroke: none,
|
|
36
|
+
[
|
|
37
|
+
#place(
|
|
38
|
+
center + top,
|
|
39
|
+
align(center)[
|
|
40
|
+
#set text(12pt, font: font, fill: LETTERHEAD_COLOR, weight: "bold")
|
|
41
|
+
#title\
|
|
42
|
+
#text(10.5pt)[#caption]
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
],
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if letterhead-seal != none {
|
|
50
|
+
let seal-body = if falsey(letterhead-seal-subtitle) {
|
|
51
|
+
block[
|
|
52
|
+
#fit-box(width: 2in, height: 1in)[#letterhead-seal]
|
|
53
|
+
]
|
|
54
|
+
} else {
|
|
55
|
+
// Isolate seal column from document `font_size`: stack `em` spacing and subtitle
|
|
56
|
+
// must not scale with body text (see frontmatter `set text(size: font_size)`).
|
|
57
|
+
block(width: 2in)[
|
|
58
|
+
#set text(9pt, font: font, fill: LETTERHEAD_COLOR, weight: "bold")
|
|
59
|
+
#align(left)[
|
|
60
|
+
// Spacing applies between positional stack children only, not one `[…]` body.
|
|
61
|
+
#stack(
|
|
62
|
+
spacing: 0.5em,
|
|
63
|
+
fit-box(width: 2in, height: 1in)[#letterhead-seal],
|
|
64
|
+
upper(ensure-string(letterhead-seal-subtitle)),
|
|
65
|
+
)
|
|
66
|
+
]
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
place(
|
|
70
|
+
left + top,
|
|
71
|
+
dx: -0.5in,
|
|
72
|
+
dy: -.5in,
|
|
73
|
+
seal-body,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// HEADER SECTIONS
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// AFH 33-337 "The Heading Section" specifies exact placement and format for:
|
|
82
|
+
// - Date: 1 inch from right edge, 1.75 inches from top
|
|
83
|
+
// - MEMORANDUM FOR: Second line below date
|
|
84
|
+
// - FROM: Second line below MEMORANDUM FOR
|
|
85
|
+
// - SUBJECT: Second line below FROM
|
|
86
|
+
|
|
87
|
+
// AFH 33-337 "Date": "Place the date 1 inch from the right edge, 1.75 inches from the top"
|
|
88
|
+
#let render-date-section(date, memo-style: "usaf") = {
|
|
89
|
+
align(right)[#display-date(date, memo-style: memo-style)]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// AFH 33-337 "MEMORANDUM FOR": "Place 'MEMORANDUM FOR' on the second line below the date"
|
|
93
|
+
#let render-for-section(recipients, cols) = {
|
|
94
|
+
blank-line()
|
|
95
|
+
grid(
|
|
96
|
+
columns: (auto, auto, 1fr),
|
|
97
|
+
"MEMORANDUM FOR",
|
|
98
|
+
" ",
|
|
99
|
+
align(left)[
|
|
100
|
+
#if type(recipients) == array {
|
|
101
|
+
create-auto-grid(recipients, column-gutter: spacing.tab, cols: cols)
|
|
102
|
+
} else {
|
|
103
|
+
recipients
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// AFH 33-337 "FROM:": "Place 'FROM:' in uppercase, flush with the left margin,
|
|
110
|
+
// on the second line below the last line of the MEMORANDUM FOR element"
|
|
111
|
+
#let render-from-section(from-info) = {
|
|
112
|
+
blank-line()
|
|
113
|
+
from-info = ensure-string(from-info)
|
|
114
|
+
|
|
115
|
+
grid(
|
|
116
|
+
columns: (auto, auto, 1fr),
|
|
117
|
+
"FROM:", " ", align(left)[#from-info],
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// AFH 33-337 "SUBJECT:": "In all uppercase letters place 'SUBJECT:', flush with the
|
|
122
|
+
// left margin, on the second line below the last line of the FROM element"
|
|
123
|
+
#let render-subject-section(subject-text) = {
|
|
124
|
+
blank-line()
|
|
125
|
+
grid(
|
|
126
|
+
columns: (auto, auto, 1fr),
|
|
127
|
+
"SUBJECT:", " ", [#subject-text],
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#let render-references-section(references) = {
|
|
132
|
+
if not falsey(references) {
|
|
133
|
+
blank-line()
|
|
134
|
+
grid(
|
|
135
|
+
columns: (auto, auto, 1fr),
|
|
136
|
+
"References:", " ", enum(..references, numbering: "(a) ", body-indent: 0pt),
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// SIGNATURE BLOCK
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// AFH 33-337 "Signature Block": "Start the signature block on the fifth line below
|
|
145
|
+
// the last line of text and 4.5 inches from the left edge of the page"
|
|
146
|
+
// AFH 33-337 "Do not place the signature element on a continuation page by itself"
|
|
147
|
+
|
|
148
|
+
#let render-signature-block(signature-lines, signature-blank-lines: 4) = {
|
|
149
|
+
signature-lines = ensure-array(signature-lines)
|
|
150
|
+
// AFH 33-337: "The signature block is never on a page by itself"
|
|
151
|
+
// Note: Perfect enforcement isn't feasible without over-engineering
|
|
152
|
+
// We use weak: false spacing and breakable: false to discourage orphaning
|
|
153
|
+
// AFH 33-337: "fifth line below" = 4 blank lines between text and signature block
|
|
154
|
+
blank-lines(signature-blank-lines, weak: false)
|
|
155
|
+
block(breakable: false)[
|
|
156
|
+
#align(left)[
|
|
157
|
+
// AFH 33-337: "4.5 inches from the left edge of the page"
|
|
158
|
+
// We use (4.5in - margin) because Typst's pad() is relative to the text area, not page edge
|
|
159
|
+
#pad(left: 4.5in - spacing.margin)[
|
|
160
|
+
#text(hyphenate: false)[
|
|
161
|
+
#for line in signature-lines {
|
|
162
|
+
par(hanging-indent: 4 * 0.5em, line)
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
]
|
|
166
|
+
]
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// ACTION LINE RENDERING
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// Renders the Approve / Disapprove action line for indorsement memos.
|
|
174
|
+
// action: "none" = no action line displayed (hidden), "undecided" = both options
|
|
175
|
+
// rendered plain (no circle), "approve" = Approve circled,
|
|
176
|
+
// "disapprove" = Disapprove circled. The action line is rendered when
|
|
177
|
+
// action is "undecided", "approve", or "disapprove".
|
|
178
|
+
|
|
179
|
+
#let render-action-line(action) = {
|
|
180
|
+
assert(
|
|
181
|
+
action in ("none", "undecided", "approve", "disapprove"),
|
|
182
|
+
message: "action must be \"none\", \"undecided\", \"approve\", or \"disapprove\"",
|
|
183
|
+
)
|
|
184
|
+
blank-line()
|
|
185
|
+
// Circle the selected option using a box with rounded corners
|
|
186
|
+
// Use baseline parameter to maintain vertical text alignment
|
|
187
|
+
let approve-text = if action == "approve" {
|
|
188
|
+
box(stroke: 0.5pt + black, radius: 2pt, inset: 2pt, baseline: 2pt)[Approve]
|
|
189
|
+
} else if action == "disapprove" {
|
|
190
|
+
strike[Approve]
|
|
191
|
+
} else {
|
|
192
|
+
[Approve]
|
|
193
|
+
}
|
|
194
|
+
let disapprove-text = if action == "disapprove" {
|
|
195
|
+
box(stroke: 0.5pt + black, radius: 2pt, inset: 2pt, baseline: 2pt)[Disapprove]
|
|
196
|
+
} else if action == "approve" {
|
|
197
|
+
strike[Disapprove]
|
|
198
|
+
} else {
|
|
199
|
+
[Disapprove]
|
|
200
|
+
}
|
|
201
|
+
// Keep the action line with the following content (body or signature block)
|
|
202
|
+
// using the same sticky-block pattern that body.typ applies to the last
|
|
203
|
+
// paragraph, per AFH 33-337 §11 orphan-prevention rules.
|
|
204
|
+
block(sticky: true)[#approve-text / #disapprove-text]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// =============================================================================
|
|
208
|
+
// TABLE RENDERING
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// AFH 33-337 does not specify table formatting, so we follow the general
|
|
211
|
+
// aesthetic principles of the standard: plain black borders, no decorative
|
|
212
|
+
// fills, and the body font inherited throughout.
|
|
213
|
+
|
|
214
|
+
/// Renders a table with USAF memorandum–consistent formatting.
|
|
215
|
+
///
|
|
216
|
+
/// Applies simple 0.5pt black cell borders and standard padding to any
|
|
217
|
+
/// Typst `table` element, keeping the visual style clean and formal.
|
|
218
|
+
/// Font and size are inherited from the surrounding body text.
|
|
219
|
+
///
|
|
220
|
+
/// - it (content): The table element to style and render
|
|
221
|
+
/// -> content
|
|
222
|
+
#let render-memo-table(it) = {
|
|
223
|
+
// AFH 33-337 does not specify table formatting, so we follow the general
|
|
224
|
+
// aesthetic principles of the standard: bold headers for clarity.
|
|
225
|
+
show table.cell.where(y: 0): set text(weight: "bold")
|
|
226
|
+
set table(
|
|
227
|
+
stroke: 0.5pt + black,
|
|
228
|
+
inset: (x: 0.5em, y: 0.4em),
|
|
229
|
+
)
|
|
230
|
+
it
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// =============================================================================
|
|
234
|
+
// BACKMATTER SECTIONS
|
|
235
|
+
// =============================================================================
|
|
236
|
+
// AFH 33-337 "Attachment or Attachments": "Place 'Attachment:' (for a single attachment)
|
|
237
|
+
// or '# Attachments:' (for two or more attachments) at the left margin, on the third
|
|
238
|
+
// line below the signature element"
|
|
239
|
+
// AFH 33-337 "Courtesy Copy Element": "place 'cc:' flush with the left margin, on the
|
|
240
|
+
// second line below the attachment element"
|
|
241
|
+
|
|
242
|
+
#let render-backmatter-section(
|
|
243
|
+
content,
|
|
244
|
+
section-label,
|
|
245
|
+
numbering-style: none,
|
|
246
|
+
continuation-label: none,
|
|
247
|
+
) = {
|
|
248
|
+
let formatted-content = {
|
|
249
|
+
// Use text() wrapper to prevent section label from being treated as a paragraph
|
|
250
|
+
text()[#section-label]
|
|
251
|
+
linebreak()
|
|
252
|
+
if numbering-style != none {
|
|
253
|
+
let items = ensure-array(content)
|
|
254
|
+
enum(..items, numbering: numbering-style)
|
|
255
|
+
} else {
|
|
256
|
+
ensure-string(content)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
context {
|
|
261
|
+
let available-space = page.height - here().position().y - 1in
|
|
262
|
+
if measure(formatted-content).height > available-space {
|
|
263
|
+
// Attachments pass continuation-label ("… (listed on next page):" per AFH 33-337).
|
|
264
|
+
// cc: and DISTRIBUTION: use a neutral default — "listed" applies to attachment lists only.
|
|
265
|
+
let continuation-text = if continuation-label != none {
|
|
266
|
+
text()[#continuation-label]
|
|
267
|
+
} else {
|
|
268
|
+
text()[#(section-label + " (continued on next page)")]
|
|
269
|
+
}
|
|
270
|
+
continuation-text
|
|
271
|
+
pagebreak()
|
|
272
|
+
}
|
|
273
|
+
formatted-content
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#let calculate-backmatter-spacing(is-first-section) = {
|
|
278
|
+
context {
|
|
279
|
+
let line_count = if is-first-section { 2 } else { 1 }
|
|
280
|
+
blank-lines(line_count)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#let render-backmatter-sections(
|
|
285
|
+
attachments: none,
|
|
286
|
+
cc: none,
|
|
287
|
+
distribution: none,
|
|
288
|
+
leading-pagebreak: false,
|
|
289
|
+
) = {
|
|
290
|
+
let has-backmatter = (
|
|
291
|
+
(attachments != none and attachments.len() > 0)
|
|
292
|
+
or (cc != none and cc.len() > 0)
|
|
293
|
+
or (distribution != none and distribution.len() > 0)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if leading-pagebreak and has-backmatter {
|
|
297
|
+
pagebreak(weak: true)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if attachments != none and attachments.len() > 0 {
|
|
301
|
+
calculate-backmatter-spacing(true)
|
|
302
|
+
let attachment-count = attachments.len()
|
|
303
|
+
let section-label = if attachment-count == 1 { "Attachment:" } else { str(attachment-count) + " Attachments:" }
|
|
304
|
+
let continuation-label = (
|
|
305
|
+
(if attachment-count == 1 { "Attachment" } else { str(attachment-count) + " Attachments" })
|
|
306
|
+
+ " (listed on next page):"
|
|
307
|
+
)
|
|
308
|
+
render-backmatter-section(attachments, section-label, numbering-style: "1.", continuation-label: continuation-label)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if cc != none and cc.len() > 0 {
|
|
312
|
+
calculate-backmatter-spacing(attachments == none or attachments.len() == 0)
|
|
313
|
+
render-backmatter-section(cc, "cc:")
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if distribution != none and distribution.len() > 0 {
|
|
317
|
+
calculate-backmatter-spacing((attachments == none or attachments.len() == 0) and (cc == none or cc.len() == 0))
|
|
318
|
+
render-backmatter-section(distribution, "DISTRIBUTION:")
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|