@meonode/canvas 1.7.0 → 1.7.1

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.
@@ -6,6 +6,36 @@ import { BoxNode } from '../canvas/layout.canvas.util.js';
6
6
  * Scoped to a single RootNode.render() call; discarded after rendering.
7
7
  */
8
8
  export type RenderImageCache = Map<string, Promise<CanvasImage>>;
9
+ /**
10
+ * A simple LRU cache for resolved `CanvasImage` objects.
11
+ *
12
+ * - Persists across render passes so repeated renders of the same tree don't
13
+ * re-fetch every image.
14
+ * - Bounded by `maxSize` entries; least-recently-used entries are evicted first.
15
+ * - Call `dispose()` to eagerly release all held images, or rely on the
16
+ * automatic `process.on('exit')` hook that clears the singleton.
17
+ */
18
+ export declare class ImageLRUCache {
19
+ private readonly map;
20
+ readonly maxSize: number;
21
+ constructor(maxSize: number);
22
+ get(key: string): CanvasImage | undefined;
23
+ set(key: string, image: CanvasImage): void;
24
+ has(key: string): boolean;
25
+ get size(): number;
26
+ dispose(): void;
27
+ }
28
+ /**
29
+ * Returns the singleton `ImageLRUCache`, creating it on first access.
30
+ * Registers a one-time process cleanup hook to clear the cache
31
+ * so native image buffers are freed when the process shuts down.
32
+ */
33
+ export declare function getImageCache(maxSize?: number): ImageLRUCache;
34
+ /**
35
+ * Explicitly disposes the global image cache.
36
+ * Useful in tests or when tearing down the rendering engine.
37
+ */
38
+ export declare function disposeImageCache(): void;
9
39
  /**
10
40
  * Renders images with configurable sizing, positioning, and effects.
11
41
  * Supports object-fit modes, positioning, border radius, and saturation filters.
@@ -24,8 +54,14 @@ export declare class ImageNode extends BoxNode {
24
54
  */
25
55
  private _fetchCanvasImage;
26
56
  /**
27
- * Loads and processes an image, using the render-scoped cache to avoid
28
- * re-fetching the same source within a single render pass.
57
+ * Loads and processes an image.
58
+ *
59
+ * Resolution order:
60
+ * 1. Persistent LRU cache (cross-render) — instant hit, no I/O.
61
+ * 2. Per-render dedup cache — avoids duplicate in-flight fetches within a single render.
62
+ * 3. Fresh fetch via `_fetchCanvasImage()`.
63
+ *
64
+ * Resolved images are stored back into the LRU cache for future renders.
29
65
  */
30
66
  private _loadImage;
31
67
  getLoadingPromise(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"image.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/image.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACnF,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,IAAI,WAAW,EAAa,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAsBxD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;AAEhE;;;GAGG;AACH,qBAAa,SAAU,SAAQ,OAAO;IAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,CAAA;IACrC,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAA6B;gBAEvC,KAAK,EAAE,UAAU;IAYtB,IAAI,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD;;;OAGG;YACW,iBAAiB;IAmE/B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAgDX,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC;;;OAGG;cACgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoJrH;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,UAAU,KAAG,aAGxC,CAAA"}
1
+ {"version":3,"file":"image.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/image.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACnF,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,IAAI,WAAW,EAAa,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAsBxD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;AAWhE;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA8B;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;gBAEZ,OAAO,EAAE,MAAM;IAI3B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IASzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAa1C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,IAAI,IAAI;CAGhB;AAUD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,MAA2B,GAAG,aAAa,CAejF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;;GAGG;AACH,qBAAa,SAAU,SAAQ,OAAO;IAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,CAAA;IACrC,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAA6B;gBAEvC,KAAK,EAAE,UAAU;IAYtB,IAAI,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD;;;OAGG;YACW,iBAAiB;IAmE/B;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;IAwEX,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC;;;OAGG;cACgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoJrH;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,UAAU,KAAG,aAGxC,CAAA"}
@@ -22,6 +22,86 @@ function calculateOffsetFromValue(positionValue, availableSpace) {
22
22
  console.warn(`[ImageNode] Invalid objectPosition value format: ${value}. Defaulting to 50%.`);
23
23
  return availableSpace * 0.5;
24
24
  }
25
+ /**
26
+ * A simple LRU cache for resolved `CanvasImage` objects.
27
+ *
28
+ * - Persists across render passes so repeated renders of the same tree don't
29
+ * re-fetch every image.
30
+ * - Bounded by `maxSize` entries; least-recently-used entries are evicted first.
31
+ * - Call `dispose()` to eagerly release all held images, or rely on the
32
+ * automatic `process.on('exit')` hook that clears the singleton.
33
+ */
34
+ class ImageLRUCache {
35
+ map = new Map();
36
+ maxSize;
37
+ constructor(maxSize) {
38
+ this.maxSize = maxSize;
39
+ }
40
+ get(key) {
41
+ const entry = this.map.get(key);
42
+ if (!entry)
43
+ return undefined;
44
+ // Move to end (most-recently used)
45
+ this.map.delete(key);
46
+ this.map.set(key, entry);
47
+ return entry.image;
48
+ }
49
+ set(key, image) {
50
+ // If key already exists, refresh it
51
+ if (this.map.has(key)) {
52
+ this.map.delete(key);
53
+ }
54
+ // Evict oldest if at capacity
55
+ while (this.map.size >= this.maxSize) {
56
+ const oldest = this.map.keys().next().value;
57
+ this.map.delete(oldest);
58
+ }
59
+ this.map.set(key, { image, key });
60
+ }
61
+ has(key) {
62
+ return this.map.has(key);
63
+ }
64
+ get size() {
65
+ return this.map.size;
66
+ }
67
+ dispose() {
68
+ this.map.clear();
69
+ }
70
+ }
71
+ /** Module-level singleton — lazily created on first render. */
72
+ let _globalImageCache = null;
73
+ const DEFAULT_CACHE_SIZE = 128;
74
+ // Symbol key on process to track hook registration across module reloads (e.g. Jest resetModules)
75
+ const HOOK_KEY = Symbol.for('__meonode_canvas_image_cache_hook__');
76
+ /**
77
+ * Returns the singleton `ImageLRUCache`, creating it on first access.
78
+ * Registers a one-time process cleanup hook to clear the cache
79
+ * so native image buffers are freed when the process shuts down.
80
+ */
81
+ function getImageCache(maxSize = DEFAULT_CACHE_SIZE) {
82
+ if (!_globalImageCache) {
83
+ _globalImageCache = new ImageLRUCache(maxSize);
84
+ }
85
+ if (!globalThis[HOOK_KEY]) {
86
+ globalThis[HOOK_KEY] = true;
87
+ const cleanup = () => {
88
+ _globalImageCache?.dispose();
89
+ _globalImageCache = null;
90
+ };
91
+ process.once('exit', cleanup);
92
+ process.once('SIGINT', cleanup);
93
+ process.once('SIGTERM', cleanup);
94
+ }
95
+ return _globalImageCache;
96
+ }
97
+ /**
98
+ * Explicitly disposes the global image cache.
99
+ * Useful in tests or when tearing down the rendering engine.
100
+ */
101
+ function disposeImageCache() {
102
+ _globalImageCache?.dispose();
103
+ _globalImageCache = null;
104
+ }
25
105
  /**
26
106
  * Renders images with configurable sizing, positioning, and effects.
27
107
  * Supports object-fit modes, positioning, border radius, and saturation filters.
@@ -113,8 +193,14 @@ class ImageNode extends layout_canvas_util.BoxNode {
113
193
  return skiaCanvas.loadImage(finalSource);
114
194
  }
115
195
  /**
116
- * Loads and processes an image, using the render-scoped cache to avoid
117
- * re-fetching the same source within a single render pass.
196
+ * Loads and processes an image.
197
+ *
198
+ * Resolution order:
199
+ * 1. Persistent LRU cache (cross-render) — instant hit, no I/O.
200
+ * 2. Per-render dedup cache — avoids duplicate in-flight fetches within a single render.
201
+ * 3. Fresh fetch via `_fetchCanvasImage()`.
202
+ *
203
+ * Resolved images are stored back into the LRU cache for future renders.
118
204
  */
