@meonode/canvas 1.5.6 → 1.6.0-beta.2

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 (65) hide show
  1. package/dist/cjs/canvas/canvas.type.d.ts +34 -1
  2. package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
  3. package/dist/cjs/canvas/chart.canvas.util.d.ts +2 -2
  4. package/dist/cjs/canvas/chart.canvas.util.d.ts.map +1 -1
  5. package/dist/cjs/canvas/chart.canvas.util.js +5 -2
  6. package/dist/cjs/canvas/chart.canvas.util.js.map +1 -1
  7. package/dist/cjs/canvas/grid.canvas.util.d.ts +3 -3
  8. package/dist/cjs/canvas/grid.canvas.util.d.ts.map +1 -1
  9. package/dist/cjs/canvas/grid.canvas.util.js +34 -1
  10. package/dist/cjs/canvas/grid.canvas.util.js.map +1 -1
  11. package/dist/cjs/canvas/image.canvas.util.d.ts +16 -8
  12. package/dist/cjs/canvas/image.canvas.util.d.ts.map +1 -1
  13. package/dist/cjs/canvas/image.canvas.util.js +82 -72
  14. package/dist/cjs/canvas/image.canvas.util.js.map +1 -1
  15. package/dist/cjs/canvas/layout.canvas.util.d.ts +4 -4
  16. package/dist/cjs/canvas/layout.canvas.util.d.ts.map +1 -1
  17. package/dist/cjs/canvas/layout.canvas.util.js +24 -3
  18. package/dist/cjs/canvas/layout.canvas.util.js.map +1 -1
  19. package/dist/cjs/canvas/root.canvas.util.d.ts +23 -4
  20. package/dist/cjs/canvas/root.canvas.util.d.ts.map +1 -1
  21. package/dist/cjs/canvas/root.canvas.util.js +228 -5
  22. package/dist/cjs/canvas/root.canvas.util.js.map +1 -1
  23. package/dist/cjs/canvas/text.canvas.util.d.ts +2 -2
  24. package/dist/cjs/canvas/text.canvas.util.d.ts.map +1 -1
  25. package/dist/cjs/canvas/text.canvas.util.js +5 -1
  26. package/dist/cjs/canvas/text.canvas.util.js.map +1 -1
  27. package/dist/cjs/canvas/worker.types.d.ts +76 -0
  28. package/dist/cjs/canvas/worker.types.d.ts.map +1 -0
  29. package/dist/cjs/index.d.ts +2 -1
  30. package/dist/cjs/index.d.ts.map +1 -1
  31. package/dist/cjs/index.js +2 -0
  32. package/dist/cjs/index.js.map +1 -1
  33. package/dist/cjs/render.worker.d.ts +2 -0
  34. package/dist/cjs/render.worker.d.ts.map +1 -0
  35. package/dist/cjs/render.worker.js +73 -0
  36. package/dist/cjs/render.worker.js.map +1 -0
  37. package/dist/esm/canvas/canvas.type.d.ts +34 -1
  38. package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
  39. package/dist/esm/canvas/chart.canvas.util.d.ts +2 -2
  40. package/dist/esm/canvas/chart.canvas.util.d.ts.map +1 -1
  41. package/dist/esm/canvas/chart.canvas.util.js +6 -3
  42. package/dist/esm/canvas/grid.canvas.util.d.ts +3 -3
  43. package/dist/esm/canvas/grid.canvas.util.d.ts.map +1 -1
  44. package/dist/esm/canvas/grid.canvas.util.js +34 -3
  45. package/dist/esm/canvas/image.canvas.util.d.ts +16 -8
  46. package/dist/esm/canvas/image.canvas.util.d.ts.map +1 -1
  47. package/dist/esm/canvas/image.canvas.util.js +82 -72
  48. package/dist/esm/canvas/layout.canvas.util.d.ts +4 -4
  49. package/dist/esm/canvas/layout.canvas.util.d.ts.map +1 -1
  50. package/dist/esm/canvas/layout.canvas.util.js +24 -3
  51. package/dist/esm/canvas/root.canvas.util.d.ts +23 -4
  52. package/dist/esm/canvas/root.canvas.util.d.ts.map +1 -1
  53. package/dist/esm/canvas/root.canvas.util.js +227 -7
  54. package/dist/esm/canvas/text.canvas.util.d.ts +2 -2
  55. package/dist/esm/canvas/text.canvas.util.d.ts.map +1 -1
  56. package/dist/esm/canvas/text.canvas.util.js +5 -1
  57. package/dist/esm/canvas/worker.types.d.ts +76 -0
  58. package/dist/esm/canvas/worker.types.d.ts.map +1 -0
  59. package/dist/esm/index.d.ts +2 -1
  60. package/dist/esm/index.d.ts.map +1 -1
  61. package/dist/esm/index.js +2 -2
  62. package/dist/esm/render.worker.d.ts +2 -0
  63. package/dist/esm/render.worker.d.ts.map +1 -0
  64. package/dist/esm/render.worker.js +70 -0
  65. package/package.json +1 -1
