@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.
- package/dist/cjs/canvas/canvas.helper.d.ts +9 -11
- package/dist/cjs/canvas/canvas.helper.d.ts.map +1 -1
- package/dist/cjs/canvas/canvas.helper.js +39 -47
- package/dist/cjs/canvas/canvas.helper.js.map +1 -1
- package/dist/cjs/canvas/canvas.type.d.ts +1 -1
- package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
- package/dist/cjs/canvas/grid.canvas.util.d.ts +4 -4
- package/dist/cjs/canvas/grid.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/grid.canvas.util.js +21 -4
- package/dist/cjs/canvas/grid.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/image.canvas.util.d.ts +0 -1
- package/dist/cjs/canvas/image.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/image.canvas.util.js +72 -72
- package/dist/cjs/canvas/image.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/layout.canvas.util.d.ts +16 -17
- package/dist/cjs/canvas/layout.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/layout.canvas.util.js +17 -24
- package/dist/cjs/canvas/layout.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/root.canvas.util.d.ts +4 -2
- package/dist/cjs/canvas/root.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/root.canvas.util.js +7 -3
- package/dist/cjs/canvas/root.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/text.canvas.util.d.ts +20 -27
- package/dist/cjs/canvas/text.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/text.canvas.util.js +27 -45
- package/dist/cjs/canvas/text.canvas.util.js.map +1 -1
- package/dist/esm/canvas/canvas.helper.d.ts +9 -11
- package/dist/esm/canvas/canvas.helper.d.ts.map +1 -1
- package/dist/esm/canvas/canvas.helper.js +39 -47
- package/dist/esm/canvas/canvas.type.d.ts +1 -1
- package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
- package/dist/esm/canvas/grid.canvas.util.d.ts +4 -4
- package/dist/esm/canvas/grid.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/grid.canvas.util.js +21 -4
- package/dist/esm/canvas/image.canvas.util.d.ts +0 -1
- package/dist/esm/canvas/image.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/image.canvas.util.js +72 -72
- package/dist/esm/canvas/layout.canvas.util.d.ts +16 -17
- package/dist/esm/canvas/layout.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/layout.canvas.util.js +17 -24
- package/dist/esm/canvas/root.canvas.util.d.ts +4 -2
- package/dist/esm/canvas/root.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/root.canvas.util.js +7 -3
- package/dist/esm/canvas/text.canvas.util.d.ts +20 -27
- package/dist/esm/canvas/text.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/text.canvas.util.js +27 -45
- 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
|
|
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
|
|
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
|
|
116
|
-
* @param index
|
|
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
|
|
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
|
|
299
|
-
* @param {number} offsetX
|
|
300
|
-
* @param {number} offsetY
|
|
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
|
|
460
|
-
* @param
|
|
461
|
-
* @param
|
|
462
|
-
* @param
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
95
|
-
*
|
|
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
|
|
104
|
-
* @param
|
|
105
|
-
* @param
|
|
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
|
|
115
|
-
* @param
|
|
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
|
|
137
|
-
* @param
|
|
138
|
-
* @param
|
|
139
|
-
* @param
|
|
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":"
|
|
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
|
|
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
|
|
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
|
-
*
|
|
311
|
-
*
|
|
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
|
|
539
|
-
* @param
|
|
540
|
-
* @param
|
|
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
|
|
708
|
-
* @param
|
|
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
|
|
794
|
-
* @param
|
|
795
|
-
* @param
|
|
796
|
-
* @param
|
|
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.
|
|
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
|
-
"@
|
|
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-
|
|
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
|
-
"
|
|
74
|
+
"sharp": "^0.34.5",
|
|
79
75
|
"skia-canvas": "^3.0.8",
|
|
80
76
|
"tinycolor2": "^1.6.0",
|
|
81
77
|
"tslib": "^2.8.1",
|