@weasel-js/text 1.14.0 → 1.14.2

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.
@@ -15,16 +15,16 @@ function tabAdvance(x, stop, min) {
15
15
  const toNext = (Math.floor(x / stop) + 1) * stop - x;
16
16
  return toNext < min ? toNext + stop : toNext;
17
17
  }
18
- function widthWithTabs(start, end, tabs2, stopOf2, flatWidth2, piece) {
18
+ function widthWithTabs(start2, end, tabs2, stopOf2, flatWidth2, piece) {
19
19
  let x = 0;
20
- let from = start;
20
+ let from = start2;
21
21
  const lay = (to) => {
22
22
  if (to <= from) return;
23
23
  piece?.(from, to, x);
24
24
  x += flatWidth2(from, to);
25
25
  };
26
26
  for (const t of tabs2) {
27
- if (t < start) continue;
27
+ if (t < start2) continue;
28
28
  if (t >= end) break;
29
29
  lay(t);
30
30
  const { stop, min } = stopOf2(t);
@@ -76,53 +76,51 @@ var DEFAULT_TEXT_STYLE = {
76
76
  textTransform: "none",
77
77
  fontVariantCaps: "normal"
78
78
  };
79
- var NOTHING = Object.freeze({});
80
79
  function resolveTextStyle(style, paint, scale = 1) {
80
+ if (!style && resolveFill(paint?.fill) === null && (paint?.stroke ?? void 0) === void 0) {
81
+ return DEFAULT_TEXT_STYLE;
82
+ }
83
+ return resolveTextStyleInto({ ...DEFAULT_TEXT_STYLE }, style, paint, scale);
84
+ }
85
+ function resolveTextStyleInto(out, style, paint, scale = 1) {
86
+ const s = style ?? NO_STYLE;
81
87
  const fill2 = resolveFill(paint?.fill);
82
88
  const stroke = paint?.stroke ?? void 0;
83
- if (!style) {
84
- return fill2 === null && stroke === void 0 ? DEFAULT_TEXT_STYLE : {
85
- ...DEFAULT_TEXT_STYLE,
86
- fill: fill2,
87
- caretColor: paintColor(fill2?.paint ?? null),
88
- selectionBackground: defaultSelectionBackground(paintColor(fill2?.paint ?? null)),
89
- ...stroke !== void 0 ? { stroke } : NOTHING
90
- };
91
- }
92
- const caretColor = style.caretColor ?? paintColor(fill2?.paint ?? null);
89
+ const caretColor = s.caretColor ?? paintColor(fill2?.paint ?? null);
93
90
  let selectionBackground;
94
- if (style.selectionBackground === "none") {
91
+ if (s.selectionBackground === "none") {
95
92
  selectionBackground = null;
96
- } else if (style.selectionBackground != null) {
97
- selectionBackground = style.selectionBackground;
93
+ } else if (s.selectionBackground != null) {
94
+ selectionBackground = s.selectionBackground;
98
95
  } else {
99
96
  selectionBackground = defaultSelectionBackground(caretColor);
100
97
  }
101
- return {
102
- fontSize: resolveScreenLength(style.fontSize ?? DEFAULT_TEXT_STYLE.fontSize, scale),
103
- fontFamily: style.fontFamily ?? DEFAULT_TEXT_STYLE.fontFamily,
104
- fontWeight: style.fontWeight ?? DEFAULT_TEXT_STYLE.fontWeight,
105
- fontStyle: style.fontStyle ?? DEFAULT_TEXT_STYLE.fontStyle,
106
- align: style.align ?? DEFAULT_TEXT_STYLE.align,
107
- direction: style.direction ?? DEFAULT_TEXT_STYLE.direction,
108
- lineHeight: style.lineHeight ?? DEFAULT_TEXT_STYLE.lineHeight,
109
- tabSize: style.tabSize ?? DEFAULT_TEXT_STYLE.tabSize,
110
- wrap: style.wrap ?? DEFAULT_TEXT_STYLE.wrap,
111
- fill: fill2,
112
- caretColor,
113
- selectionBackground,
114
- selectionColor: style.selectionColor ?? null,
115
- letterSpacing: resolveScreenLength(style.letterSpacing ?? DEFAULT_TEXT_STYLE.letterSpacing, scale),
116
- underline: style.underline ?? DEFAULT_TEXT_STYLE.underline,
117
- strikethrough: style.strikethrough ?? DEFAULT_TEXT_STYLE.strikethrough,
118
- overline: style.overline ?? DEFAULT_TEXT_STYLE.overline,
119
- textTransform: style.textTransform ?? DEFAULT_TEXT_STYLE.textTransform,
120
- fontVariantCaps: style.fontVariantCaps ?? DEFAULT_TEXT_STYLE.fontVariantCaps,
121
- ...style.script !== void 0 ? { script: style.script } : NOTHING,
122
- ...style.lang ? { lang: style.lang } : NOTHING,
123
- ...stroke !== void 0 ? { stroke } : NOTHING
124
- };
98
+ out.fontSize = resolveScreenLength(s.fontSize ?? DEFAULT_TEXT_STYLE.fontSize, scale);
99
+ out.fontFamily = s.fontFamily ?? DEFAULT_TEXT_STYLE.fontFamily;
100
+ out.fontWeight = s.fontWeight ?? DEFAULT_TEXT_STYLE.fontWeight;
101
+ out.fontStyle = s.fontStyle ?? DEFAULT_TEXT_STYLE.fontStyle;
102
+ out.align = s.align ?? DEFAULT_TEXT_STYLE.align;
103
+ out.direction = s.direction ?? DEFAULT_TEXT_STYLE.direction;
104
+ out.lineHeight = s.lineHeight ?? DEFAULT_TEXT_STYLE.lineHeight;
105
+ out.tabSize = s.tabSize ?? DEFAULT_TEXT_STYLE.tabSize;
106
+ out.wrap = s.wrap ?? DEFAULT_TEXT_STYLE.wrap;
107
+ out.fill = fill2;
108
+ out.caretColor = caretColor;
109
+ out.selectionBackground = selectionBackground;
110
+ out.selectionColor = s.selectionColor ?? null;
111
+ out.letterSpacing = resolveScreenLength(s.letterSpacing ?? DEFAULT_TEXT_STYLE.letterSpacing, scale);
112
+ out.underline = s.underline ?? DEFAULT_TEXT_STYLE.underline;
113
+ out.strikethrough = s.strikethrough ?? DEFAULT_TEXT_STYLE.strikethrough;
114
+ out.overline = s.overline ?? DEFAULT_TEXT_STYLE.overline;
115
+ out.textTransform = s.textTransform ?? DEFAULT_TEXT_STYLE.textTransform;
116
+ out.fontVariantCaps = s.fontVariantCaps ?? DEFAULT_TEXT_STYLE.fontVariantCaps;
117
+ if (s.script !== void 0 || out.script !== void 0) out.script = s.script;
118
+ const lang = s.lang || void 0;
119
+ if (lang !== void 0 || out.lang !== void 0) out.lang = lang;
120
+ if (stroke !== void 0 || out.stroke !== void 0) out.stroke = stroke;
121
+ return out;
125
122
  }
123
+ var NO_STYLE = Object.freeze({});
126
124
  function fontString(s) {
127
125
  return `${s.fontStyle} ${s.fontWeight} ${s.fontSize}px ${cssFamilyName(s.fontFamily)}`;
128
126
  }
@@ -333,7 +331,7 @@ function decode() {
333
331
  const values = new Uint8Array(RUNS);
334
332
  const slot = new Uint8Array(128);
335
333
  for (let i = 0; i < ALPHABET.length; i++) slot[ALPHABET.charCodeAt(i)] = PALETTE[i];
336
- let cp = 0;
334
+ let cp2 = 0;
337
335
  let run = 0;
338
336
  let len = 0;
339
337
  for (let i = 0; i < PACKED.length; i++) {
@@ -341,11 +339,11 @@ function decode() {
341
339
  if (code <= 57 && code >= 48) len = len * 10 + code - 48;
342
340
  else {
343
341
  const v = slot[code];
344
- starts[run] = cp;
342
+ starts[run] = cp2;
345
343
  values[run] = v;
346
- if (cp < 65536) direct.fill(v, cp, Math.min(cp + len, 65536));
344
+ if (cp2 < 65536) direct.fill(v, cp2, Math.min(cp2 + len, 65536));
347
345
  run++;
348
- cp += len;
346
+ cp2 += len;
349
347
  len = 0;
350
348
  }
351
349
  }
@@ -353,16 +351,16 @@ function decode() {
353
351
  astralStarts = starts;
354
352
  astralValues = values;
355
353
  }
356
- function lineBreakPropsOf(cp) {
354
+ function lineBreakPropsOf(cp2) {
357
355
  if (bmp === null) decode();
358
- if (cp < 65536) return bmp[cp];
359
- if (cp > 1114111) return AL;
356
+ if (cp2 < 65536) return bmp[cp2];
357
+ if (cp2 > 1114111) return AL;
360
358
  const starts = astralStarts;
361
359
  let lo = 0;
362
360
  let hi = starts.length - 1;
363
361
  while (lo < hi) {
364
362
  const mid = lo + hi + 1 >> 1;
365
- if (starts[mid] <= cp) lo = mid;
363
+ if (starts[mid] <= cp2) lo = mid;
366
364
  else hi = mid - 1;
367
365
  }
368
366
  return astralValues[lo];
@@ -427,8 +425,8 @@ var HYPHEN = 8208;
427
425
  var DOTTED_CIRCLE = 9676;
428
426
  var isQU = (c) => c === QU2 || c === QU_PI2 || c === QU_PF2;
429
427
  var isHard = (c) => c === BK2 || c === CR2 || c === LF2 || c === NL2;
430
- function isHardLineBreak(cp) {
431
- return isHard(lineBreakPropsOf2(cp) & CLASS_MASK2);
428
+ function isHardLineBreak(cp2) {
429
+ return isHard(lineBreakPropsOf2(cp2) & CLASS_MASK2);
432
430
  }
433
431
  var isAlpha = (c) => c === AL2 || c === HL2;
434
432
  var isKorean = (c) => c === JL2 || c === JV2 || c === JT2 || c === H22 || c === H32;
@@ -446,14 +444,28 @@ function growScratch(n) {
446
444
  if (shared.cls.length < n) shared = newScratch(Math.min(SCRATCH_MAX, Math.max(n, shared.cls.length * 2)));
447
445
  return shared;
448
446
  }
447
+ var NO_INPUT = [];
448
+ var input = NO_INPUT;
449
+ var cls = shared.cls;
450
+ var ea = shared.ea;
451
+ var pict = shared.pict;
452
+ var cp = shared.cp;
453
+ var start = shared.start;
454
+ var units = 0;
449
455
  function lineBreakOpportunities(cps) {
456
+ return lineBreakOpportunitiesInto(cps, new Uint8Array(cps.length + 1));
457
+ }
458
+ function lineBreakOpportunitiesInto(cps, out) {
450
459
  const n = cps.length;
451
- const out = new Uint8Array(n + 1);
452
- if (n === 0) return out;
460
+ for (let i = 0; i < n; i++) out[i] = NO_BREAK;
461
+ if (n === 0) {
462
+ out[0] = NO_BREAK;
463
+ return out;
464
+ }
453
465
  out[n] = BREAK_MANDATORY;
454
466
  const scratch2 = n <= SCRATCH_MAX ? growScratch(n) : newScratch(n);
455
- const { cls, ea, pict, cp } = scratch2;
456
- const start = scratch2.start;
467
+ input = cps;
468
+ ({ cls, ea, pict, cp, start } = scratch2);
457
469
  let u = 0;
458
470
  for (let i = 0; i < n; i++) {
459
471
  const props = lineBreakPropsOf2(cps[i]);
@@ -475,111 +487,113 @@ function lineBreakOpportunities(cps) {
475
487
  start[u] = i;
476
488
  u++;
477
489
  }
478
- const units = u;
479
- const prevIsZWJ = (b) => (lineBreakPropsOf2(cps[start[b] - 1]) & CLASS_MASK2) === ZWJ2;
480
- const beforeSpaces = (k) => {
481
- while (k >= 0 && cls[k] === SP2) k--;
482
- return k;
483
- };
490
+ units = u;
484
491
  for (let b = 1; b < units; b++) {
485
492
  out[start[b]] = decide(b);
486
493
  }
494
+ input = NO_INPUT;
495
+ if (scratch2 !== shared) ({ cls, ea, pict, cp, start } = shared);
487
496
  return out;
488
- function decide(b) {
489
- const a = b - 1;
490
- const A = cls[a];
491
- const B = cls[b];
492
- if (A === CR2 && B === LF2) return NO_BREAK;
493
- if (isHard(A)) return BREAK_MANDATORY;
494
- if (isHard(B) || B === SP2 || B === ZW2) return NO_BREAK;
495
- const s = beforeSpaces(a);
496
- if (s >= 0 && cls[s] === ZW2) return BREAK_ALLOWED;
497
- if (prevIsZWJ(b)) return NO_BREAK;
498
- if (A === WJ2 || B === WJ2 || A === GL2) return NO_BREAK;
499
- if (B === GL2 && A !== SP2 && A !== BA2 && A !== HY2) return NO_BREAK;
500
- if (B === CL2 || B === CP2 || B === EX2 || B === SY2) return NO_BREAK;
501
- const S = s >= 0 ? cls[s] : -1;
502
- if (S === OP2) return NO_BREAK;
503
- if (S === QU_PI2) {
504
- const p = s - 1;
505
- if (p < 0) return NO_BREAK;
506
- const P = cls[p];
507
- if (isHard(P) || P === OP2 || isQU(P) || P === GL2 || P === SP2 || P === ZW2) return NO_BREAK;
508
- }
509
- if (B === QU_PF2) {
510
- if (b + 1 >= units) return NO_BREAK;
497
+ }
498
+ var prevIsZWJ = (b) => (lineBreakPropsOf2(input[start[b] - 1]) & CLASS_MASK2) === ZWJ2;
499
+ function beforeSpaces(k) {
500
+ while (k >= 0 && cls[k] === SP2) k--;
501
+ return k;
502
+ }
503
+ function decide(b) {
504
+ const a = b - 1;
505
+ const A = cls[a];
506
+ const B = cls[b];
507
+ if (A === CR2 && B === LF2) return NO_BREAK;
508
+ if (isHard(A)) return BREAK_MANDATORY;
509
+ if (isHard(B) || B === SP2 || B === ZW2) return NO_BREAK;
510
+ const s = beforeSpaces(a);
511
+ if (s >= 0 && cls[s] === ZW2) return BREAK_ALLOWED;
512
+ if (prevIsZWJ(b)) return NO_BREAK;
513
+ if (A === WJ2 || B === WJ2 || A === GL2) return NO_BREAK;
514
+ if (B === GL2 && A !== SP2 && A !== BA2 && A !== HY2) return NO_BREAK;
515
+ if (B === CL2 || B === CP2 || B === EX2 || B === SY2) return NO_BREAK;
516
+ const S = s >= 0 ? cls[s] : -1;
517
+ if (S === OP2) return NO_BREAK;
518
+ if (S === QU_PI2) {
519
+ const p = s - 1;
520
+ if (p < 0) return NO_BREAK;
521
+ const P = cls[p];
522
+ if (isHard(P) || P === OP2 || isQU(P) || P === GL2 || P === SP2 || P === ZW2) return NO_BREAK;
523
+ }
524
+ if (B === QU_PF2) {
525
+ if (b + 1 >= units) return NO_BREAK;
526
+ const N = cls[b + 1];
527
+ if (N === SP2 || N === GL2 || N === WJ2 || N === CL2 || isQU(N) || N === CP2 || N === EX2 || N === IS2 || N === SY2 || isHard(N) || N === ZW2) return NO_BREAK;
528
+ }
529
+ if (A === SP2 && B === IS2 && b + 1 < units && cls[b + 1] === NU2) return BREAK_ALLOWED;
530
+ if (B === IS2) return NO_BREAK;
531
+ if ((S === CL2 || S === CP2) && B === NS2) return NO_BREAK;
532
+ if (S === B22 && B === B22) return NO_BREAK;
533
+ if (A === SP2) return BREAK_ALLOWED;
534
+ if (B === QU2 || B === QU_PF2) return NO_BREAK;
535
+ if (A === QU2 || A === QU_PI2) return NO_BREAK;
536
+ if (isQU(B)) {
537
+ if (!ea[a]) return NO_BREAK;
538
+ if (b + 1 >= units || !ea[b + 1]) return NO_BREAK;
539
+ }
540
+ if (isQU(A)) {
541
+ if (!ea[b]) return NO_BREAK;
542
+ if (a === 0 || !ea[a - 1]) return NO_BREAK;
543
+ }
544
+ if (A === CB2 || B === CB2) return BREAK_ALLOWED;
545
+ if ((A === HY2 || cp[a] === HYPHEN) && B === AL2) {
546
+ if (a === 0) return NO_BREAK;
547
+ const P = cls[a - 1];
548
+ if (isHard(P) || P === SP2 || P === ZW2 || P === CB2 || P === GL2) return NO_BREAK;
549
+ }
550
+ if (B === BA2 || B === HY2 || B === NS2 || A === BB2) return NO_BREAK;
551
+ if (a > 0 && cls[a - 1] === HL2 && (A === HY2 || A === BA2 && !ea[a]) && B !== HL2) return NO_BREAK;
552
+ if (A === SY2 && B === HL2) return NO_BREAK;
553
+ if (B === IN2) return NO_BREAK;
554
+ if (isAlpha(A) && B === NU2 || A === NU2 && isAlpha(B)) return NO_BREAK;
555
+ if (A === PR2 && isIdeoLike(B) || isIdeoLike(A) && B === PO2) return NO_BREAK;
556
+ if ((A === PR2 || A === PO2) && isAlpha(B) || isAlpha(A) && (B === PR2 || B === PO2)) return NO_BREAK;
557
+ if (B === PO2 || B === PR2) {
558
+ let k = A === CL2 || A === CP2 ? a - 1 : a;
559
+ while (k >= 0 && (cls[k] === SY2 || cls[k] === IS2)) k--;
560
+ if (k >= 0 && cls[k] === NU2) return NO_BREAK;
561
+ }
562
+ if (A === PO2 || A === PR2) {
563
+ if (B === NU2) return NO_BREAK;
564
+ if (B === OP2 && b + 1 < units) {
511
565
  const N = cls[b + 1];
512
- if (N === SP2 || N === GL2 || N === WJ2 || N === CL2 || isQU(N) || N === CP2 || N === EX2 || N === IS2 || N === SY2 || isHard(N) || N === ZW2) return NO_BREAK;
513
- }
514
- if (A === SP2 && B === IS2 && b + 1 < units && cls[b + 1] === NU2) return BREAK_ALLOWED;
515
- if (B === IS2) return NO_BREAK;
516
- if ((S === CL2 || S === CP2) && B === NS2) return NO_BREAK;
517
- if (S === B22 && B === B22) return NO_BREAK;
518
- if (A === SP2) return BREAK_ALLOWED;
519
- if (B === QU2 || B === QU_PF2) return NO_BREAK;
520
- if (A === QU2 || A === QU_PI2) return NO_BREAK;
521
- if (isQU(B)) {
522
- if (!ea[a]) return NO_BREAK;
523
- if (b + 1 >= units || !ea[b + 1]) return NO_BREAK;
524
- }
525
- if (isQU(A)) {
526
- if (!ea[b]) return NO_BREAK;
527
- if (a === 0 || !ea[a - 1]) return NO_BREAK;
528
- }
529
- if (A === CB2 || B === CB2) return BREAK_ALLOWED;
530
- if ((A === HY2 || cp[a] === HYPHEN) && B === AL2) {
531
- if (a === 0) return NO_BREAK;
532
- const P = cls[a - 1];
533
- if (isHard(P) || P === SP2 || P === ZW2 || P === CB2 || P === GL2) return NO_BREAK;
534
- }
535
- if (B === BA2 || B === HY2 || B === NS2 || A === BB2) return NO_BREAK;
536
- if (a > 0 && cls[a - 1] === HL2 && (A === HY2 || A === BA2 && !ea[a]) && B !== HL2) return NO_BREAK;
537
- if (A === SY2 && B === HL2) return NO_BREAK;
538
- if (B === IN2) return NO_BREAK;
539
- if (isAlpha(A) && B === NU2 || A === NU2 && isAlpha(B)) return NO_BREAK;
540
- if (A === PR2 && isIdeoLike(B) || isIdeoLike(A) && B === PO2) return NO_BREAK;
541
- if ((A === PR2 || A === PO2) && isAlpha(B) || isAlpha(A) && (B === PR2 || B === PO2)) return NO_BREAK;
542
- if (B === PO2 || B === PR2) {
543
- let k = A === CL2 || A === CP2 ? a - 1 : a;
544
- while (k >= 0 && (cls[k] === SY2 || cls[k] === IS2)) k--;
545
- if (k >= 0 && cls[k] === NU2) return NO_BREAK;
546
- }
547
- if (A === PO2 || A === PR2) {
548
- if (B === NU2) return NO_BREAK;
549
- if (B === OP2 && b + 1 < units) {
550
- const N = cls[b + 1];
551
- if (N === NU2) return NO_BREAK;
552
- if (N === IS2 && b + 2 < units && cls[b + 2] === NU2) return NO_BREAK;
553
- }
554
- }
555
- if ((A === HY2 || A === IS2) && B === NU2) return NO_BREAK;
556
- if (B === NU2) {
557
- let k = a;
558
- while (k >= 0 && (cls[k] === SY2 || cls[k] === IS2)) k--;
559
- if (k >= 0 && cls[k] === NU2) return NO_BREAK;
560
- }
561
- if (A === JL2 && (B === JL2 || B === JV2 || B === H22 || B === H32)) return NO_BREAK;
562
- if ((A === JV2 || A === H22) && (B === JV2 || B === JT2)) return NO_BREAK;
563
- if ((A === JT2 || A === H32) && B === JT2) return NO_BREAK;
564
- if (isKorean(A) && B === PO2 || A === PR2 && isKorean(B)) return NO_BREAK;
565
- if (isAlpha(A) && isAlpha(B)) return NO_BREAK;
566
- const aksaraA = A === AK2 || A === AS2 || cp[a] === DOTTED_CIRCLE;
567
- const aksaraB = B === AK2 || B === AS2 || cp[b] === DOTTED_CIRCLE;
568
- if (A === AP2 && aksaraB) return NO_BREAK;
569
- if (aksaraA && (B === VF2 || B === VI2)) return NO_BREAK;
570
- if (A === VI2 && a > 0 && (cls[a - 1] === AK2 || cls[a - 1] === AS2 || cp[a - 1] === DOTTED_CIRCLE) && (B === AK2 || cp[b] === DOTTED_CIRCLE)) return NO_BREAK;
571
- if (aksaraA && aksaraB && b + 1 < units && cls[b + 1] === VF2) return NO_BREAK;
572
- if (A === IS2 && isAlpha(B)) return NO_BREAK;
573
- if ((isAlpha(A) || A === NU2) && B === OP2 && !ea[b]) return NO_BREAK;
574
- if (A === CP2 && !ea[a] && (isAlpha(B) || B === NU2)) return NO_BREAK;
575
- if (A === RI2 && B === RI2) {
576
- let k = a;
577
- while (k >= 0 && cls[k] === RI2) k--;
578
- if ((a - k) % 2 === 1) return NO_BREAK;
566
+ if (N === NU2) return NO_BREAK;
567
+ if (N === IS2 && b + 2 < units && cls[b + 2] === NU2) return NO_BREAK;
579
568
  }
580
- if (B === EM2 && (A === EB2 || pict[a])) return NO_BREAK;
581
- return BREAK_ALLOWED;
582
569
  }
570
+ if ((A === HY2 || A === IS2) && B === NU2) return NO_BREAK;
571
+ if (B === NU2) {
572
+ let k = a;
573
+ while (k >= 0 && (cls[k] === SY2 || cls[k] === IS2)) k--;
574
+ if (k >= 0 && cls[k] === NU2) return NO_BREAK;
575
+ }
576
+ if (A === JL2 && (B === JL2 || B === JV2 || B === H22 || B === H32)) return NO_BREAK;
577
+ if ((A === JV2 || A === H22) && (B === JV2 || B === JT2)) return NO_BREAK;
578
+ if ((A === JT2 || A === H32) && B === JT2) return NO_BREAK;
579
+ if (isKorean(A) && B === PO2 || A === PR2 && isKorean(B)) return NO_BREAK;
580
+ if (isAlpha(A) && isAlpha(B)) return NO_BREAK;
581
+ const aksaraA = A === AK2 || A === AS2 || cp[a] === DOTTED_CIRCLE;
582
+ const aksaraB = B === AK2 || B === AS2 || cp[b] === DOTTED_CIRCLE;
583
+ if (A === AP2 && aksaraB) return NO_BREAK;
584
+ if (aksaraA && (B === VF2 || B === VI2)) return NO_BREAK;
585
+ if (A === VI2 && a > 0 && (cls[a - 1] === AK2 || cls[a - 1] === AS2 || cp[a - 1] === DOTTED_CIRCLE) && (B === AK2 || cp[b] === DOTTED_CIRCLE)) return NO_BREAK;
586
+ if (aksaraA && aksaraB && b + 1 < units && cls[b + 1] === VF2) return NO_BREAK;
587
+ if (A === IS2 && isAlpha(B)) return NO_BREAK;
588
+ if ((isAlpha(A) || A === NU2) && B === OP2 && !ea[b]) return NO_BREAK;
589
+ if (A === CP2 && !ea[a] && (isAlpha(B) || B === NU2)) return NO_BREAK;
590
+ if (A === RI2 && B === RI2) {
591
+ let k = a;
592
+ while (k >= 0 && cls[k] === RI2) k--;
593
+ if ((a - k) % 2 === 1) return NO_BREAK;
594
+ }
595
+ if (B === EM2 && (A === EB2 || pict[a])) return NO_BREAK;
596
+ return BREAK_ALLOWED;
583
597
  }
584
598
 
585
599
  // src/layout/bidiRegistry.ts
@@ -601,9 +615,9 @@ function paragraphDirection(text, direction = "auto", engine = registered2) {
601
615
  if (engine === null) return "ltr";
602
616
  const cps = [];
603
617
  for (const ch of text) {
604
- const cp = ch.codePointAt(0);
605
- if (isHardLineBreak(cp)) break;
606
- cps.push(cp);
618
+ const cp2 = ch.codePointAt(0);
619
+ if (isHardLineBreak(cp2)) break;
620
+ cps.push(cp2);
607
621
  }
608
622
  return directionOfLevel(engine.baseLevel(engine.analyze(cps, "auto")));
609
623
  }
@@ -630,16 +644,16 @@ var RTL_BLOCK_NEUTRALS = [
630
644
  [64830, 64831],
631
645
  [65279, 65279]
632
646
  ];
633
- function within(ranges, cp) {
634
- for (let i = 0; i < ranges.length; i++) if (cp >= ranges[i][0] && cp <= ranges[i][1]) return true;
647
+ function within(ranges, cp2) {
648
+ for (let i = 0; i < ranges.length; i++) if (cp2 >= ranges[i][0] && cp2 <= ranges[i][1]) return true;
635
649
  return false;
636
650
  }
637
651
  var warnedLogicalRtl = false;
638
652
  function warnRtlWithoutEngine(codePoints) {
639
653
  if (warnedLogicalRtl) return;
640
654
  for (let i = 0; i < codePoints.length; i++) {
641
- const cp = codePoints[i];
642
- if (!within(RTL_BLOCKS, cp) || within(RTL_BLOCK_NUMBERS, cp) || within(RTL_BLOCK_NEUTRALS, cp)) continue;
655
+ const cp2 = codePoints[i];
656
+ if (!within(RTL_BLOCKS, cp2) || within(RTL_BLOCK_NUMBERS, cp2) || within(RTL_BLOCK_NEUTRALS, cp2)) continue;
643
657
  warnedLogicalRtl = true;
644
658
  console.warn(
645
659
  // Naming the import in `from "<pkg>"` form would trip labkit's
@@ -656,13 +670,13 @@ function _resetBidiWarningForTests() {
656
670
  // src/layout/bidiLines.ts
657
671
  function analyzeParagraphs(engine, codePoints, isBreak, direction) {
658
672
  const paragraphs = [];
659
- let start = 0;
673
+ let start2 = 0;
660
674
  for (let i = 0; i <= codePoints.length; i++) {
661
675
  if (i < codePoints.length && !isBreak(i)) continue;
662
- if (i > start) {
663
- paragraphs.push({ start, end: i, analysis: engine.analyze(codePoints.slice(start, i), direction) });
676
+ if (i > start2) {
677
+ paragraphs.push({ start: start2, end: i, analysis: engine.analyze(codePoints.slice(start2, i), direction) });
664
678
  }
665
- start = i + 1;
679
+ start2 = i + 1;
666
680
  }
667
681
  const holding = (at) => {
668
682
  let lo = 0;
@@ -729,17 +743,20 @@ function logicalOrder(count) {
729
743
  if (count <= SHARED_ORDER_LIMIT) logicalOrders[count] = order;
730
744
  return order;
731
745
  }
732
- function orderLine(paragraphs, start, count) {
746
+ function orderLine(paragraphs, start2, count) {
733
747
  if (paragraphs === null || count === 0) return logicalOrder(count);
748
+ return reorderedLine(paragraphs, start2, count);
749
+ }
750
+ function reorderedLine(paragraphs, start2, count) {
734
751
  const levels = new Array(count).fill(0);
735
- const r = paragraphs.reorder(start, start + count);
752
+ const r = paragraphs.reorder(start2, start2 + count);
736
753
  for (let pos = 0; pos < count; pos++) {
737
754
  const level = r.levels[pos];
738
755
  if (typeof level === "number" && level >= 0) levels[pos] = level;
739
756
  }
740
757
  const kept = /* @__PURE__ */ new Set();
741
758
  for (const at of r.order) {
742
- const pos = at - start;
759
+ const pos = at - start2;
743
760
  if (Number.isInteger(pos) && pos >= 0 && pos < count) kept.add(pos);
744
761
  }
745
762
  return { visual: restoreRemoved([...kept], count, (pos) => levels[pos] % 2 === 1), levels };
@@ -785,6 +802,9 @@ function giveDoubles(a) {
785
802
  function giveRefs(a) {
786
803
  give(refs, a);
787
804
  }
805
+ function truncate(a, n) {
806
+ while (a.length > n) a.pop();
807
+ }
788
808
 
789
809
  // src/layout/layoutColumns.ts
790
810
  var NUM = 13;
@@ -808,12 +828,21 @@ var EMPTY_NUM = [];
808
828
  var EMPTY_REF = [];
809
829
  var spare = [];
810
830
  var SPARE_MAX = 1024;
831
+ var stamps = 0;
811
832
  var LayoutColumns = class _LayoutColumns {
812
833
  num;
813
834
  ref;
814
835
  /** The layout's line boxes, its `lines`. A recycled layout's are refilled
815
836
  * by the next layout to take its columns. */
816
837
  boxes = [];
838
+ /** The layout's `bounds`, refilled like its boxes. */
839
+ bounds = { width: 0.5, height: 0.5 };
840
+ /** The layout itself, `{ lines: boxes, bounds }`, handed to every layout
841
+ * that takes these columns. */
842
+ laid = null;
843
+ /** Which of those layouts this is, new each time the columns are taken:
844
+ * what tells a layout apart from the one these columns held before. */
845
+ stamp = 0;
817
846
  /** The paint cache's paintings of this layout, kept here rather than in a
818
847
  * map keyed by the layout, so that recycling one deletes no map entry. */
819
848
  paintings = [];
@@ -831,15 +860,20 @@ var LayoutColumns = class _LayoutColumns {
831
860
  /** Columns for `size` code points: a recycled layout's, refilled, when the
832
861
  * pool holds one. */
833
862
  static take(size) {
834
- const cols = spare.pop();
835
- if (cols === void 0) return new _LayoutColumns(size);
836
- cols.num = takeDoubles(size * NUM);
837
- cols.ref = takeRefs(size * REF);
863
+ let cols = spare.pop();
864
+ if (cols === void 0) {
865
+ cols = new _LayoutColumns(size);
866
+ } else {
867
+ cols.num = takeDoubles(size * NUM);
868
+ cols.ref = takeRefs(size * REF);
869
+ }
870
+ cols.stamp = ++stamps;
838
871
  return cols;
839
872
  }
840
873
  /** Give the arrays back to the pool, and these columns with their line
841
- * boxes to the next layout. A line box still held afterwards reads that
842
- * layout's code points, so only a layout nobody holds is recycled. */
874
+ * boxes and result to the next layout. A line box or result still held
875
+ * afterwards reads that layout's code points, so only a layout nobody holds
876
+ * is recycled. */
843
877
  recycle() {
844
878
  giveDoubles(this.num);
845
879
  const { ref } = this;
@@ -847,7 +881,7 @@ var LayoutColumns = class _LayoutColumns {
847
881
  giveRefs(ref);
848
882
  this.num = EMPTY_NUM;
849
883
  this.ref = EMPTY_REF;
850
- this.paintings.length = 0;
884
+ truncate(this.paintings, 0);
851
885
  this.owners = 0;
852
886
  this.listed = false;
853
887
  this.pinned = false;
@@ -882,17 +916,17 @@ var LayoutColumns = class _LayoutColumns {
882
916
  }
883
917
  /** Record that the `slot`-th code point the pen reaches on the line starting
884
918
  * at `start` is the line's `pos`-th in logical order. */
885
- setVisual(start, slot, pos) {
886
- this.num[(start + slot) * NUM + VISUAL] = pos;
919
+ setVisual(start2, slot, pos) {
920
+ this.num[(start2 + slot) * NUM + VISUAL] = pos;
887
921
  }
888
922
  /** Logical index, within its line, of the glyph at visual `slot`. */
889
- visual(start, slot) {
890
- return this.num[(start + slot) * NUM + VISUAL];
923
+ visual(start2, slot) {
924
+ return this.num[(start2 + slot) * NUM + VISUAL];
891
925
  }
892
926
  /** Fill `out` with code point `start + pos`'s placed glyph and return it.
893
927
  * Every field is overwritten, so one object can walk a whole layout. */
894
- glyph(start, pos, out) {
895
- const i = start + pos;
928
+ glyph(start2, pos, out) {
929
+ const i = start2 + pos;
896
930
  const n = i * NUM, r = i * REF;
897
931
  const { num, ref } = this;
898
932
  out.run = num[n + RUN];
@@ -912,12 +946,12 @@ var LayoutColumns = class _LayoutColumns {
912
946
  return out;
913
947
  }
914
948
  /** Code point `start + pos`'s `LaidOutCell.drawsInk`, without building the cell. */
915
- drawsInk(start, pos) {
916
- return (this.num[(start + pos) * NUM + FLAGS] & DRAWS_INK) !== 0;
949
+ drawsInk(start2, pos) {
950
+ return (this.num[(start2 + pos) * NUM + FLAGS] & DRAWS_INK) !== 0;
917
951
  }
918
952
  /** Code point `start + pos`'s cell, as a new object. */
919
- cell(start, pos) {
920
- const n = (start + pos) * NUM;
953
+ cell(start2, pos) {
954
+ const n = (start2 + pos) * NUM;
921
955
  const { num } = this;
922
956
  const flags = num[n + FLAGS];
923
957
  return {
@@ -951,19 +985,12 @@ function blankGlyph() {
951
985
  };
952
986
  }
953
987
  var LineBox = class _LineBox {
954
- x0;
955
- y0;
956
- x1;
957
- y1;
958
- baselineY;
959
- srcEnd;
960
- justified;
961
988
  #cols;
962
989
  #start;
963
990
  #count;
964
991
  /** Whether a view was built, replacing its accessor with a value. */
965
992
  #viewed = false;
966
- constructor(x0, y0, x1, y1, baselineY, srcEnd, justified, cols, start, count) {
993
+ constructor(x0, y0, x1, y1, baselineY, srcEnd, justified, cols, start2, count) {
967
994
  this.x0 = x0;
968
995
  this.y0 = y0;
969
996
  this.x1 = x1;
@@ -972,13 +999,13 @@ var LineBox = class _LineBox {
972
999
  this.srcEnd = srcEnd;
973
1000
  this.justified = justified;
974
1001
  this.#cols = cols;
975
- this.#start = start;
1002
+ this.#start = start2;
976
1003
  this.#count = count;
977
1004
  Object.defineProperties(this, _LineBox.#views);
978
1005
  }
979
1006
  /** Set this box to another line of the same columns, as the constructor
980
1007
  * would, its views unbuilt again. */
981
- refill(x0, y0, x1, y1, baselineY, srcEnd, justified, start, count) {
1008
+ refill(x0, y0, x1, y1, baselineY, srcEnd, justified, start2, count) {
982
1009
  this.x0 = x0;
983
1010
  this.y0 = y0;
984
1011
  this.x1 = x1;
@@ -986,7 +1013,7 @@ var LineBox = class _LineBox {
986
1013
  this.baselineY = baselineY;
987
1014
  this.srcEnd = srcEnd;
988
1015
  this.justified = justified;
989
- this.#start = start;
1016
+ this.#start = start2;
990
1017
  this.#count = count;
991
1018
  if (this.#viewed) {
992
1019
  this.#viewed = false;
@@ -1052,7 +1079,7 @@ function graphemeEnds(text, into) {
1052
1079
  } else {
1053
1080
  const seg = graphemeSegmenter();
1054
1081
  if (seg) {
1055
- for (const s of seg.segment(text)) out[n++] = s.index + s.segment.length;
1082
+ n = segmentedEnds(seg, text, out);
1056
1083
  } else {
1057
1084
  let at = 0;
1058
1085
  for (const ch of text) out[n++] = at += ch.length;
@@ -1061,6 +1088,32 @@ function graphemeEnds(text, into) {
1061
1088
  out.length = n;
1062
1089
  return out;
1063
1090
  }
1091
+ var plain = (u) => u < 768 && u !== 13;
1092
+ function segmentedEnds(seg, text, out) {
1093
+ const len = text.length;
1094
+ let segments = null;
1095
+ let n = 0;
1096
+ let i = 0;
1097
+ while (i < len) {
1098
+ if (plain(text.charCodeAt(i)) && (i + 1 === len || plain(text.charCodeAt(i + 1)))) {
1099
+ out[n++] = ++i;
1100
+ continue;
1101
+ }
1102
+ let end = i + 1;
1103
+ for (; ; ) {
1104
+ while (end < len && !plain(text.charCodeAt(end))) end++;
1105
+ if (end === len) break;
1106
+ end++;
1107
+ if (end === len || plain(text.charCodeAt(end))) break;
1108
+ }
1109
+ segments ??= seg.segment(text);
1110
+ while (i < end) {
1111
+ const s = segments.containing(i);
1112
+ out[n++] = i = s.index + s.segment.length;
1113
+ }
1114
+ }
1115
+ return n;
1116
+ }
1064
1117
  function graphemeCount(text) {
1065
1118
  return graphemeEnds(text).length;
1066
1119
  }
@@ -1087,20 +1140,20 @@ function slotsFor(shaper, face, text, glyphs) {
1087
1140
  let g = 0;
1088
1141
  let i = 0;
1089
1142
  while (i < glyphs.length) {
1090
- const start = glyphs[i].cluster;
1143
+ const start2 = glyphs[i].cluster;
1091
1144
  let j = i;
1092
- while (j < glyphs.length && glyphs[j].cluster === start) j++;
1145
+ while (j < glyphs.length && glyphs[j].cluster === start2) j++;
1093
1146
  const end = j < glyphs.length ? glyphs[j].cluster : text.length;
1094
- while (g < ends.length && ends[g] <= start) g++;
1147
+ while (g < ends.length && ends[g] <= start2) g++;
1095
1148
  let inner = 0;
1096
1149
  for (let k = g; k < ends.length && ends[k] < end; k++) inner++;
1097
1150
  if (!holdsNotdef(glyphs, i, j)) {
1098
1151
  const total = advanceOf(glyphs, i, j);
1099
1152
  const share = total / (inner + 1);
1100
1153
  const shift = glyphs[i].rtl ? share - total : 0;
1101
- slots2[start] = clusterSlot(shaper, face, glyphs, i, j, text.slice(start, end), shift, share);
1154
+ slots2[start2] = clusterSlot(shaper, face, glyphs, i, j, text.slice(start2, end), shift, share);
1102
1155
  const opens = share === 0 ? COVERED : { cluster: NOTHING_DRAWN, advance: share };
1103
- for (let at = start + 1, k = g; at < end; at++) {
1156
+ for (let at = start2 + 1, k = g; at < end; at++) {
1104
1157
  const opening = k < ends.length && ends[k] === at;
1105
1158
  if (opening) k++;
1106
1159
  slots2[at] = opening ? opens : COVERED;
@@ -1138,7 +1191,7 @@ function clusterSlot(shaper, face, glyphs, from, to, text, shift, advance) {
1138
1191
  // src/layout/layoutEntries.ts
1139
1192
  var NONSPACING_MARK = /^[\p{Mn}\p{Me}]$/u;
1140
1193
  function measureRuns(runs, tabSize, pool, into, shaper = null) {
1141
- const entries = into ?? [];
1194
+ const entries2 = into ?? [];
1142
1195
  let n = 0;
1143
1196
  let prev;
1144
1197
  let srcIndex = 0;
@@ -1162,8 +1215,8 @@ function measureRuns(runs, tabSize, pool, into, shaper = null) {
1162
1215
  let at = 0;
1163
1216
  const text = run.text;
1164
1217
  while (at < text.length) {
1165
- const cp = text.codePointAt(at);
1166
- const len = cp > 65535 ? 2 : 1;
1218
+ const cp2 = text.codePointAt(at);
1219
+ const len = cp2 > 65535 ? 2 : 1;
1167
1220
  const slot = shaped?.[at];
1168
1221
  const fontSize = run.sizeMap ? run.sizeMap[at] : run.fontSize;
1169
1222
  if (at === clusterStart) {
@@ -1182,72 +1235,69 @@ function measureRuns(runs, tabSize, pool, into, shaper = null) {
1182
1235
  const srcEnd = map ? runBase + map.ends[at] : runBase + at + len;
1183
1236
  at += len;
1184
1237
  srcIndex = map ? runBase + map.length : srcEnd;
1185
- const mark = isNonspacingMark(cp);
1186
- const kerningBefore = prev && !mark ? prev.metrics.kernOf(prev.cp, cp) * (prev.fontSize / prev.metrics.size) : 0;
1187
- const blank = blankEntry(
1188
- pool,
1189
- n,
1190
- runIndex,
1191
- cp,
1192
- fontSize,
1193
- Math.max(run.fontSize, run.strutSize ?? 0),
1194
- // Optional on the authored run type; absent, every y would go NaN.
1195
- run.baselineShift ?? 0,
1196
- run.rotate ?? 0,
1197
- srcStart,
1198
- srcEnd,
1199
- clusterEnd,
1200
- resolved2,
1201
- metrics
1202
- );
1238
+ const mark = isNonspacingMark(cp2);
1239
+ const kerningBefore = prev && !mark ? prev.metrics.kernOf(prev.cp, cp2) * (prev.fontSize / prev.metrics.size) : 0;
1240
+ const blank = blankEntry(pool, n, runIndex, run, cp2, fontSize, srcStart, srcEnd, clusterEnd, resolved2, metrics);
1203
1241
  if (color) {
1204
- entries[n++] = clusterEnd ? ink(blank, color, 0, tracking) : blank;
1242
+ if (clusterEnd) {
1243
+ blank.tracking = tracking;
1244
+ ink(blank, color);
1245
+ }
1246
+ entries2[n++] = blank;
1205
1247
  prev = void 0;
1206
- } else if (isHardLineBreak(cp)) {
1207
- entries[n++] = blank;
1248
+ } else if (isHardLineBreak(cp2)) {
1249
+ entries2[n++] = blank;
1208
1250
  prev = void 0;
1209
- } else if (cp === 9 || cp === 32) {
1251
+ } else if (cp2 === 9 || cp2 === 32) {
1210
1252
  const space = metrics.advanceOf(32);
1211
1253
  const advance = space !== null ? space * (fontSize / metrics.size) : fontSize * 0.25;
1212
- if (cp === 9) {
1213
- entries[n++] = { ...blank, tab: { stop: tabSize * (advance + run.letterSpacing), min: advance / 2 } };
1254
+ if (cp2 === 9) {
1255
+ entries2[n++] = { ...blank, tab: { stop: tabSize * (advance + run.letterSpacing), min: advance / 2 } };
1214
1256
  prev = void 0;
1215
1257
  } else {
1216
- const own2 = glyphIn(resolved2, 32, HIT);
1217
- blank.font = own2?.font ?? null;
1218
- blank.glyph = own2?.glyph ?? null;
1258
+ const own = glyphIn(resolved2, 32, HIT);
1259
+ blank.font = own?.font ?? null;
1260
+ blank.glyph = own?.glyph ?? null;
1219
1261
  blank.advance = advance;
1220
1262
  blank.tracking = tracking;
1221
1263
  blank.kerningBefore = kerningBefore;
1222
1264
  prev = blank;
1223
- entries[n++] = prev;
1265
+ entries2[n++] = prev;
1224
1266
  }
1225
1267
  } else if (slot !== void 0) {
1226
- entries[n++] = shape(blank, slot, tracking);
1268
+ blank.tracking = tracking;
1269
+ entries2[n++] = shape(blank, slot);
1227
1270
  prev = void 0;
1228
1271
  } else {
1229
- const hit = resolveGlyph(resolved2, run.fontFamily, run.fontWeight, run.fontStyle, cp, true, HIT);
1230
- const entry = hit ? ink(blank, hit, kerningBefore, tracking) : blank;
1231
- entries[n++] = entry;
1232
- if (!mark) prev = entry;
1272
+ const hit = resolveGlyph(resolved2, run.fontFamily, run.fontWeight, run.fontStyle, cp2, true, HIT);
1273
+ if (hit) {
1274
+ blank.kerningBefore = kerningBefore;
1275
+ blank.tracking = tracking;
1276
+ ink(blank, hit);
1277
+ }
1278
+ entries2[n++] = blank;
1279
+ if (!mark) prev = blank;
1233
1280
  }
1234
1281
  }
1235
1282
  }
1236
- entries.length = n;
1237
- return entries;
1283
+ entries2.length = n;
1284
+ return entries2;
1238
1285
  }
1239
- function isNonspacingMark(cp) {
1240
- return cp >= 768 && NONSPACING_MARK.test(String.fromCodePoint(cp));
1286
+ function isNonspacingMark(cp2) {
1287
+ return cp2 >= 768 && NONSPACING_MARK.test(String.fromCodePoint(cp2));
1241
1288
  }
1242
1289
  var CLUSTER_ENDS = [];
1243
1290
  var HIT = { resolved: null, metrics: null, advance: 0, font: null, glyph: null };
1244
1291
  var POOL_MAX = 4096;
1245
- function blankEntry(pool, i, run, cp, fontSize, lineSize, baselineShift, rotate, srcIndex, srcEnd, clusterEnd, resolved2, metrics) {
1292
+ function blankEntry(pool, i, run, r, cp2, fontSize, srcIndex, srcEnd, clusterEnd, resolved2, metrics) {
1293
+ const lineSize = Math.max(r.fontSize, r.strutSize ?? 0);
1294
+ const baselineShift = r.baselineShift ?? 0;
1295
+ const rotate = r.rotate ?? 0;
1246
1296
  const e = pool?.[i];
1247
1297
  if (!e) {
1248
1298
  const made = {
1249
1299
  run,
1250
- cp,
1300
+ cp: cp2,
1251
1301
  fontSize,
1252
1302
  lineSize,
1253
1303
  baselineShift,
@@ -1270,7 +1320,7 @@ function blankEntry(pool, i, run, cp, fontSize, lineSize, baselineShift, rotate,
1270
1320
  return made;
1271
1321
  }
1272
1322
  e.run = run;
1273
- e.cp = cp;
1323
+ e.cp = cp2;
1274
1324
  e.fontSize = fontSize;
1275
1325
  e.lineSize = lineSize;
1276
1326
  e.baselineShift = baselineShift;
@@ -1290,26 +1340,22 @@ function blankEntry(pool, i, run, cp, fontSize, lineSize, baselineShift, rotate,
1290
1340
  e.drawsInk = false;
1291
1341
  return e;
1292
1342
  }
1293
- function ink(e, hit, kerning, tracking) {
1343
+ function ink(e, hit) {
1294
1344
  e.resolved = hit.resolved;
1295
1345
  e.metrics = hit.metrics;
1296
1346
  e.font = hit.font;
1297
1347
  e.glyph = hit.glyph;
1298
1348
  e.advance = hit.advance * (e.fontSize / hit.metrics.size);
1299
- e.tracking = tracking;
1300
- e.kerningBefore = kerning;
1301
1349
  e.drawsInk = true;
1302
- return e;
1303
1350
  }
1304
- function shape(e, slot, tracking) {
1351
+ function shape(e, slot) {
1305
1352
  e.shaped = slot.cluster;
1306
1353
  e.advance = slot.advance * e.fontSize;
1307
- e.tracking = tracking;
1308
1354
  e.drawsInk = slot.cluster.d !== null;
1309
1355
  return e;
1310
1356
  }
1311
- function mirrorEntries(entries, levels, mirror, runs) {
1312
- return entries.map((entry, pos) => {
1357
+ function mirrorEntries(entries2, levels, mirror, runs) {
1358
+ return entries2.map((entry, pos) => {
1313
1359
  const odd = levels[pos] % 2 === 1;
1314
1360
  if (entry.shaped !== null && entry.shaped.rtl !== odd) {
1315
1361
  const flipped = mirror(entry.cp);
@@ -1317,39 +1363,57 @@ function mirrorEntries(entries, levels, mirror, runs) {
1317
1363
  }
1318
1364
  if (!odd) return entry;
1319
1365
  if (entry.shaped !== null) return entry;
1320
- const cp = mirror(entry.cp);
1321
- if (cp === null || cp === entry.cp) return entry;
1322
- if (entry.font === null) return { ...entry, cp };
1366
+ const cp2 = mirror(entry.cp);
1367
+ if (cp2 === null || cp2 === entry.cp) return entry;
1368
+ if (entry.font === null) return { ...entry, cp: cp2 };
1323
1369
  const run = runs[entry.run];
1324
- const hit = resolveGlyph(entry.resolved, run.fontFamily, run.fontWeight, run.fontStyle, cp, false);
1370
+ const hit = resolveGlyph(entry.resolved, run.fontFamily, run.fontWeight, run.fontStyle, cp2, false);
1325
1371
  if (!hit) return entry;
1326
- return { ...entry, cp, resolved: hit.resolved, metrics: hit.metrics, font: hit.font, glyph: hit.glyph };
1372
+ return { ...entry, cp: cp2, resolved: hit.resolved, metrics: hit.metrics, font: hit.font, glyph: hit.glyph };
1327
1373
  });
1328
1374
  }
1329
1375
 
1330
1376
  // src/layout/lineBreak/wrapLines.ts
1331
- var hangs = (cp) => cp === 32 || cp === 9;
1377
+ var hangs = (cp2) => cp2 === 32 || cp2 === 9;
1332
1378
  function wrapLines(cps, maxWidth, widthOf) {
1333
1379
  const lines = [];
1334
- wrapSpans(cps, maxWidth, widthOf, (start, end, wrapped) => {
1335
- lines.push({ start, end, wrapped });
1380
+ wrapSpans(cps, { maxWidth }, widthOf, (start2, end, wrapped) => {
1381
+ lines.push({ start: start2, end, wrapped });
1336
1382
  });
1337
1383
  return lines;
1338
1384
  }
1339
- function wrapSpans(cps, maxWidth, widthOf, emit) {
1385
+ var breakScratch = new Uint8Array(64);
1386
+ var breakScratchTaken = false;
1387
+ var BREAK_SCRATCH_MAX = 1 << 16;
1388
+ function wrapSpans(cps, box, widthOf, emit) {
1389
+ const n = cps.length;
1390
+ if (!Number.isFinite(box.maxWidth)) {
1391
+ wrapAt(cps, box, widthOf, emit, null);
1392
+ } else if (breakScratchTaken || n >= BREAK_SCRATCH_MAX) {
1393
+ wrapAt(cps, box, widthOf, emit, lineBreakOpportunities(cps));
1394
+ } else {
1395
+ if (breakScratch.length < n + 1) breakScratch = new Uint8Array(Math.max(n + 1, breakScratch.length * 2));
1396
+ breakScratchTaken = true;
1397
+ try {
1398
+ wrapAt(cps, box, widthOf, emit, lineBreakOpportunitiesInto(cps, breakScratch));
1399
+ } finally {
1400
+ breakScratchTaken = false;
1401
+ }
1402
+ }
1403
+ }
1404
+ function wrapAt(cps, box, widthOf, emit, breaks) {
1340
1405
  const n = cps.length;
1341
- const breaks = Number.isFinite(maxWidth) ? lineBreakOpportunities(cps) : null;
1342
- let start = 0;
1406
+ let start2 = 0;
1343
1407
  let i = 0;
1344
1408
  while (i < n) {
1345
- const cp = cps[i];
1346
- if (isHardLineBreak(cp)) {
1347
- emit(start, i, false);
1348
- i += cp === 13 && cps[i + 1] === 10 ? 2 : 1;
1349
- start = i;
1409
+ const cp2 = cps[i];
1410
+ if (isHardLineBreak(cp2)) {
1411
+ emit(start2, i, false);
1412
+ i += cp2 === 13 && cps[i + 1] === 10 ? 2 : 1;
1413
+ start2 = i;
1350
1414
  continue;
1351
1415
  }
1352
- if (hangs(cp)) {
1416
+ if (hangs(cp2)) {
1353
1417
  i++;
1354
1418
  continue;
1355
1419
  }
@@ -1359,13 +1423,13 @@ function wrapSpans(cps, maxWidth, widthOf, emit) {
1359
1423
  if (!hangs(cps[j])) ink2 = j + 1;
1360
1424
  j++;
1361
1425
  }
1362
- if (breaks && i > start && widthOf(start, ink2) > maxWidth) {
1363
- emit(start, i, true);
1364
- start = i;
1426
+ if (breaks && i > start2 && widthOf(start2, ink2) > box.maxWidth) {
1427
+ emit(start2, i, true);
1428
+ start2 = i;
1365
1429
  }
1366
1430
  i = j;
1367
1431
  }
1368
- if (start < n) emit(start, n, false);
1432
+ if (start2 < n) emit(start2, n, false);
1369
1433
  }
1370
1434
 
1371
1435
  // src/layout/layoutLines.ts
@@ -1383,21 +1447,21 @@ var broken = [];
1383
1447
  var brokenCount = 0;
1384
1448
  var linePool = [];
1385
1449
  var LINE_POOL_MAX = 64;
1386
- function flatWidth(start, end) {
1387
- return cur[start].advance + cur[start].tracking + sum[end] - sum[start + 1];
1450
+ function flatWidth(start2, end) {
1451
+ return cur[start2].advance + cur[start2].tracking + sum[end] - sum[start2 + 1];
1388
1452
  }
1389
1453
  var stopOf = (k) => cur[k].tab;
1390
- function tabbedWidth(start, end) {
1391
- return widthWithTabs(start, end, tabs, stopOf, flatWidth);
1454
+ function tabbedWidth(start2, end) {
1455
+ return widthWithTabs(start2, end, tabs, stopOf, flatWidth);
1392
1456
  }
1393
- function pushSpan(start, end, wrapped) {
1457
+ function pushSpan(start2, end, wrapped) {
1394
1458
  const i = brokenCount++;
1395
1459
  let line = linePool[i];
1396
1460
  if (line === void 0) {
1397
1461
  line = { start: 0, entries: [], width: 0.5, height: 0.5, ascent: 0.5, wrapped: false, srcEnd: 0, end: 0 };
1398
1462
  if (i < LINE_POOL_MAX) linePool[i] = line;
1399
1463
  }
1400
- line.start = start;
1464
+ line.start = start2;
1401
1465
  line.end = end;
1402
1466
  line.wrapped = wrapped;
1403
1467
  line.width = 0;
@@ -1406,34 +1470,35 @@ function pushSpan(start, end, wrapped) {
1406
1470
  line.srcEnd = 0;
1407
1471
  broken[i] = line;
1408
1472
  }
1409
- function breakLines(entries, cps, maxWidth, lineHeight) {
1410
- if (sum.length < entries.length + 1) sum = doubles(entries.length + 1);
1473
+ function breakLines(entries2, cps, opts) {
1474
+ const { lineHeight } = opts;
1475
+ if (sum.length < entries2.length + 1) sum = doubles(entries2.length + 1);
1411
1476
  sum[0] = 0;
1412
1477
  let tabCount = 0;
1413
1478
  brokenCount = 0;
1414
- for (let k = 0; k < entries.length; k++) {
1415
- const e = entries[k];
1479
+ for (let k = 0; k < entries2.length; k++) {
1480
+ const e = entries2[k];
1416
1481
  sum[k + 1] = sum[k] + e.kerningBefore + e.advance + e.tracking;
1417
1482
  if (e.tab) tabs[tabCount++] = k;
1418
1483
  }
1419
1484
  tabs.length = tabCount;
1420
- cur = entries;
1421
- wrapSpans(cps, maxWidth, tabCount === 0 ? flatWidth : tabbedWidth, pushSpan);
1485
+ cur = entries2;
1486
+ wrapSpans(cps, opts, tabCount === 0 ? flatWidth : tabbedWidth, pushSpan);
1422
1487
  cur = NONE;
1423
1488
  broken.length = brokenCount;
1424
1489
  for (let i = 0; i < broken.length; i++) {
1425
1490
  const line = broken[i];
1426
1491
  if (line.start === line.end) {
1427
- const brk = entries[line.end];
1492
+ const brk = entries2[line.end];
1428
1493
  line.height = brk.lineSize * lineHeight;
1429
1494
  line.ascent = aboveBaseline(brk, lineHeight);
1430
1495
  line.srcEnd = brk.srcIndex;
1431
- line.entries.length = 0;
1496
+ truncate(line.entries, 0);
1432
1497
  continue;
1433
1498
  }
1434
1499
  const out = line.entries;
1435
1500
  for (let k = line.start; k < line.end; k++) {
1436
- const e = entries[k];
1501
+ const e = entries2[k];
1437
1502
  if (k === line.start) e.kerningBefore = 0;
1438
1503
  if (e.tab) e.advance = tabAdvance(line.width, e.tab.stop, e.tab.min);
1439
1504
  out[k - line.start] = e;
@@ -1442,25 +1507,25 @@ function breakLines(entries, cps, maxWidth, lineHeight) {
1442
1507
  line.ascent = Math.max(line.ascent, aboveBaseline(e, lineHeight));
1443
1508
  }
1444
1509
  out.length = line.end - line.start;
1445
- line.srcEnd = entries[line.end - 1].srcEnd;
1510
+ line.srcEnd = entries2[line.end - 1].srcEnd;
1446
1511
  }
1447
1512
  return broken;
1448
1513
  }
1449
- function hungWidth(entries) {
1514
+ function hungWidth(entries2) {
1450
1515
  let hung = 0;
1451
- for (let k = entries.length - 1; k >= 0 && hangs(entries[k].cp); k--) {
1452
- const e = entries[k];
1516
+ for (let k = entries2.length - 1; k >= 0 && hangs(entries2[k].cp); k--) {
1517
+ const e = entries2[k];
1453
1518
  hung += e.kerningBefore + e.advance + e.tracking;
1454
1519
  }
1455
1520
  return hung;
1456
1521
  }
1457
- function wordGaps(entries) {
1522
+ function wordGaps(entries2) {
1458
1523
  const gaps = /* @__PURE__ */ new Set();
1459
1524
  let first = 0;
1460
- while (first < entries.length && entries[first].cp === 32) first++;
1461
- let last2 = entries.length - 1;
1462
- while (last2 > first && entries[last2].cp === 32) last2--;
1463
- for (let k = first + 1; k < last2; k++) if (entries[k].cp === 32) gaps.add(k);
1525
+ while (first < entries2.length && entries2[first].cp === 32) first++;
1526
+ let last2 = entries2.length - 1;
1527
+ while (last2 > first && entries2[last2].cp === 32) last2--;
1528
+ for (let k = first + 1; k < last2; k++) if (entries2[k].cp === 32) gaps.add(k);
1464
1529
  return gaps;
1465
1530
  }
1466
1531
  function alignOffset(align, alignWidth, inkWidth) {
@@ -1471,16 +1536,17 @@ function alignOffset(align, alignWidth, inkWidth) {
1471
1536
  }
1472
1537
  var NO_GAPS = /* @__PURE__ */ new Set();
1473
1538
  var placed = { box: null, inkWidth: 0.5 };
1474
- function placeLine(line, y, order, align, alignWidth, justify, cols, start, at) {
1475
- const { entries } = line;
1476
- const hung = hungWidth(entries);
1539
+ function placeLine(line, y, order, align, opts, cols, start2, at) {
1540
+ const { alignWidth, justify } = opts;
1541
+ const { entries: entries2 } = line;
1542
+ const hung = hungWidth(entries2);
1477
1543
  const inkWidth = line.width - hung;
1478
1544
  const slack = alignWidth - inkWidth;
1479
- const gaps = justify && line.wrapped && Number.isFinite(alignWidth) && slack > 0 ? wordGaps(entries) : NO_GAPS;
1545
+ const gaps = justify && line.wrapped && Number.isFinite(alignWidth) && slack > 0 ? wordGaps(entries2) : NO_GAPS;
1480
1546
  const gapExtra = gaps.size > 0 ? slack / gaps.size : 0;
1481
1547
  const spread = gapExtra * gaps.size;
1482
1548
  const inkLeft = spread > 0 ? 0 : alignOffset(align, alignWidth, inkWidth);
1483
- const hangsLeft = hung > 0 && order.levels[entries.length - 1] % 2 === 1;
1549
+ const hangsLeft = hung > 0 && order.levels[entries2.length - 1] % 2 === 1;
1484
1550
  const left = hangsLeft ? inkLeft - hung : inkLeft;
1485
1551
  const lineBaselineY = y + line.ascent;
1486
1552
  let penX = left;
@@ -1490,21 +1556,21 @@ function placeLine(line, y, order, align, alignWidth, justify, cols, start, at)
1490
1556
  const { visual } = order;
1491
1557
  for (let v = 0; v < visual.length; v++) {
1492
1558
  const pos = visual[v];
1493
- const e = entries[pos];
1494
- const kern = prevPos < 0 ? 0 : pos === prevPos + 1 ? e.kerningBefore : prevPos === pos + 1 ? entries[prevPos].kerningBefore : 0;
1495
- if (kern !== 0) cols.widen(start + (wide >= 0 ? wide : prevPos), kern);
1559
+ const e = entries2[pos];
1560
+ const kern = prevPos < 0 ? 0 : pos === prevPos + 1 ? e.kerningBefore : prevPos === pos + 1 ? entries2[prevPos].kerningBefore : 0;
1561
+ if (kern !== 0) cols.widen(start2 + (wide >= 0 ? wide : prevPos), kern);
1496
1562
  penX += kern;
1497
1563
  prevPos = pos;
1498
1564
  const step = e.advance + e.tracking + (gaps.has(pos) ? gapExtra : 0);
1499
1565
  if (step !== 0) wide = pos;
1500
- cols.set(start + pos, e, order.levels[pos], penX, step, lineBaselineY - e.baselineShift);
1501
- cols.setVisual(start, slot++, pos);
1566
+ cols.set(start2 + pos, e, order.levels[pos], penX, step, lineBaselineY - e.baselineShift);
1567
+ cols.setVisual(start2, slot++, pos);
1502
1568
  penX += step;
1503
1569
  }
1504
1570
  const x1 = left + line.width + spread;
1505
1571
  const y1 = y + line.height;
1506
1572
  const prior = cols.boxes[at];
1507
- placed.box = prior !== void 0 ? prior.refill(left, y, x1, y1, lineBaselineY, line.srcEnd, spread > 0, start, entries.length) : cols.boxes[at] = new LineBox(left, y, x1, y1, lineBaselineY, line.srcEnd, spread > 0, cols, start, entries.length);
1573
+ placed.box = prior !== void 0 ? prior.refill(left, y, x1, y1, lineBaselineY, line.srcEnd, spread > 0, start2, entries2.length) : cols.boxes[at] = new LineBox(left, y, x1, y1, lineBaselineY, line.srcEnd, spread > 0, cols, start2, entries2.length);
1508
1574
  placed.inkWidth = inkWidth + spread;
1509
1575
  return placed;
1510
1576
  }
@@ -1544,6 +1610,11 @@ var ENTRY_POOL = [];
1544
1610
  var ENTRIES = [];
1545
1611
  var CPS = [];
1546
1612
  var OPTS = resolveLayoutOpts({ maxWidth: 0, lineHeight: 0, align: "left" });
1613
+ var LINE_OPTS = { maxWidth: 0.5, alignWidth: 0.5, lineHeight: 0.5, justify: false };
1614
+ var hardBreakAt = (i) => isHardLineBreak(CPS[i]);
1615
+ function mirrorLine(bidi, entries2, levels, runs) {
1616
+ return mirrorEntries(entries2, levels, (cp2) => bidi.mirror(cp2), runs);
1617
+ }
1547
1618
  function layoutRuns(runs, opts) {
1548
1619
  const {
1549
1620
  maxWidth,
@@ -1556,42 +1627,49 @@ function layoutRuns(runs, opts) {
1556
1627
  direction,
1557
1628
  tabSize
1558
1629
  } = resolveLayoutOpts(opts, OPTS);
1559
- const entries = measureRuns(runs, tabSize, ENTRY_POOL, ENTRIES, shaper);
1630
+ const entries2 = measureRuns(runs, tabSize, ENTRY_POOL, ENTRIES, shaper);
1560
1631
  const cps = CPS;
1561
- cps.length = entries.length;
1562
- for (let k = 0; k < entries.length; k++) cps[k] = entries[k].cp;
1563
- const paragraphs = bidi ? analyzeParagraphs(bidi, cps, (i) => isHardLineBreak(cps[i]), direction) : null;
1632
+ cps.length = entries2.length;
1633
+ for (let k = 0; k < entries2.length; k++) cps[k] = entries2[k].cp;
1634
+ const paragraphs = bidi ? analyzeParagraphs(bidi, cps, hardBreakAt, direction) : null;
1564
1635
  if (!bidi) warnRtlWithoutEngine(cps);
1565
- const broken2 = breakLines(entries, cps, maxWidth, lineHeight);
1636
+ const lineOpts = LINE_OPTS;
1637
+ lineOpts.maxWidth = maxWidth;
1638
+ lineOpts.alignWidth = alignWidth;
1639
+ lineOpts.lineHeight = lineHeight;
1640
+ lineOpts.justify = justify;
1641
+ const broken2 = breakLines(entries2, cps, lineOpts);
1566
1642
  let size = 0;
1567
1643
  for (let i = 0; i < broken2.length; i++) size += broken2[i].entries.length;
1568
1644
  const cols = broken2.length > 0 ? LayoutColumns.take(size) : null;
1569
1645
  const lines = cols !== null ? cols.boxes : [];
1570
1646
  let y = 0;
1571
1647
  let width = 0;
1572
- let start = 0;
1648
+ let start2 = 0;
1573
1649
  for (let i = 0; i < broken2.length; i++) {
1574
1650
  const line = broken2[i];
1575
1651
  const order = orderLine(paragraphs, line.start, line.entries.length);
1576
- if (bidi) line.entries = mirrorEntries(line.entries, order.levels, (cp) => bidi.mirror(cp), runs);
1652
+ if (bidi) line.entries = mirrorLine(bidi, line.entries, order.levels, runs);
1577
1653
  const reads = direction !== "auto" ? direction : directionOfLevel(line.entries.length > 0 ? paragraphs?.baseLevel(line.start) ?? 0 : 0);
1578
1654
  const placed2 = placeLine(
1579
1655
  line,
1580
1656
  y,
1581
1657
  order,
1582
1658
  resolveAlign(align, reads),
1583
- alignWidth,
1584
- justify,
1659
+ lineOpts,
1585
1660
  cols,
1586
- start,
1661
+ start2,
1587
1662
  i
1588
1663
  );
1589
- start += line.entries.length;
1664
+ start2 += line.entries.length;
1590
1665
  width = Math.max(width, placed2.inkWidth);
1591
1666
  y += line.height;
1592
1667
  }
1593
1668
  lines.length = broken2.length;
1594
- return { lines, bounds: { width, height: y } };
1669
+ if (cols === null) return { lines, bounds: { width, height: y } };
1670
+ cols.bounds.width = width;
1671
+ cols.bounds.height = y;
1672
+ return cols.laid ??= { lines, bounds: cols.bounds };
1595
1673
  }
1596
1674
 
1597
1675
  // src/layout/glyphQuads.ts
@@ -1767,7 +1845,7 @@ var SPARE_GROUPS_MAX = 1024;
1767
1845
  function giveGroup(group) {
1768
1846
  if (spareGroups.length >= SPARE_GROUPS_MAX || group.stroke !== void 0 || group.colorGlyphs !== void 0 || quadsViewed.has(group)) return;
1769
1847
  group.quadData = NO_QUADS;
1770
- group.glyphs.length = 0;
1848
+ truncate(group.glyphs, 0);
1771
1849
  spareGroups.push(group);
1772
1850
  }
1773
1851
  var slots = [];
@@ -1789,7 +1867,7 @@ function takeGroups(out) {
1789
1867
  out.push(slots[i].group);
1790
1868
  slots[i].group = null;
1791
1869
  }
1792
- slots.length = 0;
1870
+ truncate(slots, 0);
1793
1871
  last.group = null;
1794
1872
  }
1795
1873
 
@@ -1820,6 +1898,9 @@ function runFaces(runs) {
1820
1898
  return (run) => faces[run];
1821
1899
  }
1822
1900
  var isDecorated = (r) => r.underline || r.strikethrough || r.overline;
1901
+ function rulePaintOf(runs) {
1902
+ return (run) => fillHasInk(runs[run].fill) ? paintKey(runs[run].fill.paint) : null;
1903
+ }
1823
1904
  var cursor = blankGlyph();
1824
1905
  var scratch = [];
1825
1906
  var SCRATCH_MAX2 = 1 << 14;
@@ -1828,10 +1909,10 @@ function paintRuns(laid, runs, opts = {}) {
1828
1909
  }
1829
1910
  function paintRunsInto(laid, runs, opts, out) {
1830
1911
  const { groups, decorations } = out;
1831
- groups.length = 0;
1832
- decorations.length = 0;
1912
+ truncate(groups, 0);
1913
+ truncate(decorations, 0);
1833
1914
  const faceOf = runs.some(isDecorated) ? runFaces(runs) : null;
1834
- const rulePaint = faceOf && ((run) => fillHasInk(runs[run].fill) ? paintKey(runs[run].fill.paint) : null);
1915
+ const rulePaint = faceOf && rulePaintOf(runs);
1835
1916
  let glyphCount = 0;
1836
1917
  const { lines } = laid;
1837
1918
  for (let i = 0; i < lines.length; i++) glyphCount += LineBox.count(lines[i]);
@@ -1843,15 +1924,15 @@ function paintRunsInto(laid, runs, opts, out) {
1843
1924
  for (let li = 0; li < lines.length; li++) {
1844
1925
  const line = lines[li];
1845
1926
  const cols = LineBox.columns(line);
1846
- const start = cols !== null ? LineBox.start(line) : 0;
1927
+ const start2 = cols !== null ? LineBox.start(line) : 0;
1847
1928
  const count = LineBox.count(line);
1848
1929
  const rules = faceOf && lineDecorations(runs, faceOf, rulePaint);
1849
1930
  for (let slot = 0; slot < count; slot++) {
1850
- const pos = cols !== null ? cols.visual(start, slot) : -1;
1851
- const g = cols !== null ? cols.glyph(start, pos, cursor) : line.glyphs[slot];
1931
+ const pos = cols !== null ? cols.visual(start2, slot) : -1;
1932
+ const g = cols !== null ? cols.glyph(start2, pos, cursor) : line.glyphs[slot];
1852
1933
  rules?.add(g);
1853
1934
  const run = runs[g.run];
1854
- const inks = cols !== null ? cols.drawsInk(start, pos) : line.cells[g.cell].drawsInk;
1935
+ const inks = cols !== null ? cols.drawsInk(start2, pos) : line.cells[g.cell].drawsInk;
1855
1936
  const outline = inks ? glyphOutlineD(g, run, opts.outlineMinSize) : null;
1856
1937
  if (!fillHasInk(run.fill) && !(outline !== null && strokeHasInk(run.stroke))) continue;
1857
1938
  if (outline !== null) {
@@ -1892,9 +1973,51 @@ function paintRunsInto(laid, runs, opts, out) {
1892
1973
  takeGroups(groups);
1893
1974
  return out;
1894
1975
  }
1976
+
1977
+ // src/layout/runsEntries.ts
1978
+ var entries = /* @__PURE__ */ new WeakMap();
1979
+ var spare2 = [];
1980
+ var SPARE_MAX2 = 1024;
1981
+ function entryOf(runs) {
1982
+ return entries.get(runs);
1983
+ }
1984
+ function entryFor(runs) {
1985
+ let e = entries.get(runs);
1986
+ if (e === void 0) {
1987
+ e = spare2.pop() ?? {
1988
+ generation: -1,
1989
+ variants: [],
1990
+ painting: null,
1991
+ paintedOn: null,
1992
+ paintedStamp: 0,
1993
+ paintMin: void 0
1994
+ };
1995
+ entries.set(runs, e);
1996
+ }
1997
+ return e;
1998
+ }
1999
+ function dropEntry(runs) {
2000
+ const e = entries.get(runs);
2001
+ if (e !== void 0) entries.delete(runs);
2002
+ return e;
2003
+ }
2004
+ function giveEntry(e) {
2005
+ e.generation = -1;
2006
+ e.painting = null;
2007
+ e.paintedOn = null;
2008
+ e.paintMin = void 0;
2009
+ if (spare2.length < SPARE_MAX2) spare2.push(e);
2010
+ }
2011
+ function _resetRunsEntriesForTests() {
2012
+ entries = /* @__PURE__ */ new WeakMap();
2013
+ }
2014
+
2015
+ // src/layout/paintCache.ts
1895
2016
  var PAINT_CACHE_VARIANT_LIMIT = 32;
1896
- var own = /* @__PURE__ */ new WeakMap();
1897
2017
  var shared2 = /* @__PURE__ */ new WeakMap();
2018
+ function stampOf(laid) {
2019
+ return laid.lines.length > 0 ? LineBox.columns(laid.lines[0])?.stamp ?? 0 : 0;
2020
+ }
1898
2021
  function listOf(laid) {
1899
2022
  const cols = laid.lines.length > 0 ? LineBox.columns(laid.lines[0]) : null;
1900
2023
  if (cols !== null) return cols.paintings;
@@ -1902,10 +2025,10 @@ function listOf(laid) {
1902
2025
  if (list === void 0) shared2.set(laid, list = []);
1903
2026
  return list;
1904
2027
  }
1905
- var spare2 = [];
1906
- var SPARE_MAX2 = 1024;
2028
+ var spare3 = [];
2029
+ var SPARE_MAX3 = 1024;
1907
2030
  function painting(laid, runs, opts, generation, key, bucket) {
1908
- const p = spare2.pop();
2031
+ const p = spare3.pop();
1909
2032
  if (p === void 0) {
1910
2033
  return {
1911
2034
  generation,
@@ -1953,13 +2076,15 @@ function keyOf(p) {
1953
2076
  function cachedPaintRuns(laid, runs, opts = {}, use = "keep") {
1954
2077
  const generation = glyphGeneration();
1955
2078
  const min = opts.outlineMinSize;
1956
- const mine = own.get(runs);
1957
- const mineHere = mine !== void 0 && mine.laid === laid && mine.painting.generation === generation;
1958
- if (mineHere && mine.min === min) return handOut(mine.painting, use);
2079
+ const mine = entryOf(runs);
2080
+ const stamp = stampOf(laid);
2081
+ const own = mine !== void 0 && mine.paintedOn === laid && mine.paintedStamp === stamp ? mine.painting : null;
2082
+ const mineHere = own !== null && own.generation === generation;
2083
+ if (mineHere && mine.paintMin === min) return handOut(own, use);
1959
2084
  const bucket = outlineBucket(runs, min);
1960
- if (mineHere && mine.painting.bucket === bucket) {
1961
- mine.min = min;
1962
- return handOut(mine.painting, use);
2085
+ if (mineHere && own.bucket === bucket) {
2086
+ mine.paintMin = min;
2087
+ return handOut(own, use);
1963
2088
  }
1964
2089
  const list = listOf(laid);
1965
2090
  let p = null;
@@ -1978,34 +2103,17 @@ function cachedPaintRuns(laid, runs, opts = {}, use = "keep") {
1978
2103
  }
1979
2104
  if (list.length >= PAINT_CACHE_VARIANT_LIMIT) list.shift();
1980
2105
  list.push(p);
1981
- if (mine !== void 0) {
1982
- mine.laid = laid;
1983
- mine.painting = p;
1984
- mine.min = min;
1985
- } else {
1986
- const o = spareOwn.pop();
1987
- if (o === void 0) {
1988
- own.set(runs, { laid, painting: p, min });
1989
- } else {
1990
- o.laid = laid;
1991
- o.painting = p;
1992
- o.min = min;
1993
- own.set(runs, o);
1994
- }
1995
- }
2106
+ const e = mine ?? entryFor(runs);
2107
+ e.paintedOn = laid;
2108
+ e.paintedStamp = stamp;
2109
+ e.painting = p;
2110
+ e.paintMin = min;
1996
2111
  return handOut(p, use);
1997
2112
  }
1998
2113
  function handOut(p, use) {
1999
2114
  if (use === "keep") p.pinned = true;
2000
2115
  return p.painted;
2001
2116
  }
2002
- function forgetPaintRuns(runs) {
2003
- const mine = own.get(runs);
2004
- if (mine === void 0) return;
2005
- own.delete(runs);
2006
- if (spareOwn.length < SPARE_MAX2) spareOwn.push(mine);
2007
- }
2008
- var spareOwn = [];
2009
2117
  function recyclePaintings(laid) {
2010
2118
  const cols = laid.lines.length > 0 ? LineBox.columns(laid.lines[0]) : null;
2011
2119
  let list;
@@ -2026,17 +2134,16 @@ function recyclePaintings(laid) {
2026
2134
  group.quadData = NO_QUADS;
2027
2135
  giveGroup(group);
2028
2136
  }
2029
- groups.length = 0;
2030
- p.painted.decorations.length = 0;
2137
+ truncate(groups, 0);
2138
+ truncate(p.painted.decorations, 0);
2031
2139
  p.runs = null;
2032
2140
  p.key = null;
2033
- if (spare2.length < SPARE_MAX2) spare2.push(p);
2141
+ if (spare3.length < SPARE_MAX3) spare3.push(p);
2034
2142
  }
2035
- list.length = 0;
2143
+ truncate(list, 0);
2036
2144
  }
2037
2145
  var LAYOUT_CACHE_VARIANT_LIMIT = 8;
2038
2146
  var LAYOUT_CACHE_STRUCTURAL_LIMIT = 64;
2039
- var cache2 = /* @__PURE__ */ new WeakMap();
2040
2147
  var structural = [];
2041
2148
  var structuralGeneration = -1;
2042
2149
  var clock = 0;
@@ -2058,9 +2165,8 @@ function release(laid) {
2058
2165
  recyclePaintings(laid);
2059
2166
  cols.recycle();
2060
2167
  }
2061
- var spareEntries = [];
2062
2168
  var spareVariants = [];
2063
- var SPARE_MAX3 = 1024;
2169
+ var SPARE_MAX4 = 1024;
2064
2170
  function disown(vs) {
2065
2171
  for (let i = 0; i < vs.length; i++) {
2066
2172
  const v = vs[i];
@@ -2070,9 +2176,9 @@ function disown(vs) {
2070
2176
  release(v.laid);
2071
2177
  }
2072
2178
  v.laid = NO_LAYOUT;
2073
- if (spareVariants.length < SPARE_MAX3) spareVariants.push(v);
2179
+ if (spareVariants.length < SPARE_MAX4) spareVariants.push(v);
2074
2180
  }
2075
- vs.length = 0;
2181
+ truncate(vs, 0);
2076
2182
  }
2077
2183
  var NO_LAYOUT = { lines: [], bounds: { width: 0, height: 0 } };
2078
2184
  function fill(v, opts, bidi, laid) {
@@ -2089,8 +2195,8 @@ function fill(v, opts, bidi, laid) {
2089
2195
  return v;
2090
2196
  }
2091
2197
  function variant(opts, bidi, laid) {
2092
- const spare3 = spareVariants.pop();
2093
- if (spare3 !== void 0) return fill(spare3, opts, bidi, laid);
2198
+ const spare4 = spareVariants.pop();
2199
+ if (spare4 !== void 0) return fill(spare4, opts, bidi, laid);
2094
2200
  return {
2095
2201
  maxWidth: opts.maxWidth,
2096
2202
  alignWidth: opts.alignWidth ?? opts.maxWidth,
@@ -2131,7 +2237,7 @@ function runsHash(runs) {
2131
2237
  h = mix(h, r.fontFeatures?.length ?? 0);
2132
2238
  h = mixStr(h, r.lang ?? "");
2133
2239
  }
2134
- return h;
2240
+ return h & 1073741823;
2135
2241
  }
2136
2242
  function sameNums(a, b) {
2137
2243
  if (a === b) return true;
@@ -2159,12 +2265,8 @@ function sameRuns(a, b) {
2159
2265
  function cachedLayoutRuns(runs, opts, use = "keep") {
2160
2266
  const fonts = faceGeneration();
2161
2267
  const bidi = layoutBidi(opts);
2162
- let entry = cache2.get(runs);
2163
- if (entry === void 0) {
2164
- entry = spareEntries.pop() ?? { generation: fonts, variants: [] };
2165
- entry.generation = fonts;
2166
- cache2.set(runs, entry);
2167
- } else if (entry.generation !== fonts) {
2268
+ const entry = entryFor(runs);
2269
+ if (entry.generation !== fonts) {
2168
2270
  entry.generation = fonts;
2169
2271
  disown(entry.variants);
2170
2272
  } else {
@@ -2186,12 +2288,10 @@ function cachedLayoutRuns(runs, opts, use = "keep") {
2186
2288
  return handOut2(laid, use);
2187
2289
  }
2188
2290
  function dropRuns(runs) {
2189
- const entry = cache2.get(runs);
2190
- forgetPaintRuns(runs);
2291
+ const entry = dropEntry(runs);
2191
2292
  if (entry === void 0) return;
2192
- cache2.delete(runs);
2193
2293
  disown(entry.variants);
2194
- if (spareEntries.length < SPARE_MAX3) spareEntries.push(entry);
2294
+ giveEntry(entry);
2195
2295
  }
2196
2296
  function structuralLayout(runs, opts, bidi, fonts) {
2197
2297
  if (structuralGeneration !== fonts) {
@@ -2245,12 +2345,12 @@ function unlist(s) {
2245
2345
  release(s.laid);
2246
2346
  }
2247
2347
  function _resetLayoutCacheForTests() {
2248
- cache2 = /* @__PURE__ */ new WeakMap();
2348
+ _resetRunsEntriesForTests();
2249
2349
  structural = [];
2250
2350
  structuralGeneration = -1;
2251
2351
  clock = 0;
2252
2352
  }
2253
2353
 
2254
- export { BREAK_ALLOWED, BREAK_MANDATORY, DECORATION_KINDS, DEFAULT_DECORATION_METRICS, DEFAULT_TAB_SIZE, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, QUAD_STRIDE, _resetBidiWarningForTests, _resetLayoutCacheForTests, cachedLayoutRuns, cachedPaintRuns, decorationMetrics, decorationRule, dropRuns, emboldenGlyph, emboldenGlyphD, faceOutlineD, fontString, graphemeCount, hangs, hasGlyphQuad, isHardLineBreak, layoutRuns, lineBreakOpportunities, lineDecorations, paintRuns, paragraphDirection, registerBidi, registerShaper, resolveAlign, resolveTextStyle, runFaces, shapesWithFeature, syntheticBoldOffset, tabAdvance, widthWithTabs, wrapLines };
2255
- //# sourceMappingURL=chunk-FODNMBHI.js.map
2256
- //# sourceMappingURL=chunk-FODNMBHI.js.map
2354
+ export { BREAK_ALLOWED, BREAK_MANDATORY, DECORATION_KINDS, DEFAULT_DECORATION_METRICS, DEFAULT_TAB_SIZE, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, QUAD_STRIDE, _resetBidiWarningForTests, _resetLayoutCacheForTests, cachedLayoutRuns, cachedPaintRuns, decorationMetrics, decorationRule, dropRuns, emboldenGlyph, emboldenGlyphD, faceOutlineD, fontString, graphemeCount, hangs, hasGlyphQuad, isHardLineBreak, layoutRuns, lineBreakOpportunities, lineDecorations, paintRuns, paragraphDirection, registerBidi, registerShaper, resolveAlign, resolveTextStyle, resolveTextStyleInto, runFaces, shapesWithFeature, syntheticBoldOffset, tabAdvance, widthWithTabs, wrapLines };
2355
+ //# sourceMappingURL=chunk-5BJN6MC6.js.map
2356
+ //# sourceMappingURL=chunk-5BJN6MC6.js.map