gesso-core 0.6.18 → 0.6.20

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.
@@ -804,6 +804,10 @@ var DirtyNodeSet = class {
804
804
  isEmpty() {
805
805
  return this.nodes.size === 0;
806
806
  }
807
+ /** The nodes in the set, without draining it. Not to be changed while walked. */
808
+ values() {
809
+ return this.nodes.values();
810
+ }
807
811
  /**
808
812
  * Whether any node in the set carries one of `flags`, without
809
813
  * draining it.
@@ -3919,6 +3923,19 @@ const UiProperties = {
3919
3923
  inherited: false,
3920
3924
  affects: 6
3921
3925
  }),
3926
+ /**
3927
+ * Whether a scroll container shows its overlay scrollbars: \`'auto'\`
3928
+ * (the default) while scrolling, \`'none'\` never, and then they cannot
3929
+ * be grabbed either. For a container scrolled by something else, as a
3930
+ * picture is moved inside a window onto it, where a bar would claim
3931
+ * the person could scroll it.
3932
+ */
3933
+ scrollbars: defineProperty({
3934
+ name: "scrollbars",
3935
+ defaultValue: void 0,
3936
+ inherited: false,
3937
+ affects: 6
3938
+ }),
3922
3939
  inset: defineProperty({
3923
3940
  name: "inset",
3924
3941
  defaultValue: void 0,
@@ -5161,6 +5178,36 @@ var UiGraph = class {
5161
5178
  this.nodeRemovedListener?.(node);
5162
5179
  for (let i = removed.length - 1; i > 0; i--) this.detachNode(removed[i]);
5163
5180
  }
5181
+ /**
5182
+ * Moves a child before \`reference\` among its own siblings, or to the
5183
+ * end when that is null. Its parent is unchanged, and so is everything
5184
+ * it inherits, so its subtree is not visited: a lazy list recycling its
5185
+ * rows moves one from end to end on every frame of a scroll, and
5186
+ * re-inheriting walked the whole row each time.
5187
+ */
5188
+ moveWithin(child, reference) {
5189
+ const parent = child.parent;
5190
+ if (parent === null) throw new Error(`Node '${child.id}' has no parent to move within.`);
5191
+ if (reference !== null && reference.parent !== parent) throw new Error(`Reference node '${reference.id}' is not a sibling of '${child.id}'.`);
5192
+ if (child === reference || child.nextSibling === reference) return;
5193
+ this.detachNode(child);
5194
+ parent.childOrderVersion++;
5195
+ child.parent = parent;
5196
+ if (reference === null) {
5197
+ const last = parent.lastChild;
5198
+ child.previousSibling = last;
5199
+ if (last !== null) last.nextSibling = child;
5200
+ else parent.firstChild = child;
5201
+ parent.lastChild = child;
5202
+ return;
5203
+ }
5204
+ const previous = reference.previousSibling;
5205
+ if (previous !== null) previous.nextSibling = child;
5206
+ else parent.firstChild = child;
5207
+ child.previousSibling = previous;
5208
+ child.nextSibling = reference;
5209
+ reference.previousSibling = child;
5210
+ }
5164
5211
  detachNode(node) {
5165
5212
  const parent = node.parent;
5166
5213
  if (!parent) return;
@@ -7707,9 +7754,14 @@ function linkRunAtPointIn(geometry, x, y) {
7707
7754
  * testing. This is the same shape as the CSS
7708
7755
  * `inert` attribute: a disabled Button stays
7709
7756
  * inert even when it wraps Text children.
7710
- * pointerEvents — 'none' makes the node AND its subtree skipped
7711
- * by hit testing (CSS-like). Use it to block a
7712
- * whole region.
7757
+ * pointerEvents — inherited, as in CSS: 'none' makes the node and
7758
+ * its subtree skipped by hit testing, except where
7759
+ * a descendant sets 'auto', which takes presses
7760
+ * again. An overlay that lets presses through to
7761
+ * what is under it, with popups on it that take
7762
+ * them, is 'none' with 'auto' on each popup.
7763
+ * 'none' on the node itself also keeps it from
7764
+ * focus and keyboard, as before.
7713
7765
  * hitTestable — false skips only the node itself; children are
7714
7766
  * still tested. Use it for decorative containers
7715
7767
  * that must not swallow clicks.
@@ -7721,13 +7773,29 @@ function linkRunAtPointIn(geometry, x, y) {
7721
7773
  */
7722
7774
  /** The node and its whole subtree are excluded from pointer input. */
7723
7775
  function isNodeInert(node) {
7724
- if (node.properties.get("disabled") === true) return true;
7725
7776
  if (node.properties.get("pointerEvents") === "none") return true;
7777
+ return isSubtreeInert(node);
7778
+ }
7779
+ /**
7780
+ * Disabled or hidden: the node and everything under it, with no way
7781
+ * back for a descendant. `pointerEvents: 'none'` is not this, since a
7782
+ * descendant can set 'auto'; the hit tester follows that itself.
7783
+ */
7784
+ function isSubtreeInert(node) {
7785
+ if (node.properties.get("disabled") === true) return true;
7726
7786
  if (node.properties.get("visible") === false) return true;
7727
7787
  const opacity = node.properties.get("opacity");
7728
7788
  if (typeof opacity === "number" && Number.isFinite(opacity) && opacity <= 0) return true;
7729
7789
  return false;
7730
7790
  }
7791
+ /** `pointerEvents` as CSS inherits it: the nearest value set from the node up, 'auto' when none is. */
7792
+ function pointerEventsOf(node) {
7793
+ for (let current = node; current !== null; current = current.parent) {
7794
+ const value = current.properties.get("pointerEvents");
7795
+ if (value === "auto" || value === "none") return value;
7796
+ }
7797
+ return "auto";
7798
+ }
7731
7799
  /** The node itself cannot be the target of a hit, but children can. */
7732
7800
  function isNodeHitTestable(node) {
7733
7801
  return node.properties.get("hitTestable") !== false;
@@ -9358,6 +9426,7 @@ function scrollbarThumb(rec, axis) {
9358
9426
  return thumbFor(rec, axis);
9359
9427
  }
9360
9428
  function thumbFor(rec, axis) {
9429
+ if (rec.hidesScrollbars) return null;
9361
9430
  const viewport = axis === "y" ? rec.height : rec.width;
9362
9431
  const content = axis === "y" ? rec.contentHeight : rec.contentWidth;
9363
9432
  if (!(content > viewport) || viewport <= 0) return null;
@@ -9473,6 +9542,13 @@ var UiHitTester = class {
9473
9542
  liftedScratch = [];
9474
9543
  /** True while testing inside a lifted subtree, so it is not skipped again. */
9475
9544
  inLifted = false;
9545
+ /**
9546
+ * `pointerEvents` as the node being tested has it, inherited: true
9547
+ * under a 'none' that nothing nearer has set back to 'auto'. Such a
9548
+ * node is passed over, but its children are still tested, since one
9549
+ * of them may say 'auto'.
9550
+ */
9551
+ pointerNone = false;
9476
9552
  constructor(layout, root) {
9477
9553
  this.layout = layout;
9478
9554
  this.root = root;
@@ -9507,7 +9583,9 @@ var UiHitTester = class {
9507
9583
  const out = [];
9508
9584
  this.forEachLifted(x, y, (node, px, py) => {
9509
9585
  this.inLifted = true;
9586
+ this.pointerNone = pointerEventsOf(node.parent) === "none";
9510
9587
  this.collectNode(node, px, py, out);
9588
+ this.pointerNone = false;
9511
9589
  this.inLifted = false;
9512
9590
  return false;
9513
9591
  });
@@ -9515,7 +9593,16 @@ var UiHitTester = class {
9515
9593
  return out;
9516
9594
  }
9517
9595
  collectNode(node, x, y, out) {
9518
- if (isNodeInert(node)) return;
9596
+ if (isSubtreeInert(node)) return;
9597
+ const outer = this.pointerNone;
9598
+ this.pointerNone = this.inheritsNone(node, outer);
9599
+ try {
9600
+ this.collectOwn(node, x, y, out);
9601
+ } finally {
9602
+ this.pointerNone = outer;
9603
+ }
9604
+ }
9605
+ collectOwn(node, x, y, out) {
9519
9606
  const rec = this.layout.recordFor(node);
9520
9607
  if (rec === void 0 || !this.invertPoint(node, rec, x, y)) return;
9521
9608
  if (rec.lifted && !this.inLifted) return;
@@ -9526,7 +9613,7 @@ var UiHitTester = class {
9526
9613
  const shiftX = rec.clips && rec.scrollable ? rec.scrollX : 0;
9527
9614
  const shiftY = rec.clips && rec.scrollable ? rec.scrollY : 0;
9528
9615
  this.collectChildren(node, px + shiftX, py + shiftY, out);
9529
- if (inside && isNodeHitTestable(node)) out.push(node);
9616
+ if (inside && this.targetable(node)) out.push(node);
9530
9617
  }
9531
9618
  collectChildren(parent, x, y, out) {
9532
9619
  const order = this.layout.recordFor(parent)?.paintOrder;
@@ -9609,7 +9696,9 @@ var UiHitTester = class {
9609
9696
  hitTestLifted(x, y) {
9610
9697
  return this.forEachLifted(x, y, (node, px, py) => {
9611
9698
  this.inLifted = true;
9699
+ this.pointerNone = pointerEventsOf(node.parent) === "none";
9612
9700
  const hit = this.hitTestNode(node, px, py);
9701
+ this.pointerNone = false;
9613
9702
  this.inLifted = false;
9614
9703
  return hit;
9615
9704
  });
@@ -9675,7 +9764,25 @@ var UiHitTester = class {
9675
9764
  this.point.y = py;
9676
9765
  }
9677
9766
  hitTestNode(node, x, y) {
9678
- if (isNodeInert(node)) return false;
9767
+ if (isSubtreeInert(node)) return false;
9768
+ const outer = this.pointerNone;
9769
+ this.pointerNone = this.inheritsNone(node, outer);
9770
+ try {
9771
+ return this.hitTestOwn(node, x, y);
9772
+ } finally {
9773
+ this.pointerNone = outer;
9774
+ }
9775
+ }
9776
+ /** Whether a node is under a 'none': its own value, else its parent's. */
9777
+ inheritsNone(node, parentNone) {
9778
+ const own = node.properties.get("pointerEvents");
9779
+ return own === "none" ? true : own === "auto" ? false : parentNone;
9780
+ }
9781
+ /** Whether a press may land on the node itself. */
9782
+ targetable(node) {
9783
+ return !this.pointerNone && isNodeHitTestable(node);
9784
+ }
9785
+ hitTestOwn(node, x, y) {
9679
9786
  const rec = this.layout.recordFor(node);
9680
9787
  if (rec === void 0) return false;
9681
9788
  if (rec.lifted && !this.inLifted) return false;
@@ -9684,7 +9791,7 @@ var UiHitTester = class {
9684
9791
  const py = this.point.y - rec.stickyOffsetY;
9685
9792
  if (rec.clips) {
9686
9793
  if (px < rec.x || px >= rec.x + rec.width || py < rec.y || py >= rec.y + rec.height) return false;
9687
- if ((rec.scrollable || rec.textScrollbars) && scrollLeaderOf(node) === null && this.hitScrollbar(node, rec, px, py)) return true;
9794
+ if (!this.pointerNone && (rec.scrollable || rec.textScrollbars) && scrollLeaderOf(node) === null && this.hitScrollbar(node, rec, px, py)) return true;
9688
9795
  if (this.zoneOnly) {
9689
9796
  const shiftX = rec.scrollX;
9690
9797
  const shiftY = rec.scrollY;
@@ -9693,10 +9800,10 @@ var UiHitTester = class {
9693
9800
  const shiftX = rec.scrollable ? rec.scrollX : 0;
9694
9801
  const shiftY = rec.scrollable ? rec.scrollY : 0;
9695
9802
  if (this.hitTestChildren(node, px + shiftX, py + shiftY)) return true;
9696
- return isNodeHitTestable(node) && this.recordHit(node, rec, px, py);
9803
+ return this.targetable(node) && this.recordHit(node, rec, px, py);
9697
9804
  }
9698
9805
  if (this.hitTestChildren(node, px, py)) return true;
9699
- if (this.zoneOnly || !isNodeHitTestable(node)) return false;
9806
+ if (this.zoneOnly || !this.targetable(node)) return false;
9700
9807
  if (px < rec.x || px >= rec.x + rec.width || py < rec.y || py >= rec.y + rec.height) return false;
9701
9808
  return this.recordHit(node, rec, px, py);
9702
9809
  }
@@ -10871,6 +10978,11 @@ var UiWheelController = class {
10871
10978
  * wheel of a burst land correctly even though the first could not.
10872
10979
  */
10873
10980
  lastWheelTarget = null;
10981
+ /**
10982
+ * The container the wheel last scrolled, for a frame's report of where
10983
+ * scrolling put it. Null until the wheel has scrolled something.
10984
+ */
10985
+ lastScrolled = null;
10874
10986
  wheel(x, y, deltaX, deltaY, modifiers = noKeyModifiers(), deltaMode = 0, wheelDeltaY, at) {
10875
10987
  const target = this.hitTester.hitTest(x, y)?.node ?? null;
10876
10988
  this.lastWheelTarget = target;
@@ -11023,7 +11135,8 @@ var UiWheelController = class {
11023
11135
  dirX: 0,
11024
11136
  dirY: 0,
11025
11137
  quiet: 0,
11026
- fresh: false
11138
+ fresh: false,
11139
+ fitted: false
11027
11140
  };
11028
11141
  this.pacing.set(container, paced);
11029
11142
  }
@@ -11039,9 +11152,11 @@ var UiWheelController = class {
11039
11152
  });
11040
11153
  while (paced.samples.length > 2 && paced.samples[0].t < this.eventTime - VELOCITY_WINDOW_MS) paced.samples.shift();
11041
11154
  this.scrollSink.scrollBy(container, 0, 0, "instant");
11155
+ this.lastScrolled = container;
11042
11156
  return;
11043
11157
  }
11044
11158
  this.scrollSink.scrollBy(container, dx, dy, behavior);
11159
+ this.lastScrolled = container;
11045
11160
  }
11046
11161
  /**
11047
11162
  * A container's state as a step should judge it: during a paced gesture,
@@ -11069,16 +11184,15 @@ var UiWheelController = class {
11069
11184
  * comes a varying time after the last step: applied as they arrive, a
11070
11185
  * steady flick moves the page by uneven amounts, which reads as
11071
11186
  * judder. Evening that out by waiting for steps puts the page behind
11072
- * the hand. So this does what a browser does for its own scrolling:
11073
- * it puts the page where the input will have reached when the frame is
11074
- * shown, the steps so far plus their velocity times the time since the
11075
- * last one, never more than a frame and a half ahead. Steps come out
11076
- * even, and nothing trails.
11187
+ * the hand. So this does what a browser does for its own scrolling: it
11188
+ * resamples the steps at the frame's time, reading a line fitted through
11189
+ * the last `VELOCITY_WINDOW_MS` of them. Frames come out even, and
11190
+ * nothing trails.
11077
11191
  *
11078
- * A gesture's first step, with no velocity yet, goes as it is. Once the
11079
- * steps stop, the prediction is withdrawn and the page settles exactly
11080
- * where they add up to; a trackpad's momentum slows to nothing first,
11081
- * so there is next to nothing to withdraw.
11192
+ * A gesture's first step, with no line yet, goes as it is. Once the
11193
+ * steps stop, the page eases over a few frames to exactly where they add
11194
+ * up to; a trackpad's momentum slows to nothing first, so there is next
11195
+ * to nothing to take up.
11082
11196
  *
11083
11197
  * True while a gesture is still being paced, so the host keeps frames
11084
11198
  * coming until it has settled.
@@ -11098,14 +11212,38 @@ var UiWheelController = class {
11098
11212
  let y = paced.totalY;
11099
11213
  const samples = paced.samples;
11100
11214
  const last = samples[samples.length - 1];
11101
- let first = last;
11102
- for (let k = samples.length - 2; k >= 0 && last !== void 0 && samples[k].t >= last.t - RECENT_MS; k--) first = samples[k];
11215
+ const first = samples[0];
11103
11216
  const since = time === void 0 || last === void 0 ? -1 : time - last.t;
11104
- if (last !== void 0 && first !== void 0 && since >= -1 && since < STALE_MS && last.t - first.t >= 2) {
11105
- const span = last.t - first.t;
11106
- const ahead = Math.min(Math.max(since, 0) + this.frameInterval, this.frameInterval * MAX_AHEAD_FRAMES);
11107
- x += (last.x - first.x) / span * ahead;
11108
- y += (last.y - first.y) / span * ahead;
11217
+ const at = time === void 0 ? void 0 : time - RESAMPLE_MS;
11218
+ if (at !== void 0 && last !== void 0 && first !== void 0 && since >= -1 && at - last.t < STALE_MS && samples.length >= MIN_FIT_STEPS && last.t - first.t >= 2) {
11219
+ let meanT = 0;
11220
+ let meanX = 0;
11221
+ let meanY = 0;
11222
+ for (const sample of samples) {
11223
+ meanT += sample.t;
11224
+ meanX += sample.x;
11225
+ meanY += sample.y;
11226
+ }
11227
+ meanT /= samples.length;
11228
+ meanX /= samples.length;
11229
+ meanY /= samples.length;
11230
+ let varT = 0;
11231
+ let covX = 0;
11232
+ let covY = 0;
11233
+ for (const sample of samples) {
11234
+ const dt = sample.t - meanT;
11235
+ varT += dt * dt;
11236
+ covX += dt * (sample.x - meanX);
11237
+ covY += dt * (sample.y - meanY);
11238
+ }
11239
+ x = meanX + covX / varT * (at - meanT);
11240
+ y = meanY + covY / varT * (at - meanT);
11241
+ paced.fitted = true;
11242
+ } else if (paced.fitted) {
11243
+ const drawnX = paced.lastX - paced.baseX;
11244
+ const drawnY = paced.lastY - paced.baseY;
11245
+ x = Math.abs(x - drawnX) < SETTLED_PX ? x : drawnX + (x - drawnX) * SETTLE_RATE;
11246
+ y = Math.abs(y - drawnY) < SETTLED_PX ? y : drawnY + (y - drawnY) * SETTLE_RATE;
11109
11247
  }
11110
11248
  const state = this.scrollSink.containerState(container);
11111
11249
  if (state === void 0) {
@@ -11131,7 +11269,7 @@ var UiWheelController = class {
11131
11269
  if (dx !== 0 || dy !== 0) this.scrollSink.scrollBy(container, dx, dy, "instant");
11132
11270
  paced.lastX = toX;
11133
11271
  paced.lastY = toY;
11134
- if (toX === Math.min(Math.max(paced.baseX + paced.totalX, 0), state.maxScrollX) && toY === Math.min(Math.max(paced.baseY + paced.totalY, 0), state.maxScrollY) && paced.quiet >= QUIET_FRAMES) this.pacing.delete(container);
11272
+ if (toX === Math.min(Math.max(paced.baseX + paced.totalX, 0), state.maxScrollX) && toY === Math.min(Math.max(paced.baseY + paced.totalY, 0), state.maxScrollY) && paced.quiet >= QUIET_FRAMES && (since < 0 || since >= STALE_MS)) this.pacing.delete(container);
11135
11273
  }
11136
11274
  return this.pacing.size > 0;
11137
11275
  }
@@ -11185,18 +11323,29 @@ function isScrollContainer(node) {
11185
11323
  * hand from a trackpad to a mouse.
11186
11324
  */
11187
11325
  const PRECISE_MEMORY_MS = 400;
11188
- /** How far back the velocity is read: a few of a trackpad's steps. */
11189
- const VELOCITY_WINDOW_MS = 40;
11190
- /** How long after the last step its velocity is still trusted. */
11191
- const STALE_MS = 40;
11192
11326
  /**
11193
- * The furthest ahead a frame predicts, in frames: a frame's lead, for the
11194
- * frame it takes to be shown, plus the time since the last step, which is
11195
- * what evens out where each frame falls between steps.
11327
+ * How far back the steps are fitted: five or six of a trackpad's. Less, and
11328
+ * the line follows each step's rounding; more, and it lags a flick that is
11329
+ * speeding up or slowing down. Measured on a real flick on a Mac, 48 ms left
11330
+ * a sixth of the frame-to-frame variation of the last-few-steps velocity,
11331
+ * with no frame standing still and the page no further behind the hand.
11196
11332
  */
11197
- const MAX_AHEAD_FRAMES = 1.5;
11198
- /** How far back the velocity is read for a prediction. */
11199
- const RECENT_MS = 20;
11333
+ const VELOCITY_WINDOW_MS = 48;
11334
+ /** How long after the last step its line is still trusted. */
11335
+ const STALE_MS = 40;
11336
+ /**
11337
+ * How long before the frame the line is read. Each millisecond is one more
11338
+ * of lag and less of a guess: measured on recorded flicks, 12 left the
11339
+ * worst frames a third as uneven as reading at the frame, and the page
11340
+ * about 11 ms behind the hand, against 15 for steps applied as they come.
11341
+ */
11342
+ const RESAMPLE_MS = 12;
11343
+ /** Steps a line is fitted through: two are a line through their rounding. */
11344
+ const MIN_FIT_STEPS = 3;
11345
+ /** How much of the way to where the steps add up to a frame goes once they stop. */
11346
+ const SETTLE_RATE = .35;
11347
+ /** Close enough to where the steps add up to, to go the rest at once. */
11348
+ const SETTLED_PX = .25;
11200
11349
  /** The display's interval before one has been measured. */
11201
11350
  const DEFAULT_FRAME_MS = 1e3 / 60;
11202
11351
  /** Quiet frames, once settled, after which a gesture is over. */
@@ -13926,6 +14075,8 @@ var LayoutRecord = class {
13926
14075
  * multiline, the overlay scrollbars.
13927
14076
  */
13928
14077
  scrollsText = false;
14078
+ /** A scroll container set to show no scrollbars (\`scrollbars: 'none'\`). */
14079
+ hidesScrollbars = false;
13929
14080
  /**
13930
14081
  * A multiline editable, which shows the overlay scrollbars a scroll
13931
14082
  * container does over its own text. A single-line field scrolls
@@ -14188,6 +14339,7 @@ var LayoutRecord = class {
14188
14339
  this.paintReachBottom = 0;
14189
14340
  this.scrollable = false;
14190
14341
  this.scrollsText = false;
14342
+ this.hidesScrollbars = false;
14191
14343
  this.textScrollbars = false;
14192
14344
  this.mirrored = false;
14193
14345
  this.sticky = false;
@@ -18207,6 +18359,7 @@ var LayoutEngine = class {
18207
18359
  const overflow = props.get("overflow");
18208
18360
  rec.scrollable = node.type === "scroll-view" || overflow === "scroll" || overflow === "auto" || overflow === "hidden" && scrollLeaderOf(node) !== null;
18209
18361
  rec.scrollsText = node.type === "editable-text";
18362
+ rec.hidesScrollbars = props.get("scrollbars") === "none";
18210
18363
  rec.textScrollbars = rec.scrollsText && props.get("multiline") === true;
18211
18364
  rec.mirrored = (rec.scrollable || rec.textScrollbars) && this.startEdge(node) === "right";
18212
18365
  rec.clips = rec.scrollable || overflow === "hidden" || node.type === "editable-text";
@@ -20382,15 +20535,10 @@ var ScrollLayerCache = class {
20382
20535
  * is how every one of those ends.
20383
20536
  */
20384
20537
  refused = /* @__PURE__ */ new Set();
20385
- geometryVersion = NaN;
20386
20538
  dpr = 0;
20387
20539
  surfaceWidth = 0;
20388
20540
  surfaceHeight = 0;
20389
20541
  frame = null;
20390
- /** Whether this frame changed nothing a layer could not absorb: no layout, children, content or environment. */
20391
- calm = false;
20392
- /** Nodes this frame dirtied, and every ancestor of each, less a container that only scrolled. */
20393
- disturbed = /* @__PURE__ */ new Set();
20394
20542
  /** Consecutive frames each container without a layer has scrolled quietly. See `SCROLL_LAYER_SETTLE_FRAMES`. */
20395
20543
  streaks = /* @__PURE__ */ new WeakMap();
20396
20544
  /**
@@ -20422,46 +20570,26 @@ var ScrollLayerCache = class {
20422
20570
  /**
20423
20571
  * Settles which layers this frame's changes leave standing, before
20424
20572
  * anything is drawn. Every rule here drops; none keeps. A frame the
20425
- * caller could not vouch for drops them all.
20573
+ * caller could not vouch for drops them all. What changed inside a
20574
+ * container is not settled here but drawn over: see \`damage\`.
20426
20575
  */
20427
20576
  beginFrame(changes, dpr, surfaceWidth, surfaceHeight) {
20428
20577
  const interrupted = this.open;
20429
20578
  this.open = true;
20430
- this.disturbed.clear();
20431
- this.calm = changes !== void 0;
20432
- for (const [node, flags] of changes?.frame.entries() ?? []) {
20433
- if ((flags & -355) !== 0) {
20434
- this.calm = false;
20435
- break;
20436
- }
20437
- for (let current = (flags & 32) !== 0 ? node.parent : node; current !== null; current = current.parent) this.disturbed.add(current);
20438
- }
20439
- if (interrupted || changes === void 0 || dpr !== this.dpr || surfaceWidth !== this.surfaceWidth || surfaceHeight !== this.surfaceHeight || changes.geometryVersion !== this.geometryVersion) {
20579
+ if (interrupted || changes === void 0 || dpr !== this.dpr || surfaceWidth !== this.surfaceWidth || surfaceHeight !== this.surfaceHeight) {
20440
20580
  this.dropAll();
20441
20581
  this.dpr = dpr;
20442
20582
  this.surfaceWidth = surfaceWidth;
20443
20583
  this.surfaceHeight = surfaceHeight;
20444
- this.geometryVersion = changes?.geometryVersion ?? NaN;
20445
20584
  this.frame = changes?.frame ?? null;
20446
20585
  return;
20447
20586
  }
20448
20587
  const frame = changes.frame;
20449
20588
  this.frame = frame;
20450
- if (this.layers.size === 0 && this.refused.size === 0) return;
20589
+ for (const node of this.layers.keys()) if (dirtyAbove(frame, node)) this.forget(node);
20451
20590
  for (const [node, flags] of frame.entries()) {
20452
- if ((flags & -355) !== 0) {
20453
- this.dropAll();
20454
- return;
20455
- }
20456
- const scrolledOnly = flags === 32;
20457
- for (let current = scrolledOnly ? node.parent : node; current !== null; current = current.parent) this.forget(current);
20458
- }
20459
- for (const node of [...this.layers.keys(), ...this.refused]) for (let ancestor = node.parent; ancestor !== null; ancestor = ancestor.parent) {
20460
- const flags = frame.dirtyFlagsFor(ancestor);
20461
- if (flags !== 0 && flags !== 32) {
20462
- this.forget(node);
20463
- break;
20464
- }
20591
+ if (flags === 32) continue;
20592
+ for (const refused of this.refused) if (node === refused || contains(node, refused) || contains(refused, node)) this.refused.delete(refused);
20465
20593
  }
20466
20594
  }
20467
20595
  /** Drops the layers the frame did not composite: their container was culled, removed, or drawn directly. */
@@ -20475,14 +20603,16 @@ var ScrollLayerCache = class {
20475
20603
  }
20476
20604
  /**
20477
20605
  * Whether a container with no layer should get one now: it has
20478
- * scrolled on the last few frames running with nothing else in or
20479
- * above it changing, which is the evidence a layer would be kept, and
20480
- * it has not just shown it holds something a layer cannot.
20606
+ * scrolled on the last few frames running with nothing above it
20607
+ * changing, which is the evidence a layer would be kept, and it has
20608
+ * not just shown it holds something a layer cannot. What changes
20609
+ * inside it is absorbed item by item (see \`damage\`), so a lazy list
20610
+ * mounting rows as it scrolls keeps its layer.
20481
20611
  */
20482
20612
  wants(node) {
20483
20613
  const frame = this.frame;
20484
20614
  if (frame === null || this.layers.size >= MAX_LAYERS || this.refused.has(node)) return false;
20485
- if (!this.calm || this.disturbed.has(node) || dirtyAbove(frame, node)) {
20615
+ if (dirtyAbove(frame, node)) {
20486
20616
  this.streaks.delete(node);
20487
20617
  return false;
20488
20618
  }
@@ -20512,9 +20642,10 @@ var ScrollLayerCache = class {
20512
20642
  offsetX: placement.offsetX,
20513
20643
  offsetY: placement.offsetY,
20514
20644
  dpr: this.dpr,
20515
- scrollX: rec.scrollX,
20516
- scrollY: rec.scrollY,
20517
- used: true
20645
+ scrollX: snapScroll(rec.scrollX, this.dpr),
20646
+ scrollY: snapScroll(rec.scrollY, this.dpr),
20647
+ used: true,
20648
+ items: /* @__PURE__ */ new Map()
20518
20649
  };
20519
20650
  this.layers.set(node, layer);
20520
20651
  this.stats.built++;
@@ -20535,8 +20666,8 @@ var ScrollLayerCache = class {
20535
20666
  * device pixels, which a copy cannot follow without resampling.
20536
20667
  */
20537
20668
  shift(layer, rec) {
20538
- const exactX = (layer.scrollX - rec.scrollX) * layer.dpr;
20539
- const exactY = (layer.scrollY - rec.scrollY) * layer.dpr;
20669
+ const exactX = (layer.scrollX - snapScroll(rec.scrollX, layer.dpr)) * layer.dpr;
20670
+ const exactY = (layer.scrollY - snapScroll(rec.scrollY, layer.dpr)) * layer.dpr;
20540
20671
  const dx = Math.round(exactX);
20541
20672
  const dy = Math.round(exactY);
20542
20673
  if (Math.abs(exactX - dx) > EPSILON || Math.abs(exactY - dy) > EPSILON) return null;
@@ -20574,6 +20705,79 @@ var ScrollLayerCache = class {
20574
20705
  });
20575
20706
  return strips;
20576
20707
  }
20708
+ /**
20709
+ * The parts of a layer this frame's changes inside the container left
20710
+ * wrong, in the layer's pixels, or \`'all'\` when that is most of it.
20711
+ * Called after \`shift\`, so the layer's offset is the frame's.
20712
+ *
20713
+ * Judged item by item, an item being one of the container's children
20714
+ * (through any fragments): a lazy list's row, say. An item added,
20715
+ * removed or moved is wrong where it was and where it is; an item with
20716
+ * anything changed inside it is wrong where it is. Whatever none of
20717
+ * that touches is right already, which in a lazy list scrolling is
20718
+ * everything on screen: the rows it mounts and unmounts are in its
20719
+ * overscan, and the spacers that stand for the rest only grow and
20720
+ * shrink off the top and bottom.
20721
+ */
20722
+ damage(layer, container, layout) {
20723
+ const frame = this.frame;
20724
+ if (frame === null) return "all";
20725
+ const boxes = [];
20726
+ for (const [node, flags] of frame.entries()) {
20727
+ if (node === container) {
20728
+ if ((flags & 128) !== 0) return "all";
20729
+ continue;
20730
+ }
20731
+ const item = itemHolding(container, node);
20732
+ if (item === null) continue;
20733
+ const was = layer.items.get(item);
20734
+ const now = boxOf(layout, item);
20735
+ if (was !== void 0) boxes.push(was);
20736
+ if (now !== void 0 && (was === void 0 || !sameBox(was, now))) boxes.push(now);
20737
+ }
20738
+ const seen = /* @__PURE__ */ new Set();
20739
+ for (const item of itemsOf(container)) {
20740
+ seen.add(item);
20741
+ const now = boxOf(layout, item);
20742
+ const was = layer.items.get(item);
20743
+ if (now === void 0) continue;
20744
+ if (was === void 0) boxes.push(now);
20745
+ else if (!sameBox(was, now)) boxes.push(was, now);
20746
+ }
20747
+ for (const [item, was] of layer.items) if (!seen.has(item)) boxes.push(was);
20748
+ const rects = [];
20749
+ let area = 0;
20750
+ for (const box of boxes) {
20751
+ const rect = this.toLayer(layer, box);
20752
+ if (rect !== null && !rects.some((other) => sameBox(other, rect))) {
20753
+ rects.push(rect);
20754
+ area += rect.width * rect.height;
20755
+ }
20756
+ }
20757
+ return area > layer.width * layer.height * MOSTLY ? "all" : rects;
20758
+ }
20759
+ /** Notes where the container's items are, for the next frame's \`damage\`. */
20760
+ remember(layer, container, layout) {
20761
+ layer.items.clear();
20762
+ for (const item of itemsOf(container)) {
20763
+ const box = boxOf(layout, item);
20764
+ if (box !== void 0) layer.items.set(item, box);
20765
+ }
20766
+ }
20767
+ /** An item's box in the layer's pixels, widened for what paints past it, or null off the layer. */
20768
+ toLayer(layer, box) {
20769
+ const dpr = layer.dpr;
20770
+ const left = Math.max(0, Math.floor(layer.offsetX + dpr * (box.x - layer.scrollX - 8)));
20771
+ const top = Math.max(0, Math.floor(layer.offsetY + dpr * (box.y - layer.scrollY - 8)));
20772
+ const right = Math.min(layer.width, Math.ceil(layer.offsetX + dpr * (box.x + box.width - layer.scrollX + 8)));
20773
+ const bottom = Math.min(layer.height, Math.ceil(layer.offsetY + dpr * (box.y + box.height - layer.scrollY + 8)));
20774
+ return right > left && bottom > top ? {
20775
+ x: left,
20776
+ y: top,
20777
+ width: right - left,
20778
+ height: bottom - top
20779
+ } : null;
20780
+ }
20577
20781
  /** Stops building layers for a container until its subtree is next dirtied. */
20578
20782
  refuse(node) {
20579
20783
  this.drop(node);
@@ -20596,14 +20800,63 @@ var ScrollLayerCache = class {
20596
20800
  this.refused.delete(node);
20597
20801
  }
20598
20802
  };
20599
- /** Whether an ancestor was dirtied by more than its own scroll: inherited paint reaches the subtree without saying so. */
20803
+ /**
20804
+ * Whether an ancestor's properties or environment changed. An inherited
20805
+ * value set on an ancestor marks every descendant that reads it as well
20806
+ * (the graph re-provides it), so what it changes inside the container
20807
+ * is damage like any other; this keeps the old caution for the rest.
20808
+ * An ancestor marked for paint alone is the runtime asking for a frame
20809
+ * (a scrollbar fading, an inspector outline), which nothing inside the
20810
+ * container hears; one laid out again either leaves the container where
20811
+ * it was or moves it, which \`fits\` hears about; one whose children
20812
+ * changed has changed nothing inside this one. The last two happen on
20813
+ * every frame a lazy list mounts a row, since a layout need is marked
20814
+ * all the way up.
20815
+ */
20600
20816
  function dirtyAbove(frame, node) {
20601
- for (let ancestor = node.parent; ancestor !== null; ancestor = ancestor.parent) {
20602
- const flags = frame.dirtyFlagsFor(ancestor);
20603
- if (flags !== 0 && flags !== 32) return true;
20604
- }
20817
+ for (let ancestor = node.parent; ancestor !== null; ancestor = ancestor.parent) if ((frame.dirtyFlagsFor(ancestor) & INHERITED) !== 0) return true;
20818
+ return false;
20819
+ }
20820
+ /** What an ancestor's dirt can say about how its descendants look. */
20821
+ const INHERITED = 192;
20822
+ /** Whether \`ancestor\` is above \`node\` in the tree. */
20823
+ function contains(ancestor, node) {
20824
+ for (let current = node.parent; current !== null; current = current.parent) if (current === ancestor) return true;
20605
20825
  return false;
20606
20826
  }
20827
+ /** A container's items: its children, and the children of any fragment among them, in order. */
20828
+ function itemsOf(container) {
20829
+ const items = [];
20830
+ const visit = (parent) => {
20831
+ for (let child = parent.firstChild; child !== null; child = child.nextSibling) if (child.type === "fragment") visit(child);
20832
+ else items.push(child);
20833
+ };
20834
+ visit(container);
20835
+ return items;
20836
+ }
20837
+ /** The item of a container that holds a node, the node itself if it is one, or null outside the container or above its items. */
20838
+ function itemHolding(container, node) {
20839
+ let item = null;
20840
+ for (let current = node; current !== null; current = current.parent) {
20841
+ if (current === container) return item;
20842
+ if (current.type !== "fragment") item = current;
20843
+ }
20844
+ return null;
20845
+ }
20846
+ function sameBox(a, b) {
20847
+ return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
20848
+ }
20849
+ function boxOf(layout, node) {
20850
+ const rec = layout.recordFor(node);
20851
+ return rec === void 0 ? void 0 : {
20852
+ x: rec.x,
20853
+ y: rec.y,
20854
+ width: rec.width,
20855
+ height: rec.height
20856
+ };
20857
+ }
20858
+ /** Damage over this share of a layer repaints all of it in one walk. */
20859
+ const MOSTLY = .5;
20607
20860
  /**
20608
20861
  * Where a scroll container's viewport lands in device pixels, or null
20609
20862
  * when a layer could not reproduce the direct draw exactly: no
@@ -20649,6 +20902,273 @@ function clearLayer(ctx, width, height) {
20649
20902
  ctx.clearRect(0, 0, width, height);
20650
20903
  }
20651
20904
  //#endregion
20905
+ //#region src/rendering/canvas2d/TextRasterCache.ts
20906
+ /**
20907
+ * Text kept as pixels, so a frame that only moved it copies it rather
20908
+ * than drawing its glyphs again.
20909
+ *
20910
+ * **Why.** Drawing glyphs is most of what a frame of text costs:
20911
+ * profiled while flicking through a file in Easel, `fillText` was 1,064
20912
+ * of the renderer's 1,985 ms. A scroll moves every line on screen and
20913
+ * changes none of them, so every one of those glyphs was drawn again
20914
+ * only to land a few pixels from where it was. In WKWebView a frame of
20915
+ * that cost 4 to 7 ms, which is more than a 165 Hz refresh holds.
20916
+ *
20917
+ * **What is kept.** What `drawText` draws for one node, drawn once into
20918
+ * a canvas of its own at the device scale and fraction of a pixel it
20919
+ * lands on, and copied onto the frame from then on. The copy lands on
20920
+ * whole device pixels, so the pixels are the ones a direct draw at that
20921
+ * fraction would have made, and a scroll, which moves by whole device
20922
+ * pixels, keeps the fraction.
20923
+ *
20924
+ * **When it is drawn again.** When anything `drawText` reads changes:
20925
+ * the text, its runs, its font, its colour, its box, the scale, or the
20926
+ * fraction of a pixel it sits at. Each is compared every frame, which is
20927
+ * a few comparisons; a run list or a colour that has not changed is the
20928
+ * same object it was.
20929
+ *
20930
+ * **What it is not for.** Text that changes on most frames (a clock, a
20931
+ * counter, an animated colour) would be drawn twice a frame, into the
20932
+ * cache and out of it. A node redrawn on most of its recent frames is
20933
+ * drawn directly instead, until it has been still for a while.
20934
+ */
20935
+ var TextRasterCache = class {
20936
+ createCanvas;
20937
+ entries = /* @__PURE__ */ new Map();
20938
+ frame = 0;
20939
+ pixels = 0;
20940
+ /** Draws served from the cache and drawn into it, since the last reset. For budget specs. */
20941
+ stats = {
20942
+ copied: 0,
20943
+ drawn: 0,
20944
+ direct: 0
20945
+ };
20946
+ constructor(createCanvas) {
20947
+ this.createCanvas = createCanvas;
20948
+ }
20949
+ beginFrame() {
20950
+ this.frame++;
20951
+ }
20952
+ /** Lets go of text not drawn lately: removed, scrolled far away, or hidden. */
20953
+ endFrame() {
20954
+ for (const [node, entry] of this.entries) if (this.frame - entry.used > IDLE_FRAMES) this.drop(node, entry);
20955
+ }
20956
+ dropAll() {
20957
+ for (const [node, entry] of this.entries) this.drop(node, entry);
20958
+ }
20959
+ resetStats() {
20960
+ this.stats.copied = 0;
20961
+ this.stats.drawn = 0;
20962
+ this.stats.direct = 0;
20963
+ }
20964
+ /**
20965
+ * Draws a node's text from the cache, drawing it into the cache first
20966
+ * when what it holds is out of date. False when the text should be
20967
+ * drawn directly: a context that cannot say where it draws, one that
20968
+ * rotates or skews, text too large to keep, or text that changes too
20969
+ * often to be worth keeping.
20970
+ */
20971
+ draw(ctx, node, box, paint, measurer) {
20972
+ const m = ctx.getTransform?.();
20973
+ if (m === void 0 || m.b !== 0 || m.c !== 0 || !(m.a > 0) || !(m.d > 0)) return false;
20974
+ let entry = this.entries.get(node);
20975
+ if (entry !== void 0 && entry.directUntil > this.frame) {
20976
+ entry.used = this.frame;
20977
+ this.stats.direct++;
20978
+ return false;
20979
+ }
20980
+ let extent = entry !== void 0 && entry.drawnFrom(paint, box, m.a, m.d) ? entry.extent : null;
20981
+ if (extent === null) {
20982
+ extent = inkExtent(box, paint, measurer);
20983
+ if (extent === null) return false;
20984
+ }
20985
+ const left = m.a * (box.x + extent.left) + m.e;
20986
+ const top = m.d * (box.y + extent.top) + m.f;
20987
+ const x = Math.floor(left);
20988
+ const y = Math.floor(top);
20989
+ const width = Math.ceil(m.a * (extent.right - extent.left) + (left - x)) + 1;
20990
+ const height = Math.ceil(m.d * (extent.bottom - extent.top) + (top - y)) + 1;
20991
+ if (width * height > MAX_ENTRY_PIXELS) return false;
20992
+ const offsetX = m.e - x;
20993
+ const offsetY = m.f - y;
20994
+ let raster = entry ?? null;
20995
+ if (raster === null || !raster.holds(paint, box, m.a, m.d, offsetX, offsetY, width, height)) {
20996
+ raster = this.redraw(node, entry, paint, box, measurer, m.a, m.d, offsetX, offsetY, width, height, extent);
20997
+ if (raster === null) return false;
20998
+ }
20999
+ raster.used = this.frame;
21000
+ ctx.save();
21001
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
21002
+ ctx.drawImage(raster.canvas, x, y, raster.canvasWidth, raster.canvasHeight);
21003
+ ctx.restore();
21004
+ this.stats.copied++;
21005
+ return true;
21006
+ }
21007
+ redraw(node, previous, paint, box, measurer, scaleX, scaleY, offsetX, offsetY, width, height, extent) {
21008
+ let entry = previous;
21009
+ if (entry !== void 0) {
21010
+ entry.churn = this.frame - entry.drawnAt <= CHURN_FRAMES ? entry.churn + 1 : 0;
21011
+ if (entry.churn >= CHURN_LIMIT) {
21012
+ entry.churn = 0;
21013
+ entry.directUntil = this.frame + DIRECT_FRAMES;
21014
+ entry.used = this.frame;
21015
+ this.stats.direct++;
21016
+ return null;
21017
+ }
21018
+ }
21019
+ if (entry === void 0 || entry.canvasWidth < width || entry.canvasHeight < height) {
21020
+ const canvasWidth = Math.max(width, entry?.canvasWidth ?? 0);
21021
+ const canvasHeight = Math.max(height, entry?.canvasHeight ?? 0);
21022
+ const canvas = this.createCanvas(canvasWidth, canvasHeight);
21023
+ const context = canvas?.getContext("2d");
21024
+ if (canvas === null || !context) return null;
21025
+ this.make(canvasWidth * canvasHeight);
21026
+ if (entry !== void 0) {
21027
+ this.pixels -= entry.canvasWidth * entry.canvasHeight;
21028
+ entry.canvas = canvas;
21029
+ entry.context = context;
21030
+ entry.canvasWidth = canvasWidth;
21031
+ entry.canvasHeight = canvasHeight;
21032
+ } else {
21033
+ entry = new TextRaster(canvas, context, canvasWidth, canvasHeight);
21034
+ this.entries.set(node, entry);
21035
+ }
21036
+ this.pixels += canvasWidth * canvasHeight;
21037
+ }
21038
+ const context = entry.context;
21039
+ context.setTransform(1, 0, 0, 1, 0, 0);
21040
+ context.clearRect(0, 0, entry.canvasWidth, entry.canvasHeight);
21041
+ context.setTransform(scaleX, 0, 0, scaleY, offsetX, offsetY);
21042
+ context.globalAlpha = 1;
21043
+ drawText(context, box, paint, measurer);
21044
+ entry.remember(paint, box, scaleX, scaleY, offsetX, offsetY, width, height);
21045
+ entry.extent = extent;
21046
+ entry.drawnAt = this.frame;
21047
+ this.stats.drawn++;
21048
+ return entry;
21049
+ }
21050
+ /** Makes room for `needed` more pixels, letting go of what was drawn least recently. */
21051
+ make(needed) {
21052
+ if (this.pixels + needed <= MAX_PIXELS) return;
21053
+ const byAge = [...this.entries].sort((a, b) => a[1].used - b[1].used);
21054
+ for (const [node, entry] of byAge) {
21055
+ if (this.pixels + needed <= MAX_PIXELS || entry.used === this.frame) break;
21056
+ this.drop(node, entry);
21057
+ }
21058
+ }
21059
+ drop(node, entry) {
21060
+ this.entries.delete(node);
21061
+ this.pixels -= entry.canvasWidth * entry.canvasHeight;
21062
+ entry.canvas.width = 0;
21063
+ entry.canvas.height = 0;
21064
+ }
21065
+ };
21066
+ /** One node's text as pixels, and what it was drawn from. */
21067
+ var TextRaster = class {
21068
+ canvas;
21069
+ context;
21070
+ canvasWidth;
21071
+ canvasHeight;
21072
+ used = 0;
21073
+ drawnAt = 0;
21074
+ churn = 0;
21075
+ directUntil = 0;
21076
+ /** Where the ink reaches, from the box's corner, in layout pixels. */
21077
+ extent = null;
21078
+ text;
21079
+ spans;
21080
+ font = "";
21081
+ color = "";
21082
+ style = "";
21083
+ linkHover = 0;
21084
+ boxWidth = 0;
21085
+ boxHeight = 0;
21086
+ scaleX = 0;
21087
+ scaleY = 0;
21088
+ offsetX = 0;
21089
+ offsetY = 0;
21090
+ width = 0;
21091
+ height = 0;
21092
+ constructor(canvas, context, canvasWidth, canvasHeight) {
21093
+ this.canvas = canvas;
21094
+ this.context = context;
21095
+ this.canvasWidth = canvasWidth;
21096
+ this.canvasHeight = canvasHeight;
21097
+ }
21098
+ holds(paint, box, scaleX, scaleY, offsetX, offsetY, width, height) {
21099
+ return this.drawnFrom(paint, box, scaleX, scaleY) && Math.abs(this.offsetX - offsetX) < SAME_PLACE && Math.abs(this.offsetY - offsetY) < SAME_PLACE && this.width === width && this.height === height;
21100
+ }
21101
+ /** Whether the pixels are of this text, in this style, at this size and scale, wherever they are. */
21102
+ drawnFrom(paint, box, scaleX, scaleY) {
21103
+ return this.text === paint.text && this.spans === paint.spans && this.linkHover === paint.linkHover && this.boxWidth === box.width && this.boxHeight === box.height && this.scaleX === scaleX && this.scaleY === scaleY && this.font === fontOf(paint) && this.color === colorToCss(paint.textColor) && this.style === styleOf(paint);
21104
+ }
21105
+ remember(paint, box, scaleX, scaleY, offsetX, offsetY, width, height) {
21106
+ this.text = paint.text;
21107
+ this.spans = paint.spans;
21108
+ this.linkHover = paint.linkHover;
21109
+ this.boxWidth = box.width;
21110
+ this.boxHeight = box.height;
21111
+ this.scaleX = scaleX;
21112
+ this.scaleY = scaleY;
21113
+ this.offsetX = offsetX;
21114
+ this.offsetY = offsetY;
21115
+ this.width = width;
21116
+ this.height = height;
21117
+ this.font = fontOf(paint);
21118
+ this.color = colorToCss(paint.textColor);
21119
+ this.style = styleOf(paint);
21120
+ }
21121
+ };
21122
+ /**
21123
+ * The lines' extent, and a margin for what glyphs reach past their line
21124
+ * box (an italic's lean, a descender below a tight line height, an
21125
+ * underline). Null for text that draws nothing.
21126
+ */
21127
+ function inkExtent(box, paint, measurer) {
21128
+ const placements = layoutTextLines(box, paint, measurer);
21129
+ if (placements.length === 0) return null;
21130
+ let left = Infinity;
21131
+ let top = Infinity;
21132
+ let right = -Infinity;
21133
+ let bottom = -Infinity;
21134
+ for (const line of placements) {
21135
+ left = Math.min(left, line.x);
21136
+ top = Math.min(top, line.y);
21137
+ right = Math.max(right, line.x + line.width);
21138
+ bottom = Math.max(bottom, line.y + line.height);
21139
+ }
21140
+ const margin = Math.ceil(paint.fontSize * INK_MARGIN) + 2;
21141
+ return {
21142
+ left: left - box.x - margin,
21143
+ top: top - box.y - margin,
21144
+ right: right - box.x + margin,
21145
+ bottom: bottom - box.y + margin
21146
+ };
21147
+ }
21148
+ function fontOf(paint) {
21149
+ return `${paint.fontStyle} ${paint.fontVariant} ${paint.fontWeight} ${paint.fontStretch} ${paint.fontSize}/${paint.lineHeight} ${paint.fontFamily}`;
21150
+ }
21151
+ /** Everything else `drawText` reads that places or decorates the glyphs. */
21152
+ function styleOf(paint) {
21153
+ return `${paint.letterSpacing}|${paint.fontKerning}|${paint.textDecoration}|${paint.textAlign}|${paint.verticalAlign}|${paint.textWrap}|${paint.maxLines}|${paint.textOverflow}|${paint.rtl}`;
21154
+ }
21155
+ /** How far past its line box, as a share of the font size, a glyph's ink may reach. */
21156
+ const INK_MARGIN = .5;
21157
+ /** Within this much of a device pixel, text is at the same place. */
21158
+ const SAME_PLACE = 1 / 256;
21159
+ /** The largest single piece of text kept: a few screens of a long paragraph. */
21160
+ const MAX_ENTRY_PIXELS = 4e6;
21161
+ /** Everything kept, at most: about 128 MB. */
21162
+ const MAX_PIXELS = 32e6;
21163
+ /** Frames text is kept for without being drawn: about two seconds at 60 Hz. */
21164
+ const IDLE_FRAMES = 120;
21165
+ /** Drawn again within this many frames of the last time counts toward churn. */
21166
+ const CHURN_FRAMES = 2;
21167
+ /** Times in a row text may churn before it is drawn directly. */
21168
+ const CHURN_LIMIT = 3;
21169
+ /** Frames churning text is drawn directly for before the cache tries it again. */
21170
+ const DIRECT_FRAMES = 60;
21171
+ //#endregion
20652
21172
  //#region src/rendering/canvas2d/Canvas2DRenderer.ts
20653
21173
  /**
20654
21174
  * Canvas2D rendering backend.
@@ -20760,6 +21280,13 @@ var Canvas2DRenderer = class {
20760
21280
  constructor(options) {
20761
21281
  this.options = options;
20762
21282
  this.layers = new ScrollLayerCache(options.createLayerCanvas ?? offscreenLayerCanvas, options.scrollLayerSettleFrames);
21283
+ this.textCache = options.textCache === false ? null : new TextRasterCache(options.createLayerCanvas ?? offscreenLayerCanvas);
21284
+ }
21285
+ /** Text kept as pixels; see `TextRasterCache.ts`. */
21286
+ textCache;
21287
+ /** What the text cache did since the last reset, or null with it off. For budget specs. */
21288
+ get textCacheStats() {
21289
+ return this.textCache?.stats ?? null;
20763
21290
  }
20764
21291
  /** What the scroll layers did since the last reset. For budget specs. */
20765
21292
  get scrollLayerStats() {
@@ -20785,11 +21312,13 @@ var Canvas2DRenderer = class {
20785
21312
  /** A restored scroll layer reads as healthy and holds nothing. */
20786
21313
  surfaceRestored() {
20787
21314
  this.layers.dropAll();
21315
+ this.textCache?.dropAll();
20788
21316
  }
20789
21317
  dispose() {
20790
21318
  this.disposed = true;
20791
21319
  this.scaledImages.dispose();
20792
21320
  this.layers.dropAll();
21321
+ this.textCache?.dropAll();
20793
21322
  }
20794
21323
  render(root, context) {
20795
21324
  const raw = this.surface.getContext2D();
@@ -20803,11 +21332,13 @@ var Canvas2DRenderer = class {
20803
21332
  this.liftedPass.length = 0;
20804
21333
  this.paintOwner = null;
20805
21334
  this.layerDepth = 0;
21335
+ this.textCache?.beginFrame();
20806
21336
  this.layers.beginFrame(this.options.scrollLayers === false ? void 0 : context.changes, this.surface.dpr, this.surface.physicalWidth, this.surface.physicalHeight);
20807
21337
  this.renderNode(root, context, ctx, true, false);
20808
21338
  this.renderLifted(context, ctx);
20809
21339
  if (context.overlay !== void 0 && context.overlay.length > 0) drawOverlayShapes(ctx, context.overlay);
20810
21340
  this.layers.endFrame();
21341
+ this.textCache?.endFrame();
20811
21342
  }
20812
21343
  /**
20813
21344
  * Folds a node's style into the shared scratch and records that it
@@ -20872,7 +21403,7 @@ var Canvas2DRenderer = class {
20872
21403
  if (rec.clips) this.popCull();
20873
21404
  if (hasText) {
20874
21405
  if (this.paintOwner !== node) this.resolvePaint(node);
20875
- this.paintContent(ctx, rec, this.paint, context);
21406
+ this.paintContent(ctx, node, rec, this.paint, context);
20876
21407
  }
20877
21408
  if (rec.liftBoundary && this.liftedPass.length > liftedBefore) this.renderLifted(context, ctx, liftedBefore, node);
20878
21409
  if (rec.clips) ctx.restore();
@@ -21146,7 +21677,7 @@ var Canvas2DRenderer = class {
21146
21677
  } else ctx.strokeRect(x, y, width, height);
21147
21678
  }
21148
21679
  }
21149
- paintContent(ctx, rec, paint, context) {
21680
+ paintContent(ctx, node, rec, paint, context) {
21150
21681
  if (paint.text !== void 0) {
21151
21682
  this.contentBox.x = rec.x + rec.paddingLeft - rec.scrollX;
21152
21683
  this.contentBox.y = rec.y + rec.paddingTop - rec.scrollY;
@@ -21160,6 +21691,7 @@ var Canvas2DRenderer = class {
21160
21691
  this.paintHighlightedText(ctx, paint, context);
21161
21692
  return;
21162
21693
  }
21694
+ if (this.textCache?.draw(ctx, node, this.contentBox, paint, context.text) === true) return;
21163
21695
  drawText(ctx, this.contentBox, paint, context.text);
21164
21696
  }
21165
21697
  }
@@ -21261,8 +21793,21 @@ var Canvas2DRenderer = class {
21261
21793
  layers.drop(node);
21262
21794
  return false;
21263
21795
  }
21264
- for (const strip of strips) this.paintLayer(layer, strip, node, rec, context);
21796
+ const damage = layers.damage(layer, node, context.layout);
21797
+ const wrong = damage === "all" ? [{
21798
+ x: 0,
21799
+ y: 0,
21800
+ width: layer.width,
21801
+ height: layer.height
21802
+ }] : damage;
21803
+ if (wrong.length > 0) {
21804
+ const front = layer.frontContext;
21805
+ front.setTransform(1, 0, 0, 1, 0, 0);
21806
+ for (const rect of wrong) front.clearRect(rect.x, rect.y, rect.width, rect.height);
21807
+ }
21808
+ if (strips.length > 0 || wrong.length > 0) this.paintLayer(layer, [...strips, ...wrong], node, rec, context);
21265
21809
  }
21810
+ layers.remember(layer, node, context.layout);
21266
21811
  layer.used = true;
21267
21812
  layers.stats.composited++;
21268
21813
  ctx.save();
@@ -21284,13 +21829,13 @@ var Canvas2DRenderer = class {
21284
21829
  * strip saves is raster, which is the cost it exists for; the walk
21285
21830
  * costs what a direct draw's walk would.
21286
21831
  */
21287
- paintLayer(layer, strip, node, rec, context) {
21832
+ paintLayer(layer, strips, node, rec, context) {
21288
21833
  const lctx = new PreciseContext(layer.frontContext);
21289
21834
  lctx.save();
21290
- if (strip !== null) {
21835
+ if (strips !== null) {
21291
21836
  lctx.setTransform(1, 0, 0, 1, 0, 0);
21292
21837
  lctx.beginPath();
21293
- lctx.rect(strip.x, strip.y, strip.width, strip.height);
21838
+ for (const strip of strips) lctx.rect(strip.x, strip.y, strip.width, strip.height);
21294
21839
  lctx.clip();
21295
21840
  }
21296
21841
  const dpr = layer.dpr;
@@ -21304,6 +21849,26 @@ var Canvas2DRenderer = class {
21304
21849
  this.cullY = rec.y + layer.scrollY;
21305
21850
  this.cullWidth = rec.width;
21306
21851
  this.cullHeight = rec.height;
21852
+ if (strips !== null && strips.length > 0) {
21853
+ let left = Infinity;
21854
+ let top = Infinity;
21855
+ let right = -Infinity;
21856
+ let bottom = -Infinity;
21857
+ for (const strip of strips) {
21858
+ left = Math.min(left, strip.x);
21859
+ top = Math.min(top, strip.y);
21860
+ right = Math.max(right, strip.x + strip.width);
21861
+ bottom = Math.max(bottom, strip.y + strip.height);
21862
+ }
21863
+ const x0 = Math.max(this.cullX, (left - layer.offsetX) / dpr + layer.scrollX - 8);
21864
+ const y0 = Math.max(this.cullY, (top - layer.offsetY) / dpr + layer.scrollY - 8);
21865
+ const x1 = Math.min(this.cullX + this.cullWidth, (right - layer.offsetX) / dpr + layer.scrollX + 8);
21866
+ const y1 = Math.min(this.cullY + this.cullHeight, (bottom - layer.offsetY) / dpr + layer.scrollY + 8);
21867
+ this.cullX = x0;
21868
+ this.cullY = y0;
21869
+ this.cullWidth = Math.max(0, x1 - x0);
21870
+ this.cullHeight = Math.max(0, y1 - y0);
21871
+ }
21307
21872
  this.layerDepth++;
21308
21873
  try {
21309
21874
  this.renderChildren(node, context, lctx, true);
@@ -21512,6 +22077,6 @@ function createCanvasSurface(host) {
21512
22077
  return new CanvasSurface(host);
21513
22078
  }
21514
22079
  //#endregion
21515
- export { isImeKey as $, UiCompositionEvent as $a, defaultShadows as $i, proportionalFontMetrics as $n, gradientPaint as $r, registerFontStack as $t, withOverrideSource as A, colorValuesEqual as Aa, bandOf as Ai, linkHoverOf as An, propertyIsInherited as Ar, adjacentField as At, FlexDirection as B, DirtyNodeSet as Ba, noThemeExtensions as Bi, isEditableNode as Bn, validateRole as Br, createCanvasTextStyle as Bt, SCROLLBAR_LINGER_MS as C, textStylesEqual as Ca, propertyValuesEqual as Ci, isNodeInert as Cn, resolveString as Cr, hasDrawnText as Ct, formatExplanation as D, UiBasicColors as Da, noInsets as Di, resolveCursor as Dn, inheritedPropertyFlags as Dr, selectionRectsIn as Dt, formatConstraints as E, lightColors as Ea, insetsEqual as Ei, isPressable as En, getPropertyNames as Er, paragraphGeometryFrom as Et, clampSize as F, rgba as Fa, themesEqual as Fi, CARET_WIDTH as Fn, UI_SEMANTIC_STATES as Fr, detectEditingPlatform as Ft, CrossAxisAlignment as G, writeOverrideProperty as Ga, highContrastColors as Gi, resolveColorValue as Gn, paintValuesEqual as Gr, runFontString as Gt, placeGridItems as H, overrideSources as Ha, themeExtensionsEqual as Hi, isReadOnly as Hn, UI_SUBGRID_AXES as Hr, fillTextRects as Ht, constraintsEqual as I, createEnvironmentKey as Ia, withContrast as Ii, EDITOR_PROP as In, isUiRole as Ir, isPrintable as It, parseCrossAxisAlignment as J, hasVisualState as Ja, densityFactors as Ji, CharacterCountTextMeasurer as Jn, parseTransform as Jr, textRunBackgrounds as Jt, MainAxisAlignment as K, UiVisualState as Ka, raiseContrast as Ki, isVideoSurface as Kn, pathValuesEqual as Kr, spanOf as Kt, tightenConstraints as L, findEnvironmentKey as La, withDensity as Li, caretVisibleAt as Ln, isUiSemanticState as Lr, EditableLayout as Lt, contentOffset as M, normalizeColor as Ma, unknownContainerSize as Mi, linkRunAtPointIn as Mn, propertyValuesEqualByName as Mr, edgeField as Mt, LayoutRecord as N, parseColor as Na, darkTheme as Ni, setLinkHover as Nn, UiProperties as Nr, editingGroupOf as Nt, formatNumber as O, colorToHex as Oa, observeViewportInsets as Oi, clearLinkHover as On, inheritedPropertyNames as Or, wordRangeIn as Ot, Constraints as P, rgb8 as Pa, lightTheme as Pi, CARET_BLINK_MS as Pn, UI_ROLES as Pr, commandForKey as Pt, capturePointer as Q, UiBeforeInputEvent as Qa, spacingSteps as Qi, layoutParagraph as Qn, MAX_GRADIENT_STOPS as Qr, fontStackFor as Qt, MAX_MEASURES_PER_CHILD as R, UiEnvironment as Ra, defineThemeExtension as Ri, editorFor as Rn, normalizeStates as Rr, applyCanvasTextStyle as Rt, SCROLLBAR_FADE_MS as S, textSpansEqual as Sa, defineProperty as Si, isNodeHitTestable as Sn, resolvePropertyByName as Sr, UiInputDispatcher as St, describeOverrides as T, darkColors as Ta, UiInsetRegistry as Ti, isNodeTabStop as Tn, findPropertyDefinition as Tr, paragraphGeometry as Tt, sizeGridTracks as U, resetOverrideWarnings as Ua, withThemeExtension as Ui, nextCaretToggle as Un, validateSubgrid as Ur, layoutTextLines as Ut, parseFlexDirection as V, clearOverrideProperty as Va, themeExtension as Vi, isMultiline as Vn, validateStates as Vr, drawText as Vt, AlignContent as W, writeDeclaredProperty as Wa, contrastRatio as Wi, syncEditorValue as Wn, inputsEqual as Wr, runCanvasStyleInto as Wt, CanvasPlatformSurface as X, visualStatesEqual as Xa, scaleSpacing as Xi, spannedRunsFor as Xn, transformIsIdentity as Xr, bumpFontStack as Xt, parseMainAxisAlignment as Y, visualState as Ya, defaultSpacing as Yi, ParagraphTextMeasurer as Yn, transform as Yr, textRunDecorations as Yt, UiPlatformAdapter as Z, MOUSE_POINTER as Za, spacingEqual as Zi, ELLIPSIS as Zn, transformsEqual as Zr, clearFontStacks as Zt, LABEL_PADDING_X as _, resolvedSpansOf as _a, borderRadiusEqual as _i, selectableTextNodes as _n, UiGraph as _r, scrollLeaderOf as _t, PaintPictureCache as a, boxShadowReach as aa, fr as ai, computeObjectFitRect as an, UiPasteEvent as ao, offsetAtX as ar, UiGestureRecognizer as at, snapScroll as b, spansDifferOnlyInPaint as ba, normalizeBorderRadius as bi, setSelectionRange as bn, resolveNumber as br, scrollbarThumbs as bt, EMPTY_RECORDING as c, isShapeName as ca, isMinMaxTrack as ci, normalizeTextAlign as cn, UiSelectionChangeEvent as co, EditableTextModel as cr, isScrollContainer as ct, tracePathData as d, isTypographyRole as da, percent as di, TEXT_MATCHES_PROP as dn, UiWheelEvent as do, lineStartAt as dr, UiFocusManager as dt, shadowsEqual as ea, gradientsEqual as ei, DEFAULT_FONT_FAMILY as en, UiEventType as eo, caretRectFor as er, pointerDeviceOf as et, ScaledImageCache as f, typographyEqual as fa, repeat as fi, clearMatchRanges as fn, noKeyModifiers as fo, nextGraphemeEnd as fr, UiHitTester as ft, paintsAfterChildren as g, hiddenRangesOf as ga, borderRadiusCorners as gi, clearSelectionRange as gn, UiBinding as go, wordRangeAt as gr, SCROLLBAR_THICKNESS as gt, hasDecorationPhase as h, flattenTextSpans as ha, borderRadius as hi, TEXT_SELECTION_PROP as hn, DirtyFlags as ho, previousWordStart as hr, SCROLLBAR_MIN_THUMB as ht, traceRoundedRect as i, boxShadowRadius as ia, auto as ii, colorToCss as in, UiKeyboardEvent as io, offsetAtPoint as ir, UiTouchScroller as it, accumulatedOffsetTo as j, colorsEqual as ja, containerBands as ji, linkOf as jn, propertyNarrowers as jr, comparePositions as jt, labelNode as k, colorToRgba as ka, UiContainerSizeSource as ki, hasTextLinks as kn, propertyEffects as kr, textInputOf as kt, PaintRecorder as l, shapesEqual as la, isPercentLength as li, normalizeVerticalAlign as ln, UiTextChangeEvent as lo, graphemeBoundaries as lr, UiKeyboardController as lt, decorationRect as m, editableSpansOf as ma, UiBorderRadiuses as mi, setMatchRanges as mn, UiNodeType as mo, previousGraphemeStart as mr, SCROLLBAR_INSET as mt, createCanvasSurface as n, boxShadow as na, radialGradient as ni, DEFAULT_FONT_WEIGHT as nn, UiGestureEvent as no, lineIndexForOffset as nr, touchActionFor as nt, paintPictures as o, boxShadowsEqual as oa, isAutoLength as oi, createPaintState as on, UiPinchEvent as oo, offsetForVerticalMove as or, UiPinchRecognizer as ot, decorationColor as p, defaultTextStyle as pa, resolveLength as pi, matchRangesOf as pn, wheelDeltaYOf as po, nextWordEnd as pr, SCROLLBAR_HOVER_ZONE as pt, parseAlignContent as q, defaultVisualState as qa, relativeLuminance as qi, videoFrameSize as qn, UiTransforms as qr, textMeasureRequest as qt, Canvas2DRenderer as r, boxShadowArraysEqual as ra, validateGradient as ri, DEFAULT_TEXT_COLOR as rn, UiInputEvent as ro, lineLimit as rr, UiEditingController as rt, PaintTarget as s, defaultShapes as sa, isFrLength as si, isPaintVisible as sn, UiPointerEvent as so, selectionRects as sr, UiWheelController as st, CanvasSurface as t, BOX_SHADOW_BLUR_REACH as ta, linearGradient as ti, DEFAULT_FONT_SIZE as tn, UiFocusEvent as to, lineIndexAtY as tr, prepareInputSurface as tt, replayPaint as u, defaultTypography as ua, minmax as ui, resolvePaintState as un, UiWheelDeltaMode as uo, lineEndAt as ur, UiPointerController as ut, drawOverlayShapes as v, spanAtOffset as va, borderRadiusIsZero as vi, selectableTextOf as vn, DEFAULT_LINE_HEIGHT_FACTOR as vr, scrollRange as vt, describeLength as w, colorsEqualPalette as wa, UiEnvironmentKeys as wi, isNodeSelectable as wn, closestPropertyName as wr, offsetAtPointIn as wt, LayoutEngine as x, textContentOf as xa, uniformBorderRadius as xi, isNodeFocusable as xn, resolveProperty as xr, scrollbarZoneAt as xt, labelOrigin as y, spannedTextOf as ya, borderRadiusValuesEqual as yi, selectionRangeOf as yn, resolveBoolean as yr, scrollbarThumb as yt, isLayoutProtocol as z, UiNode as za, hasThemeExtension as zi, editorOf as zn, statesEqual as zr, buildFontString as zt };
22080
+ export { isImeKey as $, MOUSE_POINTER as $a, spacingEqual as $i, ELLIPSIS as $n, transformsEqual as $r, registerFontStack as $t, withOverrideSource as A, colorToHex as Aa, observeViewportInsets as Ai, clearLinkHover as An, inheritedPropertyNames as Ar, adjacentField as At, FlexDirection as B, UiEnvironment as Ba, defineThemeExtension as Bi, editorFor as Bn, normalizeStates as Br, createCanvasTextStyle as Bt, SCROLLBAR_LINGER_MS as C, textContentOf as Ca, uniformBorderRadius as Ci, isNodeInert as Cn, resolveProperty as Cr, hasDrawnText as Ct, formatExplanation as D, darkColors as Da, UiInsetRegistry as Di, isSubtreeInert as Dn, findPropertyDefinition as Dr, selectionRectsIn as Dt, formatConstraints as E, colorsEqualPalette as Ea, UiEnvironmentKeys as Ei, isPressable as En, closestPropertyName as Er, paragraphGeometryFrom as Et, clampSize as F, parseColor as Fa, darkTheme as Fi, setLinkHover as Fn, UiProperties as Fr, detectEditingPlatform as Ft, CrossAxisAlignment as G, resetOverrideWarnings as Ga, withThemeExtension as Gi, nextCaretToggle as Gn, validateSubgrid as Gr, runFontString as Gt, placeGridItems as H, DirtyNodeSet as Ha, noThemeExtensions as Hi, isEditableNode as Hn, validateRole as Hr, fillTextRects as Ht, constraintsEqual as I, rgb8 as Ia, lightTheme as Ii, CARET_BLINK_MS as In, UI_ROLES as Ir, isPrintable as It, parseCrossAxisAlignment as J, UiVisualState as Ja, raiseContrast as Ji, isVideoSurface as Jn, pathValuesEqual as Jr, textRunBackgrounds as Jt, MainAxisAlignment as K, writeDeclaredProperty as Ka, contrastRatio as Ki, syncEditorValue as Kn, inputsEqual as Kr, spanOf as Kt, tightenConstraints as L, rgba as La, themesEqual as Li, CARET_WIDTH as Ln, UI_SEMANTIC_STATES as Lr, EditableLayout as Lt, contentOffset as M, colorValuesEqual as Ma, bandOf as Mi, linkHoverOf as Mn, propertyIsInherited as Mr, edgeField as Mt, LayoutRecord as N, colorsEqual as Na, containerBands as Ni, linkOf as Nn, propertyNarrowers as Nr, editingGroupOf as Nt, formatNumber as O, lightColors as Oa, insetsEqual as Oi, pointerEventsOf as On, getPropertyNames as Or, wordRangeIn as Ot, Constraints as P, normalizeColor as Pa, unknownContainerSize as Pi, linkRunAtPointIn as Pn, propertyValuesEqualByName as Pr, commandForKey as Pt, capturePointer as Q, visualStatesEqual as Qa, scaleSpacing as Qi, spannedRunsFor as Qn, transformIsIdentity as Qr, fontStackFor as Qt, MAX_MEASURES_PER_CHILD as R, createEnvironmentKey as Ra, withContrast as Ri, EDITOR_PROP as Rn, isUiRole as Rr, applyCanvasTextStyle as Rt, SCROLLBAR_FADE_MS as S, spansDifferOnlyInPaint as Sa, normalizeBorderRadius as Si, isNodeHitTestable as Sn, resolveNumber as Sr, UiInputDispatcher as St, describeOverrides as T, textStylesEqual as Ta, propertyValuesEqual as Ti, isNodeTabStop as Tn, resolveString as Tr, paragraphGeometry as Tt, sizeGridTracks as U, clearOverrideProperty as Ua, themeExtension as Ui, isMultiline as Un, validateStates as Ur, layoutTextLines as Ut, parseFlexDirection as V, UiNode as Va, hasThemeExtension as Vi, editorOf as Vn, statesEqual as Vr, drawText as Vt, AlignContent as W, overrideSources as Wa, themeExtensionsEqual as Wi, isReadOnly as Wn, UI_SUBGRID_AXES as Wr, runCanvasStyleInto as Wt, CanvasPlatformSurface as X, hasVisualState as Xa, densityFactors as Xi, CharacterCountTextMeasurer as Xn, parseTransform as Xr, bumpFontStack as Xt, parseMainAxisAlignment as Y, defaultVisualState as Ya, relativeLuminance as Yi, videoFrameSize as Yn, UiTransforms as Yr, textRunDecorations as Yt, UiPlatformAdapter as Z, visualState as Za, defaultSpacing as Zi, ParagraphTextMeasurer as Zn, transform as Zr, clearFontStacks as Zt, LABEL_PADDING_X as _, flattenTextSpans as _a, borderRadius as _i, selectableTextNodes as _n, DirtyFlags as _o, previousWordStart as _r, scrollLeaderOf as _t, PaintPictureCache as a, boxShadowArraysEqual as aa, validateGradient as ai, computeObjectFitRect as an, UiInputEvent as ao, lineLimit as ar, UiGestureRecognizer as at, snapScroll as b, spanAtOffset as ba, borderRadiusIsZero as bi, setSelectionRange as bn, DEFAULT_LINE_HEIGHT_FACTOR as br, scrollbarThumbs as bt, EMPTY_RECORDING as c, boxShadowsEqual as ca, isAutoLength as ci, normalizeTextAlign as cn, UiPinchEvent as co, offsetForVerticalMove as cr, isScrollContainer as ct, tracePathData as d, shapesEqual as da, isPercentLength as di, TEXT_MATCHES_PROP as dn, UiTextChangeEvent as do, graphemeBoundaries as dr, UiFocusManager as dt, spacingSteps as ea, MAX_GRADIENT_STOPS as ei, DEFAULT_FONT_FAMILY as en, UiBeforeInputEvent as eo, layoutParagraph as er, pointerDeviceOf as et, ScaledImageCache as f, defaultTypography as fa, minmax as fi, clearMatchRanges as fn, UiWheelDeltaMode as fo, lineEndAt as fr, UiHitTester as ft, paintsAfterChildren as g, editableSpansOf as ga, UiBorderRadiuses as gi, clearSelectionRange as gn, UiNodeType as go, previousGraphemeStart as gr, SCROLLBAR_THICKNESS as gt, hasDecorationPhase as h, defaultTextStyle as ha, resolveLength as hi, TEXT_SELECTION_PROP as hn, wheelDeltaYOf as ho, nextWordEnd as hr, SCROLLBAR_MIN_THUMB as ht, traceRoundedRect as i, boxShadow as ia, radialGradient as ii, colorToCss as in, UiGestureEvent as io, lineIndexForOffset as ir, UiTouchScroller as it, accumulatedOffsetTo as j, colorToRgba as ja, UiContainerSizeSource as ji, hasTextLinks as jn, propertyEffects as jr, comparePositions as jt, labelNode as k, UiBasicColors as ka, noInsets as ki, resolveCursor as kn, inheritedPropertyFlags as kr, textInputOf as kt, PaintRecorder as l, defaultShapes as la, isFrLength as li, normalizeVerticalAlign as ln, UiPointerEvent as lo, selectionRects as lr, UiKeyboardController as lt, decorationRect as m, typographyEqual as ma, repeat as mi, setMatchRanges as mn, noKeyModifiers as mo, nextGraphemeEnd as mr, SCROLLBAR_INSET as mt, createCanvasSurface as n, shadowsEqual as na, gradientsEqual as ni, DEFAULT_FONT_WEIGHT as nn, UiEventType as no, caretRectFor as nr, touchActionFor as nt, paintPictures as o, boxShadowRadius as oa, auto as oi, createPaintState as on, UiKeyboardEvent as oo, offsetAtPoint as or, UiPinchRecognizer as ot, decorationColor as p, isTypographyRole as pa, percent as pi, matchRangesOf as pn, UiWheelEvent as po, lineStartAt as pr, SCROLLBAR_HOVER_ZONE as pt, parseAlignContent as q, writeOverrideProperty as qa, highContrastColors as qi, resolveColorValue as qn, paintValuesEqual as qr, textMeasureRequest as qt, Canvas2DRenderer as r, BOX_SHADOW_BLUR_REACH as ra, linearGradient as ri, DEFAULT_TEXT_COLOR as rn, UiFocusEvent as ro, lineIndexAtY as rr, UiEditingController as rt, PaintTarget as s, boxShadowReach as sa, fr as si, isPaintVisible as sn, UiPasteEvent as so, offsetAtX as sr, UiWheelController as st, CanvasSurface as t, defaultShadows as ta, gradientPaint as ti, DEFAULT_FONT_SIZE as tn, UiCompositionEvent as to, proportionalFontMetrics as tr, prepareInputSurface as tt, replayPaint as u, isShapeName as ua, isMinMaxTrack as ui, resolvePaintState as un, UiSelectionChangeEvent as uo, EditableTextModel as ur, UiPointerController as ut, drawOverlayShapes as v, hiddenRangesOf as va, borderRadiusCorners as vi, selectableTextOf as vn, UiBinding as vo, wordRangeAt as vr, scrollRange as vt, describeLength as w, textSpansEqual as wa, defineProperty as wi, isNodeSelectable as wn, resolvePropertyByName as wr, offsetAtPointIn as wt, LayoutEngine as x, spannedTextOf as xa, borderRadiusValuesEqual as xi, isNodeFocusable as xn, resolveBoolean as xr, scrollbarZoneAt as xt, labelOrigin as y, resolvedSpansOf as ya, borderRadiusEqual as yi, selectionRangeOf as yn, UiGraph as yr, scrollbarThumb as yt, isLayoutProtocol as z, findEnvironmentKey as za, withDensity as zi, caretVisibleAt as zn, isUiSemanticState as zr, buildFontString as zt };
21516
22081
 
21517
- //# sourceMappingURL=CanvasSurface-BSvtTgJZ.js.map
22082
+ //# sourceMappingURL=CanvasSurface-VUJRrcXl.js.map