modern-text 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,29 +21,32 @@
21
21
  ## Usage
22
22
 
23
23
  ```ts
24
+ import { fonts } from 'modern-font'
24
25
  import { renderText } from 'modern-text'
25
26
 
26
- const view = document.createElement('canvas')
27
- document.body.append(view)
27
+ fonts.loadFallbackFont('/fallback.woff').then(() => {
28
+ const view = document.createElement('canvas')
29
+ document.body.append(view)
28
30
 
29
- renderText({
30
- view,
31
- style: {
32
- width: 100,
33
- height: 200,
34
- fontSize: 22,
35
- textDecoration: 'underline',
36
- },
37
- content: [
38
- {
39
- letterSpacing: 3,
40
- fragments: [
41
- { content: 'He', color: 'red', fontSize: 12 },
42
- { content: 'llo', color: 'black' },
43
- ],
31
+ renderText({
32
+ view,
33
+ style: {
34
+ width: 100,
35
+ height: 200,
36
+ fontSize: 22,
37
+ textDecoration: 'underline',
44
38
  },
45
- { content: ', ', color: 'grey' },
46
- { content: 'World!', color: 'black' },
47
- ],
39
+ content: [
40
+ {
41
+ letterSpacing: 3,
42
+ fragments: [
43
+ { content: 'He', color: 'red', fontSize: 12 },
44
+ { content: 'llo', color: 'black' },
45
+ ],
46
+ },
47
+ { content: ', ', color: 'grey' },
48
+ { content: 'World!', color: 'black' },
49
+ ],
50
+ })
48
51
  })
49
52
  ```
package/dist/index.cjs CHANGED
@@ -377,6 +377,49 @@ class Character {
377
377
  }
378
378
  }
379
379
 
