@meonode/canvas 1.0.0-beta.4 → 1.0.0-beta.5

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 (47) hide show
  1. package/dist/cjs/canvas/canvas.helper.d.ts +9 -11
  2. package/dist/cjs/canvas/canvas.helper.d.ts.map +1 -1
  3. package/dist/cjs/canvas/canvas.helper.js +39 -47
  4. package/dist/cjs/canvas/canvas.helper.js.map +1 -1
  5. package/dist/cjs/canvas/canvas.type.d.ts +1 -1
  6. package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
  7. package/dist/cjs/canvas/grid.canvas.util.d.ts +4 -4
  8. package/dist/cjs/canvas/grid.canvas.util.d.ts.map +1 -1
  9. package/dist/cjs/canvas/grid.canvas.util.js +21 -4
  10. package/dist/cjs/canvas/grid.canvas.util.js.map +1 -1
  11. package/dist/cjs/canvas/image.canvas.util.d.ts +0 -1
  12. package/dist/cjs/canvas/image.canvas.util.d.ts.map +1 -1
  13. package/dist/cjs/canvas/image.canvas.util.js +72 -72
  14. package/dist/cjs/canvas/image.canvas.util.js.map +1 -1
  15. package/dist/cjs/canvas/layout.canvas.util.d.ts +16 -17
  16. package/dist/cjs/canvas/layout.canvas.util.d.ts.map +1 -1
  17. package/dist/cjs/canvas/layout.canvas.util.js +17 -24
  18. package/dist/cjs/canvas/layout.canvas.util.js.map +1 -1
  19. package/dist/cjs/canvas/root.canvas.util.d.ts +4 -2
  20. package/dist/cjs/canvas/root.canvas.util.d.ts.map +1 -1
  21. package/dist/cjs/canvas/root.canvas.util.js +7 -3
  22. package/dist/cjs/canvas/root.canvas.util.js.map +1 -1
  23. package/dist/cjs/canvas/text.canvas.util.d.ts +20 -27
  24. package/dist/cjs/canvas/text.canvas.util.d.ts.map +1 -1
  25. package/dist/cjs/canvas/text.canvas.util.js +27 -45
  26. package/dist/cjs/canvas/text.canvas.util.js.map +1 -1
  27. package/dist/esm/canvas/canvas.helper.d.ts +9 -11
  28. package/dist/esm/canvas/canvas.helper.d.ts.map +1 -1
  29. package/dist/esm/canvas/canvas.helper.js +39 -47
  30. package/dist/esm/canvas/canvas.type.d.ts +1 -1
  31. package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
  32. package/dist/esm/canvas/grid.canvas.util.d.ts +4 -4
  33. package/dist/esm/canvas/grid.canvas.util.d.ts.map +1 -1
  34. package/dist/esm/canvas/grid.canvas.util.js +21 -4
  35. package/dist/esm/canvas/image.canvas.util.d.ts +0 -1
  36. package/dist/esm/canvas/image.canvas.util.d.ts.map +1 -1
  37. package/dist/esm/canvas/image.canvas.util.js +72 -72
  38. package/dist/esm/canvas/layout.canvas.util.d.ts +16 -17
  39. package/dist/esm/canvas/layout.canvas.util.d.ts.map +1 -1
  40. package/dist/esm/canvas/layout.canvas.util.js +17 -24
  41. package/dist/esm/canvas/root.canvas.util.d.ts +4 -2
  42. package/dist/esm/canvas/root.canvas.util.d.ts.map +1 -1
  43. package/dist/esm/canvas/root.canvas.util.js +7 -3
  44. package/dist/esm/canvas/text.canvas.util.d.ts +20 -27
  45. package/dist/esm/canvas/text.canvas.util.d.ts.map +1 -1
  46. package/dist/esm/canvas/text.canvas.util.js +27 -45
  47. package/package.json +5 -9
