@meonode/canvas 1.7.1 → 2.0.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.
Files changed (48) hide show
  1. package/README.md +161 -7
  2. package/dist/cjs/canvas/canvas.type.d.ts +68 -5
  3. package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
  4. package/dist/cjs/canvas/image.canvas.util.d.ts +11 -35
  5. package/dist/cjs/canvas/image.canvas.util.d.ts.map +1 -1
  6. package/dist/cjs/canvas/image.canvas.util.js +37 -109
  7. package/dist/cjs/canvas/image.canvas.util.js.map +1 -1
  8. package/dist/cjs/canvas/root.canvas.util.d.ts +27 -7
  9. package/dist/cjs/canvas/root.canvas.util.d.ts.map +1 -1
  10. package/dist/cjs/canvas/root.canvas.util.js +59 -39
  11. package/dist/cjs/canvas/root.canvas.util.js.map +1 -1
  12. package/dist/cjs/index.d.ts +3 -2
  13. package/dist/cjs/index.d.ts.map +1 -1
  14. package/dist/cjs/index.js +3 -1
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/util/disk.cache.d.ts +10 -0
  17. package/dist/cjs/util/disk.cache.d.ts.map +1 -0
  18. package/dist/cjs/util/disk.cache.js +73 -0
  19. package/dist/cjs/util/disk.cache.js.map +1 -0
  20. package/dist/cjs/worker/render.worker.d.ts.map +1 -0
  21. package/dist/cjs/{render.worker.js → worker/render.worker.js} +1 -1
  22. package/dist/cjs/worker/render.worker.js.map +1 -0
  23. package/dist/{esm/canvas → cjs/worker}/worker.types.d.ts.map +1 -1
  24. package/dist/esm/canvas/canvas.type.d.ts +68 -5
  25. package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
  26. package/dist/esm/canvas/image.canvas.util.d.ts +11 -35
  27. package/dist/esm/canvas/image.canvas.util.d.ts.map +1 -1
  28. package/dist/esm/canvas/image.canvas.util.js +38 -107
  29. package/dist/esm/canvas/root.canvas.util.d.ts +27 -7
  30. package/dist/esm/canvas/root.canvas.util.d.ts.map +1 -1
  31. package/dist/esm/canvas/root.canvas.util.js +60 -41
  32. package/dist/esm/index.d.ts +3 -2
  33. package/dist/esm/index.d.ts.map +1 -1
  34. package/dist/esm/index.js +3 -2
  35. package/dist/esm/util/disk.cache.d.ts +10 -0
  36. package/dist/esm/util/disk.cache.d.ts.map +1 -0
  37. package/dist/esm/util/disk.cache.js +66 -0
  38. package/dist/esm/worker/render.worker.d.ts.map +1 -0
  39. package/dist/esm/{render.worker.js → worker/render.worker.js} +1 -1
  40. package/dist/{cjs/canvas → esm/worker}/worker.types.d.ts.map +1 -1
  41. package/package.json +2 -1
  42. package/dist/cjs/render.worker.d.ts.map +0 -1
  43. package/dist/cjs/render.worker.js.map +0 -1
  44. package/dist/esm/render.worker.d.ts.map +0 -1
  45. /package/dist/cjs/{render.worker.d.ts → worker/render.worker.d.ts} +0 -0
  46. /package/dist/cjs/{canvas → worker}/worker.types.d.ts +0 -0
  47. /package/dist/esm/{render.worker.d.ts → worker/render.worker.d.ts} +0 -0
  48. /package/dist/esm/{canvas → worker}/worker.types.d.ts +0 -0
@@ -1,6 +1,7 @@
1
1
  import { FontLibrary, Canvas } from 'skia-canvas';
2
2
  import { ColumnNode, RowNode, BoxNode } from './layout.canvas.util.js';
3
- import { ImageNode, disposeImageCache, getImageCache } from './image.canvas.util.js';
3
+ import { ImageNode } from './image.canvas.util.js';
4
+ import { deleteDiskCache } from '../util/disk.cache.js';
4
5
  import { TextNode } from './text.canvas.util.js';
5
6
  import { ChartNode } from './chart.canvas.util.js';
6
7
  import { GridItemNode, GridNode } from './grid.canvas.util.js';
@@ -14,26 +15,48 @@ import { fileURLToPath } from 'node:url';
14
15
 
15
16
  /** Registry to track fonts that have already been loaded */
16
17
  const registeredFonts = new Map();
