modern-text 0.2.30 → 0.2.31

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,5 @@
1
- import { BoundingBox, Path2D, Vector2, Matrix3, parseSvg, parseSvgToDom } from 'modern-path2d';
1
+ import { BoundingBox, Path2D, Vector2, Matrix3, parseSvg, parseSvgToDom, getPathsBoundingBox } from 'modern-path2d';
2
+ export * from 'modern-path2d';
2
3
  import { fonts, Woff, Ttf } from 'modern-font';
3
4
  export * from 'modern-font';
4
5
 
@@ -75,10 +76,58 @@ function drawPath(options) {
75
76
  ctx.restore();
76
77
  }
77
78
 
78
- var __defProp$4 = Object.defineProperty;
79
- var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
80
- var __publicField$4 = (obj, key, value) => {
81
- __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
79
+ function fillBackground(ctx, text) {
80
+ const { computedStyle: style, paragraphs } = text;
81
+ function fillBackground2(color, x, y, width, height) {
82
+ ctx.fillStyle = color;
83
+ ctx.fillRect(x, y, width, height);
84
+ }
85
+ if (style?.backgroundColor) {
86
+ fillBackground2(style.backgroundColor, 0, 0, ctx.canvas.width, ctx.canvas.height);
87
+ }
88
+ paragraphs.forEach((paragraph) => {
89
+ if (paragraph.style?.backgroundColor) {
90
+ fillBackground2(paragraph.computedStyle.backgroundColor, ...paragraph.boundingBox.toArray());
91
+ }
92
+ paragraph.fragments.forEach((fragment) => {
93
+ if (fragment.style?.backgroundColor) {
94
+ fillBackground2(fragment.computedStyle.backgroundColor, ...fragment.boundingBox.toArray());
95
+ }
96
+ });
97
+ });
98
+ }
99
+
100
+ function setupView(ctx, pixelRatio, boundingBox) {
101
+ const { left, top, width, height } = boundingBox;
102
+ const view = ctx.canvas;
103
+ view.dataset.viewbox = String(`${left} ${top} ${width} ${height}`);
104
+ view.dataset.pixelRatio = String(pixelRatio);
105
+ view.width = Math.max(1, Math.ceil(width * pixelRatio));
106
+ view.height = Math.max(1, Math.ceil(height * pixelRatio));
107
+ view.style.marginTop = `${top}px`;
108
+ view.style.marginLeft = `${left}px`;
109
+ view.style.width = `${width}px`;
110
+ view.style.height = `${height}px`;
111
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
112
+ ctx.scale(pixelRatio, pixelRatio);
113
+ ctx.translate(-left, -top);
114
+ }
115
+
116
+ function uploadColors(ctx, text) {
117
+ const { paragraphs, computedStyle: style, renderBoundingBox } = text;
118
+ uploadColor(style, renderBoundingBox, ctx);
119
+ paragraphs.forEach((paragraph) => {
120
+ uploadColor(paragraph.computedStyle, paragraph.boundingBox, ctx);
121
+ paragraph.fragments.forEach((fragment) => {
122
+ uploadColor(fragment.computedStyle, fragment.boundingBox, ctx);
123
+ });
124
+ });
125
+ }
126
+
127
+ var __defProp$3 = Object.defineProperty;
128
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
129
+ var __publicField$3 = (obj, key, value) => {
130
+ __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
82
131
  return value;
83
132
  };
84
133
  const set1 = /* @__PURE__ */ new Set(["\xA9", "\xAE", "\xF7"]);
@@ -113,21 +162,21 @@ class Character {
113
162
  this.index = index;
114
163
  this.parent = parent;
115
164
  // measure dom
116
- __publicField$4(this, "boundingBox", new BoundingBox());
117
- __publicField$4(this, "textWidth", 0);
118
- __publicField$4(this, "textHeight", 0);
165
+ __publicField$3(this, "boundingBox", new BoundingBox());
166
+ __publicField$3(this, "textWidth", 0);
167
+ __publicField$3(this, "textHeight", 0);
119
168
  // font glyph
120
- __publicField$4(this, "glyphHeight", 0);
121
- __publicField$4(this, "glyphWidth", 0);
122
- __publicField$4(this, "underlinePosition", 0);
123
- __publicField$4(this, "underlineThickness", 0);
124
- __publicField$4(this, "yStrikeoutPosition", 0);
125
- __publicField$4(this, "yStrikeoutSize", 0);
126
- __publicField$4(this, "baseline", 0);
127
- __publicField$4(this, "centerDiviation", 0);
128
- __publicField$4(this, "path", new Path2D());
129
- __publicField$4(this, "glyphBox", new BoundingBox());
130
- __publicField$4(this, "center", new Vector2());
169
+ __publicField$3(this, "glyphHeight", 0);
170
+ __publicField$3(this, "glyphWidth", 0);
171
+ __publicField$3(this, "underlinePosition", 0);
172
+ __publicField$3(this, "underlineThickness", 0);
173
+ __publicField$3(this, "yStrikeoutPosition", 0);
174
+ __publicField$3(this, "yStrikeoutSize", 0);
175
+ __publicField$3(this, "baseline", 0);
176
+ __publicField$3(this, "centerDiviation", 0);
177
+ __publicField$3(this, "path", new Path2D());
178
+ __publicField$3(this, "glyphBox", new BoundingBox());
179
+ __publicField$3(this, "center", new Vector2());
131
180
  }
132
181
  get computedStyle() {
133
182
  return this.parent.computedStyle;
@@ -319,10 +368,6 @@ class Character {
319
368
  } else {
320
369
  min ?? (min = Vector2.MAX);
321
370
  max ?? (max = Vector2.MIN);
322
- min.x = Math.min(min.x, this.boundingBox.left);
323
- min.y = Math.min(min.y, this.boundingBox.top);
324
- max.x = Math.max(max.x, this.boundingBox.right);
325
- max.y = Math.max(max.y, this.boundingBox.bottom);
326
371
  return { min, max };
327
372
  }
328
373
  }
@@ -341,6 +386,9 @@ class Character {
341
386
  }
342
387
  }
343
388
 
