@grahlnn/comps 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4070 @@
1
+ // torph/src/components/Torph.tsx
2
+ import {
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState
7
+ } from "react";
8
+
9
+ // torph/src/vendor/pretext/bidi.ts
10
+ var baseTypes = [
11
+ "BN",
12
+ "BN",
13
+ "BN",
14
+ "BN",
15
+ "BN",
16
+ "BN",
17
+ "BN",
18
+ "BN",
19
+ "BN",
20
+ "S",
21
+ "B",
22
+ "S",
23
+ "WS",
24
+ "B",
25
+ "BN",
26
+ "BN",
27
+ "BN",
28
+ "BN",
29
+ "BN",
30
+ "BN",
31
+ "BN",
32
+ "BN",
33
+ "BN",
34
+ "BN",
35
+ "BN",
36
+ "BN",
37
+ "BN",
38
+ "BN",
39
+ "B",
40
+ "B",
41
+ "B",
42
+ "S",
43
+ "WS",
44
+ "ON",
45
+ "ON",
46
+ "ET",
47
+ "ET",
48
+ "ET",
49
+ "ON",
50
+ "ON",
51
+ "ON",
52
+ "ON",
53
+ "ON",
54
+ "ON",
55
+ "CS",
56
+ "ON",
57
+ "CS",
58
+ "ON",
59
+ "EN",
60
+ "EN",
61
+ "EN",
62
+ "EN",
63
+ "EN",
64
+ "EN",
65
+ "EN",
66
+ "EN",
67
+ "EN",
68
+ "EN",
69
+ "ON",
70
+ "ON",
71
+ "ON",
72
+ "ON",
73
+ "ON",
74
+ "ON",
75
+ "ON",
76
+ "L",
77
+ "L",
78
+ "L",
79
+ "L",
80
+ "L",
81
+ "L",
82
+ "L",
83
+ "L",
84
+ "L",
85
+ "L",
86
+ "L",
87
+ "L",
88
+ "L",
89
+ "L",
90
+ "L",
91
+ "L",
92
+ "L",
93
+ "L",
94
+ "L",
95
+ "L",
96
+ "L",
97
+ "L",
98
+ "L",
99
+ "L",
100
+ "L",
101
+ "L",
102
+ "ON",
103
+ "ON",
104
+ "ON",
105
+ "ON",
106
+ "ON",
107
+ "ON",
108
+ "L",
109
+ "L",
110
+ "L",
111
+ "L",
112
+ "L",
113
+ "L",
114
+ "L",
115
+ "L",
116
+ "L",
117
+ "L",
118
+ "L",
119
+ "L",
120
+ "L",
121
+ "L",
122
+ "L",
123
+ "L",
124
+ "L",
125
+ "L",
126
+ "L",
127
+ "L",
128
+ "L",
129
+ "L",
130
+ "L",
131
+ "L",
132
+ "L",
133
+ "L",
134
+ "ON",
135
+ "ON",
136
+ "ON",
137
+ "ON",
138
+ "BN",
139
+ "BN",
140
+ "BN",
141
+ "BN",
142
+ "BN",
143
+ "BN",
144
+ "B",
145
+ "BN",
146
+ "BN",
147
+ "BN",
148
+ "BN",
149
+ "BN",
150
+ "BN",
151
+ "BN",
152
+ "BN",
153
+ "BN",
154
+ "BN",
155
+ "BN",
156
+ "BN",
157
+ "BN",
158
+ "BN",
159
+ "BN",
160
+ "BN",
161
+ "BN",
162
+ "BN",
163
+ "BN",
164
+ "BN",
165
+ "BN",
166
+ "BN",
167
+ "BN",
168
+ "BN",
169
+ "BN",
170
+ "BN",
171
+ "CS",
172
+ "ON",
173
+ "ET",
174
+ "ET",
175
+ "ET",
176
+ "ET",
177
+ "ON",
178
+ "ON",
179
+ "ON",
180
+ "ON",
181
+ "L",
182
+ "ON",
183
+ "ON",
184
+ "ON",
185
+ "ON",
186
+ "ON",
187
+ "ET",
188
+ "ET",
189
+ "EN",
190
+ "EN",
191
+ "ON",
192
+ "L",
193
+ "ON",
194
+ "ON",
195
+ "ON",
196
+ "EN",
197
+ "L",
198
+ "ON",
199
+ "ON",
200
+ "ON",
201
+ "ON",
202
+ "ON",
203
+ "L",
204
+ "L",
205
+ "L",
206
+ "L",
207
+ "L",
208
+ "L",
209
+ "L",
210
+ "L",
211
+ "L",
212
+ "L",
213
+ "L",
214
+ "L",
215
+ "L",
216
+ "L",
217
+ "L",
218
+ "L",
219
+ "L",
220
+ "L",
221
+ "L",
222
+ "L",
223
+ "L",
224
+ "L",
225
+ "L",
226
+ "ON",
227
+ "L",
228
+ "L",
229
+ "L",
230
+ "L",
231
+ "L",
232
+ "L",
233
+ "L",
234
+ "L",
235
+ "L",
236
+ "L",
237
+ "L",
238
+ "L",
239
+ "L",
240
+ "L",
241
+ "L",
242
+ "L",
243
+ "L",
244
+ "L",
245
+ "L",
246
+ "L",
247
+ "L",
248
+ "L",
249
+ "L",
250
+ "L",
251
+ "L",
252
+ "L",
253
+ "L",
254
+ "L",
255
+ "L",
256
+ "L",
257
+ "L",
258
+ "ON",
259
+ "L",
260
+ "L",
261
+ "L",
262
+ "L",
263
+ "L",
264
+ "L",
265
+ "L",
266
+ "L"
267
+ ];
268
+ var arabicTypes = [
269
+ "AL",
270
+ "AL",
271
+ "AL",
272
+ "AL",
273
+ "AL",
274
+ "AL",
275
+ "AL",
276
+ "AL",
277
+ "AL",
278
+ "AL",
279
+ "AL",
280
+ "AL",
281
+ "CS",
282
+ "AL",
283
+ "ON",
284
+ "ON",
285
+ "NSM",
286
+ "NSM",
287
+ "NSM",
288
+ "NSM",
289
+ "NSM",
290
+ "NSM",
291
+ "AL",
292
+ "AL",
293
+ "AL",
294
+ "AL",
295
+ "AL",
296
+ "AL",
297
+ "AL",
298
+ "AL",
299
+ "AL",
300
+ "AL",
301
+ "AL",
302
+ "AL",
303
+ "AL",
304
+ "AL",
305
+ "AL",
306
+ "AL",
307
+ "AL",
308
+ "AL",
309
+ "AL",
310
+ "AL",
311
+ "AL",
312
+ "AL",
313
+ "AL",
314
+ "AL",
315
+ "AL",
316
+ "AL",
317
+ "AL",
318
+ "AL",
319
+ "AL",
320
+ "AL",
321
+ "AL",
322
+ "AL",
323
+ "AL",
324
+ "AL",
325
+ "AL",
326
+ "AL",
327
+ "AL",
328
+ "AL",
329
+ "AL",
330
+ "AL",
331
+ "AL",
332
+ "AL",
333
+ "AL",
334
+ "AL",
335
+ "AL",
336
+ "AL",
337
+ "AL",
338
+ "AL",
339
+ "AL",
340
+ "AL",
341
+ "AL",
342
+ "AL",
343
+ "AL",
344
+ "NSM",
345
+ "NSM",
346
+ "NSM",
347
+ "NSM",
348
+ "NSM",
349
+ "NSM",
350
+ "NSM",
351
+ "NSM",
352
+ "NSM",
353
+ "NSM",
354
+ "NSM",
355
+ "NSM",
356
+ "NSM",
357
+ "NSM",
358
+ "AL",
359
+ "AL",
360
+ "AL",
361
+ "AL",
362
+ "AL",
363
+ "AL",
364
+ "AL",
365
+ "AN",
366
+ "AN",
367
+ "AN",
368
+ "AN",
369
+ "AN",
370
+ "AN",
371
+ "AN",
372
+ "AN",
373
+ "AN",
374
+ "AN",
375
+ "ET",
376
+ "AN",
377
+ "AN",
378
+ "AL",
379
+ "AL",
380
+ "AL",
381
+ "NSM",
382
+ "AL",
383
+ "AL",
384
+ "AL",
385
+ "AL",
386
+ "AL",
387
+ "AL",
388
+ "AL",
389
+ "AL",
390
+ "AL",
391
+ "AL",
392
+ "AL",
393
+ "AL",
394
+ "AL",
395
+ "AL",
396
+ "AL",
397
+ "AL",
398
+ "AL",
399
+ "AL",
400
+ "AL",
401
+ "AL",
402
+ "AL",
403
+ "AL",
404
+ "AL",
405
+ "AL",
406
+ "AL",
407
+ "AL",
408
+ "AL",
409
+ "AL",
410
+ "AL",
411
+ "AL",
412
+ "AL",
413
+ "AL",
414
+ "AL",
415
+ "AL",
416
+ "AL",
417
+ "AL",
418
+ "AL",
419
+ "AL",
420
+ "AL",
421
+ "AL",
422
+ "AL",
423
+ "AL",
424
+ "AL",
425
+ "AL",
426
+ "AL",
427
+ "AL",
428
+ "AL",
429
+ "AL",
430
+ "AL",
431
+ "AL",
432
+ "AL",
433
+ "AL",
434
+ "AL",
435
+ "AL",
436
+ "AL",
437
+ "AL",
438
+ "AL",
439
+ "AL",
440
+ "AL",
441
+ "AL",
442
+ "AL",
443
+ "AL",
444
+ "AL",
445
+ "AL",
446
+ "AL",
447
+ "AL",
448
+ "AL",
449
+ "AL",
450
+ "AL",
451
+ "AL",
452
+ "AL",
453
+ "AL",
454
+ "AL",
455
+ "AL",
456
+ "AL",
457
+ "AL",
458
+ "AL",
459
+ "AL",
460
+ "AL",
461
+ "AL",
462
+ "AL",
463
+ "AL",
464
+ "AL",
465
+ "AL",
466
+ "AL",
467
+ "AL",
468
+ "AL",
469
+ "AL",
470
+ "AL",
471
+ "AL",
472
+ "AL",
473
+ "AL",
474
+ "AL",
475
+ "AL",
476
+ "AL",
477
+ "AL",
478
+ "AL",
479
+ "AL",
480
+ "AL",
481
+ "AL",
482
+ "AL",
483
+ "NSM",
484
+ "NSM",
485
+ "NSM",
486
+ "NSM",
487
+ "NSM",
488
+ "NSM",
489
+ "NSM",
490
+ "NSM",
491
+ "NSM",
492
+ "NSM",
493
+ "NSM",
494
+ "NSM",
495
+ "NSM",
496
+ "NSM",
497
+ "NSM",
498
+ "NSM",
499
+ "NSM",
500
+ "NSM",
501
+ "NSM",
502
+ "ON",
503
+ "NSM",
504
+ "NSM",
505
+ "NSM",
506
+ "NSM",
507
+ "AL",
508
+ "AL",
509
+ "AL",
510
+ "AL",
511
+ "AL",
512
+ "AL",
513
+ "AL",
514
+ "AL",
515
+ "AL",
516
+ "AL",
517
+ "AL",
518
+ "AL",
519
+ "AL",
520
+ "AL",
521
+ "AL",
522
+ "AL",
523
+ "AL",
524
+ "AL"
525
+ ];
526
+ function classifyChar(charCode) {
527
+ if (charCode <= 255)
528
+ return baseTypes[charCode];
529
+ if (1424 <= charCode && charCode <= 1524)
530
+ return "R";
531
+ if (1536 <= charCode && charCode <= 1791)
532
+ return arabicTypes[charCode & 255];
533
+ if (1792 <= charCode && charCode <= 2220)
534
+ return "AL";
535
+ return "L";
536
+ }
537
+ function computeBidiLevels(str) {
538
+ const len = str.length;
539
+ if (len === 0)
540
+ return null;
541
+ const types = new Array(len);
542
+ let numBidi = 0;
543
+ for (let i = 0;i < len; i++) {
544
+ const t = classifyChar(str.charCodeAt(i));
545
+ if (t === "R" || t === "AL" || t === "AN")
546
+ numBidi++;
547
+ types[i] = t;
548
+ }
549
+ if (numBidi === 0)
550
+ return null;
551
+ const startLevel = len / numBidi < 0.3 ? 0 : 1;
552
+ const levels = new Int8Array(len);
553
+ for (let i = 0;i < len; i++)
554
+ levels[i] = startLevel;
555
+ const e = startLevel & 1 ? "R" : "L";
556
+ const sor = e;
557
+ let lastType = sor;
558
+ for (let i = 0;i < len; i++) {
559
+ if (types[i] === "NSM")
560
+ types[i] = lastType;
561
+ else
562
+ lastType = types[i];
563
+ }
564
+ lastType = sor;
565
+ for (let i = 0;i < len; i++) {
566
+ const t = types[i];
567
+ if (t === "EN")
568
+ types[i] = lastType === "AL" ? "AN" : "EN";
569
+ else if (t === "R" || t === "L" || t === "AL")
570
+ lastType = t;
571
+ }
572
+ for (let i = 0;i < len; i++) {
573
+ if (types[i] === "AL")
574
+ types[i] = "R";
575
+ }
576
+ for (let i = 1;i < len - 1; i++) {
577
+ if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
578
+ types[i] = "EN";
579
+ }
580
+ if (types[i] === "CS" && (types[i - 1] === "EN" || types[i - 1] === "AN") && types[i + 1] === types[i - 1]) {
581
+ types[i] = types[i - 1];
582
+ }
583
+ }
584
+ for (let i = 0;i < len; i++) {
585
+ if (types[i] !== "EN")
586
+ continue;
587
+ let j;
588
+ for (j = i - 1;j >= 0 && types[j] === "ET"; j--)
589
+ types[j] = "EN";
590
+ for (j = i + 1;j < len && types[j] === "ET"; j++)
591
+ types[j] = "EN";
592
+ }
593
+ for (let i = 0;i < len; i++) {
594
+ const t = types[i];
595
+ if (t === "WS" || t === "ES" || t === "ET" || t === "CS")
596
+ types[i] = "ON";
597
+ }
598
+ lastType = sor;
599
+ for (let i = 0;i < len; i++) {
600
+ const t = types[i];
601
+ if (t === "EN")
602
+ types[i] = lastType === "L" ? "L" : "EN";
603
+ else if (t === "R" || t === "L")
604
+ lastType = t;
605
+ }
606
+ for (let i = 0;i < len; i++) {
607
+ if (types[i] !== "ON")
608
+ continue;
609
+ let end = i + 1;
610
+ while (end < len && types[end] === "ON")
611
+ end++;
612
+ const before = i > 0 ? types[i - 1] : sor;
613
+ const after = end < len ? types[end] : sor;
614
+ const bDir = before !== "L" ? "R" : "L";
615
+ const aDir = after !== "L" ? "R" : "L";
616
+ if (bDir === aDir) {
617
+ for (let j = i;j < end; j++)
618
+ types[j] = bDir;
619
+ }
620
+ i = end - 1;
621
+ }
622
+ for (let i = 0;i < len; i++) {
623
+ if (types[i] === "ON")
624
+ types[i] = e;
625
+ }
626
+ for (let i = 0;i < len; i++) {
627
+ const t = types[i];
628
+ if ((levels[i] & 1) === 0) {
629
+ if (t === "R")
630
+ levels[i]++;
631
+ else if (t === "AN" || t === "EN")
632
+ levels[i] += 2;
633
+ } else if (t === "L" || t === "AN" || t === "EN") {
634
+ levels[i]++;
635
+ }
636
+ }
637
+ return levels;
638
+ }
639
+ function computeSegmentLevels(normalized, segStarts) {
640
+ const bidiLevels = computeBidiLevels(normalized);
641
+ if (bidiLevels === null)
642
+ return null;
643
+ const segLevels = new Int8Array(segStarts.length);
644
+ for (let i = 0;i < segStarts.length; i++) {
645
+ segLevels[i] = bidiLevels[segStarts[i]];
646
+ }
647
+ return segLevels;
648
+ }
649
+
650
+ // torph/src/vendor/pretext/analysis.ts
651
+ var collapsibleWhitespaceRunRe = /[ \t\n\r\f]+/g;
652
+ var needsWhitespaceNormalizationRe = /[\t\n\r\f]| {2,}|^ | $/;
653
+ function getWhiteSpaceProfile(whiteSpace) {
654
+ const mode = whiteSpace ?? "normal";
655
+ return mode === "pre-wrap" ? { mode, preserveOrdinarySpaces: true, preserveHardBreaks: true } : { mode, preserveOrdinarySpaces: false, preserveHardBreaks: false };
656
+ }
657
+ function normalizeWhitespaceNormal(text) {
658
+ if (!needsWhitespaceNormalizationRe.test(text))
659
+ return text;
660
+ let normalized = text.replace(collapsibleWhitespaceRunRe, " ");
661
+ if (normalized.charCodeAt(0) === 32) {
662
+ normalized = normalized.slice(1);
663
+ }
664
+ if (normalized.length > 0 && normalized.charCodeAt(normalized.length - 1) === 32) {
665
+ normalized = normalized.slice(0, -1);
666
+ }
667
+ return normalized;
668
+ }
669
+ function normalizeWhitespacePreWrap(text) {
670
+ if (!/[\r\f]/.test(text))
671
+ return text.replace(/\r\n/g, `
672
+ `);
673
+ return text.replace(/\r\n/g, `
674
+ `).replace(/[\r\f]/g, `
675
+ `);
676
+ }
677
+ var sharedWordSegmenter = null;
678
+ var segmenterLocale;
679
+ function getSharedWordSegmenter() {
680
+ if (sharedWordSegmenter === null) {
681
+ sharedWordSegmenter = new Intl.Segmenter(segmenterLocale, { granularity: "word" });
682
+ }
683
+ return sharedWordSegmenter;
684
+ }
685
+ function clearAnalysisCaches() {
686
+ sharedWordSegmenter = null;
687
+ }
688
+ var arabicScriptRe = /\p{Script=Arabic}/u;
689
+ var combiningMarkRe = /\p{M}/u;
690
+ var decimalDigitRe = /\p{Nd}/u;
691
+ function containsArabicScript(text) {
692
+ return arabicScriptRe.test(text);
693
+ }
694
+ function isCJK(s) {
695
+ for (const ch of s) {
696
+ const c = ch.codePointAt(0);
697
+ if (c >= 19968 && c <= 40959 || c >= 13312 && c <= 19903 || c >= 131072 && c <= 173791 || c >= 173824 && c <= 177983 || c >= 177984 && c <= 178207 || c >= 178208 && c <= 183983 || c >= 183984 && c <= 191471 || c >= 196608 && c <= 201551 || c >= 63744 && c <= 64255 || c >= 194560 && c <= 195103 || c >= 12288 && c <= 12351 || c >= 12352 && c <= 12447 || c >= 12448 && c <= 12543 || c >= 44032 && c <= 55215 || c >= 65280 && c <= 65519) {
698
+ return true;
699
+ }
700
+ }
701
+ return false;
702
+ }
703
+ var kinsokuStart = new Set([
704
+ ",",
705
+ ".",
706
+ "!",
707
+ ":",
708
+ ";",
709
+ "?",
710
+ "、",
711
+ "。",
712
+ "・",
713
+ ")",
714
+ "〕",
715
+ "〉",
716
+ "》",
717
+ "」",
718
+ "』",
719
+ "】",
720
+ "〗",
721
+ "〙",
722
+ "〛",
723
+ "ー",
724
+ "々",
725
+ "〻",
726
+ "ゝ",
727
+ "ゞ",
728
+ "ヽ",
729
+ "ヾ"
730
+ ]);
731
+ var kinsokuEnd = new Set([
732
+ '"',
733
+ "(",
734
+ "[",
735
+ "{",
736
+ "“",
737
+ "‘",
738
+ "«",
739
+ "‹",
740
+ "(",
741
+ "〔",
742
+ "〈",
743
+ "《",
744
+ "「",
745
+ "『",
746
+ "【",
747
+ "〖",
748
+ "〘",
749
+ "〚"
750
+ ]);
751
+ var forwardStickyGlue = new Set(["'", "’"]);
752
+ var leftStickyPunctuation = new Set([
753
+ ".",
754
+ ",",
755
+ "!",
756
+ "?",
757
+ ":",
758
+ ";",
759
+ "،",
760
+ "؛",
761
+ "؟",
762
+ "।",
763
+ "॥",
764
+ "၊",
765
+ "။",
766
+ "၌",
767
+ "၍",
768
+ "၏",
769
+ ")",
770
+ "]",
771
+ "}",
772
+ "%",
773
+ '"',
774
+ "”",
775
+ "’",
776
+ "»",
777
+ "›",
778
+ "…"
779
+ ]);
780
+ var arabicNoSpaceTrailingPunctuation = new Set([":", ".", "،", "؛"]);
781
+ var myanmarMedialGlue = new Set(["၏"]);
782
+ var closingQuoteChars = new Set([
783
+ "”",
784
+ "’",
785
+ "»",
786
+ "›",
787
+ "」",
788
+ "』",
789
+ "】",
790
+ "》",
791
+ "〉",
792
+ "〕",
793
+ ")"
794
+ ]);
795
+ function isLeftStickyPunctuationSegment(segment) {
796
+ if (isEscapedQuoteClusterSegment(segment))
797
+ return true;
798
+ let sawPunctuation = false;
799
+ for (const ch of segment) {
800
+ if (leftStickyPunctuation.has(ch)) {
801
+ sawPunctuation = true;
802
+ continue;
803
+ }
804
+ if (sawPunctuation && combiningMarkRe.test(ch))
805
+ continue;
806
+ return false;
807
+ }
808
+ return sawPunctuation;
809
+ }
810
+ function isCJKLineStartProhibitedSegment(segment) {
811
+ for (const ch of segment) {
812
+ if (!kinsokuStart.has(ch) && !leftStickyPunctuation.has(ch))
813
+ return false;
814
+ }
815
+ return segment.length > 0;
816
+ }
817
+ function isForwardStickyClusterSegment(segment) {
818
+ if (isEscapedQuoteClusterSegment(segment))
819
+ return true;
820
+ for (const ch of segment) {
821
+ if (!kinsokuEnd.has(ch) && !forwardStickyGlue.has(ch) && !combiningMarkRe.test(ch))
822
+ return false;
823
+ }
824
+ return segment.length > 0;
825
+ }
826
+ function isEscapedQuoteClusterSegment(segment) {
827
+ let sawQuote = false;
828
+ for (const ch of segment) {
829
+ if (ch === "\\" || combiningMarkRe.test(ch))
830
+ continue;
831
+ if (kinsokuEnd.has(ch) || leftStickyPunctuation.has(ch) || forwardStickyGlue.has(ch)) {
832
+ sawQuote = true;
833
+ continue;
834
+ }
835
+ return false;
836
+ }
837
+ return sawQuote;
838
+ }
839
+ function splitTrailingForwardStickyCluster(text) {
840
+ const chars = Array.from(text);
841
+ let splitIndex = chars.length;
842
+ while (splitIndex > 0) {
843
+ const ch = chars[splitIndex - 1];
844
+ if (combiningMarkRe.test(ch)) {
845
+ splitIndex--;
846
+ continue;
847
+ }
848
+ if (kinsokuEnd.has(ch) || forwardStickyGlue.has(ch)) {
849
+ splitIndex--;
850
+ continue;
851
+ }
852
+ break;
853
+ }
854
+ if (splitIndex <= 0 || splitIndex === chars.length)
855
+ return null;
856
+ return {
857
+ head: chars.slice(0, splitIndex).join(""),
858
+ tail: chars.slice(splitIndex).join("")
859
+ };
860
+ }
861
+ function isRepeatedSingleCharRun(segment, ch) {
862
+ if (segment.length === 0)
863
+ return false;
864
+ for (const part of segment) {
865
+ if (part !== ch)
866
+ return false;
867
+ }
868
+ return true;
869
+ }
870
+ function endsWithArabicNoSpacePunctuation(segment) {
871
+ if (!containsArabicScript(segment) || segment.length === 0)
872
+ return false;
873
+ return arabicNoSpaceTrailingPunctuation.has(segment[segment.length - 1]);
874
+ }
875
+ function endsWithMyanmarMedialGlue(segment) {
876
+ if (segment.length === 0)
877
+ return false;
878
+ return myanmarMedialGlue.has(segment[segment.length - 1]);
879
+ }
880
+ function splitLeadingSpaceAndMarks(segment) {
881
+ if (segment.length < 2 || segment[0] !== " ")
882
+ return null;
883
+ const marks = segment.slice(1);
884
+ if (/^\p{M}+$/u.test(marks)) {
885
+ return { space: " ", marks };
886
+ }
887
+ return null;
888
+ }
889
+ function endsWithClosingQuote(text) {
890
+ for (let i = text.length - 1;i >= 0; i--) {
891
+ const ch = text[i];
892
+ if (closingQuoteChars.has(ch))
893
+ return true;
894
+ if (!leftStickyPunctuation.has(ch))
895
+ return false;
896
+ }
897
+ return false;
898
+ }
899
+ function classifySegmentBreakChar(ch, whiteSpaceProfile) {
900
+ if (whiteSpaceProfile.preserveOrdinarySpaces || whiteSpaceProfile.preserveHardBreaks) {
901
+ if (ch === " ")
902
+ return "preserved-space";
903
+ if (ch === "\t")
904
+ return "tab";
905
+ if (whiteSpaceProfile.preserveHardBreaks && ch === `
906
+ `)
907
+ return "hard-break";
908
+ }
909
+ if (ch === " ")
910
+ return "space";
911
+ if (ch === " " || ch === " " || ch === "⁠" || ch === "\uFEFF") {
912
+ return "glue";
913
+ }
914
+ if (ch === "​")
915
+ return "zero-width-break";
916
+ if (ch === "­")
917
+ return "soft-hyphen";
918
+ return "text";
919
+ }
920
+ function splitSegmentByBreakKind(segment, isWordLike, start, whiteSpaceProfile) {
921
+ const pieces = [];
922
+ let currentKind = null;
923
+ let currentText = "";
924
+ let currentStart = start;
925
+ let currentWordLike = false;
926
+ let offset = 0;
927
+ for (const ch of segment) {
928
+ const kind = classifySegmentBreakChar(ch, whiteSpaceProfile);
929
+ const wordLike = kind === "text" && isWordLike;
930
+ if (currentKind !== null && kind === currentKind && wordLike === currentWordLike) {
931
+ currentText += ch;
932
+ offset += ch.length;
933
+ continue;
934
+ }
935
+ if (currentKind !== null) {
936
+ pieces.push({
937
+ text: currentText,
938
+ isWordLike: currentWordLike,
939
+ kind: currentKind,
940
+ start: currentStart
941
+ });
942
+ }
943
+ currentKind = kind;
944
+ currentText = ch;
945
+ currentStart = start + offset;
946
+ currentWordLike = wordLike;
947
+ offset += ch.length;
948
+ }
949
+ if (currentKind !== null) {
950
+ pieces.push({
951
+ text: currentText,
952
+ isWordLike: currentWordLike,
953
+ kind: currentKind,
954
+ start: currentStart
955
+ });
956
+ }
957
+ return pieces;
958
+ }
959
+ function isTextRunBoundary(kind) {
960
+ return kind === "space" || kind === "preserved-space" || kind === "zero-width-break" || kind === "hard-break";
961
+ }
962
+ var urlSchemeSegmentRe = /^[A-Za-z][A-Za-z0-9+.-]*:$/;
963
+ function isUrlLikeRunStart(segmentation, index) {
964
+ const text = segmentation.texts[index];
965
+ if (text.startsWith("www."))
966
+ return true;
967
+ return urlSchemeSegmentRe.test(text) && index + 1 < segmentation.len && segmentation.kinds[index + 1] === "text" && segmentation.texts[index + 1] === "//";
968
+ }
969
+ function isUrlQueryBoundarySegment(text) {
970
+ return text.includes("?") && (text.includes("://") || text.startsWith("www."));
971
+ }
972
+ function mergeUrlLikeRuns(segmentation) {
973
+ const texts = segmentation.texts.slice();
974
+ const isWordLike = segmentation.isWordLike.slice();
975
+ const kinds = segmentation.kinds.slice();
976
+ const starts = segmentation.starts.slice();
977
+ for (let i = 0;i < segmentation.len; i++) {
978
+ if (kinds[i] !== "text" || !isUrlLikeRunStart(segmentation, i))
979
+ continue;
980
+ let j = i + 1;
981
+ while (j < segmentation.len && !isTextRunBoundary(kinds[j])) {
982
+ texts[i] += texts[j];
983
+ isWordLike[i] = true;
984
+ const endsQueryPrefix = texts[j].includes("?");
985
+ kinds[j] = "text";
986
+ texts[j] = "";
987
+ j++;
988
+ if (endsQueryPrefix)
989
+ break;
990
+ }
991
+ }
992
+ let compactLen = 0;
993
+ for (let read = 0;read < texts.length; read++) {
994
+ const text = texts[read];
995
+ if (text.length === 0)
996
+ continue;
997
+ if (compactLen !== read) {
998
+ texts[compactLen] = text;
999
+ isWordLike[compactLen] = isWordLike[read];
1000
+ kinds[compactLen] = kinds[read];
1001
+ starts[compactLen] = starts[read];
1002
+ }
1003
+ compactLen++;
1004
+ }
1005
+ texts.length = compactLen;
1006
+ isWordLike.length = compactLen;
1007
+ kinds.length = compactLen;
1008
+ starts.length = compactLen;
1009
+ return {
1010
+ len: compactLen,
1011
+ texts,
1012
+ isWordLike,
1013
+ kinds,
1014
+ starts
1015
+ };
1016
+ }
1017
+ function mergeUrlQueryRuns(segmentation) {
1018
+ const texts = [];
1019
+ const isWordLike = [];
1020
+ const kinds = [];
1021
+ const starts = [];
1022
+ for (let i = 0;i < segmentation.len; i++) {
1023
+ const text = segmentation.texts[i];
1024
+ texts.push(text);
1025
+ isWordLike.push(segmentation.isWordLike[i]);
1026
+ kinds.push(segmentation.kinds[i]);
1027
+ starts.push(segmentation.starts[i]);
1028
+ if (!isUrlQueryBoundarySegment(text))
1029
+ continue;
1030
+ const nextIndex = i + 1;
1031
+ if (nextIndex >= segmentation.len || isTextRunBoundary(segmentation.kinds[nextIndex])) {
1032
+ continue;
1033
+ }
1034
+ let queryText = "";
1035
+ const queryStart = segmentation.starts[nextIndex];
1036
+ let j = nextIndex;
1037
+ while (j < segmentation.len && !isTextRunBoundary(segmentation.kinds[j])) {
1038
+ queryText += segmentation.texts[j];
1039
+ j++;
1040
+ }
1041
+ if (queryText.length > 0) {
1042
+ texts.push(queryText);
1043
+ isWordLike.push(true);
1044
+ kinds.push("text");
1045
+ starts.push(queryStart);
1046
+ i = j - 1;
1047
+ }
1048
+ }
1049
+ return {
1050
+ len: texts.length,
1051
+ texts,
1052
+ isWordLike,
1053
+ kinds,
1054
+ starts
1055
+ };
1056
+ }
1057
+ var numericJoinerChars = new Set([":", "-", "/", "×", ",", ".", "+", "–", "—"]);
1058
+ var asciiPunctuationChainSegmentRe = /^[A-Za-z0-9_]+[,:;]*$/;
1059
+ var asciiPunctuationChainTrailingJoinersRe = /[,:;]+$/;
1060
+ function segmentContainsDecimalDigit(text) {
1061
+ for (const ch of text) {
1062
+ if (decimalDigitRe.test(ch))
1063
+ return true;
1064
+ }
1065
+ return false;
1066
+ }
1067
+ function isNumericRunSegment(text) {
1068
+ if (text.length === 0)
1069
+ return false;
1070
+ for (const ch of text) {
1071
+ if (decimalDigitRe.test(ch) || numericJoinerChars.has(ch))
1072
+ continue;
1073
+ return false;
1074
+ }
1075
+ return true;
1076
+ }
1077
+ function mergeNumericRuns(segmentation) {
1078
+ const texts = [];
1079
+ const isWordLike = [];
1080
+ const kinds = [];
1081
+ const starts = [];
1082
+ for (let i = 0;i < segmentation.len; i++) {
1083
+ const text = segmentation.texts[i];
1084
+ const kind = segmentation.kinds[i];
1085
+ if (kind === "text" && isNumericRunSegment(text) && segmentContainsDecimalDigit(text)) {
1086
+ let mergedText = text;
1087
+ let j = i + 1;
1088
+ while (j < segmentation.len && segmentation.kinds[j] === "text" && isNumericRunSegment(segmentation.texts[j])) {
1089
+ mergedText += segmentation.texts[j];
1090
+ j++;
1091
+ }
1092
+ texts.push(mergedText);
1093
+ isWordLike.push(true);
1094
+ kinds.push("text");
1095
+ starts.push(segmentation.starts[i]);
1096
+ i = j - 1;
1097
+ continue;
1098
+ }
1099
+ texts.push(text);
1100
+ isWordLike.push(segmentation.isWordLike[i]);
1101
+ kinds.push(kind);
1102
+ starts.push(segmentation.starts[i]);
1103
+ }
1104
+ return {
1105
+ len: texts.length,
1106
+ texts,
1107
+ isWordLike,
1108
+ kinds,
1109
+ starts
1110
+ };
1111
+ }
1112
+ function mergeAsciiPunctuationChains(segmentation) {
1113
+ const texts = [];
1114
+ const isWordLike = [];
1115
+ const kinds = [];
1116
+ const starts = [];
1117
+ for (let i = 0;i < segmentation.len; i++) {
1118
+ const text = segmentation.texts[i];
1119
+ const kind = segmentation.kinds[i];
1120
+ const wordLike = segmentation.isWordLike[i];
1121
+ if (kind === "text" && wordLike && asciiPunctuationChainSegmentRe.test(text)) {
1122
+ let mergedText = text;
1123
+ let j = i + 1;
1124
+ while (asciiPunctuationChainTrailingJoinersRe.test(mergedText) && j < segmentation.len && segmentation.kinds[j] === "text" && segmentation.isWordLike[j] && asciiPunctuationChainSegmentRe.test(segmentation.texts[j])) {
1125
+ mergedText += segmentation.texts[j];
1126
+ j++;
1127
+ }
1128
+ texts.push(mergedText);
1129
+ isWordLike.push(true);
1130
+ kinds.push("text");
1131
+ starts.push(segmentation.starts[i]);
1132
+ i = j - 1;
1133
+ continue;
1134
+ }
1135
+ texts.push(text);
1136
+ isWordLike.push(wordLike);
1137
+ kinds.push(kind);
1138
+ starts.push(segmentation.starts[i]);
1139
+ }
1140
+ return {
1141
+ len: texts.length,
1142
+ texts,
1143
+ isWordLike,
1144
+ kinds,
1145
+ starts
1146
+ };
1147
+ }
1148
+ function splitHyphenatedNumericRuns(segmentation) {
1149
+ const texts = [];
1150
+ const isWordLike = [];
1151
+ const kinds = [];
1152
+ const starts = [];
1153
+ for (let i = 0;i < segmentation.len; i++) {
1154
+ const text = segmentation.texts[i];
1155
+ if (segmentation.kinds[i] === "text" && text.includes("-")) {
1156
+ const parts = text.split("-");
1157
+ let shouldSplit = parts.length > 1;
1158
+ for (let j = 0;j < parts.length; j++) {
1159
+ const part = parts[j];
1160
+ if (!shouldSplit)
1161
+ break;
1162
+ if (part.length === 0 || !segmentContainsDecimalDigit(part) || !isNumericRunSegment(part)) {
1163
+ shouldSplit = false;
1164
+ }
1165
+ }
1166
+ if (shouldSplit) {
1167
+ let offset = 0;
1168
+ for (let j = 0;j < parts.length; j++) {
1169
+ const part = parts[j];
1170
+ const splitText = j < parts.length - 1 ? `${part}-` : part;
1171
+ texts.push(splitText);
1172
+ isWordLike.push(true);
1173
+ kinds.push("text");
1174
+ starts.push(segmentation.starts[i] + offset);
1175
+ offset += splitText.length;
1176
+ }
1177
+ continue;
1178
+ }
1179
+ }
1180
+ texts.push(text);
1181
+ isWordLike.push(segmentation.isWordLike[i]);
1182
+ kinds.push(segmentation.kinds[i]);
1183
+ starts.push(segmentation.starts[i]);
1184
+ }
1185
+ return {
1186
+ len: texts.length,
1187
+ texts,
1188
+ isWordLike,
1189
+ kinds,
1190
+ starts
1191
+ };
1192
+ }
1193
+ function mergeGlueConnectedTextRuns(segmentation) {
1194
+ const texts = [];
1195
+ const isWordLike = [];
1196
+ const kinds = [];
1197
+ const starts = [];
1198
+ let read = 0;
1199
+ while (read < segmentation.len) {
1200
+ let text = segmentation.texts[read];
1201
+ let wordLike = segmentation.isWordLike[read];
1202
+ let kind = segmentation.kinds[read];
1203
+ let start = segmentation.starts[read];
1204
+ if (kind === "glue") {
1205
+ let glueText = text;
1206
+ const glueStart = start;
1207
+ read++;
1208
+ while (read < segmentation.len && segmentation.kinds[read] === "glue") {
1209
+ glueText += segmentation.texts[read];
1210
+ read++;
1211
+ }
1212
+ if (read < segmentation.len && segmentation.kinds[read] === "text") {
1213
+ text = glueText + segmentation.texts[read];
1214
+ wordLike = segmentation.isWordLike[read];
1215
+ kind = "text";
1216
+ start = glueStart;
1217
+ read++;
1218
+ } else {
1219
+ texts.push(glueText);
1220
+ isWordLike.push(false);
1221
+ kinds.push("glue");
1222
+ starts.push(glueStart);
1223
+ continue;
1224
+ }
1225
+ } else {
1226
+ read++;
1227
+ }
1228
+ if (kind === "text") {
1229
+ while (read < segmentation.len && segmentation.kinds[read] === "glue") {
1230
+ let glueText = "";
1231
+ while (read < segmentation.len && segmentation.kinds[read] === "glue") {
1232
+ glueText += segmentation.texts[read];
1233
+ read++;
1234
+ }
1235
+ if (read < segmentation.len && segmentation.kinds[read] === "text") {
1236
+ text += glueText + segmentation.texts[read];
1237
+ wordLike = wordLike || segmentation.isWordLike[read];
1238
+ read++;
1239
+ continue;
1240
+ }
1241
+ text += glueText;
1242
+ }
1243
+ }
1244
+ texts.push(text);
1245
+ isWordLike.push(wordLike);
1246
+ kinds.push(kind);
1247
+ starts.push(start);
1248
+ }
1249
+ return {
1250
+ len: texts.length,
1251
+ texts,
1252
+ isWordLike,
1253
+ kinds,
1254
+ starts
1255
+ };
1256
+ }
1257
+ function carryTrailingForwardStickyAcrossCJKBoundary(segmentation) {
1258
+ const texts = segmentation.texts.slice();
1259
+ const isWordLike = segmentation.isWordLike.slice();
1260
+ const kinds = segmentation.kinds.slice();
1261
+ const starts = segmentation.starts.slice();
1262
+ for (let i = 0;i < texts.length - 1; i++) {
1263
+ if (kinds[i] !== "text" || kinds[i + 1] !== "text")
1264
+ continue;
1265
+ if (!isCJK(texts[i]) || !isCJK(texts[i + 1]))
1266
+ continue;
1267
+ const split = splitTrailingForwardStickyCluster(texts[i]);
1268
+ if (split === null)
1269
+ continue;
1270
+ texts[i] = split.head;
1271
+ texts[i + 1] = split.tail + texts[i + 1];
1272
+ starts[i + 1] = starts[i] + split.head.length;
1273
+ }
1274
+ return {
1275
+ len: texts.length,
1276
+ texts,
1277
+ isWordLike,
1278
+ kinds,
1279
+ starts
1280
+ };
1281
+ }
1282
+ function buildMergedSegmentation(normalized, profile, whiteSpaceProfile) {
1283
+ const wordSegmenter = getSharedWordSegmenter();
1284
+ let mergedLen = 0;
1285
+ const mergedTexts = [];
1286
+ const mergedWordLike = [];
1287
+ const mergedKinds = [];
1288
+ const mergedStarts = [];
1289
+ for (const s of wordSegmenter.segment(normalized)) {
1290
+ for (const piece of splitSegmentByBreakKind(s.segment, s.isWordLike ?? false, s.index, whiteSpaceProfile)) {
1291
+ const isText = piece.kind === "text";
1292
+ if (profile.carryCJKAfterClosingQuote && isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === "text" && isCJK(piece.text) && isCJK(mergedTexts[mergedLen - 1]) && endsWithClosingQuote(mergedTexts[mergedLen - 1])) {
1293
+ mergedTexts[mergedLen - 1] += piece.text;
1294
+ mergedWordLike[mergedLen - 1] = mergedWordLike[mergedLen - 1] || piece.isWordLike;
1295
+ } else if (isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === "text" && isCJKLineStartProhibitedSegment(piece.text) && isCJK(mergedTexts[mergedLen - 1])) {
1296
+ mergedTexts[mergedLen - 1] += piece.text;
1297
+ mergedWordLike[mergedLen - 1] = mergedWordLike[mergedLen - 1] || piece.isWordLike;
1298
+ } else if (isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === "text" && endsWithMyanmarMedialGlue(mergedTexts[mergedLen - 1])) {
1299
+ mergedTexts[mergedLen - 1] += piece.text;
1300
+ mergedWordLike[mergedLen - 1] = mergedWordLike[mergedLen - 1] || piece.isWordLike;
1301
+ } else if (isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === "text" && piece.isWordLike && containsArabicScript(piece.text) && endsWithArabicNoSpacePunctuation(mergedTexts[mergedLen - 1])) {
1302
+ mergedTexts[mergedLen - 1] += piece.text;
1303
+ mergedWordLike[mergedLen - 1] = true;
1304
+ } else if (isText && !piece.isWordLike && mergedLen > 0 && mergedKinds[mergedLen - 1] === "text" && piece.text.length === 1 && piece.text !== "-" && piece.text !== "—" && isRepeatedSingleCharRun(mergedTexts[mergedLen - 1], piece.text)) {
1305
+ mergedTexts[mergedLen - 1] += piece.text;
1306
+ } else if (isText && !piece.isWordLike && mergedLen > 0 && mergedKinds[mergedLen - 1] === "text" && (isLeftStickyPunctuationSegment(piece.text) || piece.text === "-" && mergedWordLike[mergedLen - 1])) {
1307
+ mergedTexts[mergedLen - 1] += piece.text;
1308
+ } else {
1309
+ mergedTexts[mergedLen] = piece.text;
1310
+ mergedWordLike[mergedLen] = piece.isWordLike;
1311
+ mergedKinds[mergedLen] = piece.kind;
1312
+ mergedStarts[mergedLen] = piece.start;
1313
+ mergedLen++;
1314
+ }
1315
+ }
1316
+ }
1317
+ for (let i = 1;i < mergedLen; i++) {
1318
+ if (mergedKinds[i] === "text" && !mergedWordLike[i] && isEscapedQuoteClusterSegment(mergedTexts[i]) && mergedKinds[i - 1] === "text") {
1319
+ mergedTexts[i - 1] += mergedTexts[i];
1320
+ mergedWordLike[i - 1] = mergedWordLike[i - 1] || mergedWordLike[i];
1321
+ mergedTexts[i] = "";
1322
+ }
1323
+ }
1324
+ for (let i = mergedLen - 2;i >= 0; i--) {
1325
+ if (mergedKinds[i] === "text" && !mergedWordLike[i] && isForwardStickyClusterSegment(mergedTexts[i])) {
1326
+ let j = i + 1;
1327
+ while (j < mergedLen && mergedTexts[j] === "")
1328
+ j++;
1329
+ if (j < mergedLen && mergedKinds[j] === "text") {
1330
+ mergedTexts[j] = mergedTexts[i] + mergedTexts[j];
1331
+ mergedStarts[j] = mergedStarts[i];
1332
+ mergedTexts[i] = "";
1333
+ }
1334
+ }
1335
+ }
1336
+ let compactLen = 0;
1337
+ for (let read = 0;read < mergedLen; read++) {
1338
+ const text = mergedTexts[read];
1339
+ if (text.length === 0)
1340
+ continue;
1341
+ if (compactLen !== read) {
1342
+ mergedTexts[compactLen] = text;
1343
+ mergedWordLike[compactLen] = mergedWordLike[read];
1344
+ mergedKinds[compactLen] = mergedKinds[read];
1345
+ mergedStarts[compactLen] = mergedStarts[read];
1346
+ }
1347
+ compactLen++;
1348
+ }
1349
+ mergedTexts.length = compactLen;
1350
+ mergedWordLike.length = compactLen;
1351
+ mergedKinds.length = compactLen;
1352
+ mergedStarts.length = compactLen;
1353
+ const compacted = mergeGlueConnectedTextRuns({
1354
+ len: compactLen,
1355
+ texts: mergedTexts,
1356
+ isWordLike: mergedWordLike,
1357
+ kinds: mergedKinds,
1358
+ starts: mergedStarts
1359
+ });
1360
+ const withMergedUrls = carryTrailingForwardStickyAcrossCJKBoundary(mergeAsciiPunctuationChains(splitHyphenatedNumericRuns(mergeNumericRuns(mergeUrlQueryRuns(mergeUrlLikeRuns(compacted))))));
1361
+ for (let i = 0;i < withMergedUrls.len - 1; i++) {
1362
+ const split = splitLeadingSpaceAndMarks(withMergedUrls.texts[i]);
1363
+ if (split === null)
1364
+ continue;
1365
+ if (withMergedUrls.kinds[i] !== "space" && withMergedUrls.kinds[i] !== "preserved-space" || withMergedUrls.kinds[i + 1] !== "text" || !containsArabicScript(withMergedUrls.texts[i + 1])) {
1366
+ continue;
1367
+ }
1368
+ withMergedUrls.texts[i] = split.space;
1369
+ withMergedUrls.isWordLike[i] = false;
1370
+ withMergedUrls.kinds[i] = withMergedUrls.kinds[i] === "preserved-space" ? "preserved-space" : "space";
1371
+ withMergedUrls.texts[i + 1] = split.marks + withMergedUrls.texts[i + 1];
1372
+ withMergedUrls.starts[i + 1] = withMergedUrls.starts[i] + split.space.length;
1373
+ }
1374
+ return withMergedUrls;
1375
+ }
1376
+ function compileAnalysisChunks(segmentation, whiteSpaceProfile) {
1377
+ if (segmentation.len === 0)
1378
+ return [];
1379
+ if (!whiteSpaceProfile.preserveHardBreaks) {
1380
+ return [
1381
+ {
1382
+ startSegmentIndex: 0,
1383
+ endSegmentIndex: segmentation.len,
1384
+ consumedEndSegmentIndex: segmentation.len
1385
+ }
1386
+ ];
1387
+ }
1388
+ const chunks = [];
1389
+ let startSegmentIndex = 0;
1390
+ for (let i = 0;i < segmentation.len; i++) {
1391
+ if (segmentation.kinds[i] !== "hard-break")
1392
+ continue;
1393
+ chunks.push({
1394
+ startSegmentIndex,
1395
+ endSegmentIndex: i,
1396
+ consumedEndSegmentIndex: i + 1
1397
+ });
1398
+ startSegmentIndex = i + 1;
1399
+ }
1400
+ if (startSegmentIndex < segmentation.len) {
1401
+ chunks.push({
1402
+ startSegmentIndex,
1403
+ endSegmentIndex: segmentation.len,
1404
+ consumedEndSegmentIndex: segmentation.len
1405
+ });
1406
+ }
1407
+ return chunks;
1408
+ }
1409
+ function analyzeText(text, profile, whiteSpace = "normal") {
1410
+ const whiteSpaceProfile = getWhiteSpaceProfile(whiteSpace);
1411
+ const normalized = whiteSpaceProfile.mode === "pre-wrap" ? normalizeWhitespacePreWrap(text) : normalizeWhitespaceNormal(text);
1412
+ if (normalized.length === 0) {
1413
+ return {
1414
+ normalized,
1415
+ chunks: [],
1416
+ len: 0,
1417
+ texts: [],
1418
+ isWordLike: [],
1419
+ kinds: [],
1420
+ starts: []
1421
+ };
1422
+ }
1423
+ const segmentation = buildMergedSegmentation(normalized, profile, whiteSpaceProfile);
1424
+ return {
1425
+ normalized,
1426
+ chunks: compileAnalysisChunks(segmentation, whiteSpaceProfile),
1427
+ ...segmentation
1428
+ };
1429
+ }
1430
+
1431
+ // torph/src/vendor/pretext/measurement.ts
1432
+ var measureContext = null;
1433
+ var segmentMetricCaches = new Map;
1434
+ var cachedEngineProfile = null;
1435
+ var emojiPresentationRe = /\p{Emoji_Presentation}/u;
1436
+ var maybeEmojiRe = /[\p{Emoji_Presentation}\p{Extended_Pictographic}\p{Regional_Indicator}\uFE0F\u20E3]/u;
1437
+ var sharedGraphemeSegmenter = null;
1438
+ var emojiCorrectionCache = new Map;
1439
+ function getMeasureContext() {
1440
+ if (measureContext !== null)
1441
+ return measureContext;
1442
+ if (typeof OffscreenCanvas !== "undefined") {
1443
+ measureContext = new OffscreenCanvas(1, 1).getContext("2d");
1444
+ return measureContext;
1445
+ }
1446
+ if (typeof document !== "undefined") {
1447
+ measureContext = document.createElement("canvas").getContext("2d");
1448
+ return measureContext;
1449
+ }
1450
+ throw new Error("Text measurement requires OffscreenCanvas or a DOM canvas context.");
1451
+ }
1452
+ function getSegmentMetricCache(font) {
1453
+ let cache = segmentMetricCaches.get(font);
1454
+ if (!cache) {
1455
+ cache = new Map;
1456
+ segmentMetricCaches.set(font, cache);
1457
+ }
1458
+ return cache;
1459
+ }
1460
+ function getSegmentMetrics(seg, cache) {
1461
+ let metrics = cache.get(seg);
1462
+ if (metrics === undefined) {
1463
+ const ctx = getMeasureContext();
1464
+ metrics = {
1465
+ width: ctx.measureText(seg).width,
1466
+ containsCJK: isCJK(seg)
1467
+ };
1468
+ cache.set(seg, metrics);
1469
+ }
1470
+ return metrics;
1471
+ }
1472
+ function getEngineProfile() {
1473
+ if (cachedEngineProfile !== null)
1474
+ return cachedEngineProfile;
1475
+ if (typeof navigator === "undefined") {
1476
+ cachedEngineProfile = {
1477
+ lineFitEpsilon: 0.005,
1478
+ carryCJKAfterClosingQuote: false,
1479
+ preferPrefixWidthsForBreakableRuns: false,
1480
+ preferEarlySoftHyphenBreak: false
1481
+ };
1482
+ return cachedEngineProfile;
1483
+ }
1484
+ const ua = navigator.userAgent;
1485
+ const vendor = navigator.vendor;
1486
+ const isSafari = vendor === "Apple Computer, Inc." && ua.includes("Safari/") && !ua.includes("Chrome/") && !ua.includes("Chromium/") && !ua.includes("CriOS/") && !ua.includes("FxiOS/") && !ua.includes("EdgiOS/");
1487
+ const isChromium = ua.includes("Chrome/") || ua.includes("Chromium/") || ua.includes("CriOS/") || ua.includes("Edg/");
1488
+ cachedEngineProfile = {
1489
+ lineFitEpsilon: isSafari ? 1 / 64 : 0.005,
1490
+ carryCJKAfterClosingQuote: isChromium,
1491
+ preferPrefixWidthsForBreakableRuns: isSafari,
1492
+ preferEarlySoftHyphenBreak: isSafari
1493
+ };
1494
+ return cachedEngineProfile;
1495
+ }
1496
+ function parseFontSize(font) {
1497
+ const m = font.match(/(\d+(?:\.\d+)?)\s*px/);
1498
+ return m ? parseFloat(m[1]) : 16;
1499
+ }
1500
+ function getSharedGraphemeSegmenter() {
1501
+ if (sharedGraphemeSegmenter === null) {
1502
+ sharedGraphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
1503
+ }
1504
+ return sharedGraphemeSegmenter;
1505
+ }
1506
+ function isEmojiGrapheme(g) {
1507
+ return emojiPresentationRe.test(g) || g.includes("️");
1508
+ }
1509
+ function textMayContainEmoji(text) {
1510
+ return maybeEmojiRe.test(text);
1511
+ }
1512
+ function getEmojiCorrection(font, fontSize) {
1513
+ let correction = emojiCorrectionCache.get(font);
1514
+ if (correction !== undefined)
1515
+ return correction;
1516
+ const ctx = getMeasureContext();
1517
+ ctx.font = font;
1518
+ const canvasW = ctx.measureText("\uD83D\uDE00").width;
1519
+ correction = 0;
1520
+ if (canvasW > fontSize + 0.5 && typeof document !== "undefined" && document.body !== null) {
1521
+ const span = document.createElement("span");
1522
+ span.style.font = font;
1523
+ span.style.display = "inline-block";
1524
+ span.style.visibility = "hidden";
1525
+ span.style.position = "absolute";
1526
+ span.textContent = "\uD83D\uDE00";
1527
+ document.body.appendChild(span);
1528
+ const domW = span.getBoundingClientRect().width;
1529
+ document.body.removeChild(span);
1530
+ if (canvasW - domW > 0.5) {
1531
+ correction = canvasW - domW;
1532
+ }
1533
+ }
1534
+ emojiCorrectionCache.set(font, correction);
1535
+ return correction;
1536
+ }
1537
+ function countEmojiGraphemes(text) {
1538
+ let count = 0;
1539
+ const graphemeSegmenter = getSharedGraphemeSegmenter();
1540
+ for (const g of graphemeSegmenter.segment(text)) {
1541
+ if (isEmojiGrapheme(g.segment))
1542
+ count++;
1543
+ }
1544
+ return count;
1545
+ }
1546
+ function getEmojiCount(seg, metrics) {
1547
+ if (metrics.emojiCount === undefined) {
1548
+ metrics.emojiCount = countEmojiGraphemes(seg);
1549
+ }
1550
+ return metrics.emojiCount;
1551
+ }
1552
+ function getCorrectedSegmentWidth(seg, metrics, emojiCorrection) {
1553
+ if (emojiCorrection === 0)
1554
+ return metrics.width;
1555
+ return metrics.width - getEmojiCount(seg, metrics) * emojiCorrection;
1556
+ }
1557
+ function getSegmentGraphemeWidths(seg, metrics, cache, emojiCorrection) {
1558
+ if (metrics.graphemeWidths !== undefined)
1559
+ return metrics.graphemeWidths;
1560
+ const widths = [];
1561
+ const graphemeSegmenter = getSharedGraphemeSegmenter();
1562
+ for (const gs of graphemeSegmenter.segment(seg)) {
1563
+ const graphemeMetrics = getSegmentMetrics(gs.segment, cache);
1564
+ widths.push(getCorrectedSegmentWidth(gs.segment, graphemeMetrics, emojiCorrection));
1565
+ }
1566
+ metrics.graphemeWidths = widths.length > 1 ? widths : null;
1567
+ return metrics.graphemeWidths;
1568
+ }
1569
+ function getSegmentGraphemePrefixWidths(seg, metrics, cache, emojiCorrection) {
1570
+ if (metrics.graphemePrefixWidths !== undefined)
1571
+ return metrics.graphemePrefixWidths;
1572
+ const prefixWidths = [];
1573
+ const graphemeSegmenter = getSharedGraphemeSegmenter();
1574
+ let prefix = "";
1575
+ for (const gs of graphemeSegmenter.segment(seg)) {
1576
+ prefix += gs.segment;
1577
+ const prefixMetrics = getSegmentMetrics(prefix, cache);
1578
+ prefixWidths.push(getCorrectedSegmentWidth(prefix, prefixMetrics, emojiCorrection));
1579
+ }
1580
+ metrics.graphemePrefixWidths = prefixWidths.length > 1 ? prefixWidths : null;
1581
+ return metrics.graphemePrefixWidths;
1582
+ }
1583
+ function getFontMeasurementState(font, needsEmojiCorrection) {
1584
+ const ctx = getMeasureContext();
1585
+ ctx.font = font;
1586
+ const cache = getSegmentMetricCache(font);
1587
+ const fontSize = parseFontSize(font);
1588
+ const emojiCorrection = needsEmojiCorrection ? getEmojiCorrection(font, fontSize) : 0;
1589
+ return { cache, fontSize, emojiCorrection };
1590
+ }
1591
+ function clearMeasurementCaches() {
1592
+ segmentMetricCaches.clear();
1593
+ emojiCorrectionCache.clear();
1594
+ sharedGraphemeSegmenter = null;
1595
+ }
1596
+
1597
+ // torph/src/vendor/pretext/line-break.ts
1598
+ function canBreakAfter(kind) {
1599
+ return kind === "space" || kind === "preserved-space" || kind === "tab" || kind === "zero-width-break" || kind === "soft-hyphen";
1600
+ }
1601
+ function getTabAdvance(lineWidth, tabStopAdvance) {
1602
+ if (tabStopAdvance <= 0)
1603
+ return 0;
1604
+ const remainder = lineWidth % tabStopAdvance;
1605
+ if (Math.abs(remainder) <= 0.000001)
1606
+ return tabStopAdvance;
1607
+ return tabStopAdvance - remainder;
1608
+ }
1609
+ function getBreakableAdvance(graphemeWidths, graphemePrefixWidths, graphemeIndex, preferPrefixWidths) {
1610
+ if (!preferPrefixWidths || graphemePrefixWidths === null) {
1611
+ return graphemeWidths[graphemeIndex];
1612
+ }
1613
+ return graphemePrefixWidths[graphemeIndex] - (graphemeIndex > 0 ? graphemePrefixWidths[graphemeIndex - 1] : 0);
1614
+ }
1615
+ function fitSoftHyphenBreak(graphemeWidths, initialWidth, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, cumulativeWidths) {
1616
+ let fitCount = 0;
1617
+ let fittedWidth = initialWidth;
1618
+ while (fitCount < graphemeWidths.length) {
1619
+ const nextWidth = cumulativeWidths ? initialWidth + graphemeWidths[fitCount] : fittedWidth + graphemeWidths[fitCount];
1620
+ const nextLineWidth = fitCount + 1 < graphemeWidths.length ? nextWidth + discretionaryHyphenWidth : nextWidth;
1621
+ if (nextLineWidth > maxWidth + lineFitEpsilon)
1622
+ break;
1623
+ fittedWidth = nextWidth;
1624
+ fitCount++;
1625
+ }
1626
+ return { fitCount, fittedWidth };
1627
+ }
1628
+ function walkPreparedLinesSimple(prepared, maxWidth, onLine) {
1629
+ const { widths, kinds, breakableWidths, breakablePrefixWidths } = prepared;
1630
+ if (widths.length === 0)
1631
+ return 0;
1632
+ const engineProfile = getEngineProfile();
1633
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
1634
+ let lineCount = 0;
1635
+ let lineW = 0;
1636
+ let hasContent = false;
1637
+ let lineStartSegmentIndex = 0;
1638
+ let lineStartGraphemeIndex = 0;
1639
+ let lineEndSegmentIndex = 0;
1640
+ let lineEndGraphemeIndex = 0;
1641
+ let pendingBreakSegmentIndex = -1;
1642
+ let pendingBreakPaintWidth = 0;
1643
+ function clearPendingBreak() {
1644
+ pendingBreakSegmentIndex = -1;
1645
+ pendingBreakPaintWidth = 0;
1646
+ }
1647
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
1648
+ lineCount++;
1649
+ onLine?.({
1650
+ startSegmentIndex: lineStartSegmentIndex,
1651
+ startGraphemeIndex: lineStartGraphemeIndex,
1652
+ endSegmentIndex,
1653
+ endGraphemeIndex,
1654
+ width
1655
+ });
1656
+ lineW = 0;
1657
+ hasContent = false;
1658
+ clearPendingBreak();
1659
+ }
1660
+ function startLineAtSegment(segmentIndex, width) {
1661
+ hasContent = true;
1662
+ lineStartSegmentIndex = segmentIndex;
1663
+ lineStartGraphemeIndex = 0;
1664
+ lineEndSegmentIndex = segmentIndex + 1;
1665
+ lineEndGraphemeIndex = 0;
1666
+ lineW = width;
1667
+ }
1668
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
1669
+ hasContent = true;
1670
+ lineStartSegmentIndex = segmentIndex;
1671
+ lineStartGraphemeIndex = graphemeIndex;
1672
+ lineEndSegmentIndex = segmentIndex;
1673
+ lineEndGraphemeIndex = graphemeIndex + 1;
1674
+ lineW = width;
1675
+ }
1676
+ function appendWholeSegment(segmentIndex, width) {
1677
+ if (!hasContent) {
1678
+ startLineAtSegment(segmentIndex, width);
1679
+ return;
1680
+ }
1681
+ lineW += width;
1682
+ lineEndSegmentIndex = segmentIndex + 1;
1683
+ lineEndGraphemeIndex = 0;
1684
+ }
1685
+ function updatePendingBreak(segmentIndex, segmentWidth) {
1686
+ if (!canBreakAfter(kinds[segmentIndex]))
1687
+ return;
1688
+ pendingBreakSegmentIndex = segmentIndex + 1;
1689
+ pendingBreakPaintWidth = lineW - segmentWidth;
1690
+ }
1691
+ function appendBreakableSegment(segmentIndex) {
1692
+ appendBreakableSegmentFrom(segmentIndex, 0);
1693
+ }
1694
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
1695
+ const gWidths = breakableWidths[segmentIndex];
1696
+ const gPrefixWidths = breakablePrefixWidths[segmentIndex] ?? null;
1697
+ for (let g = startGraphemeIndex;g < gWidths.length; g++) {
1698
+ const gw = getBreakableAdvance(gWidths, gPrefixWidths, g, engineProfile.preferPrefixWidthsForBreakableRuns);
1699
+ if (!hasContent) {
1700
+ startLineAtGrapheme(segmentIndex, g, gw);
1701
+ continue;
1702
+ }
1703
+ if (lineW + gw > maxWidth + lineFitEpsilon) {
1704
+ emitCurrentLine();
1705
+ startLineAtGrapheme(segmentIndex, g, gw);
1706
+ } else {
1707
+ lineW += gw;
1708
+ lineEndSegmentIndex = segmentIndex;
1709
+ lineEndGraphemeIndex = g + 1;
1710
+ }
1711
+ }
1712
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === gWidths.length) {
1713
+ lineEndSegmentIndex = segmentIndex + 1;
1714
+ lineEndGraphemeIndex = 0;
1715
+ }
1716
+ }
1717
+ let i = 0;
1718
+ while (i < widths.length) {
1719
+ const w = widths[i];
1720
+ const kind = kinds[i];
1721
+ if (!hasContent) {
1722
+ if (w > maxWidth && breakableWidths[i] !== null) {
1723
+ appendBreakableSegment(i);
1724
+ } else {
1725
+ startLineAtSegment(i, w);
1726
+ }
1727
+ updatePendingBreak(i, w);
1728
+ i++;
1729
+ continue;
1730
+ }
1731
+ const newW = lineW + w;
1732
+ if (newW > maxWidth + lineFitEpsilon) {
1733
+ if (canBreakAfter(kind)) {
1734
+ appendWholeSegment(i, w);
1735
+ emitCurrentLine(i + 1, 0, lineW - w);
1736
+ i++;
1737
+ continue;
1738
+ }
1739
+ if (pendingBreakSegmentIndex >= 0) {
1740
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex || lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0) {
1741
+ emitCurrentLine();
1742
+ continue;
1743
+ }
1744
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
1745
+ continue;
1746
+ }
1747
+ if (w > maxWidth && breakableWidths[i] !== null) {
1748
+ emitCurrentLine();
1749
+ appendBreakableSegment(i);
1750
+ i++;
1751
+ continue;
1752
+ }
1753
+ emitCurrentLine();
1754
+ continue;
1755
+ }
1756
+ appendWholeSegment(i, w);
1757
+ updatePendingBreak(i, w);
1758
+ i++;
1759
+ }
1760
+ if (hasContent)
1761
+ emitCurrentLine();
1762
+ return lineCount;
1763
+ }
1764
+ function walkPreparedLines(prepared, maxWidth, onLine) {
1765
+ if (prepared.simpleLineWalkFastPath) {
1766
+ return walkPreparedLinesSimple(prepared, maxWidth, onLine);
1767
+ }
1768
+ const {
1769
+ widths,
1770
+ lineEndFitAdvances,
1771
+ lineEndPaintAdvances,
1772
+ kinds,
1773
+ breakableWidths,
1774
+ breakablePrefixWidths,
1775
+ discretionaryHyphenWidth,
1776
+ tabStopAdvance,
1777
+ chunks
1778
+ } = prepared;
1779
+ if (widths.length === 0 || chunks.length === 0)
1780
+ return 0;
1781
+ const engineProfile = getEngineProfile();
1782
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
1783
+ let lineCount = 0;
1784
+ let lineW = 0;
1785
+ let hasContent = false;
1786
+ let lineStartSegmentIndex = 0;
1787
+ let lineStartGraphemeIndex = 0;
1788
+ let lineEndSegmentIndex = 0;
1789
+ let lineEndGraphemeIndex = 0;
1790
+ let pendingBreakSegmentIndex = -1;
1791
+ let pendingBreakFitWidth = 0;
1792
+ let pendingBreakPaintWidth = 0;
1793
+ let pendingBreakKind = null;
1794
+ function clearPendingBreak() {
1795
+ pendingBreakSegmentIndex = -1;
1796
+ pendingBreakFitWidth = 0;
1797
+ pendingBreakPaintWidth = 0;
1798
+ pendingBreakKind = null;
1799
+ }
1800
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
1801
+ lineCount++;
1802
+ onLine?.({
1803
+ startSegmentIndex: lineStartSegmentIndex,
1804
+ startGraphemeIndex: lineStartGraphemeIndex,
1805
+ endSegmentIndex,
1806
+ endGraphemeIndex,
1807
+ width
1808
+ });
1809
+ lineW = 0;
1810
+ hasContent = false;
1811
+ clearPendingBreak();
1812
+ }
1813
+ function startLineAtSegment(segmentIndex, width) {
1814
+ hasContent = true;
1815
+ lineStartSegmentIndex = segmentIndex;
1816
+ lineStartGraphemeIndex = 0;
1817
+ lineEndSegmentIndex = segmentIndex + 1;
1818
+ lineEndGraphemeIndex = 0;
1819
+ lineW = width;
1820
+ }
1821
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
1822
+ hasContent = true;
1823
+ lineStartSegmentIndex = segmentIndex;
1824
+ lineStartGraphemeIndex = graphemeIndex;
1825
+ lineEndSegmentIndex = segmentIndex;
1826
+ lineEndGraphemeIndex = graphemeIndex + 1;
1827
+ lineW = width;
1828
+ }
1829
+ function appendWholeSegment(segmentIndex, width) {
1830
+ if (!hasContent) {
1831
+ startLineAtSegment(segmentIndex, width);
1832
+ return;
1833
+ }
1834
+ lineW += width;
1835
+ lineEndSegmentIndex = segmentIndex + 1;
1836
+ lineEndGraphemeIndex = 0;
1837
+ }
1838
+ function updatePendingBreakForWholeSegment(segmentIndex, segmentWidth) {
1839
+ if (!canBreakAfter(kinds[segmentIndex]))
1840
+ return;
1841
+ const fitAdvance = kinds[segmentIndex] === "tab" ? 0 : lineEndFitAdvances[segmentIndex];
1842
+ const paintAdvance = kinds[segmentIndex] === "tab" ? segmentWidth : lineEndPaintAdvances[segmentIndex];
1843
+ pendingBreakSegmentIndex = segmentIndex + 1;
1844
+ pendingBreakFitWidth = lineW - segmentWidth + fitAdvance;
1845
+ pendingBreakPaintWidth = lineW - segmentWidth + paintAdvance;
1846
+ pendingBreakKind = kinds[segmentIndex];
1847
+ }
1848
+ function appendBreakableSegment(segmentIndex) {
1849
+ appendBreakableSegmentFrom(segmentIndex, 0);
1850
+ }
1851
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
1852
+ const gWidths = breakableWidths[segmentIndex];
1853
+ const gPrefixWidths = breakablePrefixWidths[segmentIndex] ?? null;
1854
+ for (let g = startGraphemeIndex;g < gWidths.length; g++) {
1855
+ const gw = getBreakableAdvance(gWidths, gPrefixWidths, g, engineProfile.preferPrefixWidthsForBreakableRuns);
1856
+ if (!hasContent) {
1857
+ startLineAtGrapheme(segmentIndex, g, gw);
1858
+ continue;
1859
+ }
1860
+ if (lineW + gw > maxWidth + lineFitEpsilon) {
1861
+ emitCurrentLine();
1862
+ startLineAtGrapheme(segmentIndex, g, gw);
1863
+ } else {
1864
+ lineW += gw;
1865
+ lineEndSegmentIndex = segmentIndex;
1866
+ lineEndGraphemeIndex = g + 1;
1867
+ }
1868
+ }
1869
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === gWidths.length) {
1870
+ lineEndSegmentIndex = segmentIndex + 1;
1871
+ lineEndGraphemeIndex = 0;
1872
+ }
1873
+ }
1874
+ function continueSoftHyphenBreakableSegment(segmentIndex) {
1875
+ if (pendingBreakKind !== "soft-hyphen")
1876
+ return false;
1877
+ const gWidths = breakableWidths[segmentIndex];
1878
+ if (gWidths === null)
1879
+ return false;
1880
+ const fitWidths = engineProfile.preferPrefixWidthsForBreakableRuns ? breakablePrefixWidths[segmentIndex] ?? gWidths : gWidths;
1881
+ const usesPrefixWidths = fitWidths !== gWidths;
1882
+ const { fitCount, fittedWidth } = fitSoftHyphenBreak(fitWidths, lineW, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, usesPrefixWidths);
1883
+ if (fitCount === 0)
1884
+ return false;
1885
+ lineW = fittedWidth;
1886
+ lineEndSegmentIndex = segmentIndex;
1887
+ lineEndGraphemeIndex = fitCount;
1888
+ clearPendingBreak();
1889
+ if (fitCount === gWidths.length) {
1890
+ lineEndSegmentIndex = segmentIndex + 1;
1891
+ lineEndGraphemeIndex = 0;
1892
+ return true;
1893
+ }
1894
+ emitCurrentLine(segmentIndex, fitCount, fittedWidth + discretionaryHyphenWidth);
1895
+ appendBreakableSegmentFrom(segmentIndex, fitCount);
1896
+ return true;
1897
+ }
1898
+ function emitEmptyChunk(chunk) {
1899
+ lineCount++;
1900
+ onLine?.({
1901
+ startSegmentIndex: chunk.startSegmentIndex,
1902
+ startGraphemeIndex: 0,
1903
+ endSegmentIndex: chunk.consumedEndSegmentIndex,
1904
+ endGraphemeIndex: 0,
1905
+ width: 0
1906
+ });
1907
+ clearPendingBreak();
1908
+ }
1909
+ for (let chunkIndex = 0;chunkIndex < chunks.length; chunkIndex++) {
1910
+ const chunk = chunks[chunkIndex];
1911
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex) {
1912
+ emitEmptyChunk(chunk);
1913
+ continue;
1914
+ }
1915
+ hasContent = false;
1916
+ lineW = 0;
1917
+ lineStartSegmentIndex = chunk.startSegmentIndex;
1918
+ lineStartGraphemeIndex = 0;
1919
+ lineEndSegmentIndex = chunk.startSegmentIndex;
1920
+ lineEndGraphemeIndex = 0;
1921
+ clearPendingBreak();
1922
+ let i = chunk.startSegmentIndex;
1923
+ while (i < chunk.endSegmentIndex) {
1924
+ const kind = kinds[i];
1925
+ const w = kind === "tab" ? getTabAdvance(lineW, tabStopAdvance) : widths[i];
1926
+ if (kind === "soft-hyphen") {
1927
+ if (hasContent) {
1928
+ lineEndSegmentIndex = i + 1;
1929
+ lineEndGraphemeIndex = 0;
1930
+ pendingBreakSegmentIndex = i + 1;
1931
+ pendingBreakFitWidth = lineW + discretionaryHyphenWidth;
1932
+ pendingBreakPaintWidth = lineW + discretionaryHyphenWidth;
1933
+ pendingBreakKind = kind;
1934
+ }
1935
+ i++;
1936
+ continue;
1937
+ }
1938
+ if (!hasContent) {
1939
+ if (w > maxWidth && breakableWidths[i] !== null) {
1940
+ appendBreakableSegment(i);
1941
+ } else {
1942
+ startLineAtSegment(i, w);
1943
+ }
1944
+ updatePendingBreakForWholeSegment(i, w);
1945
+ i++;
1946
+ continue;
1947
+ }
1948
+ const newW = lineW + w;
1949
+ if (newW > maxWidth + lineFitEpsilon) {
1950
+ const currentBreakFitWidth = lineW + (kind === "tab" ? 0 : lineEndFitAdvances[i]);
1951
+ const currentBreakPaintWidth = lineW + (kind === "tab" ? w : lineEndPaintAdvances[i]);
1952
+ if (pendingBreakKind === "soft-hyphen" && engineProfile.preferEarlySoftHyphenBreak && pendingBreakFitWidth <= maxWidth + lineFitEpsilon) {
1953
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
1954
+ continue;
1955
+ }
1956
+ if (pendingBreakKind === "soft-hyphen" && continueSoftHyphenBreakableSegment(i)) {
1957
+ i++;
1958
+ continue;
1959
+ }
1960
+ if (canBreakAfter(kind) && currentBreakFitWidth <= maxWidth + lineFitEpsilon) {
1961
+ appendWholeSegment(i, w);
1962
+ emitCurrentLine(i + 1, 0, currentBreakPaintWidth);
1963
+ i++;
1964
+ continue;
1965
+ }
1966
+ if (pendingBreakSegmentIndex >= 0 && pendingBreakFitWidth <= maxWidth + lineFitEpsilon) {
1967
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex || lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0) {
1968
+ emitCurrentLine();
1969
+ continue;
1970
+ }
1971
+ const nextSegmentIndex = pendingBreakSegmentIndex;
1972
+ emitCurrentLine(nextSegmentIndex, 0, pendingBreakPaintWidth);
1973
+ i = nextSegmentIndex;
1974
+ continue;
1975
+ }
1976
+ if (w > maxWidth && breakableWidths[i] !== null) {
1977
+ emitCurrentLine();
1978
+ appendBreakableSegment(i);
1979
+ i++;
1980
+ continue;
1981
+ }
1982
+ emitCurrentLine();
1983
+ continue;
1984
+ }
1985
+ appendWholeSegment(i, w);
1986
+ updatePendingBreakForWholeSegment(i, w);
1987
+ i++;
1988
+ }
1989
+ if (hasContent) {
1990
+ const finalPaintWidth = pendingBreakSegmentIndex === chunk.consumedEndSegmentIndex ? pendingBreakPaintWidth : lineW;
1991
+ emitCurrentLine(chunk.consumedEndSegmentIndex, 0, finalPaintWidth);
1992
+ }
1993
+ }
1994
+ return lineCount;
1995
+ }
1996
+
1997
+ // torph/src/vendor/pretext/layout.ts
1998
+ var sharedGraphemeSegmenter2 = null;
1999
+ var sharedLineTextCaches = new WeakMap;
2000
+ function getSharedGraphemeSegmenter2() {
2001
+ if (sharedGraphemeSegmenter2 === null) {
2002
+ sharedGraphemeSegmenter2 = new Intl.Segmenter(undefined, { granularity: "grapheme" });
2003
+ }
2004
+ return sharedGraphemeSegmenter2;
2005
+ }
2006
+ function createEmptyPrepared(includeSegments) {
2007
+ if (includeSegments) {
2008
+ return {
2009
+ widths: [],
2010
+ lineEndFitAdvances: [],
2011
+ lineEndPaintAdvances: [],
2012
+ kinds: [],
2013
+ simpleLineWalkFastPath: true,
2014
+ segLevels: null,
2015
+ breakableWidths: [],
2016
+ breakablePrefixWidths: [],
2017
+ discretionaryHyphenWidth: 0,
2018
+ tabStopAdvance: 0,
2019
+ chunks: [],
2020
+ segments: []
2021
+ };
2022
+ }
2023
+ return {
2024
+ widths: [],
2025
+ lineEndFitAdvances: [],
2026
+ lineEndPaintAdvances: [],
2027
+ kinds: [],
2028
+ simpleLineWalkFastPath: true,
2029
+ segLevels: null,
2030
+ breakableWidths: [],
2031
+ breakablePrefixWidths: [],
2032
+ discretionaryHyphenWidth: 0,
2033
+ tabStopAdvance: 0,
2034
+ chunks: []
2035
+ };
2036
+ }
2037
+ function measureAnalysis(analysis, font, includeSegments) {
2038
+ const graphemeSegmenter = getSharedGraphemeSegmenter2();
2039
+ const engineProfile = getEngineProfile();
2040
+ const { cache, emojiCorrection } = getFontMeasurementState(font, textMayContainEmoji(analysis.normalized));
2041
+ const discretionaryHyphenWidth = getCorrectedSegmentWidth("-", getSegmentMetrics("-", cache), emojiCorrection);
2042
+ const spaceWidth = getCorrectedSegmentWidth(" ", getSegmentMetrics(" ", cache), emojiCorrection);
2043
+ const tabStopAdvance = spaceWidth * 8;
2044
+ if (analysis.len === 0)
2045
+ return createEmptyPrepared(includeSegments);
2046
+ const widths = [];
2047
+ const lineEndFitAdvances = [];
2048
+ const lineEndPaintAdvances = [];
2049
+ const kinds = [];
2050
+ let simpleLineWalkFastPath = analysis.chunks.length <= 1;
2051
+ const segStarts = includeSegments ? [] : null;
2052
+ const breakableWidths = [];
2053
+ const breakablePrefixWidths = [];
2054
+ const segments = includeSegments ? [] : null;
2055
+ const preparedStartByAnalysisIndex = Array.from({ length: analysis.len });
2056
+ const preparedEndByAnalysisIndex = Array.from({ length: analysis.len });
2057
+ function pushMeasuredSegment(text, width, lineEndFitAdvance, lineEndPaintAdvance, kind, start, breakable, breakablePrefix) {
2058
+ if (kind !== "text" && kind !== "space" && kind !== "zero-width-break") {
2059
+ simpleLineWalkFastPath = false;
2060
+ }
2061
+ widths.push(width);
2062
+ lineEndFitAdvances.push(lineEndFitAdvance);
2063
+ lineEndPaintAdvances.push(lineEndPaintAdvance);
2064
+ kinds.push(kind);
2065
+ segStarts?.push(start);
2066
+ breakableWidths.push(breakable);
2067
+ breakablePrefixWidths.push(breakablePrefix);
2068
+ if (segments !== null)
2069
+ segments.push(text);
2070
+ }
2071
+ for (let mi = 0;mi < analysis.len; mi++) {
2072
+ preparedStartByAnalysisIndex[mi] = widths.length;
2073
+ const segText = analysis.texts[mi];
2074
+ const segWordLike = analysis.isWordLike[mi];
2075
+ const segKind = analysis.kinds[mi];
2076
+ const segStart = analysis.starts[mi];
2077
+ if (segKind === "soft-hyphen") {
2078
+ pushMeasuredSegment(segText, 0, discretionaryHyphenWidth, discretionaryHyphenWidth, segKind, segStart, null, null);
2079
+ preparedEndByAnalysisIndex[mi] = widths.length;
2080
+ continue;
2081
+ }
2082
+ if (segKind === "hard-break") {
2083
+ pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null, null);
2084
+ preparedEndByAnalysisIndex[mi] = widths.length;
2085
+ continue;
2086
+ }
2087
+ if (segKind === "tab") {
2088
+ pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null, null);
2089
+ preparedEndByAnalysisIndex[mi] = widths.length;
2090
+ continue;
2091
+ }
2092
+ const segMetrics = getSegmentMetrics(segText, cache);
2093
+ if (segKind === "text" && segMetrics.containsCJK) {
2094
+ let unitText = "";
2095
+ let unitStart = 0;
2096
+ for (const gs of graphemeSegmenter.segment(segText)) {
2097
+ const grapheme = gs.segment;
2098
+ if (unitText.length === 0) {
2099
+ unitText = grapheme;
2100
+ unitStart = gs.index;
2101
+ continue;
2102
+ }
2103
+ if (kinsokuEnd.has(unitText) || kinsokuStart.has(grapheme) || leftStickyPunctuation.has(grapheme) || engineProfile.carryCJKAfterClosingQuote && isCJK(grapheme) && endsWithClosingQuote(unitText)) {
2104
+ unitText += grapheme;
2105
+ continue;
2106
+ }
2107
+ const unitMetrics = getSegmentMetrics(unitText, cache);
2108
+ const w2 = getCorrectedSegmentWidth(unitText, unitMetrics, emojiCorrection);
2109
+ pushMeasuredSegment(unitText, w2, w2, w2, "text", segStart + unitStart, null, null);
2110
+ unitText = grapheme;
2111
+ unitStart = gs.index;
2112
+ }
2113
+ if (unitText.length > 0) {
2114
+ const unitMetrics = getSegmentMetrics(unitText, cache);
2115
+ const w2 = getCorrectedSegmentWidth(unitText, unitMetrics, emojiCorrection);
2116
+ pushMeasuredSegment(unitText, w2, w2, w2, "text", segStart + unitStart, null, null);
2117
+ }
2118
+ preparedEndByAnalysisIndex[mi] = widths.length;
2119
+ continue;
2120
+ }
2121
+ const w = getCorrectedSegmentWidth(segText, segMetrics, emojiCorrection);
2122
+ const lineEndFitAdvance = segKind === "space" || segKind === "preserved-space" || segKind === "zero-width-break" ? 0 : w;
2123
+ const lineEndPaintAdvance = segKind === "space" || segKind === "zero-width-break" ? 0 : w;
2124
+ if (segWordLike && segText.length > 1) {
2125
+ const graphemeWidths = getSegmentGraphemeWidths(segText, segMetrics, cache, emojiCorrection);
2126
+ const graphemePrefixWidths = engineProfile.preferPrefixWidthsForBreakableRuns ? getSegmentGraphemePrefixWidths(segText, segMetrics, cache, emojiCorrection) : null;
2127
+ pushMeasuredSegment(segText, w, lineEndFitAdvance, lineEndPaintAdvance, segKind, segStart, graphemeWidths, graphemePrefixWidths);
2128
+ } else {
2129
+ pushMeasuredSegment(segText, w, lineEndFitAdvance, lineEndPaintAdvance, segKind, segStart, null, null);
2130
+ }
2131
+ preparedEndByAnalysisIndex[mi] = widths.length;
2132
+ }
2133
+ const chunks = mapAnalysisChunksToPreparedChunks(analysis.chunks, preparedStartByAnalysisIndex, preparedEndByAnalysisIndex);
2134
+ const segLevels = segStarts === null ? null : computeSegmentLevels(analysis.normalized, segStarts);
2135
+ if (segments !== null) {
2136
+ return {
2137
+ widths,
2138
+ lineEndFitAdvances,
2139
+ lineEndPaintAdvances,
2140
+ kinds,
2141
+ simpleLineWalkFastPath,
2142
+ segLevels,
2143
+ breakableWidths,
2144
+ breakablePrefixWidths,
2145
+ discretionaryHyphenWidth,
2146
+ tabStopAdvance,
2147
+ chunks,
2148
+ segments
2149
+ };
2150
+ }
2151
+ return {
2152
+ widths,
2153
+ lineEndFitAdvances,
2154
+ lineEndPaintAdvances,
2155
+ kinds,
2156
+ simpleLineWalkFastPath,
2157
+ segLevels,
2158
+ breakableWidths,
2159
+ breakablePrefixWidths,
2160
+ discretionaryHyphenWidth,
2161
+ tabStopAdvance,
2162
+ chunks
2163
+ };
2164
+ }
2165
+ function mapAnalysisChunksToPreparedChunks(chunks, preparedStartByAnalysisIndex, preparedEndByAnalysisIndex) {
2166
+ const preparedChunks = [];
2167
+ for (let i = 0;i < chunks.length; i++) {
2168
+ const chunk = chunks[i];
2169
+ const startSegmentIndex = chunk.startSegmentIndex < preparedStartByAnalysisIndex.length ? preparedStartByAnalysisIndex[chunk.startSegmentIndex] : preparedEndByAnalysisIndex[preparedEndByAnalysisIndex.length - 1] ?? 0;
2170
+ const endSegmentIndex = chunk.endSegmentIndex < preparedStartByAnalysisIndex.length ? preparedStartByAnalysisIndex[chunk.endSegmentIndex] : preparedEndByAnalysisIndex[preparedEndByAnalysisIndex.length - 1] ?? 0;
2171
+ const consumedEndSegmentIndex = chunk.consumedEndSegmentIndex < preparedStartByAnalysisIndex.length ? preparedStartByAnalysisIndex[chunk.consumedEndSegmentIndex] : preparedEndByAnalysisIndex[preparedEndByAnalysisIndex.length - 1] ?? 0;
2172
+ preparedChunks.push({
2173
+ startSegmentIndex,
2174
+ endSegmentIndex,
2175
+ consumedEndSegmentIndex
2176
+ });
2177
+ }
2178
+ return preparedChunks;
2179
+ }
2180
+ function prepareInternal(text, font, includeSegments, options) {
2181
+ const analysis = analyzeText(text, getEngineProfile(), options?.whiteSpace);
2182
+ return measureAnalysis(analysis, font, includeSegments);
2183
+ }
2184
+ function prepareWithSegments(text, font, options) {
2185
+ return prepareInternal(text, font, true, options);
2186
+ }
2187
+ function getInternalPrepared(prepared) {
2188
+ return prepared;
2189
+ }
2190
+ function toLayoutLineRange(line) {
2191
+ return {
2192
+ width: line.width,
2193
+ start: {
2194
+ segmentIndex: line.startSegmentIndex,
2195
+ graphemeIndex: line.startGraphemeIndex
2196
+ },
2197
+ end: {
2198
+ segmentIndex: line.endSegmentIndex,
2199
+ graphemeIndex: line.endGraphemeIndex
2200
+ }
2201
+ };
2202
+ }
2203
+ function walkLineRanges(prepared, maxWidth, onLine) {
2204
+ if (prepared.widths.length === 0)
2205
+ return 0;
2206
+ return walkPreparedLines(getInternalPrepared(prepared), maxWidth, (line) => {
2207
+ onLine(toLayoutLineRange(line));
2208
+ });
2209
+ }
2210
+ function clearCache() {
2211
+ clearAnalysisCaches();
2212
+ sharedGraphemeSegmenter2 = null;
2213
+ sharedLineTextCaches = new WeakMap;
2214
+ clearMeasurementCaches();
2215
+ }
2216
+ // torph/src/utils/text-layout/pretextMorph.ts
2217
+ var UNBOUNDED_LAYOUT_WIDTH = Number.MAX_SAFE_INTEGER / 4;
2218
+ var PREPARED_TEXT_CACHE_LIMIT = 256;
2219
+ var PROBE_COMPLEX_WHITESPACE_RE = /[\t\n\r\f]| {2,}|^ | $/;
2220
+ var FAST_PATH_RTL_RE = /[\u0590-\u08FF\uFB1D-\uFDFD\uFE70-\uFEFC]/u;
2221
+ var PROBE_SYSTEM_UI_RE = /\bsystem-ui\b/i;
2222
+ var preparedTextCache = new Map;
2223
+ var preparedSegmentGraphemeCache = new WeakMap;
2224
+ var sharedGraphemeSegmenter3 = null;
2225
+ function getSharedGraphemeSegmenter3() {
2226
+ if (sharedGraphemeSegmenter3 !== null) {
2227
+ return sharedGraphemeSegmenter3;
2228
+ }
2229
+ sharedGraphemeSegmenter3 = new Intl.Segmenter(undefined, {
2230
+ granularity: "grapheme"
2231
+ });
2232
+ return sharedGraphemeSegmenter3;
2233
+ }
2234
+ function readPretextWhiteSpace(whiteSpace) {
2235
+ return whiteSpace === "pre-wrap" ? "pre-wrap" : "normal";
2236
+ }
2237
+ function getPreparedTextCacheKey(renderText, layoutContext) {
2238
+ return `${layoutContext.font}\x00${layoutContext.whiteSpace}\x00${renderText}`;
2239
+ }
2240
+ function getPreparedText(text, renderText, layoutContext) {
2241
+ const cacheKey = getPreparedTextCacheKey(renderText, layoutContext);
2242
+ const cached = preparedTextCache.get(cacheKey);
2243
+ if (cached !== undefined) {
2244
+ preparedTextCache.delete(cacheKey);
2245
+ preparedTextCache.set(cacheKey, cached);
2246
+ return cached;
2247
+ }
2248
+ const prepared = prepareWithSegments(text, layoutContext.font, {
2249
+ whiteSpace: readPretextWhiteSpace(layoutContext.whiteSpace)
2250
+ });
2251
+ preparedTextCache.set(cacheKey, prepared);
2252
+ if (preparedTextCache.size > PREPARED_TEXT_CACHE_LIMIT) {
2253
+ const oldest = preparedTextCache.keys().next();
2254
+ if (!oldest.done) {
2255
+ preparedTextCache.delete(oldest.value);
2256
+ }
2257
+ }
2258
+ return prepared;
2259
+ }
2260
+ function clearPretextMorphCaches() {
2261
+ preparedTextCache.clear();
2262
+ sharedGraphemeSegmenter3 = null;
2263
+ clearCache();
2264
+ }
2265
+ function getPreparedSegmentGraphemeTexts(prepared, segmentIndex) {
2266
+ let cache = preparedSegmentGraphemeCache.get(prepared);
2267
+ if (cache === undefined) {
2268
+ cache = new Map;
2269
+ preparedSegmentGraphemeCache.set(prepared, cache);
2270
+ }
2271
+ const cached = cache.get(segmentIndex);
2272
+ if (cached !== undefined) {
2273
+ return cached;
2274
+ }
2275
+ const graphemes = [];
2276
+ for (const segment of getSharedGraphemeSegmenter3().segment(prepared.segments[segmentIndex])) {
2277
+ graphemes.push(segment.segment);
2278
+ }
2279
+ cache.set(segmentIndex, graphemes);
2280
+ return graphemes;
2281
+ }
2282
+ function deriveAdvancesFromPrefixWidths(prefixWidths) {
2283
+ const advances = [];
2284
+ for (let index = 0;index < prefixWidths.length; index += 1) {
2285
+ const previous = index === 0 ? 0 : prefixWidths[index - 1];
2286
+ advances.push(prefixWidths[index] - previous);
2287
+ }
2288
+ return advances;
2289
+ }
2290
+ function getSegmentGraphemeAdvances(prepared, segmentIndex, layoutContext) {
2291
+ const segmentText = prepared.segments[segmentIndex];
2292
+ const graphemes = getPreparedSegmentGraphemeTexts(prepared, segmentIndex);
2293
+ if (graphemes.length <= 1) {
2294
+ return {
2295
+ graphemes,
2296
+ advances: [prepared.widths[segmentIndex]]
2297
+ };
2298
+ }
2299
+ const cachedPrefixWidths = prepared.breakablePrefixWidths[segmentIndex];
2300
+ if (cachedPrefixWidths !== null) {
2301
+ return {
2302
+ graphemes,
2303
+ advances: deriveAdvancesFromPrefixWidths(cachedPrefixWidths)
2304
+ };
2305
+ }
2306
+ const cachedWidths = prepared.breakableWidths[segmentIndex];
2307
+ if (cachedWidths !== null) {
2308
+ return {
2309
+ graphemes,
2310
+ advances: cachedWidths
2311
+ };
2312
+ }
2313
+ const { cache, emojiCorrection } = getFontMeasurementState(layoutContext.font, textMayContainEmoji(segmentText));
2314
+ const segmentMetrics = getSegmentMetrics(segmentText, cache);
2315
+ const prefixWidths = getSegmentGraphemePrefixWidths(segmentText, segmentMetrics, cache, emojiCorrection);
2316
+ return {
2317
+ graphemes,
2318
+ advances: prefixWidths === null ? [prepared.widths[segmentIndex]] : deriveAdvancesFromPrefixWidths(prefixWidths)
2319
+ };
2320
+ }
2321
+ function getPretextMaxWidth(layoutContext) {
2322
+ return layoutContext.whiteSpace === "nowrap" ? UNBOUNDED_LAYOUT_WIDTH : Math.max(0, layoutContext.width);
2323
+ }
2324
+ function normalizeFeatureSetting(value) {
2325
+ return value.length === 0 ? "normal" : value;
2326
+ }
2327
+ function hasUnsupportedPretextMorphFeatures(text, layoutContext) {
2328
+ if (layoutContext === null) {
2329
+ return true;
2330
+ }
2331
+ if (text.length === 0) {
2332
+ return false;
2333
+ }
2334
+ if (layoutContext.font.length === 0 || layoutContext.lineHeightPx <= 0) {
2335
+ return true;
2336
+ }
2337
+ if (layoutContext.direction !== "ltr") {
2338
+ return true;
2339
+ }
2340
+ if (layoutContext.textTransform !== "none") {
2341
+ return true;
2342
+ }
2343
+ if (Math.abs(layoutContext.letterSpacingPx) > 0.01) {
2344
+ return true;
2345
+ }
2346
+ if (Math.abs(layoutContext.wordSpacingPx) > 0.01) {
2347
+ return true;
2348
+ }
2349
+ if (normalizeFeatureSetting(layoutContext.fontFeatureSettings) !== "normal" || normalizeFeatureSetting(layoutContext.fontVariationSettings) !== "normal") {
2350
+ return true;
2351
+ }
2352
+ if (text.includes("­") || FAST_PATH_RTL_RE.test(text)) {
2353
+ return true;
2354
+ }
2355
+ return false;
2356
+ }
2357
+ function shouldProbePretextMorph(text, layoutContext) {
2358
+ return PROBE_COMPLEX_WHITESPACE_RE.test(text) || PROBE_SYSTEM_UI_RE.test(layoutContext.font);
2359
+ }
2360
+ function getPretextMorphRenderedText(text, layoutContext) {
2361
+ if (layoutContext === null) {
2362
+ return text;
2363
+ }
2364
+ return layoutContext.whiteSpace === "pre-wrap" ? normalizeWhitespacePreWrap(text) : normalizeWhitespaceNormal(text);
2365
+ }
2366
+ function getPretextMorphStyleSignature(layoutContext) {
2367
+ if (layoutContext === null) {
2368
+ return null;
2369
+ }
2370
+ const engineProfile = getEngineProfile();
2371
+ return [
2372
+ layoutContext.font,
2373
+ layoutContext.whiteSpace,
2374
+ layoutContext.direction,
2375
+ layoutContext.textTransform,
2376
+ layoutContext.letterSpacingPx.toFixed(4),
2377
+ layoutContext.wordSpacingPx.toFixed(4),
2378
+ normalizeFeatureSetting(layoutContext.fontFeatureSettings),
2379
+ normalizeFeatureSetting(layoutContext.fontVariationSettings),
2380
+ String(engineProfile.lineFitEpsilon),
2381
+ engineProfile.carryCJKAfterClosingQuote ? "1" : "0",
2382
+ engineProfile.preferPrefixWidthsForBreakableRuns ? "1" : "0",
2383
+ engineProfile.preferEarlySoftHyphenBreak ? "1" : "0"
2384
+ ].join("\x00");
2385
+ }
2386
+ function getPretextMorphMeasurementBackend(text, layoutContext) {
2387
+ if (layoutContext === null) {
2388
+ return "dom";
2389
+ }
2390
+ if (text.length === 0) {
2391
+ return "pretext";
2392
+ }
2393
+ if (hasUnsupportedPretextMorphFeatures(text, layoutContext)) {
2394
+ return "dom";
2395
+ }
2396
+ return shouldProbePretextMorph(text, layoutContext) ? "probe" : "pretext";
2397
+ }
2398
+ function pushSegmentGraphemeRange({
2399
+ advances,
2400
+ graphemes,
2401
+ startGraphemeIndex,
2402
+ endGraphemeIndex,
2403
+ top,
2404
+ left,
2405
+ ordinal,
2406
+ output,
2407
+ lineHeightPx
2408
+ }) {
2409
+ let nextLeft = left;
2410
+ let nextOrdinal = ordinal;
2411
+ for (let graphemeIndex = startGraphemeIndex;graphemeIndex < endGraphemeIndex; graphemeIndex += 1) {
2412
+ const glyph = graphemes[graphemeIndex];
2413
+ const advance = advances[graphemeIndex];
2414
+ output.push({
2415
+ glyph,
2416
+ key: `${glyph}:${nextOrdinal}`,
2417
+ left: nextLeft,
2418
+ top,
2419
+ width: advance,
2420
+ height: lineHeightPx
2421
+ });
2422
+ nextOrdinal += 1;
2423
+ nextLeft += advance;
2424
+ }
2425
+ return {
2426
+ left: nextLeft,
2427
+ ordinal: nextOrdinal
2428
+ };
2429
+ }
2430
+ function measureMorphSnapshotWithPretext(text, layoutContext) {
2431
+ const renderText = getPretextMorphRenderedText(text, layoutContext);
2432
+ if (text.length === 0) {
2433
+ return {
2434
+ text,
2435
+ renderText,
2436
+ width: 0,
2437
+ height: 0,
2438
+ graphemes: []
2439
+ };
2440
+ }
2441
+ const prepared = getPreparedText(text, renderText, layoutContext);
2442
+ const graphemes = [];
2443
+ let width = 0;
2444
+ let ordinal = 0;
2445
+ let lineIndex = 0;
2446
+ const lineCount = walkLineRanges(prepared, getPretextMaxWidth(layoutContext), (line) => {
2447
+ const top = lineIndex * layoutContext.lineHeightPx;
2448
+ let left = 0;
2449
+ for (let segmentIndex = line.start.segmentIndex;segmentIndex < line.end.segmentIndex; segmentIndex += 1) {
2450
+ const startGraphemeIndex = segmentIndex === line.start.segmentIndex ? line.start.graphemeIndex : 0;
2451
+ const { advances, graphemes: segmentGraphemes } = getSegmentGraphemeAdvances(prepared, segmentIndex, layoutContext);
2452
+ const next = pushSegmentGraphemeRange({
2453
+ advances,
2454
+ graphemes: segmentGraphemes,
2455
+ startGraphemeIndex,
2456
+ endGraphemeIndex: segmentGraphemes.length,
2457
+ top,
2458
+ left,
2459
+ ordinal,
2460
+ output: graphemes,
2461
+ lineHeightPx: layoutContext.lineHeightPx
2462
+ });
2463
+ left = next.left;
2464
+ ordinal = next.ordinal;
2465
+ }
2466
+ if (line.end.graphemeIndex > 0) {
2467
+ const startGraphemeIndex = line.start.segmentIndex === line.end.segmentIndex ? line.start.graphemeIndex : 0;
2468
+ const { advances, graphemes: segmentGraphemes } = getSegmentGraphemeAdvances(prepared, line.end.segmentIndex, layoutContext);
2469
+ const next = pushSegmentGraphemeRange({
2470
+ advances,
2471
+ graphemes: segmentGraphemes,
2472
+ startGraphemeIndex,
2473
+ endGraphemeIndex: line.end.graphemeIndex,
2474
+ top,
2475
+ left,
2476
+ ordinal,
2477
+ output: graphemes,
2478
+ lineHeightPx: layoutContext.lineHeightPx
2479
+ });
2480
+ left = next.left;
2481
+ ordinal = next.ordinal;
2482
+ }
2483
+ width = Math.max(width, left);
2484
+ lineIndex += 1;
2485
+ });
2486
+ return {
2487
+ text,
2488
+ renderText,
2489
+ width,
2490
+ height: lineCount * layoutContext.lineHeightPx,
2491
+ graphemes
2492
+ };
2493
+ }
2494
+
2495
+ // torph/src/components/Torph.tsx
2496
+ import { jsxDEV } from "react/jsx-dev-runtime";
2497
+ var MORPH = {
2498
+ durationMs: 280,
2499
+ maxFadeMs: 150,
2500
+ ease: "cubic-bezier(0.22, 1, 0.36, 1)",
2501
+ geometryEpsilon: 0.5,
2502
+ lineGroupingEpsilon: 1
2503
+ };
2504
+ var MORPH_SEGMENT_CACHE_LIMIT = 256;
2505
+ var DOM_MEASUREMENT_SNAPSHOT_CACHE_LIMIT = 8;
2506
+ var EMPTY_STATE = {
2507
+ stage: "idle",
2508
+ measurement: null,
2509
+ plan: null
2510
+ };
2511
+ var EMPTY_SESSION = {
2512
+ committed: null,
2513
+ target: null,
2514
+ animating: false
2515
+ };
2516
+ var EMPTY_TIMELINE = {
2517
+ prepareFrame: null,
2518
+ animateFrame: null,
2519
+ finalizeTimer: null
2520
+ };
2521
+ var ZERO_BRIDGE = {
2522
+ offsetX: 0,
2523
+ offsetY: 0
2524
+ };
2525
+ var EMPTY_SEGMENTS = [];
2526
+ var morphSegmentCache = new Map;
2527
+ var pretextMorphTrustCache = new Map;
2528
+ var morphMeasurementEpoch = 1;
2529
+ var activeMorphMeasurementConsumers = 0;
2530
+ var detachMorphMeasurementInvalidationListeners = null;
2531
+ var SCREEN_READER_ONLY_STYLE = {
2532
+ position: "absolute",
2533
+ width: "1px",
2534
+ height: "1px",
2535
+ margin: "-1px",
2536
+ padding: 0,
2537
+ border: 0,
2538
+ clip: "rect(0 0 0 0)",
2539
+ clipPath: "inset(50%)",
2540
+ overflow: "hidden",
2541
+ whiteSpace: "nowrap"
2542
+ };
2543
+ var MEASUREMENT_LAYER_STYLE = {
2544
+ pointerEvents: "none",
2545
+ visibility: "hidden",
2546
+ position: "absolute",
2547
+ top: 0,
2548
+ left: 0,
2549
+ right: 0,
2550
+ display: "block"
2551
+ };
2552
+ var FALLBACK_TEXT_STYLE = {
2553
+ display: "block",
2554
+ gridArea: "1 / 1"
2555
+ };
2556
+ var OVERLAY_STYLE = {
2557
+ position: "absolute",
2558
+ inset: 0,
2559
+ minWidth: 0,
2560
+ pointerEvents: "none"
2561
+ };
2562
+ var SHARED_GLYPH_TYPOGRAPHY_STYLE = {
2563
+ font: "inherit",
2564
+ fontKerning: "inherit",
2565
+ fontFeatureSettings: "inherit",
2566
+ fontOpticalSizing: "inherit",
2567
+ fontStretch: "inherit",
2568
+ fontStyle: "inherit",
2569
+ fontVariant: "inherit",
2570
+ fontVariantNumeric: "inherit",
2571
+ fontVariationSettings: "inherit",
2572
+ fontWeight: "inherit",
2573
+ letterSpacing: "inherit",
2574
+ textTransform: "inherit",
2575
+ wordSpacing: "inherit",
2576
+ direction: "inherit"
2577
+ };
2578
+ var MEASUREMENT_GLYPH_STYLE = {
2579
+ ...SHARED_GLYPH_TYPOGRAPHY_STYLE,
2580
+ display: "inline"
2581
+ };
2582
+ var ABSOLUTE_GLYPH_STYLE = {
2583
+ ...SHARED_GLYPH_TYPOGRAPHY_STYLE,
2584
+ position: "absolute",
2585
+ display: "block",
2586
+ overflow: "visible",
2587
+ transformOrigin: "left top",
2588
+ whiteSpace: "pre"
2589
+ };
2590
+ var graphemeSegmenter = null;
2591
+ var domMeasurementService = null;
2592
+ function parsePx(value) {
2593
+ const parsed = Number.parseFloat(value);
2594
+ if (Number.isFinite(parsed)) {
2595
+ return parsed;
2596
+ }
2597
+ return null;
2598
+ }
2599
+ function readFont(styles) {
2600
+ if (styles.font.length > 0) {
2601
+ return styles.font;
2602
+ }
2603
+ return `${styles.fontStyle} ${styles.fontVariant} ${styles.fontWeight} ${styles.fontSize} / ${styles.lineHeight} ${styles.fontFamily}`;
2604
+ }
2605
+ function readLineHeightPx(styles) {
2606
+ const lineHeightPx = parsePx(styles.lineHeight);
2607
+ if (lineHeightPx !== null) {
2608
+ return lineHeightPx;
2609
+ }
2610
+ const fontSizePx = parsePx(styles.fontSize);
2611
+ return (fontSizePx ?? 0) * 1.2;
2612
+ }
2613
+ function readSpacingPx(value) {
2614
+ if (value === "normal") {
2615
+ return 0;
2616
+ }
2617
+ return parsePx(value) ?? 0;
2618
+ }
2619
+ function readWhiteSpace(value) {
2620
+ if (value === "normal" || value === "nowrap" || value === "pre-wrap") {
2621
+ return value;
2622
+ }
2623
+ throw new Error(`Torph only supports white-space: normal | nowrap | pre-wrap. Received: ${value}`);
2624
+ }
2625
+ function readContentWidth(node, styles) {
2626
+ const rectWidth = node.getBoundingClientRect().width;
2627
+ const paddingLeft = parsePx(styles.paddingLeft) ?? 0;
2628
+ const paddingRight = parsePx(styles.paddingRight) ?? 0;
2629
+ const borderLeft = parsePx(styles.borderLeftWidth) ?? 0;
2630
+ const borderRight = parsePx(styles.borderRightWidth) ?? 0;
2631
+ return Math.max(0, rectWidth - paddingLeft - paddingRight - borderLeft - borderRight);
2632
+ }
2633
+ function readLayoutContext(node, width) {
2634
+ const styles = getComputedStyle(node);
2635
+ const parentDisplay = (node.parentElement && getComputedStyle(node.parentElement).display) ?? "block";
2636
+ return {
2637
+ display: styles.display,
2638
+ direction: styles.direction,
2639
+ font: readFont(styles),
2640
+ fontFeatureSettings: styles.fontFeatureSettings,
2641
+ fontVariationSettings: styles.fontVariationSettings,
2642
+ letterSpacingPx: readSpacingPx(styles.letterSpacing),
2643
+ lineHeightPx: readLineHeightPx(styles),
2644
+ parentDisplay,
2645
+ textTransform: styles.textTransform,
2646
+ whiteSpace: readWhiteSpace(styles.whiteSpace),
2647
+ width: width ?? readContentWidth(node, styles),
2648
+ wordSpacingPx: readSpacingPx(styles.wordSpacing),
2649
+ measurementVersion: 0
2650
+ };
2651
+ }
2652
+ function sameLayoutContext(a, b) {
2653
+ if (a === null) {
2654
+ return false;
2655
+ }
2656
+ return a.display === b.display && a.direction === b.direction && a.font === b.font && a.fontFeatureSettings === b.fontFeatureSettings && a.fontVariationSettings === b.fontVariationSettings && Math.abs(a.letterSpacingPx - b.letterSpacingPx) < MORPH.geometryEpsilon && Math.abs(a.lineHeightPx - b.lineHeightPx) < MORPH.geometryEpsilon && a.parentDisplay === b.parentDisplay && a.textTransform === b.textTransform && a.whiteSpace === b.whiteSpace && Math.abs(a.width - b.width) < MORPH.geometryEpsilon && Math.abs(a.wordSpacingPx - b.wordSpacingPx) < MORPH.geometryEpsilon;
2657
+ }
2658
+ function clearPretextMorphTrustCache() {
2659
+ pretextMorphTrustCache.clear();
2660
+ }
2661
+ function clearMorphMeasurementCaches() {
2662
+ morphSegmentCache.clear();
2663
+ clearPretextMorphTrustCache();
2664
+ clearPretextMorphCaches();
2665
+ }
2666
+ function bumpMorphMeasurementEpoch() {
2667
+ morphMeasurementEpoch += 1;
2668
+ }
2669
+ function getMorphMeasurementEpoch() {
2670
+ return morphMeasurementEpoch;
2671
+ }
2672
+ function isSingleLineSnapshot(snapshot) {
2673
+ if (snapshot.graphemes.length <= 1) {
2674
+ return true;
2675
+ }
2676
+ const firstTop = snapshot.graphemes[0].top;
2677
+ return snapshot.graphemes.every((grapheme) => nearlyEqual(grapheme.top, firstTop, MORPH.lineGroupingEpsilon));
2678
+ }
2679
+ function acquireMorphMeasurementInvalidationListeners() {
2680
+ activeMorphMeasurementConsumers += 1;
2681
+ if (detachMorphMeasurementInvalidationListeners === null) {
2682
+ const handleFontChange = () => {
2683
+ clearMorphMeasurementCaches();
2684
+ bumpMorphMeasurementEpoch();
2685
+ };
2686
+ document.fonts.ready.then(handleFontChange);
2687
+ if (typeof document.fonts.addEventListener === "function") {
2688
+ document.fonts.addEventListener("loadingdone", handleFontChange);
2689
+ }
2690
+ detachMorphMeasurementInvalidationListeners = () => {
2691
+ if (typeof document.fonts.removeEventListener === "function") {
2692
+ document.fonts.removeEventListener("loadingdone", handleFontChange);
2693
+ }
2694
+ };
2695
+ }
2696
+ }
2697
+ function releaseMorphMeasurementInvalidationListeners() {
2698
+ activeMorphMeasurementConsumers = Math.max(0, activeMorphMeasurementConsumers - 1);
2699
+ if (activeMorphMeasurementConsumers === 0 && detachMorphMeasurementInvalidationListeners !== null) {
2700
+ detachMorphMeasurementInvalidationListeners();
2701
+ detachMorphMeasurementInvalidationListeners = null;
2702
+ }
2703
+ }
2704
+ function useObservedLayoutContext(deps) {
2705
+ const ref = useRef(null);
2706
+ const [layoutContext, setLayoutContext] = useState(null);
2707
+ useLayoutEffect(() => {
2708
+ const node = ref.current;
2709
+ if (node === null) {
2710
+ return;
2711
+ }
2712
+ let disposed = false;
2713
+ const commitLayoutContext = (next, refreshMeasurements = false) => {
2714
+ setLayoutContext((previous) => {
2715
+ if (sameLayoutContext(previous, next) && !refreshMeasurements) {
2716
+ return previous;
2717
+ }
2718
+ const measurementVersion = (previous?.measurementVersion ?? 0) + 1;
2719
+ return {
2720
+ ...next,
2721
+ measurementVersion
2722
+ };
2723
+ });
2724
+ };
2725
+ const syncLayoutContext = ({
2726
+ width,
2727
+ refreshMeasurements = false
2728
+ } = {}) => {
2729
+ if (disposed) {
2730
+ return;
2731
+ }
2732
+ const next = readLayoutContext(node, width);
2733
+ commitLayoutContext(next, refreshMeasurements);
2734
+ };
2735
+ const initialLayoutContext = readLayoutContext(node);
2736
+ const shouldObserveWrappingWidth = initialLayoutContext.whiteSpace !== "nowrap" && !supportsIntrinsicWidthLock(initialLayoutContext.display, initialLayoutContext.parentDisplay);
2737
+ commitLayoutContext(initialLayoutContext, true);
2738
+ let resizeObserver = null;
2739
+ if (shouldObserveWrappingWidth) {
2740
+ resizeObserver = new ResizeObserver(([entry]) => {
2741
+ syncLayoutContext({
2742
+ width: entry?.contentRect.width
2743
+ });
2744
+ });
2745
+ }
2746
+ resizeObserver?.observe(node);
2747
+ acquireMorphMeasurementInvalidationListeners();
2748
+ return () => {
2749
+ disposed = true;
2750
+ resizeObserver?.disconnect();
2751
+ releaseMorphMeasurementInvalidationListeners();
2752
+ };
2753
+ }, deps);
2754
+ return { ref, layoutContext };
2755
+ }
2756
+ function getSegmenter() {
2757
+ if (graphemeSegmenter !== null) {
2758
+ return graphemeSegmenter;
2759
+ }
2760
+ const segmenterConstructor = Intl.Segmenter;
2761
+ if (segmenterConstructor === undefined) {
2762
+ throw new Error("Torph requires Intl.Segmenter for grapheme-safe pairing.");
2763
+ }
2764
+ graphemeSegmenter = new segmenterConstructor(undefined, {
2765
+ granularity: "grapheme"
2766
+ });
2767
+ return graphemeSegmenter;
2768
+ }
2769
+ function createMeasurementGlyphNode() {
2770
+ const node = document.createElement("span");
2771
+ node.style.font = "inherit";
2772
+ node.style.fontKerning = "inherit";
2773
+ node.style.fontFeatureSettings = "inherit";
2774
+ node.style.fontOpticalSizing = "inherit";
2775
+ node.style.fontStretch = "inherit";
2776
+ node.style.fontStyle = "inherit";
2777
+ node.style.fontVariant = "inherit";
2778
+ node.style.fontVariantNumeric = "inherit";
2779
+ node.style.fontVariationSettings = "inherit";
2780
+ node.style.fontWeight = "inherit";
2781
+ node.style.letterSpacing = "inherit";
2782
+ node.style.textTransform = "inherit";
2783
+ node.style.wordSpacing = "inherit";
2784
+ node.style.direction = "inherit";
2785
+ node.style.display = "inline";
2786
+ return node;
2787
+ }
2788
+ function getDomMeasurementService() {
2789
+ if (domMeasurementService !== null) {
2790
+ return domMeasurementService;
2791
+ }
2792
+ const root = document.createElement("div");
2793
+ root.setAttribute("aria-hidden", "true");
2794
+ root.style.position = "fixed";
2795
+ root.style.left = "0";
2796
+ root.style.top = "0";
2797
+ root.style.width = "0";
2798
+ root.style.height = "0";
2799
+ root.style.overflow = "hidden";
2800
+ root.style.visibility = "hidden";
2801
+ root.style.pointerEvents = "none";
2802
+ root.style.zIndex = "-1";
2803
+ root.style.contain = "layout style paint";
2804
+ const host = document.createElement("span");
2805
+ root.appendChild(host);
2806
+ document.body.appendChild(root);
2807
+ domMeasurementService = {
2808
+ root,
2809
+ host,
2810
+ glyphNodes: []
2811
+ };
2812
+ return domMeasurementService;
2813
+ }
2814
+ function syncMeasurementGlyphNodes(service, segments) {
2815
+ while (service.glyphNodes.length < segments.length) {
2816
+ const node = createMeasurementGlyphNode();
2817
+ service.host.appendChild(node);
2818
+ service.glyphNodes.push(node);
2819
+ }
2820
+ while (service.glyphNodes.length > segments.length) {
2821
+ const node = service.glyphNodes.pop();
2822
+ node?.remove();
2823
+ }
2824
+ for (let index = 0;index < segments.length; index += 1) {
2825
+ service.glyphNodes[index].textContent = segments[index].glyph;
2826
+ }
2827
+ }
2828
+ function applyMeasurementHostStyle({
2829
+ host,
2830
+ root,
2831
+ layoutContext,
2832
+ useContentInlineSize
2833
+ }) {
2834
+ const styles = getComputedStyle(root);
2835
+ host.style.position = "absolute";
2836
+ host.style.top = "0";
2837
+ host.style.left = "0";
2838
+ host.style.right = "auto";
2839
+ host.style.display = "block";
2840
+ host.style.margin = "0";
2841
+ host.style.padding = "0";
2842
+ host.style.border = "0";
2843
+ host.style.minWidth = "0";
2844
+ host.style.boxSizing = "content-box";
2845
+ host.style.font = readFont(styles);
2846
+ host.style.fontKerning = styles.fontKerning;
2847
+ host.style.fontFeatureSettings = styles.fontFeatureSettings;
2848
+ host.style.fontOpticalSizing = styles.fontOpticalSizing;
2849
+ host.style.fontSynthesis = styles.fontSynthesis;
2850
+ host.style.fontStretch = styles.fontStretch;
2851
+ host.style.fontStyle = styles.fontStyle;
2852
+ host.style.fontVariant = styles.fontVariant;
2853
+ host.style.fontVariantAlternates = styles.fontVariantAlternates;
2854
+ host.style.fontVariantCaps = styles.fontVariantCaps;
2855
+ host.style.fontVariantEastAsian = styles.fontVariantEastAsian;
2856
+ host.style.fontVariantLigatures = styles.fontVariantLigatures;
2857
+ host.style.fontVariantNumeric = styles.fontVariantNumeric;
2858
+ host.style.fontVariantPosition = styles.fontVariantPosition;
2859
+ host.style.fontVariationSettings = styles.fontVariationSettings;
2860
+ host.style.fontWeight = styles.fontWeight;
2861
+ host.style.letterSpacing = styles.letterSpacing;
2862
+ host.style.lineHeight = styles.lineHeight;
2863
+ host.style.textRendering = styles.textRendering;
2864
+ host.style.textTransform = styles.textTransform;
2865
+ host.style.whiteSpace = styles.whiteSpace;
2866
+ host.style.wordSpacing = styles.wordSpacing;
2867
+ host.style.direction = styles.direction;
2868
+ host.style.width = `${layoutContext.width}px`;
2869
+ if (useContentInlineSize || layoutContext.whiteSpace === "nowrap") {
2870
+ host.style.width = "max-content";
2871
+ }
2872
+ }
2873
+ function segmentTorphText(text) {
2874
+ const segments = [];
2875
+ let ordinal = 0;
2876
+ for (const segment of getSegmenter().segment(text)) {
2877
+ segments.push({
2878
+ glyph: segment.segment,
2879
+ key: `${segment.segment}:${ordinal}`
2880
+ });
2881
+ ordinal += 1;
2882
+ }
2883
+ return segments;
2884
+ }
2885
+ function readCachedMorphSegments(text) {
2886
+ const cached = morphSegmentCache.get(text);
2887
+ if (cached !== undefined) {
2888
+ morphSegmentCache.delete(text);
2889
+ morphSegmentCache.set(text, cached);
2890
+ return cached;
2891
+ }
2892
+ const segments = segmentTorphText(text);
2893
+ morphSegmentCache.set(text, segments);
2894
+ if (morphSegmentCache.size > MORPH_SEGMENT_CACHE_LIMIT) {
2895
+ const oldest = morphSegmentCache.keys().next();
2896
+ if (!oldest.done) {
2897
+ morphSegmentCache.delete(oldest.value);
2898
+ }
2899
+ }
2900
+ return segments;
2901
+ }
2902
+ function getDomMeasurementRequestKey(text, renderText, layoutContext, useContentInlineSize) {
2903
+ let inlineSizeMode = "container";
2904
+ if (useContentInlineSize) {
2905
+ inlineSizeMode = "content";
2906
+ }
2907
+ return `dom\x00${inlineSizeMode}\x00${text}\x00${renderText}\x00${layoutContext.measurementVersion}\x00${getMorphMeasurementEpoch()}`;
2908
+ }
2909
+ function readCachedMorphSnapshot(cache, cacheKey) {
2910
+ const cached = cache.get(cacheKey);
2911
+ if (cached === undefined) {
2912
+ return null;
2913
+ }
2914
+ cache.delete(cacheKey);
2915
+ cache.set(cacheKey, cached);
2916
+ return cached;
2917
+ }
2918
+ function rememberCachedMorphSnapshot(cache, cacheKey, snapshot) {
2919
+ cache.delete(cacheKey);
2920
+ cache.set(cacheKey, snapshot);
2921
+ if (cache.size > DOM_MEASUREMENT_SNAPSHOT_CACHE_LIMIT) {
2922
+ const oldest = cache.keys().next();
2923
+ if (!oldest.done) {
2924
+ cache.delete(oldest.value);
2925
+ }
2926
+ }
2927
+ }
2928
+ function assertMeasurementLayer(layer, segments) {
2929
+ if (layer === null) {
2930
+ throw new Error("Torph measurement layer is missing.");
2931
+ }
2932
+ if (layer.children.length !== segments.length) {
2933
+ throw new Error(`Torph measurement layer is out of sync. Expected ${segments.length} glyph nodes, received ${layer.children.length}.`);
2934
+ }
2935
+ return layer;
2936
+ }
2937
+ function readMeasuredGlyphLayouts(layer, layerRect, segments) {
2938
+ const measuredGlyphs = [];
2939
+ const layerOffsetTop = layer.offsetTop;
2940
+ for (let index = 0;index < segments.length; index += 1) {
2941
+ const segment = segments[index];
2942
+ const child = layer.children[index];
2943
+ if (!(child instanceof HTMLElement)) {
2944
+ throw new Error(`Torph glyph node ${index} is not an HTMLElement.`);
2945
+ }
2946
+ const rect = child.getBoundingClientRect();
2947
+ measuredGlyphs.push({
2948
+ glyph: segment.glyph,
2949
+ key: segment.key,
2950
+ left: rect.left - layerRect.left,
2951
+ top: child.offsetTop - layerOffsetTop,
2952
+ width: rect.width
2953
+ });
2954
+ }
2955
+ return measuredGlyphs;
2956
+ }
2957
+ function assignMeasuredGlyphLineIndices(measuredGlyphs) {
2958
+ const lineIndices = [];
2959
+ let lineCount = 0;
2960
+ let currentLineTop = null;
2961
+ for (const glyph of measuredGlyphs) {
2962
+ if (currentLineTop === null || Math.abs(glyph.top - currentLineTop) > MORPH.lineGroupingEpsilon) {
2963
+ currentLineTop = glyph.top;
2964
+ lineCount += 1;
2965
+ }
2966
+ lineIndices.push(lineCount - 1);
2967
+ }
2968
+ return {
2969
+ lineCount,
2970
+ lineIndices
2971
+ };
2972
+ }
2973
+ function measureMorphSnapshotFromLayer(text, renderText, segments, layer) {
2974
+ if (renderText.length === 0) {
2975
+ return {
2976
+ text,
2977
+ renderText,
2978
+ width: 0,
2979
+ height: 0,
2980
+ graphemes: []
2981
+ };
2982
+ }
2983
+ const measurementLayer = assertMeasurementLayer(layer, segments);
2984
+ const layerRect = measurementLayer.getBoundingClientRect();
2985
+ const measuredGlyphs = readMeasuredGlyphLayouts(measurementLayer, layerRect, segments);
2986
+ const { lineCount, lineIndices } = assignMeasuredGlyphLineIndices(measuredGlyphs);
2987
+ let lineHeight = 0;
2988
+ if (lineCount !== 0) {
2989
+ lineHeight = layerRect.height / lineCount;
2990
+ }
2991
+ let width = 0;
2992
+ const graphemes = measuredGlyphs.map((glyph, index) => {
2993
+ const lineIndex = lineIndices[index];
2994
+ if (lineIndex === undefined) {
2995
+ throw new Error("Torph failed to assign a line index.");
2996
+ }
2997
+ width = Math.max(width, glyph.left + glyph.width);
2998
+ return {
2999
+ glyph: glyph.glyph,
3000
+ key: glyph.key,
3001
+ left: glyph.left,
3002
+ top: lineIndex * lineHeight,
3003
+ width: glyph.width,
3004
+ height: lineHeight
3005
+ };
3006
+ });
3007
+ return {
3008
+ text,
3009
+ renderText,
3010
+ width,
3011
+ height: layerRect.height,
3012
+ graphemes
3013
+ };
3014
+ }
3015
+ function measureMorphSnapshotWithDomService({
3016
+ root,
3017
+ layoutContext,
3018
+ text,
3019
+ renderText,
3020
+ segments,
3021
+ useContentInlineSize
3022
+ }) {
3023
+ if (renderText.length === 0) {
3024
+ return {
3025
+ text,
3026
+ renderText,
3027
+ width: 0,
3028
+ height: 0,
3029
+ graphemes: []
3030
+ };
3031
+ }
3032
+ const service = getDomMeasurementService();
3033
+ applyMeasurementHostStyle({
3034
+ host: service.host,
3035
+ root,
3036
+ layoutContext,
3037
+ useContentInlineSize
3038
+ });
3039
+ syncMeasurementGlyphNodes(service, segments);
3040
+ return measureMorphSnapshotFromLayer(text, renderText, segments, service.host);
3041
+ }
3042
+ function readRootOrigin(node) {
3043
+ const rect = node.getBoundingClientRect();
3044
+ return { left: rect.left, top: rect.top };
3045
+ }
3046
+ function getTrustedPretextMeasurementBackend(text, layoutContext) {
3047
+ const backend = getPretextMorphMeasurementBackend(text, layoutContext);
3048
+ if (backend !== "probe") {
3049
+ return backend;
3050
+ }
3051
+ const signature = getPretextMorphStyleSignature(layoutContext);
3052
+ if (signature === null) {
3053
+ return "dom";
3054
+ }
3055
+ const trusted = pretextMorphTrustCache.get(signature);
3056
+ if (trusted === undefined) {
3057
+ return "probe";
3058
+ }
3059
+ if (trusted) {
3060
+ return "pretext";
3061
+ }
3062
+ return "dom";
3063
+ }
3064
+ function shouldMeasureUsingContentInlineSize(layoutContext, layoutHint) {
3065
+ if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
3066
+ return true;
3067
+ }
3068
+ if (layoutContext.whiteSpace === "nowrap") {
3069
+ return true;
3070
+ }
3071
+ if (layoutHint === null || !isSingleLineSnapshot(layoutHint.snapshot)) {
3072
+ return false;
3073
+ }
3074
+ return nearlyEqual(layoutHint.layoutInlineSize, layoutHint.snapshot.width);
3075
+ }
3076
+ function createMorphMeasurementRequest({
3077
+ text,
3078
+ layoutContext,
3079
+ layoutHint
3080
+ }) {
3081
+ if (layoutContext === null) {
3082
+ return null;
3083
+ }
3084
+ const renderText = getPretextMorphRenderedText(text, layoutContext);
3085
+ const useContentInlineSize = shouldMeasureUsingContentInlineSize(layoutContext, layoutHint);
3086
+ const measurementBackend = getTrustedPretextMeasurementBackend(text, layoutContext);
3087
+ let segments = readCachedMorphSegments(renderText);
3088
+ if (measurementBackend === "pretext") {
3089
+ segments = EMPTY_SEGMENTS;
3090
+ }
3091
+ let domMeasurementKey = null;
3092
+ if (measurementBackend === "dom" && renderText.length > 0) {
3093
+ domMeasurementKey = getDomMeasurementRequestKey(text, renderText, layoutContext, useContentInlineSize);
3094
+ }
3095
+ return {
3096
+ text,
3097
+ renderText,
3098
+ segments,
3099
+ measurementBackend,
3100
+ useContentInlineSize,
3101
+ domMeasurementKey
3102
+ };
3103
+ }
3104
+ function rememberPretextMeasurementTrust(layoutContext, trusted) {
3105
+ const signature = getPretextMorphStyleSignature(layoutContext);
3106
+ if (signature === null) {
3107
+ return;
3108
+ }
3109
+ pretextMorphTrustCache.set(signature, trusted);
3110
+ }
3111
+ function areSnapshotsEquivalentForPretextTrust(left, right) {
3112
+ if (left.renderText !== right.renderText || left.graphemes.length !== right.graphemes.length) {
3113
+ return false;
3114
+ }
3115
+ if (Math.abs(left.width - right.width) > MORPH.geometryEpsilon || Math.abs(left.height - right.height) > MORPH.geometryEpsilon) {
3116
+ return false;
3117
+ }
3118
+ for (let index = 0;index < left.graphemes.length; index += 1) {
3119
+ const from = left.graphemes[index];
3120
+ const to = right.graphemes[index];
3121
+ if (from.glyph !== to.glyph) {
3122
+ return false;
3123
+ }
3124
+ if (Math.abs(from.left - to.left) > MORPH.geometryEpsilon || Math.abs(from.top - to.top) > MORPH.geometryEpsilon || Math.abs(from.width - to.width) > MORPH.geometryEpsilon || Math.abs(from.height - to.height) > MORPH.geometryEpsilon) {
3125
+ return false;
3126
+ }
3127
+ }
3128
+ return true;
3129
+ }
3130
+ function measureFromNodes({
3131
+ root,
3132
+ layoutContext,
3133
+ layoutHint,
3134
+ layer,
3135
+ measurementBackend,
3136
+ snapshotOverride,
3137
+ text,
3138
+ renderText,
3139
+ segments
3140
+ }) {
3141
+ const useContentInlineSize = shouldMeasureUsingContentInlineSize(layoutContext, layoutHint);
3142
+ let measurementLayoutContext = layoutContext;
3143
+ if (useContentInlineSize && layoutContext.whiteSpace !== "nowrap") {
3144
+ measurementLayoutContext = {
3145
+ ...layoutContext,
3146
+ width: Number.MAX_SAFE_INTEGER / 4
3147
+ };
3148
+ }
3149
+ const snapshot = snapshotOverride ?? (() => {
3150
+ let pretextSnapshot = null;
3151
+ if (measurementBackend !== "dom") {
3152
+ pretextSnapshot = measureMorphSnapshotWithPretext(text, measurementLayoutContext);
3153
+ }
3154
+ let domSnapshot = null;
3155
+ if (measurementBackend === "dom") {
3156
+ domSnapshot = measureMorphSnapshotFromLayer(text, renderText, segments, layer);
3157
+ } else if (measurementBackend !== "pretext") {
3158
+ domSnapshot = measureMorphSnapshotWithDomService({
3159
+ root,
3160
+ layoutContext,
3161
+ text,
3162
+ renderText,
3163
+ segments,
3164
+ useContentInlineSize
3165
+ });
3166
+ }
3167
+ if (measurementBackend === "probe" && pretextSnapshot !== null && domSnapshot !== null) {
3168
+ const trusted = areSnapshotsEquivalentForPretextTrust(pretextSnapshot, domSnapshot);
3169
+ rememberPretextMeasurementTrust(layoutContext, trusted);
3170
+ if (trusted) {
3171
+ return pretextSnapshot;
3172
+ }
3173
+ return domSnapshot;
3174
+ }
3175
+ const resolvedSnapshot = pretextSnapshot ?? domSnapshot;
3176
+ if (resolvedSnapshot === null) {
3177
+ throw new Error("Torph failed to resolve a measurement snapshot.");
3178
+ }
3179
+ return resolvedSnapshot;
3180
+ })();
3181
+ let layoutInlineSize = layoutContext.width;
3182
+ if (useContentInlineSize) {
3183
+ layoutInlineSize = snapshot.width;
3184
+ }
3185
+ let reservedInlineSize = null;
3186
+ if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
3187
+ reservedInlineSize = snapshot.width;
3188
+ }
3189
+ return {
3190
+ snapshot,
3191
+ layoutInlineSize,
3192
+ reservedInlineSize,
3193
+ rootOrigin: readRootOrigin(root)
3194
+ };
3195
+ }
3196
+ function pinMeasurementToCurrentOrigin(measurement, origin) {
3197
+ if (nearlyEqual(measurement.rootOrigin.left, origin.left) && nearlyEqual(measurement.rootOrigin.top, origin.top)) {
3198
+ return measurement;
3199
+ }
3200
+ return {
3201
+ snapshot: measurement.snapshot,
3202
+ layoutInlineSize: measurement.layoutInlineSize,
3203
+ reservedInlineSize: measurement.reservedInlineSize,
3204
+ rootOrigin: origin
3205
+ };
3206
+ }
3207
+ function bucketByGlyph(graphemes) {
3208
+ const buckets = new Map;
3209
+ for (const grapheme of graphemes) {
3210
+ const bucket = buckets.get(grapheme.glyph);
3211
+ if (bucket) {
3212
+ bucket.push(grapheme);
3213
+ } else {
3214
+ buckets.set(grapheme.glyph, [grapheme]);
3215
+ }
3216
+ }
3217
+ return buckets;
3218
+ }
3219
+ function pairMorphCharacters(previous, next) {
3220
+ const previousBuckets = bucketByGlyph(previous);
3221
+ const nextBuckets = bucketByGlyph(next);
3222
+ const pairings = [];
3223
+ for (const [glyph, previousItems] of previousBuckets) {
3224
+ const nextItems = nextBuckets.get(glyph) ?? [];
3225
+ const shared = Math.min(previousItems.length, nextItems.length);
3226
+ for (let index = 0;index < shared; index += 1) {
3227
+ pairings.push({
3228
+ kind: "move",
3229
+ from: previousItems[index],
3230
+ to: nextItems[index]
3231
+ });
3232
+ }
3233
+ for (let index = shared;index < previousItems.length; index += 1) {
3234
+ pairings.push({
3235
+ kind: "exit",
3236
+ from: previousItems[index]
3237
+ });
3238
+ }
3239
+ }
3240
+ for (const [glyph, nextItems] of nextBuckets) {
3241
+ const previousItems = previousBuckets.get(glyph) ?? [];
3242
+ const shared = Math.min(previousItems.length, nextItems.length);
3243
+ for (let index = shared;index < nextItems.length; index += 1) {
3244
+ pairings.push({
3245
+ kind: "enter",
3246
+ to: nextItems[index]
3247
+ });
3248
+ }
3249
+ }
3250
+ return pairings;
3251
+ }
3252
+ function resolveMorphFrameBounds(previous, next) {
3253
+ return {
3254
+ width: Math.max(previous.width, next.width),
3255
+ height: Math.max(previous.height, next.height)
3256
+ };
3257
+ }
3258
+ function buildMorphVisualBridge(previous, next) {
3259
+ return {
3260
+ offsetX: previous.rootOrigin.left - next.rootOrigin.left,
3261
+ offsetY: previous.rootOrigin.top - next.rootOrigin.top
3262
+ };
3263
+ }
3264
+ function buildMorphPlan(previous, next, visualBridge = ZERO_BRIDGE) {
3265
+ const pairings = pairMorphCharacters(previous.snapshot.graphemes, next.snapshot.graphemes);
3266
+ const movesByDestinationKey = new Map;
3267
+ const exitItems = [];
3268
+ for (const pairing of pairings) {
3269
+ if (pairing.kind === "move") {
3270
+ movesByDestinationKey.set(pairing.to.key, pairing);
3271
+ continue;
3272
+ }
3273
+ if (pairing.kind === "exit") {
3274
+ exitItems.push(pairing.from);
3275
+ }
3276
+ }
3277
+ const frame = resolveMorphFrameBounds(previous.snapshot, next.snapshot);
3278
+ return {
3279
+ frameWidth: frame.width,
3280
+ frameHeight: frame.height,
3281
+ layoutInlineSizeFrom: previous.layoutInlineSize,
3282
+ layoutInlineSizeTo: next.layoutInlineSize,
3283
+ visualBridge,
3284
+ liveItems: next.snapshot.graphemes.map((grapheme) => {
3285
+ const move = movesByDestinationKey.get(grapheme.key);
3286
+ if (move) {
3287
+ return {
3288
+ ...grapheme,
3289
+ kind: "move",
3290
+ fromLeft: move.from.left,
3291
+ fromTop: move.from.top
3292
+ };
3293
+ }
3294
+ return {
3295
+ ...grapheme,
3296
+ kind: "enter",
3297
+ fromLeft: null,
3298
+ fromTop: null
3299
+ };
3300
+ }),
3301
+ exitItems
3302
+ };
3303
+ }
3304
+ function nearlyEqual(a, b, epsilon = MORPH.geometryEpsilon) {
3305
+ return Math.abs(a - b) <= epsilon;
3306
+ }
3307
+ function sameSnapshot(a, b) {
3308
+ if (a === b) {
3309
+ return true;
3310
+ }
3311
+ if (a.text !== b.text || a.renderText !== b.renderText || a.graphemes.length !== b.graphemes.length) {
3312
+ return false;
3313
+ }
3314
+ if (!nearlyEqual(a.width, b.width) || !nearlyEqual(a.height, b.height)) {
3315
+ return false;
3316
+ }
3317
+ for (let index = 0;index < a.graphemes.length; index += 1) {
3318
+ const left = a.graphemes[index];
3319
+ const right = b.graphemes[index];
3320
+ if (left.glyph !== right.glyph || left.key !== right.key) {
3321
+ return false;
3322
+ }
3323
+ if (!nearlyEqual(left.left, right.left) || !nearlyEqual(left.top, right.top) || !nearlyEqual(left.width, right.width) || !nearlyEqual(left.height, right.height)) {
3324
+ return false;
3325
+ }
3326
+ }
3327
+ return true;
3328
+ }
3329
+ function sameMeasurement(a, b) {
3330
+ if (a === b) {
3331
+ return true;
3332
+ }
3333
+ return sameSnapshot(a.snapshot, b.snapshot) && nearlyEqual(a.layoutInlineSize, b.layoutInlineSize) && (a.reservedInlineSize === null && b.reservedInlineSize === null || a.reservedInlineSize !== null && b.reservedInlineSize !== null && nearlyEqual(a.reservedInlineSize, b.reservedInlineSize)) && nearlyEqual(a.rootOrigin.left, b.rootOrigin.left) && nearlyEqual(a.rootOrigin.top, b.rootOrigin.top);
3334
+ }
3335
+ function refreshAnimatingTarget(activeTarget, measurement) {
3336
+ if (sameMeasurement(activeTarget, measurement)) {
3337
+ return activeTarget;
3338
+ }
3339
+ return {
3340
+ snapshot: activeTarget.snapshot,
3341
+ layoutInlineSize: measurement.layoutInlineSize,
3342
+ reservedInlineSize: measurement.reservedInlineSize,
3343
+ rootOrigin: measurement.rootOrigin
3344
+ };
3345
+ }
3346
+ function reuseCommittedMeasurement(committed, measurement) {
3347
+ if (sameMeasurement(committed, measurement)) {
3348
+ return committed;
3349
+ }
3350
+ return measurement;
3351
+ }
3352
+ function createStaticState(measurement) {
3353
+ return {
3354
+ stage: "idle",
3355
+ measurement,
3356
+ plan: null
3357
+ };
3358
+ }
3359
+ function areFontsReady() {
3360
+ return document.fonts.status === "loaded";
3361
+ }
3362
+ function cancelTimeline(timeline) {
3363
+ if (timeline.prepareFrame !== null) {
3364
+ cancelAnimationFrame(timeline.prepareFrame);
3365
+ timeline.prepareFrame = null;
3366
+ }
3367
+ if (timeline.animateFrame !== null) {
3368
+ cancelAnimationFrame(timeline.animateFrame);
3369
+ timeline.animateFrame = null;
3370
+ }
3371
+ if (timeline.finalizeTimer !== null) {
3372
+ window.clearTimeout(timeline.finalizeTimer);
3373
+ timeline.finalizeTimer = null;
3374
+ }
3375
+ }
3376
+ function resetMorph(session, timeline, setState) {
3377
+ cancelTimeline(timeline);
3378
+ session.committed = null;
3379
+ session.target = null;
3380
+ session.animating = false;
3381
+ setState(EMPTY_STATE);
3382
+ }
3383
+ function commitStaticMeasurement(session, measurement, setState) {
3384
+ session.committed = measurement;
3385
+ session.target = null;
3386
+ session.animating = false;
3387
+ setState(createStaticState(measurement));
3388
+ }
3389
+ function scheduleMorphTimeline({
3390
+ session,
3391
+ timeline,
3392
+ measurement,
3393
+ plan,
3394
+ setState
3395
+ }) {
3396
+ timeline.prepareFrame = requestAnimationFrame(() => {
3397
+ timeline.prepareFrame = null;
3398
+ setState((current) => {
3399
+ if (current.measurement !== measurement || current.plan !== plan) {
3400
+ return current;
3401
+ }
3402
+ return {
3403
+ stage: "animate",
3404
+ measurement,
3405
+ plan
3406
+ };
3407
+ });
3408
+ timeline.animateFrame = requestAnimationFrame(() => {
3409
+ timeline.animateFrame = null;
3410
+ timeline.finalizeTimer = window.setTimeout(() => {
3411
+ timeline.finalizeTimer = null;
3412
+ commitStaticMeasurement(session, session.target ?? measurement, setState);
3413
+ }, MORPH.durationMs);
3414
+ });
3415
+ });
3416
+ }
3417
+ function startMorph({
3418
+ nextMeasurement,
3419
+ session,
3420
+ timeline,
3421
+ setState
3422
+ }) {
3423
+ let sourceMeasurement = session.committed;
3424
+ if (session.animating && session.target) {
3425
+ sourceMeasurement = session.target;
3426
+ }
3427
+ if (sourceMeasurement === null) {
3428
+ commitStaticMeasurement(session, nextMeasurement, setState);
3429
+ return;
3430
+ }
3431
+ const previousMeasurement = pinMeasurementToCurrentOrigin(sourceMeasurement, nextMeasurement.rootOrigin);
3432
+ const visualBridge = buildMorphVisualBridge(previousMeasurement, nextMeasurement);
3433
+ const plan = buildMorphPlan(previousMeasurement, nextMeasurement, visualBridge);
3434
+ session.target = nextMeasurement;
3435
+ session.animating = true;
3436
+ setState({
3437
+ stage: "prepare",
3438
+ measurement: nextMeasurement,
3439
+ plan
3440
+ });
3441
+ scheduleMorphTimeline({
3442
+ session,
3443
+ timeline,
3444
+ measurement: nextMeasurement,
3445
+ plan,
3446
+ setState
3447
+ });
3448
+ }
3449
+ function reconcileMorphChange({
3450
+ root,
3451
+ measurementLayer,
3452
+ measurementBackend,
3453
+ snapshotOverride,
3454
+ text,
3455
+ renderText,
3456
+ segments,
3457
+ layoutContext,
3458
+ session,
3459
+ timeline,
3460
+ setState
3461
+ }) {
3462
+ if (root === null || layoutContext === null) {
3463
+ resetMorph(session, timeline, setState);
3464
+ return null;
3465
+ }
3466
+ if (measurementBackend === null) {
3467
+ throw new Error("Torph measurement backend is missing.");
3468
+ }
3469
+ let layoutHint = session.committed;
3470
+ if (session.animating && session.target !== null) {
3471
+ layoutHint = session.target;
3472
+ }
3473
+ const nextMeasurement = measureFromNodes({
3474
+ root,
3475
+ layoutContext,
3476
+ layoutHint,
3477
+ layer: measurementLayer,
3478
+ measurementBackend,
3479
+ snapshotOverride,
3480
+ text,
3481
+ renderText,
3482
+ segments
3483
+ });
3484
+ if (session.animating && session.target !== null) {
3485
+ if (nextMeasurement.snapshot.renderText === session.target.snapshot.renderText) {
3486
+ session.target = refreshAnimatingTarget(session.target, nextMeasurement);
3487
+ return nextMeasurement;
3488
+ }
3489
+ }
3490
+ cancelTimeline(timeline);
3491
+ if (session.committed === null) {
3492
+ commitStaticMeasurement(session, nextMeasurement, setState);
3493
+ return nextMeasurement;
3494
+ }
3495
+ if (!areFontsReady()) {
3496
+ commitStaticMeasurement(session, nextMeasurement, setState);
3497
+ return nextMeasurement;
3498
+ }
3499
+ if (session.committed.snapshot.renderText === nextMeasurement.snapshot.renderText) {
3500
+ commitStaticMeasurement(session, reuseCommittedMeasurement(session.committed, nextMeasurement), setState);
3501
+ return nextMeasurement;
3502
+ }
3503
+ startMorph({
3504
+ nextMeasurement,
3505
+ session,
3506
+ timeline,
3507
+ setState
3508
+ });
3509
+ return nextMeasurement;
3510
+ }
3511
+ function syncCommittedRootOriginWhenIdle({
3512
+ root,
3513
+ layoutContext,
3514
+ state,
3515
+ session
3516
+ }) {
3517
+ if (root === null || layoutContext === null) {
3518
+ return;
3519
+ }
3520
+ if (state.stage !== "idle" || state.measurement === null) {
3521
+ return;
3522
+ }
3523
+ const nextRootOrigin = readRootOrigin(root);
3524
+ const committedMeasurement = state.measurement;
3525
+ if (nearlyEqual(committedMeasurement.rootOrigin.left, nextRootOrigin.left) && nearlyEqual(committedMeasurement.rootOrigin.top, nextRootOrigin.top)) {
3526
+ session.committed = committedMeasurement;
3527
+ return;
3528
+ }
3529
+ session.committed = {
3530
+ snapshot: committedMeasurement.snapshot,
3531
+ layoutInlineSize: committedMeasurement.layoutInlineSize,
3532
+ reservedInlineSize: committedMeasurement.reservedInlineSize,
3533
+ rootOrigin: nextRootOrigin
3534
+ };
3535
+ }
3536
+ function getFadeDuration(fraction) {
3537
+ return Math.min(MORPH.durationMs * fraction, MORPH.maxFadeMs);
3538
+ }
3539
+ function getLiveTransform(item, stage, visualBridge) {
3540
+ if (stage !== "prepare") {
3541
+ return "translate(0px, 0px)";
3542
+ }
3543
+ if (item.kind === "move") {
3544
+ return `translate(${(item.fromLeft ?? item.left) - item.left + visualBridge.offsetX}px, ${(item.fromTop ?? item.top) - item.top + visualBridge.offsetY}px)`;
3545
+ }
3546
+ return `translate(${visualBridge.offsetX}px, ${visualBridge.offsetY}px)`;
3547
+ }
3548
+ function getLiveOpacity(item, stage) {
3549
+ if (stage === "prepare" && item.kind === "enter") {
3550
+ return 0;
3551
+ }
3552
+ return 1;
3553
+ }
3554
+ function getLiveTransition(item, stage) {
3555
+ if (stage !== "animate") {
3556
+ return;
3557
+ }
3558
+ if (item.kind === "enter") {
3559
+ return `opacity ${getFadeDuration(0.5)}ms linear ${getFadeDuration(0.25)}ms`;
3560
+ }
3561
+ return `transform ${MORPH.durationMs}ms ${MORPH.ease}, opacity ${getFadeDuration(0.25)}ms linear`;
3562
+ }
3563
+ function getExitOpacity(stage) {
3564
+ if (stage === "animate") {
3565
+ return 0;
3566
+ }
3567
+ return 1;
3568
+ }
3569
+ function getExitTransform(visualBridge) {
3570
+ return `translate(${visualBridge.offsetX}px, ${visualBridge.offsetY}px)`;
3571
+ }
3572
+ function getExitTransition(stage) {
3573
+ if (stage !== "animate") {
3574
+ return;
3575
+ }
3576
+ return `transform ${MORPH.durationMs}ms ${MORPH.ease}, opacity ${getFadeDuration(0.25)}ms linear`;
3577
+ }
3578
+ function supportsIntrinsicWidthLock(display, parentDisplay) {
3579
+ const parentNeedsReservation = parentDisplay === "flex" || parentDisplay === "inline-flex" || parentDisplay === "grid" || parentDisplay === "inline-grid";
3580
+ return display === "inline" || display === "inline-block" || display === "inline-flex" || display === "inline-grid" || parentNeedsReservation;
3581
+ }
3582
+ function getRootDisplay(layoutContext) {
3583
+ if (layoutContext === null) {
3584
+ return "grid";
3585
+ }
3586
+ if (supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay)) {
3587
+ return "inline-grid";
3588
+ }
3589
+ return "grid";
3590
+ }
3591
+ function getRootStyle(stage, plan, measurement, layoutContext) {
3592
+ let width = measurement?.reservedInlineSize ?? undefined;
3593
+ if (plan !== null) {
3594
+ width = plan.layoutInlineSizeTo;
3595
+ if (stage === "prepare") {
3596
+ width = plan.layoutInlineSizeFrom;
3597
+ }
3598
+ }
3599
+ const height = plan?.frameHeight ?? measurement?.snapshot.height;
3600
+ const shouldTransitionWidth = stage === "animate" && plan !== null && !nearlyEqual(plan.layoutInlineSizeFrom, plan.layoutInlineSizeTo);
3601
+ const style = {
3602
+ position: "relative",
3603
+ display: getRootDisplay(layoutContext)
3604
+ };
3605
+ if (width !== undefined) {
3606
+ style.width = width;
3607
+ }
3608
+ if (height !== undefined) {
3609
+ style.height = height;
3610
+ }
3611
+ if (shouldTransitionWidth) {
3612
+ style.transition = `width ${MORPH.durationMs}ms ${MORPH.ease}`;
3613
+ }
3614
+ return style;
3615
+ }
3616
+ function getMeasurementLayerStyle(layoutContext, useContentInlineSize = false) {
3617
+ const intrinsicWidthLock = layoutContext !== null && (useContentInlineSize || supportsIntrinsicWidthLock(layoutContext.display, layoutContext.parentDisplay));
3618
+ if (!intrinsicWidthLock) {
3619
+ return MEASUREMENT_LAYER_STYLE;
3620
+ }
3621
+ return {
3622
+ ...MEASUREMENT_LAYER_STYLE,
3623
+ right: "auto",
3624
+ width: "max-content"
3625
+ };
3626
+ }
3627
+ function resolveFlowText(committedMeasurement, stateMeasurement, text) {
3628
+ return committedMeasurement?.snapshot.text ?? stateMeasurement?.snapshot.text ?? text;
3629
+ }
3630
+ function resolveVisibleFlowText(pendingBootstrapText, committedMeasurement, stateMeasurement, text) {
3631
+ return pendingBootstrapText ?? resolveFlowText(committedMeasurement, stateMeasurement, text);
3632
+ }
3633
+ function resolveActivationBootstrapText(isActivated, previousText, nextText) {
3634
+ if (!isActivated && nextText !== previousText) {
3635
+ return previousText;
3636
+ }
3637
+ return null;
3638
+ }
3639
+ function getOverlayStyle(plan) {
3640
+ return {
3641
+ ...OVERLAY_STYLE,
3642
+ right: "auto",
3643
+ bottom: "auto",
3644
+ width: plan.frameWidth,
3645
+ height: plan.frameHeight
3646
+ };
3647
+ }
3648
+ function getFallbackTextStyle(shouldRenderOverlay) {
3649
+ if (!shouldRenderOverlay) {
3650
+ return FALLBACK_TEXT_STYLE;
3651
+ }
3652
+ return {
3653
+ ...FALLBACK_TEXT_STYLE,
3654
+ visibility: "hidden",
3655
+ pointerEvents: "none"
3656
+ };
3657
+ }
3658
+ function getLiveGlyphStyle(item, stage, visualBridge) {
3659
+ return {
3660
+ ...ABSOLUTE_GLYPH_STYLE,
3661
+ left: item.left,
3662
+ top: item.top,
3663
+ width: item.width,
3664
+ height: item.height,
3665
+ lineHeight: `${item.height}px`,
3666
+ opacity: getLiveOpacity(item, stage),
3667
+ transform: getLiveTransform(item, stage, visualBridge),
3668
+ transition: getLiveTransition(item, stage)
3669
+ };
3670
+ }
3671
+ function getExitGlyphStyle(item, stage, visualBridge) {
3672
+ return {
3673
+ ...ABSOLUTE_GLYPH_STYLE,
3674
+ left: item.left,
3675
+ top: item.top,
3676
+ width: item.width,
3677
+ height: item.height,
3678
+ lineHeight: `${item.height}px`,
3679
+ opacity: getExitOpacity(stage),
3680
+ transform: getExitTransform(visualBridge),
3681
+ transition: getExitTransition(stage)
3682
+ };
3683
+ }
3684
+ function MorphOverlay({ stage, plan }) {
3685
+ let exitItems = [];
3686
+ if (stage !== "idle") {
3687
+ exitItems = plan.exitItems;
3688
+ }
3689
+ return /* @__PURE__ */ jsxDEV("div", {
3690
+ "aria-hidden": "true",
3691
+ style: getOverlayStyle(plan),
3692
+ children: [
3693
+ exitItems.map((item) => /* @__PURE__ */ jsxDEV("span", {
3694
+ style: getExitGlyphStyle(item, stage, plan.visualBridge),
3695
+ children: item.glyph
3696
+ }, `exit-${item.key}`, false, undefined, this)),
3697
+ plan.liveItems.map((item) => /* @__PURE__ */ jsxDEV("span", {
3698
+ style: getLiveGlyphStyle(item, stage, plan.visualBridge),
3699
+ children: item.glyph
3700
+ }, item.key, false, undefined, this))
3701
+ ]
3702
+ }, undefined, true, undefined, this);
3703
+ }
3704
+ function MeasurementLayer({
3705
+ layerRef,
3706
+ layoutContext,
3707
+ text,
3708
+ segments,
3709
+ useContentInlineSize
3710
+ }) {
3711
+ let glyphs = segments;
3712
+ if (text.length === 0) {
3713
+ glyphs = EMPTY_SEGMENTS;
3714
+ }
3715
+ return /* @__PURE__ */ jsxDEV("span", {
3716
+ ref: layerRef,
3717
+ "aria-hidden": "true",
3718
+ style: getMeasurementLayerStyle(layoutContext, useContentInlineSize),
3719
+ children: glyphs.map((segment) => /* @__PURE__ */ jsxDEV("span", {
3720
+ "data-morph-key": segment.key,
3721
+ style: MEASUREMENT_GLYPH_STYLE,
3722
+ children: segment.glyph
3723
+ }, segment.key, false, undefined, this))
3724
+ }, undefined, false, undefined, this);
3725
+ }
3726
+ function useMorphTransition(text, className, bootstrapText = null) {
3727
+ const { ref, layoutContext } = useObservedLayoutContext([className]);
3728
+ const measurementLayerRef = useRef(null);
3729
+ const bootstrapMeasurementLayerRef = useRef(null);
3730
+ const completedDomMeasurementKeyRef = useRef(null);
3731
+ const domMeasurementSnapshotCacheRef = useRef(new Map);
3732
+ const pendingBootstrapTextRef = useRef(bootstrapText);
3733
+ const sessionRef = useRef({ ...EMPTY_SESSION });
3734
+ const timelineRef = useRef({ ...EMPTY_TIMELINE });
3735
+ const [domMeasurementRequestKey, setDomMeasurementRequestKey] = useState(null);
3736
+ const [state, setState] = useState(EMPTY_STATE);
3737
+ if (pendingBootstrapTextRef.current === null && bootstrapText !== null && bootstrapText !== text && sessionRef.current.committed === null) {
3738
+ pendingBootstrapTextRef.current = bootstrapText;
3739
+ }
3740
+ let measurementHint = sessionRef.current.committed;
3741
+ if (sessionRef.current.animating) {
3742
+ measurementHint = sessionRef.current.target ?? sessionRef.current.committed;
3743
+ }
3744
+ const measurementRequest = useMemo(() => createMorphMeasurementRequest({
3745
+ text,
3746
+ layoutContext,
3747
+ layoutHint: measurementHint
3748
+ }), [text, layoutContext, measurementHint]);
3749
+ let pendingBootstrapText = null;
3750
+ if (sessionRef.current.committed === null) {
3751
+ pendingBootstrapText = pendingBootstrapTextRef.current;
3752
+ }
3753
+ const bootstrapMeasurementRequest = useMemo(() => {
3754
+ if (pendingBootstrapText === null) {
3755
+ return null;
3756
+ }
3757
+ return createMorphMeasurementRequest({
3758
+ text: pendingBootstrapText,
3759
+ layoutContext,
3760
+ layoutHint: null
3761
+ });
3762
+ }, [pendingBootstrapText, layoutContext]);
3763
+ const renderText = measurementRequest?.renderText ?? text;
3764
+ const useContentInlineSize = measurementRequest?.useContentInlineSize ?? false;
3765
+ const measurementBackend = measurementRequest?.measurementBackend ?? null;
3766
+ const segments = measurementRequest?.segments ?? EMPTY_SEGMENTS;
3767
+ const domMeasurementKey = measurementRequest?.domMeasurementKey ?? null;
3768
+ const shouldRenderBootstrapSourceMeasurementLayer = bootstrapMeasurementRequest?.measurementBackend === "dom" && bootstrapMeasurementRequest.renderText.length > 0;
3769
+ useLayoutEffect(() => {
3770
+ if (ref.current === null || layoutContext === null) {
3771
+ completedDomMeasurementKeyRef.current = null;
3772
+ if (domMeasurementRequestKey !== null) {
3773
+ setDomMeasurementRequestKey(null);
3774
+ }
3775
+ reconcileMorphChange({
3776
+ root: ref.current,
3777
+ measurementLayer: measurementLayerRef.current,
3778
+ measurementBackend,
3779
+ snapshotOverride: null,
3780
+ text,
3781
+ renderText,
3782
+ segments,
3783
+ layoutContext,
3784
+ session: sessionRef.current,
3785
+ timeline: timelineRef.current,
3786
+ setState
3787
+ });
3788
+ return;
3789
+ }
3790
+ if (pendingBootstrapText !== null && pendingBootstrapText !== text && bootstrapMeasurementRequest !== null && measurementRequest !== null) {
3791
+ let bootstrapDomSnapshot = null;
3792
+ if (bootstrapMeasurementRequest.domMeasurementKey !== null) {
3793
+ bootstrapDomSnapshot = readCachedMorphSnapshot(domMeasurementSnapshotCacheRef.current, bootstrapMeasurementRequest.domMeasurementKey);
3794
+ }
3795
+ if (bootstrapMeasurementRequest.domMeasurementKey !== null && bootstrapDomSnapshot === null && bootstrapMeasurementLayerRef.current === null) {
3796
+ return;
3797
+ }
3798
+ const bootstrapMeasurement = measureFromNodes({
3799
+ root: ref.current,
3800
+ layoutContext,
3801
+ layoutHint: null,
3802
+ layer: bootstrapMeasurementLayerRef.current,
3803
+ measurementBackend: bootstrapMeasurementRequest.measurementBackend,
3804
+ snapshotOverride: bootstrapDomSnapshot,
3805
+ text: bootstrapMeasurementRequest.text,
3806
+ renderText: bootstrapMeasurementRequest.renderText,
3807
+ segments: bootstrapMeasurementRequest.segments
3808
+ });
3809
+ if (bootstrapMeasurementRequest.domMeasurementKey !== null && bootstrapDomSnapshot === null) {
3810
+ rememberCachedMorphSnapshot(domMeasurementSnapshotCacheRef.current, bootstrapMeasurementRequest.domMeasurementKey, bootstrapMeasurement.snapshot);
3811
+ }
3812
+ completedDomMeasurementKeyRef.current = null;
3813
+ pendingBootstrapTextRef.current = null;
3814
+ if (domMeasurementRequestKey !== null) {
3815
+ setDomMeasurementRequestKey(null);
3816
+ }
3817
+ commitStaticMeasurement(sessionRef.current, bootstrapMeasurement, setState);
3818
+ return;
3819
+ }
3820
+ if (domMeasurementKey !== null) {
3821
+ const cachedSnapshot = readCachedMorphSnapshot(domMeasurementSnapshotCacheRef.current, domMeasurementKey);
3822
+ if (cachedSnapshot !== null) {
3823
+ completedDomMeasurementKeyRef.current = domMeasurementKey;
3824
+ if (domMeasurementRequestKey !== null) {
3825
+ setDomMeasurementRequestKey(null);
3826
+ }
3827
+ reconcileMorphChange({
3828
+ root: ref.current,
3829
+ measurementLayer: null,
3830
+ measurementBackend,
3831
+ snapshotOverride: cachedSnapshot,
3832
+ text,
3833
+ renderText,
3834
+ segments,
3835
+ layoutContext,
3836
+ session: sessionRef.current,
3837
+ timeline: timelineRef.current,
3838
+ setState
3839
+ });
3840
+ return;
3841
+ }
3842
+ if (completedDomMeasurementKeyRef.current !== domMeasurementKey) {
3843
+ if (domMeasurementRequestKey !== domMeasurementKey) {
3844
+ setDomMeasurementRequestKey(domMeasurementKey);
3845
+ return;
3846
+ }
3847
+ if (measurementLayerRef.current === null) {
3848
+ return;
3849
+ }
3850
+ const nextMeasurement = reconcileMorphChange({
3851
+ root: ref.current,
3852
+ measurementLayer: measurementLayerRef.current,
3853
+ measurementBackend,
3854
+ snapshotOverride: null,
3855
+ text,
3856
+ renderText,
3857
+ segments,
3858
+ layoutContext,
3859
+ session: sessionRef.current,
3860
+ timeline: timelineRef.current,
3861
+ setState
3862
+ });
3863
+ if (nextMeasurement !== null) {
3864
+ rememberCachedMorphSnapshot(domMeasurementSnapshotCacheRef.current, domMeasurementKey, nextMeasurement.snapshot);
3865
+ }
3866
+ completedDomMeasurementKeyRef.current = domMeasurementKey;
3867
+ if (domMeasurementRequestKey !== null) {
3868
+ setDomMeasurementRequestKey(null);
3869
+ }
3870
+ return;
3871
+ }
3872
+ if (domMeasurementRequestKey !== null) {
3873
+ setDomMeasurementRequestKey(null);
3874
+ }
3875
+ return;
3876
+ }
3877
+ completedDomMeasurementKeyRef.current = null;
3878
+ if (domMeasurementRequestKey !== null) {
3879
+ setDomMeasurementRequestKey(null);
3880
+ }
3881
+ reconcileMorphChange({
3882
+ root: ref.current,
3883
+ measurementLayer: measurementLayerRef.current,
3884
+ measurementBackend,
3885
+ snapshotOverride: null,
3886
+ text,
3887
+ renderText,
3888
+ segments,
3889
+ layoutContext,
3890
+ session: sessionRef.current,
3891
+ timeline: timelineRef.current,
3892
+ setState
3893
+ });
3894
+ }, [
3895
+ text,
3896
+ renderText,
3897
+ segments,
3898
+ layoutContext,
3899
+ measurementBackend,
3900
+ measurementRequest,
3901
+ pendingBootstrapText,
3902
+ bootstrapMeasurementRequest,
3903
+ domMeasurementKey,
3904
+ domMeasurementRequestKey
3905
+ ]);
3906
+ useLayoutEffect(() => {
3907
+ syncCommittedRootOriginWhenIdle({
3908
+ root: ref.current,
3909
+ layoutContext,
3910
+ state,
3911
+ session: sessionRef.current
3912
+ });
3913
+ }, [layoutContext, state]);
3914
+ useLayoutEffect(() => {
3915
+ return () => {
3916
+ cancelTimeline(timelineRef.current);
3917
+ };
3918
+ }, []);
3919
+ return {
3920
+ committedMeasurement: sessionRef.current.committed,
3921
+ domMeasurementRequestKey,
3922
+ ref,
3923
+ bootstrapMeasurementLayerRef,
3924
+ measurementBackend,
3925
+ measurementLayerRef,
3926
+ renderText,
3927
+ segments,
3928
+ layoutContext,
3929
+ state,
3930
+ pendingBootstrapText,
3931
+ shouldRenderBootstrapSourceMeasurementLayer,
3932
+ bootstrapMeasurementRequest,
3933
+ useContentInlineSize
3934
+ };
3935
+ }
3936
+ function StaticTorph({ text, className }) {
3937
+ return /* @__PURE__ */ jsxDEV("div", {
3938
+ className,
3939
+ children: [
3940
+ /* @__PURE__ */ jsxDEV("span", {
3941
+ style: SCREEN_READER_ONLY_STYLE,
3942
+ children: text
3943
+ }, undefined, false, undefined, this),
3944
+ /* @__PURE__ */ jsxDEV("span", {
3945
+ "aria-hidden": "true",
3946
+ style: FALLBACK_TEXT_STYLE,
3947
+ children: text
3948
+ }, undefined, false, undefined, this)
3949
+ ]
3950
+ }, undefined, true, undefined, this);
3951
+ }
3952
+ function ActiveTorph({
3953
+ text,
3954
+ className,
3955
+ bootstrapText
3956
+ }) {
3957
+ const {
3958
+ committedMeasurement,
3959
+ domMeasurementRequestKey,
3960
+ ref,
3961
+ bootstrapMeasurementLayerRef,
3962
+ measurementBackend,
3963
+ measurementLayerRef,
3964
+ renderText,
3965
+ segments,
3966
+ layoutContext,
3967
+ state,
3968
+ pendingBootstrapText,
3969
+ shouldRenderBootstrapSourceMeasurementLayer,
3970
+ bootstrapMeasurementRequest,
3971
+ useContentInlineSize
3972
+ } = useMorphTransition(text, className, bootstrapText);
3973
+ const plan = state.plan;
3974
+ const shouldRenderOverlay = state.stage !== "idle" && plan !== null;
3975
+ const shouldRenderMeasurementLayer = measurementBackend === "dom" && domMeasurementRequestKey !== null;
3976
+ const flowText = resolveVisibleFlowText(pendingBootstrapText, committedMeasurement, state.measurement, text);
3977
+ let bootstrapMeasurementLayer = null;
3978
+ if (shouldRenderBootstrapSourceMeasurementLayer && bootstrapMeasurementRequest !== null) {
3979
+ bootstrapMeasurementLayer = /* @__PURE__ */ jsxDEV(MeasurementLayer, {
3980
+ layerRef: bootstrapMeasurementLayerRef,
3981
+ layoutContext,
3982
+ text: bootstrapMeasurementRequest.renderText,
3983
+ segments: bootstrapMeasurementRequest.segments,
3984
+ useContentInlineSize: bootstrapMeasurementRequest.useContentInlineSize
3985
+ }, undefined, false, undefined, this);
3986
+ }
3987
+ let measurementLayer = null;
3988
+ if (shouldRenderMeasurementLayer) {
3989
+ measurementLayer = /* @__PURE__ */ jsxDEV(MeasurementLayer, {
3990
+ layerRef: measurementLayerRef,
3991
+ layoutContext,
3992
+ text: renderText,
3993
+ segments,
3994
+ useContentInlineSize
3995
+ }, undefined, false, undefined, this);
3996
+ }
3997
+ let overlay = null;
3998
+ if (shouldRenderOverlay) {
3999
+ overlay = /* @__PURE__ */ jsxDEV(MorphOverlay, {
4000
+ stage: state.stage,
4001
+ plan
4002
+ }, undefined, false, undefined, this);
4003
+ }
4004
+ return /* @__PURE__ */ jsxDEV("div", {
4005
+ ref,
4006
+ className,
4007
+ style: getRootStyle(state.stage, plan, state.measurement, layoutContext),
4008
+ children: [
4009
+ /* @__PURE__ */ jsxDEV("span", {
4010
+ style: SCREEN_READER_ONLY_STYLE,
4011
+ children: text
4012
+ }, undefined, false, undefined, this),
4013
+ /* @__PURE__ */ jsxDEV("span", {
4014
+ "aria-hidden": "true",
4015
+ style: getFallbackTextStyle(shouldRenderOverlay),
4016
+ children: flowText
4017
+ }, undefined, false, undefined, this),
4018
+ bootstrapMeasurementLayer,
4019
+ measurementLayer,
4020
+ overlay
4021
+ ]
4022
+ }, undefined, true, undefined, this);
4023
+ }
4024
+ function Torph({
4025
+ text,
4026
+ className
4027
+ }) {
4028
+ const [isActivated, setIsActivated] = useState(false);
4029
+ const previousTextRef = useRef(text);
4030
+ const bootstrapText = resolveActivationBootstrapText(isActivated, previousTextRef.current, text);
4031
+ const shouldActivate = isActivated || bootstrapText !== null;
4032
+ useLayoutEffect(() => {
4033
+ if (!isActivated && text !== previousTextRef.current) {
4034
+ previousTextRef.current = text;
4035
+ setIsActivated(true);
4036
+ return;
4037
+ }
4038
+ previousTextRef.current = text;
4039
+ }, [isActivated, text]);
4040
+ if (shouldActivate) {
4041
+ return /* @__PURE__ */ jsxDEV(ActiveTorph, {
4042
+ text,
4043
+ className,
4044
+ bootstrapText
4045
+ }, undefined, false, undefined, this);
4046
+ }
4047
+ return /* @__PURE__ */ jsxDEV(StaticTorph, {
4048
+ text,
4049
+ className
4050
+ }, undefined, false, undefined, this);
4051
+ }
4052
+ export {
4053
+ supportsIntrinsicWidthLock,
4054
+ resolveVisibleFlowText,
4055
+ resolveMorphFrameBounds,
4056
+ resolveFlowText,
4057
+ resolveActivationBootstrapText,
4058
+ pairMorphCharacters,
4059
+ measureMorphSnapshotFromLayer,
4060
+ getRootStyle,
4061
+ getRootDisplay,
4062
+ getMeasurementLayerStyle,
4063
+ getLiveTransition,
4064
+ getLiveTransform,
4065
+ getExitTransition,
4066
+ getExitTransform,
4067
+ buildMorphVisualBridge,
4068
+ buildMorphPlan,
4069
+ Torph
4070
+ };