modern-text 1.9.1 → 1.10.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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { T as Text } from './shared/modern-text.B2pcGP3X.mjs';
2
- export { C as Character, F as Fragment, M as Measurer, P as Paragraph, c as backgroundPlugin, i as createSVGLoader, j as createSVGParser, b as definePlugin, d as drawPath, w as filterEmpty, g as getHighlightStyle, e as getTransform2D, v as hexToRgb, h as highlightPlugin, n as isEqualObject, q as isEqualValue, l as listStylePlugin, o as outlinePlugin, p as parseColor, m as parseColormap, k as parseValueNumber, r as renderPlugin, s as setupView, t as textDecorationPlugin, f as textDefaultStyle, u as uploadColor, a as uploadColors } from './shared/modern-text.B2pcGP3X.mjs';
1
+ import { T as Text } from './shared/modern-text.BD1kUWDw.mjs';
2
+ export { C as Canvas2DRenderer, a as Character, F as Fragment, M as Measurer, P as Paragraph, b as backgroundPlugin, f as createSvgLoader, i as createSvgParser, d as definePlugin, q as filterEmpty, g as getHighlightStyle, c as getTransform2D, n as hexToRgb, h as highlightPlugin, k as isEqualObject, m as isEqualValue, l as listStylePlugin, o as outlinePlugin, j as parseColormap, p as parseValueNumber, r as renderPlugin, t as textDecorationPlugin, e as textDefaultStyle } from './shared/modern-text.BD1kUWDw.mjs';
3
3
  import 'modern-idoc';
4
4
  import 'modern-path2d';
5
5
  import 'modern-font';
@@ -1,107 +1,176 @@
1
- import { isNone, getDefaultStyle, Reactivable, normalizeText, property } from 'modern-idoc';
2
- import { Path2D, BoundingBox, setCanvasContext, svgToDom, svgToPath2DSet, Path2DSet, Vector2, Matrix3 } from 'modern-path2d';
1
+ import { isGradient, parseGradient, isNone, getDefaultStyle, Reactivable, normalizeText, property } from 'modern-idoc';
2
+ import { setCanvasContext, Path2D, BoundingBox, svgToDom, svgToPath2DSet, Path2DSet, Vector2, Matrix3 } from 'modern-path2d';
3
3
  import { fonts } from 'modern-font';
4
4
 
