@meonode/canvas 1.6.0-beta.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/canvas/image.canvas.util.d.ts +14 -6
- package/dist/cjs/canvas/image.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/image.canvas.util.js +78 -71
- package/dist/cjs/canvas/image.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/root.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/root.canvas.util.js +104 -25
- package/dist/cjs/canvas/root.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/worker.types.d.ts +76 -0
- package/dist/cjs/canvas/worker.types.d.ts.map +1 -0
- package/dist/cjs/render.worker.js +58 -9
- package/dist/cjs/render.worker.js.map +1 -1
- package/dist/esm/canvas/image.canvas.util.d.ts +14 -6
- package/dist/esm/canvas/image.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/image.canvas.util.js +78 -71
- package/dist/esm/canvas/root.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/root.canvas.util.js +104 -25
- package/dist/esm/canvas/worker.types.d.ts +76 -0
- package/dist/esm/canvas/worker.types.d.ts.map +1 -0
- package/dist/esm/render.worker.js +58 -9
- package/package.json +1 -1
|
@@ -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 { ColumnNode, BoxNode, RowNode } from '@/canvas/layout.canvas.util.js'\nimport type { BaseProps, RootProps, NodeDescriptor } from '@/canvas/canvas.type.js'\nimport { ImageNode } 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 * 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\n/**\n * Minimal Canvas-compatible wrapper returned when rendering in worker mode.\n * Exposes toBuffer / toBufferSync so callers can use the result identically.\n */\nclass WorkerCanvas {\n constructor(private readonly _buffer: Buffer) {}\n toBufferSync(_format?: string) {\n return this._buffer\n }\n toBuffer(_format?: string): Promise<Buffer> {\n return Promise.resolve(this._buffer)\n }\n}\n\n/** Lazy-instantiated worker pool singleton */\nclass WorkerPool {\n private workers: Worker[] = []\n private idle: Worker[] = []\n private queue: Array<{ id: number; props: RootProps }> = []\n private pending = new Map<number, { resolve: (b: Buffer) => void; reject: (e: Error) => void }>()\n private nextId = 0\n\n constructor(size: number) {\n this.init(size)\n }\n\n private async 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 worker = new Worker(workerFile)\n worker.on('message', ({ id, buffer, error }: { id: number; buffer?: Buffer; error?: string }) => {\n const task = this.pending.get(id)\n if (!task) return\n this.pending.delete(id)\n this.idle.push(worker)\n this.drain()\n if (error) {\n task.reject(new Error(error))\n } else {\n task.resolve(buffer!)\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 worker.postMessage({ id: task.id, props: task.props })\n }\n }\n\n render(props: RootProps): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const id = this.nextId++\n this.pending.set(id, { resolve, reject })\n if (this.idle.length > 0) {\n const worker = this.idle.pop()!\n worker.postMessage({ id, props })\n } else {\n this.queue.push({ id, props })\n }\n })\n }\n\n terminate() {\n this.workers.forEach(w => w.terminate())\n }\n}\n\n/**\n * Converts a NodeDescriptor 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: NodeDescriptor): 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 NodeDescriptor 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 NodeDescriptor)\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 const imageNodes = this.findAllImageNodes()\n if (imageNodes.length > 0) {\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()\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 buffer = await _workerPool.render(props)\n return new WorkerCanvas(buffer) as unknown as Canvas\n }\n return new RootNode(props).render()\n}\n"],"names":["cpus","path","fileURLToPath","Worker","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, NodeDescriptor } 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 * 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 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 }\n worker.postMessage(request)\n } else {\n this.queue.push({ id, props })\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 NodeDescriptor 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: NodeDescriptor): 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 NodeDescriptor 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 NodeDescriptor)\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","BoxNode","ColumnNode","RowNode","GridNode","GridItemNode","ImageNode","TextNode","ChartNode","fs","FontLibrary","Style","Canvas"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ExportFormat, ExportOptions, SaveOptions, RenderOptions } from 'skia-canvas';
|
|
2
|
+
import type { RootProps } from '../canvas/canvas.type.js';
|
|
3
|
+
export interface CanvasCallMap {
|
|
4
|
+
toBuffer: {
|
|
5
|
+
args: [ExportFormat, ExportOptions?];
|
|
6
|
+
result: Buffer;
|
|
7
|
+
};
|
|
8
|
+
toURL: {
|
|
9
|
+
args: [ExportFormat, ExportOptions?];
|
|
10
|
+
result: string;
|
|
11
|
+
};
|
|
12
|
+
toFile: {
|
|
13
|
+
args: [string, SaveOptions?];
|
|
14
|
+
result: void;
|
|
15
|
+
};
|
|
16
|
+
toSharp: {
|
|
17
|
+
args: [RenderOptions?];
|
|
18
|
+
result: Buffer;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export type CanvasCallMethod = keyof CanvasCallMap;
|
|
22
|
+
export type CallArgs<M extends CanvasCallMethod> = CanvasCallMap[M]['args'];
|
|
23
|
+
export type CallResult<M extends CanvasCallMethod> = CanvasCallMap[M]['result'];
|
|
24
|
+
export interface WorkerRenderRequest {
|
|
25
|
+
type: 'render';
|
|
26
|
+
taskId: number;
|
|
27
|
+
props: RootProps;
|
|
28
|
+
}
|
|
29
|
+
/** Discriminated union — narrows args alongside method in switch statements */
|
|
30
|
+
export type WorkerCallRequest = {
|
|
31
|
+
type: 'call';
|
|
32
|
+
taskId: number;
|
|
33
|
+
canvasId: number;
|
|
34
|
+
method: 'toBuffer';
|
|
35
|
+
args: CallArgs<'toBuffer'>;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'call';
|
|
38
|
+
taskId: number;
|
|
39
|
+
canvasId: number;
|
|
40
|
+
method: 'toURL';
|
|
41
|
+
args: CallArgs<'toURL'>;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'call';
|
|
44
|
+
taskId: number;
|
|
45
|
+
canvasId: number;
|
|
46
|
+
method: 'toFile';
|
|
47
|
+
args: CallArgs<'toFile'>;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'call';
|
|
50
|
+
taskId: number;
|
|
51
|
+
canvasId: number;
|
|
52
|
+
method: 'toSharp';
|
|
53
|
+
args: CallArgs<'toSharp'>;
|
|
54
|
+
};
|
|
55
|
+
export interface WorkerReleaseRequest {
|
|
56
|
+
type: 'release';
|
|
57
|
+
canvasId: number;
|
|
58
|
+
}
|
|
59
|
+
export type WorkerRequest = WorkerRenderRequest | WorkerCallRequest | WorkerReleaseRequest;
|
|
60
|
+
export interface WorkerRenderResponse {
|
|
61
|
+
taskId: number;
|
|
62
|
+
canvasId: number;
|
|
63
|
+
buffer: Buffer;
|
|
64
|
+
width: number;
|
|
65
|
+
height: number;
|
|
66
|
+
}
|
|
67
|
+
export interface WorkerCallResponse {
|
|
68
|
+
taskId: number;
|
|
69
|
+
result: Buffer | string | void;
|
|
70
|
+
}
|
|
71
|
+
export interface WorkerErrorResponse {
|
|
72
|
+
taskId: number;
|
|
73
|
+
error: string;
|
|
74
|
+
}
|
|
75
|
+
export type WorkerResponse = WorkerRenderResponse | WorkerCallResponse | WorkerErrorResponse;
|
|
76
|
+
//# sourceMappingURL=worker.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.types.d.ts","sourceRoot":"","sources":["../../../src/canvas/worker.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,20 +5,69 @@ var root_canvas_util = require('./canvas/root.canvas.util.js');
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Worker thread entry point for off-main-thread canvas rendering.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* Message protocol (main → worker):
|
|
10
|
+
* { type: 'render', taskId, props } — render and keep Canvas alive
|
|
11
|
+
* { type: 'call', taskId, canvasId, method, args } — call a method on a live Canvas
|
|
12
|
+
* { type: 'release', canvasId } — free Canvas from memory
|
|
13
|
+
*
|
|
14
|
+
* Responses (worker → main):
|
|
15
|
+
* WorkerRenderResponse — render complete (includes pre-encoded PNG buffer)
|
|
16
|
+
* WorkerCallResponse — method call result
|
|
17
|
+
* WorkerErrorResponse — any failure
|
|
10
18
|
*/
|
|
11
19
|
if (!worker_threads.parentPort) {
|
|
12
20
|
throw new Error('[render.worker] Must be run as a worker thread');
|
|
13
21
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
const canvases = new Map();
|
|
23
|
+
let nextCanvasId = 0;
|
|
24
|
+
function reply(msg) {
|
|
25
|
+
worker_threads.parentPort.postMessage(msg);
|
|
26
|
+
}
|
|
27
|
+
worker_threads.parentPort.on('message', async (msg) => {
|
|
28
|
+
if (msg.type === 'render') {
|
|
29
|
+
try {
|
|
30
|
+
const canvas = await new root_canvas_util.RootNode(msg.props).render();
|
|
31
|
+
const canvasId = nextCanvasId++;
|
|
32
|
+
canvases.set(canvasId, canvas);
|
|
33
|
+
reply({ taskId: msg.taskId, canvasId, buffer: canvas.toBufferSync('png'), width: canvas.width, height: canvas.height });
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
reply({ taskId: msg.taskId, error: String(err) });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (msg.type === 'call') {
|
|
40
|
+
const canvas = canvases.get(msg.canvasId);
|
|
41
|
+
if (!canvas) {
|
|
42
|
+
reply({ taskId: msg.taskId, error: `[render.worker] Canvas ${msg.canvasId} not found` });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
let result;
|
|
47
|
+
switch (msg.method) {
|
|
48
|
+
case 'toBuffer':
|
|
49
|
+
result = await canvas.toBuffer(...msg.args);
|
|
50
|
+
break;
|
|
51
|
+
case 'toURL':
|
|
52
|
+
result = await canvas.toURL(...msg.args);
|
|
53
|
+
break;
|
|
54
|
+
case 'toFile':
|
|
55
|
+
result = await canvas.toFile(...msg.args);
|
|
56
|
+
break;
|
|
57
|
+
case 'toSharp':
|
|
58
|
+
// Sharp instances can't be transferred across threads — serialize to buffer
|
|
59
|
+
result = await canvas.toSharp(...msg.args).toBuffer();
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
reply({ taskId: msg.taskId, result });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
reply({ taskId: msg.taskId, error: String(err) });
|
|
66
|
+
}
|
|
19
67
|
}
|
|
20
|
-
|
|
21
|
-
|
|
68
|
+
else {
|
|
69
|
+
// type === 'release'
|
|
70
|
+
canvases.delete(msg.canvasId);
|
|
22
71
|
}
|
|
23
72
|
});
|
|
24
73
|
//# sourceMappingURL=render.worker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.worker.js","sources":["../../../src/render.worker.ts"],"sourcesContent":["/**\n * Worker thread entry point for off-main-thread canvas rendering.\n *
|
|
1
|
+
{"version":3,"file":"render.worker.js","sources":["../../../src/render.worker.ts"],"sourcesContent":["/**\n * Worker thread entry point for off-main-thread canvas rendering.\n *\n * Message protocol (main → worker):\n * { type: 'render', taskId, props } — render and keep Canvas alive\n * { type: 'call', taskId, canvasId, method, args } — call a method on a live Canvas\n * { type: 'release', canvasId } — free Canvas from memory\n *\n * Responses (worker → main):\n * WorkerRenderResponse — render complete (includes pre-encoded PNG buffer)\n * WorkerCallResponse — method call result\n * WorkerErrorResponse — any failure\n */\nimport { parentPort } from 'worker_threads'\nimport { RootNode } from '@/canvas/root.canvas.util.js'\nimport type { Canvas } from 'skia-canvas'\nimport type { WorkerRequest, WorkerRenderResponse, WorkerCallResponse, WorkerErrorResponse } from '@/canvas/worker.types.js'\n\nif (!parentPort) {\n throw new Error('[render.worker] Must be run as a worker thread')\n}\n\nconst canvases = new Map<number, Canvas>()\nlet nextCanvasId = 0\n\nfunction reply(msg: WorkerRenderResponse | WorkerCallResponse | WorkerErrorResponse) {\n parentPort!.postMessage(msg)\n}\n\nparentPort.on('message', async (msg: WorkerRequest) => {\n if (msg.type === 'render') {\n try {\n const canvas = await new RootNode(msg.props).render()\n const canvasId = nextCanvasId++\n canvases.set(canvasId, canvas)\n reply({ taskId: msg.taskId, canvasId, buffer: canvas.toBufferSync('png'), width: canvas.width, height: canvas.height })\n } catch (err) {\n reply({ taskId: msg.taskId, error: String(err) })\n }\n } else if (msg.type === 'call') {\n const canvas = canvases.get(msg.canvasId)\n if (!canvas) {\n reply({ taskId: msg.taskId, error: `[render.worker] Canvas ${msg.canvasId} not found` })\n return\n }\n try {\n let result: Buffer | string | void\n switch (msg.method) {\n case 'toBuffer':\n result = await canvas.toBuffer(...msg.args)\n break\n case 'toURL':\n result = await canvas.toURL(...msg.args)\n break\n case 'toFile':\n result = await canvas.toFile(...msg.args)\n break\n case 'toSharp':\n // Sharp instances can't be transferred across threads — serialize to buffer\n result = await canvas.toSharp(...msg.args).toBuffer()\n break\n }\n reply({ taskId: msg.taskId, result })\n } catch (err) {\n reply({ taskId: msg.taskId, error: String(err) })\n }\n } else {\n // type === 'release'\n canvases.delete(msg.canvasId)\n }\n})\n"],"names":["parentPort","RootNode"],"mappings":";;;;;AAAA;;;;;;;;;;;;AAYG;AAMH,IAAI,CAACA,yBAAU,EAAE;AACf,IAAA,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC;AACnE;AAEA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB;AAC1C,IAAI,YAAY,GAAG,CAAC;AAEpB,SAAS,KAAK,CAAC,GAAoE,EAAA;AACjF,IAAAA,yBAAW,CAAC,WAAW,CAAC,GAAG,CAAC;AAC9B;AAEAA,yBAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,GAAkB,KAAI;AACpD,IAAA,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE;AACzB,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,MAAM,IAAIC,yBAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE;AACrD,YAAA,MAAM,QAAQ,GAAG,YAAY,EAAE;AAC/B,YAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC;AAC9B,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACzH;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD;IACF;AAAO,SAAA,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE;QAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE;AACX,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,0BAA0B,GAAG,CAAC,QAAQ,CAAA,UAAA,CAAY,EAAE,CAAC;YACxF;QACF;AACA,QAAA,IAAI;AACF,YAAA,IAAI,MAA8B;AAClC,YAAA,QAAQ,GAAG,CAAC,MAAM;AAChB,gBAAA,KAAK,UAAU;oBACb,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;oBAC3C;AACF,gBAAA,KAAK,OAAO;oBACV,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;oBACxC;AACF,gBAAA,KAAK,QAAQ;oBACX,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;oBACzC;AACF,gBAAA,KAAK,SAAS;;AAEZ,oBAAA,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;oBACrD;;YAEJ,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QACvC;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD;IACF;SAAO;;AAEL,QAAA,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC/B;AACF,CAAC,CAAC;;"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { BaseProps, ImageProps, NodeDescriptor } from '../canvas/canvas.type.js';
|
|
2
|
-
import { type CanvasRenderingContext2D } from 'skia-canvas';
|
|
2
|
+
import { type CanvasRenderingContext2D, Image as CanvasImage } from 'skia-canvas';
|
|
3
3
|
import { BoxNode } from '../canvas/layout.canvas.util.js';
|
|
4
|
+
/**
|
|
5
|
+
* Per-render image cache — keyed by `src|color` for string sources.
|
|
6
|
+
* Scoped to a single RootNode.render() call; discarded after rendering.
|
|
7
|
+
*/
|
|
8
|
+
export type RenderImageCache = Map<string, Promise<CanvasImage>>;
|
|
4
9
|
/**
|
|
5
10
|
* Renders images with configurable sizing, positioning, and effects.
|
|
6
11
|
* Supports object-fit modes, positioning, border radius, and saturation filters.
|
|
@@ -12,12 +17,15 @@ export declare class ImageNode extends BoxNode {
|
|
|
12
17
|
private naturalHeight;
|
|
13
18
|
private loadingPromise;
|
|
14
19
|
constructor(props: ImageProps);
|
|
15
|
-
load(): Promise<void>;
|
|
20
|
+
load(cache?: RenderImageCache): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Fetches and processes the image source into a CanvasImage.
|
|
23
|
+
* Does not touch node state — pure fetch logic.
|
|
24
|
+
*/
|
|
25
|
+
private _fetchCanvasImage;
|
|
16
26
|
/**
|
|
17
|
-
* Loads and processes an image
|
|
18
|
-
*
|
|
19
|
-
* @returns Promise that resolves when image loading completes
|
|
20
|
-
* @throws Error if image loading fails
|
|
27
|
+
* Loads and processes an image, using the render-scoped cache to avoid
|
|
28
|
+
* re-fetching the same source within a single render pass.
|
|
21
29
|
*/
|
|
22
30
|
private _loadImage;
|
|
23
31
|
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,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAE,KAAK,wBAAwB,
|
|
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,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACpF,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,cAGxC,CAAA"}
|
|
@@ -39,23 +39,82 @@ class ImageNode extends BoxNode {
|
|
|
39
39
|
...props,
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
load() {
|
|
42
|
+
load(cache) {
|
|
43
43
|
if (!this.loadingPromise) {
|
|
44
|
-
this.loadingPromise = this._loadImage();
|
|
44
|
+
this.loadingPromise = this._loadImage(cache);
|
|
45
45
|
}
|
|
46
46
|
return this.loadingPromise;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* @returns Promise that resolves when image loading completes
|
|
52
|
-
* @throws Error if image loading fails
|
|
49
|
+
* Fetches and processes the image source into a CanvasImage.
|
|
50
|
+
* Does not touch node state — pure fetch logic.
|
|
53
51
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
async _fetchCanvasImage() {
|
|
53
|
+
const { fileTypeFromBuffer, fileTypeFromFile } = await import('file-type');
|
|
54
|
+
let finalSource = this.props.src;
|
|
55
|
+
let isSvg = false;
|
|
56
|
+
let contentBuffer = null;
|
|
57
|
+
let detectedMime;
|
|
58
|
+
if (typeof this.props.src === 'string') {
|
|
59
|
+
if (this.props.src.startsWith('http')) {
|
|
60
|
+
const response = await fetch(this.props.src);
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`HTTP error ${response.status} fetching image: ${this.props.src}`);
|
|
63
|
+
}
|
|
64
|
+
const imageArrayBuffer = await response.arrayBuffer();
|
|
65
|
+
contentBuffer = Buffer.from(imageArrayBuffer);
|
|
66
|
+
finalSource = contentBuffer;
|
|
67
|
+
const fileTypeResult = await fileTypeFromBuffer(contentBuffer);
|
|
68
|
+
detectedMime = fileTypeResult?.mime;
|
|
69
|
+
isSvg = detectedMime === 'image/svg+xml';
|
|
70
|
+
if ((!detectedMime || detectedMime === 'application/xml') && contentBuffer.toString('utf-8').includes('<svg')) {
|
|
71
|
+
isSvg = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
finalSource = this.props.src;
|
|
76
|
+
const filePath = this.props.src;
|
|
77
|
+
try {
|
|
78
|
+
const fileTypeResult = await fileTypeFromFile(filePath);
|
|
79
|
+
detectedMime = fileTypeResult?.mime;
|
|
80
|
+
isSvg = detectedMime === 'image/svg+xml';
|
|
81
|
+
if ((!detectedMime || detectedMime === 'application/xml') && filePath.toLowerCase().endsWith('.svg')) {
|
|
82
|
+
isSvg = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
isSvg = filePath.toLowerCase().endsWith('.svg');
|
|
87
|
+
}
|
|
88
|
+
if (isSvg && this.props.color) {
|
|
89
|
+
try {
|
|
90
|
+
contentBuffer = await promises.readFile(filePath);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
isSvg = false;
|
|
94
|
+
contentBuffer = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
contentBuffer = this.props.src;
|
|
101
|
+
finalSource = contentBuffer;
|
|
102
|
+
const fileTypeResult = await fileTypeFromBuffer(contentBuffer);
|
|
103
|
+
detectedMime = fileTypeResult?.mime;
|
|
104
|
+
isSvg = detectedMime === 'image/svg+xml';
|
|
105
|
+
}
|
|
106
|
+
if (isSvg && this.props.color && contentBuffer) {
|
|
107
|
+
const svgString = contentBuffer.toString('utf-8');
|
|
108
|
+
const modifiedSvgString = svgString.replace(/fill="[^"]*"/g, `fill="${this.props.color}"`);
|
|
109
|
+
finalSource = modifiedSvgString !== svgString ? Buffer.from(modifiedSvgString) : contentBuffer;
|
|
110
|
+
}
|
|
111
|
+
return loadImage(finalSource);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Loads and processes an image, using the render-scoped cache to avoid
|
|
115
|
+
* re-fetching the same source within a single render pass.
|
|
116
|
+
*/
|
|
117
|
+
_loadImage(cache) {
|
|
59
118
|
if (!this.props.src) {
|
|
60
119
|
const aspectRatioFromProps = typeof this.props.aspectRatio === 'number' && this.props.aspectRatio > 0 ? this.props.aspectRatio : undefined;
|
|
61
120
|
this.node.setAspectRatio(aspectRatioFromProps);
|
|
@@ -65,71 +124,19 @@ class ImageNode extends BoxNode {
|
|
|
65
124
|
}
|
|
66
125
|
return new Promise(resolve => {
|
|
67
126
|
const load = async () => {
|
|
68
|
-
const { fileTypeFromBuffer, fileTypeFromFile } = await import('file-type');
|
|
69
|
-
let finalSource = this.props.src;
|
|
70
|
-
let isSvg = false;
|
|
71
|
-
let contentBuffer = null;
|
|
72
|
-
let detectedMime;
|
|
73
127
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
const imageArrayBuffer = await response.arrayBuffer();
|
|
81
|
-
contentBuffer = Buffer.from(imageArrayBuffer);
|
|
82
|
-
finalSource = contentBuffer;
|
|
83
|
-
const fileTypeResult = await fileTypeFromBuffer(contentBuffer);
|
|
84
|
-
detectedMime = fileTypeResult?.mime;
|
|
85
|
-
isSvg = detectedMime === 'image/svg+xml';
|
|
86
|
-
if ((!detectedMime || detectedMime === 'application/xml') && contentBuffer.toString('utf-8').includes('<svg')) {
|
|
87
|
-
isSvg = true;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
finalSource = this.props.src;
|
|
92
|
-
const filePath = this.props.src;
|
|
93
|
-
try {
|
|
94
|
-
const fileTypeResult = await fileTypeFromFile(filePath);
|
|
95
|
-
detectedMime = fileTypeResult?.mime;
|
|
96
|
-
isSvg = detectedMime === 'image/svg+xml';
|
|
97
|
-
if ((!detectedMime || detectedMime === 'application/xml') && filePath.toLowerCase().endsWith('.svg')) {
|
|
98
|
-
isSvg = true;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
isSvg = filePath.toLowerCase().endsWith('.svg');
|
|
103
|
-
}
|
|
104
|
-
if (isSvg && this.props.color) {
|
|
105
|
-
try {
|
|
106
|
-
contentBuffer = await promises.readFile(filePath);
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
isSvg = false;
|
|
110
|
-
contentBuffer = null;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
128
|
+
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;
|
|
131
|
+
if (!cache.has(cacheKey)) {
|
|
132
|
+
cache.set(cacheKey, this._fetchCanvasImage());
|
|
113
133
|
}
|
|
134
|
+
imagePromise = cache.get(cacheKey);
|
|
114
135
|
}
|
|
115
136
|
else {
|
|
116
|
-
|
|
117
|
-
finalSource = contentBuffer;
|
|
118
|
-
const fileTypeResult = await fileTypeFromBuffer(contentBuffer);
|
|
119
|
-
detectedMime = fileTypeResult?.mime;
|
|
120
|
-
isSvg = detectedMime === 'image/svg+xml';
|
|
121
|
-
}
|
|
122
|
-
if (isSvg && this.props.color && contentBuffer) {
|
|
123
|
-
const svgString = contentBuffer.toString('utf-8');
|
|
124
|
-
const modifiedSvgString = svgString.replace(/fill="[^"]*"/g, `fill="${this.props.color}"`);
|
|
125
|
-
if (modifiedSvgString !== svgString) {
|
|
126
|
-
finalSource = Buffer.from(modifiedSvgString);
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
finalSource = contentBuffer;
|
|
130
|
-
}
|
|
137
|
+
imagePromise = this._fetchCanvasImage();
|
|
131
138
|
}
|
|
132
|
-
const img = await
|
|
139
|
+
const img = await imagePromise;
|
|
133
140
|
this.loadedImage = img;
|
|
134
141
|
this.naturalWidth = img.width;
|
|
135
142
|
this.naturalHeight = img.height;
|
|
@@ -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;
|
|
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,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAiBnF,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;AA2LD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAmB7D;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"}
|