@meonode/canvas 2.0.5 → 3.0.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.
Files changed (84) hide show
  1. package/CONTRIBUTING.md +11 -9
  2. package/README.md +9 -21
  3. package/dist/cjs/canvas/canvas.helper.d.ts +1 -1
  4. package/dist/cjs/canvas/canvas.type.d.ts +9 -4
  5. package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
  6. package/dist/cjs/canvas/chart.canvas.d.ts +7 -3
  7. package/dist/cjs/canvas/chart.canvas.d.ts.map +1 -1
  8. package/dist/cjs/canvas/chart.canvas.js +3 -4
  9. package/dist/cjs/canvas/chart.canvas.js.map +1 -1
  10. package/dist/cjs/canvas/grid.canvas.d.ts +2 -2
  11. package/dist/cjs/canvas/grid.canvas.d.ts.map +1 -1
  12. package/dist/cjs/canvas/grid.canvas.js +2 -10
  13. package/dist/cjs/canvas/grid.canvas.js.map +1 -1
  14. package/dist/cjs/canvas/image.canvas.d.ts +2 -2
  15. package/dist/cjs/canvas/image.canvas.js +2 -2
  16. package/dist/cjs/canvas/image.canvas.js.map +1 -1
  17. package/dist/cjs/canvas/layout.canvas.d.ts +5 -1
  18. package/dist/cjs/canvas/layout.canvas.d.ts.map +1 -1
  19. package/dist/cjs/canvas/layout.canvas.js +59 -68
  20. package/dist/cjs/canvas/layout.canvas.js.map +1 -1
  21. package/dist/cjs/canvas/root.canvas.d.ts +9 -16
  22. package/dist/cjs/canvas/root.canvas.d.ts.map +1 -1
  23. package/dist/cjs/canvas/root.canvas.js +56 -43
  24. package/dist/cjs/canvas/root.canvas.js.map +1 -1
  25. package/dist/cjs/canvas/text.canvas.d.ts +7 -3
  26. package/dist/cjs/canvas/text.canvas.d.ts.map +1 -1
  27. package/dist/cjs/canvas/text.canvas.js +25 -85
  28. package/dist/cjs/canvas/text.canvas.js.map +1 -1
  29. package/dist/cjs/index.d.ts +2 -2
  30. package/dist/cjs/index.d.ts.map +1 -1
  31. package/dist/cjs/index.js +1 -1
  32. package/dist/cjs/util/disk.cache.d.ts +5 -0
  33. package/dist/cjs/util/disk.cache.d.ts.map +1 -1
  34. package/dist/cjs/util/disk.cache.js +51 -24
  35. package/dist/cjs/util/disk.cache.js.map +1 -1
  36. package/dist/cjs/worker/canvas-handlers.d.ts +14 -0
  37. package/dist/cjs/worker/canvas-handlers.d.ts.map +1 -0
  38. package/dist/cjs/worker/canvas-handlers.js +42 -0
  39. package/dist/cjs/worker/canvas-handlers.js.map +1 -0
  40. package/dist/cjs/worker/comlink.pool.d.ts +1 -1
  41. package/dist/cjs/worker/comlink.pool.d.ts.map +1 -1
  42. package/dist/cjs/worker/comlink.pool.js +3 -0
  43. package/dist/cjs/worker/comlink.pool.js.map +1 -1
  44. package/dist/cjs/worker/comlink.setup.d.ts.map +1 -1
  45. package/dist/cjs/worker/comlink.setup.js +1 -1
  46. package/dist/cjs/worker/comlink.setup.js.map +1 -1
  47. package/dist/cjs/worker/render.worker.js +9 -32
  48. package/dist/cjs/worker/render.worker.js.map +1 -1
  49. package/dist/esm/canvas/canvas.helper.d.ts +1 -1
  50. package/dist/esm/canvas/canvas.type.d.ts +9 -4
  51. package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
  52. package/dist/esm/canvas/chart.canvas.d.ts +7 -3
  53. package/dist/esm/canvas/chart.canvas.d.ts.map +1 -1
  54. package/dist/esm/canvas/chart.canvas.js +3 -4
  55. package/dist/esm/canvas/grid.canvas.d.ts +2 -2
  56. package/dist/esm/canvas/grid.canvas.d.ts.map +1 -1
  57. package/dist/esm/canvas/grid.canvas.js +1 -9
  58. package/dist/esm/canvas/image.canvas.d.ts +2 -2
  59. package/dist/esm/canvas/image.canvas.js +2 -2
  60. package/dist/esm/canvas/layout.canvas.d.ts +5 -1
  61. package/dist/esm/canvas/layout.canvas.d.ts.map +1 -1
  62. package/dist/esm/canvas/layout.canvas.js +59 -69
  63. package/dist/esm/canvas/root.canvas.d.ts +9 -16
  64. package/dist/esm/canvas/root.canvas.d.ts.map +1 -1
  65. package/dist/esm/canvas/root.canvas.js +57 -43
  66. package/dist/esm/canvas/text.canvas.d.ts +7 -3
  67. package/dist/esm/canvas/text.canvas.d.ts.map +1 -1
  68. package/dist/esm/canvas/text.canvas.js +25 -85
  69. package/dist/esm/index.d.ts +2 -2
  70. package/dist/esm/index.d.ts.map +1 -1
  71. package/dist/esm/index.js +2 -2
  72. package/dist/esm/util/disk.cache.d.ts +5 -0
  73. package/dist/esm/util/disk.cache.d.ts.map +1 -1
  74. package/dist/esm/util/disk.cache.js +51 -25
  75. package/dist/esm/worker/canvas-handlers.d.ts +14 -0
  76. package/dist/esm/worker/canvas-handlers.d.ts.map +1 -0
  77. package/dist/esm/worker/canvas-handlers.js +39 -0
  78. package/dist/esm/worker/comlink.pool.d.ts +1 -1
  79. package/dist/esm/worker/comlink.pool.d.ts.map +1 -1
  80. package/dist/esm/worker/comlink.pool.js +3 -0
  81. package/dist/esm/worker/comlink.setup.d.ts.map +1 -1
  82. package/dist/esm/worker/comlink.setup.js +1 -1
  83. package/dist/esm/worker/render.worker.js +9 -32
  84. package/package.json +17 -21
