@jasy/pdf 1.0.0-alpha.7 → 1.0.0-alpha.9
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/README.md +11 -10
- package/dist/api/content.d.ts +5 -4
- package/dist/api/content.js +13 -7
- package/dist/api/descriptor.js +23 -0
- package/dist/api/dimension.d.ts +56 -0
- package/dist/api/dimension.js +27 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +134 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +3 -0
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +18 -7
- package/dist/api/layout.d.ts +20 -6
- package/dist/api/layout.js +31 -6
- package/dist/api/page-builder.d.ts +8 -0
- package/dist/api/page-builder.js +12 -0
- package/dist/api/structure.d.ts +11 -0
- package/dist/api/structure.js +17 -4
- package/dist/api/text.d.ts +42 -6
- package/dist/api/text.js +29 -5
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +16 -0
- package/dist/crypto/security-handler.js +26 -0
- package/dist/edit/acroform-reader.d.ts +70 -0
- package/dist/edit/acroform-reader.js +126 -0
- package/dist/edit/appearance.d.ts +47 -0
- package/dist/edit/appearance.js +171 -0
- package/dist/edit/document.d.ts +145 -0
- package/dist/edit/document.js +642 -0
- package/dist/edit/fill.d.ts +65 -0
- package/dist/edit/fill.js +424 -0
- package/dist/edit/flatten.d.ts +54 -0
- package/dist/edit/flatten.js +462 -0
- package/dist/edit/index.d.ts +20 -0
- package/dist/edit/index.js +18 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +68 -0
- package/dist/edit/objects.js +83 -0
- package/dist/edit/writer.d.ts +60 -0
- package/dist/edit/writer.js +283 -0
- package/dist/elements/container-element.d.ts +21 -1
- package/dist/elements/container-element.js +25 -10
- package/dist/elements/forms/checkbox-element.d.ts +51 -0
- package/dist/elements/forms/checkbox-element.js +48 -0
- package/dist/elements/forms/choice-element.d.ts +53 -0
- package/dist/elements/forms/choice-element.js +59 -0
- package/dist/elements/forms/push-button-element.d.ts +56 -0
- package/dist/elements/forms/push-button-element.js +49 -0
- package/dist/elements/forms/radio-element.d.ts +52 -0
- package/dist/elements/forms/radio-element.js +49 -0
- package/dist/elements/forms/signature-element.d.ts +54 -0
- package/dist/elements/forms/signature-element.js +47 -0
- package/dist/elements/forms/text-field-element.d.ts +61 -0
- package/dist/elements/forms/text-field-element.js +54 -0
- package/dist/elements/image-element.d.ts +18 -4
- package/dist/elements/image-element.js +27 -12
- package/dist/elements/index.d.ts +6 -0
- package/dist/elements/index.js +6 -0
- package/dist/elements/layout/expanded-element.d.ts +3 -1
- package/dist/elements/layout/expanded-element.js +12 -3
- package/dist/elements/layout/padding-element.d.ts +4 -2
- package/dist/elements/layout/padding-element.js +4 -2
- package/dist/elements/layout/page-builder-element.js +2 -0
- package/dist/elements/pdf-element.d.ts +59 -1
- package/dist/elements/pdf-element.js +62 -1
- package/dist/elements/rectangle-element.d.ts +20 -6
- package/dist/elements/rectangle-element.js +25 -12
- package/dist/elements/row-element.d.ts +24 -1
- package/dist/elements/row-element.js +25 -10
- package/dist/elements/text-element.d.ts +60 -1
- package/dist/elements/text-element.js +132 -33
- package/dist/forms/acroform.d.ts +63 -0
- package/dist/forms/acroform.js +427 -0
- package/dist/forms/appearance.d.ts +58 -0
- package/dist/forms/appearance.js +204 -0
- package/dist/forms/field.d.ts +164 -0
- package/dist/forms/field.js +1 -0
- package/dist/forms/pdf.d.ts +19 -0
- package/dist/forms/pdf.js +28 -0
- package/dist/ir/display-list.d.ts +40 -7
- package/dist/ir/display-list.js +8 -1
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +53 -0
- package/dist/layout/box-constraints.js +72 -0
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/image-renderer.js +2 -1
- package/dist/renderer/page-renderer.js +8 -2
- package/dist/renderer/pdf-backend.d.ts +6 -0
- package/dist/renderer/pdf-backend.js +86 -31
- package/dist/renderer/pdf-document-renderer.js +8 -1
- package/dist/renderer/pdf-renderer.js +19 -1
- package/dist/renderer/rectangle-renderer.js +8 -2
- package/dist/renderer/text-renderer.d.ts +2 -2
- package/dist/renderer/text-renderer.js +150 -42
- package/dist/text/advance.js +5 -2
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/line-breaker.d.ts +35 -1
- package/dist/text/line-breaker.js +76 -16
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +67 -0
- package/dist/text/text-style.d.ts +25 -0
- package/dist/text/text-style.js +44 -1
- package/dist/utils/dest-registry.d.ts +5 -2
- package/dist/utils/dest-registry.js +5 -5
- package/dist/utils/flex-layout.d.ts +24 -4
- package/dist/utils/flex-layout.js +169 -7
- package/dist/utils/font-metrics.d.ts +5 -0
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/outline.js +1 -2
- package/dist/utils/pdf-object-manager.d.ts +39 -0
- package/dist/utils/pdf-object-manager.js +166 -15
- package/dist/utils/struct-tree.js +2 -3
- package/dist/utils/ttf-parser.d.ts +6 -0
- package/dist/utils/ttf-parser.js +19 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +10 -0
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/validators/element-validator.js +14 -3
- package/package.json +10 -2
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { escPdf, num2, pdfColor } from "./pdf.js";
|
|
2
|
+
import { wrapStringIntoLines } from "../text/line-breaker.js";
|
|
3
|
+
// Appearance streams (/AP): the actual drawn content of a field's box, in the field's OWN coordinate
|
|
4
|
+
// space (bottom-left origin, [0 0 w h]). Shared between CREATE (bake the field's look now) and, later,
|
|
5
|
+
// FILL-existing (regenerate a field's look after setting its value). This is why it is its own module.
|
|
6
|
+
/** Draw the field box: background fill + border stroke, inset so the stroke stays inside the BBox. */
|
|
7
|
+
function box(w, h, style) {
|
|
8
|
+
const ops = [];
|
|
9
|
+
if (style.background)
|
|
10
|
+
ops.push(`${pdfColor(style.background)} rg 0 0 ${num2(w)} ${num2(h)} re f`);
|
|
11
|
+
if (style.border && style.borderWidth > 0) {
|
|
12
|
+
const bw = style.borderWidth;
|
|
13
|
+
const i = bw / 2;
|
|
14
|
+
ops.push(`${pdfColor(style.border)} RG ${num2(bw)} w ${num2(i)} ${num2(i)} ${num2(w - bw)} ${num2(h - bw)} re S`);
|
|
15
|
+
}
|
|
16
|
+
return ops.join("\n");
|
|
17
|
+
}
|
|
18
|
+
/** The unchecked appearance: just the box. */
|
|
19
|
+
export function checkboxOff(w, h, style) {
|
|
20
|
+
return box(w, h, style);
|
|
21
|
+
}
|
|
22
|
+
/** The checked appearance: the box plus a vector checkmark (font-free, so it renders everywhere). */
|
|
23
|
+
export function checkboxOn(w, h, style) {
|
|
24
|
+
const lw = Math.max(1, Math.min(w, h) * 0.12);
|
|
25
|
+
// A tick: down-stroke to the low point, then up-stroke to the high point. Round caps/joins (2 J / 1 j).
|
|
26
|
+
const p = (fx, fy) => `${num2(fx * w)} ${num2(fy * h)}`;
|
|
27
|
+
const tick = `${pdfColor(style.color)} RG ${num2(lw)} w 2 J 1 j ` +
|
|
28
|
+
`${p(0.22, 0.52)} m ${p(0.42, 0.3)} l ${p(0.78, 0.74)} l S`;
|
|
29
|
+
return `${box(w, h, style)}\n${tick}`;
|
|
30
|
+
}
|
|
31
|
+
// A circle path (4 cubic-bezier arcs, kappa = 0.5523) around (cx, cy) with radius r. Not stroked/filled -
|
|
32
|
+
// the caller appends `S` (stroke) or `f` (fill).
|
|
33
|
+
function circlePath(cx, cy, r) {
|
|
34
|
+
const k = 0.5523 * r;
|
|
35
|
+
const n = num2;
|
|
36
|
+
return (`${n(cx + r)} ${n(cy)} m ` +
|
|
37
|
+
`${n(cx + r)} ${n(cy + k)} ${n(cx + k)} ${n(cy + r)} ${n(cx)} ${n(cy + r)} c ` +
|
|
38
|
+
`${n(cx - k)} ${n(cy + r)} ${n(cx - r)} ${n(cy + k)} ${n(cx - r)} ${n(cy)} c ` +
|
|
39
|
+
`${n(cx - r)} ${n(cy - k)} ${n(cx - k)} ${n(cy - r)} ${n(cx)} ${n(cy - r)} c ` +
|
|
40
|
+
`${n(cx + k)} ${n(cy - r)} ${n(cx + r)} ${n(cy - k)} ${n(cx + r)} ${n(cy)} c`);
|
|
41
|
+
}
|
|
42
|
+
/** The ring of a radio button (the outline), in [0 0 w h]. */
|
|
43
|
+
function radioRing(w, h, style) {
|
|
44
|
+
const r = Math.min(w, h) / 2;
|
|
45
|
+
const bw = style.borderWidth > 0 ? style.borderWidth : 1;
|
|
46
|
+
const ops = [];
|
|
47
|
+
if (style.background)
|
|
48
|
+
ops.push(`${pdfColor(style.background)} rg ${circlePath(w / 2, h / 2, r)} f`);
|
|
49
|
+
const stroke = style.border ?? style.color;
|
|
50
|
+
ops.push(`${pdfColor(stroke)} RG ${num2(bw)} w ${circlePath(w / 2, h / 2, r - bw / 2)} S`);
|
|
51
|
+
return ops.join("\n");
|
|
52
|
+
}
|
|
53
|
+
/** An unselected radio button: just the ring. */
|
|
54
|
+
export function radioOff(w, h, style) {
|
|
55
|
+
return radioRing(w, h, style);
|
|
56
|
+
}
|
|
57
|
+
/** A selected radio button: the ring plus a filled inner dot. */
|
|
58
|
+
export function radioOn(w, h, style) {
|
|
59
|
+
const r = Math.min(w, h) / 2;
|
|
60
|
+
const dot = `${pdfColor(style.color)} rg ${circlePath(w / 2, h / 2, r * 0.45)} f`;
|
|
61
|
+
return `${radioRing(w, h, style)}\n${dot}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A push button's face: the box plus its caption, centred. The caller measures the caption (this module
|
|
65
|
+
* stays free of font metrics and the writer) and passes `captionWidth` in points and `capHeight` as an em
|
|
66
|
+
* fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `fontRes` is
|
|
67
|
+
* the font's resource name inside the appearance stream's own /Resources.
|
|
68
|
+
*/
|
|
69
|
+
export function pushButtonFace(w, h, style, caption, captionWidth, capHeight, fontRes, size) {
|
|
70
|
+
// Baseline placed so the CAPITALS sit optically centred in the box, not the em square.
|
|
71
|
+
const y = (h - capHeight * size) / 2;
|
|
72
|
+
const caption_ = caption ? centredText(w, h, style, caption, captionWidth, y, fontRes, size) : "";
|
|
73
|
+
return `${box(w, h, style)}\n${caption_}`;
|
|
74
|
+
}
|
|
75
|
+
/** One centred, clipped text run inside a field box. Clipping means an over-long caption is cut at the
|
|
76
|
+
* border instead of bleeding across the page. Shared by the push button and the signature placeholder. */
|
|
77
|
+
function centredText(w, h, style, text, textWidth, baselineY, fontRes, size) {
|
|
78
|
+
return (`q 0 0 ${num2(w)} ${num2(h)} re W n ` +
|
|
79
|
+
`BT /${fontRes} ${num2(size)} Tf ${pdfColor(style.color)} rg ` +
|
|
80
|
+
`${num2((w - textWidth) / 2)} ${num2(baselineY)} Td (${escPdf(text)}) Tj ET Q`);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* An EMPTY signature field: the box, a signing rule across the lower third, and an optional hint centred
|
|
84
|
+
* just above it. This is the placeholder look - once someone actually signs, the signing tool replaces
|
|
85
|
+
* this appearance with its own.
|
|
86
|
+
*/
|
|
87
|
+
export function signatureFace(w, h, style, label, labelWidth, fontRes, size) {
|
|
88
|
+
const ruleY = h * 0.3;
|
|
89
|
+
const inset = w * 0.08;
|
|
90
|
+
const stroke = style.border ?? style.color;
|
|
91
|
+
const rule = `${pdfColor(stroke)} RG ${num2(Math.max(0.5, style.borderWidth * 0.75))} w ` +
|
|
92
|
+
`${num2(inset)} ${num2(ruleY)} m ${num2(w - inset)} ${num2(ruleY)} l S`;
|
|
93
|
+
const hint = label ? centredText(w, h, style, label, labelWidth, ruleY + 6, fontRes, size) : "";
|
|
94
|
+
return `${box(w, h, style)}\n${rule}\n${hint}`;
|
|
95
|
+
}
|
|
96
|
+
// Horizontal padding inside a field box, on top of the border - the same small inset Acrobat leaves so
|
|
97
|
+
// the value does not touch the frame.
|
|
98
|
+
const FIELD_PAD = 2;
|
|
99
|
+
// A list box highlights its selected rows. Viewers each pick their own colour; once we bake the
|
|
100
|
+
// appearance WE decide, and this light blue is the familiar selection tint.
|
|
101
|
+
const SELECTION_TINT = "0.60 0.75 0.95";
|
|
102
|
+
/** One pre-measured line of field text. The caller owns wrapping + measuring, so this module stays free
|
|
103
|
+
* of font metrics (and can be reused verbatim when filling an existing PDF). */
|
|
104
|
+
/**
|
|
105
|
+
* A field value split into the lines it is DRAWN as.
|
|
106
|
+
*
|
|
107
|
+
* An explicit line break in the value is a line break, and only then does width wrapping apply within
|
|
108
|
+
* each paragraph. Wrapping on width alone ran the paragraphs together - a multi-line field showed
|
|
109
|
+
* "first lineSecond line" - and it did so on BOTH paths, creating a field and filling one, which is why
|
|
110
|
+
* this lives here rather than in either of them.
|
|
111
|
+
*/
|
|
112
|
+
export function wrapFieldValue(value, fontFamily, fontSize, fontStyle, maxWidth, metrics, multiline) {
|
|
113
|
+
if (!multiline)
|
|
114
|
+
return [value.replace(/\r\n?|\n/g, " ")];
|
|
115
|
+
// CR, LF and CRLF all mean one break (PDF 7.3.4.2 says the same about a literal string).
|
|
116
|
+
return value
|
|
117
|
+
.split(/\r\n?|\n/)
|
|
118
|
+
.flatMap((para) => para === ""
|
|
119
|
+
? [""]
|
|
120
|
+
: wrapStringIntoLines(para, fontFamily, fontSize, fontStyle, maxWidth, metrics));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The size a field draws its value at. `0` in `/DA` is the PDF convention for "auto, fit the box", and
|
|
124
|
+
* both paths - creating a field and filling one - have to resolve it the SAME way, or the same field
|
|
125
|
+
* comes out at two different sizes depending on which one touched it last.
|
|
126
|
+
*/
|
|
127
|
+
export function fieldDrawSize(fontSize, height, borderWidth, capHeight) {
|
|
128
|
+
if (fontSize > 0)
|
|
129
|
+
return fontSize;
|
|
130
|
+
const available = height - 2 * (2 + borderWidth);
|
|
131
|
+
return Math.max(4, Math.min(12, (available * 0.7) / (capHeight || 0.7)));
|
|
132
|
+
}
|
|
133
|
+
/** Where a line of `width` starts, given the box, its padding and the requested alignment. */
|
|
134
|
+
function alignX(boxWidth, pad, width, align = "left") {
|
|
135
|
+
if (align === "center")
|
|
136
|
+
return (boxWidth - width) / 2;
|
|
137
|
+
if (align === "right")
|
|
138
|
+
return boxWidth - pad - width;
|
|
139
|
+
return pad;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `BT … ET` for absolutely-placed lines. `Tm` (not `Td`) so each line's position is independent.
|
|
143
|
+
*
|
|
144
|
+
* Field text is drawn UNKERNED (a plain `Tj`, never a `TJ` array), even though ordinary document text
|
|
145
|
+
* kerns by default. That is deliberate: the moment someone clicks into a field, the viewer regenerates
|
|
146
|
+
* this appearance from `/DA` - and viewers do not kern field text. Kerning here would make the value
|
|
147
|
+
* visibly shift on first click. The measurement matches: `getStringWidth` is the plain glyph sum, so
|
|
148
|
+
* measured still equals drawn.
|
|
149
|
+
*/
|
|
150
|
+
function textBlock(lines, style, size, fontRes) {
|
|
151
|
+
const runs = lines
|
|
152
|
+
.map((l) => `1 0 0 1 ${num2(l.x)} ${num2(l.y)} Tm (${escPdf(l.text)}) Tj`)
|
|
153
|
+
.join("\n");
|
|
154
|
+
return `BT /${fontRes} ${num2(size)} Tf ${pdfColor(style.color)} rg\n${runs}\nET`;
|
|
155
|
+
}
|
|
156
|
+
/** Everything a field draws is clipped to its own box, so a long value is cut at the frame rather than
|
|
157
|
+
* bleeding across the page. */
|
|
158
|
+
function clipped(w, h, ops) {
|
|
159
|
+
return `q 0 0 ${num2(w)} ${num2(h)} re W n\n${ops}\nQ`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* A text field's value, baked. `lines` is already wrapped (one entry for a single-line field). A
|
|
163
|
+
* single line sits optically centred; a multi-line field stacks from the top, both measured off the
|
|
164
|
+
* font's cap height rather than a guessed ratio.
|
|
165
|
+
*/
|
|
166
|
+
export function textFieldFace(w, h, style, lines, capHeight, size, fontRes, multiline, align = "left") {
|
|
167
|
+
const face = box(w, h, style);
|
|
168
|
+
if (lines.length === 0)
|
|
169
|
+
return face;
|
|
170
|
+
const pad = FIELD_PAD + style.borderWidth;
|
|
171
|
+
const lineHeight = size * 1.15;
|
|
172
|
+
const placed = lines.map((l, i) => ({
|
|
173
|
+
text: l.text,
|
|
174
|
+
x: alignX(w, pad, l.width, align),
|
|
175
|
+
// Multi-line: the first line's capitals start one cap-height below the top inset, then step down.
|
|
176
|
+
// Single line: centre the capitals in the box.
|
|
177
|
+
y: multiline ? h - pad - capHeight * size - i * lineHeight : (h - capHeight * size) / 2,
|
|
178
|
+
}));
|
|
179
|
+
return `${face}\n${clipped(w, h, textBlock(placed, style, size, fontRes))}`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A list box, baked: the visible options stacked from the top, with a highlight behind the selected
|
|
183
|
+
* ones. A combo box (dropdown) uses `textFieldFace` instead - it shows only its current value.
|
|
184
|
+
*/
|
|
185
|
+
export function listBoxFace(w, h, style, lines, capHeight, size, fontRes, align = "left") {
|
|
186
|
+
const pad = FIELD_PAD + style.borderWidth;
|
|
187
|
+
const lineHeight = size * 1.15;
|
|
188
|
+
const rowTop = (i) => h - pad - (i + 1) * lineHeight;
|
|
189
|
+
// Highlights first, so the text sits on top of them.
|
|
190
|
+
const highlights = lines
|
|
191
|
+
.map((l, i) => l.selected
|
|
192
|
+
? `${SELECTION_TINT} rg ${num2(pad / 2)} ${num2(rowTop(i))} ${num2(w - pad)} ${num2(lineHeight)} re f`
|
|
193
|
+
: "")
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.join("\n");
|
|
196
|
+
const placed = lines.map((l, i) => ({
|
|
197
|
+
text: l.text,
|
|
198
|
+
x: alignX(w, pad, l.width, align),
|
|
199
|
+
// Baseline inside the row: lift it off the row bottom by the slack under the capitals.
|
|
200
|
+
y: rowTop(i) + (lineHeight - capHeight * size) / 2,
|
|
201
|
+
}));
|
|
202
|
+
const inner = [highlights, textBlock(placed, style, size, fontRes)].filter(Boolean).join("\n");
|
|
203
|
+
return `${box(w, h, style)}\n${clipped(w, h, inner)}`;
|
|
204
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Color } from "../common/color.js";
|
|
2
|
+
/**
|
|
3
|
+
* The semantic description of ONE AcroForm field, independent of whether it is being CREATED (the field
|
|
4
|
+
* elements produce it) or FILLED in an existing PDF (the reader parses existing fields into it, later).
|
|
5
|
+
* This shared model is the spine that makes "create" and "fill existing" two parts of one feature.
|
|
6
|
+
*
|
|
7
|
+
* A discriminated union, one `kind` per PDF field family (/Tx, /Btn, /Ch, /Sig) - all of them
|
|
8
|
+
* implemented, so a `switch` over `kind` is exhaustive.
|
|
9
|
+
*/
|
|
10
|
+
export type FormFieldSpec = TextFieldSpec | CheckboxSpec | RadioSpec | ChoiceSpec | PushButtonSpec | SignatureSpec;
|
|
11
|
+
/**
|
|
12
|
+
* What every field kind shares, whatever its family. Kept in one place so a new field type cannot
|
|
13
|
+
* silently miss them.
|
|
14
|
+
*/
|
|
15
|
+
export interface CommonFieldProps {
|
|
16
|
+
/** A tooltip / accessible name (/TU). */
|
|
17
|
+
tooltip?: string;
|
|
18
|
+
/** Show the field but do not let it be changed. */
|
|
19
|
+
readOnly?: boolean;
|
|
20
|
+
/** Mark it as a field that must be filled in before the form is submitted (/Ff Required). */
|
|
21
|
+
required?: boolean;
|
|
22
|
+
/** Hide the widget entirely - neither on screen nor in print. */
|
|
23
|
+
hidden?: boolean;
|
|
24
|
+
/** Include the widget when printing (default true). `false` keeps it on screen only. */
|
|
25
|
+
print?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** How a value is aligned inside its field box (the PDF calls this "quadding", /Q). */
|
|
28
|
+
export type FieldAlign = "left" | "center" | "right";
|
|
29
|
+
/**
|
|
30
|
+
* A variable text field (/Tx). The PDF variants live in field flags; here they are typed props, so you
|
|
31
|
+
* never hand-assemble a `/Ff` bitmask.
|
|
32
|
+
*/
|
|
33
|
+
export interface TextFieldSpec extends CommonFieldProps {
|
|
34
|
+
kind: "text";
|
|
35
|
+
/** The field's name (/T) - what you read the value back by. Must be unique in the document. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** The current text value (/V). */
|
|
38
|
+
value?: string;
|
|
39
|
+
/** A tooltip / accessible name for the field (/TU). */
|
|
40
|
+
tooltip?: string;
|
|
41
|
+
/** Wrap across multiple lines instead of a single line (the Multiline flag). */
|
|
42
|
+
multiline?: boolean;
|
|
43
|
+
/** Mask the typed characters (the Password flag). */
|
|
44
|
+
password?: boolean;
|
|
45
|
+
/** How the value sits in the box (default left). Applies to the baked appearance AND /Q. */
|
|
46
|
+
align?: FieldAlign;
|
|
47
|
+
/** Maximum number of characters the field accepts (/MaxLen). */
|
|
48
|
+
maxLength?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A checkbox (/Btn, neither the Radio nor the Pushbutton flag). Its value is the "on" export name when
|
|
52
|
+
* checked and `Off` when not - PDF checkboxes use NAMES, not strings; we hide that behind `checked`.
|
|
53
|
+
*/
|
|
54
|
+
export interface CheckboxSpec extends CommonFieldProps {
|
|
55
|
+
kind: "checkbox";
|
|
56
|
+
/** The field name (/T). */
|
|
57
|
+
name: string;
|
|
58
|
+
/** Whether it starts checked. */
|
|
59
|
+
checked?: boolean;
|
|
60
|
+
/** The "on" export value (the /AP state name + /V when checked). Default `"Yes"`. */
|
|
61
|
+
onValue?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One radio button (/Btn with the Radio flag). Every radio sharing a `group` is ONE AcroForm field whose
|
|
65
|
+
* `/Kids` are the individual buttons - mutually exclusive, so picking one clears the rest. `value` is this
|
|
66
|
+
* button's export name (unique within the group); it becomes the field's value when this one is picked.
|
|
67
|
+
*/
|
|
68
|
+
export interface RadioSpec extends CommonFieldProps {
|
|
69
|
+
kind: "radio";
|
|
70
|
+
/** The shared group name (/T of the parent field). All radios with the same `group` are one field. */
|
|
71
|
+
group: string;
|
|
72
|
+
/** This button's export value - unique in the group; the field's /V when this one is selected. */
|
|
73
|
+
value: string;
|
|
74
|
+
/** Whether this button starts selected (at most one per group should be). */
|
|
75
|
+
selected?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** One entry of a choice field. `label` is what the reader sees; `value` is what gets stored. */
|
|
78
|
+
export interface ChoiceOption {
|
|
79
|
+
value: string;
|
|
80
|
+
label?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A choice field (/Ch): a dropdown (combo box) or a list box. `combo` picks which. The value(s) are
|
|
84
|
+
* export strings from `options`. A combo may be `editable` (the reader can type a value not in the list);
|
|
85
|
+
* a list box may allow `multiSelect`.
|
|
86
|
+
*/
|
|
87
|
+
export interface ChoiceSpec extends CommonFieldProps {
|
|
88
|
+
kind: "choice";
|
|
89
|
+
/** The field name (/T). */
|
|
90
|
+
name: string;
|
|
91
|
+
/** The selectable options. */
|
|
92
|
+
options: ChoiceOption[];
|
|
93
|
+
/** `true` = dropdown (combo box), `false` = list box. */
|
|
94
|
+
combo: boolean;
|
|
95
|
+
/** Combo only: let the reader type a value that is not in the list. */
|
|
96
|
+
editable?: boolean;
|
|
97
|
+
/** List box only: allow more than one selection. */
|
|
98
|
+
multiSelect?: boolean;
|
|
99
|
+
/** The selected value (single-select). */
|
|
100
|
+
value?: string;
|
|
101
|
+
/** How the value sits in the box (default left). */
|
|
102
|
+
align?: FieldAlign;
|
|
103
|
+
/** The selected values (a `multiSelect` list box). */
|
|
104
|
+
values?: string[];
|
|
105
|
+
/** Tooltip / accessible name. */
|
|
106
|
+
tooltip?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* What a push button does when clicked. A typed union, so an action can never be half-specified (a
|
|
110
|
+
* submit without a target is not expressible). Scripted (JavaScript) actions are deliberately out of
|
|
111
|
+
* scope - a jasy form is data, not a program.
|
|
112
|
+
*/
|
|
113
|
+
export type ButtonAction = {
|
|
114
|
+
kind: "reset";
|
|
115
|
+
} | {
|
|
116
|
+
kind: "submit";
|
|
117
|
+
url: string;
|
|
118
|
+
} | {
|
|
119
|
+
kind: "url";
|
|
120
|
+
url: string;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* A push button (/Btn with the Pushbutton flag). Unlike every other field it holds NO value - it is a
|
|
124
|
+
* click target that fires an action. Its caption is baked into the appearance stream, so the button
|
|
125
|
+
* looks the same in every viewer and in print.
|
|
126
|
+
*/
|
|
127
|
+
export interface PushButtonSpec extends CommonFieldProps {
|
|
128
|
+
kind: "pushbutton";
|
|
129
|
+
/** The field name (/T). */
|
|
130
|
+
name: string;
|
|
131
|
+
/** The caption drawn on the button. */
|
|
132
|
+
label: string;
|
|
133
|
+
/** What clicking it does; omit for a button that does nothing (a plain label). */
|
|
134
|
+
action?: ButtonAction;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A signature field (/Sig) - a PLACEHOLDER where a signature can later be applied. jasy creates the
|
|
138
|
+
* field and its empty appearance; it does not sign (real signing needs a certificate and a byte-range
|
|
139
|
+
* digest, a separate feature). An unsigned field has no /V; a signer fills that in later.
|
|
140
|
+
*/
|
|
141
|
+
export interface SignatureSpec extends CommonFieldProps {
|
|
142
|
+
kind: "signature";
|
|
143
|
+
/** The field name (/T). */
|
|
144
|
+
name: string;
|
|
145
|
+
/** A hint drawn inside the empty box, e.g. "Signature" or the signer's role. */
|
|
146
|
+
label?: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* How a field's WIDGET is painted: the box (border + fill) and how its value looks (font size + colour).
|
|
150
|
+
* Kept separate from the semantic spec because it maps to the widget `/MK` + `/DA`, not the field value -
|
|
151
|
+
* and the fill-existing path reuses it to restyle a field it did not create.
|
|
152
|
+
*/
|
|
153
|
+
export interface FieldStyle {
|
|
154
|
+
/** Border (stroke) colour of the field box. Omit for no border. */
|
|
155
|
+
border?: Color;
|
|
156
|
+
/** Background fill of the field box. Omit for transparent. */
|
|
157
|
+
background?: Color;
|
|
158
|
+
/** The value's text colour (drawn into `/DA`). */
|
|
159
|
+
color: Color;
|
|
160
|
+
/** Font size in points; `0` lets the viewer auto-size to the box (the PDF convention). */
|
|
161
|
+
fontSize: number;
|
|
162
|
+
/** Border thickness in points (only drawn when a `border` is set). */
|
|
163
|
+
borderWidth: number;
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Color } from "../common/color.js";
|
|
2
|
+
import type { FontStyle } from "../utils/pdf-object-manager.js";
|
|
3
|
+
/** The regular face, for measuring a field's built-in Helvetica. `FontStyle` is a string enum, so this
|
|
4
|
+
* is its exact value - asserted here once instead of importing the enum (which would be a runtime
|
|
5
|
+
* import of the writer, see above). */
|
|
6
|
+
export declare const NORMAL_STYLE: FontStyle;
|
|
7
|
+
/** Escape a PDF literal string: backslash first, then the string delimiters. */
|
|
8
|
+
export declare const escPdf: (s: string) => string;
|
|
9
|
+
/** Trim a number to 2 decimals (PDF operands). */
|
|
10
|
+
export declare const num2: (n: number) => number;
|
|
11
|
+
/** A colour as PDF operands "r g b" in 0..1 (Color stores 0..255). */
|
|
12
|
+
export declare const pdfColor: (c: Color) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Escape a PDF **Name** token (`/Yes`, `/Off`, a checkbox's export value). A Name may only hold the
|
|
15
|
+
* regular characters; whitespace, the delimiters `()<>[]{}/%` and `#` itself must be written `#XX` by
|
|
16
|
+
* BYTE. Without this, an export value like `"Ja / Nein"` would break the dictionary it sits in.
|
|
17
|
+
* Literal strings - `(text)` - use `escPdf` instead; the two escapes are not interchangeable.
|
|
18
|
+
*/
|
|
19
|
+
export declare const escName: (s: string) => string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Small PDF-writer helpers shared across the forms module (widget dicts + appearance streams).
|
|
2
|
+
//
|
|
3
|
+
// Note: everything under `forms/` imports the object manager TYPE-ONLY. Keeping the runtime dependency
|
|
4
|
+
// one-way (the writer owns a collector, never the reverse) avoids an ESM import cycle.
|
|
5
|
+
/** The regular face, for measuring a field's built-in Helvetica. `FontStyle` is a string enum, so this
|
|
6
|
+
* is its exact value - asserted here once instead of importing the enum (which would be a runtime
|
|
7
|
+
* import of the writer, see above). */
|
|
8
|
+
export const NORMAL_STYLE = "normal";
|
|
9
|
+
/** Escape a PDF literal string: backslash first, then the string delimiters. */
|
|
10
|
+
export const escPdf = (s) => s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
11
|
+
/** Trim a number to 2 decimals (PDF operands). */
|
|
12
|
+
export const num2 = (n) => Number(n.toFixed(2));
|
|
13
|
+
/** A colour as PDF operands "r g b" in 0..1 (Color stores 0..255). */
|
|
14
|
+
export const pdfColor = (c) => c
|
|
15
|
+
.toArray()
|
|
16
|
+
.map((v) => (v / 255).toFixed(3))
|
|
17
|
+
.join(" ");
|
|
18
|
+
/**
|
|
19
|
+
* Escape a PDF **Name** token (`/Yes`, `/Off`, a checkbox's export value). A Name may only hold the
|
|
20
|
+
* regular characters; whitespace, the delimiters `()<>[]{}/%` and `#` itself must be written `#XX` by
|
|
21
|
+
* BYTE. Without this, an export value like `"Ja / Nein"` would break the dictionary it sits in.
|
|
22
|
+
* Literal strings - `(text)` - use `escPdf` instead; the two escapes are not interchangeable.
|
|
23
|
+
*/
|
|
24
|
+
export const escName = (s) => Array.from(new TextEncoder().encode(s))
|
|
25
|
+
.map((b) => b >= 0x21 && b <= 0x7e && !"()<>[]{}/%#".includes(String.fromCharCode(b))
|
|
26
|
+
? String.fromCharCode(b)
|
|
27
|
+
: "#" + b.toString(16).padStart(2, "0").toUpperCase())
|
|
28
|
+
.join("");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Color } from "../common/color.js";
|
|
2
2
|
import { FontStyle } from "../utils/pdf-object-manager.js";
|
|
3
|
+
import type { FormFieldSpec, FieldStyle } from "../forms/field.js";
|
|
3
4
|
/**
|
|
4
5
|
* Display list - the seam between layout and the PDF backend.
|
|
5
6
|
*
|
|
@@ -31,6 +32,9 @@ export interface TextRun {
|
|
|
31
32
|
x: number;
|
|
32
33
|
y: number;
|
|
33
34
|
text: string;
|
|
35
|
+
/** The glyphs to draw, when the run was SHAPED. The backend emits these instead of encoding `text`,
|
|
36
|
+
* which stays logical for `ToUnicode` and the accessible tree. */
|
|
37
|
+
glyphs?: number[];
|
|
34
38
|
fontFamily: string;
|
|
35
39
|
fontStyle: FontStyle;
|
|
36
40
|
fontSize: number;
|
|
@@ -39,17 +43,31 @@ export interface TextRun {
|
|
|
39
43
|
tag?: StructTag;
|
|
40
44
|
}
|
|
41
45
|
/** An axis-aligned rectangle. An absent `fill` or `stroke` means that part is not drawn. */
|
|
46
|
+
/**
|
|
47
|
+
* Corner radii in points (CSS `border-radius`). Any corner left out falls back to 0. A single number
|
|
48
|
+
* everywhere is the common case and stays a plain `number` on the nodes, so nothing changed for a
|
|
49
|
+
* document that never asks for different corners.
|
|
50
|
+
*/
|
|
51
|
+
export interface Radii {
|
|
52
|
+
tl?: number;
|
|
53
|
+
tr?: number;
|
|
54
|
+
br?: number;
|
|
55
|
+
bl?: number;
|
|
56
|
+
}
|
|
57
|
+
/** True when any corner asks for a radius - the gate between a plain rect and a curved path. */
|
|
58
|
+
export declare function isRounded(radius: number | Radii | undefined): boolean;
|
|
42
59
|
export interface Rect {
|
|
43
60
|
type: "rect";
|
|
44
61
|
x: number;
|
|
45
62
|
y: number;
|
|
46
63
|
width: number;
|
|
47
64
|
height: number;
|
|
48
|
-
|
|
65
|
+
/** Solid colour, or a gradient whose anchor points are in absolute page coordinates. */
|
|
66
|
+
fill?: Color | Gradient;
|
|
49
67
|
stroke?: Color;
|
|
50
68
|
strokeWidth: number;
|
|
51
|
-
/** Corner radius in points
|
|
52
|
-
radius?: number;
|
|
69
|
+
/** Corner radius in points - one number for all four, or per corner. Absent/0 = sharp (plain `re`). */
|
|
70
|
+
radius?: number | Radii;
|
|
53
71
|
tag?: StructTag;
|
|
54
72
|
}
|
|
55
73
|
/** A straight line segment between two points. */
|
|
@@ -83,8 +101,8 @@ export interface Image {
|
|
|
83
101
|
width: number;
|
|
84
102
|
height: number;
|
|
85
103
|
};
|
|
86
|
-
/** Corner radius in points for the image box; absent/0 = sharp
|
|
87
|
-
radius?: number;
|
|
104
|
+
/** Corner radius in points for the image box - one number or per corner; absent/0 = sharp. */
|
|
105
|
+
radius?: number | Radii;
|
|
88
106
|
tag?: StructTag;
|
|
89
107
|
}
|
|
90
108
|
/**
|
|
@@ -98,7 +116,7 @@ export interface ClipPush {
|
|
|
98
116
|
y: number;
|
|
99
117
|
width: number;
|
|
100
118
|
height: number;
|
|
101
|
-
radius?: number;
|
|
119
|
+
radius?: number | Radii;
|
|
102
120
|
}
|
|
103
121
|
/** Closes the most recent `ClipPush` (restores the graphics state). */
|
|
104
122
|
export interface ClipPop {
|
|
@@ -145,6 +163,21 @@ export interface Outline {
|
|
|
145
163
|
title: string;
|
|
146
164
|
level: number;
|
|
147
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* An interactive form field (AcroForm). Like `Link` it draws NOTHING into the content stream - it becomes
|
|
168
|
+
* a Widget ANNOTATION on the page (`/Annots`) and a field in the catalog `/AcroForm`, both built from the
|
|
169
|
+
* shared `FormFieldSpec`. The rect is the field box in top-left engine coords; the Y-flip converts it to
|
|
170
|
+
* page space. See `forms/acroform.ts` for the widget/field assembly.
|
|
171
|
+
*/
|
|
172
|
+
export interface FormFieldNode {
|
|
173
|
+
type: "formfield";
|
|
174
|
+
x: number;
|
|
175
|
+
y: number;
|
|
176
|
+
width: number;
|
|
177
|
+
height: number;
|
|
178
|
+
field: FormFieldSpec;
|
|
179
|
+
style: FieldStyle;
|
|
180
|
+
}
|
|
148
181
|
/**
|
|
149
182
|
* Pushes an affine transform (a `q` + `cm` in the content stream): everything between this and the
|
|
150
183
|
* matching `TransformPop` is painted through `matrix`. This is what `Rotated` wraps its child's nodes
|
|
@@ -227,4 +260,4 @@ export interface Path {
|
|
|
227
260
|
tag?: StructTag;
|
|
228
261
|
}
|
|
229
262
|
/** The closed set of primitives the PDF backend knows how to draw. */
|
|
230
|
-
export type IRNode = TextRun | Rect | Line | Image | ClipPush | ClipPop | Path | Link | Outline | Anchor | TransformPush | TransformPop;
|
|
263
|
+
export type IRNode = TextRun | Rect | Line | Image | ClipPush | ClipPop | Path | Link | Outline | Anchor | FormFieldNode | TransformPush | TransformPop;
|
package/dist/ir/display-list.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/** True when any corner asks for a radius - the gate between a plain rect and a curved path. */
|
|
2
|
+
export function isRounded(radius) {
|
|
3
|
+
if (radius === undefined)
|
|
4
|
+
return false;
|
|
5
|
+
if (typeof radius === "number")
|
|
6
|
+
return radius > 0;
|
|
7
|
+
return ((radius.tl ?? 0) > 0 || (radius.tr ?? 0) > 0 || (radius.br ?? 0) > 0 || (radius.bl ?? 0) > 0);
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flex alignment vocabulary. It lives here rather than in `utils/flex-layout.ts` because the base
|
|
3
|
+
* `PDFElement` needs `CrossAlign` for `alignSelf`, and flex-layout imports the elements - defining it
|
|
4
|
+
* there would close a cycle.
|
|
5
|
+
*/
|
|
6
|
+
/** Distribution along the MAIN axis (CSS `justify-content`). */
|
|
7
|
+
export type MainAlign = "start" | "center" | "end" | "between" | "around";
|
|
8
|
+
/** Position/size ACROSS the axis (CSS `align-items` / `align-self`). `stretch` fills the cross extent;
|
|
9
|
+
* the others place the child at its natural cross size. */
|
|
10
|
+
export type CrossAlign = "start" | "center" | "end" | "stretch";
|
|
@@ -41,6 +41,12 @@ export declare class BoxConstraints {
|
|
|
41
41
|
* what a padding/border element hands its child after reserving its own insets.
|
|
42
42
|
*/
|
|
43
43
|
deflate(horizontal: number, vertical: number): BoxConstraints;
|
|
44
|
+
/**
|
|
45
|
+
* Tightens this box by an element's own `min`/`max` on either axis. A max never widens the offered
|
|
46
|
+
* room, and a min may push past it - `minHeight: 500` in a 300pt region means 500, and pagination
|
|
47
|
+
* deals with the overflow, the same answer CSS gives.
|
|
48
|
+
*/
|
|
49
|
+
narrow(minWidth?: number, maxWidth?: number, minHeight?: number, maxHeight?: number): BoxConstraints;
|
|
44
50
|
/** Returns the constraints clamped to lie within `parent` (Flutter's enforce). */
|
|
45
51
|
enforce(parent: BoxConstraints): BoxConstraints;
|
|
46
52
|
}
|
|
@@ -52,6 +58,53 @@ export declare class BoxConstraints {
|
|
|
52
58
|
* Shared by every sized element so `width: "50%"` behaves identically across Box / Column / Row / Image.
|
|
53
59
|
*/
|
|
54
60
|
export declare function resolveExtent(fixed: number | undefined, factor: number | undefined, boundedMax: number, hasBounded: boolean): number | undefined;
|
|
61
|
+
/** What one axis of a sized element was asked for: a size, plus the bounds it must stay inside. */
|
|
62
|
+
export interface ExtentSpec {
|
|
63
|
+
fixed?: number;
|
|
64
|
+
factor?: number;
|
|
65
|
+
min?: number;
|
|
66
|
+
minFactor?: number;
|
|
67
|
+
max?: number;
|
|
68
|
+
maxFactor?: number;
|
|
69
|
+
}
|
|
70
|
+
/** The sizing options every sized element accepts. Each `*Factor` is the percentage form of its pair. */
|
|
71
|
+
export interface SizingParams {
|
|
72
|
+
width?: number;
|
|
73
|
+
height?: number;
|
|
74
|
+
widthFactor?: number;
|
|
75
|
+
heightFactor?: number;
|
|
76
|
+
minWidth?: number;
|
|
77
|
+
maxWidth?: number;
|
|
78
|
+
minHeight?: number;
|
|
79
|
+
maxHeight?: number;
|
|
80
|
+
minWidthFactor?: number;
|
|
81
|
+
maxWidthFactor?: number;
|
|
82
|
+
minHeightFactor?: number;
|
|
83
|
+
maxHeightFactor?: number;
|
|
84
|
+
/** width / height. Derives whichever axis is left open; see `resolveSize`. */
|
|
85
|
+
aspectRatio?: number;
|
|
86
|
+
}
|
|
87
|
+
/** Split `SizingParams` into the per-axis form `resolveSize` takes. */
|
|
88
|
+
export declare function extentSpecs(p: SizingParams): {
|
|
89
|
+
width: ExtentSpec;
|
|
90
|
+
height: ExtentSpec;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Resolve both axes of a sized element: relative sizing, then `aspectRatio`, then `min`/`max`.
|
|
94
|
+
*
|
|
95
|
+
* The order follows CSS: a ratio fills in the axis you left open, and min/max clamp afterwards - so an
|
|
96
|
+
* explicit bound wins over the ratio, exactly as `min-height` beats `aspect-ratio` in a browser.
|
|
97
|
+
*
|
|
98
|
+
* `min`/`max` also come back as NARROWED constraints, because an axis with no explicit size still has
|
|
99
|
+
* to obey them. `maxWidth` alone means "fill, but no wider than this", and the caller's own fill and
|
|
100
|
+
* shrink-wrap paths get that for free by using the returned constraints instead of the ones passed in.
|
|
101
|
+
* A `width` of `undefined` keeps its usual meaning: fill or shrink-wrap.
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveSize(width: ExtentSpec, height: ExtentSpec, aspectRatio: number | undefined, constraints: BoxConstraints): {
|
|
104
|
+
width?: number;
|
|
105
|
+
height?: number;
|
|
106
|
+
constraints: BoxConstraints;
|
|
107
|
+
};
|
|
55
108
|
/** The size an element resolves to and returns UP the tree. */
|
|
56
109
|
export interface Size {
|
|
57
110
|
width: number;
|