text-to-canvas 1.2.1 → 3.0.0
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/CHANGELOG.md +13 -3
- package/README.md +7 -5
- package/dist/text-to-canvas.cjs +185 -17
- package/dist/text-to-canvas.esm.min.js +319 -221
- package/dist/text-to-canvas.esm.min.js.map +1 -1
- package/dist/text-to-canvas.min.js +1 -1
- package/dist/text-to-canvas.min.js.map +1 -1
- package/dist/text-to-canvas.mjs +185 -17
- package/dist/text-to-canvas.umd.min.js +1 -1
- package/dist/text-to-canvas.umd.min.js.map +1 -1
- package/dist/types/model.d.ts +77 -5
- package/dist/types/util/split.d.ts +1 -1
- package/dist/types/util/style.d.ts +2 -2
- package/package.json +26 -25
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
The format is inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
|
|
5
|
+
The format is inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## v3.0.0
|
|
8
|
+
|
|
9
|
+
- __Breaking:__ The `RenderSpec.textBaseline` property type has changed from `CanvasTextBaseline` to `RenderTextBaseline` which is a narrowing of possible baselines used for actual rendering.
|
|
10
|
+
- Added new `TextFormat.underline` and `TextFormat.strikethrough` options ([#600](https://github.com/stefcameron/text-to-canvas/pull/600))
|
|
11
|
+
- Set to true to enable with all defaults, or specify an object with `{ color?, thickness?, offset? }` properties to have more customization.
|
|
12
|
+
- By default, the thickness and offset scale with the font size based on `24px` font size (which yields a `1px` thick line that is just beneath the text for underline and centered on the text for strikethrough). This simply seemed to be the best balance for the handful of fonts that were tested (all fonts currently available in the demo app). Tweak as needed for the font(s) you will use.
|
|
13
|
+
|
|
14
|
+
## v2.0.0
|
|
15
|
+
|
|
16
|
+
- __Breaking:__ Updated minimum supported of Node to `>=22` and builds now target `ES2022` at minimum.
|
|
7
17
|
|
|
8
18
|
## v1.2.1
|
|
9
19
|
|
|
@@ -12,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
12
22
|
## v1.2.0
|
|
13
23
|
|
|
14
24
|
- New `strokeColor` and `strokeWidth` text formatting options to control the outline of the text ([#292](https://github.com/stefcameron/text-to-canvas/issues/292)).
|
|
15
|
-
|
|
25
|
+
- Note that due to how the `strokeText()` and `measureText()` Canvas APIs work, the stroke is __not considered__ in text placement. Setting a large width will result in the stroke "bleeding" outside the text box.
|
|
16
26
|
|
|
17
27
|
## v1.1.2
|
|
18
28
|
|
package/README.md
CHANGED
|
@@ -76,8 +76,8 @@ Use in Node is only supported to the extent that appropriate bundles are provide
|
|
|
76
76
|
|
|
77
77
|
Two bundles are provided for this type of target:
|
|
78
78
|
|
|
79
|
-
- `./dist/text-to-canvas.esm.min.js` (ESM, `import`,
|
|
80
|
-
- `./dist/text-to-canvas.min.js` (CJS, `require()`,
|
|
79
|
+
- `./dist/text-to-canvas.esm.min.js` (ESM, `import`, ES2022+)
|
|
80
|
+
- `./dist/text-to-canvas.min.js` (CJS, `require()`, ES2022+)
|
|
81
81
|
|
|
82
82
|
Used implicitly when using the library in a larger app bundled with a bundler like Webpack, Rollup, or Vite.
|
|
83
83
|
|
|
@@ -129,7 +129,7 @@ const { height } = drawText(...);
|
|
|
129
129
|
|
|
130
130
|
One bundle is provided for this type of target:
|
|
131
131
|
|
|
132
|
-
- `./dist/text-to-canvas.umd.min.js` (UMD,
|
|
132
|
+
- `./dist/text-to-canvas.umd.min.js` (UMD, ES2022+)
|
|
133
133
|
|
|
134
134
|
Used implicitly when loading the library directly in a browser:
|
|
135
135
|
|
|
@@ -148,8 +148,8 @@ Used implicitly when loading the library directly in a browser:
|
|
|
148
148
|
|
|
149
149
|
Two bundles are provided for this type of target:
|
|
150
150
|
|
|
151
|
-
- `./dist/text-to-canvas.mjs` (ESM/MJS, `import`, Node
|
|
152
|
-
- `./dist/text-to-canvas.cjs` (CJS, `require()`, Node
|
|
151
|
+
- `./dist/text-to-canvas.mjs` (ESM/MJS, `import`, Node v22+)
|
|
152
|
+
- `./dist/text-to-canvas.cjs` (CJS, `require()`, Node v22+)
|
|
153
153
|
|
|
154
154
|
> ⚠️ Other than the bundles, __Node is not formally supported by this library__, and neither is the `node-canvas` library used in the demo. Whatever "Node Canvas" library you use, make sure it supports the [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) API and it _should_ work.
|
|
155
155
|
|
|
@@ -188,6 +188,8 @@ You can run this demo locally with `npm run node:demo`
|
|
|
188
188
|
| `strokeColor` | `'black'` | Base stroke color, same as css color. Examples: `blue`, `#00ff00`. |
|
|
189
189
|
| `strokeWidth` | `0` | Base stroke width. Positive number; `<=0` means none. Can be fractional. ⚠️ Word splitting does not take into account the stroke, which is applied on the __center__ of the edges of the text via the [strokeText()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeText) Canvas API. Setting a thick stroke will cause it to bleed out of the text box. |
|
|
190
190
|
| `justify` | `false` | Justify text if `true`, it will insert spaces between words when necessary. |
|
|
191
|
+
| `underline` | `false` | If the text or word should be underlined. Can also be an object with customization options like color, thickness, and offset. |
|
|
192
|
+
| `strikethrough` | `false` | If the text or word should have a strikethrough. Can also be an object with customization options like color, thickness, and offset. |
|
|
191
193
|
| `inferWhitespace` | `true` | If whitespace in the text should be inferred. Only applies if the text given to `drawText()` is a `Word[]`. If the text is a `string`, this config setting is ignored. |
|
|
192
194
|
| `overflow` | `true` | Allows the text to overflow out of the box if the box is too narrow/short to fit it all. `false` will clip the text to the box's boundaries. |
|
|
193
195
|
| `debug` | `false` | Draws the border and alignment lines of the text box for debugging purposes. |
|
package/dist/text-to-canvas.cjs
CHANGED
|
@@ -6,22 +6,120 @@ const DEFAULT_FONT_COLOR = "black";
|
|
|
6
6
|
const DEFAULT_STROKE_COLOR = DEFAULT_FONT_COLOR;
|
|
7
7
|
const DEFAULT_STROKE_WIDTH = 0;
|
|
8
8
|
const DEFAULT_STROKE_JOIN = "round";
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
const _formatMerge = (...sources) => {
|
|
10
|
+
const target = {
|
|
11
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
12
|
+
fontSize: DEFAULT_FONT_SIZE,
|
|
13
|
+
fontWeight: "400",
|
|
14
|
+
fontStyle: "",
|
|
15
|
+
fontVariant: "",
|
|
16
|
+
fontColor: DEFAULT_FONT_COLOR,
|
|
17
|
+
strokeColor: DEFAULT_STROKE_COLOR,
|
|
18
|
+
strokeWidth: DEFAULT_STROKE_WIDTH,
|
|
19
|
+
underline: {
|
|
20
|
+
color: DEFAULT_FONT_COLOR,
|
|
21
|
+
thickness: 0,
|
|
22
|
+
offset: 0
|
|
21
23
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
strikethrough: {
|
|
25
|
+
color: DEFAULT_FONT_COLOR,
|
|
26
|
+
thickness: 0,
|
|
27
|
+
offset: 0
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
sources.forEach((source) => {
|
|
31
|
+
if (source && typeof source === "object" && !Array.isArray(source)) {
|
|
32
|
+
Object.entries(source).forEach(([sourceKey, sourceValue]) => {
|
|
33
|
+
if (sourceValue !== void 0) {
|
|
34
|
+
if (sourceKey === "underline" || sourceKey === "strikethrough") {
|
|
35
|
+
if (typeof sourceValue === "boolean") {
|
|
36
|
+
if (sourceValue) {
|
|
37
|
+
target[sourceKey].color = "";
|
|
38
|
+
target[sourceKey].thickness = NaN;
|
|
39
|
+
target[sourceKey].offset = NaN;
|
|
40
|
+
}
|
|
41
|
+
} else if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
|
|
42
|
+
const underStrikeSource = sourceValue;
|
|
43
|
+
const underStrikeSourceKeys = Object.keys(
|
|
44
|
+
underStrikeSource
|
|
45
|
+
);
|
|
46
|
+
underStrikeSourceKeys.forEach((k) => {
|
|
47
|
+
if (underStrikeSource[k] !== void 0) {
|
|
48
|
+
target[sourceKey][k] = underStrikeSource[k];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (!underStrikeSourceKeys.includes("color")) {
|
|
52
|
+
target[sourceKey].color = "";
|
|
53
|
+
}
|
|
54
|
+
if (!underStrikeSourceKeys.includes("thickness")) {
|
|
55
|
+
target[sourceKey].thickness = NaN;
|
|
56
|
+
}
|
|
57
|
+
if (!underStrikeSourceKeys.includes("offset")) {
|
|
58
|
+
target[sourceKey].offset = NaN;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
target[sourceKey] = sourceValue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return target;
|
|
69
|
+
};
|
|
70
|
+
const getTextFormat = (format, baseFormat) => {
|
|
71
|
+
const underStrikeProps = [
|
|
72
|
+
"underline",
|
|
73
|
+
"strikethrough"
|
|
74
|
+
];
|
|
75
|
+
const fullBaseFormat = {
|
|
76
|
+
...baseFormat
|
|
77
|
+
};
|
|
78
|
+
const fullFormat = {
|
|
79
|
+
...format
|
|
80
|
+
};
|
|
81
|
+
underStrikeProps.forEach((prop) => {
|
|
82
|
+
[fullBaseFormat, fullFormat].forEach((obj) => {
|
|
83
|
+
if (obj[prop] !== void 0) {
|
|
84
|
+
if (obj[prop] === true) {
|
|
85
|
+
obj[prop] = {
|
|
86
|
+
thickness: NaN
|
|
87
|
+
};
|
|
88
|
+
} else if (obj[prop] === false) {
|
|
89
|
+
obj[prop] = {
|
|
90
|
+
thickness: 0
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
obj[prop] = {
|
|
94
|
+
...obj[prop]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
const result = _formatMerge(fullBaseFormat, fullFormat);
|
|
101
|
+
const fontSizeFactor = result.fontSize / 24;
|
|
102
|
+
underStrikeProps.forEach((prop) => {
|
|
103
|
+
if (result[prop].color === "") {
|
|
104
|
+
result[prop].color = result.fontColor;
|
|
105
|
+
}
|
|
106
|
+
if (Number.isNaN(result[prop].thickness)) {
|
|
107
|
+
result[prop].thickness = fontSizeFactor;
|
|
108
|
+
}
|
|
109
|
+
if (Number.isNaN(result[prop].offset)) {
|
|
110
|
+
if (result.fontFamily.startsWith("Roboto") || result.fontFamily.startsWith("Verdana")) {
|
|
111
|
+
result[prop].offset = -2;
|
|
112
|
+
} else if (result.fontFamily === "Inter" || result.fontFamily.startsWith("Montserrat")) {
|
|
113
|
+
result[prop].offset = -1;
|
|
114
|
+
} else if (result.fontFamily.startsWith("Comic Sans")) {
|
|
115
|
+
result[prop].offset = -5;
|
|
116
|
+
} else {
|
|
117
|
+
result[prop].offset = 0;
|
|
118
|
+
}
|
|
119
|
+
result[prop].offset *= fontSizeFactor;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return result;
|
|
25
123
|
};
|
|
26
124
|
const getTextStyle = ({
|
|
27
125
|
fontFamily,
|
|
@@ -362,7 +460,7 @@ const splitWords = ({
|
|
|
362
460
|
return {
|
|
363
461
|
lines: [],
|
|
364
462
|
textAlign: "center",
|
|
365
|
-
textBaseline: "
|
|
463
|
+
textBaseline: "top",
|
|
366
464
|
width: positioning.width,
|
|
367
465
|
height: 0
|
|
368
466
|
};
|
|
@@ -472,6 +570,72 @@ const getTextHeight = ({
|
|
|
472
570
|
}) => {
|
|
473
571
|
return _getHeight(ctx, text, style);
|
|
474
572
|
};
|
|
573
|
+
const _drawUnderline = ({
|
|
574
|
+
pw,
|
|
575
|
+
textBaseline,
|
|
576
|
+
baseFormat,
|
|
577
|
+
ctx
|
|
578
|
+
}) => {
|
|
579
|
+
const lineWidth = pw.format?.underline.thickness ?? baseFormat.underline.thickness;
|
|
580
|
+
if (lineWidth <= 0) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
ctx.save();
|
|
584
|
+
ctx.lineWidth = lineWidth;
|
|
585
|
+
ctx.strokeStyle = pw.format?.underline.color ?? baseFormat.underline.color;
|
|
586
|
+
let underlineY = pw.y;
|
|
587
|
+
switch (textBaseline) {
|
|
588
|
+
case "top":
|
|
589
|
+
underlineY += pw.height - (pw.word.metrics?.fontBoundingBoxDescent ?? 0);
|
|
590
|
+
break;
|
|
591
|
+
case "bottom":
|
|
592
|
+
underlineY -= (pw.word.metrics?.fontBoundingBoxDescent ?? 0) / 2;
|
|
593
|
+
break;
|
|
594
|
+
default: {
|
|
595
|
+
const never = textBaseline;
|
|
596
|
+
return never;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
underlineY += pw.format?.underline.offset ?? baseFormat.underline.offset;
|
|
600
|
+
ctx.beginPath();
|
|
601
|
+
ctx.moveTo(pw.x, underlineY);
|
|
602
|
+
ctx.lineTo(pw.x + pw.width, underlineY);
|
|
603
|
+
ctx.stroke();
|
|
604
|
+
ctx.restore();
|
|
605
|
+
};
|
|
606
|
+
const _drawStrikethrough = ({
|
|
607
|
+
pw,
|
|
608
|
+
textBaseline,
|
|
609
|
+
baseFormat,
|
|
610
|
+
ctx
|
|
611
|
+
}) => {
|
|
612
|
+
const lineWidth = pw.format?.strikethrough.thickness ?? baseFormat.strikethrough.thickness;
|
|
613
|
+
if (lineWidth <= 0) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
ctx.save();
|
|
617
|
+
ctx.lineWidth = lineWidth;
|
|
618
|
+
ctx.strokeStyle = pw.format?.strikethrough.color ?? baseFormat.strikethrough.color;
|
|
619
|
+
let strikethroughY = pw.y;
|
|
620
|
+
switch (textBaseline) {
|
|
621
|
+
case "top":
|
|
622
|
+
strikethroughY += pw.height / 2;
|
|
623
|
+
break;
|
|
624
|
+
case "bottom":
|
|
625
|
+
strikethroughY -= pw.height / 2 - (pw.word.metrics?.fontBoundingBoxDescent ?? 0) / 3;
|
|
626
|
+
break;
|
|
627
|
+
default: {
|
|
628
|
+
const never = textBaseline;
|
|
629
|
+
return never;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
strikethroughY += pw.format?.strikethrough.offset ?? baseFormat.strikethrough.offset;
|
|
633
|
+
ctx.beginPath();
|
|
634
|
+
ctx.moveTo(pw.x, strikethroughY);
|
|
635
|
+
ctx.lineTo(pw.x + pw.width, strikethroughY);
|
|
636
|
+
ctx.stroke();
|
|
637
|
+
ctx.restore();
|
|
638
|
+
};
|
|
475
639
|
const drawText = (ctx, text, config) => {
|
|
476
640
|
const baseFormat = getTextFormat({
|
|
477
641
|
fontFamily: config.fontFamily,
|
|
@@ -481,7 +645,9 @@ const drawText = (ctx, text, config) => {
|
|
|
481
645
|
fontWeight: config.fontWeight,
|
|
482
646
|
fontColor: config.fontColor,
|
|
483
647
|
strokeColor: config.strokeColor,
|
|
484
|
-
strokeWidth: config.strokeWidth
|
|
648
|
+
strokeWidth: config.strokeWidth,
|
|
649
|
+
underline: config.underline,
|
|
650
|
+
strikethrough: config.strikethrough
|
|
485
651
|
});
|
|
486
652
|
const {
|
|
487
653
|
width: boxWidth,
|
|
@@ -544,6 +710,8 @@ const drawText = (ctx, text, config) => {
|
|
|
544
710
|
ctx.restore();
|
|
545
711
|
}
|
|
546
712
|
}
|
|
713
|
+
_drawUnderline({ pw, textBaseline, baseFormat, ctx });
|
|
714
|
+
_drawStrikethrough({ pw, textBaseline, baseFormat, ctx });
|
|
547
715
|
});
|
|
548
716
|
});
|
|
549
717
|
if (config.debug) {
|