@meonode/canvas 2.0.5 → 3.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 (69) hide show
  1. package/CONTRIBUTING.md +9 -9
  2. package/README.md +9 -21
  3. package/dist/cjs/canvas/canvas.helper.d.ts +1 -1
  4. package/dist/cjs/canvas/canvas.type.d.ts +9 -4
  5. package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
  6. package/dist/cjs/canvas/chart.canvas.d.ts +7 -3
  7. package/dist/cjs/canvas/chart.canvas.d.ts.map +1 -1
  8. package/dist/cjs/canvas/chart.canvas.js +3 -4
  9. package/dist/cjs/canvas/chart.canvas.js.map +1 -1
  10. package/dist/cjs/canvas/grid.canvas.d.ts +2 -2
  11. package/dist/cjs/canvas/grid.canvas.d.ts.map +1 -1
  12. package/dist/cjs/canvas/grid.canvas.js +2 -8
  13. package/dist/cjs/canvas/grid.canvas.js.map +1 -1
  14. package/dist/cjs/canvas/image.canvas.d.ts +2 -2
  15. package/dist/cjs/canvas/image.canvas.js.map +1 -1
  16. package/dist/cjs/canvas/layout.canvas.d.ts +5 -1
  17. package/dist/cjs/canvas/layout.canvas.d.ts.map +1 -1
  18. package/dist/cjs/canvas/layout.canvas.js +59 -68
  19. package/dist/cjs/canvas/layout.canvas.js.map +1 -1
  20. package/dist/cjs/canvas/root.canvas.d.ts +9 -16
  21. package/dist/cjs/canvas/root.canvas.d.ts.map +1 -1
  22. package/dist/cjs/canvas/root.canvas.js +56 -43
  23. package/dist/cjs/canvas/root.canvas.js.map +1 -1
  24. package/dist/cjs/canvas/text.canvas.d.ts +7 -3
  25. package/dist/cjs/canvas/text.canvas.d.ts.map +1 -1
  26. package/dist/cjs/canvas/text.canvas.js +25 -85
  27. package/dist/cjs/canvas/text.canvas.js.map +1 -1
  28. package/dist/cjs/index.d.ts +2 -2
  29. package/dist/cjs/index.d.ts.map +1 -1
  30. package/dist/cjs/index.js +1 -1
  31. package/dist/cjs/util/disk.cache.d.ts +5 -0
  32. package/dist/cjs/util/disk.cache.d.ts.map +1 -1
  33. package/dist/cjs/util/disk.cache.js +23 -8
  34. package/dist/cjs/util/disk.cache.js.map +1 -1
  35. package/dist/cjs/worker/comlink.pool.d.ts +1 -1
  36. package/dist/cjs/worker/comlink.pool.d.ts.map +1 -1
  37. package/dist/cjs/worker/comlink.pool.js +3 -0
  38. package/dist/cjs/worker/comlink.pool.js.map +1 -1
  39. package/dist/cjs/worker/comlink.setup.js +0 -1
  40. package/dist/cjs/worker/comlink.setup.js.map +1 -1
  41. package/dist/esm/canvas/canvas.helper.d.ts +1 -1
  42. package/dist/esm/canvas/canvas.type.d.ts +9 -4
  43. package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
  44. package/dist/esm/canvas/chart.canvas.d.ts +7 -3
  45. package/dist/esm/canvas/chart.canvas.d.ts.map +1 -1
  46. package/dist/esm/canvas/chart.canvas.js +3 -4
  47. package/dist/esm/canvas/grid.canvas.d.ts +2 -2
  48. package/dist/esm/canvas/grid.canvas.d.ts.map +1 -1
  49. package/dist/esm/canvas/grid.canvas.js +1 -7
  50. package/dist/esm/canvas/image.canvas.d.ts +2 -2
  51. package/dist/esm/canvas/layout.canvas.d.ts +5 -1
  52. package/dist/esm/canvas/layout.canvas.d.ts.map +1 -1
  53. package/dist/esm/canvas/layout.canvas.js +59 -69
  54. package/dist/esm/canvas/root.canvas.d.ts +9 -16
  55. package/dist/esm/canvas/root.canvas.d.ts.map +1 -1
  56. package/dist/esm/canvas/root.canvas.js +57 -43
  57. package/dist/esm/canvas/text.canvas.d.ts +7 -3
  58. package/dist/esm/canvas/text.canvas.d.ts.map +1 -1
  59. package/dist/esm/canvas/text.canvas.js +25 -85
  60. package/dist/esm/index.d.ts +2 -2
  61. package/dist/esm/index.d.ts.map +1 -1
  62. package/dist/esm/index.js +2 -2
  63. package/dist/esm/util/disk.cache.d.ts +5 -0
  64. package/dist/esm/util/disk.cache.d.ts.map +1 -1
  65. package/dist/esm/util/disk.cache.js +23 -9
  66. package/dist/esm/worker/comlink.pool.d.ts +1 -1
  67. package/dist/esm/worker/comlink.pool.d.ts.map +1 -1
  68. package/dist/esm/worker/comlink.pool.js +3 -0
  69. package/package.json +9 -16
