modern-text 0.4.2 → 0.4.3
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 +165 -74
- package/dist/index.d.cts +22 -6
- package/dist/index.d.mts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.js +3 -3
- package/dist/index.mjs +166 -76
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Path2D, BoundingBox, getPathsBoundingBox, Matrix3, parseSvg, Vector2 } from 'modern-path2d';
|
|
2
2
|
import { fonts } from 'modern-font';
|
|
3
3
|
|
|
4
4
|
function parseColor(ctx, source, box) {
|
|
@@ -146,16 +146,26 @@ class Character {
|
|
|
146
146
|
this.content = content;
|
|
147
147
|
this.index = index;
|
|
148
148
|
this.parent = parent;
|
|
149
|
+
__publicField$3(this, "path", new Path2D());
|
|
149
150
|
__publicField$3(this, "lineBox", new BoundingBox());
|
|
150
151
|
__publicField$3(this, "inlineBox", new BoundingBox());
|
|
151
152
|
__publicField$3(this, "glyphBox");
|
|
152
153
|
__publicField$3(this, "underlinePosition", 0);
|
|
153
154
|
__publicField$3(this, "underlineThickness", 0);
|
|
154
|
-
__publicField$3(this, "
|
|
155
|
-
__publicField$3(this, "
|
|
155
|
+
__publicField$3(this, "strikeoutPosition", 0);
|
|
156
|
+
__publicField$3(this, "strikeoutSize", 0);
|
|
157
|
+
__publicField$3(this, "ascender", 0);
|
|
158
|
+
__publicField$3(this, "descender", 0);
|
|
159
|
+
__publicField$3(this, "typoAscender", 0);
|
|
160
|
+
__publicField$3(this, "typoDescender", 0);
|
|
161
|
+
__publicField$3(this, "typoLineGap", 0);
|
|
162
|
+
__publicField$3(this, "winAscent", 0);
|
|
163
|
+
__publicField$3(this, "winDescent", 0);
|
|
164
|
+
__publicField$3(this, "xHeight", 0);
|
|
165
|
+
__publicField$3(this, "capHeight", 0);
|
|
156
166
|
__publicField$3(this, "baseline", 0);
|
|
157
167
|
__publicField$3(this, "centerDiviation", 0);
|
|
158
|
-
__publicField$3(this, "
|
|
168
|
+
__publicField$3(this, "fontStyle");
|
|
159
169
|
}
|
|
160
170
|
get center() {
|
|
161
171
|
return this.glyphBox?.center;
|
|
@@ -182,25 +192,34 @@ class Character {
|
|
|
182
192
|
if (!sfnt) {
|
|
183
193
|
return this;
|
|
184
194
|
}
|
|
185
|
-
const {
|
|
195
|
+
const { hhea, os2, post, head } = sfnt;
|
|
196
|
+
const unitsPerEm = head.unitsPerEm;
|
|
197
|
+
const ascender = hhea.ascent;
|
|
198
|
+
const descender = hhea.descent;
|
|
186
199
|
const { content, computedStyle } = this;
|
|
187
200
|
const { fontSize } = computedStyle;
|
|
188
201
|
const rate = unitsPerEm / fontSize;
|
|
189
202
|
const advanceWidth = sfnt.getAdvanceWidth(content, fontSize);
|
|
190
203
|
const advanceHeight = (ascender + Math.abs(descender)) / rate;
|
|
191
204
|
const baseline = ascender / rate;
|
|
192
|
-
const yStrikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
|
|
193
|
-
const yStrikeoutSize = os2.yStrikeoutSize / rate;
|
|
194
|
-
const underlinePosition = (ascender - post.underlinePosition) / rate;
|
|
195
|
-
const underlineThickness = post.underlineThickness / rate;
|
|
196
205
|
this.inlineBox.width = advanceWidth;
|
|
197
206
|
this.inlineBox.height = advanceHeight;
|
|
198
|
-
this.underlinePosition = underlinePosition;
|
|
199
|
-
this.underlineThickness = underlineThickness;
|
|
200
|
-
this.
|
|
201
|
-
this.
|
|
207
|
+
this.underlinePosition = (ascender - post.underlinePosition) / rate;
|
|
208
|
+
this.underlineThickness = post.underlineThickness / rate;
|
|
209
|
+
this.strikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
|
|
210
|
+
this.strikeoutSize = os2.yStrikeoutSize / rate;
|
|
211
|
+
this.ascender = ascender / rate;
|
|
212
|
+
this.descender = descender / rate;
|
|
213
|
+
this.typoAscender = os2.sTypoAscender / rate;
|
|
214
|
+
this.typoDescender = os2.sTypoDescender / rate;
|
|
215
|
+
this.typoLineGap = os2.sTypoLineGap / rate;
|
|
216
|
+
this.winAscent = os2.usWinAscent / rate;
|
|
217
|
+
this.winDescent = os2.usWinDescent / rate;
|
|
218
|
+
this.xHeight = os2.sxHeight / rate;
|
|
219
|
+
this.capHeight = os2.sCapHeight / rate;
|
|
202
220
|
this.baseline = baseline;
|
|
203
221
|
this.centerDiviation = advanceHeight / 2 - baseline;
|
|
222
|
+
this.fontStyle = fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle];
|
|
204
223
|
return this;
|
|
205
224
|
}
|
|
206
225
|
update(fonts) {
|
|
@@ -214,11 +233,12 @@ class Character {
|
|
|
214
233
|
content,
|
|
215
234
|
computedStyle: style,
|
|
216
235
|
baseline,
|
|
217
|
-
inlineBox
|
|
236
|
+
inlineBox,
|
|
237
|
+
ascender,
|
|
238
|
+
descender,
|
|
239
|
+
typoAscender,
|
|
240
|
+
fontStyle
|
|
218
241
|
} = this;
|
|
219
|
-
const { os2, head, ascender, descender } = sfnt;
|
|
220
|
-
const typoAscender = os2.sTypoAscender;
|
|
221
|
-
const fontStyle = fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle];
|
|
222
242
|
const { left, top } = inlineBox;
|
|
223
243
|
const needsItalic = style.fontStyle === "italic" && fontStyle !== "italic";
|
|
224
244
|
let x = left;
|
|
@@ -276,7 +296,6 @@ class Character {
|
|
|
276
296
|
}
|
|
277
297
|
}
|
|
278
298
|
}
|
|
279
|
-
path.addCommands(this._decoration());
|
|
280
299
|
const fontWeight = style.fontWeight ?? 400;
|
|
281
300
|
if (fontWeight in fontWeightMap && ((fontWeight === 700 || fontWeight === "bold") && fontStyle !== "bold")) {
|
|
282
301
|
path.bold(fontWeightMap[fontWeight] * style.fontSize * 0.05);
|
|
@@ -290,49 +309,6 @@ class Character {
|
|
|
290
309
|
this.glyphBox = this.getGlyphBoundingBox();
|
|
291
310
|
return this;
|
|
292
311
|
}
|
|
293
|
-
_decoration() {
|
|
294
|
-
const { isVertical, underlinePosition, yStrikeoutPosition } = this;
|
|
295
|
-
const { textDecoration, fontSize } = this.computedStyle;
|
|
296
|
-
const { left, top, width, height } = this.inlineBox;
|
|
297
|
-
const lineWidth = 0.1 * fontSize;
|
|
298
|
-
let start;
|
|
299
|
-
switch (textDecoration) {
|
|
300
|
-
case "underline":
|
|
301
|
-
if (isVertical) {
|
|
302
|
-
start = left;
|
|
303
|
-
} else {
|
|
304
|
-
start = top + underlinePosition;
|
|
305
|
-
}
|
|
306
|
-
break;
|
|
307
|
-
case "line-through":
|
|
308
|
-
if (isVertical) {
|
|
309
|
-
start = left + width / 2;
|
|
310
|
-
} else {
|
|
311
|
-
start = top + yStrikeoutPosition;
|
|
312
|
-
}
|
|
313
|
-
break;
|
|
314
|
-
case "none":
|
|
315
|
-
default:
|
|
316
|
-
return [];
|
|
317
|
-
}
|
|
318
|
-
if (isVertical) {
|
|
319
|
-
return [
|
|
320
|
-
{ type: "M", x: start, y: top },
|
|
321
|
-
{ type: "L", x: start, y: top + height },
|
|
322
|
-
{ type: "L", x: start + lineWidth, y: top + height },
|
|
323
|
-
{ type: "L", x: start + lineWidth, y: top },
|
|
324
|
-
{ type: "Z" }
|
|
325
|
-
];
|
|
326
|
-
} else {
|
|
327
|
-
return [
|
|
328
|
-
{ type: "M", x: left, y: start },
|
|
329
|
-
{ type: "L", x: left + width, y: start },
|
|
330
|
-
{ type: "L", x: left + width, y: start + lineWidth },
|
|
331
|
-
{ type: "L", x: left, y: start + lineWidth },
|
|
332
|
-
{ type: "Z" }
|
|
333
|
-
];
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
312
|
_italic(path, startPoint) {
|
|
337
313
|
path.skew(-0.24, 0, startPoint || {
|
|
338
314
|
y: this.inlineBox.top + this.baseline,
|
|
@@ -721,14 +697,13 @@ function highlight() {
|
|
|
721
697
|
paths,
|
|
722
698
|
update: (text) => {
|
|
723
699
|
paths.length = 0;
|
|
724
|
-
const { characters } = text;
|
|
725
|
-
let group;
|
|
726
700
|
const groups = [];
|
|
701
|
+
let group;
|
|
727
702
|
let prevStyle;
|
|
728
|
-
|
|
729
|
-
const { isVertical, computedStyle: style } = character;
|
|
703
|
+
text.forEachCharacter((character) => {
|
|
704
|
+
const { isVertical, computedStyle: style, inlineBox, fontSize } = character;
|
|
730
705
|
if (!isNone(style.highlightImage) && character.glyphBox) {
|
|
731
|
-
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 ===
|
|
706
|
+
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) {
|
|
732
707
|
group.push(character);
|
|
733
708
|
} else {
|
|
734
709
|
group = [];
|
|
@@ -738,15 +713,15 @@ function highlight() {
|
|
|
738
713
|
}
|
|
739
714
|
prevStyle = style;
|
|
740
715
|
});
|
|
741
|
-
groups.filter((
|
|
742
|
-
const char =
|
|
716
|
+
groups.filter((characters) => characters.length).map((characters) => {
|
|
717
|
+
const char = characters[0];
|
|
743
718
|
return {
|
|
744
719
|
style: char.computedStyle,
|
|
745
|
-
|
|
746
|
-
box: BoundingBox.from(...
|
|
720
|
+
unitHeight: char.typoAscender + char.typoDescender,
|
|
721
|
+
box: BoundingBox.from(...characters.map((c) => c.glyphBox))
|
|
747
722
|
};
|
|
748
723
|
}).forEach((group2) => {
|
|
749
|
-
const { style, box: groupBox,
|
|
724
|
+
const { style, box: groupBox, unitHeight } = group2;
|
|
750
725
|
const { fontSize, writingMode } = style;
|
|
751
726
|
const isVertical = writingMode.includes("vertical");
|
|
752
727
|
const strokeWidthScale = parseStrokeWidthScale(style.highlightStrokeWidth, fontSize, groupBox.width);
|
|
@@ -757,7 +732,6 @@ function highlight() {
|
|
|
757
732
|
const box = getPathsBoundingBox(svgPaths, true);
|
|
758
733
|
const refBox = getPathsBoundingBox(refPaths, false);
|
|
759
734
|
const unitWidth = charsPerRepeat ? fontSize * charsPerRepeat : isVertical ? groupBox.height : groupBox.width;
|
|
760
|
-
const unitHeight = baseline * 0.8;
|
|
761
735
|
const transform = getTransformMatrix(
|
|
762
736
|
box,
|
|
763
737
|
refBox,
|
|
@@ -919,7 +893,7 @@ function render() {
|
|
|
919
893
|
return boxes.length ? BoundingBox.from(...boxes) : void 0;
|
|
920
894
|
},
|
|
921
895
|
render: (ctx, text) => {
|
|
922
|
-
const {
|
|
896
|
+
const { paragraphs, glyphBox, effects, style } = text;
|
|
923
897
|
function fillBackground(color, box) {
|
|
924
898
|
ctx.fillStyle = color;
|
|
925
899
|
ctx.fillRect(box.left, box.top, box.width, box.height);
|
|
@@ -938,7 +912,7 @@ function render() {
|
|
|
938
912
|
ctx.save();
|
|
939
913
|
const [a, c, e, b, d, f] = getTransform2D(text, style2).transpose().elements;
|
|
940
914
|
ctx.transform(a, b, c, d, e, f);
|
|
941
|
-
|
|
915
|
+
text.forEachCharacter((character) => {
|
|
942
916
|
if (character.parent.style?.backgroundColor) {
|
|
943
917
|
fillBackground(character.parent.style.backgroundColor, character.inlineBox);
|
|
944
918
|
}
|
|
@@ -983,6 +957,112 @@ function getTransform2D(text, style) {
|
|
|
983
957
|
return tempM1.clone();
|
|
984
958
|
}
|
|
985
959
|
|
|
960
|
+
function textDecoration() {
|
|
961
|
+
const paths = [];
|
|
962
|
+
return definePlugin({
|
|
963
|
+
name: "textDecoration",
|
|
964
|
+
paths,
|
|
965
|
+
update: (text) => {
|
|
966
|
+
paths.length = 0;
|
|
967
|
+
const groups = [];
|
|
968
|
+
let group;
|
|
969
|
+
let prevStyle;
|
|
970
|
+
text.forEachCharacter((character) => {
|
|
971
|
+
const { computedStyle: style, underlinePosition, underlineThickness, strikeoutPosition, strikeoutSize } = character;
|
|
972
|
+
if (!isNone(style.textDecoration)) {
|
|
973
|
+
let flag = false;
|
|
974
|
+
if (prevStyle?.textDecoration === style.textDecoration && prevStyle?.writingMode === style.writingMode) {
|
|
975
|
+
switch (style.textDecoration) {
|
|
976
|
+
case "underline":
|
|
977
|
+
if (group[0].underlinePosition === underlinePosition && group[0].underlineThickness === underlineThickness) {
|
|
978
|
+
flag = true;
|
|
979
|
+
}
|
|
980
|
+
break;
|
|
981
|
+
case "line-through":
|
|
982
|
+
if (group[0].strikeoutPosition === strikeoutPosition && group[0].strikeoutSize === strikeoutSize) {
|
|
983
|
+
flag = true;
|
|
984
|
+
}
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (flag) {
|
|
989
|
+
group.push(character);
|
|
990
|
+
} else {
|
|
991
|
+
group = [];
|
|
992
|
+
group.push(character);
|
|
993
|
+
groups.push(group);
|
|
994
|
+
}
|
|
995
|
+
prevStyle = style;
|
|
996
|
+
} else {
|
|
997
|
+
prevStyle = void 0;
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
groups.forEach((group2) => {
|
|
1001
|
+
const { computedStyle: style, isVertical, underlinePosition, underlineThickness, strikeoutPosition, strikeoutSize } = group2[0];
|
|
1002
|
+
const { textDecoration: textDecoration2 } = style;
|
|
1003
|
+
const { left, top, width, height } = BoundingBox.from(...group2.map((c) => c.inlineBox));
|
|
1004
|
+
let strokePosition = isVertical ? left : top;
|
|
1005
|
+
let strokeWidth = 0;
|
|
1006
|
+
switch (textDecoration2) {
|
|
1007
|
+
case "underline":
|
|
1008
|
+
strokePosition += underlinePosition;
|
|
1009
|
+
strokeWidth = underlineThickness * 2;
|
|
1010
|
+
break;
|
|
1011
|
+
case "line-through":
|
|
1012
|
+
strokePosition += strikeoutPosition;
|
|
1013
|
+
strokeWidth = strikeoutSize * 2;
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
strokePosition -= strokeWidth;
|
|
1017
|
+
if (isVertical) {
|
|
1018
|
+
paths.push(new Path2D([
|
|
1019
|
+
{ type: "M", x: strokePosition, y: top },
|
|
1020
|
+
{ type: "L", x: strokePosition, y: top + height },
|
|
1021
|
+
{ type: "L", x: strokePosition + strokeWidth, y: top + height },
|
|
1022
|
+
{ type: "L", x: strokePosition + strokeWidth, y: top },
|
|
1023
|
+
{ type: "Z" }
|
|
1024
|
+
]));
|
|
1025
|
+
} else {
|
|
1026
|
+
paths.push(new Path2D([
|
|
1027
|
+
{ type: "M", x: left, y: strokePosition },
|
|
1028
|
+
{ type: "L", x: left + width, y: strokePosition },
|
|
1029
|
+
{ type: "L", x: left + width, y: strokePosition + strokeWidth },
|
|
1030
|
+
{ type: "L", x: left, y: strokePosition + strokeWidth },
|
|
1031
|
+
{ type: "Z" }
|
|
1032
|
+
]));
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
},
|
|
1036
|
+
render: (ctx, text) => {
|
|
1037
|
+
const { effects, fontSize } = text;
|
|
1038
|
+
if (effects) {
|
|
1039
|
+
effects.forEach((style) => {
|
|
1040
|
+
ctx.save();
|
|
1041
|
+
const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
|
|
1042
|
+
ctx.transform(a, b, c, d, e, f);
|
|
1043
|
+
paths.forEach((path) => {
|
|
1044
|
+
drawPath({
|
|
1045
|
+
ctx,
|
|
1046
|
+
path,
|
|
1047
|
+
fontSize,
|
|
1048
|
+
...style
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
ctx.restore();
|
|
1052
|
+
});
|
|
1053
|
+
} else {
|
|
1054
|
+
paths.forEach((path) => {
|
|
1055
|
+
drawPath({
|
|
1056
|
+
ctx,
|
|
1057
|
+
path,
|
|
1058
|
+
fontSize
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
986
1066
|
var __defProp = Object.defineProperty;
|
|
987
1067
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
988
1068
|
var __publicField = (obj, key, value) => {
|
|
@@ -1057,7 +1137,7 @@ class Text {
|
|
|
1057
1137
|
this.measureDom = measureDom;
|
|
1058
1138
|
this.effects = effects;
|
|
1059
1139
|
this.fonts = fonts;
|
|
1060
|
-
this.use(
|
|
1140
|
+
this.use(listStyle()).use(textDecoration()).use(highlight()).use(render());
|
|
1061
1141
|
}
|
|
1062
1142
|
get fontSize() {
|
|
1063
1143
|
return this.computedStyle.fontSize;
|
|
@@ -1072,6 +1152,16 @@ class Text {
|
|
|
1072
1152
|
this.plugins.set(plugin.name, plugin);
|
|
1073
1153
|
return this;
|
|
1074
1154
|
}
|
|
1155
|
+
forEachCharacter(handle) {
|
|
1156
|
+
this.paragraphs.forEach((p, paragraphIndex) => {
|
|
1157
|
+
p.fragments.forEach((f, fragmentIndex) => {
|
|
1158
|
+
f.characters.forEach((c, characterIndex) => {
|
|
1159
|
+
handle(c, { paragraphIndex, fragmentIndex, characterIndex });
|
|
1160
|
+
});
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1163
|
+
return this;
|
|
1164
|
+
}
|
|
1075
1165
|
updateParagraphs() {
|
|
1076
1166
|
this.computedStyle = { ...defaultTextStyles, ...this.style };
|
|
1077
1167
|
let { content, computedStyle: style } = this;
|
|
@@ -1243,4 +1333,4 @@ function renderText(options) {
|
|
|
1243
1333
|
return new Text(options).render(options);
|
|
1244
1334
|
}
|
|
1245
1335
|
|
|
1246
|
-
export { Character, Fragment, Measurer, Paragraph, Text, defaultTextStyles, definePlugin, drawPath, filterEmpty, getTransform2D, highlight, isNone, listStyle, measureText, parseColor, render, renderText, setupView, uploadColor, uploadColors };
|
|
1336
|
+
export { Character, Fragment, Measurer, Paragraph, Text, defaultTextStyles, definePlugin, drawPath, filterEmpty, getTransform2D, highlight, isNone, listStyle, measureText, parseColor, render, renderText, setupView, textDecoration, uploadColor, uploadColors };
|