@@ -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
- * Loads and processes an image from various sources (URL, file path, or Buffer).
50
- * Handles SVG color modifications and sets natural dimensions with an aspect ratio.
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
- _loadImage() {
55
- if (this.loadingPromise)
56
- return this.loadingPromise;
57
- if (this.loadedImage)
58
- return Promise.resolve();
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
- if (typeof this.props.src === 'string') {
75
- if (this.props.src.startsWith('http')) {
76
- const response = await fetch(this.props.src);
77
- if (!response.ok) {
78
- throw new Error(`HTTP error ${response.status} fetching image: ${this.props.src}`);
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
- contentBuffer = this.props.src;
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 loadImage(finalSource);
139
+ const img = await imagePromise;
133
140
  this.loadedImage = img;
134
141
  this.naturalWidth = img.width;
135
142
  this.naturalHeight = img.height;
@@ -305,6 +312,9 @@ class ImageNode extends BoxNode {
305
312
  /**
306
313
  * Factory function to create ImageNode instances
307
314
  */
308
- const Image = (props) => new ImageNode(props);
315
+ const Image = (props) => ({
316
+ __type: 'Image',
317
+ props: props,
318
+ });
309
319
 
310
320
  export { Image, ImageNode };
@@ -1,5 +1,5 @@
1
1
  import { type CanvasRenderingContext2D } from 'skia-canvas';
2
- import type { BaseProps, BoxProps } from '../canvas/canvas.type.js';
2
+ import type { BaseProps, BoxProps, NodeDescriptor } from '../canvas/canvas.type.js';
3
3
  import { Node } from '../constant/common.const.js';
4
4
  /**
5
5
  * @class BoxNode
@@ -92,7 +92,7 @@ export declare class BoxNode {
92
92
  * @param {BoxProps} props Box properties and configuration.
93
93
  * @returns {BoxNode} New BoxNode instance.
94
94
  */
95
- export declare const Box: (props: BoxProps) => BoxNode;
95
+ export declare const Box: ({ children, ...rest }: BoxProps) => NodeDescriptor;
96
96
  /**
97
97
  * @class ColumnNode
98
98
  * Node class for vertical column layout
@@ -105,7 +105,7 @@ export declare class ColumnNode extends BoxNode {
105
105
  * @param {BoxProps} props Column properties and configuration.
106
106
  * @returns {ColumnNode} New ColumnNode instance.
107
107
  */
108
- export declare const Column: (props: BoxProps) => ColumnNode;
108
+ export declare const Column: ({ children, ...rest }: BoxProps) => NodeDescriptor;
109
109
  /**
110
110
  * @class RowNode
111
111
  * @classdesc Node class for horizontal row layout.
@@ -118,5 +118,5 @@ export declare class RowNode extends BoxNode {
118
118
  * @param {BoxProps} props Row properties and configuration.
119
119
  * @returns {RowNode} New RowNode instance.
120
120
  */
121
- export declare const Row: (props: BoxProps) => RowNode;
121
+ export declare const Row: ({ children, ...rest }: BoxProps) => NodeDescriptor;
122
122
  //# sourceMappingURL=layout.canvas.util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/layout.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,yBAAyB,CAAA;AAGlF,OAAa,EAAS,IAAI,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;;;GAIG;AACH,qBAAa,OAAO;IAClB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE/B;;OAEG;IACH,IAAI,EAAE,IAAI,CAAA;IAEV;;OAEG;IACH,QAAQ,EAAE,OAAO,EAAE,CAAA;IAEnB;;OAEG;IACH,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ;;;OAGG;gBACS,KAAK,GAAE,QAAQ,GAAG,SAAc;IAqB5C;;OAEG;IACI,sBAAsB;IAW7B;;;OAGG;IACH,SAAS,CAAC,sBAAsB,CAAC,WAAW,EAAE,QAAQ,GAAG,SAAS;IAkClE;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IAI/B;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;IAanD;;;OAGG;IACI,cAAc,IAAI,OAAO;IAiBhC;;OAEG;IACH,SAAS,CAAC,+BAA+B;IAIzC;;;OAGG;IACH,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ;IAqInC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,OAAO,GAAE,MAAU,EAAE,OAAO,GAAE,MAAU;IA+J9E;;;;;;;;OAQG;IACH,SAAS,CAAC,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoR5G;AAED;;;;GAIG;AACH,eAAO,MAAM,GAAG,GAAI,OAAO,QAAQ,KAAG,OAA6B,CAAA;AAEnE;;;GAGG;AACH,qBAAa,UAAW,SAAQ,OAAO;gBACzB,KAAK,GAAE,QAAQ,GAAG,SAAc;CAS7C;AAED;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,QAAQ,KAAG,UAAmC,CAAA;AAE5E;;;GAGG;AACH,qBAAa,OAAQ,SAAQ,OAAO;gBACtB,KAAK,GAAE,QAAQ,GAAG,SAAc;CAS7C;AAED;;;;GAIG;AACH,eAAO,MAAM,GAAG,GAAI,OAAO,QAAQ,KAAG,OAA6B,CAAA"}
1
+ {"version":3,"file":"layout.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/layout.canvas.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAkB,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAGlG,OAAa,EAAS,IAAI,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;;;GAIG;AACH,qBAAa,OAAO;IAClB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE/B;;OAEG;IACH,IAAI,EAAE,IAAI,CAAA;IAEV;;OAEG;IACH,QAAQ,EAAE,OAAO,EAAE,CAAA;IAEnB;;OAEG;IACH,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ;;;OAGG;gBACS,KAAK,GAAE,QAAQ,GAAG,SAAc;IAqB5C;;OAEG;IACI,sBAAsB;IAW7B;;;OAGG;IACH,SAAS,CAAC,sBAAsB,CAAC,WAAW,EAAE,QAAQ,GAAG,SAAS;IAkClE;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IAI/B;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;IAanD;;;OAGG;IACI,cAAc,IAAI,OAAO;IAiBhC;;OAEG;IACH,SAAS,CAAC,+BAA+B;IAIzC;;;OAGG;IACH,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ;IAqInC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,OAAO,GAAE,MAAU,EAAE,OAAO,GAAE,MAAU;IA+J9E;;;;;;;;OAQG;IACH,SAAS,CAAC,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoR5G;AAWD;;;;GAIG;AACH,eAAO,MAAM,GAAG,GAAI,uBAAuB,QAAQ,KAAG,cAIpD,CAAA;AAEF;;;GAGG;AACH,qBAAa,UAAW,SAAQ,OAAO;gBACzB,KAAK,GAAE,QAAQ,GAAG,SAAc;CAS7C;AAED;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,uBAAuB,QAAQ,KAAG,cAIvD,CAAA;AAEF;;;GAGG;AACH,qBAAa,OAAQ,SAAQ,OAAO;gBACtB,KAAK,GAAE,QAAQ,GAAG,SAAc;CAS7C;AAED;;;;GAIG;AACH,eAAO,MAAM,GAAG,GAAI,uBAAuB,QAAQ,KAAG,cAIpD,CAAA"}
@@ -718,12 +718,25 @@ class BoxNode {
718
718
  });
719
719
  }
720
720
  }
721
+ /**
722
+ * Normalizes children into a flat NodeDescriptor array, filtering falsy values.
723
+ */
724
+ function normalizeDescriptorChildren(children) {
725
+ if (children === undefined || children === null || children === false)
726
+ return undefined;
727
+ const arr = (Array.isArray(children) ? children : [children]).filter(Boolean);
728
+ return arr.length > 0 ? arr : undefined;
729
+ }
721
730
  /**
722
731
  * Creates a new BoxNode instance.
723
732
  * @param {BoxProps} props Box properties and configuration.
724
733
  * @returns {BoxNode} New BoxNode instance.
725
734
  */
726
- const Box = (props) => new BoxNode(props);
735
+ const Box = ({ children, ...rest }) => ({
736
+ __type: 'Box',
737
+ props: rest,
738
+ children: normalizeDescriptorChildren(children),
739
+ });
727
740
  /**
728
741
  * @class ColumnNode
729
742
  * Node class for vertical column layout
@@ -744,7 +757,11 @@ class ColumnNode extends BoxNode {
744
757
  * @param {BoxProps} props Column properties and configuration.
745
758
  * @returns {ColumnNode} New ColumnNode instance.
746
759
  */
747
- const Column = (props) => new ColumnNode(props);
760
+ const Column = ({ children, ...rest }) => ({
761
+ __type: 'Column',
762
+ props: rest,
763
+ children: normalizeDescriptorChildren(children),
764
+ });
748
765
  /**
749
766
  * @class RowNode
750
767
  * @classdesc Node class for horizontal row layout.
@@ -765,6 +782,10 @@ class RowNode extends BoxNode {
765
782
  * @param {BoxProps} props Row properties and configuration.
766
783
  * @returns {RowNode} New RowNode instance.
767
784
  */
768
- const Row = (props) => new RowNode(props);
785
+ const Row = ({ children, ...rest }) => ({
786
+ __type: 'Row',
787
+ props: rest,
788
+ children: normalizeDescriptorChildren(children),
789
+ });
769
790
 
770
791
  export { Box, BoxNode, Column, ColumnNode, Row, RowNode };
@@ -1,7 +1,24 @@
1
1
  import { Canvas } from 'skia-canvas';
2
- import { ColumnNode } from '../canvas/layout.canvas.util.js';
3
- import type { BaseProps, RootProps } from '../canvas/canvas.type.js';
2
+ import { ColumnNode, BoxNode } from '../canvas/layout.canvas.util.js';
3
+ import type { BaseProps, RootProps, NodeDescriptor } from '../canvas/canvas.type.js';
4
4
  export declare const _clearRegisteredFonts: () => void;
5
+ export interface CanvasEngineConfig {
6
+ /** Run rendering in worker threads to avoid blocking the event loop (default: true) */
7
+ workerMode?: boolean;
8
+ /** Number of worker threads in the pool (default: os.cpus().length - 1) */
9
+ workers?: number;
10
+ }
11
+ /**
12
+ * Configure the canvas rendering engine.
13
+ * Call this once at application startup before rendering.
14
+ */
15
+ export declare function configure(options: CanvasEngineConfig): void;
16
+ /**
17
+ * Converts a NodeDescriptor tree into actual BoxNode instances.
18
+ * Used both for non-worker rendering (inline tree building) and inside
19
+ * the render worker (reconstructing the tree from serialized descriptors).
20
+ */
21
+ export declare function buildTree(descriptor: NodeDescriptor): BoxNode;
5
22
  /**
6
23
  * Root node that manages the canvas rendering context and coordinates overall layout and drawing.
7
24
  * Inherits from ColumnNode to provide vertical layout capabilities.
@@ -36,9 +53,11 @@ export declare class RootNode extends ColumnNode {
36
53
  render(): Promise<Canvas>;
37
54
  }
38
55
  /**
39
- * Creates and renders a new root node with the given properties
56
+ * Creates and renders a new root node with the given properties.
57
+ * When worker mode is enabled via configure(), rendering runs in a worker thread
58
+ * and the returned object implements the same toBuffer/toBufferSync interface.
40
59
  * @param props Configuration properties for the root node
41
- * @returns Promise resolving to the rendered Canvas instance
60
+ * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)
42
61
  */
43
62
  export declare const Root: (props: RootProps) => Promise<Canvas>;
44
63
  //# sourceMappingURL=root.canvas.util.d.ts.map
@@ -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;AAChF,OAAO,EAAE,UAAU,EAAW,MAAM,gCAAgC,CAAA;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAUnE,eAAO,MAAM,qBAAqB,YAEjC,CAAA;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;IAyCxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CA2ChC;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAU,OAAO,SAAS,oBAAuC,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"}
@@ -1,12 +1,209 @@
1
1
  import { FontLibrary, Canvas } from 'skia-canvas';
2
- import { ColumnNode } from './layout.canvas.util.js';
2
+ import { ColumnNode, RowNode, BoxNode } from './layout.canvas.util.js';
3
3
  import { ImageNode } from './image.canvas.util.js';
4
+ import { TextNode } from './text.canvas.util.js';
5
+ import { ChartNode } from './chart.canvas.util.js';
6
+ import { GridItemNode, GridNode } from './grid.canvas.util.js';
4
7
  import { Style } from '../constant/common.const.js';
5
8
  import * as path from 'node:path';
6
9
  import * as fs from 'node:fs';
10
+ import { cpus } from 'node:os';
11
+ import { Worker } from 'node:worker_threads';
12
+ import { fileURLToPath } from 'node:url';
7
13
 
8
14
  /** Registry to track fonts that have already been loaded */
9
15
  const registeredFonts = new Map();
16
+ /** Engine configuration */
17
+ let _workerMode = true;
18
+ let _workerPoolSize = Math.max(1, cpus().length - 1);
19
+ let _workerPool = null;
20
+ /**
21
+ * Configure the canvas rendering engine.
22
+ * Call this once at application startup before rendering.
23
+ */
24
+ function configure(options) {
25
+ if (options.workerMode !== undefined)
26
+ _workerMode = options.workerMode;
27
+ if (options.workers !== undefined)
28
+ _workerPoolSize = options.workers;
29
+ if (_workerMode) {
30
+ _workerPool = new WorkerPool(_workerPoolSize);
31
+ }
32
+ }
33
+ /**
34
+ * Proxies all skia-canvas Canvas APIs to a Canvas instance living inside a worker thread.
35
+ * Sync methods (toBufferSync, toURLSync) return from a pre-encoded PNG buffer.
36
+ * Async methods (toBuffer, toURL, toFile, getters) delegate to the worker.
37
+ */
38
+ class WorkerCanvas {
39
+ width;
40
+ height;
41
+ _buffer; // pre-encoded PNG for sync use
42
+ _pool;
43
+ _workerIdx;
44
+ _canvasId;
45
+ constructor(opts) {
46
+ this._buffer = opts.buffer;
47
+ this.width = opts.width;
48
+ this.height = opts.height;
49
+ this._pool = opts.pool;
50
+ this._workerIdx = opts.workerIdx;
51
+ this._canvasId = opts.canvasId;
52
+ }
53
+ _call(method, ...args) {
54
+ return this._pool.callOnCanvas(this._workerIdx, this._canvasId, method, args);
55
+ }
56
+ // --- Sync methods: return from pre-encoded PNG buffer ---
57
+ toBufferSync(_format, _options) {
58
+ return this._buffer;
59
+ }
60
+ toURLSync(_format, _options) {
61
+ return `data:image/png;base64,${this._buffer.toString('base64')}`;
62
+ }
63
+ // --- Async methods: delegate to worker ---
64
+ toBuffer(format, options) {
65
+ return this._call('toBuffer', format, options);
66
+ }
67
+ toURL(format, options) {
68
+ return this._call('toURL', format, options);
69
+ }
70
+ toFile(filename, options) {
71
+ return this._call('toFile', filename, options);
72
+ }
73
+ /** Returns a Buffer (Sharp instance cannot be transferred across threads) */
74
+ toSharp(options) {
75
+ return this._call('toSharp', options);
76
+ }
77
+ toSharpSync(_options) {
78
+ throw new Error('[canvas] toSharpSync() is not available in worker mode — use toSharp() instead');
79
+ }
80
+ // --- Async convenience getters ---
81
+ get png() {
82
+ return this._call('toBuffer', 'png');
83
+ }
84
+ get webp() {
85
+ return this._call('toBuffer', 'webp');
86
+ }
87
+ get jpg() {
88
+ return this._call('toBuffer', 'jpg');
89
+ }
90
+ get svg() {
91
+ return this._call('toBuffer', 'svg');
92
+ }
93
+ get pdf() {
94
+ return this._call('toBuffer', 'pdf');
95
+ }
96
+ get raw() {
97
+ return this._call('toBuffer', 'raw');
98
+ }
99
+ /** Release the Canvas from worker memory. Call when done with this object. */
100
+ release() {
101
+ this._pool.releaseCanvas(this._workerIdx, this._canvasId);
102
+ }
103
+ }
104
+ /** Worker thread pool — routes render and canvas-call messages */
105
+ class WorkerPool {
106
+ workers = [];
107
+ idle = [];
108
+ queue = [];
109
+ pending = new Map();
110
+ nextId = 0;
111
+ constructor(size) {
112
+ this.init(size);
113
+ }
114
+ init(size) {
115
+ const workerFile = path.join(path.dirname(fileURLToPath(import.meta.url)), '../render.worker.js');
116
+ for (let i = 0; i < size; i++) {
117
+ const workerIdx = i;
118
+ const worker = new Worker(workerFile);
119
+ worker.on('message', (msg) => {
120
+ const task = this.pending.get(msg.taskId);
121
+ if (!task)
122
+ return;
123
+ this.pending.delete(msg.taskId);
124
+ if ('error' in msg) {
125
+ task.reject(new Error(msg.error));
126
+ return;
127
+ }
128
+ if ('canvasId' in msg) {
129
+ // Render complete — put worker back to idle
130
+ this.idle.push(worker);
131
+ this.drain();
132
+ const result = { buffer: msg.buffer, canvasId: msg.canvasId, workerIdx, width: msg.width, height: msg.height };
133
+ task.resolve(result);
134
+ }
135
+ else {
136
+ // Canvas method call complete
137
+ task.resolve(msg.result);
138
+ }
139
+ });
140
+ this.workers.push(worker);
141
+ this.idle.push(worker);
142
+ }
143
+ }
144
+ drain() {
145
+ while (this.queue.length > 0 && this.idle.length > 0) {
146
+ const task = this.queue.shift();
147
+ const worker = this.idle.pop();
148
+ const request = { type: 'render', taskId: task.id, props: task.props };
149
+ worker.postMessage(request);
150
+ }
151
+ }
152
+ render(props) {
153
+ return new Promise((resolve, reject) => {
154
+ const id = this.nextId++;
155
+ this.pending.set(id, { resolve: resolve, reject });
156
+ if (this.idle.length > 0) {
157
+ const worker = this.idle.pop();
158
+ const request = { type: 'render', taskId: id, props };
159
+ worker.postMessage(request);
160
+ }
161
+ else {
162
+ this.queue.push({ id, props });
163
+ }
164
+ });
165
+ }
166
+ callOnCanvas(workerIdx, canvasId, method, args) {
167
+ return new Promise((resolve, reject) => {
168
+ const id = this.nextId++;
169
+ this.pending.set(id, { resolve: resolve, reject });
170
+ const request = { type: 'call', taskId: id, canvasId, method, args };
171
+ this.workers[workerIdx].postMessage(request);
172
+ });
173
+ }
174
+ releaseCanvas(workerIdx, canvasId) {
175
+ const request = { type: 'release', canvasId };
176
+ this.workers[workerIdx]?.postMessage(request);
177
+ }
178
+ terminate() {
179
+ this.workers.forEach(w => w.terminate());
180
+ }
181
+ }
182
+ /**
183
+ * Converts a NodeDescriptor tree into actual BoxNode instances.
184
+ * Used both for non-worker rendering (inline tree building) and inside
185
+ * the render worker (reconstructing the tree from serialized descriptors).
186
+ */
187
+ function buildTree(descriptor) {
188
+ switch (descriptor.__type) {
189
+ case 'Box':
190
+ return new BoxNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) });
191
+ case 'Column':
192
+ return new ColumnNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) });
193
+ case 'Row':
194
+ return new RowNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) });
195
+ case 'Grid':
196
+ return new GridNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) });
197
+ case 'GridItem':
198
+ return new GridItemNode({ ...descriptor.props, children: descriptor.children?.map(buildTree) });
199
+ case 'Image':
200
+ return new ImageNode(descriptor.props);
201
+ case 'Text':
202
+ return new TextNode(descriptor.text, descriptor.props);
203
+ case 'Chart':
204
+ return new ChartNode(descriptor.props);
205
+ }
206
+ }
10
207
  /**
11
208
  * Root node that manages the canvas rendering context and coordinates overall layout and drawing.
12
209
  * Inherits from ColumnNode to provide vertical layout capabilities.
@@ -56,6 +253,16 @@ class RootNode extends ColumnNode {
56
253
  this.targetWidth = props.width;
57
254
  this.targetHeight = props.height;
58
255
  this.node.setWidth(this.targetWidth);
256
+ // Convert any NodeDescriptor children to actual BoxNode instances
257
+ if (this.props.children) {
258
+ const childArray = Array.isArray(this.props.children) ? this.props.children : [this.props.children];
259
+ this.props.children = childArray.map(child => {
260
+ if (child && typeof child === 'object' && '__type' in child) {
261
+ return buildTree(child);
262
+ }
263
+ return child;
264
+ });
265
+ }
59
266
  // Initialize children nodes
60
267
  this.processInitialChildren();
61
268
  }
@@ -81,15 +288,17 @@ class RootNode extends ColumnNode {
81
288
  * @returns Promise resolving to the rendered Canvas instance
82
289
  */
