@airmark/quiver 0.32.0 → 0.32.2
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 +4 -4
- package/quills/usaf_memo/0.3.0/Quill.yaml +25 -20
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/body.typ +94 -11
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +21 -11
- package/quills/usaf_memo/0.3.0/packages/tonguetoquill-usaf-memo/src/utils.typ +18 -11
- package/quills/usaf_memo/0.3.0/plate.typ +30 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airmark/quiver",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.2",
|
|
4
4
|
"description": "Airmark Quiver — Air Force and DAF document Quills for Quillmark.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"node": ">=24"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@quillmark/quiver": "0.
|
|
20
|
-
"@quillmark/wasm": "^0.
|
|
21
|
-
"quillkit": "0.5.
|
|
19
|
+
"@quillmark/quiver": "0.27.0",
|
|
20
|
+
"@quillmark/wasm": "^0.110.0",
|
|
21
|
+
"quillkit": "0.5.4"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "quillkit test",
|
|
@@ -15,6 +15,11 @@ main:
|
|
|
15
15
|
The first paragraph. Top-level paragraphs are auto-numbered; do not add manual numbering.
|
|
16
16
|
|
|
17
17
|
- Nested bullets are automatically lettered.
|
|
18
|
+
|
|
19
|
+
Lines that take no number, letter, or bullet — a roster of names, an address — go in a block quote, typeset as written. End a line with a backslash to break it.
|
|
20
|
+
|
|
21
|
+
> FIRST M. LAST, Maj, USAF\
|
|
22
|
+
> SECOND N. LAST, Capt, USAF
|
|
18
23
|
fields:
|
|
19
24
|
memo_for:
|
|
20
25
|
type: array
|
|
@@ -46,13 +51,21 @@ main:
|
|
|
46
51
|
description: "If recipients are on the same installation, use only the office symbol. For recipients on other installations, include the full mailing address to enable return correspondence. Omit entirely to produce a Memorandum for Record (no MEMO FROM line)."
|
|
47
52
|
|
|
48
53
|
subject:
|
|
49
|
-
type:
|
|
54
|
+
type: richtext
|
|
50
55
|
inline: true
|
|
51
56
|
example: Subject of the Memorandum
|
|
52
57
|
ui:
|
|
53
58
|
group: addressing
|
|
54
|
-
description: Be brief and clear. Capitalize the first letter of each word except articles, prepositions, and conjunctions. Include suspense dates in parentheses if applicable.
|
|
55
|
-
|
|
59
|
+
description: "Be brief and clear. Capitalize the first letter of each word except articles, prepositions, and conjunctions. Include suspense dates in parentheses if applicable. Italicize publication titles with *asterisks* (standard Markdown emphasis), as in the References field."
|
|
60
|
+
|
|
61
|
+
date:
|
|
62
|
+
type: date
|
|
63
|
+
default: ""
|
|
64
|
+
ui:
|
|
65
|
+
group: addressing
|
|
66
|
+
compact: true
|
|
67
|
+
description: YYYY-MM-DD. Leave blank to use today's date.
|
|
68
|
+
|
|
56
69
|
signature_block:
|
|
57
70
|
type: array
|
|
58
71
|
items:
|
|
@@ -195,14 +208,14 @@ main:
|
|
|
195
208
|
attachments:
|
|
196
209
|
type: array
|
|
197
210
|
items:
|
|
198
|
-
type:
|
|
211
|
+
type: richtext
|
|
199
212
|
inline: true
|
|
200
213
|
default: []
|
|
201
214
|
example:
|
|
202
215
|
- Attachment description, YYYY MMM DD
|
|
203
216
|
ui:
|
|
204
217
|
group: additional
|
|
205
|
-
description: List attachments in the order they are mentioned in the memo. Briefly describe each; do not use 'as stated' or abbreviations.
|
|
218
|
+
description: "List attachments in the order they are mentioned in the memo. Briefly describe each; do not use 'as stated' or abbreviations. Italicize publication titles with *asterisks* (standard Markdown emphasis), as in the References field."
|
|
206
219
|
|
|
207
220
|
memo_style:
|
|
208
221
|
type: enum
|
|
@@ -223,14 +236,6 @@ main:
|
|
|
223
236
|
compact: true
|
|
224
237
|
description: Body text size in points; decimals allowed (e.g. 11.5).
|
|
225
238
|
|
|
226
|
-
date:
|
|
227
|
-
type: date
|
|
228
|
-
default: ""
|
|
229
|
-
ui:
|
|
230
|
-
group: additional
|
|
231
|
-
compact: true
|
|
232
|
-
description: YYYY-MM-DD. Leave blank to use today's date.
|
|
233
|
-
|
|
234
239
|
card_kinds:
|
|
235
240
|
indorsement:
|
|
236
241
|
description: Chain of routing endorsements. Each endorsement block adds an official response or forwarding action to the original memo.
|
|
@@ -254,6 +259,13 @@ card_kinds:
|
|
|
254
259
|
group: addressing
|
|
255
260
|
compact: true
|
|
256
261
|
description: Office symbol or organization receiving the endorsed memo.
|
|
262
|
+
date:
|
|
263
|
+
type: date
|
|
264
|
+
default: ""
|
|
265
|
+
ui:
|
|
266
|
+
group: addressing
|
|
267
|
+
compact: true
|
|
268
|
+
description: "YYYY-MM-DD. Date the endorser signs/forwards the memo (per AFH 33-337 Ch. 14, this is distinct from the originating memo's date). Leave blank to render a fillable PDF text field for the endorser to type the date into when signing."
|
|
257
269
|
signature_block:
|
|
258
270
|
type: array
|
|
259
271
|
items:
|
|
@@ -288,10 +300,3 @@ card_kinds:
|
|
|
288
300
|
group: additional
|
|
289
301
|
compact: true
|
|
290
302
|
description: "Decision taken by the endorser, shown as an either/or line with the choice underlined and the rejected option struck out. Leave blank to hide the line entirely. The wording is automatic: the last indorsement is the approval authority and reads Approve/Disapprove, while every earlier one is a coordinating official and reads Concur/Nonconcur. Use 'undecided' to print the pair unmarked for the endorser to mark by hand."
|
|
291
|
-
date:
|
|
292
|
-
type: date
|
|
293
|
-
default: ""
|
|
294
|
-
ui:
|
|
295
|
-
group: additional
|
|
296
|
-
compact: true
|
|
297
|
-
description: "YYYY-MM-DD. Date the endorser signs/forwards the memo (per AFH 33-337 Ch. 14, this is distinct from the originating memo's date). Leave blank to render a fillable PDF text field for the endorser to type the date into when signing."
|
|
@@ -60,6 +60,25 @@
|
|
|
60
60
|
level-counts
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/// The horizontal offset at which a paragraph's *text* sits: its indent plus
|
|
64
|
+
/// the width of the number label that precedes it.
|
|
65
|
+
///
|
|
66
|
+
/// This is where a later block of the same paragraph — a continuation, a block
|
|
67
|
+
/// quote — lines up, so the two callers share one measurement rather than each
|
|
68
|
+
/// spelling it. A top-level paragraph carries no indent and its label is the
|
|
69
|
+
/// left margin itself, so the offset is zero.
|
|
70
|
+
///
|
|
71
|
+
/// - level (int): Nesting level (0-based)
|
|
72
|
+
/// - level-counts (dictionary): Current counter values per level
|
|
73
|
+
/// - indent-fn (function): `(level, level-counts) -> length`
|
|
74
|
+
/// -> length
|
|
75
|
+
#let paragraph-text-offset(level, level-counts, indent-fn) = {
|
|
76
|
+
if level <= 0 { return 0pt }
|
|
77
|
+
let current-value = level-counts.at(str(level), default: 1)
|
|
78
|
+
let number-text = numbering(get-paragraph-numbering-format(level), current-value)
|
|
79
|
+
indent-fn(level, level-counts) + measure([#number-text#" "]).width
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
/// Formats a paragraph (or continuation) with a given indent strategy.
|
|
64
83
|
///
|
|
65
84
|
/// - body (content): Paragraph content
|
|
@@ -69,12 +88,13 @@
|
|
|
69
88
|
/// - continuation (bool): If true, adds number-label width to alignment
|
|
70
89
|
/// -> content
|
|
71
90
|
#let format-par(body, level, level-counts, indent-fn, continuation: false) = {
|
|
72
|
-
let indent-width = indent-fn(level, level-counts)
|
|
73
91
|
if continuation {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
// Offset the first line only, which is how the numbered paragraph this
|
|
93
|
+
// block continues is set: its own wrapped lines return to the left margin
|
|
94
|
+
// too, so the two read as one paragraph.
|
|
95
|
+
[#h(paragraph-text-offset(level, level-counts, indent-fn))#body]
|
|
77
96
|
} else {
|
|
97
|
+
let indent-width = indent-fn(level, level-counts)
|
|
78
98
|
let current-value = level-counts.at(str(level), default: 1)
|
|
79
99
|
let number-text = numbering(get-paragraph-numbering-format(level), current-value)
|
|
80
100
|
[#h(indent-width)#number-text#" "#body]
|
|
@@ -145,6 +165,38 @@
|
|
|
145
165
|
})
|
|
146
166
|
t
|
|
147
167
|
}
|
|
168
|
+
// Collect block quotes — captured as-is, with paragraph processing kept
|
|
169
|
+
// out of them. AFH 33-337 numbers paragraphs and letters subparagraphs, and
|
|
170
|
+
// a body sometimes has to hold lines that are neither: a roster of names, a
|
|
171
|
+
// quoted passage, an address. The block quote is where an author says so —
|
|
172
|
+
// its content is emitted verbatim, so nothing inside it takes a number, a
|
|
173
|
+
// letter, or a bullet.
|
|
174
|
+
//
|
|
175
|
+
// Returning `none` rather than the quote is what makes that true: the
|
|
176
|
+
// quote's own paragraphs are never laid out here, so the `show par` above
|
|
177
|
+
// never sees them and cannot number them. The buffered body is emitted in
|
|
178
|
+
// the second pass, where these show rules are out of scope. Typst's own
|
|
179
|
+
// block-quote framing (padding, attribution) is dropped with the element;
|
|
180
|
+
// what reaches the page is the author's lines and nothing else.
|
|
181
|
+
//
|
|
182
|
+
// The nesting level rides along so a quote inside a list item lines up with
|
|
183
|
+
// that item's text (see the emission below). @quillmark/wasm 0.109.0 is
|
|
184
|
+
// what makes that level trustworthy: before it, a container inside a list
|
|
185
|
+
// item opened at column 0 and terminated the list, so a quote's siblings
|
|
186
|
+
// came back as top-level paragraphs (#123). It now nests, and this quill
|
|
187
|
+
// stopped declining the construct with it.
|
|
188
|
+
show quote.where(block: true): q => context {
|
|
189
|
+
let nest_level = NEST_DOWN.get().at(0) - NEST_UP.get().at(0)
|
|
190
|
+
PAR_BUFFER.update(pars => {
|
|
191
|
+
pars.push((
|
|
192
|
+
content: q.body,
|
|
193
|
+
nest_level: nest_level,
|
|
194
|
+
kind: "quote",
|
|
195
|
+
))
|
|
196
|
+
pars
|
|
197
|
+
})
|
|
198
|
+
none
|
|
199
|
+
}
|
|
148
200
|
{
|
|
149
201
|
show heading: h => {
|
|
150
202
|
IS_HEADING.update(true)
|
|
@@ -194,9 +246,9 @@
|
|
|
194
246
|
// Second pass: consume par buffer
|
|
195
247
|
//
|
|
196
248
|
// PAR_BUFFER item dictionary layout:
|
|
197
|
-
// item.content — the paragraph body or
|
|
249
|
+
// item.content — the paragraph body, table element, or block-quote body
|
|
198
250
|
// item.nest_level — nesting depth (−1 for tables)
|
|
199
|
-
// item.kind — "par", "heading", "table", or "
|
|
251
|
+
// item.kind — "par", "heading", "table", "continuation", or "quote"
|
|
200
252
|
context {
|
|
201
253
|
let heading_buffer = none
|
|
202
254
|
let heading_level = 0
|
|
@@ -230,13 +282,15 @@
|
|
|
230
282
|
// A buffered heading runs into this element only when the two belong to
|
|
231
283
|
// the same item: a later block of this list item ("continuation"), or,
|
|
232
284
|
// at top level, the next paragraph. The first block of the *next* item,
|
|
233
|
-
// a table (nest_level −1, so the level test alone excludes it),
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
285
|
+
// a table (nest_level −1, so the level test alone excludes it), a block
|
|
286
|
+
// quote (verbatim: a heading prepended to it would be ink the author did
|
|
287
|
+
// not put inside the quote), and another heading all fail the test, and
|
|
288
|
+
// each would otherwise carry the heading's text somewhere it was not
|
|
289
|
+
// authored. Those emit the heading on its own line, the treatment a
|
|
290
|
+
// heading before a table takes.
|
|
237
291
|
if heading_buffer != none {
|
|
238
292
|
let runs_in = (
|
|
239
|
-
kind
|
|
293
|
+
kind not in ("heading", "quote")
|
|
240
294
|
and item.nest_level == heading_level
|
|
241
295
|
and (heading_level == 0 or kind == "continuation")
|
|
242
296
|
)
|
|
@@ -269,6 +323,35 @@
|
|
|
269
323
|
let final_par = {
|
|
270
324
|
if kind == "table" {
|
|
271
325
|
render-memo-table(item_content)
|
|
326
|
+
} else if kind == "quote" {
|
|
327
|
+
// A block quote is the body's unlabeled block: no number, no letter,
|
|
328
|
+
// no bullet — the author's lines as written. It is placed, not
|
|
329
|
+
// processed, which is what lets a memorandum carry a roster of names,
|
|
330
|
+
// an address, or a quoted passage without AFH 33-337 numbering
|
|
331
|
+
// claiming the first line of it.
|
|
332
|
+
//
|
|
333
|
+
// Two things are imposed, and both serve that reading.
|
|
334
|
+
//
|
|
335
|
+
// Where the block starts: at the offset where the text of the
|
|
336
|
+
// paragraph it sits in starts, so a quote inside a subparagraph hangs
|
|
337
|
+
// under that subparagraph's text instead of falling back to the left
|
|
338
|
+
// margin. `pad` shifts the whole block, where a continuation takes a
|
|
339
|
+
// first-line `h(..)`: a quote is kept for its line structure, and
|
|
340
|
+
// lines that wrapped back to the margin would read as a block other
|
|
341
|
+
// than the one authored. Top level offsets by 0pt — flush at the
|
|
342
|
+
// margin, where an unnumbered line belongs.
|
|
343
|
+
//
|
|
344
|
+
// How its own paragraphs are spaced: `par.leading` and `par.spacing`
|
|
345
|
+
// are both half an em in a memorandum, so a paragraph break inside
|
|
346
|
+
// the quote would land on the page as an ordinary line break and read
|
|
347
|
+
// as one block where the author wrote two. A blank line — what the
|
|
348
|
+
// body puts between its own paragraphs — keeps that break visible.
|
|
349
|
+
let quoted = {
|
|
350
|
+
set par(spacing: spacing.line + line-stride())
|
|
351
|
+
item_content
|
|
352
|
+
}
|
|
353
|
+
let offset = paragraph-text-offset(nest_level, level-counts, indent-fn)
|
|
354
|
+
if offset == 0pt { quoted } else { pad(left: offset, quoted) }
|
|
272
355
|
} else if kind == "continuation" {
|
|
273
356
|
// Continuation block within a multi-block list item:
|
|
274
357
|
// indent to align with preceding numbered paragraph's text, no new number.
|
|
@@ -264,23 +264,33 @@
|
|
|
264
264
|
// but a gap left outside can be consumed at the foot of one page while
|
|
265
265
|
// the block starts at the top margin of the next, which is also what put
|
|
266
266
|
// the signing field off the page (see below).
|
|
267
|
-
#v(gap)
|
|
268
267
|
#if signing-field != none {
|
|
269
268
|
// The signing field covers those blank lines — where a signature is
|
|
270
|
-
// actually written — so it is placed over the gap.
|
|
271
|
-
// the
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// the
|
|
276
|
-
//
|
|
277
|
-
//
|
|
269
|
+
// actually written — so it is placed over the gap. It is placed BEFORE
|
|
270
|
+
// the gap is emitted: `place` anchors at the current flow position, so
|
|
271
|
+
// placing it after `v(gap)` anchors the box at the first name line and
|
|
272
|
+
// paints it down over the printed signature block. Anchored here it
|
|
273
|
+
// still travels with the block onto whatever page the block lands on,
|
|
274
|
+
// since the gap is inside the unbreakable block rather than ahead of
|
|
275
|
+
// it.
|
|
276
|
+
//
|
|
277
|
+
// The widget keeps its own size (the helper's default is 50pt tall and
|
|
278
|
+
// it positions itself, so an `align` around it does nothing) and is
|
|
279
|
+
// offset to the BOTTOM of the gap: it always ends where the printed
|
|
280
|
+
// name begins, and whatever the gap has beyond the widget's height
|
|
281
|
+
// stays clear between the body text and the widget's frame.
|
|
282
|
+
let widget-height = {
|
|
283
|
+
let h = measure(signing-field).height
|
|
284
|
+
if h > 0pt { h } else { 50pt }
|
|
285
|
+
}
|
|
286
|
+
let drop = gap - widget-height - 3pt
|
|
278
287
|
place(
|
|
279
288
|
dx: left-pad,
|
|
280
|
-
dy: 0pt,
|
|
281
|
-
box(width: body-width - left-pad, height:
|
|
289
|
+
dy: if drop > 0pt { drop } else { 0pt },
|
|
290
|
+
box(width: body-width - left-pad, height: widget-height, signing-field),
|
|
282
291
|
)
|
|
283
292
|
}
|
|
293
|
+
#v(gap)
|
|
284
294
|
#align(left)[
|
|
285
295
|
#pad(left: left-pad)[
|
|
286
296
|
#text(hyphenate: false)[
|
|
@@ -18,26 +18,33 @@
|
|
|
18
18
|
// Value is a `length` set once in `frontmatter`.
|
|
19
19
|
#let LINE_STRIDE = state("LINE_STRIDE")
|
|
20
20
|
|
|
21
|
+
/// The height one wrapped line of body text occupies.
|
|
22
|
+
///
|
|
23
|
+
/// Read from `LINE_STRIDE` (cached in `frontmatter`), falling back to an inline
|
|
24
|
+
/// measurement when the cache is unset. Reads state, so callers must already be
|
|
25
|
+
/// in a `context`.
|
|
26
|
+
///
|
|
27
|
+
/// -> length
|
|
28
|
+
#let line-stride() = {
|
|
29
|
+
let stride = LINE_STRIDE.get()
|
|
30
|
+
if stride == none {
|
|
31
|
+
let one-line = measure(par(spacing: 0pt)[x]).height
|
|
32
|
+
stride = measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line
|
|
33
|
+
}
|
|
34
|
+
stride
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
/// Creates vertical spacing equivalent to multiple blank lines.
|
|
22
38
|
///
|
|
23
39
|
/// Adds `count` wrapped-line strides on top of the natural inter-paragraph
|
|
24
40
|
/// gap, so a blank line occupies exactly the same vertical space as a line
|
|
25
|
-
/// produced by natural paragraph wrapping.
|
|
26
|
-
/// `LINE_STRIDE` (cached in `frontmatter`) and falls back to an inline
|
|
27
|
-
/// measurement when the cache is unset.
|
|
41
|
+
/// produced by natural paragraph wrapping.
|
|
28
42
|
///
|
|
29
43
|
/// - count (int): Number of blank lines to create
|
|
30
44
|
/// -> content
|
|
31
45
|
#let blank-lines(count) = {
|
|
32
46
|
if count <= 0 { return }
|
|
33
|
-
context {
|
|
34
|
-
let stride = LINE_STRIDE.get()
|
|
35
|
-
if stride == none {
|
|
36
|
-
let one-line = measure(par(spacing: 0pt)[x]).height
|
|
37
|
-
stride = measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line
|
|
38
|
-
}
|
|
39
|
-
v(stride * count)
|
|
40
|
-
}
|
|
47
|
+
context { v(line-stride() * count) }
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/// Creates vertical spacing equivalent to one blank line.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
#import "@local/quillmark-helper:0.1.0":
|
|
1
|
+
#import "@local/quillmark-helper:0.1.0": (
|
|
2
|
+
data, display, field-region, form-field, signature-field,
|
|
3
|
+
)
|
|
2
4
|
#import "@local/tonguetoquill-usaf-memo:4.0.0": (
|
|
3
5
|
backmatter, date-pattern, frontmatter, indorsement, mainmatter,
|
|
4
6
|
)
|
|
@@ -41,9 +43,24 @@
|
|
|
41
43
|
// but its ink would be born inside the package and carry no schema address.
|
|
42
44
|
// `display` places the field's *content* projection instead: the glyphs are
|
|
43
45
|
// born in the generated helper, so the memo date stays click-to-edit however
|
|
44
|
-
// deep the package formats it.
|
|
45
|
-
//
|
|
46
|
-
date
|
|
46
|
+
// deep the package formats it.
|
|
47
|
+
//
|
|
48
|
+
// A blank date means today's, and the plate stamps it rather than falling
|
|
49
|
+
// through to `frontmatter`'s own `datetime.today()`: package-born ink carries
|
|
50
|
+
// no address, so the one date a memo never types would be the one date a
|
|
51
|
+
// preview cannot click. `field-region` claims that ink for the field instead.
|
|
52
|
+
//
|
|
53
|
+
// The stamp is markup, not the bare `str` `.display()` returns: a `str` off a
|
|
54
|
+
// function call carries no source position, and ink with none is unclaimable.
|
|
55
|
+
date: {
|
|
56
|
+
let pattern = date-pattern(memo-style: memo_style)
|
|
57
|
+
let authored = display("date", pattern)
|
|
58
|
+
if authored != none {
|
|
59
|
+
authored
|
|
60
|
+
} else {
|
|
61
|
+
field-region("date", [#datetime.today().display(pattern)])
|
|
62
|
+
}
|
|
63
|
+
},
|
|
47
64
|
|
|
48
65
|
// Receiver information
|
|
49
66
|
memo_for: data.memo_for,
|
|
@@ -102,7 +119,14 @@
|
|
|
102
119
|
#backmatter(
|
|
103
120
|
// Signature block
|
|
104
121
|
signature_block: data.signature_block,
|
|
105
|
-
|
|
122
|
+
// The widget sits at the bottom of AFH 33-337's four blank lines and is
|
|
123
|
+
// sized to two and a half of them, so the line and a half above it stays
|
|
124
|
+
// clear of the body text without moving the block off the fifth line.
|
|
125
|
+
signing_field: signature-field(
|
|
126
|
+
"Signature",
|
|
127
|
+
field: "signature_block",
|
|
128
|
+
height: body_font_size * 2.5,
|
|
129
|
+
),
|
|
106
130
|
|
|
107
131
|
// Optional cc
|
|
108
132
|
..if data.cc.len() > 0 { (cc: data.cc) },
|
|
@@ -167,6 +191,7 @@
|
|
|
167
191
|
signing_field: signature-field(
|
|
168
192
|
"Ind_" + str(i) + "_Signature",
|
|
169
193
|
field: card.at("$path") + "signature_block",
|
|
194
|
+
height: body_font_size * 2.5,
|
|
170
195
|
),
|
|
171
196
|
..if card.format != "" { (format: card.format) },
|
|
172
197
|
date: resolved_date,
|