@broadpaper/forme 0.1.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/LICENSE +143 -0
- package/THIRD-PARTY-NOTICES.md +255 -0
- package/dist/chunk-KE7FFMY2.js +1438 -0
- package/dist/chunk-KE7FFMY2.js.map +1 -0
- package/dist/index.browser.d.ts +14 -0
- package/dist/index.browser.js +60 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.d.ts +369 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,1438 @@
|
|
|
1
|
+
// src/style.ts
|
|
2
|
+
var PX_TO_PT = 72 / 96;
|
|
3
|
+
function pxToPt(px) {
|
|
4
|
+
return Math.round(px * PX_TO_PT * 1e3) / 1e3;
|
|
5
|
+
}
|
|
6
|
+
function parseDeclarations(style) {
|
|
7
|
+
const out = {};
|
|
8
|
+
if (!style) return out;
|
|
9
|
+
if (typeof style === "object") {
|
|
10
|
+
for (const [k, v] of Object.entries(style)) {
|
|
11
|
+
if (v === void 0 || v === null || v === "") continue;
|
|
12
|
+
out[kebab(k)] = String(v);
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
for (const decl of String(style).split(";")) {
|
|
17
|
+
const i = decl.indexOf(":");
|
|
18
|
+
if (i < 0) continue;
|
|
19
|
+
const k = decl.slice(0, i).trim();
|
|
20
|
+
const v = decl.slice(i + 1).trim();
|
|
21
|
+
if (k && v) out[k] = v;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function kebab(s) {
|
|
26
|
+
return s.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
27
|
+
}
|
|
28
|
+
function length(value) {
|
|
29
|
+
const v = value.trim();
|
|
30
|
+
if (v === "auto" || v === "none" || v === "inherit") return void 0;
|
|
31
|
+
if (v.endsWith("%")) return v;
|
|
32
|
+
const n = Number.parseFloat(v);
|
|
33
|
+
if (!Number.isFinite(n)) return void 0;
|
|
34
|
+
if (v.endsWith("em")) return void 0;
|
|
35
|
+
return pxToPt(n);
|
|
36
|
+
}
|
|
37
|
+
function num(value) {
|
|
38
|
+
const n = Number.parseFloat(value);
|
|
39
|
+
return Number.isFinite(n) ? n : void 0;
|
|
40
|
+
}
|
|
41
|
+
function colour(value) {
|
|
42
|
+
const v = value.trim().toLowerCase();
|
|
43
|
+
if (!v || v === "transparent" || v === "none" || v === "inherit" || v === "currentcolor") return void 0;
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
function edges(value) {
|
|
47
|
+
const parts = value.trim().split(/\s+/).map((p) => length(p));
|
|
48
|
+
if (parts.some((p) => typeof p !== "number")) return void 0;
|
|
49
|
+
const n = parts;
|
|
50
|
+
const [t, r, b, l] = n.length === 1 ? [n[0], n[0], n[0], n[0]] : n.length === 2 ? [n[0], n[1], n[0], n[1]] : n.length === 3 ? [n[0], n[1], n[2], n[1]] : [n[0], n[1], n[2], n[3]];
|
|
51
|
+
return { top: t, right: r, bottom: b, left: l };
|
|
52
|
+
}
|
|
53
|
+
function border(value) {
|
|
54
|
+
const v = value.trim();
|
|
55
|
+
if (!v || v === "none" || v.startsWith("0 ") || v === "0") return void 0;
|
|
56
|
+
const widthMatch = /(-?[\d.]+)px/.exec(v);
|
|
57
|
+
const colourMatch = /(#[0-9a-f]{3,8}|rgba?\([^)]+\))/i.exec(v);
|
|
58
|
+
const width = widthMatch ? pxToPt(Number(widthMatch[1])) : 0.75;
|
|
59
|
+
if (/\bnone\b/.test(v)) return void 0;
|
|
60
|
+
return { width, colour: colourMatch ? colourMatch[0] : void 0 };
|
|
61
|
+
}
|
|
62
|
+
var FLEX_DIRECTION = { row: "row", column: "column", "row-reverse": "row-reverse", "column-reverse": "column-reverse" };
|
|
63
|
+
var JUSTIFY = { "flex-start": "flex-start", start: "flex-start", "flex-end": "flex-end", end: "flex-end", center: "center", "space-between": "space-between", "space-around": "space-around", "space-evenly": "space-evenly" };
|
|
64
|
+
var ALIGN = { "flex-start": "flex-start", start: "flex-start", "flex-end": "flex-end", end: "flex-end", center: "center", stretch: "stretch", baseline: "baseline" };
|
|
65
|
+
var TEXT_ALIGN = { left: "left", right: "right", center: "center", justify: "justify" };
|
|
66
|
+
var IGNORED = /* @__PURE__ */ new Set([
|
|
67
|
+
"box-sizing",
|
|
68
|
+
"-webkit-font-smoothing",
|
|
69
|
+
"text-rendering",
|
|
70
|
+
"font-variant-numeric",
|
|
71
|
+
"pointer-events",
|
|
72
|
+
"user-select",
|
|
73
|
+
"cursor",
|
|
74
|
+
"text-overflow",
|
|
75
|
+
"white-space",
|
|
76
|
+
"word-wrap",
|
|
77
|
+
"overflow-wrap",
|
|
78
|
+
"box-decoration-break",
|
|
79
|
+
"-webkit-box-decoration-break",
|
|
80
|
+
"text-underline-offset",
|
|
81
|
+
"text-decoration-thickness",
|
|
82
|
+
"outline",
|
|
83
|
+
"transition",
|
|
84
|
+
"list-style",
|
|
85
|
+
"border-collapse",
|
|
86
|
+
"border-spacing",
|
|
87
|
+
"table-layout",
|
|
88
|
+
"vertical-align",
|
|
89
|
+
"min-width",
|
|
90
|
+
"max-width",
|
|
91
|
+
"-webkit-line-clamp",
|
|
92
|
+
"-webkit-box-orient",
|
|
93
|
+
"object-fit",
|
|
94
|
+
"content",
|
|
95
|
+
"zoom",
|
|
96
|
+
"transform-origin"
|
|
97
|
+
]);
|
|
98
|
+
function translateStyle(css) {
|
|
99
|
+
const s = {};
|
|
100
|
+
const unsupported = [];
|
|
101
|
+
const set = (k, v) => {
|
|
102
|
+
if (v !== void 0) s[k] = v;
|
|
103
|
+
};
|
|
104
|
+
for (const [prop, raw] of Object.entries(css)) {
|
|
105
|
+
const value = raw.trim();
|
|
106
|
+
if (IGNORED.has(prop)) continue;
|
|
107
|
+
switch (prop) {
|
|
108
|
+
case "display":
|
|
109
|
+
if (value === "flex" || value === "inline-flex") set("display", "flex");
|
|
110
|
+
else if (value === "grid") {
|
|
111
|
+
set("display", "flex");
|
|
112
|
+
set("flexDirection", "row");
|
|
113
|
+
} else if (value === "none") set("display", "none");
|
|
114
|
+
break;
|
|
115
|
+
case "flex-direction":
|
|
116
|
+
set("flexDirection", FLEX_DIRECTION[value]);
|
|
117
|
+
break;
|
|
118
|
+
case "justify-content":
|
|
119
|
+
set("justifyContent", JUSTIFY[value]);
|
|
120
|
+
break;
|
|
121
|
+
case "align-items":
|
|
122
|
+
set("alignItems", ALIGN[value]);
|
|
123
|
+
break;
|
|
124
|
+
case "align-self":
|
|
125
|
+
set("alignSelf", ALIGN[value]);
|
|
126
|
+
break;
|
|
127
|
+
case "flex-wrap":
|
|
128
|
+
set("flexWrap", value === "wrap" ? "wrap" : "nowrap");
|
|
129
|
+
break;
|
|
130
|
+
case "flex":
|
|
131
|
+
set("flexGrow", num(value.split(/\s+/)[0] ?? "0"));
|
|
132
|
+
break;
|
|
133
|
+
case "flex-grow":
|
|
134
|
+
set("flexGrow", num(value));
|
|
135
|
+
break;
|
|
136
|
+
case "flex-shrink":
|
|
137
|
+
set("flexShrink", num(value));
|
|
138
|
+
break;
|
|
139
|
+
case "gap":
|
|
140
|
+
case "row-gap":
|
|
141
|
+
case "column-gap": {
|
|
142
|
+
const parts = value.split(/\s+/).map((p) => length(p));
|
|
143
|
+
const first = typeof parts[0] === "number" ? parts[0] : void 0;
|
|
144
|
+
const second = parts.length > 1 && typeof parts[1] === "number" ? parts[1] : first;
|
|
145
|
+
if (prop === "gap") {
|
|
146
|
+
set("rowGap", first);
|
|
147
|
+
set("columnGap", second);
|
|
148
|
+
} else set(prop === "row-gap" ? "rowGap" : "columnGap", first);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "grid-template-columns":
|
|
152
|
+
break;
|
|
153
|
+
case "grid-column": {
|
|
154
|
+
const span = /^span\s+(\d+)$/.exec(value);
|
|
155
|
+
if (span) {
|
|
156
|
+
set("flexGrow", Number(span[1]));
|
|
157
|
+
set("flexBasis", 0);
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case "width":
|
|
162
|
+
set("width", length(value));
|
|
163
|
+
break;
|
|
164
|
+
case "height": {
|
|
165
|
+
const h = length(value);
|
|
166
|
+
if (typeof h === "number") set("height", h);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "padding":
|
|
170
|
+
set("padding", edges(value));
|
|
171
|
+
break;
|
|
172
|
+
case "padding-top":
|
|
173
|
+
case "padding-right":
|
|
174
|
+
case "padding-bottom":
|
|
175
|
+
case "padding-left":
|
|
176
|
+
set(`padding${cap(prop.slice(8))}`, length(value));
|
|
177
|
+
break;
|
|
178
|
+
case "margin":
|
|
179
|
+
set("margin", edges(value));
|
|
180
|
+
break;
|
|
181
|
+
case "margin-top":
|
|
182
|
+
case "margin-right":
|
|
183
|
+
case "margin-bottom":
|
|
184
|
+
case "margin-left":
|
|
185
|
+
set(`margin${cap(prop.slice(7))}`, length(value));
|
|
186
|
+
break;
|
|
187
|
+
case "background":
|
|
188
|
+
case "background-color":
|
|
189
|
+
set("backgroundColor", colour(value));
|
|
190
|
+
break;
|
|
191
|
+
case "color":
|
|
192
|
+
set("color", colour(value));
|
|
193
|
+
break;
|
|
194
|
+
case "font-family":
|
|
195
|
+
set("fontFamily", value.split(",")[0].trim().replace(/^['"]|['"]$/g, ""));
|
|
196
|
+
break;
|
|
197
|
+
case "font-size":
|
|
198
|
+
set("fontSize", length(value));
|
|
199
|
+
break;
|
|
200
|
+
case "font-weight":
|
|
201
|
+
set("fontWeight", value === "bold" ? 700 : value === "normal" ? 400 : num(value));
|
|
202
|
+
break;
|
|
203
|
+
case "font-style":
|
|
204
|
+
if (value === "italic") set("fontStyle", "italic");
|
|
205
|
+
break;
|
|
206
|
+
case "line-height":
|
|
207
|
+
set("lineHeight", num(value));
|
|
208
|
+
break;
|
|
209
|
+
case "letter-spacing":
|
|
210
|
+
if (value.endsWith("px")) set("letterSpacing", length(value));
|
|
211
|
+
break;
|
|
212
|
+
case "text-align":
|
|
213
|
+
set("textAlign", TEXT_ALIGN[value]);
|
|
214
|
+
break;
|
|
215
|
+
case "text-transform":
|
|
216
|
+
if (value !== "none") set("textTransform", value);
|
|
217
|
+
break;
|
|
218
|
+
case "text-decoration":
|
|
219
|
+
if (value.includes("underline")) set("textDecoration", "underline");
|
|
220
|
+
else if (value.includes("line-through")) set("textDecoration", "line-through");
|
|
221
|
+
break;
|
|
222
|
+
case "opacity":
|
|
223
|
+
set("opacity", num(value));
|
|
224
|
+
break;
|
|
225
|
+
case "overflow":
|
|
226
|
+
if (value === "hidden") set("overflow", "hidden");
|
|
227
|
+
break;
|
|
228
|
+
case "border-radius": {
|
|
229
|
+
const r = length(value.split(/\s+/)[0] ?? "0");
|
|
230
|
+
set("borderRadius", typeof r === "number" ? r : void 0);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "border": {
|
|
234
|
+
const b = border(value);
|
|
235
|
+
if (b) {
|
|
236
|
+
set("borderWidth", b.width);
|
|
237
|
+
set("borderColor", b.colour);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "border-top":
|
|
242
|
+
case "border-right":
|
|
243
|
+
case "border-bottom":
|
|
244
|
+
case "border-left": {
|
|
245
|
+
const side = cap(prop.slice(7));
|
|
246
|
+
const b = border(value);
|
|
247
|
+
if (b) {
|
|
248
|
+
set(`border${side}Width`, b.width);
|
|
249
|
+
set(`border${side}Color`, b.colour);
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "position":
|
|
254
|
+
if (value === "absolute" || value === "relative") set("position", value);
|
|
255
|
+
break;
|
|
256
|
+
case "top":
|
|
257
|
+
case "left":
|
|
258
|
+
case "right":
|
|
259
|
+
case "bottom":
|
|
260
|
+
set(prop, length(value));
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
unsupported.push(prop);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (s.display === "flex" && s.flexDirection === void 0) s.flexDirection = "row";
|
|
267
|
+
return { style: s, unsupported };
|
|
268
|
+
}
|
|
269
|
+
function cap(s) {
|
|
270
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/vnode.ts
|
|
274
|
+
import { classNames, styleString, walkVNodes } from "@broadpaper/core";
|
|
275
|
+
import { mapStyle, mapColumnWidth } from "@formepdf/shared";
|
|
276
|
+
|
|
277
|
+
// src/svg.ts
|
|
278
|
+
import { toHtml } from "@broadpaper/core";
|
|
279
|
+
var COORDS = {
|
|
280
|
+
rect: [["x", 0], ["y", 1]],
|
|
281
|
+
circle: [["cx", 0], ["cy", 1]],
|
|
282
|
+
ellipse: [["cx", 0], ["cy", 1]],
|
|
283
|
+
line: [["x1", 0], ["y1", 1], ["x2", 0], ["y2", 1]],
|
|
284
|
+
text: [["x", 0], ["y", 1]],
|
|
285
|
+
tspan: [["x", 0], ["y", 1]],
|
|
286
|
+
image: [["x", 0], ["y", 1]],
|
|
287
|
+
use: [["x", 0], ["y", 1]]
|
|
288
|
+
};
|
|
289
|
+
var TRANSLATE = /^\s*translate\(\s*(-?[\d.eE+]+)\s*(?:[, ]\s*(-?[\d.eE+]+))?\s*\)\s*$/;
|
|
290
|
+
function translationOf(value) {
|
|
291
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
292
|
+
const m = TRANSLATE.exec(value);
|
|
293
|
+
if (!m) return "unsupported";
|
|
294
|
+
return [Number(m[1]), m[2] === void 0 ? 0 : Number(m[2])];
|
|
295
|
+
}
|
|
296
|
+
var PATH_COMMANDS = {
|
|
297
|
+
m: { size: 2, coords: [0, 1] },
|
|
298
|
+
l: { size: 2, coords: [0, 1] },
|
|
299
|
+
t: { size: 2, coords: [0, 1] },
|
|
300
|
+
h: { size: 1, coords: [0] },
|
|
301
|
+
v: { size: 1, coords: [1] },
|
|
302
|
+
c: { size: 6, coords: [0, 1, 0, 1, 0, 1] },
|
|
303
|
+
s: { size: 4, coords: [0, 1, 0, 1] },
|
|
304
|
+
q: { size: 4, coords: [0, 1, 0, 1] },
|
|
305
|
+
// rx ry rotation large-arc sweep x y — only the final pair is a position.
|
|
306
|
+
a: { size: 7, coords: [-1, -1, -1, -1, -1, 0, 1] },
|
|
307
|
+
z: { size: 0, coords: [] }
|
|
308
|
+
};
|
|
309
|
+
function translatePathData(d, dx, dy) {
|
|
310
|
+
if (!dx && !dy) return d;
|
|
311
|
+
const tokens = d.match(/[a-zA-Z]|-?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g);
|
|
312
|
+
if (!tokens) return d;
|
|
313
|
+
const out = [];
|
|
314
|
+
let command = "";
|
|
315
|
+
let i = 0;
|
|
316
|
+
let first = true;
|
|
317
|
+
while (i < tokens.length) {
|
|
318
|
+
const token = tokens[i];
|
|
319
|
+
if (/[a-zA-Z]/.test(token)) {
|
|
320
|
+
command = token;
|
|
321
|
+
out.push(token);
|
|
322
|
+
i++;
|
|
323
|
+
if (command.toLowerCase() === "z") {
|
|
324
|
+
first = false;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const spec = PATH_COMMANDS[command.toLowerCase()];
|
|
329
|
+
if (!spec || spec.size === 0) {
|
|
330
|
+
while (i < tokens.length) out.push(tokens[i++]);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
const absolute = command === command.toUpperCase() || first && command === "m";
|
|
334
|
+
for (let p = 0; p < spec.size && i < tokens.length; p++) {
|
|
335
|
+
const axis = spec.coords[p];
|
|
336
|
+
const value = Number(tokens[i]);
|
|
337
|
+
out.push(absolute && axis >= 0 ? String(round(value + (axis === 0 ? dx : dy))) : tokens[i]);
|
|
338
|
+
i++;
|
|
339
|
+
}
|
|
340
|
+
if (first) {
|
|
341
|
+
first = false;
|
|
342
|
+
if (command === "m") command = "l";
|
|
343
|
+
else if (command === "M") command = "L";
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return out.join(" ");
|
|
347
|
+
}
|
|
348
|
+
function round(n) {
|
|
349
|
+
return Math.round(n * 1e3) / 1e3;
|
|
350
|
+
}
|
|
351
|
+
function translatePoints(points, dx, dy) {
|
|
352
|
+
const nums = points.match(/-?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g);
|
|
353
|
+
if (!nums) return points;
|
|
354
|
+
return nums.map((n, i) => round(Number(n) + (i % 2 === 0 ? dx : dy))).join(" ");
|
|
355
|
+
}
|
|
356
|
+
function shift(v, dx, dy, warn) {
|
|
357
|
+
const own = translationOf(v.props.transform);
|
|
358
|
+
let tx = dx;
|
|
359
|
+
let ty = dy;
|
|
360
|
+
const props = { ...v.props };
|
|
361
|
+
if (own === "unsupported") {
|
|
362
|
+
warn(`SVG transform "${String(v.props.transform)}" is not translate() and was left to the engine, which ignores it`);
|
|
363
|
+
} else if (own) {
|
|
364
|
+
tx += own[0];
|
|
365
|
+
ty += own[1];
|
|
366
|
+
delete props.transform;
|
|
367
|
+
}
|
|
368
|
+
if (tx || ty) {
|
|
369
|
+
for (const [attr, axis] of COORDS[v.tag] ?? []) {
|
|
370
|
+
const current = props[attr];
|
|
371
|
+
const base = current === void 0 || current === null || current === "" ? 0 : Number(current);
|
|
372
|
+
if (Number.isFinite(base)) props[attr] = round(base + (axis === 0 ? tx : ty));
|
|
373
|
+
}
|
|
374
|
+
if (v.tag === "path" && typeof props.d === "string") props.d = translatePathData(props.d, tx, ty);
|
|
375
|
+
if ((v.tag === "polyline" || v.tag === "polygon") && typeof props.points === "string") props.points = translatePoints(props.points, tx, ty);
|
|
376
|
+
}
|
|
377
|
+
const children = v.children.map((c) => typeof c === "string" ? c : shift(c, tx, ty, warn));
|
|
378
|
+
return { ...v, props, children };
|
|
379
|
+
}
|
|
380
|
+
function textContent(v) {
|
|
381
|
+
if (typeof v === "string") return v;
|
|
382
|
+
return v.children.map(textContent).join("");
|
|
383
|
+
}
|
|
384
|
+
function numeric(value, fallback) {
|
|
385
|
+
const n = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
|
|
386
|
+
return Number.isFinite(n) ? n : fallback;
|
|
387
|
+
}
|
|
388
|
+
var SCREEN_ONLY = /^data-bp-/;
|
|
389
|
+
function splitSvg(v, warn) {
|
|
390
|
+
const flat = shift(v, 0, 0, warn);
|
|
391
|
+
const labels = [];
|
|
392
|
+
const strip = (node, inherited) => {
|
|
393
|
+
const own = {
|
|
394
|
+
fontFamily: node.props["font-family"] ?? inherited.fontFamily,
|
|
395
|
+
fill: node.props.fill ?? inherited.fill
|
|
396
|
+
};
|
|
397
|
+
if (node.tag === "text") {
|
|
398
|
+
const text = textContent(node).trim();
|
|
399
|
+
if (text) {
|
|
400
|
+
const anchor = String(node.props["text-anchor"] ?? "start");
|
|
401
|
+
labels.push({
|
|
402
|
+
x: numeric(node.props.x, 0),
|
|
403
|
+
y: numeric(node.props.y, 0),
|
|
404
|
+
text,
|
|
405
|
+
anchor: anchor === "middle" || anchor === "end" ? anchor : "start",
|
|
406
|
+
fontSize: numeric(node.props["font-size"], 10),
|
|
407
|
+
fontFamily: own.fontFamily,
|
|
408
|
+
fontWeight: node.props["font-weight"] ? numeric(node.props["font-weight"], 400) : void 0,
|
|
409
|
+
fill: own.fill
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const children = [];
|
|
415
|
+
for (const c of node.children) {
|
|
416
|
+
if (typeof c === "string") {
|
|
417
|
+
children.push(c);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const kept2 = strip(c, own);
|
|
421
|
+
if (kept2) children.push(kept2);
|
|
422
|
+
}
|
|
423
|
+
const props = {};
|
|
424
|
+
for (const [k, value] of Object.entries(node.props)) if (!SCREEN_ONLY.test(k)) props[k] = value;
|
|
425
|
+
return { ...node, props, children };
|
|
426
|
+
};
|
|
427
|
+
const kept = strip(flat, {});
|
|
428
|
+
return { markup: kept.children.map((c) => typeof c === "string" ? c : toHtml(c)).join(""), labels };
|
|
429
|
+
}
|
|
430
|
+
var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
|
|
431
|
+
function decodeEntities(s) {
|
|
432
|
+
return s.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
433
|
+
if (body.startsWith("#x") || body.startsWith("#X")) return String.fromCodePoint(Number.parseInt(body.slice(2), 16));
|
|
434
|
+
if (body.startsWith("#")) return String.fromCodePoint(Number.parseInt(body.slice(1), 10));
|
|
435
|
+
return ENTITIES[body] ?? whole;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
var TAG = /<\s*(\/)?\s*([a-zA-Z][\w:.-]*)((?:\s+[\w:.-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s">]+))*)\s*(\/)?\s*>/g;
|
|
439
|
+
var ATTR = /([\w:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s">]+))/g;
|
|
440
|
+
var SKIP = /<\?[\s\S]*?\?>|<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<![\s\S]*?>/g;
|
|
441
|
+
function parseSvgMarkup(source) {
|
|
442
|
+
const text = source.replace(SKIP, "");
|
|
443
|
+
const root = { tag: "#root", props: {}, children: [] };
|
|
444
|
+
const stack = [root];
|
|
445
|
+
let cursor = 0;
|
|
446
|
+
TAG.lastIndex = 0;
|
|
447
|
+
for (let m = TAG.exec(text); m; m = TAG.exec(text)) {
|
|
448
|
+
const between = text.slice(cursor, m.index);
|
|
449
|
+
if (between.trim()) stack[stack.length - 1].children.push(decodeEntities(between));
|
|
450
|
+
cursor = TAG.lastIndex;
|
|
451
|
+
const [, closing, tag, rawAttrs, selfClosing] = m;
|
|
452
|
+
if (closing) {
|
|
453
|
+
if (stack.length > 1) stack.pop();
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const props = {};
|
|
457
|
+
ATTR.lastIndex = 0;
|
|
458
|
+
for (let a = ATTR.exec(rawAttrs ?? ""); a; a = ATTR.exec(rawAttrs ?? "")) {
|
|
459
|
+
props[a[1]] = decodeEntities(a[2] ?? a[3] ?? a[4] ?? "");
|
|
460
|
+
}
|
|
461
|
+
const node = { tag, props, children: [] };
|
|
462
|
+
stack[stack.length - 1].children.push(node);
|
|
463
|
+
if (!selfClosing) stack.push(node);
|
|
464
|
+
}
|
|
465
|
+
const svg = root.children.find((c) => typeof c !== "string" && c.tag.toLowerCase() === "svg");
|
|
466
|
+
return svg ?? null;
|
|
467
|
+
}
|
|
468
|
+
function svgFromDataUri(src) {
|
|
469
|
+
const match = /^data:image\/svg\+xml(;[^,]*)?,(.*)$/is.exec(src.trim());
|
|
470
|
+
if (!match) return null;
|
|
471
|
+
const isBase64 = (match[1] ?? "").toLowerCase().includes("base64");
|
|
472
|
+
try {
|
|
473
|
+
const body = match[2] ?? "";
|
|
474
|
+
const markup = isBase64 ? new TextDecoder().decode(Uint8Array.from(atob(body), (c) => c.charCodeAt(0))) : decodeURIComponent(body);
|
|
475
|
+
return parseSvgMarkup(markup);
|
|
476
|
+
} catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/vnode.ts
|
|
482
|
+
var INLINE_TAGS = /* @__PURE__ */ new Set(["span", "strong", "b", "em", "i", "u", "s", "code", "a", "small"]);
|
|
483
|
+
var TEXT_BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "blockquote", "figcaption", "label", "td", "th", "div"]);
|
|
484
|
+
function declarationsOf(v) {
|
|
485
|
+
return parseDeclarations(typeof v.props.style === "string" ? v.props.style : styleString(v.props.style));
|
|
486
|
+
}
|
|
487
|
+
function styleOf(v, ctx) {
|
|
488
|
+
const decls = declarationsOf(v);
|
|
489
|
+
const { style, unsupported } = translateStyle(decls);
|
|
490
|
+
for (const u of unsupported) ctx.warnings.add(`unsupported CSS property "${u}" (ignored)`);
|
|
491
|
+
return mapStyle(style);
|
|
492
|
+
}
|
|
493
|
+
function textOf(v) {
|
|
494
|
+
if (typeof v === "string") return v;
|
|
495
|
+
if (v.tag === "br") return "\n";
|
|
496
|
+
return v.children.map(textOf).join("");
|
|
497
|
+
}
|
|
498
|
+
function hasBlockChild(v) {
|
|
499
|
+
return v.children.some((c) => typeof c !== "string" && !INLINE_TAGS.has(c.tag) && c.tag !== "br");
|
|
500
|
+
}
|
|
501
|
+
function holdsWrappedText(v) {
|
|
502
|
+
let found = false;
|
|
503
|
+
walkVNodes(v, (n) => {
|
|
504
|
+
if (n.props["data-part"] === "text") found = true;
|
|
505
|
+
return !found;
|
|
506
|
+
});
|
|
507
|
+
return found;
|
|
508
|
+
}
|
|
509
|
+
function leaf(node, ann = { children: [] }) {
|
|
510
|
+
return { node, ann };
|
|
511
|
+
}
|
|
512
|
+
function svgNode(v, ctx) {
|
|
513
|
+
const width = Number(v.props.width) || 0;
|
|
514
|
+
const height = Number(v.props.height) || 0;
|
|
515
|
+
const { markup, labels } = splitSvg(v, (message) => ctx.warnings.add(message));
|
|
516
|
+
const viewBox = typeof v.props.viewBox === "string" ? v.props.viewBox : `0 0 ${width} ${height}`;
|
|
517
|
+
const box2 = mapStyle({ width: pxToPt(width), height: pxToPt(height) });
|
|
518
|
+
const shapes = {
|
|
519
|
+
kind: { type: "Svg", width: pxToPt(width), height: pxToPt(height), view_box: viewBox, content: markup },
|
|
520
|
+
style: { ...box2, ...styleOf(v, ctx) },
|
|
521
|
+
children: []
|
|
522
|
+
};
|
|
523
|
+
if (!labels.length) return leaf(shapes);
|
|
524
|
+
const vb = viewBox.trim().split(/[\s,]+/).map(Number);
|
|
525
|
+
const sx = vb.length === 4 && vb[2] ? width / vb[2] : 1;
|
|
526
|
+
const sy = vb.length === 4 && vb[3] ? height / vb[3] : 1;
|
|
527
|
+
const children = [shapes, ...labels.map((l) => labelNode(l, sx, sy))];
|
|
528
|
+
return {
|
|
529
|
+
node: {
|
|
530
|
+
kind: { type: "View" },
|
|
531
|
+
// Relative, so the labels are placed against this box rather than the page.
|
|
532
|
+
style: mapStyle({ position: "relative", width: pxToPt(width), height: pxToPt(height) }),
|
|
533
|
+
children
|
|
534
|
+
},
|
|
535
|
+
ann: { children: children.map(() => ({ children: [] })) }
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function labelNode(l, sx, sy) {
|
|
539
|
+
const x = l.x * sx;
|
|
540
|
+
const size = l.fontSize * Math.min(sx, sy);
|
|
541
|
+
const estimate = Math.max(size, l.text.length * size * 0.55);
|
|
542
|
+
const width = estimate * 1.5;
|
|
543
|
+
const left = l.anchor === "middle" ? x - estimate / 2 : l.anchor === "end" ? x - estimate : x;
|
|
544
|
+
const style = {
|
|
545
|
+
position: "absolute",
|
|
546
|
+
left: pxToPt(left),
|
|
547
|
+
// `y` is a baseline; a box starts roughly one ascender higher.
|
|
548
|
+
top: pxToPt(l.y * sy - size * 0.8),
|
|
549
|
+
width: pxToPt(width),
|
|
550
|
+
fontSize: pxToPt(size),
|
|
551
|
+
textAlign: "left"
|
|
552
|
+
};
|
|
553
|
+
if (l.fill) style.color = l.fill;
|
|
554
|
+
if (l.fontFamily) style.fontFamily = l.fontFamily.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
|
|
555
|
+
if (l.fontWeight) style.fontWeight = l.fontWeight;
|
|
556
|
+
return { kind: { type: "Text", content: l.text }, style: mapStyle(style), children: [] };
|
|
557
|
+
}
|
|
558
|
+
function sizeFrom(img, svg) {
|
|
559
|
+
const own = { width: Number(svg.props.width) || 0, height: Number(svg.props.height) || 0 };
|
|
560
|
+
const box2 = String(svg.props.viewBox ?? "").trim().split(/[\s,]+/).map(Number);
|
|
561
|
+
const natural = {
|
|
562
|
+
width: own.width || (box2.length === 4 ? box2[2] : 0),
|
|
563
|
+
height: own.height || (box2.length === 4 ? box2[3] : 0)
|
|
564
|
+
};
|
|
565
|
+
const css = parseDeclarations(typeof img.props.style === "string" ? img.props.style : styleString(img.props.style));
|
|
566
|
+
const asked = { width: Number.parseFloat(css.width ?? ""), height: Number.parseFloat(css.height ?? "") };
|
|
567
|
+
const ratio = natural.width && natural.height ? natural.width / natural.height : 0;
|
|
568
|
+
let width = Number.isFinite(asked.width) ? asked.width : 0;
|
|
569
|
+
let height = Number.isFinite(asked.height) ? asked.height : 0;
|
|
570
|
+
if (!width && height && ratio) width = height * ratio;
|
|
571
|
+
if (!height && width && ratio) height = width / ratio;
|
|
572
|
+
return { width: width || natural.width, height: height || natural.height };
|
|
573
|
+
}
|
|
574
|
+
function tableNode(v, ctx) {
|
|
575
|
+
const columns = [];
|
|
576
|
+
const rows = [];
|
|
577
|
+
const rowAnns = [];
|
|
578
|
+
const rowKinds = [];
|
|
579
|
+
const visit = (node, kind) => {
|
|
580
|
+
for (const child of node.children) {
|
|
581
|
+
if (typeof child === "string") continue;
|
|
582
|
+
if (child.tag === "colgroup") {
|
|
583
|
+
for (const col of child.children) {
|
|
584
|
+
if (typeof col === "string") continue;
|
|
585
|
+
const w = parseDeclarations(typeof col.props.style === "string" ? col.props.style : styleString(col.props.style)).width;
|
|
586
|
+
const parsed = w ? length(w) : void 0;
|
|
587
|
+
columns.push({
|
|
588
|
+
width: mapColumnWidth(
|
|
589
|
+
typeof parsed === "string" ? { fraction: Number.parseFloat(parsed) / 100 } : typeof parsed === "number" ? { fixed: parsed } : "auto"
|
|
590
|
+
)
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
} else if (child.tag === "thead") visit(child, "header");
|
|
594
|
+
else if (child.tag === "tbody") visit(child, "body");
|
|
595
|
+
else if (child.tag === "tfoot") visit(child, "footer");
|
|
596
|
+
else if (child.tag === "tr") {
|
|
597
|
+
const cells = [];
|
|
598
|
+
const cellAnns = [];
|
|
599
|
+
for (const cell of child.children) {
|
|
600
|
+
if (typeof cell === "string") continue;
|
|
601
|
+
const inner = childNodes(cell, ctx);
|
|
602
|
+
cells.push({
|
|
603
|
+
kind: { type: "TableCell", col_span: Number(cell.props.colspan) || 1, row_span: Number(cell.props.rowspan) || 1 },
|
|
604
|
+
style: styleOf(cell, ctx),
|
|
605
|
+
children: inner.nodes
|
|
606
|
+
});
|
|
607
|
+
cellAnns.push({ children: inner.anns });
|
|
608
|
+
}
|
|
609
|
+
rows.push({ kind: { type: "TableRow", is_header: kind === "header" }, style: styleOf(child, ctx), children: cells });
|
|
610
|
+
rowAnns.push({ children: cellAnns });
|
|
611
|
+
rowKinds.push(kind);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
visit(v, "body");
|
|
616
|
+
return {
|
|
617
|
+
node: { kind: { type: "Table", columns }, style: styleOf(v, ctx), children: rows },
|
|
618
|
+
ann: { rowKinds, children: rowAnns }
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function childNodes(v, ctx, inherited = {}) {
|
|
622
|
+
const nodes = [];
|
|
623
|
+
const anns = [];
|
|
624
|
+
for (const child of v.children) {
|
|
625
|
+
if (typeof child === "string") {
|
|
626
|
+
if (!child.trim()) continue;
|
|
627
|
+
nodes.push({ kind: { type: "Text", content: child }, style: inherited, children: [] });
|
|
628
|
+
anns.push({ children: [] });
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const t = translate(child, ctx);
|
|
632
|
+
if (!t) continue;
|
|
633
|
+
nodes.push(t.node);
|
|
634
|
+
anns.push(t.ann);
|
|
635
|
+
}
|
|
636
|
+
return { nodes, anns };
|
|
637
|
+
}
|
|
638
|
+
function translate(v, ctx) {
|
|
639
|
+
const cls = classNames(v.props.class);
|
|
640
|
+
if (!ctx.design && /bp-(placeholder|empty-section|empty-column)\b/.test(cls)) return null;
|
|
641
|
+
if (/bp-page-break\b/.test(cls) && !ctx.suppressPageBreaks) return leaf({ kind: { type: "PageBreak" }, style: {}, children: [] });
|
|
642
|
+
let result;
|
|
643
|
+
switch (v.tag) {
|
|
644
|
+
case "svg":
|
|
645
|
+
result = svgNode(v, ctx);
|
|
646
|
+
break;
|
|
647
|
+
case "table":
|
|
648
|
+
result = tableNode(v, ctx);
|
|
649
|
+
break;
|
|
650
|
+
case "img": {
|
|
651
|
+
const src = typeof v.props.src === "string" ? v.props.src : "";
|
|
652
|
+
if (!src) return null;
|
|
653
|
+
const vector = svgFromDataUri(src);
|
|
654
|
+
if (vector) {
|
|
655
|
+
const sized = { ...vector, props: { ...vector.props, ...sizeFrom(v, vector) } };
|
|
656
|
+
result = svgNode(sized, ctx);
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
const node = { kind: { type: "Image", src }, style: styleOf(v, ctx), children: [] };
|
|
660
|
+
if (typeof v.props.alt === "string" && v.props.alt) node.alt = v.props.alt;
|
|
661
|
+
result = leaf(node);
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
case "br":
|
|
665
|
+
result = leaf({ kind: { type: "Text", content: "\n" }, style: {}, children: [] });
|
|
666
|
+
break;
|
|
667
|
+
case "ul":
|
|
668
|
+
case "ol": {
|
|
669
|
+
const items = [];
|
|
670
|
+
const itemAnns = [];
|
|
671
|
+
for (const li of v.children) {
|
|
672
|
+
if (typeof li === "string") continue;
|
|
673
|
+
const inner = childNodes(li, ctx);
|
|
674
|
+
items.push({ kind: { type: "ListItem" }, style: styleOf(li, ctx), children: inner.nodes });
|
|
675
|
+
itemAnns.push({ children: inner.anns });
|
|
676
|
+
}
|
|
677
|
+
result = {
|
|
678
|
+
// Marker names are the CSS `list-style-type` spellings, not capitalised
|
|
679
|
+
// variant names: the engine rejects the whole document over one wrong
|
|
680
|
+
// enum, so a single bullet list used to mean no PDF at all.
|
|
681
|
+
node: { kind: { type: "List", ordered: v.tag === "ol", marker_type: v.tag === "ol" ? "decimal" : "disc", start: 1 }, style: styleOf(v, ctx), children: items },
|
|
682
|
+
ann: { children: itemAnns }
|
|
683
|
+
};
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case "colgroup":
|
|
687
|
+
case "col":
|
|
688
|
+
return null;
|
|
689
|
+
default:
|
|
690
|
+
result = null;
|
|
691
|
+
}
|
|
692
|
+
if (!result) {
|
|
693
|
+
const style = styleOf(v, ctx);
|
|
694
|
+
const heading = /^h([1-6])$/.exec(v.tag);
|
|
695
|
+
const content = textOf(v);
|
|
696
|
+
const blank2 = !content.trim();
|
|
697
|
+
if (blank2 && !Object.keys(style).length && !v.children.length) return null;
|
|
698
|
+
const decls = declarationsOf(v);
|
|
699
|
+
const laysOut = decls.display === "flex" || decls.display === "inline-flex" || decls.display === "grid";
|
|
700
|
+
const textual = (TEXT_BLOCK_TAGS.has(v.tag) || INLINE_TAGS.has(v.tag) || heading) && !hasBlockChild(v) && !laysOut;
|
|
701
|
+
if (textual && (!blank2 || /bp-p--empty/.test(cls))) {
|
|
702
|
+
const kind = heading ? { type: "Heading", level: Number(heading[1]), content } : { type: "Text", content };
|
|
703
|
+
const node = { kind, style, children: [] };
|
|
704
|
+
if (v.tag === "a" && typeof v.props.href === "string") node.href = v.props.href;
|
|
705
|
+
result = leaf(node);
|
|
706
|
+
} else {
|
|
707
|
+
const inner = childNodes(v, ctx);
|
|
708
|
+
const draws = Object.keys(style).some((k) => /^border[A-Za-z]*Width$/.test(k)) || style.backgroundColor !== void 0 || style.height !== void 0;
|
|
709
|
+
if (inner.nodes.length === 0 && !draws) return null;
|
|
710
|
+
if (decls.display === "grid") applyGridAsFlex(style, inner.nodes, decls);
|
|
711
|
+
const node = { kind: { type: "View" }, style, children: inner.nodes };
|
|
712
|
+
if (v.tag === "a" && typeof v.props.href === "string") node.href = v.props.href;
|
|
713
|
+
result = { node, ann: { children: inner.anns } };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const key = v.props["data-key"];
|
|
717
|
+
if (typeof key === "string") {
|
|
718
|
+
result.ann.key = key;
|
|
719
|
+
if (holdsWrappedText(v)) result.ann.text = true;
|
|
720
|
+
if (!result.ann.rowKinds) {
|
|
721
|
+
const rows = findRowKinds(result.ann);
|
|
722
|
+
if (rows) result.ann.rowKinds = rows;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return result;
|
|
726
|
+
}
|
|
727
|
+
function applyGridAsFlex(style, children, decls) {
|
|
728
|
+
const tracks = /repeat\(\s*(\d+)/.exec(decls["grid-template-columns"] ?? "");
|
|
729
|
+
const spanned = children.filter((c) => typeof c.style.flexGrow === "number" && c.style.flexGrow > 0).length;
|
|
730
|
+
const perRow = tracks && spanned === 0 ? Number(tracks[1]) : children.length;
|
|
731
|
+
const wraps = children.length > perRow;
|
|
732
|
+
style.flexWrap = wraps ? "Wrap" : "NoWrap";
|
|
733
|
+
for (const child of children) {
|
|
734
|
+
if (child.style.flexGrow === void 0) {
|
|
735
|
+
child.style.flexGrow = 1;
|
|
736
|
+
child.style.flexBasis = wraps ? { Percent: 100 / Math.max(1, perRow) } : { Pt: 0 };
|
|
737
|
+
}
|
|
738
|
+
if (child.style.flexShrink === void 0) child.style.flexShrink = 1;
|
|
739
|
+
if (child.style.minWidth === void 0) child.style.minWidth = { Pt: 0 };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function findRowKinds(ann) {
|
|
743
|
+
if (ann.rowKinds) return ann.rowKinds;
|
|
744
|
+
for (const c of ann.children) {
|
|
745
|
+
const found = findRowKinds(c);
|
|
746
|
+
if (found) return found;
|
|
747
|
+
}
|
|
748
|
+
return void 0;
|
|
749
|
+
}
|
|
750
|
+
function vnodeToForme(v, ctx) {
|
|
751
|
+
return translate(v, ctx)?.node ?? null;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/watermark.ts
|
|
755
|
+
import { mapStyle as mapStyle2 } from "@formepdf/shared";
|
|
756
|
+
function watermarkNode(spec) {
|
|
757
|
+
return {
|
|
758
|
+
kind: { type: "Watermark", text: spec.text, font_size: spec.fontSize, angle: spec.angle },
|
|
759
|
+
style: mapStyle2({ color: spec.color, opacity: spec.opacity }),
|
|
760
|
+
children: []
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/measure.ts
|
|
765
|
+
import { renderNode } from "@broadpaper/core";
|
|
766
|
+
var UNBOUNDED_PAGE_HEIGHT = 2e5;
|
|
767
|
+
function descendants(el, nodeType, out = []) {
|
|
768
|
+
for (const c of el.children ?? []) {
|
|
769
|
+
if (c.nodeType === nodeType) out.push(c);
|
|
770
|
+
descendants(c, nodeType, out);
|
|
771
|
+
}
|
|
772
|
+
return out;
|
|
773
|
+
}
|
|
774
|
+
function collect(ann, el, origin, out) {
|
|
775
|
+
if (ann.key) {
|
|
776
|
+
const g = {
|
|
777
|
+
top: round2(el.y - origin.y),
|
|
778
|
+
height: round2(el.height),
|
|
779
|
+
// Both coordinates are relative to the section, which is what the
|
|
780
|
+
// paginator hands back as a repeated header’s offset inside the page
|
|
781
|
+
// content box. Leaving x absolute pushed every repeated table header one
|
|
782
|
+
// page margin to the right and its last column off the page.
|
|
783
|
+
left: round2(el.x - origin.x),
|
|
784
|
+
width: round2(el.width)
|
|
785
|
+
};
|
|
786
|
+
if (ann.text) {
|
|
787
|
+
const lines = descendants(el, "TextLine");
|
|
788
|
+
if (lines.length) {
|
|
789
|
+
const sorted = [...lines].sort((a, b) => a.y - b.y);
|
|
790
|
+
const bounds = [];
|
|
791
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
792
|
+
const bottom = sorted[i].y + sorted[i].height;
|
|
793
|
+
bounds.push(round2((bottom + sorted[i + 1].y) / 2 - origin.y));
|
|
794
|
+
}
|
|
795
|
+
bounds.push(round2(el.y + el.height - origin.y));
|
|
796
|
+
g.lines = bounds;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (ann.rowKinds) {
|
|
800
|
+
const rows = descendants(el, "TableRow").sort((a, b) => a.y - b.y);
|
|
801
|
+
g.rows = rows.map((r, i) => ({ top: round2(r.y - origin.y), height: round2(r.height), kind: ann.rowKinds[i] ?? "body" }));
|
|
802
|
+
}
|
|
803
|
+
out[ann.key] = g;
|
|
804
|
+
}
|
|
805
|
+
const kids = el.children ?? [];
|
|
806
|
+
for (let i = 0; i < ann.children.length; i++) {
|
|
807
|
+
const childEl = kids[i];
|
|
808
|
+
if (!childEl) continue;
|
|
809
|
+
collect(ann.children[i], childEl, origin, out);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function round2(n) {
|
|
813
|
+
return Math.round(n * 100) / 100;
|
|
814
|
+
}
|
|
815
|
+
function geometryFromLayout(sections, pageElements, width) {
|
|
816
|
+
const out = {};
|
|
817
|
+
for (let i = 0; i < sections.length; i++) {
|
|
818
|
+
const el = pageElements[i];
|
|
819
|
+
const { section, ann } = sections[i];
|
|
820
|
+
if (!el) {
|
|
821
|
+
out[section.key] = { height: 0, width, nodes: {} };
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const nodes = {};
|
|
825
|
+
collect(ann, el, { x: el.x, y: el.y }, nodes);
|
|
826
|
+
const geom = { height: round2(el.height), width, nodes };
|
|
827
|
+
nodes[section.key] = { top: 0, height: geom.height, left: 0, width: round2(el.width) };
|
|
828
|
+
out[section.key] = geom;
|
|
829
|
+
}
|
|
830
|
+
return out;
|
|
831
|
+
}
|
|
832
|
+
function scale(el, factor) {
|
|
833
|
+
return {
|
|
834
|
+
...el,
|
|
835
|
+
x: el.x * factor,
|
|
836
|
+
y: el.y * factor,
|
|
837
|
+
width: el.width * factor,
|
|
838
|
+
height: el.height * factor,
|
|
839
|
+
children: (el.children ?? []).map((c) => scale(c, factor))
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
var FormeMeasurer = class {
|
|
843
|
+
constructor(opts) {
|
|
844
|
+
this.opts = opts;
|
|
845
|
+
}
|
|
846
|
+
opts;
|
|
847
|
+
warnings = /* @__PURE__ */ new Set();
|
|
848
|
+
renderOptions() {
|
|
849
|
+
return {
|
|
850
|
+
registry: this.opts.registry,
|
|
851
|
+
theme: this.opts.theme,
|
|
852
|
+
styles: this.opts.styles,
|
|
853
|
+
mode: this.opts.mode ?? "preview",
|
|
854
|
+
page: { number: 1, total: 1 },
|
|
855
|
+
format: this.opts.format,
|
|
856
|
+
width: this.contentWidth
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
get contentWidth() {
|
|
860
|
+
return Math.max(0, this.opts.pageWidth - this.opts.margins.left - this.opts.margins.right);
|
|
861
|
+
}
|
|
862
|
+
async measure(sections, width = this.contentWidth) {
|
|
863
|
+
if (!sections.length) return {};
|
|
864
|
+
const ctx = { warnings: this.warnings, design: (this.opts.mode ?? "preview") === "design", suppressPageBreaks: true };
|
|
865
|
+
const renderOpts = { ...this.renderOptions(), width };
|
|
866
|
+
const measured = [];
|
|
867
|
+
for (const section of sections) {
|
|
868
|
+
const v = renderNode(section, renderOpts, width);
|
|
869
|
+
if (!v) continue;
|
|
870
|
+
const t = translate(v, ctx);
|
|
871
|
+
if (t) measured.push({ section, node: t.node, ann: t.ann });
|
|
872
|
+
}
|
|
873
|
+
if (!measured.length) return {};
|
|
874
|
+
const m = this.opts.margins;
|
|
875
|
+
const doc = {
|
|
876
|
+
children: [
|
|
877
|
+
{
|
|
878
|
+
kind: {
|
|
879
|
+
type: "Page",
|
|
880
|
+
config: {
|
|
881
|
+
// Tall enough that nothing can paginate: this is a measuring page.
|
|
882
|
+
size: { Custom: { width: pxToPt(this.opts.pageWidth), height: UNBOUNDED_PAGE_HEIGHT } },
|
|
883
|
+
margin: { top: pxToPt(m.top), right: pxToPt(m.right), bottom: pxToPt(m.bottom), left: pxToPt(m.left) },
|
|
884
|
+
wrap: true
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
style: {},
|
|
888
|
+
children: measured.map((x) => x.node)
|
|
889
|
+
}
|
|
890
|
+
],
|
|
891
|
+
metadata: {},
|
|
892
|
+
defaultPage: { size: "A4", margin: { top: 54, right: 54, bottom: 54, left: 54 }, wrap: true },
|
|
893
|
+
defaultStyle: {}
|
|
894
|
+
};
|
|
895
|
+
if (this.opts.fonts?.length) doc.fonts = this.opts.fonts;
|
|
896
|
+
const result = await this.opts.render(doc);
|
|
897
|
+
const pages = result.layout?.pages ?? [];
|
|
898
|
+
const toPx = 96 / 72;
|
|
899
|
+
const elements = (pages[0]?.elements ?? []).map((e) => scale(e, toPx));
|
|
900
|
+
for (const w of result.warnings ?? []) this.warnings.add(w);
|
|
901
|
+
return geometryFromLayout(measured, elements, width);
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// src/editor.ts
|
|
906
|
+
function hash(s) {
|
|
907
|
+
let h = 2166136261;
|
|
908
|
+
for (let i = 0; i < s.length; i++) {
|
|
909
|
+
h ^= s.charCodeAt(i);
|
|
910
|
+
h = Math.imul(h, 16777619);
|
|
911
|
+
}
|
|
912
|
+
return (h >>> 0).toString(36) + s.length.toString(36);
|
|
913
|
+
}
|
|
914
|
+
var FormeEditorMeasurer = class {
|
|
915
|
+
constructor(config) {
|
|
916
|
+
this.config = config;
|
|
917
|
+
}
|
|
918
|
+
config;
|
|
919
|
+
opts = null;
|
|
920
|
+
cache = /* @__PURE__ */ new Map();
|
|
921
|
+
seen = /* @__PURE__ */ new Set();
|
|
922
|
+
setOptions(opts) {
|
|
923
|
+
this.opts = opts;
|
|
924
|
+
}
|
|
925
|
+
clearCache() {
|
|
926
|
+
this.cache.clear();
|
|
927
|
+
}
|
|
928
|
+
destroy() {
|
|
929
|
+
this.cache.clear();
|
|
930
|
+
}
|
|
931
|
+
key(section, width, mode) {
|
|
932
|
+
return hash(JSON.stringify(section)) + ":" + Math.round(width * 100) + ":" + mode;
|
|
933
|
+
}
|
|
934
|
+
async measure(sections, width) {
|
|
935
|
+
const opts = this.opts;
|
|
936
|
+
if (!opts) throw new Error("FormeEditorMeasurer.setOptions() must be called before measure()");
|
|
937
|
+
const out = {};
|
|
938
|
+
const pending = [];
|
|
939
|
+
const keys = /* @__PURE__ */ new Map();
|
|
940
|
+
for (const section of sections) {
|
|
941
|
+
const k = this.key(section, width, opts.mode);
|
|
942
|
+
const hit = this.cache.get(k);
|
|
943
|
+
if (hit) out[section.key] = hit;
|
|
944
|
+
else {
|
|
945
|
+
pending.push(section);
|
|
946
|
+
keys.set(section.key, k);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (!pending.length) return out;
|
|
950
|
+
const measurer = new FormeMeasurer({
|
|
951
|
+
registry: opts.registry,
|
|
952
|
+
theme: opts.theme,
|
|
953
|
+
styles: opts.styles,
|
|
954
|
+
format: opts.format,
|
|
955
|
+
mode: opts.mode,
|
|
956
|
+
pageWidth: width,
|
|
957
|
+
margins: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
958
|
+
fonts: this.config.fonts,
|
|
959
|
+
render: this.config.render
|
|
960
|
+
});
|
|
961
|
+
const measured = await measurer.measure(pending, width);
|
|
962
|
+
for (const [sectionKey, geometry] of Object.entries(measured)) {
|
|
963
|
+
out[sectionKey] = geometry;
|
|
964
|
+
const k = keys.get(sectionKey);
|
|
965
|
+
if (k) this.cache.set(k, geometry);
|
|
966
|
+
}
|
|
967
|
+
for (const w of measurer.warnings) {
|
|
968
|
+
if (this.seen.has(w)) continue;
|
|
969
|
+
this.seen.add(w);
|
|
970
|
+
this.config.onWarning?.(w);
|
|
971
|
+
}
|
|
972
|
+
return out;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
// src/api.ts
|
|
977
|
+
import {
|
|
978
|
+
computePageGeometry,
|
|
979
|
+
mergeTheme,
|
|
980
|
+
renderNode as renderNode3,
|
|
981
|
+
resolveTemplate,
|
|
982
|
+
migrateTemplate
|
|
983
|
+
} from "@broadpaper/core";
|
|
984
|
+
import { mapStyle as mapStyle4 } from "@formepdf/shared";
|
|
985
|
+
import { producerString, resolveLicenseStatus, watermarkFor } from "@broadpaper/license";
|
|
986
|
+
import { BroadPaper, configure, resolveLicenseStatus as resolveLicenseStatus2, verifyLicense, parseLicense } from "@broadpaper/license";
|
|
987
|
+
import { computePageGeometry as pageGeometry, paginate, resolveValue } from "@broadpaper/core";
|
|
988
|
+
|
|
989
|
+
// src/paged.ts
|
|
990
|
+
import {
|
|
991
|
+
renderNode as renderNode2,
|
|
992
|
+
walkVNodes as walkVNodes2
|
|
993
|
+
} from "@broadpaper/core";
|
|
994
|
+
import { mapStyle as mapStyle3 } from "@formepdf/shared";
|
|
995
|
+
|
|
996
|
+
// src/clip.ts
|
|
997
|
+
import { styleString as styleString2 } from "@broadpaper/core";
|
|
998
|
+
var EPS = 0.01;
|
|
999
|
+
function cropToWindow(root, geom, from, to) {
|
|
1000
|
+
const w = { geom, from, to, dropped: 0 };
|
|
1001
|
+
const node = crop(root, w, void 0);
|
|
1002
|
+
return { node, offset: w.dropped };
|
|
1003
|
+
}
|
|
1004
|
+
function outside(w, top, height) {
|
|
1005
|
+
return top + height <= w.from + EPS || top >= w.to - EPS;
|
|
1006
|
+
}
|
|
1007
|
+
function crop(v, w, rows) {
|
|
1008
|
+
const key = typeof v.props["data-key"] === "string" ? v.props["data-key"] : void 0;
|
|
1009
|
+
const g = key ? w.geom.nodes[key] : void 0;
|
|
1010
|
+
if (g) {
|
|
1011
|
+
if (outside(w, g.top, g.height)) return blank(v, g.height);
|
|
1012
|
+
if (g.rows) rows = g.rows;
|
|
1013
|
+
}
|
|
1014
|
+
if (v.tag === "table" && rows) return cropTable(v, w, rows);
|
|
1015
|
+
let changed = false;
|
|
1016
|
+
const children = v.children.map((c) => {
|
|
1017
|
+
if (typeof c === "string") return c;
|
|
1018
|
+
const next = crop(c, w, rows);
|
|
1019
|
+
if (next !== c) changed = true;
|
|
1020
|
+
return next;
|
|
1021
|
+
});
|
|
1022
|
+
return changed ? { ...v, children } : v;
|
|
1023
|
+
}
|
|
1024
|
+
function blank(v, height) {
|
|
1025
|
+
const kept = styleString2(v.props.style).split(";").map((d) => d.trim()).filter((d) => d && !/^height\s*:/i.test(d));
|
|
1026
|
+
kept.push(`height:${round3(height)}px`);
|
|
1027
|
+
return { ...v, props: { ...v.props, style: kept.join(";") }, children: [] };
|
|
1028
|
+
}
|
|
1029
|
+
function cropTable(table, w, rows) {
|
|
1030
|
+
if (rows.length === 0) return table;
|
|
1031
|
+
const keep = rows.map((r) => !outside(w, r.top, r.height));
|
|
1032
|
+
if (keep.every(Boolean)) return table;
|
|
1033
|
+
const first = keep.indexOf(true);
|
|
1034
|
+
if (first === -1) return { tag: "div", props: { style: "height:0" }, children: [] };
|
|
1035
|
+
w.dropped += rows[first].top - rows[0].top;
|
|
1036
|
+
let index = 0;
|
|
1037
|
+
const rewrite = (node) => {
|
|
1038
|
+
const children = [];
|
|
1039
|
+
for (const c of node.children) {
|
|
1040
|
+
if (typeof c === "string") continue;
|
|
1041
|
+
if (c.tag === "thead" || c.tag === "tbody" || c.tag === "tfoot") {
|
|
1042
|
+
const next = rewrite(c);
|
|
1043
|
+
if (next) children.push(next);
|
|
1044
|
+
} else if (c.tag === "tr") {
|
|
1045
|
+
if (keep[index++]) children.push(c);
|
|
1046
|
+
} else children.push(c);
|
|
1047
|
+
}
|
|
1048
|
+
if (node !== table && !children.some((c) => typeof c !== "string" && c.tag === "tr")) return null;
|
|
1049
|
+
return { ...node, children };
|
|
1050
|
+
};
|
|
1051
|
+
return rewrite(table);
|
|
1052
|
+
}
|
|
1053
|
+
function round3(n) {
|
|
1054
|
+
return Math.round(n * 100) / 100;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/paged.ts
|
|
1058
|
+
function box(left, top, width, height, children, clip = true, bleed = 0) {
|
|
1059
|
+
const style = mapStyle3({
|
|
1060
|
+
position: "absolute",
|
|
1061
|
+
left: pxToPt(left),
|
|
1062
|
+
top: pxToPt(top),
|
|
1063
|
+
width: pxToPt(width + bleed),
|
|
1064
|
+
height: pxToPt(height),
|
|
1065
|
+
...clip ? { overflow: "hidden" } : {}
|
|
1066
|
+
});
|
|
1067
|
+
return { kind: { type: "View" }, style, children };
|
|
1068
|
+
}
|
|
1069
|
+
function headerOnly(v) {
|
|
1070
|
+
const clone = structuredClone(v);
|
|
1071
|
+
walkVNodes2(clone, (n) => {
|
|
1072
|
+
if (n.tag === "table") {
|
|
1073
|
+
n.children = n.children.filter((c) => typeof c !== "string" && (c.tag === "colgroup" || c.tag === "thead"));
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
return true;
|
|
1077
|
+
});
|
|
1078
|
+
return clone;
|
|
1079
|
+
}
|
|
1080
|
+
function sectionsByKey(doc) {
|
|
1081
|
+
const map = /* @__PURE__ */ new Map();
|
|
1082
|
+
const visit = (n) => {
|
|
1083
|
+
map.set(n.key, n);
|
|
1084
|
+
n.children.forEach(visit);
|
|
1085
|
+
};
|
|
1086
|
+
for (const list of [doc.body, doc.header.default, doc.header.firstPage ?? [], doc.footer.default, doc.footer.firstPage ?? []]) list.forEach(visit);
|
|
1087
|
+
return map;
|
|
1088
|
+
}
|
|
1089
|
+
function isPageDependent(nodes) {
|
|
1090
|
+
const check = (n) => n.pageDependent === true || n.children.some(check);
|
|
1091
|
+
return nodes.some(check);
|
|
1092
|
+
}
|
|
1093
|
+
function emitPages(input) {
|
|
1094
|
+
const { resolved, paged, page, renderOpts, ctx, geometry } = input;
|
|
1095
|
+
const byKey = sectionsByKey(resolved);
|
|
1096
|
+
const width = page.contentWidth;
|
|
1097
|
+
const bleed = page.margins.right;
|
|
1098
|
+
const bodyDependsOnPage = isPageDependent(resolved.body);
|
|
1099
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1100
|
+
const translateSection = (section, opts, from, to) => {
|
|
1101
|
+
const cacheKey = `${section.key}|${from}|${to}`;
|
|
1102
|
+
if (!bodyDependsOnPage && cache.has(cacheKey)) return cache.get(cacheKey);
|
|
1103
|
+
const v = renderNode2(section, opts, width);
|
|
1104
|
+
if (!v) return null;
|
|
1105
|
+
const geom = geometry[section.key];
|
|
1106
|
+
const cropped = geom ? cropToWindow(v, geom, from, to) : { node: v, offset: 0 };
|
|
1107
|
+
const t = translate(cropped.node, ctx);
|
|
1108
|
+
if (!t) return null;
|
|
1109
|
+
const out = { node: t.node, offset: cropped.offset };
|
|
1110
|
+
if (!bodyDependsOnPage) cache.set(cacheKey, out);
|
|
1111
|
+
return out;
|
|
1112
|
+
};
|
|
1113
|
+
const region = (sections, opts) => {
|
|
1114
|
+
const out = [];
|
|
1115
|
+
for (const s of sections) {
|
|
1116
|
+
const v = renderNode2(s, opts, width);
|
|
1117
|
+
if (!v) continue;
|
|
1118
|
+
const t = translate(v, ctx);
|
|
1119
|
+
if (t) out.push(t.node);
|
|
1120
|
+
}
|
|
1121
|
+
return out;
|
|
1122
|
+
};
|
|
1123
|
+
return paged.pages.map((p) => {
|
|
1124
|
+
const opts = { ...renderOpts, page: { number: p.number, total: paged.totalPages } };
|
|
1125
|
+
const children = [];
|
|
1126
|
+
if (input.watermark) children.push(watermarkNode(input.watermark));
|
|
1127
|
+
const headerSections = p.first && resolved.header.firstPage ? resolved.header.firstPage : resolved.header.default;
|
|
1128
|
+
if (headerSections.length && page.headerHeight > 0) {
|
|
1129
|
+
children.push(box(page.margins.left, page.headerTop, width, page.headerHeight, region(headerSections, opts)));
|
|
1130
|
+
}
|
|
1131
|
+
const fragments = [];
|
|
1132
|
+
for (const f of p.fragments) {
|
|
1133
|
+
const section = byKey.get(f.sectionKey);
|
|
1134
|
+
if (!section) continue;
|
|
1135
|
+
const extra = f.repeatHeaders.reduce((a, r) => a + r.height, 0);
|
|
1136
|
+
const inner = [];
|
|
1137
|
+
for (const r of f.repeatHeaders) {
|
|
1138
|
+
const table = byKey.get(r.tableKey);
|
|
1139
|
+
if (!table) continue;
|
|
1140
|
+
const v = renderNode2(table, opts, r.width);
|
|
1141
|
+
if (!v) continue;
|
|
1142
|
+
const t = translate(headerOnly(v), ctx);
|
|
1143
|
+
if (t) inner.push(box(r.left, 0, r.width, r.height, [t.node]));
|
|
1144
|
+
}
|
|
1145
|
+
const content = translateSection(section, opts, f.from, f.to);
|
|
1146
|
+
if (content) {
|
|
1147
|
+
const shifted = {
|
|
1148
|
+
kind: { type: "View" },
|
|
1149
|
+
style: mapStyle3({ position: "absolute", left: 0, top: pxToPt(content.offset - f.from), width: pxToPt(width) }),
|
|
1150
|
+
children: [content.node]
|
|
1151
|
+
};
|
|
1152
|
+
inner.push(box(0, extra, width, f.to - f.from, [shifted], true, bleed));
|
|
1153
|
+
}
|
|
1154
|
+
fragments.push(box(0, f.y, width, f.height, inner, true, bleed));
|
|
1155
|
+
}
|
|
1156
|
+
children.push(box(page.margins.left, page.contentTop, width, page.contentHeight, fragments, true, bleed));
|
|
1157
|
+
const footerSections = p.first && resolved.footer.firstPage ? resolved.footer.firstPage : resolved.footer.default;
|
|
1158
|
+
if (footerSections.length && page.footerHeight > 0) {
|
|
1159
|
+
children.push(box(page.margins.left, page.footerTop, width, page.footerHeight, region(footerSections, opts)));
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
kind: {
|
|
1163
|
+
type: "Page",
|
|
1164
|
+
config: {
|
|
1165
|
+
size: { Custom: { width: pxToPt(page.width), height: pxToPt(page.height) } },
|
|
1166
|
+
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
1167
|
+
// Nothing may reflow: this page is exactly what the paginator decided.
|
|
1168
|
+
wrap: false
|
|
1169
|
+
}
|
|
1170
|
+
},
|
|
1171
|
+
style: input.background ? mapStyle3({ backgroundColor: input.background }) : {},
|
|
1172
|
+
children
|
|
1173
|
+
};
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/api.ts
|
|
1178
|
+
async function buildFormeDocument(opts) {
|
|
1179
|
+
const license = await resolveLicenseStatus(opts.license !== void 0 ? { license: opts.license } : {});
|
|
1180
|
+
const { template } = migrateTemplate(opts.template);
|
|
1181
|
+
const theme = mergeTheme(opts.theme);
|
|
1182
|
+
const geom = computePageGeometry(template.page);
|
|
1183
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
1184
|
+
const format = {
|
|
1185
|
+
locale: opts.locale ?? template.settings?.locale ?? "en-GB",
|
|
1186
|
+
currency: opts.currency ?? template.settings?.currency ?? "GBP",
|
|
1187
|
+
timeZone: opts.timeZone ?? template.settings?.timeZone,
|
|
1188
|
+
now
|
|
1189
|
+
};
|
|
1190
|
+
const resolved = resolveTemplate({
|
|
1191
|
+
template,
|
|
1192
|
+
registry: opts.registry,
|
|
1193
|
+
theme,
|
|
1194
|
+
dataSources: opts.dataSources,
|
|
1195
|
+
data: opts.data,
|
|
1196
|
+
mode: "preview",
|
|
1197
|
+
now,
|
|
1198
|
+
locale: format.locale,
|
|
1199
|
+
currency: format.currency,
|
|
1200
|
+
timeZone: format.timeZone
|
|
1201
|
+
});
|
|
1202
|
+
const renderOpts = {
|
|
1203
|
+
registry: opts.registry,
|
|
1204
|
+
theme,
|
|
1205
|
+
styles: template.styles,
|
|
1206
|
+
mode: "preview",
|
|
1207
|
+
page: { number: 1, total: 1 },
|
|
1208
|
+
format,
|
|
1209
|
+
width: geom.contentWidth
|
|
1210
|
+
};
|
|
1211
|
+
const ctx = { warnings: /* @__PURE__ */ new Set() };
|
|
1212
|
+
const translate2 = (sections) => {
|
|
1213
|
+
const out = [];
|
|
1214
|
+
for (const s of sections) {
|
|
1215
|
+
const v = renderNode3(s, renderOpts, geom.contentWidth);
|
|
1216
|
+
if (!v) continue;
|
|
1217
|
+
const node = vnodeToForme(v, ctx);
|
|
1218
|
+
if (node) out.push(node);
|
|
1219
|
+
}
|
|
1220
|
+
return out;
|
|
1221
|
+
};
|
|
1222
|
+
const children = [];
|
|
1223
|
+
const mark = watermarkFor(license);
|
|
1224
|
+
if (mark) children.push(watermarkNode(mark));
|
|
1225
|
+
const header = translate2(resolved.header.default);
|
|
1226
|
+
if (header.length) children.push({ kind: { type: "Fixed", position: "Header" }, style: {}, children: header });
|
|
1227
|
+
const footer = translate2(resolved.footer.default);
|
|
1228
|
+
if (footer.length) children.push({ kind: { type: "Fixed", position: "Footer" }, style: {}, children: footer });
|
|
1229
|
+
children.push(...translate2(resolved.body));
|
|
1230
|
+
const page = {
|
|
1231
|
+
kind: {
|
|
1232
|
+
type: "Page",
|
|
1233
|
+
config: {
|
|
1234
|
+
size: { Custom: { width: pxToPt(geom.width), height: pxToPt(geom.height) } },
|
|
1235
|
+
margin: { top: pxToPt(geom.margins.top), right: pxToPt(geom.margins.right), bottom: pxToPt(geom.margins.bottom), left: pxToPt(geom.margins.left) },
|
|
1236
|
+
wrap: true
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
style: {},
|
|
1240
|
+
children
|
|
1241
|
+
};
|
|
1242
|
+
const doc = {
|
|
1243
|
+
children: [page],
|
|
1244
|
+
metadata: { title: template.name, creator: producerString(license), ...opts.metadata ?? {} },
|
|
1245
|
+
defaultPage: { size: "A4", margin: { top: 54, right: 54, bottom: 54, left: 54 }, wrap: true },
|
|
1246
|
+
defaultStyle: mapStyle4({ fontSize: pxToPt(theme.typography.baseFontSize ?? 11), fontFamily: theme.typography.bodyFont.split(",")[0].trim().replace(/^['"]|['"]$/g, "") })
|
|
1247
|
+
};
|
|
1248
|
+
if (opts.fonts?.length) doc.fonts = opts.fonts.map((f) => ({ family: f.family, src: f.src, weight: f.weight ?? 400, style: f.style ?? "normal" }));
|
|
1249
|
+
if (opts.tagged) doc.tagged = true;
|
|
1250
|
+
if (opts.pdfUa) doc.pdfUa = true;
|
|
1251
|
+
if (opts.pdfA) doc.pdfa = opts.pdfA;
|
|
1252
|
+
const warnings = [...ctx.warnings, ...resolved.warnings.map((w) => w.message)];
|
|
1253
|
+
return { doc, warnings, license };
|
|
1254
|
+
}
|
|
1255
|
+
var engineLoader = null;
|
|
1256
|
+
var engine = null;
|
|
1257
|
+
function setEngineLoader(loader) {
|
|
1258
|
+
engineLoader = loader;
|
|
1259
|
+
}
|
|
1260
|
+
async function defaultRenderer(doc) {
|
|
1261
|
+
if (!engineLoader) {
|
|
1262
|
+
throw new Error(
|
|
1263
|
+
"No Forme engine is available. Import @broadpaper/forme from its package entry point, or pass a `renderer` to render with an engine you loaded yourself."
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
engine ??= await engineLoader();
|
|
1267
|
+
return engine(doc);
|
|
1268
|
+
}
|
|
1269
|
+
async function renderPdf(opts) {
|
|
1270
|
+
const started = Date.now();
|
|
1271
|
+
const { doc, warnings } = await buildFormeDocument(opts);
|
|
1272
|
+
const render = opts.renderer ?? defaultRenderer;
|
|
1273
|
+
const result = await render(doc);
|
|
1274
|
+
const layout = result.layout ?? null;
|
|
1275
|
+
return {
|
|
1276
|
+
pdf: result.pdf,
|
|
1277
|
+
pages: layout?.pages.length ?? 0,
|
|
1278
|
+
layout,
|
|
1279
|
+
warnings: [...warnings, ...result.warnings ?? []],
|
|
1280
|
+
durationMs: Date.now() - started
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
async function renderPdfPaginated(opts) {
|
|
1284
|
+
const started = Date.now();
|
|
1285
|
+
const license = await resolveLicenseStatus(opts.license !== void 0 ? { license: opts.license } : {});
|
|
1286
|
+
const { template } = migrateTemplate(opts.template);
|
|
1287
|
+
const theme = mergeTheme(opts.theme);
|
|
1288
|
+
const geom = pageGeometry(template.page);
|
|
1289
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
1290
|
+
const format = {
|
|
1291
|
+
locale: opts.locale ?? template.settings?.locale ?? "en-GB",
|
|
1292
|
+
currency: opts.currency ?? template.settings?.currency ?? "GBP",
|
|
1293
|
+
timeZone: opts.timeZone ?? template.settings?.timeZone,
|
|
1294
|
+
now
|
|
1295
|
+
};
|
|
1296
|
+
const resolved = resolveTemplate({
|
|
1297
|
+
template,
|
|
1298
|
+
registry: opts.registry,
|
|
1299
|
+
theme,
|
|
1300
|
+
dataSources: opts.dataSources,
|
|
1301
|
+
data: opts.data,
|
|
1302
|
+
mode: "preview",
|
|
1303
|
+
now,
|
|
1304
|
+
locale: format.locale,
|
|
1305
|
+
currency: format.currency,
|
|
1306
|
+
timeZone: format.timeZone
|
|
1307
|
+
});
|
|
1308
|
+
const renderOpts = {
|
|
1309
|
+
registry: opts.registry,
|
|
1310
|
+
theme,
|
|
1311
|
+
styles: template.styles,
|
|
1312
|
+
mode: "preview",
|
|
1313
|
+
page: { number: 1, total: 1 },
|
|
1314
|
+
format,
|
|
1315
|
+
width: geom.contentWidth
|
|
1316
|
+
};
|
|
1317
|
+
const ctx = { warnings: /* @__PURE__ */ new Set(), suppressPageBreaks: true };
|
|
1318
|
+
const render = opts.renderer ?? defaultRenderer;
|
|
1319
|
+
const measure = async (sections) => {
|
|
1320
|
+
if (!sections.length) return {};
|
|
1321
|
+
const measured = [];
|
|
1322
|
+
for (const section of sections) {
|
|
1323
|
+
const v = renderNode3(section, renderOpts, geom.contentWidth);
|
|
1324
|
+
if (!v) continue;
|
|
1325
|
+
const t = translate(v, ctx);
|
|
1326
|
+
if (t) measured.push({ section, node: t.node, ann: t.ann });
|
|
1327
|
+
}
|
|
1328
|
+
if (!measured.length) return {};
|
|
1329
|
+
const doc2 = {
|
|
1330
|
+
children: [
|
|
1331
|
+
{
|
|
1332
|
+
kind: {
|
|
1333
|
+
type: "Page",
|
|
1334
|
+
config: {
|
|
1335
|
+
size: { Custom: { width: pxToPt(geom.width), height: UNBOUNDED_PAGE_HEIGHT } },
|
|
1336
|
+
margin: { top: pxToPt(geom.margins.top), right: pxToPt(geom.margins.right), bottom: pxToPt(geom.margins.bottom), left: pxToPt(geom.margins.left) },
|
|
1337
|
+
wrap: true
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
style: {},
|
|
1341
|
+
children: measured.map((m) => m.node)
|
|
1342
|
+
}
|
|
1343
|
+
],
|
|
1344
|
+
metadata: {},
|
|
1345
|
+
defaultPage: { size: "A4", margin: { top: 54, right: 54, bottom: 54, left: 54 }, wrap: true },
|
|
1346
|
+
defaultStyle: baseStyle(theme)
|
|
1347
|
+
};
|
|
1348
|
+
if (opts.fonts?.length) doc2.fonts = fontEntries(opts.fonts);
|
|
1349
|
+
const result2 = await render(doc2);
|
|
1350
|
+
const pages2 = result2.layout?.pages ?? [];
|
|
1351
|
+
const scale2 = 1 / PX_TO_PT_LOCAL;
|
|
1352
|
+
const scaled = pages2[0]?.elements.map((e) => scaleElement(e, scale2)) ?? [];
|
|
1353
|
+
return geometryFromLayout(measured, scaled, geom.contentWidth);
|
|
1354
|
+
};
|
|
1355
|
+
const geometry = await measure(resolved.body);
|
|
1356
|
+
const regionGeometry = await measure([...resolved.header.default, ...resolved.header.firstPage ?? [], ...resolved.footer.default, ...resolved.footer.firstPage ?? []]);
|
|
1357
|
+
const paged = paginate({ document: resolved, geometry, page: geom, minPages: 1, regionGeometry });
|
|
1358
|
+
const pages = emitPages({
|
|
1359
|
+
resolved,
|
|
1360
|
+
paged,
|
|
1361
|
+
page: geom,
|
|
1362
|
+
renderOpts,
|
|
1363
|
+
ctx,
|
|
1364
|
+
geometry,
|
|
1365
|
+
background: template.page.background ? resolveValue(template.page.background, theme, "#ffffff") : void 0,
|
|
1366
|
+
watermark: watermarkFor(license)
|
|
1367
|
+
});
|
|
1368
|
+
const doc = {
|
|
1369
|
+
children: pages,
|
|
1370
|
+
metadata: { title: template.name, creator: producerString(license), ...opts.metadata ?? {} },
|
|
1371
|
+
defaultPage: { size: "A4", margin: { top: 54, right: 54, bottom: 54, left: 54 }, wrap: true },
|
|
1372
|
+
defaultStyle: baseStyle(theme)
|
|
1373
|
+
};
|
|
1374
|
+
if (opts.fonts?.length) doc.fonts = fontEntries(opts.fonts);
|
|
1375
|
+
if (opts.tagged) doc.tagged = true;
|
|
1376
|
+
if (opts.pdfUa) doc.pdfUa = true;
|
|
1377
|
+
if (opts.pdfA) doc.pdfa = opts.pdfA;
|
|
1378
|
+
const result = await render(doc);
|
|
1379
|
+
const layout = result.layout ?? null;
|
|
1380
|
+
return {
|
|
1381
|
+
pdf: result.pdf,
|
|
1382
|
+
pages: layout?.pages.length ?? paged.totalPages,
|
|
1383
|
+
layout,
|
|
1384
|
+
paged,
|
|
1385
|
+
geometry,
|
|
1386
|
+
warnings: [
|
|
1387
|
+
...ctx.warnings,
|
|
1388
|
+
...resolved.warnings.map((w) => w.message),
|
|
1389
|
+
...paged.warnings.map((w) => w.message),
|
|
1390
|
+
...result.warnings ?? []
|
|
1391
|
+
],
|
|
1392
|
+
durationMs: Date.now() - started
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
var PX_TO_PT_LOCAL = 72 / 96;
|
|
1396
|
+
function scaleElement(el, scale2) {
|
|
1397
|
+
return {
|
|
1398
|
+
...el,
|
|
1399
|
+
x: el.x * scale2,
|
|
1400
|
+
y: el.y * scale2,
|
|
1401
|
+
width: el.width * scale2,
|
|
1402
|
+
height: el.height * scale2,
|
|
1403
|
+
children: (el.children ?? []).map((c) => scaleElement(c, scale2))
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
function baseStyle(theme) {
|
|
1407
|
+
return mapStyle4({
|
|
1408
|
+
fontSize: pxToPt(theme.typography.baseFontSize ?? 11),
|
|
1409
|
+
fontFamily: theme.typography.bodyFont.split(",")[0].trim().replace(/^['"]|['"]$/g, "")
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
function fontEntries(fonts) {
|
|
1413
|
+
return fonts.map((f) => ({ family: f.family, src: f.src, weight: f.weight ?? 400, style: f.style ?? "normal" }));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
export {
|
|
1417
|
+
PX_TO_PT,
|
|
1418
|
+
pxToPt,
|
|
1419
|
+
parseDeclarations,
|
|
1420
|
+
translateStyle,
|
|
1421
|
+
translate,
|
|
1422
|
+
vnodeToForme,
|
|
1423
|
+
watermarkNode,
|
|
1424
|
+
UNBOUNDED_PAGE_HEIGHT,
|
|
1425
|
+
geometryFromLayout,
|
|
1426
|
+
FormeMeasurer,
|
|
1427
|
+
FormeEditorMeasurer,
|
|
1428
|
+
buildFormeDocument,
|
|
1429
|
+
setEngineLoader,
|
|
1430
|
+
renderPdf,
|
|
1431
|
+
renderPdfPaginated,
|
|
1432
|
+
BroadPaper,
|
|
1433
|
+
configure,
|
|
1434
|
+
resolveLicenseStatus2 as resolveLicenseStatus,
|
|
1435
|
+
verifyLicense,
|
|
1436
|
+
parseLicense
|
|
1437
|
+
};
|
|
1438
|
+
//# sourceMappingURL=chunk-KE7FFMY2.js.map
|