83
290
  async render() {
84
- // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources
291
+ // Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
292
+ // A per-render cache deduplicates identical src+color combinations within this render pass.
85
293
  const imageNodes = this.findAllImageNodes();
86
294
  if (imageNodes.length > 0) {
295
+ const imageCache = new Map();
87
296
  const CONCURRENCY = 5;
88
297
  const queue = [...imageNodes];
89
298
  const workers = Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
90
299
  while (queue.length > 0) {
91
300
  const node = queue.shift();
92
- await node.load();
301
+ await node.load(imageCache);
93
302
  }
94
303
  });
95
304
  await Promise.allSettled(workers);
@@ -118,10 +327,21 @@ class RootNode extends ColumnNode {
118
327
  }
119
328
  }
120
329
  /**
121
- * Creates and renders a new root node with the given properties
330
+ * Creates and renders a new root node with the given properties.
331
+ * When worker mode is enabled via configure(), rendering runs in a worker thread
332
+ * and the returned object implements the same toBuffer/toBufferSync interface.
122
333
  * @param props Configuration properties for the root node
123
- * @returns Promise resolving to the rendered Canvas instance
334
+ * @returns Promise resolving to the rendered Canvas (or WorkerCanvas in worker mode)
124
335
  */
125
- const Root = async (props) => await new RootNode(props).render();
336
+ const Root = async (props) => {
337
+ if (_workerMode) {
338
+ if (!_workerPool) {
339
+ _workerPool = new WorkerPool(_workerPoolSize);
340
+ }
341
+ const result = await _workerPool.render(props);
342
+ return new WorkerCanvas({ ...result, pool: _workerPool });
343
+ }
344
+ return new RootNode(props).render();
345
+ };
126
346
 