119
205
  _loadImage(cache) {
120
206
  if (!this.props.src) {
@@ -127,9 +213,26 @@ class ImageNode extends layout_canvas_util.BoxNode {
127
213
  return new Promise(resolve => {
128
214
  const load = async () => {
129
215
  try {
216
+ const lru = getImageCache();
217
+ const cacheKey = typeof this.props.src === 'string' ? (this.props.color ? `${this.props.src}|${this.props.color}` : this.props.src) : null;
218
+ // 1. Check persistent LRU cache
219
+ if (cacheKey) {
220
+ const cached = lru.get(cacheKey);
221
+ if (cached) {
222
+ this.loadedImage = cached;
223
+ this.naturalWidth = cached.width;
224
+ this.naturalHeight = cached.height;
225
+ const calculatedAspectRatio = cached.width > 0 && cached.height > 0 ? cached.width / cached.height : undefined;
226
+ const finalAspectRatio = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : calculatedAspectRatio;
227
+ this.node.setAspectRatio(finalAspectRatio);
228
+ this.props.onLoad?.();
229
+ resolve();
230
+ return;
231
+ }
232
+ }
233
+ // 2. Per-render dedup cache or fresh fetch
130
234
  let imagePromise;
131
- if (cache && typeof this.props.src === 'string') {
132
- const cacheKey = this.props.color ? `${this.props.src}|${this.props.color}` : this.props.src;
235
+ if (cache && cacheKey) {
133
236
  if (!cache.has(cacheKey)) {
134
237
  cache.set(cacheKey, this._fetchCanvasImage());
135
238
  }
@@ -139,6 +242,10 @@ class ImageNode extends layout_canvas_util.BoxNode {
139
242
  imagePromise = this._fetchCanvasImage();
140
243
  }
141
244
  const img = await imagePromise;
245
+ // 3. Store in persistent LRU cache
246
+ if (cacheKey) {
247
+ lru.set(cacheKey, img);
248
+ }
142
249
  this.loadedImage = img;
143
250
  this.naturalWidth = img.width;
144
251
  this.naturalHeight = img.height;
@@ -320,5 +427,8 @@ const Image = (props) => ({
320
427
  });
321
428
 
322
429
  exports.Image = Image;
430
+ exports.ImageLRUCache = ImageLRUCache;
323
431
  exports.ImageNode = ImageNode;
432
+ exports.disposeImageCache = disposeImageCache;
433
+ exports.getImageCache = getImageCache;
324
434
  //# sourceMappingURL=image.canvas.util.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.canvas.util.js","sources":["../../../../src/canvas/image.canvas.util.ts"],"sourcesContent":["import type { BaseProps, ImageProps, CanvasElement } from '@/canvas/canvas.type.js'\nimport { type CanvasRenderingContext2D, Image as CanvasImage, loadImage } from 'skia-canvas'\nimport { BoxNode } from '@/canvas/layout.canvas.util.js'\nimport { drawRoundedRectPath, parseBorderRadius } from '@/canvas/canvas.helper.js'\nimport { promises as fs } from 'fs'\nimport { Style } from '@/constant/common.const.js'\n\n/**\n * Calculates pixel offset for image positioning based on percentage or pixel values.\n * This handles centering, edge alignment, and percentage-based positioning.\n */\nfunction calculateOffsetFromValue(positionValue: number | `${number}%` | undefined, availableSpace: number): number {\n const value = positionValue ?? '50%'\n if (typeof value === 'number') {\n return value\n }\n if (typeof value === 'string' && value.endsWith('%')) {\n const percentage = parseFloat(value) / 100\n return availableSpace * percentage\n }\n console.warn(`[ImageNode] Invalid objectPosition value format: ${value}. Defaulting to 50%.`)\n return availableSpace * 0.5\n}\n\n/**\n * Per-render image cache — keyed by `src|color` for string sources.\n * Scoped to a single RootNode.render() call; discarded after rendering.\n */\nexport type RenderImageCache = Map<string, Promise<CanvasImage>>\n\n/**\n * Renders images with configurable sizing, positioning, and effects.\n * Supports object-fit modes, positioning, border radius, and saturation filters.\n */\nexport class ImageNode extends BoxNode {\n declare props: ImageProps & BaseProps\n private loadedImage: CanvasImage | null = null\n private naturalWidth = 0\n private naturalHeight = 0\n private loadingPromise: Promise<void> | null = null\n\n constructor(props: ImageProps) {\n super({ name: 'Image', ...props, children: undefined })\n\n this.props = {\n objectFit: 'fill',\n overflow: Style.Overflow.Hidden,\n saturate: 1,\n objectPosition: { Left: '50%', Top: '50%' },\n ...props,\n }\n }\n\n public load(cache?: RenderImageCache): Promise<void> {\n if (!this.loadingPromise) {\n this.loadingPromise = this._loadImage(cache)\n }\n return this.loadingPromise\n }\n\n /**\n * Fetches and processes the image source into a CanvasImage.\n * Does not touch node state — pure fetch logic.\n */\n private async _fetchCanvasImage(): Promise<CanvasImage> {\n const { fileTypeFromBuffer, fileTypeFromFile } = await import('file-type')\n let finalSource: string | Buffer = this.props.src\n let isSvg = false\n let contentBuffer: Buffer | null = null\n let detectedMime: string | undefined\n\n if (typeof this.props.src === 'string') {\n if (this.props.src.startsWith('http')) {\n const response = await fetch(this.props.src)\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status} fetching image: ${this.props.src}`)\n }\n const imageArrayBuffer = await response.arrayBuffer()\n contentBuffer = Buffer.from(imageArrayBuffer)\n finalSource = contentBuffer\n\n const fileTypeResult = await fileTypeFromBuffer(contentBuffer)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n\n if ((!detectedMime || detectedMime === 'application/xml') && contentBuffer.toString('utf-8').includes('<svg')) {\n isSvg = true\n }\n } else {\n finalSource = this.props.src\n const filePath = this.props.src\n\n try {\n const fileTypeResult = await fileTypeFromFile(filePath)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n\n if ((!detectedMime || detectedMime === 'application/xml') && filePath.toLowerCase().endsWith('.svg')) {\n isSvg = true\n }\n } catch {\n isSvg = filePath.toLowerCase().endsWith('.svg')\n }\n\n if (isSvg && this.props.color) {\n try {\n contentBuffer = await fs.readFile(filePath)\n } catch {\n isSvg = false\n contentBuffer = null\n }\n }\n }\n } else {\n contentBuffer = this.props.src\n finalSource = contentBuffer\n\n const fileTypeResult = await fileTypeFromBuffer(contentBuffer)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n }\n\n if (isSvg && this.props.color && contentBuffer) {\n const svgString = contentBuffer.toString('utf-8')\n const modifiedSvgString = svgString.replace(/fill=\"[^\"]*\"/g, `fill=\"${this.props.color}\"`)\n finalSource = modifiedSvgString !== svgString ? Buffer.from(modifiedSvgString) : contentBuffer\n }\n\n return loadImage(finalSource as never)\n }\n\n /**\n * Loads and processes an image, using the render-scoped cache to avoid\n * re-fetching the same source within a single render pass.\n */\n private _loadImage(cache?: RenderImageCache): Promise<void> {\n if (!this.props.src) {\n const aspectRatioFromProps = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined\n this.node.setAspectRatio(aspectRatioFromProps)\n this.naturalWidth = 0\n this.naturalHeight = 0\n return Promise.resolve()\n }\n\n return new Promise(resolve => {\n const load = async () => {\n try {\n let imagePromise: Promise<CanvasImage>\n\n if (cache && typeof this.props.src === 'string') {\n const cacheKey = this.props.color ? `${this.props.src}|${this.props.color}` : this.props.src\n if (!cache.has(cacheKey)) {\n cache.set(cacheKey, this._fetchCanvasImage())\n }\n imagePromise = cache.get(cacheKey)!\n } else {\n imagePromise = this._fetchCanvasImage()\n }\n\n const img = await imagePromise\n this.loadedImage = img\n this.naturalWidth = img.width\n this.naturalHeight = img.height\n\n const calculatedAspectRatio = this.naturalWidth > 0 && this.naturalHeight > 0 ? this.naturalWidth / this.naturalHeight : undefined\n const finalAspectRatio = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : calculatedAspectRatio\n\n this.node.setAspectRatio(finalAspectRatio)\n this.props.onLoad?.()\n resolve()\n } catch (error: any) {\n this.naturalWidth = 0\n this.naturalHeight = 0\n const finalAspectRatioOnError = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined\n this.node.setAspectRatio(finalAspectRatioOnError)\n this.props.onError?.(error)\n resolve()\n }\n }\n load()\n })\n }\n\n public getLoadingPromise(): Promise<void> {\n return this.loadingPromise ?? this.load()\n }\n\n /**\n * Renders the image with correct sizing, clipping, and positioning.\n * Handles object-fit, object-position, and visual effects like saturation.\n */\n protected override _renderContent(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {\n super._renderContent(ctx, x, y, width, height)\n\n if (!this.loadedImage || width <= 0 || height <= 0) return\n const img = this.loadedImage\n const imgW = this.naturalWidth\n const imgH = this.naturalHeight\n if (imgW <= 0 || imgH <= 0) return\n\n // Calculate content box accounting for padding and borders\n const paddingLeft = this.node.getComputedPadding(Style.Edge.Left)\n const paddingTop = this.node.getComputedPadding(Style.Edge.Top)\n const paddingRight = this.node.getComputedPadding(Style.Edge.Right)\n const paddingBottom = this.node.getComputedPadding(Style.Edge.Bottom)\n const borderLeft = this.node.getComputedBorder(Style.Edge.Left)\n const borderTop = this.node.getComputedBorder(Style.Edge.Top)\n const borderRight = this.node.getComputedBorder(Style.Edge.Right)\n const borderBottom = this.node.getComputedBorder(Style.Edge.Bottom)\n const contentX = x + borderLeft + paddingLeft\n const contentY = y + borderTop + paddingTop\n const contentWidth = Math.max(0, width - borderLeft - paddingLeft - borderRight - paddingRight)\n const contentHeight = Math.max(0, height - borderTop - paddingTop - borderBottom - paddingBottom)\n\n if (contentWidth <= 0 || contentHeight <= 0) return\n\n // Apply clipping for border radius\n ctx.save()\n const outerRadii = parseBorderRadius(this.props.borderRadius)\n const innerBorderRadii = {\n TopLeft: Math.max(0, outerRadii.TopLeft - borderTop),\n TopRight: Math.max(0, outerRadii.TopRight - borderTop),\n BottomRight: Math.max(0, outerRadii.BottomRight - borderBottom),\n BottomLeft: Math.max(0, outerRadii.BottomLeft - borderBottom),\n }\n const contentRadii = {\n TopLeft: Math.max(0, innerBorderRadii.TopLeft - Math.max(paddingLeft, paddingTop)),\n TopRight: Math.max(0, innerBorderRadii.TopRight - Math.max(paddingRight, paddingTop)),\n BottomRight: Math.max(0, innerBorderRadii.BottomRight - Math.max(paddingRight, paddingBottom)),\n BottomLeft: Math.max(0, innerBorderRadii.BottomLeft - Math.max(paddingLeft, paddingBottom)),\n }\n drawRoundedRectPath(ctx, contentX, contentY, contentWidth, contentHeight, contentRadii)\n ctx.clip()\n\n // Calculate image dimensions based on object-fit\n const nodeRatio = contentWidth / contentHeight\n const imgRatio = imgW / imgH\n const objectFit = this.props.objectFit\n let dw = contentWidth\n let dh = contentHeight\n\n if (objectFit === 'contain') {\n if (imgRatio > nodeRatio) {\n dw = contentWidth\n dh = contentWidth / imgRatio\n } else {\n dh = contentHeight\n dw = contentHeight * imgRatio\n }\n } else if (objectFit === 'cover') {\n if (imgRatio > nodeRatio) {\n dh = contentHeight\n dw = contentHeight * imgRatio\n } else {\n dw = contentWidth\n dh = contentWidth / imgRatio\n }\n } else if (objectFit === 'none') {\n dw = imgW\n dh = imgH\n } else if (objectFit === 'scale-down') {\n if (imgW <= contentWidth && imgH <= contentHeight) {\n dw = imgW\n dh = imgH\n } else {\n if (imgRatio > nodeRatio) {\n dw = contentWidth\n dh = contentWidth / imgRatio\n } else {\n dh = contentHeight\n dw = contentHeight * imgRatio\n }\n }\n }\n\n // Calculate image position based on object-position\n const sx = 0\n const sy = 0\n const sw = imgW\n const sh = imgH\n\n const availableWidth = contentWidth - dw\n const availableHeight = contentHeight - dh\n const posProps = this.props.objectPosition || {}\n const horizontalValue = posProps.Left !== undefined ? posProps.Left : posProps.Right !== undefined ? posProps.Right : '50%'\n const verticalValue = posProps.Top !== undefined ? posProps.Top : posProps.Bottom !== undefined ? posProps.Bottom : '50%'\n\n let offsetX = calculateOffsetFromValue(horizontalValue, availableWidth)\n let offsetY = calculateOffsetFromValue(verticalValue, availableHeight)\n\n if (posProps.Left === undefined && posProps.Right !== undefined) {\n offsetX = availableWidth - offsetX\n }\n if (posProps.Top === undefined && posProps.Bottom !== undefined) {\n offsetY = availableHeight - offsetY\n }\n\n const dx = contentX + offsetX\n const dy = contentY + offsetY\n\n // Draw image with filters\n ctx.save()\n try {\n if (this.props.dropShadow) {\n const shadow = this.props.dropShadow\n const shadowBlur = Math.max(shadow.offsetX ?? 0, shadow.offsetY ?? 0)\n ctx.shadowOffsetX = shadow.offsetX ?? 0\n ctx.shadowOffsetY = shadow.offsetY ?? 0\n ctx.shadowBlur = Math.max(0, shadow.blur ?? shadowBlur)\n ctx.shadowColor = shadow.color ?? 'black'\n }\n\n const saturateValue = this.props.saturate ?? 1\n let filterString = ''\n if (saturateValue !== 1) {\n filterString += `saturate(${saturateValue * 100}%) `\n }\n\n if (filterString) {\n const currentFilter = ctx.filter && ctx.filter !== 'none' ? ctx.filter + ' ' : ''\n ctx.filter = currentFilter + filterString.trim()\n }\n\n const finalDX = Math.floor(dx)\n const finalDY = Math.floor(dy)\n const finalDW = Math.ceil(dw + (dx - finalDX))\n const finalDH = Math.ceil(dh + (dy - finalDY))\n\n if (finalDW > 0 && finalDH > 0) {\n ctx.drawImage(img, sx, sy, sw, sh, finalDX, finalDY, finalDW, finalDH)\n }\n } catch (drawError) {\n console.error('[ImageNode] Error drawing image:', drawError)\n } finally {\n ctx.restore()\n }\n\n ctx.restore()\n }\n}\n\n/**\n * Factory function to create ImageNode instances\n */\nexport const Image = (props: ImageProps): CanvasElement => ({\n __type: 'Image',\n props: props as Omit<ImageProps, 'onLoad' | 'onError'>,\n})\n"],"names":["BoxNode","Style","fs","loadImage","parseBorderRadius","drawRoundedRectPath"],"mappings":"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"}
1
+ {"version":3,"file":"image.canvas.util.js","sources":["../../../../src/canvas/image.canvas.util.ts"],"sourcesContent":["import type { BaseProps, ImageProps, CanvasElement } from '@/canvas/canvas.type.js'\nimport { type CanvasRenderingContext2D, Image as CanvasImage, loadImage } from 'skia-canvas'\nimport { BoxNode } from '@/canvas/layout.canvas.util.js'\nimport { drawRoundedRectPath, parseBorderRadius } from '@/canvas/canvas.helper.js'\nimport { promises as fs } from 'fs'\nimport { Style } from '@/constant/common.const.js'\n\n/**\n * Calculates pixel offset for image positioning based on percentage or pixel values.\n * This handles centering, edge alignment, and percentage-based positioning.\n */\nfunction calculateOffsetFromValue(positionValue: number | `${number}%` | undefined, availableSpace: number): number {\n const value = positionValue ?? '50%'\n if (typeof value === 'number') {\n return value\n }\n if (typeof value === 'string' && value.endsWith('%')) {\n const percentage = parseFloat(value) / 100\n return availableSpace * percentage\n }\n console.warn(`[ImageNode] Invalid objectPosition value format: ${value}. Defaulting to 50%.`)\n return availableSpace * 0.5\n}\n\n/**\n * Per-render image cache — keyed by `src|color` for string sources.\n * Scoped to a single RootNode.render() call; discarded after rendering.\n */\nexport type RenderImageCache = Map<string, Promise<CanvasImage>>\n\n// ---------------------------------------------------------------------------\n// Persistent LRU image cache — survives across render passes\n// ---------------------------------------------------------------------------\n\ninterface LRUEntry {\n image: CanvasImage\n key: string\n}\n\n/**\n * A simple LRU cache for resolved `CanvasImage` objects.\n *\n * - Persists across render passes so repeated renders of the same tree don't\n * re-fetch every image.\n * - Bounded by `maxSize` entries; least-recently-used entries are evicted first.\n * - Call `dispose()` to eagerly release all held images, or rely on the\n * automatic `process.on('exit')` hook that clears the singleton.\n */\nexport class ImageLRUCache {\n private readonly map = new Map<string, LRUEntry>()\n readonly maxSize: number\n\n constructor(maxSize: number) {\n this.maxSize = maxSize\n }\n\n get(key: string): CanvasImage | undefined {\n const entry = this.map.get(key)\n if (!entry) return undefined\n // Move to end (most-recently used)\n this.map.delete(key)\n this.map.set(key, entry)\n return entry.image\n }\n\n set(key: string, image: CanvasImage): void {\n // If key already exists, refresh it\n if (this.map.has(key)) {\n this.map.delete(key)\n }\n // Evict oldest if at capacity\n while (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value!\n this.map.delete(oldest)\n }\n this.map.set(key, { image, key })\n }\n\n has(key: string): boolean {\n return this.map.has(key)\n }\n\n get size(): number {\n return this.map.size\n }\n\n dispose(): void {\n this.map.clear()\n }\n}\n\n/** Module-level singleton — lazily created on first render. */\nlet _globalImageCache: ImageLRUCache | null = null\n\nconst DEFAULT_CACHE_SIZE = 128\n\n// Symbol key on process to track hook registration across module reloads (e.g. Jest resetModules)\nconst HOOK_KEY = Symbol.for('__meonode_canvas_image_cache_hook__')\n\n/**\n * Returns the singleton `ImageLRUCache`, creating it on first access.\n * Registers a one-time process cleanup hook to clear the cache\n * so native image buffers are freed when the process shuts down.\n */\nexport function getImageCache(maxSize: number = DEFAULT_CACHE_SIZE): ImageLRUCache {\n if (!_globalImageCache) {\n _globalImageCache = new ImageLRUCache(maxSize)\n }\n if (!(globalThis as any)[HOOK_KEY]) {\n ;(globalThis as any)[HOOK_KEY] = true\n const cleanup = () => {\n _globalImageCache?.dispose()\n _globalImageCache = null\n }\n process.once('exit', cleanup)\n process.once('SIGINT', cleanup)\n process.once('SIGTERM', cleanup)\n }\n return _globalImageCache\n}\n\n/**\n * Explicitly disposes the global image cache.\n * Useful in tests or when tearing down the rendering engine.\n */\nexport function disposeImageCache(): void {\n _globalImageCache?.dispose()\n _globalImageCache = null\n}\n\n/**\n * Renders images with configurable sizing, positioning, and effects.\n * Supports object-fit modes, positioning, border radius, and saturation filters.\n */\nexport class ImageNode extends BoxNode {\n declare props: ImageProps & BaseProps\n private loadedImage: CanvasImage | null = null\n private naturalWidth = 0\n private naturalHeight = 0\n private loadingPromise: Promise<void> | null = null\n\n constructor(props: ImageProps) {\n super({ name: 'Image', ...props, children: undefined })\n\n this.props = {\n objectFit: 'fill',\n overflow: Style.Overflow.Hidden,\n saturate: 1,\n objectPosition: { Left: '50%', Top: '50%' },\n ...props,\n }\n }\n\n public load(cache?: RenderImageCache): Promise<void> {\n if (!this.loadingPromise) {\n this.loadingPromise = this._loadImage(cache)\n }\n return this.loadingPromise\n }\n\n /**\n * Fetches and processes the image source into a CanvasImage.\n * Does not touch node state — pure fetch logic.\n */\n private async _fetchCanvasImage(): Promise<CanvasImage> {\n const { fileTypeFromBuffer, fileTypeFromFile } = await import('file-type')\n let finalSource: string | Buffer = this.props.src\n let isSvg = false\n let contentBuffer: Buffer | null = null\n let detectedMime: string | undefined\n\n if (typeof this.props.src === 'string') {\n if (this.props.src.startsWith('http')) {\n const response = await fetch(this.props.src)\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status} fetching image: ${this.props.src}`)\n }\n const imageArrayBuffer = await response.arrayBuffer()\n contentBuffer = Buffer.from(imageArrayBuffer)\n finalSource = contentBuffer\n\n const fileTypeResult = await fileTypeFromBuffer(contentBuffer)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n\n if ((!detectedMime || detectedMime === 'application/xml') && contentBuffer.toString('utf-8').includes('<svg')) {\n isSvg = true\n }\n } else {\n finalSource = this.props.src\n const filePath = this.props.src\n\n try {\n const fileTypeResult = await fileTypeFromFile(filePath)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n\n if ((!detectedMime || detectedMime === 'application/xml') && filePath.toLowerCase().endsWith('.svg')) {\n isSvg = true\n }\n } catch {\n isSvg = filePath.toLowerCase().endsWith('.svg')\n }\n\n if (isSvg && this.props.color) {\n try {\n contentBuffer = await fs.readFile(filePath)\n } catch {\n isSvg = false\n contentBuffer = null\n }\n }\n }\n } else {\n contentBuffer = this.props.src\n finalSource = contentBuffer\n\n const fileTypeResult = await fileTypeFromBuffer(contentBuffer)\n detectedMime = fileTypeResult?.mime\n isSvg = detectedMime === 'image/svg+xml'\n }\n\n if (isSvg && this.props.color && contentBuffer) {\n const svgString = contentBuffer.toString('utf-8')\n const modifiedSvgString = svgString.replace(/fill=\"[^\"]*\"/g, `fill=\"${this.props.color}\"`)\n finalSource = modifiedSvgString !== svgString ? Buffer.from(modifiedSvgString) : contentBuffer\n }\n\n return loadImage(finalSource as never)\n }\n\n /**\n * Loads and processes an image.\n *\n * Resolution order:\n * 1. Persistent LRU cache (cross-render) — instant hit, no I/O.\n * 2. Per-render dedup cache — avoids duplicate in-flight fetches within a single render.\n * 3. Fresh fetch via `_fetchCanvasImage()`.\n *\n * Resolved images are stored back into the LRU cache for future renders.\n */\n private _loadImage(cache?: RenderImageCache): Promise<void> {\n if (!this.props.src) {\n const aspectRatioFromProps = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined\n this.node.setAspectRatio(aspectRatioFromProps)\n this.naturalWidth = 0\n this.naturalHeight = 0\n return Promise.resolve()\n }\n\n return new Promise(resolve => {\n const load = async () => {\n try {\n const lru = getImageCache()\n const cacheKey = typeof this.props.src === 'string' ? (this.props.color ? `${this.props.src}|${this.props.color}` : this.props.src) : null\n\n // 1. Check persistent LRU cache\n if (cacheKey) {\n const cached = lru.get(cacheKey)\n if (cached) {\n this.loadedImage = cached\n this.naturalWidth = cached.width\n this.naturalHeight = cached.height\n const calculatedAspectRatio = cached.width > 0 && cached.height > 0 ? cached.width / cached.height : undefined\n const finalAspectRatio = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : calculatedAspectRatio\n this.node.setAspectRatio(finalAspectRatio)\n this.props.onLoad?.()\n resolve()\n return\n }\n }\n\n // 2. Per-render dedup cache or fresh fetch\n let imagePromise: Promise<CanvasImage>\n if (cache && cacheKey) {\n if (!cache.has(cacheKey)) {\n cache.set(cacheKey, this._fetchCanvasImage())\n }\n imagePromise = cache.get(cacheKey)!\n } else {\n imagePromise = this._fetchCanvasImage()\n }\n\n const img = await imagePromise\n\n // 3. Store in persistent LRU cache\n if (cacheKey) {\n lru.set(cacheKey, img)\n }\n\n this.loadedImage = img\n this.naturalWidth = img.width\n this.naturalHeight = img.height\n\n const calculatedAspectRatio = this.naturalWidth > 0 && this.naturalHeight > 0 ? this.naturalWidth / this.naturalHeight : undefined\n const finalAspectRatio = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : calculatedAspectRatio\n\n this.node.setAspectRatio(finalAspectRatio)\n this.props.onLoad?.()\n resolve()\n } catch (error: any) {\n this.naturalWidth = 0\n this.naturalHeight = 0\n const finalAspectRatioOnError = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined\n this.node.setAspectRatio(finalAspectRatioOnError)\n this.props.onError?.(error)\n resolve()\n }\n }\n load()\n })\n }\n\n public getLoadingPromise(): Promise<void> {\n return this.loadingPromise ?? this.load()\n }\n\n /**\n * Renders the image with correct sizing, clipping, and positioning.\n * Handles object-fit, object-position, and visual effects like saturation.\n */\n protected override _renderContent(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {\n super._renderContent(ctx, x, y, width, height)\n\n if (!this.loadedImage || width <= 0 || height <= 0) return\n const img = this.loadedImage\n const imgW = this.naturalWidth\n const imgH = this.naturalHeight\n if (imgW <= 0 || imgH <= 0) return\n\n // Calculate content box accounting for padding and borders\n const paddingLeft = this.node.getComputedPadding(Style.Edge.Left)\n const paddingTop = this.node.getComputedPadding(Style.Edge.Top)\n const paddingRight = this.node.getComputedPadding(Style.Edge.Right)\n const paddingBottom = this.node.getComputedPadding(Style.Edge.Bottom)\n const borderLeft = this.node.getComputedBorder(Style.Edge.Left)\n const borderTop = this.node.getComputedBorder(Style.Edge.Top)\n const borderRight = this.node.getComputedBorder(Style.Edge.Right)\n const borderBottom = this.node.getComputedBorder(Style.Edge.Bottom)\n const contentX = x + borderLeft + paddingLeft\n const contentY = y + borderTop + paddingTop\n const contentWidth = Math.max(0, width - borderLeft - paddingLeft - borderRight - paddingRight)\n const contentHeight = Math.max(0, height - borderTop - paddingTop - borderBottom - paddingBottom)\n\n if (contentWidth <= 0 || contentHeight <= 0) return\n\n // Apply clipping for border radius\n ctx.save()\n const outerRadii = parseBorderRadius(this.props.borderRadius)\n const innerBorderRadii = {\n TopLeft: Math.max(0, outerRadii.TopLeft - borderTop),\n TopRight: Math.max(0, outerRadii.TopRight - borderTop),\n BottomRight: Math.max(0, outerRadii.BottomRight - borderBottom),\n BottomLeft: Math.max(0, outerRadii.BottomLeft - borderBottom),\n }\n const contentRadii = {\n TopLeft: Math.max(0, innerBorderRadii.TopLeft - Math.max(paddingLeft, paddingTop)),\n TopRight: Math.max(0, innerBorderRadii.TopRight - Math.max(paddingRight, paddingTop)),\n BottomRight: Math.max(0, innerBorderRadii.BottomRight - Math.max(paddingRight, paddingBottom)),\n BottomLeft: Math.max(0, innerBorderRadii.BottomLeft - Math.max(paddingLeft, paddingBottom)),\n }\n drawRoundedRectPath(ctx, contentX, contentY, contentWidth, contentHeight, contentRadii)\n ctx.clip()\n\n // Calculate image dimensions based on object-fit\n const nodeRatio = contentWidth / contentHeight\n const imgRatio = imgW / imgH\n const objectFit = this.props.objectFit\n let dw = contentWidth\n let dh = contentHeight\n\n if (objectFit === 'contain') {\n if (imgRatio > nodeRatio) {\n dw = contentWidth\n dh = contentWidth / imgRatio\n } else {\n dh = contentHeight\n dw = contentHeight * imgRatio\n }\n } else if (objectFit === 'cover') {\n if (imgRatio > nodeRatio) {\n dh = contentHeight\n dw = contentHeight * imgRatio\n } else {\n dw = contentWidth\n dh = contentWidth / imgRatio\n }\n } else if (objectFit === 'none') {\n dw = imgW\n dh = imgH\n } else if (objectFit === 'scale-down') {\n if (imgW <= contentWidth && imgH <= contentHeight) {\n dw = imgW\n dh = imgH\n } else {\n if (imgRatio > nodeRatio) {\n dw = contentWidth\n dh = contentWidth / imgRatio\n } else {\n dh = contentHeight\n dw = contentHeight * imgRatio\n }\n }\n }\n\n // Calculate image position based on object-position\n const sx = 0\n const sy = 0\n const sw = imgW\n const sh = imgH\n\n const availableWidth = contentWidth - dw\n const availableHeight = contentHeight - dh\n const posProps = this.props.objectPosition || {}\n const horizontalValue = posProps.Left !== undefined ? posProps.Left : posProps.Right !== undefined ? posProps.Right : '50%'\n const verticalValue = posProps.Top !== undefined ? posProps.Top : posProps.Bottom !== undefined ? posProps.Bottom : '50%'\n\n let offsetX = calculateOffsetFromValue(horizontalValue, availableWidth)\n let offsetY = calculateOffsetFromValue(verticalValue, availableHeight)\n\n if (posProps.Left === undefined && posProps.Right !== undefined) {\n offsetX = availableWidth - offsetX\n }\n if (posProps.Top === undefined && posProps.Bottom !== undefined) {\n offsetY = availableHeight - offsetY\n }\n\n const dx = contentX + offsetX\n const dy = contentY + offsetY\n\n // Draw image with filters\n ctx.save()\n try {\n if (this.props.dropShadow) {\n const shadow = this.props.dropShadow\n const shadowBlur = Math.max(shadow.offsetX ?? 0, shadow.offsetY ?? 0)\n ctx.shadowOffsetX = shadow.offsetX ?? 0\n ctx.shadowOffsetY = shadow.offsetY ?? 0\n ctx.shadowBlur = Math.max(0, shadow.blur ?? shadowBlur)\n ctx.shadowColor = shadow.color ?? 'black'\n }\n\n const saturateValue = this.props.saturate ?? 1\n let filterString = ''\n if (saturateValue !== 1) {\n filterString += `saturate(${saturateValue * 100}%) `\n }\n\n if (filterString) {\n const currentFilter = ctx.filter && ctx.filter !== 'none' ? ctx.filter + ' ' : ''\n ctx.filter = currentFilter + filterString.trim()\n }\n\n const finalDX = Math.floor(dx)\n const finalDY = Math.floor(dy)\n const finalDW = Math.ceil(dw + (dx - finalDX))\n const finalDH = Math.ceil(dh + (dy - finalDY))\n\n if (finalDW > 0 && finalDH > 0) {\n ctx.drawImage(img, sx, sy, sw, sh, finalDX, finalDY, finalDW, finalDH)\n }\n } catch (drawError) {\n console.error('[ImageNode] Error drawing image:', drawError)\n } finally {\n ctx.restore()\n }\n\n ctx.restore()\n }\n}\n\n/**\n * Factory function to create ImageNode instances\n */\nexport const Image = (props: ImageProps): CanvasElement => ({\n __type: 'Image',\n props: props as Omit<ImageProps, 'onLoad' | 'onError'>,\n})\n"],"names":["BoxNode","Style","fs","loadImage","parseBorderRadius","drawRoundedRectPath"],"mappings":"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"}
@@ -7,6 +7,8 @@ export interface CanvasEngineConfig {
7
7
  workerMode?: boolean;
8
8
  /** Number of worker threads in the pool (default: os.cpus().length - 1) */
9
9
  workers?: number;
10
+ /** Maximum number of resolved images to keep in the persistent LRU cache (default: 128) */
11
+ imageCacheSize?: number;
10
12
  }
11
13
  /**
12
14
  * Configure the canvas rendering engine.
@@ -1 +1 @@
1
- {"version":3,"file":"root.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/root.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA8C,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,MAAM,gCAAgC,CAAA;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAkBlF,eAAO,MAAM,qBAAqB,YAEjC,CAAA;AAOD,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB,QAMpD;AA4LD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAmB5D;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACtC,6CAA6C;IAC7C,OAAO,CAAC,MAAM,CAAoB;IAClC,8CAA8C;IAC9C,OAAO,CAAC,GAAG,CAAwC;IACnD,4CAA4C;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;;;OAIG;gBACS,KAAK,EAAE,SAAS,GAAG,SAAS;IAoDxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CA6ChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAU,OAAO,SAAS,KAAG,OAAO,CAAC,MAAM,CAS3D,CAAA"}
1
+ {"version":3,"file":"root.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/root.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA8C,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,MAAM,gCAAgC,CAAA;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAkBlF,eAAO,MAAM,qBAAqB,YAEjC,CAAA;AAOD,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB,QAWpD;AA4LD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAmB5D;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACtC,6CAA6C;IAC7C,OAAO,CAAC,MAAM,CAAoB;IAClC,8CAA8C;IAC9C,OAAO,CAAC,GAAG,CAAwC;IACnD,4CAA4C;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;;;OAIG;gBACS,KAAK,EAAE,SAAS,GAAG,SAAS;IAoDxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAmDhC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAU,OAAO,SAAS,KAAG,OAAO,CAAC,MAAM,CAe3D,CAAA"}
@@ -50,6 +50,11 @@ function configure(options) {
50
50
  _workerMode = options.workerMode;
51
51
  if (options.workers !== undefined)
52
52
  _workerPoolSize = options.workers;
53
+ if (options.imageCacheSize !== undefined) {
54
+ // Dispose existing cache and create a new one with the specified size
55
+ image_canvas_util.disposeImageCache();
56
+ image_canvas_util.getImageCache(options.imageCacheSize);
57
+ }
53
58
  if (_workerMode) {
54
59
  _workerPool = new WorkerPool(_workerPoolSize);
55
60
  }
@@ -313,42 +318,49 @@ class RootNode extends layout_canvas_util.ColumnNode {
313
318
  * @returns Promise resolving to the rendered Canvas instance
314
319
  */
315
320
  async render() {
316
- // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
317
- // A per-render cache deduplicates identical src+color combinations within this render pass.
318
- const imageNodes = this.findAllImageNodes();
319
- if (imageNodes.length > 0) {
320
- const imageCache = new Map();
321
- const CONCURRENCY = 5;
322
- const queue = [...imageNodes];
323
- const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
324
- while (queue.length > 0) {
325
- const node = queue.shift();
326
- await node.load(imageCache);
327
- }
328
- });
329
- await Promise.allSettled(workers);
330
- }
331
- // Step 2: Calculate initial layout
332
- this.node.calculateLayout(this.targetWidth, undefined, common_const.Style.Direction.LTR);
333
- // Step 3: Allow nodes to finalize their layout
334
- const needRecalculate = this.finalizeLayout();
335
- if (needRecalculate) {
321
+ try {
322
+ // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
323
+ // A per-render cache deduplicates identical src+color combinations within this render pass.
324
+ const imageNodes = this.findAllImageNodes();
325
+ if (imageNodes.length > 0) {
326
+ const imageCache = new Map();
327
+ const CONCURRENCY = 5;
328
+ const queue = [...imageNodes];
329
+ const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
330
+ while (queue.length > 0) {
331
+ const node = queue.shift();
332
+ await node.load(imageCache);
333
+ }
334
+ });
335
+ await Promise.allSettled(workers);
336
+ }
337
+ // Step 2: Calculate initial layout
336
338
  this.node.calculateLayout(this.targetWidth, undefined, common_const.Style.Direction.LTR);
339
+ // Step 3: Allow nodes to finalize their layout
340
+ const needRecalculate = this.finalizeLayout();
341
+ if (needRecalculate) {
342
+ this.node.calculateLayout(this.targetWidth, undefined, common_const.Style.Direction.LTR);
343
+ }
344
+ // Step 4: Create a canvas with calculated dimensions
345
+ const calculatedContentHeight = this.node.getComputedHeight();
346
+ const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale);
347
+ const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale));
348
+ // Step 5: Set up canvas context
349
+ this.canvas = new skiaCanvas.Canvas(finalCanvasWidth, finalCanvasHeight);
350
+ this.ctx = this.canvas.getContext('2d');
351
+ this.ctx.scale(this.scale, this.scale);
352
+ // Step 6: Render content
353
+ super.render(this.ctx, 0, 0);
354
+ if (!this.canvas) {
355
+ throw new Error('Canvas not initialized');
356
+ }
357
+ return this.canvas;
337
358
  }
338
- // Step 4: Create a canvas with calculated dimensions
339
- const calculatedContentHeight = this.node.getComputedHeight();
340
- const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale);
341
- const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale));
342
- // Step 5: Set up canvas context
343
- this.canvas = new skiaCanvas.Canvas(finalCanvasWidth, finalCanvasHeight);
344
- this.ctx = this.canvas.getContext('2d');
345
- this.ctx.scale(this.scale, this.scale);
346
- // Step 6: Render content
347
- super.render(this.ctx, 0, 0);
348
- if (!this.canvas) {
349
- throw new Error('Canvas not initialized');
359
+ finally {
360
+ // Always clear the persistent image cache after render (success or error)
361
+ // so resolved CanvasImage references don't outlive the render pass.
362
+ image_canvas_util.disposeImageCache();
350
363
  }
351
- return this.canvas;
352
364
  }
353
365
  }
