@hyper-elements/react 0.0.1 → 0.3.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/dist/pdf.js ADDED
@@ -0,0 +1,1254 @@
1
+ import { createHash } from 'crypto';
2
+ import { View, Page, renderToBuffer, Font, Path, Polygon, Line, Ellipse, Circle, Rect, Svg, Link, Image, Text, Document } from '@react-pdf/renderer';
3
+ import { createElement } from 'react';
4
+ import { prepare, ElementsError } from '@hyper-elements/react';
5
+
6
+ // ../core/dist/pdf.js
7
+ var ok = (value) => ({ ok: true, value });
8
+ var fail = (message) => ({ ok: false, message });
9
+ var show = (input) => {
10
+ try {
11
+ return typeof input === "string" ? JSON.stringify(input) : typeof input === "function" ? "a function" : JSON.stringify(input) ?? String(input);
12
+ } catch {
13
+ return String(input);
14
+ }
15
+ };
16
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17
+ function kind(spec) {
18
+ return spec;
19
+ }
20
+ var NUMBER = "-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)";
21
+ var LENGTH = new RegExp(`^(${NUMBER})(px|%|pt|mm|in)?$`);
22
+ function readLength(input) {
23
+ if (typeof input === "number") return Number.isFinite(input) ? { value: input, unit: "px" } : void 0;
24
+ if (typeof input !== "string") return void 0;
25
+ const match = LENGTH.exec(input.trim());
26
+ return match ? { value: Number(match[1]), unit: match[2] ?? "px" } : void 0;
27
+ }
28
+ function px(input, min, what) {
29
+ const length2 = readLength(input);
30
+ if (!length2 || length2.unit !== "px") return fail(`${what} must be a number of px or a px string ("8px"), not ${show(input)}`);
31
+ if (length2.value < min) return fail(`${what} can't be less than ${min}, not ${show(input)}`);
32
+ return ok(length2.value);
33
+ }
34
+ function length(units = ["px"], options = {}) {
35
+ const min = options.min ?? 0;
36
+ const accepts = `a length (${units.map((u) => u === "px" ? "a number of px" : u).join(", ")})`;
37
+ return kind({
38
+ name: "length",
39
+ editor: "length",
40
+ accepts,
41
+ parse(input) {
42
+ const parsed = readLength(input);
43
+ if (!parsed) return fail(`expected ${accepts}, not ${show(input)}`);
44
+ if (!units.includes(parsed.unit)) return fail(`${parsed.unit} isn't accepted here; expected ${accepts}`);
45
+ if (parsed.value < min) return fail(`can't be less than ${min}, not ${show(input)}`);
46
+ return ok(parsed.unit === "px" ? parsed.value : `${parsed.value}${parsed.unit}`);
47
+ },
48
+ jsonSchema: () => ({
49
+ anyOf: [
50
+ ...units.includes("px") ? [{ type: "number", minimum: min }] : [],
51
+ { type: "string", pattern: `^${NUMBER}(${units.map((u) => u === "%" ? "%" : u).join("|")})${units.includes("px") ? "?" : ""}$` }
52
+ ]
53
+ })
54
+ });
55
+ }
56
+ var SIDES = ["top", "right", "bottom", "left"];
57
+ function fourSides(values) {
58
+ const [a, b = a, c = a, d = b] = values;
59
+ return [a, b, c, d];
60
+ }
61
+ function insets() {
62
+ const accepts = 'px for all sides, "8px 16px" (1 to 4 values), [v, h] or [t, r, b, l], or { top, right, bottom, left }';
63
+ const read = (values) => {
64
+ if (values.length < 1 || values.length > 4) return fail(`expected ${accepts}`);
65
+ const out = [];
66
+ for (const value of values) {
67
+ const side = px(value, 0, "each side");
68
+ if (!side.ok) return side;
69
+ out.push(side.value);
70
+ }
71
+ const [top, right, bottom, left] = fourSides(out);
72
+ return ok({ top, right, bottom, left });
73
+ };
74
+ return kind({
75
+ name: "insets",
76
+ editor: "insets",
77
+ accepts,
78
+ parse(input) {
79
+ if (typeof input === "number") return read([input]);
80
+ if (typeof input === "string") return read(input.trim().split(/\s+/));
81
+ if (Array.isArray(input)) return read(input);
82
+ if (isObject(input)) {
83
+ const extra = Object.keys(input).find((key) => !SIDES.includes(key));
84
+ if (extra) return fail(`"${extra}" isn't a side; expected top, right, bottom or left`);
85
+ const out = { top: 0, right: 0, bottom: 0, left: 0 };
86
+ for (const side of SIDES) {
87
+ if (input[side] === void 0) continue;
88
+ const value = px(input[side], 0, side);
89
+ if (!value.ok) return value;
90
+ out[side] = value.value;
91
+ }
92
+ return ok(out);
93
+ }
94
+ return fail(`expected ${accepts}, not ${show(input)}`);
95
+ },
96
+ jsonSchema: () => {
97
+ const side = { anyOf: [{ type: "number", minimum: 0 }, { type: "string", pattern: `^${NUMBER}(px)?$` }] };
98
+ return {
99
+ anyOf: [
100
+ side.anyOf[0],
101
+ { type: "string" },
102
+ { type: "array", items: side, minItems: 1, maxItems: 4 },
103
+ { type: "object", properties: Object.fromEntries(SIDES.map((s) => [s, side])), additionalProperties: false }
104
+ ]
105
+ };
106
+ }
107
+ });
108
+ }
109
+ var BORDER_STYLES = ["solid", "dashed", "dotted", "none"];
110
+ function borderSide(input) {
111
+ const out = { width: 1, style: "solid", color: "#000000" };
112
+ if (typeof input === "string") {
113
+ for (const token of input.match(/rgba?\([^)]*\)|\S+/g) ?? []) {
114
+ if (readLength(token)) {
115
+ const width = px(token, 0, "a border's width");
116
+ if (!width.ok) return width;
117
+ out.width = width.value;
118
+ } else if (BORDER_STYLES.includes(token)) {
119
+ out.style = token;
120
+ } else {
121
+ const c = parseColor(token);
122
+ if (!c.ok) return fail(`"${token}" in ${show(input)} isn't a width, a style (${BORDER_STYLES.join(", ")}) or a color`);
123
+ out.color = c.value;
124
+ }
125
+ }
126
+ return ok(out);
127
+ }
128
+ if (!isObject(input)) return fail(`expected "1px solid #ccc" or { width, style, color }, not ${show(input)}`);
129
+ const extra = Object.keys(input).find((key) => !["width", "style", "color"].includes(key));
130
+ if (extra) return fail(`"${extra}" isn't part of a border side; expected width, style or color`);
131
+ if (input.width !== void 0) {
132
+ const width = px(input.width, 0, "a border's width");
133
+ if (!width.ok) return width;
134
+ out.width = width.value;
135
+ }
136
+ if (input.style !== void 0) {
137
+ if (!BORDER_STYLES.includes(input.style)) return fail(`a border's style is one of ${BORDER_STYLES.join(", ")}, not ${show(input.style)}`);
138
+ out.style = input.style;
139
+ }
140
+ if (input.color !== void 0) {
141
+ const c = parseColor(input.color);
142
+ if (!c.ok) return c;
143
+ out.color = c.value;
144
+ }
145
+ return ok(out);
146
+ }
147
+ function border() {
148
+ return kind({
149
+ name: "border",
150
+ editor: "border",
151
+ accepts: '"1px solid #ccc", { width, style, color } for all sides, or { top, right, bottom, left } with one of those each',
152
+ parse(input) {
153
+ const perSide = isObject(input) && Object.keys(input).some((key) => SIDES.includes(key));
154
+ if (!perSide) {
155
+ const side = borderSide(input);
156
+ return side.ok ? ok({ top: side.value, right: side.value, bottom: side.value, left: side.value }) : side;
157
+ }
158
+ const out = {};
159
+ for (const [key, value] of Object.entries(input)) {
160
+ if (!SIDES.includes(key)) return fail(`"${key}" isn't a side; expected top, right, bottom or left`);
161
+ const side = borderSide(value);
162
+ if (!side.ok) return fail(`${key}: ${side.message}`);
163
+ out[key] = side.value;
164
+ }
165
+ return ok(out);
166
+ },
167
+ jsonSchema: () => {
168
+ const side = {
169
+ anyOf: [
170
+ { type: "string" },
171
+ {
172
+ type: "object",
173
+ properties: { width: { anyOf: [{ type: "number", minimum: 0 }, { type: "string" }] }, style: { enum: [...BORDER_STYLES] }, color: { type: "string" } },
174
+ additionalProperties: false
175
+ }
176
+ ]
177
+ };
178
+ return { anyOf: [...side.anyOf, { type: "object", properties: Object.fromEntries(SIDES.map((s) => [s, side])), additionalProperties: false }] };
179
+ }
180
+ });
181
+ }
182
+ var CORNERS = ["topLeft", "topRight", "bottomRight", "bottomLeft"];
183
+ function radius() {
184
+ const accepts = "px for every corner, [topLeft, topRight, bottomRight, bottomLeft], or { topLeft, topRight, bottomRight, bottomLeft }";
185
+ return kind({
186
+ name: "radius",
187
+ editor: "radius",
188
+ accepts,
189
+ parse(input) {
190
+ let values;
191
+ if (Array.isArray(input)) {
192
+ if (input.length !== 4) return fail(`expected ${accepts}`);
193
+ values = input;
194
+ } else if (isObject(input)) {
195
+ const extra = Object.keys(input).find((key) => !CORNERS.includes(key));
196
+ if (extra) return fail(`"${extra}" isn't a corner; expected ${CORNERS.join(", ")}`);
197
+ values = CORNERS.map((corner) => input[corner] ?? 0);
198
+ } else {
199
+ return px(input, 0, "a radius");
200
+ }
201
+ const out = [];
202
+ for (const value of values) {
203
+ const corner = px(value, 0, "a corner");
204
+ if (!corner.ok) return corner;
205
+ out.push(corner.value);
206
+ }
207
+ if (out.every((value) => value === out[0])) return ok(out[0]);
208
+ const [topLeft, topRight, bottomRight, bottomLeft] = out;
209
+ return ok({ topLeft, topRight, bottomRight, bottomLeft });
210
+ },
211
+ jsonSchema: () => {
212
+ const corner = { anyOf: [{ type: "number", minimum: 0 }, { type: "string", pattern: `^${NUMBER}(px)?$` }] };
213
+ return {
214
+ anyOf: [
215
+ ...corner.anyOf,
216
+ { type: "array", items: corner, minItems: 4, maxItems: 4 },
217
+ { type: "object", properties: Object.fromEntries(CORNERS.map((c) => [c, corner])), additionalProperties: false }
218
+ ]
219
+ };
220
+ }
221
+ });
222
+ }
223
+ function enumOf(values) {
224
+ const accepts = `one of ${values.map((v) => JSON.stringify(v)).join(", ")}`;
225
+ return kind({
226
+ name: "enum",
227
+ editor: "choice",
228
+ accepts,
229
+ parse: (input) => values.includes(input) ? ok(input) : fail(`expected ${accepts}, not ${show(input)}`),
230
+ jsonSchema: () => ({ enum: [...values] })
231
+ });
232
+ }
233
+ function text(options = {}) {
234
+ return kind({
235
+ name: "text",
236
+ editor: "text",
237
+ accepts: "text",
238
+ parse(input) {
239
+ if (typeof input !== "string") return fail(`expected text, not ${show(input)}`);
240
+ if (options.maxLength !== void 0 && input.length > options.maxLength) return fail(`is longer than ${options.maxLength} characters`);
241
+ if (options.pattern && !options.pattern.test(input)) return fail(options.mismatch ?? `doesn't match ${options.pattern.source}`);
242
+ return ok(input);
243
+ },
244
+ jsonSchema: () => ({ type: "string", ...options.maxLength !== void 0 ? { maxLength: options.maxLength } : {}, ...options.pattern ? { pattern: options.pattern.source } : {} })
245
+ });
246
+ }
247
+ function boolean() {
248
+ return kind({
249
+ name: "boolean",
250
+ editor: "toggle",
251
+ accepts: "true or false",
252
+ parse: (input) => typeof input === "boolean" ? ok(input) : fail(`expected true or false, not ${show(input)}`),
253
+ jsonSchema: () => ({ type: "boolean" })
254
+ });
255
+ }
256
+ var NAMED_COLORS = new Set(
257
+ "aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen transparent".split(" ")
258
+ );
259
+ var HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
260
+ var RGB = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d*\.?\d+%?)\s*)?\)$/i;
261
+ var hex2 = (n) => n.toString(16).padStart(2, "0");
262
+ function parseColor(input) {
263
+ if (typeof input !== "string") return fail(`expected a color ("#0879a1", "rgb(\u2026)", "rgba(\u2026)" or a CSS name), not ${show(input)}`);
264
+ const value = input.trim().toLowerCase();
265
+ const hex = HEX.exec(value);
266
+ if (hex) {
267
+ const digits = hex[1].length <= 4 ? [...hex[1]].map((d) => d + d).join("") : hex[1];
268
+ if (digits.length === 6 || digits.slice(6) === "ff") return ok(`#${digits.slice(0, 6)}`);
269
+ const alpha = Math.round(parseInt(digits.slice(6), 16) / 255 * 1e3) / 1e3;
270
+ return ok(`rgba(${parseInt(digits.slice(0, 2), 16)}, ${parseInt(digits.slice(2, 4), 16)}, ${parseInt(digits.slice(4, 6), 16)}, ${alpha})`);
271
+ }
272
+ const rgb = RGB.exec(value);
273
+ if (rgb) {
274
+ const channels = rgb.slice(1, 4).map(Number);
275
+ if (channels.some((c) => c > 255)) return fail(`a color channel is 0 to 255 in ${show(input)}`);
276
+ let alpha = 1;
277
+ if (rgb[4] !== void 0) {
278
+ alpha = rgb[4].endsWith("%") ? Number(rgb[4].slice(0, -1)) / 100 : Number(rgb[4]);
279
+ if (!(alpha >= 0 && alpha <= 1)) return fail(`a color's alpha is 0 to 1 in ${show(input)}`);
280
+ }
281
+ return ok(alpha === 1 ? `#${channels.map(hex2).join("")}` : `rgba(${channels.join(", ")}, ${alpha})`);
282
+ }
283
+ if (NAMED_COLORS.has(value)) return ok(value);
284
+ return fail(`expected a color ("#0879a1", "rgb(\u2026)", "rgba(\u2026)" or a CSS name), not ${show(input)}`);
285
+ }
286
+ function color() {
287
+ return kind({
288
+ name: "color",
289
+ editor: "color",
290
+ accepts: 'a color ("#0879a1", "rgb(\u2026)", "rgba(\u2026)" or a CSS name)',
291
+ parse: parseColor,
292
+ jsonSchema: () => ({ type: "string" })
293
+ });
294
+ }
295
+ var FONT_UNSAFE = /[;{}<>\\\n\r]/;
296
+ function fontFamily() {
297
+ const accepts = 'a font stack, as CSS writes it ("Inter, Arial, sans-serif")';
298
+ return kind({
299
+ name: "fontFamily",
300
+ editor: "fontFamily",
301
+ accepts,
302
+ parse(input) {
303
+ if (isObject(input) && "src" in input) return fail("a web font is declared once, in the root's fonts ({ family, src, weight, style }); fontFamily names it in a stack");
304
+ if (typeof input !== "string" || !input.trim() || FONT_UNSAFE.test(input)) return fail(`expected ${accepts}, not ${show(input)}`);
305
+ return ok(input.trim());
306
+ },
307
+ jsonSchema: () => ({ type: "string", minLength: 1 })
308
+ });
309
+ }
310
+ function fontStack(stack) {
311
+ return stack.split(",").map((family) => family.trim().replace(/^(["'])(.*)\1$/, "$2")).filter(Boolean);
312
+ }
313
+ var FONT_FORMATS = ["woff2", "woff", "truetype", "opentype"];
314
+ function fontFaces() {
315
+ const accepts = "a list of { family, src, format, weight, style }";
316
+ const face = (input, index) => {
317
+ const at = `font ${index + 1}`;
318
+ if (!isObject(input)) return fail(`${at}: expected { family, src, format, weight, style }, not ${show(input)}`);
319
+ const extra = Object.keys(input).find((key) => !["family", "src", "format", "weight", "style"].includes(key));
320
+ if (extra) return fail(`${at}: "${extra}" isn't part of a font; expected family, src, format, weight or style`);
321
+ if (typeof input.family !== "string" || !input.family.trim() || FONT_UNSAFE.test(input.family) || input.family.includes(",")) {
322
+ return fail(`${at}: family is one font's name ("Inter"), not ${show(input.family)}`);
323
+ }
324
+ if (typeof input.src !== "string" || !input.src.trim() || unsafeUrl(input.src, /^data:(font|application)\//)) {
325
+ return fail(`${at}: src is the font file's http(s) URL (or a data:font URI), not ${show(input.src)}`);
326
+ }
327
+ const out = { family: input.family.trim(), src: input.src.trim() };
328
+ if (input.format !== void 0) {
329
+ if (!FONT_FORMATS.includes(input.format)) return fail(`${at}: format is one of ${FONT_FORMATS.join(", ")}, not ${show(input.format)}`);
330
+ out.format = input.format;
331
+ }
332
+ if (input.weight !== void 0) {
333
+ const range = typeof input.weight === "string" ? /^\s*(\d+)\s+(\d+)\s*$/.exec(input.weight) : Array.isArray(input.weight) && input.weight.length === 2 && input.weight.every(Number.isInteger) ? [null, ...input.weight] : null;
334
+ const weight = input.weight === "normal" ? 400 : input.weight === "bold" ? 700 : input.weight;
335
+ if (range && Number(range[1]) >= 1 && Number(range[2]) <= 1e3 && Number(range[1]) <= Number(range[2])) out.weight = [Number(range[1]), Number(range[2])];
336
+ else if (typeof weight === "number" && Number.isInteger(weight) && weight >= 1 && weight <= 1e3) out.weight = weight;
337
+ else return fail(`${at}: weight is 1 to 1000, "normal", "bold", or a range ("100 900"), not ${show(input.weight)}`);
338
+ }
339
+ if (input.style !== void 0) {
340
+ if (input.style !== "normal" && input.style !== "italic") return fail(`${at}: style is "normal" or "italic", not ${show(input.style)}`);
341
+ out.style = input.style;
342
+ }
343
+ return ok(out);
344
+ };
345
+ return kind({
346
+ name: "fontFaces",
347
+ editor: "fontFaces",
348
+ accepts,
349
+ parse(input) {
350
+ if (!Array.isArray(input)) return fail(`expected ${accepts}, not ${show(input)}`);
351
+ const out = [];
352
+ for (const [index, item] of input.entries()) {
353
+ const parsed = face(item, index);
354
+ if (!parsed.ok) return parsed;
355
+ out.push(parsed.value);
356
+ }
357
+ return ok(out);
358
+ },
359
+ jsonSchema: () => ({
360
+ type: "array",
361
+ items: {
362
+ type: "object",
363
+ required: ["family", "src"],
364
+ properties: {
365
+ family: { type: "string", minLength: 1 },
366
+ src: { type: "string", minLength: 1 },
367
+ format: { enum: [...FONT_FORMATS] },
368
+ weight: { anyOf: [{ type: "number", minimum: 1, maximum: 1e3 }, { type: "string" }, { type: "array", items: { type: "integer", minimum: 1, maximum: 1e3 }, minItems: 2, maxItems: 2 }] },
369
+ style: { enum: ["normal", "italic"] }
370
+ },
371
+ additionalProperties: false
372
+ }
373
+ })
374
+ });
375
+ }
376
+ function fontWeight() {
377
+ return kind({
378
+ name: "fontWeight",
379
+ editor: "fontWeight",
380
+ accepts: '100 to 900 in hundreds, "normal" or "bold"',
381
+ parse(input) {
382
+ if (input === "normal") return ok(400);
383
+ if (input === "bold") return ok(700);
384
+ if (typeof input === "number" && input >= 100 && input <= 900 && input % 100 === 0) return ok(input);
385
+ return fail(`expected 100 to 900 in hundreds, "normal" or "bold", not ${show(input)}`);
386
+ },
387
+ jsonSchema: () => ({ anyOf: [{ enum: [100, 200, 300, 400, 500, 600, 700, 800, 900] }, { enum: ["normal", "bold"] }] })
388
+ });
389
+ }
390
+ function lineHeight() {
391
+ const accepts = `a multiple of the font size (1.5), a percentage ("150%"), px ("24px") or "normal" (the font's own)`;
392
+ return kind({
393
+ name: "lineHeight",
394
+ editor: "lineHeight",
395
+ accepts,
396
+ parse(input) {
397
+ if (input === "normal") return ok("normal");
398
+ if (typeof input === "number") return input > 0 && Number.isFinite(input) ? ok(input) : fail(`expected ${accepts}, not ${show(input)}`);
399
+ const parsed = typeof input === "string" && /^\s*-?[\d.]+\s*$/.test(input) ? void 0 : readLength(input);
400
+ if (!parsed || parsed.value <= 0 || parsed.unit !== "px" && parsed.unit !== "%") return fail(`expected ${accepts}, not ${show(input)}`);
401
+ return ok(parsed.unit === "%" ? Math.round(parsed.value * 1e3) / 1e5 : `${parsed.value}px`);
402
+ },
403
+ jsonSchema: () => ({ anyOf: [{ type: "number", exclusiveMinimum: 0 }, { type: "string", pattern: `^${NUMBER}(px|%)$` }, { const: "normal" }] })
404
+ });
405
+ }
406
+ var SERVICE_TAG = /<%[\s\S]*?%>|\{%[\s\S]*?%\}|\{\{[\s\S]*?\}\}|\uFDD0\d+\uFDD1/g;
407
+ function unsafeUrl(url, allowedData) {
408
+ const compact = url.replace(SERVICE_TAG, "").replace(/[\u0000- \u007f-\u009f]/g, "").toLowerCase();
409
+ if (compact.startsWith("data:")) return !allowedData || !allowedData.test(compact);
410
+ return /^(javascript|vbscript|file):/.test(compact);
411
+ }
412
+ var YOUTUBE = /^https:\/\/(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:[^#]*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([A-Za-z0-9_-]{6,})/;
413
+ var VIMEO = /^https:\/\/(?:www\.|player\.)?vimeo\.com\/(?:video\/)?(\d+)/;
414
+ function videoParts(url) {
415
+ const youtube = YOUTUBE.exec(url);
416
+ if (youtube) return { provider: "youtube", id: youtube[1], embed: `https://www.youtube.com/embed/${youtube[1]}`, thumbnail: `https://img.youtube.com/vi/${youtube[1]}/0.jpg` };
417
+ const vimeo = VIMEO.exec(url);
418
+ if (vimeo) return { provider: "vimeo", id: vimeo[1], embed: `https://player.vimeo.com/video/${vimeo[1]}` };
419
+ return void 0;
420
+ }
421
+ function linkParts(value) {
422
+ return typeof value === "string" ? { href: value } : value;
423
+ }
424
+ function assetParts(value) {
425
+ return typeof value === "string" ? { src: value } : value;
426
+ }
427
+ function defineComponent(definition) {
428
+ return definition;
429
+ }
430
+ function prop(kind2, options) {
431
+ return { kind: kind2, ...options };
432
+ }
433
+ var writer = { level: "full", source: "writer" };
434
+ var css = { level: "full", source: "css" };
435
+ var textStyle = {
436
+ color: { kind: color(), label: "Text color", inherited: true, support: { email: writer, web: css } },
437
+ // A web font in a stack is reported from the root's fonts (whether it loads depends on the client).
438
+ fontFamily: { kind: fontFamily(), label: "Font", inherited: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
439
+ // Word rounds sizes to half points (13px is 10pt): a fraction of a px.
440
+ fontSize: { kind: length(["px"], { min: 1 }), label: "Font size", inherited: true, mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
441
+ fontWeight: {
442
+ kind: fontWeight(),
443
+ label: "Font weight",
444
+ inherited: true,
445
+ support: { email: { level: "outlook", values: [100, 200, 300, 500], shows: "normal weight: Word draws only normal and bold (600 and over)", source: "caniemail", feature: "css-font-weight" }, web: css }
446
+ },
447
+ fontStyle: { kind: enumOf(["normal", "italic"]), label: "Font style", inherited: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
448
+ // Written in px with mso-line-height-rule: exactly, which Word keeps (it drops unitless ones).
449
+ lineHeight: { kind: lineHeight(), label: "Line height", inherited: true, mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
450
+ letterSpacing: {
451
+ kind: length(["px"], { min: -100 }),
452
+ label: "Letter spacing",
453
+ inherited: true,
454
+ mobile: true,
455
+ support: { email: { level: "outlook", shows: "normal spacing: Word keeps letter spacing but doesn't draw it", source: "outlook-captures" }, web: css }
456
+ },
457
+ textAlign: { kind: enumOf(["left", "center", "right", "justify"]), label: "Alignment", inherited: true, mobile: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
458
+ textTransform: {
459
+ kind: enumOf(["none", "uppercase", "lowercase", "capitalize"]),
460
+ label: "Letter case",
461
+ inherited: true,
462
+ support: { email: { level: "outlook", values: ["lowercase"], shows: "the text as written: Word doesn't lowercase it", source: "caniemail", feature: "css-text-transform" }, web: css }
463
+ },
464
+ textDecoration: {
465
+ kind: enumOf(["none", "underline", "line-through"]),
466
+ label: "Decoration",
467
+ support: {
468
+ // Removed, so "none" leaves a link the client's own underline: said only where a line is asked for.
469
+ email: { level: "partial", values: ["underline", "line-through"], shows: "no line: the client removes it", source: "caniemail", feature: "css-text-decoration", clients: ["gmail-app-other", "protonmail", "orange", "sfr"] },
470
+ web: css
471
+ }
472
+ }
473
+ };
474
+ var inheritedTextStyle = (({ textDecoration: _, ...rest }) => rest)(textStyle);
475
+ var boxStyle = {
476
+ // Widths are the resolver's, written as table, cell and image attributes in px (what Word sizes by).
477
+ width: { kind: length(["px", "%"]), label: "Width", mobile: true, support: { email: writer, web: css } },
478
+ minWidth: { kind: length(["px", "%"]), label: "Minimum width", support: { email: writer, web: css } },
479
+ maxWidth: { kind: length(["px", "%"]), label: "Maximum width", mobile: true, support: { email: writer, web: css } },
480
+ height: { kind: length(["px"]), label: "Height", support: { email: writer, web: css } },
481
+ // Cell padding, which Word keeps (it drops a div's).
482
+ padding: { kind: insets(), label: "Padding", mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
483
+ backgroundColor: { kind: color(), label: "Background color", support: { email: writer, web: css } },
484
+ border: { kind: border(), label: "Border", mobile: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
485
+ borderRadius: {
486
+ kind: radius(),
487
+ label: "Corner radius",
488
+ support: {
489
+ email: [
490
+ { level: "outlook", shows: "square corners", source: "caniemail", feature: "css-border-radius" },
491
+ { level: "partial", shows: "square corners", source: "caniemail", feature: "css-border-radius", clients: ["gmx", "web-de", "orange"] }
492
+ ],
493
+ web: css
494
+ }
495
+ },
496
+ overflow: {
497
+ kind: enumOf(["visible", "hidden"]),
498
+ label: "Overflow",
499
+ description: "Whether content past a set height is cut off.",
500
+ support: { email: { level: "outlook", shows: "content past the height: Word doesn't cut it off", values: ["hidden"], source: "caniemail" }, web: css }
501
+ }
502
+ };
503
+ var layoutStyle = {
504
+ gap: { kind: length(["px"]), label: "Gap", description: "Space between children.", mobile: true, support: { email: writer, web: css } },
505
+ alignItems: {
506
+ kind: enumOf(["start", "center", "end", "stretch"]),
507
+ label: "Align items",
508
+ description: "Where children sit across the layout's direction (vertically in a Row, horizontally in a Column), as CSS align-items. Across a Column it places what has its own size (a Button, an Image, an Svg, a box with a width); a block of text fills the width, so its words move with its textAlign.",
509
+ // A cell's align, and a Row's cells' valign.
510
+ support: { email: writer, web: css }
511
+ },
512
+ justifyContent: {
513
+ kind: enumOf(["start", "center", "end", "space-between"]),
514
+ label: "Justify content",
515
+ description: "Where children sit along the layout's direction, as CSS justify-content.",
516
+ support: { email: writer, web: css }
517
+ }
518
+ };
519
+ function pick(group, ...keys) {
520
+ return Object.fromEntries(keys.map((key) => [key, group[key]]));
521
+ }
522
+ var WRITTEN = { email: { level: "full", source: "writer" }, web: { level: "full", source: "writer" } };
523
+ var NO_WEB_FONTS = ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "protonmail", "fastmail", "gmx", "web-de", "orange", "sfr", "laposte", "mail-ru", "ionos"];
524
+ var ROOT_PROPS = {
525
+ title: prop(text(), { support: WRITTEN, label: "Title", description: "The document's title (shown in a browser tab, and by some email clients)." }),
526
+ lang: prop(text({ maxLength: 35 }), {
527
+ support: {
528
+ ...WRITTEN,
529
+ email: { level: "partial", shows: "no language: the client removes it, and a screen reader reads the email in its own", source: "caniemail", feature: "html-lang", clients: ["hey", "gmx", "web-de", "orange", "ionos"] }
530
+ },
531
+ label: "Language",
532
+ description: 'Its language, as a BCP 47 tag ("en", "ar").'
533
+ }),
534
+ dir: prop(enumOf(["ltr", "rtl", "auto"]), {
535
+ support: {
536
+ ...WRITTEN,
537
+ email: { level: "partial", values: ["rtl", "auto"], shows: "left to right: the client removes the direction", source: "caniemail", feature: "html-dir", clients: ["gmx", "web-de"] }
538
+ },
539
+ label: "Text direction"
540
+ }),
541
+ fonts: prop(fontFaces(), {
542
+ support: {
543
+ email: { level: "partial", shows: "no web font loads: the next font in each stack shows", source: "caniemail", feature: "css-at-font-face", clients: NO_WEB_FONTS },
544
+ web: { level: "full", source: "writer" }
545
+ },
546
+ label: "Web fonts",
547
+ description: "The font files the template loads: a family's file for each weight and style it uses. Text names a family in its fontFamily stack, before the fonts to show where it can't load."
548
+ })
549
+ };
550
+ var ROOT_STYLE = {
551
+ ...inheritedTextStyle,
552
+ backgroundColor: { ...boxStyle.backgroundColor, label: "Background color", description: "The window's background, behind the content." },
553
+ ...pick(layoutStyle, "gap", "alignItems")
554
+ };
555
+ var ROOT_DEFAULT_STYLE = {
556
+ color: "#000000",
557
+ fontFamily: "Arial, Helvetica, sans-serif",
558
+ fontSize: 16,
559
+ fontWeight: 400,
560
+ lineHeight: 1.5,
561
+ textAlign: "left"
562
+ };
563
+ var TEXT_EDITOR = { title: "Text", paths: ["style.fontFamily", "style.fontSize", "style.fontWeight", "style.color", "style.lineHeight", "style.letterSpacing", "style.textAlign"] };
564
+ var WRITTEN2 = { email: { level: "full", source: "writer" }, web: { level: "full", source: "writer" } };
565
+ var SIZES = {
566
+ A3: [297 / 25.4, 420 / 25.4],
567
+ A4: [210 / 25.4, 297 / 25.4],
568
+ A5: [148 / 25.4, 210 / 25.4],
569
+ Letter: [8.5, 11],
570
+ Legal: [8.5, 14],
571
+ Tabloid: [11, 17]
572
+ };
573
+ var PX_PER_INCH = 96;
574
+ function pageBox(props) {
575
+ const size = props.size ?? "A4";
576
+ const [w, h] = SIZES[size] ?? SIZES.A4;
577
+ const landscape = props.orientation === "landscape";
578
+ const width = Math.round((landscape ? h : w) * PX_PER_INCH * 100) / 100;
579
+ const height = Math.round((landscape ? w : h) * PX_PER_INCH * 100) / 100;
580
+ const margin = props.margin ?? { top: 48, right: 48, bottom: 48, left: 48 };
581
+ return {
582
+ width,
583
+ height,
584
+ margin,
585
+ contentWidth: Math.round(width - margin.left - margin.right),
586
+ contentHeight: Math.round(height - margin.top - margin.bottom),
587
+ css: `${size} ${landscape ? "landscape" : "portrait"}`
588
+ };
589
+ }
590
+ ({
591
+ ...inheritedTextStyle,
592
+ ...pick(layoutStyle, "gap", "alignItems")
593
+ });
594
+ defineComponent({
595
+ type: "Document",
596
+ label: "Document",
597
+ description: "A printed document: its children on pages of its size (Document.Page, or split by PageBreaks), a PageHeader and a PageFooter on each. Renders to print HTML, and to PDF with @hyper-elements/react/pdf.",
598
+ categories: ["root"],
599
+ props: {
600
+ size: prop(enumOf(["A3", "A4", "A5", "Letter", "Legal", "Tabloid"]), { support: WRITTEN2, label: "Page size", default: "A4" }),
601
+ orientation: prop(enumOf(["portrait", "landscape"]), { support: WRITTEN2, label: "Orientation", default: "portrait" }),
602
+ margin: prop(insets(), {
603
+ support: WRITTEN2,
604
+ label: "Margins",
605
+ description: "Around each page's content (its header and footer too), in px: 96 to the inch.",
606
+ default: { top: 48, right: 48, bottom: 48, left: 48 }
607
+ }),
608
+ ...ROOT_PROPS
609
+ },
610
+ style: { ...ROOT_STYLE, backgroundColor: { ...ROOT_STYLE.backgroundColor, description: "Each page's background, out to its edges." } },
611
+ defaultStyle: ROOT_DEFAULT_STYLE,
612
+ children: { types: ["DocumentPage", "PageBreak", "PageHeader", "PageFooter"], categories: ["section", "layout", "block"] },
613
+ sizing: "none",
614
+ targets: { web: {} },
615
+ editor: {
616
+ groups: [
617
+ { title: "Document", paths: ["props.size", "props.orientation", "props.margin", "props.title"] },
618
+ { title: "Background", paths: ["style.backgroundColor"] },
619
+ TEXT_EDITOR
620
+ ]
621
+ }
622
+ });
623
+ ({ ...inheritedTextStyle, ...pick(boxStyle, "padding", "backgroundColor", "border"), ...pick(layoutStyle, "gap", "alignItems") });
624
+ defineComponent({
625
+ type: "PageNumber",
626
+ label: "Page number",
627
+ description: "In a Document's text: the number of the page it's on, or (total) how many pages there are.",
628
+ categories: ["inline"],
629
+ props: {
630
+ total: prop(boolean(), { support: WRITTEN2, label: "Page count", description: "How many pages the Document has, instead of this page's number.", default: false })
631
+ },
632
+ style: {},
633
+ children: {},
634
+ sizing: "none",
635
+ targets: { web: {} },
636
+ // A Page's or a Canvas's text has no page to number.
637
+ roots: ["Document"],
638
+ notAllowed: { code: "document/page-number", message: (root) => `A PageNumber is a page's number in a Document; this ${root} has no pages` }
639
+ });
640
+ function documentPages(root) {
641
+ const pages = [];
642
+ let run = [];
643
+ const end = () => {
644
+ if (run.length) pages.push({ children: run });
645
+ run = [];
646
+ };
647
+ let header;
648
+ let footer;
649
+ for (const child of root.children) {
650
+ if (typeof child === "string") continue;
651
+ if (child.type === "PageHeader") header = child;
652
+ else if (child.type === "PageFooter") footer = child;
653
+ else if (child.type === "PageBreak") end();
654
+ else if (child.type === "DocumentPage") {
655
+ end();
656
+ pages.push({ page: child, children: child.children.filter((c) => typeof c !== "string") });
657
+ } else run.push(child);
658
+ }
659
+ end();
660
+ if (!pages.length) pages.push({ children: [] });
661
+ return { pages, header, footer };
662
+ }
663
+ var LAYOUT_VERSION = 1;
664
+ var DROPPED = /<(style|script|head|title|template)\b[^>]*>[\s\S]*?<\/\1\s*>|<!--[\s\S]*?-->/gi;
665
+ var LINE = /<\s*(br|\/?(p|div|h[1-6]|li|ul|ol|tr|table|blockquote|section|article|header|footer|pre))\b[^>]*>/gi;
666
+ var CELL = /<\s*\/\s*(td|th)\s*>/gi;
667
+ var TAG = /<[^>]*>/g;
668
+ var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ", copy: "\xA9", reg: "\xAE", trade: "\u2122", mdash: "\u2014", ndash: "\u2013", hellip: "\u2026", rsquo: "\u2019", lsquo: "\u2018", rdquo: "\u201D", ldquo: "\u201C", bull: "\u2022", middot: "\xB7", euro: "\u20AC", pound: "\xA3" };
669
+ var decode = (text2) => text2.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, name) => {
670
+ if (name[0] !== "#") return Object.prototype.hasOwnProperty.call(ENTITIES, name.toLowerCase()) ? ENTITIES[name.toLowerCase()] : match;
671
+ const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
672
+ return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : match;
673
+ });
674
+ function markupText(html) {
675
+ const text2 = decode(html.replace(DROPPED, "").replace(LINE, "\n").replace(CELL, " ").replace(TAG, ""));
676
+ return text2.split("\n").map((line) => line.replace(/[ \t\r\f\v\u00a0]+/g, " ").trim()).filter(Boolean).join("\n");
677
+ }
678
+ function htmlText(node) {
679
+ return (typeof node.props.text === "string" ? node.props.text : markupText(String(node.props.html ?? ""))).trim();
680
+ }
681
+ var pt = (px2) => Math.round(px2 * 0.75 * 1e3) / 1e3;
682
+ var STANDARD = [
683
+ [/^(courier|courier new|consolas|menlo|monaco|monospace|ui-monospace|sf mono)$/i, "Courier"],
684
+ [/^(times|times new roman|georgia|garamond|cambria|serif|ui-serif|palatino)$/i, "Times-Roman"]
685
+ ];
686
+ function pdfFamily(stack, given) {
687
+ const families = fontStack(stack);
688
+ const known = families.find((family) => given.has(family.toLowerCase()));
689
+ if (known) return known.toLowerCase();
690
+ for (const family of families) for (const [pattern, standard] of STANDARD) if (pattern.test(family)) return standard;
691
+ return "Helvetica";
692
+ }
693
+ function textStyle2(node, given) {
694
+ const text2 = node.text;
695
+ return {
696
+ color: text2.color,
697
+ fontFamily: pdfFamily(String(text2.fontFamily), given),
698
+ fontSize: pt(text2.fontSize),
699
+ fontWeight: text2.fontWeight,
700
+ fontStyle: text2.fontStyle,
701
+ // A multiple of the font size.
702
+ lineHeight: text2.fontSize ? Math.round(text2.lineHeightPx / text2.fontSize * 1e3) / 1e3 : void 0,
703
+ letterSpacing: text2.letterSpacing ? pt(text2.letterSpacing) : void 0,
704
+ textAlign: text2.textAlign,
705
+ textTransform: text2.textTransform === "none" ? void 0 : text2.textTransform
706
+ };
707
+ }
708
+ function runStyle(node, given) {
709
+ const style = node.style;
710
+ const out = { ...textStyle2(node, given), textAlign: void 0 };
711
+ if (style.textDecoration !== void 0) out.textDecoration = String(style.textDecoration);
712
+ if (style.backgroundColor !== void 0) out.backgroundColor = String(style.backgroundColor);
713
+ if (style.verticalAlign === "super" || style.verticalAlign === "sub") out.verticalAlign = style.verticalAlign;
714
+ return out;
715
+ }
716
+ var sides = (value) => value && typeof value === "object" ? value : void 0;
717
+ function boxStyle2(node) {
718
+ const style = node.style;
719
+ const out = {};
720
+ const padding = sides(style.padding);
721
+ if (padding) Object.assign(out, { paddingTop: pt(padding.top), paddingRight: pt(padding.right), paddingBottom: pt(padding.bottom), paddingLeft: pt(padding.left) });
722
+ if (typeof style.backgroundColor === "string") out.backgroundColor = style.backgroundColor;
723
+ Object.assign(out, borderStyle(style.border));
724
+ Object.assign(out, radiusStyle(style.borderRadius));
725
+ if (typeof style.height === "number") out.height = pt(style.height);
726
+ if (style.overflow === "hidden") out.overflow = "hidden";
727
+ return out;
728
+ }
729
+ function borderStyle(border2) {
730
+ const out = {};
731
+ if (!border2) return out;
732
+ for (const side of ["top", "right", "bottom", "left"]) {
733
+ const line = border2[side];
734
+ if (!line) continue;
735
+ const name = side[0].toUpperCase() + side.slice(1);
736
+ out[`border${name}Width`] = line.style === "none" ? 0 : pt(line.width);
737
+ out[`border${name}Style`] = line.style === "none" ? "solid" : line.style;
738
+ out[`border${name}Color`] = line.color;
739
+ }
740
+ return out;
741
+ }
742
+ function radiusStyle(radius2) {
743
+ if (radius2 === void 0) return {};
744
+ if (typeof radius2 === "number") return { borderRadius: pt(radius2) };
745
+ return {
746
+ borderTopLeftRadius: pt(radius2.topLeft),
747
+ borderTopRightRadius: pt(radius2.topRight),
748
+ borderBottomRightRadius: pt(radius2.bottomRight),
749
+ borderBottomLeftRadius: pt(radius2.bottomLeft)
750
+ };
751
+ }
752
+ var FLEX = { start: "flex-start", center: "center", end: "flex-end", stretch: "stretch", "space-between": "space-between" };
753
+ var flex2 = (value) => typeof value === "string" ? FLEX[value] : void 0;
754
+ function alignSelf(child, stack) {
755
+ const sized = child.style.width !== void 0 || child.style.maxWidth !== void 0;
756
+ if (child.type === "Divider" || child.definition.sizing !== "shrink" && !sized) return void 0;
757
+ const align = child.style.alignSelf ?? stack.style.alignItems;
758
+ if (align === "stretch") return void 0;
759
+ if (align) return flex2(align);
760
+ return { left: "flex-start", justify: "flex-start", center: "center", right: "flex-end" }[stack.text.textAlign];
761
+ }
762
+ var registered = /* @__PURE__ */ new Set();
763
+ var base64 = (bytes) => Buffer.from(bytes).toString("base64");
764
+ function register(fonts) {
765
+ const byFamily = /* @__PURE__ */ new Map();
766
+ for (const font of fonts) byFamily.set(font.family, [...byFamily.get(font.family) ?? [], { data: base64(font.data), weight: font.weight, style: font.style }]);
767
+ const names = /* @__PURE__ */ new Map();
768
+ for (const [family, files] of byFamily) {
769
+ const hash = createHash("sha256");
770
+ for (const file of files) hash.update(`${file.weight}|${file.style}|${file.data}
771
+ `);
772
+ const name = `elements-${hash.digest("hex").slice(0, 32)}`;
773
+ if (!registered.has(name)) {
774
+ for (const file of files) Font.register({ family: name, src: `data:font/ttf;base64,${file.data}`, fontWeight: file.weight, fontStyle: file.style });
775
+ registered.add(name);
776
+ }
777
+ names.set(family, { name, styles: new Set(files.map((file) => file.style)) });
778
+ }
779
+ return names;
780
+ }
781
+ var SHAPES = { rect: Rect, circle: Circle, ellipse: Ellipse, line: Line, polygon: Polygon, path: Path };
782
+ var el = (type, props, ...children) => createElement(type, props, ...children);
783
+ var reactPdf = async (document) => {
784
+ const names = register(document.fonts);
785
+ const styled = (style) => {
786
+ const family = style.fontFamily === void 0 ? void 0 : names.get(String(style.fontFamily));
787
+ if (!family) return style;
788
+ const fontStyle = family.styles.has(String(style.fontStyle ?? "normal")) ? style.fontStyle : [...family.styles][0];
789
+ return { ...style, fontFamily: family.name, fontStyle };
790
+ };
791
+ const run = (item, key) => {
792
+ if (typeof item === "string") return item;
793
+ switch (item.kind) {
794
+ case "span":
795
+ return el(Text, { key, style: styled(item.style) }, item.runs.map(run));
796
+ case "link":
797
+ return el(Link, { key, src: item.href, style: styled(item.style) }, item.runs.map(run));
798
+ case "image":
799
+ return el(Image, { key, src: item.src, style: item.style });
800
+ }
801
+ };
802
+ const block = (item, key) => {
803
+ switch (item.kind) {
804
+ case "box":
805
+ return el(View, { key, style: styled(item.style) }, item.children.map(block));
806
+ case "text":
807
+ return el(Text, { key, style: styled(item.style) }, item.runs.map(run));
808
+ case "image":
809
+ return el(Image, { key, src: item.src, style: item.style });
810
+ case "link":
811
+ return el(Link, { key, src: item.href, style: styled(item.style) }, item.children.map(block));
812
+ case "drawing":
813
+ return el(
814
+ Svg,
815
+ { key, width: item.width, height: item.height, viewBox: item.viewBox },
816
+ item.shapes.map((shape, i) => el(SHAPES[shape.kind], { key: i, ...shape.attributes }))
817
+ );
818
+ }
819
+ };
820
+ const pages = document.pages.map(
821
+ (page, index) => el(
822
+ Page,
823
+ {
824
+ key: index,
825
+ size: [page.width, page.height],
826
+ style: { paddingTop: page.margin.top, paddingRight: page.margin.right, paddingBottom: page.margin.bottom, paddingLeft: page.margin.left, backgroundColor: page.background, flexDirection: "column" }
827
+ },
828
+ // The page's box inside its margins, which nothing leaves: what doesn't fit is cut, not moved on.
829
+ el(
830
+ View,
831
+ { wrap: false, style: { height: page.contentHeight, overflow: "hidden", flexDirection: "column" } },
832
+ page.header && block(page.header),
833
+ // The content laid out at its own height and cut where the page ends: react-pdf squeezes text into a box's
834
+ // height (lines drawn over each other), not into one placed on its own.
835
+ el(
836
+ View,
837
+ { style: { flexGrow: 1, flexShrink: 1, overflow: "hidden", position: "relative" } },
838
+ el(View, { style: { position: "absolute", top: 0, left: 0, right: 0, flexDirection: "column", ...page.gap !== void 0 ? { gap: page.gap } : {} } }, page.content.map(block))
839
+ ),
840
+ page.footer && block(page.footer)
841
+ )
842
+ )
843
+ );
844
+ const element = el(Document, { title: document.title, language: document.lang, creationDate: document.date, modificationDate: document.date, producer: "Hyper Elements", creator: "Hyper Elements" }, pages);
845
+ return new Uint8Array(await renderToBuffer(element));
846
+ };
847
+ var thumbnailOf = (video) => video.props.thumbnail ? assetParts(video.props.thumbnail).src : videoParts(String(video.props.src))?.thumbnail;
848
+ var shown = (node) => !node.hidden?.desktop;
849
+ var nodes = (node) => node.children.filter((child) => typeof child !== "string" && shown(child));
850
+ var present = (items) => items.filter((item) => item !== void 0);
851
+ function writeDocument(root, context) {
852
+ const box = pageBox(root.props);
853
+ const { pages, header, footer } = documentPages(root);
854
+ const total = pages.length;
855
+ const write = new Writer(context);
856
+ return {
857
+ title: root.props.title,
858
+ lang: root.props.lang,
859
+ date: context.date,
860
+ fonts: context.fonts,
861
+ pages: pages.map(({ page, children }, index) => {
862
+ write.page = { number: index + 1, total };
863
+ const content = page ?? root;
864
+ const margin = { top: pt(box.margin.top), right: pt(box.margin.right), bottom: pt(box.margin.bottom), left: pt(box.margin.left) };
865
+ return {
866
+ width: pt(box.width),
867
+ height: pt(box.height),
868
+ margin,
869
+ // Paper is white: a page with no background of its own paints one, as a viewer may show a page left
870
+ // transparent as black (in dark mode), under text that's dark.
871
+ background: page?.style.backgroundColor ?? root.style.backgroundColor ?? "#ffffff",
872
+ // The room inside the margins, as react-pdf works it out (the paper's height less them, in points), less a
873
+ // thousandth: react-pdf warns that a box taller than the room can't wrap, and Yoga lays it out in 32-bit
874
+ // floats (A4's 769.89 points is 769.8900146). The page box's contentHeight is a whole px (1027 for 1026.52).
875
+ contentHeight: Math.floor((pt(box.height) - margin.bottom - margin.top - 1e-3) * 1e3) / 1e3,
876
+ header: header && write.block(header, root),
877
+ content: present(children.filter(shown).map((child) => write.block(child, content))),
878
+ gap: typeof content.style.gap === "number" ? pt(content.style.gap) : void 0,
879
+ footer: footer && write.block(footer, root)
880
+ };
881
+ })
882
+ };
883
+ }
884
+ var gap = (node) => typeof node.style.gap === "number" ? { gap: pt(node.style.gap) } : {};
885
+ var Writer = class {
886
+ constructor(context) {
887
+ this.context = context;
888
+ this.page = { number: 1, total: 1 };
889
+ }
890
+ /** A block in a stack (or a Row, `row`), at the width the resolver gave it. */
891
+ block(node, parent, row = false) {
892
+ const placed = row ? { width: node.width !== null ? pt(node.width) : void 0, flexShrink: 0, flexGrow: 0, alignSelf: node.definition.sizing === "shrink" ? flex2(parent.style.alignItems ?? "start") : void 0 } : { alignSelf: alignSelf(node, parent), width: node.width !== null && (node.style.width !== void 0 || node.style.maxWidth !== void 0 || node.definition.sizing === "shrink") ? pt(node.width) : void 0 };
893
+ const families = this.context.families;
894
+ switch (node.type) {
895
+ case "Text":
896
+ case "Heading":
897
+ return { kind: "text", style: { ...textStyle2(node, families), ...boxStyle2(node), ...placed }, runs: this.runs(node) };
898
+ case "Button":
899
+ return {
900
+ kind: "link",
901
+ href: linkParts(node.props.href).href,
902
+ style: { ...placed, textDecoration: "none" },
903
+ children: [{ kind: "box", style: { ...boxStyle2(node), width: node.width !== null ? pt(node.width) : void 0 }, children: [{ kind: "text", style: { ...textStyle2(node, families), textAlign: "center" }, runs: this.runs(node) }] }]
904
+ };
905
+ case "Image":
906
+ return this.image(node, placed);
907
+ case "Link": {
908
+ const words = node.children.some((child) => typeof child === "string" || child.definition.categories.includes("inline"));
909
+ return {
910
+ kind: "link",
911
+ href: linkParts(node.props.href).href,
912
+ style: { ...placed, textDecoration: "none" },
913
+ children: words ? [{ kind: "text", style: textStyle2(node, families), runs: this.runs(node) }] : present(nodes(node).map((child) => this.block(child, node)))
914
+ };
915
+ }
916
+ case "Divider": {
917
+ const line = node.style.borderTop;
918
+ return { kind: "box", style: { ...boxStyle2(node), ...placed, width: node.width !== null ? pt(node.width) : void 0 }, children: [{ kind: "box", style: borderStyle(line ? { top: line } : void 0), children: [] }] };
919
+ }
920
+ case "Row":
921
+ return {
922
+ kind: "box",
923
+ style: { ...boxStyle2(node), ...placed, flexDirection: "row", ...gap(node), alignItems: flex2(node.style.alignItems ?? "start"), justifyContent: flex2(node.style.justifyContent) },
924
+ children: present(nodes(node).map((child) => this.block(child, node, true)))
925
+ };
926
+ case "Layer":
927
+ return this.layer(node, placed);
928
+ case "List":
929
+ return this.list(node, placed);
930
+ case "Table":
931
+ return this.table(node, placed);
932
+ case "Video":
933
+ return this.video(node, placed);
934
+ case "Html":
935
+ return { kind: "text", style: { ...textStyle2(node, families), ...placed }, runs: [htmlText(node)] };
936
+ case "Svg":
937
+ return this.svg(node, placed);
938
+ case "Section":
939
+ case "Column":
940
+ case "PageHeader":
941
+ case "PageFooter":
942
+ return { kind: "box", style: { ...boxStyle2(node), ...placed, flexDirection: "column", ...gap(node), justifyContent: flex2(node.style.justifyContent) }, children: present(nodes(node).map((child) => this.block(child, node))) };
943
+ default:
944
+ throw new Error(`[Elements] The PDF writer draws no ${node.type}`);
945
+ }
946
+ }
947
+ /** A block of text's children: its words and runs (Span, Link, a line break, a merge tag, a page number). */
948
+ runs(node) {
949
+ return present(
950
+ node.children.map((child) => {
951
+ if (typeof child === "string") return child;
952
+ if (!shown(child)) return void 0;
953
+ switch (child.type) {
954
+ case "LineBreak":
955
+ return "\n";
956
+ case "MergeTag":
957
+ return `{{${child.props.name}}}`;
958
+ case "PageNumber":
959
+ return String(child.props.total ? this.page.total : this.page.number);
960
+ case "Link":
961
+ return { kind: "link", href: linkParts(child.props.href).href, style: runStyle(child, this.context.families), runs: this.runs(child) };
962
+ case "Image": {
963
+ const image = this.image(child, {});
964
+ return image.kind === "image" ? image : String(child.props.alt ?? "");
965
+ }
966
+ default:
967
+ return { kind: "span", style: runStyle(child, this.context.families), runs: this.runs(child) };
968
+ }
969
+ })
970
+ );
971
+ }
972
+ /** An Image; one the PDF can't draw (an SVG), its alt text in a box of its size, as a browser shows one it can't. */
973
+ image(node, placed, cover) {
974
+ const { src, width, height } = assetParts(node.props.src);
975
+ const drawn = node.width ?? width;
976
+ const style = { ...placed, ...boxStyle2(node), width: drawn !== void 0 && drawn !== null ? pt(drawn) : void 0 };
977
+ if (cover !== void 0) Object.assign(style, { width: "100%", height: pt(cover), objectFit: "cover" });
978
+ else if (typeof node.style.height !== "number" && drawn && width && height) style.height = pt(drawn * height / width);
979
+ const source = this.context.image(src);
980
+ if (source) return { kind: "image", src: source, style };
981
+ const alt = String(node.props.alt ?? "");
982
+ return { kind: "box", style: { ...style, objectFit: void 0, overflow: "hidden" }, children: alt ? [{ kind: "text", style: textStyle2(node, this.context.families), runs: [alt] }] : [] };
983
+ }
984
+ /** A Layer: its first child in place, the others over it, each in the Layer's whole box, placed by its alignment. */
985
+ layer(node, placed) {
986
+ const [base, ...over] = nodes(node);
987
+ const height = typeof node.style.height === "number" ? node.style.height : void 0;
988
+ const under = base && (base.type === "Image" && height !== void 0 ? this.image(base, {}, height) : this.block(base, node));
989
+ return {
990
+ kind: "box",
991
+ style: { ...boxStyle2(node), ...placed, position: "relative", overflow: node.style.borderRadius !== void 0 ? "hidden" : void 0 },
992
+ children: present([
993
+ under,
994
+ ...over.map((child) => ({
995
+ kind: "box",
996
+ style: { position: "absolute", top: 0, right: 0, bottom: 0, left: 0, flexDirection: "column", justifyContent: flex2(node.style.alignItems ?? "start"), alignItems: flex2(node.style.justifyItems ?? "stretch") },
997
+ children: present([this.block(child, node)])
998
+ }))
999
+ ])
1000
+ };
1001
+ }
1002
+ list(node, placed) {
1003
+ const indent = typeof node.style.indent === "number" ? node.style.indent : 24;
1004
+ const start = Number(node.props.start ?? 1);
1005
+ return {
1006
+ kind: "box",
1007
+ style: { ...boxStyle2(node), ...placed, flexDirection: "column", ...gap(node) },
1008
+ children: nodes(node).map((item, i) => ({
1009
+ kind: "box",
1010
+ style: { flexDirection: "row" },
1011
+ children: [
1012
+ { kind: "text", style: { ...textStyle2(item, this.context.families), width: pt(indent), textAlign: "left" }, runs: [node.props.type === "number" ? `${start + i}.` : "\u2022"] },
1013
+ { kind: "box", style: { flexGrow: 1, flexShrink: 1, flexDirection: "column" }, children: this.itemParts(item) }
1014
+ ]
1015
+ }))
1016
+ };
1017
+ }
1018
+ /** A list item's text and the Lists in it, in their order: each run of text a paragraph, each List under it. */
1019
+ itemParts(item) {
1020
+ const parts = [];
1021
+ let text2 = [];
1022
+ const flush = () => {
1023
+ if (text2.some((child) => typeof child !== "string" || child.trim() !== "")) parts.push({ kind: "text", style: textStyle2(item, this.context.families), runs: this.runs({ ...item, children: text2 }) });
1024
+ text2 = [];
1025
+ };
1026
+ for (const child of item.children) {
1027
+ if (typeof child !== "string" && child.type === "List") {
1028
+ flush();
1029
+ if (shown(child)) parts.push(this.list(child, {}));
1030
+ } else text2.push(child);
1031
+ }
1032
+ flush();
1033
+ return parts;
1034
+ }
1035
+ table(node, placed) {
1036
+ const columns = node.columns ?? null;
1037
+ const cellPadding = node.style.cellPadding;
1038
+ const rowBorder = node.style.rowBorder;
1039
+ const rows = nodes(node);
1040
+ return {
1041
+ kind: "box",
1042
+ style: { ...boxStyle2(node), ...placed, flexDirection: "column" },
1043
+ children: rows.map((row, r) => {
1044
+ let column = 0;
1045
+ return {
1046
+ kind: "box",
1047
+ style: { flexDirection: "row", ...boxStyle2(row), ...r < rows.length - 1 && rowBorder ? borderStyle({ bottom: rowBorder }) : {} },
1048
+ children: nodes(row).map((cell) => {
1049
+ const span = Math.max(1, Number(cell.props.colSpan ?? 1));
1050
+ const width = columns ? columns.slice(column, column + span).reduce((sum, w) => sum + w, 0) : void 0;
1051
+ column += span;
1052
+ const padding = cellPadding ? { paddingTop: pt(cellPadding.top), paddingRight: pt(cellPadding.right), paddingBottom: pt(cellPadding.bottom), paddingLeft: pt(cellPadding.left) } : {};
1053
+ return {
1054
+ kind: "box",
1055
+ style: { ...padding, ...boxStyle2(cell), width: width !== void 0 ? pt(width) : void 0, flexGrow: width === void 0 ? 1 : 0 },
1056
+ children: [{ kind: "text", style: { ...textStyle2(cell, this.context.families), ...row.props.header ? { fontWeight: 700 } : {} }, runs: this.runs(cell) }]
1057
+ };
1058
+ })
1059
+ };
1060
+ })
1061
+ };
1062
+ }
1063
+ /** An Svg: its box in points, its shapes in its viewBox's units. */
1064
+ svg(node, placed) {
1065
+ const { width, height, viewBox } = node.props;
1066
+ const paint = (value) => value === "transparent" ? "none" : value;
1067
+ const KINDS = { Rect: "rect", Circle: "circle", Ellipse: "ellipse", Line: "line", Polygon: "polygon", Path: "path" };
1068
+ const shapes = nodes(node).filter((shape) => KINDS[shape.type]).map((shape) => ({
1069
+ kind: KINDS[shape.type],
1070
+ attributes: { ...shape.props, fill: paint(shape.style.fill), stroke: paint(shape.style.stroke), strokeWidth: shape.style.strokeWidth, opacity: shape.style.opacity }
1071
+ }));
1072
+ return {
1073
+ kind: "box",
1074
+ style: { ...boxStyle2(node), ...placed, width: void 0 },
1075
+ children: [{ kind: "drawing", width: pt(width), height: pt(height), viewBox: viewBox ?? `0 0 ${width} ${height}`, style: {}, shapes }]
1076
+ };
1077
+ }
1078
+ /** A Video: its thumbnail linked to it where its bytes are given, else a link with its title. */
1079
+ video(node, placed) {
1080
+ const src = String(node.props.src);
1081
+ const thumbnail = thumbnailOf(node);
1082
+ const image = thumbnail ? this.context.image(thumbnail) : void 0;
1083
+ return {
1084
+ kind: "link",
1085
+ href: src,
1086
+ style: { ...placed, textDecoration: "none" },
1087
+ children: [image ? { kind: "image", src: image, style: { width: node.width !== null ? pt(node.width) : void 0 } } : { kind: "text", style: textStyle2(node, this.context.families), runs: [String(node.props.title ?? "Video")] }]
1088
+ };
1089
+ }
1090
+ };
1091
+ var FIXED_DATE = new Date(Date.UTC(2026, 0, 1));
1092
+ async function renderPdf(element, options = {}) {
1093
+ const { root, diagnostics, layout } = prepare(element);
1094
+ readable(layout, "pdf");
1095
+ if (!root) throw new ElementsError(diagnostics);
1096
+ if (root.type !== "Document") {
1097
+ throw new ElementsError([...diagnostics, { code: "pdf/root", severity: "error", message: `PDF output is a Document's: this template is a ${root.type}`, path: "root" }]);
1098
+ }
1099
+ const found = [];
1100
+ const fonts = fontFiles(root, options.fonts ?? [], found);
1101
+ const images = imageSources(root, options.images ?? {}, found);
1102
+ if (found.some((d) => d.severity === "error")) throw new ElementsError([...diagnostics, ...found]);
1103
+ found.push(...missingFaces(root, fonts, options.fonts ?? []), ...beyondStandard(root, new Set(fonts.map((font) => font.family))));
1104
+ const pages = writeDocument(root, { families: new Set(fonts.map((font) => font.family)), fonts, image: (src) => images.get(src), date: options.date ?? FIXED_DATE });
1105
+ return { pdf: await reactPdf(pages), diagnostics: [...diagnostics, ...found] };
1106
+ }
1107
+ function readable(layout, output) {
1108
+ if (layout === LAYOUT_VERSION) return;
1109
+ throw new ElementsError([
1110
+ {
1111
+ code: `${output}/version`,
1112
+ severity: "error",
1113
+ message: `@hyper-elements/react/${output} reads layout version ${LAYOUT_VERSION}; the main entry writes ${typeof layout === "number" ? layout : "none"}: update @hyper-elements/react`,
1114
+ path: "root"
1115
+ }
1116
+ ]);
1117
+ }
1118
+ function base642(bytes) {
1119
+ return Buffer.from(bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes).toString("base64");
1120
+ }
1121
+ var woff2 = (bytes) => String.fromCharCode(...bytes.subarray(0, 4)) === "wOF2";
1122
+ function fontFiles(root, given, found) {
1123
+ const files = given.map((font) => ({ font, data: font.src instanceof ArrayBuffer ? new Uint8Array(font.src) : font.src }));
1124
+ const drawn = files.filter(({ data }) => !woff2(data));
1125
+ const families = new Set(drawn.map(({ font }) => font.family.toLowerCase()));
1126
+ for (const { font, data } of files) {
1127
+ if (!woff2(data)) continue;
1128
+ const meanwhile = families.has(font.family.toLowerCase()) ? `${font.family}'s other files` : "a standard PDF font";
1129
+ found.push({ code: "pdf/font", severity: "warning", message: `${font.family}'s file is WOFF2: a PDF draws TTF, OTF and WOFF files. Its text is drawn in ${meanwhile} meanwhile`, path: "root.props.fonts" });
1130
+ }
1131
+ const named = new Set(given.map((font) => font.family.toLowerCase()));
1132
+ for (const face of root.props.fonts ?? []) {
1133
+ if (named.has(face.family.toLowerCase())) continue;
1134
+ found.push({
1135
+ code: "pdf/font",
1136
+ severity: "warning",
1137
+ message: `PDF output loads no fonts: give ${face.family}'s file in renderPdf's fonts option. Its text is drawn in a standard PDF font meanwhile`,
1138
+ path: "root.props.fonts"
1139
+ });
1140
+ }
1141
+ return drawn.map(({ font, data }) => ({ family: font.family.toLowerCase(), data, weight: font.weight ?? 400, style: font.style ?? "normal" }));
1142
+ }
1143
+ function missingFaces(root, fonts, given) {
1144
+ const out = /* @__PURE__ */ new Map();
1145
+ const families = new Set(fonts.map((font) => font.family));
1146
+ const visit = (node) => {
1147
+ if (node.hidden?.desktop) return;
1148
+ const family = pdfFamily(String(node.text.fontFamily), families);
1149
+ if (families.has(family) && node.children.some((child) => typeof child === "string" && child.trim())) {
1150
+ const faces = fonts.filter((font) => font.family === family);
1151
+ const { fontWeight: weight, fontStyle: style } = node.text;
1152
+ const key = `${family}|${weight}|${style}`;
1153
+ if (!faces.some((face) => face.weight === weight && face.style === style) && !out.has(key)) {
1154
+ const name = given.find((font) => font.family.toLowerCase() === family)?.family ?? family;
1155
+ const has = faces.map((face) => `${face.weight}${face.style === "italic" ? " italic" : ""}`).join(", ");
1156
+ out.set(key, {
1157
+ code: "pdf/font-weight",
1158
+ severity: "warning",
1159
+ message: `Text in ${name} at ${weight}${style === "italic" ? " italic" : ""}, which is given at ${has} only: it's drawn in the nearest. Give that file in renderPdf's fonts option`,
1160
+ path: node.path,
1161
+ ...node.id ? { nodeId: node.id } : {}
1162
+ });
1163
+ }
1164
+ }
1165
+ for (const child of node.children) if (typeof child !== "string") visit(child);
1166
+ };
1167
+ visit(root);
1168
+ return [...out.values()];
1169
+ }
1170
+ var WIN_ANSI = "\u20AC\u201A\u0192\u201E\u2026\u2020\u2021\u02C6\u2030\u0160\u2039\u0152\u017D\u2018\u2019\u201C\u201D\u2022\u2013\u2014\u02DC\u2122\u0161\u203A\u0153\u017E\u0178";
1171
+ var drawnInStandard = (char) => {
1172
+ const code = char.codePointAt(0);
1173
+ return code >= 32 && code <= 126 || code >= 160 && code <= 255 || WIN_ANSI.includes(char) || char === "\n" || char === " " || char === "\r";
1174
+ };
1175
+ function beyondStandard(root, families) {
1176
+ const out = /* @__PURE__ */ new Map();
1177
+ const visit = (node) => {
1178
+ if (node.hidden?.desktop) return;
1179
+ const family = pdfFamily(String(node.text.fontFamily), families);
1180
+ if (!families.has(family) && !out.has(family)) {
1181
+ const char = node.children.flatMap((child) => typeof child === "string" ? [...child] : []).find((c) => !drawnInStandard(c));
1182
+ if (char) {
1183
+ out.set(family, {
1184
+ code: "pdf/font",
1185
+ severity: "warning",
1186
+ message: `Text in ${family} has characters a standard PDF font can't draw ("${char}"): it draws Western European characters only, and no font file is given for this text's fontFamily. Give one that has them in renderPdf's fonts option. They come out as other characters meanwhile`,
1187
+ path: node.path,
1188
+ ...node.id ? { nodeId: node.id } : {}
1189
+ });
1190
+ }
1191
+ }
1192
+ for (const child of node.children) if (typeof child !== "string") visit(child);
1193
+ };
1194
+ visit(root);
1195
+ return [...out.values()];
1196
+ }
1197
+ function mime(bytes) {
1198
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "image/png";
1199
+ if (bytes[0] === 255 && bytes[1] === 216) return "image/jpeg";
1200
+ const head = new TextDecoder().decode(bytes.subarray(0, 256)).trimStart();
1201
+ if (head.startsWith("<svg") || head.startsWith("<?xml")) return "image/svg+xml";
1202
+ return void 0;
1203
+ }
1204
+ var DRAWN = /^data:image\/(png|jpeg|jpg)[;,]/;
1205
+ var SVG = /^data:image\/svg\+xml[;,]/;
1206
+ function imageSources(root, given, found) {
1207
+ const out = /* @__PURE__ */ new Map();
1208
+ const bytes = (src) => given[src] instanceof ArrayBuffer ? new Uint8Array(given[src]) : given[src];
1209
+ const resolve = (src) => {
1210
+ if (out.has(src)) return out.get(src);
1211
+ if (DRAWN.test(src)) out.set(src, src);
1212
+ else if (SVG.test(src)) return "svg";
1213
+ else if (given[src]) {
1214
+ const type = mime(bytes(src));
1215
+ if (type === "image/svg+xml") return "svg";
1216
+ if (type) out.set(src, `data:${type};base64,${base642(bytes(src))}`);
1217
+ }
1218
+ return out.get(src);
1219
+ };
1220
+ const visit = (node) => {
1221
+ if (node.hidden?.desktop) return;
1222
+ if (node.type === "Image") {
1223
+ const { src } = assetParts(node.props.src);
1224
+ const source = resolve(src);
1225
+ const where = { path: `${node.path}.props.src`, ...node.id ? { nodeId: node.id } : {} };
1226
+ if (source === "svg") {
1227
+ found.push({
1228
+ code: "pdf/image",
1229
+ severity: "warning",
1230
+ message: "A PDF draws PNG and JPEG images, not SVG: give this one as a PNG or JPEG (its file in renderPdf's images option, by its URL, or a data: URI). Its alt text is shown in its box meanwhile",
1231
+ ...where
1232
+ });
1233
+ } else if (!source) {
1234
+ found.push({
1235
+ code: "pdf/image",
1236
+ severity: "error",
1237
+ message: given[src] ? "This image's file isn't a PNG or JPEG, the formats a PDF draws" : "PDF output loads no images: give this one's file in renderPdf's images option (by its URL), or its src as a data: URI",
1238
+ ...where
1239
+ });
1240
+ }
1241
+ }
1242
+ if (node.type === "Video") {
1243
+ const thumbnail = thumbnailOf(node);
1244
+ if (thumbnail) resolve(thumbnail);
1245
+ }
1246
+ for (const child of node.children) if (typeof child !== "string") visit(child);
1247
+ };
1248
+ visit(root);
1249
+ return out;
1250
+ }
1251
+
1252
+ export { renderPdf };
1253
+ //# sourceMappingURL=pdf.js.map
1254
+ //# sourceMappingURL=pdf.js.map