@fcannizzaro/streamdeck-react 0.1.10 → 0.1.12

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.
Files changed (76) hide show
  1. package/LICENSE +190 -21
  2. package/README.md +2 -0
  3. package/dist/action.d.ts +2 -2
  4. package/dist/action.js +1 -2
  5. package/dist/adapter/index.d.ts +2 -0
  6. package/dist/adapter/physical-device.d.ts +2 -0
  7. package/dist/adapter/physical-device.js +153 -0
  8. package/dist/adapter/types.d.ts +127 -0
  9. package/dist/bundler-shared.d.ts +11 -0
  10. package/dist/bundler-shared.js +11 -0
  11. package/dist/context/event-bus.d.ts +1 -1
  12. package/dist/context/event-bus.js +1 -1
  13. package/dist/context/touchstrip-context.d.ts +2 -0
  14. package/dist/context/touchstrip-context.js +5 -0
  15. package/dist/devtools/bridge.d.ts +35 -7
  16. package/dist/devtools/bridge.js +152 -46
  17. package/dist/devtools/highlight.d.ts +5 -0
  18. package/dist/devtools/highlight.js +107 -57
  19. package/dist/devtools/index.js +6 -0
  20. package/dist/devtools/observers/lifecycle.d.ts +4 -4
  21. package/dist/devtools/server.d.ts +6 -1
  22. package/dist/devtools/server.js +6 -1
  23. package/dist/devtools/types.d.ts +50 -6
  24. package/dist/font-inline.d.ts +5 -1
  25. package/dist/font-inline.js +8 -3
  26. package/dist/hooks/animation.d.ts +154 -0
  27. package/dist/hooks/animation.js +381 -0
  28. package/dist/hooks/events.js +2 -6
  29. package/dist/hooks/sdk.js +11 -11
  30. package/dist/hooks/touchstrip.d.ts +6 -0
  31. package/dist/hooks/touchstrip.js +37 -0
  32. package/dist/hooks/utility.js +3 -2
  33. package/dist/index.d.ts +9 -2
  34. package/dist/index.js +4 -2
  35. package/dist/manifest-codegen.d.ts +38 -0
  36. package/dist/manifest-codegen.js +110 -0
  37. package/dist/plugin.js +86 -106
  38. package/dist/reconciler/host-config.js +19 -1
  39. package/dist/reconciler/vnode.d.ts +26 -2
  40. package/dist/reconciler/vnode.js +40 -10
  41. package/dist/render/buffer-pool.d.ts +19 -0
  42. package/dist/render/buffer-pool.js +51 -0
  43. package/dist/render/cache.d.ts +29 -0
  44. package/dist/render/cache.js +137 -5
  45. package/dist/render/image-cache.d.ts +54 -0
  46. package/dist/render/image-cache.js +144 -0
  47. package/dist/render/metrics.d.ts +57 -0
  48. package/dist/render/metrics.js +98 -0
  49. package/dist/render/pipeline.d.ts +36 -1
  50. package/dist/render/pipeline.js +304 -34
  51. package/dist/render/png.d.ts +1 -1
  52. package/dist/render/png.js +26 -11
  53. package/dist/render/render-pool.d.ts +24 -0
  54. package/dist/render/render-pool.js +130 -0
  55. package/dist/render/svg.d.ts +7 -0
  56. package/dist/render/svg.js +139 -0
  57. package/dist/render/worker.d.ts +1 -0
  58. package/dist/rollup.d.ts +23 -9
  59. package/dist/rollup.js +24 -9
  60. package/dist/roots/registry.d.ts +9 -11
  61. package/dist/roots/registry.js +39 -42
  62. package/dist/roots/root.d.ts +9 -6
  63. package/dist/roots/root.js +52 -29
  64. package/dist/roots/settings-equality.d.ts +5 -0
  65. package/dist/roots/settings-equality.js +24 -0
  66. package/dist/roots/{touchbar-root.d.ts → touchstrip-root.d.ts} +30 -8
  67. package/dist/roots/touchstrip-root.js +263 -0
  68. package/dist/types.d.ts +73 -23
  69. package/dist/vite.d.ts +22 -8
  70. package/dist/vite.js +24 -8
  71. package/package.json +7 -4
  72. package/dist/context/touchbar-context.d.ts +0 -2
  73. package/dist/context/touchbar-context.js +0 -5
  74. package/dist/hooks/touchbar.d.ts +0 -6
  75. package/dist/hooks/touchbar.js +0 -37
  76. package/dist/roots/touchbar-root.js +0 -175
@@ -1,62 +1,329 @@
1
- import { vnodeToElement } from "../reconciler/vnode.js";
2
- import { fnv1a } from "./cache.js";
1
+ import { isContainerDirty } from "../reconciler/vnode.js";
2
+ import { computeCacheKey, computeTreeHash, fnv1a } from "./cache.js";
3
+ import { getBufferPool } from "./buffer-pool.js";
3
4
  import { encodePng } from "./png.js";
4
- import { createElement } from "react";
5
- import { fromJsx } from "@takumi-rs/helpers/jsx";
5
+ import { getImageCache, getTouchStripCache } from "./image-cache.js";
6
+ import { metrics } from "./metrics.js";
7
+ import { serializeSvgTree } from "./svg.js";
6
8
  //#region src/render/pipeline.ts
9
+ var ROOT_STYLE = {
10
+ display: "flex",
11
+ width: "100%",
12
+ height: "100%"
13
+ };
14
+ /** Warn threshold for consecutive identical renders in debug mode. */
15
+ var DUP_RENDER_WARN_THRESHOLD = 3;
16
+ /** Maximum recommended VNode tree depth. Warn in debug mode when exceeded. */
17
+ var MAX_DEPTH_WARN = 25;
18
+ /** Whether we've already warned about tree depth (avoid log spam). */
19
+ var depthWarned = false;
20
+ var SKIP_PROPS = new Set([
21
+ "children",
22
+ "className",
23
+ "src",
24
+ "tw"
25
+ ]);
26
+ function copyPropsToNode(target, props) {
27
+ for (const key of Object.keys(props)) if (!SKIP_PROPS.has(key)) target[key] = props[key];
28
+ }
29
+ function vnodeToTakumiNode(node, depth = 0) {
30
+ if (!depthWarned && depth > MAX_DEPTH_WARN) {
31
+ depthWarned = true;
32
+ console.warn(`[@fcannizzaro/streamdeck-react] VNode tree depth ${depth} exceeds recommended limit ${MAX_DEPTH_WARN}. Deep nesting increases render cost.`);
33
+ }
34
+ if (node.type === "#text") return {
35
+ type: "text",
36
+ text: node.text ?? ""
37
+ };
38
+ const props = node.props;
39
+ const rawTw = typeof props.tw === "string" ? props.tw : void 0;
40
+ const className = props.className;
41
+ let tw = rawTw;
42
+ if (typeof className === "string" && className.length > 0) tw = tw ? tw + " " + className : className;
43
+ if (node.type === "img" && typeof props.src === "string") {
44
+ const result = {
45
+ type: "image",
46
+ src: props.src
47
+ };
48
+ if (tw) result.tw = tw;
49
+ copyPropsToNode(result, props);
50
+ return result;
51
+ }
52
+ if (node.type === "svg") {
53
+ const result = {
54
+ type: "image",
55
+ src: serializeSvgTree(node),
56
+ tagName: "svg"
57
+ };
58
+ const width = typeof props.width === "number" ? props.width : void 0;
59
+ const height = typeof props.height === "number" ? props.height : void 0;
60
+ if (width != null) result.width = width;
61
+ if (height != null) result.height = height;
62
+ if (tw) result.tw = tw;
63
+ if (props.style) result.style = props.style;
64
+ return result;
65
+ }
66
+ const result = { type: "container" };
67
+ if (tw) result.tw = tw;
68
+ copyPropsToNode(result, props);
69
+ if (node.children.length > 0) result.children = node.children.map((child) => vnodeToTakumiNode(child, depth + 1));
70
+ return result;
71
+ }
72
+ /** Build the root Takumi container wrapping the VNode children. */
73
+ function buildTakumiRoot(container) {
74
+ return {
75
+ type: "container",
76
+ style: ROOT_STYLE,
77
+ children: container.children.map(vnodeToTakumiNode)
78
+ };
79
+ }
80
+ function measureTree(nodes) {
81
+ let maxDepth = 0;
82
+ let count = 0;
83
+ function walk(children, depth) {
84
+ for (const child of children) {
85
+ count++;
86
+ if (depth > maxDepth) maxDepth = depth;
87
+ walk(child.children, depth + 1);
88
+ }
89
+ }
90
+ walk(nodes, 1);
91
+ return {
92
+ depth: maxDepth,
93
+ count
94
+ };
95
+ }
7
96
  function bufferToDataUri(buffer, format) {
8
97
  return `data:image/${format};base64,${(Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)).toString("base64")}`;
9
98
  }