17
- /** Engine configuration */
18
- let _workerMode = true;
19
- let _workerPoolSize = Math.max(1, cpus().length - 1);
18
+ /**
19
+ * FinalizationRegistry to clean up WorkerCanvas instances that were not explicitly released.
20
+ * This is a safety net — users should still call .release() explicitly.
21
+ */
22
+ const canvasRegistry = new FinalizationRegistry(heldValue => {
23
+ // Best-effort cleanup — worker may already be terminated
24
+ try {
25
+ // Access workers via a public method or make it accessible
26
+ // For now, just try to send the message and let errors be caught
27
+ if (_workerPool) {
28
+ ;
29
+ _workerPool.workers?.[heldValue.workerIdx]?.postMessage({ type: 'release', canvasId: heldValue.canvasId });
30
+ }
31
+ }
32
+ catch {
33
+ // Worker already gone — nothing to clean up
34
+ }
35
+ });
36
+ /** Engine configuration — legacy support for configure() */
37
+ let _defaultWorkerMode = true;
38
+ let _defaultWorkerPoolSize = Math.max(1, cpus().length - 1);
20
39
  let _workerPool = null;
21
40
  /**
22
41
  * Configure the canvas rendering engine.
23
42
  * Call this once at application startup before rendering.
43
+ * @deprecated Pass workerMode and workers directly to Root() props instead.
24
44
  */
25
45
  function configure(options) {
26
46
  if (options.workerMode !== undefined)
27
- _workerMode = options.workerMode;
47
+ _defaultWorkerMode = options.workerMode;
28
48
  if (options.workers !== undefined)
29
- _workerPoolSize = options.workers;
30
- if (options.imageCacheSize !== undefined) {
31
- // Dispose existing cache and create a new one with the specified size
32
- disposeImageCache();
33
- getImageCache(options.imageCacheSize);
34
- }
35
- if (_workerMode) {
36
- _workerPool = new WorkerPool(_workerPoolSize);
49
+ _defaultWorkerPoolSize = options.workers;
50
+ }
51
+ /**
52
+ * Terminate all worker pools and free worker thread resources.
53
+ * Call this when shutting down a long-running server to clean up immediately.
54
+ * After calling, you must call configure() again before rendering.
55
+ */
56
+ function terminate() {
57
+ if (_workerPool) {
58
+ _workerPool.terminate();
59
+ _workerPool = null;
37
60
  }
38
61
  }
39
62
  /**
@@ -55,6 +78,8 @@ class WorkerCanvas {
55
78
  this._pool = opts.pool;
56
79
  this._workerIdx = opts.workerIdx;
57
80
  this._canvasId = opts.canvasId;
81
+ // Register for finalizer-based cleanup if user forgets to call .release()
82
+ canvasRegistry.register(this, { workerIdx: opts.workerIdx, canvasId: opts.canvasId }, this);
58
83
  }
59
84
  _call(method, ...args) {
60
85
  return this._pool.callOnCanvas(this._workerIdx, this._canvasId, method, args);
@@ -105,6 +130,8 @@ class WorkerCanvas {
105
130
  /** Release the Canvas from worker memory. Call when done with this object. */
106
131
  release() {
107
132
  this._pool.releaseCanvas(this._workerIdx, this._canvasId);
133
+ // Unregister from finalizer since we're explicitly cleaning up
134
+ canvasRegistry.unregister(this);
108
135
  }
109
136
  }
110
137
  /** Worker thread pool — routes render and canvas-call messages */
@@ -118,7 +145,7 @@ class WorkerPool {
118
145
  this.init(size);
119
146
  }
