modern-text 0.2.5 → 0.2.7
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 +142 -95
- package/dist/index.d.cts +17 -20
- package/dist/index.d.mts +17 -20
- package/dist/index.d.ts +17 -20
- package/dist/index.js +2 -2
- package/dist/index.mjs +143 -96
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoundingBox, Path2D,
|
|
1
|
+
import { BoundingBox, Path2D, Vector2, parseSvgToDom, parseSvg, Matrix3 } from 'modern-path2d';
|
|
2
2
|
import { fonts, Woff, Ttf } from 'modern-font';
|
|
3
3
|
export * from 'modern-font';
|
|
4
4
|
|
|
@@ -170,9 +170,9 @@ class Character {
|
|
|
170
170
|
this.index = index;
|
|
171
171
|
this.parent = parent;
|
|
172
172
|
__publicField$4(this, "boundingBox", new BoundingBox());
|
|
173
|
-
__publicField$4(this, "path", new Path2D());
|
|
174
173
|
__publicField$4(this, "textWidth", 0);
|
|
175
174
|
__publicField$4(this, "textHeight", 0);
|
|
175
|
+
__publicField$4(this, "path", new Path2D());
|
|
176
176
|
// glyph
|
|
177
177
|
__publicField$4(this, "commands", []);
|
|
178
178
|
}
|
|
@@ -192,11 +192,14 @@ class Character {
|
|
|
192
192
|
}
|
|
193
193
|
return void 0;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
updateGlyph(font = this._font()) {
|
|
196
|
+
if (!font) {
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
const { unitsPerEm, ascender, descender, os2, post } = font;
|
|
196
200
|
const { content, computedStyle, boundingBox, isVertical } = this;
|
|
197
201
|
const { left, top, height } = boundingBox;
|
|
198
202
|
const { fontSize } = computedStyle;
|
|
199
|
-
const { unitsPerEm, ascender, descender, os2, post } = font;
|
|
200
203
|
const rate = unitsPerEm / fontSize;
|
|
201
204
|
const glyphWidth = font.getAdvanceWidth(content, fontSize);
|
|
202
205
|
const glyphHeight = (ascender + Math.abs(descender)) / rate;
|
|
@@ -217,84 +220,7 @@ class Character {
|
|
|
217
220
|
this.centerPoint = this.glyphBox.getCenterPoint();
|
|
218
221
|
return this;
|
|
219
222
|
}
|
|
220
|
-
|
|
221
|
-
const { isVertical, underlinePosition, yStrikeoutPosition } = this;
|
|
222
|
-
const { textDecoration, fontSize } = this.computedStyle;
|
|
223
|
-
const { left, top, width, height } = this.boundingBox;
|
|
224
|
-
const lineWidth = 0.1 * fontSize;
|
|
225
|
-
let start;
|
|
226
|
-
switch (textDecoration) {
|
|
227
|
-
case "underline":
|
|
228
|
-
if (isVertical) {
|
|
229
|
-
start = left;
|
|
230
|
-
} else {
|
|
231
|
-
start = top + underlinePosition;
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
case "line-through":
|
|
235
|
-
if (isVertical) {
|
|
236
|
-
start = left + width / 2;
|
|
237
|
-
} else {
|
|
238
|
-
start = top + yStrikeoutPosition;
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
case "none":
|
|
242
|
-
default:
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
if (isVertical) {
|
|
246
|
-
return [
|
|
247
|
-
{ type: "M", x: start, y: top },
|
|
248
|
-
{ type: "L", x: start, y: top + height },
|
|
249
|
-
{ type: "L", x: start + lineWidth, y: top + height },
|
|
250
|
-
{ type: "L", x: start + lineWidth, y: top },
|
|
251
|
-
{ type: "Z" }
|
|
252
|
-
];
|
|
253
|
-
} else {
|
|
254
|
-
return [
|
|
255
|
-
{ type: "M", x: left, y: start },
|
|
256
|
-
{ type: "L", x: left + width, y: start },
|
|
257
|
-
{ type: "L", x: left + width, y: start + lineWidth },
|
|
258
|
-
{ type: "L", x: left, y: start + lineWidth },
|
|
259
|
-
{ type: "Z" }
|
|
260
|
-
];
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
_transform(commands, cb) {
|
|
264
|
-
return commands.map((rawCmd) => {
|
|
265
|
-
const cmd = { ...rawCmd };
|
|
266
|
-
switch (cmd.type) {
|
|
267
|
-
case "L":
|
|
268
|
-
case "M":
|
|
269
|
-
[cmd.x, cmd.y] = cb(cmd.x, cmd.y);
|
|
270
|
-
break;
|
|
271
|
-
case "Q":
|
|
272
|
-
[cmd.x1, cmd.y1] = cb(cmd.x1, cmd.y1);
|
|
273
|
-
[cmd.x, cmd.y] = cb(cmd.x, cmd.y);
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
return cmd;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
_italic(commands, startPoint) {
|
|
280
|
-
const { baseline, glyphWidth } = this;
|
|
281
|
-
const { left, top } = this.boundingBox;
|
|
282
|
-
const _startPoint = startPoint || {
|
|
283
|
-
y: top + baseline,
|
|
284
|
-
x: left + glyphWidth / 2
|
|
285
|
-
};
|
|
286
|
-
return this._transform(commands, (x, y) => {
|
|
287
|
-
const p = getSkewPoint({ x, y }, _startPoint, -0.24, 0);
|
|
288
|
-
return [p.x, p.y];
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
_rotation90(commands, point) {
|
|
292
|
-
return this._transform(commands, (x, y) => {
|
|
293
|
-
const p = getPointPosition({ x, y }, point, 90);
|
|
294
|
-
return [p.x, p.y];
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
updatePath() {
|
|
223
|
+
updateCommands() {
|
|
298
224
|
const font = this._font();
|
|
299
225
|
if (!font) {
|
|
300
226
|
return this;
|
|
@@ -309,7 +235,7 @@ class Character {
|
|
|
309
235
|
baseline,
|
|
310
236
|
glyphHeight,
|
|
311
237
|
glyphWidth
|
|
312
|
-
} = this.
|
|
238
|
+
} = this.updateGlyph(font);
|
|
313
239
|
const { os2, ascender, descender } = font;
|
|
314
240
|
const usWinAscent = ascender;
|
|
315
241
|
const usWinDescent = descender;
|
|
@@ -367,14 +293,127 @@ class Character {
|
|
|
367
293
|
}
|
|
368
294
|
commands.push(...this._decoration());
|
|
369
295
|
this.commands = commands;
|
|
370
|
-
this
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
updatePath() {
|
|
299
|
+
this.path?.copy(new Path2D(this.commands));
|
|
371
300
|
return this;
|
|
372
301
|
}
|
|
373
302
|
update() {
|
|
374
|
-
this.updatePath();
|
|
303
|
+
this.updateCommands().updatePath();
|
|
375
304
|
return this;
|
|
376
305
|
}
|
|
377
|
-
|
|
306
|
+
_decoration() {
|
|
307
|
+
const { isVertical, underlinePosition, yStrikeoutPosition } = this;
|
|
308
|
+
const { textDecoration, fontSize } = this.computedStyle;
|
|
309
|
+
const { left, top, width, height } = this.boundingBox;
|
|
310
|
+
const lineWidth = 0.1 * fontSize;
|
|
311
|
+
let start;
|
|
312
|
+
switch (textDecoration) {
|
|
313
|
+
case "underline":
|
|
314
|
+
if (isVertical) {
|
|
315
|
+
start = left;
|
|
316
|
+
} else {
|
|
317
|
+
start = top + underlinePosition;
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case "line-through":
|
|
321
|
+
if (isVertical) {
|
|
322
|
+
start = left + width / 2;
|
|
323
|
+
} else {
|
|
324
|
+
start = top + yStrikeoutPosition;
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
case "none":
|
|
328
|
+
default:
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
if (isVertical) {
|
|
332
|
+
return [
|
|
333
|
+
{ type: "M", x: start, y: top },
|
|
334
|
+
{ type: "L", x: start, y: top + height },
|
|
335
|
+
{ type: "L", x: start + lineWidth, y: top + height },
|
|
336
|
+
{ type: "L", x: start + lineWidth, y: top },
|
|
337
|
+
{ type: "Z" }
|
|
338
|
+
];
|
|
339
|
+
} else {
|
|
340
|
+
return [
|
|
341
|
+
{ type: "M", x: left, y: start },
|
|
342
|
+
{ type: "L", x: left + width, y: start },
|
|
343
|
+
{ type: "L", x: left + width, y: start + lineWidth },
|
|
344
|
+
{ type: "L", x: left, y: start + lineWidth },
|
|
345
|
+
{ type: "Z" }
|
|
346
|
+
];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
_italic(commands, startPoint) {
|
|
350
|
+
const { baseline, glyphWidth } = this;
|
|
351
|
+
const { left, top } = this.boundingBox;
|
|
352
|
+
const _startPoint = startPoint || {
|
|
353
|
+
y: top + baseline,
|
|
354
|
+
x: left + glyphWidth / 2
|
|
355
|
+
};
|
|
356
|
+
return this._transform(commands, (x, y) => {
|
|
357
|
+
const p = getSkewPoint({ x, y }, _startPoint, -0.24, 0);
|
|
358
|
+
return [p.x, p.y];
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
_rotation90(commands, point) {
|
|
362
|
+
return this._transform(commands, (x, y) => {
|
|
363
|
+
const p = getPointPosition({ x, y }, point, 90);
|
|
364
|
+
return [p.x, p.y];
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
_transform(commands, cb) {
|
|
368
|
+
return commands.map((rawCmd) => {
|
|
369
|
+
const cmd = { ...rawCmd };
|
|
370
|
+
switch (cmd.type) {
|
|
371
|
+
case "L":
|
|
372
|
+
case "M":
|
|
373
|
+
[cmd.x, cmd.y] = cb(cmd.x, cmd.y);
|
|
374
|
+
break;
|
|
375
|
+
case "Q":
|
|
376
|
+
[cmd.x1, cmd.y1] = cb(cmd.x1, cmd.y1);
|
|
377
|
+
[cmd.x, cmd.y] = cb(cmd.x, cmd.y);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
return cmd;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
forEachCommand(cb) {
|
|
384
|
+
const commands = this.commands;
|
|
385
|
+
const last = { x: 0, y: 0 };
|
|
386
|
+
const first = { x: 0, y: 0 };
|
|
387
|
+
let isFirst = true;
|
|
388
|
+
let doSetFirstPoint = false;
|
|
389
|
+
for (let i = 0, len = commands.length; i < len; i++) {
|
|
390
|
+
if (isFirst) {
|
|
391
|
+
doSetFirstPoint = true;
|
|
392
|
+
isFirst = false;
|
|
393
|
+
}
|
|
394
|
+
let command = commands[i];
|
|
395
|
+
command = cb(command, i, { last, first }) ?? command;
|
|
396
|
+
switch (command.type) {
|
|
397
|
+
case "M":
|
|
398
|
+
case "L":
|
|
399
|
+
case "Q":
|
|
400
|
+
last.x = command.x;
|
|
401
|
+
last.y = command.y;
|
|
402
|
+
if (doSetFirstPoint) {
|
|
403
|
+
first.x = last.x;
|
|
404
|
+
first.y = last.y;
|
|
405
|
+
}
|
|
406
|
+
break;
|
|
407
|
+
case "Z":
|
|
408
|
+
last.x = first.x;
|
|
409
|
+
last.y = first.y;
|
|
410
|
+
isFirst = true;
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
378
417
|
let last = { x: 0, y: 0 };
|
|
379
418
|
this.commands.forEach((cmd) => {
|
|
380
419
|
switch (cmd.type) {
|
|
@@ -402,6 +441,13 @@ class Character {
|
|
|
402
441
|
});
|
|
403
442
|
return { min, max };
|
|
404
443
|
}
|
|
444
|
+
getBoundingBox() {
|
|
445
|
+
const min = Vector2.MAX;
|
|
446
|
+
const max = Vector2.MIN;
|
|
447
|
+
this.getMinMax(min, max);
|
|
448
|
+
this.path.getMinMax(min, max);
|
|
449
|
+
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
450
|
+
}
|
|
405
451
|
drawTo(ctx, config = {}) {
|
|
406
452
|
drawPaths({
|
|
407
453
|
ctx,
|
|
@@ -544,8 +590,8 @@ class Highlighter extends Feature {
|
|
|
544
590
|
if (!this.paths.length) {
|
|
545
591
|
return new BoundingBox();
|
|
546
592
|
}
|
|
547
|
-
const min =
|
|
548
|
-
const max =
|
|
593
|
+
const min = Vector2.MAX;
|
|
594
|
+
const max = Vector2.MIN;
|
|
549
595
|
this.paths.forEach((path) => path.getMinMax(min, max));
|
|
550
596
|
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
551
597
|
}
|
|
@@ -571,7 +617,7 @@ class Highlighter extends Feature {
|
|
|
571
617
|
this.paths = groups.filter((characters2) => characters2.length).map((characters2) => {
|
|
572
618
|
return {
|
|
573
619
|
url: characters2[0].parent.highlight.url,
|
|
574
|
-
box: BoundingBox.from(...characters2.map((c) => c.
|
|
620
|
+
box: BoundingBox.from(...characters2.map((c) => c.boundingBox)),
|
|
575
621
|
baseline: Math.max(...characters2.map((c) => c.baseline))
|
|
576
622
|
};
|
|
577
623
|
}).map((group2) => this._parseGroup(group2, fontSize)).flat();
|
|
@@ -579,8 +625,8 @@ class Highlighter extends Feature {
|
|
|
579
625
|
_parseSvg(url) {
|
|
580
626
|
const svg = parseSvgToDom(url);
|
|
581
627
|
const paths = parseSvg(svg);
|
|
582
|
-
const min =
|
|
583
|
-
const max =
|
|
628
|
+
const min = Vector2.MAX;
|
|
629
|
+
const max = Vector2.MIN;
|
|
584
630
|
paths.forEach((path) => path.getMinMax(min, max));
|
|
585
631
|
const { x, y, width, height } = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
586
632
|
const viewBox = svg.getAttribute("viewBox").split(" ").map(Number);
|
|
@@ -1040,14 +1086,15 @@ class Text {
|
|
|
1040
1086
|
const { paragraphs, boundingBox } = this.measure();
|
|
1041
1087
|
this.paragraphs = paragraphs;
|
|
1042
1088
|
this.boundingBox = boundingBox;
|
|
1043
|
-
|
|
1089
|
+
const characters = this.characters;
|
|
1090
|
+
characters.forEach((c) => c.update());
|
|
1091
|
+
this._highlighter.highlight();
|
|
1044
1092
|
if (this.deformation) {
|
|
1045
1093
|
this._deformer.deform();
|
|
1046
1094
|
}
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
this.characters.forEach((c) => c.getMinMax(min, max));
|
|
1095
|
+
const min = Vector2.MAX;
|
|
1096
|
+
const max = Vector2.MIN;
|
|
1097
|
+
characters.forEach((c) => c.getMinMax(min, max));
|
|
1051
1098
|
this.renderBoundingBox = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
1052
1099
|
return this;
|
|
1053
1100
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modern-text",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.7",
|
|
5
5
|
"packageManager": "pnpm@9.9.0",
|
|
6
6
|
"description": "Measure and render text in a way that describes the DOM.",
|
|
7
7
|
"author": "wxm",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"modern-font": "^0.1.5",
|
|
61
|
-
"modern-path2d": "^0.1.
|
|
61
|
+
"modern-path2d": "^0.1.8"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@antfu/eslint-config": "^3.7.3",
|