5
- function parseColor(ctx, source, box) {
6
- if (typeof source === "string" && source.startsWith("linear-gradient")) {
7
- const { x0, y0, x1, y1, stops } = parseCssLinearGradient(source, box.left, box.top, box.width, box.height);
8
- const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
9
- stops.forEach((stop) => gradient.addColorStop(stop.offset, stop.color));
10
- return gradient;
5
+ class Canvas2DRenderer {
6
+ constructor(text, context) {
7
+ this.text = text;
8
+ this.context = context;
11
9
  }
12
- return source;
13
- }
14
- function uploadColor(style, box, ctx) {
15
- if (style?.color) {
16
- style.color = parseColor(ctx, style.color, box);
17
- }
18
- if (style?.backgroundColor) {
19
- style.backgroundColor = parseColor(ctx, style.backgroundColor, box);
20
- }
21
- if (style?.textStrokeColor) {
22
- style.textStrokeColor = parseColor(ctx, style.textStrokeColor, box);
23
- }
24
- }
25
- function parseCssLinearGradient(css, x, y, width, height) {
26
- const str = css.match(/linear-gradient\((.+)\)$/)?.[1] ?? "";
27
- const first = str.split(",")[0];
28
- const cssDeg = first.includes("deg") ? first : "0deg";
29
- const matched = str.replace(cssDeg, "").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+%)/gi);
30
- const deg = Number(cssDeg.replace("deg", "")) || 0;
31
- const rad = deg * Math.PI / 180;
32
- const offsetX = width * Math.sin(rad);
33
- const offsetY = height * Math.cos(rad);
34
- return {
35
- x0: x + width / 2 - offsetX,
36
- y0: y + height / 2 + offsetY,
37
- x1: x + width / 2 + offsetX,
38
- y1: y + height / 2 - offsetY,
39
- stops: Array.from(matched).map((res) => {
40
- let color = res[2];
41
- if (color.startsWith("(")) {
42
- color = color.split(",").length > 3 ? `rgba${color}` : `rgb${color}`;
43
- } else {
44
- color = `#${color}`;
10
+ pixelRatio = window?.devicePixelRatio || 1;
11
+ _setupView = () => {
12
+ const pixelRatio = this.pixelRatio;
13
+ const ctx = this.context;
14
+ const { left, top, width, height } = this.text.boundingBox;
15
+ const view = ctx.canvas;
16
+ view.dataset.viewBox = String(`${left} ${top} ${width} ${height}`);
17
+ view.dataset.pixelRatio = String(pixelRatio);
18
+ const canvasWidth = width;
19
+ const canvasHeight = height;
20
+ view.width = Math.max(1, Math.ceil(canvasWidth * pixelRatio));
21
+ view.height = Math.max(1, Math.ceil(canvasHeight * pixelRatio));
22
+ view.style.width = `${canvasWidth}px`;
23
+ view.style.height = `${canvasHeight}px`;
24
+ ctx.clearRect(0, 0, view.width, view.height);
25
+ ctx.scale(pixelRatio, pixelRatio);
26
+ ctx.translate(-left, -top);
27
+ };
28
+ _setupColors = () => {
29
+ const { paragraphs, computedStyle, glyphBox } = this.text;
30
+ this.uploadColor(computedStyle, glyphBox);
31
+ paragraphs.forEach((paragraph) => {
32
+ this.uploadColor(paragraph.computedStyle, paragraph.lineBox);
33
+ paragraph.fragments.forEach((fragment) => {
34
+ this.uploadColor(fragment.computedStyle, fragment.inlineBox);
35
+ });
36
+ });
37
+ };
38
+ setup = () => {
39
+ this._setupView();
40
+ this._setupColors();
41
+ return this;
42
+ };
43
+ _parseColor = (source, box) => {
44
+ if (typeof source === "string" && isGradient(source)) {
45
+ const gradient = parseGradient(source)[0];
46
+ if (gradient) {
47
+ switch (gradient.type) {
48
+ case "linear-gradient": {
49
+ let deg = 0;
50
+ if (gradient.orientation) {
51
+ switch (gradient.orientation.type) {
52
+ case "angular":
53
+ deg = Number(gradient.orientation.value);
54
+ break;
55
+ }
56
+ }
57
+ const { left, top, width, height } = box;
58
+ const rad = deg * Math.PI / 180;
59
+ const offsetX = width * Math.sin(rad);
60
+ const offsetY = height * Math.cos(rad);
61
+ const canvasGradient = this.context.createLinearGradient(
62
+ left + width / 2 - offsetX,
63
+ top + height / 2 + offsetY,
64
+ left + width / 2 + offsetX,
65
+ top + height / 2 - offsetY
66
+ );
67
+ gradient.colorStops.forEach((colorStop) => {
68
+ let offset = 0;
69
+ if (colorStop.length) {
70
+ switch (colorStop.length.type) {
71
+ case "%":
72
+ offset = Number(colorStop.length.value) / 100;
73
+ break;
74
+ }
75
+ }
76
+ switch (colorStop.type) {
77
+ case "rgb":
78
+ canvasGradient.addColorStop(offset, `rgb(${colorStop.value.join(", ")})`);
79
+ break;
80
+ case "rgba":
81
+ canvasGradient.addColorStop(offset, `rgba(${colorStop.value.join(", ")})`);
82
+ break;
83
+ }
84
+ });
85
+ return canvasGradient;
86
+ }
87
+ }
45
88
  }
46
- return {
47
- offset: Number(res[3].replace("%", "")) / 100,
48
- color
49
- };
50
- })
89
+ }
90
+ return source;
51
91
  };
52
- }
53
-
54
- function drawPath(options) {
55
- const { ctx, path, fontSize, clipRect } = options;
56
- ctx.save();
57
- ctx.beginPath();
58
- const pathStyle = path.style;
59
- const style = {
60
- ...pathStyle,
61
- fill: options.color ?? pathStyle.fill,
62
- stroke: options.textStrokeColor ?? pathStyle.stroke,
63
- strokeWidth: options.textStrokeWidth ? options.textStrokeWidth * fontSize : pathStyle.strokeWidth,
64
- strokeLinecap: "round",
65
- strokeLinejoin: "round",
66
- shadowOffsetX: (options.shadowOffsetX ?? 0) * fontSize,
67
- shadowOffsetY: (options.shadowOffsetY ?? 0) * fontSize,
68
- shadowBlur: (options.shadowBlur ?? 0) * fontSize,
69
- shadowColor: options.shadowColor
92
+ _uploadedStyles = [
93
+ "color",
94
+ "backgroundColor",
95
+ "textStrokeColor"
96
+ ];
97
+ uploadColor = (style, box) => {
98
+ this._uploadedStyles.forEach((key) => {
99
+ style[key] = this._parseColor(style[key], box);
100
+ });
70
101
  };
71
- if (clipRect) {
72
- ctx.rect(clipRect.left, clipRect.top, clipRect.width, clipRect.height);
73
- ctx.clip();
102
+ drawPath = (path, options = {}) => {
103
+ const ctx = this.context;
104
+ const {
105
+ fontSize = this.text.computedStyle.fontSize,
106
+ clipRect
107
+ } = options;
108
+ ctx.save();
74
109
  ctx.beginPath();
75
- }
76
- path.drawTo(ctx, style);
77
- ctx.restore();
78
- }
79
-
80
- function setupView(ctx, pixelRatio, boundingBox) {
81
- const { left, top, width, height } = boundingBox;
82
- const view = ctx.canvas;
83
- view.dataset.viewBox = String(`${left} ${top} ${width} ${height}`);
84
- view.dataset.pixelRatio = String(pixelRatio);
85
- const canvasWidth = width;
86
- const canvasHeight = height;
87
- view.width = Math.max(1, Math.ceil(canvasWidth * pixelRatio));
88
- view.height = Math.max(1, Math.ceil(canvasHeight * pixelRatio));
89
- view.style.width = `${canvasWidth}px`;
90
- view.style.height = `${canvasHeight}px`;
91
- ctx.clearRect(0, 0, view.width, view.height);
92
- ctx.scale(pixelRatio, pixelRatio);
93
- ctx.translate(-left, -top);
94
- }
95
-
96
- function uploadColors(ctx, text) {
97
- const { paragraphs, computedStyle: style, glyphBox } = text;
98
- uploadColor(style, glyphBox, ctx);
99
- paragraphs.forEach((paragraph) => {
100
- uploadColor(paragraph.computedStyle, paragraph.lineBox, ctx);
101
- paragraph.fragments.forEach((fragment) => {
102
- uploadColor(fragment.computedStyle, fragment.inlineBox, ctx);
110
+ if (clipRect) {
111
+ ctx.rect(clipRect.left, clipRect.top, clipRect.width, clipRect.height);
112
+ ctx.clip();
113
+ ctx.beginPath();
114
+ }
115
+ const pathStyle = path.style;
116
+ const stroke = options.textStrokeColor ?? pathStyle.stroke;
117
+ const strokeWidth = options.textStrokeWidth ? options.textStrokeWidth * fontSize : pathStyle.strokeWidth;
118
+ path.drawTo(ctx, {
119
+ ...pathStyle,
120
+ fill: options.color ?? pathStyle.fill,
121
+ stroke: strokeWidth === void 0 || strokeWidth > 0 ? stroke : void 0,
122
+ strokeWidth,
123
+ strokeLinecap: "round",
124
+ strokeLinejoin: "round",
125
+ shadowOffsetX: (options.shadowOffsetX ?? 0) * fontSize,
126
+ shadowOffsetY: (options.shadowOffsetY ?? 0) * fontSize,
127
+ shadowBlur: (options.shadowBlur ?? 0) * fontSize,
128
+ shadowColor: options.shadowColor
103
129
  });
104
- });
130
+ ctx.restore();
131
+ };
132
+ drawCharacter = (character, userStyle = {}) => {
133
+ const ctx = this.context;
134
+ const {
135
+ computedStyle,
136
+ path,
137
+ glyphBox,
138
+ isVertical,
139
+ content,
140
+ inlineBox,
141
+ baseline
142
+ } = character;
143
+ const style = {
144
+ ...computedStyle,
145
+ ...userStyle
146
+ };
147
+ if (glyphBox) {
148
+ this.drawPath(path, style);
149
+ } else {
150
+ ctx.save();
151
+ ctx.beginPath();
152
+ const pathStyle = path.style;
153
+ setCanvasContext(ctx, {
154
+ ...pathStyle,
155
+ fill: style.color ?? pathStyle.fill,
156
+ stroke: style.textStrokeColor ?? pathStyle.stroke,
157
+ strokeWidth: style.textStrokeWidth ? style.textStrokeWidth * style.fontSize : pathStyle.strokeWidth,
158
+ shadowOffsetX: (style.shadowOffsetX ?? 0) * style.fontSize,
159
+ shadowOffsetY: (style.shadowOffsetY ?? 0) * style.fontSize,
160
+ shadowBlur: (style.shadowBlur ?? 0) * style.fontSize,
161
+ shadowColor: style.shadowColor
162
+ });
163
+ ctx.font = `${style.fontSize}px ${style.fontFamily}`;
164
+ if (isVertical) {
165
+ ctx.textBaseline = "middle";
166
+ ctx.fillText(content, inlineBox.left, inlineBox.top + inlineBox.height / 2);
167
+ } else {
168
+ ctx.textBaseline = "alphabetic";
169
+ ctx.fillText(content, inlineBox.left, inlineBox.top + baseline);
170
+ }
171
+ ctx.restore();
172
+ }
173
+ };
105
174
  }
106
175
 
107
176
  const set1 = /* @__PURE__ */ new Set(["\xA9", "\xAE", "\xF7"]);
@@ -361,46 +430,9 @@ class Character {
361
430
  const { min, max } = minMax;
362
431
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
363
432
  }
364
- drawTo(ctx, config = {}) {
365
- const style = this.computedStyle;
366
- const options = {
367
- ctx,
368
- path: this.path,
369
- fontSize: style.fontSize,
370
- color: style.color,
371
- ...config
372
- };
373
- if (this.glyphBox) {
374
- drawPath(options);
375
- } else {
376
- ctx.save();
377
- ctx.beginPath();
378
- const pathStyle = this.path.style;
379
- const _style = {
380
- ...pathStyle,
381
- fill: options.color ?? pathStyle.fill,
382
- stroke: options.textStrokeColor ?? pathStyle.stroke,
383
- strokeWidth: options.textStrokeWidth ? options.textStrokeWidth * options.fontSize : pathStyle.strokeWidth,
384
- shadowOffsetX: (options.shadowOffsetX ?? 0) * options.fontSize,
385
- shadowOffsetY: (options.shadowOffsetY ?? 0) * options.fontSize,
386
- shadowBlur: (options.shadowBlur ?? 0) * options.fontSize,
387
- shadowColor: options.shadowColor
388
- };
389
- setCanvasContext(ctx, _style);
390
- ctx.font = `${options.fontSize}px ${options.fontFamily}`;
391
- if (this.isVertical) {
392
- ctx.textBaseline = "middle";
393
- ctx.fillText(this.content, this.inlineBox.left, this.inlineBox.top + this.inlineBox.height / 2);
394
- } else {
395
- ctx.textBaseline = "alphabetic";
396
- ctx.fillText(this.content, this.inlineBox.left, this.inlineBox.top + this.baseline);
397
- }
398
- ctx.restore();
399
- }
400
- }
401
433
  }
402
434
 
403
- function createSVGLoader() {
435
+ function createSvgLoader() {
404
436
  const loaded = /* @__PURE__ */ new Map();
405
437
  async function load(svg) {
406
438
  if (!loaded.has(svg)) {
@@ -423,7 +455,7 @@ function createSVGLoader() {
423
455
  };
424
456
  }
425
457
 
426
- function createSVGParser(loader) {
458
+ function createSvgParser(loader) {
427
459
  const parsed = /* @__PURE__ */ new Map();
428
460
  function parse(svg) {
429
461
  let result = parsed.get(svg);
@@ -851,8 +883,8 @@ class Measurer {
851
883
 
852
884
  function backgroundPlugin() {
853
885
  const pathSet = new Path2DSet();
854
- const loader = createSVGLoader();
855
- const parser = createSVGParser(loader);
886
+ const loader = createSvgLoader();
887
+ const parser = createSvgParser(loader);
856
888
  return {
857
889
  name: "background",
858
890
  pathSet,
@@ -932,32 +964,33 @@ function backgroundPlugin() {
932
964
  pathSet.paths.push(...paths);
933
965
  },
934
966
  renderOrder: -2,
935
- render: (ctx, text) => {
967
+ render: (renderer) => {
968
+ const { text, context } = renderer;
936
969
  const { boundingBox, computedStyle: style } = text;
937
970
  if (!isNone(style.backgroundColor)) {
938
- ctx.fillStyle = style.backgroundColor;
939
- ctx.fillRect(...boundingBox.array);
971
+ context.fillStyle = style.backgroundColor;
972
+ context.fillRect(...boundingBox.array);
940
973
  }
941
974
  pathSet.paths.forEach((path) => {
942
- drawPath({ ctx, path, fontSize: style.fontSize });
975
+ renderer.drawPath(path);
943
976
  if (text.debug) {
944
977
  const box = new Path2DSet([path]).getBoundingBox();
945
978
  if (box) {
946
- ctx.strokeRect(box.x, box.y, box.width, box.height);
979
+ context.strokeRect(box.x, box.y, box.width, box.height);
947
980
  }
948
981
  }
949
982
  });
950
983
  text.paragraphs.forEach((p) => {
951
984
  const { lineBox, style: style2 } = p;
952
985
  if (!isNone(style2.backgroundColor)) {
953
- ctx.fillStyle = style2.backgroundColor;
954
- ctx.fillRect(...lineBox.array);
986
+ context.fillStyle = style2.backgroundColor;
987
+ context.fillRect(...lineBox.array);
955
988
  }
956
989
  p.fragments.forEach((f) => {
957
990
  const { inlineBox, style: style3 } = f;
958
991
  if (!isNone(style3.backgroundColor)) {
959
- ctx.fillStyle = style3.backgroundColor;
960
- ctx.fillRect(...inlineBox.array);
992
+ context.fillStyle = style3.backgroundColor;
993
+ context.fillRect(...inlineBox.array);
961
994
  }
962
995
  });
963
996
  });
@@ -987,8 +1020,8 @@ function getHighlightStyle(style) {
987
1020
  function highlightPlugin() {
988
1021
  const pathSet = new Path2DSet();
989
1022
  const clipRects = [];
990
- const loader = createSVGLoader();
991
- const parser = createSVGParser(loader);
1023
+ const loader = createSvgLoader();
1024
+ const parser = createSvgParser(loader);
992
1025
  return definePlugin({
993
1026
  name: "highlight",
994
1027
  pathSet,
@@ -1231,18 +1264,14 @@ function highlightPlugin() {
1231
1264
  });
1232
1265
  return BoundingBox.from(...boundingBoxs);
1233
1266
  },
1234
- render: (ctx, text) => {
1267
+ render: (renderer) => {
1268
+ const { text, context } = renderer;
1235
1269
  pathSet.paths.forEach((path, index) => {
1236
- drawPath({
1237
- ctx,
1238
- path,
1239
- fontSize: text.computedStyle.fontSize,
1240
- clipRect: clipRects[index]
1241
- });
1270
+ renderer.drawPath(path, { clipRect: clipRects[index] });
1242
1271
  if (text.debug) {
1243
1272
  const box = new Path2DSet([path]).getBoundingBox();
1244
1273
  if (box) {
1245
- ctx.strokeRect(box.x, box.y, box.width, box.height);
1274
+ context.strokeRect(box.x, box.y, box.width, box.height);
1246
1275
  }
1247
1276
  }
1248
1277
  });
@@ -1357,10 +1386,10 @@ function renderPlugin() {
1357
1386
  });
1358
1387
  },
1359
1388
  getBoundingBox: (text) => {
1360
- const { characters, fontSize, effects } = text;
1389
+ const { characters, fontSize, computedEffects } = text;
1361
1390
  const boxes = [];
1362
1391
  characters.forEach((character) => {
1363
- effects?.forEach((style) => {
1392
+ computedEffects.forEach((style) => {
1364
1393
  if (!character.glyphBox) {
1365
1394
  return;
1366
1395
  }
@@ -1386,31 +1415,37 @@ function renderPlugin() {
1386
1415
  });
1387
1416
  return boxes.length ? BoundingBox.from(...boxes) : void 0;
1388
1417
  },
1389
- render: (ctx, text) => {
1390
- const { paragraphs, glyphBox, effects } = text;
1391
- if (effects) {
1392
- effects.forEach((style) => {
1393
- uploadColor(style, glyphBox, ctx);
1394
- ctx.save();
1418
+ render: (renderer) => {
1419
+ const { text, context } = renderer;
1420
+ const { paragraphs, glyphBox, computedEffects } = text;
1421
+ if (computedEffects.length) {
1422
+ computedEffects.forEach((style) => {
1423
+ renderer.uploadColor(style, glyphBox);
1424
+ context.save();
1395
1425
  const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
1396
- ctx.transform(a, b, c, d, e, f);
1426
+ context.transform(a, b, c, d, e, f);
1397
1427
  text.forEachCharacter((character) => {
1398
- character.drawTo(ctx, style);
1428
+ renderer.drawCharacter(character, style);
1399
1429
  });
1400
- ctx.restore();
1430
+ context.restore();
1401
1431
  });
1402
1432
  } else {
1403
1433
  paragraphs.forEach((paragraph) => {
1404
1434
  paragraph.fragments.forEach((fragment) => {
1405
1435
  fragment.characters.forEach((character) => {
1406
- character.drawTo(ctx);
1436
+ renderer.drawCharacter(character);
1407
1437
  });
1408
1438
  });
1409
1439
  });
1410
1440
  }
1411
1441
  if (text.debug) {
1412
1442
  paragraphs.forEach((paragraph) => {
1413
- ctx.strokeRect(paragraph.lineBox.x, paragraph.lineBox.y, paragraph.lineBox.width, paragraph.lineBox.height);
1443
+ context.strokeRect(
1444
+ paragraph.lineBox.x,
1445
+ paragraph.lineBox.y,
1446
+ paragraph.lineBox.width,
1447
+ paragraph.lineBox.height
1448
+ );
1414
1449
  });
1415
1450
  }
1416
1451
  }
@@ -1544,30 +1579,22 @@ function textDecorationPlugin() {
1544
1579
  pathSet.paths.push(path);
1545
1580
  });
1546
1581
  },
1547
- render: (ctx, text) => {
1548
- const { effects, computedStyle: style } = text;
1549
- if (effects) {
1550
- effects.forEach((effectStyle) => {
1551
- ctx.save();
1582
+ render: (renderer) => {
1583
+ const { text, context } = renderer;
1584
+ const { computedEffects } = text;
1585
+ if (computedEffects.length) {
1586
+ computedEffects.forEach((effectStyle) => {
1587
+ context.save();
1552
1588
  const [a, c, e, b, d, f] = getTransform2D(text, effectStyle).transpose().elements;
1553
- ctx.transform(a, b, c, d, e, f);
1589
+ context.transform(a, b, c, d, e, f);
1554
1590
  pathSet.paths.forEach((path) => {
1555
- drawPath({
1556
- ctx,
1557
- path,
1558
- fontSize: style.fontSize,
1559
- ...effectStyle
1560
- });
1591
+ renderer.drawPath(path, effectStyle);
1561
1592
  });
1562
- ctx.restore();
1593
+ context.restore();
1563
1594
  });
1564
1595
  } else {
1565
1596
  pathSet.paths.forEach((path) => {
1566
- drawPath({
1567
- ctx,
1568
- path,
1569
- fontSize: style.fontSize
1570
- });
1597
+ renderer.drawPath(path);
1571
1598
  });
1572
1599
  }
1573
1600
  }
@@ -1587,6 +1614,7 @@ const textDefaultStyle = getDefaultStyle();
1587
1614
  class Text extends Reactivable {
1588
1615
  needsUpdate = true;
1589
1616
  computedStyle = { ...textDefaultStyle };
1617
+ computedEffects = [];
1590
1618
  paragraphs = [];
1591
1619
  lineBox = new BoundingBox();
1592
1620
  rawGlyphBox = new BoundingBox();
@@ -1631,7 +1659,7 @@ class Text extends Reactivable {
1631
1659
  (options.plugins ?? []).forEach((plugin) => {
1632
1660
  this.use(plugin);
1633
1661
  });
1634
- this.updateParagraphs();
1662
+ this._update();
1635
1663
  }
1636
1664
  use(plugin) {
1637
1665
  this.plugins.set(plugin.name, plugin);
@@ -1650,8 +1678,9 @@ class Text extends Reactivable {
1650
1678
  async load() {
1651
1679
  await Promise.all(Array.from(this.plugins.values()).map((p) => p.load?.(this)));
1652
1680
  }
1653
- updateParagraphs() {
1681
+ _update() {
1654
1682
  this.computedStyle = { ...textDefaultStyle, ...this.style };
1683
+ this.computedEffects = this.effects?.map((v) => ({ ...v })) ?? [];
1655
1684
  const { content } = this;
1656
1685
  const paragraphs = [];
1657
1686
  content.forEach((p, pIndex) => {
@@ -1674,7 +1703,7 @@ class Text extends Reactivable {
1674
1703
  return this;
1675
1704
  }
1676
1705
  createDom() {
1677
- this.updateParagraphs();
1706
+ this._update();
1678
1707
  return this.measurer.createDom(this.paragraphs, this.computedStyle);
1679
1708
  }
1680
1709
  measure(dom = this.measureDom) {
@@ -1686,7 +1715,7 @@ class Text extends Reactivable {
1686
1715
  pathBox: this.pathBox,
1687
1716
  boundingBox: this.boundingBox
1688
1717
  };
1689
- this.updateParagraphs();
1718
+ this._update();
1690
1719
  const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom);
1691
1720
  this.paragraphs = result.paragraphs;
1692
1721
  this.lineBox = result.boundingBox;
@@ -1704,7 +1733,7 @@ class Text extends Reactivable {
1704
1733
  }
1705
1734
  });
1706
1735
  this.glyphBox = this.getGlyphBox();
1707
- this.updatePathBox().updateBoundingBox();
1736
+ this._updatePathBox()._updateBoundingBox();
1708
1737
  for (const key in old) {
1709
1738
  result[key] = this[key];
1710
1739
  this[key] = old[key];
@@ -1732,7 +1761,7 @@ class Text extends Reactivable {
1732
1761
  max.y - min.y
1733
1762
  );
1734
1763
  }
1735
- updatePathBox() {
1764
+ _updatePathBox() {
1736
1765
  this.pathBox = BoundingBox.from(
1737
1766
  this.glyphBox,
1738
1767
  ...Array.from(this.plugins.values()).map((plugin) => {
@@ -1741,7 +1770,7 @@ class Text extends Reactivable {
1741
1770
  );
1742
1771
  return this;
1743
1772
  }
1744
- updateBoundingBox() {
1773
+ _updateBoundingBox() {
1745
1774
  this.boundingBox = BoundingBox.from(
1746
1775
  this.rawGlyphBox,
1747
1776
  this.lineBox,
@@ -1771,19 +1800,15 @@ class Text extends Reactivable {
1771
1800
  if (this.needsUpdate) {
1772
1801
  this.update();
1773
1802
  }
1774
- setupView(ctx, pixelRatio, this.boundingBox);
1775
- uploadColors(ctx, this);
1803
+ const renderer = new Canvas2DRenderer(this, ctx);
1804
+ renderer.pixelRatio = pixelRatio;
1805
+ renderer.setup();
1776
1806
  Array.from(this.plugins.values()).sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
1777
1807
  if (plugin.render) {
1778
- plugin.render?.(ctx, this);
1808
+ plugin.render?.(renderer);
1779
1809
  } else if (plugin.pathSet) {
1780
- const style = this.computedStyle;
1781
1810
  plugin.pathSet.paths.forEach((path) => {
1782
- drawPath({
1783
- ctx,
1784
- path,
1785
- fontSize: style.fontSize
1786
- });
1811
+ renderer.drawPath(path);
1787
1812
  });
1788
1813
  }
1789
1814
  });
@@ -1819,4 +1844,4 @@ __decorateClass([
1819
1844
  property({ internal: true })
1820
1845
  ], Text.prototype, "fonts");
1821
1846
 
1822
- export { Character as C, Fragment as F, Measurer as M, Paragraph as P, Text as T, uploadColors as a, definePlugin as b, backgroundPlugin as c, drawPath as d, getTransform2D as e, textDefaultStyle as f, getHighlightStyle as g, highlightPlugin as h, createSVGLoader as i, createSVGParser as j, parseValueNumber as k, listStylePlugin as l, parseColormap as m, isEqualObject as n, outlinePlugin as o, parseColor as p, isEqualValue as q, renderPlugin as r, setupView as s, textDecorationPlugin as t, uploadColor as u, hexToRgb as v, filterEmpty as w };
1847
+ export { Canvas2DRenderer as C, Fragment as F, Measurer as M, Paragraph as P, Text as T, Character as a, backgroundPlugin as b, getTransform2D as c, definePlugin as d, textDefaultStyle as e, createSvgLoader as f, getHighlightStyle as g, highlightPlugin as h, createSvgParser as i, parseColormap as j, isEqualObject as k, listStylePlugin as l, isEqualValue as m, hexToRgb as n, outlinePlugin as o, parseValueNumber as p, filterEmpty as q, renderPlugin as r, textDecorationPlugin as t };