354
366
  /**
@@ -359,14 +371,21 @@ class RootNode extends layout_canvas_util.ColumnNode {
359
371
  * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)
360
372
  */
361
373
  const Root = async (props) => {
362
- if (_workerMode) {
363
- if (!_workerPool) {
364
- _workerPool = new WorkerPool(_workerPoolSize);
374
+ try {
375
+ if (_workerMode) {
376
+ if (!_workerPool) {
377
+ _workerPool = new WorkerPool(_workerPoolSize);
378
+ }
379
+ const result = await _workerPool.render(props);
380
+ return new WorkerCanvas({ ...result, pool: _workerPool });
365
381
  }
366
- const result = await _workerPool.render(props);
367
- return new WorkerCanvas({ ...result, pool: _workerPool });
382
+ return await new RootNode(props).render();
383
+ }
384
+ catch (err) {
385
+ // Ensure cache is cleared even if Root-level orchestration fails
386
+ image_canvas_util.disposeImageCache();
387
+ throw err;
368
388
  }
369
- return new RootNode(props).render();
370
389
  };
371
390
 
372
391
  exports.Root = Root;
@@ -1 +1 @@
1
- {"version":3,"file":"root.canvas.util.js","sources":["../../../../src/canvas/root.canvas.util.ts"],"sourcesContent":["import { Canvas, FontLibrary, type CanvasRenderingContext2D } from 'skia-canvas'\nimport type { ExportFormat, ExportOptions, SaveOptions, RenderOptions } from 'skia-canvas'\nimport { ColumnNode, BoxNode, RowNode } from '@/canvas/layout.canvas.util.js'\nimport type { BaseProps, RootProps, CanvasElement } from '@/canvas/canvas.type.js'\nimport type { CanvasCallMethod, CallArgs, CallResult, WorkerCallRequest, WorkerResponse, WorkerRequest } from '@/canvas/worker.types.js'\nimport { ImageNode, type RenderImageCache } from '@/canvas/image.canvas.util.js'\nimport { TextNode } from '@/canvas/text.canvas.util.js'\nimport { ChartNode } from '@/canvas/chart.canvas.util.js'\nimport { GridNode, GridItemNode } from '@/canvas/grid.canvas.util.js'\nimport { Style } from '@/constant/common.const.js'\nimport { WorkerPreProcessor } from '@/canvas/canvas.helper.js'\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { cpus } from 'node:os'\nimport { Worker } from 'node:worker_threads'\nimport { fileURLToPath } from 'node:url'\n\n/** Registry to track fonts that have already been loaded */\nconst registeredFonts = new Map<string, Set<string>>()\n\n// Exported for testing purposes only\nexport const _clearRegisteredFonts = () => {\n registeredFonts.clear()\n}\n\n/** Engine configuration */\nlet _workerMode = true\nlet _workerPoolSize = Math.max(1, cpus().length - 1)\nlet _workerPool: WorkerPool | null = null\n\nexport interface CanvasEngineConfig {\n /** Run rendering in worker threads to avoid blocking the event loop (default: true) */\n workerMode?: boolean\n /** Number of worker threads in the pool (default: os.cpus().length - 1) */\n workers?: number\n}\n\n/**\n * Configure the canvas rendering engine.\n * Call this once at application startup before rendering.\n */\nexport function configure(options: CanvasEngineConfig) {\n if (options.workerMode !== undefined) _workerMode = options.workerMode\n if (options.workers !== undefined) _workerPoolSize = options.workers\n if (_workerMode) {\n _workerPool = new WorkerPool(_workerPoolSize)\n }\n}\n\ninterface PendingTask {\n resolve: (value: unknown) => void\n reject: (err: Error) => void\n}\n\ninterface PoolRenderResult {\n buffer: Buffer\n canvasId: number\n workerIdx: number\n width: number\n height: number\n}\n\n/**\n * Proxies all skia-canvas Canvas APIs to a Canvas instance living inside a worker thread.\n * Sync methods (toBufferSync, toURLSync) return from a pre-encoded PNG buffer.\n * Async methods (toBuffer, toURL, toFile, getters) delegate to the worker.\n */\nclass WorkerCanvas {\n readonly width: number\n readonly height: number\n private readonly _buffer: Buffer // pre-encoded PNG for sync use\n private readonly _pool: WorkerPool\n private readonly _workerIdx: number\n private readonly _canvasId: number\n\n constructor(opts: PoolRenderResult & { pool: WorkerPool }) {\n this._buffer = opts.buffer\n this.width = opts.width\n this.height = opts.height\n this._pool = opts.pool\n this._workerIdx = opts.workerIdx\n this._canvasId = opts.canvasId\n }\n\n private _call<M extends CanvasCallMethod>(method: M, ...args: CallArgs<M>): Promise<CallResult<M>> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, method, args)\n }\n\n // --- Sync methods: return from pre-encoded PNG buffer ---\n\n toBufferSync(_format?: ExportFormat, _options?: ExportOptions): Buffer {\n return this._buffer\n }\n\n toURLSync(_format?: ExportFormat, _options?: ExportOptions): string {\n return `data:image/png;base64,${this._buffer.toString('base64')}`\n }\n\n // --- Async methods: delegate to worker ---\n\n toBuffer(format: ExportFormat, options?: ExportOptions): Promise<Buffer> {\n return this._call('toBuffer', format, options)\n }\n\n toURL(format: ExportFormat, options?: ExportOptions): Promise<string> {\n return this._call('toURL', format, options)\n }\n\n toFile(filename: string, options?: SaveOptions): Promise<void> {\n return this._call('toFile', filename, options)\n }\n\n /** Returns a Buffer (Sharp instance cannot be transferred across threads) */\n toSharp(options?: RenderOptions): Promise<Buffer> {\n return this._call('toSharp', options)\n }\n\n toSharpSync(_options?: RenderOptions): never {\n throw new Error('[canvas] toSharpSync() is not available in worker mode — use toSharp() instead')\n }\n\n // --- Async convenience getters ---\n\n get png(): Promise<Buffer> {\n return this._call('toBuffer', 'png')\n }\n get webp(): Promise<Buffer> {\n return this._call('toBuffer', 'webp')\n }\n get jpg(): Promise<Buffer> {\n return this._call('toBuffer', 'jpg')\n }\n get svg(): Promise<Buffer> {\n return this._call('toBuffer', 'svg')\n }\n get pdf(): Promise<Buffer> {\n return this._call('toBuffer', 'pdf')\n }\n get raw(): Promise<Buffer> {\n return this._call('toBuffer', 'raw')\n }\n\n /** Release the Canvas from worker memory. Call when done with this object. */\n release(): void {\n this._pool.releaseCanvas(this._workerIdx, this._canvasId)\n }\n}\n\n/** Worker thread pool — routes render and canvas-call messages */\nclass WorkerPool {\n private workers: Worker[] = []\n private idle: Worker[] = []\n private queue: Array<{ id: number; props: RootProps }> = []\n private pending = new Map<number, PendingTask>()\n private nextId = 0\n\n constructor(size: number) {\n this.init(size)\n }\n\n private init(size: number) {\n const workerFile = path.join(path.dirname(fileURLToPath(import.meta.url)), '../render.worker.js')\n\n for (let i = 0; i < size; i++) {\n const workerIdx = i\n const worker = new Worker(workerFile)\n worker.on('message', (msg: WorkerResponse) => {\n const task = this.pending.get(msg.taskId)\n if (!task) return\n this.pending.delete(msg.taskId)\n\n if ('error' in msg) {\n task.reject(new Error(msg.error))\n return\n }\n\n if ('canvasId' in msg) {\n // Render complete — put worker back to idle\n this.idle.push(worker)\n this.drain()\n const result: PoolRenderResult = { buffer: msg.buffer, canvasId: msg.canvasId, workerIdx, width: msg.width, height: msg.height }\n task.resolve(result)\n } else {\n // Canvas method call complete\n task.resolve(msg.result)\n }\n })\n this.workers.push(worker)\n this.idle.push(worker)\n }\n }\n\n private drain() {\n while (this.queue.length > 0 && this.idle.length > 0) {\n const task = this.queue.shift()!\n const worker = this.idle.pop()!\n const request: WorkerRequest = { type: 'render', taskId: task.id, props: task.props }\n worker.postMessage(request)\n }\n }\n\n render(props: RootProps): Promise<PoolRenderResult> {\n const sanitizedProps = WorkerPreProcessor.process(props)\n return new Promise<PoolRenderResult>((resolve, reject) => {\n const id = this.nextId++\n this.pending.set(id, { resolve: resolve as (v: unknown) => void, reject })\n if (this.idle.length > 0) {\n const worker = this.idle.pop()!\n const request: WorkerRequest = { type: 'render', taskId: id, props: sanitizedProps }\n worker.postMessage(request)\n } else {\n this.queue.push({ id, props: sanitizedProps })\n }\n })\n }\n\n callOnCanvas<M extends CanvasCallMethod>(workerIdx: number, canvasId: number, method: M, args: CallArgs<M>): Promise<CallResult<M>> {\n return new Promise<CallResult<M>>((resolve, reject) => {\n const id = this.nextId++\n this.pending.set(id, { resolve: resolve as (v: unknown) => void, reject })\n const request = { type: 'call' as const, taskId: id, canvasId, method, args } as WorkerCallRequest\n this.workers[workerIdx].postMessage(request)\n })\n }\n\n releaseCanvas(workerIdx: number, canvasId: number): void {\n const request: WorkerRequest = { type: 'release', canvasId }\n this.workers[workerIdx]?.postMessage(request)\n }\n\n terminate() {\n this.workers.forEach(w => w.terminate())\n }\n}\n\n/**\n * Converts a CanvasElement tree into actual BoxNode instances.\n * Used both for non-worker rendering (inline tree building) and inside\n * the render worker (reconstructing the tree from serialized descriptors).\n */\nexport function buildTree(descriptor: CanvasElement): BoxNode {\n switch (descriptor.__type) {\n case 'Box':\n return new BoxNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) })\n case 'Column':\n return new ColumnNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) })\n case 'Row':\n return new RowNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) })\n case 'Grid':\n return new GridNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) as any })\n case 'GridItem':\n return new GridItemNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) as any })\n case 'Image':\n return new ImageNode(descriptor.props as any)\n case 'Text':\n return new TextNode(descriptor.text, descriptor.props)\n case 'Chart':\n return new ChartNode(descriptor.props as any)\n }\n}\n\n/**\n * Root node that manages the canvas rendering context and coordinates overall layout and drawing.\n * Inherits from ColumnNode to provide vertical layout capabilities.\n */\nexport class RootNode extends ColumnNode {\n /** The canvas instance used for rendering */\n private canvas: Canvas | undefined\n /** The 2D rendering context for the canvas */\n private ctx: CanvasRenderingContext2D | null = null\n /** Target width for the canvas in pixels */\n private readonly targetWidth: number\n /** Target height for the canvas in pixels */\n private readonly targetHeight: number\n /** Scale factor for rendering (e.g. 2 for 2x resolution) */\n private readonly scale: number\n\n /**\n * Creates a new root node for canvas rendering\n * @param props Configuration properties for the root node\n * @throws Error if width property is not provided\n */\n constructor(props: RootProps & BaseProps) {\n // Call the parent constructor with root name and props\n super({ name: 'Root', ...props })\n\n this.props = props\n\n // Validate the required width property\n if (!props.width) {\n throw new Error('Width and height are required for Root')\n }\n\n // Register provided fonts with caching\n if (props.fonts?.length) {\n for (const font of props.fonts) {\n const family = font.family\n const paths = font.paths.map(p => path.resolve(p))\n\n if (!registeredFonts.has(family)) {\n registeredFonts.set(family, new Set())\n }\n\n const cachedPaths = registeredFonts.get(family)!\n const newPaths = paths.filter(p => !cachedPaths.has(p) && fs.existsSync(p))\n\n if (newPaths.length > 0) {\n FontLibrary.use({ [family]: newPaths })\n newPaths.forEach(p => cachedPaths.add(p))\n }\n }\n }\n\n // Set up scale and width\n this.scale = props.scale || 1\n this.targetWidth = props.width\n this.targetHeight = props.height\n this.node.setWidth(this.targetWidth)\n\n // Convert any CanvasElement children to actual BoxNode instances\n if (this.props.children) {\n const childArray = Array.isArray(this.props.children) ? this.props.children : [this.props.children]\n this.props.children = childArray.map(child => {\n if (child && typeof child === 'object' && '__type' in child) {\n return buildTree(child as CanvasElement)\n }\n return child\n }) as any\n }\n\n // Initialize children nodes\n this.processInitialChildren()\n }\n\n /**\n * Traverses the node tree to find all ImageNode instances using breadth-first search\n * @returns Array of all ImageNode instances found in the tree\n */\n private findAllImageNodes(): ImageNode[] {\n const imageNodes: ImageNode[] = []\n const queue: BoxNode[] = [this]\n while (queue.length > 0) {\n const node = queue.shift()!\n if (node instanceof ImageNode) {\n imageNodes.push(node)\n }\n queue.push(...node.children)\n }\n return imageNodes\n }\n\n /**\n * Renders the entire node tree to a canvas, handling image loading, layout calculation,\n * and final drawing\n * @returns Promise resolving to the rendered Canvas instance\n */\n async render(): Promise<Canvas> {\n // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.\n // A per-render cache deduplicates identical src+color combinations within this render pass.\n const imageNodes = this.findAllImageNodes()\n if (imageNodes.length > 0) {\n const imageCache: RenderImageCache = new Map()\n const CONCURRENCY = 5\n const queue = [...imageNodes]\n const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {\n while (queue.length > 0) {\n const node = queue.shift()!\n await node.load(imageCache)\n }\n })\n await Promise.allSettled(workers)\n }\n\n // Step 2: Calculate initial layout\n this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR)\n\n // Step 3: Allow nodes to finalize their layout\n const needRecalculate = this.finalizeLayout()\n if (needRecalculate) {\n this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR)\n }\n\n // Step 4: Create a canvas with calculated dimensions\n const calculatedContentHeight = this.node.getComputedHeight()\n const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale)\n const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale))\n\n // Step 5: Set up canvas context\n this.canvas = new Canvas(finalCanvasWidth, finalCanvasHeight)\n this.ctx = this.canvas.getContext('2d')\n this.ctx.scale(this.scale, this.scale)\n\n // Step 6: Render content\n super.render(this.ctx, 0, 0)\n\n if (!this.canvas) {\n throw new Error('Canvas not initialized')\n }\n\n return this.canvas\n }\n}\n\n/**\n * Creates and renders a new root node with the given properties.\n * When worker mode is enabled via configure(), rendering runs in a worker thread\n * and the returned object implements the same toBuffer/toBufferSync interface.\n * @param props Configuration properties for the root node\n * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)\n */\nexport const Root = async (props: RootProps): Promise<Canvas> => {\n if (_workerMode) {\n if (!_workerPool) {\n _workerPool = new WorkerPool(_workerPoolSize)\n }\n const result = await _workerPool.render(props)\n return new WorkerCanvas({ ...result, pool: _workerPool }) as unknown as Canvas\n }\n return new RootNode(props).render()\n}\n"],"names":["cpus","path","fileURLToPath","Worker","WorkerPreProcessor","BoxNode","ColumnNode","RowNode","GridNode","GridItemNode","ImageNode","TextNode","ChartNode","fs","FontLibrary","Style","Canvas"],"mappings":"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"}
1
+ {"version":3,"file":"root.canvas.util.js","sources":["../../../../src/canvas/root.canvas.util.ts"],"sourcesContent":["import { Canvas, FontLibrary, type CanvasRenderingContext2D } from 'skia-canvas'\nimport type { ExportFormat, ExportOptions, SaveOptions, RenderOptions } from 'skia-canvas'\nimport { ColumnNode, BoxNode, RowNode } from '@/canvas/layout.canvas.util.js'\nimport type { BaseProps, RootProps, CanvasElement } from '@/canvas/canvas.type.js'\nimport type { CanvasCallMethod, CallArgs, CallResult, WorkerCallRequest, WorkerResponse, WorkerRequest } from '@/canvas/worker.types.js'\nimport { ImageNode, type RenderImageCache, disposeImageCache, getImageCache } from '@/canvas/image.canvas.util.js'\nimport { TextNode } from '@/canvas/text.canvas.util.js'\nimport { ChartNode } from '@/canvas/chart.canvas.util.js'\nimport { GridNode, GridItemNode } from '@/canvas/grid.canvas.util.js'\nimport { Style } from '@/constant/common.const.js'\nimport { WorkerPreProcessor } from '@/canvas/canvas.helper.js'\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { cpus } from 'node:os'\nimport { Worker } from 'node:worker_threads'\nimport { fileURLToPath } from 'node:url'\n\n/** Registry to track fonts that have already been loaded */\nconst registeredFonts = new Map<string, Set<string>>()\n\n// Exported for testing purposes only\nexport const _clearRegisteredFonts = () => {\n registeredFonts.clear()\n}\n\n/** Engine configuration */\nlet _workerMode = true\nlet _workerPoolSize = Math.max(1, cpus().length - 1)\nlet _workerPool: WorkerPool | null = null\n\nexport interface CanvasEngineConfig {\n /** Run rendering in worker threads to avoid blocking the event loop (default: true) */\n workerMode?: boolean\n /** Number of worker threads in the pool (default: os.cpus().length - 1) */\n workers?: number\n /** Maximum number of resolved images to keep in the persistent LRU cache (default: 128) */\n imageCacheSize?: number\n}\n\n/**\n * Configure the canvas rendering engine.\n * Call this once at application startup before rendering.\n */\nexport function configure(options: CanvasEngineConfig) {\n if (options.workerMode !== undefined) _workerMode = options.workerMode\n if (options.workers !== undefined) _workerPoolSize = options.workers\n if (options.imageCacheSize !== undefined) {\n // Dispose existing cache and create a new one with the specified size\n disposeImageCache()\n getImageCache(options.imageCacheSize)\n }\n if (_workerMode) {\n _workerPool = new WorkerPool(_workerPoolSize)\n }\n}\n\ninterface PendingTask {\n resolve: (value: unknown) => void\n reject: (err: Error) => void\n}\n\ninterface PoolRenderResult {\n buffer: Buffer\n canvasId: number\n workerIdx: number\n width: number\n height: number\n}\n\n/**\n * Proxies all skia-canvas Canvas APIs to a Canvas instance living inside a worker thread.\n * Sync methods (toBufferSync, toURLSync) return from a pre-encoded PNG buffer.\n * Async methods (toBuffer, toURL, toFile, getters) delegate to the worker.\n */\nclass WorkerCanvas {\n readonly width: number\n readonly height: number\n private readonly _buffer: Buffer // pre-encoded PNG for sync use\n private readonly _pool: WorkerPool\n private readonly _workerIdx: number\n private readonly _canvasId: number\n\n constructor(opts: PoolRenderResult & { pool: WorkerPool }) {\n this._buffer = opts.buffer\n this.width = opts.width\n this.height = opts.height\n this._pool = opts.pool\n this._workerIdx = opts.workerIdx\n this._canvasId = opts.canvasId\n }\n\n private _call<M extends CanvasCallMethod>(method: M, ...args: CallArgs<M>): Promise<CallResult<M>> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, method, args)\n }\n\n // --- Sync methods: return from pre-encoded PNG buffer ---\n\n toBufferSync(_format?: ExportFormat, _options?: ExportOptions): Buffer {\n return this._buffer\n }\n\n toURLSync(_format?: ExportFormat, _options?: ExportOptions): string {\n return `data:image/png;base64,${this._buffer.toString('base64')}`\n }\n\n // --- Async methods: delegate to worker ---\n\n toBuffer(format: ExportFormat, options?: ExportOptions): Promise<Buffer> {\n return this._call('toBuffer', format, options)\n }\n\n toURL(format: ExportFormat, options?: ExportOptions): Promise<string> {\n return this._call('toURL', format, options)\n }\n\n toFile(filename: string, options?: SaveOptions): Promise<void> {\n return this._call('toFile', filename, options)\n }\n\n /** Returns a Buffer (Sharp instance cannot be transferred across threads) */\n toSharp(options?: RenderOptions): Promise<Buffer> {\n return this._call('toSharp', options)\n }\n\n toSharpSync(_options?: RenderOptions): never {\n throw new Error('[canvas] toSharpSync() is not available in worker mode — use toSharp() instead')\n }\n\n // --- Async convenience getters ---\n\n get png(): Promise<Buffer> {\n return this._call('toBuffer', 'png')\n }\n get webp(): Promise<Buffer> {\n return this._call('toBuffer', 'webp')\n }\n get jpg(): Promise<Buffer> {\n return this._call('toBuffer', 'jpg')\n }\n get svg(): Promise<Buffer> {\n return this._call('toBuffer', 'svg')\n }\n get pdf(): Promise<Buffer> {\n return this._call('toBuffer', 'pdf')\n }\n get raw(): Promise<Buffer> {\n return this._call('toBuffer', 'raw')\n }\n\n /** Release the Canvas from worker memory. Call when done with this object. */\n release(): void {\n this._pool.releaseCanvas(this._workerIdx, this._canvasId)\n }\n}\n\n/** Worker thread pool — routes render and canvas-call messages */\nclass WorkerPool {\n private workers: Worker[] = []\n private idle: Worker[] = []\n private queue: Array<{ id: number; props: RootProps }> = []\n private pending = new Map<number, PendingTask>()\n private nextId = 0\n\n constructor(size: number) {\n this.init(size)\n }\n\n private init(size: number) {\n const workerFile = path.join(path.dirname(fileURLToPath(import.meta.url)), '../render.worker.js')\n\n for (let i = 0; i < size; i++) {\n const workerIdx = i\n const worker = new Worker(workerFile)\n worker.on('message', (msg: WorkerResponse) => {\n const task = this.pending.get(msg.taskId)\n if (!task) return\n this.pending.delete(msg.taskId)\n\n if ('error' in msg) {\n task.reject(new Error(msg.error))\n return\n }\n\n if ('canvasId' in msg) {\n // Render complete — put worker back to idle\n this.idle.push(worker)\n this.drain()\n const result: PoolRenderResult = { buffer: msg.buffer, canvasId: msg.canvasId, workerIdx, width: msg.width, height: msg.height }\n task.resolve(result)\n } else {\n // Canvas method call complete\n task.resolve(msg.result)\n }\n })\n this.workers.push(worker)\n this.idle.push(worker)\n }\n }\n\n private drain() {\n while (this.queue.length > 0 && this.idle.length > 0) {\n const task = this.queue.shift()!\n const worker = this.idle.pop()!\n const request: WorkerRequest = { type: 'render', taskId: task.id, props: task.props }\n worker.postMessage(request)\n }\n }\n\n render(props: RootProps): Promise<PoolRenderResult> {\n const sanitizedProps = WorkerPreProcessor.process(props)\n return new Promise<PoolRenderResult>((resolve, reject) => {\n const id = this.nextId++\n this.pending.set(id, { resolve: resolve as (v: unknown) => void, reject })\n if (this.idle.length > 0) {\n const worker = this.idle.pop()!\n const request: WorkerRequest = { type: 'render', taskId: id, props: sanitizedProps }\n worker.postMessage(request)\n } else {\n this.queue.push({ id, props: sanitizedProps })\n }\n })\n }\n\n callOnCanvas<M extends CanvasCallMethod>(workerIdx: number, canvasId: number, method: M, args: CallArgs<M>): Promise<CallResult<M>> {\n return new Promise<CallResult<M>>((resolve, reject) => {\n const id = this.nextId++\n this.pending.set(id, { resolve: resolve as (v: unknown) => void, reject })\n const request = { type: 'call' as const, taskId: id, canvasId, method, args } as WorkerCallRequest\n this.workers[workerIdx].postMessage(request)\n })\n }\n\n releaseCanvas(workerIdx: number, canvasId: number): void {\n const request: WorkerRequest = { type: 'release', canvasId }\n this.workers[workerIdx]?.postMessage(request)\n }\n\n terminate() {\n this.workers.forEach(w => w.terminate())\n }\n}\n\n/**\n * Converts a CanvasElement tree into actual BoxNode instances.\n * Used both for non-worker rendering (inline tree building) and inside\n * the render worker (reconstructing the tree from serialized descriptors).\n */\nexport function buildTree(descriptor: CanvasElement): BoxNode {\n switch (descriptor.__type) {\n case 'Box':\n return new BoxNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) })\n case 'Column':\n return new ColumnNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) })\n case 'Row':\n return new RowNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) })\n case 'Grid':\n return new GridNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) as any })\n case 'GridItem':\n return new GridItemNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) as any })\n case 'Image':\n return new ImageNode(descriptor.props as any)\n case 'Text':\n return new TextNode(descriptor.text, descriptor.props)\n case 'Chart':\n return new ChartNode(descriptor.props as any)\n }\n}\n\n/**\n * Root node that manages the canvas rendering context and coordinates overall layout and drawing.\n * Inherits from ColumnNode to provide vertical layout capabilities.\n */\nexport class RootNode extends ColumnNode {\n /** The canvas instance used for rendering */\n private canvas: Canvas | undefined\n /** The 2D rendering context for the canvas */\n private ctx: CanvasRenderingContext2D | null = null\n /** Target width for the canvas in pixels */\n private readonly targetWidth: number\n /** Target height for the canvas in pixels */\n private readonly targetHeight: number\n /** Scale factor for rendering (e.g. 2 for 2x resolution) */\n private readonly scale: number\n\n /**\n * Creates a new root node for canvas rendering\n * @param props Configuration properties for the root node\n * @throws Error if width property is not provided\n */\n constructor(props: RootProps & BaseProps) {\n // Call the parent constructor with root name and props\n super({ name: 'Root', ...props })\n\n this.props = props\n\n // Validate the required width property\n if (!props.width) {\n throw new Error('Width and height are required for Root')\n }\n\n // Register provided fonts with caching\n if (props.fonts?.length) {\n for (const font of props.fonts) {\n const family = font.family\n const paths = font.paths.map(p => path.resolve(p))\n\n if (!registeredFonts.has(family)) {\n registeredFonts.set(family, new Set())\n }\n\n const cachedPaths = registeredFonts.get(family)!\n const newPaths = paths.filter(p => !cachedPaths.has(p) && fs.existsSync(p))\n\n if (newPaths.length > 0) {\n FontLibrary.use({ [family]: newPaths })\n newPaths.forEach(p => cachedPaths.add(p))\n }\n }\n }\n\n // Set up scale and width\n this.scale = props.scale || 1\n this.targetWidth = props.width\n this.targetHeight = props.height\n this.node.setWidth(this.targetWidth)\n\n // Convert any CanvasElement children to actual BoxNode instances\n if (this.props.children) {\n const childArray = Array.isArray(this.props.children) ? this.props.children : [this.props.children]\n this.props.children = childArray.map(child => {\n if (child && typeof child === 'object' && '__type' in child) {\n return buildTree(child as CanvasElement)\n }\n return child\n }) as any\n }\n\n // Initialize children nodes\n this.processInitialChildren()\n }\n\n /**\n * Traverses the node tree to find all ImageNode instances using breadth-first search\n * @returns Array of all ImageNode instances found in the tree\n */\n private findAllImageNodes(): ImageNode[] {\n const imageNodes: ImageNode[] = []\n const queue: BoxNode[] = [this]\n while (queue.length > 0) {\n const node = queue.shift()!\n if (node instanceof ImageNode) {\n imageNodes.push(node)\n }\n queue.push(...node.children)\n }\n return imageNodes\n }\n\n /**\n * Renders the entire node tree to a canvas, handling image loading, layout calculation,\n * and final drawing\n * @returns Promise resolving to the rendered Canvas instance\n */\n async render(): Promise<Canvas> {\n try {\n // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.\n // A per-render cache deduplicates identical src+color combinations within this render pass.\n const imageNodes = this.findAllImageNodes()\n if (imageNodes.length > 0) {\n const imageCache: RenderImageCache = new Map()\n const CONCURRENCY = 5\n const queue = [...imageNodes]\n const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {\n while (queue.length > 0) {\n const node = queue.shift()!\n await node.load(imageCache)\n }\n })\n await Promise.allSettled(workers)\n }\n\n // Step 2: Calculate initial layout\n this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR)\n\n // Step 3: Allow nodes to finalize their layout\n const needRecalculate = this.finalizeLayout()\n if (needRecalculate) {\n this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR)\n }\n\n // Step 4: Create a canvas with calculated dimensions\n const calculatedContentHeight = this.node.getComputedHeight()\n const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale)\n const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale))\n\n // Step 5: Set up canvas context\n this.canvas = new Canvas(finalCanvasWidth, finalCanvasHeight)\n this.ctx = this.canvas.getContext('2d')\n this.ctx.scale(this.scale, this.scale)\n\n // Step 6: Render content\n super.render(this.ctx, 0, 0)\n\n if (!this.canvas) {\n throw new Error('Canvas not initialized')\n }\n\n return this.canvas\n } finally {\n // Always clear the persistent image cache after render (success or error)\n // so resolved CanvasImage references don't outlive the render pass.\n disposeImageCache()\n }\n }\n}\n\n/**\n * Creates and renders a new root node with the given properties.\n * When worker mode is enabled via configure(), rendering runs in a worker thread\n * and the returned object implements the same toBuffer/toBufferSync interface.\n * @param props Configuration properties for the root node\n * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)\n */\nexport const Root = async (props: RootProps): Promise<Canvas> => {\n try {\n if (_workerMode) {\n if (!_workerPool) {\n _workerPool = new WorkerPool(_workerPoolSize)\n }\n const result = await _workerPool.render(props)\n return new WorkerCanvas({ ...result, pool: _workerPool }) as unknown as Canvas\n }\n return await new RootNode(props).render()\n } catch (err) {\n // Ensure cache is cleared even if Root-level orchestration fails\n disposeImageCache()\n throw err\n }\n}\n"],"names":["cpus","disposeImageCache","getImageCache","path","fileURLToPath","Worker","WorkerPreProcessor","BoxNode","ColumnNode","RowNode","GridNode","GridItemNode","ImageNode","TextNode","ChartNode","fs","FontLibrary","Style","Canvas"],"mappings":"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"}
@@ -1,7 +1,7 @@
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 } from './canvas/image.canvas.util.js';
4
+ export { Image, disposeImageCache } from './canvas/image.canvas.util.js';
5
5
  export { Text } from './canvas/text.canvas.util.js';
6
6
  export { Root, configure } from './canvas/root.canvas.util.js';
7
7
  export { GridItem } from './canvas/grid.canvas.util.js';
@@ -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,MAAM,+BAA+B,CAAA;AACrD,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,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"}
package/dist/cjs/index.js CHANGED
@@ -20,6 +20,7 @@ exports.Box = layout_canvas_util.Box;
20
20
  exports.Column = layout_canvas_util.Column;
21
21
  exports.Row = layout_canvas_util.Row;
22
22
  exports.Image = image_canvas_util.Image;
23
+ exports.disposeImageCache = image_canvas_util.disposeImageCache;
23
24
  exports.Text = text_canvas_util.Text;
24
25
  exports.Root = root_canvas_util.Root;
25
26
  exports.configure = root_canvas_util.configure;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -6,6 +6,36 @@ import { BoxNode } from '../canvas/layout.canvas.util.js';
6
6
  * Scoped to a single RootNode.render() call; discarded after rendering.
7
7
  */
8
8
  export type RenderImageCache = Map<string, Promise<CanvasImage>>;
9
+ /**
10
+ * A simple LRU cache for resolved `CanvasImage` objects.
11
+ *
12
+ * - Persists across render passes so repeated renders of the same tree don't
13
+ * re-fetch every image.
14
+ * - Bounded by `maxSize` entries; least-recently-used entries are evicted first.
15
+ * - Call `dispose()` to eagerly release all held images, or rely on the
16
+ * automatic `process.on('exit')` hook that clears the singleton.
17
+ */
18
+ export declare class ImageLRUCache {
19
+ private readonly map;
20
+ readonly maxSize: number;
21
+ constructor(maxSize: number);
22
+ get(key: string): CanvasImage | undefined;
23
+ set(key: string, image: CanvasImage): void;
24
+ has(key: string): boolean;
25
+ get size(): number;
26
+ dispose(): void;
27
+ }
28
+ /**
29
+ * Returns the singleton `ImageLRUCache`, creating it on first access.
30
+ * Registers a one-time process cleanup hook to clear the cache
31
+ * so native image buffers are freed when the process shuts down.
32
+ */
33
+ export declare function getImageCache(maxSize?: number): ImageLRUCache;
34
+ /**
35
+ * Explicitly disposes the global image cache.
36
+ * Useful in tests or when tearing down the rendering engine.
37
+ */
38
+ export declare function disposeImageCache(): void;
9
39
  /**
10
40
  * Renders images with configurable sizing, positioning, and effects.
11
41
  * Supports object-fit modes, positioning, border radius, and saturation filters.
@@ -24,8 +54,14 @@ export declare class ImageNode extends BoxNode {
24
54
  */
25
55
  private _fetchCanvasImage;
26
56
  /**
27
- * Loads and processes an image, using the render-scoped cache to avoid
28
- * re-fetching the same source within a single render pass.
57
+ * Loads and processes an image.
58
+ *
59
+ * Resolution order:
60
+ * 1. Persistent LRU cache (cross-render) — instant hit, no I/O.
61
+ * 2. Per-render dedup cache — avoids duplicate in-flight fetches within a single render.
62
+ * 3. Fresh fetch via `_fetchCanvasImage()`.
63
+ *
64
+ * Resolved images are stored back into the LRU cache for future renders.
29
65
  */
30
66
  private _loadImage;
31
67
  getLoadingPromise(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"image.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/image.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACnF,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,IAAI,WAAW,EAAa,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAsBxD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;AAEhE;;;GAGG;AACH,qBAAa,SAAU,SAAQ,OAAO;IAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,CAAA;IACrC,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAA6B;gBAEvC,KAAK,EAAE,UAAU;IAYtB,IAAI,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD;;;OAGG;YACW,iBAAiB;IAmE/B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAgDX,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC;;;OAGG;cACgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoJrH;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,UAAU,KAAG,aAGxC,CAAA"}
1
+ {"version":3,"file":"image.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/image.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACnF,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,IAAI,WAAW,EAAa,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAsBxD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAA;AAWhE;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA8B;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;gBAEZ,OAAO,EAAE,MAAM;IAI3B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IASzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAa1C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,IAAI,IAAI;CAGhB;AAUD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,MAA2B,GAAG,aAAa,CAejF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;;GAGG;AACH,qBAAa,SAAU,SAAQ,OAAO;IAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,CAAA;IACrC,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAA6B;gBAEvC,KAAK,EAAE,UAAU;IAYtB,IAAI,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD;;;OAGG;YACW,iBAAiB;IAmE/B;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;IAwEX,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC;;;OAGG;cACgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoJrH;AAED;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,UAAU,KAAG,aAGxC,CAAA"}
@@ -20,6 +20,86 @@ function calculateOffsetFromValue(positionValue, availableSpace) {
20
20
  console.warn(`[ImageNode] Invalid objectPosition value format: ${value}. Defaulting to 50%.`);
21
21
  return availableSpace * 0.5;
22
22
  }
23
+ /**
24
+ * A simple LRU cache for resolved `CanvasImage` objects.
25
+ *
26
+ * - Persists across render passes so repeated renders of the same tree don't
27
+ * re-fetch every image.
28
+ * - Bounded by `maxSize` entries; least-recently-used entries are evicted first.
29
+ * - Call `dispose()` to eagerly release all held images, or rely on the
30
+ * automatic `process.on('exit')` hook that clears the singleton.
31
+ */
32
+ class ImageLRUCache {
33
+ map = new Map();
34
+ maxSize;
35
+ constructor(maxSize) {
36
+ this.maxSize = maxSize;
37
+ }
38
+ get(key) {
39
+ const entry = this.map.get(key);
40
+ if (!entry)
41
+ return undefined;
42
+ // Move to end (most-recently used)
43
+ this.map.delete(key);
44
+ this.map.set(key, entry);
45
+ return entry.image;
46
+ }
47
+ set(key, image) {
48
+ // If key already exists, refresh it
49
+ if (this.map.has(key)) {
50
+ this.map.delete(key);
51
+ }
52
+ // Evict oldest if at capacity
53
+ while (this.map.size >= this.maxSize) {
54
+ const oldest = this.map.keys().next().value;
55
+ this.map.delete(oldest);
56
+ }
57
+ this.map.set(key, { image, key });
58
+ }
59
+ has(key) {
60
+ return this.map.has(key);
61
+ }
62
+ get size() {
63
+ return this.map.size;
64
+ }
65
+ dispose() {
66
+ this.map.clear();
67
+ }
68
+ }
69
+ /** Module-level singleton — lazily created on first render. */
70
+ let _globalImageCache = null;
71
+ const DEFAULT_CACHE_SIZE = 128;
72
+ // Symbol key on process to track hook registration across module reloads (e.g. Jest resetModules)
73
+ const HOOK_KEY = Symbol.for('__meonode_canvas_image_cache_hook__');
74
+ /**
75
+ * Returns the singleton `ImageLRUCache`, creating it on first access.
76
+ * Registers a one-time process cleanup hook to clear the cache
77
+ * so native image buffers are freed when the process shuts down.
78
+ */
79
+ function getImageCache(maxSize = DEFAULT_CACHE_SIZE) {
80
+ if (!_globalImageCache) {
81
+ _globalImageCache = new ImageLRUCache(maxSize);
82
+ }
83
+ if (!globalThis[HOOK_KEY]) {
84
+ globalThis[HOOK_KEY] = true;
85
+ const cleanup = () => {
86
+ _globalImageCache?.dispose();
87
+ _globalImageCache = null;
88
+ };
89
+ process.once('exit', cleanup);
90
+ process.once('SIGINT', cleanup);
91
+ process.once('SIGTERM', cleanup);
92
+ }
93
+ return _globalImageCache;
94
+ }
95
+ /**
96
+ * Explicitly disposes the global image cache.
97
+ * Useful in tests or when tearing down the rendering engine.
98
+ */
99
+ function disposeImageCache() {
100
+ _globalImageCache?.dispose();
101
+ _globalImageCache = null;
102
+ }
23
103
  /**
24
104
  * Renders images with configurable sizing, positioning, and effects.
25
105
  * Supports object-fit modes, positioning, border radius, and saturation filters.
@@ -111,8 +191,14 @@ class ImageNode extends BoxNode {
111
191
  return loadImage(finalSource);
112
192
  }
113
193
  /**
114
- * Loads and processes an image, using the render-scoped cache to avoid
115
- * re-fetching the same source within a single render pass.
194
+ * Loads and processes an image.
195
+ *
196
+ * Resolution order:
197
+ * 1. Persistent LRU cache (cross-render) — instant hit, no I/O.
198
+ * 2. Per-render dedup cache — avoids duplicate in-flight fetches within a single render.
199
+ * 3. Fresh fetch via `_fetchCanvasImage()`.
200
+ *
201
+ * Resolved images are stored back into the LRU cache for future renders.
116
202
  */
117
203
  _loadImage(cache) {
118
204
  if (!this.props.src) {
@@ -125,9 +211,26 @@ class ImageNode extends BoxNode {
125
211
  return new Promise(resolve => {
126
212
  const load = async () => {
127
213
  try {
214
+ const lru = getImageCache();
215
+ const cacheKey = typeof this.props.src === 'string' ? (this.props.color ? `${this.props.src}|${this.props.color}` : this.props.src) : null;
216
+ // 1. Check persistent LRU cache
217
+ if (cacheKey) {
218
+ const cached = lru.get(cacheKey);
219
+ if (cached) {
220
+ this.loadedImage = cached;
221
+ this.naturalWidth = cached.width;
222
+ this.naturalHeight = cached.height;
223
+ const calculatedAspectRatio = cached.width > 0 && cached.height > 0 ? cached.width / cached.height : undefined;
224
+ const finalAspectRatio = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : calculatedAspectRatio;
225
+ this.node.setAspectRatio(finalAspectRatio);
226
+ this.props.onLoad?.();
227
+ resolve();
228
+ return;
229
+ }
230
+ }
231
+ // 2. Per-render dedup cache or fresh fetch
128
232
  let imagePromise;
129
- if (cache && typeof this.props.src === 'string') {
130
- const cacheKey = this.props.color ? `${this.props.src}|${this.props.color}` : this.props.src;
233
+ if (cache && cacheKey) {
131
234
  if (!cache.has(cacheKey)) {
132
235
  cache.set(cacheKey, this._fetchCanvasImage());
133
236
  }
@@ -137,6 +240,10 @@ class ImageNode extends BoxNode {
137
240
  imagePromise = this._fetchCanvasImage();
138
241
  }
139
242
  const img = await imagePromise;
243
+ // 3. Store in persistent LRU cache
244
+ if (cacheKey) {
245
+ lru.set(cacheKey, img);
246
+ }
140
247
  this.loadedImage = img;
141
248
  this.naturalWidth = img.width;
142
249
  this.naturalHeight = img.height;
@@ -317,4 +424,4 @@ const Image = (props) => ({
317
424
  props: props,
318
425
  });
319
426
 
320
- export { Image, ImageNode };
427
+ export { Image, ImageLRUCache, ImageNode, disposeImageCache, getImageCache };
@@ -7,6 +7,8 @@ export interface CanvasEngineConfig {
7
7
  workerMode?: boolean;
8
8
  /** Number of worker threads in the pool (default: os.cpus().length - 1) */
9
9
  workers?: number;
10
+ /** Maximum number of resolved images to keep in the persistent LRU cache (default: 128) */
11
+ imageCacheSize?: number;
10
12
  }
11
13
  /**
12
14
  * Configure the canvas rendering engine.
@@ -1 +1 @@
1
- {"version":3,"file":"root.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/root.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA8C,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,MAAM,gCAAgC,CAAA;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAkBlF,eAAO,MAAM,qBAAqB,YAEjC,CAAA;AAOD,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB,QAMpD;AA4LD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAmB5D;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACtC,6CAA6C;IAC7C,OAAO,CAAC,MAAM,CAAoB;IAClC,8CAA8C;IAC9C,OAAO,CAAC,GAAG,CAAwC;IACnD,4CAA4C;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;;;OAIG;gBACS,KAAK,EAAE,SAAS,GAAG,SAAS;IAoDxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CA6ChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAU,OAAO,SAAS,KAAG,OAAO,CAAC,MAAM,CAS3D,CAAA"}
1
+ {"version":3,"file":"root.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/root.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA8C,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,MAAM,gCAAgC,CAAA;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAkBlF,eAAO,MAAM,qBAAqB,YAEjC,CAAA;AAOD,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB,QAWpD;AA4LD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAmB5D;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACtC,6CAA6C;IAC7C,OAAO,CAAC,MAAM,CAAoB;IAClC,8CAA8C;IAC9C,OAAO,CAAC,GAAG,CAAwC;IACnD,4CAA4C;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;;;OAIG;gBACS,KAAK,EAAE,SAAS,GAAG,SAAS;IAoDxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAmDhC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAU,OAAO,SAAS,KAAG,OAAO,CAAC,MAAM,CAe3D,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { FontLibrary, Canvas } from 'skia-canvas';
2
2
  import { ColumnNode, RowNode, BoxNode } from './layout.canvas.util.js';
3
- import { ImageNode } from './image.canvas.util.js';
3
+ import { ImageNode, disposeImageCache, getImageCache } from './image.canvas.util.js';
4
4
  import { TextNode } from './text.canvas.util.js';
5
5
  import { ChartNode } from './chart.canvas.util.js';
6
6
  import { GridItemNode, GridNode } from './grid.canvas.util.js';
@@ -27,6 +27,11 @@ function configure(options) {
27
27
  _workerMode = options.workerMode;
28
28
  if (options.workers !== undefined)
29
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
+ }
30
35
  if (_workerMode) {
31
36
  _workerPool = new WorkerPool(_workerPoolSize);
32
37
  }
@@ -290,42 +295,49 @@ class RootNode extends ColumnNode {
290
295
  * @returns Promise resolving to the rendered Canvas instance
291
296
  */
292
297
  async render() {
293
- // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
294
- // A per-render cache deduplicates identical src+color combinations within this render pass.
295
- const imageNodes = this.findAllImageNodes();
296
- if (imageNodes.length > 0) {
297
- const imageCache = new Map();
298
- const CONCURRENCY = 5;
299
- const queue = [...imageNodes];
300
- const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
301
- while (queue.length > 0) {
302
- const node = queue.shift();
303
- await node.load(imageCache);
304
- }
305
- });
306
- await Promise.allSettled(workers);
307
- }
308
- // Step 2: Calculate initial layout
309
- this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR);
310
- // Step 3: Allow nodes to finalize their layout
311
- const needRecalculate = this.finalizeLayout();
312
- if (needRecalculate) {
298
+ try {
299
+ // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
300
+ // A per-render cache deduplicates identical src+color combinations within this render pass.
301
+ const imageNodes = this.findAllImageNodes();
302
+ if (imageNodes.length > 0) {
303
+ const imageCache = new Map();
304
+ const CONCURRENCY = 5;
305
+ const queue = [...imageNodes];
306
+ const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
307
+ while (queue.length > 0) {
308
+ const node = queue.shift();
309
+ await node.load(imageCache);
310
+ }
311
+ });
312
+ await Promise.allSettled(workers);
313
+ }
314
+ // Step 2: Calculate initial layout
313
315
  this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR);
316
+ // Step 3: Allow nodes to finalize their layout
317
+ const needRecalculate = this.finalizeLayout();
318
+ if (needRecalculate) {
319
+ this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR);
320
+ }
321
+ // Step 4: Create a canvas with calculated dimensions
322
+ const calculatedContentHeight = this.node.getComputedHeight();
323
+ const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale);
324
+ const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale));
325
+ // Step 5: Set up canvas context
326
+ this.canvas = new Canvas(finalCanvasWidth, finalCanvasHeight);
327
+ this.ctx = this.canvas.getContext('2d');
328
+ this.ctx.scale(this.scale, this.scale);
329
+ // Step 6: Render content
330
+ super.render(this.ctx, 0, 0);
331
+ if (!this.canvas) {
332
+ throw new Error('Canvas not initialized');
333
+ }
334
+ return this.canvas;
314
335
  }
315
- // Step 4: Create a canvas with calculated dimensions
316
- const calculatedContentHeight = this.node.getComputedHeight();
317
- const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale);
318
- const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale));
319
- // Step 5: Set up canvas context
320
- this.canvas = new Canvas(finalCanvasWidth, finalCanvasHeight);
321
- this.ctx = this.canvas.getContext('2d');
322
- this.ctx.scale(this.scale, this.scale);
323
- // Step 6: Render content
324
- super.render(this.ctx, 0, 0);
325
- if (!this.canvas) {
326
- throw new Error('Canvas not initialized');
336
+ 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();
327
340
  }
