modern-text 0.2.3 → 0.2.4

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,5 +1,4 @@
1
1
  import { BoundingBox, Path2D, Point2D, parseSvgToDom, parseSvg, Matrix3 } from 'modern-path2d';
2
- export * from 'modern-path2d';
3
2
  import { fonts, Woff, Ttf } from 'modern-font';
4
3
  export * from 'modern-font';
5
4
 
@@ -184,12 +183,12 @@ class Character {
184
183
  get fontSize() {
185
184
  return this.computedStyle.fontSize;
186
185
  }
187
- _updateFont() {
186
+ _font() {
188
187
  const font = fonts.get(this.computedStyle.fontFamily)?.font;
189
188
  if (font instanceof Woff || font instanceof Ttf) {
190
- this.font = font.sfnt;
189
+ return font.sfnt;
191
190
  }
192
- return this;
191
+ return void 0;
193
192
  }
194
193
  _updateGlyph(font) {
195
194
  const { content, computedStyle, boundingBox, isVertical } = this;
@@ -216,8 +215,85 @@ class Character {
216
215
  this.centerPoint = this.glyphBox.getCenterPoint();
217
216
  return this;
218
217
  }
218
+ _decoration() {
219
+ const { isVertical, underlinePosition, yStrikeoutPosition } = this;
220
+ const { textDecoration, fontSize } = this.computedStyle;
221
+ const { left, top, width, height } = this.boundingBox;
222
+ const lineWidth = 0.1 * fontSize;
223
+ let start;
224
+ switch (textDecoration) {
225
+ case "underline":
226
+ if (isVertical) {
227
+ start = left;
228
+ } else {
229
+ start = top + underlinePosition;
230
+ }
231
+ break;
232
+ case "line-through":
233
+ if (isVertical) {
234
+ start = left + width / 2;
235
+ } else {
236
+ start = top + yStrikeoutPosition;
237
+ }
238
+ break;
239
+ case "none":
240
+ default:
241
+ return [];
242
+ }
243
+ if (isVertical) {
244
+ return [
245
+ { type: "M", x: start, y: top },
246
+ { type: "L", x: start, y: top + height },
247
+ { type: "L", x: start + lineWidth, y: top + height },
248
+ { type: "L", x: start + lineWidth, y: top },
249
+ { type: "Z" }
250
+ ];
251
+ } else {
252
+ return [
253
+ { type: "M", x: left, y: start },
254
+ { type: "L", x: left + width, y: start },
255
+ { type: "L", x: left + width, y: start + lineWidth },
256
+ { type: "L", x: left, y: start + lineWidth },
257
+ { type: "Z" }
258
+ ];
259
+ }
260
+ }
261
+ _transform(commands, cb) {
262
+ return commands.map((rawCmd) => {
263
+ const cmd = { ...rawCmd };
264
+ switch (cmd.type) {
265
+ case "L":
266
+ case "M":
267
+ [cmd.x, cmd.y] = cb(cmd.x, cmd.y);
268
+ break;
269
+ case "Q":
270
+ [cmd.x1, cmd.y1] = cb(cmd.x1, cmd.y1);
271
+ [cmd.x, cmd.y] = cb(cmd.x, cmd.y);
272
+ break;
273
+ }
274
+ return cmd;
275
+ });
276
+ }
277
+ _italic(commands, startPoint) {
278
+ const { baseline, glyphWidth } = this;
279
+ const { left, top } = this.boundingBox;
280
+ const _startPoint = startPoint || {
281
+ y: top + baseline,
282
+ x: left + glyphWidth / 2
283
+ };
284
+ return this._transform(commands, (x, y) => {
285
+ const p = getSkewPoint({ x, y }, _startPoint, -0.24, 0);
286
+ return [p.x, p.y];
287
+ });
288
+ }
289
+ _rotation90(commands, point) {
290
+ return this._transform(commands, (x, y) => {
291
+ const p = getPointPosition({ x, y }, point, 90);
292
+ return [p.x, p.y];
293
+ });
294
+ }
219
295
  updatePath() {
220
- const font = this._updateFont().font;
296
+ const font = this._font();
221
297
  if (!font) {
222
298
  return this;
223
299
  }
@@ -230,16 +306,14 @@ class Character {
230
306
  computedStyle,
231
307
  baseline,
232
308
  glyphHeight,
233
- glyphWidth,
234
- yStrikeoutPosition,
235
- underlinePosition
309
+ glyphWidth
236
310
  } = this._updateGlyph(font);
237
311
  const { os2, ascender, descender } = font;
238
312
  const usWinAscent = ascender;
239
313
  const usWinDescent = descender;
240
314
  const typoAscender = os2.sTypoAscender;
241
- const { left, top, width, height } = boundingBox;
242
- const { fontSize, fontStyle, textDecoration } = computedStyle;
315
+ const { left, top } = boundingBox;
316
+ const { fontSize, fontStyle } = computedStyle;
243
317
  let x = left;
244
318
  let y = top + baseline;
245
319
  let glyphIndex;
@@ -258,119 +332,74 @@ class Character {
258
332
  x: x + glyphWidth / 2
259
333
  };
260
334
  if (fontStyle === "italic") {
261
- setItalic(
335
+ commands = this._italic(
262
336
  commands,
263
337
  isVertical ? {
264
338
  x: point.x,
265
339
  y: top - (glyphHeight - glyphWidth) / 2 + baseline
266
- } : null
340
+ } : void 0
267
341
  );
268
342
  }
269
- set90Rotation(commands, point);
343
+ commands = this._rotation90(commands, point);
270
344
  } else {
271
345
  if (glyphIndex !== void 0) {
272
346
  commands = font.glyf.glyphs.get(glyphIndex).getPathCommands(x, y, fontSize);
273
347
  if (fontStyle === "italic") {
274
- setItalic(
348
+ commands = this._italic(
275
349
  commands,
276
350
  isVertical ? {
277
351
  x: x + glyphWidth / 2,
278
352
  y: top + typoAscender / (usWinAscent + Math.abs(usWinDescent)) * glyphHeight
279
- } : null
353
+ } : void 0
280
354
  );
281
355
  }
282
356
  } else {
283
357
  commands = font.getPathCommands(content, x, y, fontSize) ?? [];
284
358
  if (fontStyle === "italic") {
285
- setItalic(
359
+ commands = this._italic(
286
360
  commands,
287
- isVertical ? { x: x + glyphHeight / 2, y } : null
361
+ isVertical ? { x: x + glyphHeight / 2, y } : void 0
288
362
  );
289
363
  }
290
364
  }
291
365
  }
292
- const lineWidth = 0.1 * fontSize;
293
- if (isVertical) {
294
- const create = (start, len) => [
295
- { type: "M", x: start, y: top },
296
- { type: "L", x: start, y: top + height },
297
- { type: "L", x: start + len, y: top + height },
298
- { type: "L", x: start + len, y: top },
299
- { type: "Z" }
300
- ];
301
- switch (textDecoration) {
302
- case "underline":
303
- commands.push(...create(left, lineWidth));
304
- break;
305
- case "line-through":
306
- commands.push(...create(left + width / 2, lineWidth));
307
- break;
308
- }
309
- } else {
310
- const create = (start, len) => [
311
- { type: "M", x: left, y: start },
312
- { type: "L", x: left + width, y: start },
313
- { type: "L", x: left + width, y: start + len },
314
- { type: "L", x: left, y: start + len },
315
- { type: "Z" }
316
- ];
317
- switch (textDecoration) {
318
- case "underline":
319
- commands.push(...create(top + underlinePosition, lineWidth));
320
- break;
321
- case "line-through":
322
- commands.push(...create(top + yStrikeoutPosition, lineWidth));
323
- break;
324
- }
325
- }
366
+ commands.push(...this._decoration());
367
+ this.commands = commands;
326
368
  this.path = new Path2D(commands);
327
369
  return this;
328
- function setItalic(commands2, startPoint) {
329
- const _startPoint = startPoint || {
330
- y: top + baseline,
331
- x: left + glyphWidth / 2
332
- };
333
- commands2.forEach((command) => {
334
- ["", "1", "2"].forEach((arg) => {
335
- if (command[`x${arg}`]) {
336
- const pos = getSkewPoint(
337
- {
338
- x: command[`x${arg}`],
339
- y: command[`y${arg}`]
340
- },
341
- _startPoint,
342
- -0.24,
343
- 0
344
- );
345
- command[`x${arg}`] = pos.x;
346
- command[`y${arg}`] = pos.y;
347
- }
348
- });
349
- });
350
- }
351
- function set90Rotation(commands2, point) {
352
- commands2.forEach((command) => {
353
- ["", "1", "2"].forEach((arg) => {
354
- if (command[`x${arg}`]) {
355
- const pos = getPointPosition(
356
- {
357
- x: command[`x${arg}`],
358
- y: command[`y${arg}`]
359
- },
360
- point,
361
- 90
362
- );
363
- command[`x${arg}`] = pos.x;
364
- command[`y${arg}`] = pos.y;
365
- }
366
- });
367
- });
368
- }
369
370
  }
370
371
  update() {
371
372
  this.updatePath();
372
373
  return this;
373
374
  }
375
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
376
+ let last = { x: 0, y: 0 };
377
+ this.commands.forEach((cmd) => {
378
+ switch (cmd.type) {
379
+ case "L":
380
+ case "M":
381
+ min.x = Math.min(min.x, cmd.x);
382
+ min.y = Math.min(min.y, cmd.y);
383
+ max.x = Math.max(max.x, cmd.x);
384
+ max.y = Math.max(max.y, cmd.y);
385
+ last = { x: cmd.x, y: cmd.y };
386
+ break;
387
+ case "Q": {
388
+ const x1 = 0.5 * (last.x + cmd.x1);
389
+ const y1 = 0.5 * (last.y + cmd.y1);
390
+ const x2 = 0.5 * (last.x + cmd.x);
391
+ const y2 = 0.5 * (last.y + cmd.y);
392
+ min.x = Math.min(min.x, last.x, cmd.x, x1, x2);
393
+ min.y = Math.min(min.y, last.y, cmd.y, y1, y2);
394
+ max.x = Math.max(max.x, last.x, cmd.x, x1, x2);
395
+ max.y = Math.max(max.y, last.y, cmd.y, y1, y2);
396
+ last = { x: cmd.x, y: cmd.y };
397
+ break;
398
+ }
399
+ }
400
+ });
401
+ return { min, max };
402
+ }
374
403
  drawTo(ctx, config = {}) {
375
404
  drawPaths({
376
405
  ctx,
@@ -455,6 +484,7 @@ class Feature {
455
484
 
456
485
  class Deformer extends Feature {
457
486
  deform() {
487
+ this._text.deformation?.();
458
488
  }
459
489
  }
460
490
 
@@ -994,8 +1024,11 @@ class Text {
994
1024
  }
995
1025
  measure(dom = this.measureDom) {
996
1026
  this.computedStyle = { ...defaultTextStyles, ...this.style };
1027
+ const old = this.paragraphs;
997
1028
  this.paragraphs = this._parser.parse();
998
- return this._measurer.measure(dom);
1029
+ const result = this._measurer.measure(dom);
1030
+ this.paragraphs = old;
1031
+ return result;
999
1032
  }
1000
1033
  requestUpdate() {
1001
1034
  this.needsUpdate = true;
@@ -1012,7 +1045,7 @@ class Text {
1012
1045
  this._highlighter.highlight();
1013
1046
  const min = Point2D.MAX;
1014
1047
  const max = Point2D.MIN;
1015
- this.characters.forEach((c) => c.path.getMinMax(min, max));
1048
+ this.characters.forEach((c) => c.getMinMax(min, max));
1016
1049
  this.renderBoundingBox = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1017
1050
  return this;
1018
1051
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "modern-text",
3
3
  "type": "module",
4
- "version": "0.2.3",
4
+ "version": "0.2.4",
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",