modern-text 1.3.0 → 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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Path2D, BoundingBox, setCanvasContext, Path2DSet, Matrix3, svgToDOM, svgToPath2DSet, Vector2 } from 'modern-path2d';
1
+ import { Path2D, BoundingBox, setCanvasContext, svgToDOM, svgToPath2DSet, Path2DSet, Matrix3, Vector2 } from 'modern-path2d';
2
2
  import { getDefaultStyle } from 'modern-idoc';
3
3
  import { fonts } from 'modern-font';
4
4
 
@@ -375,6 +375,49 @@ class Character {
375
375
  }
376
376
  }
377
377
 
378
+ function createSVGLoader() {
379
+ const loaded = /* @__PURE__ */ new Map();
380
+ async function load(svg) {
381
+ if (!loaded.has(svg)) {
382
+ loaded.set(svg, svg);
383
+ try {
384
+ loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
385
+ } catch (err) {
386
+ console.warn(err);
387
+ loaded.delete(svg);
388
+ }
389
+ }
390
+ }
391
+ function needsLoad(source) {
392
+ return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("blob://");
393
+ }
394
+ return {
395
+ loaded,
396
+ needsLoad,
397
+ load
398
+ };
399
+ }
400
+
401
+ function createSVGParser(loader) {
402
+ const parsed = /* @__PURE__ */ new Map();
403
+ function parse(svg) {
404
+ let result = parsed.get(svg);
405
+ if (!result) {
406
+ const dom = svgToDOM(
407
+ loader.needsLoad(svg) ? loader.loaded.get(svg) ?? svg : svg
408
+ );
409
+ const pathSet = svgToPath2DSet(dom);
410
+ result = { dom, pathSet };
411
+ parsed.set(svg, result);
412
+ }
413
+ return result;
414
+ }
415
+ return {
416
+ parsed,
417
+ parse
418
+ };
419
+ }
420
+
378
421
  function parseValueNumber(value, ctx) {
379
422
  if (typeof value === "number") {
380
423
  return value;
@@ -451,9 +494,6 @@ function filterEmpty(val) {
451
494
  }
452
495
  return res;
453
496
  }
454
- function needsFetch(source) {
455
- return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("blob://");
456
- }
457
497
 
458
498
  class Fragment {
459
499
  constructor(content, style = {}, parent) {
@@ -565,36 +605,38 @@ class Measurer {
565
605
  switch (rootStyle.textAlign) {
566
606
  case "start":
567
607
  case "left":
568
- style.justifyContent = "start";
608
+ style.justifyContent = "flex-start";
569
609
  break;
570
610
  case "center":
571
611
  style.justifyContent = "center";
572
612
  break;
573
613
  case "end":
574
614
  case "right":
575
- style.justifyContent = "end";
615
+ style.justifyContent = "flex-end";
576
616
  break;
577
617
  }
578
618
  switch (rootStyle.verticalAlign) {
579
619
  case "top":
580
- style.alignItems = "top";
620
+ style.alignItems = "flex-start";
581
621
  break;
582
622
  case "middle":
583
623
  style.alignItems = "center";
584
624
  break;
585
625
  case "bottom":
586
- style.alignItems = "end";
626
+ style.alignItems = "flex-end";
587
627
  break;
588
628
  }
589
629
  const isFlex = Boolean(style.justifyContent || style.alignItems);
590
630
  Object.assign(dom.style, {
591
- boxSizing: "border-box",
592
- display: isFlex ? "inline-flex" : void 0,
593
- width: "max-content",
594
- height: "max-content",
631
+ ...this._styleToDomStyle({
632
+ ...style,
633
+ boxSizing: style.boxSizing ?? "border-box",
634
+ display: style.display ?? (isFlex ? "inline-flex" : void 0),
635
+ width: style.width ?? "max-content",
636
+ height: style.height ?? "max-content"
637
+ }),
595
638
  whiteSpace: "pre-wrap",
596
639
  wordBreak: "break-all",
597
- ...this._styleToDomStyle(style),
598
640
  position: "fixed",
599
641
  visibility: "hidden"
600
642
  });
@@ -872,9 +914,64 @@ class EventEmitter {
872
914
  }
873
915
 
874
916
  function background() {
917
+ const pathSet = new Path2DSet();
918
+ const loader = createSVGLoader();
919
+ const parser = createSVGParser(loader);
875
920
  return {
876
- name: "background"
877
- // TODO
921
+ name: "background",
922
+ pathSet,
923
+ load: async (text) => {
924
+ const { backgroundImage } = text.style;
925
+ if (backgroundImage && loader.needsLoad(backgroundImage)) {
926
+ await loader.load(backgroundImage);
927
+ }
928
+ },
929
+ update: (text) => {
930
+ pathSet.paths.length = 0;
931
+ const { style, lineBox, isVertical } = text;
932
+ if (isNone(style.backgroundImage))
933
+ return;
934
+ const { pathSet: imagePathSet } = parser.parse(style.backgroundImage);
935
+ const imageBox = imagePathSet.getBoundingBox(true);
936
+ const transform = new Matrix3();
937
+ transform.translate(-imageBox.x, -imageBox.y);
938
+ if (isVertical) {
939
+ transform.scale(lineBox.height / imageBox.width, lineBox.width / imageBox.height);
940
+ const tx = lineBox.height / 2;
941
+ const ty = lineBox.width / 2;
942
+ transform.translate(-tx, -ty);
943
+ transform.rotate(-Math.PI / 2);
944
+ transform.translate(ty, tx);
945
+ } else {
946
+ transform.scale(lineBox.width / imageBox.width, lineBox.height / imageBox.height);
947
+ }
948
+ transform.translate(lineBox.x, lineBox.y);
949
+ imagePathSet.paths.forEach((originalPath) => {
950
+ pathSet.paths.push(
951
+ originalPath.clone().applyTransform(transform)
952
+ );
953
+ });
954
+ },
955
+ render: (ctx, text) => {
956
+ const { boundingBox, computedStyle: style } = text;
957
+ if (!isNone(style.backgroundColor)) {
958
+ ctx.fillStyle = style.backgroundColor;
959
+ ctx.fillRect(...boundingBox.array);
960
+ }
961
+ pathSet.paths.forEach((path) => {
962
+ drawPath({
963
+ ctx,
964
+ path,
965
+ fontSize: style.fontSize
966
+ });
967
+ if (text.debug) {
968
+ const box = new Path2DSet([path]).getBoundingBox();
969
+ if (box) {
970
+ ctx.strokeRect(box.x, box.y, box.width, box.height);
971
+ }
972
+ }
973
+ });
974
+ }
878
975
  };
879
976
  }
880
977
 
@@ -898,53 +995,30 @@ function getHighlightStyle(style) {
898
995
  };
899
996
  }
900
997
  function highlight() {
901
- const paths = [];
998
+ const pathSet = new Path2DSet();
902
999
  const clipRects = [];
903
- const loaded = /* @__PURE__ */ new Map();
904
- const parsed = /* @__PURE__ */ new Map();
905
- async function loadSvg(svg) {
906
- if (!loaded.has(svg)) {
907
- loaded.set(svg, svg);
908
- try {
909
- loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
910
- } catch (err) {
911
- console.warn(err);
912
- loaded.delete(svg);
913
- }
914
- }
915
- }
916
- function getPaths(svg) {
917
- let result = parsed.get(svg);
918
- if (!result) {
919
- const dom = svgToDOM(
920
- needsFetch(svg) ? loaded.get(svg) ?? svg : svg
921
- );
922
- const pathSet = svgToPath2DSet(dom);
923
- result = { dom, pathSet };
924
- parsed.set(svg, result);
925
- }
926
- return result;
927
- }
1000
+ const loader = createSVGLoader();
1001
+ const parser = createSVGParser(loader);
928
1002
  return definePlugin({
929
1003
  name: "highlight",
930
- paths,
1004
+ pathSet,
931
1005
  load: async (text) => {
932
1006
  const set = /* @__PURE__ */ new Set();
933
1007
  text.forEachCharacter((character) => {
934
1008
  const { computedStyle: style } = character;
935
1009
  const { image, referImage } = getHighlightStyle(style);
936
- if (needsFetch(image)) {
1010
+ if (loader.needsLoad(image)) {
937
1011
  set.add(image);
938
1012
  }
939
- if (needsFetch(referImage)) {
1013
+ if (loader.needsLoad(referImage)) {
940
1014
  set.add(referImage);
941
1015
  }
942
1016
  });
943
- await Promise.all(Array.from(set).map((src) => loadSvg(src)));
1017
+ await Promise.all(Array.from(set).map((src) => loader.load(src)));
944
1018
  },
945
1019
  update: (text) => {
946
1020
  clipRects.length = 0;
947
- paths.length = 0;
1021
+ pathSet.paths.length = 0;
948
1022
  let groups = [];
949
1023
  let group;
950
1024
  let prevHighlight;
@@ -991,11 +1065,15 @@ function highlight() {
991
1065
  ...characters.filter((c) => c.glyphBox).map((c) => c.glyphBox)
992
1066
  );
993
1067
  const {
994
- computedStyle: style
1068
+ computedStyle: style,
1069
+ isVertical,
1070
+ inlineBox,
1071
+ glyphBox = inlineBox,
1072
+ strikeoutPosition,
1073
+ underlinePosition
995
1074
  } = char;
996
1075
  const {
997
- fontSize,
998
- writingMode
1076
+ fontSize
999
1077
  } = style;
1000
1078
  const {
1001
1079
  image,
@@ -1005,39 +1083,40 @@ function highlight() {
1005
1083
  size,
1006
1084
  thickness
1007
1085
  } = getHighlightStyle(style);
1008
- const isVertical = writingMode.includes("vertical");
1009
1086
  const _thickness = parseValueNumber(thickness, { fontSize, total: groupBox.width }) / groupBox.width;
1010
1087
  const _colormap = parseColormap(colormap);
1011
- const { pathSet: svgPathSet, dom: svgDom } = getPaths(image);
1012
- const aBox = svgPathSet.getBoundingBox(true);
1013
- const styleScale = fontSize / aBox.width * 2;
1014
- const cBox = new BoundingBox().copy(groupBox);
1088
+ const { pathSet: imagePathSet, dom: imageDom } = parser.parse(image);
1089
+ const imageBox = imagePathSet.getBoundingBox(true);
1090
+ const styleScale = fontSize / imageBox.width * 2;
1091
+ const targetBox = new BoundingBox().copy(groupBox);
1015
1092
  if (isVertical) {
1016
- cBox.width = groupBox.height;
1017
- cBox.height = groupBox.width;
1018
- cBox.left = groupBox.left + groupBox.width;
1093
+ targetBox.width = groupBox.height;
1094
+ targetBox.height = groupBox.width;
1095
+ targetBox.left = groupBox.left + groupBox.width;
1019
1096
  }
1020
- const rawWidth = Math.floor(cBox.width);
1097
+ const rawWidth = Math.floor(targetBox.width);
1021
1098
  let userWidth = rawWidth;
1022
1099
  if (size !== "cover") {
1023
1100
  userWidth = parseValueNumber(size, { fontSize, total: groupBox.width }) || rawWidth;
1024
- cBox.width = userWidth;
1101
+ targetBox.width = userWidth;
1025
1102
  }
1026
- if (!isNone(referImage) && isNone(line)) {
1027
- const bBox = getPaths(referImage).pathSet.getBoundingBox(true);
1028
- aBox.copy(bBox);
1103
+ const hasReferImage = !isNone(referImage) && isNone(line);
1104
+ if (hasReferImage) {
1105
+ imageBox.copy(
1106
+ parser.parse(referImage).pathSet.getBoundingBox(true)
1107
+ );
1029
1108
  } else {
1030
1109
  let _line;
1031
1110
  if (isNone(line)) {
1032
- if (aBox.width / aBox.height > 4) {
1111
+ if (imageBox.width / imageBox.height > 4) {
1033
1112
  _line = "underline";
1034
- const viewBox = svgDom.getAttribute("viewBox");
1113
+ const viewBox = imageDom.getAttribute("viewBox");
1035
1114
  if (viewBox) {
1036
1115
  const [_x, y, _w, h] = viewBox.split(" ").map((v) => Number(v));
1037
1116
  const viewCenter = y + h / 2;
1038
- if (aBox.y < viewCenter && aBox.y + aBox.height > viewCenter) {
1117
+ if (imageBox.y < viewCenter && imageBox.y + imageBox.height > viewCenter) {
1039
1118
  _line = "line-through";
1040
- } else if (aBox.y + aBox.height < viewCenter) {
1119
+ } else if (imageBox.y + imageBox.height < viewCenter) {
1041
1120
  _line = "overline";
1042
1121
  } else {
1043
1122
  _line = "underline";
@@ -1051,89 +1130,95 @@ function highlight() {
1051
1130
  }
1052
1131
  switch (_line) {
1053
1132
  case "outline": {
1054
- const paddingX = cBox.width * 0.2;
1055
- const paddingY = cBox.height * 0.2;
1056
- cBox.width += paddingX;
1057
- cBox.height += paddingY;
1133
+ const paddingX = targetBox.width * 0.2;
1134
+ const paddingY = targetBox.height * 0.2;
1058
1135
  if (isVertical) {
1059
- cBox.x -= paddingY / 2;
1060
- cBox.y -= paddingX / 2;
1061
- cBox.x += cBox.height;
1136
+ targetBox.x -= paddingY / 2;
1137
+ targetBox.y -= paddingX / 2;
1138
+ targetBox.x -= targetBox.height;
1062
1139
  } else {
1063
- cBox.x -= paddingX / 2;
1064
- cBox.y -= paddingY / 2;
1140
+ targetBox.x -= paddingX / 2;
1141
+ targetBox.y -= paddingY / 2;
1065
1142
  }
1143
+ targetBox.width += paddingX;
1144
+ targetBox.height += paddingY;
1066
1145
  break;
1067
1146
  }
1068
1147
  case "overline":
1069
- cBox.height = aBox.height * styleScale;
1148
+ targetBox.height = imageBox.height * styleScale;
1070
1149
  if (isVertical) {
1071
- cBox.x = char.inlineBox.left + char.inlineBox.width;
1150
+ targetBox.x = inlineBox.left + inlineBox.width;
1072
1151
  } else {
1073
- cBox.y = char.inlineBox.top;
1152
+ targetBox.y = inlineBox.top;
1074
1153
  }
1075
1154
  break;
1076
1155
  case "line-through":
1077
- cBox.height = aBox.height * styleScale;
1156
+ targetBox.height = imageBox.height * styleScale;
1078
1157
  if (isVertical) {
1079
- cBox.x = char.inlineBox.left + char.inlineBox.width - char.strikeoutPosition + cBox.height / 2;
1158
+ targetBox.x = inlineBox.left + inlineBox.width - strikeoutPosition + targetBox.height / 2;
1080
1159
  } else {
1081
- cBox.y = char.inlineBox.top + char.strikeoutPosition - cBox.height / 2;
1160
+ targetBox.y = inlineBox.top + strikeoutPosition - targetBox.height / 2;
1082
1161
  }
1083
1162
  break;
1084
1163
  case "underline":
1085
- cBox.height = aBox.height * styleScale;
1164
+ targetBox.height = imageBox.height * styleScale;
1086
1165
  if (isVertical) {
1087
- cBox.x = char.inlineBox.left + char.inlineBox.width - char.underlinePosition;
1166
+ targetBox.x = glyphBox.left + glyphBox.width - underlinePosition;
1088
1167
  } else {
1089
- cBox.y = char.inlineBox.top + char.underlinePosition;
1168
+ targetBox.y = inlineBox.top + underlinePosition;
1090
1169
  }
1091
1170
  break;
1092
1171
  }
1093
1172
  }
1094
- const transform = new Matrix3().translate(-aBox.x, -aBox.y).scale(cBox.width / aBox.width, cBox.height / aBox.height);
1173
+ const transform = new Matrix3();
1174
+ transform.translate(-imageBox.x, -imageBox.y);
1175
+ transform.scale(targetBox.width / imageBox.width, targetBox.height / imageBox.height);
1095
1176
  if (isVertical) {
1177
+ const tx = targetBox.width / 2;
1178
+ const ty = targetBox.height / 2;
1179
+ if (!hasReferImage) {
1180
+ transform.translate(-tx, -ty);
1181
+ }
1096
1182
  transform.rotate(-Math.PI / 2);
1183
+ if (!hasReferImage) {
1184
+ transform.translate(ty, tx);
1185
+ }
1097
1186
  }
1098
- transform.translate(cBox.x, cBox.y);
1187
+ transform.translate(targetBox.x, targetBox.y);
1099
1188
  for (let i2 = 0; i2 < Math.ceil(rawWidth / userWidth); i2++) {
1100
1189
  const _transform = transform.clone();
1101
1190
  if (isVertical) {
1102
- _transform.translate(0, i2 * cBox.width);
1191
+ _transform.translate(0, i2 * targetBox.width);
1103
1192
  } else {
1104
- _transform.translate(i2 * cBox.width, 0);
1193
+ _transform.translate(i2 * targetBox.width, 0);
1105
1194
  }
1106
- svgPathSet.paths.forEach((originalPath) => {
1195
+ imagePathSet.paths.forEach((originalPath) => {
1107
1196
  const path = originalPath.clone().applyTransform(_transform);
1108
- if (path.style.strokeWidth) {
1197
+ if (path.style.strokeWidth)
1109
1198
  path.style.strokeWidth *= styleScale * _thickness;
1110
- }
1111
- if (path.style.strokeMiterlimit) {
1199
+ if (path.style.strokeMiterlimit)
1112
1200
  path.style.strokeMiterlimit *= styleScale;
1113
- }
1114
- if (path.style.strokeDashoffset) {
1201
+ if (path.style.strokeDashoffset)
1115
1202
  path.style.strokeDashoffset *= styleScale;
1116
- }
1117
- if (path.style.strokeDasharray) {
1203
+ if (path.style.strokeDasharray)
1118
1204
  path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
1119
- }
1120
1205
  if (path.style.fill && path.style.fill in _colormap) {
1121
1206
  path.style.fill = _colormap[path.style.fill];
1122
1207
  }
1123
1208
  if (path.style.stroke && path.style.stroke in _colormap) {
1124
1209
  path.style.stroke = _colormap[path.style.stroke];
1125
1210
  }
1126
- paths.push(path);
1211
+ pathSet.paths.push(path);
1127
1212
  if (rawWidth !== userWidth) {
1128
1213
  if (isVertical) {
1129
- clipRects[paths.length - 1] = new BoundingBox(
1214
+ clipRects[pathSet.paths.length - 1] = new BoundingBox(
1130
1215
  groupBox.left - groupBox.width * 2,
1131
1216
  groupBox.top,
1132
1217
  groupBox.width * 4,
1133
1218
  groupBox.height
1134
1219
  );
1135
1220
  } else {
1136
- clipRects[paths.length - 1] = new BoundingBox(
1221
+ clipRects[pathSet.paths.length - 1] = new BoundingBox(
1137
1222
  groupBox.left,
1138
1223
  groupBox.top - groupBox.height * 2,
1139
1224
  groupBox.width,
@@ -1146,8 +1231,24 @@ function highlight() {
1146
1231
  }
1147
1232
  },
1148
1233
  renderOrder: -1,
1234
+ getBoundingBox: () => {
1235
+ const boundingBoxs = [];
1236
+ pathSet.paths.forEach((path, index) => {
1237
+ const clipRect = clipRects[index];
1238
+ let box = path.getBoundingBox();
1239
+ if (clipRect) {
1240
+ const x = Math.max(box.x, clipRect.x);
1241
+ const y = Math.max(box.y, clipRect.y);
1242
+ const right = Math.min(box.right, clipRect.right);
1243
+ const bottom = Math.min(box.bottom, clipRect.bottom);
1244
+ box = new BoundingBox(x, y, right - x, bottom - y);
1245
+ }
1246
+ boundingBoxs.push(box);
1247
+ });
1248
+ return BoundingBox.from(...boundingBoxs);
1249
+ },
1149
1250
  render: (ctx, text) => {
1150
- paths.forEach((path, index) => {
1251
+ pathSet.paths.forEach((path, index) => {
1151
1252
  drawPath({
1152
1253
  ctx,
1153
1254
  path,
@@ -1171,12 +1272,12 @@ function genDisc(r, color) {
1171
1272
  </svg>`;
1172
1273
  }
1173
1274
  function listStyle() {
1174
- const paths = [];
1275
+ const pathSet = new Path2DSet();
1175
1276
  return definePlugin({
1176
1277
  name: "listStyle",
1177
- paths,
1278
+ pathSet,
1178
1279
  update: (text) => {
1179
- paths.length = 0;
1280
+ pathSet.paths.length = 0;
1180
1281
  const { paragraphs, isVertical, fontSize } = text;
1181
1282
  const padding = fontSize * 0.45;
1182
1283
  paragraphs.forEach((paragraph) => {
@@ -1230,7 +1331,7 @@ function listStyle() {
1230
1331
  inlineBox.top + (inlineBox.height - imageBox.height * _scale) / 2
1231
1332
  );
1232
1333
  }
1233
- paths.push(...imagePathSet.paths.map((p) => {
1334
+ pathSet.paths.push(...imagePathSet.paths.map((p) => {
1234
1335
  const path = p.clone();
1235
1336
  path.applyTransform(m);
1236
1337
  if (path.style.fill && path.style.fill in colormap) {
@@ -1293,39 +1394,21 @@ function render() {
1293
1394
  return boxes.length ? BoundingBox.from(...boxes) : void 0;
1294
1395
  },
1295
1396
  render: (ctx, text) => {
1296
- const { paragraphs, glyphBox, effects, style } = text;
1297
- function fillBackground(color, box) {
1298
- ctx.fillStyle = color;
1299
- ctx.fillRect(box.left, box.top, box.width, box.height);
1300
- }
1301
- if (style?.backgroundColor) {
1302
- fillBackground(style.backgroundColor, new BoundingBox(0, 0, ctx.canvas.width, ctx.canvas.height));
1303
- }
1304
- paragraphs.forEach((paragraph) => {
1305
- if (paragraph.style?.backgroundColor) {
1306
- fillBackground(paragraph.style.backgroundColor, paragraph.lineBox);
1307
- }
1308
- });
1397
+ const { paragraphs, glyphBox, effects } = text;
1309
1398
  if (effects) {
1310
- effects.forEach((style2) => {
1311
- uploadColor(style2, glyphBox, ctx);
1399
+ effects.forEach((style) => {
1400
+ uploadColor(style, glyphBox, ctx);
1312
1401
  ctx.save();
1313
- const [a, c, e, b, d, f] = getTransform2D(text, style2).transpose().elements;
1402
+ const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
1314
1403
  ctx.transform(a, b, c, d, e, f);
1315
1404
  text.forEachCharacter((character) => {
1316
- if (character.parent.style?.backgroundColor) {
1317
- fillBackground(character.parent.style.backgroundColor, character.inlineBox);
1318
- }
1319
- character.drawTo(ctx, style2);
1405
+ character.drawTo(ctx, style);
1320
1406
  });
1321
1407
  ctx.restore();
1322
1408
  });
1323
1409
  } else {
1324
1410
  paragraphs.forEach((paragraph) => {
1325
1411
  paragraph.fragments.forEach((fragment) => {
1326
- if (fragment.style?.backgroundColor) {
1327
- fillBackground(fragment.computedStyle.backgroundColor, fragment.inlineBox);
1328
- }
1329
1412
  fragment.characters.forEach((character) => {
1330
1413
  character.drawTo(ctx);
1331
1414
  });
@@ -1363,12 +1446,12 @@ function getTransform2D(text, style) {
1363
1446
  }
1364
1447
 
1365
1448
  function textDecoration() {
1366
- const paths = [];
1449
+ const pathSet = new Path2DSet();
1367
1450
  return definePlugin({
1368
1451
  name: "textDecoration",
1369
- paths,
1452
+ pathSet,
1370
1453
  update: (text) => {
1371
- paths.length = 0;
1454
+ pathSet.paths.length = 0;
1372
1455
  const groups = [];
1373
1456
  let group;
1374
1457
  let prevStyle;
@@ -1428,7 +1511,8 @@ function textDecoration() {
1428
1511
  color,
1429
1512
  textDecoration: textDecoration2
1430
1513
  } = style;
1431
- const { left, top, width, height } = BoundingBox.from(...group2.map((c) => c.inlineBox));
1514
+ const inlineBox = BoundingBox.from(...group2.map((c) => c.inlineBox));
1515
+ const { left, top, width, height } = inlineBox;
1432
1516
  let position = isVertical ? left + width : top;
1433
1517
  const direction = isVertical ? -1 : 1;
1434
1518
  let thickness = 0;
@@ -1468,7 +1552,7 @@ function textDecoration() {
1468
1552
  fill: color
1469
1553
  });
1470
1554
  }
1471
- paths.push(path);
1555
+ pathSet.paths.push(path);
1472
1556
  });
1473
1557
  },
1474
1558
  render: (ctx, text) => {
@@ -1478,7 +1562,7 @@ function textDecoration() {
1478
1562
  ctx.save();
1479
1563
  const [a, c, e, b, d, f] = getTransform2D(text, effectStyle).transpose().elements;
1480
1564
  ctx.transform(a, b, c, d, e, f);
1481
- paths.forEach((path) => {
1565
+ pathSet.paths.forEach((path) => {
1482
1566
  drawPath({
1483
1567
  ctx,
1484
1568
  path,
@@ -1489,7 +1573,7 @@ function textDecoration() {
1489
1573
  ctx.restore();
1490
1574
  });
1491
1575
  } else {
1492
- paths.forEach((path) => {
1576
+ pathSet.paths.forEach((path) => {
1493
1577
  drawPath({
1494
1578
  ctx,
1495
1579
  path,
@@ -1659,22 +1743,16 @@ class Text extends EventEmitter {
1659
1743
  this.pathBox = BoundingBox.from(
1660
1744
  this.glyphBox,
1661
1745
  ...Array.from(this.plugins.values()).map((plugin) => {
1662
- return plugin.getBoundingBox ? plugin.getBoundingBox(this) : new Path2DSet(plugin.paths ?? []).getBoundingBox();
1746
+ return plugin.getBoundingBox ? plugin.getBoundingBox(this) : plugin.pathSet?.getBoundingBox();
1663
1747
  }).filter(Boolean)
1664
1748
  );
1665
1749
  return this;
1666
1750
  }
1667
1751
  updateBoundingBox() {
1668
- const { lineBox, rawGlyphBox, pathBox } = this;
1669
- const left = Math.min(pathBox.left, pathBox.left + lineBox.left - rawGlyphBox.left);
1670
- const top = Math.min(pathBox.top, pathBox.top + lineBox.top - rawGlyphBox.top);
1671
- const right = Math.max(pathBox.right, pathBox.right + lineBox.right - rawGlyphBox.right);
1672
- const bottom = Math.max(pathBox.bottom, pathBox.bottom + lineBox.bottom - rawGlyphBox.bottom);
1673
- this.boundingBox = new BoundingBox(
1674
- left,
1675
- top,
1676
- right - left,
1677
- bottom - top
1752
+ this.boundingBox = BoundingBox.from(
1753
+ this.rawGlyphBox,
1754
+ this.lineBox,
1755
+ this.pathBox
1678
1756
  );
1679
1757
  return this;
1680
1758
  }
@@ -1705,9 +1783,9 @@ class Text extends EventEmitter {
1705
1783
  Array.from(this.plugins.values()).sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
1706
1784
  if (plugin.render) {
1707
1785
  plugin.render?.(ctx, this);
1708
- } else if (plugin.paths) {
1786
+ } else if (plugin.pathSet) {
1709
1787
  const style = this.computedStyle;
1710
- plugin.paths.forEach((path) => {
1788
+ plugin.pathSet.paths.forEach((path) => {
1711
1789
  drawPath({
1712
1790
  ctx,
1713
1791
  path,
@@ -1740,4 +1818,4 @@ function renderText(options, load) {
1740
1818
  return text.render(options);
1741
1819
  }
1742
1820
 
1743
- export { Character, Fragment, Measurer, Paragraph, Text, background, definePlugin, drawPath, filterEmpty, getHighlightStyle, getTransform2D, hexToRgb, highlight, isEqualObject, isEqualValue, isNone, listStyle, measureText, needsFetch, outline, parseColor, parseColormap, parseValueNumber, render, renderText, setupView, textDecoration, textDefaultStyle, uploadColor, uploadColors };
1821
+ export { Character, Fragment, Measurer, Paragraph, Text, background, createSVGLoader, createSVGParser, definePlugin, drawPath, filterEmpty, getHighlightStyle, getTransform2D, hexToRgb, highlight, isEqualObject, isEqualValue, isNone, listStyle, measureText, outline, parseColor, parseColormap, parseValueNumber, render, renderText, setupView, textDecoration, textDefaultStyle, uploadColor, uploadColors };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "modern-text",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "1.3.2",
5
5
  "packageManager": "pnpm@9.15.1",
6
6
  "description": "Measure and render text in a way that describes the DOM.",
7
7
  "author": "wxm",
@@ -57,22 +57,22 @@
57
57
  "prepare": "simple-git-hooks"
58
58
  },
59
59
  "dependencies": {
60
- "modern-font": "^0.4.0",
61
- "modern-idoc": "^0.2.10",
62
- "modern-path2d": "^1.2.10"
60
+ "modern-font": "^0.4.1",
61
+ "modern-idoc": "^0.2.13",
62
+ "modern-path2d": "^1.2.18"
63
63
  },
64
64
  "devDependencies": {
65
- "@antfu/eslint-config": "^4.8.1",
66
- "@types/node": "^22.13.10",
67
- "bumpp": "^10.0.3",
65
+ "@antfu/eslint-config": "^4.11.0",
66
+ "@types/node": "^22.13.14",
67
+ "bumpp": "^10.1.0",
68
68
  "conventional-changelog-cli": "^5.0.0",
69
- "eslint": "^9.22.0",
70
- "lint-staged": "^15.4.3",
71
- "simple-git-hooks": "^2.11.1",
69
+ "eslint": "^9.23.0",
70
+ "lint-staged": "^15.5.0",
71
+ "simple-git-hooks": "^2.12.1",
72
72
  "typescript": "^5.8.2",
73
73
  "unbuild": "^3.5.0",
74
- "vite": "^6.2.1",
75
- "vitest": "^3.0.8"
74
+ "vite": "^6.2.3",
75
+ "vitest": "^3.0.9"
76
76
  },
77
77
  "simple-git-hooks": {
78
78
  "pre-commit": "pnpm lint-staged"