embedded-react 0.3.0 → 0.4.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/aot/compile.mjs +2407 -697
- package/aot/screenshot-smoke.mjs +34 -17
- package/aot/style-map.mjs +156 -80
- package/assets/bake-font.mjs +45 -21
- package/assets/bake-image.mjs +7 -5
- package/assets/bake-svg.mjs +563 -0
- package/assets/build-builtin-font.mjs +25 -12
- package/assets/emit-c.mjs +52 -20
- package/assets/emit-container.mjs +5 -3
- package/assets/emit-pack.mjs +8 -2
- package/assets/index.mjs +25 -16
- package/assets/rasterize.mjs +45 -11
- package/assets/svg-loader.mjs +81 -0
- package/build.mjs +43 -20
- package/cli.mjs +134 -52
- package/pack-container.mjs +84 -35
- package/package.json +8 -3
- package/persist-transform.mjs +23 -9
- package/qjsc-wasm.mjs +19 -8
- package/sim/embedded-react.wasm +0 -0
- package/sim-server.mjs +160 -48
- package/src/embedded-react/Animated.js +51 -36
- package/src/embedded-react/AppRegistry.js +4 -4
- package/src/embedded-react/Easing.js +1 -1
- package/src/embedded-react/LayoutAnimation.js +13 -6
- package/src/embedded-react/StyleSheet.js +1 -1
- package/src/embedded-react/imperative.js +19 -7
- package/src/embedded-react/index.js +8 -8
- package/src/embedded-react/layout-anim-config.js +13 -9
- package/src/embedded-react/split-style.js +6 -6
- package/src/embedded-react/svg-ops.js +369 -41
- package/src/embedded-react/usePersistentState.js +3 -3
- package/src/host-config.js +137 -18
- package/src/native-ui.js +3 -1
- package/src/props.js +22 -10
- package/src/renderer.js +3 -3
|
@@ -22,16 +22,94 @@
|
|
|
22
22
|
// shapes to path ops, bakes viewBox + <G> translate/scale into coordinates, and resolves inherited
|
|
23
23
|
// paint. The opcodes below MUST stay in sync with er_scene.h.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
25
|
+
// Op-tape vocabulary — exported so the build-time SVG baker (assets/bake-svg.mjs) can reuse it without
|
|
26
|
+
// duplicating the encoding (and without pulling its SVG-parser dependency into this runtime module).
|
|
27
|
+
export const VOP_SHAPE = 0;
|
|
28
|
+
export const VOP_MOVE = 1;
|
|
29
|
+
export const VOP_LINE = 2;
|
|
30
|
+
export const VOP_QUAD = 3;
|
|
31
|
+
export const VOP_CUBIC = 4;
|
|
32
|
+
export const VOP_ARC = 5;
|
|
33
|
+
export const VOP_CLOSE = 6;
|
|
34
|
+
|
|
35
|
+
export const CAP = {butt: 0, round: 1, square: 2};
|
|
36
|
+
export const JOIN = {miter: 0, round: 1, bevel: 2};
|
|
37
|
+
|
|
38
|
+
// Paint record width in the flat paint table: [fill, stroke, strokeWidth, miter, cap, join, fillRule,
|
|
39
|
+
// fillGrad, strokeGrad]. fillGrad/strokeGrad are 1-based indices into the gradient table (0 = solid). MUST
|
|
40
|
+
// match the bridge's VEC_PAINT_STRIDE (native_ui_bridge.c) and the AOT's paint emission.
|
|
41
|
+
export const PAINT_STRIDE = 9;
|
|
42
|
+
|
|
43
|
+
// Gradient table encoding — a gradient descriptor is { type, stops: [{color, offset}], ax, ay, bx, by, r }.
|
|
44
|
+
// type 1 = linear (axis (ax,ay)->(bx,by)), 2 = radial (centre (ax,ay), radius r). The flat float record is
|
|
45
|
+
// [type, stopCount, (color, offset) × GRAD_MAX_STOPS, ax, ay, bx, by, r]; MUST match the bridge's
|
|
46
|
+
// VEC_GRAD_STRIDE and the engine's ER_VGRAD_MAX_STOPS.
|
|
47
|
+
export const GRAD_MAX_STOPS = 8; // MUST match the engine's ER_VGRAD_MAX_STOPS (er_scene.h)
|
|
48
|
+
export const GRAD_STRIDE = 2 + GRAD_MAX_STOPS * 2 + 5; // 23
|
|
49
|
+
export const GRAD_LINEAR = 1;
|
|
50
|
+
export const GRAD_RADIAL = 2;
|
|
51
|
+
export const GRAD_CONIC = 3;
|
|
52
|
+
|
|
53
|
+
/** Linearly interpolates two straight-alpha ARGB8888 colors, per channel. */
|
|
54
|
+
function lerpArgb(c0, c1, t) {
|
|
55
|
+
const lp = sh => {
|
|
56
|
+
const a = (c0 >>> sh) & 0xff;
|
|
57
|
+
const b = (c1 >>> sh) & 0xff;
|
|
58
|
+
return Math.round(a + (b - a) * t) & 0xff;
|
|
59
|
+
};
|
|
60
|
+
return ((lp(24) << 24) | (lp(16) << 16) | (lp(8) << 8) | lp(0)) >>> 0;
|
|
61
|
+
}
|
|
32
62
|
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
/** Gradient color at position t (stops ascending by offset; clamps to the endpoint colors). */
|
|
64
|
+
function evalStopsAt(stops, t) {
|
|
65
|
+
if (!stops.length) return 0;
|
|
66
|
+
if (t <= stops[0].offset) return stops[0].color >>> 0;
|
|
67
|
+
const last = stops[stops.length - 1];
|
|
68
|
+
if (t >= last.offset) return last.color >>> 0;
|
|
69
|
+
for (let i = 0; i < stops.length - 1; i++) {
|
|
70
|
+
const a = stops[i];
|
|
71
|
+
const b = stops[i + 1];
|
|
72
|
+
if (t <= b.offset) {
|
|
73
|
+
const span = b.offset - a.offset;
|
|
74
|
+
return lerpArgb(
|
|
75
|
+
a.color >>> 0,
|
|
76
|
+
b.color >>> 0,
|
|
77
|
+
span > 0 ? (t - a.offset) / span : 0,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return last.color >>> 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Reduces a stop list to at most `max` entries, resampling evenly across the stop range when it's over. */
|
|
85
|
+
function capStops(stops, max) {
|
|
86
|
+
if (stops.length <= max) return stops;
|
|
87
|
+
const lo = stops[0].offset;
|
|
88
|
+
const hi = stops[stops.length - 1].offset;
|
|
89
|
+
const out = [];
|
|
90
|
+
for (let i = 0; i < max; i++) {
|
|
91
|
+
const t = max === 1 ? lo : lo + (hi - lo) * (i / (max - 1));
|
|
92
|
+
out.push({color: evalStopsAt(stops, t), offset: t});
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Encodes gradient descriptors into the flat float array NativeUI.setVectorOps takes (GRAD_STRIDE/gradient).
|
|
98
|
+
* A gradient with more than GRAD_MAX_STOPS stops is resampled down (not truncated) so it still ramps right. */
|
|
99
|
+
export function encodeVectorGradients(grads) {
|
|
100
|
+
const out = [];
|
|
101
|
+
if (!grads) return out;
|
|
102
|
+
for (const g of grads) {
|
|
103
|
+
const stops = capStops(g.stops || [], GRAD_MAX_STOPS);
|
|
104
|
+
out.push(g.type, stops.length);
|
|
105
|
+
for (let s = 0; s < GRAD_MAX_STOPS; s++) {
|
|
106
|
+
const st = stops[s];
|
|
107
|
+
out.push(st ? st.color >>> 0 : 0, st ? st.offset : 0);
|
|
108
|
+
}
|
|
109
|
+
out.push(g.ax || 0, g.ay || 0, g.bx || 0, g.by || 0, g.r || 0);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
35
113
|
|
|
36
114
|
// Minimal named-color set (extend as needed); everything else goes through hex/rgb parsing.
|
|
37
115
|
const NAMED = {
|
|
@@ -70,7 +148,10 @@ function parseColorString(c) {
|
|
|
70
148
|
if (s in NAMED) return NAMED[s] >>> 0;
|
|
71
149
|
if (s[0] === '#') {
|
|
72
150
|
s = s.slice(1);
|
|
73
|
-
let r,
|
|
151
|
+
let r,
|
|
152
|
+
g,
|
|
153
|
+
b,
|
|
154
|
+
a = 255;
|
|
74
155
|
if (s.length === 3 || s.length === 4) {
|
|
75
156
|
r = parseInt(s[0] + s[0], 16);
|
|
76
157
|
g = parseInt(s[1] + s[1], 16);
|
|
@@ -89,7 +170,7 @@ function parseColorString(c) {
|
|
|
89
170
|
if (s.startsWith('rgb')) {
|
|
90
171
|
const m = s.match(/rgba?\(([^)]+)\)/);
|
|
91
172
|
if (m) {
|
|
92
|
-
const p = m[1].split(',').map(
|
|
173
|
+
const p = m[1].split(',').map(x => x.trim());
|
|
93
174
|
const r = parseInt(p[0], 10) || 0;
|
|
94
175
|
const g = parseInt(p[1], 10) || 0;
|
|
95
176
|
const b = parseInt(p[2], 10) || 0;
|
|
@@ -114,7 +195,7 @@ function tokenizePath(d) {
|
|
|
114
195
|
let cur = null;
|
|
115
196
|
while ((m = re.exec(d)) !== null) {
|
|
116
197
|
if (m[1]) {
|
|
117
|
-
cur = {
|
|
198
|
+
cur = {cmd: m[1], args: []};
|
|
118
199
|
out.push(cur);
|
|
119
200
|
} else if (cur) {
|
|
120
201
|
cur.args.push(parseFloat(m[2]));
|
|
@@ -168,7 +249,12 @@ function arcToCubics(x0, y0, rx, ry, phiDeg, largeArc, sweep, x, y) {
|
|
|
168
249
|
return a;
|
|
169
250
|
};
|
|
170
251
|
const theta1 = ang(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
171
|
-
let dtheta = ang(
|
|
252
|
+
let dtheta = ang(
|
|
253
|
+
(x1p - cxp) / rx,
|
|
254
|
+
(y1p - cyp) / ry,
|
|
255
|
+
(-x1p - cxp) / rx,
|
|
256
|
+
(-y1p - cyp) / ry,
|
|
257
|
+
);
|
|
172
258
|
if (!sweep && dtheta > 0) dtheta -= 2 * Math.PI;
|
|
173
259
|
if (sweep && dtheta < 0) dtheta += 2 * Math.PI;
|
|
174
260
|
const segs = Math.ceil(Math.abs(dtheta) / (Math.PI / 2));
|
|
@@ -191,7 +277,14 @@ function arcToCubics(x0, y0, rx, ry, phiDeg, largeArc, sweep, x, y) {
|
|
|
191
277
|
const d1y = -rx * sinP * sin1 + ry * cosP * cos1;
|
|
192
278
|
const d2x = -rx * cosP * sin2 - ry * sinP * cos2;
|
|
193
279
|
const d2y = -rx * sinP * sin2 + ry * cosP * cos2;
|
|
194
|
-
out.push([
|
|
280
|
+
out.push([
|
|
281
|
+
px + t * d1x,
|
|
282
|
+
py + t * d1y,
|
|
283
|
+
e2x - t * d2x,
|
|
284
|
+
e2y - t * d2y,
|
|
285
|
+
e2x,
|
|
286
|
+
e2y,
|
|
287
|
+
]);
|
|
195
288
|
px = e2x;
|
|
196
289
|
py = e2y;
|
|
197
290
|
th = th2;
|
|
@@ -219,8 +312,8 @@ export function parsePath(d) {
|
|
|
219
312
|
const a = tk.args;
|
|
220
313
|
const U = C.toUpperCase();
|
|
221
314
|
let i = 0;
|
|
222
|
-
const rx =
|
|
223
|
-
const ry =
|
|
315
|
+
const rx = v => (rel ? cx + v : v);
|
|
316
|
+
const ry = v => (rel ? cy + v : v);
|
|
224
317
|
if (U === 'M') {
|
|
225
318
|
// First pair is moveto; later pairs are implicit linetos.
|
|
226
319
|
cx = rx(a[i++]);
|
|
@@ -313,7 +406,8 @@ export function parsePath(d) {
|
|
|
313
406
|
const ex = rx(a[i++]);
|
|
314
407
|
const ey = ry(a[i++]);
|
|
315
408
|
const cubics = arcToCubics(cx, cy, arx, ary, rot, laf, swf, ex, ey);
|
|
316
|
-
for (const c of cubics)
|
|
409
|
+
for (const c of cubics)
|
|
410
|
+
ops.push(VOP_CUBIC, c[0], c[1], c[2], c[3], c[4], c[5]);
|
|
317
411
|
cx = ex;
|
|
318
412
|
cy = ey;
|
|
319
413
|
}
|
|
@@ -339,7 +433,19 @@ function circleOps(p) {
|
|
|
339
433
|
const cy = num(p.cy, 0);
|
|
340
434
|
const r = num(p.r, 0);
|
|
341
435
|
// Start at angle 0, full circular arc.
|
|
342
|
-
return [
|
|
436
|
+
return [
|
|
437
|
+
VOP_MOVE,
|
|
438
|
+
cx + r,
|
|
439
|
+
cy,
|
|
440
|
+
VOP_ARC,
|
|
441
|
+
cx,
|
|
442
|
+
cy,
|
|
443
|
+
r,
|
|
444
|
+
0,
|
|
445
|
+
2 * Math.PI,
|
|
446
|
+
0,
|
|
447
|
+
VOP_CLOSE,
|
|
448
|
+
];
|
|
343
449
|
}
|
|
344
450
|
|
|
345
451
|
function ellipseOps(p) {
|
|
@@ -357,11 +463,32 @@ function rectOps(p) {
|
|
|
357
463
|
const y = num(p.y, 0);
|
|
358
464
|
const w = num(p.width, 0);
|
|
359
465
|
const h = num(p.height, 0);
|
|
360
|
-
return [
|
|
466
|
+
return [
|
|
467
|
+
VOP_MOVE,
|
|
468
|
+
x,
|
|
469
|
+
y,
|
|
470
|
+
VOP_LINE,
|
|
471
|
+
x + w,
|
|
472
|
+
y,
|
|
473
|
+
VOP_LINE,
|
|
474
|
+
x + w,
|
|
475
|
+
y + h,
|
|
476
|
+
VOP_LINE,
|
|
477
|
+
x,
|
|
478
|
+
y + h,
|
|
479
|
+
VOP_CLOSE,
|
|
480
|
+
];
|
|
361
481
|
}
|
|
362
482
|
|
|
363
483
|
function lineOps(p) {
|
|
364
|
-
return [
|
|
484
|
+
return [
|
|
485
|
+
VOP_MOVE,
|
|
486
|
+
num(p.x1, 0),
|
|
487
|
+
num(p.y1, 0),
|
|
488
|
+
VOP_LINE,
|
|
489
|
+
num(p.x2, 0),
|
|
490
|
+
num(p.y2, 0),
|
|
491
|
+
];
|
|
365
492
|
}
|
|
366
493
|
|
|
367
494
|
// Arc convenience: angles in DEGREES, clockwise from 12 o'clock (the gauge/dial convention). Emits a
|
|
@@ -371,7 +498,18 @@ function arcOpsCW(cx, cy, r, a0deg, a1deg) {
|
|
|
371
498
|
// The engine's arc angle runs from +X (cos/sin); top-clockwise => subtract 90°.
|
|
372
499
|
const a0 = ((a0deg - 90) * Math.PI) / 180;
|
|
373
500
|
const a1 = ((a1deg - 90) * Math.PI) / 180;
|
|
374
|
-
return [
|
|
501
|
+
return [
|
|
502
|
+
VOP_MOVE,
|
|
503
|
+
cx + r * Math.cos(a0),
|
|
504
|
+
cy + r * Math.sin(a0),
|
|
505
|
+
VOP_ARC,
|
|
506
|
+
cx,
|
|
507
|
+
cy,
|
|
508
|
+
r,
|
|
509
|
+
a0,
|
|
510
|
+
a1,
|
|
511
|
+
0,
|
|
512
|
+
];
|
|
375
513
|
}
|
|
376
514
|
|
|
377
515
|
// --- Imperative shapes -> op-tape (the fast path; no JSX, no React) -------------------------------
|
|
@@ -401,7 +539,7 @@ export function shapesToVector(shapes) {
|
|
|
401
539
|
for (let si = 0; si < shapes.length; si++) {
|
|
402
540
|
const s = shapes[si];
|
|
403
541
|
const opStart = ops.length;
|
|
404
|
-
const paintIndex = paints.length /
|
|
542
|
+
const paintIndex = paints.length / PAINT_STRIDE;
|
|
405
543
|
ops.push(VOP_SHAPE, paintIndex);
|
|
406
544
|
if (s.arc) {
|
|
407
545
|
const cx = s.arc[0];
|
|
@@ -409,18 +547,55 @@ export function shapesToVector(shapes) {
|
|
|
409
547
|
const r = s.arc[2];
|
|
410
548
|
const a0 = ((s.arc[3] - 90) * Math.PI) / 180;
|
|
411
549
|
const a1 = ((s.arc[4] - 90) * Math.PI) / 180;
|
|
412
|
-
ops.push(
|
|
550
|
+
ops.push(
|
|
551
|
+
VOP_MOVE,
|
|
552
|
+
cx + r * Math.cos(a0),
|
|
553
|
+
cy + r * Math.sin(a0),
|
|
554
|
+
VOP_ARC,
|
|
555
|
+
cx,
|
|
556
|
+
cy,
|
|
557
|
+
r,
|
|
558
|
+
a0,
|
|
559
|
+
a1,
|
|
560
|
+
0,
|
|
561
|
+
);
|
|
413
562
|
} else if (s.circle) {
|
|
414
563
|
const cx = s.circle[0];
|
|
415
564
|
const cy = s.circle[1];
|
|
416
565
|
const r = s.circle[2];
|
|
417
|
-
ops.push(
|
|
566
|
+
ops.push(
|
|
567
|
+
VOP_MOVE,
|
|
568
|
+
cx + r,
|
|
569
|
+
cy,
|
|
570
|
+
VOP_ARC,
|
|
571
|
+
cx,
|
|
572
|
+
cy,
|
|
573
|
+
r,
|
|
574
|
+
0,
|
|
575
|
+
2 * Math.PI,
|
|
576
|
+
0,
|
|
577
|
+
VOP_CLOSE,
|
|
578
|
+
);
|
|
418
579
|
} else if (s.rect) {
|
|
419
580
|
const x = s.rect[0];
|
|
420
581
|
const y = s.rect[1];
|
|
421
582
|
const w = s.rect[2];
|
|
422
583
|
const h = s.rect[3];
|
|
423
|
-
ops.push(
|
|
584
|
+
ops.push(
|
|
585
|
+
VOP_MOVE,
|
|
586
|
+
x,
|
|
587
|
+
y,
|
|
588
|
+
VOP_LINE,
|
|
589
|
+
x + w,
|
|
590
|
+
y,
|
|
591
|
+
VOP_LINE,
|
|
592
|
+
x + w,
|
|
593
|
+
y + h,
|
|
594
|
+
VOP_LINE,
|
|
595
|
+
x,
|
|
596
|
+
y + h,
|
|
597
|
+
VOP_CLOSE,
|
|
598
|
+
);
|
|
424
599
|
} else if (s.line) {
|
|
425
600
|
ops.push(VOP_MOVE, s.line[0], s.line[1], VOP_LINE, s.line[2], s.line[3]);
|
|
426
601
|
} else if (s.path) {
|
|
@@ -438,10 +613,12 @@ export function shapesToVector(shapes) {
|
|
|
438
613
|
num(s.miter, 4),
|
|
439
614
|
CAP[s.cap] ?? 0,
|
|
440
615
|
JOIN[s.join] ?? 0,
|
|
441
|
-
s.fillRule === 'evenodd' ? 1 : 0
|
|
616
|
+
s.fillRule === 'evenodd' ? 1 : 0,
|
|
617
|
+
0, // fill_grad
|
|
618
|
+
0, // stroke_grad — the imperative path has no gradients
|
|
442
619
|
);
|
|
443
620
|
}
|
|
444
|
-
return {
|
|
621
|
+
return {ops, paints};
|
|
445
622
|
}
|
|
446
623
|
|
|
447
624
|
// --- Flatten an <Svg> subtree --------------------------------------------------------------------
|
|
@@ -457,12 +634,13 @@ const PAINT_DEFAULT = {
|
|
|
457
634
|
};
|
|
458
635
|
|
|
459
636
|
function mergePaint(base, props) {
|
|
460
|
-
const out = {
|
|
461
|
-
for (const k of Object.keys(PAINT_DEFAULT))
|
|
637
|
+
const out = {...base};
|
|
638
|
+
for (const k of Object.keys(PAINT_DEFAULT))
|
|
639
|
+
if (props[k] != null) out[k] = props[k];
|
|
462
640
|
return out;
|
|
463
641
|
}
|
|
464
642
|
|
|
465
|
-
/** Maps the resolved paint to the
|
|
643
|
+
/** Maps the resolved paint to the PAINT_STRIDE-number record [fill,stroke,w,miter,cap,join,rule,fillGrad]. */
|
|
466
644
|
function paintRecord(paint, scale) {
|
|
467
645
|
return [
|
|
468
646
|
parseColor(paint.fill),
|
|
@@ -472,6 +650,8 @@ function paintRecord(paint, scale) {
|
|
|
472
650
|
CAP[paint.strokeLinecap] ?? 0,
|
|
473
651
|
JOIN[paint.strokeLinejoin] ?? 0,
|
|
474
652
|
paint.fillRule === 'evenodd' ? 1 : 0,
|
|
653
|
+
0, // fill_grad: inline <Svg> children don't reference gradients (baked <Svg source> does)
|
|
654
|
+
0, // stroke_grad
|
|
475
655
|
];
|
|
476
656
|
}
|
|
477
657
|
|
|
@@ -479,8 +659,8 @@ function paintRecord(paint, scale) {
|
|
|
479
659
|
function transformOps(ops, T) {
|
|
480
660
|
const out = [];
|
|
481
661
|
let i = 0;
|
|
482
|
-
const ax =
|
|
483
|
-
const ay =
|
|
662
|
+
const ax = v => v * T.sx + T.tx;
|
|
663
|
+
const ay = v => v * T.sy + T.ty;
|
|
484
664
|
while (i < ops.length) {
|
|
485
665
|
const op = ops[i++];
|
|
486
666
|
out.push(op);
|
|
@@ -489,10 +669,24 @@ function transformOps(ops, T) {
|
|
|
489
669
|
} else if (op === VOP_QUAD) {
|
|
490
670
|
out.push(ax(ops[i++]), ay(ops[i++]), ax(ops[i++]), ay(ops[i++]));
|
|
491
671
|
} else if (op === VOP_CUBIC) {
|
|
492
|
-
out.push(
|
|
672
|
+
out.push(
|
|
673
|
+
ax(ops[i++]),
|
|
674
|
+
ay(ops[i++]),
|
|
675
|
+
ax(ops[i++]),
|
|
676
|
+
ay(ops[i++]),
|
|
677
|
+
ax(ops[i++]),
|
|
678
|
+
ay(ops[i++]),
|
|
679
|
+
);
|
|
493
680
|
} else if (op === VOP_ARC) {
|
|
494
681
|
// center + radius scale (uniform scale assumed for arcs)
|
|
495
|
-
out.push(
|
|
682
|
+
out.push(
|
|
683
|
+
ax(ops[i++]),
|
|
684
|
+
ay(ops[i++]),
|
|
685
|
+
ops[i++] * T.sx,
|
|
686
|
+
ops[i++],
|
|
687
|
+
ops[i++],
|
|
688
|
+
ops[i++],
|
|
689
|
+
);
|
|
496
690
|
}
|
|
497
691
|
// VOP_CLOSE has no args
|
|
498
692
|
}
|
|
@@ -518,13 +712,16 @@ export function flattenSvg(props) {
|
|
|
518
712
|
const paints = [];
|
|
519
713
|
|
|
520
714
|
// Root transform from viewBox vs width/height.
|
|
521
|
-
let root = {
|
|
715
|
+
let root = {sx: 1, sy: 1, tx: 0, ty: 0};
|
|
522
716
|
if (props.viewBox && props.width && props.height) {
|
|
523
|
-
const vb = String(props.viewBox)
|
|
717
|
+
const vb = String(props.viewBox)
|
|
718
|
+
.trim()
|
|
719
|
+
.split(/[\s,]+/)
|
|
720
|
+
.map(parseFloat);
|
|
524
721
|
if (vb.length === 4 && vb[2] > 0 && vb[3] > 0) {
|
|
525
722
|
const sx = num(props.width, vb[2]) / vb[2];
|
|
526
723
|
const sy = num(props.height, vb[3]) / vb[3];
|
|
527
|
-
root = {
|
|
724
|
+
root = {sx, sy, tx: -vb[0] * sx, ty: -vb[1] * sy};
|
|
528
725
|
}
|
|
529
726
|
}
|
|
530
727
|
|
|
@@ -538,7 +735,12 @@ export function flattenSvg(props) {
|
|
|
538
735
|
const s = num(p.scale, 1);
|
|
539
736
|
const gx = num(p.x ?? p.translateX, 0);
|
|
540
737
|
const gy = num(p.y ?? p.translateY, 0);
|
|
541
|
-
const childT = {
|
|
738
|
+
const childT = {
|
|
739
|
+
sx: T.sx * s,
|
|
740
|
+
sy: T.sy * s,
|
|
741
|
+
tx: gx * T.sx + T.tx,
|
|
742
|
+
ty: gy * T.sy + T.ty,
|
|
743
|
+
};
|
|
542
744
|
walk(p.children, merged, childT);
|
|
543
745
|
continue;
|
|
544
746
|
}
|
|
@@ -549,10 +751,16 @@ export function flattenSvg(props) {
|
|
|
549
751
|
else if (c.type === 'Rect') shapeOps = rectOps(p);
|
|
550
752
|
else if (c.type === 'Line') shapeOps = lineOps(p);
|
|
551
753
|
else if (c.type === 'Arc')
|
|
552
|
-
shapeOps = arcOpsCW(
|
|
754
|
+
shapeOps = arcOpsCW(
|
|
755
|
+
num(p.cx, 0),
|
|
756
|
+
num(p.cy, 0),
|
|
757
|
+
num(p.r, 0),
|
|
758
|
+
num(p.startAngle, 0),
|
|
759
|
+
num(p.endAngle, 0),
|
|
760
|
+
);
|
|
553
761
|
if (!shapeOps || shapeOps.length === 0) continue;
|
|
554
762
|
|
|
555
|
-
const paintIndex = paints.length /
|
|
763
|
+
const paintIndex = paints.length / PAINT_STRIDE;
|
|
556
764
|
const scale = (T.sx + T.sy) / 2; // stroke-width scale (uniform assumed)
|
|
557
765
|
paints.push(...paintRecord(merged, scale));
|
|
558
766
|
ops.push(VOP_SHAPE, paintIndex, ...transformOps(shapeOps, T));
|
|
@@ -560,5 +768,125 @@ export function flattenSvg(props) {
|
|
|
560
768
|
};
|
|
561
769
|
|
|
562
770
|
walk(props.children, PAINT_DEFAULT, root);
|
|
563
|
-
return {
|
|
771
|
+
return {ops, paints};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// --- Imported SVG artifacts (<Svg source>) -------------------------------------------------------
|
|
775
|
+
|
|
776
|
+
/** Scales a full op-tape (with SHAPE headers) by (sx, sy): coordinates only, paint indices untouched. */
|
|
777
|
+
function scaleTape(ops, sx, sy) {
|
|
778
|
+
const out = [];
|
|
779
|
+
let i = 0;
|
|
780
|
+
while (i < ops.length) {
|
|
781
|
+
const op = ops[i++];
|
|
782
|
+
out.push(op);
|
|
783
|
+
if (op === VOP_SHAPE)
|
|
784
|
+
out.push(ops[i++]); // paint index — copy, do not scale
|
|
785
|
+
else if (op === VOP_MOVE || op === VOP_LINE)
|
|
786
|
+
out.push(ops[i++] * sx, ops[i++] * sy);
|
|
787
|
+
else if (op === VOP_QUAD)
|
|
788
|
+
out.push(ops[i++] * sx, ops[i++] * sy, ops[i++] * sx, ops[i++] * sy);
|
|
789
|
+
else if (op === VOP_CUBIC)
|
|
790
|
+
out.push(
|
|
791
|
+
ops[i++] * sx,
|
|
792
|
+
ops[i++] * sy,
|
|
793
|
+
ops[i++] * sx,
|
|
794
|
+
ops[i++] * sy,
|
|
795
|
+
ops[i++] * sx,
|
|
796
|
+
ops[i++] * sy,
|
|
797
|
+
);
|
|
798
|
+
else if (op === VOP_ARC)
|
|
799
|
+
out.push(
|
|
800
|
+
ops[i++] * sx,
|
|
801
|
+
ops[i++] * sy,
|
|
802
|
+
ops[i++] * sx,
|
|
803
|
+
ops[i++],
|
|
804
|
+
ops[i++],
|
|
805
|
+
ops[i++],
|
|
806
|
+
);
|
|
807
|
+
// VOP_CLOSE has no args.
|
|
808
|
+
}
|
|
809
|
+
return out;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Scales an imported vector artifact ({ops, paints, width, height} from the .svg baker) from its intrinsic
|
|
814
|
+
* size to a target box, also scaling stroke widths. Returns the original arrays untouched when no scaling
|
|
815
|
+
* is needed (the common <Svg source> case where the box equals the intrinsic size).
|
|
816
|
+
*/
|
|
817
|
+
export function scaleVectorArtifact(art, targetW, targetH) {
|
|
818
|
+
const sx = art.width ? targetW / art.width : 1;
|
|
819
|
+
const sy = art.height ? targetH / art.height : 1;
|
|
820
|
+
const grads = art.gradients || [];
|
|
821
|
+
if (sx === 1 && sy === 1)
|
|
822
|
+
return {ops: art.ops, paints: art.paints, gradients: grads};
|
|
823
|
+
const sw = Math.sqrt(Math.abs(sx * sy)) || 1; // uniform stroke-width / radius scale
|
|
824
|
+
const paints = art.paints.slice();
|
|
825
|
+
for (let k = 2; k < paints.length; k += PAINT_STRIDE) paints[k] *= sw; // index 2 of each record = strokeWidth
|
|
826
|
+
// Gradient geometry lives in the same coordinate space as the path, so it scales with it (radius uniformly).
|
|
827
|
+
const gradients = grads.map(g => ({
|
|
828
|
+
...g,
|
|
829
|
+
ax: (g.ax || 0) * sx,
|
|
830
|
+
ay: (g.ay || 0) * sy,
|
|
831
|
+
bx: (g.bx || 0) * sx,
|
|
832
|
+
by: (g.by || 0) * sy,
|
|
833
|
+
// Radial r is a length (scales); conic r is a start angle (invariant under uniform scale) — don't scale it.
|
|
834
|
+
r: g.type === GRAD_CONIC ? g.r || 0 : (g.r || 0) * sw,
|
|
835
|
+
}));
|
|
836
|
+
return {ops: scaleTape(art.ops, sx, sy), paints, gradients};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// --- Bridge-cap diagnostics ----------------------------------------------------------------------
|
|
840
|
+
|
|
841
|
+
let _warnedVecOps = false;
|
|
842
|
+
let _warnedVecPaints = false;
|
|
843
|
+
let _warnedVecGrads = false;
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Warns (once per kind) when an <Svg>'s compiled geometry exceeds the native bridge's caps and will be
|
|
847
|
+
* SILENTLY TRUNCATED. The bridge (native_ui_bridge.c) copies the op-tape into a fixed buffer and caps the
|
|
848
|
+
* paint table; past the cap, geometry/shapes are dropped. The engine has its own pool diagnostics (on
|
|
849
|
+
* stderr, debug builds) for what happens further in; this surfaces the JS->engine boundary truncation in
|
|
850
|
+
* the developer's console. Pass the live caps from NativeUI.maxVectorOps / maxVectorPaints / maxVectorGrads
|
|
851
|
+
* (undefined on an older bridge => no-op).
|
|
852
|
+
*
|
|
853
|
+
* @param {number} opsLen Op-tape length (flat float count).
|
|
854
|
+
* @param {number} paintsLen Paint-table length (PAINT_STRIDE numbers per shape).
|
|
855
|
+
* @param {number} maxOps Bridge op-tape cap (NativeUI.maxVectorOps).
|
|
856
|
+
* @param {number} maxPaints Bridge paint cap, in SHAPES (NativeUI.maxVectorPaints).
|
|
857
|
+
* @param {number} [gradsLen] Gradient count (number of gradients referenced by the shapes).
|
|
858
|
+
* @param {number} [maxGrads] Bridge gradient cap (NativeUI.maxVectorGrads).
|
|
859
|
+
*/
|
|
860
|
+
export function warnVectorCaps(
|
|
861
|
+
opsLen,
|
|
862
|
+
paintsLen,
|
|
863
|
+
maxOps,
|
|
864
|
+
maxPaints,
|
|
865
|
+
gradsLen,
|
|
866
|
+
maxGrads,
|
|
867
|
+
) {
|
|
868
|
+
if (!_warnedVecOps && maxOps > 0 && opsLen > maxOps) {
|
|
869
|
+
_warnedVecOps = true;
|
|
870
|
+
console.warn(
|
|
871
|
+
`embedded-react: an <Svg> op-tape is too long (${opsLen} > ${maxOps} floats) and will be truncated — ` +
|
|
872
|
+
`the shape gets cut off. Simplify the path (fewer/coarser curves) or split it across <Svg> nodes; ` +
|
|
873
|
+
`raising the limit needs VEC_BRIDGE_MAX_OPS + ERUI_VECTOR_TAPE_MAX.`,
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
const shapes = (paintsLen / PAINT_STRIDE) | 0;
|
|
877
|
+
if (!_warnedVecPaints && maxPaints > 0 && shapes > maxPaints) {
|
|
878
|
+
_warnedVecPaints = true;
|
|
879
|
+
console.warn(
|
|
880
|
+
`embedded-react: an <Svg> has ${shapes} shapes (> ${maxPaints}) — the extra shapes won't render. ` +
|
|
881
|
+
`Split them across multiple <Svg> nodes; raising the limit needs VEC_BRIDGE_MAX_PAINTS + ERUI_VECTOR_PAINTS_MAX.`,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
if (!_warnedVecGrads && maxGrads > 0 && gradsLen > maxGrads) {
|
|
885
|
+
_warnedVecGrads = true;
|
|
886
|
+
console.warn(
|
|
887
|
+
`embedded-react: an <Svg> references ${gradsLen} gradients (> ${maxGrads}) — the extra gradients are ` +
|
|
888
|
+
`dropped and shapes that use them fall back to solid fills/strokes. Reuse fewer distinct gradients across ` +
|
|
889
|
+
`shapes; raising the limit needs VEC_BRIDGE_MAX_GRADS + ERUI_VECTOR_GRADS_MAX.`,
|
|
890
|
+
);
|
|
891
|
+
}
|
|
564
892
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
//
|
|
23
23
|
// Values must be JSON-serializable (numbers, strings, booleans, plain objects/arrays). The simulator
|
|
24
24
|
// resets the persisted state when you press R (manual reload) or restart it.
|
|
25
|
-
import {
|
|
25
|
+
import {useState, useCallback} from 'react';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Drop-in useState that persists across simulator reloads, keyed by a stable string.
|
|
@@ -48,8 +48,8 @@ export function usePersistentState(key, initial) {
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
const set = useCallback(
|
|
51
|
-
|
|
52
|
-
setValue(
|
|
51
|
+
next => {
|
|
52
|
+
setValue(prev => {
|
|
53
53
|
const v = typeof next === 'function' ? next(prev) : next;
|
|
54
54
|
const store = globalThis.__erPersist;
|
|
55
55
|
if (store) {
|