120
147
  init(size) {
121
- const workerFile = path.join(path.dirname(fileURLToPath(import.meta.url)), '../render.worker.js');
148
+ const workerFile = path.join(path.dirname(fileURLToPath(import.meta.url)), '../worker/render.worker.js');
122
149
  for (let i = 0; i < size; i++) {
123
150
  const workerIdx = i;
124
151
  const worker = new Worker(workerFile);
@@ -295,6 +322,7 @@ class RootNode extends ColumnNode {
295
322
  * @returns Promise resolving to the rendered Canvas instance
296
323
  */
297
324
  async render() {
325
+ const diskCacheKeys = this.props.useDiskCache ? new Set() : undefined;
298
326
  try {
299
327
  // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
300
328
  // A per-render cache deduplicates identical src+color combinations within this render pass.
@@ -306,7 +334,7 @@ class RootNode extends ColumnNode {
306
334
  const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
307
335
  while (queue.length > 0) {
308
336
  const node = queue.shift();
309
- await node.load(imageCache);
337
+ await node.load(imageCache, diskCacheKeys);
310
338
  }
311
339
  });
312
340
  await Promise.allSettled(workers);
@@ -334,35 +362,26 @@ class RootNode extends ColumnNode {
334
362
  return this.canvas;
335
363
  }
336
364
  finally {
337
- // Always clear the persistent image cache after render (success or error)
338
- // so resolved CanvasImage references don't outlive the render pass.
339
- disposeImageCache();
365
+ if (diskCacheKeys?.size) {
366
+ await Promise.allSettled([...diskCacheKeys].map(key => deleteDiskCache(key)));
367
+ }
340
368
  }
341
369
  }
342
370
  }
343
- /**
344
- * Creates and renders a new root node with the given properties.
345
- * When worker mode is enabled via configure(), rendering runs in a worker thread
346
- * and the returned object implements the same toBuffer/toBufferSync interface.
347
- * @param props Configuration properties for the root node
348
- * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)
349
- */
350
- const Root = async (props) => {
351
- try {
352
- if (_workerMode) {
353
- if (!_workerPool) {
354
- _workerPool = new WorkerPool(_workerPoolSize);
355
- }
356
- const result = await _workerPool.render(props);
357
- return new WorkerCanvas({ ...result, pool: _workerPool });
371
+ async function Root(props) {
372
+ // Determine worker mode: props override legacy configure()
373
+ const workerMode = props.workerMode ?? _defaultWorkerMode;
374
+ const workerPoolSize = props.workers ?? _defaultWorkerPoolSize;
375
+ if (workerMode) {
376
+ // Lazy initialize worker pool
377
+ if (!_workerPool) {
378
+ _workerPool = new WorkerPool(workerPoolSize);
358
379
  }
359
- return await new RootNode(props).render();
380
+ const result = await _workerPool.render(props);
381
+ return new WorkerCanvas({ ...result, pool: _workerPool });
360
382
  }
361
- catch (err) {
362
- // Ensure cache is cleared even if Root-level orchestration fails
363
- disposeImageCache();
364
- throw err;
365
- }
366
- };
383
+ // Non-worker mode — render directly and return Canvas
384
+ return new RootNode(props).render();
385
+ }
367
386
 
368
- export { Root, RootNode, buildTree, configure };
387
+ export { Root, RootNode, buildTree, configure, terminate };
@@ -1,10 +1,11 @@
1
1
  export * from './constant/common.const.js';
2
2
  export * from './canvas/canvas.type.js';
3
3
  export { Box, Column, Row, type BoxNode } from './canvas/layout.canvas.util.js';
4
- export { Image, disposeImageCache } from './canvas/image.canvas.util.js';
4
+ export { Image } from './canvas/image.canvas.util.js';
5
5
  export { Text } from './canvas/text.canvas.util.js';
6
- export { Root, configure } from './canvas/root.canvas.util.js';
6
+ export { Root, configure, terminate } from './canvas/root.canvas.util.js';
7
7
  export { GridItem } from './canvas/grid.canvas.util.js';
8
8
  export { Grid } from './canvas/grid.canvas.util.js';
9
9
  export { Chart } from './canvas/chart.canvas.util.js';
10
+ export { clearDiskCache } from './util/disk.cache.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAA;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  export { Border, Style } from './constant/common.const.js';
2
2
  export { Box, Column, Row } from './canvas/layout.canvas.util.js';
3
- export { Image, disposeImageCache } from './canvas/image.canvas.util.js';
3
+ export { Image } from './canvas/image.canvas.util.js';
4
4
  export { Text } from './canvas/text.canvas.util.js';
5
- export { Root, configure } from './canvas/root.canvas.util.js';
5
+ export { Root, configure, terminate } from './canvas/root.canvas.util.js';
6
6
  export { Grid, GridItem } from './canvas/grid.canvas.util.js';
7
7
  export { Chart } from './canvas/chart.canvas.util.js';
8
+ export { clearDiskCache } from './util/disk.cache.js';
8
9
  export * from 'yoga-layout';
@@ -0,0 +1,10 @@
1
+ export declare function hashBuffer(buf: Buffer): string;
2
+ export declare function readDiskCache(key: string): Promise<Buffer | null>;
3
+ export declare function writeDiskCache(key: string, data: Buffer): Promise<void>;
4
+ export declare function deleteDiskCache(key: string): Promise<void>;
5
+ /**
6
+ * Delete the entire disk cache directory.
7
+ * Called on process exit to clean up any orphaned cache files.
8
+ */
9
+ export declare function clearDiskCache(): Promise<void>;
10
+ //# sourceMappingURL=disk.cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk.cache.d.ts","sourceRoot":"","sources":["../../../src/util/disk.cache.ts"],"names":[],"mappings":"AAaA,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOvE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7E;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAMpD"}
@@ -0,0 +1,66 @@
1
+ import { createHash } from 'crypto';
2
+ import { promises } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ const CACHE_DIR = join(process.cwd(), '.cache', 'files');
6
+ let _dirEnsured = false;
7
+ async function ensureDir() {
8
+ if (_dirEnsured)
9
+ return;
10
+ await promises.mkdir(CACHE_DIR, { recursive: true });
11
+ _dirEnsured = true;
12
+ }
13
+ function hashBuffer(buf) {
14
+ return createHash('sha256').update(buf).digest('hex');
15
+ }
16
+ async function readDiskCache(key) {
17
+ try {
18
+ await ensureDir();
19
+ return await promises.readFile(join(CACHE_DIR, key));
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ async function writeDiskCache(key, data) {
26
+ try {
27
+ await ensureDir();
28
+ await promises.writeFile(join(CACHE_DIR, key), data);
29
+ }
30
+ catch {
31
+ // best-effort — cache write failures are non-fatal
32
+ }
33
+ }
34
+ async function deleteDiskCache(key) {
35
+ try {
36
+ await promises.unlink(join(CACHE_DIR, key));
37
+ }
38
+ catch {
39
+ // non-fatal — file may not exist if write failed earlier
40
+ }
41
+ }
42
+ /**
43
+ * Delete the entire disk cache directory.
44
+ * Called on process exit to clean up any orphaned cache files.
45
+ */
46
+ async function clearDiskCache() {
47
+ try {
48
+ await promises.rm(CACHE_DIR, { recursive: true, force: true });
49
+ }
50
+ catch {
51
+ // non-fatal — directory may not exist
52
+ }
53
+ }
54
+ // Clean up disk cache on process exit to handle crashes mid-render
55
+ process.on('beforeExit', () => {
56
+ // Fire and forget — best effort cleanup
57
+ clearDiskCache();
58
+ });
59
+ // Also clean up on SIGINT/SIGTERM for graceful shutdowns
60
+ const cleanupOnExit = () => {
61
+ clearDiskCache().finally(() => process.exit(0));
62
+ };
63
+ process.on('SIGINT', cleanupOnExit);
64
+ process.on('SIGTERM', cleanupOnExit);
65
+
66
+ export { clearDiskCache, deleteDiskCache, hashBuffer, readDiskCache, writeDiskCache };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.worker.d.ts","sourceRoot":"","sources":["../../../src/worker/render.worker.ts"],"names":[],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  import { parentPort } from 'worker_threads';
2
- import { RootNode } from './canvas/root.canvas.util.js';
2
+ import { RootNode } from '../canvas/root.canvas.util.js';
3
3
 
4
4
  /**
5
5
  * Worker thread entry point for off-main-thread canvas rendering.
@@ -1 +1 @@
1
- {"version":3,"file":"worker.types.d.ts","sourceRoot":"","sources":["../../../src/canvas/worker.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worker.types.d.ts","sourceRoot":"","sources":["../../../src/worker/worker.types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meonode/canvas",
3
- "version": "1.7.1",
3
+ "version": "2.0.0",
4
4
  "description": "A declarative, component-based library for server-side canvas image generation. Write complex visuals with simple functions, similar to the composition style of @meonode/ui.",
5
5
  "keywords": [
6
6
  "canvas",
@@ -42,6 +42,7 @@
42
42
  "lint": "eslint . --ext .ts,.tsx,.js,.jsx,.mjs --fix",
43
43
  "format": "prettier --write .",
44
44
  "generate:samples": "npx tsx scripts/generate_sample_charts.ts && npx tsx scripts/generate_sample_grids.ts && npx tsx scripts/generate_sample_nested_grids.ts",
45
+ "check:memory": "npx tsx --expose-gc scripts/check_memory.ts",
45
46
  "prepare": "husky"
46
47
  },
47
48
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"render.worker.d.ts","sourceRoot":"","sources":["../../src/render.worker.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"render.worker.js","sources":["../../../src/render.worker.ts"],"sourcesContent":["/**\n * Worker thread entry point for off-main-thread canvas rendering.\n *\n * Message protocol (main → worker):\n * { type: 'render', taskId, props } — render and keep Canvas alive\n * { type: 'call', taskId, canvasId, method, args } — call a method on a live Canvas\n * { type: 'release', canvasId } — free Canvas from memory\n *\n * Responses (worker → main):\n * WorkerRenderResponse — render complete (includes pre-encoded PNG buffer)\n * WorkerCallResponse — method call result\n * WorkerErrorResponse — any failure\n */\nimport { parentPort } from 'worker_threads'\nimport { RootNode } from '@/canvas/root.canvas.util.js'\nimport type { Canvas } from 'skia-canvas'\nimport type { WorkerRequest, WorkerRenderResponse, WorkerCallResponse, WorkerErrorResponse } from '@/canvas/worker.types.js'\n\nif (!parentPort) {\n throw new Error('[render.worker] Must be run as a worker thread')\n}\n\nconst canvases = new Map<number, Canvas>()\nlet nextCanvasId = 0\n\nfunction reply(msg: WorkerRenderResponse | WorkerCallResponse | WorkerErrorResponse) {\n parentPort!.postMessage(msg)\n}\n\nparentPort.on('message', async (msg: WorkerRequest) => {\n if (msg.type === 'render') {\n try {\n const canvas = await new RootNode(msg.props).render()\n const canvasId = nextCanvasId++\n canvases.set(canvasId, canvas)\n reply({ taskId: msg.taskId, canvasId, buffer: canvas.toBufferSync('png'), width: canvas.width, height: canvas.height })\n } catch (err) {\n reply({ taskId: msg.taskId, error: String(err) })\n }\n } else if (msg.type === 'call') {\n const canvas = canvases.get(msg.canvasId)\n if (!canvas) {\n reply({ taskId: msg.taskId, error: `[render.worker] Canvas ${msg.canvasId} not found` })\n return\n }\n try {\n let result: Buffer | string | void\n switch (msg.method) {\n case 'toBuffer':\n result = await canvas.toBuffer(...msg.args)\n break\n case 'toURL':\n result = await canvas.toURL(...msg.args)\n break\n case 'toFile':\n result = await canvas.toFile(...msg.args)\n break\n case 'toSharp':\n // Sharp instances can't be transferred across threads — serialize to buffer\n result = await canvas.toSharp(...msg.args).toBuffer()\n break\n }\n reply({ taskId: msg.taskId, result })\n } catch (err) {\n reply({ taskId: msg.taskId, error: String(err) })\n }\n } else {\n // type === 'release'\n canvases.delete(msg.canvasId)\n }\n})\n"],"names":["parentPort","RootNode"],"mappings":";;;;;AAAA;;;;;;;;;;;;AAYG;AAMH,IAAI,CAACA,yBAAU,EAAE;AACf,IAAA,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC;AACnE;AAEA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB;AAC1C,IAAI,YAAY,GAAG,CAAC;AAEpB,SAAS,KAAK,CAAC,GAAoE,EAAA;AACjF,IAAAA,yBAAW,CAAC,WAAW,CAAC,GAAG,CAAC;AAC9B;AAEAA,yBAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,GAAkB,KAAI;AACpD,IAAA,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE;AACzB,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,MAAM,IAAIC,yBAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE;AACrD,YAAA,MAAM,QAAQ,GAAG,YAAY,EAAE;AAC/B,YAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC;AAC9B,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACzH;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD;IACF;AAAO,SAAA,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE;QAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE;AACX,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,0BAA0B,GAAG,CAAC,QAAQ,CAAA,UAAA,CAAY,EAAE,CAAC;YACxF;QACF;AACA,QAAA,IAAI;AACF,YAAA,IAAI,MAA8B;AAClC,YAAA,QAAQ,GAAG,CAAC,MAAM;AAChB,gBAAA,KAAK,UAAU;oBACb,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;oBAC3C;AACF,gBAAA,KAAK,OAAO;oBACV,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;oBACxC;AACF,gBAAA,KAAK,QAAQ;oBACX,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;oBACzC;AACF,gBAAA,KAAK,SAAS;;AAEZ,oBAAA,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;oBACrD;;YAEJ,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QACvC;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD;IACF;SAAO;;AAEL,QAAA,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC/B;AACF,CAAC,CAAC;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"render.worker.d.ts","sourceRoot":"","sources":["../../src/render.worker.ts"],"names":[],"mappings":""}
File without changes
File without changes