@effing/canvas 0.1.0

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/index.js ADDED
@@ -0,0 +1,2070 @@
1
+ // src/index.ts
2
+ import { createCanvas as _createCanvas } from "@napi-rs/canvas";
3
+ import {
4
+ Canvas,
5
+ GlobalFonts as GlobalFonts2,
6
+ loadImage as loadImage4,
7
+ Image
8
+ } from "@napi-rs/canvas";
9
+
10
+ // src/lottie.ts
11
+ import { LottieAnimation } from "@napi-rs/canvas";
12
+ function loadLottie(data, options) {
13
+ const jsonString = typeof data === "string" ? data : data.toString("utf-8");
14
+ return LottieAnimation.loadFromData(jsonString, {
15
+ resourcePath: options?.resourcePath
16
+ });
17
+ }
18
+ function renderLottieFrame(ctx, animation, frame) {
19
+ animation.seekFrame(frame);
20
+ animation.render(ctx);
21
+ }
22
+
23
+ // src/jsx/draw/index.ts
24
+ import { loadImage as loadImage3 } from "@napi-rs/canvas";
25
+
26
+ // src/jsx/language.ts
27
+ function isEmoji(char) {
28
+ const cp = char.codePointAt(0);
29
+ if (cp === void 0) return false;
30
+ if (cp >= 128512 && cp <= 128591) return true;
31
+ if (cp >= 127744 && cp <= 128511) return true;
32
+ if (cp >= 128640 && cp <= 128767) return true;
33
+ if (cp >= 129280 && cp <= 129535) return true;
34
+ if (cp >= 9728 && cp <= 9983) return true;
35
+ if (cp >= 9984 && cp <= 10175) return true;
36
+ if (cp >= 11088 && cp <= 11093) return true;
37
+ if (cp >= 8205 && cp <= 8205) return true;
38
+ if (cp >= 65024 && cp <= 65039) return true;
39
+ if (cp >= 129536 && cp <= 129647) return true;
40
+ if (cp >= 129648 && cp <= 129791) return true;
41
+ if (cp >= 8986 && cp <= 9203) return true;
42
+ if (cp >= 9193 && cp <= 9210) return true;
43
+ if (cp >= 9642 && cp <= 9726) return true;
44
+ if (cp >= 10548 && cp <= 10549) return true;
45
+ if (cp >= 11013 && cp <= 11015) return true;
46
+ if (cp >= 12336 && cp <= 12336) return true;
47
+ if (cp >= 12349 && cp <= 12349) return true;
48
+ if (cp >= 12951 && cp <= 12953) return true;
49
+ return false;
50
+ }
51
+
52
+ // src/jsx/text/linebreak.ts
53
+ import LineBreaker from "linebreak";
54
+ function findBreakOpportunities(text) {
55
+ const breaker = new LineBreaker(text);
56
+ const opportunities = [];
57
+ let bk = breaker.nextBreak();
58
+ while (bk) {
59
+ opportunities.push({
60
+ position: bk.position,
61
+ required: bk.required ?? false
62
+ });
63
+ bk = breaker.nextBreak();
64
+ }
65
+ return opportunities;
66
+ }
67
+
68
+ // src/jsx/text/measure.ts
69
+ import { createCanvas } from "@napi-rs/canvas";
70
+ var scratchCtx = null;
71
+ function getScratchCtx() {
72
+ if (!scratchCtx) {
73
+ scratchCtx = createCanvas(1, 1).getContext("2d");
74
+ }
75
+ return scratchCtx;
76
+ }
77
+ function setFont(ctx, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal") {
78
+ ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
79
+ }
80
+ function measureText(text, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal", ctx) {
81
+ const c = ctx ?? getScratchCtx();
82
+ setFont(c, fontSize, fontFamily, fontWeight, fontStyle);
83
+ const m = c.measureText(text);
84
+ const ascent = m.fontBoundingBoxAscent ?? m.actualBoundingBoxAscent ?? fontSize * 0.8;
85
+ const descent = m.fontBoundingBoxDescent ?? m.actualBoundingBoxDescent ?? fontSize * 0.2;
86
+ return {
87
+ width: m.width,
88
+ ascent,
89
+ descent,
90
+ height: ascent + descent
91
+ };
92
+ }
93
+ function measureWord(word, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal", ctx, letterSpacing = 0) {
94
+ const base = measureText(
95
+ word,
96
+ fontSize,
97
+ fontFamily,
98
+ fontWeight,
99
+ fontStyle,
100
+ ctx
101
+ ).width;
102
+ if (letterSpacing === 0 || word.length === 0) return base;
103
+ return base + letterSpacing * word.length;
104
+ }
105
+
106
+ // src/jsx/text/index.ts
107
+ function emojiAwareMeasureWord(word, fontSize, fontFamily, fontWeight, fontStyle, ctx, letterSpacing = 0) {
108
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
109
+ let totalWidth = 0;
110
+ let textBuffer = "";
111
+ for (const { segment } of segmenter.segment(word)) {
112
+ let isEmojiSegment = false;
113
+ for (const char of segment) {
114
+ if (isEmoji(char)) {
115
+ isEmojiSegment = true;
116
+ break;
117
+ }
118
+ }
119
+ if (isEmojiSegment) {
120
+ if (textBuffer) {
121
+ totalWidth += measureWord(
122
+ textBuffer,
123
+ fontSize,
124
+ fontFamily,
125
+ fontWeight,
126
+ fontStyle,
127
+ ctx,
128
+ letterSpacing
129
+ );
130
+ textBuffer = "";
131
+ }
132
+ totalWidth += fontSize;
133
+ } else {
134
+ textBuffer += segment;
135
+ }
136
+ }
137
+ if (textBuffer) {
138
+ totalWidth += measureWord(
139
+ textBuffer,
140
+ fontSize,
141
+ fontFamily,
142
+ fontWeight,
143
+ fontStyle,
144
+ ctx,
145
+ letterSpacing
146
+ );
147
+ }
148
+ return totalWidth;
149
+ }
150
+ function layoutText(text, style, maxWidth, ctx, emojiEnabled) {
151
+ const fontSize = style.fontSize ?? 16;
152
+ const fontFamily = style.fontFamily ?? "sans-serif";
153
+ const fontWeight = style.fontWeight ?? 400;
154
+ const fontStyle = style.fontStyle ?? "normal";
155
+ const color = style.color ?? "black";
156
+ const textAlign = style.textAlign ?? "left";
157
+ const refMetrics = measureText(
158
+ "M",
159
+ fontSize,
160
+ fontFamily,
161
+ fontWeight,
162
+ fontStyle,
163
+ ctx
164
+ );
165
+ const lineHeightPx = resolveLineHeight(
166
+ style.lineHeight,
167
+ fontSize,
168
+ refMetrics
169
+ );
170
+ const letterSpacing = typeof style.letterSpacing === "number" ? style.letterSpacing : 0;
171
+ const whiteSpace = style.whiteSpace ?? "normal";
172
+ const wordBreak = style.wordBreak ?? "normal";
173
+ const textOverflow = style.textOverflow ?? "clip";
174
+ const textDecoration = style.textDecoration;
175
+ const measure = emojiEnabled ? (word, ls) => emojiAwareMeasureWord(
176
+ word,
177
+ fontSize,
178
+ fontFamily,
179
+ fontWeight,
180
+ fontStyle,
181
+ ctx,
182
+ ls ?? letterSpacing
183
+ ) : (word, ls) => measureWord(
184
+ word,
185
+ fontSize,
186
+ fontFamily,
187
+ fontWeight,
188
+ fontStyle,
189
+ ctx,
190
+ ls ?? letterSpacing
191
+ );
192
+ let processedText = text;
193
+ if (style.textTransform === "uppercase") {
194
+ processedText = text.toUpperCase();
195
+ } else if (style.textTransform === "lowercase") {
196
+ processedText = text.toLowerCase();
197
+ } else if (style.textTransform === "capitalize") {
198
+ processedText = text.replace(/\b\w/g, (c) => c.toUpperCase());
199
+ }
200
+ const noWrap = whiteSpace === "nowrap" || whiteSpace === "pre";
201
+ const preserveWhitespace = whiteSpace === "pre" || whiteSpace === "pre-wrap";
202
+ const paragraphs = preserveWhitespace ? processedText.split("\n") : processedText.split("\n");
203
+ const lines = [];
204
+ for (const paragraph of paragraphs) {
205
+ if (noWrap) {
206
+ lines.push(paragraph);
207
+ continue;
208
+ }
209
+ const wrapped = wrapText(
210
+ paragraph,
211
+ maxWidth,
212
+ fontSize,
213
+ fontFamily,
214
+ fontWeight,
215
+ fontStyle,
216
+ letterSpacing,
217
+ wordBreak,
218
+ ctx,
219
+ measure
220
+ );
221
+ lines.push(...wrapped);
222
+ }
223
+ if (textOverflow === "ellipsis" && noWrap && lines.length === 1) {
224
+ const line = lines[0];
225
+ const lineWidth = measure(line);
226
+ if (lineWidth > maxWidth) {
227
+ lines[0] = truncateWithEllipsis(
228
+ line,
229
+ maxWidth,
230
+ fontSize,
231
+ fontFamily,
232
+ fontWeight,
233
+ fontStyle,
234
+ ctx,
235
+ letterSpacing
236
+ );
237
+ }
238
+ }
239
+ const segments = [];
240
+ let totalHeight = 0;
241
+ let maxLineWidth = 0;
242
+ for (let i = 0; i < lines.length; i++) {
243
+ const line = lines[i];
244
+ const lineWidth = measure(line);
245
+ let x = 0;
246
+ if (textAlign === "center") {
247
+ x = (maxWidth - lineWidth) / 2;
248
+ } else if (textAlign === "right") {
249
+ x = maxWidth - lineWidth;
250
+ }
251
+ const metrics = measureText(
252
+ line || "M",
253
+ fontSize,
254
+ fontFamily,
255
+ fontWeight,
256
+ fontStyle,
257
+ ctx
258
+ );
259
+ segments.push({
260
+ text: line,
261
+ x,
262
+ y: totalHeight + (lineHeightPx + metrics.ascent - metrics.descent) / 2,
263
+ width: lineWidth,
264
+ height: lineHeightPx,
265
+ fontSize,
266
+ fontFamily,
267
+ fontWeight,
268
+ fontStyle,
269
+ color,
270
+ ascent: metrics.ascent,
271
+ textDecoration,
272
+ letterSpacing,
273
+ lineIndex: i
274
+ });
275
+ totalHeight += lineHeightPx;
276
+ maxLineWidth = Math.max(maxLineWidth, lineWidth);
277
+ }
278
+ return {
279
+ segments,
280
+ width: maxLineWidth,
281
+ height: totalHeight
282
+ };
283
+ }
284
+ function resolveLineHeight(lineHeight, fontSize, metrics) {
285
+ if (lineHeight === void 0 || lineHeight === "normal") {
286
+ return metrics ? metrics.ascent + metrics.descent : fontSize * 1.2;
287
+ }
288
+ if (typeof lineHeight === "number") {
289
+ return lineHeight > 5 ? lineHeight : lineHeight * fontSize;
290
+ }
291
+ const parsed = parseFloat(String(lineHeight));
292
+ return isNaN(parsed) ? fontSize * 1.2 : parsed;
293
+ }
294
+ function wrapText(text, maxWidth, fontSize, fontFamily, fontWeight, fontStyle, letterSpacing, wordBreak, ctx, measureFn) {
295
+ if (!text) return [""];
296
+ const mw = measureFn ?? ((word) => measureWord(
297
+ word,
298
+ fontSize,
299
+ fontFamily,
300
+ fontWeight,
301
+ fontStyle,
302
+ ctx,
303
+ letterSpacing
304
+ ));
305
+ const breakOpps = findBreakOpportunities(text);
306
+ const lines = [];
307
+ let lineStart = 0;
308
+ let lastBreak = 0;
309
+ for (const opp of breakOpps) {
310
+ const segment = text.slice(lineStart, opp.position);
311
+ const segWidth = mw(segment);
312
+ if (segWidth > maxWidth && lastBreak > lineStart) {
313
+ const line = text.slice(lineStart, lastBreak).replace(/\s+$/, "");
314
+ lines.push(line);
315
+ lineStart = lastBreak;
316
+ } else if (segWidth > maxWidth && wordBreak === "break-all") {
317
+ const broken = forceBreakWord(
318
+ text,
319
+ lineStart,
320
+ opp.position,
321
+ maxWidth,
322
+ fontSize,
323
+ fontFamily,
324
+ fontWeight,
325
+ fontStyle,
326
+ ctx,
327
+ letterSpacing,
328
+ measureFn
329
+ );
330
+ lines.push(...broken.lines);
331
+ lineStart = broken.endPos;
332
+ }
333
+ if (opp.required) {
334
+ const line = text.slice(lineStart, opp.position).replace(/\s+$/, "");
335
+ lines.push(line);
336
+ lineStart = opp.position;
337
+ }
338
+ lastBreak = opp.position;
339
+ }
340
+ if (lineStart < text.length) {
341
+ const remaining = text.slice(lineStart).replace(/\s+$/, "");
342
+ if (remaining) {
343
+ const remWidth = mw(remaining);
344
+ if (remWidth > maxWidth && wordBreak === "break-all") {
345
+ const broken = forceBreakWord(
346
+ text,
347
+ lineStart,
348
+ text.length,
349
+ maxWidth,
350
+ fontSize,
351
+ fontFamily,
352
+ fontWeight,
353
+ fontStyle,
354
+ ctx,
355
+ letterSpacing,
356
+ measureFn
357
+ );
358
+ lines.push(...broken.lines);
359
+ } else {
360
+ lines.push(remaining);
361
+ }
362
+ }
363
+ }
364
+ return lines.length > 0 ? lines : [""];
365
+ }
366
+ function forceBreakWord(text, start, end, maxWidth, fontSize, fontFamily, fontWeight, fontStyle, ctx, letterSpacing = 0, measureFn) {
367
+ const mw = measureFn ?? ((word) => measureWord(
368
+ word,
369
+ fontSize,
370
+ fontFamily,
371
+ fontWeight,
372
+ fontStyle,
373
+ ctx,
374
+ letterSpacing
375
+ ));
376
+ const lines = [];
377
+ let pos = start;
378
+ while (pos < end) {
379
+ let breakPos = pos + 1;
380
+ while (breakPos < end) {
381
+ const chunk = text.slice(pos, breakPos + 1);
382
+ const w = mw(chunk);
383
+ if (w > maxWidth) break;
384
+ breakPos++;
385
+ }
386
+ const line = text.slice(pos, breakPos);
387
+ if (line.trim()) lines.push(line);
388
+ pos = breakPos;
389
+ }
390
+ return { lines, endPos: end };
391
+ }
392
+ function truncateWithEllipsis(text, maxWidth, fontSize, fontFamily, fontWeight, fontStyle, ctx, letterSpacing = 0) {
393
+ const ellipsis = "\u2026";
394
+ const ellipsisWidth = measureWord(
395
+ ellipsis,
396
+ fontSize,
397
+ fontFamily,
398
+ fontWeight,
399
+ fontStyle,
400
+ ctx,
401
+ letterSpacing
402
+ );
403
+ const availWidth = maxWidth - ellipsisWidth;
404
+ for (let i = text.length - 1; i > 0; i--) {
405
+ const truncated = text.slice(0, i);
406
+ const w = measureWord(
407
+ truncated,
408
+ fontSize,
409
+ fontFamily,
410
+ fontWeight,
411
+ fontStyle,
412
+ ctx,
413
+ letterSpacing
414
+ );
415
+ if (w <= availWidth) {
416
+ return truncated + ellipsis;
417
+ }
418
+ }
419
+ return ellipsis;
420
+ }
421
+ function createTextMeasureFunc(text, style, ctx, emojiEnabled) {
422
+ const measureStyle = { ...style, textOverflow: "clip" };
423
+ return (width, _widthMode, _height, _heightMode) => {
424
+ const maxWidth = width > 0 ? width : Infinity;
425
+ const result = layoutText(text, measureStyle, maxWidth, ctx, emojiEnabled);
426
+ const wrapped = result.segments.length > 1;
427
+ const reportedWidth = wrapped ? Math.min(maxWidth, width > 0 ? width : result.width) : result.width;
428
+ return { width: Math.min(reportedWidth, maxWidth), height: result.height };
429
+ };
430
+ }
431
+
432
+ // src/jsx/draw/clip.ts
433
+ function applyClip(ctx, x, y, width, height, borderRadius) {
434
+ ctx.beginPath();
435
+ if (borderRadius && hasRadius(borderRadius)) {
436
+ const { topLeft, topRight, bottomRight, bottomLeft } = borderRadius;
437
+ roundedRect(
438
+ ctx,
439
+ x,
440
+ y,
441
+ width,
442
+ height,
443
+ topLeft,
444
+ topRight,
445
+ bottomRight,
446
+ bottomLeft
447
+ );
448
+ } else {
449
+ ctx.rect(x, y, width, height);
450
+ }
451
+ ctx.clip();
452
+ }
453
+ function roundedRect(ctx, x, y, w, h, tl, tr, br, bl) {
454
+ const maxR = Math.min(w, h) / 2;
455
+ tl = Math.min(tl, maxR);
456
+ tr = Math.min(tr, maxR);
457
+ br = Math.min(br, maxR);
458
+ bl = Math.min(bl, maxR);
459
+ ctx.moveTo(x + tl, y);
460
+ ctx.lineTo(x + w - tr, y);
461
+ if (tr > 0) ctx.arcTo(x + w, y, x + w, y + tr, tr);
462
+ ctx.lineTo(x + w, y + h - br);
463
+ if (br > 0) ctx.arcTo(x + w, y + h, x + w - br, y + h, br);
464
+ ctx.lineTo(x + bl, y + h);
465
+ if (bl > 0) ctx.arcTo(x, y + h, x, y + h - bl, bl);
466
+ ctx.lineTo(x, y + tl);
467
+ if (tl > 0) ctx.arcTo(x, y, x + tl, y, tl);
468
+ ctx.closePath();
469
+ }
470
+ function hasRadius(r) {
471
+ return r.topLeft > 0 || r.topRight > 0 || r.bottomRight > 0 || r.bottomLeft > 0;
472
+ }
473
+
474
+ // src/jsx/draw/gradient.ts
475
+ function createGradientFromCSS(ctx, cssGradient, x, y, width, height) {
476
+ const trimmed = cssGradient.trim();
477
+ if (trimmed.startsWith("linear-gradient")) {
478
+ return parseLinearGradient(ctx, trimmed, x, y, width, height);
479
+ }
480
+ if (trimmed.startsWith("radial-gradient")) {
481
+ return parseRadialGradient(ctx, trimmed, x, y, width, height);
482
+ }
483
+ return null;
484
+ }
485
+ function parseLinearGradient(ctx, css, x, y, width, height) {
486
+ const match = css.match(/linear-gradient\((.*)\)/s);
487
+ if (!match) return null;
488
+ const content = match[1].trim();
489
+ const parts = splitGradientArgs(content);
490
+ let angle = 180;
491
+ let colorStartIdx = 0;
492
+ const first = parts[0]?.trim();
493
+ if (first) {
494
+ if (first.startsWith("to ")) {
495
+ angle = directionToAngle(first);
496
+ colorStartIdx = 1;
497
+ } else if (first.endsWith("deg")) {
498
+ angle = parseFloat(first);
499
+ colorStartIdx = 1;
500
+ } else if (first.endsWith("turn")) {
501
+ angle = parseFloat(first) * 360;
502
+ colorStartIdx = 1;
503
+ }
504
+ }
505
+ const rad = (angle - 90) * Math.PI / 180;
506
+ const cx = x + width / 2;
507
+ const cy = y + height / 2;
508
+ const halfDiag = Math.abs(width * Math.cos(rad)) / 2 + Math.abs(height * Math.sin(rad)) / 2;
509
+ const x0 = cx - halfDiag * Math.cos(rad);
510
+ const y0 = cy - halfDiag * Math.sin(rad);
511
+ const x1 = cx + halfDiag * Math.cos(rad);
512
+ const y1 = cy + halfDiag * Math.sin(rad);
513
+ const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
514
+ const stops = parts.slice(colorStartIdx);
515
+ addColorStops(gradient, stops);
516
+ return gradient;
517
+ }
518
+ function parseRadialGradient(ctx, css, x, y, width, height) {
519
+ const match = css.match(/radial-gradient\((.*)\)/s);
520
+ if (!match) return null;
521
+ const content = match[1].trim();
522
+ const parts = splitGradientArgs(content);
523
+ const cx = x + width / 2;
524
+ const cy = y + height / 2;
525
+ const radius = Math.max(width, height) / 2;
526
+ const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
527
+ let colorStartIdx = 0;
528
+ const first = parts[0]?.trim() ?? "";
529
+ if (first.startsWith("circle") || first.startsWith("ellipse") || first.startsWith("closest") || first.startsWith("farthest")) {
530
+ colorStartIdx = 1;
531
+ }
532
+ addColorStops(gradient, parts.slice(colorStartIdx));
533
+ return gradient;
534
+ }
535
+ function addColorStops(gradient, stops) {
536
+ if (stops.length === 0) return;
537
+ for (let i = 0; i < stops.length; i++) {
538
+ const stop = stops[i].trim();
539
+ const percentMatch = stop.match(/^(.+?)\s+(\d+(?:\.\d+)?%?)$/);
540
+ if (percentMatch) {
541
+ const color = percentMatch[1];
542
+ const pos = percentMatch[2];
543
+ const offset = pos.endsWith("%") ? parseFloat(pos) / 100 : parseFloat(pos);
544
+ gradient.addColorStop(Math.max(0, Math.min(1, offset)), color);
545
+ } else {
546
+ const offset = stops.length === 1 ? 0.5 : i / (stops.length - 1);
547
+ gradient.addColorStop(offset, stop);
548
+ }
549
+ }
550
+ }
551
+ function directionToAngle(dir) {
552
+ const map = {
553
+ "to top": 0,
554
+ "to right": 90,
555
+ "to bottom": 180,
556
+ "to left": 270,
557
+ "to top right": 45,
558
+ "to top left": 315,
559
+ "to bottom right": 135,
560
+ "to bottom left": 225
561
+ };
562
+ return map[dir] ?? 180;
563
+ }
564
+ function splitGradientArgs(content) {
565
+ const parts = [];
566
+ let current = "";
567
+ let parenDepth = 0;
568
+ for (const char of content) {
569
+ if (char === "(") parenDepth++;
570
+ if (char === ")") parenDepth--;
571
+ if (char === "," && parenDepth === 0) {
572
+ parts.push(current);
573
+ current = "";
574
+ } else {
575
+ current += char;
576
+ }
577
+ }
578
+ if (current.trim()) parts.push(current);
579
+ return parts;
580
+ }
581
+
582
+ // src/jsx/draw/image.ts
583
+ import { loadImage } from "@napi-rs/canvas";
584
+
585
+ // src/jsx/draw/object-fit.ts
586
+ function computeCover(imgW, imgH, boxX, boxY, boxW, boxH) {
587
+ const imgRatio = imgW / imgH;
588
+ const boxRatio = boxW / boxH;
589
+ let sx, sy, sw, sh;
590
+ if (imgRatio > boxRatio) {
591
+ sh = imgH;
592
+ sw = imgH * boxRatio;
593
+ sx = (imgW - sw) / 2;
594
+ sy = 0;
595
+ } else {
596
+ sw = imgW;
597
+ sh = imgW / boxRatio;
598
+ sx = 0;
599
+ sy = (imgH - sh) / 2;
600
+ }
601
+ return { sx, sy, sw, sh, dx: boxX, dy: boxY, dw: boxW, dh: boxH };
602
+ }
603
+ function computeContain(imgW, imgH, boxX, boxY, boxW, boxH) {
604
+ const imgRatio = imgW / imgH;
605
+ const boxRatio = boxW / boxH;
606
+ let dw, dh;
607
+ if (imgRatio > boxRatio) {
608
+ dw = boxW;
609
+ dh = boxW / imgRatio;
610
+ } else {
611
+ dh = boxH;
612
+ dw = boxH * imgRatio;
613
+ }
614
+ const dx = boxX + (boxW - dw) / 2;
615
+ const dy = boxY + (boxH - dh) / 2;
616
+ return { sx: 0, sy: 0, sw: imgW, sh: imgH, dx, dy, dw, dh };
617
+ }
618
+
619
+ // src/jsx/draw/image.ts
620
+ async function drawImage(ctx, src, x, y, width, height, style) {
621
+ const image = await loadImage(src);
622
+ const objectFit = style?.objectFit ?? "fill";
623
+ if (objectFit === "fill") {
624
+ ctx.drawImage(image, x, y, width, height);
625
+ return;
626
+ }
627
+ const imgW = image.width;
628
+ const imgH = image.height;
629
+ if (objectFit === "contain") {
630
+ const r = computeContain(imgW, imgH, x, y, width, height);
631
+ ctx.drawImage(image, r.dx, r.dy, r.dw, r.dh);
632
+ } else if (objectFit === "cover") {
633
+ const r = computeCover(imgW, imgH, x, y, width, height);
634
+ ctx.drawImage(image, r.sx, r.sy, r.sw, r.sh, r.dx, r.dy, r.dw, r.dh);
635
+ } else if (objectFit === "none") {
636
+ const dx = x + (width - imgW) / 2;
637
+ const dy = y + (height - imgH) / 2;
638
+ ctx.drawImage(image, dx, dy);
639
+ } else if (objectFit === "scale-down") {
640
+ if (imgW <= width && imgH <= height) {
641
+ const dx = x + (width - imgW) / 2;
642
+ const dy = y + (height - imgH) / 2;
643
+ ctx.drawImage(image, dx, dy);
644
+ } else {
645
+ const r = computeContain(imgW, imgH, x, y, width, height);
646
+ ctx.drawImage(image, r.dx, r.dy, r.dw, r.dh);
647
+ }
648
+ }
649
+ }
650
+
651
+ // src/jsx/draw/rect.ts
652
+ function drawRect(ctx, x, y, width, height, style) {
653
+ const borderRadius = getBorderRadius(style);
654
+ const hasRoundedCorners = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
655
+ if (style.boxShadow) {
656
+ drawBoxShadow(ctx, x, y, width, height, style.boxShadow, borderRadius);
657
+ }
658
+ if (style.backgroundColor) {
659
+ ctx.fillStyle = style.backgroundColor;
660
+ if (hasRoundedCorners) {
661
+ ctx.beginPath();
662
+ roundedRect(
663
+ ctx,
664
+ x,
665
+ y,
666
+ width,
667
+ height,
668
+ borderRadius.topLeft,
669
+ borderRadius.topRight,
670
+ borderRadius.bottomRight,
671
+ borderRadius.bottomLeft
672
+ );
673
+ ctx.fill();
674
+ } else {
675
+ ctx.fillRect(x, y, width, height);
676
+ }
677
+ }
678
+ drawBorders(ctx, x, y, width, height, style, borderRadius);
679
+ }
680
+ function getBorderRadius(style) {
681
+ return {
682
+ topLeft: toNumber(style.borderTopLeftRadius),
683
+ topRight: toNumber(style.borderTopRightRadius),
684
+ bottomRight: toNumber(style.borderBottomRightRadius),
685
+ bottomLeft: toNumber(style.borderBottomLeftRadius)
686
+ };
687
+ }
688
+ function getBorderRadiusFromStyle(style) {
689
+ return getBorderRadius(style);
690
+ }
691
+ function drawBorders(ctx, x, y, width, height, style, borderRadius) {
692
+ const hasRoundedCorners = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
693
+ const tw = toNumber(style.borderTopWidth);
694
+ const rw = toNumber(style.borderRightWidth);
695
+ const bw = toNumber(style.borderBottomWidth);
696
+ const lw = toNumber(style.borderLeftWidth);
697
+ if (tw === 0 && rw === 0 && bw === 0 && lw === 0) return;
698
+ const allSameWidth = tw === rw && rw === bw && bw === lw && tw > 0;
699
+ const tc = style.borderTopColor ?? "black";
700
+ const rc = style.borderRightColor ?? "black";
701
+ const bc = style.borderBottomColor ?? "black";
702
+ const lc = style.borderLeftColor ?? "black";
703
+ const allSameColor = tc === rc && rc === bc && bc === lc;
704
+ if (allSameWidth && allSameColor) {
705
+ ctx.strokeStyle = tc;
706
+ ctx.lineWidth = tw;
707
+ if (hasRoundedCorners) {
708
+ ctx.beginPath();
709
+ const half = tw / 2;
710
+ roundedRect(
711
+ ctx,
712
+ x + half,
713
+ y + half,
714
+ width - tw,
715
+ height - tw,
716
+ Math.max(0, borderRadius.topLeft - half),
717
+ Math.max(0, borderRadius.topRight - half),
718
+ Math.max(0, borderRadius.bottomRight - half),
719
+ Math.max(0, borderRadius.bottomLeft - half)
720
+ );
721
+ ctx.stroke();
722
+ } else {
723
+ ctx.strokeRect(x + tw / 2, y + tw / 2, width - tw, height - tw);
724
+ }
725
+ return;
726
+ }
727
+ if (tw > 0) {
728
+ ctx.strokeStyle = tc;
729
+ ctx.lineWidth = tw;
730
+ ctx.beginPath();
731
+ ctx.moveTo(x, y + tw / 2);
732
+ ctx.lineTo(x + width, y + tw / 2);
733
+ ctx.stroke();
734
+ }
735
+ if (rw > 0) {
736
+ ctx.strokeStyle = rc;
737
+ ctx.lineWidth = rw;
738
+ ctx.beginPath();
739
+ ctx.moveTo(x + width - rw / 2, y);
740
+ ctx.lineTo(x + width - rw / 2, y + height);
741
+ ctx.stroke();
742
+ }
743
+ if (bw > 0) {
744
+ ctx.strokeStyle = bc;
745
+ ctx.lineWidth = bw;
746
+ ctx.beginPath();
747
+ ctx.moveTo(x, y + height - bw / 2);
748
+ ctx.lineTo(x + width, y + height - bw / 2);
749
+ ctx.stroke();
750
+ }
751
+ if (lw > 0) {
752
+ ctx.strokeStyle = lc;
753
+ ctx.lineWidth = lw;
754
+ ctx.beginPath();
755
+ ctx.moveTo(x + lw / 2, y);
756
+ ctx.lineTo(x + lw / 2, y + height);
757
+ ctx.stroke();
758
+ }
759
+ }
760
+ function drawBoxShadow(ctx, x, y, width, height, boxShadow, borderRadius) {
761
+ const parts = boxShadow.match(
762
+ /(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(-?\d+(?:\.\d+)?)\s*(?:px)?(?:\s+(-?\d+(?:\.\d+)?)\s*(?:px)?)?\s+(.*)/
763
+ );
764
+ if (!parts) return;
765
+ const offsetX = parseFloat(parts[1]);
766
+ const offsetY = parseFloat(parts[2]);
767
+ const blur = parseFloat(parts[3]);
768
+ const color = parts[5].trim();
769
+ const radii = [
770
+ borderRadius.topLeft,
771
+ borderRadius.topRight,
772
+ borderRadius.bottomRight,
773
+ borderRadius.bottomLeft
774
+ ];
775
+ const margin = blur * 2 + Math.abs(offsetX) + Math.abs(offsetY);
776
+ ctx.save();
777
+ ctx.beginPath();
778
+ ctx.rect(x - margin, y - margin, width + margin * 2, height + margin * 2);
779
+ ctx.roundRect(x, y, width, height, radii);
780
+ ctx.clip("evenodd");
781
+ ctx.filter = `blur(${blur / 2}px)`;
782
+ ctx.translate(offsetX, offsetY);
783
+ ctx.fillStyle = color;
784
+ ctx.beginPath();
785
+ ctx.roundRect(x, y, width, height, radii);
786
+ ctx.fill();
787
+ ctx.restore();
788
+ }
789
+ function toNumber(v) {
790
+ if (typeof v === "number") return v;
791
+ if (v === void 0 || v === null) return 0;
792
+ const n = parseFloat(String(v));
793
+ return isNaN(n) ? 0 : n;
794
+ }
795
+
796
+ // src/jsx/draw/svg.ts
797
+ import { Path2D } from "@napi-rs/canvas";
798
+ function drawSvgContainer(ctx, node, x, y, width, height) {
799
+ ctx.save();
800
+ ctx.translate(x, y);
801
+ const viewBox = node.props.viewBox;
802
+ if (viewBox) {
803
+ const parts = viewBox.split(/[\s,]+/).map(Number);
804
+ if (parts.length === 4) {
805
+ const [vbX, vbY, vbW, vbH] = parts;
806
+ const scaleX = width / vbW;
807
+ const scaleY = height / vbH;
808
+ ctx.scale(scaleX, scaleY);
809
+ ctx.translate(-vbX, -vbY);
810
+ }
811
+ }
812
+ const inheritedFill = node.props.fill ?? "black";
813
+ const children = node.props.children;
814
+ if (children != null) {
815
+ const childArray = Array.isArray(children) ? children : [children];
816
+ for (const child of childArray) {
817
+ if (child != null && typeof child === "object") {
818
+ drawSvgChild(ctx, child, inheritedFill);
819
+ }
820
+ }
821
+ }
822
+ ctx.restore();
823
+ }
824
+ function drawSvgChild(ctx, child, inheritedFill) {
825
+ const { type, props } = child;
826
+ switch (type) {
827
+ case "path":
828
+ drawPath(ctx, props, inheritedFill);
829
+ break;
830
+ case "circle":
831
+ drawCircle(ctx, props, inheritedFill);
832
+ break;
833
+ case "rect":
834
+ drawSvgRect(ctx, props, inheritedFill);
835
+ break;
836
+ case "line":
837
+ drawLine(ctx, props);
838
+ break;
839
+ case "ellipse":
840
+ drawEllipse(ctx, props, inheritedFill);
841
+ break;
842
+ case "polygon":
843
+ drawPolygon(ctx, props, inheritedFill);
844
+ break;
845
+ case "polyline":
846
+ drawPolyline(ctx, props, inheritedFill);
847
+ break;
848
+ case "g":
849
+ drawGroup(ctx, child, inheritedFill);
850
+ break;
851
+ }
852
+ }
853
+ function drawPath(ctx, props, inheritedFill) {
854
+ const d = props.d;
855
+ if (!d) return;
856
+ const path = new Path2D(d);
857
+ applyFillAndStroke(ctx, props, path, inheritedFill);
858
+ }
859
+ function drawCircle(ctx, props, inheritedFill) {
860
+ const cx = Number(props.cx ?? 0);
861
+ const cy = Number(props.cy ?? 0);
862
+ const r = Number(props.r ?? 0);
863
+ if (r <= 0) return;
864
+ const path = new Path2D();
865
+ path.arc(cx, cy, r, 0, Math.PI * 2);
866
+ applyFillAndStroke(ctx, props, path, inheritedFill);
867
+ }
868
+ function drawSvgRect(ctx, props, inheritedFill) {
869
+ const rx = Number(props.x ?? 0);
870
+ const ry = Number(props.y ?? 0);
871
+ const w = Number(props.width ?? 0);
872
+ const h = Number(props.height ?? 0);
873
+ if (w <= 0 || h <= 0) return;
874
+ const path = new Path2D();
875
+ path.rect(rx, ry, w, h);
876
+ applyFillAndStroke(ctx, props, path, inheritedFill);
877
+ }
878
+ function drawLine(ctx, props) {
879
+ const x1 = Number(props.x1 ?? 0);
880
+ const y1 = Number(props.y1 ?? 0);
881
+ const x2 = Number(props.x2 ?? 0);
882
+ const y2 = Number(props.y2 ?? 0);
883
+ const path = new Path2D();
884
+ path.moveTo(x1, y1);
885
+ path.lineTo(x2, y2);
886
+ applyStroke(ctx, props, path);
887
+ }
888
+ function drawEllipse(ctx, props, inheritedFill) {
889
+ const cx = Number(props.cx ?? 0);
890
+ const cy = Number(props.cy ?? 0);
891
+ const rx = Number(props.rx ?? 0);
892
+ const ry = Number(props.ry ?? 0);
893
+ if (rx <= 0 || ry <= 0) return;
894
+ const path = new Path2D();
895
+ path.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
896
+ applyFillAndStroke(ctx, props, path, inheritedFill);
897
+ }
898
+ function drawPolygon(ctx, props, inheritedFill) {
899
+ const points = parsePoints(props.points);
900
+ if (points.length < 2) return;
901
+ const path = new Path2D();
902
+ path.moveTo(points[0][0], points[0][1]);
903
+ for (let i = 1; i < points.length; i++) {
904
+ path.lineTo(points[i][0], points[i][1]);
905
+ }
906
+ path.closePath();
907
+ applyFillAndStroke(ctx, props, path, inheritedFill);
908
+ }
909
+ function drawPolyline(ctx, props, inheritedFill) {
910
+ const points = parsePoints(props.points);
911
+ if (points.length < 2) return;
912
+ const path = new Path2D();
913
+ path.moveTo(points[0][0], points[0][1]);
914
+ for (let i = 1; i < points.length; i++) {
915
+ path.lineTo(points[i][0], points[i][1]);
916
+ }
917
+ applyFillAndStroke(ctx, props, path, inheritedFill);
918
+ }
919
+ function drawGroup(ctx, node, inheritedFill) {
920
+ const children = node.children ?? node.props.children;
921
+ if (children == null) return;
922
+ const groupFill = node.props.fill ?? inheritedFill;
923
+ const childArray = Array.isArray(children) ? children : [children];
924
+ for (const child of childArray) {
925
+ if (child != null && typeof child === "object") {
926
+ drawSvgChild(ctx, child, groupFill);
927
+ }
928
+ }
929
+ }
930
+ function parsePoints(value) {
931
+ if (!value) return [];
932
+ const nums = value.trim().split(/[\s,]+/).map(Number);
933
+ const result = [];
934
+ for (let i = 0; i + 1 < nums.length; i += 2) {
935
+ result.push([nums[i], nums[i + 1]]);
936
+ }
937
+ return result;
938
+ }
939
+ function applyFillAndStroke(ctx, props, path, inheritedFill) {
940
+ const fill = props.fill ?? inheritedFill;
941
+ if (fill !== "none") {
942
+ ctx.fillStyle = fill;
943
+ ctx.fill(path);
944
+ }
945
+ applyStroke(ctx, props, path);
946
+ }
947
+ function applyStroke(ctx, props, path) {
948
+ const stroke = props.stroke;
949
+ if (!stroke || stroke === "none") return;
950
+ ctx.strokeStyle = stroke;
951
+ ctx.lineWidth = Number(props.strokeWidth ?? 1);
952
+ ctx.lineCap = props.strokeLinecap ?? "butt";
953
+ ctx.lineJoin = props.strokeLinejoin ?? "miter";
954
+ ctx.stroke(path);
955
+ }
956
+
957
+ // src/jsx/draw/text.ts
958
+ import { loadImage as loadImage2 } from "@napi-rs/canvas";
959
+
960
+ // src/jsx/emoji.ts
961
+ var emojiApis = {
962
+ twemoji: (code) => `https://cdnjs.cloudflare.com/ajax/libs/twemoji/16.0.1/svg/${code.toLowerCase()}.svg`,
963
+ openmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/",
964
+ blobmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/",
965
+ noto: "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/",
966
+ fluent: (code) => `https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/${code.toLowerCase()}_color.svg`,
967
+ fluentFlat: (code) => `https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/${code.toLowerCase()}_flat.svg`
968
+ };
969
+ var U200D = String.fromCharCode(8205);
970
+ var UFE0Fg = /\uFE0F/g;
971
+ function getEmojiCode(char) {
972
+ return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, "") : char);
973
+ }
974
+ function toCodePoint(unicodeSurrogates) {
975
+ const r = [];
976
+ let c = 0, p = 0, i = 0;
977
+ while (i < unicodeSurrogates.length) {
978
+ c = unicodeSurrogates.charCodeAt(i++);
979
+ if (p) {
980
+ r.push((65536 + (p - 55296 << 10) + (c - 56320)).toString(16));
981
+ p = 0;
982
+ } else if (55296 <= c && c <= 56319) {
983
+ p = c;
984
+ } else {
985
+ r.push(c.toString(16));
986
+ }
987
+ }
988
+ return r.join("-");
989
+ }
990
+ var emojiCache = {};
991
+ async function loadEmoji(type, code) {
992
+ const key = type + ":" + code;
993
+ if (key in emojiCache) return emojiCache[key];
994
+ const api = emojiApis[type];
995
+ if (typeof api === "function") {
996
+ return emojiCache[key] = fetch(api(code)).then((r) => r.text());
997
+ }
998
+ return emojiCache[key] = fetch(`${api}${code.toUpperCase()}.svg`).then(
999
+ (r) => r.text()
1000
+ );
1001
+ }
1002
+
1003
+ // src/jsx/text/emoji-split.ts
1004
+ function splitTextIntoRuns(text, measureText2, emojiSize) {
1005
+ const runs = [];
1006
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1007
+ let currentText = "";
1008
+ let currentX = 0;
1009
+ let textStartX = 0;
1010
+ for (const { segment } of segmenter.segment(text)) {
1011
+ if (isEmojiGrapheme(segment)) {
1012
+ if (currentText) {
1013
+ const textWidth = measureText2(currentText);
1014
+ runs.push({
1015
+ kind: "text",
1016
+ text: currentText,
1017
+ x: textStartX,
1018
+ width: textWidth
1019
+ });
1020
+ currentX = textStartX + textWidth;
1021
+ currentText = "";
1022
+ }
1023
+ runs.push({
1024
+ kind: "emoji",
1025
+ char: segment,
1026
+ x: currentX,
1027
+ width: emojiSize
1028
+ });
1029
+ currentX += emojiSize;
1030
+ textStartX = currentX;
1031
+ } else {
1032
+ if (!currentText) textStartX = currentX;
1033
+ currentText += segment;
1034
+ }
1035
+ }
1036
+ if (currentText) {
1037
+ const textWidth = measureText2(currentText);
1038
+ runs.push({
1039
+ kind: "text",
1040
+ text: currentText,
1041
+ x: textStartX,
1042
+ width: textWidth
1043
+ });
1044
+ }
1045
+ return runs;
1046
+ }
1047
+ function isEmojiGrapheme(grapheme) {
1048
+ for (const char of grapheme) {
1049
+ if (isEmoji(char)) return true;
1050
+ }
1051
+ return false;
1052
+ }
1053
+
1054
+ // src/jsx/draw/text.ts
1055
+ var emojiImageCache = /* @__PURE__ */ new Map();
1056
+ function loadEmojiImage(style, char) {
1057
+ const code = getEmojiCode(char);
1058
+ const key = style + ":" + code;
1059
+ let cached = emojiImageCache.get(key);
1060
+ if (!cached) {
1061
+ cached = loadEmoji(style, code).then((svgText) => {
1062
+ if (!svgText || !svgText.includes("<svg")) return null;
1063
+ const dataUri = "data:image/svg+xml;base64," + Buffer.from(svgText).toString("base64");
1064
+ return loadImage2(dataUri);
1065
+ }).catch(() => null);
1066
+ emojiImageCache.set(key, cached);
1067
+ }
1068
+ return cached;
1069
+ }
1070
+ async function drawText(ctx, segments, offsetX, offsetY, textShadow, emojiStyle) {
1071
+ for (const seg of segments) {
1072
+ if (!seg.text) continue;
1073
+ setFont(ctx, seg.fontSize, seg.fontFamily, seg.fontWeight, seg.fontStyle);
1074
+ ctx.fillStyle = seg.color;
1075
+ const x = offsetX + seg.x;
1076
+ const y = offsetY + seg.y;
1077
+ if (emojiStyle) {
1078
+ await drawSegmentWithEmoji(ctx, seg, x, y, textShadow, emojiStyle);
1079
+ } else if (seg.letterSpacing && seg.letterSpacing !== 0) {
1080
+ drawTextWithLetterSpacing(ctx, seg.text, x, y, seg.letterSpacing);
1081
+ } else {
1082
+ if (textShadow) {
1083
+ drawTextShadow(ctx, seg.text, x, y, textShadow);
1084
+ }
1085
+ ctx.fillText(seg.text, x, y);
1086
+ }
1087
+ if (seg.textDecoration) {
1088
+ drawTextDecoration(ctx, seg, offsetX, offsetY);
1089
+ }
1090
+ }
1091
+ }
1092
+ async function drawSegmentWithEmoji(ctx, seg, x, y, textShadow, emojiStyle) {
1093
+ const runs = splitTextIntoRuns(
1094
+ seg.text,
1095
+ (text) => {
1096
+ setFont(ctx, seg.fontSize, seg.fontFamily, seg.fontWeight, seg.fontStyle);
1097
+ return ctx.measureText(text).width;
1098
+ },
1099
+ seg.fontSize
1100
+ );
1101
+ for (const run of runs) {
1102
+ if (run.kind === "text") {
1103
+ if (textShadow) {
1104
+ drawTextShadow(ctx, run.text, x + run.x, y, textShadow);
1105
+ }
1106
+ ctx.fillText(run.text, x + run.x, y);
1107
+ } else {
1108
+ const img = await loadEmojiImage(emojiStyle, run.char);
1109
+ if (img) {
1110
+ const emojiSize = seg.fontSize;
1111
+ const emojiY = y - seg.ascent + (seg.height - seg.fontSize) / 2;
1112
+ ctx.drawImage(img, x + run.x, emojiY, emojiSize, emojiSize);
1113
+ } else {
1114
+ ctx.fillText(run.char, x + run.x, y);
1115
+ }
1116
+ }
1117
+ }
1118
+ }
1119
+ function drawTextWithLetterSpacing(ctx, text, x, y, letterSpacing) {
1120
+ let currentX = x;
1121
+ for (const char of text) {
1122
+ ctx.fillText(char, currentX, y);
1123
+ const metrics = ctx.measureText(char);
1124
+ currentX += metrics.width + letterSpacing;
1125
+ }
1126
+ }
1127
+ function drawTextShadow(ctx, text, x, y, shadow) {
1128
+ const parts = shadow.match(
1129
+ /(-?\d+(?:\.\d+)?)\s*px?\s+(-?\d+(?:\.\d+)?)\s*px?\s+(-?\d+(?:\.\d+)?)\s*px?\s+(.*)/
1130
+ );
1131
+ if (!parts) return;
1132
+ ctx.save();
1133
+ ctx.shadowOffsetX = parseFloat(parts[1]);
1134
+ ctx.shadowOffsetY = parseFloat(parts[2]);
1135
+ ctx.shadowBlur = parseFloat(parts[3]);
1136
+ ctx.shadowColor = parts[4].trim();
1137
+ ctx.fillText(text, x, y);
1138
+ ctx.restore();
1139
+ }
1140
+ function drawTextDecoration(ctx, seg, offsetX, offsetY) {
1141
+ const decoration = seg.textDecoration;
1142
+ if (!decoration || decoration === "none") return;
1143
+ ctx.strokeStyle = seg.color;
1144
+ ctx.lineWidth = Math.max(1, seg.fontSize * 0.1);
1145
+ const x = offsetX + seg.x;
1146
+ const baseY = offsetY + seg.y;
1147
+ if (decoration.includes("underline")) {
1148
+ const y = baseY + seg.ascent * 0.1;
1149
+ ctx.beginPath();
1150
+ ctx.moveTo(x, y);
1151
+ ctx.lineTo(x + seg.width, y);
1152
+ ctx.stroke();
1153
+ }
1154
+ if (decoration.includes("line-through")) {
1155
+ const y = baseY - seg.fontSize * 0.3;
1156
+ ctx.beginPath();
1157
+ ctx.moveTo(x, y);
1158
+ ctx.lineTo(x + seg.width, y);
1159
+ ctx.stroke();
1160
+ }
1161
+ if (decoration.includes("overline")) {
1162
+ const y = baseY - seg.fontSize * 0.85;
1163
+ ctx.beginPath();
1164
+ ctx.moveTo(x, y);
1165
+ ctx.lineTo(x + seg.width, y);
1166
+ ctx.stroke();
1167
+ }
1168
+ }
1169
+
1170
+ // src/jsx/draw/index.ts
1171
+ async function drawNode(ctx, node, parentX, parentY, debug, emojiStyle) {
1172
+ const x = parentX + node.x;
1173
+ const y = parentY + node.y;
1174
+ const { width, height, style } = node;
1175
+ if (style.display === "none") return;
1176
+ const opacity = style.opacity ?? 1;
1177
+ if (opacity <= 0) return;
1178
+ ctx.save();
1179
+ if (opacity < 1) {
1180
+ ctx.globalAlpha *= opacity;
1181
+ }
1182
+ if (style.filter) {
1183
+ ctx.filter = style.filter;
1184
+ }
1185
+ if (style.transform) {
1186
+ applyTransform(
1187
+ ctx,
1188
+ style.transform,
1189
+ x,
1190
+ y,
1191
+ width,
1192
+ height,
1193
+ style.transformOrigin
1194
+ );
1195
+ }
1196
+ const isClipped = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden";
1197
+ if (isClipped) {
1198
+ const borderRadius = getBorderRadiusFromStyle(style);
1199
+ applyClip(ctx, x, y, width, height, borderRadius);
1200
+ }
1201
+ if (style.backgroundColor || style.borderTopWidth || style.borderRightWidth || style.borderBottomWidth || style.borderLeftWidth || style.boxShadow) {
1202
+ drawRect(ctx, x, y, width, height, style);
1203
+ }
1204
+ if (style.backgroundImage) {
1205
+ const gradient = createGradientFromCSS(
1206
+ ctx,
1207
+ style.backgroundImage,
1208
+ x,
1209
+ y,
1210
+ width,
1211
+ height
1212
+ );
1213
+ if (gradient) {
1214
+ ctx.fillStyle = gradient;
1215
+ const borderRadius = getBorderRadiusFromStyle(style);
1216
+ if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
1217
+ ctx.beginPath();
1218
+ roundedRect(
1219
+ ctx,
1220
+ x,
1221
+ y,
1222
+ width,
1223
+ height,
1224
+ borderRadius.topLeft,
1225
+ borderRadius.topRight,
1226
+ borderRadius.bottomRight,
1227
+ borderRadius.bottomLeft
1228
+ );
1229
+ ctx.fill();
1230
+ } else {
1231
+ ctx.fillRect(x, y, width, height);
1232
+ }
1233
+ } else {
1234
+ const urlMatch = style.backgroundImage.match(/url\(["']?(.*?)["']?\)/);
1235
+ if (urlMatch) {
1236
+ const borderRadius = getBorderRadiusFromStyle(style);
1237
+ const hasRadius2 = borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0;
1238
+ if (hasRadius2) {
1239
+ applyClip(ctx, x, y, width, height, borderRadius);
1240
+ }
1241
+ const image = await loadImage3(urlMatch[1]);
1242
+ const bgSize = style.backgroundSize;
1243
+ if (bgSize === "cover") {
1244
+ const r = computeCover(
1245
+ image.width,
1246
+ image.height,
1247
+ x,
1248
+ y,
1249
+ width,
1250
+ height
1251
+ );
1252
+ ctx.drawImage(image, r.sx, r.sy, r.sw, r.sh, r.dx, r.dy, r.dw, r.dh);
1253
+ } else {
1254
+ let tileW, tileH;
1255
+ if (bgSize === "contain") {
1256
+ const r = computeContain(
1257
+ image.width,
1258
+ image.height,
1259
+ 0,
1260
+ 0,
1261
+ width,
1262
+ height
1263
+ );
1264
+ tileW = r.dw;
1265
+ tileH = r.dh;
1266
+ } else if (bgSize === "100% 100%") {
1267
+ tileW = width;
1268
+ tileH = height;
1269
+ } else {
1270
+ tileW = image.width;
1271
+ tileH = image.height;
1272
+ }
1273
+ for (let ty = y; ty < y + height; ty += tileH) {
1274
+ for (let tx = x; tx < x + width; tx += tileW) {
1275
+ ctx.drawImage(image, tx, ty, tileW, tileH);
1276
+ }
1277
+ }
1278
+ }
1279
+ }
1280
+ }
1281
+ }
1282
+ if (debug) {
1283
+ ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
1284
+ ctx.lineWidth = 1;
1285
+ ctx.strokeRect(x, y, width, height);
1286
+ }
1287
+ if (node.textContent !== void 0 && node.textContent !== "") {
1288
+ const paddingTop = toNumber2(style.paddingTop);
1289
+ const paddingLeft = toNumber2(style.paddingLeft);
1290
+ const paddingRight = toNumber2(style.paddingRight);
1291
+ const borderTopW = toNumber2(style.borderTopWidth);
1292
+ const borderLeftW = toNumber2(style.borderLeftWidth);
1293
+ const borderRightW = toNumber2(style.borderRightWidth);
1294
+ const contentX = x + paddingLeft + borderLeftW;
1295
+ const contentY = y + paddingTop + borderTopW;
1296
+ const contentWidth = width - paddingLeft - paddingRight - borderLeftW - borderRightW;
1297
+ const textLayout = layoutText(
1298
+ node.textContent,
1299
+ style,
1300
+ contentWidth,
1301
+ ctx,
1302
+ !!emojiStyle
1303
+ );
1304
+ await drawText(
1305
+ ctx,
1306
+ textLayout.segments,
1307
+ contentX,
1308
+ contentY,
1309
+ style.textShadow,
1310
+ emojiStyle
1311
+ );
1312
+ }
1313
+ if (node.type === "img" && node.props.src) {
1314
+ const paddingTop = toNumber2(style.paddingTop);
1315
+ const paddingLeft = toNumber2(style.paddingLeft);
1316
+ const paddingRight = toNumber2(style.paddingRight);
1317
+ const paddingBottom = toNumber2(style.paddingBottom);
1318
+ const imgX = x + paddingLeft;
1319
+ const imgY = y + paddingTop;
1320
+ const imgW = width - paddingLeft - paddingRight;
1321
+ const imgH = height - paddingTop - paddingBottom;
1322
+ if (!isClipped) {
1323
+ const borderRadius = getBorderRadiusFromStyle(style);
1324
+ if (borderRadius.topLeft > 0 || borderRadius.topRight > 0 || borderRadius.bottomRight > 0 || borderRadius.bottomLeft > 0) {
1325
+ applyClip(ctx, imgX, imgY, imgW, imgH, borderRadius);
1326
+ }
1327
+ }
1328
+ await drawImage(
1329
+ ctx,
1330
+ node.props.src,
1331
+ imgX,
1332
+ imgY,
1333
+ imgW,
1334
+ imgH,
1335
+ style
1336
+ );
1337
+ }
1338
+ if (node.type === "svg") {
1339
+ drawSvgContainer(ctx, node, x, y, width, height);
1340
+ } else {
1341
+ for (const child of node.children) {
1342
+ await drawNode(ctx, child, x, y, debug, emojiStyle);
1343
+ }
1344
+ }
1345
+ ctx.restore();
1346
+ }
1347
+ function applyTransform(ctx, transform, x, y, width, height, transformOrigin) {
1348
+ let ox = x + width / 2;
1349
+ let oy = y + height / 2;
1350
+ if (transformOrigin) {
1351
+ const parts = transformOrigin.split(/\s+/);
1352
+ ox = resolveOrigin(parts[0], x, width);
1353
+ oy = resolveOrigin(parts[1], y, height);
1354
+ }
1355
+ ctx.translate(ox, oy);
1356
+ const funcs = transform.matchAll(/(\w+)\(([^)]+)\)/g);
1357
+ for (const [, name, args] of funcs) {
1358
+ const values = args.split(",").map((s) => s.trim());
1359
+ switch (name) {
1360
+ case "translate":
1361
+ case "translateX":
1362
+ case "translateY": {
1363
+ const tx = name === "translateY" ? 0 : parseFloat(values[0]);
1364
+ const ty = name === "translateX" ? 0 : parseFloat(values[name === "translate" ? 1 : 0] ?? "0");
1365
+ ctx.translate(tx, ty);
1366
+ break;
1367
+ }
1368
+ case "scale":
1369
+ case "scaleX":
1370
+ case "scaleY": {
1371
+ const sx = name === "scaleY" ? 1 : parseFloat(values[0]);
1372
+ const sy = name === "scaleX" ? 1 : parseFloat(values[name === "scale" ? 1 : 0] ?? String(sx));
1373
+ ctx.scale(sx, sy);
1374
+ break;
1375
+ }
1376
+ case "rotate": {
1377
+ const angle = parseAngle(values[0]);
1378
+ ctx.rotate(angle);
1379
+ break;
1380
+ }
1381
+ case "skewX": {
1382
+ const angle = parseAngle(values[0]);
1383
+ ctx.transform(1, 0, Math.tan(angle), 1, 0, 0);
1384
+ break;
1385
+ }
1386
+ case "skewY": {
1387
+ const angle = parseAngle(values[0]);
1388
+ ctx.transform(1, Math.tan(angle), 0, 1, 0, 0);
1389
+ break;
1390
+ }
1391
+ }
1392
+ }
1393
+ ctx.translate(-ox, -oy);
1394
+ }
1395
+ function resolveOrigin(value, base, size) {
1396
+ if (!value) return base + size / 2;
1397
+ if (value === "left" || value === "top") return base;
1398
+ if (value === "right" || value === "bottom") return base + size;
1399
+ if (value === "center") return base + size / 2;
1400
+ if (value.endsWith("%")) return base + parseFloat(value) / 100 * size;
1401
+ return base + parseFloat(value);
1402
+ }
1403
+ function parseAngle(value) {
1404
+ if (value.endsWith("deg")) return parseFloat(value) * Math.PI / 180;
1405
+ if (value.endsWith("rad")) return parseFloat(value);
1406
+ if (value.endsWith("turn")) return parseFloat(value) * 2 * Math.PI;
1407
+ return parseFloat(value);
1408
+ }
1409
+ function toNumber2(v) {
1410
+ if (typeof v === "number") return v;
1411
+ if (v === void 0 || v === null) return 0;
1412
+ const n = parseFloat(String(v));
1413
+ return isNaN(n) ? 0 : n;
1414
+ }
1415
+
1416
+ // src/jsx/font.ts
1417
+ import { GlobalFonts } from "@napi-rs/canvas";
1418
+ var registeredFonts = /* @__PURE__ */ new Set();
1419
+ function registerFont(font) {
1420
+ const key = `${font.name}:${font.weight}:${font.style}`;
1421
+ if (registeredFonts.has(key)) return;
1422
+ const buffer = Buffer.isBuffer(font.data) ? font.data : Buffer.from(font.data);
1423
+ GlobalFonts.register(buffer, font.name);
1424
+ registeredFonts.add(key);
1425
+ }
1426
+ function registerFontFromPath(path, nameAlias) {
1427
+ GlobalFonts.registerFromPath(path, nameAlias ?? "");
1428
+ }
1429
+ function registeredFamilies() {
1430
+ return GlobalFonts.families.map((f) => f.family);
1431
+ }
1432
+ function ensureFontsRegistered(fonts) {
1433
+ for (const font of fonts) {
1434
+ registerFont(font);
1435
+ }
1436
+ }
1437
+
1438
+ // src/jsx/style/expand.ts
1439
+ var SIDES = ["Top", "Right", "Bottom", "Left"];
1440
+ function parseSides(value) {
1441
+ const parts = value.toString().split(/\s+/).filter(Boolean);
1442
+ switch (parts.length) {
1443
+ case 1:
1444
+ return [parts[0], parts[0], parts[0], parts[0]];
1445
+ case 2:
1446
+ return [parts[0], parts[1], parts[0], parts[1]];
1447
+ case 3:
1448
+ return [parts[0], parts[1], parts[2], parts[1]];
1449
+ default:
1450
+ return [parts[0], parts[1], parts[2], parts[3]];
1451
+ }
1452
+ }
1453
+ function parseValue(v) {
1454
+ if (v === void 0 || v === null) return void 0;
1455
+ const s = String(v);
1456
+ if (s === "auto") return "auto";
1457
+ const n = parseFloat(s);
1458
+ if (!isNaN(n)) return n;
1459
+ return s;
1460
+ }
1461
+ function expandStyle(raw) {
1462
+ const style = { ...raw };
1463
+ if (style.margin !== void 0) {
1464
+ const sides = parseSides(String(style.margin));
1465
+ for (let i = 0; i < 4; i++) {
1466
+ const key = `margin${SIDES[i]}`;
1467
+ if (style[key] === void 0) style[key] = parseValue(sides[i]);
1468
+ }
1469
+ delete style.margin;
1470
+ }
1471
+ if (style.padding !== void 0) {
1472
+ const sides = parseSides(String(style.padding));
1473
+ for (let i = 0; i < 4; i++) {
1474
+ const key = `padding${SIDES[i]}`;
1475
+ if (style[key] === void 0) style[key] = parseValue(sides[i]);
1476
+ }
1477
+ delete style.padding;
1478
+ }
1479
+ if (style.borderRadius !== void 0) {
1480
+ const sides = parseSides(String(style.borderRadius));
1481
+ const corners = [
1482
+ "borderTopLeftRadius",
1483
+ "borderTopRightRadius",
1484
+ "borderBottomRightRadius",
1485
+ "borderBottomLeftRadius"
1486
+ ];
1487
+ for (let i = 0; i < 4; i++) {
1488
+ if (style[corners[i]] === void 0)
1489
+ style[corners[i]] = parseValue(sides[i]);
1490
+ }
1491
+ delete style.borderRadius;
1492
+ }
1493
+ if (style.borderWidth !== void 0) {
1494
+ const sides = parseSides(String(style.borderWidth));
1495
+ for (let i = 0; i < 4; i++) {
1496
+ const key = `border${SIDES[i]}Width`;
1497
+ if (style[key] === void 0) style[key] = parseValue(sides[i]);
1498
+ }
1499
+ delete style.borderWidth;
1500
+ }
1501
+ if (style.borderColor !== void 0) {
1502
+ const val = style.borderColor;
1503
+ for (const side of SIDES) {
1504
+ const key = `border${side}Color`;
1505
+ if (style[key] === void 0) style[key] = val;
1506
+ }
1507
+ delete style.borderColor;
1508
+ }
1509
+ if (style.borderStyle !== void 0) {
1510
+ const val = style.borderStyle;
1511
+ for (const side of SIDES) {
1512
+ const key = `border${side}Style`;
1513
+ if (style[key] === void 0) style[key] = val;
1514
+ }
1515
+ delete style.borderStyle;
1516
+ }
1517
+ if (style.border !== void 0) {
1518
+ const parts = String(style.border).split(/\s+/);
1519
+ const width = parseValue(parts[0]);
1520
+ const borderStyle = parts[1] ?? "solid";
1521
+ const color = parts[2] ?? "black";
1522
+ for (const side of SIDES) {
1523
+ if (style[`border${side}Width`] === void 0)
1524
+ style[`border${side}Width`] = width;
1525
+ if (style[`border${side}Style`] === void 0)
1526
+ style[`border${side}Style`] = borderStyle;
1527
+ if (style[`border${side}Color`] === void 0)
1528
+ style[`border${side}Color`] = color;
1529
+ }
1530
+ delete style.border;
1531
+ }
1532
+ if (style.flex !== void 0) {
1533
+ const val = String(style.flex);
1534
+ const parts = val.split(/\s+/);
1535
+ if (parts.length === 1) {
1536
+ const n = parseFloat(parts[0]);
1537
+ if (!isNaN(n)) {
1538
+ if (style.flexGrow === void 0) style.flexGrow = n;
1539
+ if (style.flexShrink === void 0) style.flexShrink = 1;
1540
+ if (style.flexBasis === void 0) style.flexBasis = 0;
1541
+ }
1542
+ } else if (parts.length === 2) {
1543
+ if (style.flexGrow === void 0) style.flexGrow = parseFloat(parts[0]);
1544
+ if (style.flexShrink === void 0)
1545
+ style.flexShrink = parseFloat(parts[1]);
1546
+ } else if (parts.length >= 3) {
1547
+ if (style.flexGrow === void 0) style.flexGrow = parseFloat(parts[0]);
1548
+ if (style.flexShrink === void 0)
1549
+ style.flexShrink = parseFloat(parts[1]);
1550
+ if (style.flexBasis === void 0) style.flexBasis = parseValue(parts[2]);
1551
+ }
1552
+ delete style.flex;
1553
+ }
1554
+ if (style.gap !== void 0) {
1555
+ const sides = parseSides(String(style.gap));
1556
+ if (style.rowGap === void 0) style.rowGap = parseValue(sides[0]);
1557
+ if (style.columnGap === void 0) style.columnGap = parseValue(sides[1]);
1558
+ delete style.gap;
1559
+ }
1560
+ if (style.background !== void 0) {
1561
+ const bg = String(style.background);
1562
+ if (bg.includes("gradient(") || bg.includes("url(")) {
1563
+ if (style.backgroundImage === void 0) style.backgroundImage = bg;
1564
+ } else {
1565
+ if (style.backgroundColor === void 0) style.backgroundColor = bg;
1566
+ }
1567
+ delete style.background;
1568
+ }
1569
+ if (style.overflow !== void 0) {
1570
+ if (style.overflowX === void 0) style.overflowX = style.overflow;
1571
+ if (style.overflowY === void 0) style.overflowY = style.overflow;
1572
+ }
1573
+ if (typeof style.fontFamily === "string") {
1574
+ style.fontFamily = style.fontFamily.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean).join(", ");
1575
+ }
1576
+ return style;
1577
+ }
1578
+
1579
+ // src/jsx/style/compute.ts
1580
+ var DEFAULT_STYLE = {
1581
+ display: "flex",
1582
+ flexDirection: "row",
1583
+ flexWrap: "nowrap",
1584
+ flexGrow: 0,
1585
+ flexShrink: 0,
1586
+ alignItems: "stretch",
1587
+ justifyContent: "flex-start",
1588
+ position: "relative",
1589
+ fontSize: 16,
1590
+ fontWeight: 400,
1591
+ fontStyle: "normal",
1592
+ color: "black",
1593
+ lineHeight: "normal",
1594
+ textAlign: "left",
1595
+ whiteSpace: "normal",
1596
+ wordBreak: "normal",
1597
+ textOverflow: "clip",
1598
+ opacity: 1,
1599
+ overflow: "visible"
1600
+ };
1601
+ var INHERITABLE_PROPS = [
1602
+ "color",
1603
+ "fontSize",
1604
+ "fontFamily",
1605
+ "fontWeight",
1606
+ "fontStyle",
1607
+ "textAlign",
1608
+ "textTransform",
1609
+ "textDecoration",
1610
+ "lineHeight",
1611
+ "letterSpacing",
1612
+ "whiteSpace",
1613
+ "wordBreak",
1614
+ "textOverflow"
1615
+ ];
1616
+ function resolveStyle(rawStyle, parentStyle) {
1617
+ const style = { ...rawStyle };
1618
+ for (const prop of INHERITABLE_PROPS) {
1619
+ if (style[prop] === void 0 && parentStyle[prop] !== void 0) {
1620
+ style[prop] = parentStyle[prop];
1621
+ }
1622
+ }
1623
+ if (typeof style.fontSize === "string") {
1624
+ const parsed = parseFloat(style.fontSize);
1625
+ style.fontSize = isNaN(parsed) ? parentStyle.fontSize : parsed;
1626
+ }
1627
+ if (typeof style.lineHeight === "string") {
1628
+ const str = style.lineHeight;
1629
+ if (str.endsWith("%")) {
1630
+ style.lineHeight = parseFloat(str) / 100;
1631
+ } else {
1632
+ const parsed = parseFloat(str);
1633
+ if (!isNaN(parsed)) {
1634
+ style.lineHeight = parsed;
1635
+ }
1636
+ }
1637
+ }
1638
+ if (typeof style.letterSpacing === "string") {
1639
+ const parsed = parseFloat(style.letterSpacing);
1640
+ if (!isNaN(parsed)) {
1641
+ style.letterSpacing = parsed;
1642
+ }
1643
+ }
1644
+ return style;
1645
+ }
1646
+
1647
+ // src/jsx/yoga.ts
1648
+ import Yoga, {
1649
+ Align,
1650
+ Display,
1651
+ Edge,
1652
+ FlexDirection,
1653
+ Gutter,
1654
+ Justify,
1655
+ Overflow,
1656
+ PositionType,
1657
+ Wrap
1658
+ } from "yoga-layout";
1659
+ function createYogaNode() {
1660
+ return Yoga.Node.create();
1661
+ }
1662
+ function freeYogaNode(node) {
1663
+ node.freeRecursive();
1664
+ }
1665
+
1666
+ // src/jsx/style/properties.ts
1667
+ function applyStylesToYoga(node, style) {
1668
+ if (style.display === "none") {
1669
+ node.setDisplay(Display.None);
1670
+ } else {
1671
+ node.setDisplay(Display.Flex);
1672
+ }
1673
+ {
1674
+ const map = {
1675
+ row: FlexDirection.Row,
1676
+ "row-reverse": FlexDirection.RowReverse,
1677
+ column: FlexDirection.Column,
1678
+ "column-reverse": FlexDirection.ColumnReverse
1679
+ };
1680
+ node.setFlexDirection(
1681
+ map[style.flexDirection ?? "row"] ?? FlexDirection.Row
1682
+ );
1683
+ }
1684
+ if (style.justifyContent) {
1685
+ const map = {
1686
+ "flex-start": Justify.FlexStart,
1687
+ "flex-end": Justify.FlexEnd,
1688
+ center: Justify.Center,
1689
+ "space-between": Justify.SpaceBetween,
1690
+ "space-around": Justify.SpaceAround,
1691
+ "space-evenly": Justify.SpaceEvenly
1692
+ };
1693
+ const jc = map[style.justifyContent];
1694
+ if (jc !== void 0) node.setJustifyContent(jc);
1695
+ }
1696
+ if (style.alignItems) {
1697
+ const map = {
1698
+ "flex-start": Align.FlexStart,
1699
+ "flex-end": Align.FlexEnd,
1700
+ center: Align.Center,
1701
+ stretch: Align.Stretch,
1702
+ baseline: Align.Baseline
1703
+ };
1704
+ const ai = map[style.alignItems];
1705
+ if (ai !== void 0) node.setAlignItems(ai);
1706
+ }
1707
+ if (style.alignSelf) {
1708
+ const map = {
1709
+ auto: Align.Auto,
1710
+ "flex-start": Align.FlexStart,
1711
+ "flex-end": Align.FlexEnd,
1712
+ center: Align.Center,
1713
+ stretch: Align.Stretch,
1714
+ baseline: Align.Baseline
1715
+ };
1716
+ const as_ = map[style.alignSelf];
1717
+ if (as_ !== void 0) node.setAlignSelf(as_);
1718
+ }
1719
+ if (style.alignContent) {
1720
+ const map = {
1721
+ "flex-start": Align.FlexStart,
1722
+ "flex-end": Align.FlexEnd,
1723
+ center: Align.Center,
1724
+ stretch: Align.Stretch,
1725
+ "space-between": Align.SpaceBetween,
1726
+ "space-around": Align.SpaceAround
1727
+ };
1728
+ const ac = map[style.alignContent];
1729
+ if (ac !== void 0) node.setAlignContent(ac);
1730
+ }
1731
+ if (style.flexWrap) {
1732
+ const map = {
1733
+ nowrap: Wrap.NoWrap,
1734
+ wrap: Wrap.Wrap,
1735
+ "wrap-reverse": Wrap.WrapReverse
1736
+ };
1737
+ const fw = map[style.flexWrap];
1738
+ if (fw !== void 0) node.setFlexWrap(fw);
1739
+ }
1740
+ if (style.flexGrow !== void 0) node.setFlexGrow(style.flexGrow);
1741
+ node.setFlexShrink(style.flexShrink ?? 0);
1742
+ if (style.flexBasis !== void 0) {
1743
+ if (typeof style.flexBasis === "number") {
1744
+ node.setFlexBasis(style.flexBasis);
1745
+ } else if (String(style.flexBasis).endsWith("%")) {
1746
+ node.setFlexBasis(String(style.flexBasis));
1747
+ } else if (style.flexBasis === "auto") {
1748
+ node.setFlexBasis("auto");
1749
+ } else {
1750
+ const n = parseFloat(String(style.flexBasis));
1751
+ if (!isNaN(n)) node.setFlexBasis(n);
1752
+ }
1753
+ }
1754
+ applyDimension(node, "setWidth", style.width);
1755
+ applyDimension(node, "setHeight", style.height);
1756
+ applyDimension(node, "setMinWidth", style.minWidth);
1757
+ applyDimension(node, "setMinHeight", style.minHeight);
1758
+ applyDimension(node, "setMaxWidth", style.maxWidth);
1759
+ applyDimension(node, "setMaxHeight", style.maxHeight);
1760
+ if (style.position === "absolute") {
1761
+ node.setPositionType(PositionType.Absolute);
1762
+ } else {
1763
+ node.setPositionType(PositionType.Relative);
1764
+ }
1765
+ applyEdgeValue(node, "setPosition", Edge.Top, style.top);
1766
+ applyEdgeValue(node, "setPosition", Edge.Right, style.right);
1767
+ applyEdgeValue(node, "setPosition", Edge.Bottom, style.bottom);
1768
+ applyEdgeValue(node, "setPosition", Edge.Left, style.left);
1769
+ applyEdgeValue(node, "setMargin", Edge.Top, style.marginTop);
1770
+ applyEdgeValue(node, "setMargin", Edge.Right, style.marginRight);
1771
+ applyEdgeValue(node, "setMargin", Edge.Bottom, style.marginBottom);
1772
+ applyEdgeValue(node, "setMargin", Edge.Left, style.marginLeft);
1773
+ applyEdgeValue(node, "setPadding", Edge.Top, style.paddingTop);
1774
+ applyEdgeValue(node, "setPadding", Edge.Right, style.paddingRight);
1775
+ applyEdgeValue(node, "setPadding", Edge.Bottom, style.paddingBottom);
1776
+ applyEdgeValue(node, "setPadding", Edge.Left, style.paddingLeft);
1777
+ if (style.borderTopWidth !== void 0)
1778
+ node.setBorder(Edge.Top, style.borderTopWidth);
1779
+ if (style.borderRightWidth !== void 0)
1780
+ node.setBorder(Edge.Right, style.borderRightWidth);
1781
+ if (style.borderBottomWidth !== void 0)
1782
+ node.setBorder(Edge.Bottom, style.borderBottomWidth);
1783
+ if (style.borderLeftWidth !== void 0)
1784
+ node.setBorder(Edge.Left, style.borderLeftWidth);
1785
+ if (style.rowGap !== void 0) node.setGap(Gutter.Row, style.rowGap);
1786
+ if (style.columnGap !== void 0)
1787
+ node.setGap(Gutter.Column, style.columnGap);
1788
+ if (style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden") {
1789
+ node.setOverflow(Overflow.Hidden);
1790
+ } else {
1791
+ node.setOverflow(Overflow.Visible);
1792
+ }
1793
+ }
1794
+ function applyDimension(node, setter, value) {
1795
+ if (value === void 0) return;
1796
+ if (value === "auto") {
1797
+ if (setter === "setWidth" || setter === "setHeight") {
1798
+ node[setter]("auto");
1799
+ }
1800
+ return;
1801
+ }
1802
+ if (typeof value === "number") {
1803
+ node[setter](value);
1804
+ return;
1805
+ }
1806
+ const s = String(value);
1807
+ if (s.endsWith("%")) {
1808
+ node[setter](s);
1809
+ } else {
1810
+ const n = parseFloat(s);
1811
+ if (!isNaN(n)) node[setter](n);
1812
+ }
1813
+ }
1814
+ function applyEdgeValue(node, setter, edge, value) {
1815
+ if (value === void 0) return;
1816
+ if (value === "auto" && setter === "setMargin") {
1817
+ node.setMargin(edge, "auto");
1818
+ return;
1819
+ }
1820
+ if (typeof value === "number") {
1821
+ node[setter](edge, value);
1822
+ return;
1823
+ }
1824
+ const s = String(value);
1825
+ if (s.endsWith("%")) {
1826
+ node[setter](edge, s);
1827
+ } else {
1828
+ const n = parseFloat(s);
1829
+ if (!isNaN(n)) node[setter](edge, n);
1830
+ }
1831
+ }
1832
+
1833
+ // src/jsx/layout.ts
1834
+ function buildLayoutTree(element, containerWidth, containerHeight, ctx, emojiEnabled) {
1835
+ const rootYogaNode = createYogaNode();
1836
+ const rootNode = buildNode(
1837
+ element,
1838
+ DEFAULT_STYLE,
1839
+ rootYogaNode,
1840
+ ctx,
1841
+ emojiEnabled
1842
+ );
1843
+ rootYogaNode.setWidth(containerWidth);
1844
+ rootYogaNode.setHeight(containerHeight);
1845
+ rootYogaNode.calculateLayout(containerWidth, containerHeight);
1846
+ const layoutTree = extractLayout(rootNode, rootYogaNode);
1847
+ freeYogaNode(rootYogaNode);
1848
+ return layoutTree;
1849
+ }
1850
+ function buildNode(element, parentStyle, yogaNode, ctx, emojiEnabled) {
1851
+ if (element === null || element === void 0 || typeof element === "boolean") {
1852
+ return {
1853
+ type: "empty",
1854
+ style: parentStyle,
1855
+ children: [],
1856
+ props: {},
1857
+ yogaNode
1858
+ };
1859
+ }
1860
+ if (typeof element === "string" || typeof element === "number") {
1861
+ const text = String(element);
1862
+ const style2 = resolveStyle(void 0, parentStyle);
1863
+ const measureFunc = createTextMeasureFunc(text, style2, ctx, emojiEnabled);
1864
+ yogaNode.setMeasureFunc(measureFunc);
1865
+ return {
1866
+ type: "text",
1867
+ style: style2,
1868
+ children: [],
1869
+ textContent: text,
1870
+ props: {},
1871
+ yogaNode
1872
+ };
1873
+ }
1874
+ if (Array.isArray(element)) {
1875
+ const style2 = resolveStyle(void 0, parentStyle);
1876
+ const children2 = [];
1877
+ for (let i = 0; i < element.length; i++) {
1878
+ const child = element[i];
1879
+ if (child === null || child === void 0 || typeof child === "boolean")
1880
+ continue;
1881
+ const childYogaNode = createYogaNode();
1882
+ yogaNode.insertChild(childYogaNode, children2.length);
1883
+ children2.push(buildNode(child, style2, childYogaNode, ctx, emojiEnabled));
1884
+ }
1885
+ return {
1886
+ type: "div",
1887
+ style: style2,
1888
+ children: children2,
1889
+ props: {},
1890
+ yogaNode
1891
+ };
1892
+ }
1893
+ const el = element;
1894
+ const type = el.type;
1895
+ if (typeof type === "function") {
1896
+ const rendered = type(
1897
+ el.props ?? {}
1898
+ );
1899
+ return buildNode(rendered, parentStyle, yogaNode, ctx, emojiEnabled);
1900
+ }
1901
+ const props = el.props ?? {};
1902
+ const rawStyle = props.style ?? {};
1903
+ const expanded = expandStyle(rawStyle);
1904
+ const style = resolveStyle(expanded, parentStyle);
1905
+ const tagName = String(type);
1906
+ if (tagName === "svg") {
1907
+ if (props.width != null && style.width === void 0)
1908
+ style.width = Number(props.width);
1909
+ if (props.height != null && style.height === void 0)
1910
+ style.height = Number(props.height);
1911
+ const viewBox = props.viewBox;
1912
+ if (viewBox) {
1913
+ const parts = viewBox.split(/[\s,]+/).map(Number);
1914
+ if (parts.length === 4) {
1915
+ const [, , vbW, vbH] = parts;
1916
+ if (vbW > 0 && vbH > 0) {
1917
+ const w = typeof style.width === "number" ? style.width : void 0;
1918
+ const h = typeof style.height === "number" ? style.height : void 0;
1919
+ if (w !== void 0 && h === void 0) {
1920
+ style.height = w * (vbH / vbW);
1921
+ } else if (h !== void 0 && w === void 0) {
1922
+ style.width = h * (vbW / vbH);
1923
+ } else if (w === void 0 && h === void 0) {
1924
+ style.width = vbW;
1925
+ style.height = vbH;
1926
+ }
1927
+ }
1928
+ }
1929
+ }
1930
+ }
1931
+ applyStylesToYoga(yogaNode, style);
1932
+ if (tagName === "svg") {
1933
+ return {
1934
+ type: tagName,
1935
+ style,
1936
+ children: [],
1937
+ props,
1938
+ yogaNode
1939
+ };
1940
+ }
1941
+ const textContent = extractTextContent(props.children);
1942
+ if (textContent !== void 0 && !hasElementChildren(props.children)) {
1943
+ const childStyle = resolveStyle(void 0, style);
1944
+ const childYogaNode = createYogaNode();
1945
+ const measureFunc = createTextMeasureFunc(
1946
+ textContent,
1947
+ childStyle,
1948
+ ctx,
1949
+ emojiEnabled
1950
+ );
1951
+ childYogaNode.setMeasureFunc(measureFunc);
1952
+ yogaNode.insertChild(childYogaNode, 0);
1953
+ return {
1954
+ type: tagName,
1955
+ style,
1956
+ children: [
1957
+ {
1958
+ type: "text",
1959
+ style: childStyle,
1960
+ children: [],
1961
+ textContent,
1962
+ props: {},
1963
+ yogaNode: childYogaNode
1964
+ }
1965
+ ],
1966
+ props,
1967
+ yogaNode
1968
+ };
1969
+ }
1970
+ const children = [];
1971
+ const rawChildren = props.children;
1972
+ if (rawChildren !== void 0 && rawChildren !== null) {
1973
+ const childArray = Array.isArray(rawChildren) ? rawChildren : [rawChildren];
1974
+ for (const child of childArray) {
1975
+ if (child === null || child === void 0 || typeof child === "boolean")
1976
+ continue;
1977
+ const childYogaNode = createYogaNode();
1978
+ yogaNode.insertChild(childYogaNode, children.length);
1979
+ children.push(buildNode(child, style, childYogaNode, ctx, emojiEnabled));
1980
+ }
1981
+ }
1982
+ return {
1983
+ type: tagName,
1984
+ style,
1985
+ children,
1986
+ props,
1987
+ yogaNode
1988
+ };
1989
+ }
1990
+ function extractLayout(node, yogaNode) {
1991
+ const layout = yogaNode.getComputedLayout();
1992
+ return {
1993
+ type: node.type,
1994
+ style: node.style,
1995
+ children: node.children.map((child, i) => {
1996
+ const childYoga = yogaNode.getChild(i);
1997
+ return extractLayout(child, childYoga);
1998
+ }),
1999
+ textContent: node.textContent,
2000
+ props: node.props,
2001
+ x: layout.left,
2002
+ y: layout.top,
2003
+ width: layout.width,
2004
+ height: layout.height
2005
+ };
2006
+ }
2007
+ function extractTextContent(children) {
2008
+ if (children === void 0 || children === null) return void 0;
2009
+ if (typeof children === "string") return children;
2010
+ if (typeof children === "number") return String(children);
2011
+ if (Array.isArray(children)) {
2012
+ const parts = [];
2013
+ for (const child of children) {
2014
+ if (typeof child === "string") {
2015
+ parts.push(child);
2016
+ } else if (typeof child === "number") {
2017
+ parts.push(String(child));
2018
+ } else if (child !== null && child !== void 0 && typeof child !== "boolean") {
2019
+ return void 0;
2020
+ }
2021
+ }
2022
+ return parts.join("");
2023
+ }
2024
+ return void 0;
2025
+ }
2026
+ function hasElementChildren(children) {
2027
+ if (children === void 0 || children === null || typeof children === "boolean")
2028
+ return false;
2029
+ if (typeof children === "string" || typeof children === "number")
2030
+ return false;
2031
+ if (Array.isArray(children)) {
2032
+ return children.some(
2033
+ (child) => child !== null && child !== void 0 && typeof child !== "boolean" && typeof child !== "string" && typeof child !== "number"
2034
+ );
2035
+ }
2036
+ return typeof children === "object";
2037
+ }
2038
+
2039
+ // src/jsx/index.ts
2040
+ async function renderReactElement(ctx, element, options) {
2041
+ ensureFontsRegistered(options.fonts);
2042
+ const width = ctx.canvas.width;
2043
+ const height = ctx.canvas.height;
2044
+ const emojiStyle = options.emoji === "none" ? void 0 : options.emoji ?? "twemoji";
2045
+ const layoutTree = buildLayoutTree(element, width, height, ctx, !!emojiStyle);
2046
+ await drawNode(ctx, layoutTree, 0, 0, options.debug ?? false, emojiStyle);
2047
+ }
2048
+
2049
+ // src/index.ts
2050
+ function createCanvas2(width, height) {
2051
+ const canvas = _createCanvas(width, height);
2052
+ const origEncode = canvas.encode.bind(canvas);
2053
+ canvas.encode = (async (...args) => Buffer.from(await origEncode(...args)));
2054
+ return canvas;
2055
+ }
2056
+ export {
2057
+ Canvas,
2058
+ GlobalFonts2 as GlobalFonts,
2059
+ Image,
2060
+ LottieAnimation,
2061
+ createCanvas2 as createCanvas,
2062
+ loadImage4 as loadImage,
2063
+ loadLottie,
2064
+ registerFont,
2065
+ registerFontFromPath,
2066
+ registeredFamilies,
2067
+ renderLottieFrame,
2068
+ renderReactElement
2069
+ };
2070
+ //# sourceMappingURL=index.js.map