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/README.md +23 -20
- package/dist/index.cjs +227 -148
- package/dist/index.d.cts +23 -4
- package/dist/index.d.mts +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +5 -5
- package/dist/index.mjs +227 -149
- package/package.json +12 -12
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Path2D, BoundingBox, setCanvasContext,
|
|
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 = "
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
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
|
|
998
|
+
const pathSet = new Path2DSet();
|
|
902
999
|
const clipRects = [];
|
|
903
|
-
const
|
|
904
|
-
const
|
|
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
|
-
|
|
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 (
|
|
1010
|
+
if (loader.needsLoad(image)) {
|
|
937
1011
|
set.add(image);
|
|
938
1012
|
}
|
|
939
|
-
if (
|
|
1013
|
+
if (loader.needsLoad(referImage)) {
|
|
940
1014
|
set.add(referImage);
|
|
941
1015
|
}
|
|
942
1016
|
});
|
|
943
|
-
await Promise.all(Array.from(set).map((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:
|
|
1012
|
-
const
|
|
1013
|
-
const styleScale = fontSize /
|
|
1014
|
-
const
|
|
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
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1093
|
+
targetBox.width = groupBox.height;
|
|
1094
|
+
targetBox.height = groupBox.width;
|
|
1095
|
+
targetBox.left = groupBox.left + groupBox.width;
|
|
1019
1096
|
}
|
|
1020
|
-
const rawWidth = Math.floor(
|
|
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
|
-
|
|
1101
|
+
targetBox.width = userWidth;
|
|
1025
1102
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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 (
|
|
1111
|
+
if (imageBox.width / imageBox.height > 4) {
|
|
1033
1112
|
_line = "underline";
|
|
1034
|
-
const 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 (
|
|
1117
|
+
if (imageBox.y < viewCenter && imageBox.y + imageBox.height > viewCenter) {
|
|
1039
1118
|
_line = "line-through";
|
|
1040
|
-
} else if (
|
|
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 =
|
|
1055
|
-
const paddingY =
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1136
|
+
targetBox.x -= paddingY / 2;
|
|
1137
|
+
targetBox.y -= paddingX / 2;
|
|
1138
|
+
targetBox.x -= targetBox.height;
|
|
1062
1139
|
} else {
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
1148
|
+
targetBox.height = imageBox.height * styleScale;
|
|
1070
1149
|
if (isVertical) {
|
|
1071
|
-
|
|
1150
|
+
targetBox.x = inlineBox.left + inlineBox.width;
|
|
1072
1151
|
} else {
|
|
1073
|
-
|
|
1152
|
+
targetBox.y = inlineBox.top;
|
|
1074
1153
|
}
|
|
1075
1154
|
break;
|
|
1076
1155
|
case "line-through":
|
|
1077
|
-
|
|
1156
|
+
targetBox.height = imageBox.height * styleScale;
|
|
1078
1157
|
if (isVertical) {
|
|
1079
|
-
|
|
1158
|
+
targetBox.x = inlineBox.left + inlineBox.width - strikeoutPosition + targetBox.height / 2;
|
|
1080
1159
|
} else {
|
|
1081
|
-
|
|
1160
|
+
targetBox.y = inlineBox.top + strikeoutPosition - targetBox.height / 2;
|
|
1082
1161
|
}
|
|
1083
1162
|
break;
|
|
1084
1163
|
case "underline":
|
|
1085
|
-
|
|
1164
|
+
targetBox.height = imageBox.height * styleScale;
|
|
1086
1165
|
if (isVertical) {
|
|
1087
|
-
|
|
1166
|
+
targetBox.x = glyphBox.left + glyphBox.width - underlinePosition;
|
|
1088
1167
|
} else {
|
|
1089
|
-
|
|
1168
|
+
targetBox.y = inlineBox.top + underlinePosition;
|
|
1090
1169
|
}
|
|
1091
1170
|
break;
|
|
1092
1171
|
}
|
|
1093
1172
|
}
|
|
1094
|
-
const transform = new Matrix3()
|
|
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(
|
|
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 *
|
|
1191
|
+
_transform.translate(0, i2 * targetBox.width);
|
|
1103
1192
|
} else {
|
|
1104
|
-
_transform.translate(i2 *
|
|
1193
|
+
_transform.translate(i2 * targetBox.width, 0);
|
|
1105
1194
|
}
|
|
1106
|
-
|
|
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
|
|
1275
|
+
const pathSet = new Path2DSet();
|
|
1175
1276
|
return definePlugin({
|
|
1176
1277
|
name: "listStyle",
|
|
1177
|
-
|
|
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
|
|
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((
|
|
1311
|
-
uploadColor(
|
|
1399
|
+
effects.forEach((style) => {
|
|
1400
|
+
uploadColor(style, glyphBox, ctx);
|
|
1312
1401
|
ctx.save();
|
|
1313
|
-
const [a, c, e, b, d, f] = getTransform2D(text,
|
|
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
|
-
|
|
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
|
|
1449
|
+
const pathSet = new Path2DSet();
|
|
1367
1450
|
return definePlugin({
|
|
1368
1451
|
name: "textDecoration",
|
|
1369
|
-
|
|
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
|
|
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) :
|
|
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
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
61
|
-
"modern-idoc": "^0.2.
|
|
62
|
-
"modern-path2d": "^1.2.
|
|
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.
|
|
66
|
-
"@types/node": "^22.13.
|
|
67
|
-
"bumpp": "^10.0
|
|
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.
|
|
70
|
-
"lint-staged": "^15.
|
|
71
|
-
"simple-git-hooks": "^2.
|
|
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.
|
|
75
|
-
"vitest": "^3.0.
|
|
74
|
+
"vite": "^6.2.3",
|
|
75
|
+
"vitest": "^3.0.9"
|
|
76
76
|
},
|
|
77
77
|
"simple-git-hooks": {
|
|
78
78
|
"pre-commit": "pnpm lint-staged"
|