@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/image.cjs ADDED
@@ -0,0 +1,472 @@
1
+ 'use strict';
2
+
3
+ var resvgJs = require('@resvg/resvg-js');
4
+ var react$1 = require('react');
5
+ var satori = require('satori');
6
+ var react = require('@hyper-elements/react');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var satori__default = /*#__PURE__*/_interopDefault(satori);
11
+
12
+ // ../core/dist/image.js
13
+ new Set(
14
+ "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(" ")
15
+ );
16
+ function fontStack(stack) {
17
+ return stack.split(",").map((family) => family.trim().replace(/^(["'])(.*)\1$/, "$2")).filter(Boolean);
18
+ }
19
+ function assetParts(value) {
20
+ return typeof value === "string" ? { src: value } : value;
21
+ }
22
+ var LAYOUT_VERSION = 1;
23
+ var DROPPED = /<(style|script|head|title|template)\b[^>]*>[\s\S]*?<\/\1\s*>|<!--[\s\S]*?-->/gi;
24
+ var LINE = /<\s*(br|\/?(p|div|h[1-6]|li|ul|ol|tr|table|blockquote|section|article|header|footer|pre))\b[^>]*>/gi;
25
+ var CELL = /<\s*\/\s*(td|th)\s*>/gi;
26
+ var TAG = /<[^>]*>/g;
27
+ 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" };
28
+ var decode = (text) => text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (match, name) => {
29
+ if (name[0] !== "#") return Object.prototype.hasOwnProperty.call(ENTITIES, name.toLowerCase()) ? ENTITIES[name.toLowerCase()] : match;
30
+ const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
31
+ return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : match;
32
+ });
33
+ function markupText(html) {
34
+ const text = decode(html.replace(DROPPED, "").replace(LINE, "\n").replace(CELL, " ").replace(TAG, ""));
35
+ return text.split("\n").map((line) => line.replace(/[ \t\r\f\v\u00a0]+/g, " ").trim()).filter(Boolean).join("\n");
36
+ }
37
+ function htmlText(node) {
38
+ return (typeof node.props.text === "string" ? node.props.text : markupText(String(node.props.html ?? ""))).trim();
39
+ }
40
+ function clean(node) {
41
+ if (Array.isArray(node)) return node.map(clean);
42
+ if (!react$1.isValidElement(node)) return node;
43
+ const { style, children, ...props } = node.props;
44
+ const kept = style && Object.fromEntries(Object.entries(style).filter(([, value]) => value !== void 0 && value !== null));
45
+ const list = [].concat(children ?? []).filter((child) => child !== null && child !== void 0 && child !== false).map(clean);
46
+ return react$1.createElement(node.type, { ...props, key: node.key, ...kept ? { style: kept } : {} }, ...list);
47
+ }
48
+ var picture = (item, key) => react$1.createElement("img", { key, src: item.src, alt: item.alt, style: item.style });
49
+ var run = (item, key) => {
50
+ if (typeof item === "string") return item;
51
+ if (item.kind === "span") return react$1.createElement("span", { key, style: item.style }, ...item.runs.map(run));
52
+ return picture(item, key);
53
+ };
54
+ var block = (item, key) => {
55
+ switch (item.kind) {
56
+ case "box":
57
+ return react$1.createElement("div", { key, style: item.style }, ...item.children.map(block));
58
+ case "text":
59
+ return react$1.createElement("div", { key, style: item.style }, ...item.runs.map(run));
60
+ case "image":
61
+ return picture(item, key);
62
+ case "drawing":
63
+ return react$1.createElement(
64
+ "svg",
65
+ { key, width: item.width, height: item.height, viewBox: item.viewBox, xmlns: "http://www.w3.org/2000/svg" },
66
+ ...item.shapes.map((shape, i) => react$1.createElement(shape.kind, { key: i, ...shape.attributes }))
67
+ );
68
+ }
69
+ };
70
+ var satoriResvg = async (canvas, output) => {
71
+ const tree = clean(react$1.createElement("div", { style: canvas.style }, ...canvas.children.map(block)));
72
+ const fonts = canvas.fonts.map((font) => ({ name: font.family, data: font.data, weight: font.weight, style: font.style }));
73
+ const svg = await satori__default.default(tree, { width: canvas.width, height: canvas.height, fonts });
74
+ if (output.format === "svg") return new TextEncoder().encode(svg);
75
+ return new Uint8Array(new resvgJs.Resvg(svg, { fitTo: { mode: "width", value: Math.round(canvas.width * output.scale) }, font: { loadSystemFonts: false } }).render().asPng());
76
+ };
77
+ var shown = (node) => !node.hidden?.desktop;
78
+ var nodes = (node) => node.children.filter((child) => typeof child !== "string" && shown(child));
79
+ var present = (items) => items.filter((item) => item !== void 0);
80
+ var px = (value) => `${Math.round(value * 100) / 100}px`;
81
+ var FLEX = { start: "flex-start", center: "center", end: "flex-end", stretch: "stretch", "space-between": "space-between" };
82
+ var flex = (value) => typeof value === "string" ? FLEX[value] : void 0;
83
+ var aligned = (style) => {
84
+ const justify = { center: "center", right: "flex-end" }[String(style.textAlign)];
85
+ return justify ? { ...style, justifyContent: justify } : style;
86
+ };
87
+ function writeCanvas(root, context) {
88
+ const write = new Writer(context);
89
+ return {
90
+ width: Number(root.props.width),
91
+ height: Number(root.props.height),
92
+ style: {
93
+ display: "flex",
94
+ flexDirection: "column",
95
+ width: px(Number(root.props.width)),
96
+ height: px(Number(root.props.height)),
97
+ overflow: "hidden",
98
+ justifyContent: flex(root.style.justifyContent),
99
+ ...write.box(root),
100
+ ...write.text(root),
101
+ ...gap(root)
102
+ },
103
+ children: present(nodes(root).map((child) => write.block(child, root))),
104
+ fonts: context.fonts
105
+ };
106
+ }
107
+ var gap = (node) => typeof node.style.gap === "number" ? { gap: px(node.style.gap) } : {};
108
+ var Writer = class {
109
+ constructor(context) {
110
+ this.context = context;
111
+ }
112
+ /** The family a stack is drawn in: the first given in it, else the first given. */
113
+ family(stack) {
114
+ return fontStack(stack).find((family) => this.context.families.has(family.toLowerCase())) ?? this.context.fallback;
115
+ }
116
+ text(node) {
117
+ const text = node.text;
118
+ return {
119
+ color: text.color,
120
+ fontFamily: this.family(String(text.fontFamily)),
121
+ fontSize: px(text.fontSize),
122
+ fontWeight: text.fontWeight,
123
+ fontStyle: text.fontStyle,
124
+ lineHeight: px(text.lineHeightPx),
125
+ letterSpacing: text.letterSpacing ? px(text.letterSpacing) : void 0,
126
+ textAlign: text.textAlign,
127
+ textTransform: text.textTransform === "none" ? void 0 : text.textTransform
128
+ };
129
+ }
130
+ box(node) {
131
+ const style = node.style;
132
+ const out = {};
133
+ const padding = style.padding;
134
+ if (padding) out.padding = `${px(padding.top)} ${px(padding.right)} ${px(padding.bottom)} ${px(padding.left)}`;
135
+ if (typeof style.backgroundColor === "string") out.backgroundColor = style.backgroundColor;
136
+ const border = style.border;
137
+ for (const side of ["top", "right", "bottom", "left"]) {
138
+ const line = border?.[side];
139
+ if (line) out[`border${side[0].toUpperCase()}${side.slice(1)}`] = line.style === "none" ? "none" : `${px(line.width)} ${line.style} ${line.color}`;
140
+ }
141
+ const radius = style.borderRadius;
142
+ if (typeof radius === "number") out.borderRadius = px(radius);
143
+ else if (radius) out.borderRadius = `${px(radius.topLeft)} ${px(radius.topRight)} ${px(radius.bottomRight)} ${px(radius.bottomLeft)}`;
144
+ if (typeof style.height === "number") out.height = px(style.height);
145
+ if (style.overflow === "hidden") out.overflow = "hidden";
146
+ return out;
147
+ }
148
+ /** Where a child of its own size sits across a stack, as in the other outputs. */
149
+ alignSelf(child, stack) {
150
+ const sized = child.style.width !== void 0 || child.style.maxWidth !== void 0;
151
+ if (child.type === "Divider" || child.definition.sizing !== "shrink" && !sized) return void 0;
152
+ const align = child.style.alignSelf ?? stack.style.alignItems;
153
+ if (align === "stretch") return void 0;
154
+ if (align) return flex(align);
155
+ return { left: "flex-start", justify: "flex-start", center: "center", right: "flex-end" }[stack.text.textAlign];
156
+ }
157
+ block(node, parent, row = false) {
158
+ const width = node.width !== null ? px(node.width) : void 0;
159
+ const placed = row ? { width, flexShrink: 0, flexGrow: 0, alignSelf: node.definition.sizing === "shrink" ? flex(parent.style.alignItems ?? "start") : void 0 } : { alignSelf: this.alignSelf(node, parent), width: node.style.width !== void 0 || node.style.maxWidth !== void 0 || node.definition.sizing === "shrink" ? width : void 0 };
160
+ switch (node.type) {
161
+ case "Text":
162
+ case "Heading":
163
+ return this.paragraph(node, { ...placed, ...this.box(node) });
164
+ case "Button":
165
+ return { kind: "box", style: { display: "flex", justifyContent: "center", ...placed, ...this.box(node), width }, children: [this.paragraph(node, { textAlign: "center" })] };
166
+ case "Image":
167
+ return this.image(node, placed);
168
+ case "Divider": {
169
+ const line = node.style.borderTop;
170
+ return {
171
+ kind: "box",
172
+ style: { display: "flex", ...this.box(node), ...placed, width },
173
+ children: [{ kind: "box", style: { display: "flex", flexGrow: 1, borderTop: line ? `${px(line.width)} ${line.style} ${line.color}` : void 0 }, children: [] }]
174
+ };
175
+ }
176
+ case "Row":
177
+ return {
178
+ kind: "box",
179
+ style: { display: "flex", flexDirection: "row", ...this.box(node), ...placed, ...gap(node), alignItems: flex(node.style.alignItems ?? "start"), justifyContent: flex(node.style.justifyContent) },
180
+ children: present(nodes(node).map((child) => this.block(child, node, true)))
181
+ };
182
+ case "Layer":
183
+ return this.layer(node, placed);
184
+ case "Svg":
185
+ return this.svg(node, placed);
186
+ case "Link":
187
+ if (node.children.some((child) => typeof child === "string" || child.definition.categories.includes("inline"))) return this.paragraph(node, { ...placed, ...this.box(node) });
188
+ return { kind: "box", style: { display: "flex", flexDirection: "column", ...placed }, children: present(nodes(node).map((child) => this.block(child, node))) };
189
+ case "List":
190
+ return this.list(node, placed);
191
+ case "Table":
192
+ return this.table(node, placed);
193
+ case "Html":
194
+ return { kind: "text", style: aligned({ display: "flex", ...this.text(node), ...placed }), runs: [htmlText(node)] };
195
+ case "Video":
196
+ return { kind: "text", style: aligned({ display: "flex", ...this.text(node), ...this.box(node), ...placed }), runs: [String(node.props.title ?? "Video")] };
197
+ case "Section":
198
+ case "Column":
199
+ return {
200
+ kind: "box",
201
+ style: { display: "flex", flexDirection: "column", ...this.box(node), ...placed, ...gap(node), justifyContent: flex(node.style.justifyContent) },
202
+ children: present(nodes(node).map((child) => this.block(child, node)))
203
+ };
204
+ default:
205
+ throw new Error(`[Elements] The image writer draws no ${node.type}`);
206
+ }
207
+ }
208
+ /** A List: each item a marker (its number, or a bullet) beside its text, a List in it under the text. */
209
+ list(node, placed) {
210
+ const indent = typeof node.style.indent === "number" ? node.style.indent : 24;
211
+ const start = Number(node.props.start ?? 1);
212
+ return {
213
+ kind: "box",
214
+ style: { display: "flex", flexDirection: "column", ...this.box(node), ...placed, ...gap(node) },
215
+ children: nodes(node).map((item, i) => ({
216
+ kind: "box",
217
+ style: { display: "flex", flexDirection: "row" },
218
+ children: [
219
+ { kind: "text", style: { display: "flex", ...this.text(item), width: px(indent), flexShrink: 0, textAlign: "left" }, runs: [node.props.type === "number" ? `${start + i}.` : "\u2022"] },
220
+ { kind: "box", style: { display: "flex", flexDirection: "column", flexGrow: 1, flexShrink: 1 }, children: this.itemParts(item) }
221
+ ]
222
+ }))
223
+ };
224
+ }
225
+ /** A list item's text and the Lists in it, in their order: each run of text a paragraph, each List under it. */
226
+ itemParts(item) {
227
+ const parts = [];
228
+ let text = [];
229
+ const flush = () => {
230
+ if (text.some((child) => typeof child !== "string" || child.trim() !== "")) parts.push(this.paragraph({ ...item, children: text }, {}));
231
+ text = [];
232
+ };
233
+ for (const child of item.children) {
234
+ if (typeof child !== "string" && child.type === "List") {
235
+ flush();
236
+ if (shown(child)) parts.push(this.list(child, {}));
237
+ } else text.push(child);
238
+ }
239
+ flush();
240
+ return parts;
241
+ }
242
+ /** A Table: its rows one under another, each cell at its columns' width (a header row's text bold). */
243
+ table(node, placed) {
244
+ const columns = node.columns ?? null;
245
+ const cellPadding = node.style.cellPadding;
246
+ const rowBorder = node.style.rowBorder;
247
+ const rows = nodes(node);
248
+ return {
249
+ kind: "box",
250
+ style: { display: "flex", flexDirection: "column", ...this.box(node), ...placed },
251
+ children: rows.map((row, r) => {
252
+ let column = 0;
253
+ const line = r < rows.length - 1 && rowBorder ? `${px(rowBorder.width)} ${rowBorder.style} ${rowBorder.color}` : void 0;
254
+ return {
255
+ kind: "box",
256
+ style: { display: "flex", flexDirection: "row", ...this.box(row), borderBottom: line },
257
+ children: nodes(row).map((cell) => {
258
+ const span = Math.max(1, Number(cell.props.colSpan ?? 1));
259
+ const width = columns ? columns.slice(column, column + span).reduce((sum, w) => sum + w, 0) : void 0;
260
+ column += span;
261
+ const padding = cellPadding ? `${px(cellPadding.top)} ${px(cellPadding.right)} ${px(cellPadding.bottom)} ${px(cellPadding.left)}` : void 0;
262
+ return {
263
+ kind: "box",
264
+ style: { display: "flex", flexDirection: "column", padding, ...this.box(cell), width: width !== void 0 ? px(width) : void 0, flexGrow: width === void 0 ? 1 : 0, flexShrink: 0 },
265
+ children: [this.paragraph(cell, row.props.header ? { fontWeight: 700 } : {})]
266
+ };
267
+ })
268
+ };
269
+ })
270
+ };
271
+ }
272
+ /** A block of text: its runs, a line break starting a new line. */
273
+ paragraph(node, style) {
274
+ const breaks = node.children.some((child) => typeof child !== "string" && child.type === "LineBreak");
275
+ return { kind: "text", style: aligned({ display: "flex", flexWrap: "wrap", ...this.text(node), ...style, whiteSpace: breaks ? "pre-wrap" : void 0 }), runs: this.runs(node) };
276
+ }
277
+ runs(node) {
278
+ return present(
279
+ node.children.map((child) => {
280
+ if (typeof child === "string") return child;
281
+ if (!shown(child)) return void 0;
282
+ switch (child.type) {
283
+ case "LineBreak":
284
+ return "\n";
285
+ case "MergeTag":
286
+ return `{{${child.props.name}}}`;
287
+ case "Image":
288
+ return this.image(child, {});
289
+ default: {
290
+ const style = { ...this.text(child), textAlign: void 0 };
291
+ if (child.style.textDecoration !== void 0) style.textDecoration = String(child.style.textDecoration);
292
+ if (typeof child.style.backgroundColor === "string") style.backgroundColor = child.style.backgroundColor;
293
+ return { kind: "span", style, runs: this.runs(child) };
294
+ }
295
+ }
296
+ })
297
+ );
298
+ }
299
+ image(node, placed, cover) {
300
+ const { src, width, height } = assetParts(node.props.src);
301
+ const source = this.context.image(src);
302
+ if (!source) return void 0;
303
+ const drawn = node.width ?? width;
304
+ const style = { ...placed, ...this.box(node) };
305
+ if (cover !== void 0) Object.assign(style, { width: "100%", height: px(cover), objectFit: "cover" });
306
+ else if (drawn && width && height) Object.assign(style, { width: px(drawn), height: typeof node.style.height === "number" ? px(node.style.height) : px(drawn * height / width) });
307
+ return { kind: "image", src: source, alt: String(node.props.alt ?? ""), style };
308
+ }
309
+ /** A Layer: its first child in place, the others over it, each in the Layer's whole box, placed by its alignment. */
310
+ layer(node, placed) {
311
+ const [base, ...over] = nodes(node);
312
+ const height = typeof node.style.height === "number" ? node.style.height : void 0;
313
+ const under = base && (base.type === "Image" && height !== void 0 ? this.image(base, {}, height) : this.block(base, node));
314
+ return {
315
+ kind: "box",
316
+ style: { display: "flex", position: "relative", ...this.box(node), ...placed, overflow: node.style.borderRadius !== void 0 ? "hidden" : void 0 },
317
+ children: present([
318
+ under,
319
+ ...over.map((child) => ({
320
+ kind: "box",
321
+ style: { display: "flex", flexDirection: "column", position: "absolute", top: 0, right: 0, bottom: 0, left: 0, justifyContent: flex(node.style.alignItems ?? "start"), alignItems: flex(node.style.justifyItems ?? "stretch") },
322
+ children: present([this.block(child, node)])
323
+ }))
324
+ ])
325
+ };
326
+ }
327
+ /** An Svg as a drawing, its shapes in its viewBox's units. */
328
+ svg(node, placed) {
329
+ const { width, height, viewBox } = node.props;
330
+ const paint = (value) => value === "transparent" ? "none" : value;
331
+ const KINDS = { Rect: "rect", Circle: "circle", Ellipse: "ellipse", Line: "line", Polygon: "polygon", Path: "path" };
332
+ const shapes = nodes(node).map((shape) => ({
333
+ kind: KINDS[shape.type],
334
+ attributes: { ...shape.props, fill: paint(shape.style.fill), stroke: paint(shape.style.stroke), strokeWidth: shape.style.strokeWidth, opacity: shape.style.opacity }
335
+ }));
336
+ return { kind: "box", style: { display: "flex", ...this.box(node), ...placed, width: void 0 }, children: [{ kind: "drawing", width, height, viewBox: viewBox ?? `0 0 ${width} ${height}`, shapes }] };
337
+ }
338
+ };
339
+ async function renderImage(element, options = {}) {
340
+ const { root, diagnostics, layout } = react.prepare(element);
341
+ readable(layout, "image");
342
+ if (!root) throw new react.ElementsError(diagnostics);
343
+ if (root.type !== "Canvas") {
344
+ throw new react.ElementsError([...diagnostics, { code: "image/root", severity: "error", message: `Image output is a Canvas's: this template is a ${root.type}`, path: "root" }]);
345
+ }
346
+ const found = [];
347
+ const fonts = fontsOf(root, options.fonts ?? [], found);
348
+ const images = imageSources(root, options.images ?? {}, found);
349
+ if (found.some((d) => d.severity === "error")) throw new react.ElementsError([...diagnostics, ...found]);
350
+ found.push(...missingFaces(root, fonts));
351
+ const width = Number(root.props.width);
352
+ const height = Number(root.props.height);
353
+ const families = new Set(fonts.map((font) => font.family.toLowerCase()));
354
+ const canvas = writeCanvas(root, { families, fallback: fonts[0]?.family ?? "", fonts, image: (src) => images.get(src) });
355
+ const scale = options.scale ?? 1;
356
+ if (options.format === "svg") return { image: await satoriResvg(canvas, { format: "svg", scale }), width, height, diagnostics: [...diagnostics, ...found] };
357
+ const image = await satoriResvg(canvas, { format: "png", scale });
358
+ return { image, width: Math.round(width * scale), height: Math.round(height * scale), diagnostics: [...diagnostics, ...found] };
359
+ }
360
+ function readable(layout, output) {
361
+ if (layout === LAYOUT_VERSION) return;
362
+ throw new react.ElementsError([
363
+ {
364
+ code: `${output}/version`,
365
+ severity: "error",
366
+ message: `@hyper-elements/react/${output} reads layout version ${LAYOUT_VERSION}; the main entry writes ${typeof layout === "number" ? layout : "none"}: update @hyper-elements/react`,
367
+ path: "root"
368
+ }
369
+ ]);
370
+ }
371
+ var buffer = (bytes) => {
372
+ if (bytes instanceof ArrayBuffer) return bytes;
373
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
374
+ };
375
+ var weight = (value) => Math.min(900, Math.max(100, Math.round((value ?? 400) / 100) * 100));
376
+ var woff2 = (bytes) => String.fromCharCode(...new Uint8Array(buffer(bytes)).subarray(0, 4)) === "wOF2";
377
+ function fontsOf(root, given, found) {
378
+ const drawn = (family, bytes) => {
379
+ if (!woff2(bytes)) return true;
380
+ found.push({ code: "image/font", severity: "warning", message: `${family}'s file is WOFF2: an image draws TTF, OTF and WOFF files. Its text is drawn in another font meanwhile`, path: "root.props.fonts" });
381
+ return false;
382
+ };
383
+ const out = given.filter((font) => drawn(font.family, font.src)).map((font) => ({ family: font.family, data: buffer(font.src), weight: weight(font.weight), style: font.style ?? "normal" }));
384
+ const named = new Set(out.map((font) => font.family.toLowerCase()));
385
+ for (const face of root.props.fonts ?? []) {
386
+ if (named.has(face.family.toLowerCase())) continue;
387
+ const data = /^data:[^,]*;base64,(.*)$/.exec(face.src)?.[1];
388
+ if (data && !drawn(face.family, Uint8Array.from(Buffer.from(data, "base64")))) continue;
389
+ if (data) {
390
+ out.push({ family: face.family, data: buffer(Uint8Array.from(Buffer.from(data, "base64"))), weight: weight(Array.isArray(face.weight) ? face.weight[0] : face.weight), style: face.style ?? "normal" });
391
+ continue;
392
+ }
393
+ found.push({ code: "image/font", severity: "warning", message: `Image output loads no fonts: give ${face.family}'s file in renderImage's fonts option. Its text is drawn in another font meanwhile`, path: "root.props.fonts" });
394
+ }
395
+ if (!out.length && hasText(root)) {
396
+ found.push({ code: "image/font", severity: "error", message: "An image draws its text with the fonts it's given, and has none: give a font file in renderImage's fonts option", path: "root" });
397
+ }
398
+ return out;
399
+ }
400
+ function missingFaces(root, fonts) {
401
+ const out = /* @__PURE__ */ new Map();
402
+ const given = /* @__PURE__ */ new Map();
403
+ for (const font of fonts) given.set(font.family.toLowerCase(), [...given.get(font.family.toLowerCase()) ?? [], font]);
404
+ const visit = (node) => {
405
+ if (node.hidden?.desktop) return;
406
+ if (node.children.some((child) => typeof child === "string" && child.trim())) {
407
+ const stack = fontStack(String(node.text.fontFamily));
408
+ const family = stack.find((name) => given.has(name.toLowerCase())) ?? fonts[0]?.family;
409
+ const faces = family ? given.get(family.toLowerCase()) ?? [] : [];
410
+ const weight2 = node.text.fontWeight;
411
+ const style = node.text.fontStyle;
412
+ if (family && !faces.some((face) => face.weight === weight2 && (face.style ?? "normal") === style)) {
413
+ const key = `${family}|${weight2}|${style}`;
414
+ if (!out.has(key)) {
415
+ const has = faces.map((face) => `${face.weight}${face.style === "italic" ? " italic" : ""}`).join(", ");
416
+ out.set(key, {
417
+ code: "image/font-weight",
418
+ severity: "warning",
419
+ message: `Text in ${family} at ${weight2}${style === "italic" ? " italic" : ""}, which is given at ${has} only: it's drawn in the nearest. Give that file in renderImage's fonts option`,
420
+ path: node.path,
421
+ ...node.id ? { nodeId: node.id } : {}
422
+ });
423
+ }
424
+ }
425
+ }
426
+ for (const child of node.children) if (typeof child !== "string") visit(child);
427
+ };
428
+ visit(root);
429
+ return [...out.values()];
430
+ }
431
+ function hasText(node) {
432
+ if (node.hidden?.desktop) return false;
433
+ return node.children.some((child) => typeof child === "string" ? child.trim() !== "" : hasText(child));
434
+ }
435
+ function mime(bytes) {
436
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "image/png";
437
+ if (bytes[0] === 255 && bytes[1] === 216) return "image/jpeg";
438
+ if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return "image/gif";
439
+ const head = new TextDecoder().decode(bytes.subarray(0, 256)).trimStart();
440
+ if (head.startsWith("<svg") || head.startsWith("<?xml")) return "image/svg+xml";
441
+ return void 0;
442
+ }
443
+ var DRAWN = /^data:image\/(png|jpeg|jpg|gif|svg\+xml)[;,]/;
444
+ function imageSources(root, given, found) {
445
+ const out = /* @__PURE__ */ new Map();
446
+ const visit = (node) => {
447
+ if (node.hidden?.desktop) return;
448
+ if (node.type === "Image") {
449
+ const { src } = assetParts(node.props.src);
450
+ const bytes = given[src] ? new Uint8Array(buffer(given[src])) : void 0;
451
+ const type = bytes && mime(bytes);
452
+ if (DRAWN.test(src)) out.set(src, src);
453
+ else if (bytes && type) out.set(src, `data:${type};base64,${Buffer.from(bytes).toString("base64")}`);
454
+ else {
455
+ found.push({
456
+ code: "image/image",
457
+ severity: "error",
458
+ message: bytes ? "This image's file isn't a PNG, JPEG, GIF or SVG" : "Image output loads no images: give this one's file in renderImage's images option (by its URL), or its src as a data: URI",
459
+ path: `${node.path}.props.src`,
460
+ ...node.id ? { nodeId: node.id } : {}
461
+ });
462
+ }
463
+ }
464
+ for (const child of node.children) if (typeof child !== "string") visit(child);
465
+ };
466
+ visit(root);
467
+ return out;
468
+ }
469
+
470
+ exports.renderImage = renderImage;
471
+ //# sourceMappingURL=image.cjs.map
472
+ //# sourceMappingURL=image.cjs.map