99
+ function emitProfile(config, times, opts) {
100
+ const stats = measureTree(opts.container.children);
101
+ const cache = config.imageCacheMaxBytes > 0 ? getImageCache(config.imageCacheMaxBytes) : null;
102
+ config.onProfile({
103
+ vnodeConversionMs: times.t1 - times.t0,
104
+ takumiRenderMs: times.t2 - times.t1,
105
+ hashMs: times.t3 - times.t2,
106
+ base64Ms: 0,
107
+ totalMs: times.t3 - times.t0,
108
+ skipped: opts.skipped,
109
+ cacheHit: opts.cacheHit,
110
+ treeDepth: stats.depth,
111
+ nodeCount: stats.count,
112
+ cacheStats: cache?.stats ?? null
113
+ });
114
+ }
10
115
  async function renderToDataUri(container, width, height, config) {
11
116
  if (container.children.length === 0) return null;
12
- const { node, stylesheets } = await fromJsx(createElement("div", { style: {
13
- display: "flex",
14
- width: "100%",
15
- height: "100%"
16
- } }, ...container.children.map(vnodeToElement)));
17
- const buffer = await config.renderer.render(node, {
18
- width,
19
- height,
20
- format: config.imageFormat,
21
- stylesheets,
22
- devicePixelRatio: config.devicePixelRatio
23
- });
117
+ metrics.recordFlush();
118
+ if (config.caching && !isContainerDirty(container)) {
119
+ metrics.recordDirtySkip();
120
+ return null;
121
+ }
122
+ const profiling = config.onProfile != null;
123
+ const t0 = profiling ? performance.now() : 0;
124
+ let t1 = t0;
125
+ let t2 = t0;
126
+ let treeHash;
127
+ let cacheKey;
128
+ if (config.caching && config.imageCacheMaxBytes > 0) {
129
+ treeHash = computeTreeHash(container);
130
+ cacheKey = computeCacheKey(treeHash, width, height, config.devicePixelRatio, config.imageFormat);
131
+ const cached = getImageCache(config.imageCacheMaxBytes).get(cacheKey);
132
+ if (cached !== void 0) {
133
+ metrics.recordCacheHit();
134
+ if (profiling) {
135
+ const tNow = performance.now();
136
+ emitProfile(config, {
137
+ t0,
138
+ t1: tNow,
139
+ t2: tNow,
140
+ t3: tNow
141
+ }, {
142
+ skipped: false,
143
+ cacheHit: true,
144
+ container
145
+ });
146
+ }
147
+ container._dupCount = 0;
148
+ config.onRender?.(container, cached);
149
+ return cached;
150
+ }
151
+ }
152
+ let buffer;
153
+ if (config.renderPool?.isAvailable) {
154
+ buffer = await config.renderPool.render(container.children, width, height, config.imageFormat, config.devicePixelRatio);
155
+ t2 = profiling ? performance.now() : 0;
156
+ t1 = t0;
157
+ } else {
158
+ const rootNode = buildTakumiRoot(container);
159
+ t1 = profiling ? performance.now() : 0;
160
+ buffer = await config.renderer.render(rootNode, {
161
+ width,
162
+ height,
163
+ format: config.imageFormat,
164
+ devicePixelRatio: config.devicePixelRatio
165
+ });
166
+ t2 = profiling ? performance.now() : 0;
167
+ }
24
168
  if (config.caching) {
25
169
  const hash = fnv1a(buffer);
26
- if (hash === container.lastSvgHash) return null;
170
+ if (hash === container.lastSvgHash) {
171
+ metrics.recordHashDedup();
172
+ if (config.debug) {
173
+ container._dupCount++;
174
+ if (container._dupCount > DUP_RENDER_WARN_THRESHOLD) console.warn(`[@fcannizzaro/streamdeck-react] ${container._dupCount} consecutive identical renders — component likely re-rendering without visual change`);
175
+ }
176
+ if (profiling) {
177
+ const tEnd = performance.now();
178
+ emitProfile(config, {
179
+ t0,
180
+ t1,
181
+ t2,
182
+ t3: tEnd
183
+ }, {
184
+ skipped: true,
185
+ cacheHit: false,
186
+ container
187
+ });
188
+ }
189
+ return null;
190
+ }
27
191
  container.lastSvgHash = hash;
192
+ container._dupCount = 0;
28
193
  }
29
194
  const dataUri = bufferToDataUri(buffer, config.imageFormat);
195
+ if (config.caching && config.imageCacheMaxBytes > 0) {
196
+ if (treeHash === void 0 || cacheKey === void 0) {
197
+ treeHash = computeTreeHash(container);
198
+ cacheKey = computeCacheKey(treeHash, width, height, config.devicePixelRatio, config.imageFormat);
199
+ }
200
+ getImageCache(config.imageCacheMaxBytes).set(cacheKey, dataUri, dataUri.length * 2 + 64);
201
+ }
202
+ metrics.recordRender(t2 - t0);
203
+ if (profiling) {
204
+ const tEnd = performance.now();
205
+ emitProfile(config, {
206
+ t0,
207
+ t1,
208
+ t2,
209
+ t3: tEnd
210
+ }, {
211
+ skipped: false,
212
+ cacheHit: false,
213
+ container
214
+ });
215
+ }
30
216
  config.onRender?.(container, dataUri);
31
217
  return dataUri;
32
218
  }