380
+ function createSVGLoader() {
381
+ const loaded = /* @__PURE__ */ new Map();
382
+ async function load(svg) {
383
+ if (!loaded.has(svg)) {
384
+ loaded.set(svg, svg);
385
+ try {
386
+ loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
387
+ } catch (err) {
388
+ console.warn(err);
389
+ loaded.delete(svg);
390
+ }
391
+ }
392
+ }
393
+ function needsLoad(source) {
394
+ return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("blob://");
395
+ }
396
+ return {
397
+ loaded,
398
+ needsLoad,
399
+ load
400
+ };
401
+ }
402
+
403
+ function createSVGParser(loader) {
404
+ const parsed = /* @__PURE__ */ new Map();
405
+ function parse(svg) {
406
+ let result = parsed.get(svg);
407
+ if (!result) {
408
+ const dom = modernPath2d.svgToDOM(
409
+ loader.needsLoad(svg) ? loader.loaded.get(svg) ?? svg : svg
410
+ );
411
+ const pathSet = modernPath2d.svgToPath2DSet(dom);
412
+ result = { dom, pathSet };
413
+ parsed.set(svg, result);
414
+ }
415
+ return result;
416
+ }
417
+ return {
418
+ parsed,
419
+ parse
420
+ };
421
+ }
422
+
380
423
  function parseValueNumber(value, ctx) {
381
424
  if (typeof value === "number") {
382
425
  return value;
@@ -453,9 +496,6 @@ function filterEmpty(val) {
453
496
  }
454
497
  return res;
455
498
  }
456
- function needsFetch(source) {
457
- return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("blob://");
458
- }
459
499
 
460
500
  class Fragment {
461
501
  constructor(content, style = {}, parent) {
@@ -876,9 +916,64 @@ class EventEmitter {
876
916
  }
877
917
 
878
918
  function background() {
919
+ const pathSet = new modernPath2d.Path2DSet();
920
+ const loader = createSVGLoader();
921
+ const parser = createSVGParser(loader);
879
922
  return {
880
- name: "background"
881
- // TODO
923
+ name: "background",
924
+ pathSet,
925
+ load: async (text) => {
926
+ const { backgroundImage } = text.style;
927
+ if (backgroundImage && loader.needsLoad(backgroundImage)) {
928
+ await loader.load(backgroundImage);
929
+ }
930
+ },
931
+ update: (text) => {
932
+ pathSet.paths.length = 0;
933
+ const { style, lineBox, isVertical } = text;
934
+ if (isNone(style.backgroundImage))
935
+ return;
936
+ const { pathSet: imagePathSet } = parser.parse(style.backgroundImage);
937
+ const imageBox = imagePathSet.getBoundingBox(true);
938
+ const transform = new modernPath2d.Matrix3();
939
+ transform.translate(-imageBox.x, -imageBox.y);
940
+ if (isVertical) {
941
+ transform.scale(lineBox.height / imageBox.width, lineBox.width / imageBox.height);
942
+ const tx = lineBox.height / 2;
943
+ const ty = lineBox.width / 2;
944
+ transform.translate(-tx, -ty);
945
+ transform.rotate(-Math.PI / 2);
946
+ transform.translate(ty, tx);
947
+ } else {
948
+ transform.scale(lineBox.width / imageBox.width, lineBox.height / imageBox.height);
949
+ }
950
+ transform.translate(lineBox.x, lineBox.y);
951
+ imagePathSet.paths.forEach((originalPath) => {
952
+ pathSet.paths.push(
953
+ originalPath.clone().applyTransform(transform)
954
+ );
955
+ });
956
+ },
957
+ render: (ctx, text) => {
958
+ const { boundingBox, computedStyle: style } = text;
959
+ if (!isNone(style.backgroundColor)) {
960
+ ctx.fillStyle = style.backgroundColor;
961
+ ctx.fillRect(...boundingBox.array);
962
+ }
963
+ pathSet.paths.forEach((path) => {
964
+ drawPath({
965
+ ctx,
966
+ path,
967
+ fontSize: style.fontSize
968
+ });
969
+ if (text.debug) {
970
+ const box = new modernPath2d.Path2DSet([path]).getBoundingBox();
971
+ if (box) {
972
+ ctx.strokeRect(box.x, box.y, box.width, box.height);
973
+ }
974
+ }
975
+ });
976
+ }
882
977
  };
883
978
  }
884
979
 
@@ -902,53 +997,30 @@ function getHighlightStyle(style) {
902
997
  };
903
998
  }
904
999
  function highlight() {
905
- const paths = [];
1000
+ const pathSet = new modernPath2d.Path2DSet();
906
1001
  const clipRects = [];
907
- const loaded = /* @__PURE__ */ new Map();
908
- const parsed = /* @__PURE__ */ new Map();
909
- async function loadSvg(svg) {
910
- if (!loaded.has(svg)) {
911
- loaded.set(svg, svg);
912
- try {
913
- loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
914
- } catch (err) {
915
- console.warn(err);
916
- loaded.delete(svg);
917
- }
918
- }
919
- }
920
- function getPaths(svg) {
921
- let result = parsed.get(svg);
922
- if (!result) {
923
- const dom = modernPath2d.svgToDOM(
924
- needsFetch(svg) ? loaded.get(svg) ?? svg : svg
925
- );
926
- const pathSet = modernPath2d.svgToPath2DSet(dom);
927
- result = { dom, pathSet };
928
- parsed.set(svg, result);
929
- }
930
- return result;
931
- }
1002
+ const loader = createSVGLoader();
1003
+ const parser = createSVGParser(loader);
932
1004
  return definePlugin({
933
1005
  name: "highlight",
934
- paths,
1006
+ pathSet,
935
1007
  load: async (text) => {
936
1008
  const set = /* @__PURE__ */ new Set();
937
1009
  text.forEachCharacter((character) => {
938
1010
  const { computedStyle: style } = character;
939
1011
  const { image, referImage } = getHighlightStyle(style);
940
- if (needsFetch(image)) {
1012
+ if (loader.needsLoad(image)) {
941
1013
  set.add(image);
942
1014
  }
943
- if (needsFetch(referImage)) {
1015
+ if (loader.needsLoad(referImage)) {
944
1016
  set.add(referImage);
945
1017
  }
946
1018
  });
947
- await Promise.all(Array.from(set).map((src) => loadSvg(src)));
1019
+ await Promise.all(Array.from(set).map((src) => loader.load(src)));
948
1020
  },
949
1021
  update: (text) => {
950
1022
  clipRects.length = 0;
951
- paths.length = 0;
1023
+ pathSet.paths.length = 0;
952
1024
  let groups = [];
953
1025
  let group;
954
1026
  let prevHighlight;
@@ -995,11 +1067,15 @@ function highlight() {
995
1067
  ...characters.filter((c) => c.glyphBox).map((c) => c.glyphBox)
996
1068
  );
997
1069
  const {
998
- computedStyle: style
1070
+ computedStyle: style,
1071
+ isVertical,
1072
+ inlineBox,
1073
+ glyphBox = inlineBox,
1074
+ strikeoutPosition,
1075
+ underlinePosition
999
1076
  } = char;
1000
1077
  const {
1001
- fontSize,
1002
- writingMode
1078
+ fontSize
1003
1079
  } = style;
1004
1080
  const {
1005
1081
  image,
@@ -1009,39 +1085,40 @@ function highlight() {
1009
1085
  size,
1010
1086
  thickness
1011
1087
  } = getHighlightStyle(style);
1012
- const isVertical = writingMode.includes("vertical");
1013
1088
  const _thickness = parseValueNumber(thickness, { fontSize, total: groupBox.width }) / groupBox.width;
1014
1089
  const _colormap = parseColormap(colormap);
1015
- const { pathSet: svgPathSet, dom: svgDom } = getPaths(image);
1016
- const aBox = svgPathSet.getBoundingBox(true);
1017
- const styleScale = fontSize / aBox.width * 2;
1018
- const cBox = new modernPath2d.BoundingBox().copy(groupBox);
1090
+ const { pathSet: imagePathSet, dom: imageDom } = parser.parse(image);
1091
+ const imageBox = imagePathSet.getBoundingBox(true);
1092
+ const styleScale = fontSize / imageBox.width * 2;
1093
+ const targetBox = new modernPath2d.BoundingBox().copy(groupBox);
1019
1094
  if (isVertical) {
1020
- cBox.width = groupBox.height;
1021
- cBox.height = groupBox.width;
1022
- cBox.left = groupBox.left + groupBox.width;
1095
+ targetBox.width = groupBox.height;
1096
+ targetBox.height = groupBox.width;
1097
+ targetBox.left = groupBox.left + groupBox.width;
1023
1098
  }
1024
- const rawWidth = Math.floor(cBox.width);
1099
+ const rawWidth = Math.floor(targetBox.width);
1025
1100
  let userWidth = rawWidth;
1026
1101
  if (size !== "cover") {
1027
1102
  userWidth = parseValueNumber(size, { fontSize, total: groupBox.width }) || rawWidth;
1028
- cBox.width = userWidth;
1103
+ targetBox.width = userWidth;
1029
1104
  }
1030
- if (!isNone(referImage) && isNone(line)) {
1031
- const bBox = getPaths(referImage).pathSet.getBoundingBox(true);
1032
- aBox.copy(bBox);
1105
+ const hasReferImage = !isNone(referImage) && isNone(line);
1106
+ if (hasReferImage) {
1107
+ imageBox.copy(
1108
+ parser.parse(referImage).pathSet.getBoundingBox(true)
1109
+ );
1033
1110
  } else {
1034
1111
  let _line;
1035
1112
  if (isNone(line)) {
1036
- if (aBox.width / aBox.height > 4) {
1113
+ if (imageBox.width / imageBox.height > 4) {
1037
1114
  _line = "underline";
1038
- const viewBox = svgDom.getAttribute("viewBox");
1115
+ const viewBox = imageDom.getAttribute("viewBox");
1039
1116
  if (viewBox) {
1040
1117
  const [_x, y, _w, h] = viewBox.split(" ").map((v) => Number(v));
1041
1118
  const viewCenter = y + h / 2;
1042
- if (aBox.y < viewCenter && aBox.y + aBox.height > viewCenter) {
1119
+ if (imageBox.y < viewCenter && imageBox.y + imageBox.height > viewCenter) {
1043
1120
  _line = "line-through";
1044
- } else if (aBox.y + aBox.height < viewCenter) {
1121
+ } else if (imageBox.y + imageBox.height < viewCenter) {
1045
1122
  _line = "overline";
1046
1123
  } else {
1047
1124
  _line = "underline";
@@ -1055,89 +1132,95 @@ function highlight() {
1055
1132
  }
1056
1133
  switch (_line) {
1057
1134
  case "outline": {
1058
- const paddingX = cBox.width * 0.2;
1059
- const paddingY = cBox.height * 0.2;
1060
- cBox.width += paddingX;
1061
- cBox.height += paddingY;
1135
+ const paddingX = targetBox.width * 0.2;
1136
+ const paddingY = targetBox.height * 0.2;
1062
1137
  if (isVertical) {
1063
- cBox.x -= paddingY / 2;
1064
- cBox.y -= paddingX / 2;
1065
- cBox.x += cBox.height;
1138
+ targetBox.x -= paddingY / 2;
1139
+ targetBox.y -= paddingX / 2;
1140
+ targetBox.x -= targetBox.height;
1066
1141
  } else {
1067
- cBox.x -= paddingX / 2;
1068
- cBox.y -= paddingY / 2;
1142
+ targetBox.x -= paddingX / 2;
1143
+ targetBox.y -= paddingY / 2;
1069
1144
  }
1145
+ targetBox.width += paddingX;
1146
+ targetBox.height += paddingY;
1070
1147
  break;
1071
1148
  }
1072
1149
  case "overline":
1073
- cBox.height = aBox.height * styleScale;
1150
+ targetBox.height = imageBox.height * styleScale;
1074
1151
  if (isVertical) {
1075
- cBox.x = char.inlineBox.left + char.inlineBox.width;
1152
+ targetBox.x = inlineBox.left + inlineBox.width;
1076
1153
  } else {
1077
- cBox.y = char.inlineBox.top;
1154
+ targetBox.y = inlineBox.top;
1078
1155
  }
1079
1156
  break;
1080
1157
  case "line-through":
1081
- cBox.height = aBox.height * styleScale;
1158
+ targetBox.height = imageBox.height * styleScale;
1082
1159
  if (isVertical) {
1083
- cBox.x = char.inlineBox.left + char.inlineBox.width - char.strikeoutPosition + cBox.height / 2;
1160
+ targetBox.x = inlineBox.left + inlineBox.width - strikeoutPosition + targetBox.height / 2;
1084
1161
  } else {
1085
- cBox.y = char.inlineBox.top + char.strikeoutPosition - cBox.height / 2;
1162
+ targetBox.y = inlineBox.top + strikeoutPosition - targetBox.height / 2;
1086
1163
  }
1087
1164
  break;
1088
1165
  case "underline":
1089
- cBox.height = aBox.height * styleScale;
1166
+ targetBox.height = imageBox.height * styleScale;
1090
1167
  if (isVertical) {
1091
- cBox.x = char.inlineBox.left + char.inlineBox.width - char.underlinePosition;
1168
+ targetBox.x = glyphBox.left + glyphBox.width - underlinePosition;
1092
1169
  } else {
1093
- cBox.y = char.inlineBox.top + char.underlinePosition;
1170
+ targetBox.y = inlineBox.top + underlinePosition;
1094
1171
  }
1095
1172
  break;
1096
1173
  }
1097
1174
  }
1098
- const transform = new modernPath2d.Matrix3().translate(-aBox.x, -aBox.y).scale(cBox.width / aBox.width, cBox.height / aBox.height);
1175
+ const transform = new modernPath2d.Matrix3();
1176
+ transform.translate(-imageBox.x, -imageBox.y);
1177
+ transform.scale(targetBox.width / imageBox.width, targetBox.height / imageBox.height);
1099
1178
  if (isVertical) {
1179
+ const tx = targetBox.width / 2;
1180
+ const ty = targetBox.height / 2;
1181
+ if (!hasReferImage) {
1182
+ transform.translate(-tx, -ty);
1183
+ }
1100
1184
  transform.rotate(-Math.PI / 2);
1185
+ if (!hasReferImage) {
1186
+ transform.translate(ty, tx);
1187
+ }
1101
1188
  }
1102
- transform.translate(cBox.x, cBox.y);
1189
+ transform.translate(targetBox.x, targetBox.y);
1103
1190
  for (let i2 = 0; i2 < Math.ceil(rawWidth / userWidth); i2++) {
1104
1191
  const _transform = transform.clone();
1105
1192
  if (isVertical) {
1106
- _transform.translate(0, i2 * cBox.width);
1193
+ _transform.translate(0, i2 * targetBox.width);
1107
1194
  } else {
1108
- _transform.translate(i2 * cBox.width, 0);
1195
+ _transform.translate(i2 * targetBox.width, 0);
1109
1196
  }
1110
- svgPathSet.paths.forEach((originalPath) => {
1197
+ imagePathSet.paths.forEach((originalPath) => {
1111
1198
  const path = originalPath.clone().applyTransform(_transform);
1112
- if (path.style.strokeWidth) {
1199
+ if (path.style.strokeWidth)
1113
1200
  path.style.strokeWidth *= styleScale * _thickness;
1114
- }
1115
- if (path.style.strokeMiterlimit) {
1201
+ if (path.style.strokeMiterlimit)
1116
1202
  path.style.strokeMiterlimit *= styleScale;
1117
- }
1118
- if (path.style.strokeDashoffset) {
1203
+ if (path.style.strokeDashoffset)
1119
1204
  path.style.strokeDashoffset *= styleScale;
1120
- }
1121
- if (path.style.strokeDasharray) {
1205
+ if (path.style.strokeDasharray)
1122
1206
  path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
1123
- }
1124
1207
  if (path.style.fill && path.style.fill in _colormap) {
1125
1208
  path.style.fill = _colormap[path.style.fill];
1126
1209
  }
1127
1210
  if (path.style.stroke && path.style.stroke in _colormap) {
1128
1211
  path.style.stroke = _colormap[path.style.stroke];
1129
1212
  }
1130
- paths.push(path);
1213
+ pathSet.paths.push(path);
1131
1214
  if (rawWidth !== userWidth) {
1132
1215
  if (isVertical) {
1133
- clipRects[paths.length - 1] = new modernPath2d.BoundingBox(
1216
+ clipRects[pathSet.paths.length - 1] = new modernPath2d.BoundingBox(
1134
1217
  groupBox.left - groupBox.width * 2,
1135
1218
  groupBox.top,
1136
1219
  groupBox.width * 4,
1137
1220
  groupBox.height
1138
1221
  );
1139
1222
  } else {
1140
- clipRects[paths.length - 1] = new modernPath2d.BoundingBox(
1223
+ clipRects[pathSet.paths.length - 1] = new modernPath2d.BoundingBox(
1141
1224
  groupBox.left,
1142
1225
  groupBox.top - groupBox.height * 2,
1143
1226
  groupBox.width,
@@ -1150,8 +1233,24 @@ function highlight() {
1150
1233
  }
1151
1234
  },
1152
1235
  renderOrder: -1,
1236
+ getBoundingBox: () => {
1237
+ const boundingBoxs = [];
1238
+ pathSet.paths.forEach((path, index) => {
1239
+ const clipRect = clipRects[index];
1240
+ let box = path.getBoundingBox();
1241
+ if (clipRect) {
1242
+ const x = Math.max(box.x, clipRect.x);
1243
+ const y = Math.max(box.y, clipRect.y);
1244
+ const right = Math.min(box.right, clipRect.right);
1245
+ const bottom = Math.min(box.bottom, clipRect.bottom);
1246
+ box = new modernPath2d.BoundingBox(x, y, right - x, bottom - y);
1247
+ }
1248
+ boundingBoxs.push(box);
1249
+ });
1250
+ return modernPath2d.BoundingBox.from(...boundingBoxs);
1251
+ },
1153
1252
  render: (ctx, text) => {
1154
- paths.forEach((path, index) => {
1253
+ pathSet.paths.forEach((path, index) => {
1155
1254
  drawPath({
1156
1255
  ctx,
1157
1256
  path,
@@ -1175,12 +1274,12 @@ function genDisc(r, color) {
1175
1274
  </svg>`;
1176
1275
  }
1177
1276
  function listStyle() {
1178
- const paths = [];
1277
+ const pathSet = new modernPath2d.Path2DSet();
1179
1278
  return definePlugin({
1180
1279
  name: "listStyle",
1181
- paths,
1280
+ pathSet,
1182
1281
  update: (text) => {
1183
- paths.length = 0;
1282
+ pathSet.paths.length = 0;
1184
1283
  const { paragraphs, isVertical, fontSize } = text;
1185
1284
  const padding = fontSize * 0.45;
1186
1285
  paragraphs.forEach((paragraph) => {
@@ -1234,7 +1333,7 @@ function listStyle() {
1234
1333
  inlineBox.top + (inlineBox.height - imageBox.height * _scale) / 2
1235
1334
  );
1236
1335
  }
1237
- paths.push(...imagePathSet.paths.map((p) => {
1336
+ pathSet.paths.push(...imagePathSet.paths.map((p) => {
1238
1337
  const path = p.clone();
1239
1338
  path.applyTransform(m);
1240
1339
  if (path.style.fill && path.style.fill in colormap) {
@@ -1297,39 +1396,21 @@ function render() {
1297
1396
  return boxes.length ? modernPath2d.BoundingBox.from(...boxes) : void 0;
1298
1397
  },
1299
1398
  render: (ctx, text) => {
1300
- const { paragraphs, glyphBox, effects, style } = text;
1301
- function fillBackground(color, box) {
1302
- ctx.fillStyle = color;
1303
- ctx.fillRect(box.left, box.top, box.width, box.height);
1304
- }
1305
- if (style?.backgroundColor) {
1306
- fillBackground(style.backgroundColor, new modernPath2d.BoundingBox(0, 0, ctx.canvas.width, ctx.canvas.height));
1307
- }
1308
- paragraphs.forEach((paragraph) => {
1309
- if (paragraph.style?.backgroundColor) {
1310
- fillBackground(paragraph.style.backgroundColor, paragraph.lineBox);
1311
- }
1312
- });
1399
+ const { paragraphs, glyphBox, effects } = text;
1313
1400
  if (effects) {
1314
- effects.forEach((style2) => {
1315
- uploadColor(style2, glyphBox, ctx);
1401
+ effects.forEach((style) => {
1402
+ uploadColor(style, glyphBox, ctx);
1316
1403
  ctx.save();
1317
- const [a, c, e, b, d, f] = getTransform2D(text, style2).transpose().elements;
1404
+ const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
1318
1405
  ctx.transform(a, b, c, d, e, f);
1319
1406
  text.forEachCharacter((character) => {
1320
- if (character.parent.style?.backgroundColor) {
1321
- fillBackground(character.parent.style.backgroundColor, character.inlineBox);
1322
- }
1323
- character.drawTo(ctx, style2);
1407
+ character.drawTo(ctx, style);
1324
1408
  });
1325
1409
  ctx.restore();
1326
1410
  });
1327
1411
  } else {
1328
1412
  paragraphs.forEach((paragraph) => {
1329
1413
  paragraph.fragments.forEach((fragment) => {
1330
- if (fragment.style?.backgroundColor) {
1331
- fillBackground(fragment.computedStyle.backgroundColor, fragment.inlineBox);
1332
- }
1333
1414
  fragment.characters.forEach((character) => {
1334
1415
  character.drawTo(ctx);
1335
1416
  });
@@ -1367,12 +1448,12 @@ function getTransform2D(text, style) {
1367
1448
  }
1368
1449
 
1369
1450
  function textDecoration() {
1370
- const paths = [];
1451
+ const pathSet = new modernPath2d.Path2DSet();
1371
1452
  return definePlugin({
1372
1453
  name: "textDecoration",
1373
- paths,
1454
+ pathSet,
1374
1455
  update: (text) => {
1375
- paths.length = 0;
1456
+ pathSet.paths.length = 0;
1376
1457
  const groups = [];
1377
1458
  let group;
1378
1459
  let prevStyle;
@@ -1432,7 +1513,8 @@ function textDecoration() {
1432
1513
  color,
1433
1514
  textDecoration: textDecoration2
1434
1515
  } = style;
1435
- const { left, top, width, height } = modernPath2d.BoundingBox.from(...group2.map((c) => c.inlineBox));
1516
+ const inlineBox = modernPath2d.BoundingBox.from(...group2.map((c) => c.inlineBox));
1517
+ const { left, top, width, height } = inlineBox;
1436
1518
  let position = isVertical ? left + width : top;
1437
1519
  const direction = isVertical ? -1 : 1;
1438
1520
  let thickness = 0;
@@ -1472,7 +1554,7 @@ function textDecoration() {
1472
1554
  fill: color
1473
1555
  });
1474
1556
  }
1475
- paths.push(path);
1557
+ pathSet.paths.push(path);
1476
1558
  });
1477
1559
  },
1478
1560
  render: (ctx, text) => {
@@ -1482,7 +1564,7 @@ function textDecoration() {
1482
1564
  ctx.save();
1483
1565
  const [a, c, e, b, d, f] = getTransform2D(text, effectStyle).transpose().elements;
1484
1566
  ctx.transform(a, b, c, d, e, f);
1485
- paths.forEach((path) => {
1567
+ pathSet.paths.forEach((path) => {
1486
1568
  drawPath({
1487
1569
  ctx,
1488
1570
  path,
@@ -1493,7 +1575,7 @@ function textDecoration() {
1493
1575
  ctx.restore();
1494
1576
  });
1495
1577
  } else {
1496
- paths.forEach((path) => {
1578
+ pathSet.paths.forEach((path) => {
1497
1579
  drawPath({
1498
1580
  ctx,
1499
1581
  path,
@@ -1663,22 +1745,16 @@ class Text extends EventEmitter {
1663
1745
  this.pathBox = modernPath2d.BoundingBox.from(
1664
1746
  this.glyphBox,
1665
1747
  ...Array.from(this.plugins.values()).map((plugin) => {
1666
- return plugin.getBoundingBox ? plugin.getBoundingBox(this) : new modernPath2d.Path2DSet(plugin.paths ?? []).getBoundingBox();
1748
+ return plugin.getBoundingBox ? plugin.getBoundingBox(this) : plugin.pathSet?.getBoundingBox();
1667
1749
  }).filter(Boolean)
1668
1750
  );
1669
1751
  return this;
1670
1752
  }
1671
1753
  updateBoundingBox() {
1672
- const { lineBox, rawGlyphBox, pathBox } = this;
1673
- const left = Math.min(pathBox.left, pathBox.left + lineBox.left - rawGlyphBox.left);
1674
- const top = Math.min(pathBox.top, pathBox.top + lineBox.top - rawGlyphBox.top);
1675
- const right = Math.max(pathBox.right, pathBox.right + lineBox.right - rawGlyphBox.right);
1676
- const bottom = Math.max(pathBox.bottom, pathBox.bottom + lineBox.bottom - rawGlyphBox.bottom);
1677
- this.boundingBox = new modernPath2d.BoundingBox(
1678
- left,
1679
- top,
1680
- right - left,
1681
- bottom - top
1754
+ this.boundingBox = modernPath2d.BoundingBox.from(
1755
+ this.rawGlyphBox,
1756
+ this.lineBox,
1757
+ this.pathBox
1682
1758
  );
1683
1759
  return this;
1684
1760
  }
@@ -1709,9 +1785,9 @@ class Text extends EventEmitter {
1709
1785
  Array.from(this.plugins.values()).sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
1710
1786
  if (plugin.render) {
1711
1787
  plugin.render?.(ctx, this);
1712
- } else if (plugin.paths) {
1788
+ } else if (plugin.pathSet) {
1713
1789
  const style = this.computedStyle;
1714
- plugin.paths.forEach((path) => {
1790
+ plugin.pathSet.paths.forEach((path) => {
1715
1791
  drawPath({
1716
1792
  ctx,
1717
1793
  path,
@@ -1750,6 +1826,8 @@ exports.Measurer = Measurer;
1750
1826
  exports.Paragraph = Paragraph;
1751
1827
  exports.Text = Text;
1752
1828
  exports.background = background;
1829
+ exports.createSVGLoader = createSVGLoader;
1830
+ exports.createSVGParser = createSVGParser;
1753
1831
  exports.definePlugin = definePlugin;
1754
1832
  exports.drawPath = drawPath;
1755
1833
  exports.filterEmpty = filterEmpty;
@@ -1762,7 +1840,6 @@ exports.isEqualValue = isEqualValue;
1762
1840
  exports.isNone = isNone;
1763
1841
  exports.listStyle = listStyle;
1764
1842
  exports.measureText = measureText;
1765
- exports.needsFetch = needsFetch;
1766
1843
  exports.outline = outline;
1767
1844
  exports.parseColor = parseColor;
1768
1845
  exports.parseColormap = parseColormap;