@tonguetoquill/collection 0.2.5 → 0.2.7
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
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
// Formalizer Engine – rendering engine
|
|
2
2
|
// Renders pixel-perfect PDF form replicas from a PyMuPDF-extracted schema.
|
|
3
3
|
|
|
4
|
+
/// Global configuration for text rendering.
|
|
5
|
+
/// Adjust these to change the overall form text appearance.
|
|
6
|
+
#let FORM_MAX_TEXT_SIZE = 14pt
|
|
7
|
+
#let FORM_MIN_TEXT_SIZE = 6pt
|
|
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.25em)
|
|
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
|
+
|
|
4
75
|
/// Render a single field's content overlay.
|
|
5
76
|
///
|
|
6
77
|
/// - field-type (str): normalised lowercase type
|
|
@@ -11,21 +82,7 @@
|
|
|
11
82
|
#let render-field(field-type, value, width, height, field) = {
|
|
12
83
|
if field-type == "text" {
|
|
13
84
|
if value != none and str(value) != "" {
|
|
14
|
-
|
|
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
|
-
}
|
|
85
|
+
render-text-field(str(value), width, height, 1.5pt, 1pt)
|
|
29
86
|
}
|
|
30
87
|
} else if field-type == "checkbox" {
|
|
31
88
|
if value == true {
|
|
@@ -57,21 +114,7 @@
|
|
|
57
114
|
}
|
|
58
115
|
}
|
|
59
116
|
if display != "" {
|
|
60
|
-
|
|
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
|
-
}
|
|
117
|
+
render-text-field(display, width, height, 2pt, 1pt)
|
|
75
118
|
}
|
|
76
119
|
}
|
|
77
120
|
}
|