@vectojs/core 1.38.0 → 1.39.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.
@@ -400,6 +400,18 @@ var Entity = (_class2 = class {
400
400
  }
401
401
  /** Attach a single child (the O(1) common path). See {@link add}. */
402
402
  _addOne(child) {
403
+ if (child === this) {
404
+ throw new Error(`Entity.add(): cannot add entity "${child.id}" under itself.`);
405
+ }
406
+ let ancestor = this.parent;
407
+ while (ancestor) {
408
+ if (ancestor === child) {
409
+ throw new Error(
410
+ `Entity.add(): cannot add entity "${child.id}" under "${this.id}" \u2014 an entity cannot be added under its own descendant.`
411
+ );
412
+ }
413
+ ancestor = ancestor.parent;
414
+ }
403
415
  if (child.parent) child.parent.remove(child);
404
416
  child.parent = this;
405
417
  this.children.push(child);
@@ -487,13 +499,26 @@ var Entity = (_class2 = class {
487
499
  * @example entity.animate({ x: 400, opacity: 0 }, 500);
488
500
  */
489
501
  animate(targetProps, durationMs) {
502
+ if (!Number.isFinite(durationMs) || durationMs <= 0) {
503
+ for (const key in targetProps) {
504
+ const end = targetProps[key];
505
+ if (typeof end !== "number") continue;
506
+ if (ANIMATABLE_PROPS.has(key)) {
507
+ this._applyAnimated(key, end);
508
+ } else {
509
+ this[key] = end;
510
+ }
511
+ }
512
+ _optionalChain([this, 'access', _51 => _51.scene, 'optionalAccess', _52 => _52.markDirty, 'call', _53 => _53({ entity: this.id, reason: "animation-start" })]);
513
+ return this;
514
+ }
490
515
  (this.animations ??= []).push({
491
516
  target: targetProps,
492
517
  duration: durationMs,
493
518
  startTime: -1,
494
519
  startProps: {}
495
520
  });
496
- _optionalChain([this, 'access', _51 => _51.scene, 'optionalAccess', _52 => _52.markDirty, 'call', _53 => _53({ entity: this.id, reason: "animation-start" })]);
521
+ _optionalChain([this, 'access', _54 => _54.scene, 'optionalAccess', _55 => _55.markDirty, 'call', _56 => _56({ entity: this.id, reason: "animation-start" })]);
497
522
  return this;
498
523
  }
499
524
  /** Write a driver-computed value to a backing field without re-triggering the setter. */
@@ -540,26 +565,26 @@ var Entity = (_class2 = class {
540
565
  * that need to seed a starting state (e.g. the presence helper's enter `from`).
541
566
  */
542
567
  setImmediate(prop, v) {
543
- const existing = _optionalChain([this, 'access', _54 => _54._drivers, 'optionalAccess', _55 => _55.get, 'call', _56 => _56(prop)]);
568
+ const existing = _optionalChain([this, 'access', _57 => _57._drivers, 'optionalAccess', _58 => _58.get, 'call', _59 => _59(prop)]);
544
569
  if (existing) this._settleDriver(existing);
545
- _optionalChain([this, 'access', _57 => _57._drivers, 'optionalAccess', _58 => _58.delete, 'call', _59 => _59(prop)]);
570
+ _optionalChain([this, 'access', _60 => _60._drivers, 'optionalAccess', _61 => _61.delete, 'call', _62 => _62(prop)]);
546
571
  this._applyAnimated(prop, v);
547
572
  }
548
573
  _settleDriver(driver) {
549
574
  const active = driver;
550
575
  const onDone = active.onDone;
551
576
  active.onDone = void 0;
552
- _optionalChain([onDone, 'optionalCall', _60 => _60()]);
577
+ _optionalChain([onDone, 'optionalCall', _63 => _63()]);
553
578
  }
554
579
  _spawnDriver(prop, to, cfg) {
555
- if (prop !== "opacity" && _optionalChain([this, 'access', _61 => _61.scene, 'optionalAccess', _62 => _62.prefersReducedMotion])) {
556
- const existing2 = _optionalChain([this, 'access', _63 => _63._drivers, 'optionalAccess', _64 => _64.get, 'call', _65 => _65(prop)]);
580
+ if (prop !== "opacity" && _optionalChain([this, 'access', _64 => _64.scene, 'optionalAccess', _65 => _65.prefersReducedMotion])) {
581
+ const existing2 = _optionalChain([this, 'access', _66 => _66._drivers, 'optionalAccess', _67 => _67.get, 'call', _68 => _68(prop)]);
557
582
  if (existing2) this._settleDriver(existing2);
558
- _optionalChain([this, 'access', _66 => _66._drivers, 'optionalAccess', _67 => _67.delete, 'call', _68 => _68(prop)]);
583
+ _optionalChain([this, 'access', _69 => _69._drivers, 'optionalAccess', _70 => _70.delete, 'call', _71 => _71(prop)]);
559
584
  this._applyAnimated(prop, to);
560
585
  return;
561
586
  }
562
- const existing = _optionalChain([this, 'access', _69 => _69._drivers, 'optionalAccess', _70 => _70.get, 'call', _71 => _71(prop)]);
587
+ const existing = _optionalChain([this, 'access', _72 => _72._drivers, 'optionalAccess', _73 => _73.get, 'call', _74 => _74(prop)]);
563
588
  if (existing) {
564
589
  this._settleDriver(existing);
565
590
  existing.retarget(to);
@@ -568,12 +593,17 @@ var Entity = (_class2 = class {
568
593
  const from = this._currentOf(prop);
569
594
  const driver = _animation.isTweenConfig.call(void 0, cfg) ? new (0, _animation.TweenDriver)(from, to, cfg) : new (0, _animation.SpringDriver)(from, to, cfg === "spring" ? {} : cfg);
570
595
  (this._drivers ??= /* @__PURE__ */ new Map()).set(prop, driver);
571
- _optionalChain([this, 'access', _72 => _72.scene, 'optionalAccess', _73 => _73.markDirty, 'call', _74 => _74({ entity: this.id, reason: "driver-added" })]);
572
- _optionalChain([this, 'access', _75 => _75.scene, 'optionalAccess', _76 => _76._registerActiveDriverEntity, 'call', _77 => _77(this)]);
596
+ const s = this.scene;
597
+ if (s && this._driversTickedFrame === s.currentFrame && s._updateWalkDt !== null) {
598
+ driver.tick(s._updateWalkDt);
599
+ this._applyDriverTick(prop, driver);
600
+ }
601
+ _optionalChain([this, 'access', _75 => _75.scene, 'optionalAccess', _76 => _76.markDirty, 'call', _77 => _77({ entity: this.id, reason: "driver-added" })]);
602
+ _optionalChain([this, 'access', _78 => _78.scene, 'optionalAccess', _79 => _79._registerActiveDriverEntity, 'call', _80 => _80(this)]);
573
603
  }
574
604
  /** Assignment path when a declarative transition is configured for `prop`. */
575
605
  _animateProp(prop, to) {
576
- const cfg = _optionalChain([this, 'access', _78 => _78._transitions, 'optionalAccess', _79 => _79.get, 'call', _80 => _80(prop)]);
606
+ const cfg = _optionalChain([this, 'access', _81 => _81._transitions, 'optionalAccess', _82 => _82.get, 'call', _83 => _83(prop)]);
577
607
  if (!cfg) {
578
608
  this._applyAnimated(prop, to);
579
609
  return;
@@ -602,7 +632,7 @@ var Entity = (_class2 = class {
602
632
  entries.map(
603
633
  (e) => new Promise((resolve) => {
604
634
  this._spawnDriver(e[0], e[1], cfg);
605
- const d = _optionalChain([this, 'access', _81 => _81._drivers, 'optionalAccess', _82 => _82.get, 'call', _83 => _83(e[0])]);
635
+ const d = _optionalChain([this, 'access', _84 => _84._drivers, 'optionalAccess', _85 => _85.get, 'call', _86 => _86(e[0])]);
606
636
  if (!d)
607
637
  resolve();
608
638
  else d.onDone = resolve;
@@ -624,7 +654,7 @@ var Entity = (_class2 = class {
624
654
  this._applyAnimated(prop, driver.value);
625
655
  }
626
656
  }
627
- _optionalChain([this, 'access', _84 => _84.scene, 'optionalAccess', _85 => _85.markDirty, 'call', _86 => _86({ entity: this.id, reason: "driver-tick" })]);
657
+ _optionalChain([this, 'access', _87 => _87.scene, 'optionalAccess', _88 => _88.markDirty, 'call', _89 => _89({ entity: this.id, reason: "driver-tick" })]);
628
658
  }
629
659
  /**
630
660
  * Internal: this entity's active-driver map (read-only view), or `null` if
@@ -649,15 +679,11 @@ var Entity = (_class2 = class {
649
679
  if (driver.isDone()) {
650
680
  this._applyAnimated(prop, driver.target);
651
681
  this._settleDriver(driver);
652
- _optionalChain([this, 'access', _87 => _87._drivers, 'optionalAccess', _88 => _88.delete, 'call', _89 => _89(prop)]);
682
+ _optionalChain([this, 'access', _90 => _90._drivers, 'optionalAccess', _91 => _91.delete, 'call', _92 => _92(prop)]);
653
683
  } else {
654
684
  this._applyAnimated(prop, driver.value);
655
685
  }
656
686
  }
657
- /** Internal: true if this entity currently has any active property driver. */
658
- _hasActiveDrivers() {
659
- return !!this._drivers && this._drivers.size > 0;
660
- }
661
687
  /**
662
688
  * Advance the entity's internal state for one frame.
663
689
  *
@@ -710,7 +736,7 @@ var Entity = (_class2 = class {
710
736
  * @example entity.on('click', (e) => console.log('clicked', e));
711
737
  */
712
738
  on(event, callback, options) {
713
- const map = _optionalChain([options, 'optionalAccess', _90 => _90.capture]) ? this.captureListeners ??= /* @__PURE__ */ new Map() : this.listeners ??= /* @__PURE__ */ new Map();
739
+ const map = _optionalChain([options, 'optionalAccess', _93 => _93.capture]) ? this.captureListeners ??= /* @__PURE__ */ new Map() : this.listeners ??= /* @__PURE__ */ new Map();
714
740
  if (!map.has(event)) {
715
741
  map.set(event, []);
716
742
  }
@@ -726,7 +752,7 @@ var Entity = (_class2 = class {
726
752
  * @returns `this` for method chaining.
727
753
  */
728
754
  off(event, callback, options) {
729
- const handlers = _optionalChain([(_optionalChain([options, 'optionalAccess', _91 => _91.capture]) ? this.captureListeners : this.listeners), 'optionalAccess', _92 => _92.get, 'call', _93 => _93(event)]);
755
+ const handlers = _optionalChain([(_optionalChain([options, 'optionalAccess', _94 => _94.capture]) ? this.captureListeners : this.listeners), 'optionalAccess', _95 => _95.get, 'call', _96 => _96(event)]);
730
756
  if (handlers) {
731
757
  const idx = handlers.indexOf(callback);
732
758
  if (idx !== -1) handlers.splice(idx, 1);
@@ -763,8 +789,8 @@ var Entity = (_class2 = class {
763
789
  }
764
790
  this._drivers.clear();
765
791
  }
766
- _optionalChain([this, 'access', _94 => _94.listeners, 'optionalAccess', _95 => _95.clear, 'call', _96 => _96()]);
767
- _optionalChain([this, 'access', _97 => _97.captureListeners, 'optionalAccess', _98 => _98.clear, 'call', _99 => _99()]);
792
+ _optionalChain([this, 'access', _97 => _97.listeners, 'optionalAccess', _98 => _98.clear, 'call', _99 => _99()]);
793
+ _optionalChain([this, 'access', _100 => _100.captureListeners, 'optionalAccess', _101 => _101.clear, 'call', _102 => _102()]);
768
794
  if (this.parent) {
769
795
  this.parent.remove(this);
770
796
  }
@@ -779,7 +805,7 @@ var Entity = (_class2 = class {
779
805
  * @param payload - Arbitrary data forwarded to each listener.
780
806
  */
781
807
  emit(event, payload) {
782
- const handlers = _optionalChain([this, 'access', _100 => _100.listeners, 'optionalAccess', _101 => _101.get, 'call', _102 => _102(event)]);
808
+ const handlers = _optionalChain([this, 'access', _103 => _103.listeners, 'optionalAccess', _104 => _104.get, 'call', _105 => _105(event)]);
783
809
  if (handlers) {
784
810
  handlers.forEach((h) => h(payload));
785
811
  }
@@ -793,19 +819,19 @@ var Entity = (_class2 = class {
793
819
  * with `requestAnimationFrame(() => document.getElementById(id)?.focus())`.
794
820
  */
795
821
  focus() {
796
- const el = _optionalChain([this, 'access', _103 => _103.scene, 'optionalAccess', _104 => _104.getA11yElement, 'call', _105 => _105(this.id)]);
822
+ const el = _optionalChain([this, 'access', _106 => _106.scene, 'optionalAccess', _107 => _107.getA11yElement, 'call', _108 => _108(this.id)]);
797
823
  if (el) {
798
824
  el.focus();
799
825
  return;
800
826
  }
801
827
  requestAnimationFrame(() => {
802
- const retry = _optionalChain([this, 'access', _106 => _106.scene, 'optionalAccess', _107 => _107.getA11yElement, 'call', _108 => _108(this.id)]);
828
+ const retry = _optionalChain([this, 'access', _109 => _109.scene, 'optionalAccess', _110 => _110.getA11yElement, 'call', _111 => _111(this.id)]);
803
829
  if (retry) retry.focus();
804
830
  });
805
831
  }
806
832
  /** Run one node's listeners for the event, honoring stopImmediatePropagation. */
807
833
  fireListeners(node, map, event) {
808
- const handlers = _optionalChain([map, 'optionalAccess', _109 => _109.get, 'call', _110 => _110(event.type)]);
834
+ const handlers = _optionalChain([map, 'optionalAccess', _112 => _112.get, 'call', _113 => _113(event.type)]);
809
835
  if (!handlers) return;
810
836
  event.currentTarget = node;
811
837
  for (const h of handlers.slice()) {
@@ -1026,6 +1052,14 @@ var Entity = (_class2 = class {
1026
1052
  * Accumulated world rotation: this entity's own `rotation` plus
1027
1053
  * that of every ancestor.
1028
1054
  *
1055
+ * Valid only under positive scales: the sum models the composed matrix
1056
+ * `T*S*R` correctly while every ancestor's `scaleX`/`scaleY` is positive,
1057
+ * but a mirrored (negative-scale) ancestor flips handedness, which an
1058
+ * additive sum cannot represent — the result is then off by the mirror.
1059
+ * For mirror-safe rotation, derive the angle from
1060
+ * {@link getWorldTransform}'s matrix (e.g. `atan2(b, a)`), as SVGEntity's
1061
+ * signed-scale handling already does.
1062
+ *
1029
1063
  * @returns The accumulated world rotation in radians.
1030
1064
  */
1031
1065
  getWorldRotation() {
@@ -1211,7 +1245,7 @@ var Entity = (_class2 = class {
1211
1245
  * @returns `true` if at least one animation or property driver remains.
1212
1246
  */
1213
1247
  hasPendingAnimations() {
1214
- return (_nullishCoalesce(_optionalChain([this, 'access', _111 => _111.animations, 'optionalAccess', _112 => _112.length]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([this, 'access', _113 => _113._drivers, 'optionalAccess', _114 => _114.size]), () => ( 0))) > 0;
1248
+ return (_nullishCoalesce(_optionalChain([this, 'access', _114 => _114.animations, 'optionalAccess', _115 => _115.length]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([this, 'access', _116 => _116._drivers, 'optionalAccess', _117 => _117.size]), () => ( 0))) > 0;
1215
1249
  }
1216
1250
  }, _class2);
1217
1251
 
@@ -1244,17 +1278,16 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1244
1278
  // long-lived shared atlas image would retain the whole entity.
1245
1279
  __init47() {this.atlasDecodeTarget = null}
1246
1280
  __init48() {this.atlasDecodeHandler = null}
1247
- __init49() {this.rgbColorCache = /* @__PURE__ */ new Map()}
1248
- __init50() {this.fontStringCache = []}
1249
- __init51() {this.layoutResult = null}
1281
+ __init49() {this.fontStringCache = []}
1282
+ __init50() {this.layoutResult = null}
1250
1283
  /**
1251
1284
  * Visual rows rebuilt from {@link layoutResult} (see
1252
1285
  * {@link rebuildProjectionLines}). Empty until a layout reply lands and the
1253
1286
  * reply's shaped glyphs can be mapped back to the source text 1:1.
1254
1287
  */
1255
- __init52() {this.projectionLines = []}
1288
+ __init51() {this.projectionLines = []}
1256
1289
  constructor(text, options) {
1257
- super();_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);_class3.prototype.__init48.call(this);_class3.prototype.__init49.call(this);_class3.prototype.__init50.call(this);_class3.prototype.__init51.call(this);_class3.prototype.__init52.call(this);;
1290
+ super();_class3.prototype.__init42.call(this);_class3.prototype.__init43.call(this);_class3.prototype.__init44.call(this);_class3.prototype.__init45.call(this);_class3.prototype.__init46.call(this);_class3.prototype.__init47.call(this);_class3.prototype.__init48.call(this);_class3.prototype.__init49.call(this);_class3.prototype.__init50.call(this);_class3.prototype.__init51.call(this);;
1258
1291
  this.font = options.font;
1259
1292
  this.texture = options.texture;
1260
1293
  this.fallbackFont = _nullishCoalesce(options.fallbackFont, () => ( "sans-serif"));
@@ -1299,7 +1332,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1299
1332
  const target = this.texture;
1300
1333
  this.atlasDecodeHandler = () => {
1301
1334
  this.detachAtlasDecodeListener();
1302
- _optionalChain([this, 'access', _115 => _115.scene, 'optionalAccess', _116 => _116.markDirty, 'call', _117 => _117()]);
1335
+ _optionalChain([this, 'access', _118 => _118.scene, 'optionalAccess', _119 => _119.markDirty, 'call', _120 => _120()]);
1303
1336
  };
1304
1337
  target.addEventListener("load", this.atlasDecodeHandler);
1305
1338
  target.addEventListener("error", this.atlasDecodeHandler);
@@ -1381,7 +1414,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1381
1414
  this.layoutResult = res;
1382
1415
  this.contentEpoch++;
1383
1416
  this.rebuildProjectionLines();
1384
- _optionalChain([this, 'access', _118 => _118.scene, 'optionalAccess', _119 => _119.markDirty, 'call', _120 => _120()]);
1417
+ _optionalChain([this, 'access', _121 => _121.scene, 'optionalAccess', _122 => _122.markDirty, 'call', _123 => _123()]);
1385
1418
  }
1386
1419
  });
1387
1420
  }
@@ -1400,8 +1433,8 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1400
1433
  getContentProjection() {
1401
1434
  if (!this.text) return null;
1402
1435
  const metrics = this.font.data.metrics;
1403
- const asc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _121 => _121.ascender]), () => ( 0.8));
1404
- const desc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _122 => _122.descender]), () => ( -0.2));
1436
+ const asc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _124 => _124.ascender]), () => ( 0.8));
1437
+ const desc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _125 => _125.descender]), () => ( -0.2));
1405
1438
  const actualLineHeight = _nullishCoalesce(this.lineHeight, () => ( this.fontSize * (asc - desc)));
1406
1439
  return {
1407
1440
  text: this.text,
@@ -1431,18 +1464,33 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1431
1464
  shaped.push(String.fromCodePoint(res.codePoints[i]));
1432
1465
  }
1433
1466
  const sourceWithoutNewlines = this.text.replace(/\n/g, "");
1434
- if (this.textAlign !== "left" || shaped.join("") !== sourceWithoutNewlines || res.yCoords.length !== res.codePoints.length) {
1467
+ if (this.textAlign !== "left" || // The layout worker breaks lines on \n only, so a \r survives as a real
1468
+ // glyph — a phantom ~1em advance at every CRLF line end — yet would
1469
+ // still compare equal below after \n is stripped from both sides. The
1470
+ // contract promises the coarse branch whenever the reply cannot
1471
+ // reproduce the source exactly, and that includes any \r (#692).
1472
+ this.text.includes("\r") || shaped.join("") !== sourceWithoutNewlines || res.yCoords.length !== res.codePoints.length) {
1435
1473
  this.projectionLines = [];
1436
1474
  return;
1437
1475
  }
1438
1476
  const metrics = this.font.data.metrics;
1439
- const asc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _123 => _123.ascender]), () => ( 0.8));
1440
- const desc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _124 => _124.descender]), () => ( -0.2));
1477
+ const asc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _126 => _126.ascender]), () => ( 0.8));
1478
+ const desc = _nullishCoalesce(_optionalChain([metrics, 'optionalAccess', _127 => _127.descender]), () => ( -0.2));
1441
1479
  const actualLineHeight = _nullishCoalesce(this.lineHeight, () => ( this.fontSize * (asc - desc)));
1442
1480
  const baseline = asc * this.fontSize;
1443
- const srcIdx = [];
1444
- for (let i = 0; i < this.text.length; i++) {
1445
- if (this.text[i] !== "\n") srcIdx.push(i);
1481
+ const srcStart = [];
1482
+ const srcEnd = [];
1483
+ {
1484
+ let cursor = 0;
1485
+ while (cursor < this.text.length) {
1486
+ const code = this.text.codePointAt(cursor);
1487
+ const width = code > 65535 ? 2 : 1;
1488
+ if (code !== 10) {
1489
+ srcStart.push(cursor);
1490
+ srcEnd.push(cursor + width);
1491
+ }
1492
+ cursor += width;
1493
+ }
1446
1494
  }
1447
1495
  const glyphsByLine = /* @__PURE__ */ new Map();
1448
1496
  let maxIdx = -1;
@@ -1458,8 +1506,8 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1458
1506
  let previousEnd = 0;
1459
1507
  for (let i = 0; i <= maxIdx; i++) {
1460
1508
  const glyphs = _nullishCoalesce(glyphsByLine.get(i), () => ( []));
1461
- const start = glyphs.length > 0 ? srcIdx[glyphs[0]] : previousEnd;
1462
- const end = glyphs.length > 0 ? srcIdx[glyphs[glyphs.length - 1]] + 1 : start;
1509
+ const start = glyphs.length > 0 ? srcStart[glyphs[0]] : previousEnd;
1510
+ const end = glyphs.length > 0 ? srcEnd[glyphs[glyphs.length - 1]] : start;
1463
1511
  starts.push(start);
1464
1512
  ends.push(end);
1465
1513
  previousEnd = Math.max(previousEnd, end);
@@ -1512,7 +1560,6 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1512
1560
  const code = this.layoutResult.codePoints[i];
1513
1561
  const nodeX = this.layoutResult.xCoords[i];
1514
1562
  const nodeY = this.layoutResult.yCoords[i];
1515
- const packedStyle = this.layoutResult.packedStyles[i];
1516
1563
  const def = this.font.getGlyph(code);
1517
1564
  if (!def || !def.atlasBounds || !def.planeBounds) continue;
1518
1565
  const { atlasBounds: ab, planeBounds: pb } = def;
@@ -1526,15 +1573,6 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1526
1573
  const glyphH = (pb.top - pb.bottom) * this.fontSize * worldScaleY;
1527
1574
  const v0 = this.font.data.atlas.yOrigin === "bottom" ? 1 - ab.top / ah : ab.top / ah;
1528
1575
  const v1 = this.font.data.atlas.yOrigin === "bottom" ? 1 - ab.bottom / ah : ab.bottom / ah;
1529
- const colorVal = packedStyle >>> 8;
1530
- let runColor = this.rgbColorCache.get(colorVal);
1531
- if (!runColor) {
1532
- const r = colorVal >> 16 & 255;
1533
- const g = colorVal >> 8 & 255;
1534
- const b = colorVal & 255;
1535
- runColor = `rgb(${r},${g},${b})`;
1536
- this.rgbColorCache.set(colorVal, runColor);
1537
- }
1538
1576
  scene.pointRenderer.addGlyph(
1539
1577
  glyphX,
1540
1578
  glyphY,
@@ -1544,7 +1582,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1544
1582
  v0,
1545
1583
  ab.right / aw,
1546
1584
  v1,
1547
- runColor,
1585
+ this.color,
1548
1586
  worldOpacity,
1549
1587
  worldRot
1550
1588
  );
@@ -1564,16 +1602,7 @@ var MSDFTextEntity = (_class3 = class extends Entity {
1564
1602
  const nodeY = this.layoutResult.yCoords[i];
1565
1603
  const packedStyle = this.layoutResult.packedStyles[i];
1566
1604
  const fontString = this.fontStringCache[packedStyle & 3];
1567
- const colorVal = packedStyle >>> 8;
1568
- let runColor = this.rgbColorCache.get(colorVal);
1569
- if (!runColor) {
1570
- const r = colorVal >> 16 & 255;
1571
- const g = colorVal >> 8 & 255;
1572
- const b = colorVal & 255;
1573
- runColor = `rgb(${r},${g},${b})`;
1574
- this.rgbColorCache.set(colorVal, runColor);
1575
- }
1576
- renderer.fillText(String.fromCodePoint(code), nodeX, nodeY, fontString, runColor);
1605
+ renderer.fillText(String.fromCodePoint(code), nodeX, nodeY, fontString, this.color);
1577
1606
  }
1578
1607
  }
1579
1608
  destroy() {
@@ -1588,11 +1617,32 @@ var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
1588
1617
  function isSvgWhitespace(ch) {
1589
1618
  return ch === " " || ch === " " || ch === "\n" || ch === "\r";
1590
1619
  }
1620
+ function findSvgTagEnd(source, from) {
1621
+ let quote = null;
1622
+ for (let i = from; i < source.length; i++) {
1623
+ const ch = source[i];
1624
+ if (quote !== null) {
1625
+ if (ch === quote) quote = null;
1626
+ } else if (ch === '"' || ch === "'") {
1627
+ quote = ch;
1628
+ } else if (ch === ">") {
1629
+ return i;
1630
+ }
1631
+ }
1632
+ return -1;
1633
+ }
1634
+ function isPercentDimension(value) {
1635
+ return value.trim().endsWith("%");
1636
+ }
1637
+ function viewBoxDimensions(value) {
1638
+ const parts = value.split(/[\s,]+/).map(parseFloat);
1639
+ return parts.length === 4 && parts.every(Number.isFinite) ? { width: parts[2], height: parts[3] } : null;
1640
+ }
1591
1641
  function readSvgAttribute(source, name) {
1592
1642
  const lowerSource = source.toLowerCase();
1593
1643
  const svgStart = lowerSource.indexOf("<svg");
1594
1644
  if (svgStart < 0) return null;
1595
- const tagEnd = source.indexOf(">", svgStart + 4);
1645
+ const tagEnd = findSvgTagEnd(source, svgStart + 4);
1596
1646
  if (tagEnd < 0) return null;
1597
1647
  const tag = source.slice(svgStart + 4, tagEnd);
1598
1648
  const lowerTag = tag.toLowerCase();
@@ -1621,23 +1671,23 @@ var SVGEntity = (_class4 = class extends Entity {
1621
1671
  * rasterized. Set to `'transparent'` to opt out and keep the box empty.
1622
1672
  * Default `'rgba(248,113,113,0.9)'`.
1623
1673
  */
1624
- __init53() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
1674
+ __init52() {this.fallbackStroke = "rgba(248,113,113,0.9)"}
1625
1675
  /** Fill behind the fallback marker. Default `'rgba(248,113,113,0.12)'`. */
1626
- __init54() {this.fallbackFill = "rgba(248,113,113,0.12)"}
1627
- __init55() {this.svgSource = ""}
1628
- __init56() {this.imageBitmap = null}
1629
- __init57() {this.imageElement = null}
1630
- __init58() {this.blobURL = null}
1631
- __init59() {this.currentImg = null}
1632
- __init60() {this.lodTimeout = null}
1633
- __init61() {this.rasterFailed = false}
1634
- __init62() {this.cachedDoc = null}
1635
- __init63() {this.baseWidth = 100}
1636
- __init64() {this.baseHeight = 100}
1637
- __init65() {this.lastRasterizedScale = 1}
1638
- __init66() {this.targetScale = 1}
1676
+ __init53() {this.fallbackFill = "rgba(248,113,113,0.12)"}
1677
+ __init54() {this.svgSource = ""}
1678
+ __init55() {this.imageBitmap = null}
1679
+ __init56() {this.imageElement = null}
1680
+ __init57() {this.blobURL = null}
1681
+ __init58() {this.currentImg = null}
1682
+ __init59() {this.lodTimeout = null}
1683
+ __init60() {this.rasterFailed = false}
1684
+ __init61() {this.cachedDoc = null}
1685
+ __init62() {this.baseWidth = 100}
1686
+ __init63() {this.baseHeight = 100}
1687
+ __init64() {this.lastRasterizedScale = 1}
1688
+ __init65() {this.targetScale = 1}
1639
1689
  constructor(svgSource, id) {
1640
- super(id);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);_class4.prototype.__init62.call(this);_class4.prototype.__init63.call(this);_class4.prototype.__init64.call(this);_class4.prototype.__init65.call(this);_class4.prototype.__init66.call(this);;
1690
+ super(id);_class4.prototype.__init52.call(this);_class4.prototype.__init53.call(this);_class4.prototype.__init54.call(this);_class4.prototype.__init55.call(this);_class4.prototype.__init56.call(this);_class4.prototype.__init57.call(this);_class4.prototype.__init58.call(this);_class4.prototype.__init59.call(this);_class4.prototype.__init60.call(this);_class4.prototype.__init61.call(this);_class4.prototype.__init62.call(this);_class4.prototype.__init63.call(this);_class4.prototype.__init64.call(this);_class4.prototype.__init65.call(this);;
1641
1691
  this.setSVGSource(svgSource);
1642
1692
  }
1643
1693
  setSVGSource(svgSource) {
@@ -1664,14 +1714,14 @@ var SVGEntity = (_class4 = class extends Entity {
1664
1714
  const wAttr = svgEl.getAttribute("width");
1665
1715
  const hAttr = svgEl.getAttribute("height");
1666
1716
  const vbAttr = svgEl.getAttribute("viewBox");
1667
- if (wAttr && hAttr) {
1717
+ if (wAttr && hAttr && !isPercentDimension(wAttr) && !isPercentDimension(hAttr)) {
1668
1718
  width = parseFloat(wAttr) || 100;
1669
1719
  height = parseFloat(hAttr) || 100;
1670
1720
  } else if (vbAttr) {
1671
- const parts = vbAttr.split(/[\s,]+/).map(parseFloat);
1672
- if (parts.length === 4) {
1673
- width = parts[2];
1674
- height = parts[3];
1721
+ const dims = viewBoxDimensions(vbAttr);
1722
+ if (dims) {
1723
+ width = dims.width;
1724
+ height = dims.height;
1675
1725
  }
1676
1726
  }
1677
1727
  }
@@ -1683,14 +1733,14 @@ var SVGEntity = (_class4 = class extends Entity {
1683
1733
  const wAttr = readSvgAttribute(this.svgSource, "width");
1684
1734
  const hAttr = readSvgAttribute(this.svgSource, "height");
1685
1735
  const vbAttr = readSvgAttribute(this.svgSource, "viewBox");
1686
- if (wAttr && hAttr) {
1736
+ if (wAttr && hAttr && !isPercentDimension(wAttr) && !isPercentDimension(hAttr)) {
1687
1737
  width = parseFloat(wAttr) || 100;
1688
1738
  height = parseFloat(hAttr) || 100;
1689
1739
  } else if (vbAttr) {
1690
- const parts = vbAttr.split(/[\s,]+/).map(parseFloat);
1691
- if (parts.length === 4) {
1692
- width = parts[2];
1693
- height = parts[3];
1740
+ const dims = viewBoxDimensions(vbAttr);
1741
+ if (dims) {
1742
+ width = dims.width;
1743
+ height = dims.height;
1694
1744
  }
1695
1745
  }
1696
1746
  }