33
219
  async function renderToRaw(container, width, height, config) {
34
220
  if (container.children.length === 0) return null;
35
- const { node, stylesheets } = await fromJsx(createElement("div", { style: {
36
- display: "flex",
37
- width: "100%",
38
- height: "100%"
39
- } }, ...container.children.map(vnodeToElement)));
40
- const buffer = await config.renderer.render(node, {
41
- width,
42
- height,
43
- format: "raw",
44
- stylesheets,
45
- devicePixelRatio: config.devicePixelRatio
46
- });
221
+ metrics.recordFlush();
222
+ if (config.caching && !isContainerDirty(container)) {
223
+ metrics.recordDirtySkip();
224
+ return null;
225
+ }
226
+ const profiling = config.onProfile != null;
227
+ const t0 = profiling ? performance.now() : 0;
228
+ let t1 = t0;
229
+ let t2 = t0;
230
+ let treeHash;
231
+ let cacheKey;
232
+ if (config.caching && config.touchStripCacheMaxBytes > 0) {
233
+ treeHash = computeTreeHash(container);
234
+ cacheKey = computeCacheKey(treeHash, width, height, config.devicePixelRatio, "raw");
235
+ const cached = getTouchStripCache(config.touchStripCacheMaxBytes).get(cacheKey);
236
+ if (cached !== void 0) {
237
+ metrics.recordCacheHit();
238
+ if (profiling) {
239
+ const tNow = performance.now();
240
+ emitProfile(config, {
241
+ t0,
242
+ t1: tNow,
243
+ t2: tNow,
244
+ t3: tNow
245
+ }, {
246
+ skipped: false,
247
+ cacheHit: true,
248
+ container
249
+ });
250
+ }
251
+ return {
252
+ buffer: cached,
253
+ width,
254
+ height
255
+ };
256
+ }
257
+ }
258
+ let buffer;
259
+ if (config.renderPool?.isAvailable) {
260
+ buffer = await config.renderPool.render(container.children, width, height, "raw", config.devicePixelRatio);
261
+ t2 = profiling ? performance.now() : 0;
262
+ t1 = t0;
263
+ } else {
264
+ const rootNode = buildTakumiRoot(container);
265
+ t1 = profiling ? performance.now() : 0;
266
+ buffer = await config.renderer.render(rootNode, {
267
+ width,
268
+ height,
269
+ format: "raw",
270
+ devicePixelRatio: config.devicePixelRatio
271
+ });
272
+ t2 = profiling ? performance.now() : 0;
273
+ }
47
274
  if (config.caching) {
48
275
  const hash = fnv1a(buffer);
49
- if (hash === container.lastSvgHash) return null;
276
+ if (hash === container.lastSvgHash) {
277
+ metrics.recordHashDedup();
278
+ if (profiling) {
279
+ const tEnd = performance.now();
280
+ emitProfile(config, {
281
+ t0,
282
+ t1,
283
+ t2,
284
+ t3: tEnd
285
+ }, {
286
+ skipped: true,
287
+ cacheHit: false,
288
+ container
289
+ });
290
+ }
291
+ return null;
292
+ }
50
293
  container.lastSvgHash = hash;
51
294
  }
295
+ const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
296
+ if (config.caching && config.touchStripCacheMaxBytes > 0) {
297
+ if (treeHash === void 0 || cacheKey === void 0) {
298
+ treeHash = computeTreeHash(container);
299
+ cacheKey = computeCacheKey(treeHash, width, height, config.devicePixelRatio, "raw");
300
+ }
301
+ getTouchStripCache(config.touchStripCacheMaxBytes).set(cacheKey, buf, buf.byteLength + 64);
302
+ }
303
+ metrics.recordRender(t2 - t0);
304
+ if (profiling) {
305
+ const tEnd = performance.now();
306
+ emitProfile(config, {
307
+ t0,
308
+ t1,
309
+ t2,
310
+ t3: tEnd
311
+ }, {
312
+ skipped: false,
313
+ cacheHit: false,
314
+ container
315
+ });
316
+ }
52
317
  return {
53
- buffer: Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer),
318
+ buffer: buf,
54
319
  width,
55
320
  height
56
321
  };
57
322
  }
