@weasel-js/text 1.9.3 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,29 @@ var __export = (target, all) => {
6
6
  for (var name in all)
7
7
  __defProp(target, name, { get: all[name], enumerable: true });
8
8
  };
9
+
10
+ // src/layout/tabStops.ts
11
+ var DEFAULT_TAB_SIZE = 8;
12
+ function tabAdvance(x, stop, min) {
13
+ if (!(stop > 0)) return 0;
14
+ const toNext = (Math.floor(x / stop) + 1) * stop - x;
15
+ return toNext < min ? toNext + stop : toNext;
16
+ }
17
+ function widthWithTabs(start, end, tabs, stopOf, flatWidth) {
18
+ let x = 0;
19
+ let from = start;
20
+ for (const t of tabs) {
21
+ if (t < start) continue;
22
+ if (t >= end) break;
23
+ if (t > from) x += flatWidth(from, t);
24
+ const { stop, min } = stopOf(t);
25
+ x += tabAdvance(x, stop, min);
26
+ from = t + 1;
27
+ }
28
+ return from < end ? x + flatWidth(from, end) : x;
29
+ }
30
+
31
+ // src/textStyle.ts
9
32
  function resolveAlign(align, direction) {
10
33
  if (align === "start" || align === "justify") return direction === "rtl" ? "right" : "left";
11
34
  if (align === "end") return direction === "rtl" ? "left" : "right";
@@ -24,8 +47,9 @@ var DEFAULT_TEXT_STYLE = {
24
47
  fontWeight: 400,
25
48
  fontStyle: "normal",
26
49
  align: "left",
27
- direction: "ltr",
50
+ direction: "auto",
28
51
  lineHeight: 1.2,
52
+ tabSize: DEFAULT_TAB_SIZE,
29
53
  wrap: false,
30
54
  fill: DEFAULT_FILL,
31
55
  caretColor: paintColor(DEFAULT_FILL),
@@ -67,6 +91,7 @@ function resolveTextStyle(style, paint, scale = 1) {
67
91
  align: style.align ?? DEFAULT_TEXT_STYLE.align,
68
92
  direction: style.direction ?? DEFAULT_TEXT_STYLE.direction,
69
93
  lineHeight: style.lineHeight ?? DEFAULT_TEXT_STYLE.lineHeight,
94
+ tabSize: style.tabSize ?? DEFAULT_TEXT_STYLE.tabSize,
70
95
  wrap: style.wrap ?? DEFAULT_TEXT_STYLE.wrap,
71
96
  fill,
72
97
  caretColor,
@@ -468,8 +493,112 @@ function lineBreakOpportunities(cps) {
468
493
  }
469
494
  }
470
495
 
496
+ // src/layout/bidiRegistry.ts
497
+ var registered = null;
498
+ function registerBidi(engine) {
499
+ registered = engine;
500
+ return () => {
501
+ if (registered === engine) registered = null;
502
+ };
503
+ }
504
+ function registeredBidi() {
505
+ return registered;
506
+ }
507
+ function directionOfLevel(level) {
508
+ return level % 2 === 1 ? "rtl" : "ltr";
509
+ }
510
+ function paragraphDirection(text, direction = "auto", engine = registered) {
511
+ if (direction !== "auto") return direction;
512
+ if (engine === null) return "ltr";
513
+ const cps = [];
514
+ for (const ch of text) {
515
+ const cp = ch.codePointAt(0);
516
+ if (isHardLineBreak(cp)) break;
517
+ cps.push(cp);
518
+ }
519
+ return directionOfLevel(engine.baseLevel(engine.analyze(cps, "auto")));
520
+ }
521
+
522
+ // src/layout/bidiLines.ts
523
+ function analyzeParagraphs(engine, codePoints, isBreak, direction) {
524
+ const paragraphs = [];
525
+ let start = 0;
526
+ for (let i = 0; i <= codePoints.length; i++) {
527
+ if (i < codePoints.length && !isBreak(i)) continue;
528
+ if (i > start) {
529
+ paragraphs.push({ start, end: i, analysis: engine.analyze(codePoints.slice(start, i), direction) });
530
+ }
531
+ start = i + 1;
532
+ }
533
+ const holding = (at) => {
534
+ let lo = 0;
535
+ let hi = paragraphs.length - 1;
536
+ while (lo < hi) {
537
+ const mid = lo + hi >> 1;
538
+ if (paragraphs[mid].end <= at) lo = mid + 1;
539
+ else hi = mid;
540
+ }
541
+ const p = paragraphs[lo];
542
+ return p !== void 0 && at >= p.start && at < p.end ? p : void 0;
543
+ };
544
+ return {
545
+ reorder(from, to) {
546
+ const p = holding(from);
547
+ if (p === void 0 || to > p.end) return { order: [], levels: [] };
548
+ const r = engine.reorder(p.analysis, from - p.start, to - p.start);
549
+ return { order: r.order.map((i) => i + p.start), levels: r.levels };
550
+ },
551
+ baseLevel(at) {
552
+ const p = holding(at);
553
+ return p === void 0 ? 0 : engine.baseLevel(p.analysis);
554
+ }
555
+ };
556
+ }
557
+ function restoreRemoved(order, count, rtl) {
558
+ if (order.length === count) return [...order];
559
+ const kept = new Set(order);
560
+ const leading = [];
561
+ const following = /* @__PURE__ */ new Map();
562
+ let anchor = -1;
563
+ let firstKept = -1;
564
+ for (let k = 0; k < count; k++) {
565
+ if (kept.has(k)) {
566
+ anchor = k;
567
+ if (firstKept < 0) firstKept = k;
568
+ } else if (anchor < 0) {
569
+ leading.push(k);
570
+ } else {
571
+ const list = following.get(anchor);
572
+ if (list) list.push(k);
573
+ else following.set(anchor, [k]);
574
+ }
575
+ }
576
+ if (firstKept < 0) return leading;
577
+ const out = [];
578
+ for (const p of order) {
579
+ const after = following.get(p) ?? [];
580
+ const before = p === firstKept ? leading : [];
581
+ if (rtl(p)) out.push(...[...after].reverse(), p, ...[...before].reverse());
582
+ else out.push(...before, p, ...after);
583
+ }
584
+ return out;
585
+ }
586
+
587
+ // src/layout/refId.ts
588
+ var nextRefId = 1;
589
+ var refIds = /* @__PURE__ */ new WeakMap();
590
+ function refId(o) {
591
+ let id = refIds.get(o);
592
+ if (id === void 0) {
593
+ id = nextRefId++;
594
+ refIds.set(o, id);
595
+ }
596
+ return id;
597
+ }
598
+
471
599
  // src/measure/graphemes.ts
472
600
  var segmenter;
601
+ var NEEDS_SEGMENTING = /[\r\u0300-\uffff]/;
473
602
  function graphemeSegmenter() {
474
603
  if (segmenter === void 0) {
475
604
  segmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
@@ -478,6 +607,10 @@ function graphemeSegmenter() {
478
607
  }
479
608
  function graphemeEnds(text) {
480
609
  const out = [];
610
+ if (!NEEDS_SEGMENTING.test(text)) {
611
+ for (let i = 1; i <= text.length; i++) out.push(i);
612
+ return out;
613
+ }
481
614
  const seg = graphemeSegmenter();
482
615
  if (seg) {
483
616
  for (const s of seg.segment(text)) out.push(s.index + s.segment.length);
@@ -492,7 +625,7 @@ function graphemeCount(text) {
492
625
  }
493
626
 
494
627
  // src/layout/lineBreak/wrapLines.ts
495
- var SPACE = 32;
628
+ var hangs = (cp) => cp === 32 || cp === 9;
496
629
  function wrapLines(cps, maxWidth, widthOf) {
497
630
  const n = cps.length;
498
631
  const breaks = Number.isFinite(maxWidth) ? lineBreakOpportunities(cps) : null;
@@ -507,14 +640,14 @@ function wrapLines(cps, maxWidth, widthOf) {
507
640
  start = i;
508
641
  continue;
509
642
  }
510
- if (cp === SPACE) {
643
+ if (hangs(cp)) {
511
644
  i++;
512
645
  continue;
513
646
  }
514
647
  let j = i + 1;
515
648
  let ink = i + 1;
516
649
  while (j < n && !isHardLineBreak(cps[j]) && (!breaks || breaks[j] === NO_BREAK)) {
517
- if (cps[j] !== SPACE) ink = j + 1;
650
+ if (!hangs(cps[j])) ink = j + 1;
518
651
  j++;
519
652
  }
520
653
  if (breaks && i > start && widthOf(start, ink) > maxWidth) {
@@ -542,7 +675,7 @@ function atlasMetrics(font) {
542
675
  function fillKey(p) {
543
676
  if (p === null) return "none";
544
677
  if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
545
- return `nx:${Math.random()}`;
678
+ return `nx:${refId(p)}`;
546
679
  }
547
680
  function sameFill(a, b) {
548
681
  if (a === b) return true;
@@ -609,7 +742,7 @@ function getOrCreateGroup(ctx, run, resolved2, page, sourceOverride) {
609
742
  }
610
743
  return g;
611
744
  }
612
- function resolveGlyph(run, font, resolved2, cp) {
745
+ function resolveGlyph(run, font, resolved2, cp, warn = true) {
613
746
  const direct = font.charMap.get(cp);
614
747
  if (direct) return { glyph: direct, font, resolved: resolved2 };
615
748
  const fallback = resolveGlyphFallback(run.fontFamily, run.fontWeight, run.fontStyle);
@@ -620,7 +753,7 @@ function resolveGlyph(run, font, resolved2, cp) {
620
753
  return { glyph, font: face.font, resolved: fallback };
621
754
  }
622
755
  }
623
- warnMissingGlyphOnce(resolved2.resolved.family, cp);
756
+ if (warn) warnMissingGlyphOnce(resolved2.resolved.family, cp);
624
757
  return null;
625
758
  }
626
759
  var RTL_BLOCKS = [
@@ -639,7 +772,7 @@ function warnLogicalRtlOnce() {
639
772
  console.warn(
640
773
  // Naming the import in `from "<pkg>"` form would trip labkit's
641
774
  // consumer smoke test, which greps dist for that exact shape.
642
- "weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine was supplied, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import `bidi` out of the `@weasel-js/bidi` package and add it to the layout options."
775
+ "weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine is registered, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import `bidi` out of the `@weasel-js/bidi` package and call `registerBidi(bidi)` once at startup."
643
776
  );
644
777
  }
645
778
  var warnedMissingGlyphs = /* @__PURE__ */ new Set();
@@ -663,8 +796,10 @@ function warnNoMetricsOnce(family, weight, style) {
663
796
  }
664
797
  function layoutRuns(runs, opts) {
665
798
  const ctx = { groups: /* @__PURE__ */ new Map() };
666
- const align = resolveAlign(opts.align, opts.direction ?? "ltr");
799
+ const bidi = opts.bidi ?? registeredBidi();
800
+ const direction = opts.direction ?? "auto";
667
801
  const justify = opts.justify === true || opts.align === "justify";
802
+ const tabSize = opts.tabSize ?? DEFAULT_TAB_SIZE;
668
803
  const lineSize = (e) => Math.max(e.run.fontSize, e.run.strutSize ?? 0);
669
804
  const aboveBaseline = (e) => {
670
805
  const k = lineSize(e) / e.metrics.size;
@@ -673,6 +808,7 @@ function layoutRuns(runs, opts) {
673
808
  return (lineSize(e) * opts.lineHeight - ascent - e.metrics.descent * k) / 2 + ascent;
674
809
  };
675
810
  const entries = [];
811
+ const closesCluster = [];
676
812
  const runFaces = /* @__PURE__ */ new Map();
677
813
  let prevCp;
678
814
  let prevMetrics;
@@ -706,12 +842,14 @@ function layoutRuns(runs, opts) {
706
842
  const sizes = run.sizeMap;
707
843
  let at = 0;
708
844
  const runTracking = run.letterSpacing;
709
- const clusterEnds = runTracking === 0 ? null : new Set(graphemeEnds(run.text));
845
+ const clusterEnds = new Set(graphemeEnds(run.text));
710
846
  for (const ch of [...run.text]) {
711
847
  const cp = ch.codePointAt(0);
712
848
  const fontSize = sizes ? sizes[at] : run.fontSize;
713
849
  const scale = fontSize / metrics.size;
714
- const tracking = clusterEnds === null || clusterEnds.has(at + ch.length) ? runTracking : 0;
850
+ const clusterEnd = clusterEnds.has(at + ch.length);
851
+ closesCluster.push(clusterEnd);
852
+ const tracking = clusterEnd ? runTracking : 0;
715
853
  const isNewline = isHardLineBreak(cp);
716
854
  const isSpace = cp === 32;
717
855
  const srcStart = map ? runBase + map.starts[at] : runBase + at;
@@ -743,10 +881,36 @@ function layoutRuns(runs, opts) {
743
881
  prevFontSize = void 0;
744
882
  continue;
745
883
  }
746
- if (isSpace) {
884
+ const isTab = cp === 9;
885
+ if (isSpace || isTab) {
747
886
  const spaceGlyph = resolved2.dynamicFace ? resolved2.dynamicFace.requestGlyph(32) : font?.charMap.get(32) ?? null;
748
887
  const spaceAdvance = spaceGlyph ? spaceGlyph.xadvance : metrics.advanceOf(32);
749
888
  const advance = spaceAdvance !== null ? spaceAdvance * scale : fontSize * 0.25;
889
+ if (isTab) {
890
+ entries.push({
891
+ run,
892
+ font,
893
+ metrics,
894
+ glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
895
+ cp,
896
+ advance: 0,
897
+ tracking: 0,
898
+ kerningBefore: 0,
899
+ isSpace: false,
900
+ isNewline: false,
901
+ tab: { stop: tabSize * (advance + runTracking), min: advance / 2 },
902
+ drawsInk: false,
903
+ resolved: resolved2,
904
+ fontSize,
905
+ srcIndex: srcStart,
906
+ srcEnd,
907
+ flat: 0
908
+ });
909
+ prevCp = void 0;
910
+ prevMetrics = void 0;
911
+ prevFontSize = void 0;
912
+ continue;
913
+ }
750
914
  let kerningBefore2 = 0;
751
915
  if (prevCp !== void 0 && prevMetrics !== void 0 && prevFontSize !== void 0) {
752
916
  kerningBefore2 = prevMetrics.kernOf(prevCp, cp) * (prevFontSize / prevMetrics.size);
@@ -883,17 +1047,22 @@ function layoutRuns(runs, opts) {
883
1047
  }
884
1048
  }
885
1049
  for (let i = 0; i < entries.length; i++) entries[i].flat = i;
886
- const analysis = opts.bidi ? opts.bidi.analyze(entries.map((e) => e.cp), opts.direction ?? "auto") : null;
887
- if (!opts.bidi && entries.some((e) => RTL_BLOCKS.some(([lo, hi]) => e.cp >= lo && e.cp <= hi))) {
1050
+ const paragraphs = bidi ? analyzeParagraphs(bidi, entries.map((e) => e.cp), (i) => entries[i].isNewline, direction) : null;
1051
+ if (!bidi && entries.some((e) => RTL_BLOCKS.some(([lo, hi]) => e.cp >= lo && e.cp <= hi))) {
888
1052
  warnLogicalRtlOnce();
889
1053
  }
890
- const openingWidth = (e) => e.isSpace ? 0 : e.advance + e.tracking;
1054
+ const openingWidth = (e) => e.advance + e.tracking;
891
1055
  const sum = new Float64Array(entries.length + 1);
892
1056
  for (let k = 0; k < entries.length; k++) {
893
1057
  const e = entries[k];
894
1058
  sum[k + 1] = sum[k] + e.kerningBefore + e.advance + e.tracking;
895
1059
  }
896
- const widthOf = (start, end) => openingWidth(entries[start]) + sum[end] - sum[start + 1];
1060
+ const flatWidth = (start, end) => openingWidth(entries[start]) + sum[end] - sum[start + 1];
1061
+ const tabs = [];
1062
+ entries.forEach((e, k) => {
1063
+ if (e.tab) tabs.push(k);
1064
+ });
1065
+ const widthOf = tabs.length === 0 ? flatWidth : (start, end) => widthWithTabs(start, end, tabs, (k) => entries[k].tab, flatWidth);
897
1066
  const lines = [];
898
1067
  for (const span2 of wrapLines(entries.map((e) => e.cp), opts.maxWidth, widthOf)) {
899
1068
  const line = { entries: [], width: 0, height: 0, ...span2.wrapped ? { wrapped: true } : {} };
@@ -904,7 +1073,8 @@ function layoutRuns(runs, opts) {
904
1073
  }
905
1074
  for (let k = span2.start; k < span2.end; k++) {
906
1075
  const e = entries[k];
907
- const placed = k > span2.start ? e : e.isSpace ? { ...e, kerningBefore: 0, advance: 0, tracking: 0 } : { ...e, kerningBefore: 0 };
1076
+ const opened = k > span2.start ? e : { ...e, kerningBefore: 0 };
1077
+ const placed = e.tab ? { ...opened, advance: tabAdvance(line.width, e.tab.stop, e.tab.min) } : opened;
908
1078
  line.entries.push(placed);
909
1079
  line.width += placed.kerningBefore + placed.advance + placed.tracking;
910
1080
  line.height = Math.max(line.height, lineSize(e) * opts.lineHeight);
@@ -943,7 +1113,7 @@ function layoutRuns(runs, opts) {
943
1113
  let hung = 0;
944
1114
  for (let k = line.entries.length - 1; k >= 0; k--) {
945
1115
  const e = line.entries[k];
946
- if (!e.isSpace) break;
1116
+ if (!e.isSpace && !e.tab) break;
947
1117
  hung += e.kerningBefore + e.advance + e.tracking;
948
1118
  }
949
1119
  const inkWidth = line.width - hung;
@@ -958,6 +1128,7 @@ function layoutRuns(runs, opts) {
958
1128
  if (gaps.size > 0) gapExtra = (alignWidth - inkWidth) / gaps.size;
959
1129
  }
960
1130
  const spread = gapExtra * gaps.size;
1131
+ const align = resolveAlign(opts.align, direction !== "auto" ? direction : directionOfLevel(line.entries.length > 0 ? paragraphs?.baseLevel(line.entries[0].flat) ?? 0 : 0));
961
1132
  const alignShift = (() => {
962
1133
  if (spread > 0) return 0;
963
1134
  if (align === "left") return 0;
@@ -978,10 +1149,10 @@ function layoutRuns(runs, opts) {
978
1149
  flatOf.forEach((f, pos) => posOfFlat.set(f, pos));
979
1150
  let visual = line.entries.map((_, k) => k);
980
1151
  let levelOf = () => 0;
981
- if (opts.bidi && analysis !== null && line.entries.length > 0) {
1152
+ if (paragraphs !== null && line.entries.length > 0) {
982
1153
  const from = flatOf[0];
983
1154
  const to = flatOf[flatOf.length - 1] + 1;
984
- const r = opts.bidi.reorder(analysis, from, to);
1155
+ const r = paragraphs.reorder(from, to);
985
1156
  const seen = /* @__PURE__ */ new Set();
986
1157
  const ordered = [];
987
1158
  for (const f of r.order) {
@@ -991,28 +1162,34 @@ function layoutRuns(runs, opts) {
991
1162
  seen.add(pos);
992
1163
  }
993
1164
  }
994
- for (let k = 0; k < line.entries.length; k++) if (!seen.has(k)) ordered.push(k);
995
- visual = ordered;
996
1165
  levelOf = (pos) => {
997
1166
  const l = r.levels[flatOf[pos] - from];
998
1167
  return typeof l === "number" && l >= 0 ? l : 0;
999
1168
  };
1169
+ visual = restoreRemoved(ordered, line.entries.length, (pos) => levelOf(pos) % 2 === 1);
1000
1170
  }
1001
- if (opts.bidi) {
1171
+ if (bidi) {
1002
1172
  for (let pos = 0; pos < line.entries.length; pos++) {
1003
1173
  if (levelOf(pos) % 2 === 0) continue;
1004
1174
  const e = line.entries[pos];
1005
- const m = opts.bidi.mirror(e.cp);
1175
+ const m = bidi.mirror(e.cp);
1006
1176
  if (m === null || m === e.cp) continue;
1007
1177
  if (e.font !== null) {
1008
- const hit = resolveGlyph(e.run, e.font, e.resolved, m);
1178
+ const hit = resolveGlyph(e.run, e.font, e.resolved, m, false);
1009
1179
  if (!hit) continue;
1180
+ if (hit.font !== e.font) {
1181
+ e.font = hit.font;
1182
+ e.metrics = atlasMetrics(hit.font);
1183
+ e.resolved = hit.resolved;
1184
+ }
1010
1185
  e.glyph = hit.glyph;
1011
1186
  }
1012
1187
  e.cp = m;
1013
1188
  }
1014
1189
  }
1015
- let penX = lineX0;
1190
+ const hangsLeft = hung > 0 && levelOf(line.entries.length - 1) % 2 === 1;
1191
+ const lineLeft = hangsLeft ? lineX0 - hung : lineX0;
1192
+ let penX = lineLeft;
1016
1193
  let prevPos = -1;
1017
1194
  for (const pos of visual) {
1018
1195
  const e = line.entries[pos];
@@ -1026,7 +1203,8 @@ function layoutRuns(runs, opts) {
1026
1203
  x: penX,
1027
1204
  advance: step,
1028
1205
  level: levelOf(pos),
1029
- drawsInk: e.drawsInk
1206
+ drawsInk: e.drawsInk,
1207
+ clusterEnd: closesCluster[e.flat]
1030
1208
  };
1031
1209
  cells[pos] = cell;
1032
1210
  const scale = e.fontSize / e.metrics.size;
@@ -1094,9 +1272,9 @@ function layoutRuns(runs, opts) {
1094
1272
  const lastCell = line.entries[line.entries.length - 1];
1095
1273
  const srcEnd = lastCell ? lastCell.srcEnd : line.blank?.srcIndex ?? 0;
1096
1274
  lineBoxes.push({
1097
- x0: lineX0,
1275
+ x0: lineLeft,
1098
1276
  y0: penY,
1099
- x1: lineX0 + line.width + spread,
1277
+ x1: lineLeft + line.width + spread,
1100
1278
  y1: penY + line.height,
1101
1279
  baselineY: lineBaselineY,
1102
1280
  cells,
@@ -1128,17 +1306,8 @@ function outlineBucket(runs, min) {
1128
1306
  function variantKey(runs, opts) {
1129
1307
  const alignWidth = opts.alignWidth ?? opts.maxWidth;
1130
1308
  const justify = opts.justify === true || opts.align === "justify";
1131
- return `${opts.maxWidth}|${alignWidth}|${opts.lineHeight}|${opts.align}|${justify ? "j" : ""}|${outlineBucket(runs, opts.outlineMinSize)}`;
1132
- }
1133
- var nextRefId = 1;
1134
- var refIds = /* @__PURE__ */ new WeakMap();
1135
- function refId(o) {
1136
- let id = refIds.get(o);
1137
- if (id === void 0) {
1138
- id = nextRefId++;
1139
- refIds.set(o, id);
1140
- }
1141
- return id;
1309
+ const bidi = opts.bidi ?? registeredBidi();
1310
+ return `${opts.maxWidth}|${alignWidth}|${opts.lineHeight}|${opts.align}|${justify ? "j" : ""}|${outlineBucket(runs, opts.outlineMinSize)}|${opts.direction ?? ""}|${bidi ? refId(bidi) : ""}|${opts.tabSize ?? ""}`;
1142
1311
  }
1143
1312
  function paintKey(p) {
1144
1313
  if (p === null) return "none";
@@ -1160,7 +1329,7 @@ function strokeKey2(s) {
1160
1329
  function runsKey(runs) {
1161
1330
  let out = "";
1162
1331
  for (const r of runs) {
1163
- out += `${r.text.length}:${r.text}|${r.fontFamily.length}:${r.fontFamily}|${r.fontSize}|${r.fontWeight}|${r.fontStyle}|${r.letterSpacing}|${r.underline ? 1 : 0}${r.strikethrough ? 1 : 0}${r.overline ? 1 : 0}|${r.baselineShift}|${paintKey(r.fill)}|${strokeKey2(r.stroke)}|${r.srcMap ? `${r.srcMap.length}:${r.srcMap.starts.join(",")}` : ""}|${r.sizeMap ? r.sizeMap.join(",") : ""}|`;
1332
+ out += `${r.text.length}:${r.text}|${r.fontFamily.length}:${r.fontFamily}|${r.fontSize}|${r.fontWeight}|${r.fontStyle}|${r.letterSpacing}|${r.underline ? 1 : 0}${r.strikethrough ? 1 : 0}${r.overline ? 1 : 0}|${r.baselineShift}|${r.strutSize ?? ""}|${paintKey(r.fill)}|${strokeKey2(r.stroke)}|${r.srcMap ? `${r.srcMap.length}:${r.srcMap.starts.join(",")}` : ""}|${r.sizeMap ? r.sizeMap.join(",") : ""}|`;
1164
1333
  }
1165
1334
  return out;
1166
1335
  }
@@ -1202,6 +1371,6 @@ function _resetLayoutCacheForTests() {
1202
1371
  structuralGeneration = -1;
1203
1372
  }
1204
1373
 
1205
- export { BREAK_ALLOWED, BREAK_MANDATORY, DECORATION_KINDS, DEFAULT_DECORATION_METRICS, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, _resetLayoutCacheForTests, cachedLayoutRuns, decorationMetrics, decorationRule, fontString, graphemeCount, isHardLineBreak, layoutRuns, lineBreakOpportunities, resolveAlign, resolveTextStyle, wrapLines };
1206
- //# sourceMappingURL=chunk-L6UJ4RQK.js.map
1207
- //# sourceMappingURL=chunk-L6UJ4RQK.js.map
1374
+ export { BREAK_ALLOWED, BREAK_MANDATORY, DECORATION_KINDS, DEFAULT_DECORATION_METRICS, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, _resetLayoutCacheForTests, cachedLayoutRuns, decorationMetrics, decorationRule, fontString, graphemeCount, hangs, isHardLineBreak, layoutRuns, lineBreakOpportunities, paragraphDirection, registerBidi, resolveAlign, resolveTextStyle, wrapLines };
1375
+ //# sourceMappingURL=chunk-VZNK3C6F.js.map
1376
+ //# sourceMappingURL=chunk-VZNK3C6F.js.map