@@ -12,6 +12,7 @@ import { cpus } from 'node:os';
12
12
 
13
13
  /** Registry to track fonts that have already been loaded */
14
14
  const registeredFonts = new Map();
15
+ let _fontRegistrationLock = null;
15
16
  /**
16
17
  * FinalizationRegistry to clean up WorkerCanvas instances that were not explicitly released.
17
18
  * This is a safety net — users should still call .release() explicitly.
@@ -24,25 +25,10 @@ const canvasRegistry = new FinalizationRegistry(heldValue => {
24
25
  // Worker already gone — nothing to clean up
25
26
  }
26
27
  });
27
- /** Engine configuration — legacy support for configure() */
28
- let _defaultWorkerMode = true;
29
- let _defaultWorkerPoolSize = Math.max(1, cpus().length - 1);
30
28
  let _workerPool = null;
31
- /**
32
- * Configure the canvas rendering engine.
33
- * Call this once at application startup before rendering.
34
- * @deprecated Pass workerMode and workers directly to Root() props instead.
35
- */
36
- function configure(options) {
37
- if (options.workerMode !== undefined)
38
- _defaultWorkerMode = options.workerMode;
39
- if (options.workers !== undefined)
40
- _defaultWorkerPoolSize = options.workers;
41
- }
42
29
  /**
43
30
  * Terminate all worker pools and free worker thread resources.
44
31
  * Call this when shutting down a long-running server to clean up immediately.
45
- * After calling, you must call configure() again before rendering.
46
32
  */
