modern-text 1.11.1 → 2.0.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/README.md +283 -22
- package/dist/deformations/index.cjs +566 -0
- package/dist/deformations/index.d.cts +11 -0
- package/dist/deformations/index.d.mts +11 -0
- package/dist/deformations/index.d.ts +11 -0
- package/dist/deformations/index.mjs +563 -0
- package/dist/index.cjs +15 -3
- package/dist/index.d.cts +186 -6
- package/dist/index.d.mts +186 -6
- package/dist/index.d.ts +186 -6
- package/dist/index.js +6 -5
- package/dist/index.mjs +4 -2
- package/dist/shared/modern-text.B2xfrqDc.cjs +556 -0
- package/dist/shared/modern-text.BD7PBYt7.d.cts +100 -0
- package/dist/shared/modern-text.BxijkspX.d.ts +100 -0
- package/dist/shared/{modern-text.BKZQdmgG.cjs → modern-text.CBgc-cQ1.cjs} +288 -18
- package/dist/shared/modern-text.CYa4lfoG.d.mts +100 -0
- package/dist/shared/{modern-text.Dqw5Z6MV.mjs → modern-text.ChzjFjsk.mjs} +283 -13
- package/dist/shared/{modern-text.Db7Uoht6.d.cts → modern-text.D4WopQCu.d.cts} +56 -22
- package/dist/shared/{modern-text.Db7Uoht6.d.mts → modern-text.D4WopQCu.d.mts} +56 -22
- package/dist/shared/{modern-text.Db7Uoht6.d.ts → modern-text.D4WopQCu.d.ts} +56 -22
- package/dist/shared/modern-text.JF1ny7A-.mjs +550 -0
- package/dist/shared/modern-text.MC5bIC9E.cjs +316 -0
- package/dist/shared/modern-text.fT17R5HY.mjs +310 -0
- package/dist/web-components/index.cjs +2 -1
- package/dist/web-components/index.d.cts +1 -1
- package/dist/web-components/index.d.mts +1 -1
- package/dist/web-components/index.d.ts +1 -1
- package/dist/web-components/index.mjs +2 -1
- package/package.json +12 -7
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isNone, isGradient, normalizeGradient, clearUndef, Reactivable, normalizeText, getDefaultStyle, property } from 'modern-idoc';
|
|
2
2
|
import { svgToDom, svgToPath2DSet, Path2DSet, Transform2D, setCanvasContext, Path2D, BoundingBox, Vector2 } from 'modern-path2d';
|
|
3
3
|
import { fonts } from 'modern-font';
|
|
4
|
+
import { a as definePlugin, b as deformationPlugin } from './modern-text.JF1ny7A-.mjs';
|
|
4
5
|
|
|
5
6
|
function createSvgLoader() {
|
|
6
7
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -528,6 +529,18 @@ class Character {
|
|
|
528
529
|
this.fontStyle = fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle];
|
|
529
530
|
return this;
|
|
530
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Populate glyph metrics only (advance width/height, ascender/descender,
|
|
534
|
+
* baseline, …) without building the glyph `path` or touching boxes.
|
|
535
|
+
*
|
|
536
|
+
* The DOM {@link DomMeasurer} never needs this — it reads positions back from the
|
|
537
|
+
* browser. A pure-JS measurer (e.g. `FontMeasurer`) must know advances *before*
|
|
538
|
+
* it can place characters, so it calls this ahead of layout. `update()` later
|
|
539
|
+
* recomputes the same metrics while building the path, so this is idempotent.
|
|
540
|
+
*/
|
|
541
|
+
measureGlyph(fonts) {
|
|
542
|
+
return this.updateGlyph(this._getFontSFNT(fonts));
|
|
543
|
+
}
|
|
531
544
|
update(fonts) {
|
|
532
545
|
const sfnt = this._getFontSFNT(fonts);
|
|
533
546
|
if (!sfnt) {
|
|
@@ -702,10 +715,6 @@ class Paragraph {
|
|
|
702
715
|
}
|
|
703
716
|
}
|
|
704
717
|
|
|
705
|
-
function definePlugin(options) {
|
|
706
|
-
return options;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
718
|
let sharedContainer;
|
|
710
719
|
function getSharedContainer() {
|
|
711
720
|
if (sharedContainer?.isConnected) {
|
|
@@ -724,7 +733,7 @@ function getSharedContainer() {
|
|
|
724
733
|
sharedContainer = container;
|
|
725
734
|
return container;
|
|
726
735
|
}
|
|
727
|
-
class
|
|
736
|
+
class DomMeasurer {
|
|
728
737
|
static notZeroStyles = /* @__PURE__ */ new Set([
|
|
729
738
|
"width",
|
|
730
739
|
"height"
|
|
@@ -759,7 +768,7 @@ class Measurer {
|
|
|
759
768
|
return cached;
|
|
760
769
|
}
|
|
761
770
|
const domStyle = {};
|
|
762
|
-
const { notZeroStyles, pxStyles } =
|
|
771
|
+
const { notZeroStyles, pxStyles } = DomMeasurer;
|
|
763
772
|
for (const key in style) {
|
|
764
773
|
const value = style[key];
|
|
765
774
|
if (notZeroStyles.has(key) && value === 0) {
|
|
@@ -1068,6 +1077,253 @@ class Measurer {
|
|
|
1068
1077
|
}
|
|
1069
1078
|
}
|
|
1070
1079
|
|
|
1080
|
+
function side(style, name, edge) {
|
|
1081
|
+
return style[`${name}${edge}`] ?? style[name] ?? 0;
|
|
1082
|
+
}
|
|
1083
|
+
class FontMeasurer {
|
|
1084
|
+
measure(paragraphs, rootStyle, _dom, fonts$1) {
|
|
1085
|
+
const _fonts = fonts$1 ?? fonts;
|
|
1086
|
+
for (const paragraph of paragraphs) {
|
|
1087
|
+
for (const fragment of paragraph.fragments) {
|
|
1088
|
+
for (const character of fragment.characters) {
|
|
1089
|
+
character.measureGlyph(_fonts);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return rootStyle.writingMode.includes("vertical") ? this._measureVertical(paragraphs, rootStyle) : this._measureHorizontal(paragraphs, rootStyle);
|
|
1094
|
+
}
|
|
1095
|
+
_rootPadding(rootStyle) {
|
|
1096
|
+
return {
|
|
1097
|
+
top: side(rootStyle, "padding", "Top"),
|
|
1098
|
+
right: side(rootStyle, "padding", "Right"),
|
|
1099
|
+
bottom: side(rootStyle, "padding", "Bottom"),
|
|
1100
|
+
left: side(rootStyle, "padding", "Left")
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
_measureHorizontal(paragraphs, rootStyle) {
|
|
1104
|
+
const rootPad = this._rootPadding(rootStyle);
|
|
1105
|
+
const hasWidth = typeof rootStyle.width === "number";
|
|
1106
|
+
const availWidth = hasWidth ? rootStyle.width - rootPad.left - rootPad.right : Infinity;
|
|
1107
|
+
let y = rootPad.top;
|
|
1108
|
+
let maxRight = rootPad.left;
|
|
1109
|
+
for (const paragraph of paragraphs) {
|
|
1110
|
+
const pStyle = paragraph.computedStyle;
|
|
1111
|
+
const pBox = paragraph.style;
|
|
1112
|
+
const mTop = side(pBox, "margin", "Top");
|
|
1113
|
+
const mBottom = side(pBox, "margin", "Bottom");
|
|
1114
|
+
const mLeft = side(pBox, "margin", "Left");
|
|
1115
|
+
const mRight = side(pBox, "margin", "Right");
|
|
1116
|
+
const pTop = side(pBox, "padding", "Top");
|
|
1117
|
+
const pBottom = side(pBox, "padding", "Bottom");
|
|
1118
|
+
const pLeft = side(pBox, "padding", "Left");
|
|
1119
|
+
const pRight = side(pBox, "padding", "Right");
|
|
1120
|
+
const liLeft = rootPad.left + mLeft + pLeft;
|
|
1121
|
+
const liAvail = availWidth === Infinity ? Infinity : availWidth - mLeft - mRight - pLeft - pRight;
|
|
1122
|
+
y += mTop + pTop;
|
|
1123
|
+
const paraTop = y;
|
|
1124
|
+
let paraRight = liLeft;
|
|
1125
|
+
const lines = this._breakLines(paragraph, liAvail);
|
|
1126
|
+
const align = pStyle.textAlign;
|
|
1127
|
+
const indent = pStyle.textIndent ?? 0;
|
|
1128
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1129
|
+
const line = lines[i];
|
|
1130
|
+
const lineIndent = i === 0 ? indent : 0;
|
|
1131
|
+
let lineHeight = pStyle.fontSize * pStyle.lineHeight;
|
|
1132
|
+
let contentWidth = 0;
|
|
1133
|
+
for (const c of line) {
|
|
1134
|
+
if (c.fontHeight > lineHeight) {
|
|
1135
|
+
lineHeight = c.fontHeight;
|
|
1136
|
+
}
|
|
1137
|
+
contentWidth += this._advance(c);
|
|
1138
|
+
}
|
|
1139
|
+
let x = liLeft + lineIndent;
|
|
1140
|
+
if (liAvail !== Infinity) {
|
|
1141
|
+
const slack = liAvail - lineIndent - contentWidth;
|
|
1142
|
+
if (align === "center") {
|
|
1143
|
+
x += slack / 2;
|
|
1144
|
+
} else if (align === "end" || align === "right") {
|
|
1145
|
+
x += slack;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
for (const c of line) {
|
|
1149
|
+
const adv = c.advanceWidth;
|
|
1150
|
+
const contentHeight = c.advanceHeight;
|
|
1151
|
+
const fontHeight = c.fontHeight;
|
|
1152
|
+
c.inlineBox.left = x;
|
|
1153
|
+
c.inlineBox.top = y + (lineHeight - contentHeight) / 2;
|
|
1154
|
+
c.inlineBox.width = adv;
|
|
1155
|
+
c.inlineBox.height = contentHeight;
|
|
1156
|
+
c.lineBox.left = x;
|
|
1157
|
+
c.lineBox.top = c.inlineBox.top + (contentHeight - fontHeight) / 2;
|
|
1158
|
+
c.lineBox.width = adv;
|
|
1159
|
+
c.lineBox.height = fontHeight;
|
|
1160
|
+
x += this._advance(c);
|
|
1161
|
+
}
|
|
1162
|
+
if (x > paraRight) {
|
|
1163
|
+
paraRight = x;
|
|
1164
|
+
}
|
|
1165
|
+
y += lineHeight;
|
|
1166
|
+
}
|
|
1167
|
+
if (paraRight > maxRight) {
|
|
1168
|
+
maxRight = paraRight;
|
|
1169
|
+
}
|
|
1170
|
+
for (const fragment of paragraph.fragments) {
|
|
1171
|
+
this._unionInto(fragment.inlineBox, fragment.characters.map((c) => c.inlineBox));
|
|
1172
|
+
}
|
|
1173
|
+
paragraph.lineBox.left = liLeft;
|
|
1174
|
+
paragraph.lineBox.top = paraTop;
|
|
1175
|
+
paragraph.lineBox.width = liAvail === Infinity ? paraRight - liLeft : liAvail;
|
|
1176
|
+
paragraph.lineBox.height = y - paraTop;
|
|
1177
|
+
y += pBottom + mBottom;
|
|
1178
|
+
}
|
|
1179
|
+
const contentBottom = y + rootPad.bottom;
|
|
1180
|
+
const totalWidth = hasWidth ? rootStyle.width : maxRight + rootPad.right;
|
|
1181
|
+
const totalHeight = typeof rootStyle.height === "number" ? rootStyle.height : contentBottom;
|
|
1182
|
+
if (typeof rootStyle.height === "number") {
|
|
1183
|
+
const slack = totalHeight - contentBottom;
|
|
1184
|
+
const va = rootStyle.verticalAlign;
|
|
1185
|
+
const dy = va === "middle" ? slack / 2 : va === "bottom" ? slack : 0;
|
|
1186
|
+
if (dy) {
|
|
1187
|
+
this._shiftAll(paragraphs, dy);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
paragraphs,
|
|
1192
|
+
boundingBox: new BoundingBox(0, 0, totalWidth, totalHeight)
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Vertical writing-mode (`vertical-rl`): columns stack right-to-left, glyphs
|
|
1197
|
+
* flow top→bottom. It is the horizontal layout with the inline and block axes
|
|
1198
|
+
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
|
|
1199
|
+
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
|
|
1200
|
+
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox is
|
|
1201
|
+
* derived exactly as DomMeasurer.measureParagraphDom's vertical branch, so it
|
|
1202
|
+
* stays accurate even though inlineBox carries the content-box rounding residual.
|
|
1203
|
+
*
|
|
1204
|
+
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
|
|
1205
|
+
*/
|
|
1206
|
+
_measureVertical(paragraphs, rootStyle) {
|
|
1207
|
+
const rootPad = this._rootPadding(rootStyle);
|
|
1208
|
+
const hasHeight = typeof rootStyle.height === "number";
|
|
1209
|
+
const availHeight = hasHeight ? rootStyle.height - rootPad.top - rootPad.bottom : Infinity;
|
|
1210
|
+
const columns = [];
|
|
1211
|
+
for (const paragraph of paragraphs) {
|
|
1212
|
+
const strut = paragraph.computedStyle.fontSize * paragraph.computedStyle.lineHeight;
|
|
1213
|
+
for (const line of this._breakLines(paragraph, availHeight)) {
|
|
1214
|
+
let thickness = strut;
|
|
1215
|
+
for (const c of line) {
|
|
1216
|
+
if (c.fontHeight > thickness) {
|
|
1217
|
+
thickness = c.fontHeight;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
columns.push({ chars: line, thickness });
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
const totalThickness = columns.reduce((sum, c) => sum + c.thickness, 0);
|
|
1224
|
+
const hasWidth = typeof rootStyle.width === "number";
|
|
1225
|
+
const blockWidth = hasWidth ? Math.max(rootStyle.width - rootPad.left - rootPad.right, totalThickness) : totalThickness;
|
|
1226
|
+
let xRight = rootPad.left + blockWidth;
|
|
1227
|
+
let maxBottom = rootPad.top;
|
|
1228
|
+
for (const column of columns) {
|
|
1229
|
+
xRight -= column.thickness;
|
|
1230
|
+
const colLeft = xRight;
|
|
1231
|
+
let y = rootPad.top;
|
|
1232
|
+
for (const c of column.chars) {
|
|
1233
|
+
const advance = c.advanceWidth;
|
|
1234
|
+
const contentWidth = c.advanceHeight;
|
|
1235
|
+
const fontHeight = c.fontHeight;
|
|
1236
|
+
c.inlineBox.top = y;
|
|
1237
|
+
c.inlineBox.height = advance;
|
|
1238
|
+
c.inlineBox.width = contentWidth;
|
|
1239
|
+
c.inlineBox.left = colLeft + (column.thickness - contentWidth) / 2;
|
|
1240
|
+
c.lineBox.left = c.inlineBox.left + (contentWidth - fontHeight) / 2;
|
|
1241
|
+
c.lineBox.top = y;
|
|
1242
|
+
c.lineBox.width = fontHeight;
|
|
1243
|
+
c.lineBox.height = advance;
|
|
1244
|
+
y += this._advance(c);
|
|
1245
|
+
}
|
|
1246
|
+
if (y > maxBottom) {
|
|
1247
|
+
maxBottom = y;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
for (const paragraph of paragraphs) {
|
|
1251
|
+
for (const fragment of paragraph.fragments) {
|
|
1252
|
+
this._unionInto(fragment.inlineBox, fragment.characters.map((c) => c.inlineBox));
|
|
1253
|
+
}
|
|
1254
|
+
this._unionInto(
|
|
1255
|
+
paragraph.lineBox,
|
|
1256
|
+
paragraph.fragments.flatMap((f) => f.characters.map((c) => c.inlineBox))
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
const totalWidth = hasWidth ? rootStyle.width : blockWidth + rootPad.left + rootPad.right;
|
|
1260
|
+
const totalHeight = hasHeight ? rootStyle.height : maxBottom + rootPad.bottom;
|
|
1261
|
+
return {
|
|
1262
|
+
paragraphs,
|
|
1263
|
+
boundingBox: new BoundingBox(0, 0, totalWidth, totalHeight)
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
/** Advance step including CSS letter-spacing (px). */
|
|
1267
|
+
_advance(character) {
|
|
1268
|
+
return character.advanceWidth + (character.computedStyle.letterSpacing ?? 0);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Break a paragraph's characters into visual lines.
|
|
1272
|
+
* v1: `word-break: break-all` (break before any character that would overflow)
|
|
1273
|
+
* plus explicit `\n`/`\r` hard breaks. The newline itself occupies no line box.
|
|
1274
|
+
*/
|
|
1275
|
+
_breakLines(paragraph, avail) {
|
|
1276
|
+
const lines = [];
|
|
1277
|
+
let current = [];
|
|
1278
|
+
let width = 0;
|
|
1279
|
+
const flush = () => {
|
|
1280
|
+
lines.push(current);
|
|
1281
|
+
current = [];
|
|
1282
|
+
width = 0;
|
|
1283
|
+
};
|
|
1284
|
+
for (const fragment of paragraph.fragments) {
|
|
1285
|
+
for (const c of fragment.characters) {
|
|
1286
|
+
if (c.content === "\n" || c.content === "\r") {
|
|
1287
|
+
flush();
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (avail !== Infinity && current.length > 0 && width + c.advanceWidth > avail) {
|
|
1291
|
+
flush();
|
|
1292
|
+
}
|
|
1293
|
+
current.push(c);
|
|
1294
|
+
width += this._advance(c);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
flush();
|
|
1298
|
+
return lines;
|
|
1299
|
+
}
|
|
1300
|
+
_unionInto(target, boxes) {
|
|
1301
|
+
const used = boxes.filter((b) => b.width !== 0 || b.height !== 0);
|
|
1302
|
+
if (!used.length) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const u = BoundingBox.from(...used);
|
|
1306
|
+
target.left = u.left;
|
|
1307
|
+
target.top = u.top;
|
|
1308
|
+
target.width = u.width;
|
|
1309
|
+
target.height = u.height;
|
|
1310
|
+
}
|
|
1311
|
+
_shiftAll(paragraphs, dy) {
|
|
1312
|
+
for (const paragraph of paragraphs) {
|
|
1313
|
+
paragraph.lineBox.top += dy;
|
|
1314
|
+
for (const fragment of paragraph.fragments) {
|
|
1315
|
+
fragment.inlineBox.top += dy;
|
|
1316
|
+
for (const character of fragment.characters) {
|
|
1317
|
+
character.inlineBox.top += dy;
|
|
1318
|
+
character.lineBox.top += dy;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
dispose() {
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1071
1327
|
function backgroundPlugin() {
|
|
1072
1328
|
const pathSet = new Path2DSet();
|
|
1073
1329
|
const loader = createSvgLoader();
|
|
@@ -1456,7 +1712,7 @@ function highlightPlugin() {
|
|
|
1456
1712
|
}
|
|
1457
1713
|
boundingBoxs.push(box);
|
|
1458
1714
|
});
|
|
1459
|
-
return BoundingBox.from(...boundingBoxs);
|
|
1715
|
+
return boundingBoxs.length ? BoundingBox.from(...boundingBoxs) : void 0;
|
|
1460
1716
|
},
|
|
1461
1717
|
render: (renderer) => {
|
|
1462
1718
|
const { text, context } = renderer;
|
|
@@ -1826,7 +2082,7 @@ class Text extends Reactivable {
|
|
|
1826
2082
|
glyphBox = new BoundingBox();
|
|
1827
2083
|
pathBox = new BoundingBox();
|
|
1828
2084
|
boundingBox = new BoundingBox();
|
|
1829
|
-
measurer = new
|
|
2085
|
+
measurer = new DomMeasurer();
|
|
1830
2086
|
plugins = /* @__PURE__ */ new Map();
|
|
1831
2087
|
pathSets = [];
|
|
1832
2088
|
_paragraphs = [];
|
|
@@ -1881,8 +2137,15 @@ class Text extends Reactivable {
|
|
|
1881
2137
|
measureDom,
|
|
1882
2138
|
fonts,
|
|
1883
2139
|
fill,
|
|
1884
|
-
outline
|
|
2140
|
+
outline,
|
|
2141
|
+
deformation
|
|
1885
2142
|
} = normalizeText(options);
|
|
2143
|
+
if (options.measurer && typeof options.measurer !== "string") {
|
|
2144
|
+
this.measurer = options.measurer;
|
|
2145
|
+
} else {
|
|
2146
|
+
const kind = options.measurer ?? (fonts ? "font" : "dom");
|
|
2147
|
+
this.measurer = kind === "font" ? new FontMeasurer() : new DomMeasurer();
|
|
2148
|
+
}
|
|
1886
2149
|
this.debug = options.debug ?? false;
|
|
1887
2150
|
this.content = content;
|
|
1888
2151
|
this.effects = effects;
|
|
@@ -1891,7 +2154,8 @@ class Text extends Reactivable {
|
|
|
1891
2154
|
this.fonts = fonts;
|
|
1892
2155
|
this.fill = fill;
|
|
1893
2156
|
this.outline = outline;
|
|
1894
|
-
this.
|
|
2157
|
+
this.deformation = deformation;
|
|
2158
|
+
this.use(backgroundPlugin()).use(outlinePlugin()).use(listStylePlugin()).use(textDecorationPlugin()).use(highlightPlugin()).use(renderPlugin()).use(deformationPlugin());
|
|
1895
2159
|
(options.plugins ?? []).forEach((plugin) => {
|
|
1896
2160
|
this.use(plugin);
|
|
1897
2161
|
});
|
|
@@ -1956,6 +2220,9 @@ class Text extends Reactivable {
|
|
|
1956
2220
|
}
|
|
1957
2221
|
createDom() {
|
|
1958
2222
|
this._update();
|
|
2223
|
+
if (!this.measurer.createDom) {
|
|
2224
|
+
throw new Error("current measurer does not support createDom()");
|
|
2225
|
+
}
|
|
1959
2226
|
return this.measurer.createDom(this.paragraphs, this.computedStyle);
|
|
1960
2227
|
}
|
|
1961
2228
|
measure(dom = this.measureDom) {
|
|
@@ -1969,7 +2236,7 @@ class Text extends Reactivable {
|
|
|
1969
2236
|
boundingBox: this.boundingBox
|
|
1970
2237
|
};
|
|
1971
2238
|
this._update();
|
|
1972
|
-
const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom);
|
|
2239
|
+
const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom, this.fonts);
|
|
1973
2240
|
this.paragraphs = result.paragraphs;
|
|
1974
2241
|
this.lineBox = result.boundingBox;
|
|
1975
2242
|
const characters = this.characters;
|
|
@@ -2101,7 +2368,7 @@ class Text extends Reactivable {
|
|
|
2101
2368
|
options.onContext?.(ctx);
|
|
2102
2369
|
}
|
|
2103
2370
|
dispose() {
|
|
2104
|
-
this.measurer.dispose();
|
|
2371
|
+
this.measurer.dispose?.();
|
|
2105
2372
|
this._renderer = void 0;
|
|
2106
2373
|
this._rendererCtx = void 0;
|
|
2107
2374
|
}
|
|
@@ -2127,6 +2394,9 @@ __decorateClass([
|
|
|
2127
2394
|
__decorateClass([
|
|
2128
2395
|
property()
|
|
2129
2396
|
], Text.prototype, "outline");
|
|
2397
|
+
__decorateClass([
|
|
2398
|
+
property()
|
|
2399
|
+
], Text.prototype, "deformation");
|
|
2130
2400
|
__decorateClass([
|
|
2131
2401
|
property({ internal: true })
|
|
2132
2402
|
], Text.prototype, "measureDom");
|
|
@@ -2134,4 +2404,4 @@ __decorateClass([
|
|
|
2134
2404
|
property({ internal: true })
|
|
2135
2405
|
], Text.prototype, "fonts");
|
|
2136
2406
|
|
|
2137
|
-
export { Canvas2DRenderer as C,
|
|
2407
|
+
export { Canvas2DRenderer as C, DomMeasurer as D, FontMeasurer as F, Paragraph as P, Text as T, Character as a, Fragment as b, backgroundPlugin as c, createSvgLoader as d, createSvgParser as e, getHighlightStyle as f, getEffectTransform2D as g, highlightPlugin as h, isEqualObject as i, isEqualValue as j, parseTransformOrigin as k, listStylePlugin as l, parseValueNumber as m, textDefaultStyle as n, outlinePlugin as o, parseColormap as p, renderPlugin as r, textDecorationPlugin as t };
|
|
@@ -57,6 +57,16 @@ declare class Character {
|
|
|
57
57
|
constructor(content: string, index: number, parent: Fragment);
|
|
58
58
|
protected _getFontSFNT(fonts?: Fonts): SFNT | undefined;
|
|
59
59
|
updateGlyph(sfnt?: SFNT | undefined): this;
|
|
60
|
+
/**
|
|
61
|
+
* Populate glyph metrics only (advance width/height, ascender/descender,
|
|
62
|
+
* baseline, …) without building the glyph `path` or touching boxes.
|
|
63
|
+
*
|
|
64
|
+
* The DOM {@link DomMeasurer} never needs this — it reads positions back from the
|
|
65
|
+
* browser. A pure-JS measurer (e.g. `FontMeasurer`) must know advances *before*
|
|
66
|
+
* it can place characters, so it calls this ahead of layout. `update()` later
|
|
67
|
+
* recomputes the same metrics while building the path, so this is idempotent.
|
|
68
|
+
*/
|
|
69
|
+
measureGlyph(fonts?: Fonts): this;
|
|
60
70
|
update(fonts?: Fonts): this;
|
|
61
71
|
protected _italic(path: Path2D, startPoint?: Vector2Like): void;
|
|
62
72
|
getGlyphMinMax(min?: Vector2, max?: Vector2, withStyle?: boolean): {
|
|
@@ -66,24 +76,6 @@ declare class Character {
|
|
|
66
76
|
getGlyphBoundingBox(withStyle?: boolean): BoundingBox | undefined;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
interface Plugin {
|
|
70
|
-
name: string;
|
|
71
|
-
pathSet?: Path2DSet;
|
|
72
|
-
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
|
|
73
|
-
update?: (text: Text$1) => void;
|
|
74
|
-
updateOrder?: number;
|
|
75
|
-
render?: (renderer: Canvas2DRenderer) => void;
|
|
76
|
-
renderOrder?: number;
|
|
77
|
-
load?: (text: Text$1) => Promise<void>;
|
|
78
|
-
context?: Record<string, any>;
|
|
79
|
-
}
|
|
80
|
-
interface Options extends TextObject {
|
|
81
|
-
debug?: boolean;
|
|
82
|
-
measureDom?: HTMLElement;
|
|
83
|
-
fonts?: Fonts;
|
|
84
|
-
plugins?: Plugin[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
79
|
interface MeasuredParagraph {
|
|
88
80
|
paragraphIndex: number;
|
|
89
81
|
left: number;
|
|
@@ -127,7 +119,7 @@ interface RootDomStyles {
|
|
|
127
119
|
section: Record<string, any>;
|
|
128
120
|
ul: Record<string, any>;
|
|
129
121
|
}
|
|
130
|
-
declare class
|
|
122
|
+
declare class DomMeasurer {
|
|
131
123
|
static notZeroStyles: Set<string>;
|
|
132
124
|
static pxStyles: Set<string>;
|
|
133
125
|
protected _styleCache: WeakMap<object, Record<string, any>>;
|
|
@@ -166,6 +158,47 @@ declare class Measurer {
|
|
|
166
158
|
dispose(): void;
|
|
167
159
|
}
|
|
168
160
|
|
|
161
|
+
interface Plugin {
|
|
162
|
+
name: string;
|
|
163
|
+
pathSet?: Path2DSet;
|
|
164
|
+
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
|
|
165
|
+
update?: (text: Text$1) => void;
|
|
166
|
+
updateOrder?: number;
|
|
167
|
+
render?: (renderer: Canvas2DRenderer) => void;
|
|
168
|
+
renderOrder?: number;
|
|
169
|
+
load?: (text: Text$1) => Promise<void>;
|
|
170
|
+
context?: Record<string, any>;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Pluggable layout backend. Implementations fill the four-level boxes
|
|
174
|
+
* (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
|
|
175
|
+
* in place and return the overall bounding box.
|
|
176
|
+
*
|
|
177
|
+
* - `DomMeasurer` — DOM-based, uses the browser as ground truth (default).
|
|
178
|
+
* - `FontMeasurer` — pure-JS, DOM-free; needs `fonts` to resolve glyph advances.
|
|
179
|
+
*
|
|
180
|
+
* `fonts` is passed positionally by `Text.measure()`; DOM-based measurers ignore
|
|
181
|
+
* it (a method may safely declare fewer parameters than the interface).
|
|
182
|
+
*/
|
|
183
|
+
interface TextMeasurer {
|
|
184
|
+
measure: (paragraphs: Paragraph[], rootStyle: FullStyle, dom?: HTMLElement, fonts?: Fonts) => MeasureDomResult;
|
|
185
|
+
createDom?: (paragraphs: Paragraph[], rootStyle: FullStyle) => HTMLElement;
|
|
186
|
+
dispose?: () => void;
|
|
187
|
+
}
|
|
188
|
+
/** Built-in layout backends. `'font'` → `FontMeasurer`, `'dom'` → `DomMeasurer`. */
|
|
189
|
+
type MeasurerKind = 'dom' | 'font';
|
|
190
|
+
interface Options extends TextObject {
|
|
191
|
+
debug?: boolean;
|
|
192
|
+
measureDom?: HTMLElement;
|
|
193
|
+
fonts?: Fonts;
|
|
194
|
+
plugins?: Plugin[];
|
|
195
|
+
/**
|
|
196
|
+
* Layout backend: `'font'` (pure-JS) or `'dom'` (browser), or a custom
|
|
197
|
+
* `TextMeasurer`. Defaults to `'font'` when `fonts` are provided, else `'dom'`.
|
|
198
|
+
*/
|
|
199
|
+
measurer?: MeasurerKind | TextMeasurer;
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
interface RenderOptions {
|
|
170
203
|
view: HTMLCanvasElement;
|
|
171
204
|
pixelRatio?: number;
|
|
@@ -207,6 +240,7 @@ declare class Text$1 extends Reactivable {
|
|
|
207
240
|
effects?: NormalizedText['effects'];
|
|
208
241
|
fill?: NormalizedText['fill'];
|
|
209
242
|
outline?: NormalizedText['outline'];
|
|
243
|
+
deformation?: NormalizedText['deformation'];
|
|
210
244
|
measureDom?: HTMLElement;
|
|
211
245
|
fonts?: Fonts;
|
|
212
246
|
needsUpdate: boolean;
|
|
@@ -220,7 +254,7 @@ declare class Text$1 extends Reactivable {
|
|
|
220
254
|
glyphBox: BoundingBox;
|
|
221
255
|
pathBox: BoundingBox;
|
|
222
256
|
boundingBox: BoundingBox;
|
|
223
|
-
measurer:
|
|
257
|
+
measurer: TextMeasurer;
|
|
224
258
|
plugins: Map<string, Plugin>;
|
|
225
259
|
pathSets: Path2DSet[];
|
|
226
260
|
protected _paragraphs: Paragraph[];
|
|
@@ -303,5 +337,5 @@ declare class Canvas2DRenderer {
|
|
|
303
337
|
drawCharacter: (character: Character, effect?: NormalizedEffect) => void;
|
|
304
338
|
}
|
|
305
339
|
|
|
306
|
-
export { Canvas2DRenderer as C, Fragment as F, Paragraph as P, Text$1 as T, Character as a,
|
|
307
|
-
export type {
|
|
340
|
+
export { Canvas2DRenderer as C, DomMeasurer as D, Fragment as F, Paragraph as P, Text$1 as T, Character as a, textDefaultStyle as t };
|
|
341
|
+
export type { MeasureDomResult as M, Options as O, RenderOptions as R, DrawShapePathsOptions as b, MeasureResult as c, MeasuredCharacter as d, MeasuredCharacterRect as e, MeasuredFragment as f, MeasuredParagraph as g, MeasurerKind as h, Plugin as i, TextEvents as j, TextMeasurer as k };
|
|
@@ -57,6 +57,16 @@ declare class Character {
|
|
|
57
57
|
constructor(content: string, index: number, parent: Fragment);
|
|
58
58
|
protected _getFontSFNT(fonts?: Fonts): SFNT | undefined;
|
|
59
59
|
updateGlyph(sfnt?: SFNT | undefined): this;
|
|
60
|
+
/**
|
|
61
|
+
* Populate glyph metrics only (advance width/height, ascender/descender,
|
|
62
|
+
* baseline, …) without building the glyph `path` or touching boxes.
|
|
63
|
+
*
|
|
64
|
+
* The DOM {@link DomMeasurer} never needs this — it reads positions back from the
|
|
65
|
+
* browser. A pure-JS measurer (e.g. `FontMeasurer`) must know advances *before*
|
|
66
|
+
* it can place characters, so it calls this ahead of layout. `update()` later
|
|
67
|
+
* recomputes the same metrics while building the path, so this is idempotent.
|
|
68
|
+
*/
|
|
69
|
+
measureGlyph(fonts?: Fonts): this;
|
|
60
70
|
update(fonts?: Fonts): this;
|
|
61
71
|
protected _italic(path: Path2D, startPoint?: Vector2Like): void;
|
|
62
72
|
getGlyphMinMax(min?: Vector2, max?: Vector2, withStyle?: boolean): {
|
|
@@ -66,24 +76,6 @@ declare class Character {
|
|
|
66
76
|
getGlyphBoundingBox(withStyle?: boolean): BoundingBox | undefined;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
interface Plugin {
|
|
70
|
-
name: string;
|
|
71
|
-
pathSet?: Path2DSet;
|
|
72
|
-
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
|
|
73
|
-
update?: (text: Text$1) => void;
|
|
74
|
-
updateOrder?: number;
|
|
75
|
-
render?: (renderer: Canvas2DRenderer) => void;
|
|
76
|
-
renderOrder?: number;
|
|
77
|
-
load?: (text: Text$1) => Promise<void>;
|
|
78
|
-
context?: Record<string, any>;
|
|
79
|
-
}
|
|
80
|
-
interface Options extends TextObject {
|
|
81
|
-
debug?: boolean;
|
|
82
|
-
measureDom?: HTMLElement;
|
|
83
|
-
fonts?: Fonts;
|
|
84
|
-
plugins?: Plugin[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
79
|
interface MeasuredParagraph {
|
|
88
80
|
paragraphIndex: number;
|
|
89
81
|
left: number;
|
|
@@ -127,7 +119,7 @@ interface RootDomStyles {
|
|
|
127
119
|
section: Record<string, any>;
|
|
128
120
|
ul: Record<string, any>;
|
|
129
121
|
}
|
|
130
|
-
declare class
|
|
122
|
+
declare class DomMeasurer {
|
|
131
123
|
static notZeroStyles: Set<string>;
|
|
132
124
|
static pxStyles: Set<string>;
|
|
133
125
|
protected _styleCache: WeakMap<object, Record<string, any>>;
|
|
@@ -166,6 +158,47 @@ declare class Measurer {
|
|
|
166
158
|
dispose(): void;
|
|
167
159
|
}
|
|
168
160
|
|
|
161
|
+
interface Plugin {
|
|
162
|
+
name: string;
|
|
163
|
+
pathSet?: Path2DSet;
|
|
164
|
+
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
|
|
165
|
+
update?: (text: Text$1) => void;
|
|
166
|
+
updateOrder?: number;
|
|
167
|
+
render?: (renderer: Canvas2DRenderer) => void;
|
|
168
|
+
renderOrder?: number;
|
|
169
|
+
load?: (text: Text$1) => Promise<void>;
|
|
170
|
+
context?: Record<string, any>;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Pluggable layout backend. Implementations fill the four-level boxes
|
|
174
|
+
* (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
|
|
175
|
+
* in place and return the overall bounding box.
|
|
176
|
+
*
|
|
177
|
+
* - `DomMeasurer` — DOM-based, uses the browser as ground truth (default).
|
|
178
|
+
* - `FontMeasurer` — pure-JS, DOM-free; needs `fonts` to resolve glyph advances.
|
|
179
|
+
*
|
|
180
|
+
* `fonts` is passed positionally by `Text.measure()`; DOM-based measurers ignore
|
|
181
|
+
* it (a method may safely declare fewer parameters than the interface).
|
|
182
|
+
*/
|
|
183
|
+
interface TextMeasurer {
|
|
184
|
+
measure: (paragraphs: Paragraph[], rootStyle: FullStyle, dom?: HTMLElement, fonts?: Fonts) => MeasureDomResult;
|
|
185
|
+
createDom?: (paragraphs: Paragraph[], rootStyle: FullStyle) => HTMLElement;
|
|
186
|
+
dispose?: () => void;
|
|
187
|
+
}
|
|
188
|
+
/** Built-in layout backends. `'font'` → `FontMeasurer`, `'dom'` → `DomMeasurer`. */
|
|
189
|
+
type MeasurerKind = 'dom' | 'font';
|
|
190
|
+
interface Options extends TextObject {
|
|
191
|
+
debug?: boolean;
|
|
192
|
+
measureDom?: HTMLElement;
|
|
193
|
+
fonts?: Fonts;
|
|
194
|
+
plugins?: Plugin[];
|
|
195
|
+
/**
|
|
196
|
+
* Layout backend: `'font'` (pure-JS) or `'dom'` (browser), or a custom
|
|
197
|
+
* `TextMeasurer`. Defaults to `'font'` when `fonts` are provided, else `'dom'`.
|
|
198
|
+
*/
|
|
199
|
+
measurer?: MeasurerKind | TextMeasurer;
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
interface RenderOptions {
|
|
170
203
|
view: HTMLCanvasElement;
|
|
171
204
|
pixelRatio?: number;
|
|
@@ -207,6 +240,7 @@ declare class Text$1 extends Reactivable {
|
|
|
207
240
|
effects?: NormalizedText['effects'];
|
|
208
241
|
fill?: NormalizedText['fill'];
|
|
209
242
|
outline?: NormalizedText['outline'];
|
|
243
|
+
deformation?: NormalizedText['deformation'];
|
|
210
244
|
measureDom?: HTMLElement;
|
|
211
245
|
fonts?: Fonts;
|
|
212
246
|
needsUpdate: boolean;
|
|
@@ -220,7 +254,7 @@ declare class Text$1 extends Reactivable {
|
|
|
220
254
|
glyphBox: BoundingBox;
|
|
221
255
|
pathBox: BoundingBox;
|
|
222
256
|
boundingBox: BoundingBox;
|
|
223
|
-
measurer:
|
|
257
|
+
measurer: TextMeasurer;
|
|
224
258
|
plugins: Map<string, Plugin>;
|
|
225
259
|
pathSets: Path2DSet[];
|
|
226
260
|
protected _paragraphs: Paragraph[];
|
|
@@ -303,5 +337,5 @@ declare class Canvas2DRenderer {
|
|
|
303
337
|
drawCharacter: (character: Character, effect?: NormalizedEffect) => void;
|
|
304
338
|
}
|
|
305
339
|
|
|
306
|
-
export { Canvas2DRenderer as C, Fragment as F, Paragraph as P, Text$1 as T, Character as a,
|
|
307
|
-
export type {
|
|
340
|
+
export { Canvas2DRenderer as C, DomMeasurer as D, Fragment as F, Paragraph as P, Text$1 as T, Character as a, textDefaultStyle as t };
|
|
341
|
+
export type { MeasureDomResult as M, Options as O, RenderOptions as R, DrawShapePathsOptions as b, MeasureResult as c, MeasuredCharacter as d, MeasuredCharacterRect as e, MeasuredFragment as f, MeasuredParagraph as g, MeasurerKind as h, Plugin as i, TextEvents as j, TextMeasurer as k };
|