127
- export { Root, RootNode };
347
+ export { Root, RootNode, buildTree, configure };
@@ -1,4 +1,4 @@
1
- import type { TextProps } from '../canvas/canvas.type.js';
1
+ import type { TextProps, NodeDescriptor } from '../canvas/canvas.type.js';
2
2
  import { type CanvasRenderingContext2D } from 'skia-canvas';
3
3
  import { BoxNode } from '../canvas/layout.canvas.util.js';
4
4
  /**
@@ -161,5 +161,5 @@ export declare class TextNode extends BoxNode {
161
161
  /**
162
162
  * Creates a new TextNode instance with rich text support
163
163
  */
164
- export declare const Text: (text: number | string, props?: TextProps) => TextNode;
164
+ export declare const Text: (text: number | string, props?: TextProps) => NodeDescriptor;
165
165
  //# sourceMappingURL=text.canvas.util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.util.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,yBAAyB,CAAA;AACrE,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAGxD;;;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;cACgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAkXrH;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,aAA8B,CAAA"}
1
+ {"version":3,"file":"text.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.util.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAe,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAGxD;;;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;cACgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAkXrH;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,KAAG,cAI9D,CAAA"}
@@ -1150,6 +1150,10 @@ class TextNode extends BoxNode {
1150
1150
  /**
1151
1151
  * Creates a new TextNode instance with rich text support
1152
1152
  */
1153
- const Text = (text, props) => new TextNode(text, props);
1153
+ const Text = (text, props) => ({
1154
+ __type: 'Text',
1155
+ text,
1156
+ props,
1157
+ });
1154
1158
 
1155
1159
  export { Text, TextNode };