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