389
+ function isNone(val) {
390
+ return !val || val === "none";
391
+ }
344
392
  function filterEmpty(val) {
345
393
  if (!val)
346
394
  return val;
@@ -353,10 +401,10 @@ function filterEmpty(val) {
353
401
  return res;
354
402
  }
355
403
 
356
- var __defProp$3 = Object.defineProperty;
357
- var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
358
- var __publicField$3 = (obj, key, value) => {
359
- __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
404
+ var __defProp$2 = Object.defineProperty;
405
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
406
+ var __publicField$2 = (obj, key, value) => {
407
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
360
408
  return value;
361
409
  };
362
410
  class Fragment {
@@ -364,8 +412,7 @@ class Fragment {
364
412
  this.content = content;
365
413
  this.style = style;
366
414
  this.parent = parent;
367
- __publicField$3(this, "boundingBox", new BoundingBox());
368
- __publicField$3(this, "highlight");
415
+ __publicField$2(this, "boundingBox", new BoundingBox());
369
416
  this.updateComputedStyle().initCharacters();
370
417
  }
371
418
  get computedContent() {
@@ -390,18 +437,18 @@ class Fragment {
390
437
  }
391
438
  }
392
439
 
393
- var __defProp$2 = Object.defineProperty;
394
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
395
- var __publicField$2 = (obj, key, value) => {
396
- __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
440
+ var __defProp$1 = Object.defineProperty;
441
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
442
+ var __publicField$1 = (obj, key, value) => {
443
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
397
444
  return value;
398
445
  };
399
446
  class Paragraph {
400
447
  constructor(style, parentStyle) {
401
448
  this.style = style;
402
449
  this.parentStyle = parentStyle;
403
- __publicField$2(this, "boundingBox", new BoundingBox());
404
- __publicField$2(this, "fragments", []);
450
+ __publicField$1(this, "boundingBox", new BoundingBox());
451
+ __publicField$1(this, "fragments", []);
405
452
  this.updateComputedStyle();
406
453
  }
407
454
  updateComputedStyle() {
@@ -418,231 +465,10 @@ class Paragraph {
418
465
  }
419
466
  }
420
467
 
421
- class Feature {
468
+ class Measurer {
422
469
  constructor(_text) {
423
470
  this._text = _text;
424
471
  }
425
- }
426
-
427
- class Deformer extends Feature {
428
- deform() {
429
- this._text.deformation?.();
430
- }
431
- }
432
-
433
- const tempM1 = new Matrix3();
434
- const tempM2 = new Matrix3();
435
- const tempV1 = new Vector2();
436
- class Effector extends Feature {
437
- getTransform2D(style) {
438
- const { fontSize, renderBoundingBox } = this._text;
439
- const offsetX = (style.offsetX ?? 0) * fontSize;
440
- const offsetY = (style.offsetY ?? 0) * fontSize;
441
- const PI_2 = Math.PI * 2;
442
- const skewX = (style.skewX ?? 0) / 360 * PI_2;
443
- const skewY = (style.skewY ?? 0) / 360 * PI_2;
444
- const { left, top, width, height } = renderBoundingBox;
445
- const centerX = left + width / 2;
446
- const centerY = top + height / 2;
447
- tempM1.identity();
448
- tempM2.makeTranslation(offsetX, offsetY);
449
- tempM1.multiply(tempM2);
450
- tempM2.makeTranslation(centerX, centerY);
451
- tempM1.multiply(tempM2);
452
- tempM2.set(1, Math.tan(skewX), 0, Math.tan(skewY), 1, 0, 0, 0, 1);
453
- tempM1.multiply(tempM2);
454
- tempM2.makeTranslation(-centerX, -centerY);
455
- tempM1.multiply(tempM2);
456
- return tempM1.clone();
457
- }
458
- getBoundingBox() {
459
- const { characters, effects, fontSize } = this._text;
460
- const boxes = [];
461
- characters.forEach((character) => {
462
- effects?.forEach((style) => {
463
- const aabb = character.glyphBox.clone();
464
- const m = this.getTransform2D(style);
465
- tempV1.set(aabb.left, aabb.top);
466
- tempV1.applyMatrix3(m);
467
- aabb.left = tempV1.x;
468
- aabb.top = tempV1.y;
469
- tempV1.set(aabb.right, aabb.bottom);
470
- tempV1.applyMatrix3(m);
471
- aabb.width = tempV1.x - aabb.left;
472
- aabb.height = tempV1.y - aabb.top;
473
- const shadowOffsetX = (style.shadowOffsetX ?? 0) * fontSize;
474
- const shadowOffsetY = (style.shadowOffsetY ?? 0) * fontSize;
475
- const textStrokeWidth = Math.max(0.1, style.textStrokeWidth ?? 0) * fontSize;
476
- aabb.left += shadowOffsetX - textStrokeWidth;
477
- aabb.top += shadowOffsetY - textStrokeWidth;
478
- aabb.width += textStrokeWidth * 2;
479
- aabb.height += textStrokeWidth * 2;
480
- boxes.push(aabb);
481
- });
482
- });
483
- return BoundingBox.from(...boxes);
484
- }
485
- draw(options) {
486
- const { ctx } = options;
487
- const { effects, characters, renderBoundingBox } = this._text;
488
- if (effects) {
489
- effects.forEach((style) => {
490
- uploadColor(style, renderBoundingBox, ctx);
491
- ctx.save();
492
- const [a, c, e, b, d, f] = this.getTransform2D(style).transpose().elements;
493
- ctx.transform(a, b, c, d, e, f);
494
- characters.forEach((character) => {
495
- character.drawTo(ctx, style);
496
- });
497
- ctx.restore();
498
- });
499
- }
500
- return this;
501
- }
502
- }
503
-
504
- var __defProp$1 = Object.defineProperty;
505
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
506
- var __publicField$1 = (obj, key, value) => {
507
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
508
- return value;
509
- };
510
- const defaultHighlightRefer = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiIgdmlld0JveD0iMCAwIDcyIDcyIiBmaWxsPSJub25lIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMyLjQwMjkgMjhIMzUuMTU5NFYzMy4xNzcxQzM1Ljk4MjEgMzIuMzExNSAzNi45NzEgMzEuODczNyAzOC4wOTQ4IDMxLjg3MzdDMzkuNjY3NiAzMS44NzM3IDQwLjkxNjYgMzIuNDI5NSA0MS44MzkgMzMuNTQzN0w0MS44NDAzIDMzLjU0NTNDNDIuNjcxNyAzNC41NzA1IDQzLjA5MTUgMzUuODU1OSA0My4wOTE1IDM3LjM4NzdDNDMuMDkxNSAzOC45NzYxIDQyLjY3MjkgNDAuMzAyOCA0MS44MTgzIDQxLjMzMDRMNDEuODE3MSA0MS4zMzE4QzQwLjg3MzEgNDIuNDQ2MSAzOS41ODMyIDQzIDM3Ljk3MjEgNDNDMzYuNzQ3NyA0MyAzNS43NDg4IDQyLjY1OTkgMzQuOTk1OCA0MS45NjkzVjQyLjcyNDdIMzIuNDAyOVYyOFpNMzcuNTQyOCAzNC4wOTI0QzM2Ljg1NDkgMzQuMDkyNCAzNi4zMDE0IDM0LjM1NjEgMzUuODQ4NyAzNC45MDA0TDM1Ljg0NTIgMzQuOTA0NkMzNS4zMzU4IDM1LjQ4NTMgMzUuMDc3NiAzNi4yOTc2IDM1LjA3NzYgMzcuMzQ4NFYzNy41MDU3QzM1LjA3NzYgMzguNDY0IDM1LjI3NzIgMzkuMjQ0MyAzNS42OTQzIDM5LjgyNzlDMzYuMTQ0MSA0MC40NTg3IDM2Ljc3MjYgNDAuNzgxMyAzNy42MjQ1IDQwLjc4MTNDMzguNTg3NCA0MC43ODEzIDM5LjI3MDcgNDAuNDUyNyAzOS43MTUyIDM5LjgxMjdDNDAuMDcyOCAzOS4yNjg0IDQwLjI3MzcgMzguNDY3MyA0MC4yNzM3IDM3LjM4NzdDNDAuMjczNyAzNi4zMTA1IDQwLjA1MzMgMzUuNTMxMyAzOS42NzgzIDM1LjAwNzdDMzkuMjM3MSAzNC40MDcxIDM4LjUzNDIgMzQuMDkyNCAzNy41NDI4IDM0LjA5MjRaIiBmaWxsPSIjMjIyNTI5Ii8+PHBhdGggZD0iTTQ5Ljg2MTQgMzEuODczN0M0OC4xNTM1IDMxLjg3MzcgNDYuODAxNiAzMi40MjM5IDQ1LjgzNDggMzMuNTM5MkM0NC45MzcgMzQuNTQ3MiA0NC40OTY2IDM1Ljg1NiA0NC40OTY2IDM3LjQyN0M0NC40OTY2IDM5LjAzNjggNDQuOTM2NyA0MC4zNjU5IDQ1Ljg1NTkgNDEuMzk0M0M0Ni44MDMxIDQyLjQ3MDYgNDguMTM0OCA0MyA0OS44MjA1IDQzQzUxLjIyNiA0MyA1Mi4zODI2IDQyLjY1NjMgNTMuMjQ3OSA0MS45Njk3QzU0LjEzNTkgNDEuMjYxNCA1NC43MDYxIDQwLjE4ODcgNTQuOTU3MyAzOC43NzkxTDU1IDM4LjUzOTdINTIuMjQ4NEw1Mi4yMjU5IDM4LjcyMDFDNTIuMTM3OSAzOS40MjUxIDUxLjg5MjUgMzkuOTI3OCA1MS41MTA5IDQwLjI1NThDNTEuMTI5NSA0MC41ODM1IDUwLjU4MzEgNDAuNzYxNiA0OS44NDA5IDQwLjc2MTZDNDkuMDAwMSA0MC43NjE2IDQ4LjM5NDkgNDAuNDcxNSA0Ny45OTA3IDM5LjkyMzdMNDcuOTg3NCAzOS45MTk0QzQ3LjUzNTYgMzkuMzQwMSA0Ny4zMTQ0IDM4LjUwNjIgNDcuMzE0NCAzNy40MDc0QzQ3LjMxNDQgMzYuMzMyMiA0Ny41NTQ0IDM1LjUxNzcgNDguMDA1OCAzNC45NTY4TDQ4LjAwNzggMzQuOTU0M0M0OC40NTM3IDM0LjM4MjUgNDkuMDYxOCAzNC4xMTIxIDQ5Ljg2MTQgMzQuMTEyMUM1MC41MjMgMzQuMTEyMSA1MS4wNDUxIDM0LjI2MTUgNTEuNDI3MiAzNC41NDA3QzUxLjc4ODQgMzQuODE5NCA1Mi4wNTMgMzUuMjQ0NyA1Mi4xODgxIDM1Ljg1NzFMNTIuMjIzOSAzNi4wMTk0SDU0Ljk1NDhMNTQuOTE3IDM1Ljc4MzVDNTQuNzA2MyAzNC40NjYgNTQuMTUzNiAzMy40NzAxIDUzLjI2MzQgMzIuODAxOUw1My4yNjAyIDMyLjc5OTVDNTIuMzk1MSAzMi4xNzU1IDUxLjI2MjEgMzEuODczNyA0OS44NjE0IDMxLjg3MzdaIiBmaWxsPSIjMjIyNTI5Ii8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yNS43NTYxIDI4LjI3NTNIMjIuNzQ0TDE3IDQyLjcyNDdIMjAuMDE0MUwyMS4zNDI5IDM5LjIwNDlIMjcuMTU3MkwyOC40ODYgNDIuNzI0N0gzMS41MDAxTDI1Ljc1NjEgMjguMjc1M1pNMjIuMjEyNSAzNi45MDc2TDI0LjI1OTYgMzEuNDUzOUwyNi4yODg1IDM2LjkwNzZIMjIuMjEyNVoiIGZpbGw9IiMyMjI1MjkiLz48L3N2Zz4=";
511
- const _Highlighter = class _Highlighter extends Feature {
512
- constructor() {
513
- super(...arguments);
514
- __publicField$1(this, "paths", []);
515
- }
516
- static get refer() {
517
- return this._refer;
518
- }
519
- static set refer(refer) {
520
- this._refer = refer;
521
- this.parsedRefer = parseSvg(refer);
522
- }
523
- getReferBoundingBox() {
524
- const max = Vector2.MIN;
525
- const min = Vector2.MAX;
526
- _Highlighter.parsedRefer.forEach((path) => {
527
- path.getMinMax(min, max);
528
- });
529
- return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
530
- }
531
- getBoundingBox() {
532
- if (!this.paths.length) {
533
- return void 0;
534
- }
535
- const min = Vector2.MAX;
536
- const max = Vector2.MIN;
537
- this.paths.forEach((v) => v.path.getMinMax(min, max));
538
- return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
539
- }
540
- highlight(perChar = false) {
541
- const { characters } = this._text;
542
- let group;
543
- const groups = [];
544
- let prevHighlight;
545
- characters.forEach((character) => {
546
- const highlight = character.parent.highlight;
547
- if (highlight?.url) {
548
- if (!perChar && prevHighlight?.url === highlight.url && prevHighlight?.strokeWidth === highlight.strokeWidth && prevHighlight?.repeatXByFontsize === highlight.repeatXByFontsize && prevHighlight?.overflowXHidden === highlight.overflowXHidden && group.length && group[0].boundingBox.top === character.boundingBox.top && group[0].fontSize === character.fontSize) {
549
- group.push(character);
550
- } else {
551
- group = [];
552
- group.push(character);
553
- groups.push(group);
554
- }
555
- }
556
- prevHighlight = highlight;
557
- });
558
- this.paths = groups.filter((characters2) => characters2.length).map((characters2) => {
559
- return {
560
- highlight: characters2[0].parent.highlight,
561
- box: BoundingBox.from(...characters2.map((c) => c.glyphBox)),
562
- baseline: Math.max(...characters2.map((c) => c.baseline)),
563
- fontSize: characters2[0].fontSize
564
- };
565
- }).map((group2) => this._parseGroup(group2)).flat();
566
- }
567
- _parseSvg(url) {
568
- const svg = parseSvgToDom(url);
569
- const paths = parseSvg(svg);
570
- const min = Vector2.MAX;
571
- const max = Vector2.MIN;
572
- paths.forEach((path) => path.getMinMax(min, max));
573
- return {
574
- paths,
575
- box: new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y)
576
- };
577
- }
578
- _parseGroup(group) {
579
- const { highlight, box: groupBox, fontSize } = group;
580
- const {
581
- strokeWidth = 1,
582
- repeatXByFontsize = 0,
583
- overflowXHidden = Boolean(repeatXByFontsize)
584
- } = highlight;
585
- const { box, paths } = this._parseSvg(highlight.url);
586
- const result = [];
587
- const referBoundingBox = this.getReferBoundingBox();
588
- const scale = {
589
- x: repeatXByFontsize ? fontSize * repeatXByFontsize * (box.width / referBoundingBox.width) / box.width : groupBox.width * (box.width / referBoundingBox.width) / box.width,
590
- y: groupBox.height * (box.height / referBoundingBox.height) / box.height
591
- };
592
- const styleScale = fontSize / box.width * 2;
593
- const unitWidth = box.width * scale.x;
594
- const total = Math.ceil(groupBox.width / unitWidth);
595
- const offset = {
596
- x: (box.left - referBoundingBox.left) * scale.x,
597
- y: (box.top - referBoundingBox.top) * scale.y
598
- };
599
- const transform = new Matrix3().translate(-box.left, -box.top).scale(scale.x, scale.y).translate(groupBox.left, groupBox.top).translate(offset.x, offset.y);
600
- for (let i = 0; i < total; i++) {
601
- const _transform = transform.clone().translate(i * unitWidth, 0);
602
- paths.forEach((original) => {
603
- const path = original.clone().matrix(_transform);
604
- if (path.style.strokeWidth) {
605
- path.style.strokeWidth *= styleScale * strokeWidth;
606
- }
607
- if (path.style.strokeMiterlimit) {
608
- path.style.strokeMiterlimit *= styleScale;
609
- }
610
- if (path.style.strokeDashoffset) {
611
- path.style.strokeDashoffset *= styleScale;
612
- }
613
- if (path.style.strokeDasharray) {
614
- path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
615
- }
616
- result.push({
617
- clipRect: overflowXHidden ? new BoundingBox(
618
- groupBox.left + offset.x * 2,
619
- groupBox.top - groupBox.height,
620
- groupBox.width - offset.x * 2,
621
- groupBox.height * 3
622
- ) : void 0,
623
- path
624
- });
625
- });
626
- }
627
- return result;
628
- }
629
- draw({ ctx }) {
630
- this.paths.forEach((v) => {
631
- drawPath({
632
- ctx,
633
- path: v.path,
634
- clipRect: v.clipRect,
635
- fontSize: this._text.computedStyle.fontSize
636
- });
637
- });
638
- return this;
639
- }
640
- };
641
- __publicField$1(_Highlighter, "_refer", defaultHighlightRefer);
642
- __publicField$1(_Highlighter, "parsedRefer", parseSvg(_Highlighter._refer));
643
- let Highlighter = _Highlighter;
644
-
645
- class Measurer extends Feature {
646
472
  _styleToDomStyle(style) {
647
473
  const _style = { ...style };
648
474
  for (const key in style) {
@@ -686,7 +512,7 @@ class Measurer extends Feature {
686
512
  });
687
513
  const ul = document.createElement("ul");
688
514
  Object.assign(ul.style, {
689
- listStyle: "none",
515
+ listStyleType: "inherit",
690
516
  padding: "0",
691
517
  margin: "0"
692
518
  });
@@ -851,7 +677,10 @@ class Measurer extends Feature {
851
677
  }
852
678
  }
853
679
 
854
- class Parser extends Feature {
680
+ class Parser {
681
+ constructor(_text) {
682
+ this._text = _text;
683
+ }
855
684
  parse() {
856
685
  let { content, computedStyle: style } = this._text;
857
686
  const paragraphs = [];
@@ -872,10 +701,9 @@ class Parser extends Feature {
872
701
  if (typeof f === "string") {
873
702
  paragraph.addFragment(f);
874
703
  } else {
875
- const { content: content2, highlight, ...fStyle } = f;
704
+ const { content: content2, ...fStyle } = f;
876
705
  if (content2 !== void 0) {
877
- const fragment = paragraph.addFragment(content2, fStyle);
878
- fragment.highlight = highlight;
706
+ paragraph.addFragment(content2, fStyle);
879
707
  }
880
708
  }
881
709
  });
@@ -884,19 +712,17 @@ class Parser extends Feature {
884
712
  const { fragments, ...pStyle } = p;
885
713
  const paragraph = new Paragraph(pStyle, style);
886
714
  fragments.forEach((f) => {
887
- const { content: content2, highlight, ...fStyle } = f;
715
+ const { content: content2, ...fStyle } = f;
888
716
  if (content2 !== void 0) {
889
- const fragment = paragraph.addFragment(content2, fStyle);
890
- fragment.highlight = highlight;
717
+ paragraph.addFragment(content2, fStyle);
891
718
  }
892
719
  });
893
720
  paragraphs.push(paragraph);
894
721
  } else if ("content" in p) {
895
- const { content: pData, highlight, ...pStyle } = p;
722
+ const { content: pData, ...pStyle } = p;
896
723
  if (pData !== void 0) {
897
724
  const paragraph = new Paragraph(pStyle, style);
898
- const fragment = paragraph.addFragment(pData);
899
- fragment.highlight = highlight;
725
+ paragraph.addFragment(pData);
900
726
  paragraphs.push(paragraph);
901
727
  }
902
728
  }
@@ -906,71 +732,273 @@ class Parser extends Feature {
906
732
  }
907
733
  }
908
734
 
909
- class Reflector extends Feature {
910
- // TODO
735
+ function plugin(options) {
736
+ return options;
911
737
  }
912
738
 
913
- class Renderer2D extends Feature {
914
- setupView(options) {
915
- const { ctx, pixelRatio } = options;
916
- const { renderBoundingBox } = this._text;
917
- const { left, top, width, height } = renderBoundingBox;
918
- const view = ctx.canvas;
919
- view.dataset.viewbox = String(`${left} ${top} ${width} ${height}`);
920
- view.dataset.pixelRatio = String(pixelRatio);
921
- view.width = Math.max(1, Math.ceil(width * pixelRatio));
922
- view.height = Math.max(1, Math.ceil(height * pixelRatio));
923
- view.style.marginTop = `${top}px`;
924
- view.style.marginLeft = `${left}px`;
925
- view.style.width = `${width}px`;
926
- view.style.height = `${height}px`;
927
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
928
- ctx.scale(pixelRatio, pixelRatio);
929
- ctx.translate(-left, -top);
930
- return this;
931
- }
932
- uploadColors(options) {
933
- const { ctx } = options;
934
- const { paragraphs, computedStyle: style, renderBoundingBox } = this._text;
935
- uploadColor(style, renderBoundingBox, ctx);
936
- paragraphs.forEach((paragraph) => {
937
- uploadColor(paragraph.computedStyle, paragraph.boundingBox, ctx);
938
- paragraph.fragments.forEach((fragment) => {
939
- uploadColor(fragment.computedStyle, fragment.boundingBox, ctx);
739
+ const tempV1 = new Vector2();
740
+ const tempM1 = new Matrix3();
741
+ const tempM2 = new Matrix3();
742
+ function effect(effects) {
743
+ return plugin({
744
+ name: "effect",
745
+ getBoundingBox: (text) => {
746
+ const { characters, fontSize } = text;
747
+ const boxes = [];
748
+ characters.forEach((character) => {
749
+ effects?.forEach((style) => {
750
+ const aabb = character.glyphBox.clone();
751
+ const m = getTransform2D(text, style);
752
+ tempV1.set(aabb.left, aabb.top);
753
+ tempV1.applyMatrix3(m);
754
+ aabb.left = tempV1.x;
755
+ aabb.top = tempV1.y;
756
+ tempV1.set(aabb.right, aabb.bottom);
757
+ tempV1.applyMatrix3(m);
758
+ aabb.width = tempV1.x - aabb.left;
759
+ aabb.height = tempV1.y - aabb.top;
760
+ const shadowOffsetX = (style.shadowOffsetX ?? 0) * fontSize;
761
+ const shadowOffsetY = (style.shadowOffsetY ?? 0) * fontSize;
762
+ const textStrokeWidth = Math.max(0.1, style.textStrokeWidth ?? 0) * fontSize;
763
+ aabb.left += shadowOffsetX - textStrokeWidth;
764
+ aabb.top += shadowOffsetY - textStrokeWidth;
765
+ aabb.width += textStrokeWidth * 2;
766
+ aabb.height += textStrokeWidth * 2;
767
+ boxes.push(aabb);
768
+ });
940
769
  });
941
- });
942
- return this;
770
+ return boxes.length ? BoundingBox.from(...boxes) : void 0;
771
+ },
772
+ render: (ctx, text) => {
773
+ const { characters, renderBoundingBox } = text;
774
+ if (effects) {
775
+ effects.forEach((style) => {
776
+ uploadColor(style, renderBoundingBox, ctx);
777
+ ctx.save();
778
+ const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
779
+ ctx.transform(a, b, c, d, e, f);
780
+ characters.forEach((character) => {
781
+ character.drawTo(ctx, style);
782
+ });
783
+ ctx.restore();
784
+ });
785
+ } else {
786
+ characters.forEach((character) => {
787
+ character.drawTo(ctx);
788
+ });
789
+ }
790
+ }
791
+ });
792
+ }
793
+ function getTransform2D(text, style) {
794
+ const { fontSize, renderBoundingBox } = text;
795
+ const offsetX = (style.offsetX ?? 0) * fontSize;
796
+ const offsetY = (style.offsetY ?? 0) * fontSize;
797
+ const PI_2 = Math.PI * 2;
798
+ const skewX = (style.skewX ?? 0) / 360 * PI_2;
799
+ const skewY = (style.skewY ?? 0) / 360 * PI_2;
800
+ const { left, top, width, height } = renderBoundingBox;
801
+ const centerX = left + width / 2;
802
+ const centerY = top + height / 2;
803
+ tempM1.identity();
804
+ tempM2.makeTranslation(offsetX, offsetY);
805
+ tempM1.multiply(tempM2);
806
+ tempM2.makeTranslation(centerX, centerY);
807
+ tempM1.multiply(tempM2);
808
+ tempM2.set(1, Math.tan(skewX), 0, Math.tan(skewY), 1, 0, 0, 0, 1);
809
+ tempM1.multiply(tempM2);
810
+ tempM2.makeTranslation(-centerX, -centerY);
811
+ tempM1.multiply(tempM2);
812
+ return tempM1.clone();
813
+ }
814
+
815
+ const defaultOptions = {
816
+ referImage: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiIgdmlld0JveD0iMCAwIDcyIDcyIiBmaWxsPSJub25lIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMyLjQwMjkgMjhIMzUuMTU5NFYzMy4xNzcxQzM1Ljk4MjEgMzIuMzExNSAzNi45NzEgMzEuODczNyAzOC4wOTQ4IDMxLjg3MzdDMzkuNjY3NiAzMS44NzM3IDQwLjkxNjYgMzIuNDI5NSA0MS44MzkgMzMuNTQzN0w0MS44NDAzIDMzLjU0NTNDNDIuNjcxNyAzNC41NzA1IDQzLjA5MTUgMzUuODU1OSA0My4wOTE1IDM3LjM4NzdDNDMuMDkxNSAzOC45NzYxIDQyLjY3MjkgNDAuMzAyOCA0MS44MTgzIDQxLjMzMDRMNDEuODE3MSA0MS4zMzE4QzQwLjg3MzEgNDIuNDQ2MSAzOS41ODMyIDQzIDM3Ljk3MjEgNDNDMzYuNzQ3NyA0MyAzNS43NDg4IDQyLjY1OTkgMzQuOTk1OCA0MS45NjkzVjQyLjcyNDdIMzIuNDAyOVYyOFpNMzcuNTQyOCAzNC4wOTI0QzM2Ljg1NDkgMzQuMDkyNCAzNi4zMDE0IDM0LjM1NjEgMzUuODQ4NyAzNC45MDA0TDM1Ljg0NTIgMzQuOTA0NkMzNS4zMzU4IDM1LjQ4NTMgMzUuMDc3NiAzNi4yOTc2IDM1LjA3NzYgMzcuMzQ4NFYzNy41MDU3QzM1LjA3NzYgMzguNDY0IDM1LjI3NzIgMzkuMjQ0MyAzNS42OTQzIDM5LjgyNzlDMzYuMTQ0MSA0MC40NTg3IDM2Ljc3MjYgNDAuNzgxMyAzNy42MjQ1IDQwLjc4MTNDMzguNTg3NCA0MC43ODEzIDM5LjI3MDcgNDAuNDUyNyAzOS43MTUyIDM5LjgxMjdDNDAuMDcyOCAzOS4yNjg0IDQwLjI3MzcgMzguNDY3MyA0MC4yNzM3IDM3LjM4NzdDNDAuMjczNyAzNi4zMTA1IDQwLjA1MzMgMzUuNTMxMyAzOS42NzgzIDM1LjAwNzdDMzkuMjM3MSAzNC40MDcxIDM4LjUzNDIgMzQuMDkyNCAzNy41NDI4IDM0LjA5MjRaIiBmaWxsPSIjMjIyNTI5Ii8+PHBhdGggZD0iTTQ5Ljg2MTQgMzEuODczN0M0OC4xNTM1IDMxLjg3MzcgNDYuODAxNiAzMi40MjM5IDQ1LjgzNDggMzMuNTM5MkM0NC45MzcgMzQuNTQ3MiA0NC40OTY2IDM1Ljg1NiA0NC40OTY2IDM3LjQyN0M0NC40OTY2IDM5LjAzNjggNDQuOTM2NyA0MC4zNjU5IDQ1Ljg1NTkgNDEuMzk0M0M0Ni44MDMxIDQyLjQ3MDYgNDguMTM0OCA0MyA0OS44MjA1IDQzQzUxLjIyNiA0MyA1Mi4zODI2IDQyLjY1NjMgNTMuMjQ3OSA0MS45Njk3QzU0LjEzNTkgNDEuMjYxNCA1NC43MDYxIDQwLjE4ODcgNTQuOTU3MyAzOC43NzkxTDU1IDM4LjUzOTdINTIuMjQ4NEw1Mi4yMjU5IDM4LjcyMDFDNTIuMTM3OSAzOS40MjUxIDUxLjg5MjUgMzkuOTI3OCA1MS41MTA5IDQwLjI1NThDNTEuMTI5NSA0MC41ODM1IDUwLjU4MzEgNDAuNzYxNiA0OS44NDA5IDQwLjc2MTZDNDkuMDAwMSA0MC43NjE2IDQ4LjM5NDkgNDAuNDcxNSA0Ny45OTA3IDM5LjkyMzdMNDcuOTg3NCAzOS45MTk0QzQ3LjUzNTYgMzkuMzQwMSA0Ny4zMTQ0IDM4LjUwNjIgNDcuMzE0NCAzNy40MDc0QzQ3LjMxNDQgMzYuMzMyMiA0Ny41NTQ0IDM1LjUxNzcgNDguMDA1OCAzNC45NTY4TDQ4LjAwNzggMzQuOTU0M0M0OC40NTM3IDM0LjM4MjUgNDkuMDYxOCAzNC4xMTIxIDQ5Ljg2MTQgMzQuMTEyMUM1MC41MjMgMzQuMTEyMSA1MS4wNDUxIDM0LjI2MTUgNTEuNDI3MiAzNC41NDA3QzUxLjc4ODQgMzQuODE5NCA1Mi4wNTMgMzUuMjQ0NyA1Mi4xODgxIDM1Ljg1NzFMNTIuMjIzOSAzNi4wMTk0SDU0Ljk1NDhMNTQuOTE3IDM1Ljc4MzVDNTQuNzA2MyAzNC40NjYgNTQuMTUzNiAzMy40NzAxIDUzLjI2MzQgMzIuODAxOUw1My4yNjAyIDMyLjc5OTVDNTIuMzk1MSAzMi4xNzU1IDUxLjI2MjEgMzEuODczNyA0OS44NjE0IDMxLjg3MzdaIiBmaWxsPSIjMjIyNTI5Ii8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yNS43NTYxIDI4LjI3NTNIMjIuNzQ0TDE3IDQyLjcyNDdIMjAuMDE0MUwyMS4zNDI5IDM5LjIwNDlIMjcuMTU3MkwyOC40ODYgNDIuNzI0N0gzMS41MDAxTDI1Ljc1NjEgMjguMjc1M1pNMjIuMjEyNSAzNi45MDc2TDI0LjI1OTYgMzEuNDUzOUwyNi4yODg1IDM2LjkwNzZIMjIuMjEyNVoiIGZpbGw9IiMyMjI1MjkiLz48L3N2Zz4="
817
+ };
818
+ function parseCharsPerRepeat(size, fontSize, total) {
819
+ if (size === "cover") {
820
+ return 0;
821
+ } else if (typeof size === "string") {
822
+ if (size.endsWith("%")) {
823
+ const rate = Number(size.substring(0, size.length - 1)) / 100;
824
+ return Math.ceil(rate * total / fontSize);
825
+ } else if (size.endsWith("rem")) {
826
+ return Number(size.substring(0, size.length - 3));
827
+ } else {
828
+ return Math.ceil(Number(size) / fontSize);
829
+ }
830
+ } else {
831
+ return Math.ceil(size / fontSize);
943
832
  }
944
- fillBackground(options) {
945
- const { ctx } = options;
946
- const { computedStyle: style, paragraphs } = this._text;
947
- function fillBackground(color, x, y, width, height) {
948
- ctx.fillStyle = color;
949
- ctx.fillRect(x, y, width, height);
833
+ }
834
+ function parseStrokeWidthScale(strokeWidth, fontSize, total) {
835
+ if (typeof strokeWidth === "string") {
836
+ if (strokeWidth.endsWith("%")) {
837
+ return Number(strokeWidth.substring(0, strokeWidth.length - 1)) / 100;
838
+ } else if (strokeWidth.endsWith("rem")) {
839
+ const value = Number(strokeWidth.substring(0, strokeWidth.length - 3));
840
+ return value * fontSize / total;
841
+ } else {
842
+ return Number(strokeWidth) / total;
843
+ }
844
+ } else {
845
+ return strokeWidth / total;
846
+ }
847
+ }
848
+ function highlight(options = {}) {
849
+ const config = { ...defaultOptions, ...options };
850
+ const referPaths = parseSvg(config.referImage);
851
+ const paths = [];
852
+ const clipRects = [];
853
+ return plugin({
854
+ name: "highlight",
855
+ paths,
856
+ update: (text) => {
857
+ paths.length = 0;
858
+ const { characters } = text;
859
+ let group;
860
+ const groups = [];
861
+ let prevStyle;
862
+ characters.forEach((character) => {
863
+ const style = character.computedStyle;
864
+ if (!isNone(style.highlightImage)) {
865
+ if (style.highlightSize !== "1rem" && prevStyle?.highlightImage === style.highlightImage && prevStyle?.highlightSize === style.highlightSize && prevStyle?.highlightStrokeWidth === style.highlightStrokeWidth && prevStyle?.highlightOverflow === style.highlightOverflow && group.length && group[0].boundingBox.top === character.boundingBox.top && group[0].fontSize === character.fontSize) {
866
+ group.push(character);
867
+ } else {
868
+ group = [];
869
+ group.push(character);
870
+ groups.push(group);
871
+ }
872
+ }
873
+ prevStyle = style;
874
+ });
875
+ groups.filter((characters2) => characters2.length).map((characters2) => {
876
+ return {
877
+ style: characters2[0].computedStyle,
878
+ box: BoundingBox.from(...characters2.map((c) => c.glyphBox))
879
+ };
880
+ }).forEach((group2) => {
881
+ const { style, box: groupBox } = group2;
882
+ const { fontSize } = style;
883
+ const strokeWidthScale = parseStrokeWidthScale(style.highlightStrokeWidth, fontSize, groupBox.width);
884
+ const charsPerRepeat = parseCharsPerRepeat(style.highlightSize, fontSize, groupBox.width);
885
+ const highlightOverflow = isNone(style.highlightOverflow) ? charsPerRepeat ? "hidden" : "visible" : style.highlightOverflow;
886
+ const svg = parseSvgToDom(style.highlightImage);
887
+ const svgPaths = parseSvg(svg);
888
+ const box = getPathsBoundingBox(svgPaths);
889
+ const referBoundingBox = getPathsBoundingBox(referPaths);
890
+ const scale = {
891
+ x: charsPerRepeat ? fontSize * charsPerRepeat * (box.width / referBoundingBox.width) / box.width : groupBox.width * (box.width / referBoundingBox.width) / box.width,
892
+ y: groupBox.height * (box.height / referBoundingBox.height) / box.height
893
+ };
894
+ const styleScale = fontSize / box.width * 2;
895
+ const unitWidth = box.width * scale.x;
896
+ const total = Math.ceil(groupBox.width / unitWidth);
897
+ const offset = {
898
+ x: (box.left - referBoundingBox.left) * scale.x,
899
+ y: (box.top - referBoundingBox.top) * scale.y
900
+ };
901
+ const transform = new Matrix3().translate(-box.left, -box.top).scale(scale.x, scale.y).translate(groupBox.left, groupBox.top).translate(offset.x, offset.y);
902
+ for (let i = 0; i < total; i++) {
903
+ const _transform = transform.clone().translate(i * unitWidth, 0);
904
+ svgPaths.forEach((original) => {
905
+ const path = original.clone().matrix(_transform);
906
+ if (path.style.strokeWidth) {
907
+ path.style.strokeWidth *= styleScale * strokeWidthScale;
908
+ }
909
+ if (path.style.strokeMiterlimit) {
910
+ path.style.strokeMiterlimit *= styleScale;
911
+ }
912
+ if (path.style.strokeDashoffset) {
913
+ path.style.strokeDashoffset *= styleScale;
914
+ }
915
+ if (path.style.strokeDasharray) {
916
+ path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
917
+ }
918
+ paths.push(path);
919
+ clipRects[paths.length - 1] = highlightOverflow === "hidden" ? new BoundingBox(
920
+ groupBox.left,
921
+ groupBox.top - groupBox.height,
922
+ groupBox.width,
923
+ groupBox.height * 3
924
+ ) : void 0;
925
+ });
926
+ }
927
+ });
928
+ },
929
+ renderOrder: -1,
930
+ render: (ctx, text) => {
931
+ paths.forEach((path, index) => {
932
+ drawPath({
933
+ ctx,
934
+ path,
935
+ clipRect: clipRects[index],
936
+ fontSize: text.computedStyle.fontSize
937
+ });
938
+ });
950
939
  }
951
- if (style?.backgroundColor) {
952
- fillBackground(style.backgroundColor, 0, 0, ctx.canvas.width, ctx.canvas.height);
940
+ });
941
+ }
942
+
943
+ function parseScale(size, fontSize, total) {
944
+ if (size === "cover") {
945
+ return 1;
946
+ } else if (typeof size === "string") {
947
+ if (size.endsWith("%")) {
948
+ return Number(size.substring(0, size.length - 1)) / 100;
949
+ } else if (size.endsWith("rem")) {
950
+ const value = Number(size.substring(0, size.length - 3));
951
+ return value * fontSize / total;
952
+ } else {
953
+ return Number(size) / total;
953
954
  }
954
- paragraphs.forEach((paragraph) => {
955
- if (paragraph.style?.backgroundColor) {
956
- fillBackground(paragraph.computedStyle.backgroundColor, ...paragraph.boundingBox.toArray());
955
+ } else {
956
+ return size / total;
957
+ }
958
+ }
959
+ function listStyle() {
960
+ const paths = [];
961
+ return plugin({
962
+ name: "listStyle",
963
+ update: (text) => {
964
+ const { paragraphs, computedStyle: style, fontSize } = text;
965
+ let listStyleSize = style.listStyleSize;
966
+ let image;
967
+ if (!isNone(style.listStyleImage)) {
968
+ image = style.listStyleImage;
969
+ } else if (!isNone(style.listStyleType)) {
970
+ const r = fontSize * 0.38 / 2;
971
+ listStyleSize = listStyleSize === "cover" ? r * 2 : listStyleSize;
972
+ switch (style.listStyleType) {
973
+ case "disc":
974
+ image = `<svg width="${r * 2}" height="${r * 2}" xmlns="http://www.w3.org/2000/svg">
975
+ <circle cx="${r}" cy="${r}" r="${r}" fill="${style.color}" />
976
+ </svg>`;
977
+ break;
978
+ }
957
979
  }
958
- paragraph.fragments.forEach((fragment) => {
959
- if (fragment.style?.backgroundColor) {
960
- fillBackground(fragment.computedStyle.backgroundColor, ...fragment.boundingBox.toArray());
980
+ if (!image) {
981
+ return;
982
+ }
983
+ const paddingLeft = fontSize * 0.45;
984
+ const imagePaths = parseSvg(image);
985
+ const imageBox = getPathsBoundingBox(imagePaths);
986
+ paragraphs.forEach((paragraph) => {
987
+ const box = paragraph.fragments[0]?.characters[0]?.getGlyphBoundingBox();
988
+ if (box) {
989
+ const scale = parseScale(listStyleSize, style.fontSize, box.height);
990
+ const reScale = box.height / imageBox.height * scale;
991
+ const m = new Matrix3();
992
+ m.translate(-imageBox.left - imageBox.width, -imageBox.top);
993
+ m.scale(reScale, reScale);
994
+ m.translate(0, box.height / 2 - imageBox.height * reScale / 2);
995
+ m.translate(box.left - paddingLeft, box.top);
996
+ paths.push(...imagePaths.map((p) => p.clone().matrix(m)));
961
997
  }
962
998
  });
963
- });
964
- return this;
965
- }
966
- draw(options) {
967
- const { ctx } = options;
968
- const { characters } = this._text;
969
- characters.forEach((character) => {
970
- character.drawTo(ctx);
971
- });
972
- return this;
973
- }
999
+ },
1000
+ paths
1001
+ });
974
1002
  }
975
1003
 
976
1004
  var __defProp = Object.defineProperty;
@@ -980,35 +1008,49 @@ var __publicField = (obj, key, value) => {
980
1008
  return value;
981
1009
  };
982
1010
  const defaultTextStyles = {
983
- color: "#000",
984
- backgroundColor: "rgba(0, 0, 0, 0)",
1011
+ writingMode: "horizontal-tb",
1012
+ verticalAlign: "baseline",
1013
+ lineHeight: 1,
1014
+ letterSpacing: 0,
1015
+ // font
985
1016
  fontSize: 14,
986
1017
  fontWeight: "normal",
987
1018
  fontFamily: "_fallback",
988
1019
  fontStyle: "normal",
989
1020
  fontKerning: "normal",
1021
+ // text
990
1022
  textWrap: "wrap",
991
1023
  textAlign: "start",
992
- verticalAlign: "baseline",
993
1024
  textTransform: "none",
1025
+ textOrientation: "mixed",
1026
+ // color
1027
+ color: "#000",
1028
+ backgroundColor: "rgba(0, 0, 0, 0)",
1029
+ // text
994
1030
  textDecoration: "none",
1031
+ // textStroke
995
1032
  textStrokeWidth: 0,
996
1033
  textStrokeColor: "#000",
997
- lineHeight: 1,
998
- letterSpacing: 0,
1034
+ // shadow
999
1035
  shadowColor: "rgba(0, 0, 0, 0)",
1000
1036
  shadowOffsetX: 0,
1001
1037
  shadowOffsetY: 0,
1002
1038
  shadowBlur: 0,
1003
- writingMode: "horizontal-tb",
1004
- textOrientation: "mixed"
1039
+ // listStyle
1040
+ listStyleType: "none",
1041
+ listStyleImage: "none",
1042
+ listStyleSize: "cover",
1043
+ listStylePosition: "outside",
1044
+ // highlight
1045
+ highlightImage: "none",
1046
+ highlightSize: "cover",
1047
+ highlightStrokeWidth: "100%",
1048
+ highlightOverflow: "none"
1005
1049
  };
1006
1050
  class Text {
1007
1051
  constructor(options = {}) {
1008
1052
  __publicField(this, "content");
1009
1053
  __publicField(this, "style");
1010
- __publicField(this, "effects");
1011
- __publicField(this, "deformation");
1012
1054
  __publicField(this, "measureDom");
1013
1055
  __publicField(this, "needsUpdate", true);
1014
1056
  __publicField(this, "computedStyle", { ...defaultTextStyles });
@@ -1017,16 +1059,12 @@ class Text {
1017
1059
  __publicField(this, "renderBoundingBox", new BoundingBox());
1018
1060
  __publicField(this, "parser", new Parser(this));
1019
1061
  __publicField(this, "measurer", new Measurer(this));
1020
- __publicField(this, "deformer", new Deformer(this));
1021
- __publicField(this, "effector", new Effector(this));
1022
- __publicField(this, "highlighter", new Highlighter(this));
1023
- __publicField(this, "renderer2D", new Renderer2D(this));
1024
- const { content = "", style = {}, effects, deformation, measureDom } = options;
1062
+ __publicField(this, "plugins", /* @__PURE__ */ new Map());
1063
+ const { content = "", style = {}, measureDom } = options;
1025
1064
  this.content = content;
1026
1065
  this.style = style;
1027
- this.effects = effects;
1028
- this.deformation = deformation;
1029
1066
  this.measureDom = measureDom;
1067
+ this.use(effect(options.effects)).use(highlight(options.highlight)).use(listStyle());
1030
1068
  }
1031
1069
  get fontSize() {
1032
1070
  return this.computedStyle.fontSize;
@@ -1034,6 +1072,10 @@ class Text {
1034
1072
  get characters() {
1035
1073
  return this.paragraphs.flatMap((p) => p.fragments.flatMap((f) => f.characters));
1036
1074
  }
1075
+ use(plugin) {
1076
+ this.plugins.set(plugin.name, plugin);
1077
+ return this;
1078
+ }
1037
1079
  measure(dom = this.measureDom) {
1038
1080
  this.computedStyle = { ...defaultTextStyles, ...this.style };
1039
1081
  const old = this.paragraphs;
@@ -1052,15 +1094,23 @@ class Text {
1052
1094
  this.boundingBox = boundingBox;
1053
1095
  const characters = this.characters;
1054
1096
  characters.forEach((c) => c.update());
1055
- if (this.deformation) {
1056
- this.deformer.deform();
1057
- } else {
1058
- this.highlighter.highlight();
1059
- }
1097
+ const plugins = [...this.plugins.values()];
1098
+ plugins.sort((a, b) => (a.updateOrder ?? 0) - (b.updateOrder ?? 0)).forEach((plugin) => {
1099
+ plugin.update?.(this);
1100
+ });
1060
1101
  const min = Vector2.MAX;
1061
1102
  const max = Vector2.MIN;
1062
1103
  characters.forEach((c) => c.getGlyphMinMax(min, max));
1063
1104
  this.renderBoundingBox = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1105
+ this.renderBoundingBox = BoundingBox.from(
1106
+ this.renderBoundingBox,
1107
+ ...plugins.map((plugin) => {
1108
+ if (plugin.getBoundingBox) {
1109
+ return plugin.getBoundingBox(this);
1110
+ }
1111
+ return getPathsBoundingBox(plugin.paths ?? []);
1112
+ }).filter(Boolean)
1113
+ );
1064
1114
  return this;
1065
1115
  }
1066
1116
  render(options) {
@@ -1072,28 +1122,24 @@ class Text {
1072
1122
  if (this.needsUpdate) {
1073
1123
  this.update();
1074
1124
  }
1075
- if (this.effects?.length) {
1076
- this.renderBoundingBox = BoundingBox.from(...[
1077
- this.renderBoundingBox,
1078
- this.effector.getBoundingBox(),
1079
- this.highlighter.getBoundingBox()
1080
- ].filter(Boolean));
1081
- } else {
1082
- this.renderBoundingBox = BoundingBox.from(...[
1083
- this.renderBoundingBox,
1084
- this.highlighter.getBoundingBox()
1085
- ].filter(Boolean));
1086
- }
1087
- this.renderer2D.setupView({ pixelRatio, ctx });
1088
- this.renderer2D.uploadColors({ ctx });
1089
- this.highlighter.draw({ ctx });
1090
- if (this.effects?.length) {
1091
- this.effector.draw({ ctx });
1092
- } else {
1093
- this.renderer2D.draw({ ctx });
1094
- }
1125
+ setupView(ctx, pixelRatio, this.renderBoundingBox);
1126
+ uploadColors(ctx, this);
1127
+ const plugins = [...this.plugins.values()];
1128
+ plugins.sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
1129
+ if (plugin.render) {
1130
+ plugin.render?.(ctx, this);
1131
+ } else if (plugin.paths) {
1132
+ plugin.paths.forEach((path) => {
1133
+ drawPath({
1134
+ ctx,
1135
+ path,
1136
+ fontSize: this.computedStyle.fontSize
1137
+ });
1138
+ });
1139
+ }
1140
+ });
1095
1141
  return this;
1096
1142
  }
1097
1143
  }
1098
1144
 
1099
- export { Character, Deformer, Effector, Fragment, Highlighter, Measurer, Paragraph, Parser, Reflector, Renderer2D, Text, defaultHighlightRefer, defaultTextStyles, drawPath, filterEmpty, parseColor, uploadColor };
1145
+ export { Character, Fragment, Measurer, Paragraph, Parser, Text, defaultTextStyles, drawPath, effect, fillBackground, filterEmpty, getTransform2D, highlight, isNone, listStyle, parseColor, plugin, setupView, uploadColor, uploadColors };