@meonode/canvas 2.0.4 → 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/CONTRIBUTING.md +9 -9
- package/README.md +9 -21
- package/dist/cjs/canvas/canvas.helper.d.ts +1 -1
- package/dist/cjs/canvas/canvas.type.d.ts +9 -4
- package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
- package/dist/cjs/canvas/chart.canvas.d.ts +7 -3
- package/dist/cjs/canvas/chart.canvas.d.ts.map +1 -1
- package/dist/cjs/canvas/chart.canvas.js +3 -4
- package/dist/cjs/canvas/chart.canvas.js.map +1 -1
- package/dist/cjs/canvas/grid.canvas.d.ts +2 -2
- package/dist/cjs/canvas/grid.canvas.d.ts.map +1 -1
- package/dist/cjs/canvas/grid.canvas.js +2 -8
- package/dist/cjs/canvas/grid.canvas.js.map +1 -1
- package/dist/cjs/canvas/image.canvas.d.ts +2 -2
- package/dist/cjs/canvas/image.canvas.js.map +1 -1
- package/dist/cjs/canvas/layout.canvas.d.ts +5 -1
- package/dist/cjs/canvas/layout.canvas.d.ts.map +1 -1
- package/dist/cjs/canvas/layout.canvas.js +59 -68
- package/dist/cjs/canvas/layout.canvas.js.map +1 -1
- package/dist/cjs/canvas/root.canvas.d.ts +9 -16
- package/dist/cjs/canvas/root.canvas.d.ts.map +1 -1
- package/dist/cjs/canvas/root.canvas.js +56 -43
- package/dist/cjs/canvas/root.canvas.js.map +1 -1
- package/dist/cjs/canvas/text.canvas.d.ts +7 -3
- package/dist/cjs/canvas/text.canvas.d.ts.map +1 -1
- package/dist/cjs/canvas/text.canvas.js +25 -85
- package/dist/cjs/canvas/text.canvas.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/util/disk.cache.d.ts +5 -0
- package/dist/cjs/util/disk.cache.d.ts.map +1 -1
- package/dist/cjs/util/disk.cache.js +23 -8
- package/dist/cjs/util/disk.cache.js.map +1 -1
- package/dist/cjs/worker/comlink.pool.d.ts +15 -4
- package/dist/cjs/worker/comlink.pool.d.ts.map +1 -1
- package/dist/cjs/worker/comlink.pool.js +69 -18
- package/dist/cjs/worker/comlink.pool.js.map +1 -1
- package/dist/cjs/worker/comlink.setup.d.ts.map +1 -1
- package/dist/cjs/worker/comlink.setup.js +1 -2
- package/dist/cjs/worker/comlink.setup.js.map +1 -1
- package/dist/cjs/worker/render.worker.js +4 -2
- package/dist/cjs/worker/render.worker.js.map +1 -1
- package/dist/cjs/worker/worker.types.d.ts +2 -1
- package/dist/cjs/worker/worker.types.d.ts.map +1 -1
- package/dist/esm/canvas/canvas.helper.d.ts +1 -1
- package/dist/esm/canvas/canvas.type.d.ts +9 -4
- package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
- package/dist/esm/canvas/chart.canvas.d.ts +7 -3
- package/dist/esm/canvas/chart.canvas.d.ts.map +1 -1
- package/dist/esm/canvas/chart.canvas.js +3 -4
- package/dist/esm/canvas/grid.canvas.d.ts +2 -2
- package/dist/esm/canvas/grid.canvas.d.ts.map +1 -1
- package/dist/esm/canvas/grid.canvas.js +1 -7
- package/dist/esm/canvas/image.canvas.d.ts +2 -2
- package/dist/esm/canvas/layout.canvas.d.ts +5 -1
- package/dist/esm/canvas/layout.canvas.d.ts.map +1 -1
- package/dist/esm/canvas/layout.canvas.js +59 -69
- package/dist/esm/canvas/root.canvas.d.ts +9 -16
- package/dist/esm/canvas/root.canvas.d.ts.map +1 -1
- package/dist/esm/canvas/root.canvas.js +57 -43
- package/dist/esm/canvas/text.canvas.d.ts +7 -3
- package/dist/esm/canvas/text.canvas.d.ts.map +1 -1
- package/dist/esm/canvas/text.canvas.js +25 -85
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/util/disk.cache.d.ts +5 -0
- package/dist/esm/util/disk.cache.d.ts.map +1 -1
- package/dist/esm/util/disk.cache.js +23 -9
- package/dist/esm/worker/comlink.pool.d.ts +15 -4
- package/dist/esm/worker/comlink.pool.d.ts.map +1 -1
- package/dist/esm/worker/comlink.pool.js +67 -18
- package/dist/esm/worker/comlink.setup.d.ts.map +1 -1
- package/dist/esm/worker/comlink.setup.js +1 -1
- package/dist/esm/worker/render.worker.js +4 -2
- package/dist/esm/worker/worker.types.d.ts +2 -1
- package/dist/esm/worker/worker.types.d.ts.map +1 -1
- package/package.json +9 -16
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Canvas } from 'skia-canvas';
|
|
2
2
|
import { parsePercentage, parseBorderRadius, drawRoundedRectPath, drawBorders } from './canvas.helper.js';
|
|
3
|
-
import { omit } from 'lodash-es';
|
|
4
|
-
import tinycolor from 'tinycolor2';
|
|
5
3
|
import { Style } from '../constant/common.const.js';
|
|
6
4
|
import YogaTypes__default from 'yoga-layout';
|
|
7
5
|
|
|
6
|
+
const _HEX_ALPHA_RE = /^#([0-9a-fA-F]{8})$/;
|
|
8
7
|
/**
|
|
9
8
|
* @class BoxNode
|
|
10
9
|
* @classdesc Base node class for rendering rectangular boxes with layout, styling, and children.
|
|
@@ -123,7 +122,8 @@ class BoxNode {
|
|
|
123
122
|
console.warn('Attempted to append an invalid child node.', child);
|
|
124
123
|
return;
|
|
125
124
|
}
|
|
126
|
-
|
|
125
|
+
const { children: _c, ...inheritedProps } = this.props;
|
|
126
|
+
child.resolveInheritedStyles(inheritedProps);
|
|
127
127
|
child.applyDefaults();
|
|
128
128
|
this.children.push(child);
|
|
129
129
|
this.node.insertChild(child.node, index);
|
|
@@ -212,69 +212,12 @@ class BoxNode {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
if (gap)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
for (const [gutter, value] of Object.entries(gap)) {
|
|
224
|
-
if (gutter in Style.Gutter) {
|
|
225
|
-
const gutterKey = gutter;
|
|
226
|
-
if (typeof value === 'string' && value.endsWith('%')) {
|
|
227
|
-
this.node.setGapPercent(Style.Gutter[gutterKey], parseFloat(value));
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
this.node.setGap(Style.Gutter[gutterKey], value);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (margin) {
|
|
237
|
-
if (typeof margin === 'number' || margin === 'auto') {
|
|
238
|
-
this.node.setMargin(Style.Edge.All, margin);
|
|
239
|
-
}
|
|
240
|
-
else if (typeof margin === 'string' && margin.endsWith('%')) {
|
|
241
|
-
this.node.setMarginPercent(Style.Edge.All, parseFloat(margin));
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
for (const [edge, value] of Object.entries(margin)) {
|
|
245
|
-
if (edge in Style.Edge) {
|
|
246
|
-
const edgeKey = edge;
|
|
247
|
-
if (typeof value === 'string' && value.endsWith('%')) {
|
|
248
|
-
this.node.setMarginPercent(Style.Edge[edgeKey], parseFloat(value));
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
this.node.setMargin(Style.Edge[edgeKey], value);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (padding) {
|
|
258
|
-
if (typeof padding === 'number') {
|
|
259
|
-
this.node.setPadding(Style.Edge.All, padding);
|
|
260
|
-
}
|
|
261
|
-
else if (typeof padding === 'string' && padding.endsWith('%')) {
|
|
262
|
-
this.node.setPaddingPercent(Style.Edge.All, parseFloat(padding));
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
for (const [edge, value] of Object.entries(padding)) {
|
|
266
|
-
if (edge in Style.Edge) {
|
|
267
|
-
const edgeKey = edge;
|
|
268
|
-
if (typeof value === 'string' && value.endsWith('%')) {
|
|
269
|
-
this.node.setPaddingPercent(Style.Edge[edgeKey], parseFloat(value));
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
this.node.setPadding(Style.Edge[edgeKey], value);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
215
|
+
if (gap)
|
|
216
|
+
_setEdgeValues(gap, Style.Gutter, (e, v) => this.node.setGap(e, v), (e, v) => this.node.setGapPercent(e, v));
|
|
217
|
+
if (margin)
|
|
218
|
+
_setEdgeValues(margin, Style.Edge, (e, v) => this.node.setMargin(e, v), (e, v) => this.node.setMarginPercent(e, v), true);
|
|
219
|
+
if (padding)
|
|
220
|
+
_setEdgeValues(padding, Style.Edge, (e, v) => this.node.setPadding(e, v), (e, v) => this.node.setPaddingPercent(e, v));
|
|
278
221
|
if (border) {
|
|
279
222
|
if (typeof border === 'number') {
|
|
280
223
|
this.node.setBorder(Style.Edge.All, border);
|
|
@@ -495,8 +438,7 @@ class BoxNode {
|
|
|
495
438
|
const backgroundColor = this.props.backgroundColor;
|
|
496
439
|
let isOpaque = false;
|
|
497
440
|
if (backgroundColor && !this.props.gradient) {
|
|
498
|
-
|
|
499
|
-
isOpaque = rgba && rgba.a === 1;
|
|
441
|
+
isOpaque = _isColorOpaque(backgroundColor);
|
|
500
442
|
}
|
|
501
443
|
// Render outset shadows if present
|
|
502
444
|
if (outsetShadows.length > 0) {
|
|
@@ -734,6 +676,54 @@ function normalizeDescriptorChildren(children) {
|
|
|
734
676
|
const arr = (Array.isArray(children) ? children : [children]).filter(Boolean);
|
|
735
677
|
return arr.length > 0 ? arr : undefined;
|
|
736
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* Generic helper to set gap/margin/padding edge values on a Yoga node.
|
|
681
|
+
* Handles scalar (number | string), percent strings, and per-edge object notation.
|
|
682
|
+
*/
|
|
683
|
+
function _setEdgeValues(value, keys, setFn, percentFn, allowAuto = false) {
|
|
684
|
+
if (typeof value === 'number' || (allowAuto && value === 'auto')) {
|
|
685
|
+
setFn(keys.All, value);
|
|
686
|
+
}
|
|
687
|
+
else if (typeof value === 'string' && percentFn && value.endsWith('%')) {
|
|
688
|
+
percentFn(keys.All, parseFloat(value));
|
|
689
|
+
}
|
|
690
|
+
else if (typeof value === 'object') {
|
|
691
|
+
for (const [key, val] of Object.entries(value)) {
|
|
692
|
+
if (key in keys) {
|
|
693
|
+
const edgeKey = keys[key];
|
|
694
|
+
if (typeof val === 'string' && percentFn && val.endsWith('%')) {
|
|
695
|
+
percentFn(edgeKey, parseFloat(val));
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
setFn(edgeKey, val);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Checks if a CSS color string represents a fully opaque color (alpha = 1).
|
|
706
|
+
* Handles hex (#RGB, #RRGGBB, #RRGGBBAA), rgb()/rgba(), and transparent.
|
|
707
|
+
*/
|
|
708
|
+
function _isColorOpaque(color) {
|
|
709
|
+
if (color === 'transparent')
|
|
710
|
+
return false;
|
|
711
|
+
const hexAlpha = _HEX_ALPHA_RE.exec(color);
|
|
712
|
+
if (hexAlpha) {
|
|
713
|
+
return parseInt(hexAlpha[1].slice(6), 16) === 255;
|
|
714
|
+
}
|
|
715
|
+
// #RGB or #RRGGBB are always opaque
|
|
716
|
+
if (color.startsWith('#'))
|
|
717
|
+
return true;
|
|
718
|
+
// rgba(r, g, b, a)
|
|
719
|
+
const rgba = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/.exec(color);
|
|
720
|
+
if (rgba) {
|
|
721
|
+
const a = rgba[4] !== undefined ? parseFloat(rgba[4]) : 1;
|
|
722
|
+
return a === 1;
|
|
723
|
+
}
|
|
724
|
+
// Unknown format — assume opaque (covers named colors like 'black', 'white', etc.)
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
737
727
|
/**
|
|
738
728
|
* Creates a new BoxNode instance.
|
|
739
729
|
* @param {BoxProps} props Box properties and configuration.
|
|
@@ -795,4 +785,4 @@ const Row = ({ children, ...rest }) => ({
|
|
|
795
785
|
children: normalizeDescriptorChildren(children),
|
|
796
786
|
});
|
|
797
787
|
|
|
798
|
-
export { Box, BoxNode, Column, ColumnNode, Row, RowNode };
|
|
788
|
+
export { Box, BoxNode, Column, ColumnNode, Row, RowNode, normalizeDescriptorChildren };
|
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
import { Canvas, type CanvasRenderingContext2D } from 'skia-canvas';
|
|
2
|
-
import { ColumnNode, BoxNode } from '
|
|
3
|
-
import type { BaseProps, RootProps, CanvasElement, RootPropsWithWorker, RootPropsWithoutWorker } from '
|
|
4
|
-
export declare const _clearRegisteredFonts: () => void;
|
|
5
|
-
export interface CanvasEngineConfig {
|
|
6
|
-
/** Run rendering in worker threads to avoid blocking the event loop (default: true) */
|
|
7
|
-
workerMode?: boolean;
|
|
8
|
-
/** Number of worker threads in the pool (default: os.cpus().length - 1) */
|
|
9
|
-
workers?: number;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Configure the canvas rendering engine.
|
|
13
|
-
* Call this once at application startup before rendering.
|
|
14
|
-
* @deprecated Pass workerMode and workers directly to Root() props instead.
|
|
15
|
-
*/
|
|
16
|
-
export declare function configure(options: CanvasEngineConfig): void;
|
|
2
|
+
import { ColumnNode, BoxNode } from './layout.canvas.js';
|
|
3
|
+
import type { BaseProps, RootProps, CanvasElement, RootPropsWithWorker, RootPropsWithoutWorker } from './canvas.type.js';
|
|
17
4
|
/**
|
|
18
5
|
* Terminate all worker pools and free worker thread resources.
|
|
19
6
|
* Call this when shutting down a long-running server to clean up immediately.
|
|
20
|
-
* After calling, you must call configure() again before rendering.
|
|
21
7
|
*/
|
|
22
8
|
export declare function terminate(): void;
|
|
23
9
|
/**
|
|
@@ -42,6 +28,8 @@ export declare class RootNode extends ColumnNode {
|
|
|
42
28
|
private readonly targetHeight;
|
|
43
29
|
/** Scale factor for rendering (e.g. 2 for 2x resolution) */
|
|
44
30
|
private readonly scale;
|
|
31
|
+
/** Max concurrent image fetches during render (default: 5) */
|
|
32
|
+
private readonly imageConcurrency;
|
|
45
33
|
/**
|
|
46
34
|
* Creates a new root node for canvas rendering
|
|
47
35
|
* @param props Configuration properties for the root node
|
|
@@ -53,6 +41,11 @@ export declare class RootNode extends ColumnNode {
|
|
|
53
41
|
* @returns Array of all ImageNode instances found in the tree
|
|
54
42
|
*/
|
|
55
43
|
private findAllImageNodes;
|
|
44
|
+
/**
|
|
45
|
+
* Registers fonts with serialization to prevent duplicate FontLibrary.use() calls
|
|
46
|
+
* when multiple Root() instances are created concurrently.
|
|
47
|
+
*/
|
|
48
|
+
private _registerFonts;
|
|
56
49
|
/**
|
|
57
50
|
* Renders the entire node tree to a canvas, handling image loading, layout calculation,
|
|
58
51
|
* and final drawing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/root.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAe,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,MAAM,2BAA2B,CAAA;AACxE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"root.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/root.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAe,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,MAAM,2BAA2B,CAAA;AACxE,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EAKvB,MAAM,yBAAyB,CAAA;AAoChC;;;GAGG;AACH,wBAAgB,SAAS,SAKxB;AAqFD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAmB5D;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,UAAU;IAC9B,KAAK,EAAE,SAAS,GAAG,SAAS,CAAA;IACpC,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,CAAoB;IACjD,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IAEzC;;;;OAIG;gBACS,KAAK,EAAE,SAAS,GAAG,SAAS;IAkCxC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAczB;;;OAGG;YACW,cAAc;IAgC5B;;;;OAIG;IACY,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACjG,MAAM,CAAC,GAAG,CAAC,EAAE,wBAAwB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAmElG;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAAC,CAAA;AACvF,wBAAgB,IAAI,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA"}
|
|
@@ -12,6 +12,7 @@ import { cpus } from 'node:os';
|
|
|
12
12
|
|
|
13
13
|
/** Registry to track fonts that have already been loaded */
|
|
14
14
|
const registeredFonts = new Map();
|
|
15
|
+
let _fontRegistrationLock = null;
|
|
15
16
|
/**
|
|
16
17
|
* FinalizationRegistry to clean up WorkerCanvas instances that were not explicitly released.
|
|
17
18
|
* This is a safety net — users should still call .release() explicitly.
|
|
@@ -24,25 +25,10 @@ const canvasRegistry = new FinalizationRegistry(heldValue => {
|
|
|
24
25
|
// Worker already gone — nothing to clean up
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
|
-
/** Engine configuration — legacy support for configure() */
|
|
28
|
-
let _defaultWorkerMode = true;
|
|
29
|
-
let _defaultWorkerPoolSize = Math.max(1, cpus().length - 1);
|
|
30
28
|
let _workerPool = null;
|
|
31
|
-
/**
|
|
32
|
-
* Configure the canvas rendering engine.
|
|
33
|
-
* Call this once at application startup before rendering.
|
|
34
|
-
* @deprecated Pass workerMode and workers directly to Root() props instead.
|
|
35
|
-
*/
|
|
36
|
-
function configure(options) {
|
|
37
|
-
if (options.workerMode !== undefined)
|
|
38
|
-
_defaultWorkerMode = options.workerMode;
|
|
39
|
-
if (options.workers !== undefined)
|
|
40
|
-
_defaultWorkerPoolSize = options.workers;
|
|
41
|
-
}
|
|
42
29
|
/**
|
|
43
30
|
* Terminate all worker pools and free worker thread resources.
|
|
44
31
|
* Call this when shutting down a long-running server to clean up immediately.
|
|
45
|
-
* After calling, you must call configure() again before rendering.
|
|
46
32
|
*/
|
|
47
33
|
function terminate() {
|
|
48
34
|
if (_workerPool) {
|
|
@@ -159,6 +145,8 @@ class RootNode extends ColumnNode {
|
|
|
159
145
|
targetHeight;
|
|
160
146
|
/** Scale factor for rendering (e.g. 2 for 2x resolution) */
|
|
161
147
|
scale;
|
|
148
|
+
/** Max concurrent image fetches during render (default: 5) */
|
|
149
|
+
imageConcurrency;
|
|
162
150
|
/**
|
|
163
151
|
* Creates a new root node for canvas rendering
|
|
164
152
|
* @param props Configuration properties for the root node
|
|
@@ -172,36 +160,22 @@ class RootNode extends ColumnNode {
|
|
|
172
160
|
if (!props.width) {
|
|
173
161
|
throw new Error('Width and height are required for Root');
|
|
174
162
|
}
|
|
175
|
-
// Register provided fonts with caching
|
|
176
|
-
if (props.fonts?.length) {
|
|
177
|
-
for (const font of props.fonts) {
|
|
178
|
-
const family = font.family;
|
|
179
|
-
const paths = font.paths.map(p => path.resolve(p));
|
|
180
|
-
if (!registeredFonts.has(family)) {
|
|
181
|
-
registeredFonts.set(family, new Set());
|
|
182
|
-
}
|
|
183
|
-
const cachedPaths = registeredFonts.get(family);
|
|
184
|
-
const newPaths = paths.filter(p => !cachedPaths.has(p) && fs.existsSync(p));
|
|
185
|
-
if (newPaths.length > 0) {
|
|
186
|
-
FontLibrary.use({ [family]: newPaths });
|
|
187
|
-
newPaths.forEach(p => cachedPaths.add(p));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
163
|
// Set up scale and width
|
|
192
164
|
this.scale = props.scale || 1;
|
|
193
165
|
this.targetWidth = props.width;
|
|
194
166
|
this.targetHeight = props.height;
|
|
167
|
+
this.imageConcurrency = props.imageConcurrency ?? 5;
|
|
195
168
|
this.node.setWidth(this.targetWidth);
|
|
196
169
|
// Convert any CanvasElement children to actual BoxNode instances
|
|
197
170
|
if (this.props.children) {
|
|
198
171
|
const childArray = Array.isArray(this.props.children) ? this.props.children : [this.props.children];
|
|
199
|
-
|
|
172
|
+
const converted = childArray.map(child => {
|
|
200
173
|
if (child && typeof child === 'object' && '__type' in child) {
|
|
201
174
|
return buildTree(child);
|
|
202
175
|
}
|
|
203
176
|
return child;
|
|
204
177
|
});
|
|
178
|
+
this.props.children = converted;
|
|
205
179
|
}
|
|
206
180
|
// Initialize children nodes
|
|
207
181
|
this.processInitialChildren();
|
|
@@ -213,8 +187,9 @@ class RootNode extends ColumnNode {
|
|
|
213
187
|
findAllImageNodes() {
|
|
214
188
|
const imageNodes = [];
|
|
215
189
|
const queue = [this];
|
|
216
|
-
|
|
217
|
-
|
|
190
|
+
let head = 0;
|
|
191
|
+
while (head < queue.length) {
|
|
192
|
+
const node = queue[head++];
|
|
218
193
|
if (node instanceof ImageNode) {
|
|
219
194
|
imageNodes.push(node);
|
|
220
195
|
}
|
|
@@ -222,12 +197,46 @@ class RootNode extends ColumnNode {
|
|
|
222
197
|
}
|
|
223
198
|
return imageNodes;
|
|
224
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Registers fonts with serialization to prevent duplicate FontLibrary.use() calls
|
|
202
|
+
* when multiple Root() instances are created concurrently.
|
|
203
|
+
*/
|
|
204
|
+
async _registerFonts() {
|
|
205
|
+
if (!this.props.fonts?.length)
|
|
206
|
+
return;
|
|
207
|
+
// Wait for any in-flight registration to complete
|
|
208
|
+
if (_fontRegistrationLock)
|
|
209
|
+
await _fontRegistrationLock;
|
|
210
|
+
_fontRegistrationLock = (async () => {
|
|
211
|
+
try {
|
|
212
|
+
for (const font of this.props.fonts) {
|
|
213
|
+
const family = font.family;
|
|
214
|
+
const paths = font.paths.map(p => path.resolve(p));
|
|
215
|
+
if (!registeredFonts.has(family)) {
|
|
216
|
+
registeredFonts.set(family, new Set());
|
|
217
|
+
}
|
|
218
|
+
const cachedPaths = registeredFonts.get(family);
|
|
219
|
+
const newPaths = paths.filter(p => !cachedPaths.has(p) && fs.existsSync(p));
|
|
220
|
+
if (newPaths.length > 0) {
|
|
221
|
+
FontLibrary.use({ [family]: newPaths });
|
|
222
|
+
newPaths.forEach(p => cachedPaths.add(p));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
_fontRegistrationLock = null;
|
|
228
|
+
}
|
|
229
|
+
})();
|
|
230
|
+
await _fontRegistrationLock;
|
|
231
|
+
}
|
|
225
232
|
async render(ctx, offsetX = 0, offsetY = 0) {
|
|
226
233
|
// If ctx is provided, delegate to parent render (used when called as a child node)
|
|
227
234
|
if (ctx) {
|
|
228
235
|
await super.render(ctx, offsetX, offsetY);
|
|
229
236
|
return;
|
|
230
237
|
}
|
|
238
|
+
// Register fonts with serialization to prevent duplicate FontLibrary.use() across concurrent Root() calls
|
|
239
|
+
await this._registerFonts();
|
|
231
240
|
const diskCacheKeys = this.props.useDiskCache ? new Set() : undefined;
|
|
232
241
|
try {
|
|
233
242
|
// Step 1: Load all images with a concurrency limit to avoid overwhelming remote sources.
|
|
@@ -235,15 +244,20 @@ class RootNode extends ColumnNode {
|
|
|
235
244
|
const imageNodes = this.findAllImageNodes();
|
|
236
245
|
if (imageNodes.length > 0) {
|
|
237
246
|
const imageCache = new Map();
|
|
238
|
-
const CONCURRENCY = 5;
|
|
239
247
|
const queue = [...imageNodes];
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
let qIdx = 0;
|
|
249
|
+
const workers = Array.from({ length: Math.min(this.imageConcurrency, queue.length) }, async () => {
|
|
250
|
+
while (qIdx < queue.length) {
|
|
251
|
+
const node = queue[qIdx++];
|
|
243
252
|
await node.load(imageCache, diskCacheKeys);
|
|
244
253
|
}
|
|
245
254
|
});
|
|
246
|
-
await Promise.allSettled(workers)
|
|
255
|
+
await Promise.allSettled(workers).then(results => {
|
|
256
|
+
results.forEach(r => {
|
|
257
|
+
if (r.status === 'rejected')
|
|
258
|
+
console.warn('[RootNode] Image load worker failed:', r.reason);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
247
261
|
}
|
|
248
262
|
// Step 2: Calculate initial layout
|
|
249
263
|
this.node.calculateLayout(this.targetWidth, undefined, Style.Direction.LTR);
|
|
@@ -275,9 +289,9 @@ class RootNode extends ColumnNode {
|
|
|
275
289
|
}
|
|
276
290
|
}
|
|
277
291
|
async function Root(props) {
|
|
278
|
-
// Determine worker mode
|
|
279
|
-
const workerMode = props.workerMode ??
|
|
280
|
-
const workerPoolSize = props.workers ??
|
|
292
|
+
// Determine worker mode
|
|
293
|
+
const workerMode = props.workerMode ?? true;
|
|
294
|
+
const workerPoolSize = props.workers ?? Math.max(1, cpus().length - 1);
|
|
281
295
|
if (workerMode) {
|
|
282
296
|
// Lazy initialize worker pool — dynamic import to avoid loading Comlink in non-worker contexts
|
|
283
297
|
if (!_workerPool) {
|
|
@@ -291,4 +305,4 @@ async function Root(props) {
|
|
|
291
305
|
return new RootNode(props).render();
|
|
292
306
|
}
|
|
293
307
|
|
|
294
|
-
export { Root, RootNode, buildTree,
|
|
308
|
+
export { Root, RootNode, buildTree, terminate };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { TextProps, CanvasElement } from '
|
|
1
|
+
import type { TextProps, CanvasElement } from './canvas.type.js';
|
|
2
2
|
import { type CanvasRenderingContext2D } from 'skia-canvas';
|
|
3
|
-
import { BoxNode } from '
|
|
3
|
+
import { BoxNode } from './layout.canvas.js';
|
|
4
4
|
/**
|
|
5
5
|
* Node for rendering text content with rich text styling support
|
|
6
6
|
* Supports color and weight variations through HTML-like tags
|
|
@@ -140,7 +140,11 @@ export declare class TextNode extends BoxNode {
|
|
|
140
140
|
*/
|
|
141
141
|
private measureSpaceWidth;
|
|
142
142
|
/**
|
|
143
|
-
*
|
|
143
|
+
* Applies this.props.fontVariant to the context, or resets to 'normal'.
|
|
144
|
+
* Centralizes the type guard + warn pattern repeated across measure/render paths.
|
|
145
|
+
*/
|
|
146
|
+
private _applyFontVariant;
|
|
147
|
+
/**
|
|
144
148
|
*
|
|
145
149
|
* Core features:
|
|
146
150
|
* - Dynamic line heights with leading/spacing controls
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGnD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEjC,KAAK,EAAE,SAAS,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAElC,IAAI,GAAE,MAAM,GAAG,MAAW,EAAE,KAAK,GAAE,SAAc;IAuB7D;;;;;;;;OAQG;WACW,gBAAgB,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,GAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACpC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;QAClC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAA;QACjD,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;KACnD;cAwBW,aAAa,IAAI,IAAI;IAoDxC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"text.canvas.d.ts","sourceRoot":"","sources":["../../../src/canvas/text.canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACpF,OAAO,EAAU,KAAK,wBAAwB,EAA2B,MAAM,aAAa,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGnD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEjC,KAAK,EAAE,SAAS,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;gBAElC,IAAI,GAAE,MAAM,GAAG,MAAW,EAAE,KAAK,GAAE,SAAc;IAuB7D;;;;;;;;OAQG;WACW,gBAAgB,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,GAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;QACpC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;QAClC,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,wBAAwB,CAAC,WAAW,CAAC,CAAA;QACjD,YAAY,CAAC,EAAE,wBAAwB,CAAC,cAAc,CAAC,CAAA;KACnD;cAwBW,aAAa,IAAI,IAAI;IAoDxC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,sBAAsB;IA8B9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAiCrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAQjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,WAAW;IA4LnB;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IA6KpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAmErB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;;;;;;;;;;OAeG;cACsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAoW3H;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAS,KAAG,aAI9D,CAAA"}
|
|
@@ -352,18 +352,7 @@ class TextNode extends BoxNode {
|
|
|
352
352
|
// Pre-measure each text segment width with its specific styling
|
|
353
353
|
for (const segment of this.segments) {
|
|
354
354
|
ctx.font = this.getFontString(segment);
|
|
355
|
-
|
|
356
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
357
|
-
}
|
|
358
|
-
else if (this.props.fontVariant !== undefined) {
|
|
359
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (segment width):`, this.props.fontVariant);
|
|
360
|
-
if (ctx.fontVariant !== 'normal')
|
|
361
|
-
ctx.fontVariant = 'normal';
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
if (ctx.fontVariant !== 'normal')
|
|
365
|
-
ctx.fontVariant = 'normal';
|
|
366
|
-
}
|
|
355
|
+
this._applyFontVariant(ctx, 'measureText (segment width)');
|
|
367
356
|
segment.width = this.addLetterSpacingExtra(segment.text, ctx.measureText(segment.text).width, parsedLetterSpacingPx);
|
|
368
357
|
}
|
|
369
358
|
// Calculate available layout width
|
|
@@ -387,18 +376,7 @@ class TextNode extends BoxNode {
|
|
|
387
376
|
// Handle empty line metrics
|
|
388
377
|
if (line.length === 0) {
|
|
389
378
|
ctx.font = this.getFontString();
|
|
390
|
-
|
|
391
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
392
|
-
}
|
|
393
|
-
else if (this.props.fontVariant !== undefined) {
|
|
394
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (empty line):`, this.props.fontVariant);
|
|
395
|
-
if (ctx.fontVariant !== 'normal')
|
|
396
|
-
ctx.fontVariant = 'normal';
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
if (ctx.fontVariant !== 'normal')
|
|
400
|
-
ctx.fontVariant = 'normal';
|
|
401
|
-
}
|
|
379
|
+
this._applyFontVariant(ctx, 'measureText (empty line)');
|
|
402
380
|
const metrics = ctx.measureText(this.metricsString);
|
|
403
381
|
maxAscent = metrics.actualBoundingBoxAscent ?? baseFontSize * 0.8;
|
|
404
382
|
maxDescent = metrics.actualBoundingBoxDescent ?? baseFontSize * 0.2;
|
|
@@ -412,18 +390,7 @@ class TextNode extends BoxNode {
|
|
|
412
390
|
const segmentSize = segment.size || baseFontSize;
|
|
413
391
|
maxFontSizeOnLine = Math.max(maxFontSizeOnLine, segmentSize);
|
|
414
392
|
ctx.font = this.getFontString(segment);
|
|
415
|
-
|
|
416
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
417
|
-
}
|
|
418
|
-
else if (this.props.fontVariant !== undefined) {
|
|
419
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (segment height):`, this.props.fontVariant);
|
|
420
|
-
if (ctx.fontVariant !== 'normal')
|
|
421
|
-
ctx.fontVariant = 'normal';
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
if (ctx.fontVariant !== 'normal')
|
|
425
|
-
ctx.fontVariant = 'normal';
|
|
426
|
-
}
|
|
393
|
+
this._applyFontVariant(ctx, 'measureText (segment height)');
|
|
427
394
|
const metrics = ctx.measureText(this.metricsString);
|
|
428
395
|
const ascent = metrics.actualBoundingBoxAscent ?? segmentSize * 0.8;
|
|
429
396
|
const descent = metrics.actualBoundingBoxDescent ?? segmentSize * 0.2;
|
|
@@ -434,18 +401,7 @@ class TextNode extends BoxNode {
|
|
|
434
401
|
// Fallback metrics for lines with only whitespace
|
|
435
402
|
if (maxAscent === 0 && maxDescent === 0 && line.length > 0) {
|
|
436
403
|
ctx.font = this.getFontString();
|
|
437
|
-
|
|
438
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
439
|
-
}
|
|
440
|
-
else if (this.props.fontVariant !== undefined) {
|
|
441
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (fallback):`, this.props.fontVariant);
|
|
442
|
-
if (ctx.fontVariant !== 'normal')
|
|
443
|
-
ctx.fontVariant = 'normal';
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
if (ctx.fontVariant !== 'normal')
|
|
447
|
-
ctx.fontVariant = 'normal';
|
|
448
|
-
}
|
|
404
|
+
this._applyFontVariant(ctx, 'measureText (fallback)');
|
|
449
405
|
const metrics = ctx.measureText(this.metricsString);
|
|
450
406
|
maxAscent = metrics.actualBoundingBoxAscent ?? baseFontSize * 0.8;
|
|
451
407
|
maxDescent = metrics.actualBoundingBoxDescent ?? baseFontSize * 0.2;
|
|
@@ -478,18 +434,7 @@ class TextNode extends BoxNode {
|
|
|
478
434
|
if (/^\s+$/.test(word))
|
|
479
435
|
continue;
|
|
480
436
|
ctx.font = this.getFontString(segment);
|
|
481
|
-
|
|
482
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
483
|
-
}
|
|
484
|
-
else if (this.props.fontVariant !== undefined) {
|
|
485
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in measureText (single line width):`, this.props.fontVariant);
|
|
486
|
-
if (ctx.fontVariant !== 'normal')
|
|
487
|
-
ctx.fontVariant = 'normal';
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
if (ctx.fontVariant !== 'normal')
|
|
491
|
-
ctx.fontVariant = 'normal';
|
|
492
|
-
}
|
|
437
|
+
this._applyFontVariant(ctx, 'measureText (single line width)');
|
|
493
438
|
const wordWidth = this.addLetterSpacingExtra(word, ctx.measureText(word).width, parsedLetterSpacingPx);
|
|
494
439
|
if (!firstWordInSingleLine) {
|
|
495
440
|
singleLineWidth += spaceWidth + parsedWordSpacingPx;
|
|
@@ -800,7 +745,24 @@ class TextNode extends BoxNode {
|
|
|
800
745
|
return width > 0 ? width : (this.props.fontSize || 16) * 0.3;
|
|
801
746
|
}
|
|
802
747
|
/**
|
|
803
|
-
*
|
|
748
|
+
* Applies this.props.fontVariant to the context, or resets to 'normal'.
|
|
749
|
+
* Centralizes the type guard + warn pattern repeated across measure/render paths.
|
|
750
|
+
*/
|
|
751
|
+
_applyFontVariant(ctx, context) {
|
|
752
|
+
if (typeof this.props.fontVariant === 'string') {
|
|
753
|
+
ctx.fontVariant = this.props.fontVariant;
|
|
754
|
+
}
|
|
755
|
+
else if (this.props.fontVariant !== undefined) {
|
|
756
|
+
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in ${context}:`, this.props.fontVariant);
|
|
757
|
+
if (ctx.fontVariant !== 'normal')
|
|
758
|
+
ctx.fontVariant = 'normal';
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
if (ctx.fontVariant !== 'normal')
|
|
762
|
+
ctx.fontVariant = 'normal';
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
804
766
|
*
|
|
805
767
|
* Core features:
|
|
806
768
|
* - Dynamic line heights with leading/spacing controls
|
|
@@ -1040,18 +1002,7 @@ class TextNode extends BoxNode {
|
|
|
1040
1002
|
// Apply segment styles
|
|
1041
1003
|
ctx.font = this.getFontString(segment);
|
|
1042
1004
|
ctx.fillStyle = segment.color || this.props.color || 'black';
|
|
1043
|
-
|
|
1044
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
1045
|
-
}
|
|
1046
|
-
else if (this.props.fontVariant !== undefined) {
|
|
1047
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in _renderContent (segment render):`, this.props.fontVariant);
|
|
1048
|
-
if (ctx.fontVariant !== 'normal')
|
|
1049
|
-
ctx.fontVariant = 'normal';
|
|
1050
|
-
}
|
|
1051
|
-
else {
|
|
1052
|
-
if (ctx.fontVariant !== 'normal')
|
|
1053
|
-
ctx.fontVariant = 'normal';
|
|
1054
|
-
}
|
|
1005
|
+
this._applyFontVariant(ctx, '_renderContent (segment render)');
|
|
1055
1006
|
// Handle text truncation and ellipsis
|
|
1056
1007
|
let textToDraw = segment.text;
|
|
1057
1008
|
let currentSegmentRenderWidth = segmentWidth;
|
|
@@ -1119,18 +1070,7 @@ class TextNode extends BoxNode {
|
|
|
1119
1070
|
if (ellipsisRemainingWidth >= ellipsisWidth) {
|
|
1120
1071
|
ctx.save();
|
|
1121
1072
|
ctx.font = this.getFontString(ellipsisStyle);
|
|
1122
|
-
|
|
1123
|
-
ctx.fontVariant = this.props.fontVariant;
|
|
1124
|
-
}
|
|
1125
|
-
else if (this.props.fontVariant !== undefined) {
|
|
1126
|
-
console.warn(`[TextNode ${this.key || ''}] Invalid fontVariant prop type in _renderContent (ellipsis draw):`, this.props.fontVariant);
|
|
1127
|
-
if (ctx.fontVariant !== 'normal')
|
|
1128
|
-
ctx.fontVariant = 'normal';
|
|
1129
|
-
}
|
|
1130
|
-
else {
|
|
1131
|
-
if (ctx.fontVariant !== 'normal')
|
|
1132
|
-
ctx.fontVariant = 'normal';
|
|
1133
|
-
}
|
|
1073
|
+
this._applyFontVariant(ctx, '_renderContent (ellipsis draw)');
|
|
1134
1074
|
ctx.fillStyle = ellipsisStyle?.color || this.props.color || 'black';
|
|
1135
1075
|
ctx.fillText(ellipsisChar, currentX, lineY, Math.max(0, ellipsisRemainingWidth + 1));
|
|
1136
1076
|
ctx.restore();
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export * from './canvas/canvas.type.js';
|
|
|
3
3
|
export { Box, Column, Row, type BoxNode } from './canvas/layout.canvas.js';
|
|
4
4
|
export { Image } from './canvas/image.canvas.js';
|
|
5
5
|
export { Text } from './canvas/text.canvas.js';
|
|
6
|
-
export { Root,
|
|
6
|
+
export { Root, terminate } from './canvas/root.canvas.js';
|
|
7
7
|
export { GridItem } from './canvas/grid.canvas.js';
|
|
8
8
|
export { Grid } from './canvas/grid.canvas.js';
|
|
9
9
|
export { Chart } from './canvas/chart.canvas.js';
|
|
10
|
-
export { clearDiskCache } from './util/disk.cache.js';
|
|
10
|
+
export { clearDiskCache, setDiskCacheDir } from './util/disk.cache.js';
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/esm/index.js
CHANGED
|
@@ -2,8 +2,8 @@ export { Border, Style } from './constant/common.const.js';
|
|
|
2
2
|
export { Box, Column, Row } from './canvas/layout.canvas.js';
|
|
3
3
|
export { Image } from './canvas/image.canvas.js';
|
|
4
4
|
export { Text } from './canvas/text.canvas.js';
|
|
5
|
-
export { Root,
|
|
5
|
+
export { Root, terminate } from './canvas/root.canvas.js';
|
|
6
6
|
export { Grid, GridItem } from './canvas/grid.canvas.js';
|
|
7
7
|
export { Chart } from './canvas/chart.canvas.js';
|
|
8
|
-
export { clearDiskCache } from './util/disk.cache.js';
|
|
8
|
+
export { clearDiskCache, setDiskCacheDir } from './util/disk.cache.js';
|
|
9
9
|
export * from 'yoga-layout';
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Override the default disk cache directory.
|
|
3
|
+
* Must be called before any cache read/write operations.
|
|
4
|
+
*/
|
|
5
|
+
export declare function setDiskCacheDir(dir: string): void;
|
|
1
6
|
export declare function hashBuffer(buf: Buffer): string;
|
|
2
7
|
export declare function readDiskCache(key: string): Promise<Buffer | null>;
|
|
3
8
|
export declare function writeDiskCache(key: string, data: Buffer): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disk.cache.d.ts","sourceRoot":"","sources":["../../../src/util/disk.cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"disk.cache.d.ts","sourceRoot":"","sources":["../../../src/util/disk.cache.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGjD;AAQD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOvE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7E;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShE;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAUpD"}
|