modern-text 0.5.13 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +137 -67
- package/dist/index.d.cts +14 -14
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +4 -4
- package/dist/index.mjs +137 -68
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -454,6 +454,9 @@ function filterEmpty(val) {
|
|
|
454
454
|
}
|
|
455
455
|
return res;
|
|
456
456
|
}
|
|
457
|
+
function needsFetch(source) {
|
|
458
|
+
return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("blob://");
|
|
459
|
+
}
|
|
457
460
|
|
|
458
461
|
var __defProp$3 = Object.defineProperty;
|
|
459
462
|
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -524,9 +527,6 @@ function definePlugin(options) {
|
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
class Measurer {
|
|
527
|
-
constructor(_text) {
|
|
528
|
-
this._text = _text;
|
|
529
|
-
}
|
|
530
530
|
_styleToDomStyle(style) {
|
|
531
531
|
const _style = { ...style };
|
|
532
532
|
for (const key in style) {
|
|
@@ -558,8 +558,7 @@ class Measurer {
|
|
|
558
558
|
* </ul>
|
|
559
559
|
* </section>
|
|
560
560
|
*/
|
|
561
|
-
createDom() {
|
|
562
|
-
const { paragraphs, computedStyle } = this._text;
|
|
561
|
+
createDom(paragraphs, rootStyle) {
|
|
563
562
|
const documentFragment = document.createDocumentFragment();
|
|
564
563
|
const dom = document.createElement("section");
|
|
565
564
|
Object.assign(dom.style, {
|
|
@@ -567,7 +566,7 @@ class Measurer {
|
|
|
567
566
|
height: "max-content",
|
|
568
567
|
whiteSpace: "pre-wrap",
|
|
569
568
|
wordBreak: "break-all",
|
|
570
|
-
...this._styleToDomStyle(
|
|
569
|
+
...this._styleToDomStyle(rootStyle),
|
|
571
570
|
position: "fixed",
|
|
572
571
|
visibility: "hidden"
|
|
573
572
|
});
|
|
@@ -662,8 +661,7 @@ class Measurer {
|
|
|
662
661
|
characters
|
|
663
662
|
};
|
|
664
663
|
}
|
|
665
|
-
measureDom(dom) {
|
|
666
|
-
const { paragraphs } = this._text;
|
|
664
|
+
measureDom(paragraphs, dom) {
|
|
667
665
|
const rect = dom.getBoundingClientRect();
|
|
668
666
|
const measured = this._measureDom(dom);
|
|
669
667
|
measured.paragraphs.forEach((p) => {
|
|
@@ -715,12 +713,12 @@ class Measurer {
|
|
|
715
713
|
boundingBox: new BoundingBox(0, 0, rect.width, rect.height)
|
|
716
714
|
};
|
|
717
715
|
}
|
|
718
|
-
measure(dom) {
|
|
716
|
+
measure(paragraphs, rootStyle, dom) {
|
|
719
717
|
let destory;
|
|
720
718
|
if (!dom) {
|
|
721
|
-
({ dom, destory } = this.createDom());
|
|
719
|
+
({ dom, destory } = this.createDom(paragraphs, rootStyle));
|
|
722
720
|
}
|
|
723
|
-
const result = this.measureDom(dom);
|
|
721
|
+
const result = this.measureDom(paragraphs, dom);
|
|
724
722
|
destory?.();
|
|
725
723
|
return result;
|
|
726
724
|
}
|
|
@@ -823,33 +821,74 @@ class EventEmitter {
|
|
|
823
821
|
function highlight() {
|
|
824
822
|
const paths = [];
|
|
825
823
|
const clipRects = [];
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
if (!
|
|
830
|
-
|
|
831
|
-
|
|
824
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
825
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
826
|
+
async function loadSvg(svg) {
|
|
827
|
+
if (!loaded.has(svg)) {
|
|
828
|
+
loaded.set(svg, svg);
|
|
829
|
+
try {
|
|
830
|
+
loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
|
|
831
|
+
} catch (err) {
|
|
832
|
+
console.warn(err);
|
|
833
|
+
loaded.delete(svg);
|
|
832
834
|
}
|
|
833
|
-
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function getPaths(svg) {
|
|
838
|
+
let result = parsed.get(svg);
|
|
839
|
+
if (!result) {
|
|
840
|
+
const dom = parseSvgToDom(
|
|
841
|
+
needsFetch(svg) ? loaded.get(svg) ?? svg : svg
|
|
842
|
+
);
|
|
834
843
|
const paths2 = parseSvg(dom);
|
|
835
844
|
result = { dom, paths: paths2 };
|
|
836
|
-
|
|
845
|
+
parsed.set(svg, result);
|
|
837
846
|
}
|
|
838
847
|
return result;
|
|
839
848
|
}
|
|
840
849
|
return definePlugin({
|
|
841
850
|
name: "highlight",
|
|
842
851
|
paths,
|
|
843
|
-
|
|
852
|
+
load: async (text) => {
|
|
853
|
+
const promises = [];
|
|
854
|
+
text.forEachCharacter((character) => {
|
|
855
|
+
const { computedStyle: style } = character;
|
|
856
|
+
const { highlightImage, highlightReferImage } = style;
|
|
857
|
+
if (needsFetch(highlightImage)) {
|
|
858
|
+
promises.push(loadSvg(highlightImage));
|
|
859
|
+
}
|
|
860
|
+
if (needsFetch(highlightReferImage)) {
|
|
861
|
+
promises.push(loadSvg(highlightReferImage));
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
await Promise.all(promises);
|
|
865
|
+
},
|
|
866
|
+
update: (text) => {
|
|
844
867
|
clipRects.length = 0;
|
|
845
868
|
paths.length = 0;
|
|
846
869
|
let groups = [];
|
|
847
870
|
let group;
|
|
848
871
|
let prevStyle;
|
|
849
872
|
text.forEachCharacter((character) => {
|
|
850
|
-
const {
|
|
851
|
-
|
|
852
|
-
|
|
873
|
+
const {
|
|
874
|
+
computedStyle: style
|
|
875
|
+
} = character;
|
|
876
|
+
const {
|
|
877
|
+
highlightImage
|
|
878
|
+
} = style;
|
|
879
|
+
if (!isNone(highlightImage)) {
|
|
880
|
+
const {
|
|
881
|
+
inlineBox,
|
|
882
|
+
isVertical
|
|
883
|
+
} = character;
|
|
884
|
+
const {
|
|
885
|
+
fontSize,
|
|
886
|
+
highlightColormap,
|
|
887
|
+
highlightLine,
|
|
888
|
+
highlightSize,
|
|
889
|
+
highlightThickness
|
|
890
|
+
} = style;
|
|
891
|
+
if ((!prevStyle || isEqualValue(prevStyle.highlightImage, highlightImage) && isEqualValue(prevStyle.highlightColormap, highlightColormap) && isEqualValue(prevStyle.highlightLine, highlightLine) && isEqualValue(prevStyle.highlightSize, highlightSize) && isEqualValue(prevStyle.highlightThickness, highlightThickness)) && group?.length && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top) && group[0].fontSize === fontSize) {
|
|
853
892
|
group.push(character);
|
|
854
893
|
} else {
|
|
855
894
|
group = [];
|
|
@@ -868,8 +907,12 @@ function highlight() {
|
|
|
868
907
|
for (let i = 0; i < groups.length; i++) {
|
|
869
908
|
const characters = groups[i];
|
|
870
909
|
const char = characters[0];
|
|
871
|
-
const groupBox = BoundingBox.from(
|
|
872
|
-
|
|
910
|
+
const groupBox = BoundingBox.from(
|
|
911
|
+
...characters.filter((c) => c.glyphBox).map((c) => c.glyphBox)
|
|
912
|
+
);
|
|
913
|
+
const {
|
|
914
|
+
computedStyle: style
|
|
915
|
+
} = char;
|
|
873
916
|
const {
|
|
874
917
|
fontSize,
|
|
875
918
|
writingMode,
|
|
@@ -883,7 +926,7 @@ function highlight() {
|
|
|
883
926
|
const isVertical = writingMode.includes("vertical");
|
|
884
927
|
const thickness = parseValueNumber(highlightThickness, { fontSize, total: groupBox.width }) / groupBox.width;
|
|
885
928
|
const colormap = parseColormap(highlightColormap);
|
|
886
|
-
const { paths: svgPaths, dom: svgDom } =
|
|
929
|
+
const { paths: svgPaths, dom: svgDom } = getPaths(highlightImage);
|
|
887
930
|
const aBox = getPathsBoundingBox(svgPaths, true);
|
|
888
931
|
const styleScale = fontSize / aBox.width * 2;
|
|
889
932
|
const cBox = new BoundingBox().copy(groupBox);
|
|
@@ -899,7 +942,7 @@ function highlight() {
|
|
|
899
942
|
cBox.width = userWidth;
|
|
900
943
|
}
|
|
901
944
|
if (!isNone(highlightReferImage) && isNone(highlightLine)) {
|
|
902
|
-
const bBox = getPathsBoundingBox(
|
|
945
|
+
const bBox = getPathsBoundingBox(getPaths(highlightReferImage).paths, true);
|
|
903
946
|
aBox.copy(bBox);
|
|
904
947
|
} else {
|
|
905
948
|
let line;
|
|
@@ -1050,8 +1093,16 @@ function listStyle() {
|
|
|
1050
1093
|
const { paragraphs, isVertical, fontSize } = text;
|
|
1051
1094
|
const padding = fontSize * 0.45;
|
|
1052
1095
|
paragraphs.forEach((paragraph) => {
|
|
1053
|
-
const {
|
|
1054
|
-
|
|
1096
|
+
const {
|
|
1097
|
+
computedStyle: style
|
|
1098
|
+
} = paragraph;
|
|
1099
|
+
const {
|
|
1100
|
+
color,
|
|
1101
|
+
listStyleImage,
|
|
1102
|
+
listStyleColormap,
|
|
1103
|
+
listStyleSize,
|
|
1104
|
+
listStyleType
|
|
1105
|
+
} = style;
|
|
1055
1106
|
const colormap = parseColormap(listStyleColormap);
|
|
1056
1107
|
let size = listStyleSize;
|
|
1057
1108
|
let image;
|
|
@@ -1080,18 +1131,10 @@ function listStyle() {
|
|
|
1080
1131
|
const m = new Matrix3();
|
|
1081
1132
|
if (isVertical) {
|
|
1082
1133
|
const reScale = fontSize / imageBox.height * scale;
|
|
1083
|
-
m.translate(-imageBox.left, -imageBox.top);
|
|
1084
|
-
m.rotate(Math.PI / 2);
|
|
1085
|
-
m.scale(reScale, reScale);
|
|
1086
|
-
m.translate(fontSize / 2 - imageBox.height * reScale / 2, 0);
|
|
1087
|
-
m.translate(box.left + (box.width - fontSize) / 2, fBox.top - padding);
|
|
1134
|
+
m.translate(-imageBox.left, -imageBox.top).rotate(Math.PI / 2).scale(reScale, reScale).translate(fontSize / 2 - imageBox.height * reScale / 2, 0).translate(box.left + (box.width - fontSize) / 2, fBox.top - padding);
|
|
1088
1135
|
} else {
|
|
1089
1136
|
const reScale = fontSize / imageBox.height * scale;
|
|
1090
|
-
m.translate(-imageBox.left, -imageBox.top);
|
|
1091
|
-
m.translate(-imageBox.width, 0);
|
|
1092
|
-
m.scale(reScale, reScale);
|
|
1093
|
-
m.translate(0, fontSize / 2 - imageBox.height * reScale / 2);
|
|
1094
|
-
m.translate(fBox.left - padding, box.top + (box.height - fontSize) / 2);
|
|
1137
|
+
m.translate(-imageBox.left, -imageBox.top).translate(-imageBox.width, 0).scale(reScale, reScale).translate(0, fontSize / 2 - imageBox.height * reScale / 2).translate(fBox.left - padding, box.top + (box.height - fontSize) / 2);
|
|
1095
1138
|
}
|
|
1096
1139
|
paths.push(...imagePaths.map((p) => {
|
|
1097
1140
|
const path = p.clone();
|
|
@@ -1227,11 +1270,24 @@ function textDecoration() {
|
|
|
1227
1270
|
let group;
|
|
1228
1271
|
let prevStyle;
|
|
1229
1272
|
text.forEachCharacter((character) => {
|
|
1230
|
-
const {
|
|
1231
|
-
|
|
1273
|
+
const {
|
|
1274
|
+
computedStyle: style,
|
|
1275
|
+
isVertical,
|
|
1276
|
+
inlineBox,
|
|
1277
|
+
underlinePosition,
|
|
1278
|
+
underlineThickness,
|
|
1279
|
+
strikeoutPosition,
|
|
1280
|
+
strikeoutSize
|
|
1281
|
+
} = character;
|
|
1282
|
+
const {
|
|
1283
|
+
color,
|
|
1284
|
+
textDecoration: textDecoration2,
|
|
1285
|
+
writingMode
|
|
1286
|
+
} = style;
|
|
1287
|
+
if (!isNone(textDecoration2)) {
|
|
1232
1288
|
let flag = false;
|
|
1233
|
-
if (prevStyle?.textDecoration ===
|
|
1234
|
-
switch (
|
|
1289
|
+
if (prevStyle?.textDecoration === textDecoration2 && prevStyle?.writingMode === writingMode && prevStyle?.color === color && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top)) {
|
|
1290
|
+
switch (textDecoration2) {
|
|
1235
1291
|
case "underline":
|
|
1236
1292
|
if (group[0].underlinePosition === underlinePosition && group[0].underlineThickness === underlineThickness) {
|
|
1237
1293
|
flag = true;
|
|
@@ -1257,8 +1313,18 @@ function textDecoration() {
|
|
|
1257
1313
|
}
|
|
1258
1314
|
});
|
|
1259
1315
|
groups.forEach((group2) => {
|
|
1260
|
-
const {
|
|
1261
|
-
|
|
1316
|
+
const {
|
|
1317
|
+
computedStyle: style,
|
|
1318
|
+
isVertical,
|
|
1319
|
+
underlinePosition,
|
|
1320
|
+
underlineThickness,
|
|
1321
|
+
strikeoutPosition,
|
|
1322
|
+
strikeoutSize
|
|
1323
|
+
} = group2[0];
|
|
1324
|
+
const {
|
|
1325
|
+
color,
|
|
1326
|
+
textDecoration: textDecoration2
|
|
1327
|
+
} = style;
|
|
1262
1328
|
const { left, top, width, height } = BoundingBox.from(...group2.map((c) => c.inlineBox));
|
|
1263
1329
|
let position = isVertical ? left + width : top;
|
|
1264
1330
|
const direction = isVertical ? -1 : 1;
|
|
@@ -1401,7 +1467,7 @@ class Text extends EventEmitter {
|
|
|
1401
1467
|
__publicField(this, "glyphBox", new BoundingBox());
|
|
1402
1468
|
__publicField(this, "pathBox", new BoundingBox());
|
|
1403
1469
|
__publicField(this, "boundingBox", new BoundingBox());
|
|
1404
|
-
__publicField(this, "measurer", new Measurer(
|
|
1470
|
+
__publicField(this, "measurer", new Measurer());
|
|
1405
1471
|
__publicField(this, "plugins", /* @__PURE__ */ new Map());
|
|
1406
1472
|
__publicField(this, "fonts");
|
|
1407
1473
|
this.debug = options.debug ?? false;
|
|
@@ -1435,6 +1501,9 @@ class Text extends EventEmitter {
|
|
|
1435
1501
|
});
|
|
1436
1502
|
return this;
|
|
1437
1503
|
}
|
|
1504
|
+
async load() {
|
|
1505
|
+
await Promise.all(Array.from(this.plugins.values()).map((p) => p.load?.(this)));
|
|
1506
|
+
}
|
|
1438
1507
|
updateParagraphs() {
|
|
1439
1508
|
this.computedStyle = { ...defaultTextStyles, ...this.style };
|
|
1440
1509
|
let { content, computedStyle: style } = this;
|
|
@@ -1486,7 +1555,7 @@ class Text extends EventEmitter {
|
|
|
1486
1555
|
this.paragraphs = paragraphs;
|
|
1487
1556
|
return this;
|
|
1488
1557
|
}
|
|
1489
|
-
|
|
1558
|
+
measure(dom = this.measureDom) {
|
|
1490
1559
|
const old = {
|
|
1491
1560
|
paragraphs: this.paragraphs,
|
|
1492
1561
|
lineBox: this.lineBox,
|
|
@@ -1496,18 +1565,16 @@ class Text extends EventEmitter {
|
|
|
1496
1565
|
boundingBox: this.boundingBox
|
|
1497
1566
|
};
|
|
1498
1567
|
this.updateParagraphs();
|
|
1499
|
-
const result = this.measurer.measure(dom);
|
|
1568
|
+
const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom);
|
|
1500
1569
|
this.paragraphs = result.paragraphs;
|
|
1501
1570
|
this.lineBox = result.boundingBox;
|
|
1502
1571
|
this.characters.forEach((c) => {
|
|
1503
1572
|
c.update(this.fonts);
|
|
1504
1573
|
});
|
|
1505
1574
|
this.rawGlyphBox = this.getGlyphBox();
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
await plugins[i].update?.(this);
|
|
1510
|
-
}
|
|
1575
|
+
Array.from(this.plugins.values()).sort((a, b) => (a.updateOrder ?? 0) - (b.updateOrder ?? 0)).forEach((plugin) => {
|
|
1576
|
+
plugin.update?.(this);
|
|
1577
|
+
});
|
|
1511
1578
|
this.glyphBox = this.getGlyphBox();
|
|
1512
1579
|
this.updatePathBox().updateBoundingBox();
|
|
1513
1580
|
for (const key in old) {
|
|
@@ -1563,30 +1630,28 @@ class Text extends EventEmitter {
|
|
|
1563
1630
|
this.needsUpdate = true;
|
|
1564
1631
|
return this;
|
|
1565
1632
|
}
|
|
1566
|
-
|
|
1567
|
-
const result =
|
|
1633
|
+
update() {
|
|
1634
|
+
const result = this.measure();
|
|
1568
1635
|
for (const key in result) {
|
|
1569
1636
|
this[key] = result[key];
|
|
1570
1637
|
}
|
|
1571
1638
|
this.emit("update", { text: this });
|
|
1639
|
+
return this;
|
|
1572
1640
|
}
|
|
1573
|
-
|
|
1641
|
+
render(options) {
|
|
1574
1642
|
const { view, pixelRatio = 2 } = options;
|
|
1575
1643
|
const ctx = view.getContext("2d");
|
|
1576
1644
|
if (!ctx) {
|
|
1577
1645
|
return;
|
|
1578
1646
|
}
|
|
1579
1647
|
if (this.needsUpdate) {
|
|
1580
|
-
|
|
1648
|
+
this.update();
|
|
1581
1649
|
}
|
|
1582
1650
|
setupView(ctx, pixelRatio, this.boundingBox);
|
|
1583
1651
|
uploadColors(ctx, this);
|
|
1584
|
-
|
|
1585
|
-
plugins.sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0));
|
|
1586
|
-
for (let i = 0; i < plugins.length; i++) {
|
|
1587
|
-
const plugin = plugins[i];
|
|
1652
|
+
Array.from(this.plugins.values()).sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
|
|
1588
1653
|
if (plugin.render) {
|
|
1589
|
-
|
|
1654
|
+
plugin.render?.(ctx, this);
|
|
1590
1655
|
} else if (plugin.paths) {
|
|
1591
1656
|
const style = this.computedStyle;
|
|
1592
1657
|
plugin.paths.forEach((path) => {
|
|
@@ -1597,17 +1662,21 @@ class Text extends EventEmitter {
|
|
|
1597
1662
|
});
|
|
1598
1663
|
});
|
|
1599
1664
|
}
|
|
1600
|
-
}
|
|
1665
|
+
});
|
|
1601
1666
|
this.emit("render", { text: this, view, pixelRatio });
|
|
1602
1667
|
}
|
|
1603
1668
|
}
|
|
1604
1669
|
|
|
1605
|
-
function measureText(options) {
|
|
1606
|
-
|
|
1670
|
+
async function measureText(options) {
|
|
1671
|
+
const text = new Text(options);
|
|
1672
|
+
await text.load();
|
|
1673
|
+
return text.measure();
|
|
1607
1674
|
}
|
|
1608
1675
|
|
|
1609
|
-
function renderText(options) {
|
|
1610
|
-
|
|
1676
|
+
async function renderText(options) {
|
|
1677
|
+
const text = new Text(options);
|
|
1678
|
+
await text.load();
|
|
1679
|
+
text.render(options);
|
|
1611
1680
|
}
|
|
1612
1681
|
|
|
1613
|
-
export { Character, Fragment, Measurer, Paragraph, Text, defaultTextStyles, definePlugin, drawPath, filterEmpty, getTransform2D, hexToRgb, highlight, isEqualObject, isEqualValue, isNone, listStyle, measureText, parseColor, parseColormap, parseValueNumber, render, renderText, setupView, textDecoration, uploadColor, uploadColors };
|
|
1682
|
+
export { Character, Fragment, Measurer, Paragraph, Text, defaultTextStyles, definePlugin, drawPath, filterEmpty, getTransform2D, hexToRgb, highlight, isEqualObject, isEqualValue, isNone, listStyle, measureText, needsFetch, parseColor, parseColormap, parseValueNumber, render, renderText, setupView, textDecoration, uploadColor, uploadColors };
|