58
323
  function cropSlice(raw, fullWidth, column, segmentWidth, segmentHeight) {
59
- const slice = Buffer.alloc(segmentWidth * segmentHeight * 4);
324
+ const pool = getBufferPool();
325
+ const sliceSize = segmentWidth * segmentHeight * 4;
326
+ const slice = pool.acquire(sliceSize);
60
327
  const srcRowBytes = fullWidth * 4;
61
328
  const dstRowBytes = segmentWidth * 4;
62
329
  const xOffset = column * segmentWidth * 4;
@@ -68,7 +335,10 @@ function cropSlice(raw, fullWidth, column, segmentWidth, segmentHeight) {
68
335
  return slice;
69
336
  }
70
337
  function sliceToDataUri(raw, fullWidth, fullHeight, column, segmentWidth, segmentHeight) {
71
- return bufferToDataUri(encodePng(segmentWidth, segmentHeight, cropSlice(raw, fullWidth, column, segmentWidth, segmentHeight)), "png");
338
+ const cropped = cropSlice(raw, fullWidth, column, segmentWidth, segmentHeight);
339
+ const png = encodePng(segmentWidth, segmentHeight, cropped);
340
+ getBufferPool().release(cropped);
341
+ return bufferToDataUri(png, "png");
72
342
  }
73
343
  //#endregion
74
- export { bufferToDataUri, renderToDataUri, renderToRaw, sliceToDataUri };
344
+ export { bufferToDataUri, buildTakumiRoot, measureTree, renderToDataUri, renderToRaw, sliceToDataUri };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Encode raw RGBA pixels into a PNG buffer.
2
+ * Encode raw RGBA pixels into a PNG buffer (synchronous).
3
3
  *
4
4
  * @param width Image width in pixels.
5
5
  * @param height Image height in pixels.
@@ -1,3 +1,4 @@
1
+ import { getBufferPool } from "./buffer-pool.js";
1
2
  import { deflateSync } from "node:zlib";
2
3
  //#region src/render/png.ts
3
4
  var crcTable = new Uint32Array(256);
@@ -35,14 +36,7 @@ var PNG_SIGNATURE = Buffer.from([
35
36
  26,
36
37
  10
37
38
  ]);
38
- /**
39
- * Encode raw RGBA pixels into a PNG buffer.
40
- *
41
- * @param width Image width in pixels.
42
- * @param height Image height in pixels.
43
- * @param rgba Raw RGBA pixel data (width × height × 4 bytes, row-major).
44
- */
45
- function encodePng(width, height, rgba) {
39
+ function buildIhdr(width, height) {
46
40
  const ihdr = Buffer.alloc(13);
47
41
  ihdr.writeUInt32BE(width, 0);
48
42
  ihdr.writeUInt32BE(height, 4);
@@ -51,15 +45,22 @@ function encodePng(width, height, rgba) {
51
45
  ihdr[10] = 0;
52
46
  ihdr[11] = 0;
53
47
  ihdr[12] = 0;
48
+ return ihdr;
49
+ }
50
+ function buildFilteredScanlines(width, height, rgba) {
54
51
  const rowBytes = width * 4;
55
- const filtered = Buffer.alloc(height * (1 + rowBytes));
52
+ const filteredSize = height * (1 + rowBytes);
53
+ const filtered = getBufferPool().acquire(filteredSize);
56
54
  for (let y = 0; y < height; y++) {
57
55
  const dstOff = y * (1 + rowBytes);
58
56
  filtered[dstOff] = 0;
59
57
  const srcOff = y * rowBytes;
60
- for (let i = 0; i < rowBytes; i++) filtered[dstOff + 1 + i] = rgba[srcOff + i];
58
+ if (Buffer.isBuffer(rgba)) rgba.copy(filtered, dstOff + 1, srcOff, srcOff + rowBytes);
59
+ else filtered.set(rgba.subarray(srcOff, srcOff + rowBytes), dstOff + 1);
61
60
  }
62
- const compressed = deflateSync(filtered);
61
+ return filtered;
62
+ }
63
+ function assemblePng(ihdr, compressed) {
63
64
  return Buffer.concat([
64
65
  PNG_SIGNATURE,
65
66
  pngChunk("IHDR", ihdr),
@@ -67,5 +68,19 @@ function encodePng(width, height, rgba) {
67
68
  pngChunk("IEND", Buffer.alloc(0))
68
69
  ]);
69
70
  }
71
+ /**
72
+ * Encode raw RGBA pixels into a PNG buffer (synchronous).
73
+ *
74
+ * @param width Image width in pixels.
75
+ * @param height Image height in pixels.
76
+ * @param rgba Raw RGBA pixel data (width × height × 4 bytes, row-major).
77
+ */
78
+ function encodePng(width, height, rgba) {
79
+ const ihdr = buildIhdr(width, height);
80
+ const filtered = buildFilteredScanlines(width, height, rgba);
81
+ const compressed = deflateSync(filtered);
82
+ getBufferPool().release(filtered);
83
+ return assemblePng(ihdr, compressed);
84
+ }
70
85
  //#endregion
71
86
  export { encodePng };
@@ -0,0 +1,24 @@
1
+ import { FontConfig } from '../types';
2
+ import { VNode } from '../reconciler/vnode';
3
+ export declare class RenderPool {
4
+ private worker;
5
+ private ready;
6
+ private failed;
7
+ private nextId;
8
+ private pending;
9
+ private initPromise;
10
+ private fonts;
11
+ constructor(fonts: FontConfig[]);
12
+ /** Start the worker. Call once during plugin initialization. */
13
+ initialize(): Promise<boolean>;
14
+ private doInitialize;
15
+ /** Whether the worker is available for offloaded rendering. */
16
+ get isAvailable(): boolean;
17
+ /**
18
+ * Render VNode children in the worker thread.
19
+ * Returns the raw raster buffer.
20
+ */
21
+ render(vnodes: VNode[], width: number, height: number, format: string, dpr: number): Promise<Buffer>;
22
+ private handleResponse;
23
+ private handleWorkerDeath;
24
+ }
@@ -0,0 +1,130 @@
1
+ import { Worker } from "node:worker_threads";
2
+ //#region src/render/render-pool.ts
3
+ /** Strip internal fields and function props from a VNode tree for worker transfer. */
4
+ function serializeVNode(node) {
5
+ const cleanProps = {};
6
+ for (const [key, value] of Object.entries(node.props)) if (typeof value !== "function" && typeof value !== "symbol") cleanProps[key] = value;
7
+ return {
8
+ type: node.type,
9
+ props: cleanProps,
10
+ children: node.children.map(serializeVNode),
11
+ text: node.text
12
+ };
13
+ }
14
+ var RenderPool = class {
15
+ worker = null;
16
+ ready = false;
17
+ failed = false;
18
+ nextId = 0;
19
+ pending = /* @__PURE__ */ new Map();
20
+ initPromise = null;
21
+ fonts;
22
+ constructor(fonts) {
23
+ this.fonts = fonts;
24
+ }
25
+ /** Start the worker. Call once during plugin initialization. */
26
+ async initialize() {
27
+ if (this.ready) return true;
28
+ if (this.failed) return false;
29
+ if (this.initPromise != null) {
30
+ await this.initPromise;
31
+ return this.ready;
32
+ }
33
+ this.initPromise = this.doInitialize();
34
+ await this.initPromise;
35
+ return this.ready;
36
+ }
37
+ async doInitialize() {
38
+ try {
39
+ this.worker = new Worker(new URL("data:video/mp2t;base64,Ly8g4pSA4pSAIFJlbmRlciBXb3JrZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACi8vCi8vIFJ1bnMgaW4gYSBzZXBhcmF0ZSB0aHJlYWQgdmlhIE5vZGUuanMgd29ya2VyX3RocmVhZHMuICBIYW5kbGVzIHRoZQovLyBmdWxsIHJlbmRlciBwaXBlbGluZTogc2VyaWFsaXplZCBWTm9kZSBkYXRhIOKGkiBUYWt1bWkgbm9kZXMg4oaSIHJhc3Rlci4KLy8KLy8gVXNlcyB0aGUgZGlyZWN0IFZOb2RlIOKGkiBUYWt1bWkgbm9kZSBieXBhc3MgKHNhbWUgYXMgbWFpbiB0aHJlYWQncwovLyB2bm9kZVRvVGFrdW1pTm9kZSBpbiBwaXBlbGluZS50cyksIHNraXBwaW5nIHZub2RlVG9FbGVtZW50KCkgYW5kCi8vIGZyb21Kc3goKSBlbnRpcmVseS4KLy8KLy8gVGhpcyB1bmJsb2NrcyB0aGUgbWFpbiB0aHJlYWQgZHVyaW5nIHRoZSBleHBlbnNpdmUgVGFrdW1pCi8vIHJhc3Rlcml6YXRpb24gc3RlcCAofjXigJMzMG1zIHBlciBmcmFtZSkuCi8vCi8vIFdoeSBjb2RlIGlzIGR1cGxpY2F0ZWQgKGlubGluZWQpOgovLyAgIFdvcmtlciB0aHJlYWRzIGNhbid0IGltcG9ydCBmcm9tIHRoZSBtYWluIGJ1bmRsZSDigJQgdGhleSBsb2FkCi8vICAgdGhlIGNvbXBpbGVkIHdvcmtlci5qcyBmaWxlIGluZGVwZW5kZW50bHkuICBTVkcgc2VyaWFsaXphdGlvbgovLyAgIGFuZCBWTm9kZeKGklRha3VtaSBjb252ZXJzaW9uIG11c3QgYmUgc2VsZi1jb250YWluZWQgaGVyZS4KLy8gICBCb3RoIG1pcnJvciB0aGUgbG9naWMgaW4gc3ZnLnRzIGFuZCBwaXBlbGluZS50cyByZXNwZWN0aXZlbHkuCi8vCi8vIFplcm8tY29weSByZXR1cm46Ci8vICAgVGhlIHJlbmRlcmVkIGJ1ZmZlciBpcyB0cmFuc2ZlcnJlZCAobm90IGNvcGllZCkgYmFjayB0byB0aGUgbWFpbgovLyAgIHRocmVhZCB2aWEgcG9zdE1lc3NhZ2UncyB0cmFuc2ZlciBsaXN0LiAgVGhpcyBhdm9pZHMgY29weWluZwovLyAgIHBvdGVudGlhbGx5IGxhcmdlIHJhc3RlciBidWZmZXJzIChlLmcuIDgwMMOXMTAww5c0ID0gMzIwS0IgZm9yCi8vICAgVG91Y2hTdHJpcCkgYWNyb3NzIHRoZSB0aHJlYWQgYm91bmRhcnkuCgppbXBvcnQgeyBwYXJlbnRQb3J0LCB3b3JrZXJEYXRhIH0gZnJvbSAibm9kZTp3b3JrZXJfdGhyZWFkcyI7CgovLyDilIDilIAgVHlwZXMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgppbnRlcmZhY2UgU2VyaWFsaXplZFZOb2RlIHsKICB0eXBlOiBzdHJpbmc7CiAgcHJvcHM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+OwogIGNoaWxkcmVuOiBTZXJpYWxpemVkVk5vZGVbXTsKICB0ZXh0Pzogc3RyaW5nOwp9CgovKiogTWF0Y2hlcyBAdGFrdW1pLXJzL2hlbHBlcnMgTm9kZSB1bmlvbi4gUGxhaW4gb2JqZWN0IGFjY2VwdGVkIGJ5IFJlbmRlcmVyLnJlbmRlcigpLiAqLwppbnRlcmZhY2UgVGFrdW1pTm9kZSB7CiAgdHlwZTogc3RyaW5nOwogIFtrZXk6IHN0cmluZ106IHVua25vd247Cn0KCmludGVyZmFjZSBJbml0TWVzc2FnZSB7CiAgdHlwZTogImluaXQiOwogIGZvbnRzOiBBcnJheTx7CiAgICBuYW1lOiBzdHJpbmc7CiAgICBkYXRhOiBBcnJheUJ1ZmZlciB8IEJ1ZmZlcjsKICAgIHdlaWdodDogbnVtYmVyOwogICAgc3R5bGU6IHN0cmluZzsKICB9PjsKfQoKaW50ZXJmYWNlIFJlbmRlck1lc3NhZ2UgewogIHR5cGU6ICJyZW5kZXIiOwogIGlkOiBudW1iZXI7CiAgdm5vZGVzOiBTZXJpYWxpemVkVk5vZGVbXTsKICB3aWR0aDogbnVtYmVyOwogIGhlaWdodDogbnVtYmVyOwogIGZvcm1hdDogc3RyaW5nOwogIGRwcjogbnVtYmVyOwp9CgppbnRlcmZhY2UgU2h1dGRvd25NZXNzYWdlIHsKICB0eXBlOiAic2h1dGRvd24iOwp9Cgp0eXBlIFdvcmtlck1lc3NhZ2UgPSBJbml0TWVzc2FnZSB8IFJlbmRlck1lc3NhZ2UgfCBTaHV0ZG93bk1lc3NhZ2U7CgovLyDilIDilIAgU1ZHIFNlcmlhbGl6YXRpb24gKGlubGluZWQgZm9yIHdvcmtlciBjb250ZXh0KSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKLy8gTWlycm9ycyB0aGUgc2VyaWFsaXplU3ZnVHJlZSgpIGZyb20gc3ZnLnRzLiBJbmxpbmVkIHRvIGF2b2lkCi8vIGNyb3NzLW1vZHVsZSBpbXBvcnQgaXNzdWVzIGluIHRoZSB3b3JrZXIgdGhyZWFkLgoKY29uc3QgU1ZHX0NBTUVMX0FUVFJTOiBSZWFkb25seVNldDxzdHJpbmc+ID0gbmV3IFNldChbCiAgImFjY2VudEhlaWdodCIsCiAgImFsaWdubWVudEJhc2VsaW5lIiwKICAiYXJhYmljRm9ybSIsCiAgImJhc2VsaW5lU2hpZnQiLAogICJjYXBIZWlnaHQiLAogICJjbGlwUGF0aCIsCiAgImNsaXBQYXRoVW5pdHMiLAogICJjbGlwUnVsZSIsCiAgImNvbG9ySW50ZXJwb2xhdGlvbiIsCiAgImNvbG9ySW50ZXJwb2xhdGlvbkZpbHRlcnMiLAogICJjb2xvclByb2ZpbGUiLAogICJjb2xvclJlbmRlcmluZyIsCiAgImVuYWJsZUJhY2tncm91bmQiLAogICJmaWxsT3BhY2l0eSIsCiAgImZpbGxSdWxlIiwKICAiZmxvb2RDb2xvciIsCiAgImZsb29kT3BhY2l0eSIsCiAgImZvbnRGYW1pbHkiLAogICJmb250U2l6ZSIsCiAgImZvbnRTaXplQWRqdXN0IiwKICAiZm9udFN0cmV0Y2giLAogICJmb250U3R5bGUiLAogICJmb250VmFyaWFudCIsCiAgImZvbnRXZWlnaHQiLAogICJnbHlwaE5hbWUiLAogICJnbHlwaE9yaWVudGF0aW9uSG9yaXpvbnRhbCIsCiAgImdseXBoT3JpZW50YXRpb25WZXJ0aWNhbCIsCiAgImhvcml6QWR2WCIsCiAgImhvcml6T3JpZ2luWCIsCiAgImltYWdlUmVuZGVyaW5nIiwKICAibGV0dGVyU3BhY2luZyIsCiAgImxpZ2h0aW5nQ29sb3IiLAogICJtYXJrZXJFbmQiLAogICJtYXJrZXJNaWQiLAogICJtYXJrZXJTdGFydCIsCiAgIm92ZXJsaW5lUG9zaXRpb24iLAogICJvdmVybGluZVRoaWNrbmVzcyIsCiAgInBhaW50T3JkZXIiLAogICJwb2ludGVyRXZlbnRzIiwKICAicHJlc2VydmVBc3BlY3RSYXRpbyIsCiAgInNoYXBlUmVuZGVyaW5nIiwKICAic3RvcENvbG9yIiwKICAic3RvcE9wYWNpdHkiLAogICJzdHJva2VEYXNoYXJyYXkiLAogICJzdHJva2VEYXNob2Zmc2V0IiwKICAic3Ryb2tlTGluZWNhcCIsCiAgInN0cm9rZUxpbmVqb2luIiwKICAic3Ryb2tlTWl0ZXJsaW1pdCIsCiAgInN0cm9rZU9wYWNpdHkiLAogICJzdHJva2VXaWR0aCIsCiAgInRleHRBbmNob3IiLAogICJ0ZXh0RGVjb3JhdGlvbiIsCiAgInRleHRSZW5kZXJpbmciLAogICJ0cmFuc2Zvcm1PcmlnaW4iLAogICJ1bmRlcmxpbmVQb3NpdGlvbiIsCiAgInVuZGVybGluZVRoaWNrbmVzcyIsCiAgInVuaWNvZGVCaWRpIiwKICAidW5pY29kZVJhbmdlIiwKICAidW5pdHNQZXJFbSIsCiAgInZBbHBoYWJldGljIiwKICAidkhhbmdpbmciLAogICJ2SWRlb2dyYXBoaWMiLAogICJ2TWF0aGVtYXRpY2FsIiwKICAidmVjdG9yRWZmZWN0IiwKICAidmVydEFkdlkiLAogICJ2ZXJ0T3JpZ2luWCIsCiAgInZlcnRPcmlnaW5ZIiwKICAid29yZFNwYWNpbmciLAogICJ3cml0aW5nTW9kZSIsCl0pOwoKY29uc3QgU1ZHX1NLSVBfUFJPUFM6IFJlYWRvbmx5U2V0PHN0cmluZz4gPSBuZXcgU2V0KFsKICAiY2hpbGRyZW4iLAogICJrZXkiLAogICJyZWYiLAogICJfX3NlbGYiLAogICJfX3NvdXJjZSIsCl0pOwoKZnVuY3Rpb24gY2FtZWxUb0tlYmFiKHN0cjogc3RyaW5nKTogc3RyaW5nIHsKICByZXR1cm4gc3RyLnJlcGxhY2UoL1tBLVpdL2csIChjaCkgPT4gYC0ke2NoLnRvTG93ZXJDYXNlKCl9YCk7Cn0KCmZ1bmN0aW9uIGVzY2FwZUF0dHIodmFsdWU6IHN0cmluZyk6IHN0cmluZyB7CiAgcmV0dXJuIHZhbHVlCiAgICAucmVwbGFjZSgvJi9nLCAiJmFtcDsiKQogICAgLnJlcGxhY2UoLyIvZywgIiZxdW90OyIpCiAgICAucmVwbGFjZSgvPC9nLCAiJmx0OyIpCiAgICAucmVwbGFjZSgvPi9nLCAiJmd0OyIpOwp9CgpmdW5jdGlvbiBzZXJpYWxpemVTdmdTdHlsZShzdHlsZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBzdHJpbmcgewogIGNvbnN0IHBhcnRzOiBzdHJpbmdbXSA9IFtdOwogIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKHN0eWxlKSkgewogICAgY29uc3QgdmFsdWUgPSBzdHlsZVtrZXldOwogICAgaWYgKHZhbHVlID09IG51bGwpIGNvbnRpbnVlOwogICAgcGFydHMucHVzaChgJHtjYW1lbFRvS2ViYWIoa2V5KX06JHtTdHJpbmcodmFsdWUpLnRyaW0oKX1gKTsKICB9CiAgcmV0dXJuIHBhcnRzLmpvaW4oIjsiKTsKfQoKZnVuY3Rpb24gc2VyaWFsaXplU3ZnQXR0cihrZXk6IHN0cmluZywgdmFsdWU6IHVua25vd24pOiBzdHJpbmcgfCBudWxsIHsKICBpZiAoU1ZHX1NLSVBfUFJPUFMuaGFzKGtleSkgfHwgdmFsdWUgPT0gbnVsbCkgcmV0dXJuIG51bGw7CiAgbGV0IGF0dHJOYW1lOiBzdHJpbmc7CiAgaWYgKGtleSA9PT0gImNsYXNzTmFtZSIpIGF0dHJOYW1lID0gImNsYXNzIjsKICBlbHNlIGlmIChTVkdfQ0FNRUxfQVRUUlMuaGFzKGtleSkpIGF0dHJOYW1lID0gY2FtZWxUb0tlYmFiKGtleSk7CiAgZWxzZSBhdHRyTmFtZSA9IGtleTsKICBpZiAoa2V5ID09PSAic3R5bGUiICYmIHR5cGVvZiB2YWx1ZSA9PT0gIm9iamVjdCIpIHsKICAgIGNvbnN0IGNzcyA9IHNlcmlhbGl6ZVN2Z1N0eWxlKHZhbHVlIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KTsKICAgIGlmICghY3NzKSByZXR1cm4gbnVsbDsKICAgIHJldHVybiBgJHthdHRyTmFtZX09IiR7ZXNjYXBlQXR0cihjc3MpfSJgOwogIH0KICBpZiAodHlwZW9mIHZhbHVlID09PSAiYm9vbGVhbiIpIHJldHVybiBgJHthdHRyTmFtZX09IiR7U3RyaW5nKHZhbHVlKX0iYDsKICByZXR1cm4gYCR7YXR0ck5hbWV9PSIke2VzY2FwZUF0dHIoU3RyaW5nKHZhbHVlKSl9ImA7Cn0KCmZ1bmN0aW9uIHNlcmlhbGl6ZVN2Z1ZOb2RlKG5vZGU6IFNlcmlhbGl6ZWRWTm9kZSk6IHN0cmluZyB7CiAgaWYgKG5vZGUudHlwZSA9PT0gIiN0ZXh0IikgcmV0dXJuIG5vZGUudGV4dCA/PyAiIjsKICBjb25zdCBhdHRyczogc3RyaW5nW10gPSBbXTsKICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBPYmplY3QuZW50cmllcyhub2RlLnByb3BzKSkgewogICAgY29uc3QgYXR0ciA9IHNlcmlhbGl6ZVN2Z0F0dHIoa2V5LCB2YWx1ZSk7CiAgICBpZiAoYXR0ciAhPSBudWxsKSBhdHRycy5wdXNoKGF0dHIpOwogIH0KICBjb25zdCBjaGlsZE1hcmt1cCA9IG5vZGUuY2hpbGRyZW4ubWFwKHNlcmlhbGl6ZVN2Z1ZOb2RlKS5qb2luKCIiKTsKICBjb25zdCBhdHRyU3RyID0gYXR0cnMubGVuZ3RoID4gMCA/IGAgJHthdHRycy5qb2luKCIgIil9YCA6ICIiOwogIHJldHVybiBgPCR7bm9kZS50eXBlfSR7YXR0clN0cn0+JHtjaGlsZE1hcmt1cH08LyR7bm9kZS50eXBlfT5gOwp9CgpmdW5jdGlvbiBzZXJpYWxpemVTdmdUcmVlKHN2Z05vZGU6IFNlcmlhbGl6ZWRWTm9kZSk6IHN0cmluZyB7CiAgaWYgKCEoInhtbG5zIiBpbiBzdmdOb2RlLnByb3BzKSkgewogICAgY29uc3QgYXVnbWVudGVkID0gewogICAgICAuLi5zdmdOb2RlLAogICAgICBwcm9wczogeyAuLi5zdmdOb2RlLnByb3BzLCB4bWxuczogImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB9LAogICAgfTsKICAgIHJldHVybiBzZXJpYWxpemVTdmdWTm9kZShhdWdtZW50ZWQpOwogIH0KICByZXR1cm4gc2VyaWFsaXplU3ZnVk5vZGUoc3ZnTm9kZSk7Cn0KCi8vIOKUgOKUgCBEaXJlY3QgVk5vZGUg4oaSIFRha3VtaSBOb2RlIENvbnZlcnNpb24g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACi8vIE1pcnJvcnMgdGhlIG1haW4tdGhyZWFkIHZub2RlVG9UYWt1bWlOb2RlKCkgZnJvbSBwaXBlbGluZS50cy4KLy8gSW5saW5lZCB0byBhdm9pZCBjcm9zcy1tb2R1bGUgaW1wb3J0IGlzc3VlcyBpbiB3b3JrZXIgY29udGV4dC4KCmZ1bmN0aW9uIHZub2RlVG9UYWt1bWlOb2RlKG5vZGU6IFNlcmlhbGl6ZWRWTm9kZSk6IFRha3VtaU5vZGUgewogIC8vIFRleHQgbm9kZXMg4oaSIFRha3VtaSBUZXh0Tm9kZQogIGlmIChub2RlLnR5cGUgPT09ICIjdGV4dCIpIHsKICAgIHJldHVybiB7IHR5cGU6ICJ0ZXh0IiwgdGV4dDogbm9kZS50ZXh0ID8/ICIiIH07CiAgfQoKICBjb25zdCB7IGNoaWxkcmVuOiBfY2hpbGRyZW4sIGNsYXNzTmFtZSwgc3JjLCAuLi5yZXN0UHJvcHMgfSA9IG5vZGUucHJvcHM7CgogIC8vIE1hcCBjbGFzc05hbWUg4oaSIHR3IChzYW1lIGxvZ2ljIGFzIG1haW4gdGhyZWFkKQogIGxldCB0dzogc3RyaW5nIHwgdW5kZWZpbmVkID0gdHlwZW9mIHJlc3RQcm9wcy50dyA9PT0gInN0cmluZyIgPyByZXN0UHJvcHMudHcgOiB1bmRlZmluZWQ7CiAgaWYgKHR5cGVvZiBjbGFzc05hbWUgPT09ICJzdHJpbmciICYmIGNsYXNzTmFtZS5sZW5ndGggPiAwKSB7CiAgICB0dyA9IHR3ID8gdHcgKyAiICIgKyBjbGFzc05hbWUgOiBjbGFzc05hbWU7CiAgfQoKICAvLyBJbWFnZSBub2RlcyDihpIgVGFrdW1pIEltYWdlTm9kZQogIGlmIChub2RlLnR5cGUgPT09ICJpbWciICYmIHR5cGVvZiBzcmMgPT09ICJzdHJpbmciKSB7CiAgICByZXR1cm4gewogICAgICB0eXBlOiAiaW1hZ2UiLAogICAgICBzcmM6IHNyYyBhcyBzdHJpbmcsCiAgICAgIC4uLih0dyA/IHsgdHcgfSA6IHt9KSwKICAgICAgLi4ucmVzdFByb3BzLAogICAgfTsKICB9CgogIC8vIFNWRyBub2RlcyDihpIgVGFrdW1pIEltYWdlTm9kZSAoc2VyaWFsaXplIHN1YnRyZWUgdG8gU1ZHIG1hcmt1cCkKICBpZiAobm9kZS50eXBlID09PSAic3ZnIikgewogICAgY29uc3Qgc3ZnTWFya3VwID0gc2VyaWFsaXplU3ZnVHJlZShub2RlKTsKICAgIGNvbnN0IHdpZHRoID0gdHlwZW9mIG5vZGUucHJvcHMud2lkdGggPT09ICJudW1iZXIiID8gbm9kZS5wcm9wcy53aWR0aCA6IHVuZGVmaW5lZDsKICAgIGNvbnN0IGhlaWdodCA9IHR5cGVvZiBub2RlLnByb3BzLmhlaWdodCA9PT0gIm51bWJlciIgPyBub2RlLnByb3BzLmhlaWdodCA6IHVuZGVmaW5lZDsKICAgIHJldHVybiB7CiAgICAgIHR5cGU6ICJpbWFnZSIsCiAgICAgIHNyYzogc3ZnTWFya3VwLAogICAgICAuLi4od2lkdGggIT0gbnVsbCA/IHsgd2lkdGggfSA6IHt9KSwKICAgICAgLi4uKGhlaWdodCAhPSBudWxsID8geyBoZWlnaHQgfSA6IHt9KSwKICAgICAgLi4uKHR3ID8geyB0dyB9IDoge30pLAogICAgICAuLi4obm9kZS5wcm9wcy5zdHlsZSA/IHsgc3R5bGU6IG5vZGUucHJvcHMuc3R5bGUgfSA6IHt9KSwKICAgICAgdGFnTmFtZTogInN2ZyIsCiAgICB9OwogIH0KCiAgLy8gQWxsIG90aGVyIG5vZGVzIOKGkiBUYWt1bWkgQ29udGFpbmVyTm9kZQogIGNvbnN0IHRha3VtaUNoaWxkcmVuID0KICAgIG5vZGUuY2hpbGRyZW4ubGVuZ3RoID4gMCA/IG5vZGUuY2hpbGRyZW4ubWFwKHZub2RlVG9UYWt1bWlOb2RlKSA6IHVuZGVmaW5lZDsKCiAgcmV0dXJuIHsKICAgIHR5cGU6ICJjb250YWluZXIiLAogICAgLi4uKHR3ID8geyB0dyB9IDoge30pLAogICAgLi4ucmVzdFByb3BzLAogICAgLi4uKHRha3VtaUNoaWxkcmVuID8geyBjaGlsZHJlbjogdGFrdW1pQ2hpbGRyZW4gfSA6IHt9KSwKICB9Owp9CgovLyDilIDilIAgUm9vdCBzdHlsZSBjb25zdGFudCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCmNvbnN0IFJPT1RfU1RZTEUgPSB7IGRpc3BsYXk6ICJmbGV4Iiwgd2lkdGg6ICIxMDAlIiwgaGVpZ2h0OiAiMTAwJSIgfSBhcyBjb25zdDsKCi8vIOKUgOKUgCBXb3JrZXIgU3RhdGUg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgpsZXQgcmVuZGVyZXI6IGltcG9ydCgiQHRha3VtaS1ycy9jb3JlIikuUmVuZGVyZXIgfCBudWxsID0gbnVsbDsKCi8vIOKUgOKUgCBNZXNzYWdlIEhhbmRsZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgphc3luYyBmdW5jdGlvbiBoYW5kbGVNZXNzYWdlKG1zZzogV29ya2VyTWVzc2FnZSk6IFByb21pc2U8dm9pZD4gewogIHN3aXRjaCAobXNnLnR5cGUpIHsKICAgIGNhc2UgImluaXQiOiB7CiAgICAgIHRyeSB7CiAgICAgICAgLy8gRHluYW1pYyBpbXBvcnQg4oCUIG1heSBmYWlsIGlmIHRoZSBuYXRpdmUgYWRkb24gY2FuJ3QgbG9hZCBpbiBhIHdvcmtlcgogICAgICAgIGNvbnN0IGNvcmUgPSBhd2FpdCBpbXBvcnQoIkB0YWt1bWktcnMvY29yZSIpOwoKICAgICAgICByZW5kZXJlciA9IG5ldyBjb3JlLlJlbmRlcmVyKHsKICAgICAgICAgIGZvbnRzOiBtc2cuZm9udHMubWFwKChmKSA9PiAoewogICAgICAgICAgICBuYW1lOiBmLm5hbWUsCiAgICAgICAgICAgIGRhdGE6IGYuZGF0YSwKICAgICAgICAgICAgd2VpZ2h0OiBmLndlaWdodCBhcyAxMDAgfCAyMDAgfCAzMDAgfCA0MDAgfCA1MDAgfCA2MDAgfCA3MDAgfCA4MDAgfCA5MDAsCiAgICAgICAgICAgIHN0eWxlOiBmLnN0eWxlIGFzICJub3JtYWwiIHwgIml0YWxpYyIsCiAgICAgICAgICB9KSksCiAgICAgICAgfSk7CgogICAgICAgIHBhcmVudFBvcnQhLnBvc3RNZXNzYWdlKHsgdHlwZTogInJlYWR5IiB9KTsKICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgcGFyZW50UG9ydCEucG9zdE1lc3NhZ2UoewogICAgICAgICAgdHlwZTogImVycm9yIiwKICAgICAgICAgIGlkOiAtMSwKICAgICAgICAgIGVycm9yOiBgV29ya2VyIGluaXQgZmFpbGVkOiAke2VyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKX1gLAogICAgICAgIH0pOwogICAgICB9CiAgICAgIGJyZWFrOwogICAgfQoKICAgIGNhc2UgInJlbmRlciI6IHsKICAgICAgaWYgKHJlbmRlcmVyID09IG51bGwpIHsKICAgICAgICBwYXJlbnRQb3J0IS5wb3N0TWVzc2FnZSh7CiAgICAgICAgICB0eXBlOiAiZXJyb3IiLAogICAgICAgICAgaWQ6IG1zZy5pZCwKICAgICAgICAgIGVycm9yOiAiV29ya2VyIG5vdCBpbml0aWFsaXplZCIsCiAgICAgICAgfSk7CiAgICAgICAgcmV0dXJuOwogICAgICB9CgogICAgICB0cnkgewogICAgICAgIC8vIDEuIENvbnZlcnQgc2VyaWFsaXplZCBWTm9kZSBkYXRhIOKGkiBUYWt1bWkgbm9kZXMgZGlyZWN0bHkgKGJ5cGFzcyBmcm9tSnN4KQogICAgICAgIGNvbnN0IGNoaWxkcmVuID0gbXNnLnZub2Rlcy5tYXAodm5vZGVUb1Rha3VtaU5vZGUpOwogICAgICAgIGNvbnN0IHJvb3ROb2RlOiBUYWt1bWlOb2RlID0gewogICAgICAgICAgdHlwZTogImNvbnRhaW5lciIsCiAgICAgICAgICBzdHlsZTogUk9PVF9TVFlMRSwKICAgICAgICAgIGNoaWxkcmVuLAogICAgICAgIH07CgogICAgICAgIC8vIDIuIFJlbmRlciB0byByYXN0ZXIgaW1hZ2UKICAgICAgICBjb25zdCBidWZmZXIgPSBhd2FpdCByZW5kZXJlci5yZW5kZXIocm9vdE5vZGUsIHsKICAgICAgICAgIHdpZHRoOiBtc2cud2lkdGgsCiAgICAgICAgICBoZWlnaHQ6IG1zZy5oZWlnaHQsCiAgICAgICAgICBmb3JtYXQ6IG1zZy5mb3JtYXQgYXMgaW1wb3J0KCJAdGFrdW1pLXJzL2NvcmUiKS5PdXRwdXRGb3JtYXQsCiAgICAgICAgICBkZXZpY2VQaXhlbFJhdGlvOiBtc2cuZHByLAogICAgICAgIH0pOwoKICAgICAgICAvLyBUcmFuc2ZlciB0aGUgYnVmZmVyICh6ZXJvLWNvcHkpIGJhY2sgdG8gdGhlIG1haW4gdGhyZWFkCiAgICAgICAgY29uc3QgYWIgPQogICAgICAgICAgYnVmZmVyIGluc3RhbmNlb2YgQXJyYXlCdWZmZXIKICAgICAgICAgICAgPyBidWZmZXIKICAgICAgICAgICAgOiBidWZmZXIuYnVmZmVyLnNsaWNlKGJ1ZmZlci5ieXRlT2Zmc2V0LCBidWZmZXIuYnl0ZU9mZnNldCArIGJ1ZmZlci5ieXRlTGVuZ3RoKTsKCiAgICAgICAgcGFyZW50UG9ydCEucG9zdE1lc3NhZ2UoCiAgICAgICAgICB7IHR5cGU6ICJyZXN1bHQiLCBpZDogbXNnLmlkLCBidWZmZXI6IGFiIH0sCiAgICAgICAgICB7IHRyYW5zZmVyOiBbYWIgYXMgQXJyYXlCdWZmZXJdIH0sCiAgICAgICAgKTsKICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgcGFyZW50UG9ydCEucG9zdE1lc3NhZ2UoewogICAgICAgICAgdHlwZTogImVycm9yIiwKICAgICAgICAgIGlkOiBtc2cuaWQsCiAgICAgICAgICBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpLAogICAgICAgIH0pOwogICAgICB9CiAgICAgIGJyZWFrOwogICAgfQoKICAgIGNhc2UgInNodXRkb3duIjogewogICAgICBwcm9jZXNzLmV4aXQoMCk7CiAgICB9CiAgfQp9CgovLyDilIDilIAgQXV0by1pbml0IGlmIGZvbnRzIHByb3ZpZGVkIHZpYSB3b3JrZXJEYXRhIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKaWYgKHdvcmtlckRhdGE/LmZvbnRzKSB7CiAgaGFuZGxlTWVzc2FnZSh7IHR5cGU6ICJpbml0IiwgZm9udHM6IHdvcmtlckRhdGEuZm9udHMgfSk7Cn0KCnBhcmVudFBvcnQhLm9uKCJtZXNzYWdlIiwgKG1zZzogV29ya2VyTWVzc2FnZSkgPT4gewogIGhhbmRsZU1lc3NhZ2UobXNnKTsKfSk7Cg==", "" + import.meta.url), { workerData: { fonts: this.fonts.map((f) => ({
40
+ name: f.name,
41
+ data: f.data,
42
+ weight: f.weight,
43
+ style: f.style
44
+ })) } });
45
+ await new Promise((resolve, reject) => {
46
+ const timeout = setTimeout(() => {
47
+ reject(/* @__PURE__ */ new Error("Worker initialization timed out (5s)"));
48
+ }, 5e3);
49
+ const onMessage = (msg) => {
50
+ if (msg.type === "ready") {
51
+ clearTimeout(timeout);
52
+ this.worker.off("message", onMessage);
53
+ this.worker.off("error", onError);
54
+ resolve();
55
+ } else if (msg.type === "error" && msg.id === -1) {
56
+ clearTimeout(timeout);
57
+ this.worker.off("message", onMessage);
58
+ this.worker.off("error", onError);
59
+ reject(new Error(msg.error ?? "Unknown worker init error"));
60
+ }
61
+ };
62
+ const onError = (err) => {
63
+ clearTimeout(timeout);
64
+ reject(err);
65
+ };
66
+ this.worker.on("message", onMessage);
67
+ this.worker.on("error", onError);
68
+ });
69
+ this.worker.on("message", this.handleResponse.bind(this));
70
+ this.worker.on("exit", (code) => {
71
+ if (code !== 0) console.warn(`[@fcannizzaro/streamdeck-react] Render worker exited with code ${code}`);
72
+ this.handleWorkerDeath();
73
+ });
74
+ this.worker.on("error", (err) => {
75
+ console.error("[@fcannizzaro/streamdeck-react] Render worker error:", err);
76
+ this.handleWorkerDeath();
77
+ });
78
+ this.ready = true;
79
+ } catch (err) {
80
+ console.warn("[@fcannizzaro/streamdeck-react] Worker initialization failed, falling back to main-thread rendering:", err instanceof Error ? err.message : err);
81
+ this.failed = true;
82
+ this.worker?.terminate().catch(() => {});
83
+ this.worker = null;
84
+ }
85
+ }
86
+ /** Whether the worker is available for offloaded rendering. */
87
+ get isAvailable() {
88
+ return this.ready && this.worker != null;
89
+ }
90
+ /**
91
+ * Render VNode children in the worker thread.
92
+ * Returns the raw raster buffer.
93
+ */
94
+ async render(vnodes, width, height, format, dpr) {
95
+ if (!this.isAvailable) throw new Error("Worker not available");
96
+ const id = this.nextId++;
97
+ const serialized = vnodes.map(serializeVNode);
98
+ return new Promise((resolve, reject) => {
99
+ this.pending.set(id, {
100
+ resolve,
101
+ reject
102
+ });
103
+ this.worker.postMessage({
104
+ type: "render",
105
+ id,
106
+ vnodes: serialized,
107
+ width,
108
+ height,
109
+ format,
110
+ dpr
111
+ });
112
+ });
113
+ }
114
+ handleResponse(msg) {
115
+ if (msg.type === "ready") return;
116
+ const req = this.pending.get(msg.id);
117
+ if (req == null) return;
118
+ this.pending.delete(msg.id);
119
+ if (msg.type === "result" && msg.buffer != null) req.resolve(Buffer.from(msg.buffer));
120
+ else if (msg.type === "error") req.reject(new Error(msg.error ?? "Unknown worker render error"));
121
+ }
122
+ handleWorkerDeath() {
123
+ this.ready = false;
124
+ this.worker = null;
125
+ for (const [_, req] of this.pending) req.reject(/* @__PURE__ */ new Error("Worker died unexpectedly"));
126
+ this.pending.clear();
127
+ }
128
+ };
129
+ //#endregion
130
+ export { RenderPool };
@@ -0,0 +1,7 @@
1
+ import { VNode } from '../reconciler/vnode';
2
+ /**
3
+ * Serialize an `<svg>` VNode (and its entire subtree) to an SVG markup string.
4
+ * Auto-injects `xmlns="http://www.w3.org/2000/svg"` if not present.
5
+ * The returned string can be used as the `src` of a Takumi ImageNode.
6
+ */
7
+ export declare function serializeSvgTree(svgNode: VNode): string;