@tonguetoquill/collection 0.6.1 → 0.7.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/package.json +1 -1
- package/quills/daf1206/0.1.0/Quill.yaml +52 -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 +19 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/FIELDS.json +158 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/form.pdf +0 -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 +270 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/out/debug.pdf +0 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/out/debug.typ +18 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/out/example.pdf +0 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/out/example.typ +17 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/page1.png +0 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/page2.png +0 -0
- package/quills/daf1206/0.1.0/packages/typst-daf1206/typst.toml +7 -0
- package/quills/daf1206/0.1.0/plate.typ +28 -0
- package/quills/daf1206/assets/Figtree-Bold.ttf +0 -0
- package/quills/daf1206/assets/Figtree-Italic.ttf +0 -0
- package/quills/daf1206/assets/Figtree-Regular.ttf +0 -0
- package/quills/daf1206/assets/GNU General Public License.txt +340 -0
- package/quills/daf1206/assets/NimbusRomNo9L-Med.otf +0 -0
- package/quills/daf1206/assets/NimbusRomNo9L-MedIta.otf +0 -0
- package/quills/daf1206/assets/NimbusRomNo9L-Reg.otf +0 -0
- package/quills/daf1206/assets/NimbusRomNo9L-RegIta.otf +0 -0
- package/quills/daf4392/0.1.0/Quill.yaml +6 -11
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/out/debug.typ +4 -0
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/out/example.typ +4 -0
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/page1.png +0 -0
- package/quills/daf4392/0.1.0/plate.typ +1 -1
- package/quills/daf4392/0.2.0/Quill.yaml +140 -0
- package/quills/daf4392/0.2.0/assets/arimo-v35-latin-700.ttf +0 -0
- package/quills/daf4392/0.2.0/assets/arimo-v35-latin-700italic.ttf +0 -0
- package/quills/daf4392/0.2.0/assets/arimo-v35-latin-italic.ttf +0 -0
- package/quills/daf4392/0.2.0/assets/arimo-v35-latin-regular.ttf +0 -0
- package/quills/daf4392/0.2.0/assets/page1.png +0 -0
- package/quills/daf4392/0.2.0/example.md +34 -0
- package/quills/daf4392/0.2.0/packages/daf4392page2_pkg/FIELDS.json +9 -0
- package/quills/daf4392/0.2.0/packages/daf4392page2_pkg/form.typ +14 -0
- package/quills/daf4392/0.2.0/packages/daf4392page2_pkg/lib.typ +227 -0
- package/quills/daf4392/0.2.0/packages/daf4392page2_pkg/typst.toml +7 -0
- package/quills/daf4392/0.2.0/plate.typ +68 -0
- package/templates/daf4392.md +2 -1
package/package.json
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Quill:
|
|
2
|
+
name: daf1206
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
backend: typst
|
|
5
|
+
plate_file: plate.typ
|
|
6
|
+
example_file: example.md
|
|
7
|
+
description: "DAF Form 1206 — Nomination for Award"
|
|
8
|
+
|
|
9
|
+
main:
|
|
10
|
+
ui:
|
|
11
|
+
hide_body: true
|
|
12
|
+
fields:
|
|
13
|
+
AWARD:
|
|
14
|
+
title: Award
|
|
15
|
+
type: string
|
|
16
|
+
required: true
|
|
17
|
+
CATEGORY:
|
|
18
|
+
title: Category (If Applicable)
|
|
19
|
+
type: string
|
|
20
|
+
AWARD_PERIOD:
|
|
21
|
+
title: Award Period
|
|
22
|
+
type: string
|
|
23
|
+
required: true
|
|
24
|
+
RANKNAME_OF_NOMINEE:
|
|
25
|
+
title: Rank/Name of Nominee (First, Middle Initial, Last)
|
|
26
|
+
type: string
|
|
27
|
+
required: true
|
|
28
|
+
MAJCOM_FLDCOM_FOA_OR_DRU:
|
|
29
|
+
title: MAJCOM/FLDCOM/FOA/DRU
|
|
30
|
+
type: string
|
|
31
|
+
DAFSCDUTY_TITLE:
|
|
32
|
+
title: DAFSC/Duty Title
|
|
33
|
+
type: string
|
|
34
|
+
NOMINEES_TELEPHONE:
|
|
35
|
+
title: Nominee's Telephone (DSN/Commercial)
|
|
36
|
+
type: string
|
|
37
|
+
UNITOFFICE_SYMBOL:
|
|
38
|
+
title: Unit/Office Symbol/Street Address/Base/State/Zip Code
|
|
39
|
+
type: string
|
|
40
|
+
UNIT_COMMANDER:
|
|
41
|
+
title: Rank/Name of Unit Commander & Telephone
|
|
42
|
+
type: string
|
|
43
|
+
ACCOMPLISHMENTS:
|
|
44
|
+
title: Specific Accomplishments (Page 1)
|
|
45
|
+
type: string
|
|
46
|
+
ui:
|
|
47
|
+
multiline: true
|
|
48
|
+
ACCOMPLISHMENTS_CONTINUED:
|
|
49
|
+
title: Specific Accomplishments (Continued - Page 2)
|
|
50
|
+
type: string
|
|
51
|
+
ui:
|
|
52
|
+
multiline: true
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
QUILL: daf1206@0.1.0
|
|
3
|
+
AWARD: "AIR AND SPACE ACHIEVEMENT MEDAL"
|
|
4
|
+
CATEGORY: "asdfasdfasdf"
|
|
5
|
+
AWARD_PERIOD: "1 Jan 24 - 31 Dec 24 asdfasdfasdf"
|
|
6
|
+
RANKNAME_OF_NOMINEE: "SSgt Doe, John A."
|
|
7
|
+
MAJCOM_FLDCOM_FOA_OR_DRU: "ACC"
|
|
8
|
+
DAFSCDUTY_TITLE: "1D7X1 / Cyber Operator"
|
|
9
|
+
NOMINEES_TELEPHONE: "DSN: 123-4567 / Comm: (123) 456-7890"
|
|
10
|
+
UNITOFFICE_SYMBOL: "1st Communications Squadron / SC, 123 Main St, AFB, ST 12345"
|
|
11
|
+
UNIT_COMMANDER: "Capt Jane Smith / DSN: 123-4568 / Comm: (123) 456-7891"
|
|
12
|
+
|
|
13
|
+
ACCOMPLISHMENTS: |-
|
|
14
|
+
- Spearheaded major network upgrade; improved speed by 50%
|
|
15
|
+
- Mentored 5 Airmen; 100% upgrade training completion rate
|
|
16
|
+
|
|
17
|
+
ACCOMPLISHMENTS_CONTINUED: |-
|
|
18
|
+
- Continued excellence in supporting base communications
|
|
19
|
+
---
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pages": [
|
|
3
|
+
{
|
|
4
|
+
"width": 612.0,
|
|
5
|
+
"height": 792.0
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"width": 612.0,
|
|
9
|
+
"height": 792.0
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"fields": [
|
|
13
|
+
{
|
|
14
|
+
"name": "AWARD",
|
|
15
|
+
"type": "text",
|
|
16
|
+
"bbox": [
|
|
17
|
+
21.479999542236328,
|
|
18
|
+
30.5999755859375,
|
|
19
|
+
307.55999755859375,
|
|
20
|
+
46.79998779296875
|
|
21
|
+
],
|
|
22
|
+
"page": 1,
|
|
23
|
+
"options": null
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "CATEGORY If Applicable",
|
|
27
|
+
"type": "text",
|
|
28
|
+
"bbox": [
|
|
29
|
+
309.1199951171875,
|
|
30
|
+
30.719970703125,
|
|
31
|
+
423.3599853515625,
|
|
32
|
+
46.79998779296875
|
|
33
|
+
],
|
|
34
|
+
"page": 1,
|
|
35
|
+
"options": null
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "AWARD PERIOD",
|
|
39
|
+
"type": "text",
|
|
40
|
+
"bbox": [
|
|
41
|
+
424.9200134277344,
|
|
42
|
+
31.20001220703125,
|
|
43
|
+
593.4000244140625,
|
|
44
|
+
46.79998779296875
|
|
45
|
+
],
|
|
46
|
+
"page": 1,
|
|
47
|
+
"options": null
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "RANKNAME OF NOMINEE First Middle Initial Last",
|
|
51
|
+
"type": "text",
|
|
52
|
+
"bbox": [
|
|
53
|
+
21.360000610351562,
|
|
54
|
+
54.0,
|
|
55
|
+
348.8399963378906,
|
|
56
|
+
68.52001953125
|
|
57
|
+
],
|
|
58
|
+
"page": 1,
|
|
59
|
+
"options": null
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "MAJCOM FLDCOM FOA OR DRU",
|
|
63
|
+
"type": "text",
|
|
64
|
+
"bbox": [
|
|
65
|
+
350.1600036621094,
|
|
66
|
+
54.0,
|
|
67
|
+
593.52001953125,
|
|
68
|
+
68.52001953125
|
|
69
|
+
],
|
|
70
|
+
"page": 1,
|
|
71
|
+
"options": null
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "DAFSCDUTY TITLE",
|
|
75
|
+
"type": "text",
|
|
76
|
+
"bbox": [
|
|
77
|
+
21.479999542236328,
|
|
78
|
+
76.91998291015625,
|
|
79
|
+
286.9200134277344,
|
|
80
|
+
93.96002197265625
|
|
81
|
+
],
|
|
82
|
+
"page": 1,
|
|
83
|
+
"options": null
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "NOMINEES TELEPHONE DSN Commercial",
|
|
87
|
+
"type": "text",
|
|
88
|
+
"bbox": [
|
|
89
|
+
288.4800109863281,
|
|
90
|
+
76.91998291015625,
|
|
91
|
+
593.4000244140625,
|
|
92
|
+
93.96002197265625
|
|
93
|
+
],
|
|
94
|
+
"page": 1,
|
|
95
|
+
"options": null
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "UNITOFFICE SYMBOLSTREET ADDRESSBASESTATEZIP CODE",
|
|
99
|
+
"type": "text",
|
|
100
|
+
"bbox": [
|
|
101
|
+
21.360000610351562,
|
|
102
|
+
101.280029296875,
|
|
103
|
+
593.52001953125,
|
|
104
|
+
115.79998779296875
|
|
105
|
+
],
|
|
106
|
+
"page": 1,
|
|
107
|
+
"options": null
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "RANKNAME OF UNIT COMMANDER First Middle Initial LastCOMMANDERS TELEPHONE DSN Commercial",
|
|
111
|
+
"type": "text",
|
|
112
|
+
"bbox": [
|
|
113
|
+
21.479999542236328,
|
|
114
|
+
123.3599853515625,
|
|
115
|
+
593.4000244140625,
|
|
116
|
+
139.20001220703125
|
|
117
|
+
],
|
|
118
|
+
"page": 1,
|
|
119
|
+
"options": null
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "SPECIFIC ACCOMPLISHMENTS Use Performance Statements IAW DAFMAN 362806",
|
|
123
|
+
"type": "text",
|
|
124
|
+
"bbox": [
|
|
125
|
+
22.079999923706055,
|
|
126
|
+
147.1199951171875,
|
|
127
|
+
592.7999877929688,
|
|
128
|
+
767.4000244140625
|
|
129
|
+
],
|
|
130
|
+
"page": 1,
|
|
131
|
+
"options": null
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "RANKNAME OF NOMINEE First Middle Initial Last_2",
|
|
135
|
+
"type": "text",
|
|
136
|
+
"bbox": [
|
|
137
|
+
21.479999542236328,
|
|
138
|
+
36.84002685546875,
|
|
139
|
+
593.4000244140625,
|
|
140
|
+
54.84002685546875
|
|
141
|
+
],
|
|
142
|
+
"page": 2,
|
|
143
|
+
"options": null
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "SPECIFIC ACCOMPLISHMENTS Use Performance Statements IAW DAFMAN 362806 Continued",
|
|
147
|
+
"type": "text",
|
|
148
|
+
"bbox": [
|
|
149
|
+
22.079999923706055,
|
|
150
|
+
62.8800048828125,
|
|
151
|
+
592.7999877929688,
|
|
152
|
+
769.2000122070312
|
|
153
|
+
],
|
|
154
|
+
"page": 2,
|
|
155
|
+
"options": null
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// form.typ (generated — do not edit)
|
|
2
|
+
#import "lib.typ": render-form
|
|
3
|
+
|
|
4
|
+
#let form(
|
|
5
|
+
debug: false,
|
|
6
|
+
AWARD: "", // text
|
|
7
|
+
CATEGORY_If_Applicable: "", // text
|
|
8
|
+
AWARD_PERIOD: "", // text
|
|
9
|
+
RANKNAME_OF_NOMINEE_First_Middle_Initial_Last: "", // text
|
|
10
|
+
MAJCOM_FLDCOM_FOA_OR_DRU: "", // text
|
|
11
|
+
DAFSCDUTY_TITLE: "", // text
|
|
12
|
+
NOMINEES_TELEPHONE__DSN__Commercial: "", // text
|
|
13
|
+
UNITOFFICE_SYMBOLSTREET_ADDRESSBASESTATEZIP_CODE: "", // text
|
|
14
|
+
RANKNAME_OF_UNIT_COMMANDER_First_Middle_Initial_LastCOMMANDERS_TELEPHONE_DSN__Commercial: "", // text
|
|
15
|
+
SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806: "", // text
|
|
16
|
+
RANKNAME_OF_NOMINEE_First_Middle_Initial_Last_2: "", // text
|
|
17
|
+
SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806_Continued: "", // text
|
|
18
|
+
) = render-form(
|
|
19
|
+
schema: json("FIELDS.json"),
|
|
20
|
+
backgrounds: ("page1.png", "page2.png",),
|
|
21
|
+
values: (
|
|
22
|
+
"AWARD": AWARD,
|
|
23
|
+
"CATEGORY If Applicable": CATEGORY_If_Applicable,
|
|
24
|
+
"AWARD PERIOD": AWARD_PERIOD,
|
|
25
|
+
"RANKNAME OF NOMINEE First Middle Initial Last": RANKNAME_OF_NOMINEE_First_Middle_Initial_Last,
|
|
26
|
+
"MAJCOM FLDCOM FOA OR DRU": MAJCOM_FLDCOM_FOA_OR_DRU,
|
|
27
|
+
"DAFSCDUTY TITLE": DAFSCDUTY_TITLE,
|
|
28
|
+
"NOMINEES TELEPHONE DSN Commercial": NOMINEES_TELEPHONE__DSN__Commercial,
|
|
29
|
+
"UNITOFFICE SYMBOLSTREET ADDRESSBASESTATEZIP CODE": UNITOFFICE_SYMBOLSTREET_ADDRESSBASESTATEZIP_CODE,
|
|
30
|
+
"RANKNAME OF UNIT COMMANDER First Middle Initial LastCOMMANDERS TELEPHONE DSN Commercial": RANKNAME_OF_UNIT_COMMANDER_First_Middle_Initial_LastCOMMANDERS_TELEPHONE_DSN__Commercial,
|
|
31
|
+
"SPECIFIC ACCOMPLISHMENTS Use Performance Statements IAW DAFMAN 362806": SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806,
|
|
32
|
+
"RANKNAME OF NOMINEE First Middle Initial Last_2": RANKNAME_OF_NOMINEE_First_Middle_Initial_Last_2,
|
|
33
|
+
"SPECIFIC ACCOMPLISHMENTS Use Performance Statements IAW DAFMAN 362806 Continued": SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806_Continued,
|
|
34
|
+
),
|
|
35
|
+
debug: debug,
|
|
36
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// Formalizer Engine – rendering engine
|
|
2
|
+
// Renders pixel-perfect PDF form replicas from a PyMuPDF-extracted schema.
|
|
3
|
+
|
|
4
|
+
/// Global configuration for text rendering.
|
|
5
|
+
/// Adjust these to change the overall form text appearance.
|
|
6
|
+
#let FORM_MAX_TEXT_SIZE = 11pt
|
|
7
|
+
#let FORM_MIN_TEXT_SIZE = 5pt
|
|
8
|
+
#let FORM_MIN_CHARS_PER_LINE = 7
|
|
9
|
+
|
|
10
|
+
/// Should this field shrink text to a single line rather than word-wrap?
|
|
11
|
+
/// True for short/narrow fields with brief content (grades, ranks, dates).
|
|
12
|
+
#let should-shrink-to-fit(display, width, height) = {
|
|
13
|
+
let aspect = width / height
|
|
14
|
+
let char-count = display.len()
|
|
15
|
+
char-count <= 10 or height < 20pt or aspect > 4.0
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Render a text-like field with shrink-to-fit and word-wrap fallback.
|
|
19
|
+
#let render-text-field(display, width, height, x-inset, y-inset) = {
|
|
20
|
+
set par(leading: 0.5em)
|
|
21
|
+
context {
|
|
22
|
+
let avail-w = width - 2 * x-inset
|
|
23
|
+
let avail-h = height - 2 * y-inset
|
|
24
|
+
let shrink = should-shrink-to-fit(display, width, height)
|
|
25
|
+
|
|
26
|
+
let final-size = FORM_MIN_TEXT_SIZE
|
|
27
|
+
let current = FORM_MAX_TEXT_SIZE
|
|
28
|
+
let step = 0.5pt
|
|
29
|
+
|
|
30
|
+
while current >= FORM_MIN_TEXT_SIZE {
|
|
31
|
+
let m = if shrink {
|
|
32
|
+
// Measure as a single line (no width constraint → no wrapping)
|
|
33
|
+
measure(text(size: current, display))
|
|
34
|
+
} else {
|
|
35
|
+
// Measure with wrapping within the available width
|
|
36
|
+
measure(block(width: avail-w, text(size: current, display)))
|
|
37
|
+
}
|
|
38
|
+
let char-m = measure(text(size: current, "0" * FORM_MIN_CHARS_PER_LINE))
|
|
39
|
+
|
|
40
|
+
if m.width <= avail-w and m.height <= avail-h and char-m.width <= avail-w {
|
|
41
|
+
final-size = current
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
current = current - step
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Fallback: if shrink-to-fit hit min size and still overflows,
|
|
48
|
+
// re-try with word-wrap enabled as a last resort.
|
|
49
|
+
if shrink and current < FORM_MIN_TEXT_SIZE {
|
|
50
|
+
current = FORM_MAX_TEXT_SIZE
|
|
51
|
+
while current >= FORM_MIN_TEXT_SIZE {
|
|
52
|
+
let m = measure(block(width: avail-w, text(size: current, display)))
|
|
53
|
+
let char-m = measure(text(size: current, "0" * FORM_MIN_CHARS_PER_LINE))
|
|
54
|
+
if m.height <= avail-h and char-m.width <= avail-w {
|
|
55
|
+
final-size = current
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
current = current - step
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Default to vertically centered. Only top-align for tall "text areas".
|
|
63
|
+
let vert-align = if height >= 40pt { top } else { horizon }
|
|
64
|
+
|
|
65
|
+
box(
|
|
66
|
+
width: width,
|
|
67
|
+
height: height,
|
|
68
|
+
clip: true,
|
|
69
|
+
inset: (x: x-inset, y: y-inset),
|
|
70
|
+
align(left + vert-align, text(size: final-size, display)),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Render a single field's content overlay.
|
|
76
|
+
///
|
|
77
|
+
/// - field-type (str): normalised lowercase type
|
|
78
|
+
/// - value: user-supplied value for this field (or none)
|
|
79
|
+
/// - width (length): field width
|
|
80
|
+
/// - height (length): field height
|
|
81
|
+
/// - field (dictionary): raw field entry from the schema
|
|
82
|
+
#let render-field(field-type, value, width, height, field) = {
|
|
83
|
+
if field-type == "text" {
|
|
84
|
+
if value != none and str(value) != "" {
|
|
85
|
+
render-text-field(str(value), width, height, 1.5pt, 1pt)
|
|
86
|
+
}
|
|
87
|
+
} else if field-type == "checkbox" {
|
|
88
|
+
if value == true {
|
|
89
|
+
box(
|
|
90
|
+
width: width,
|
|
91
|
+
height: height,
|
|
92
|
+
align(center + horizon, text(size: height * 0.8, "✓")),
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
} else if field-type == "radio" {
|
|
96
|
+
// value is true when this specific button is the selected one
|
|
97
|
+
// (resolved at group level before calling this helper)
|
|
98
|
+
if value == true {
|
|
99
|
+
let dot-r = calc.min(width, height) * 0.3
|
|
100
|
+
box(
|
|
101
|
+
width: width,
|
|
102
|
+
height: height,
|
|
103
|
+
align(center + horizon, circle(radius: dot-r, fill: black)),
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
} else if field-type == "combobox" or field-type == "listbox" {
|
|
107
|
+
let display = if value != none { str(value) } else { "" }
|
|
108
|
+
// Resolve export value → display label when options are present
|
|
109
|
+
if field.at("options", default: none) != none and value != none {
|
|
110
|
+
for opt in field.options {
|
|
111
|
+
if str(opt.at(0)) == str(value) {
|
|
112
|
+
display = str(opt.at(1))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if display != "" {
|
|
117
|
+
render-text-field(display, width, height, 2pt, 1pt)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Determine whether a single radio button should appear selected.
|
|
123
|
+
///
|
|
124
|
+
/// Strategy:
|
|
125
|
+
/// 1. If the field carries an `export_value` key, match against it.
|
|
126
|
+
/// 2. Otherwise fall back to matching the 0-based index within the group
|
|
127
|
+
/// (stringified) against the supplied value.
|
|
128
|
+
#let radio-button-selected(field, group-value, index-in-group) = {
|
|
129
|
+
if group-value == none { return false }
|
|
130
|
+
let ev = field.at("export_value", default: none)
|
|
131
|
+
if ev != none {
|
|
132
|
+
return str(ev) == str(group-value)
|
|
133
|
+
}
|
|
134
|
+
// Fallback: match against the stringified index
|
|
135
|
+
str(index-in-group) == str(group-value)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Draw a debug overlay rectangle with a label for a field.
|
|
139
|
+
///
|
|
140
|
+
/// - field-type (str): normalised lowercase type
|
|
141
|
+
/// - name (str): field name
|
|
142
|
+
/// - width (length): field width
|
|
143
|
+
/// - height (length): field height
|
|
144
|
+
#let debug-overlay(field-type, name, width, height) = {
|
|
145
|
+
let color = if field-type == "text" {
|
|
146
|
+
rgb(0, 0, 255, 40%)
|
|
147
|
+
} else if field-type == "checkbox" {
|
|
148
|
+
rgb(0, 128, 0, 40%)
|
|
149
|
+
} else if field-type == "radio" {
|
|
150
|
+
rgb(255, 165, 0, 40%)
|
|
151
|
+
} else if field-type == "combobox" {
|
|
152
|
+
rgb(128, 0, 128, 40%)
|
|
153
|
+
} else if field-type == "listbox" {
|
|
154
|
+
rgb(0, 128, 128, 40%)
|
|
155
|
+
} else {
|
|
156
|
+
rgb(128, 128, 128, 40%)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let stroke-color = if field-type == "text" {
|
|
160
|
+
rgb(0, 0, 255)
|
|
161
|
+
} else if field-type == "checkbox" {
|
|
162
|
+
rgb(0, 128, 0)
|
|
163
|
+
} else if field-type == "radio" {
|
|
164
|
+
rgb(255, 165, 0)
|
|
165
|
+
} else if field-type == "combobox" {
|
|
166
|
+
rgb(128, 0, 128)
|
|
167
|
+
} else if field-type == "listbox" {
|
|
168
|
+
rgb(0, 128, 128)
|
|
169
|
+
} else {
|
|
170
|
+
rgb(128, 128, 128)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Insert zero-width spaces after _ and - so the label can wrap
|
|
174
|
+
let breakable-name = name.replace("_", "_\u{200B}").replace("-", "-\u{200B}")
|
|
175
|
+
let label = breakable-name + " [" + field-type + "]"
|
|
176
|
+
|
|
177
|
+
box(width: width, height: height, {
|
|
178
|
+
// Semi-transparent colored background
|
|
179
|
+
rect(width: 100%, height: 100%, fill: color, stroke: 0.5pt + stroke-color)
|
|
180
|
+
// Label in top-left — uses block so text wraps within field width
|
|
181
|
+
place(
|
|
182
|
+
top + left,
|
|
183
|
+
dx: 1pt,
|
|
184
|
+
dy: 1pt,
|
|
185
|
+
block(
|
|
186
|
+
width: calc.max(width - 2pt, 10pt),
|
|
187
|
+
fill: white,
|
|
188
|
+
inset: (x: 2pt, y: 1pt),
|
|
189
|
+
radius: 2pt,
|
|
190
|
+
stroke: 0.3pt + stroke-color,
|
|
191
|
+
breakable: false,
|
|
192
|
+
text(size: 5pt, fill: stroke-color, weight: "bold", label),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Main entry point.
|
|
199
|
+
///
|
|
200
|
+
/// - schema (dictionary): result of `json("FIELDS.json")`
|
|
201
|
+
/// - backgrounds (array): list of image paths / bytes, one per page
|
|
202
|
+
/// - values (dictionary): field name → value; omit to render blank
|
|
203
|
+
/// - debug (bool): when true, draw colored overlays on each field
|
|
204
|
+
#let render-form(schema: none, backgrounds: (), values: (:), debug: false) = {
|
|
205
|
+
assert(schema != none, message: "render-form: `schema` is required")
|
|
206
|
+
assert(backgrounds.len() > 0, message: "render-form: `backgrounds` is required")
|
|
207
|
+
let pages = schema.pages
|
|
208
|
+
let fields = schema.fields
|
|
209
|
+
|
|
210
|
+
// Track how many radio buttons we have seen per group so far
|
|
211
|
+
let radio-counters = (:)
|
|
212
|
+
|
|
213
|
+
for (i, page-info) in pages.enumerate() {
|
|
214
|
+
let page-num = i + 1
|
|
215
|
+
let page-fields = fields.filter(f => f.page == page-num)
|
|
216
|
+
|
|
217
|
+
let bg = backgrounds.at(i)
|
|
218
|
+
let pw = page-info.width * 1pt
|
|
219
|
+
let ph = page-info.height * 1pt
|
|
220
|
+
|
|
221
|
+
page(
|
|
222
|
+
width: pw,
|
|
223
|
+
height: ph,
|
|
224
|
+
margin: 0pt,
|
|
225
|
+
)[
|
|
226
|
+
// Background image (the original PDF page rasterised as PNG)
|
|
227
|
+
#place(top + left, image(bg, width: pw, height: ph, fit: "stretch"))
|
|
228
|
+
|
|
229
|
+
// Field overlays
|
|
230
|
+
#for field in page-fields.filter(f => ("text", "checkbox", "radio", "combobox", "listbox").contains(lower(
|
|
231
|
+
f.type,
|
|
232
|
+
))) {
|
|
233
|
+
let x = field.bbox.at(0) * 1pt
|
|
234
|
+
let y = field.bbox.at(1) * 1pt
|
|
235
|
+
let w = (field.bbox.at(2) - field.bbox.at(0)) * 1pt
|
|
236
|
+
let h = (field.bbox.at(3) - field.bbox.at(1)) * 1pt
|
|
237
|
+
|
|
238
|
+
let field-type = lower(field.type)
|
|
239
|
+
let val = values.at(field.name, default: none)
|
|
240
|
+
|
|
241
|
+
// --- Radio: resolve group-level value to per-button boolean ---
|
|
242
|
+
if field-type == "radio" {
|
|
243
|
+
let group-name = field.name
|
|
244
|
+
let idx = radio-counters.at(group-name, default: 0)
|
|
245
|
+
radio-counters.insert(group-name, idx + 1)
|
|
246
|
+
val = radio-button-selected(field, val, idx)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
place(
|
|
250
|
+
top + left,
|
|
251
|
+
dx: x,
|
|
252
|
+
dy: y,
|
|
253
|
+
render-field(field-type, val, w, h, field),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if debug {
|
|
257
|
+
place(
|
|
258
|
+
top + left,
|
|
259
|
+
dx: x,
|
|
260
|
+
dy: y,
|
|
261
|
+
debug-overlay(field-type, field.name, w, h),
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Zero-width fence to break PDF viewer text-selection grouping
|
|
266
|
+
place(top + left, dx: x, dy: y, text(size: 0.001pt, "\u{FEFF}"))
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// debug.typ (generated — renders form with debug overlays)
|
|
2
|
+
#import "../form.typ": form
|
|
3
|
+
|
|
4
|
+
#form(
|
|
5
|
+
debug: true,
|
|
6
|
+
AWARD: "Award",
|
|
7
|
+
CATEGORY_If_Applicable: "Category If Applicable",
|
|
8
|
+
AWARD_PERIOD: "Award Period",
|
|
9
|
+
RANKNAME_OF_NOMINEE_First_Middle_Initial_Last: "Rankname Of Nominee First Middle Initial Last",
|
|
10
|
+
MAJCOM_FLDCOM_FOA_OR_DRU: "Majcom Fldcom Foa Or Dru",
|
|
11
|
+
DAFSCDUTY_TITLE: "Dafscduty Title",
|
|
12
|
+
NOMINEES_TELEPHONE__DSN__Commercial: "Nominees Telephone Dsn Commercial",
|
|
13
|
+
UNITOFFICE_SYMBOLSTREET_ADDRESSBASESTATEZIP_CODE: "Unitoffice Symbolstreet Addressbasestatezip Code",
|
|
14
|
+
RANKNAME_OF_UNIT_COMMANDER_First_Middle_Initial_LastCOMMANDERS_TELEPHONE_DSN__Commercial: "Rankname Of Unit Commander First Middle Initial Last Commanders Telephone Dsn Commercial",
|
|
15
|
+
SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806: "Specific Accomplishments Use Performance Statements Iaw Dafman",
|
|
16
|
+
RANKNAME_OF_NOMINEE_First_Middle_Initial_Last_2: "Rankname Of Nominee First Middle Initial Last",
|
|
17
|
+
SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806_Continued: "Specific Accomplishments Use Performance Statements Iaw Dafman 362806 Continued",
|
|
18
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// example.typ (edit this file to fill the form)
|
|
2
|
+
#import "../form.typ": form
|
|
3
|
+
|
|
4
|
+
#form(
|
|
5
|
+
AWARD: "Award",
|
|
6
|
+
CATEGORY_If_Applicable: "Category If Applicable",
|
|
7
|
+
AWARD_PERIOD: "Award Period",
|
|
8
|
+
RANKNAME_OF_NOMINEE_First_Middle_Initial_Last: "Rankname Of Nominee First Middle Initial Last",
|
|
9
|
+
MAJCOM_FLDCOM_FOA_OR_DRU: "Majcom Fldcom Foa Or Dru",
|
|
10
|
+
DAFSCDUTY_TITLE: "Dafscduty Title",
|
|
11
|
+
NOMINEES_TELEPHONE__DSN__Commercial: "Nominees Telephone Dsn Commercial",
|
|
12
|
+
UNITOFFICE_SYMBOLSTREET_ADDRESSBASESTATEZIP_CODE: "Unitoffice Symbolstreet Addressbasestatezip Code",
|
|
13
|
+
RANKNAME_OF_UNIT_COMMANDER_First_Middle_Initial_LastCOMMANDERS_TELEPHONE_DSN__Commercial: "Rankname Of Unit Commander First Middle Initial Last Commanders Telephone Dsn Commercial",
|
|
14
|
+
SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806: "Specific Accomplishments Use Performance Statements Iaw Dafman",
|
|
15
|
+
RANKNAME_OF_NOMINEE_First_Middle_Initial_Last_2: "Rankname Of Nominee First Middle Initial Last",
|
|
16
|
+
SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806_Continued: "Specific Accomplishments Use Performance Statements Iaw Dafman 362806 Continued",
|
|
17
|
+
)
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#import "@local/quillmark-helper:0.1.0": data
|
|
2
|
+
#import "@local/typst-daf1206:0.1.0": form
|
|
3
|
+
|
|
4
|
+
#set text(font: "Arimo")
|
|
5
|
+
|
|
6
|
+
// Build the values dictionary for the form
|
|
7
|
+
#let vals = (:)
|
|
8
|
+
|
|
9
|
+
// --- Admin fields (page 1 header) ---
|
|
10
|
+
#if "AWARD" in data { vals.insert("AWARD", data.AWARD) }
|
|
11
|
+
#if "CATEGORY" in data { vals.insert("CATEGORY_If_Applicable", data.CATEGORY) }
|
|
12
|
+
#if "AWARD_PERIOD" in data { vals.insert("AWARD_PERIOD", data.AWARD_PERIOD) }
|
|
13
|
+
|
|
14
|
+
#if "RANKNAME_OF_NOMINEE" in data { vals.insert("RANKNAME_OF_NOMINEE_First_Middle_Initial_Last", data.RANKNAME_OF_NOMINEE) }
|
|
15
|
+
#if "RANKNAME_OF_NOMINEE" in data { vals.insert("RANKNAME_OF_NOMINEE_First_Middle_Initial_Last_2", data.RANKNAME_OF_NOMINEE) }
|
|
16
|
+
|
|
17
|
+
#if "MAJCOM_FLDCOM_FOA_OR_DRU" in data { vals.insert("MAJCOM_FLDCOM_FOA_OR_DRU", data.MAJCOM_FLDCOM_FOA_OR_DRU) }
|
|
18
|
+
#if "DAFSCDUTY_TITLE" in data { vals.insert("DAFSCDUTY_TITLE", data.DAFSCDUTY_TITLE) }
|
|
19
|
+
#if "NOMINEES_TELEPHONE" in data { vals.insert("NOMINEES_TELEPHONE__DSN__Commercial", data.NOMINEES_TELEPHONE) }
|
|
20
|
+
#if "UNITOFFICE_SYMBOL" in data { vals.insert("UNITOFFICE_SYMBOLSTREET_ADDRESSBASESTATEZIP_CODE", data.UNITOFFICE_SYMBOL) }
|
|
21
|
+
#if "UNIT_COMMANDER" in data { vals.insert("RANKNAME_OF_UNIT_COMMANDER_First_Middle_Initial_LastCOMMANDERS_TELEPHONE_DSN__Commercial", data.UNIT_COMMANDER) }
|
|
22
|
+
|
|
23
|
+
// --- Accomplishments (page 1 & 2 content) ---
|
|
24
|
+
#if "ACCOMPLISHMENTS" in data { vals.insert("SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806", data.ACCOMPLISHMENTS) }
|
|
25
|
+
#if "ACCOMPLISHMENTS_CONTINUED" in data { vals.insert("SPECIFIC_ACCOMPLISHMENTS_Use_Performance_Statements_IAW_DAFMAN_362806_Continued", data.ACCOMPLISHMENTS_CONTINUED) }
|
|
26
|
+
|
|
27
|
+
// Render the form with assembled values
|
|
28
|
+
#form(..vals)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|