@meonode/canvas 1.0.0-beta.3 → 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/Readme.md +1 -1
- 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 +47 -48
- package/dist/cjs/canvas/canvas.helper.js.map +1 -1
- package/dist/cjs/canvas/canvas.type.d.ts +31 -31
- 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 +47 -48
- package/dist/esm/canvas/canvas.type.d.ts +31 -31
- 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 +16 -9
|
@@ -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,7 +1,18 @@
|
|
|
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
|
+
"keywords": [
|
|
6
|
+
"canvas",
|
|
7
|
+
"server-side",
|
|
8
|
+
"image-generation",
|
|
9
|
+
"declarative",
|
|
10
|
+
"component-based",
|
|
11
|
+
"skia-canvas",
|
|
12
|
+
"yoga-layout",
|
|
13
|
+
"node",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
5
16
|
"license": "MIT",
|
|
6
17
|
"author": "Ukasyah Rahmatullah Zada",
|
|
7
18
|
"publishConfig": {
|
|
@@ -25,7 +36,8 @@
|
|
|
25
36
|
"prepare": "husky"
|
|
26
37
|
},
|
|
27
38
|
"devDependencies": {
|
|
28
|
-
"@
|
|
39
|
+
"@eslint/js": "^9.39.1",
|
|
40
|
+
"@jest/globals": "^30.2.0",
|
|
29
41
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
30
42
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
31
43
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
@@ -35,17 +47,12 @@
|
|
|
35
47
|
"@semantic-release/npm": "^13.1.1",
|
|
36
48
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
37
49
|
"@types/jest": "^30.0.0",
|
|
38
|
-
"@types/lodash-es": "^4.17.12",
|
|
39
|
-
"@types/module-alias": "^2.0.4",
|
|
40
50
|
"@types/sharp": "^0.32.0",
|
|
41
|
-
"@types/skia-canvas": "^0.9.28",
|
|
42
|
-
"@types/tinycolor2": "^1.4.6",
|
|
43
|
-
"@types/yoga-layout": "^3.1.0",
|
|
44
51
|
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
45
52
|
"@typescript-eslint/parser": "^8.46.3",
|
|
46
53
|
"eslint": "^9.39.1",
|
|
47
54
|
"eslint-config-prettier": "^10.1.8",
|
|
48
|
-
"eslint-plugin-
|
|
55
|
+
"eslint-plugin-jsdoc": "^61.1.12",
|
|
49
56
|
"eslint-plugin-prettier": "^5.5.4",
|
|
50
57
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
51
58
|
"husky": "^9.1.7",
|
|
@@ -64,7 +71,7 @@
|
|
|
64
71
|
"dependencies": {
|
|
65
72
|
"file-type": "^21.0.0",
|
|
66
73
|
"lodash-es": "^4.17.21",
|
|
67
|
-
"
|
|
74
|
+
"sharp": "^0.34.5",
|
|
68
75
|
"skia-canvas": "^3.0.8",
|
|
69
76
|
"tinycolor2": "^1.6.0",
|
|
70
77
|
"tslib": "^2.8.1",
|