@tonguetoquill/collection 0.1.19 → 0.2.1
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 +2 -2
- package/quills/af4141/0.1.0/Quill.yaml +96 -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/design/TASK.md +20 -0
- package/quills/af4141/0.1.0/design/af4141.pdf +0 -0
- package/quills/af4141/0.1.0/example.md +36 -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 +227 -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 +48 -0
- package/quills/classic_resume/0.1.0/plate.typ +1 -1
- package/quills/cmu_letter/0.1.0/plate.typ +1 -1
- package/quills/usaf_memo/0.1.0/plate.typ +1 -1
- package/quills/usaf_memo/0.2.0/plate.typ +1 -1
- package/templates/af4141.md +88 -0
- package/templates/loc.md +2 -2
- package/templates/templates.json +6 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Formalizer Engine – rendering engine
|
|
2
|
+
// Renders pixel-perfect PDF form replicas from a PyMuPDF-extracted schema.
|
|
3
|
+
|
|
4
|
+
/// Render a single field's content overlay.
|
|
5
|
+
///
|
|
6
|
+
/// - field-type (str): normalised lowercase type
|
|
7
|
+
/// - value: user-supplied value for this field (or none)
|
|
8
|
+
/// - width (length): field width
|
|
9
|
+
/// - height (length): field height
|
|
10
|
+
/// - field (dictionary): raw field entry from the schema
|
|
11
|
+
#let render-field(field-type, value, width, height, field) = {
|
|
12
|
+
if field-type == "text" {
|
|
13
|
+
if value != none and str(value) != "" {
|
|
14
|
+
context {
|
|
15
|
+
let default-size = height * 0.6
|
|
16
|
+
let min-size = 6pt
|
|
17
|
+
let x-inset = 1.5pt
|
|
18
|
+
let m = measure(text(size: default-size, str(value)))
|
|
19
|
+
let scale = calc.min(1.0, (width - 2 * x-inset) / m.width)
|
|
20
|
+
let final-size = calc.max(min-size, default-size * scale)
|
|
21
|
+
box(
|
|
22
|
+
width: width,
|
|
23
|
+
height: height,
|
|
24
|
+
clip: true,
|
|
25
|
+
inset: (x: x-inset),
|
|
26
|
+
align(left + horizon, text(size: final-size, str(value))),
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} else if field-type == "checkbox" {
|
|
31
|
+
if value == true {
|
|
32
|
+
box(
|
|
33
|
+
width: width,
|
|
34
|
+
height: height,
|
|
35
|
+
align(center + horizon, text(size: height * 0.8, "✓")),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
} else if field-type == "radio" {
|
|
39
|
+
// value is true when this specific button is the selected one
|
|
40
|
+
// (resolved at group level before calling this helper)
|
|
41
|
+
if value == true {
|
|
42
|
+
let dot-r = calc.min(width, height) * 0.3
|
|
43
|
+
box(
|
|
44
|
+
width: width,
|
|
45
|
+
height: height,
|
|
46
|
+
align(center + horizon, circle(radius: dot-r, fill: black)),
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
} else if field-type == "combobox" or field-type == "listbox" {
|
|
50
|
+
let display = if value != none { str(value) } else { "" }
|
|
51
|
+
// Resolve export value → display label when options are present
|
|
52
|
+
if field.at("options", default: none) != none and value != none {
|
|
53
|
+
for opt in field.options {
|
|
54
|
+
if str(opt.at(0)) == str(value) {
|
|
55
|
+
display = str(opt.at(1))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if display != "" {
|
|
60
|
+
context {
|
|
61
|
+
let default-size = height * 0.6
|
|
62
|
+
let min-size = 6pt
|
|
63
|
+
let x-inset = 2pt
|
|
64
|
+
let m = measure(text(size: default-size, display))
|
|
65
|
+
let scale = calc.min(1.0, (width - 2 * x-inset) / m.width)
|
|
66
|
+
let final-size = calc.max(min-size, default-size * scale)
|
|
67
|
+
box(
|
|
68
|
+
width: width,
|
|
69
|
+
height: height,
|
|
70
|
+
clip: true,
|
|
71
|
+
inset: (x: x-inset),
|
|
72
|
+
align(left + horizon, text(size: final-size, display)),
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Determine whether a single radio button should appear selected.
|
|
80
|
+
///
|
|
81
|
+
/// Strategy:
|
|
82
|
+
/// 1. If the field carries an `export_value` key, match against it.
|
|
83
|
+
/// 2. Otherwise fall back to matching the 0-based index within the group
|
|
84
|
+
/// (stringified) against the supplied value.
|
|
85
|
+
#let radio-button-selected(field, group-value, index-in-group) = {
|
|
86
|
+
if group-value == none { return false }
|
|
87
|
+
let ev = field.at("export_value", default: none)
|
|
88
|
+
if ev != none {
|
|
89
|
+
return str(ev) == str(group-value)
|
|
90
|
+
}
|
|
91
|
+
// Fallback: match against the stringified index
|
|
92
|
+
str(index-in-group) == str(group-value)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Draw a debug overlay rectangle with a label for a field.
|
|
96
|
+
///
|
|
97
|
+
/// - field-type (str): normalised lowercase type
|
|
98
|
+
/// - name (str): field name
|
|
99
|
+
/// - width (length): field width
|
|
100
|
+
/// - height (length): field height
|
|
101
|
+
#let debug-overlay(field-type, name, width, height) = {
|
|
102
|
+
let color = if field-type == "text" {
|
|
103
|
+
rgb(0, 0, 255, 40%)
|
|
104
|
+
} else if field-type == "checkbox" {
|
|
105
|
+
rgb(0, 128, 0, 40%)
|
|
106
|
+
} else if field-type == "radio" {
|
|
107
|
+
rgb(255, 165, 0, 40%)
|
|
108
|
+
} else if field-type == "combobox" {
|
|
109
|
+
rgb(128, 0, 128, 40%)
|
|
110
|
+
} else if field-type == "listbox" {
|
|
111
|
+
rgb(0, 128, 128, 40%)
|
|
112
|
+
} else {
|
|
113
|
+
rgb(128, 128, 128, 40%)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let stroke-color = if field-type == "text" {
|
|
117
|
+
rgb(0, 0, 255)
|
|
118
|
+
} else if field-type == "checkbox" {
|
|
119
|
+
rgb(0, 128, 0)
|
|
120
|
+
} else if field-type == "radio" {
|
|
121
|
+
rgb(255, 165, 0)
|
|
122
|
+
} else if field-type == "combobox" {
|
|
123
|
+
rgb(128, 0, 128)
|
|
124
|
+
} else if field-type == "listbox" {
|
|
125
|
+
rgb(0, 128, 128)
|
|
126
|
+
} else {
|
|
127
|
+
rgb(128, 128, 128)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Insert zero-width spaces after _ and - so the label can wrap
|
|
131
|
+
let breakable-name = name.replace("_", "_\u{200B}").replace("-", "-\u{200B}")
|
|
132
|
+
let label = breakable-name + " [" + field-type + "]"
|
|
133
|
+
|
|
134
|
+
box(width: width, height: height, {
|
|
135
|
+
// Semi-transparent colored background
|
|
136
|
+
rect(width: 100%, height: 100%, fill: color, stroke: 0.5pt + stroke-color)
|
|
137
|
+
// Label in top-left — uses block so text wraps within field width
|
|
138
|
+
place(
|
|
139
|
+
top + left,
|
|
140
|
+
dx: 1pt,
|
|
141
|
+
dy: 1pt,
|
|
142
|
+
block(
|
|
143
|
+
width: calc.max(width - 2pt, 10pt),
|
|
144
|
+
fill: white,
|
|
145
|
+
inset: (x: 2pt, y: 1pt),
|
|
146
|
+
radius: 2pt,
|
|
147
|
+
stroke: 0.3pt + stroke-color,
|
|
148
|
+
breakable: false,
|
|
149
|
+
text(size: 5pt, fill: stroke-color, weight: "bold", label),
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Main entry point.
|
|
156
|
+
///
|
|
157
|
+
/// - schema (dictionary): result of `json("FIELDS.json")`
|
|
158
|
+
/// - backgrounds (array): list of image paths / bytes, one per page
|
|
159
|
+
/// - values (dictionary): field name → value; omit to render blank
|
|
160
|
+
/// - debug (bool): when true, draw colored overlays on each field
|
|
161
|
+
#let render-form(schema: none, backgrounds: (), values: (:), debug: false) = {
|
|
162
|
+
assert(schema != none, message: "render-form: `schema` is required")
|
|
163
|
+
assert(backgrounds.len() > 0, message: "render-form: `backgrounds` is required")
|
|
164
|
+
let pages = schema.pages
|
|
165
|
+
let fields = schema.fields
|
|
166
|
+
|
|
167
|
+
// Track how many radio buttons we have seen per group so far
|
|
168
|
+
let radio-counters = (:)
|
|
169
|
+
|
|
170
|
+
for (i, page-info) in pages.enumerate() {
|
|
171
|
+
let page-num = i + 1
|
|
172
|
+
let page-fields = fields.filter(f => f.page == page-num)
|
|
173
|
+
|
|
174
|
+
let bg = backgrounds.at(i)
|
|
175
|
+
let pw = page-info.width * 1pt
|
|
176
|
+
let ph = page-info.height * 1pt
|
|
177
|
+
|
|
178
|
+
page(
|
|
179
|
+
width: pw,
|
|
180
|
+
height: ph,
|
|
181
|
+
margin: 0pt,
|
|
182
|
+
)[
|
|
183
|
+
// Background image (the original PDF page rasterised as PNG)
|
|
184
|
+
#place(top + left, image(bg, width: pw, height: ph, fit: "stretch"))
|
|
185
|
+
|
|
186
|
+
// Field overlays
|
|
187
|
+
#for field in page-fields.filter(f => ("text", "checkbox", "radio", "combobox", "listbox").contains(lower(
|
|
188
|
+
f.type,
|
|
189
|
+
))) {
|
|
190
|
+
let x = field.bbox.at(0) * 1pt
|
|
191
|
+
let y = field.bbox.at(1) * 1pt
|
|
192
|
+
let w = (field.bbox.at(2) - field.bbox.at(0)) * 1pt
|
|
193
|
+
let h = (field.bbox.at(3) - field.bbox.at(1)) * 1pt
|
|
194
|
+
|
|
195
|
+
let field-type = lower(field.type)
|
|
196
|
+
let val = values.at(field.name, default: none)
|
|
197
|
+
|
|
198
|
+
// --- Radio: resolve group-level value to per-button boolean ---
|
|
199
|
+
if field-type == "radio" {
|
|
200
|
+
let group-name = field.name
|
|
201
|
+
let idx = radio-counters.at(group-name, default: 0)
|
|
202
|
+
radio-counters.insert(group-name, idx + 1)
|
|
203
|
+
val = radio-button-selected(field, val, idx)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
place(
|
|
207
|
+
top + left,
|
|
208
|
+
dx: x,
|
|
209
|
+
dy: y,
|
|
210
|
+
render-field(field-type, val, w, h, field),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if debug {
|
|
214
|
+
place(
|
|
215
|
+
top + left,
|
|
216
|
+
dx: x,
|
|
217
|
+
dy: y,
|
|
218
|
+
debug-overlay(field-type, field.name, w, h),
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Zero-width fence to break PDF viewer text-selection grouping
|
|
223
|
+
place(top + left, dx: x, dy: y, text(size: 0.001pt, "\u{FEFF}"))
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#import "@local/quillmark-helper:0.1.0": data
|
|
2
|
+
#import "@local/typst-af4141:0.1.0": form
|
|
3
|
+
|
|
4
|
+
// Column order within each 7-field row group
|
|
5
|
+
#let col-keys = (
|
|
6
|
+
"date",
|
|
7
|
+
"action",
|
|
8
|
+
"written_grade",
|
|
9
|
+
"written_grade_date",
|
|
10
|
+
"positional_grade",
|
|
11
|
+
"positional_grade_date",
|
|
12
|
+
"auth_or_remarks",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// Build the values dictionary for the form
|
|
16
|
+
#let vals = (:)
|
|
17
|
+
|
|
18
|
+
// --- Admin fields (page 1 header) ---
|
|
19
|
+
#if "name" in data { vals.insert("commonforms_text_p1_1", data.name) }
|
|
20
|
+
#if "unit" in data { vals.insert("commonforms_text_p1_2", data.unit) }
|
|
21
|
+
#if "grade" in data { vals.insert("commonforms_text_p1_3", data.grade) }
|
|
22
|
+
#if "commanders_auth" in data { vals.insert("commonforms_text_p1_116", data.commanders_auth) }
|
|
23
|
+
|
|
24
|
+
// --- Experience table rows from cards ---
|
|
25
|
+
#{
|
|
26
|
+
let row = 0
|
|
27
|
+
for card in data.CARDS {
|
|
28
|
+
if card.CARD == "experience" {
|
|
29
|
+
for (col, key) in col-keys.enumerate() {
|
|
30
|
+
let value = card.at(key, default: "")
|
|
31
|
+
if value != "" {
|
|
32
|
+
let field-name = if row < 16 {
|
|
33
|
+
// Page 1: fields start at index 4, stride 7
|
|
34
|
+
"commonforms_text_p1_" + str(4 + row * 7 + col)
|
|
35
|
+
} else {
|
|
36
|
+
// Page 2: fields start at index 1, stride 7
|
|
37
|
+
"commonforms_text_p2_" + str(1 + (row - 16) * 7 + col)
|
|
38
|
+
}
|
|
39
|
+
vals.insert(field-name, value)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
row = row + 1
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Render the form with assembled values
|
|
48
|
+
#form(..vals)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#import "@local/quillmark-helper:0.1.0": data, eval-markup, parse-date
|
|
2
|
-
#import "@
|
|
2
|
+
#import "@local/tonguetoquill-cmu-letter:0.1.0": backmatter, frontmatter, mainmatter
|
|
3
3
|
|
|
4
4
|
#show: frontmatter.with(
|
|
5
5
|
wordmark: image("assets/cmu-wordmark.svg"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#import "@local/quillmark-helper:0.1.0": data, eval-markup, parse-date
|
|
2
|
-
#import "@
|
|
2
|
+
#import "@local/tonguetoquill-usaf-memo:1.0.0": backmatter, frontmatter, indorsement, mainmatter
|
|
3
3
|
|
|
4
4
|
// Frontmatter configuration
|
|
5
5
|
#show: frontmatter.with(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#import "@local/quillmark-helper:0.1.0": data, eval-markup, parse-date
|
|
2
|
-
#import "@
|
|
2
|
+
#import "@local/tonguetoquill-usaf-memo:2.0.0": backmatter, frontmatter, indorsement, mainmatter
|
|
3
3
|
|
|
4
4
|
// Frontmatter configuration
|
|
5
5
|
#show: frontmatter.with(
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
QUILL: af4141@0.1
|
|
3
|
+
# EDIT: Full name in Last, First, Middle Initial format
|
|
4
|
+
name: !fill "LAST, FIRST M."
|
|
5
|
+
# EDIT: Unit of assignment (e.g., "1 ACCS/DOT", "726 ACS/MOC")
|
|
6
|
+
unit: !fill "UNIT/SYMBOL"
|
|
7
|
+
# EDIT: Current grade or CCC level (e.g., "SSgt", "GS-12", "3")
|
|
8
|
+
grade: !fill "GRADE"
|
|
9
|
+
# EDIT: Commander's authentication entry — often a commander's name/signature block or "Verified by unit commander"
|
|
10
|
+
commanders_auth: !fill ""
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!-- ═══════════════════════════════════════════════════════════════
|
|
14
|
+
RECORD OF EXPERIENCE CARDS
|
|
15
|
+
Each card below becomes one row in the experience table.
|
|
16
|
+
Page 1 holds up to 16 rows; Page 2 holds up to 21 rows (37 max).
|
|
17
|
+
|
|
18
|
+
Required reportable actions include:
|
|
19
|
+
• Initial/subsequent duty assignment
|
|
20
|
+
• Written and/or positional upgrade evaluations
|
|
21
|
+
• Certification events (Mission Ready, Senior Director, etc.)
|
|
22
|
+
• Downgrade or decertification
|
|
23
|
+
• PCS/PCA departure
|
|
24
|
+
• Temporary duty of significant duration
|
|
25
|
+
• Any other commander-directed entry
|
|
26
|
+
|
|
27
|
+
Leave blank any columns that do not apply to a given entry.
|
|
28
|
+
═══════════════════════════════════════════════════════════════ -->
|
|
29
|
+
|
|
30
|
+
<!-- Example 1: Initial assignment to a unit -->
|
|
31
|
+
---
|
|
32
|
+
CARD: experience
|
|
33
|
+
date: "15 Jan 2025"
|
|
34
|
+
action: "Assigned to {:UNIT/SYMBOL:} as {:Duty Title:}"
|
|
35
|
+
written_grade: ""
|
|
36
|
+
written_grade_date: ""
|
|
37
|
+
positional_grade: ""
|
|
38
|
+
positional_grade_date: ""
|
|
39
|
+
auth_or_remarks: "Initial assignment"
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<!-- Example 2: Written upgrade evaluation -->
|
|
43
|
+
---
|
|
44
|
+
CARD: experience
|
|
45
|
+
date: "{:DD Mon YYYY:}"
|
|
46
|
+
action: "Completed written upgrade evaluation for {:position/level:}"
|
|
47
|
+
written_grade: "{:1–5:}"
|
|
48
|
+
written_grade_date: "{:DD Mon YYYY:}"
|
|
49
|
+
positional_grade: ""
|
|
50
|
+
positional_grade_date: ""
|
|
51
|
+
auth_or_remarks: "Per {:AFMAN 13-1CRCV1 or applicable directive:}"
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<!-- Example 3: Positional (live-environment) upgrade / certification -->
|
|
55
|
+
---
|
|
56
|
+
CARD: experience
|
|
57
|
+
date: "{:DD Mon YYYY:}"
|
|
58
|
+
action: "Certified {:Mission Ready / Senior Director / etc.:} — positional upgrade"
|
|
59
|
+
written_grade: ""
|
|
60
|
+
written_grade_date: ""
|
|
61
|
+
positional_grade: "{:1–5:}"
|
|
62
|
+
positional_grade_date: "{:DD Mon YYYY:}"
|
|
63
|
+
auth_or_remarks: "{:Evaluator name or directive:}"
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
<!-- Example 4: Combined written + positional upgrade (both columns filled) -->
|
|
67
|
+
---
|
|
68
|
+
CARD: experience
|
|
69
|
+
date: "{:DD Mon YYYY:}"
|
|
70
|
+
action: "Upgrade evaluation — written and positional"
|
|
71
|
+
written_grade: "{:1–5:}"
|
|
72
|
+
written_grade_date: "{:DD Mon YYYY:}"
|
|
73
|
+
positional_grade: "{:1–5:}"
|
|
74
|
+
positional_grade_date: "{:DD Mon YYYY:}"
|
|
75
|
+
auth_or_remarks: "{:Authentication / remarks:}"
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
<!-- Example 5: PCS departure — no grade columns needed -->
|
|
79
|
+
---
|
|
80
|
+
CARD: experience
|
|
81
|
+
date: "{:DD Mon YYYY:}"
|
|
82
|
+
action: "PCS to {:next unit/location:}"
|
|
83
|
+
written_grade: ""
|
|
84
|
+
written_grade_date: ""
|
|
85
|
+
positional_grade: ""
|
|
86
|
+
positional_grade_date: ""
|
|
87
|
+
auth_or_remarks: "Outprocessed {:UNIT/SYMBOL:}"
|
|
88
|
+
---
|
package/templates/loc.md
CHANGED
|
@@ -43,7 +43,7 @@ signature_block: SUBJECT FIRST M. LAST, RANK, USAF
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
45
|
<!-- EDIT: First Indorsement: Subject acknowledges receipt of the LOC -->
|
|
46
|
-
I acknowledge receipt and understanding of this letter on
|
|
46
|
+
I acknowledge receipt and understanding of this letter on {:date and time:}. I understand that I have 3 duty days from the date I received this letter to provide a response and that I must include in my response any comments or documents I wish to be considered concerning this Letter of Counseling.
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
CARD: indorsement
|
|
@@ -76,4 +76,4 @@ date: "Date: _____________"
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
78
|
<!-- EDIT: Fourth Indorsement: Subject acknowledges final decision -->
|
|
79
|
-
I acknowledge receipt of the final decision regarding disposition of this Letter of Counseling on
|
|
79
|
+
I acknowledge receipt of the final decision regarding disposition of this Letter of Counseling on {:date and time:}.
|
package/templates/templates.json
CHANGED
|
@@ -40,5 +40,11 @@
|
|
|
40
40
|
"description": "USAF special pass request memorandum template",
|
|
41
41
|
"file": "pass_request.md",
|
|
42
42
|
"production": true
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "AF Form 4141",
|
|
46
|
+
"description": "Individual's Record of Duties and Experience, Ground Environment Personnel",
|
|
47
|
+
"file": "af4141.md",
|
|
48
|
+
"production": true
|
|
43
49
|
}
|
|
44
50
|
]
|