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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { BoundingBox, Path2D, Point2D, parseSvgToDom, parseSvg, Matrix3 } from 'modern-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
- _updateGlyph(font) {
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
- _decoration() {
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._updateGlyph(font);
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.path = new Path2D(commands);
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
- getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
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 = Point2D.MAX;
548
- const max = Point2D.MIN;
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.glyphBox)),
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 = Point2D.MAX;
583
- const max = Point2D.MIN;
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
- this.characters.forEach((c) => c.update());
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
- this._highlighter.highlight();
1048
- const min = Point2D.MAX;
1049
- const max = Point2D.MIN;
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.5",
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.7"
61
+ "modern-path2d": "^0.1.8"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@antfu/eslint-config": "^3.7.3",