47
33
  function terminate() {
48
34
  if (_workerPool) {
@@ -159,6 +145,8 @@ class RootNode extends ColumnNode {
159
145
  targetHeight;
160
146
  /** Scale factor for rendering (e.g. 2 for 2x resolution) */
161
147
  scale;
148
+ /** Max concurrent image fetches during render (default: 5) */
149
+ imageConcurrency;
162
150
  /**
163
151
  * Creates a new root node for canvas rendering
164
152
  * @param props Configuration properties for the root node
@@ -172,36 +160,22 @@ class RootNode extends ColumnNode {
172
160
  if (!props.width) {
173
161
  throw new Error('Width and height are required for Root');
174
162
  }
175
- // Register provided fonts with caching
176
- if (props.fonts?.length) {
177
- for (const font of props.fonts) {
178
- const family = font.family;
179
- const paths = font.paths.map(p => path.resolve(p));
180
- if (!registeredFonts.has(family)) {
181
- registeredFonts.set(family, new Set());
182
- }
183
- const cachedPaths = registeredFonts.get(family);
184
- const newPaths = paths.filter(p => !cachedPaths.has(p) && fs.existsSync(p));
185
- if (newPaths.length > 0) {
186
- FontLibrary.use({ [family]: newPaths });
187
- newPaths.forEach(p => cachedPaths.add(p));
188
- }
189
- }
190
- }
191
163
  // Set up scale and width
192
164
  this.scale = props.scale || 1;
193
165
  this.targetWidth = props.width;
194
166
  this.targetHeight = props.height;
167
+ this.imageConcurrency = props.imageConcurrency ?? 5;
195
168
  this.node.setWidth(this.targetWidth);
196
169
  // Convert any CanvasElement children to actual BoxNode instances
197
170
  if (this.props.children) {
198
171
  const childArray = Array.isArray(this.props.children) ? this.props.children : [this.props.children];
199
- this.props.children = childArray.map(child => {
172
+ const converted = childArray.map(child => {
200
173
  if (child && typeof child === 'object' && '__type' in child) {
201
174
  return buildTree(child);
202
175
  }
203
176
  return child;
204
177
  });
178
+ this.props.children = converted;
205
179
  }
206
180
  // Initialize children nodes
207
181
  this.processInitialChildren();
@@ -213,8 +187,9 @@ class RootNode extends ColumnNode {
213
187
  findAllImageNodes() {
214
188
  const imageNodes = [];
215
189
  const queue = [this];
216
- while (queue.length > 0) {
217
- const node = queue.shift();
190
+ let head = 0;
191
+ while (head < queue.length) {
192
+ const node = queue[head++];
218
193
  if (node instanceof ImageNode) {
219
194
  imageNodes.push(node);
220
195
  }
@@ -222,12 +197,46 @@ class RootNode extends ColumnNode {
222
197
  }
223
198
  return imageNodes;
224
199
  }
200
+ /**
201
+ * Registers fonts with serialization to prevent duplicate FontLibrary.use() calls
202
+ * when multiple Root() instances are created concurrently.
203
+ */
204
+ async _registerFonts() {
205
+ if (!this.props.fonts?.length)
206
+ return;
207
+ // Wait for any in-flight registration to complete
208
+ if (_fontRegistrationLock)
209
+ await _fontRegistrationLock;
210
+ _fontRegistrationLock = (async () => {
211
+ try {
212
+ for (const font of this.props.fonts) {
213
+ const family = font.family;
214
+ const paths = font.paths.map(p => path.resolve(p));
215
+ if (!registeredFonts.has(family)) {
216
+ registeredFonts.set(family, new Set());
217
+ }
218
+ const cachedPaths = registeredFonts.get(family);
219
+ const newPaths = paths.filter(p => !cachedPaths.has(p) && fs.existsSync(p));
220
+ if (newPaths.length > 0) {
221
+ FontLibrary.use({ [family]: newPaths });
222
+ newPaths.forEach(p => cachedPaths.add(p));
223
+ }
224
+ }
225
+ }
226
+ finally {
227
+ _fontRegistrationLock = null;
228
+ }
229
+ })();
230
+ await _fontRegistrationLock;
231
+ }
225
232
  async render(ctx, offsetX = 0, offsetY = 0) {
226
233
  // If ctx is provided, delegate to parent render (used when called as a child node)
227
234
  if (ctx) {
228
235
  await super.render(ctx, offsetX, offsetY);
229
236
  return;
230
237
  }
238
+ // Register fonts with serialization to prevent duplicate FontLibrary.use() across concurrent Root() calls
239
+ await this._registerFonts();
231
240
  const diskCacheKeys = this.props.useDiskCache ? new Set() : undefined;
232
241
  try {
233
242
  // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
@@ -235,15 +244,20 @@ class RootNode extends ColumnNode {
235
244
  const imageNodes = this.findAllImageNodes();
236
245
  if (imageNodes.length > 0) {
237
246
  const imageCache = new Map();
238
- const CONCURRENCY = 5;
239
247
  const queue = [...imageNodes];
240
- const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
241
- while (queue.length > 0) {
242
- const node = queue.shift();
248
+ let qIdx = 0;
249
+ const workers = Array.from({ length: Math.min(this.imageConcurrency, queue.length) }, async () => {
250
+ while (qIdx < queue.length) {
251
+ const node = queue[qIdx++];
243
252
  await node.load(imageCache, diskCacheKeys);
244
253
  }
245
254
  });
246
- await Promise.allSettled(workers);
255
+ await Promise.allSettled(workers).then(results => {
256
+ results.forEach(r => {
257
+ if (r.status === 'rejected')
258
+ console.warn('[RootNode] Image load worker failed:', r.reason);
259
+ });
260
+ });
247
261
  }
248
262
  // Step 2: Calculate initial layout
249
263
  this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR);
@@ -275,9 +289,9 @@ class RootNode extends ColumnNode {
275
289
  }
276
290
  }
277
291
  async function Root(props) {
278
- // Determine worker mode: props override legacy configure()
279
- const workerMode = props.workerMode ?? _defaultWorkerMode;
280
- const workerPoolSize = props.workers ?? _defaultWorkerPoolSize;
292
+ // Determine worker mode
293
+ const workerMode = props.workerMode ?? true;
294
+ const workerPoolSize = props.workers ?? Math.max(1, cpus().length - 1);
281
295
  if (workerMode) {
282
296
  // Lazy initialize worker pool — dynamic import to avoid loading Comlink in non-worker contexts
283
297
  if (!_workerPool) {
@@ -291,4 +305,4 @@ async function Root(props) {
291
305
  return new RootNode(props).render();
292
306
  }
293
307
 
294
- export { Root, RootNode, buildTree, configure, terminate };
308
+ export { Root, RootNode, buildTree, terminate };
@@ -1,6 +1,6 @@
1
- import type { TextProps, CanvasElement } from '../canvas/canvas.type.js';
1
+ import type { TextProps, CanvasElement } from './canvas.type.js';
2
2
  import { type CanvasRenderingContext2D } from 'skia-canvas';
3
- import { BoxNode } from '../canvas/layout.canvas.js';
3
+ import { BoxNode } from './layout.canvas.js';
4
4
  /**
5
5
  * Node for rendering text content with rich text styling support
6
6
  * Supports color and weight variations through HTML-like tags
@@ -140,7 +140,11 @@ export declare class TextNode extends BoxNode {
140
140
  */
141
141
  private measureSpaceWidth;
142
142
  /**
143
- * Renders multi-line text content with rich text styling and layout features
143
+ * Applies this.props.fontVariant to the context, or resets to 'normal'.
144
+ * Centralizes the type guard + warn pattern repeated across measure/render paths.
145
+ */
146
+ private _applyFontVariant;
147
+ /**
144
148
  *
145
149
  * Core features:
146
150
  * - Dynamic line heights with leading/spacing controls
@@ -1 +1 @@
1
- {"version":3,"file":"text.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGnD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEjC,KAAK,EAAE,SAAS,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAElC,IAAI,GAAE,MAAM,GAAG,MAAW,EAAE,KAAK,GAAE,SAAc;IAuB7D;;;;;;;;OAQG;WACW,gBAAgB,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,GAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACpC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;QAClC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAA;QACjD,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;KACnD;cAwBW,aAAa,IAAI,IAAI;IAoDxC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IA+NnB;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IA6KpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAmErB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;;;;;;;;;;;;;;OAgBG;cACsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAkX3H;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,KAAG,aAI9D,CAAA"}
1
+ {"version":3,"file":"text.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGnD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEjC,KAAK,EAAE,SAAS,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAElC,IAAI,GAAE,MAAM,GAAG,MAAW,EAAE,KAAK,GAAE,SAAc;IAuB7D;;;;;;;;OAQG;WACW,gBAAgB,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,GAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACpC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;QAClC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAA;QACjD,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;KACnD;cAwBW,aAAa,IAAI,IAAI;IAoDxC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IA4LnB;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IA6KpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAmErB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;;;;;;;;;;OAeG;cACsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoW3H;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,KAAG,aAI9D,CAAA"}
@@ -352,18 +352,7 @@ class TextNode extends BoxNode {
352
352
  // Pre-measure each text segment width with its specific styling
353
353
  for (const segment of this.segments) {
354
354
  ctx.font = this.getFontString(segment);
355
- if (typeof this.props.fontVariant === 'string') {
356
- ctx.fontVariant = this.props.fontVariant;
357
- }
358
- else if (this.props.fontVariant !== undefined) {
359
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (segment width):`, this.props.fontVariant);
360
- if (ctx.fontVariant !== 'normal')
361
- ctx.fontVariant = 'normal';
362
- }
363
- else {
364
- if (ctx.fontVariant !== 'normal')
365
- ctx.fontVariant = 'normal';
366
- }
355
+ this._applyFontVariant(ctx, 'measureText (segment width)');
367
356
  segment.width = this.addLetterSpacingExtra(segment.text, ctx.measureText(segment.text).width, parsedLetterSpacingPx);
368
357
  }
369
358
  // Calculate available layout width
@@ -387,18 +376,7 @@ class TextNode extends BoxNode {
387
376
  // Handle empty line metrics
388
377
  if (line.length === 0) {
389
378
  ctx.font = this.getFontString();
390
- if (typeof this.props.fontVariant === 'string') {
391
- ctx.fontVariant = this.props.fontVariant;
392
- }
393
- else if (this.props.fontVariant !== undefined) {
394
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (empty line):`, this.props.fontVariant);
395
- if (ctx.fontVariant !== 'normal')
396
- ctx.fontVariant = 'normal';
397
- }
398
- else {
399
- if (ctx.fontVariant !== 'normal')
400
- ctx.fontVariant = 'normal';
401
- }
379
+ this._applyFontVariant(ctx, 'measureText (empty line)');
402
380
  const metrics = ctx.measureText(this.metricsString);
403
381
  maxAscent = metrics.actualBoundingBoxAscent ?? baseFontSize * 0.8;
404
382
  maxDescent = metrics.actualBoundingBoxDescent ?? baseFontSize * 0.2;
@@ -412,18 +390,7 @@ class TextNode extends BoxNode {
412
390
  const segmentSize = segment.size || baseFontSize;
413
391
  maxFontSizeOnLine = Math.max(maxFontSizeOnLine, segmentSize);
414
392
  ctx.font = this.getFontString(segment);
415
- if (typeof this.props.fontVariant === 'string') {
416
- ctx.fontVariant = this.props.fontVariant;
417
- }
418
- else if (this.props.fontVariant !== undefined) {
419
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (segment height):`, this.props.fontVariant);
420
- if (ctx.fontVariant !== 'normal')
421
- ctx.fontVariant = 'normal';
422
- }
423
- else {
424
- if (ctx.fontVariant !== 'normal')
425
- ctx.fontVariant = 'normal';
426
- }
393
+ this._applyFontVariant(ctx, 'measureText (segment height)');
427
394
  const metrics = ctx.measureText(this.metricsString);
428
395
  const ascent = metrics.actualBoundingBoxAscent ?? segmentSize * 0.8;
429
396
  const descent = metrics.actualBoundingBoxDescent ?? segmentSize * 0.2;
@@ -434,18 +401,7 @@ class TextNode extends BoxNode {
434
401
  // Fallback metrics for lines with only whitespace
435
402
  if (maxAscent === 0 && maxDescent === 0 && line.length > 0) {
436
403
  ctx.font = this.getFontString();
437
- if (typeof this.props.fontVariant === 'string') {
438
- ctx.fontVariant = this.props.fontVariant;
439
- }
440
- else if (this.props.fontVariant !== undefined) {
441
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (fallback):`, this.props.fontVariant);
442
- if (ctx.fontVariant !== 'normal')
443
- ctx.fontVariant = 'normal';
444
- }
445
- else {
446
- if (ctx.fontVariant !== 'normal')
447
- ctx.fontVariant = 'normal';
448
- }
404
+ this._applyFontVariant(ctx, 'measureText (fallback)');
449
405
  const metrics = ctx.measureText(this.metricsString);
450
406
  maxAscent = metrics.actualBoundingBoxAscent ?? baseFontSize * 0.8;
451
407
  maxDescent = metrics.actualBoundingBoxDescent ?? baseFontSize * 0.2;
@@ -478,18 +434,7 @@ class TextNode extends BoxNode {
478
434
  if (/^\s+$/.test(word))
479
435
  continue;
480
436
  ctx.font = this.getFontString(segment);
481
- if (typeof this.props.fontVariant === 'string') {
482
- ctx.fontVariant = this.props.fontVariant;
483
- }
484
- else if (this.props.fontVariant !== undefined) {
485
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (single line width):`, this.props.fontVariant);
486
- if (ctx.fontVariant !== 'normal')
487
- ctx.fontVariant = 'normal';
488
- }
489
- else {
490
- if (ctx.fontVariant !== 'normal')
491
- ctx.fontVariant = 'normal';
492
- }
437
+ this._applyFontVariant(ctx, 'measureText (single line width)');
493
438
  const wordWidth = this.addLetterSpacingExtra(word, ctx.measureText(word).width, parsedLetterSpacingPx);
494
439
  if (!firstWordInSingleLine) {
495
440
  singleLineWidth += spaceWidth + parsedWordSpacingPx;
@@ -800,7 +745,24 @@ class TextNode extends BoxNode {
800
745
  return width > 0 ? width : (this.props.fontSize || 16) * 0.3;
801
746
  }
802
747
  /**
803
- * Renders multi-line text content with rich text styling and layout features
748
+ * Applies this.props.fontVariant to the context, or resets to 'normal'.
749
+ * Centralizes the type guard + warn pattern repeated across measure/render paths.
750
+ */
751
+ _applyFontVariant(ctx, context) {
752
+ if (typeof this.props.fontVariant === 'string') {
753
+ ctx.fontVariant = this.props.fontVariant;
754
+ }
755
+ else if (this.props.fontVariant !== undefined) {
756
+ console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in ${context}:`, this.props.fontVariant);
757
+ if (ctx.fontVariant !== 'normal')
758
+ ctx.fontVariant = 'normal';
759
+ }
760
+ else {
761
+ if (ctx.fontVariant !== 'normal')
762
+ ctx.fontVariant = 'normal';
763
+ }
764
+ }
765
+ /**
804
766
  *
805
767
  * Core features:
806
768
  * - Dynamic line heights with leading/spacing controls
@@ -1040,18 +1002,7 @@ class TextNode extends BoxNode {
1040
1002
  // Apply segment styles
1041
1003
  ctx.font = this.getFontString(segment);
1042
1004
  ctx.fillStyle = segment.color || this.props.color || 'black';
1043
- if (typeof this.props.fontVariant === 'string') {
1044
- ctx.fontVariant = this.props.fontVariant;
1045
- }
1046
- else if (this.props.fontVariant !== undefined) {
1047
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in _renderContent (segment render):`, this.props.fontVariant);
1048
- if (ctx.fontVariant !== 'normal')
1049
- ctx.fontVariant = 'normal';
1050
- }
1051
- else {
1052
- if (ctx.fontVariant !== 'normal')
1053
- ctx.fontVariant = 'normal';
1054
- }
1005
+ this._applyFontVariant(ctx, '_renderContent (segment render)');
1055
1006
  // Handle text truncation and ellipsis
1056
1007
  let textToDraw = segment.text;
1057
1008
  let currentSegmentRenderWidth = segmentWidth;
@@ -1119,18 +1070,7 @@ class TextNode extends BoxNode {
1119
1070
  if (ellipsisRemainingWidth >= ellipsisWidth) {
1120
1071
  ctx.save();
1121
1072
  ctx.font = this.getFontString(ellipsisStyle);
1122
- if (typeof this.props.fontVariant === 'string') {
1123
- ctx.fontVariant = this.props.fontVariant;
1124
- }
1125
- else if (this.props.fontVariant !== undefined) {
1126
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in _renderContent (ellipsis draw):`, this.props.fontVariant);
1127
- if (ctx.fontVariant !== 'normal')
1128
- ctx.fontVariant = 'normal';
1129
- }
1130
- else {
1131
- if (ctx.fontVariant !== 'normal')
1132
- ctx.fontVariant = 'normal';
1133
- }
1073
+ this._applyFontVariant(ctx, '_renderContent (ellipsis draw)');
1134
1074
  ctx.fillStyle = ellipsisStyle?.color || this.props.color || 'black';
1135
1075
  ctx.fillText(ellipsisChar, currentX, lineY, Math.max(0, ellipsisRemainingWidth + 1));
1136
1076
  ctx.restore();
@@ -3,9 +3,9 @@ export * from './canvas/canvas.type.js';
3
3
  export { Box, Column, Row, type BoxNode } from './canvas/layout.canvas.js';
4
4
  export { Image } from './canvas/image.canvas.js';
5
5
  export { Text } from './canvas/text.canvas.js';
6
- export { Root, configure, terminate } from './canvas/root.canvas.js';
6
+ export { Root, terminate } from './canvas/root.canvas.js';
7
7
  export { GridItem } from './canvas/grid.canvas.js';
8
8
  export { Grid } from './canvas/grid.canvas.js';
9
9
  export { Chart } from './canvas/chart.canvas.js';
10
- export { clearDiskCache } from './util/disk.cache.js';
10
+ export { clearDiskCache, setDiskCacheDir } from './util/disk.cache.js';
11
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,2BAA2B,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,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,2BAA2B,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/esm/index.js CHANGED
@@ -2,8 +2,8 @@ export { Border, Style } from './constant/common.const.js';
2
2
  export { Box, Column, Row } from './canvas/layout.canvas.js';
3
3
  export { Image } from './canvas/image.canvas.js';
4
4
  export { Text } from './canvas/text.canvas.js';
5
- export { Root, configure, terminate } from './canvas/root.canvas.js';
5
+ export { Root, terminate } from './canvas/root.canvas.js';
6
6
  export { Grid, GridItem } from './canvas/grid.canvas.js';
7
7
  export { Chart } from './canvas/chart.canvas.js';
8
- export { clearDiskCache } from './util/disk.cache.js';
8
+ export { clearDiskCache, setDiskCacheDir } from './util/disk.cache.js';
9
9
  export * from 'yoga-layout';
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Override the default disk cache directory.
3
+ * Must be called before any cache read/write operations.
4
+ */
5
+ export declare function setDiskCacheDir(dir: string): void;
1
6
  export declare function hashBuffer(buf: Buffer): string;
2
7
  export declare function readDiskCache(key: string): Promise<Buffer | null>;
3
8
  export declare function writeDiskCache(key: string, data: Buffer): Promise<void>;
@@ -1 +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,CAOpD"}
1
+ {"version":3,"file":"disk.cache.d.ts","sourceRoot":"","sources":["../../../src/util/disk.cache.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGjD;AAQD,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,CAShE;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAUpD"}
@@ -2,12 +2,20 @@ import { createHash } from 'crypto';
2
2
  import { promises } from 'fs';
3
3
  import { join } from 'path';
4
4
 
5
- const CACHE_DIR = join(process.cwd(), '.cache', 'files');
5
+ let _cacheDir = join(process.cwd(), '.cache', 'files');
6
6
  let _dirEnsured = false;
7
+ /**
8
+ * Override the default disk cache directory.
9
+ * Must be called before any cache read/write operations.
10
+ */
11
+ function setDiskCacheDir(dir) {
12
+ _cacheDir = dir;
13
+ _dirEnsured = false;
14
+ }
7
15
  async function ensureDir() {
8
16
  if (_dirEnsured)
9
17
  return;
10
- await promises.mkdir(CACHE_DIR, { recursive: true });
18
+ await promises.mkdir(_cacheDir, { recursive: true });
11
19
  _dirEnsured = true;
12
20
  }
13
21
  function hashBuffer(buf) {
@@ -16,7 +24,7 @@ function hashBuffer(buf) {
16
24
  async function readDiskCache(key) {
17
25
  try {
18
26
  await ensureDir();
19
- return await promises.readFile(join(CACHE_DIR, key));
27
+ return await promises.readFile(join(_cacheDir, key));
20
28
  }
21
29
  catch {
22
30
  return null;
@@ -25,7 +33,7 @@ async function readDiskCache(key) {
25
33
  async function writeDiskCache(key, data) {
26
34
  try {
27
35
  await ensureDir();
28
- await promises.writeFile(join(CACHE_DIR, key), data);
36
+ await promises.writeFile(join(_cacheDir, key), data);
29
37
  }
30
38
  catch {
31
39
  // best-effort — cache write failures are non-fatal
@@ -33,10 +41,13 @@ async function writeDiskCache(key, data) {
33
41
  }
34
42
  async function deleteDiskCache(key) {
35
43
  try {
36
- await promises.unlink(join(CACHE_DIR, key));
44
+ await promises.unlink(join(_cacheDir, key));
37
45
  }
38
- catch {
46
+ catch (err) {
39
47
  // non-fatal — file may not exist if write failed earlier
48
+ if (err.code !== 'ENOENT') {
49
+ console.warn(`[disk.cache] Failed to delete cache entry "${key}":`, err.message);
50
+ }
40
51
  }
41
52
  }
42
53
  /**
@@ -46,10 +57,13 @@ async function deleteDiskCache(key) {
46
57
  async function clearDiskCache() {
47
58
  _dirEnsured = false;
48
59
  try {
49
- await promises.rm(CACHE_DIR, { recursive: true, force: true });
60
+ await promises.rm(_cacheDir, { recursive: true, force: true });
50
61
  }
51
- catch {
62
+ catch (err) {
52
63
  // non-fatal — directory may not exist
64
+ if (err.code !== 'ENOENT') {
65
+ console.warn('[disk.cache] Failed to clear cache directory:', err.message);
66
+ }
53
67
  }
54
68
  }
55
69
  // Clean up disk cache on process exit to handle crashes mid-render.
@@ -69,4 +83,4 @@ const cleanupOnExit = () => {
69
83
  process.on('SIGINT', cleanupOnExit);
70
84
  process.on('SIGTERM', cleanupOnExit);
71
85
 
72
- export { clearDiskCache, deleteDiskCache, hashBuffer, readDiskCache, writeDiskCache };
86
+ export { clearDiskCache, deleteDiskCache, hashBuffer, readDiskCache, setDiskCacheDir, writeDiskCache };
@@ -1,4 +1,4 @@
1
- import type { RenderResult } from '../worker/worker.types.js';
1
+ import type { RenderResult } from './worker.types.js';
2
2
  import type { RootProps } from '../canvas/canvas.type.js';
3
3
  export interface PoolRenderResult extends RenderResult {
4
4
  workerIdx: number;
@@ -1 +1 @@
1
- {"version":3,"file":"comlink.pool.d.ts","sourceRoot":"","sources":["../../../src/worker/comlink.pool.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAa,YAAY,EAAU,MAAM,0BAA0B,CAAA;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,SAAS,EAAE,MAAM,CAAA;CAClB;AASD,oFAAoF;AACpF,eAAO,MAAM,SAAS,kBAAkB,CAAA;AAExC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,EAAE,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,CAAC,CAuB7H;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAuB3G;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,KAAK,CAAmB;gBAEpB,IAAI,EAAE,MAAM;IAYxB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,KAAK;YAQC,aAAa;IAiBrB,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAuDzD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAInH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIxD,SAAS;CAOV"}
1
+ {"version":3,"file":"comlink.pool.d.ts","sourceRoot":"","sources":["../../../src/worker/comlink.pool.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAa,YAAY,EAAU,MAAM,0BAA0B,CAAA;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,SAAS,EAAE,MAAM,CAAA;CAClB;AASD,oFAAoF;AACpF,eAAO,MAAM,SAAS,kBAAkB,CAAA;AAExC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,EAAE,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,CAAC,CAuB7H;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAuB3G;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,KAAK,CAAmB;gBAEpB,IAAI,EAAE,MAAM;IAYxB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,KAAK;YAQC,aAAa;IAiBrB,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2DzD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAInH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIxD,SAAS;CAOV"}
@@ -114,6 +114,9 @@ class ComlinkPool {
114
114
  }
115
115
  }
116
116
  async render(props) {
117
+ if (this.endpoints.length === 0) {
118
+ throw new Error('[ComlinkPool] Pool has been terminated');
119
+ }
117
120
  // Extract functions from props, replacing them with serializable sentinels.
118
121
  // A single Comlink.proxy() callback is created at the top level so Comlink
119
122
  // can correctly transfer it via its proxy transfer handler.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meonode/canvas",
3
- "version": "2.0.5",
3
+ "version": "3.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",
@@ -37,9 +37,10 @@
37
37
  }
38
38
  },
39
39
  "scripts": {
40
- "build": "rm -rf ./dist && rollup -c --bundleConfigAsCjs && tsc-alias -p tsconfig.esm.json && tsc-alias -p tsconfig.cjs.json",
41
- "test": "node --no-experimental-webstorage --experimental-vm-modules $(yarn bin jest)",
42
- "lint": "eslint . --ext .ts,.tsx,.js,.jsx,.mjs --fix",
40
+ "build": "bun -e \"fs.rmSync('./dist', {recursive:true, force:true})\" && rollup -c --bundleConfigAsCjs && bun scripts/rewrite-dts-aliases.ts",
41
+ "test": "vitest run --coverage",
42
+ "typecheck": "tsc --noEmit",
43
+ "lint": "eslint . --ext .ts,.tsx,.js,.jsx,.mjs --fix && bun run typecheck",
43
44
  "format": "prettier --write .",
44
45
  "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
46
  "check:memory": "npx tsx --expose-gc scripts/check_memory.ts",
@@ -47,7 +48,6 @@
47
48
  },
48
49
  "devDependencies": {
49
50
  "@eslint/js": "^9.39.4",
50
- "@jest/globals": "^30.3.0",
51
51
  "@rollup/plugin-commonjs": "^29.0.2",
52
52
  "@rollup/plugin-node-resolve": "^16.0.3",
53
53
  "@rollup/plugin-typescript": "^12.3.0",
@@ -56,37 +56,30 @@
56
56
  "@semantic-release/gitlab": "^13.3.2",
57
57
  "@semantic-release/npm": "^13.1.5",
58
58
  "@semantic-release/release-notes-generator": "^14.1.0",
59
- "@types/jest": "^30.0.0",
60
- "@types/lodash-es": "^4.17.12",
61
59
  "@types/node": "^25.5.0",
62
60
  "@types/sharp": "^0.32.0",
63
- "@types/tinycolor2": "^1.4.6",
64
61
  "@typescript-eslint/eslint-plugin": "^8.57.2",
65
62
  "@typescript-eslint/parser": "^8.57.2",
63
+ "@vitest/coverage-v8": "^3.2.4",
66
64
  "eslint": "^9.39.4",
67
65
  "eslint-config-prettier": "^10.1.8",
68
66
  "eslint-plugin-jsdoc": "^62.8.1",
69
67
  "eslint-plugin-prettier": "^5.5.5",
70
68
  "eslint-plugin-unused-imports": "^4.4.1",
71
69
  "husky": "^9.1.7",
72
- "jest": "^30.3.0",
73
70
  "prettier": "^3.8.1",
74
71
  "rollup": "^4.60.0",
75
72
  "rollup-plugin-tsconfig-paths": "^1.5.2",
76
- "semantic-release": "^25.0.3",
77
- "ts-jest": "^29.4.6",
78
- "tsc-alias": "^1.8.16",
73
+ "semantic-release": "24.2.9",
79
74
  "typescript": "^6.0.2",
80
- "typescript-eslint": "^8.57.2"
75
+ "typescript-eslint": "^8.57.2",
76
+ "vitest": "^3.2.4"
81
77
  },
82
- "packageManager": "yarn@4.11.0",
83
78
  "dependencies": {
84
79
  "comlink": "^4.4.2",
85
80
  "file-type": "^22.0.0",
86
- "lodash-es": "^4.17.23",
87
81
  "sharp": "^0.34.5",
88
82
  "skia-canvas": "^3.0.8",
89
- "tinycolor2": "^1.6.0",
90
83
  "tslib": "^2.8.1",
91
84
  "yoga-layout": "^3.2.1"
92
85
  },