328
- return this.canvas;
329
341
  }
330
342
  }
331
343
  /**
@@ -336,14 +348,21 @@ class RootNode extends ColumnNode {
336
348
  * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)
337
349
  */
338
350
  const Root = async (props) => {
339
- if (_workerMode) {
340
- if (!_workerPool) {
341
- _workerPool = new WorkerPool(_workerPoolSize);
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 });
342
358
  }
343
- const result = await _workerPool.render(props);
344
- return new WorkerCanvas({ ...result, pool: _workerPool });
359
+ return await new RootNode(props).render();
360
+ }
361
+ catch (err) {
362
+ // Ensure cache is cleared even if Root-level orchestration fails
363
+ disposeImageCache();
364
+ throw err;
345
365
  }
346
- return new RootNode(props).render();
347
366
  };
348
367
 
349
368
  export { Root, RootNode, buildTree, configure };
@@ -1,7 +1,7 @@
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 } from './canvas/image.canvas.util.js';
4
+ export { Image, disposeImageCache } from './canvas/image.canvas.util.js';
5
5
  export { Text } from './canvas/text.canvas.util.js';
6
6
  export { Root, configure } from './canvas/root.canvas.util.js';
7
7
  export { GridItem } from './canvas/grid.canvas.util.js';
@@ -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,MAAM,+BAA+B,CAAA;AACrD,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,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"}
package/dist/esm/index.js CHANGED
@@ -1,6 +1,6 @@
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 } from './canvas/image.canvas.util.js';
3
+ export { Image, disposeImageCache } from './canvas/image.canvas.util.js';
4
4
  export { Text } from './canvas/text.canvas.util.js';
5
5
  export { Root, configure } from './canvas/root.canvas.util.js';
6
6
  export { Grid, GridItem } from './canvas/grid.canvas.util.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meonode/canvas",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
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",