@@ -1 +1 @@
1
- {"version":3,"file":"root.canvas.js","sources":["../../../../src/canvas/root.canvas.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.js'\nimport type { BaseProps, RootProps, CanvasElement, RootPropsWithWorker, RootPropsWithoutWorker } from '@/canvas/canvas.type.js'\nimport type { ComlinkPool as ComlinkPoolType, PoolRenderResult } from '@/worker/comlink.pool.js'\nimport { ImageNode, type RenderImageCache } from '@/canvas/image.canvas.js'\nimport { deleteDiskCache } from '@/util/disk.cache.js'\nimport { TextNode } from '@/canvas/text.canvas.js'\nimport { ChartNode } from '@/canvas/chart.canvas.js'\nimport { GridNode, GridItemNode } from '@/canvas/grid.canvas.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'\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/**\n * FinalizationRegistry to clean up WorkerCanvas instances that were not explicitly released.\n * This is a safety net — users should still call .release() explicitly.\n */\nconst canvasRegistry = new FinalizationRegistry<{ workerIdx: number; canvasId: number }>(heldValue => {\n try {\n _workerPool?.releaseCanvas(heldValue.workerIdx, heldValue.canvasId)\n } catch {\n // Worker already gone — nothing to clean up\n }\n})\n\n/** Engine configuration — legacy support for configure() */\nlet _defaultWorkerMode = true\nlet _defaultWorkerPoolSize = Math.max(1, cpus().length - 1)\nlet _workerPool: ComlinkPoolType | 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 * @deprecated Pass workerMode and workers directly to Root() props instead.\n */\nexport function configure(options: CanvasEngineConfig) {\n if (options.workerMode !== undefined) _defaultWorkerMode = options.workerMode\n if (options.workers !== undefined) _defaultWorkerPoolSize = options.workers\n}\n\n/**\n * Terminate all worker pools and free worker thread resources.\n * Call this when shutting down a long-running server to clean up immediately.\n * After calling, you must call configure() again before rendering.\n */\nexport function terminate() {\n if (_workerPool) {\n _workerPool.terminate()\n _workerPool = null\n }\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\n private readonly _pool: ComlinkPoolType\n private readonly _workerIdx: number\n private readonly _canvasId: number\n\n constructor(opts: PoolRenderResult & { pool: ComlinkPoolType }) {\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 canvasRegistry.register(this, { workerIdx: opts.workerIdx, canvasId: opts.canvasId }, this)\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 via Comlink ---\n\n toBuffer(format: ExportFormat, options?: ExportOptions): Promise<Buffer> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toBuffer', [format, options]) as Promise<Buffer>\n }\n\n toURL(format: ExportFormat, options?: ExportOptions): Promise<string> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toURL', [format, options]) as Promise<string>\n }\n\n toFile(filename: string, options?: SaveOptions): Promise<void> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toFile', [filename, options]) as Promise<void>\n }\n\n toSharp(options?: RenderOptions): Promise<Buffer> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toSharp', [options]) as Promise<Buffer>\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.toBuffer('png')\n }\n get webp(): Promise<Buffer> {\n return this.toBuffer('webp')\n }\n get jpg(): Promise<Buffer> {\n return this.toBuffer('jpg')\n }\n get svg(): Promise<Buffer> {\n return this.toBuffer('svg')\n }\n get pdf(): Promise<Buffer> {\n return this.toBuffer('pdf')\n }\n get raw(): Promise<Buffer> {\n return this.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 canvasRegistry.unregister(this)\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 declare props: RootProps & BaseProps\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 | undefined\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 override async render(ctx: CanvasRenderingContext2D, offsetX?: number, offsetY?: number): Promise<void>\n async render(ctx?: CanvasRenderingContext2D, offsetX?: number, offsetY?: number): Promise<Canvas>\n async render(ctx?: CanvasRenderingContext2D, offsetX = 0, offsetY = 0): Promise<Canvas | void> {\n // If ctx is provided, delegate to parent render (used when called as a child node)\n if (ctx) {\n await super.render(ctx, offsetX, offsetY)\n return\n }\n\n const diskCacheKeys = this.props.useDiskCache ? new Set<string>() : undefined\n\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, diskCacheKeys)\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 await 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 if (diskCacheKeys?.size) {\n await Promise.allSettled([...diskCacheKeys].map(key => deleteDiskCache(key)))\n }\n }\n }\n}\n\n/**\n * Creates and renders a new root node with the given properties.\n * Rendering runs in worker threads by default for non-blocking operation.\n * @example\n * // Worker mode (default) - .release() available\n * const canvas = await Root({ width: 400, children: [...] })\n * canvas.release() // ✓ OK\n * @example\n * // Worker mode explicit - .release() available\n * const canvas = await Root({ width: 400, workerMode: true, workers: 2 })\n * canvas.release() // ✓ OK\n * @example\n * // Non-worker mode - .release() NOT available, workers not allowed\n * const canvas = await Root({ width: 400, workerMode: false })\n * canvas.release() // ✗ TypeScript error\n * @param props Configuration properties for the root node\n * @returns Canvas with .release() in worker mode, plain Canvas otherwise\n */\nexport function Root(props: RootPropsWithWorker): Promise<Canvas & { release(): void }>\nexport function Root(props: RootPropsWithoutWorker): Promise<Canvas>\nexport async function Root(props: RootProps): Promise<Canvas | (Canvas & { release(): void })> {\n // Determine worker mode: props override legacy configure()\n const workerMode = props.workerMode ?? _defaultWorkerMode\n const workerPoolSize = props.workers ?? _defaultWorkerPoolSize\n\n if (workerMode) {\n // Lazy initialize worker pool — dynamic import to avoid loading Comlink in non-worker contexts\n if (!_workerPool) {\n const { ComlinkPool } = await import('@/worker/comlink.pool.js')\n _workerPool = new ComlinkPool(workerPoolSize)\n }\n const result = await _workerPool.render(props)\n return new WorkerCanvas({ ...result, pool: _workerPool }) as unknown as Canvas & { release(): void }\n }\n\n // Non-worker mode — render directly and return Canvas\n return new RootNode(props).render()\n}\n"],"names":["cpus","BoxNode","ColumnNode","RowNode","GridNode","GridItemNode","ImageNode","TextNode","ChartNode","path","fs","FontLibrary","Style","Canvas","deleteDiskCache"],"mappings":"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"}
1
+ {"version":3,"file":"root.canvas.js","sources":["../../../../src/canvas/root.canvas.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.js'\nimport type {\n BaseProps,\n RootProps,\n CanvasElement,\n RootPropsWithWorker,\n RootPropsWithoutWorker,\n Children,\n ImageProps,\n ChartProps,\n ChartType,\n} from '@/canvas/canvas.type.js'\nimport type { ComlinkPool as ComlinkPoolType, PoolRenderResult } from '@/worker/comlink.pool.js'\nimport { ImageNode, type RenderImageCache } from '@/canvas/image.canvas.js'\nimport { deleteDiskCache } from '@/util/disk.cache.js'\nimport { TextNode } from '@/canvas/text.canvas.js'\nimport { ChartNode } from '@/canvas/chart.canvas.js'\nimport { GridNode, GridItemNode } from '@/canvas/grid.canvas.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'\n\n/** Registry to track fonts that have already been loaded */\nconst registeredFonts = new Map<string, Set<string>>()\nlet _fontRegistrationLock: Promise<void> | null = null\n\n// Clears the font registry between test runs (internal, not exported from index)\nconst _clearRegisteredFonts = () => {\n registeredFonts.clear()\n _fontRegistrationLock = null\n}\n\n/**\n * FinalizationRegistry to clean up WorkerCanvas instances that were not explicitly released.\n * This is a safety net — users should still call .release() explicitly.\n */\nconst canvasRegistry = new FinalizationRegistry<{ workerIdx: number; canvasId: number }>(heldValue => {\n try {\n _workerPool?.releaseCanvas(heldValue.workerIdx, heldValue.canvasId)\n } catch {\n // Worker already gone — nothing to clean up\n }\n})\n\nlet _workerPool: ComlinkPoolType | null = null\n\n/**\n * Terminate all worker pools and free worker thread resources.\n * Call this when shutting down a long-running server to clean up immediately.\n */\nexport function terminate() {\n if (_workerPool) {\n _workerPool.terminate()\n _workerPool = null\n }\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\n private readonly _pool: ComlinkPoolType\n private readonly _workerIdx: number\n private readonly _canvasId: number\n\n constructor(opts: PoolRenderResult & { pool: ComlinkPoolType }) {\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 canvasRegistry.register(this, { workerIdx: opts.workerIdx, canvasId: opts.canvasId }, this)\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 via Comlink ---\n\n toBuffer(format: ExportFormat, options?: ExportOptions): Promise<Buffer> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toBuffer', [format, options]) as Promise<Buffer>\n }\n\n toURL(format: ExportFormat, options?: ExportOptions): Promise<string> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toURL', [format, options]) as Promise<string>\n }\n\n toFile(filename: string, options?: SaveOptions): Promise<void> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toFile', [filename, options]) as Promise<void>\n }\n\n toSharp(options?: RenderOptions): Promise<Buffer> {\n return this._pool.callOnCanvas(this._workerIdx, this._canvasId, 'toSharp', [options]) as Promise<Buffer>\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.toBuffer('png')\n }\n get webp(): Promise<Buffer> {\n return this.toBuffer('webp')\n }\n get jpg(): Promise<Buffer> {\n return this.toBuffer('jpg')\n }\n get svg(): Promise<Buffer> {\n return this.toBuffer('svg')\n }\n get pdf(): Promise<Buffer> {\n return this.toBuffer('pdf')\n }\n get raw(): Promise<Buffer> {\n return this.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 canvasRegistry.unregister(this)\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 Children[] })\n case 'GridItem':\n return new GridItemNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) as Children[] })\n case 'Image':\n return new ImageNode(descriptor.props as ImageProps)\n case 'Text':\n return new TextNode(descriptor.text, descriptor.props)\n case 'Chart':\n return new ChartNode(descriptor.props as ChartProps<ChartType>)\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 declare props: RootProps & BaseProps\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 | undefined\n /** Scale factor for rendering (e.g. 2 for 2x resolution) */\n private readonly scale: number\n /** Max concurrent image fetches during render (default: 5) */\n private readonly imageConcurrency: 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 // Set up scale and width\n this.scale = props.scale || 1\n this.targetWidth = props.width\n this.targetHeight = props.height\n this.imageConcurrency = props.imageConcurrency ?? 5\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 const converted: Children[] = childArray.map(child => {\n if (child && typeof child === 'object' && '__type' in child) {\n return buildTree(child as CanvasElement)\n }\n return child as Children\n })\n this.props.children = converted\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 let head = 0\n while (head < queue.length) {\n const node = queue[head++]\n if (node instanceof ImageNode) {\n imageNodes.push(node)\n }\n queue.push(...node.children)\n }\n return imageNodes\n }\n\n /**\n * Registers fonts with serialization to prevent duplicate FontLibrary.use() calls\n * when multiple Root() instances are created concurrently.\n */\n private async _registerFonts(): Promise<void> {\n if (!this.props.fonts?.length) return\n\n // Wait for any in-flight registration to complete\n if (_fontRegistrationLock) await _fontRegistrationLock\n\n _fontRegistrationLock = (async () => {\n try {\n for (const font of this.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 } finally {\n _fontRegistrationLock = null\n }\n })()\n\n await _fontRegistrationLock\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 override async render(ctx: CanvasRenderingContext2D, offsetX?: number, offsetY?: number): Promise<void>\n async render(ctx?: CanvasRenderingContext2D, offsetX?: number, offsetY?: number): Promise<Canvas>\n async render(ctx?: CanvasRenderingContext2D, offsetX = 0, offsetY = 0): Promise<Canvas | void> {\n // If ctx is provided, delegate to parent render (used when called as a child node)\n if (ctx) {\n await super.render(ctx, offsetX, offsetY)\n return\n }\n\n // Register fonts with serialization to prevent duplicate FontLibrary.use() across concurrent Root() calls\n await this._registerFonts()\n\n const diskCacheKeys = this.props.useDiskCache ? new Set<string>() : undefined\n\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 queue = [...imageNodes]\n let qIdx = 0\n const workers = Array.from({ length: Math.min(this.imageConcurrency, queue.length) }, async () => {\n while (qIdx < queue.length) {\n const node = queue[qIdx++]\n await node.load(imageCache, diskCacheKeys)\n }\n })\n await Promise.allSettled(workers).then(results => {\n results.forEach(r => {\n if (r.status === 'rejected') console.warn('[RootNode] Image load worker failed:', r.reason)\n })\n })\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 await 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 if (diskCacheKeys?.size) {\n await Promise.allSettled([...diskCacheKeys].map(key => deleteDiskCache(key)))\n }\n }\n }\n}\n\n/**\n * Creates and renders a new root node with the given properties.\n * Rendering runs in worker threads by default for non-blocking operation.\n * @example\n * // Worker mode (default) - .release() available\n * const canvas = await Root({ width: 400, children: [...] })\n * canvas.release() // ✓ OK\n * @example\n * // Worker mode explicit - .release() available\n * const canvas = await Root({ width: 400, workerMode: true, workers: 2 })\n * canvas.release() // ✓ OK\n * @example\n * // Non-worker mode - .release() NOT available, workers not allowed\n * const canvas = await Root({ width: 400, workerMode: false })\n * canvas.release() // ✗ TypeScript error\n * @param props Configuration properties for the root node\n * @returns Canvas with .release() in worker mode, plain Canvas otherwise\n */\nexport function Root(props: RootPropsWithWorker): Promise<Canvas & { release(): void }>\nexport function Root(props: RootPropsWithoutWorker): Promise<Canvas>\nexport async function Root(props: RootProps): Promise<Canvas | (Canvas & { release(): void })> {\n // Determine worker mode\n const workerMode = props.workerMode ?? true\n const workerPoolSize = props.workers ?? Math.max(1, cpus().length - 1)\n\n if (workerMode) {\n // Lazy initialize worker pool — dynamic import to avoid loading Comlink in non-worker contexts\n if (!_workerPool) {\n const { ComlinkPool } = await import('@/worker/comlink.pool.js')\n _workerPool = new ComlinkPool(workerPoolSize)\n }\n const result = await _workerPool.render(props)\n return new WorkerCanvas({ ...result, pool: _workerPool }) as unknown as Canvas & { release(): void }\n }\n\n // Non-worker mode — render directly and return Canvas\n return new RootNode(props).render()\n}\n"],"names":["BoxNode","ColumnNode","RowNode","GridNode","GridItemNode","ImageNode","TextNode","ChartNode","path","fs","FontLibrary","Style","Canvas","deleteDiskCache","cpus"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import type { TextProps, CanvasElement } from '../canvas/canvas.type.js';
1
+ import type { TextProps, CanvasElement } from './canvas.type.js';
2
2
  import { type CanvasRenderingContext2D } from 'skia-canvas';
3
- import { BoxNode } from '../canvas/layout.canvas.js';
3
+ import { BoxNode } from './layout.canvas.js';
4
4
  /**
5
5
  * Node for rendering text content with rich text styling support
6
6
  * Supports color and weight variations through HTML-like tags
@@ -140,7 +140,11 @@ export declare class TextNode extends BoxNode {
140
140
  */
141
141
  private measureSpaceWidth;
142
142
  /**
143
- * Renders multi-line text content with rich text styling and layout features
143
+ * Applies this.props.fontVariant to the context, or resets to 'normal'.
144
+ * Centralizes the type guard + warn pattern repeated across measure/render paths.
145
+ */
146
+ private _applyFontVariant;
147
+ /**
144
148
  *
145
149
  * Core features:
146
150
  * - Dynamic line heights with leading/spacing controls
@@ -1 +1 @@
1
- {"version":3,"file":"text.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGnD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEjC,KAAK,EAAE,SAAS,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAElC,IAAI,GAAE,MAAM,GAAG,MAAW,EAAE,KAAK,GAAE,SAAc;IAuB7D;;;;;;;;OAQG;WACW,gBAAgB,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,GAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACpC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;QAClC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAA;QACjD,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;KACnD;cAwBW,aAAa,IAAI,IAAI;IAoDxC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IA+NnB;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IA6KpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAmErB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;;;;;;;;;;;;;;OAgBG;cACsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAkX3H;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,KAAG,aAI9D,CAAA"}
1
+ {"version":3,"file":"text.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGnD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEjC,KAAK,EAAE,SAAS,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAElC,IAAI,GAAE,MAAM,GAAG,MAAW,EAAE,KAAK,GAAE,SAAc;IAuB7D;;;;;;;;OAQG;WACW,gBAAgB,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,GAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACpC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;QAClC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAA;QACjD,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;KACnD;cAwBW,aAAa,IAAI,IAAI;IAoDxC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IA4LnB;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IA6KpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAmErB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;;;;;;;;;;OAeG;cACsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoW3H;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,KAAG,aAI9D,CAAA"}
@@ -354,18 +354,7 @@ class TextNode extends layout_canvas.BoxNode {
354
354
  // Pre-measure each text segment width with its specific styling
355
355
  for (const segment of this.segments) {
356
356
  ctx.font = this.getFontString(segment);
357
- if (typeof this.props.fontVariant === 'string') {
358
- ctx.fontVariant = this.props.fontVariant;
359
- }
360
- else if (this.props.fontVariant !== undefined) {
361
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (segment width):`, this.props.fontVariant);
362
- if (ctx.fontVariant !== 'normal')
363
- ctx.fontVariant = 'normal';
364
- }
365
- else {
366
- if (ctx.fontVariant !== 'normal')
367
- ctx.fontVariant = 'normal';
368
- }
357
+ this._applyFontVariant(ctx, 'measureText (segment width)');
369
358
  segment.width = this.addLetterSpacingExtra(segment.text, ctx.measureText(segment.text).width, parsedLetterSpacingPx);
370
359
  }
371
360
  // Calculate available layout width
@@ -389,18 +378,7 @@ class TextNode extends layout_canvas.BoxNode {
389
378
  // Handle empty line metrics
390
379
  if (line.length === 0) {
391
380
  ctx.font = this.getFontString();
392
- if (typeof this.props.fontVariant === 'string') {
393
- ctx.fontVariant = this.props.fontVariant;
394
- }
395
- else if (this.props.fontVariant !== undefined) {
396
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (empty line):`, this.props.fontVariant);
397
- if (ctx.fontVariant !== 'normal')
398
- ctx.fontVariant = 'normal';
399
- }
400
- else {
401
- if (ctx.fontVariant !== 'normal')
402
- ctx.fontVariant = 'normal';
403
- }
381
+ this._applyFontVariant(ctx, 'measureText (empty line)');
404
382
  const metrics = ctx.measureText(this.metricsString);
405
383
  maxAscent = metrics.actualBoundingBoxAscent ?? baseFontSize * 0.8;
406
384
  maxDescent = metrics.actualBoundingBoxDescent ?? baseFontSize * 0.2;
@@ -414,18 +392,7 @@ class TextNode extends layout_canvas.BoxNode {
414
392
  const segmentSize = segment.size || baseFontSize;
415
393
  maxFontSizeOnLine = Math.max(maxFontSizeOnLine, segmentSize);
416
394
  ctx.font = this.getFontString(segment);
417
- if (typeof this.props.fontVariant === 'string') {
418
- ctx.fontVariant = this.props.fontVariant;
419
- }
420
- else if (this.props.fontVariant !== undefined) {
421
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (segment height):`, this.props.fontVariant);
422
- if (ctx.fontVariant !== 'normal')
423
- ctx.fontVariant = 'normal';
424
- }
425
- else {
426
- if (ctx.fontVariant !== 'normal')
427
- ctx.fontVariant = 'normal';
428
- }
395
+ this._applyFontVariant(ctx, 'measureText (segment height)');
429
396
  const metrics = ctx.measureText(this.metricsString);
430
397
  const ascent = metrics.actualBoundingBoxAscent ?? segmentSize * 0.8;
431
398
  const descent = metrics.actualBoundingBoxDescent ?? segmentSize * 0.2;
@@ -436,18 +403,7 @@ class TextNode extends layout_canvas.BoxNode {
436
403
  // Fallback metrics for lines with only whitespace
437
404
  if (maxAscent === 0 && maxDescent === 0 && line.length > 0) {
438
405
  ctx.font = this.getFontString();
439
- if (typeof this.props.fontVariant === 'string') {
440
- ctx.fontVariant = this.props.fontVariant;
441
- }
442
- else if (this.props.fontVariant !== undefined) {
443
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (fallback):`, this.props.fontVariant);
444
- if (ctx.fontVariant !== 'normal')
445
- ctx.fontVariant = 'normal';
446
- }
447
- else {
448
- if (ctx.fontVariant !== 'normal')
449
- ctx.fontVariant = 'normal';
450
- }
406
+ this._applyFontVariant(ctx, 'measureText (fallback)');
451
407
  const metrics = ctx.measureText(this.metricsString);
452
408
  maxAscent = metrics.actualBoundingBoxAscent ?? baseFontSize * 0.8;
453
409
  maxDescent = metrics.actualBoundingBoxDescent ?? baseFontSize * 0.2;
@@ -480,18 +436,7 @@ class TextNode extends layout_canvas.BoxNode {
480
436
  if (/^\s+$/.test(word))
481
437
  continue;
482
438
  ctx.font = this.getFontString(segment);
483
- if (typeof this.props.fontVariant === 'string') {
484
- ctx.fontVariant = this.props.fontVariant;
485
- }
486
- else if (this.props.fontVariant !== undefined) {
487
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (single line width):`, this.props.fontVariant);
488
- if (ctx.fontVariant !== 'normal')
489
- ctx.fontVariant = 'normal';
490
- }
491
- else {
492
- if (ctx.fontVariant !== 'normal')
493
- ctx.fontVariant = 'normal';
494
- }
439
+ this._applyFontVariant(ctx, 'measureText (single line width)');
495
440
  const wordWidth = this.addLetterSpacingExtra(word, ctx.measureText(word).width, parsedLetterSpacingPx);
496
441
  if (!firstWordInSingleLine) {
497
442
  singleLineWidth += spaceWidth + parsedWordSpacingPx;
@@ -802,7 +747,24 @@ class TextNode extends layout_canvas.BoxNode {
802
747
  return width > 0 ? width : (this.props.fontSize || 16) * 0.3;
803
748
  }
804
749
  /**
805
- * Renders multi-line text content with rich text styling and layout features
750
+ * Applies this.props.fontVariant to the context, or resets to 'normal'.
751
+ * Centralizes the type guard + warn pattern repeated across measure/render paths.
752
+ */
753
+ _applyFontVariant(ctx, context) {
754
+ if (typeof this.props.fontVariant === 'string') {
755
+ ctx.fontVariant = this.props.fontVariant;
756
+ }
757
+ else if (this.props.fontVariant !== undefined) {
758
+ console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in ${context}:`, this.props.fontVariant);
759
+ if (ctx.fontVariant !== 'normal')
760
+ ctx.fontVariant = 'normal';
761
+ }
762
+ else {
763
+ if (ctx.fontVariant !== 'normal')
764
+ ctx.fontVariant = 'normal';
765
+ }
766
+ }
767
+ /**
806
768
  *
807
769
  * Core features:
808
770
  * - Dynamic line heights with leading/spacing controls
@@ -1042,18 +1004,7 @@ class TextNode extends layout_canvas.BoxNode {
1042
1004
  // Apply segment styles
1043
1005
  ctx.font = this.getFontString(segment);
1044
1006
  ctx.fillStyle = segment.color || this.props.color || 'black';
1045
- if (typeof this.props.fontVariant === 'string') {
1046
- ctx.fontVariant = this.props.fontVariant;
1047
- }
1048
- else if (this.props.fontVariant !== undefined) {
1049
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in _renderContent (segment render):`, this.props.fontVariant);
1050
- if (ctx.fontVariant !== 'normal')
1051
- ctx.fontVariant = 'normal';
1052
- }
1053
- else {
1054
- if (ctx.fontVariant !== 'normal')
1055
- ctx.fontVariant = 'normal';
1056
- }
1007
+ this._applyFontVariant(ctx, '_renderContent (segment render)');
1057
1008
  // Handle text truncation and ellipsis
1058
1009
  let textToDraw = segment.text;
1059
1010
  let currentSegmentRenderWidth = segmentWidth;
@@ -1121,18 +1072,7 @@ class TextNode extends layout_canvas.BoxNode {
1121
1072
  if (ellipsisRemainingWidth >= ellipsisWidth) {
1122
1073
  ctx.save();
1123
1074
  ctx.font = this.getFontString(ellipsisStyle);
1124
- if (typeof this.props.fontVariant === 'string') {
1125
- ctx.fontVariant = this.props.fontVariant;
1126
- }
1127
- else if (this.props.fontVariant !== undefined) {
1128
- console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in _renderContent (ellipsis draw):`, this.props.fontVariant);
1129
- if (ctx.fontVariant !== 'normal')
1130
- ctx.fontVariant = 'normal';
1131
- }
1132
- else {
1133
- if (ctx.fontVariant !== 'normal')
1134
- ctx.fontVariant = 'normal';
1135
- }
1075
+ this._applyFontVariant(ctx, '_renderContent (ellipsis draw)');
1136
1076
  ctx.fillStyle = ellipsisStyle?.color || this.props.color || 'black';
1137
1077
  ctx.fillText(ellipsisChar, currentX, lineY, Math.max(0, ellipsisRemainingWidth + 1));
1138
1078
  ctx.restore();