@@ -37,7 +37,7 @@ class BoxNode {
37
37
  key;
38
38
  /**
39
39
  * Creates a new BoxNode instance
40
- * @param props - Initial box properties and styling
40
+ * @param props Initial box properties and styling
41
41
  */
42
42
  constructor(props = {}) {
43
43
  const children = (Array.isArray(props?.children) ? props.children : [props.children]).filter(child => child);
@@ -72,7 +72,7 @@ class BoxNode {
72
72
  }
73
73
  /**
74
74
  * Inherits styles from the parent node.
75
- * @param {BoxProps & BaseProps} parentProps - Parent node properties to inherit from.
75
+ * @param {BoxProps & BaseProps} parentProps Parent node properties to inherit from.
76
76
  */
77
77
  resolveInheritedStyles(parentProps) {
78
78
  if (parentProps.key) {
@@ -112,8 +112,8 @@ class BoxNode {
112
112
  }
113
113
  /**
114
114
  * Appends a child node at the specified index.
115
- * @param {BoxNode} child - Child node to append.
116
- * @param index - Index to insert child at
115
+ * @param {BoxNode} child Child node to append.
116
+ * @param index Index to insert child at
117
117
  */
118
118
  appendChild(child, index) {
119
119
  if (!child || !child.node) {
@@ -152,7 +152,7 @@ class BoxNode {
152
152
  }
153
153
  /**
154
154
  * Applies layout properties to the Yoga node.
155
- * @param props - Box properties containing layout values
155
+ * @param props Box properties containing layout values
156
156
  */
157
157
  setLayout(props) {
158
158
  // --- Yoga layout property application ---
@@ -295,9 +295,9 @@ class BoxNode {
295
295
  }
296
296
  /**
297
297
  * Renders the node and its children to the canvas.
298
- * @param {CanvasRenderingContext2D} ctx - Canvas rendering context (from skia-canvas).
299
- * @param {number} offsetX - X offset for rendering.
300
- * @param {number} offsetY - Y offset for rendering.
298
+ * @param {CanvasRenderingContext2D} ctx Canvas rendering context (from skia-canvas).
299
+ * @param {number} offsetX X offset for rendering.
300
+ * @param {number} offsetY Y offset for rendering.
301
301
  */
302
302
  render(ctx, offsetX = 0, offsetY = 0) {
303
303
  const layout = this.node.getComputedLayout();
@@ -322,13 +322,7 @@ class BoxNode {
322
322
  try {
323
323
  // --- Transformation Setup ---
324
324
  const transform = this.props.transform;
325
- const needsTransform = transform &&
326
- (transform.translateX ||
327
- transform.translateY ||
328
- transform.rotate ||
329
- transform.scale ||
330
- transform.scaleX ||
331
- transform.scaleY);
325
+ const needsTransform = transform && (transform.translateX || transform.translateY || transform.rotate || transform.scale || transform.scaleX || transform.scaleY);
332
326
  let savedContextForTransform = false;
333
327
  if (needsTransform) {
334
328
  ctx.save();
@@ -455,12 +449,11 @@ class BoxNode {
455
449
  /**
456
450
  * Renders the node's visual content including background fills, shadows, and borders.
457
451
  * This is an internal method used by the render() pipeline.
458
- *
459
- * @param ctx - The skia-canvas 2D rendering context to draw into
460
- * @param x - The absolute x-coordinate where drawing should begin
461
- * @param y - The absolute y-coordinate where drawing should begin
462
- * @param width - The width of the content area to render
463
- * @param height - The height of the content area to render
452
+ * @param ctx The skia-canvas 2D rendering context to draw into
453
+ * @param x The absolute x-coordinate where drawing should begin
454
+ * @param y The absolute y-coordinate where drawing should begin
455
+ * @param width The width of the content area to render
456
+ * @param height The height of the content area to render
464
457
  */
465
458
  _renderContent(ctx, x, y, width, height) {
466
459
  // Calculate border radius values for all corners
@@ -727,7 +720,7 @@ class BoxNode {
727
720
  }
728
721
  /**
729
722
  * Creates a new BoxNode instance.
730
- * @param {BoxProps} props - Box properties and configuration.
723
+ * @param {BoxProps} props Box properties and configuration.
731
724
  * @returns {BoxNode} New BoxNode instance.
732
725
  */
733
726
  const Box = (props) => new BoxNode(props);
@@ -748,7 +741,7 @@ class ColumnNode extends BoxNode {
748
741
  }
749
742
  /**
750
743
  * Creates a new ColumnNode instance.
751
- * @param {BoxProps} props - Column properties and configuration.
744
+ * @param {BoxProps} props Column properties and configuration.
752
745
  * @returns {ColumnNode} New ColumnNode instance.
753
746
  */
754
747
  const Column = (props) => new ColumnNode(props);
@@ -769,7 +762,7 @@ class RowNode extends BoxNode {
769
762
  }
770
763
  /**
771
764
  * Creates a new RowNode instance.
772
- * @param {BoxProps} props - Row properties and configuration.
765
+ * @param {BoxProps} props Row properties and configuration.
773
766
  * @returns {RowNode} New RowNode instance.
774
767
  */
775
768
  const Row = (props) => new RowNode(props);
@@ -13,11 +13,13 @@ export declare class RootNode extends ColumnNode {
13
13
  private ctx;
14
14
  /** Target width for the canvas in pixels */
15
15
  private readonly targetWidth;
16
+ /** Target height for the canvas in pixels */
17
+ private readonly targetHeight;
16
18
  /** Scale factor for rendering (e.g. 2 for 2x resolution) */
17
19
  private readonly scale;
18
20
  /**
19
21
  * Creates a new root node for canvas rendering
20
- * @param props - Configuration properties for the root node
22
+ * @param props Configuration properties for the root node
21
23
  * @throws Error if width property is not provided
22
24
  */
23
25
  constructor(props: RootProps & BaseProps);
@@ -35,7 +37,7 @@ export declare class RootNode extends ColumnNode {
35
37
  }
36
38
  /**
37
39
  * Creates and renders a new root node with the given properties
38
- * @param props - Configuration properties for the root node
40
+ * @param props Configuration properties for the root node
39
41
  * @returns Promise resolving to the rendered Canvas instance
40
42
  */
41
43
  export declare const Root: (props: RootProps) => Promise<Canvas>;
@@ -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,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;;;OAIG;gBACS,KAAK,EAAE,SAAS,GAAG,SAAS;IAsCxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAqChC;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;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;CAqChC;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAU,OAAO,SAAS,oBAAuC,CAAA"}
@@ -18,16 +18,19 @@ class RootNode extends ColumnNode {
18
18
  ctx = null;
19
19
  /** Target width for the canvas in pixels */
20
20
  targetWidth;
21
+ /** Target height for the canvas in pixels */
22
+ targetHeight;
21
23
  /** Scale factor for rendering (e.g. 2 for 2x resolution) */
22
24
  scale;
23
25
  /**
24
26
  * Creates a new root node for canvas rendering
25
- * @param props - Configuration properties for the root node
27
+ * @param props Configuration properties for the root node
26
28
  * @throws Error if width property is not provided
27
29
  */
28
30
  constructor(props) {
29
31
  // Call the parent constructor with root name and props
30
32
  super({ name: 'Root', ...props });
33
+ this.props = props;
31
34
  // Validate the required width property
32
35
  if (!props.width) {
33
36
  throw new Error('Width and height are required for Root');
@@ -51,6 +54,7 @@ class RootNode extends ColumnNode {
51
54
  // Set up scale and width
52
55
  this.scale = props.scale || 1;
53
56
  this.targetWidth = props.width;
57
+ this.targetHeight = props.height;
54
58
  this.node.setWidth(this.targetWidth);
55
59
  // Initialize children nodes
56
60
  this.processInitialChildren();
@@ -93,7 +97,7 @@ class RootNode extends ColumnNode {
93
97
  // Step 4: Create a canvas with calculated dimensions
94
98
  const calculatedContentHeight = this.node.getComputedHeight();
95
99
  const finalCanvasWidth = Math.ceil(this.targetWidth * this.scale);
96
- const finalCanvasHeight = Math.max(1, Math.ceil(calculatedContentHeight * this.scale));
100
+ const finalCanvasHeight = this.targetHeight ? Math.ceil(this.targetHeight * this.scale) : Math.max(1, Math.ceil(calculatedContentHeight * this.scale));
97
101
  // Step 5: Set up canvas context
98
102
  this.canvas = new Canvas(finalCanvasWidth, finalCanvasHeight);
99
103
  this.ctx = this.canvas.getContext('2d');
@@ -108,7 +112,7 @@ class RootNode extends ColumnNode {
108
112
  }
109
113
  /**
110
114
  * Creates and renders a new root node with the given properties
111
- * @param props - Configuration properties for the root node
115
+ * @param props Configuration properties for the root node
112
116
  * @returns Promise resolving to the rendered Canvas instance
113
117
  */
114
118
  const Root = async (props) => await new RootNode(props).render();
@@ -33,8 +33,7 @@ export declare class TextNode extends BoxNode {
33
33
  * - \b - Backspace (removed)
34
34
  * - \f - Form feed (treated as newline)
35
35
  * - \v - Vertical tab (treated as newline)
36
- *
37
- * @param input - Raw text string potentially containing escape sequences
36
+ * @param input Raw text string potentially containing escape sequences
38
37
  * @returns Processed string with escape sequences converted
39
38
  */
40
39
  private processEscapeSequences;
@@ -52,9 +51,8 @@ export declare class TextNode extends BoxNode {
52
51
  * <color="red">, <color='red'>, <color=red>
53
52
  *
54
53
  * Tags can be nested and must be properly closed with </tag>
55
- *
56
- * @param input - Text string containing markup tags
57
- * @param baseStyle - Default style properties to apply to all segments
54
+ * @param input Text string containing markup tags
55
+ * @param baseStyle Default style properties to apply to all segments
58
56
  * @returns Array of styled text segments with consistent style properties
59
57
  */
60
58
  private parseRichText;
@@ -69,8 +67,7 @@ export declare class TextNode extends BoxNode {
69
67
  * - Style: segment <i> > base fontStyle
70
68
  * - Size: segment size > base fontSize
71
69
  * - Family: base fontFamily
72
- *
73
- * @param segmentStyle - Optional TextSegment styling to override base props
70
+ * @param segmentStyle Optional TextSegment styling to override base props
74
71
  * @returns Formatted CSS font string for canvas context
75
72
  */
76
73
  private getFontString;
@@ -87,33 +84,30 @@ export declare class TextNode extends BoxNode {
87
84
  * 2. Otherwise calculating dynamic height based on largest font size per line
88
85
  * 3. Adding leading space above/below text content
89
86
  * 4. Including specified line gaps between lines
90
- *
91
- * @param widthConstraint - Maximum allowed width in pixels for text layout
92
- * @param widthMode - YogaLayout mode determining how width constraint is applied
87
+ * @param widthConstraint Maximum allowed width in pixels for text layout
88
+ * @param widthMode YogaLayout mode determining how width constraint is applied
93
89
  * @returns Calculated minimum dimensions required to render text content
94
- * - width: Total width needed for text layout
95
- * - height: Total height including line heights and gaps
90
+ * - width: Total width needed for text layout
91
+ * - height: Total height including line heights and gaps
96
92
  */
97
93
  private measureText;
98
94
  /**
99
95
  * Wraps text segments into multiple lines while respecting width constraints and preserving styling.
100
96
  * Handles rich text attributes (color, weight, size, bold, italic) and proper word wrapping.
101
97
  * Also respects explicit newline characters (\n) for forced line breaks.
102
- *
103
- * @param ctx - Canvas rendering context used for text measurements
104
- * @param segments - Array of text segments with styling information
105
- * @param maxWidth - Maximum allowed width for each line in pixels
106
- * @param parsedWordSpacingPx - Additional spacing to add between words in pixels
98
+ * @param ctx Canvas rendering context used for text measurements
99
+ * @param segments Array of text segments with styling information
100
+ * @param maxWidth Maximum allowed width for each line in pixels
101
+ * @param parsedWordSpacingPx Additional spacing to add between words in pixels
107
102
  * @returns Array of lines, where each line contains styled text segments
108
103
  */
109
104
  private wrapTextRich;
110
105
  /**
111
106
  * Breaks a word segment into multiple segments that each fit within the specified width constraint.
112
107
  * Maintains all styling properties (color, weight, size, bold, italic) across broken segments.
113
- *
114
- * @param ctx - Canvas rendering context used for text measurements
115
- * @param segmentToBreak - Original text segment to split
116
- * @param maxWidth - Maximum width allowed for each resulting segment
108
+ * @param ctx Canvas rendering context used for text measurements
109
+ * @param segmentToBreak Original text segment to split
110
+ * @param maxWidth Maximum width allowed for each resulting segment
117
111
  * @returns Array of TextSegments, each fitting maxWidth, or original segment if no breaking needed
118
112
  */
119
113
  private breakWordRich;
@@ -132,12 +126,11 @@ export declare class TextNode extends BoxNode {
132
126
  * - Ellipsis truncation
133
127
  * - Rich text styling per segment (color, weight, size, etc)
134
128
  * - Performance optimizations (clipping, visibility checks)
135
- *
136
- * @param ctx - Canvas rendering context
137
- * @param x - Content box left position in pixels
138
- * @param y - Content box top position in pixels
139
- * @param width - Content box total width including padding
140
- * @param height - Content box total height including padding
129
+ * @param ctx Canvas rendering context
130
+ * @param x Content box left position in pixels
131
+ * @param y Content box top position in pixels
132
+ * @param width Content box total width including padding
133
+ * @param height Content box total height including padding
141
134
  */
142
135
  protected _renderContent(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number): void;
143
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"text.canvas.util.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.util.ts"],"names":[],"mappings":"AAAA,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,CAAW;IACzC,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;cAuB1C,aAAa,IAAI,IAAI;IA+DxC;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,WAAW;IA+OnB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,YAAY;IAkLpB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAmErB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;;;;;;;;;;;;;;;OAiBG;cACgB,cAAc,CAC/B,GAAG,EAAE,wBAAwB,EAC7B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM;CAiWjB;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,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;cAuB1C,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;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IA8NnB;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IAuKpB;;;;;;;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;CA8UrH;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,aAA8B,CAAA"}
@@ -2,6 +2,7 @@ import { Canvas } from 'skia-canvas';
2
2
  import { BoxNode } from './layout.canvas.util.js';
3
3
  import { Style } from '../constant/common.const.js';
4
4
 
5
+ // TODO: Add comprehensive unit tests for this file.
5
6
  /**
6
7
  * Node for rendering text content with rich text styling support
7
8
  * Supports color and weight variations through HTML-like tags
@@ -10,7 +11,7 @@ class TextNode extends BoxNode {
10
11
  segments = [];
11
12
  lines = [];
12
13
  static measurementContext = null;
13
- metricsString = 'Ag|\``';
14
+ metricsString = 'Ag|``';
14
15
  lineHeights = [];
15
16
  lineAscents = [];
16
17
  lineContentHeights = [];
@@ -92,8 +93,7 @@ class TextNode extends BoxNode {
92
93
  * - \b - Backspace (removed)
93
94
  * - \f - Form feed (treated as newline)
94
95
  * - \v - Vertical tab (treated as newline)
95
- *
96
- * @param input - Raw text string potentially containing escape sequences
96
+ * @param input Raw text string potentially containing escape sequences
97
97
  * @returns Processed string with escape sequences converted
98
98
  */
99
99
  processEscapeSequences(input) {
@@ -139,9 +139,8 @@ class TextNode extends BoxNode {
139
139
  * <color="red">, <color='red'>, <color=red>
140
140
  *
141
141
  * Tags can be nested and must be properly closed with </tag>
142
- *
143
- * @param input - Text string containing markup tags
144
- * @param baseStyle - Default style properties to apply to all segments
142
+ * @param input Text string containing markup tags
143
+ * @param baseStyle Default style properties to apply to all segments
145
144
  * @returns Array of styled text segments with consistent style properties
146
145
  */
147
146
  parseRichText(input, baseStyle) {
@@ -251,8 +250,7 @@ class TextNode extends BoxNode {
251
250
  * - Style: segment <i> > base fontStyle
252
251
  * - Size: segment size > base fontSize
253
252
  * - Family: base fontFamily
254
- *
255
- * @param segmentStyle - Optional TextSegment styling to override base props
253
+ * @param segmentStyle Optional TextSegment styling to override base props
256
254
  * @returns Formatted CSS font string for canvas context
257
255
  */
258
256
  getFontString(segmentStyle) {
@@ -303,12 +301,11 @@ class TextNode extends BoxNode {
303
301
  * 2. Otherwise calculating dynamic height based on largest font size per line
304
302
  * 3. Adding leading space above/below text content
305
303
  * 4. Including specified line gaps between lines
306
- *
307
- * @param widthConstraint - Maximum allowed width in pixels for text layout
308
- * @param widthMode - YogaLayout mode determining how width constraint is applied
304
+ * @param widthConstraint Maximum allowed width in pixels for text layout
305
+ * @param widthMode YogaLayout mode determining how width constraint is applied
309
306
  * @returns Calculated minimum dimensions required to render text content
310
- * - width: Total width needed for text layout
311
- * - height: Total height including line heights and gaps
307
+ * - width: Total width needed for text layout
308
+ * - height: Total height including line heights and gaps
312
309
  */
313
310
  measureText(widthConstraint, widthMode) {
314
311
  // Create measurement canvas if not exists
@@ -428,9 +425,7 @@ class TextNode extends BoxNode {
428
425
  // Calculate total content height for line
429
426
  const actualContentHeight = maxAscent + maxDescent;
430
427
  // Determine final line box height with leading
431
- const targetLineBoxHeight = typeof this.props.lineHeight === 'number' && this.props.lineHeight > 0
432
- ? this.props.lineHeight
433
- : maxFontSizeOnLine * defaultLineHeightMultiplier;
428
+ const targetLineBoxHeight = typeof this.props.lineHeight === 'number' && this.props.lineHeight > 0 ? this.props.lineHeight : maxFontSizeOnLine * defaultLineHeightMultiplier;
434
429
  // Use larger of target height or content height to prevent clipping
435
430
  const finalLineHeight = Math.max(actualContentHeight, targetLineBoxHeight);
436
431
  // Store line metrics for rendering
@@ -534,11 +529,10 @@ class TextNode extends BoxNode {
534
529
  * Wraps text segments into multiple lines while respecting width constraints and preserving styling.
535
530
  * Handles rich text attributes (color, weight, size, bold, italic) and proper word wrapping.
536
531
  * Also respects explicit newline characters (\n) for forced line breaks.
537
- *
538
- * @param ctx - Canvas rendering context used for text measurements
539
- * @param segments - Array of text segments with styling information
540
- * @param maxWidth - Maximum allowed width for each line in pixels
541
- * @param parsedWordSpacingPx - Additional spacing to add between words in pixels
532
+ * @param ctx Canvas rendering context used for text measurements
533
+ * @param segments Array of text segments with styling information
534
+ * @param maxWidth Maximum allowed width for each line in pixels
535
+ * @param parsedWordSpacingPx Additional spacing to add between words in pixels
542
536
  * @returns Array of lines, where each line contains styled text segments
543
537
  */
544
538
  wrapTextRich(ctx, segments, maxWidth, parsedWordSpacingPx) {
@@ -552,8 +546,7 @@ class TextNode extends BoxNode {
552
546
  const finalizeLine = (forceEmpty = false) => {
553
547
  // Remove trailing whitespace segments unless we're forcing an empty line
554
548
  if (!forceEmpty) {
555
- while (currentLineSegments.length > 0 &&
556
- /^\s+$/.test(currentLineSegments[currentLineSegments.length - 1].text)) {
549
+ while (currentLineSegments.length > 0 && /^\s+$/.test(currentLineSegments[currentLineSegments.length - 1].text)) {
557
550
  currentLineSegments.pop();
558
551
  }
559
552
  }
@@ -596,8 +589,7 @@ class TextNode extends BoxNode {
596
589
  wordWidth = ctx.measureText(wordOrSpace).width;
597
590
  wordSegment = { text: wordOrSpace, ...segmentStyle, width: wordWidth };
598
591
  }
599
- const needsSpace = currentLineSegments.length > 0 &&
600
- !/^\s+$/.test(currentLineSegments[currentLineSegments.length - 1].text);
592
+ const needsSpace = currentLineSegments.length > 0 && !/^\s+$/.test(currentLineSegments[currentLineSegments.length - 1].text);
601
593
  const spaceToAdd = needsSpace ? spaceWidth + parsedWordSpacingPx : 0;
602
594
  if (currentLineWidth + spaceToAdd + wordWidth <= maxWidth || currentLineSegments.length === 0) {
603
595
  if (needsSpace) {
@@ -703,10 +695,9 @@ class TextNode extends BoxNode {
703
695
  /**
704
696
  * Breaks a word segment into multiple segments that each fit within the specified width constraint.
705
697
  * Maintains all styling properties (color, weight, size, bold, italic) across broken segments.
706
- *
707
- * @param ctx - Canvas rendering context used for text measurements
708
- * @param segmentToBreak - Original text segment to split
709
- * @param maxWidth - Maximum width allowed for each resulting segment
698
+ * @param ctx Canvas rendering context used for text measurements
699
+ * @param segmentToBreak Original text segment to split
700
+ * @param maxWidth Maximum width allowed for each resulting segment
710
701
  * @returns Array of TextSegments, each fitting maxWidth, or original segment if no breaking needed
711
702
  */
712
703
  breakWordRich(ctx, segmentToBreak, maxWidth) {
@@ -789,12 +780,11 @@ class TextNode extends BoxNode {
789
780
  * - Ellipsis truncation
790
781
  * - Rich text styling per segment (color, weight, size, etc)
791
782
  * - Performance optimizations (clipping, visibility checks)
792
- *
793
- * @param ctx - Canvas rendering context
794
- * @param x - Content box left position in pixels
795
- * @param y - Content box top position in pixels
796
- * @param width - Content box total width including padding
797
- * @param height - Content box total height including padding
783
+ * @param ctx Canvas rendering context
784
+ * @param x Content box left position in pixels
785
+ * @param y Content box top position in pixels
786
+ * @param width Content box total width including padding
787
+ * @param height Content box total height including padding
798
788
  */
799
789
  _renderContent(ctx, x, y, width, height) {
800
790
  super._renderContent(ctx, x, y, width, height);
@@ -995,11 +985,7 @@ class TextNode extends BoxNode {
995
985
  let applyEllipsisAfter = false;
996
986
  if (isLastRenderedLine && needsEllipsis && !isSpaceSegment) {
997
987
  const currentTotalWidth = accumulatedWidth + spaceToAddBefore + segmentWidth;
998
- const spaceNeededAfter = isLastSegmentOnLine
999
- ? 0
1000
- : isJustify
1001
- ? spacePerWordGapPlusSpacing
1002
- : spaceWidth + parsedWordSpacingPx;
988
+ const spaceNeededAfter = isLastSegmentOnLine ? 0 : isJustify ? spacePerWordGapPlusSpacing : spaceWidth + parsedWordSpacingPx;
1003
989
  if (currentTotalWidth > contentWidth - spaceNeededAfter) {
1004
990
  const availableWidthForSegment = contentWidth - accumulatedWidth - spaceToAddBefore - ellipsisWidth;
1005
991
  if (availableWidthForSegment > 0) {
@@ -1033,11 +1019,7 @@ class TextNode extends BoxNode {
1033
1019
  const remainingRenderWidth = contentX + contentWidth - currentX;
1034
1020
  if (currentSegmentRenderWidth > 0 && remainingRenderWidth > 0 && !isSpaceSegment) {
1035
1021
  ctx.textAlign = 'left';
1036
- const shadows = this.props.textShadow
1037
- ? Array.isArray(this.props.textShadow)
1038
- ? this.props.textShadow
1039
- : [this.props.textShadow]
1040
- : [];
1022
+ const shadows = this.props.textShadow ? (Array.isArray(this.props.textShadow) ? this.props.textShadow : [this.props.textShadow]) : [];
1041
1023
  ctx.save();
1042
1024
  // Draw shadows
1043
1025
  for (const shadow of shadows) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meonode/canvas",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "A declarative, component-based library for generating high-quality images on a canvas, inspired by the MeoNode UI library for React. It leverages skia-canvas for rendering and yoga-layout for flexible, CSS-like layouts.",
5
5
  "keywords": [
6
6
  "canvas",
@@ -36,7 +36,8 @@
36
36
  "prepare": "husky"
37
37
  },
38
38
  "devDependencies": {
39
- "@rollup/plugin-alias": "^6.0.0",
39
+ "@eslint/js": "^9.39.1",
40
+ "@jest/globals": "^30.2.0",
40
41
  "@rollup/plugin-commonjs": "^29.0.0",
41
42
  "@rollup/plugin-node-resolve": "^16.0.3",
42
43
  "@rollup/plugin-typescript": "^12.3.0",
@@ -46,17 +47,12 @@
46
47
  "@semantic-release/npm": "^13.1.1",
47
48
  "@semantic-release/release-notes-generator": "^14.1.0",
48
49
  "@types/jest": "^30.0.0",
49
- "@types/lodash-es": "^4.17.12",
50
- "@types/module-alias": "^2.0.4",
51
50
  "@types/sharp": "^0.32.0",
52
- "@types/skia-canvas": "^0.9.28",
53
- "@types/tinycolor2": "^1.4.6",
54
- "@types/yoga-layout": "^3.1.0",
55
51
  "@typescript-eslint/eslint-plugin": "^8.46.3",
56
52
  "@typescript-eslint/parser": "^8.46.3",
57
53
  "eslint": "^9.39.1",
58
54
  "eslint-config-prettier": "^10.1.8",
59
- "eslint-plugin-import": "^2.32.0",
55
+ "eslint-plugin-jsdoc": "^61.1.12",
60
56
  "eslint-plugin-prettier": "^5.5.4",
61
57
  "eslint-plugin-unused-imports": "^4.3.0",
62
58
  "husky": "^9.1.7",
@@ -75,7 +71,7 @@
75
71
  "dependencies": {
76
72
  "file-type": "^21.0.0",
77
73
  "lodash-es": "^4.17.21",
78
- "module-alias": "^2.2.3",
74
+ "sharp": "^0.34.5",
79
75
  "skia-canvas": "^3.0.8",
80
76
  "tinycolor2": "^1.6.0",
81
77
  "tslib": "^2.8.1",