modern-text 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -20
- package/dist/index.cjs +216 -139
- 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 +216 -140
- 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) {
|
|
@@ -874,9 +914,64 @@ class EventEmitter {
|
|
|
874
914
|
}
|
|
875
915
|
|
|
876
916
|
function background() {
|
|
917
|
+
const pathSet = new Path2DSet();
|
|
918
|
+
const loader = createSVGLoader();
|
|
919
|
+
const parser = createSVGParser(loader);
|
|
877
920
|
return {
|
|
878
|
-
name: "background"
|
|
879
|
-
|
|
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
|
+
}
|
|
880
975
|
};
|
|
881
976
|
}
|
|
882
977
|
|
|
@@ -900,53 +995,30 @@ function getHighlightStyle(style) {
|
|
|
900
995
|
};
|
|
901
996
|
}
|
|
902
997
|
function highlight() {
|
|
903
|
-
const
|
|
998
|
+
const pathSet = new Path2DSet();
|
|
904
999
|
const clipRects = [];
|
|
905
|
-
const
|
|
906
|
-
const
|
|
907
|
-
async function loadSvg(svg) {
|
|
908
|
-
if (!loaded.has(svg)) {
|
|
909
|
-
loaded.set(svg, svg);
|
|
910
|
-
try {
|
|
911
|
-
loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
|
|
912
|
-
} catch (err) {
|
|
913
|
-
console.warn(err);
|
|
914
|
-
loaded.delete(svg);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
function getPaths(svg) {
|
|
919
|
-
let result = parsed.get(svg);
|
|
920
|
-
if (!result) {
|
|
921
|
-
const dom = svgToDOM(
|
|
922
|
-
needsFetch(svg) ? loaded.get(svg) ?? svg : svg
|
|
923
|
-
);
|
|
924
|
-
const pathSet = svgToPath2DSet(dom);
|
|
925
|
-
result = { dom, pathSet };
|
|
926
|
-
parsed.set(svg, result);
|
|
927
|
-
}
|
|
928
|
-
return result;
|
|
929
|
-
}
|
|
1000
|
+
const loader = createSVGLoader();
|
|
1001
|
+
const parser = createSVGParser(loader);
|
|
930
1002
|
return definePlugin({
|
|
931
1003
|
name: "highlight",
|
|
932
|
-
|
|
1004
|
+
pathSet,
|
|
933
1005
|
load: async (text) => {
|
|
934
1006
|
const set = /* @__PURE__ */ new Set();
|
|
935
1007
|
text.forEachCharacter((character) => {
|
|
936
1008
|
const { computedStyle: style } = character;
|
|
937
1009
|
const { image, referImage } = getHighlightStyle(style);
|
|
938
|
-
if (
|
|
1010
|
+
if (loader.needsLoad(image)) {
|
|
939
1011
|
set.add(image);
|
|
940
1012
|
}
|
|
941
|
-
if (
|
|
1013
|
+
if (loader.needsLoad(referImage)) {
|
|
942
1014
|
set.add(referImage);
|
|
943
1015
|
}
|
|
944
1016
|
});
|
|
945
|
-
await Promise.all(Array.from(set).map((src) =>
|
|
1017
|
+
await Promise.all(Array.from(set).map((src) => loader.load(src)));
|
|
946
1018
|
},
|
|
947
1019
|
update: (text) => {
|
|
948
1020
|
clipRects.length = 0;
|
|
949
|
-
paths.length = 0;
|
|
1021
|
+
pathSet.paths.length = 0;
|
|
950
1022
|
let groups = [];
|
|
951
1023
|
let group;
|
|
952
1024
|
let prevHighlight;
|
|
@@ -993,11 +1065,15 @@ function highlight() {
|
|
|
993
1065
|
...characters.filter((c) => c.glyphBox).map((c) => c.glyphBox)
|
|
994
1066
|
);
|
|
995
1067
|
const {
|
|
996
|
-
computedStyle: style
|
|
1068
|
+
computedStyle: style,
|
|
1069
|
+
isVertical,
|
|
1070
|
+
inlineBox,
|
|
1071
|
+
glyphBox = inlineBox,
|
|
1072
|
+
strikeoutPosition,
|
|
1073
|
+
underlinePosition
|
|
997
1074
|
} = char;
|
|
998
1075
|
const {
|
|
999
|
-
fontSize
|
|
1000
|
-
writingMode
|
|
1076
|
+
fontSize
|
|
1001
1077
|
} = style;
|
|
1002
1078
|
const {
|
|
1003
1079
|
image,
|
|
@@ -1007,39 +1083,40 @@ function highlight() {
|
|
|
1007
1083
|
size,
|
|
1008
1084
|
thickness
|
|
1009
1085
|
} = getHighlightStyle(style);
|
|
1010
|
-
const isVertical = writingMode.includes("vertical");
|
|
1011
1086
|
const _thickness = parseValueNumber(thickness, { fontSize, total: groupBox.width }) / groupBox.width;
|
|
1012
1087
|
const _colormap = parseColormap(colormap);
|
|
1013
|
-
const { pathSet:
|
|
1014
|
-
const
|
|
1015
|
-
const styleScale = fontSize /
|
|
1016
|
-
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);
|
|
1017
1092
|
if (isVertical) {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1093
|
+
targetBox.width = groupBox.height;
|
|
1094
|
+
targetBox.height = groupBox.width;
|
|
1095
|
+
targetBox.left = groupBox.left + groupBox.width;
|
|
1021
1096
|
}
|
|
1022
|
-
const rawWidth = Math.floor(
|
|
1097
|
+
const rawWidth = Math.floor(targetBox.width);
|
|
1023
1098
|
let userWidth = rawWidth;
|
|
1024
1099
|
if (size !== "cover") {
|
|
1025
1100
|
userWidth = parseValueNumber(size, { fontSize, total: groupBox.width }) || rawWidth;
|
|
1026
|
-
|
|
1101
|
+
targetBox.width = userWidth;
|
|
1027
1102
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1103
|
+
const hasReferImage = !isNone(referImage) && isNone(line);
|
|
1104
|
+
if (hasReferImage) {
|
|
1105
|
+
imageBox.copy(
|
|
1106
|
+
parser.parse(referImage).pathSet.getBoundingBox(true)
|
|
1107
|
+
);
|
|
1031
1108
|
} else {
|
|
1032
1109
|
let _line;
|
|
1033
1110
|
if (isNone(line)) {
|
|
1034
|
-
if (
|
|
1111
|
+
if (imageBox.width / imageBox.height > 4) {
|
|
1035
1112
|
_line = "underline";
|
|
1036
|
-
const viewBox =
|
|
1113
|
+
const viewBox = imageDom.getAttribute("viewBox");
|
|
1037
1114
|
if (viewBox) {
|
|
1038
1115
|
const [_x, y, _w, h] = viewBox.split(" ").map((v) => Number(v));
|
|
1039
1116
|
const viewCenter = y + h / 2;
|
|
1040
|
-
if (
|
|
1117
|
+
if (imageBox.y < viewCenter && imageBox.y + imageBox.height > viewCenter) {
|
|
1041
1118
|
_line = "line-through";
|
|
1042
|
-
} else if (
|
|
1119
|
+
} else if (imageBox.y + imageBox.height < viewCenter) {
|
|
1043
1120
|
_line = "overline";
|
|
1044
1121
|
} else {
|
|
1045
1122
|
_line = "underline";
|
|
@@ -1053,89 +1130,95 @@ function highlight() {
|
|
|
1053
1130
|
}
|
|
1054
1131
|
switch (_line) {
|
|
1055
1132
|
case "outline": {
|
|
1056
|
-
const paddingX =
|
|
1057
|
-
const paddingY =
|
|
1058
|
-
cBox.width += paddingX;
|
|
1059
|
-
cBox.height += paddingY;
|
|
1133
|
+
const paddingX = targetBox.width * 0.2;
|
|
1134
|
+
const paddingY = targetBox.height * 0.2;
|
|
1060
1135
|
if (isVertical) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1136
|
+
targetBox.x -= paddingY / 2;
|
|
1137
|
+
targetBox.y -= paddingX / 2;
|
|
1138
|
+
targetBox.x -= targetBox.height;
|
|
1064
1139
|
} else {
|
|
1065
|
-
|
|
1066
|
-
|
|
1140
|
+
targetBox.x -= paddingX / 2;
|
|
1141
|
+
targetBox.y -= paddingY / 2;
|
|
1067
1142
|
}
|
|
1143
|
+
targetBox.width += paddingX;
|
|
1144
|
+
targetBox.height += paddingY;
|
|
1068
1145
|
break;
|
|
1069
1146
|
}
|
|
1070
1147
|
case "overline":
|
|
1071
|
-
|
|
1148
|
+
targetBox.height = imageBox.height * styleScale;
|
|
1072
1149
|
if (isVertical) {
|
|
1073
|
-
|
|
1150
|
+
targetBox.x = inlineBox.left + inlineBox.width;
|
|
1074
1151
|
} else {
|
|
1075
|
-
|
|
1152
|
+
targetBox.y = inlineBox.top;
|
|
1076
1153
|
}
|
|
1077
1154
|
break;
|
|
1078
1155
|
case "line-through":
|
|
1079
|
-
|
|
1156
|
+
targetBox.height = imageBox.height * styleScale;
|
|
1080
1157
|
if (isVertical) {
|
|
1081
|
-
|
|
1158
|
+
targetBox.x = inlineBox.left + inlineBox.width - strikeoutPosition + targetBox.height / 2;
|
|
1082
1159
|
} else {
|
|
1083
|
-
|
|
1160
|
+
targetBox.y = inlineBox.top + strikeoutPosition - targetBox.height / 2;
|
|
1084
1161
|
}
|
|
1085
1162
|
break;
|
|
1086
1163
|
case "underline":
|
|
1087
|
-
|
|
1164
|
+
targetBox.height = imageBox.height * styleScale;
|
|
1088
1165
|
if (isVertical) {
|
|
1089
|
-
|
|
1166
|
+
targetBox.x = glyphBox.left + glyphBox.width - underlinePosition;
|
|
1090
1167
|
} else {
|
|
1091
|
-
|
|
1168
|
+
targetBox.y = inlineBox.top + underlinePosition;
|
|
1092
1169
|
}
|
|
1093
1170
|
break;
|
|
1094
1171
|
}
|
|
1095
1172
|
}
|
|
1096
|
-
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);
|
|
1097
1176
|
if (isVertical) {
|
|
1177
|
+
const tx = targetBox.width / 2;
|
|
1178
|
+
const ty = targetBox.height / 2;
|
|
1179
|
+
if (!hasReferImage) {
|
|
1180
|
+
transform.translate(-tx, -ty);
|
|
1181
|
+
}
|
|
1098
1182
|
transform.rotate(-Math.PI / 2);
|
|
1183
|
+
if (!hasReferImage) {
|
|
1184
|
+
transform.translate(ty, tx);
|
|
1185
|
+
}
|
|
1099
1186
|
}
|
|
1100
|
-
transform.translate(
|
|
1187
|
+
transform.translate(targetBox.x, targetBox.y);
|
|
1101
1188
|
for (let i2 = 0; i2 < Math.ceil(rawWidth / userWidth); i2++) {
|
|
1102
1189
|
const _transform = transform.clone();
|
|
1103
1190
|
if (isVertical) {
|
|
1104
|
-
_transform.translate(0, i2 *
|
|
1191
|
+
_transform.translate(0, i2 * targetBox.width);
|
|
1105
1192
|
} else {
|
|
1106
|
-
_transform.translate(i2 *
|
|
1193
|
+
_transform.translate(i2 * targetBox.width, 0);
|
|
1107
1194
|
}
|
|
1108
|
-
|
|
1195
|
+
imagePathSet.paths.forEach((originalPath) => {
|
|
1109
1196
|
const path = originalPath.clone().applyTransform(_transform);
|
|
1110
|
-
if (path.style.strokeWidth)
|
|
1197
|
+
if (path.style.strokeWidth)
|
|
1111
1198
|
path.style.strokeWidth *= styleScale * _thickness;
|
|
1112
|
-
|
|
1113
|
-
if (path.style.strokeMiterlimit) {
|
|
1199
|
+
if (path.style.strokeMiterlimit)
|
|
1114
1200
|
path.style.strokeMiterlimit *= styleScale;
|
|
1115
|
-
|
|
1116
|
-
if (path.style.strokeDashoffset) {
|
|
1201
|
+
if (path.style.strokeDashoffset)
|
|
1117
1202
|
path.style.strokeDashoffset *= styleScale;
|
|
1118
|
-
|
|
1119
|
-
if (path.style.strokeDasharray) {
|
|
1203
|
+
if (path.style.strokeDasharray)
|
|
1120
1204
|
path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
|
|
1121
|
-
}
|
|
1122
1205
|
if (path.style.fill && path.style.fill in _colormap) {
|
|
1123
1206
|
path.style.fill = _colormap[path.style.fill];
|
|
1124
1207
|
}
|
|
1125
1208
|
if (path.style.stroke && path.style.stroke in _colormap) {
|
|
1126
1209
|
path.style.stroke = _colormap[path.style.stroke];
|
|
1127
1210
|
}
|
|
1128
|
-
paths.push(path);
|
|
1211
|
+
pathSet.paths.push(path);
|
|
1129
1212
|
if (rawWidth !== userWidth) {
|
|
1130
1213
|
if (isVertical) {
|
|
1131
|
-
clipRects[paths.length - 1] = new BoundingBox(
|
|
1214
|
+
clipRects[pathSet.paths.length - 1] = new BoundingBox(
|
|
1132
1215
|
groupBox.left - groupBox.width * 2,
|
|
1133
1216
|
groupBox.top,
|
|
1134
1217
|
groupBox.width * 4,
|
|
1135
1218
|
groupBox.height
|
|
1136
1219
|
);
|
|
1137
1220
|
} else {
|
|
1138
|
-
clipRects[paths.length - 1] = new BoundingBox(
|
|
1221
|
+
clipRects[pathSet.paths.length - 1] = new BoundingBox(
|
|
1139
1222
|
groupBox.left,
|
|
1140
1223
|
groupBox.top - groupBox.height * 2,
|
|
1141
1224
|
groupBox.width,
|
|
@@ -1148,8 +1231,24 @@ function highlight() {
|
|
|
1148
1231
|
}
|
|
1149
1232
|
},
|
|
1150
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
|
+
},
|
|
1151
1250
|
render: (ctx, text) => {
|
|
1152
|
-
paths.forEach((path, index) => {
|
|
1251
|
+
pathSet.paths.forEach((path, index) => {
|
|
1153
1252
|
drawPath({
|
|
1154
1253
|
ctx,
|
|
1155
1254
|
path,
|
|
@@ -1173,12 +1272,12 @@ function genDisc(r, color) {
|
|
|
1173
1272
|
</svg>`;
|
|
1174
1273
|
}
|
|
1175
1274
|
function listStyle() {
|
|
1176
|
-
const
|
|
1275
|
+
const pathSet = new Path2DSet();
|
|
1177
1276
|
return definePlugin({
|
|
1178
1277
|
name: "listStyle",
|
|
1179
|
-
|
|
1278
|
+
pathSet,
|
|
1180
1279
|
update: (text) => {
|
|
1181
|
-
paths.length = 0;
|
|
1280
|
+
pathSet.paths.length = 0;
|
|
1182
1281
|
const { paragraphs, isVertical, fontSize } = text;
|
|
1183
1282
|
const padding = fontSize * 0.45;
|
|
1184
1283
|
paragraphs.forEach((paragraph) => {
|
|
@@ -1232,7 +1331,7 @@ function listStyle() {
|
|
|
1232
1331
|
inlineBox.top + (inlineBox.height - imageBox.height * _scale) / 2
|
|
1233
1332
|
);
|
|
1234
1333
|
}
|
|
1235
|
-
paths.push(...imagePathSet.paths.map((p) => {
|
|
1334
|
+
pathSet.paths.push(...imagePathSet.paths.map((p) => {
|
|
1236
1335
|
const path = p.clone();
|
|
1237
1336
|
path.applyTransform(m);
|
|
1238
1337
|
if (path.style.fill && path.style.fill in colormap) {
|
|
@@ -1295,39 +1394,21 @@ function render() {
|
|
|
1295
1394
|
return boxes.length ? BoundingBox.from(...boxes) : void 0;
|
|
1296
1395
|
},
|
|
1297
1396
|
render: (ctx, text) => {
|
|
1298
|
-
const { paragraphs, glyphBox, effects
|
|
1299
|
-
function fillBackground(color, box) {
|
|
1300
|
-
ctx.fillStyle = color;
|
|
1301
|
-
ctx.fillRect(box.left, box.top, box.width, box.height);
|
|
1302
|
-
}
|
|
1303
|
-
if (style?.backgroundColor) {
|
|
1304
|
-
fillBackground(style.backgroundColor, new BoundingBox(0, 0, ctx.canvas.width, ctx.canvas.height));
|
|
1305
|
-
}
|
|
1306
|
-
paragraphs.forEach((paragraph) => {
|
|
1307
|
-
if (paragraph.style?.backgroundColor) {
|
|
1308
|
-
fillBackground(paragraph.style.backgroundColor, paragraph.lineBox);
|
|
1309
|
-
}
|
|
1310
|
-
});
|
|
1397
|
+
const { paragraphs, glyphBox, effects } = text;
|
|
1311
1398
|
if (effects) {
|
|
1312
|
-
effects.forEach((
|
|
1313
|
-
uploadColor(
|
|
1399
|
+
effects.forEach((style) => {
|
|
1400
|
+
uploadColor(style, glyphBox, ctx);
|
|
1314
1401
|
ctx.save();
|
|
1315
|
-
const [a, c, e, b, d, f] = getTransform2D(text,
|
|
1402
|
+
const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
|
|
1316
1403
|
ctx.transform(a, b, c, d, e, f);
|
|
1317
1404
|
text.forEachCharacter((character) => {
|
|
1318
|
-
|
|
1319
|
-
fillBackground(character.parent.style.backgroundColor, character.inlineBox);
|
|
1320
|
-
}
|
|
1321
|
-
character.drawTo(ctx, style2);
|
|
1405
|
+
character.drawTo(ctx, style);
|
|
1322
1406
|
});
|
|
1323
1407
|
ctx.restore();
|
|
1324
1408
|
});
|
|
1325
1409
|
} else {
|
|
1326
1410
|
paragraphs.forEach((paragraph) => {
|
|
1327
1411
|
paragraph.fragments.forEach((fragment) => {
|
|
1328
|
-
if (fragment.style?.backgroundColor) {
|
|
1329
|
-
fillBackground(fragment.computedStyle.backgroundColor, fragment.inlineBox);
|
|
1330
|
-
}
|
|
1331
1412
|
fragment.characters.forEach((character) => {
|
|
1332
1413
|
character.drawTo(ctx);
|
|
1333
1414
|
});
|
|
@@ -1365,12 +1446,12 @@ function getTransform2D(text, style) {
|
|
|
1365
1446
|
}
|
|
1366
1447
|
|
|
1367
1448
|
function textDecoration() {
|
|
1368
|
-
const
|
|
1449
|
+
const pathSet = new Path2DSet();
|
|
1369
1450
|
return definePlugin({
|
|
1370
1451
|
name: "textDecoration",
|
|
1371
|
-
|
|
1452
|
+
pathSet,
|
|
1372
1453
|
update: (text) => {
|
|
1373
|
-
paths.length = 0;
|
|
1454
|
+
pathSet.paths.length = 0;
|
|
1374
1455
|
const groups = [];
|
|
1375
1456
|
let group;
|
|
1376
1457
|
let prevStyle;
|
|
@@ -1430,7 +1511,8 @@ function textDecoration() {
|
|
|
1430
1511
|
color,
|
|
1431
1512
|
textDecoration: textDecoration2
|
|
1432
1513
|
} = style;
|
|
1433
|
-
const
|
|
1514
|
+
const inlineBox = BoundingBox.from(...group2.map((c) => c.inlineBox));
|
|
1515
|
+
const { left, top, width, height } = inlineBox;
|
|
1434
1516
|
let position = isVertical ? left + width : top;
|
|
1435
1517
|
const direction = isVertical ? -1 : 1;
|
|
1436
1518
|
let thickness = 0;
|
|
@@ -1470,7 +1552,7 @@ function textDecoration() {
|
|
|
1470
1552
|
fill: color
|
|
1471
1553
|
});
|
|
1472
1554
|
}
|
|
1473
|
-
paths.push(path);
|
|
1555
|
+
pathSet.paths.push(path);
|
|
1474
1556
|
});
|
|
1475
1557
|
},
|
|
1476
1558
|
render: (ctx, text) => {
|
|
@@ -1480,7 +1562,7 @@ function textDecoration() {
|
|
|
1480
1562
|
ctx.save();
|
|
1481
1563
|
const [a, c, e, b, d, f] = getTransform2D(text, effectStyle).transpose().elements;
|
|
1482
1564
|
ctx.transform(a, b, c, d, e, f);
|
|
1483
|
-
paths.forEach((path) => {
|
|
1565
|
+
pathSet.paths.forEach((path) => {
|
|
1484
1566
|
drawPath({
|
|
1485
1567
|
ctx,
|
|
1486
1568
|
path,
|
|
@@ -1491,7 +1573,7 @@ function textDecoration() {
|
|
|
1491
1573
|
ctx.restore();
|
|
1492
1574
|
});
|
|
1493
1575
|
} else {
|
|
1494
|
-
paths.forEach((path) => {
|
|
1576
|
+
pathSet.paths.forEach((path) => {
|
|
1495
1577
|
drawPath({
|
|
1496
1578
|
ctx,
|
|
1497
1579
|
path,
|
|
@@ -1661,22 +1743,16 @@ class Text extends EventEmitter {
|
|
|
1661
1743
|
this.pathBox = BoundingBox.from(
|
|
1662
1744
|
this.glyphBox,
|
|
1663
1745
|
...Array.from(this.plugins.values()).map((plugin) => {
|
|
1664
|
-
return plugin.getBoundingBox ? plugin.getBoundingBox(this) :
|
|
1746
|
+
return plugin.getBoundingBox ? plugin.getBoundingBox(this) : plugin.pathSet?.getBoundingBox();
|
|
1665
1747
|
}).filter(Boolean)
|
|
1666
1748
|
);
|
|
1667
1749
|
return this;
|
|
1668
1750
|
}
|
|
1669
1751
|
updateBoundingBox() {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
const bottom = Math.max(pathBox.bottom, pathBox.bottom + lineBox.bottom - rawGlyphBox.bottom);
|
|
1675
|
-
this.boundingBox = new BoundingBox(
|
|
1676
|
-
left,
|
|
1677
|
-
top,
|
|
1678
|
-
right - left,
|
|
1679
|
-
bottom - top
|
|
1752
|
+
this.boundingBox = BoundingBox.from(
|
|
1753
|
+
this.rawGlyphBox,
|
|
1754
|
+
this.lineBox,
|
|
1755
|
+
this.pathBox
|
|
1680
1756
|
);
|
|
1681
1757
|
return this;
|
|
1682
1758
|
}
|
|
@@ -1707,9 +1783,9 @@ class Text extends EventEmitter {
|
|
|
1707
1783
|
Array.from(this.plugins.values()).sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
|
|
1708
1784
|
if (plugin.render) {
|
|
1709
1785
|
plugin.render?.(ctx, this);
|
|
1710
|
-
} else if (plugin.
|
|
1786
|
+
} else if (plugin.pathSet) {
|
|
1711
1787
|
const style = this.computedStyle;
|
|
1712
|
-
plugin.paths.forEach((path) => {
|
|
1788
|
+
plugin.pathSet.paths.forEach((path) => {
|
|
1713
1789
|
drawPath({
|
|
1714
1790
|
ctx,
|
|
1715
1791
|
path,
|
|
@@ -1742,4 +1818,4 @@ function renderText(options, load) {
|
|
|
1742
1818
|
return text.render(options);
|
|
1743
1819
|
}
|
|
1744
1820
|
|
|
1745
|
-
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"
|