modern-text 0.4.5 → 0.5.1

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.cjs CHANGED
@@ -152,6 +152,8 @@ class Character {
152
152
  __publicField$3(this, "lineBox", new modernPath2d.BoundingBox());
153
153
  __publicField$3(this, "inlineBox", new modernPath2d.BoundingBox());
154
154
  __publicField$3(this, "glyphBox");
155
+ __publicField$3(this, "advanceWidth", 0);
156
+ __publicField$3(this, "advanceHeight", 0);
155
157
  __publicField$3(this, "underlinePosition", 0);
156
158
  __publicField$3(this, "underlineThickness", 0);
157
159
  __publicField$3(this, "strikeoutPosition", 0);
@@ -198,14 +200,16 @@ class Character {
198
200
  const unitsPerEm = head.unitsPerEm;
199
201
  const ascender = hhea.ascent;
200
202
  const descender = hhea.descent;
201
- const { content, computedStyle } = this;
203
+ const { content, computedStyle, isVertical } = this;
202
204
  const { fontSize } = computedStyle;
203
205
  const rate = unitsPerEm / fontSize;
204
206
  const advanceWidth = sfnt.getAdvanceWidth(content, fontSize);
205
207
  const advanceHeight = (ascender + Math.abs(descender)) / rate;
206
208
  const baseline = ascender / rate;
207
- this.inlineBox.width = advanceWidth;
208
- this.inlineBox.height = advanceHeight;
209
+ this.advanceWidth = advanceWidth;
210
+ this.advanceHeight = advanceHeight;
211
+ this.inlineBox.width = isVertical ? advanceHeight : advanceWidth;
212
+ this.inlineBox.height = isVertical ? advanceWidth : advanceHeight;
209
213
  this.underlinePosition = (ascender - post.underlinePosition) / rate;
210
214
  this.underlineThickness = post.underlineThickness / rate;
211
215
  this.strikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
@@ -239,7 +243,9 @@ class Character {
239
243
  ascender,
240
244
  descender,
241
245
  typoAscender,
242
- fontStyle
246
+ fontStyle,
247
+ advanceWidth,
248
+ advanceHeight
243
249
  } = this;
244
250
  const { left, top } = inlineBox;
245
251
  const needsItalic = style.fontStyle === "italic" && fontStyle !== "italic";
@@ -248,26 +254,31 @@ class Character {
248
254
  let glyphIndex;
249
255
  const path = new modernPath2d.Path2D();
250
256
  if (isVertical) {
251
- x += (inlineBox.height - inlineBox.width) / 2;
252
- if (Math.abs(inlineBox.width - inlineBox.height) > 0.1) {
253
- y -= (ascender - typoAscender) / (ascender + Math.abs(descender)) * inlineBox.height;
257
+ x += (advanceHeight - advanceWidth) / 2;
258
+ if (Math.abs(advanceWidth - advanceHeight) > 0.1) {
259
+ y -= (ascender - typoAscender) / (ascender + Math.abs(descender)) * advanceHeight;
254
260
  }
255
261
  glyphIndex = void 0;
256
262
  }
257
263
  if (isVertical && !set1.has(content) && (content.codePointAt(0) <= 256 || set2.has(content))) {
258
264
  path.addCommands(
259
- sfnt.getPathCommands(content, x, top + baseline - (inlineBox.height - inlineBox.width) / 2, style.fontSize) ?? []
265
+ sfnt.getPathCommands(
266
+ content,
267
+ x,
268
+ top + baseline - (advanceHeight - advanceWidth) / 2,
269
+ style.fontSize
270
+ )
260
271
  );
261
272
  const point = {
262
- y: top - (inlineBox.height - inlineBox.width) / 2 + inlineBox.height / 2,
263
- x: x + inlineBox.width / 2
273
+ y: top - (advanceHeight - advanceWidth) / 2 + advanceHeight / 2,
274
+ x: x + advanceWidth / 2
264
275
  };
265
276
  if (needsItalic) {
266
277
  this._italic(
267
278
  path,
268
279
  isVertical ? {
269
280
  x: point.x,
270
- y: top - (inlineBox.height - inlineBox.width) / 2 + baseline
281
+ y: top - (advanceHeight - advanceWidth) / 2 + baseline
271
282
  } : void 0
272
283
  );
273
284
  }
@@ -281,19 +292,17 @@ class Character {
281
292
  this._italic(
282
293
  path,
283
294
  isVertical ? {
284
- x: x + inlineBox.width / 2,
285
- y: top + typoAscender / (ascender + Math.abs(descender)) * inlineBox.height
295
+ x: x + advanceWidth / 2,
296
+ y: top + typoAscender / (ascender + Math.abs(descender)) * advanceHeight
286
297
  } : void 0
287
298
  );
288
299
  }
289
300
  } else {
290
- path.addCommands(
291
- sfnt.getPathCommands(content, x, y, style.fontSize) ?? []
292
- );
301
+ path.addCommands(sfnt.getPathCommands(content, x, y, style.fontSize));
293
302
  if (needsItalic) {
294
303
  this._italic(
295
304
  path,
296
- isVertical ? { x: x + inlineBox.height / 2, y } : void 0
305
+ isVertical ? { x: x + advanceHeight / 2, y } : void 0
297
306
  );
298
307
  }
299
308
  }
@@ -347,6 +356,23 @@ class Character {
347
356
  function isNone(val) {
348
357
  return !val || val === "none";
349
358
  }
359
+ function isEqualObject(obj1, obj2) {
360
+ const keys1 = Object.keys(obj1);
361
+ const keys2 = Object.keys(obj2);
362
+ const keys = Array.from(/* @__PURE__ */ new Set([...keys1, ...keys2]));
363
+ return keys.every((key) => obj1[key] === obj2[key]);
364
+ }
365
+ function hexToRgb(hex) {
366
+ const cleanHex = hex.startsWith("#") ? hex.slice(1) : hex;
367
+ const isValidHex = /^(?:[0-9A-F]{3}|[0-9A-F]{6})$/i.test(cleanHex);
368
+ if (!isValidHex)
369
+ return null;
370
+ const fullHex = cleanHex.length === 3 ? cleanHex.split("").map((char) => char + char).join("") : cleanHex;
371
+ const r = Number.parseInt(fullHex.slice(0, 2), 16);
372
+ const g = Number.parseInt(fullHex.slice(2, 4), 16);
373
+ const b = Number.parseInt(fullHex.slice(4, 6), 16);
374
+ return `rgb(${r}, ${g}, ${b})`;
375
+ }
350
376
  function filterEmpty(val) {
351
377
  if (!val)
352
378
  return val;
@@ -594,16 +620,23 @@ class Measurer {
594
620
  top: character.top - rect.top
595
621
  });
596
622
  const item = paragraphs[paragraphIndex].fragments[fragmentIndex].characters[characterIndex];
623
+ const { fontHeight, isVertical } = item;
597
624
  const result = results[i];
598
625
  item.inlineBox.left = result.left;
599
626
  item.inlineBox.top = result.top;
600
627
  item.inlineBox.width = result.width;
601
628
  item.inlineBox.height = result.height;
602
- const fontHeight = item.fontHeight;
603
- item.lineBox.left = result.left;
604
- item.lineBox.top = result.top + (result.height - fontHeight) / 2;
605
- item.lineBox.height = fontHeight;
606
- item.lineBox.width = result.width;
629
+ if (isVertical) {
630
+ item.lineBox.left = result.left + (result.width - fontHeight) / 2;
631
+ item.lineBox.top = result.top;
632
+ item.lineBox.width = fontHeight;
633
+ item.lineBox.height = result.height;
634
+ } else {
635
+ item.lineBox.left = result.left;
636
+ item.lineBox.top = result.top + (result.height - fontHeight) / 2;
637
+ item.lineBox.width = result.width;
638
+ item.lineBox.height = fontHeight;
639
+ }
607
640
  i++;
608
641
  });
609
642
  return {
@@ -622,7 +655,6 @@ class Measurer {
622
655
  }
623
656
  }
624
657
 
625
- const defaultReferImage = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiIgdmlld0JveD0iMCAwIDcyIDcyIiBmaWxsPSJub25lIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMyLjQwMjkgMjhIMzUuMTU5NFYzMy4xNzcxQzM1Ljk4MjEgMzIuMzExNSAzNi45NzEgMzEuODczNyAzOC4wOTQ4IDMxLjg3MzdDMzkuNjY3NiAzMS44NzM3IDQwLjkxNjYgMzIuNDI5NSA0MS44MzkgMzMuNTQzN0w0MS44NDAzIDMzLjU0NTNDNDIuNjcxNyAzNC41NzA1IDQzLjA5MTUgMzUuODU1OSA0My4wOTE1IDM3LjM4NzdDNDMuMDkxNSAzOC45NzYxIDQyLjY3MjkgNDAuMzAyOCA0MS44MTgzIDQxLjMzMDRMNDEuODE3MSA0MS4zMzE4QzQwLjg3MzEgNDIuNDQ2MSAzOS41ODMyIDQzIDM3Ljk3MjEgNDNDMzYuNzQ3NyA0MyAzNS43NDg4IDQyLjY1OTkgMzQuOTk1OCA0MS45NjkzVjQyLjcyNDdIMzIuNDAyOVYyOFpNMzcuNTQyOCAzNC4wOTI0QzM2Ljg1NDkgMzQuMDkyNCAzNi4zMDE0IDM0LjM1NjEgMzUuODQ4NyAzNC45MDA0TDM1Ljg0NTIgMzQuOTA0NkMzNS4zMzU4IDM1LjQ4NTMgMzUuMDc3NiAzNi4yOTc2IDM1LjA3NzYgMzcuMzQ4NFYzNy41MDU3QzM1LjA3NzYgMzguNDY0IDM1LjI3NzIgMzkuMjQ0MyAzNS42OTQzIDM5LjgyNzlDMzYuMTQ0MSA0MC40NTg3IDM2Ljc3MjYgNDAuNzgxMyAzNy42MjQ1IDQwLjc4MTNDMzguNTg3NCA0MC43ODEzIDM5LjI3MDcgNDAuNDUyNyAzOS43MTUyIDM5LjgxMjdDNDAuMDcyOCAzOS4yNjg0IDQwLjI3MzcgMzguNDY3MyA0MC4yNzM3IDM3LjM4NzdDNDAuMjczNyAzNi4zMTA1IDQwLjA1MzMgMzUuNTMxMyAzOS42NzgzIDM1LjAwNzdDMzkuMjM3MSAzNC40MDcxIDM4LjUzNDIgMzQuMDkyNCAzNy41NDI4IDM0LjA5MjRaIiBmaWxsPSIjMjIyNTI5Ii8+PHBhdGggZD0iTTQ5Ljg2MTQgMzEuODczN0M0OC4xNTM1IDMxLjg3MzcgNDYuODAxNiAzMi40MjM5IDQ1LjgzNDggMzMuNTM5MkM0NC45MzcgMzQuNTQ3MiA0NC40OTY2IDM1Ljg1NiA0NC40OTY2IDM3LjQyN0M0NC40OTY2IDM5LjAzNjggNDQuOTM2NyA0MC4zNjU5IDQ1Ljg1NTkgNDEuMzk0M0M0Ni44MDMxIDQyLjQ3MDYgNDguMTM0OCA0MyA0OS44MjA1IDQzQzUxLjIyNiA0MyA1Mi4zODI2IDQyLjY1NjMgNTMuMjQ3OSA0MS45Njk3QzU0LjEzNTkgNDEuMjYxNCA1NC43MDYxIDQwLjE4ODcgNTQuOTU3MyAzOC43NzkxTDU1IDM4LjUzOTdINTIuMjQ4NEw1Mi4yMjU5IDM4LjcyMDFDNTIuMTM3OSAzOS40MjUxIDUxLjg5MjUgMzkuOTI3OCA1MS41MTA5IDQwLjI1NThDNTEuMTI5NSA0MC41ODM1IDUwLjU4MzEgNDAuNzYxNiA0OS44NDA5IDQwLjc2MTZDNDkuMDAwMSA0MC43NjE2IDQ4LjM5NDkgNDAuNDcxNSA0Ny45OTA3IDM5LjkyMzdMNDcuOTg3NCAzOS45MTk0QzQ3LjUzNTYgMzkuMzQwMSA0Ny4zMTQ0IDM4LjUwNjIgNDcuMzE0NCAzNy40MDc0QzQ3LjMxNDQgMzYuMzMyMiA0Ny41NTQ0IDM1LjUxNzcgNDguMDA1OCAzNC45NTY4TDQ4LjAwNzggMzQuOTU0M0M0OC40NTM3IDM0LjM4MjUgNDkuMDYxOCAzNC4xMTIxIDQ5Ljg2MTQgMzQuMTEyMUM1MC41MjMgMzQuMTEyMSA1MS4wNDUxIDM0LjI2MTUgNTEuNDI3MiAzNC41NDA3QzUxLjc4ODQgMzQuODE5NCA1Mi4wNTMgMzUuMjQ0NyA1Mi4xODgxIDM1Ljg1NzFMNTIuMjIzOSAzNi4wMTk0SDU0Ljk1NDhMNTQuOTE3IDM1Ljc4MzVDNTQuNzA2MyAzNC40NjYgNTQuMTUzNiAzMy40NzAxIDUzLjI2MzQgMzIuODAxOUw1My4yNjAyIDMyLjc5OTVDNTIuMzk1MSAzMi4xNzU1IDUxLjI2MjEgMzEuODczNyA0OS44NjE0IDMxLjg3MzdaIiBmaWxsPSIjMjIyNTI5Ii8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yNS43NTYxIDI4LjI3NTNIMjIuNzQ0TDE3IDQyLjcyNDdIMjAuMDE0MUwyMS4zNDI5IDM5LjIwNDlIMjcuMTU3MkwyOC40ODYgNDIuNzI0N0gzMS41MDAxTDI1Ljc1NjEgMjguMjc1M1pNMjIuMjEyNSAzNi45MDc2TDI0LjI1OTYgMzEuNDUzOUwyNi4yODg1IDM2LjkwNzZIMjIuMjEyNVoiIGZpbGw9IiMyMjI1MjkiLz48L3N2Zz4=";
626
658
  function parseCharsPerRepeat(size, fontSize, total) {
627
659
  if (size === "cover") {
628
660
  return 0;
@@ -639,69 +671,33 @@ function parseCharsPerRepeat(size, fontSize, total) {
639
671
  return Math.ceil(size / fontSize);
640
672
  }
641
673
  }
642
- function parseStrokeWidthScale(strokeWidth, fontSize, total) {
643
- if (typeof strokeWidth === "string") {
644
- if (strokeWidth.endsWith("%")) {
645
- return Number(strokeWidth.substring(0, strokeWidth.length - 1)) / 100;
646
- } else if (strokeWidth.endsWith("rem")) {
647
- const value = Number(strokeWidth.substring(0, strokeWidth.length - 3));
674
+ function parseThickness(thickness, fontSize, total) {
675
+ if (typeof thickness === "string") {
676
+ if (thickness.endsWith("%")) {
677
+ return Number(thickness.substring(0, thickness.length - 1)) / 100;
678
+ } else if (thickness.endsWith("rem")) {
679
+ const value = Number(thickness.substring(0, thickness.length - 3));
648
680
  return value * fontSize / total;
649
681
  } else {
650
- return Number(strokeWidth) / total;
682
+ return Number(thickness) / total;
651
683
  }
652
684
  } else {
653
- return strokeWidth / total;
654
- }
655
- }
656
- function getTransformMatrix(a, b, c, isVertical, type) {
657
- let scale;
658
- if (!isVertical) {
659
- scale = {
660
- x: c.width / b.width,
661
- y: c.height / b.height
662
- };
663
- } else {
664
- scale = {
665
- x: c.width / b.height,
666
- y: c.height / b.width
667
- };
668
- }
669
- const offset = c.center.add(
670
- a.center.sub(b.center).scale(scale.x, scale.y)
671
- );
672
- if (type === "line") {
673
- offset.sub({
674
- x: a.width / 2 * scale.x,
675
- y: a.height * scale.y
676
- });
677
- } else {
678
- offset.sub({
679
- x: a.width / 2 * scale.x,
680
- y: a.height / 2 * scale.y
681
- });
685
+ return thickness / total;
682
686
  }
683
- const m = new modernPath2d.Matrix3();
684
- m.translate(-a.left, -a.top);
685
- if (isVertical) {
686
- m.translate(-a.width / 2, -a.height / 2);
687
- m.rotate(Math.PI / 2);
688
- m.translate(a.width / 2, a.height / 2);
689
- }
690
- m.scale(scale.x, scale.y);
691
- m.translate(offset.x, offset.y);
692
- return m;
693
687
  }
694
688
  function highlight() {
695
689
  const paths = [];
696
690
  const clipRects = [];
697
691
  const svgStringToSvgPaths = /* @__PURE__ */ new Map();
698
692
  function getPaths(svg) {
699
- let paths2 = svgStringToSvgPaths.get(svg);
700
- if (!paths2) {
701
- paths2 = modernPath2d.parseSvg(svg);
702
- svgStringToSvgPaths.set(svg, paths2);
693
+ let result = svgStringToSvgPaths.get(svg);
694
+ if (!result) {
695
+ const dom = modernPath2d.parseSvgToDom(svg);
696
+ const paths2 = modernPath2d.parseSvg(dom);
697
+ result = { dom, paths: paths2 };
698
+ svgStringToSvgPaths.set(svg, result);
703
699
  }
704
- return paths2;
700
+ return result;
705
701
  }
706
702
  return definePlugin({
707
703
  name: "highlight",
@@ -714,7 +710,7 @@ function highlight() {
714
710
  text.forEachCharacter((character) => {
715
711
  const { isVertical, computedStyle: style, inlineBox, fontSize } = character;
716
712
  if (!isNone(style.highlightImage) && character.glyphBox) {
717
- if (style.highlightSize !== "1rem" && prevStyle?.highlightImage === style.highlightImage && prevStyle?.highlightSize === style.highlightSize && prevStyle?.highlightStrokeWidth === style.highlightStrokeWidth && prevStyle?.highlightOverflow === style.highlightOverflow && group?.length && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top) && group[0].fontSize === fontSize) {
713
+ if (style.highlightSize !== "1rem" && (!prevStyle || prevStyle.highlightImage === style.highlightImage && isEqualObject(prevStyle.highlightImageColors, style.highlightImageColors) && prevStyle.highlightLine === style.highlightLine && prevStyle.highlightSize === style.highlightSize && prevStyle.highlightThickness === style.highlightThickness && prevStyle.highlightOverflow === style.highlightOverflow) && group?.length && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top) && group[0].fontSize === fontSize) {
718
714
  group.push(character);
719
715
  } else {
720
716
  group = [];
@@ -732,41 +728,117 @@ function highlight() {
732
728
  };
733
729
  }).forEach((group2) => {
734
730
  const { char, groupBox } = group2;
735
- const style = char.computedStyle;
736
- const { fontSize, writingMode } = style;
731
+ const { computedStyle: style } = char;
732
+ const {
733
+ fontSize,
734
+ writingMode,
735
+ highlightThickness,
736
+ highlightSize,
737
+ highlightLine,
738
+ highlightOverflow,
739
+ highlightImage,
740
+ highlightImageColors
741
+ } = style;
737
742
  const isVertical = writingMode.includes("vertical");
738
- const strokeWidthScale = parseStrokeWidthScale(style.highlightStrokeWidth, fontSize, groupBox.width);
739
- const charsPerRepeat = parseCharsPerRepeat(style.highlightSize, fontSize, groupBox.width);
740
- const highlightOverflow = isNone(style.highlightOverflow) ? charsPerRepeat ? "hidden" : "visible" : style.highlightOverflow;
741
- const refPaths = getPaths(isNone(style.highlightReferImage) ? defaultReferImage : style.highlightReferImage);
742
- const svgPaths = getPaths(style.highlightImage);
743
- const box = modernPath2d.getPathsBoundingBox(svgPaths, true);
744
- const refBox = modernPath2d.getPathsBoundingBox(refPaths, false);
745
- const unitWidth = charsPerRepeat ? fontSize * charsPerRepeat : isVertical ? groupBox.height : groupBox.width;
746
- let unitHeight;
747
- let type;
748
- if (box.height / refBox.height > 0.8) {
749
- type = "block";
750
- unitHeight = groupBox.height;
743
+ const thickness = parseThickness(highlightThickness, fontSize, groupBox.width);
744
+ const charsPerRepeat = parseCharsPerRepeat(highlightSize, fontSize, groupBox.width);
745
+ const overflow = isNone(highlightOverflow) ? charsPerRepeat ? "hidden" : "visible" : highlightOverflow;
746
+ const colors = Object.keys(highlightImageColors).reduce((obj, key) => {
747
+ let value = highlightImageColors[key];
748
+ const keyRgb = hexToRgb(key);
749
+ const valueRgb = hexToRgb(value);
750
+ if (keyRgb) {
751
+ key = keyRgb;
752
+ }
753
+ if (valueRgb) {
754
+ value = valueRgb;
755
+ }
756
+ obj[key] = value;
757
+ return obj;
758
+ }, {});
759
+ const { paths: svgPaths, dom: svgDom } = getPaths(highlightImage);
760
+ const aBox = modernPath2d.getPathsBoundingBox(svgPaths, true);
761
+ const cBox = new modernPath2d.BoundingBox().copy(groupBox);
762
+ cBox.width = charsPerRepeat ? fontSize * charsPerRepeat : isVertical ? groupBox.height : groupBox.width;
763
+ cBox.height = isVertical ? groupBox.width : groupBox.height;
764
+ const width = isVertical ? cBox.height : cBox.width;
765
+ let line;
766
+ if (isNone(highlightLine)) {
767
+ if (aBox.width / aBox.height > 4) {
768
+ line = "underline";
769
+ const viewBox = svgDom.getAttribute("viewBox");
770
+ if (viewBox) {
771
+ const aCenter = aBox.y + aBox.height / 2;
772
+ const [_x, y, _w, h] = viewBox.split(" ").map((v) => Number(v));
773
+ const vCenter = y + h / 2;
774
+ const diff = vCenter - aCenter;
775
+ if (Math.abs(diff) < aBox.height * 2) {
776
+ line = "line-through";
777
+ } else if (diff > 0) {
778
+ line = "overline";
779
+ } else {
780
+ line = "underline";
781
+ }
782
+ }
783
+ } else {
784
+ line = "outline";
785
+ }
751
786
  } else {
752
- type = "line";
753
- unitHeight = char.inlineBox.top - groupBox.top + char.underlinePosition;
787
+ line = highlightLine;
754
788
  }
755
- const transform = getTransformMatrix(
756
- box,
757
- refBox,
758
- new modernPath2d.BoundingBox(groupBox.left, groupBox.top, isVertical ? unitHeight : unitWidth, isVertical ? unitWidth : unitHeight),
759
- isVertical,
760
- type
761
- );
762
- const styleScale = fontSize / box.width * 2;
763
- const total = Math.ceil(groupBox.width / unitWidth);
764
- for (let i = 0; i < total; i++) {
765
- const _transform = transform.clone().translate(i * unitWidth, 0);
766
- svgPaths.forEach((original) => {
767
- const path = original.clone().matrix(_transform);
789
+ switch (line) {
790
+ case "outline": {
791
+ const paddingX = cBox.width * 0.2;
792
+ const paddingY = cBox.height * 0.2;
793
+ cBox.width += paddingX;
794
+ cBox.height += paddingY;
795
+ if (isVertical) {
796
+ cBox.x -= paddingY / 2;
797
+ cBox.y -= paddingX / 2;
798
+ cBox.x += cBox.height;
799
+ } else {
800
+ cBox.x -= paddingX / 2;
801
+ cBox.y -= paddingY / 2;
802
+ }
803
+ break;
804
+ }
805
+ case "overline":
806
+ cBox.height = char.underlineThickness * 2;
807
+ if (isVertical) {
808
+ cBox.x = char.inlineBox.left + char.inlineBox.width - cBox.height;
809
+ } else {
810
+ cBox.y = char.inlineBox.top;
811
+ }
812
+ break;
813
+ case "line-through":
814
+ cBox.height = char.strikeoutSize * 2;
815
+ if (isVertical) {
816
+ cBox.x = char.inlineBox.left + char.inlineBox.width - char.strikeoutPosition - cBox.height;
817
+ } else {
818
+ cBox.y = char.inlineBox.top + char.strikeoutPosition;
819
+ }
820
+ break;
821
+ case "underline":
822
+ cBox.height = char.underlineThickness * 2;
823
+ if (isVertical) {
824
+ cBox.x = char.inlineBox.left + char.inlineBox.width - char.underlinePosition - cBox.height;
825
+ } else {
826
+ cBox.y = char.inlineBox.top + char.underlinePosition;
827
+ }
828
+ break;
829
+ }
830
+ const transform = new modernPath2d.Matrix3().translate(-aBox.x, -aBox.y).scale(cBox.width / aBox.width, cBox.height / aBox.height);
831
+ if (isVertical) {
832
+ transform.rotate(-Math.PI / 2);
833
+ }
834
+ transform.translate(cBox.x, cBox.y);
835
+ const styleScale = fontSize / aBox.width;
836
+ for (let i = 0, len = Math.ceil(groupBox.width / width); i < len; i++) {
837
+ const _transform = transform.clone().translate(i * width, 0);
838
+ svgPaths.forEach((originalPath) => {
839
+ const path = originalPath.clone().matrix(_transform);
768
840
  if (path.style.strokeWidth) {
769
- path.style.strokeWidth *= styleScale * strokeWidthScale;
841
+ path.style.strokeWidth *= styleScale * thickness;
770
842
  }
771
843
  if (path.style.strokeMiterlimit) {
772
844
  path.style.strokeMiterlimit *= styleScale;
@@ -777,8 +849,14 @@ function highlight() {
777
849
  if (path.style.strokeDasharray) {
778
850
  path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
779
851
  }
852
+ if (path.style.fill && path.style.fill in colors) {
853
+ path.style.fill = colors[path.style.fill];
854
+ }
855
+ if (path.style.stroke && path.style.stroke in colors) {
856
+ path.style.stroke = colors[path.style.stroke];
857
+ }
780
858
  paths.push(path);
781
- clipRects[paths.length - 1] = highlightOverflow === "hidden" ? new modernPath2d.BoundingBox(
859
+ clipRects[paths.length - 1] = overflow === "hidden" ? new modernPath2d.BoundingBox(
782
860
  groupBox.left,
783
861
  groupBox.top - groupBox.height,
784
862
  groupBox.width,
@@ -797,6 +875,12 @@ function highlight() {
797
875
  clipRect: clipRects[index],
798
876
  fontSize: text.computedStyle.fontSize
799
877
  });
878
+ if (text.debug) {
879
+ const box = modernPath2d.getPathsBoundingBox([path]);
880
+ if (box) {
881
+ ctx.strokeRect(box.x, box.y, box.width, box.height);
882
+ }
883
+ }
800
884
  });
801
885
  }
802
886
  });
@@ -829,17 +913,31 @@ function listStyle() {
829
913
  const padding = fontSize * 0.45;
830
914
  paragraphs.forEach((paragraph) => {
831
915
  const { computedStyle: style } = paragraph;
832
- let listStyleSize = style.listStyleSize;
916
+ const { listStyleImage, listStyleImageColors, listStyleSize, listStyleType, color } = style;
917
+ const colors = Object.keys(listStyleImageColors).reduce((obj, key) => {
918
+ let value = listStyleImageColors[key];
919
+ const keyRgb = hexToRgb(key);
920
+ const valueRgb = hexToRgb(value);
921
+ if (keyRgb) {
922
+ key = keyRgb;
923
+ }
924
+ if (valueRgb) {
925
+ value = valueRgb;
926
+ }
927
+ obj[key] = value;
928
+ return obj;
929
+ }, {});
930
+ let size = listStyleSize;
833
931
  let image;
834
- if (!isNone(style.listStyleImage)) {
835
- image = style.listStyleImage;
836
- } else if (!isNone(style.listStyleType)) {
932
+ if (!isNone(listStyleImage)) {
933
+ image = listStyleImage;
934
+ } else if (!isNone(listStyleType)) {
837
935
  const r = fontSize * 0.38 / 2;
838
- listStyleSize = listStyleSize === "cover" ? r * 2 : listStyleSize;
839
- switch (style.listStyleType) {
936
+ size = size === "cover" ? r * 2 : size;
937
+ switch (listStyleType) {
840
938
  case "disc":
841
939
  image = `<svg width="${r * 2}" height="${r * 2}" xmlns="http://www.w3.org/2000/svg">
842
- <circle cx="${r}" cy="${r}" r="${r}" fill="${style.color}" />
940
+ <circle cx="${r}" cy="${r}" r="${r}" fill="${color}" />
843
941
  </svg>`;
844
942
  break;
845
943
  }
@@ -854,7 +952,7 @@ function listStyle() {
854
952
  if (fBox) {
855
953
  const m = new modernPath2d.Matrix3();
856
954
  if (isVertical) {
857
- const scale = parseScale(listStyleSize, fontSize, fontSize);
955
+ const scale = parseScale(size, fontSize, fontSize);
858
956
  const reScale = fontSize / imageBox.height * scale;
859
957
  m.translate(-imageBox.left, -imageBox.top);
860
958
  m.rotate(Math.PI / 2);
@@ -862,7 +960,7 @@ function listStyle() {
862
960
  m.translate(fontSize / 2 - imageBox.height * reScale / 2, 0);
863
961
  m.translate(box.left + (box.width - fontSize) / 2, fBox.top - padding);
864
962
  } else {
865
- const scale = parseScale(listStyleSize, fontSize, fontSize);
963
+ const scale = parseScale(size, fontSize, fontSize);
866
964
  const reScale = fontSize / imageBox.height * scale;
867
965
  m.translate(-imageBox.left, -imageBox.top);
868
966
  m.translate(-imageBox.width, 0);
@@ -870,7 +968,17 @@ function listStyle() {
870
968
  m.translate(0, fontSize / 2 - imageBox.height * reScale / 2);
871
969
  m.translate(fBox.left - padding, box.top + (box.height - fontSize) / 2);
872
970
  }
873
- paths.push(...imagePaths.map((p) => p.clone().matrix(m)));
971
+ paths.push(...imagePaths.map((p) => {
972
+ const path = p.clone();
973
+ path.matrix(m);
974
+ if (path.style.fill && path.style.fill in colors) {
975
+ path.style.fill = colors[path.style.fill];
976
+ }
977
+ if (path.style.stroke && path.style.stroke in colors) {
978
+ path.style.stroke = colors[path.style.stroke];
979
+ }
980
+ return path;
981
+ }));
874
982
  }
875
983
  });
876
984
  }
@@ -953,6 +1061,11 @@ function render() {
953
1061
  });
954
1062
  });
955
1063
  }
1064
+ if (text.debug) {
1065
+ paragraphs.forEach((paragraph) => {
1066
+ ctx.strokeRect(paragraph.lineBox.x, paragraph.lineBox.y, paragraph.lineBox.width, paragraph.lineBox.height);
1067
+ });
1068
+ }
956
1069
  }
957
1070
  });
958
1071
  }
@@ -992,7 +1105,7 @@ function textDecoration() {
992
1105
  const { computedStyle: style, isVertical, inlineBox, underlinePosition, underlineThickness, strikeoutPosition, strikeoutSize } = character;
993
1106
  if (!isNone(style.textDecoration)) {
994
1107
  let flag = false;
995
- if (prevStyle?.textDecoration === style.textDecoration && prevStyle?.writingMode === style.writingMode && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top)) {
1108
+ if (prevStyle?.textDecoration === style.textDecoration && prevStyle?.writingMode === style.writingMode && prevStyle?.color === style.color && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top)) {
996
1109
  switch (style.textDecoration) {
997
1110
  case "underline":
998
1111
  if (group[0].underlinePosition === underlinePosition && group[0].underlineThickness === underlineThickness) {
@@ -1020,38 +1133,48 @@ function textDecoration() {
1020
1133
  });
1021
1134
  groups.forEach((group2) => {
1022
1135
  const { computedStyle: style, isVertical, underlinePosition, underlineThickness, strikeoutPosition, strikeoutSize } = group2[0];
1023
- const { textDecoration: textDecoration2 } = style;
1136
+ const { textDecoration: textDecoration2, color } = style;
1024
1137
  const { left, top, width, height } = modernPath2d.BoundingBox.from(...group2.map((c) => c.inlineBox));
1025
- let strokePosition = isVertical ? left : top;
1026
- let strokeWidth = 0;
1138
+ let position = isVertical ? left + width : top;
1139
+ const direction = isVertical ? -1 : 1;
1140
+ let thickness = 0;
1027
1141
  switch (textDecoration2) {
1142
+ case "overline":
1143
+ thickness = underlineThickness * 2;
1144
+ break;
1028
1145
  case "underline":
1029
- strokePosition += underlinePosition;
1030
- strokeWidth = underlineThickness * 2;
1146
+ position += direction * underlinePosition;
1147
+ thickness = underlineThickness * 2;
1031
1148
  break;
1032
1149
  case "line-through":
1033
- strokePosition += strikeoutPosition;
1034
- strokeWidth = strikeoutSize * 2;
1150
+ position += direction * strikeoutPosition;
1151
+ thickness = strikeoutSize * 2;
1035
1152
  break;
1036
1153
  }
1037
- strokePosition -= strokeWidth;
1154
+ position -= thickness;
1155
+ let path;
1038
1156
  if (isVertical) {
1039
- paths.push(new modernPath2d.Path2D([
1040
- { type: "M", x: strokePosition, y: top },
1041
- { type: "L", x: strokePosition, y: top + height },
1042
- { type: "L", x: strokePosition + strokeWidth, y: top + height },
1043
- { type: "L", x: strokePosition + strokeWidth, y: top },
1157
+ path = new modernPath2d.Path2D([
1158
+ { type: "M", x: position, y: top },
1159
+ { type: "L", x: position, y: top + height },
1160
+ { type: "L", x: position + thickness, y: top + height },
1161
+ { type: "L", x: position + thickness, y: top },
1044
1162
  { type: "Z" }
1045
- ]));
1163
+ ], {
1164
+ fill: color
1165
+ });
1046
1166
  } else {
1047
- paths.push(new modernPath2d.Path2D([
1048
- { type: "M", x: left, y: strokePosition },
1049
- { type: "L", x: left + width, y: strokePosition },
1050
- { type: "L", x: left + width, y: strokePosition + strokeWidth },
1051
- { type: "L", x: left, y: strokePosition + strokeWidth },
1167
+ path = new modernPath2d.Path2D([
1168
+ { type: "M", x: left, y: position },
1169
+ { type: "L", x: left + width, y: position },
1170
+ { type: "L", x: left + width, y: position + thickness },
1171
+ { type: "L", x: left, y: position + thickness },
1052
1172
  { type: "Z" }
1053
- ]));
1173
+ ], {
1174
+ fill: color
1175
+ });
1054
1176
  }
1177
+ paths.push(path);
1055
1178
  });
1056
1179
  },
1057
1180
  render: (ctx, text) => {
@@ -1066,7 +1189,6 @@ function textDecoration() {
1066
1189
  ctx,
1067
1190
  path,
1068
1191
  fontSize: style.fontSize,
1069
- color: style.color,
1070
1192
  ...effectStyle
1071
1193
  });
1072
1194
  });
@@ -1077,8 +1199,7 @@ function textDecoration() {
1077
1199
  drawPath({
1078
1200
  ctx,
1079
1201
  path,
1080
- fontSize: style.fontSize,
1081
- color: style.color
1202
+ fontSize: style.fontSize
1082
1203
  });
1083
1204
  });
1084
1205
  }
@@ -1119,13 +1240,15 @@ const defaultTextStyles = {
1119
1240
  // listStyle
1120
1241
  listStyleType: "none",
1121
1242
  listStyleImage: "none",
1243
+ listStyleImageColors: {},
1122
1244
  listStyleSize: "cover",
1123
1245
  listStylePosition: "outside",
1124
1246
  // highlight
1125
- highlightReferImage: "none",
1126
1247
  highlightImage: "none",
1248
+ highlightImageColors: {},
1249
+ highlightLine: "none",
1127
1250
  highlightSize: "cover",
1128
- highlightStrokeWidth: "100%",
1251
+ highlightThickness: "100%",
1129
1252
  highlightOverflow: "none",
1130
1253
  // shadow
1131
1254
  shadowColor: "rgba(0, 0, 0, 0)",
@@ -1139,6 +1262,7 @@ const defaultTextStyles = {
1139
1262
  };
1140
1263
  class Text {
1141
1264
  constructor(options = {}) {
1265
+ __publicField(this, "debug");
1142
1266
  __publicField(this, "content");
1143
1267
  __publicField(this, "style");
1144
1268
  __publicField(this, "effects");
@@ -1154,12 +1278,12 @@ class Text {
1154
1278
  __publicField(this, "measurer", new Measurer(this));
1155
1279
  __publicField(this, "plugins", /* @__PURE__ */ new Map());
1156
1280
  __publicField(this, "fonts");
1157
- const { content = "", style = {}, measureDom, effects, fonts } = options;
1158
- this.content = content;
1159
- this.style = style;
1160
- this.measureDom = measureDom;
1161
- this.effects = effects;
1162
- this.fonts = fonts;
1281
+ this.debug = options.debug ?? false;
1282
+ this.content = options.content ?? "";
1283
+ this.style = options.style ?? {};
1284
+ this.measureDom = options.measureDom;
1285
+ this.effects = options.effects;
1286
+ this.fonts = options.fonts;
1163
1287
  this.use(listStyle()).use(textDecoration()).use(highlight()).use(render());
1164
1288
  }
1165
1289
  get fontSize() {
@@ -1367,7 +1491,9 @@ exports.definePlugin = definePlugin;
1367
1491
  exports.drawPath = drawPath;
1368
1492
  exports.filterEmpty = filterEmpty;
1369
1493
  exports.getTransform2D = getTransform2D;
1494
+ exports.hexToRgb = hexToRgb;
1370
1495
  exports.highlight = highlight;
1496
+ exports.isEqualObject = isEqualObject;
1371
1497
  exports.isNone = isNone;
1372
1498
  exports.listStyle = listStyle;
1373
1499
  exports.measureText = measureText;