@d3plus/dom 3.0.16 → 3.1.1

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.
@@ -1,143 +1,36 @@
1
1
  /*
2
- @d3plus/dom v3.0.16
2
+ @d3plus/dom v3.1.0
3
3
  JavaScript functions for manipulating and analyzing DOM elements.
4
4
  Copyright (c) 2026 D3plus - https://d3plus.org
5
5
  @license MIT
6
6
  */
7
-
8
- (function (factory) {
9
- typeof define === 'function' && define.amd ? define(factory) :
10
- factory();
11
- })((function () { 'use strict';
12
-
13
- if (typeof window !== "undefined") {
14
- (function () {
15
- try {
16
- if (typeof SVGElement === 'undefined' || Boolean(SVGElement.prototype.innerHTML)) {
17
- return;
18
- }
19
- } catch (e) {
20
- return;
21
- }
22
-
23
- function serializeNode (node) {
24
- switch (node.nodeType) {
25
- case 1:
26
- return serializeElementNode(node);
27
- case 3:
28
- return serializeTextNode(node);
29
- case 8:
30
- return serializeCommentNode(node);
31
- }
32
- }
33
-
34
- function serializeTextNode (node) {
35
- return node.textContent.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
36
- }
37
-
38
- function serializeCommentNode (node) {
39
- return '<!--' + node.nodeValue + '-->'
40
- }
41
-
42
- function serializeElementNode (node) {
43
- var output = '';
44
-
45
- output += '<' + node.tagName;
46
-
47
- if (node.hasAttributes()) {
48
- [].forEach.call(node.attributes, function(attrNode) {
49
- output += ' ' + attrNode.name + '="' + attrNode.value + '"';
50
- });
51
- }
52
-
53
- output += '>';
54
-
55
- if (node.hasChildNodes()) {
56
- [].forEach.call(node.childNodes, function(childNode) {
57
- output += serializeNode(childNode);
58
- });
59
- }
60
-
61
- output += '</' + node.tagName + '>';
62
-
63
- return output;
64
- }
65
-
66
- Object.defineProperty(SVGElement.prototype, 'innerHTML', {
67
- get: function () {
68
- var output = '';
69
-
70
- [].forEach.call(this.childNodes, function(childNode) {
71
- output += serializeNode(childNode);
72
- });
73
-
74
- return output;
75
- },
76
- set: function (markup) {
77
- while (this.firstChild) {
78
- this.removeChild(this.firstChild);
79
- }
80
-
81
- try {
82
- var dXML = new DOMParser();
83
- dXML.async = false;
84
-
85
- var sXML = '<svg xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\'>' + markup + '</svg>';
86
- var svgDocElement = dXML.parseFromString(sXML, 'text/xml').documentElement;
87
-
88
- [].forEach.call(svgDocElement.childNodes, function(childNode) {
89
- this.appendChild(this.ownerDocument.importNode(childNode, true));
90
- }.bind(this));
91
- } catch (e) {
92
- throw new Error('Error parsing markup string');
93
- }
94
- }
95
- });
96
-
97
- Object.defineProperty(SVGElement.prototype, 'innerSVG', {
98
- get: function () {
99
- return this.innerHTML;
100
- },
101
- set: function (markup) {
102
- this.innerHTML = markup;
103
- }
104
- });
105
-
106
- })();
107
- }
108
-
109
- }));
110
-
111
7
  (function (global, factory) {
112
8
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
113
9
  typeof define === 'function' && define.amd ? define('@d3plus/dom', ['exports'], factory) :
114
10
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3plus = {}));
115
11
  })(this, (function (exports) {
116
12
  /**
117
- @function isObject
118
- @desc Detects if a variable is a javascript Object.
119
- @param {*} item
13
+ Detects if a variable is a javascript Object.
14
+ @param item The value to test.
120
15
  */ function isObject(item) {
121
16
  return item && typeof item === "object" && (typeof window === "undefined" || item !== window && item !== window.document && !(item instanceof Element)) && !Array.isArray(item) ? true : false;
122
17
  }
123
18
 
124
19
  /**
125
- @function validObject
126
- @desc Determines if the object passed is the document or window.
127
- @param {Object} obj
128
- @private
20
+ Determines if the object passed is the document or window.
21
+ @param obj @private
129
22
  */ function validObject(obj) {
130
23
  if (typeof window === "undefined") return true;
131
24
  else return obj !== window && obj !== document;
132
25
  }
133
26
  /**
134
- @function assign
135
- @desc A deeply recursive version of `Object.assign`.
136
- @param {...Object} objects
137
- @example <caption>this</caption>
27
+ A deeply recursive version of `Object.assign`.
28
+
29
+ @example <caption>this</caption>
138
30
  assign({id: "foo", deep: {group: "A"}}, {id: "bar", deep: {value: 20}}));
139
31
  @example <caption>returns this</caption>
140
32
  {id: "bar", deep: {group: "A", value: 20}}
33
+ @param objects The source objects to merge into the target.
141
34
  */ function assign(...objects) {
142
35
  const target = objects[0];
143
36
  for(let i = 1; i < objects.length; i++){
@@ -156,19 +49,31 @@
156
49
  }
157
50
 
158
51
  /**
159
- @function attrize
160
- @desc Applies each key/value in an object as an attr.
161
- @param {D3selection} elem The D3 element to apply the styles to.
162
- @param {Object} attrs An object of key/value attr pairs.
52
+ Given a DOM element, returns its background color by walking up the
53
+ ancestor chain until a non-transparent background is found. Falls back
54
+ to "rgb(255, 255, 255)" (white) if every ancestor is transparent.
55
+ @param elem The DOM element to check.
56
+ */ function backgroundColor(elem) {
57
+ let node = elem;
58
+ while(node){
59
+ const bg = getComputedStyle(node).backgroundColor;
60
+ if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
61
+ node = node.parentElement;
62
+ }
63
+ return "rgb(255, 255, 255)";
64
+ }
65
+
66
+ /**
67
+ Applies each key/value in an object as an attr.
68
+ @param e The d3 selection to apply attributes to.
69
+ @param a An object of key/value attr pairs.
163
70
  */ function attrize(e, a = {}) {
164
71
  for(const k in a)if (({}).hasOwnProperty.call(a, k)) e.attr(k, a[k]);
165
72
  }
166
73
 
167
74
  /**
168
- @function date
169
- @summary Parses numbers and strings to valid Javascript Date objects.
170
- @description Returns a javascript Date object for a given a Number (representing either a 4-digit year or milliseconds since epoch), a String representing a Quarter (ie. "Q2 1987", mapping to the last day in that quarter), or a String that is in [valid dateString format](http://dygraphs.com/date-formats.html). Besides the 4-digit year parsing, this function is useful when needing to parse negative (BC) years, which the vanilla Date object cannot parse.
171
- @param {Number|String} *date*
75
+ Parses numbers and strings into valid JavaScript Date objects, supporting years, quarters, months, and ISO 8601 formats.
76
+ @param d The date value to parse (number, string, or Date).
172
77
  */ function date(d) {
173
78
  // returns if falsey or already Date object
174
79
  if ([
@@ -207,8 +112,8 @@
207
112
  return date;
208
113
  }
209
114
  // tests for monthly formats (ie. "MM-YYYY" and "YYYY-MM")
210
- const monthPrefix = new RegExp(/^([-*\d]{1,2})\-(-*\d{1,4})$/g).exec(s);
211
- const monthSuffix = new RegExp(/^(-*\d{1,4})\-([-*\d]{1,2})$/g).exec(s);
115
+ const monthPrefix = new RegExp(/^([-*\d]{1,2})-(-*\d{1,4})$/g).exec(s);
116
+ const monthSuffix = new RegExp(/^(-*\d{1,4})-([-*\d]{1,2})$/g).exec(s);
212
117
  if (monthPrefix || monthSuffix) {
213
118
  const month = +(monthPrefix ? monthPrefix[1] : monthSuffix[2]);
214
119
  const year = +(monthPrefix ? monthPrefix[2] : monthSuffix[1]);
@@ -2449,19 +2354,12 @@
2449
2354
  selection.prototype.transition = selection_transition;
2450
2355
 
2451
2356
  /**
2452
- @function elem
2453
- @desc Manages the enter/update/exit pattern for a single DOM element.
2454
- @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
2455
- @param {Object} params Additional parameters.
2456
- @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
2457
- @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
2458
- @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
2459
- @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
2460
- @param {Number} [params.duration = 0] The duration for the d3 transition.
2461
- @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
2357
+ Manages the enter/update/exit pattern for a single DOM element, applying enter, update, and exit attributes with optional transitions.
2358
+ @param selector A CSS selector string for the element tag and classes.
2359
+ @param p Configuration object with enter, exit, update, and parent options.
2462
2360
  */ function elem(selector, p) {
2463
2361
  // overrides default params
2464
- p = Object.assign({}, {
2362
+ const params = Object.assign({}, {
2465
2363
  condition: true,
2466
2364
  enter: {},
2467
2365
  exit: {},
@@ -2469,21 +2367,2241 @@
2469
2367
  parent: select("body"),
2470
2368
  update: {}
2471
2369
  }, p);
2472
- const className = /\.([^#]+)/g.exec(selector), id = /#([^.]+)/g.exec(selector), t = transition().duration(p.duration), tag = /^([^.^#]+)/g.exec(selector)[1];
2473
- const elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector).data(p.condition ? [
2370
+ const className = /\.([^#]+)/g.exec(selector), id = /#([^.]+)/g.exec(selector), t = transition().duration(params.duration), tag = /^([^.^#]+)/g.exec(selector)[1];
2371
+ const elem = params.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector).data(params.condition ? [
2474
2372
  null
2475
2373
  ] : []);
2476
- const enter = elem.enter().append(tag).call(attrize, p.enter);
2374
+ const enter = elem.enter().append(tag).call(attrize, params.enter);
2477
2375
  if (id) enter.attr("id", id[1]);
2478
2376
  if (className) enter.attr("class", className[1]);
2479
- if (p.duration) elem.exit().transition(t).call(attrize, p.exit).remove();
2480
- else elem.exit().call(attrize, p.exit).remove();
2377
+ if (params.duration) elem.exit().transition(t).call(attrize, params.exit).remove();
2378
+ else elem.exit().call(attrize, params.exit).remove();
2481
2379
  const update = enter.merge(elem);
2482
- if (p.duration) update.transition(t).call(attrize, p.update);
2483
- else update.call(attrize, p.update);
2380
+ if (params.duration) update.transition(t).call(attrize, params.update);
2381
+ else update.call(attrize, params.update);
2484
2382
  return update;
2485
2383
  }
2486
2384
 
2385
+ // Simplified bidi metadata helper for the rich prepareWithSegments() path,
2386
+ // forked from pdf.js via Sebastian's text-layout. It classifies characters
2387
+ // into bidi types, computes embedding levels, and maps them onto prepared
2388
+ // segments for custom rendering. The line-breaking engine does not consume
2389
+ // these levels.
2390
+ const baseTypes = [
2391
+ 'BN',
2392
+ 'BN',
2393
+ 'BN',
2394
+ 'BN',
2395
+ 'BN',
2396
+ 'BN',
2397
+ 'BN',
2398
+ 'BN',
2399
+ 'BN',
2400
+ 'S',
2401
+ 'B',
2402
+ 'S',
2403
+ 'WS',
2404
+ 'B',
2405
+ 'BN',
2406
+ 'BN',
2407
+ 'BN',
2408
+ 'BN',
2409
+ 'BN',
2410
+ 'BN',
2411
+ 'BN',
2412
+ 'BN',
2413
+ 'BN',
2414
+ 'BN',
2415
+ 'BN',
2416
+ 'BN',
2417
+ 'BN',
2418
+ 'BN',
2419
+ 'B',
2420
+ 'B',
2421
+ 'B',
2422
+ 'S',
2423
+ 'WS',
2424
+ 'ON',
2425
+ 'ON',
2426
+ 'ET',
2427
+ 'ET',
2428
+ 'ET',
2429
+ 'ON',
2430
+ 'ON',
2431
+ 'ON',
2432
+ 'ON',
2433
+ 'ON',
2434
+ 'ON',
2435
+ 'CS',
2436
+ 'ON',
2437
+ 'CS',
2438
+ 'ON',
2439
+ 'EN',
2440
+ 'EN',
2441
+ 'EN',
2442
+ 'EN',
2443
+ 'EN',
2444
+ 'EN',
2445
+ 'EN',
2446
+ 'EN',
2447
+ 'EN',
2448
+ 'EN',
2449
+ 'ON',
2450
+ 'ON',
2451
+ 'ON',
2452
+ 'ON',
2453
+ 'ON',
2454
+ 'ON',
2455
+ 'ON',
2456
+ 'L',
2457
+ 'L',
2458
+ 'L',
2459
+ 'L',
2460
+ 'L',
2461
+ 'L',
2462
+ 'L',
2463
+ 'L',
2464
+ 'L',
2465
+ 'L',
2466
+ 'L',
2467
+ 'L',
2468
+ 'L',
2469
+ 'L',
2470
+ 'L',
2471
+ 'L',
2472
+ 'L',
2473
+ 'L',
2474
+ 'L',
2475
+ 'L',
2476
+ 'L',
2477
+ 'L',
2478
+ 'L',
2479
+ 'L',
2480
+ 'L',
2481
+ 'L',
2482
+ 'ON',
2483
+ 'ON',
2484
+ 'ON',
2485
+ 'ON',
2486
+ 'ON',
2487
+ 'ON',
2488
+ 'L',
2489
+ 'L',
2490
+ 'L',
2491
+ 'L',
2492
+ 'L',
2493
+ 'L',
2494
+ 'L',
2495
+ 'L',
2496
+ 'L',
2497
+ 'L',
2498
+ 'L',
2499
+ 'L',
2500
+ 'L',
2501
+ 'L',
2502
+ 'L',
2503
+ 'L',
2504
+ 'L',
2505
+ 'L',
2506
+ 'L',
2507
+ 'L',
2508
+ 'L',
2509
+ 'L',
2510
+ 'L',
2511
+ 'L',
2512
+ 'L',
2513
+ 'L',
2514
+ 'ON',
2515
+ 'ON',
2516
+ 'ON',
2517
+ 'ON',
2518
+ 'BN',
2519
+ 'BN',
2520
+ 'BN',
2521
+ 'BN',
2522
+ 'BN',
2523
+ 'BN',
2524
+ 'B',
2525
+ 'BN',
2526
+ 'BN',
2527
+ 'BN',
2528
+ 'BN',
2529
+ 'BN',
2530
+ 'BN',
2531
+ 'BN',
2532
+ 'BN',
2533
+ 'BN',
2534
+ 'BN',
2535
+ 'BN',
2536
+ 'BN',
2537
+ 'BN',
2538
+ 'BN',
2539
+ 'BN',
2540
+ 'BN',
2541
+ 'BN',
2542
+ 'BN',
2543
+ 'BN',
2544
+ 'BN',
2545
+ 'BN',
2546
+ 'BN',
2547
+ 'BN',
2548
+ 'BN',
2549
+ 'BN',
2550
+ 'BN',
2551
+ 'CS',
2552
+ 'ON',
2553
+ 'ET',
2554
+ 'ET',
2555
+ 'ET',
2556
+ 'ET',
2557
+ 'ON',
2558
+ 'ON',
2559
+ 'ON',
2560
+ 'ON',
2561
+ 'L',
2562
+ 'ON',
2563
+ 'ON',
2564
+ 'ON',
2565
+ 'ON',
2566
+ 'ON',
2567
+ 'ET',
2568
+ 'ET',
2569
+ 'EN',
2570
+ 'EN',
2571
+ 'ON',
2572
+ 'L',
2573
+ 'ON',
2574
+ 'ON',
2575
+ 'ON',
2576
+ 'EN',
2577
+ 'L',
2578
+ 'ON',
2579
+ 'ON',
2580
+ 'ON',
2581
+ 'ON',
2582
+ 'ON',
2583
+ 'L',
2584
+ 'L',
2585
+ 'L',
2586
+ 'L',
2587
+ 'L',
2588
+ 'L',
2589
+ 'L',
2590
+ 'L',
2591
+ 'L',
2592
+ 'L',
2593
+ 'L',
2594
+ 'L',
2595
+ 'L',
2596
+ 'L',
2597
+ 'L',
2598
+ 'L',
2599
+ 'L',
2600
+ 'L',
2601
+ 'L',
2602
+ 'L',
2603
+ 'L',
2604
+ 'L',
2605
+ 'L',
2606
+ 'ON',
2607
+ 'L',
2608
+ 'L',
2609
+ 'L',
2610
+ 'L',
2611
+ 'L',
2612
+ 'L',
2613
+ 'L',
2614
+ 'L',
2615
+ 'L',
2616
+ 'L',
2617
+ 'L',
2618
+ 'L',
2619
+ 'L',
2620
+ 'L',
2621
+ 'L',
2622
+ 'L',
2623
+ 'L',
2624
+ 'L',
2625
+ 'L',
2626
+ 'L',
2627
+ 'L',
2628
+ 'L',
2629
+ 'L',
2630
+ 'L',
2631
+ 'L',
2632
+ 'L',
2633
+ 'L',
2634
+ 'L',
2635
+ 'L',
2636
+ 'L',
2637
+ 'L',
2638
+ 'ON',
2639
+ 'L',
2640
+ 'L',
2641
+ 'L',
2642
+ 'L',
2643
+ 'L',
2644
+ 'L',
2645
+ 'L',
2646
+ 'L'
2647
+ ];
2648
+ const arabicTypes = [
2649
+ 'AL',
2650
+ 'AL',
2651
+ 'AL',
2652
+ 'AL',
2653
+ 'AL',
2654
+ 'AL',
2655
+ 'AL',
2656
+ 'AL',
2657
+ 'AL',
2658
+ 'AL',
2659
+ 'AL',
2660
+ 'AL',
2661
+ 'CS',
2662
+ 'AL',
2663
+ 'ON',
2664
+ 'ON',
2665
+ 'NSM',
2666
+ 'NSM',
2667
+ 'NSM',
2668
+ 'NSM',
2669
+ 'NSM',
2670
+ 'NSM',
2671
+ 'AL',
2672
+ 'AL',
2673
+ 'AL',
2674
+ 'AL',
2675
+ 'AL',
2676
+ 'AL',
2677
+ 'AL',
2678
+ 'AL',
2679
+ 'AL',
2680
+ 'AL',
2681
+ 'AL',
2682
+ 'AL',
2683
+ 'AL',
2684
+ 'AL',
2685
+ 'AL',
2686
+ 'AL',
2687
+ 'AL',
2688
+ 'AL',
2689
+ 'AL',
2690
+ 'AL',
2691
+ 'AL',
2692
+ 'AL',
2693
+ 'AL',
2694
+ 'AL',
2695
+ 'AL',
2696
+ 'AL',
2697
+ 'AL',
2698
+ 'AL',
2699
+ 'AL',
2700
+ 'AL',
2701
+ 'AL',
2702
+ 'AL',
2703
+ 'AL',
2704
+ 'AL',
2705
+ 'AL',
2706
+ 'AL',
2707
+ 'AL',
2708
+ 'AL',
2709
+ 'AL',
2710
+ 'AL',
2711
+ 'AL',
2712
+ 'AL',
2713
+ 'AL',
2714
+ 'AL',
2715
+ 'AL',
2716
+ 'AL',
2717
+ 'AL',
2718
+ 'AL',
2719
+ 'AL',
2720
+ 'AL',
2721
+ 'AL',
2722
+ 'AL',
2723
+ 'AL',
2724
+ 'NSM',
2725
+ 'NSM',
2726
+ 'NSM',
2727
+ 'NSM',
2728
+ 'NSM',
2729
+ 'NSM',
2730
+ 'NSM',
2731
+ 'NSM',
2732
+ 'NSM',
2733
+ 'NSM',
2734
+ 'NSM',
2735
+ 'NSM',
2736
+ 'NSM',
2737
+ 'NSM',
2738
+ 'AL',
2739
+ 'AL',
2740
+ 'AL',
2741
+ 'AL',
2742
+ 'AL',
2743
+ 'AL',
2744
+ 'AL',
2745
+ 'AN',
2746
+ 'AN',
2747
+ 'AN',
2748
+ 'AN',
2749
+ 'AN',
2750
+ 'AN',
2751
+ 'AN',
2752
+ 'AN',
2753
+ 'AN',
2754
+ 'AN',
2755
+ 'ET',
2756
+ 'AN',
2757
+ 'AN',
2758
+ 'AL',
2759
+ 'AL',
2760
+ 'AL',
2761
+ 'NSM',
2762
+ 'AL',
2763
+ 'AL',
2764
+ 'AL',
2765
+ 'AL',
2766
+ 'AL',
2767
+ 'AL',
2768
+ 'AL',
2769
+ 'AL',
2770
+ 'AL',
2771
+ 'AL',
2772
+ 'AL',
2773
+ 'AL',
2774
+ 'AL',
2775
+ 'AL',
2776
+ 'AL',
2777
+ 'AL',
2778
+ 'AL',
2779
+ 'AL',
2780
+ 'AL',
2781
+ 'AL',
2782
+ 'AL',
2783
+ 'AL',
2784
+ 'AL',
2785
+ 'AL',
2786
+ 'AL',
2787
+ 'AL',
2788
+ 'AL',
2789
+ 'AL',
2790
+ 'AL',
2791
+ 'AL',
2792
+ 'AL',
2793
+ 'AL',
2794
+ 'AL',
2795
+ 'AL',
2796
+ 'AL',
2797
+ 'AL',
2798
+ 'AL',
2799
+ 'AL',
2800
+ 'AL',
2801
+ 'AL',
2802
+ 'AL',
2803
+ 'AL',
2804
+ 'AL',
2805
+ 'AL',
2806
+ 'AL',
2807
+ 'AL',
2808
+ 'AL',
2809
+ 'AL',
2810
+ 'AL',
2811
+ 'AL',
2812
+ 'AL',
2813
+ 'AL',
2814
+ 'AL',
2815
+ 'AL',
2816
+ 'AL',
2817
+ 'AL',
2818
+ 'AL',
2819
+ 'AL',
2820
+ 'AL',
2821
+ 'AL',
2822
+ 'AL',
2823
+ 'AL',
2824
+ 'AL',
2825
+ 'AL',
2826
+ 'AL',
2827
+ 'AL',
2828
+ 'AL',
2829
+ 'AL',
2830
+ 'AL',
2831
+ 'AL',
2832
+ 'AL',
2833
+ 'AL',
2834
+ 'AL',
2835
+ 'AL',
2836
+ 'AL',
2837
+ 'AL',
2838
+ 'AL',
2839
+ 'AL',
2840
+ 'AL',
2841
+ 'AL',
2842
+ 'AL',
2843
+ 'AL',
2844
+ 'AL',
2845
+ 'AL',
2846
+ 'AL',
2847
+ 'AL',
2848
+ 'AL',
2849
+ 'AL',
2850
+ 'AL',
2851
+ 'AL',
2852
+ 'AL',
2853
+ 'AL',
2854
+ 'AL',
2855
+ 'AL',
2856
+ 'AL',
2857
+ 'AL',
2858
+ 'AL',
2859
+ 'AL',
2860
+ 'AL',
2861
+ 'AL',
2862
+ 'AL',
2863
+ 'NSM',
2864
+ 'NSM',
2865
+ 'NSM',
2866
+ 'NSM',
2867
+ 'NSM',
2868
+ 'NSM',
2869
+ 'NSM',
2870
+ 'NSM',
2871
+ 'NSM',
2872
+ 'NSM',
2873
+ 'NSM',
2874
+ 'NSM',
2875
+ 'NSM',
2876
+ 'NSM',
2877
+ 'NSM',
2878
+ 'NSM',
2879
+ 'NSM',
2880
+ 'NSM',
2881
+ 'NSM',
2882
+ 'ON',
2883
+ 'NSM',
2884
+ 'NSM',
2885
+ 'NSM',
2886
+ 'NSM',
2887
+ 'AL',
2888
+ 'AL',
2889
+ 'AL',
2890
+ 'AL',
2891
+ 'AL',
2892
+ 'AL',
2893
+ 'AL',
2894
+ 'AL',
2895
+ 'AL',
2896
+ 'AL',
2897
+ 'AL',
2898
+ 'AL',
2899
+ 'AL',
2900
+ 'AL',
2901
+ 'AL',
2902
+ 'AL',
2903
+ 'AL',
2904
+ 'AL'
2905
+ ];
2906
+ function classifyChar(charCode) {
2907
+ if (charCode <= 0x00ff) return baseTypes[charCode];
2908
+ if (0x0590 <= charCode && charCode <= 0x05f4) return 'R';
2909
+ if (0x0600 <= charCode && charCode <= 0x06ff) return arabicTypes[charCode & 0xff];
2910
+ if (0x0700 <= charCode && charCode <= 0x08AC) return 'AL';
2911
+ return 'L';
2912
+ }
2913
+ function computeBidiLevels(str) {
2914
+ const len = str.length;
2915
+ if (len === 0) return null;
2916
+ // eslint-disable-next-line unicorn/no-new-array
2917
+ const types = new Array(len);
2918
+ let numBidi = 0;
2919
+ for(let i = 0; i < len; i++){
2920
+ const t = classifyChar(str.charCodeAt(i));
2921
+ if (t === 'R' || t === 'AL' || t === 'AN') numBidi++;
2922
+ types[i] = t;
2923
+ }
2924
+ if (numBidi === 0) return null;
2925
+ const startLevel = len / numBidi < 0.3 ? 0 : 1;
2926
+ const levels = new Int8Array(len);
2927
+ for(let i = 0; i < len; i++)levels[i] = startLevel;
2928
+ const e = startLevel & 1 ? 'R' : 'L';
2929
+ const sor = e;
2930
+ // W1-W7
2931
+ let lastType = sor;
2932
+ for(let i = 0; i < len; i++){
2933
+ if (types[i] === 'NSM') types[i] = lastType;
2934
+ else lastType = types[i];
2935
+ }
2936
+ lastType = sor;
2937
+ for(let i = 0; i < len; i++){
2938
+ const t = types[i];
2939
+ if (t === 'EN') types[i] = lastType === 'AL' ? 'AN' : 'EN';
2940
+ else if (t === 'R' || t === 'L' || t === 'AL') lastType = t;
2941
+ }
2942
+ for(let i = 0; i < len; i++){
2943
+ if (types[i] === 'AL') types[i] = 'R';
2944
+ }
2945
+ for(let i = 1; i < len - 1; i++){
2946
+ if (types[i] === 'ES' && types[i - 1] === 'EN' && types[i + 1] === 'EN') {
2947
+ types[i] = 'EN';
2948
+ }
2949
+ if (types[i] === 'CS' && (types[i - 1] === 'EN' || types[i - 1] === 'AN') && types[i + 1] === types[i - 1]) {
2950
+ types[i] = types[i - 1];
2951
+ }
2952
+ }
2953
+ for(let i = 0; i < len; i++){
2954
+ if (types[i] !== 'EN') continue;
2955
+ let j;
2956
+ for(j = i - 1; j >= 0 && types[j] === 'ET'; j--)types[j] = 'EN';
2957
+ for(j = i + 1; j < len && types[j] === 'ET'; j++)types[j] = 'EN';
2958
+ }
2959
+ for(let i = 0; i < len; i++){
2960
+ const t = types[i];
2961
+ if (t === 'WS' || t === 'ES' || t === 'ET' || t === 'CS') types[i] = 'ON';
2962
+ }
2963
+ lastType = sor;
2964
+ for(let i = 0; i < len; i++){
2965
+ const t = types[i];
2966
+ if (t === 'EN') types[i] = lastType === 'L' ? 'L' : 'EN';
2967
+ else if (t === 'R' || t === 'L') lastType = t;
2968
+ }
2969
+ // N1-N2
2970
+ for(let i = 0; i < len; i++){
2971
+ if (types[i] !== 'ON') continue;
2972
+ let end = i + 1;
2973
+ while(end < len && types[end] === 'ON')end++;
2974
+ const before = i > 0 ? types[i - 1] : sor;
2975
+ const after = end < len ? types[end] : sor;
2976
+ const bDir = before !== 'L' ? 'R' : 'L';
2977
+ const aDir = after !== 'L' ? 'R' : 'L';
2978
+ if (bDir === aDir) {
2979
+ for(let j = i; j < end; j++)types[j] = bDir;
2980
+ }
2981
+ i = end - 1;
2982
+ }
2983
+ for(let i = 0; i < len; i++){
2984
+ if (types[i] === 'ON') types[i] = e;
2985
+ }
2986
+ // I1-I2
2987
+ for(let i = 0; i < len; i++){
2988
+ const t = types[i];
2989
+ if ((levels[i] & 1) === 0) {
2990
+ if (t === 'R') levels[i]++;
2991
+ else if (t === 'AN' || t === 'EN') levels[i] += 2;
2992
+ } else if (t === 'L' || t === 'AN' || t === 'EN') {
2993
+ levels[i]++;
2994
+ }
2995
+ }
2996
+ return levels;
2997
+ }
2998
+ function computeSegmentLevels(normalized, segStarts) {
2999
+ const bidiLevels = computeBidiLevels(normalized);
3000
+ if (bidiLevels === null) return null;
3001
+ const segLevels = new Int8Array(segStarts.length);
3002
+ for(let i = 0; i < segStarts.length; i++){
3003
+ segLevels[i] = bidiLevels[segStarts[i]];
3004
+ }
3005
+ return segLevels;
3006
+ }
3007
+
3008
+ const collapsibleWhitespaceRunRe = /[ \t\n\r\f]+/g;
3009
+ const needsWhitespaceNormalizationRe = /[\t\n\r\f]| {2,}|^ | $/;
3010
+ function getWhiteSpaceProfile(whiteSpace) {
3011
+ const mode = whiteSpace ?? 'normal';
3012
+ return mode === 'pre-wrap' ? {
3013
+ mode,
3014
+ preserveOrdinarySpaces: true,
3015
+ preserveHardBreaks: true
3016
+ } : {
3017
+ mode,
3018
+ preserveOrdinarySpaces: false,
3019
+ preserveHardBreaks: false
3020
+ };
3021
+ }
3022
+ function normalizeWhitespaceNormal(text) {
3023
+ if (!needsWhitespaceNormalizationRe.test(text)) return text;
3024
+ let normalized = text.replace(collapsibleWhitespaceRunRe, ' ');
3025
+ if (normalized.charCodeAt(0) === 0x20) {
3026
+ normalized = normalized.slice(1);
3027
+ }
3028
+ if (normalized.length > 0 && normalized.charCodeAt(normalized.length - 1) === 0x20) {
3029
+ normalized = normalized.slice(0, -1);
3030
+ }
3031
+ return normalized;
3032
+ }
3033
+ function normalizeWhitespacePreWrap(text) {
3034
+ if (!/[\r\f]/.test(text)) return text.replace(/\r\n/g, '\n');
3035
+ return text.replace(/\r\n/g, '\n').replace(/[\r\f]/g, '\n');
3036
+ }
3037
+ let sharedWordSegmenter = null;
3038
+ let segmenterLocale;
3039
+ function getSharedWordSegmenter() {
3040
+ if (sharedWordSegmenter === null) {
3041
+ sharedWordSegmenter = new Intl.Segmenter(segmenterLocale, {
3042
+ granularity: 'word'
3043
+ });
3044
+ }
3045
+ return sharedWordSegmenter;
3046
+ }
3047
+ const arabicScriptRe = /\p{Script=Arabic}/u;
3048
+ const combiningMarkRe = /\p{M}/u;
3049
+ const decimalDigitRe = /\p{Nd}/u;
3050
+ function containsArabicScript(text) {
3051
+ return arabicScriptRe.test(text);
3052
+ }
3053
+ function isCJK(s) {
3054
+ for (const ch of s){
3055
+ const c = ch.codePointAt(0);
3056
+ if (c >= 0x4E00 && c <= 0x9FFF || c >= 0x3400 && c <= 0x4DBF || c >= 0x20000 && c <= 0x2A6DF || c >= 0x2A700 && c <= 0x2B73F || c >= 0x2B740 && c <= 0x2B81F || c >= 0x2B820 && c <= 0x2CEAF || c >= 0x2CEB0 && c <= 0x2EBEF || c >= 0x30000 && c <= 0x3134F || c >= 0xF900 && c <= 0xFAFF || c >= 0x2F800 && c <= 0x2FA1F || c >= 0x3000 && c <= 0x303F || c >= 0x3040 && c <= 0x309F || c >= 0x30A0 && c <= 0x30FF || c >= 0xAC00 && c <= 0xD7AF || c >= 0xFF00 && c <= 0xFFEF) {
3057
+ return true;
3058
+ }
3059
+ }
3060
+ return false;
3061
+ }
3062
+ const kinsokuStart = new Set([
3063
+ '\uFF0C',
3064
+ '\uFF0E',
3065
+ '\uFF01',
3066
+ '\uFF1A',
3067
+ '\uFF1B',
3068
+ '\uFF1F',
3069
+ '\u3001',
3070
+ '\u3002',
3071
+ '\u30FB',
3072
+ '\uFF09',
3073
+ '\u3015',
3074
+ '\u3009',
3075
+ '\u300B',
3076
+ '\u300D',
3077
+ '\u300F',
3078
+ '\u3011',
3079
+ '\u3017',
3080
+ '\u3019',
3081
+ '\u301B',
3082
+ '\u30FC',
3083
+ '\u3005',
3084
+ '\u303B',
3085
+ '\u309D',
3086
+ '\u309E',
3087
+ '\u30FD',
3088
+ '\u30FE'
3089
+ ]);
3090
+ const kinsokuEnd = new Set([
3091
+ '"',
3092
+ '(',
3093
+ '[',
3094
+ '{',
3095
+ '“',
3096
+ '‘',
3097
+ '«',
3098
+ '‹',
3099
+ '\uFF08',
3100
+ '\u3014',
3101
+ '\u3008',
3102
+ '\u300A',
3103
+ '\u300C',
3104
+ '\u300E',
3105
+ '\u3010',
3106
+ '\u3016',
3107
+ '\u3018',
3108
+ '\u301A'
3109
+ ]);
3110
+ const forwardStickyGlue = new Set([
3111
+ "'",
3112
+ '’'
3113
+ ]);
3114
+ const leftStickyPunctuation = new Set([
3115
+ '.',
3116
+ ',',
3117
+ '!',
3118
+ '?',
3119
+ ':',
3120
+ ';',
3121
+ '\u060C',
3122
+ '\u061B',
3123
+ '\u061F',
3124
+ '\u0964',
3125
+ '\u0965',
3126
+ '\u104A',
3127
+ '\u104B',
3128
+ '\u104C',
3129
+ '\u104D',
3130
+ '\u104F',
3131
+ ')',
3132
+ ']',
3133
+ '}',
3134
+ '%',
3135
+ '"',
3136
+ '”',
3137
+ '’',
3138
+ '»',
3139
+ '›',
3140
+ '…'
3141
+ ]);
3142
+ const arabicNoSpaceTrailingPunctuation = new Set([
3143
+ ':',
3144
+ '.',
3145
+ '\u060C',
3146
+ '\u061B'
3147
+ ]);
3148
+ const myanmarMedialGlue = new Set([
3149
+ '\u104F'
3150
+ ]);
3151
+ const closingQuoteChars = new Set([
3152
+ '”',
3153
+ '’',
3154
+ '»',
3155
+ '›',
3156
+ '\u300D',
3157
+ '\u300F',
3158
+ '\u3011',
3159
+ '\u300B',
3160
+ '\u3009',
3161
+ '\u3015',
3162
+ '\uFF09'
3163
+ ]);
3164
+ function isLeftStickyPunctuationSegment(segment) {
3165
+ if (isEscapedQuoteClusterSegment(segment)) return true;
3166
+ let sawPunctuation = false;
3167
+ for (const ch of segment){
3168
+ if (leftStickyPunctuation.has(ch)) {
3169
+ sawPunctuation = true;
3170
+ continue;
3171
+ }
3172
+ if (sawPunctuation && combiningMarkRe.test(ch)) continue;
3173
+ return false;
3174
+ }
3175
+ return sawPunctuation;
3176
+ }
3177
+ function isCJKLineStartProhibitedSegment(segment) {
3178
+ for (const ch of segment){
3179
+ if (!kinsokuStart.has(ch) && !leftStickyPunctuation.has(ch)) return false;
3180
+ }
3181
+ return segment.length > 0;
3182
+ }
3183
+ function isForwardStickyClusterSegment(segment) {
3184
+ if (isEscapedQuoteClusterSegment(segment)) return true;
3185
+ for (const ch of segment){
3186
+ if (!kinsokuEnd.has(ch) && !forwardStickyGlue.has(ch) && !combiningMarkRe.test(ch)) return false;
3187
+ }
3188
+ return segment.length > 0;
3189
+ }
3190
+ function isEscapedQuoteClusterSegment(segment) {
3191
+ let sawQuote = false;
3192
+ for (const ch of segment){
3193
+ if (ch === '\\' || combiningMarkRe.test(ch)) continue;
3194
+ if (kinsokuEnd.has(ch) || leftStickyPunctuation.has(ch) || forwardStickyGlue.has(ch)) {
3195
+ sawQuote = true;
3196
+ continue;
3197
+ }
3198
+ return false;
3199
+ }
3200
+ return sawQuote;
3201
+ }
3202
+ function splitTrailingForwardStickyCluster(text) {
3203
+ const chars = Array.from(text);
3204
+ let splitIndex = chars.length;
3205
+ while(splitIndex > 0){
3206
+ const ch = chars[splitIndex - 1];
3207
+ if (combiningMarkRe.test(ch)) {
3208
+ splitIndex--;
3209
+ continue;
3210
+ }
3211
+ if (kinsokuEnd.has(ch) || forwardStickyGlue.has(ch)) {
3212
+ splitIndex--;
3213
+ continue;
3214
+ }
3215
+ break;
3216
+ }
3217
+ if (splitIndex <= 0 || splitIndex === chars.length) return null;
3218
+ return {
3219
+ head: chars.slice(0, splitIndex).join(''),
3220
+ tail: chars.slice(splitIndex).join('')
3221
+ };
3222
+ }
3223
+ function isRepeatedSingleCharRun(segment, ch) {
3224
+ if (segment.length === 0) return false;
3225
+ for (const part of segment){
3226
+ if (part !== ch) return false;
3227
+ }
3228
+ return true;
3229
+ }
3230
+ function endsWithArabicNoSpacePunctuation(segment) {
3231
+ if (!containsArabicScript(segment) || segment.length === 0) return false;
3232
+ return arabicNoSpaceTrailingPunctuation.has(segment[segment.length - 1]);
3233
+ }
3234
+ function endsWithMyanmarMedialGlue(segment) {
3235
+ if (segment.length === 0) return false;
3236
+ return myanmarMedialGlue.has(segment[segment.length - 1]);
3237
+ }
3238
+ function splitLeadingSpaceAndMarks(segment) {
3239
+ if (segment.length < 2 || segment[0] !== ' ') return null;
3240
+ const marks = segment.slice(1);
3241
+ if (/^\p{M}+$/u.test(marks)) {
3242
+ return {
3243
+ space: ' ',
3244
+ marks
3245
+ };
3246
+ }
3247
+ return null;
3248
+ }
3249
+ function endsWithClosingQuote(text) {
3250
+ for(let i = text.length - 1; i >= 0; i--){
3251
+ const ch = text[i];
3252
+ if (closingQuoteChars.has(ch)) return true;
3253
+ if (!leftStickyPunctuation.has(ch)) return false;
3254
+ }
3255
+ return false;
3256
+ }
3257
+ function classifySegmentBreakChar(ch, whiteSpaceProfile) {
3258
+ if (whiteSpaceProfile.preserveOrdinarySpaces || whiteSpaceProfile.preserveHardBreaks) {
3259
+ if (ch === ' ') return 'preserved-space';
3260
+ if (ch === '\t') return 'tab';
3261
+ if (whiteSpaceProfile.preserveHardBreaks && ch === '\n') return 'hard-break';
3262
+ }
3263
+ if (ch === ' ') return 'space';
3264
+ if (ch === '\u00A0' || ch === '\u202F' || ch === '\u2060' || ch === '\uFEFF') {
3265
+ return 'glue';
3266
+ }
3267
+ if (ch === '\u200B') return 'zero-width-break';
3268
+ if (ch === '\u00AD') return 'soft-hyphen';
3269
+ return 'text';
3270
+ }
3271
+ function splitSegmentByBreakKind(segment, isWordLike, start, whiteSpaceProfile) {
3272
+ const pieces = [];
3273
+ let currentKind = null;
3274
+ let currentText = '';
3275
+ let currentStart = start;
3276
+ let currentWordLike = false;
3277
+ let offset = 0;
3278
+ for (const ch of segment){
3279
+ const kind = classifySegmentBreakChar(ch, whiteSpaceProfile);
3280
+ const wordLike = kind === 'text' && isWordLike;
3281
+ if (currentKind !== null && kind === currentKind && wordLike === currentWordLike) {
3282
+ currentText += ch;
3283
+ offset += ch.length;
3284
+ continue;
3285
+ }
3286
+ if (currentKind !== null) {
3287
+ pieces.push({
3288
+ text: currentText,
3289
+ isWordLike: currentWordLike,
3290
+ kind: currentKind,
3291
+ start: currentStart
3292
+ });
3293
+ }
3294
+ currentKind = kind;
3295
+ currentText = ch;
3296
+ currentStart = start + offset;
3297
+ currentWordLike = wordLike;
3298
+ offset += ch.length;
3299
+ }
3300
+ if (currentKind !== null) {
3301
+ pieces.push({
3302
+ text: currentText,
3303
+ isWordLike: currentWordLike,
3304
+ kind: currentKind,
3305
+ start: currentStart
3306
+ });
3307
+ }
3308
+ return pieces;
3309
+ }
3310
+ function isTextRunBoundary(kind) {
3311
+ return kind === 'space' || kind === 'preserved-space' || kind === 'zero-width-break' || kind === 'hard-break';
3312
+ }
3313
+ const urlSchemeSegmentRe = /^[A-Za-z][A-Za-z0-9+.-]*:$/;
3314
+ function isUrlLikeRunStart(segmentation, index) {
3315
+ const text = segmentation.texts[index];
3316
+ if (text.startsWith('www.')) return true;
3317
+ return urlSchemeSegmentRe.test(text) && index + 1 < segmentation.len && segmentation.kinds[index + 1] === 'text' && segmentation.texts[index + 1] === '//';
3318
+ }
3319
+ function isUrlQueryBoundarySegment(text) {
3320
+ return text.includes('?') && (text.includes('://') || text.startsWith('www.'));
3321
+ }
3322
+ function mergeUrlLikeRuns(segmentation) {
3323
+ const texts = segmentation.texts.slice();
3324
+ const isWordLike = segmentation.isWordLike.slice();
3325
+ const kinds = segmentation.kinds.slice();
3326
+ const starts = segmentation.starts.slice();
3327
+ for(let i = 0; i < segmentation.len; i++){
3328
+ if (kinds[i] !== 'text' || !isUrlLikeRunStart(segmentation, i)) continue;
3329
+ let j = i + 1;
3330
+ while(j < segmentation.len && !isTextRunBoundary(kinds[j])){
3331
+ texts[i] += texts[j];
3332
+ isWordLike[i] = true;
3333
+ const endsQueryPrefix = texts[j].includes('?');
3334
+ kinds[j] = 'text';
3335
+ texts[j] = '';
3336
+ j++;
3337
+ if (endsQueryPrefix) break;
3338
+ }
3339
+ }
3340
+ let compactLen = 0;
3341
+ for(let read = 0; read < texts.length; read++){
3342
+ const text = texts[read];
3343
+ if (text.length === 0) continue;
3344
+ if (compactLen !== read) {
3345
+ texts[compactLen] = text;
3346
+ isWordLike[compactLen] = isWordLike[read];
3347
+ kinds[compactLen] = kinds[read];
3348
+ starts[compactLen] = starts[read];
3349
+ }
3350
+ compactLen++;
3351
+ }
3352
+ texts.length = compactLen;
3353
+ isWordLike.length = compactLen;
3354
+ kinds.length = compactLen;
3355
+ starts.length = compactLen;
3356
+ return {
3357
+ len: compactLen,
3358
+ texts,
3359
+ isWordLike,
3360
+ kinds,
3361
+ starts
3362
+ };
3363
+ }
3364
+ function mergeUrlQueryRuns(segmentation) {
3365
+ const texts = [];
3366
+ const isWordLike = [];
3367
+ const kinds = [];
3368
+ const starts = [];
3369
+ for(let i = 0; i < segmentation.len; i++){
3370
+ const text = segmentation.texts[i];
3371
+ texts.push(text);
3372
+ isWordLike.push(segmentation.isWordLike[i]);
3373
+ kinds.push(segmentation.kinds[i]);
3374
+ starts.push(segmentation.starts[i]);
3375
+ if (!isUrlQueryBoundarySegment(text)) continue;
3376
+ const nextIndex = i + 1;
3377
+ if (nextIndex >= segmentation.len || isTextRunBoundary(segmentation.kinds[nextIndex])) {
3378
+ continue;
3379
+ }
3380
+ let queryText = '';
3381
+ const queryStart = segmentation.starts[nextIndex];
3382
+ let j = nextIndex;
3383
+ while(j < segmentation.len && !isTextRunBoundary(segmentation.kinds[j])){
3384
+ queryText += segmentation.texts[j];
3385
+ j++;
3386
+ }
3387
+ if (queryText.length > 0) {
3388
+ texts.push(queryText);
3389
+ isWordLike.push(true);
3390
+ kinds.push('text');
3391
+ starts.push(queryStart);
3392
+ i = j - 1;
3393
+ }
3394
+ }
3395
+ return {
3396
+ len: texts.length,
3397
+ texts,
3398
+ isWordLike,
3399
+ kinds,
3400
+ starts
3401
+ };
3402
+ }
3403
+ const numericJoinerChars = new Set([
3404
+ ':',
3405
+ '-',
3406
+ '/',
3407
+ '×',
3408
+ ',',
3409
+ '.',
3410
+ '+',
3411
+ '\u2013',
3412
+ '\u2014'
3413
+ ]);
3414
+ const asciiPunctuationChainSegmentRe = /^[A-Za-z0-9_]+[,:;]*$/;
3415
+ const asciiPunctuationChainTrailingJoinersRe = /[,:;]+$/;
3416
+ function segmentContainsDecimalDigit(text) {
3417
+ for (const ch of text){
3418
+ if (decimalDigitRe.test(ch)) return true;
3419
+ }
3420
+ return false;
3421
+ }
3422
+ function isNumericRunSegment(text) {
3423
+ if (text.length === 0) return false;
3424
+ for (const ch of text){
3425
+ if (decimalDigitRe.test(ch) || numericJoinerChars.has(ch)) continue;
3426
+ return false;
3427
+ }
3428
+ return true;
3429
+ }
3430
+ function mergeNumericRuns(segmentation) {
3431
+ const texts = [];
3432
+ const isWordLike = [];
3433
+ const kinds = [];
3434
+ const starts = [];
3435
+ for(let i = 0; i < segmentation.len; i++){
3436
+ const text = segmentation.texts[i];
3437
+ const kind = segmentation.kinds[i];
3438
+ if (kind === 'text' && isNumericRunSegment(text) && segmentContainsDecimalDigit(text)) {
3439
+ let mergedText = text;
3440
+ let j = i + 1;
3441
+ while(j < segmentation.len && segmentation.kinds[j] === 'text' && isNumericRunSegment(segmentation.texts[j])){
3442
+ mergedText += segmentation.texts[j];
3443
+ j++;
3444
+ }
3445
+ texts.push(mergedText);
3446
+ isWordLike.push(true);
3447
+ kinds.push('text');
3448
+ starts.push(segmentation.starts[i]);
3449
+ i = j - 1;
3450
+ continue;
3451
+ }
3452
+ texts.push(text);
3453
+ isWordLike.push(segmentation.isWordLike[i]);
3454
+ kinds.push(kind);
3455
+ starts.push(segmentation.starts[i]);
3456
+ }
3457
+ return {
3458
+ len: texts.length,
3459
+ texts,
3460
+ isWordLike,
3461
+ kinds,
3462
+ starts
3463
+ };
3464
+ }
3465
+ function mergeAsciiPunctuationChains(segmentation) {
3466
+ const texts = [];
3467
+ const isWordLike = [];
3468
+ const kinds = [];
3469
+ const starts = [];
3470
+ for(let i = 0; i < segmentation.len; i++){
3471
+ const text = segmentation.texts[i];
3472
+ const kind = segmentation.kinds[i];
3473
+ const wordLike = segmentation.isWordLike[i];
3474
+ if (kind === 'text' && wordLike && asciiPunctuationChainSegmentRe.test(text)) {
3475
+ let mergedText = text;
3476
+ let j = i + 1;
3477
+ while(asciiPunctuationChainTrailingJoinersRe.test(mergedText) && j < segmentation.len && segmentation.kinds[j] === 'text' && segmentation.isWordLike[j] && asciiPunctuationChainSegmentRe.test(segmentation.texts[j])){
3478
+ mergedText += segmentation.texts[j];
3479
+ j++;
3480
+ }
3481
+ texts.push(mergedText);
3482
+ isWordLike.push(true);
3483
+ kinds.push('text');
3484
+ starts.push(segmentation.starts[i]);
3485
+ i = j - 1;
3486
+ continue;
3487
+ }
3488
+ texts.push(text);
3489
+ isWordLike.push(wordLike);
3490
+ kinds.push(kind);
3491
+ starts.push(segmentation.starts[i]);
3492
+ }
3493
+ return {
3494
+ len: texts.length,
3495
+ texts,
3496
+ isWordLike,
3497
+ kinds,
3498
+ starts
3499
+ };
3500
+ }
3501
+ function splitHyphenatedNumericRuns(segmentation) {
3502
+ const texts = [];
3503
+ const isWordLike = [];
3504
+ const kinds = [];
3505
+ const starts = [];
3506
+ for(let i = 0; i < segmentation.len; i++){
3507
+ const text = segmentation.texts[i];
3508
+ if (segmentation.kinds[i] === 'text' && text.includes('-')) {
3509
+ const parts = text.split('-');
3510
+ let shouldSplit = parts.length > 1;
3511
+ for(let j = 0; j < parts.length; j++){
3512
+ const part = parts[j];
3513
+ if (!shouldSplit) break;
3514
+ if (part.length === 0 || !segmentContainsDecimalDigit(part) || !isNumericRunSegment(part)) {
3515
+ shouldSplit = false;
3516
+ }
3517
+ }
3518
+ if (shouldSplit) {
3519
+ let offset = 0;
3520
+ for(let j = 0; j < parts.length; j++){
3521
+ const part = parts[j];
3522
+ const splitText = j < parts.length - 1 ? `${part}-` : part;
3523
+ texts.push(splitText);
3524
+ isWordLike.push(true);
3525
+ kinds.push('text');
3526
+ starts.push(segmentation.starts[i] + offset);
3527
+ offset += splitText.length;
3528
+ }
3529
+ continue;
3530
+ }
3531
+ }
3532
+ texts.push(text);
3533
+ isWordLike.push(segmentation.isWordLike[i]);
3534
+ kinds.push(segmentation.kinds[i]);
3535
+ starts.push(segmentation.starts[i]);
3536
+ }
3537
+ return {
3538
+ len: texts.length,
3539
+ texts,
3540
+ isWordLike,
3541
+ kinds,
3542
+ starts
3543
+ };
3544
+ }
3545
+ function mergeGlueConnectedTextRuns(segmentation) {
3546
+ const texts = [];
3547
+ const isWordLike = [];
3548
+ const kinds = [];
3549
+ const starts = [];
3550
+ let read = 0;
3551
+ while(read < segmentation.len){
3552
+ let text = segmentation.texts[read];
3553
+ let wordLike = segmentation.isWordLike[read];
3554
+ let kind = segmentation.kinds[read];
3555
+ let start = segmentation.starts[read];
3556
+ if (kind === 'glue') {
3557
+ let glueText = text;
3558
+ const glueStart = start;
3559
+ read++;
3560
+ while(read < segmentation.len && segmentation.kinds[read] === 'glue'){
3561
+ glueText += segmentation.texts[read];
3562
+ read++;
3563
+ }
3564
+ if (read < segmentation.len && segmentation.kinds[read] === 'text') {
3565
+ text = glueText + segmentation.texts[read];
3566
+ wordLike = segmentation.isWordLike[read];
3567
+ kind = 'text';
3568
+ start = glueStart;
3569
+ read++;
3570
+ } else {
3571
+ texts.push(glueText);
3572
+ isWordLike.push(false);
3573
+ kinds.push('glue');
3574
+ starts.push(glueStart);
3575
+ continue;
3576
+ }
3577
+ } else {
3578
+ read++;
3579
+ }
3580
+ if (kind === 'text') {
3581
+ while(read < segmentation.len && segmentation.kinds[read] === 'glue'){
3582
+ let glueText = '';
3583
+ while(read < segmentation.len && segmentation.kinds[read] === 'glue'){
3584
+ glueText += segmentation.texts[read];
3585
+ read++;
3586
+ }
3587
+ if (read < segmentation.len && segmentation.kinds[read] === 'text') {
3588
+ text += glueText + segmentation.texts[read];
3589
+ wordLike = wordLike || segmentation.isWordLike[read];
3590
+ read++;
3591
+ continue;
3592
+ }
3593
+ text += glueText;
3594
+ }
3595
+ }
3596
+ texts.push(text);
3597
+ isWordLike.push(wordLike);
3598
+ kinds.push(kind);
3599
+ starts.push(start);
3600
+ }
3601
+ return {
3602
+ len: texts.length,
3603
+ texts,
3604
+ isWordLike,
3605
+ kinds,
3606
+ starts
3607
+ };
3608
+ }
3609
+ function carryTrailingForwardStickyAcrossCJKBoundary(segmentation) {
3610
+ const texts = segmentation.texts.slice();
3611
+ const isWordLike = segmentation.isWordLike.slice();
3612
+ const kinds = segmentation.kinds.slice();
3613
+ const starts = segmentation.starts.slice();
3614
+ for(let i = 0; i < texts.length - 1; i++){
3615
+ if (kinds[i] !== 'text' || kinds[i + 1] !== 'text') continue;
3616
+ if (!isCJK(texts[i]) || !isCJK(texts[i + 1])) continue;
3617
+ const split = splitTrailingForwardStickyCluster(texts[i]);
3618
+ if (split === null) continue;
3619
+ texts[i] = split.head;
3620
+ texts[i + 1] = split.tail + texts[i + 1];
3621
+ starts[i + 1] = starts[i] + split.head.length;
3622
+ }
3623
+ return {
3624
+ len: texts.length,
3625
+ texts,
3626
+ isWordLike,
3627
+ kinds,
3628
+ starts
3629
+ };
3630
+ }
3631
+ function buildMergedSegmentation(normalized, profile, whiteSpaceProfile) {
3632
+ const wordSegmenter = getSharedWordSegmenter();
3633
+ let mergedLen = 0;
3634
+ const mergedTexts = [];
3635
+ const mergedWordLike = [];
3636
+ const mergedKinds = [];
3637
+ const mergedStarts = [];
3638
+ for (const s of wordSegmenter.segment(normalized)){
3639
+ for (const piece of splitSegmentByBreakKind(s.segment, s.isWordLike ?? false, s.index, whiteSpaceProfile)){
3640
+ const isText = piece.kind === 'text';
3641
+ if (profile.carryCJKAfterClosingQuote && isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === 'text' && isCJK(piece.text) && isCJK(mergedTexts[mergedLen - 1]) && endsWithClosingQuote(mergedTexts[mergedLen - 1])) {
3642
+ mergedTexts[mergedLen - 1] += piece.text;
3643
+ mergedWordLike[mergedLen - 1] = mergedWordLike[mergedLen - 1] || piece.isWordLike;
3644
+ } else if (isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === 'text' && isCJKLineStartProhibitedSegment(piece.text) && isCJK(mergedTexts[mergedLen - 1])) {
3645
+ mergedTexts[mergedLen - 1] += piece.text;
3646
+ mergedWordLike[mergedLen - 1] = mergedWordLike[mergedLen - 1] || piece.isWordLike;
3647
+ } else if (isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === 'text' && endsWithMyanmarMedialGlue(mergedTexts[mergedLen - 1])) {
3648
+ mergedTexts[mergedLen - 1] += piece.text;
3649
+ mergedWordLike[mergedLen - 1] = mergedWordLike[mergedLen - 1] || piece.isWordLike;
3650
+ } else if (isText && mergedLen > 0 && mergedKinds[mergedLen - 1] === 'text' && piece.isWordLike && containsArabicScript(piece.text) && endsWithArabicNoSpacePunctuation(mergedTexts[mergedLen - 1])) {
3651
+ mergedTexts[mergedLen - 1] += piece.text;
3652
+ mergedWordLike[mergedLen - 1] = true;
3653
+ } 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)) {
3654
+ mergedTexts[mergedLen - 1] += piece.text;
3655
+ } else if (isText && !piece.isWordLike && mergedLen > 0 && mergedKinds[mergedLen - 1] === 'text' && (isLeftStickyPunctuationSegment(piece.text) || piece.text === '-' && mergedWordLike[mergedLen - 1])) {
3656
+ mergedTexts[mergedLen - 1] += piece.text;
3657
+ } else {
3658
+ mergedTexts[mergedLen] = piece.text;
3659
+ mergedWordLike[mergedLen] = piece.isWordLike;
3660
+ mergedKinds[mergedLen] = piece.kind;
3661
+ mergedStarts[mergedLen] = piece.start;
3662
+ mergedLen++;
3663
+ }
3664
+ }
3665
+ }
3666
+ for(let i = 1; i < mergedLen; i++){
3667
+ if (mergedKinds[i] === 'text' && !mergedWordLike[i] && isEscapedQuoteClusterSegment(mergedTexts[i]) && mergedKinds[i - 1] === 'text') {
3668
+ mergedTexts[i - 1] += mergedTexts[i];
3669
+ mergedWordLike[i - 1] = mergedWordLike[i - 1] || mergedWordLike[i];
3670
+ mergedTexts[i] = '';
3671
+ }
3672
+ }
3673
+ for(let i = mergedLen - 2; i >= 0; i--){
3674
+ if (mergedKinds[i] === 'text' && !mergedWordLike[i] && isForwardStickyClusterSegment(mergedTexts[i])) {
3675
+ let j = i + 1;
3676
+ while(j < mergedLen && mergedTexts[j] === '')j++;
3677
+ if (j < mergedLen && mergedKinds[j] === 'text') {
3678
+ mergedTexts[j] = mergedTexts[i] + mergedTexts[j];
3679
+ mergedStarts[j] = mergedStarts[i];
3680
+ mergedTexts[i] = '';
3681
+ }
3682
+ }
3683
+ }
3684
+ let compactLen = 0;
3685
+ for(let read = 0; read < mergedLen; read++){
3686
+ const text = mergedTexts[read];
3687
+ if (text.length === 0) continue;
3688
+ if (compactLen !== read) {
3689
+ mergedTexts[compactLen] = text;
3690
+ mergedWordLike[compactLen] = mergedWordLike[read];
3691
+ mergedKinds[compactLen] = mergedKinds[read];
3692
+ mergedStarts[compactLen] = mergedStarts[read];
3693
+ }
3694
+ compactLen++;
3695
+ }
3696
+ mergedTexts.length = compactLen;
3697
+ mergedWordLike.length = compactLen;
3698
+ mergedKinds.length = compactLen;
3699
+ mergedStarts.length = compactLen;
3700
+ const compacted = mergeGlueConnectedTextRuns({
3701
+ len: compactLen,
3702
+ texts: mergedTexts,
3703
+ isWordLike: mergedWordLike,
3704
+ kinds: mergedKinds,
3705
+ starts: mergedStarts
3706
+ });
3707
+ const withMergedUrls = carryTrailingForwardStickyAcrossCJKBoundary(mergeAsciiPunctuationChains(splitHyphenatedNumericRuns(mergeNumericRuns(mergeUrlQueryRuns(mergeUrlLikeRuns(compacted))))));
3708
+ for(let i = 0; i < withMergedUrls.len - 1; i++){
3709
+ const split = splitLeadingSpaceAndMarks(withMergedUrls.texts[i]);
3710
+ if (split === null) continue;
3711
+ if (withMergedUrls.kinds[i] !== 'space' && withMergedUrls.kinds[i] !== 'preserved-space' || withMergedUrls.kinds[i + 1] !== 'text' || !containsArabicScript(withMergedUrls.texts[i + 1])) {
3712
+ continue;
3713
+ }
3714
+ withMergedUrls.texts[i] = split.space;
3715
+ withMergedUrls.isWordLike[i] = false;
3716
+ withMergedUrls.kinds[i] = withMergedUrls.kinds[i] === 'preserved-space' ? 'preserved-space' : 'space';
3717
+ withMergedUrls.texts[i + 1] = split.marks + withMergedUrls.texts[i + 1];
3718
+ withMergedUrls.starts[i + 1] = withMergedUrls.starts[i] + split.space.length;
3719
+ }
3720
+ return withMergedUrls;
3721
+ }
3722
+ function compileAnalysisChunks(segmentation, whiteSpaceProfile) {
3723
+ if (segmentation.len === 0) return [];
3724
+ if (!whiteSpaceProfile.preserveHardBreaks) {
3725
+ return [
3726
+ {
3727
+ startSegmentIndex: 0,
3728
+ endSegmentIndex: segmentation.len,
3729
+ consumedEndSegmentIndex: segmentation.len
3730
+ }
3731
+ ];
3732
+ }
3733
+ const chunks = [];
3734
+ let startSegmentIndex = 0;
3735
+ for(let i = 0; i < segmentation.len; i++){
3736
+ if (segmentation.kinds[i] !== 'hard-break') continue;
3737
+ chunks.push({
3738
+ startSegmentIndex,
3739
+ endSegmentIndex: i,
3740
+ consumedEndSegmentIndex: i + 1
3741
+ });
3742
+ startSegmentIndex = i + 1;
3743
+ }
3744
+ if (startSegmentIndex < segmentation.len) {
3745
+ chunks.push({
3746
+ startSegmentIndex,
3747
+ endSegmentIndex: segmentation.len,
3748
+ consumedEndSegmentIndex: segmentation.len
3749
+ });
3750
+ }
3751
+ return chunks;
3752
+ }
3753
+ function analyzeText(text, profile, whiteSpace = 'normal') {
3754
+ const whiteSpaceProfile = getWhiteSpaceProfile(whiteSpace);
3755
+ const normalized = whiteSpaceProfile.mode === 'pre-wrap' ? normalizeWhitespacePreWrap(text) : normalizeWhitespaceNormal(text);
3756
+ if (normalized.length === 0) {
3757
+ return {
3758
+ normalized,
3759
+ chunks: [],
3760
+ len: 0,
3761
+ texts: [],
3762
+ isWordLike: [],
3763
+ kinds: [],
3764
+ starts: []
3765
+ };
3766
+ }
3767
+ const segmentation = buildMergedSegmentation(normalized, profile, whiteSpaceProfile);
3768
+ return {
3769
+ normalized,
3770
+ chunks: compileAnalysisChunks(segmentation, whiteSpaceProfile),
3771
+ ...segmentation
3772
+ };
3773
+ }
3774
+
3775
+ let measureContext = null;
3776
+ const segmentMetricCaches = new Map();
3777
+ let cachedEngineProfile = null;
3778
+ const emojiPresentationRe = /\p{Emoji_Presentation}/u;
3779
+ const maybeEmojiRe = /[\p{Emoji_Presentation}\p{Extended_Pictographic}\p{Regional_Indicator}\uFE0F\u20E3]/u;
3780
+ let sharedGraphemeSegmenter$1 = null;
3781
+ const emojiCorrectionCache = new Map();
3782
+ function getMeasureContext() {
3783
+ if (measureContext !== null) return measureContext;
3784
+ if (typeof OffscreenCanvas !== 'undefined') {
3785
+ measureContext = new OffscreenCanvas(1, 1).getContext('2d');
3786
+ return measureContext;
3787
+ }
3788
+ if (typeof document !== 'undefined') {
3789
+ measureContext = document.createElement('canvas').getContext('2d');
3790
+ return measureContext;
3791
+ }
3792
+ throw new Error('Text measurement requires OffscreenCanvas or a DOM canvas context.');
3793
+ }
3794
+ function getSegmentMetricCache(font) {
3795
+ let cache = segmentMetricCaches.get(font);
3796
+ if (!cache) {
3797
+ cache = new Map();
3798
+ segmentMetricCaches.set(font, cache);
3799
+ }
3800
+ return cache;
3801
+ }
3802
+ function getSegmentMetrics(seg, cache) {
3803
+ let metrics = cache.get(seg);
3804
+ if (metrics === undefined) {
3805
+ const ctx = getMeasureContext();
3806
+ metrics = {
3807
+ width: ctx.measureText(seg).width,
3808
+ containsCJK: isCJK(seg)
3809
+ };
3810
+ cache.set(seg, metrics);
3811
+ }
3812
+ return metrics;
3813
+ }
3814
+ function getEngineProfile() {
3815
+ if (cachedEngineProfile !== null) return cachedEngineProfile;
3816
+ if (typeof navigator === 'undefined') {
3817
+ cachedEngineProfile = {
3818
+ lineFitEpsilon: 0.005,
3819
+ carryCJKAfterClosingQuote: false,
3820
+ preferPrefixWidthsForBreakableRuns: false,
3821
+ preferEarlySoftHyphenBreak: false
3822
+ };
3823
+ return cachedEngineProfile;
3824
+ }
3825
+ const ua = navigator.userAgent;
3826
+ const vendor = navigator.vendor;
3827
+ const isSafari = vendor === 'Apple Computer, Inc.' && ua.includes('Safari/') && !ua.includes('Chrome/') && !ua.includes('Chromium/') && !ua.includes('CriOS/') && !ua.includes('FxiOS/') && !ua.includes('EdgiOS/');
3828
+ const isChromium = ua.includes('Chrome/') || ua.includes('Chromium/') || ua.includes('CriOS/') || ua.includes('Edg/');
3829
+ cachedEngineProfile = {
3830
+ lineFitEpsilon: isSafari ? 1 / 64 : 0.005,
3831
+ carryCJKAfterClosingQuote: isChromium,
3832
+ preferPrefixWidthsForBreakableRuns: isSafari,
3833
+ preferEarlySoftHyphenBreak: isSafari
3834
+ };
3835
+ return cachedEngineProfile;
3836
+ }
3837
+ function parseFontSize(font) {
3838
+ const m = font.match(/(\d+(?:\.\d+)?)\s*px/);
3839
+ return m ? parseFloat(m[1]) : 16;
3840
+ }
3841
+ function getSharedGraphemeSegmenter$1() {
3842
+ if (sharedGraphemeSegmenter$1 === null) {
3843
+ sharedGraphemeSegmenter$1 = new Intl.Segmenter(undefined, {
3844
+ granularity: 'grapheme'
3845
+ });
3846
+ }
3847
+ return sharedGraphemeSegmenter$1;
3848
+ }
3849
+ function isEmojiGrapheme(g) {
3850
+ return emojiPresentationRe.test(g) || g.includes('\uFE0F');
3851
+ }
3852
+ function textMayContainEmoji(text) {
3853
+ return maybeEmojiRe.test(text);
3854
+ }
3855
+ function getEmojiCorrection(font, fontSize) {
3856
+ let correction = emojiCorrectionCache.get(font);
3857
+ if (correction !== undefined) return correction;
3858
+ const ctx = getMeasureContext();
3859
+ ctx.font = font;
3860
+ const canvasW = ctx.measureText('\u{1F600}').width;
3861
+ correction = 0;
3862
+ if (canvasW > fontSize + 0.5 && typeof document !== 'undefined' && document.body !== null) {
3863
+ const span = document.createElement('span');
3864
+ span.style.font = font;
3865
+ span.style.display = 'inline-block';
3866
+ span.style.visibility = 'hidden';
3867
+ span.style.position = 'absolute';
3868
+ span.textContent = '\u{1F600}';
3869
+ document.body.appendChild(span);
3870
+ const domW = span.getBoundingClientRect().width;
3871
+ document.body.removeChild(span);
3872
+ if (canvasW - domW > 0.5) {
3873
+ correction = canvasW - domW;
3874
+ }
3875
+ }
3876
+ emojiCorrectionCache.set(font, correction);
3877
+ return correction;
3878
+ }
3879
+ function countEmojiGraphemes(text) {
3880
+ let count = 0;
3881
+ const graphemeSegmenter = getSharedGraphemeSegmenter$1();
3882
+ for (const g of graphemeSegmenter.segment(text)){
3883
+ if (isEmojiGrapheme(g.segment)) count++;
3884
+ }
3885
+ return count;
3886
+ }
3887
+ function getEmojiCount(seg, metrics) {
3888
+ if (metrics.emojiCount === undefined) {
3889
+ metrics.emojiCount = countEmojiGraphemes(seg);
3890
+ }
3891
+ return metrics.emojiCount;
3892
+ }
3893
+ function getCorrectedSegmentWidth(seg, metrics, emojiCorrection) {
3894
+ if (emojiCorrection === 0) return metrics.width;
3895
+ return metrics.width - getEmojiCount(seg, metrics) * emojiCorrection;
3896
+ }
3897
+ function getSegmentGraphemeWidths(seg, metrics, cache, emojiCorrection) {
3898
+ if (metrics.graphemeWidths !== undefined) return metrics.graphemeWidths;
3899
+ const widths = [];
3900
+ const graphemeSegmenter = getSharedGraphemeSegmenter$1();
3901
+ for (const gs of graphemeSegmenter.segment(seg)){
3902
+ const graphemeMetrics = getSegmentMetrics(gs.segment, cache);
3903
+ widths.push(getCorrectedSegmentWidth(gs.segment, graphemeMetrics, emojiCorrection));
3904
+ }
3905
+ metrics.graphemeWidths = widths.length > 1 ? widths : null;
3906
+ return metrics.graphemeWidths;
3907
+ }
3908
+ function getSegmentGraphemePrefixWidths(seg, metrics, cache, emojiCorrection) {
3909
+ if (metrics.graphemePrefixWidths !== undefined) return metrics.graphemePrefixWidths;
3910
+ const prefixWidths = [];
3911
+ const graphemeSegmenter = getSharedGraphemeSegmenter$1();
3912
+ let prefix = '';
3913
+ for (const gs of graphemeSegmenter.segment(seg)){
3914
+ prefix += gs.segment;
3915
+ const prefixMetrics = getSegmentMetrics(prefix, cache);
3916
+ prefixWidths.push(getCorrectedSegmentWidth(prefix, prefixMetrics, emojiCorrection));
3917
+ }
3918
+ metrics.graphemePrefixWidths = prefixWidths.length > 1 ? prefixWidths : null;
3919
+ return metrics.graphemePrefixWidths;
3920
+ }
3921
+ function getFontMeasurementState(font, needsEmojiCorrection) {
3922
+ const ctx = getMeasureContext();
3923
+ ctx.font = font;
3924
+ const cache = getSegmentMetricCache(font);
3925
+ const fontSize = parseFontSize(font);
3926
+ const emojiCorrection = needsEmojiCorrection ? getEmojiCorrection(font, fontSize) : 0;
3927
+ return {
3928
+ cache,
3929
+ fontSize,
3930
+ emojiCorrection
3931
+ };
3932
+ }
3933
+
3934
+ function canBreakAfter(kind) {
3935
+ return kind === 'space' || kind === 'preserved-space' || kind === 'tab' || kind === 'zero-width-break' || kind === 'soft-hyphen';
3936
+ }
3937
+ function getTabAdvance(lineWidth, tabStopAdvance) {
3938
+ if (tabStopAdvance <= 0) return 0;
3939
+ const remainder = lineWidth % tabStopAdvance;
3940
+ if (Math.abs(remainder) <= 1e-6) return tabStopAdvance;
3941
+ return tabStopAdvance - remainder;
3942
+ }
3943
+ function getBreakableAdvance(graphemeWidths, graphemePrefixWidths, graphemeIndex, preferPrefixWidths) {
3944
+ if (!preferPrefixWidths || graphemePrefixWidths === null) {
3945
+ return graphemeWidths[graphemeIndex];
3946
+ }
3947
+ return graphemePrefixWidths[graphemeIndex] - (graphemeIndex > 0 ? graphemePrefixWidths[graphemeIndex - 1] : 0);
3948
+ }
3949
+ function fitSoftHyphenBreak(graphemeWidths, initialWidth, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, cumulativeWidths) {
3950
+ let fitCount = 0;
3951
+ let fittedWidth = initialWidth;
3952
+ while(fitCount < graphemeWidths.length){
3953
+ const nextWidth = cumulativeWidths ? initialWidth + graphemeWidths[fitCount] : fittedWidth + graphemeWidths[fitCount];
3954
+ const nextLineWidth = fitCount + 1 < graphemeWidths.length ? nextWidth + discretionaryHyphenWidth : nextWidth;
3955
+ if (nextLineWidth > maxWidth + lineFitEpsilon) break;
3956
+ fittedWidth = nextWidth;
3957
+ fitCount++;
3958
+ }
3959
+ return {
3960
+ fitCount,
3961
+ fittedWidth
3962
+ };
3963
+ }
3964
+ function walkPreparedLinesSimple(prepared, maxWidth, onLine) {
3965
+ const { widths, kinds, breakableWidths, breakablePrefixWidths } = prepared;
3966
+ if (widths.length === 0) return 0;
3967
+ const engineProfile = getEngineProfile();
3968
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
3969
+ let lineCount = 0;
3970
+ let lineW = 0;
3971
+ let hasContent = false;
3972
+ let lineStartSegmentIndex = 0;
3973
+ let lineStartGraphemeIndex = 0;
3974
+ let lineEndSegmentIndex = 0;
3975
+ let lineEndGraphemeIndex = 0;
3976
+ let pendingBreakSegmentIndex = -1;
3977
+ let pendingBreakPaintWidth = 0;
3978
+ function clearPendingBreak() {
3979
+ pendingBreakSegmentIndex = -1;
3980
+ pendingBreakPaintWidth = 0;
3981
+ }
3982
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
3983
+ lineCount++;
3984
+ onLine?.({
3985
+ startSegmentIndex: lineStartSegmentIndex,
3986
+ startGraphemeIndex: lineStartGraphemeIndex,
3987
+ endSegmentIndex,
3988
+ endGraphemeIndex,
3989
+ width
3990
+ });
3991
+ lineW = 0;
3992
+ hasContent = false;
3993
+ clearPendingBreak();
3994
+ }
3995
+ function startLineAtSegment(segmentIndex, width) {
3996
+ hasContent = true;
3997
+ lineStartSegmentIndex = segmentIndex;
3998
+ lineStartGraphemeIndex = 0;
3999
+ lineEndSegmentIndex = segmentIndex + 1;
4000
+ lineEndGraphemeIndex = 0;
4001
+ lineW = width;
4002
+ }
4003
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
4004
+ hasContent = true;
4005
+ lineStartSegmentIndex = segmentIndex;
4006
+ lineStartGraphemeIndex = graphemeIndex;
4007
+ lineEndSegmentIndex = segmentIndex;
4008
+ lineEndGraphemeIndex = graphemeIndex + 1;
4009
+ lineW = width;
4010
+ }
4011
+ function appendWholeSegment(segmentIndex, width) {
4012
+ if (!hasContent) {
4013
+ startLineAtSegment(segmentIndex, width);
4014
+ return;
4015
+ }
4016
+ lineW += width;
4017
+ lineEndSegmentIndex = segmentIndex + 1;
4018
+ lineEndGraphemeIndex = 0;
4019
+ }
4020
+ function updatePendingBreak(segmentIndex, segmentWidth) {
4021
+ if (!canBreakAfter(kinds[segmentIndex])) return;
4022
+ pendingBreakSegmentIndex = segmentIndex + 1;
4023
+ pendingBreakPaintWidth = lineW - segmentWidth;
4024
+ }
4025
+ function appendBreakableSegment(segmentIndex) {
4026
+ appendBreakableSegmentFrom(segmentIndex, 0);
4027
+ }
4028
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
4029
+ const gWidths = breakableWidths[segmentIndex];
4030
+ const gPrefixWidths = breakablePrefixWidths[segmentIndex] ?? null;
4031
+ for(let g = startGraphemeIndex; g < gWidths.length; g++){
4032
+ const gw = getBreakableAdvance(gWidths, gPrefixWidths, g, engineProfile.preferPrefixWidthsForBreakableRuns);
4033
+ if (!hasContent) {
4034
+ startLineAtGrapheme(segmentIndex, g, gw);
4035
+ continue;
4036
+ }
4037
+ if (lineW + gw > maxWidth + lineFitEpsilon) {
4038
+ emitCurrentLine();
4039
+ startLineAtGrapheme(segmentIndex, g, gw);
4040
+ } else {
4041
+ lineW += gw;
4042
+ lineEndSegmentIndex = segmentIndex;
4043
+ lineEndGraphemeIndex = g + 1;
4044
+ }
4045
+ }
4046
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === gWidths.length) {
4047
+ lineEndSegmentIndex = segmentIndex + 1;
4048
+ lineEndGraphemeIndex = 0;
4049
+ }
4050
+ }
4051
+ let i = 0;
4052
+ while(i < widths.length){
4053
+ const w = widths[i];
4054
+ const kind = kinds[i];
4055
+ if (!hasContent) {
4056
+ if (w > maxWidth && breakableWidths[i] !== null) {
4057
+ appendBreakableSegment(i);
4058
+ } else {
4059
+ startLineAtSegment(i, w);
4060
+ }
4061
+ updatePendingBreak(i, w);
4062
+ i++;
4063
+ continue;
4064
+ }
4065
+ const newW = lineW + w;
4066
+ if (newW > maxWidth + lineFitEpsilon) {
4067
+ if (canBreakAfter(kind)) {
4068
+ appendWholeSegment(i, w);
4069
+ emitCurrentLine(i + 1, 0, lineW - w);
4070
+ i++;
4071
+ continue;
4072
+ }
4073
+ if (pendingBreakSegmentIndex >= 0) {
4074
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
4075
+ continue;
4076
+ }
4077
+ if (w > maxWidth && breakableWidths[i] !== null) {
4078
+ emitCurrentLine();
4079
+ appendBreakableSegment(i);
4080
+ i++;
4081
+ continue;
4082
+ }
4083
+ emitCurrentLine();
4084
+ continue;
4085
+ }
4086
+ appendWholeSegment(i, w);
4087
+ updatePendingBreak(i, w);
4088
+ i++;
4089
+ }
4090
+ if (hasContent) emitCurrentLine();
4091
+ return lineCount;
4092
+ }
4093
+ function walkPreparedLines(prepared, maxWidth, onLine) {
4094
+ if (prepared.simpleLineWalkFastPath) {
4095
+ return walkPreparedLinesSimple(prepared, maxWidth, onLine);
4096
+ }
4097
+ const { widths, lineEndFitAdvances, lineEndPaintAdvances, kinds, breakableWidths, breakablePrefixWidths, discretionaryHyphenWidth, tabStopAdvance, chunks } = prepared;
4098
+ if (widths.length === 0 || chunks.length === 0) return 0;
4099
+ const engineProfile = getEngineProfile();
4100
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
4101
+ let lineCount = 0;
4102
+ let lineW = 0;
4103
+ let hasContent = false;
4104
+ let lineStartSegmentIndex = 0;
4105
+ let lineStartGraphemeIndex = 0;
4106
+ let lineEndSegmentIndex = 0;
4107
+ let lineEndGraphemeIndex = 0;
4108
+ let pendingBreakSegmentIndex = -1;
4109
+ let pendingBreakFitWidth = 0;
4110
+ let pendingBreakPaintWidth = 0;
4111
+ let pendingBreakKind = null;
4112
+ function clearPendingBreak() {
4113
+ pendingBreakSegmentIndex = -1;
4114
+ pendingBreakFitWidth = 0;
4115
+ pendingBreakPaintWidth = 0;
4116
+ pendingBreakKind = null;
4117
+ }
4118
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
4119
+ lineCount++;
4120
+ onLine?.({
4121
+ startSegmentIndex: lineStartSegmentIndex,
4122
+ startGraphemeIndex: lineStartGraphemeIndex,
4123
+ endSegmentIndex,
4124
+ endGraphemeIndex,
4125
+ width
4126
+ });
4127
+ lineW = 0;
4128
+ hasContent = false;
4129
+ clearPendingBreak();
4130
+ }
4131
+ function startLineAtSegment(segmentIndex, width) {
4132
+ hasContent = true;
4133
+ lineStartSegmentIndex = segmentIndex;
4134
+ lineStartGraphemeIndex = 0;
4135
+ lineEndSegmentIndex = segmentIndex + 1;
4136
+ lineEndGraphemeIndex = 0;
4137
+ lineW = width;
4138
+ }
4139
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
4140
+ hasContent = true;
4141
+ lineStartSegmentIndex = segmentIndex;
4142
+ lineStartGraphemeIndex = graphemeIndex;
4143
+ lineEndSegmentIndex = segmentIndex;
4144
+ lineEndGraphemeIndex = graphemeIndex + 1;
4145
+ lineW = width;
4146
+ }
4147
+ function appendWholeSegment(segmentIndex, width) {
4148
+ if (!hasContent) {
4149
+ startLineAtSegment(segmentIndex, width);
4150
+ return;
4151
+ }
4152
+ lineW += width;
4153
+ lineEndSegmentIndex = segmentIndex + 1;
4154
+ lineEndGraphemeIndex = 0;
4155
+ }
4156
+ function updatePendingBreakForWholeSegment(segmentIndex, segmentWidth) {
4157
+ if (!canBreakAfter(kinds[segmentIndex])) return;
4158
+ const fitAdvance = kinds[segmentIndex] === 'tab' ? 0 : lineEndFitAdvances[segmentIndex];
4159
+ const paintAdvance = kinds[segmentIndex] === 'tab' ? segmentWidth : lineEndPaintAdvances[segmentIndex];
4160
+ pendingBreakSegmentIndex = segmentIndex + 1;
4161
+ pendingBreakFitWidth = lineW - segmentWidth + fitAdvance;
4162
+ pendingBreakPaintWidth = lineW - segmentWidth + paintAdvance;
4163
+ pendingBreakKind = kinds[segmentIndex];
4164
+ }
4165
+ function appendBreakableSegment(segmentIndex) {
4166
+ appendBreakableSegmentFrom(segmentIndex, 0);
4167
+ }
4168
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
4169
+ const gWidths = breakableWidths[segmentIndex];
4170
+ const gPrefixWidths = breakablePrefixWidths[segmentIndex] ?? null;
4171
+ for(let g = startGraphemeIndex; g < gWidths.length; g++){
4172
+ const gw = getBreakableAdvance(gWidths, gPrefixWidths, g, engineProfile.preferPrefixWidthsForBreakableRuns);
4173
+ if (!hasContent) {
4174
+ startLineAtGrapheme(segmentIndex, g, gw);
4175
+ continue;
4176
+ }
4177
+ if (lineW + gw > maxWidth + lineFitEpsilon) {
4178
+ emitCurrentLine();
4179
+ startLineAtGrapheme(segmentIndex, g, gw);
4180
+ } else {
4181
+ lineW += gw;
4182
+ lineEndSegmentIndex = segmentIndex;
4183
+ lineEndGraphemeIndex = g + 1;
4184
+ }
4185
+ }
4186
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === gWidths.length) {
4187
+ lineEndSegmentIndex = segmentIndex + 1;
4188
+ lineEndGraphemeIndex = 0;
4189
+ }
4190
+ }
4191
+ function continueSoftHyphenBreakableSegment(segmentIndex) {
4192
+ if (pendingBreakKind !== 'soft-hyphen') return false;
4193
+ const gWidths = breakableWidths[segmentIndex];
4194
+ if (gWidths === null) return false;
4195
+ const fitWidths = engineProfile.preferPrefixWidthsForBreakableRuns ? breakablePrefixWidths[segmentIndex] ?? gWidths : gWidths;
4196
+ const usesPrefixWidths = fitWidths !== gWidths;
4197
+ const { fitCount, fittedWidth } = fitSoftHyphenBreak(fitWidths, lineW, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, usesPrefixWidths);
4198
+ if (fitCount === 0) return false;
4199
+ lineW = fittedWidth;
4200
+ lineEndSegmentIndex = segmentIndex;
4201
+ lineEndGraphemeIndex = fitCount;
4202
+ clearPendingBreak();
4203
+ if (fitCount === gWidths.length) {
4204
+ lineEndSegmentIndex = segmentIndex + 1;
4205
+ lineEndGraphemeIndex = 0;
4206
+ return true;
4207
+ }
4208
+ emitCurrentLine(segmentIndex, fitCount, fittedWidth + discretionaryHyphenWidth);
4209
+ appendBreakableSegmentFrom(segmentIndex, fitCount);
4210
+ return true;
4211
+ }
4212
+ function emitEmptyChunk(chunk) {
4213
+ lineCount++;
4214
+ onLine?.({
4215
+ startSegmentIndex: chunk.startSegmentIndex,
4216
+ startGraphemeIndex: 0,
4217
+ endSegmentIndex: chunk.consumedEndSegmentIndex,
4218
+ endGraphemeIndex: 0,
4219
+ width: 0
4220
+ });
4221
+ clearPendingBreak();
4222
+ }
4223
+ for(let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++){
4224
+ const chunk = chunks[chunkIndex];
4225
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex) {
4226
+ emitEmptyChunk(chunk);
4227
+ continue;
4228
+ }
4229
+ hasContent = false;
4230
+ lineW = 0;
4231
+ lineStartSegmentIndex = chunk.startSegmentIndex;
4232
+ lineStartGraphemeIndex = 0;
4233
+ lineEndSegmentIndex = chunk.startSegmentIndex;
4234
+ lineEndGraphemeIndex = 0;
4235
+ clearPendingBreak();
4236
+ let i = chunk.startSegmentIndex;
4237
+ while(i < chunk.endSegmentIndex){
4238
+ const kind = kinds[i];
4239
+ const w = kind === 'tab' ? getTabAdvance(lineW, tabStopAdvance) : widths[i];
4240
+ if (kind === 'soft-hyphen') {
4241
+ if (hasContent) {
4242
+ lineEndSegmentIndex = i + 1;
4243
+ lineEndGraphemeIndex = 0;
4244
+ pendingBreakSegmentIndex = i + 1;
4245
+ pendingBreakFitWidth = lineW + discretionaryHyphenWidth;
4246
+ pendingBreakPaintWidth = lineW + discretionaryHyphenWidth;
4247
+ pendingBreakKind = kind;
4248
+ }
4249
+ i++;
4250
+ continue;
4251
+ }
4252
+ if (!hasContent) {
4253
+ if (w > maxWidth && breakableWidths[i] !== null) {
4254
+ appendBreakableSegment(i);
4255
+ } else {
4256
+ startLineAtSegment(i, w);
4257
+ }
4258
+ updatePendingBreakForWholeSegment(i, w);
4259
+ i++;
4260
+ continue;
4261
+ }
4262
+ const newW = lineW + w;
4263
+ if (newW > maxWidth + lineFitEpsilon) {
4264
+ const currentBreakFitWidth = lineW + (kind === 'tab' ? 0 : lineEndFitAdvances[i]);
4265
+ const currentBreakPaintWidth = lineW + (kind === 'tab' ? w : lineEndPaintAdvances[i]);
4266
+ if (pendingBreakKind === 'soft-hyphen' && engineProfile.preferEarlySoftHyphenBreak && pendingBreakFitWidth <= maxWidth + lineFitEpsilon) {
4267
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
4268
+ continue;
4269
+ }
4270
+ if (pendingBreakKind === 'soft-hyphen' && continueSoftHyphenBreakableSegment(i)) {
4271
+ i++;
4272
+ continue;
4273
+ }
4274
+ if (canBreakAfter(kind) && currentBreakFitWidth <= maxWidth + lineFitEpsilon) {
4275
+ appendWholeSegment(i, w);
4276
+ emitCurrentLine(i + 1, 0, currentBreakPaintWidth);
4277
+ i++;
4278
+ continue;
4279
+ }
4280
+ if (pendingBreakSegmentIndex >= 0 && pendingBreakFitWidth <= maxWidth + lineFitEpsilon) {
4281
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
4282
+ continue;
4283
+ }
4284
+ if (w > maxWidth && breakableWidths[i] !== null) {
4285
+ emitCurrentLine();
4286
+ appendBreakableSegment(i);
4287
+ i++;
4288
+ continue;
4289
+ }
4290
+ emitCurrentLine();
4291
+ continue;
4292
+ }
4293
+ appendWholeSegment(i, w);
4294
+ updatePendingBreakForWholeSegment(i, w);
4295
+ i++;
4296
+ }
4297
+ if (hasContent) {
4298
+ const finalPaintWidth = pendingBreakSegmentIndex === chunk.consumedEndSegmentIndex ? pendingBreakPaintWidth : lineW;
4299
+ emitCurrentLine(chunk.consumedEndSegmentIndex, 0, finalPaintWidth);
4300
+ }
4301
+ }
4302
+ return lineCount;
4303
+ }
4304
+
4305
+ // Text measurement for browser environments using canvas measureText.
4306
+ //
4307
+ // Problem: DOM-based text measurement (getBoundingClientRect, offsetHeight)
4308
+ // forces synchronous layout reflow. When components independently measure text,
4309
+ // each measurement triggers a reflow of the entire document. This creates
4310
+ // read/write interleaving that can cost 30ms+ per frame for 500 text blocks.
4311
+ //
4312
+ // Solution: two-phase measurement centered around canvas measureText.
4313
+ // prepare(text, font) — segments text via Intl.Segmenter, measures each word
4314
+ // via canvas, caches widths, and does one cached DOM calibration read per
4315
+ // font when emoji correction is needed. Call once when text first appears.
4316
+ // layout(prepared, maxWidth, lineHeight) — walks cached word widths with pure
4317
+ // arithmetic to count lines and compute height. Call on every resize.
4318
+ // ~0.0002ms per text.
4319
+ //
4320
+ // i18n: Intl.Segmenter handles CJK (per-character breaking), Thai, Arabic, etc.
4321
+ // Bidi: simplified rich-path metadata for mixed LTR/RTL custom rendering.
4322
+ // Punctuation merging: "better." measured as one unit (matches CSS behavior).
4323
+ // Trailing whitespace: hangs past line edge without triggering breaks (CSS behavior).
4324
+ // overflow-wrap: pre-measured grapheme widths enable character-level word breaking.
4325
+ //
4326
+ // Emoji correction: Chrome/Firefox canvas measures emoji wider than DOM at font
4327
+ // sizes <24px on macOS (Apple Color Emoji). The inflation is constant per emoji
4328
+ // grapheme at a given size, font-independent. Auto-detected by comparing canvas
4329
+ // vs actual DOM emoji width (one cached DOM read per font). Safari canvas and
4330
+ // DOM agree (both wider than fontSize), so correction = 0 there.
4331
+ //
4332
+ // Limitations:
4333
+ // - system-ui font: canvas resolves to different optical variants than DOM on macOS.
4334
+ // Use named fonts (Helvetica, Inter, etc.) for guaranteed accuracy.
4335
+ // See RESEARCH.md "Discovery: system-ui font resolution mismatch".
4336
+ //
4337
+ // Based on Sebastian Markbage's text-layout research (github.com/chenglou/text-layout).
4338
+ let sharedGraphemeSegmenter = null;
4339
+ // Rich-path only. Reuses grapheme splits while materializing multiple lines
4340
+ // from the same prepared handle, without pushing that cache into the API.
4341
+ let sharedLineTextCaches = new WeakMap();
4342
+ function getSharedGraphemeSegmenter() {
4343
+ if (sharedGraphemeSegmenter === null) {
4344
+ sharedGraphemeSegmenter = new Intl.Segmenter(undefined, {
4345
+ granularity: 'grapheme'
4346
+ });
4347
+ }
4348
+ return sharedGraphemeSegmenter;
4349
+ }
4350
+ // --- Public API ---
4351
+ function createEmptyPrepared(includeSegments) {
4352
+ {
4353
+ return {
4354
+ widths: [],
4355
+ lineEndFitAdvances: [],
4356
+ lineEndPaintAdvances: [],
4357
+ kinds: [],
4358
+ simpleLineWalkFastPath: true,
4359
+ segLevels: null,
4360
+ breakableWidths: [],
4361
+ breakablePrefixWidths: [],
4362
+ discretionaryHyphenWidth: 0,
4363
+ tabStopAdvance: 0,
4364
+ chunks: [],
4365
+ segments: []
4366
+ };
4367
+ }
4368
+ }
4369
+ function measureAnalysis(analysis, font, includeSegments) {
4370
+ const graphemeSegmenter = getSharedGraphemeSegmenter();
4371
+ const engineProfile = getEngineProfile();
4372
+ const { cache, emojiCorrection } = getFontMeasurementState(font, textMayContainEmoji(analysis.normalized));
4373
+ const discretionaryHyphenWidth = getCorrectedSegmentWidth('-', getSegmentMetrics('-', cache), emojiCorrection);
4374
+ const spaceWidth = getCorrectedSegmentWidth(' ', getSegmentMetrics(' ', cache), emojiCorrection);
4375
+ const tabStopAdvance = spaceWidth * 8;
4376
+ if (analysis.len === 0) return createEmptyPrepared();
4377
+ const widths = [];
4378
+ const lineEndFitAdvances = [];
4379
+ const lineEndPaintAdvances = [];
4380
+ const kinds = [];
4381
+ let simpleLineWalkFastPath = analysis.chunks.length <= 1;
4382
+ const segStarts = [] ;
4383
+ const breakableWidths = [];
4384
+ const breakablePrefixWidths = [];
4385
+ const segments = includeSegments ? [] : null;
4386
+ const preparedStartByAnalysisIndex = Array.from({
4387
+ length: analysis.len
4388
+ });
4389
+ const preparedEndByAnalysisIndex = Array.from({
4390
+ length: analysis.len
4391
+ });
4392
+ function pushMeasuredSegment(text, width, lineEndFitAdvance, lineEndPaintAdvance, kind, start, breakable, breakablePrefix) {
4393
+ if (kind !== 'text' && kind !== 'space' && kind !== 'zero-width-break') {
4394
+ simpleLineWalkFastPath = false;
4395
+ }
4396
+ widths.push(width);
4397
+ lineEndFitAdvances.push(lineEndFitAdvance);
4398
+ lineEndPaintAdvances.push(lineEndPaintAdvance);
4399
+ kinds.push(kind);
4400
+ segStarts?.push(start);
4401
+ breakableWidths.push(breakable);
4402
+ breakablePrefixWidths.push(breakablePrefix);
4403
+ if (segments !== null) segments.push(text);
4404
+ }
4405
+ for(let mi = 0; mi < analysis.len; mi++){
4406
+ preparedStartByAnalysisIndex[mi] = widths.length;
4407
+ const segText = analysis.texts[mi];
4408
+ const segWordLike = analysis.isWordLike[mi];
4409
+ const segKind = analysis.kinds[mi];
4410
+ const segStart = analysis.starts[mi];
4411
+ if (segKind === 'soft-hyphen') {
4412
+ pushMeasuredSegment(segText, 0, discretionaryHyphenWidth, discretionaryHyphenWidth, segKind, segStart, null, null);
4413
+ preparedEndByAnalysisIndex[mi] = widths.length;
4414
+ continue;
4415
+ }
4416
+ if (segKind === 'hard-break') {
4417
+ pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null, null);
4418
+ preparedEndByAnalysisIndex[mi] = widths.length;
4419
+ continue;
4420
+ }
4421
+ if (segKind === 'tab') {
4422
+ pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null, null);
4423
+ preparedEndByAnalysisIndex[mi] = widths.length;
4424
+ continue;
4425
+ }
4426
+ const segMetrics = getSegmentMetrics(segText, cache);
4427
+ if (segKind === 'text' && segMetrics.containsCJK) {
4428
+ let unitText = '';
4429
+ let unitStart = 0;
4430
+ for (const gs of graphemeSegmenter.segment(segText)){
4431
+ const grapheme = gs.segment;
4432
+ if (unitText.length === 0) {
4433
+ unitText = grapheme;
4434
+ unitStart = gs.index;
4435
+ continue;
4436
+ }
4437
+ if (kinsokuEnd.has(unitText) || kinsokuStart.has(grapheme) || leftStickyPunctuation.has(grapheme) || engineProfile.carryCJKAfterClosingQuote && isCJK(grapheme) && endsWithClosingQuote(unitText)) {
4438
+ unitText += grapheme;
4439
+ continue;
4440
+ }
4441
+ const unitMetrics = getSegmentMetrics(unitText, cache);
4442
+ const w = getCorrectedSegmentWidth(unitText, unitMetrics, emojiCorrection);
4443
+ pushMeasuredSegment(unitText, w, w, w, 'text', segStart + unitStart, null, null);
4444
+ unitText = grapheme;
4445
+ unitStart = gs.index;
4446
+ }
4447
+ if (unitText.length > 0) {
4448
+ const unitMetrics = getSegmentMetrics(unitText, cache);
4449
+ const w = getCorrectedSegmentWidth(unitText, unitMetrics, emojiCorrection);
4450
+ pushMeasuredSegment(unitText, w, w, w, 'text', segStart + unitStart, null, null);
4451
+ }
4452
+ preparedEndByAnalysisIndex[mi] = widths.length;
4453
+ continue;
4454
+ }
4455
+ const w = getCorrectedSegmentWidth(segText, segMetrics, emojiCorrection);
4456
+ const lineEndFitAdvance = segKind === 'space' || segKind === 'preserved-space' || segKind === 'zero-width-break' ? 0 : w;
4457
+ const lineEndPaintAdvance = segKind === 'space' || segKind === 'zero-width-break' ? 0 : w;
4458
+ if (segWordLike && segText.length > 1) {
4459
+ const graphemeWidths = getSegmentGraphemeWidths(segText, segMetrics, cache, emojiCorrection);
4460
+ const graphemePrefixWidths = engineProfile.preferPrefixWidthsForBreakableRuns ? getSegmentGraphemePrefixWidths(segText, segMetrics, cache, emojiCorrection) : null;
4461
+ pushMeasuredSegment(segText, w, lineEndFitAdvance, lineEndPaintAdvance, segKind, segStart, graphemeWidths, graphemePrefixWidths);
4462
+ } else {
4463
+ pushMeasuredSegment(segText, w, lineEndFitAdvance, lineEndPaintAdvance, segKind, segStart, null, null);
4464
+ }
4465
+ preparedEndByAnalysisIndex[mi] = widths.length;
4466
+ }
4467
+ const chunks = mapAnalysisChunksToPreparedChunks(analysis.chunks, preparedStartByAnalysisIndex, preparedEndByAnalysisIndex);
4468
+ const segLevels = segStarts === null ? null : computeSegmentLevels(analysis.normalized, segStarts);
4469
+ if (segments !== null) {
4470
+ return {
4471
+ widths,
4472
+ lineEndFitAdvances,
4473
+ lineEndPaintAdvances,
4474
+ kinds,
4475
+ simpleLineWalkFastPath,
4476
+ segLevels,
4477
+ breakableWidths,
4478
+ breakablePrefixWidths,
4479
+ discretionaryHyphenWidth,
4480
+ tabStopAdvance,
4481
+ chunks,
4482
+ segments
4483
+ };
4484
+ }
4485
+ return {
4486
+ widths,
4487
+ lineEndFitAdvances,
4488
+ lineEndPaintAdvances,
4489
+ kinds,
4490
+ simpleLineWalkFastPath,
4491
+ segLevels,
4492
+ breakableWidths,
4493
+ breakablePrefixWidths,
4494
+ discretionaryHyphenWidth,
4495
+ tabStopAdvance,
4496
+ chunks
4497
+ };
4498
+ }
4499
+ function mapAnalysisChunksToPreparedChunks(chunks, preparedStartByAnalysisIndex, preparedEndByAnalysisIndex) {
4500
+ const preparedChunks = [];
4501
+ for(let i = 0; i < chunks.length; i++){
4502
+ const chunk = chunks[i];
4503
+ const startSegmentIndex = chunk.startSegmentIndex < preparedStartByAnalysisIndex.length ? preparedStartByAnalysisIndex[chunk.startSegmentIndex] : preparedEndByAnalysisIndex[preparedEndByAnalysisIndex.length - 1] ?? 0;
4504
+ const endSegmentIndex = chunk.endSegmentIndex < preparedStartByAnalysisIndex.length ? preparedStartByAnalysisIndex[chunk.endSegmentIndex] : preparedEndByAnalysisIndex[preparedEndByAnalysisIndex.length - 1] ?? 0;
4505
+ const consumedEndSegmentIndex = chunk.consumedEndSegmentIndex < preparedStartByAnalysisIndex.length ? preparedStartByAnalysisIndex[chunk.consumedEndSegmentIndex] : preparedEndByAnalysisIndex[preparedEndByAnalysisIndex.length - 1] ?? 0;
4506
+ preparedChunks.push({
4507
+ startSegmentIndex,
4508
+ endSegmentIndex,
4509
+ consumedEndSegmentIndex
4510
+ });
4511
+ }
4512
+ return preparedChunks;
4513
+ }
4514
+ function prepareInternal(text, font, includeSegments, options) {
4515
+ const analysis = analyzeText(text, getEngineProfile(), options?.whiteSpace);
4516
+ return measureAnalysis(analysis, font, includeSegments);
4517
+ }
4518
+ // Rich variant used by callers that need enough information to render the
4519
+ // laid-out lines themselves.
4520
+ function prepareWithSegments(text, font, options) {
4521
+ return prepareInternal(text, font, true, options);
4522
+ }
4523
+ function getInternalPrepared(prepared) {
4524
+ return prepared;
4525
+ }
4526
+ function getSegmentGraphemes(segmentIndex, segments, cache) {
4527
+ let graphemes = cache.get(segmentIndex);
4528
+ if (graphemes !== undefined) return graphemes;
4529
+ graphemes = [];
4530
+ const graphemeSegmenter = getSharedGraphemeSegmenter();
4531
+ for (const gs of graphemeSegmenter.segment(segments[segmentIndex])){
4532
+ graphemes.push(gs.segment);
4533
+ }
4534
+ cache.set(segmentIndex, graphemes);
4535
+ return graphemes;
4536
+ }
4537
+ function getLineTextCache(prepared) {
4538
+ let cache = sharedLineTextCaches.get(prepared);
4539
+ if (cache !== undefined) return cache;
4540
+ cache = new Map();
4541
+ sharedLineTextCaches.set(prepared, cache);
4542
+ return cache;
4543
+ }
4544
+ function lineHasDiscretionaryHyphen(kinds, startSegmentIndex, startGraphemeIndex, endSegmentIndex) {
4545
+ return endSegmentIndex > 0 && kinds[endSegmentIndex - 1] === 'soft-hyphen' && !(startSegmentIndex === endSegmentIndex && startGraphemeIndex > 0);
4546
+ }
4547
+ function buildLineTextFromRange(segments, kinds, cache, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
4548
+ let text = '';
4549
+ const endsWithDiscretionaryHyphen = lineHasDiscretionaryHyphen(kinds, startSegmentIndex, startGraphemeIndex, endSegmentIndex);
4550
+ for(let i = startSegmentIndex; i < endSegmentIndex; i++){
4551
+ if (kinds[i] === 'soft-hyphen' || kinds[i] === 'hard-break') continue;
4552
+ if (i === startSegmentIndex && startGraphemeIndex > 0) {
4553
+ text += getSegmentGraphemes(i, segments, cache).slice(startGraphemeIndex).join('');
4554
+ } else {
4555
+ text += segments[i];
4556
+ }
4557
+ }
4558
+ if (endGraphemeIndex > 0) {
4559
+ if (endsWithDiscretionaryHyphen) text += '-';
4560
+ text += getSegmentGraphemes(endSegmentIndex, segments, cache).slice(startSegmentIndex === endSegmentIndex ? startGraphemeIndex : 0, endGraphemeIndex).join('');
4561
+ } else if (endsWithDiscretionaryHyphen) {
4562
+ text += '-';
4563
+ }
4564
+ return text;
4565
+ }
4566
+ function createLayoutLine(prepared, cache, width, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
4567
+ return {
4568
+ text: buildLineTextFromRange(prepared.segments, prepared.kinds, cache, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex),
4569
+ width,
4570
+ start: {
4571
+ segmentIndex: startSegmentIndex,
4572
+ graphemeIndex: startGraphemeIndex
4573
+ },
4574
+ end: {
4575
+ segmentIndex: endSegmentIndex,
4576
+ graphemeIndex: endGraphemeIndex
4577
+ }
4578
+ };
4579
+ }
4580
+ function materializeLayoutLine(prepared, cache, line) {
4581
+ return createLayoutLine(prepared, cache, line.width, line.startSegmentIndex, line.startGraphemeIndex, line.endSegmentIndex, line.endGraphemeIndex);
4582
+ }
4583
+ // Rich layout API for callers that want the actual line contents and widths.
4584
+ // Caller still supplies lineHeight at layout time. Mirrors layout()'s break
4585
+ // decisions, but keeps extra per-line bookkeeping so it should stay off the
4586
+ // resize hot path.
4587
+ function layoutWithLines(prepared, maxWidth, lineHeight) {
4588
+ const lines = [];
4589
+ if (prepared.widths.length === 0) return {
4590
+ lineCount: 0,
4591
+ height: 0,
4592
+ lines
4593
+ };
4594
+ const graphemeCache = getLineTextCache(prepared);
4595
+ const lineCount = walkPreparedLines(getInternalPrepared(prepared), maxWidth, (line)=>{
4596
+ lines.push(materializeLayoutLine(prepared, graphemeCache, line));
4597
+ });
4598
+ return {
4599
+ lineCount,
4600
+ height: lineCount * lineHeight,
4601
+ lines
4602
+ };
4603
+ }
4604
+
2487
4605
  /**
2488
4606
  * Strips HTML and "un-escapes" escape characters.
2489
4607
  * @param {String} input
@@ -2493,44 +4611,37 @@
2493
4611
  return doc.documentElement ? doc.documentElement.textContent : input;
2494
4612
  }
2495
4613
  /**
2496
- @function textWidth
2497
- @desc Given a text string, returns the predicted pixel width of the string when placed into DOM.
2498
- @param {String|Array} text Can be either a single string or an array of strings to analyze.
2499
- @param {Object} [style] An object of CSS font styles to apply. Accepts any of the valid [CSS font property](http://www.w3schools.com/cssref/pr_font_font.asp) values.
2500
- */ function textWidth(text, style) {
2501
- style = Object.assign({
2502
- "font-size": 10,
2503
- "font-family": "sans-serif",
2504
- "font-style": "normal",
2505
- "font-weight": 400,
2506
- "font-variant": "normal"
2507
- }, style);
2508
- const context = document.createElement("canvas").getContext("2d");
2509
- const font = [];
2510
- font.push(style["font-style"]);
2511
- font.push(style["font-variant"]);
2512
- font.push(style["font-weight"]);
2513
- font.push(typeof style["font-size"] === "string" ? style["font-size"] : `${style["font-size"]}px`);
2514
- font.push(style["font-family"]);
2515
- context.font = font.join(" ");
2516
- if (text instanceof Array) return text.map((t)=>context.measureText(htmlDecode(t)).width);
2517
- return context.measureText(htmlDecode(text)).width;
4614
+ * Builds a CSS font shorthand string from a style object.
4615
+ * @param {Object} styleObj
4616
+ */ function buildFont(styleObj) {
4617
+ const style = styleObj["font-style"] || "normal";
4618
+ const variant = styleObj["font-variant"] || "normal";
4619
+ const weight = styleObj["font-weight"] || 400;
4620
+ const size = typeof styleObj["font-size"] === "string" ? styleObj["font-size"] : `${styleObj["font-size"] || 10}px`;
4621
+ const family = styleObj["font-family"] || "sans-serif";
4622
+ return `${style} ${variant} ${weight} ${size} ${family}`;
2518
4623
  }
2519
-
2520
4624
  /**
2521
- @function trim
2522
- @desc Cross-browser implementation of [trim](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim).
2523
- @param {String} str
2524
- */ function trim(str) {
2525
- return str.toString().replace(/^\s+|\s+$/g, "");
4625
+ * Measures the width of a single text string using pretext.
4626
+ * @param {String} text
4627
+ * @param {String} font CSS font shorthand
4628
+ */ function measureWidth(text, font) {
4629
+ if (!text) return 0;
4630
+ const prepared = prepareWithSegments(text, font);
4631
+ const result = layoutWithLines(prepared, Infinity, 20);
4632
+ return result.lines.length ? result.lines[0].width : 0;
4633
+ }
4634
+ function textWidth(text, style = {}) {
4635
+ const font = buildFont(style);
4636
+ if (text instanceof Array) return text.map((t)=>measureWidth(htmlDecode(t), font));
4637
+ return measureWidth(htmlDecode(text), font);
2526
4638
  }
2527
4639
 
2528
4640
  const alpha = "abcdefghiABCDEFGHI_!@#$%^&*()_+1234567890", checked = {}, height = 32;
2529
4641
  let dejavu, macos, monospace, proportional;
2530
4642
  /**
2531
- @function fontExists
2532
- @desc Given either a single font-family or a list of fonts, returns the name of the first font that can be rendered, or `false` if none are installed on the user's machine.
2533
- @param {String|Array} font Can be either a valid CSS font-family string (single or comma-separated names) or an Array of string names.
4643
+ Given either a single font-family or a list of fonts, returns the name of the first font that can be rendered, or `false` if none are installed on the user's machine.
4644
+ @param font Can be either a valid CSS font-family string (single or comma-separated names) or an Array of string names.
2534
4645
  */ const fontExists = (font)=>{
2535
4646
  if (!dejavu) {
2536
4647
  dejavu = textWidth(alpha, {
@@ -2551,7 +4662,7 @@
2551
4662
  });
2552
4663
  }
2553
4664
  if (!(font instanceof Array)) font = font.split(",");
2554
- font = font.map((f)=>trim(f));
4665
+ font = font.map((f)=>f.trim());
2555
4666
  for(let i = 0; i < font.length; i++){
2556
4667
  const fam = font[i];
2557
4668
  if (checked[fam] || [
@@ -2575,7 +4686,7 @@
2575
4686
  };
2576
4687
 
2577
4688
  /**
2578
- @desc Given an HTMLElement and a "width" or "height" string, this function returns the current calculated size for the DOM element.
4689
+ Given an HTMLElement and a "width" or "height" string, this function returns the current calculated size for the DOM element.
2579
4690
  @private
2580
4691
  */ function _elementSize(element, s) {
2581
4692
  if (!element) return undefined;
@@ -2586,32 +4697,37 @@
2586
4697
  let val = window[`inner${s.charAt(0).toUpperCase() + s.slice(1)}`];
2587
4698
  const elem = select(element);
2588
4699
  if (s === "width") {
2589
- val -= parseFloat(elem.style("margin-left"), 10);
2590
- val -= parseFloat(elem.style("margin-right"), 10);
2591
- val -= parseFloat(elem.style("padding-left"), 10);
2592
- val -= parseFloat(elem.style("padding-right"), 10);
4700
+ val -= parseFloat(elem.style("margin-left"));
4701
+ val -= parseFloat(elem.style("margin-right"));
4702
+ val -= parseFloat(elem.style("padding-left"));
4703
+ val -= parseFloat(elem.style("padding-right"));
2593
4704
  } else {
2594
- val -= parseFloat(elem.style("margin-top"), 10);
2595
- val -= parseFloat(elem.style("margin-bottom"), 10);
2596
- val -= parseFloat(elem.style("padding-top"), 10);
2597
- val -= parseFloat(elem.style("padding-bottom"), 10);
4705
+ val -= parseFloat(elem.style("margin-top"));
4706
+ val -= parseFloat(elem.style("margin-bottom"));
4707
+ val -= parseFloat(elem.style("padding-top"));
4708
+ val -= parseFloat(elem.style("padding-bottom"));
2598
4709
  }
2599
4710
  return val;
2600
4711
  } else {
2601
- let val = parseFloat(select(element).style(s), 10);
4712
+ let val = element.getBoundingClientRect()[s];
2602
4713
  if (typeof val === "number" && val > 0) {
2603
4714
  if (s === "height") {
2604
- val -= parseFloat(select(element).style("padding-top"), 10);
2605
- val -= parseFloat(select(element).style("padding-bottom"), 10);
4715
+ val -= parseFloat(select(element).style("padding-top"));
4716
+ val -= parseFloat(select(element).style("padding-bottom"));
4717
+ val -= parseFloat(select(element).style("border-top"));
4718
+ val -= parseFloat(select(element).style("border-bottom"));
4719
+ } else {
4720
+ val -= parseFloat(select(element).style("padding-left"));
4721
+ val -= parseFloat(select(element).style("padding-right"));
4722
+ val -= parseFloat(select(element).style("border-left"));
4723
+ val -= parseFloat(select(element).style("border-right"));
2606
4724
  }
2607
4725
  return val;
2608
4726
  } else return _elementSize(element.parentNode, s);
2609
4727
  }
2610
4728
  }
2611
4729
  /**
2612
- @function getSize
2613
- @desc Finds the available width and height for a specified HTMLElement, traversing it's parents until it finds something with constrained dimensions. Falls back to the inner dimensions of the browser window if none is found.
2614
- @param {HTMLElement} elem The HTMLElement to find dimensions for.
4730
+ Finds the available width and height for a specified HTMLElement, traversing it's parents until it finds something with constrained dimensions. Falls back to the inner dimensions of the browser window if none is found.
2615
4731
  @private
2616
4732
  */ function getSize(elem) {
2617
4733
  return [
@@ -2621,27 +4737,24 @@
2621
4737
  }
2622
4738
 
2623
4739
  /**
2624
- @module inViewport
2625
- @desc Returns a *Boolean* denoting whether or not a given DOM element is visible in the current window.
2626
- @param {DOMElement} elem The DOM element to analyze.
2627
- @param {Number} [buffer = 0] A pixel offset from the edge of the top and bottom of the screen. If a positive value, the element will be deemed visible when it is that many pixels away from entering the viewport. If negative, the element will have to enter the viewport by that many pixels before being deemed visible.
2628
- @private
4740
+ Determines whether a given DOM element is visible within the current viewport, with an optional pixel buffer.
4741
+ @param elem The DOM element to check.
4742
+ @param buffer Extra pixel margin around the viewport boundary.
2629
4743
  */ function inViewport(elem, buffer = 0) {
2630
- const pageX = window.pageXOffset !== undefined ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
2631
- const pageY = window.pageYOffset !== undefined ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
4744
+ const pageX = window.scrollX;
4745
+ const pageY = window.scrollY;
2632
4746
  const bounds = elem.getBoundingClientRect();
2633
4747
  const height = bounds.height, left = bounds.left + pageX, top = bounds.top + pageY, width = bounds.width;
2634
4748
  return pageY + window.innerHeight > top + buffer && pageY + buffer < top + height && pageX + window.innerWidth > left + buffer && pageX + buffer < left + width;
2635
4749
  }
2636
4750
 
2637
4751
  /**
2638
- @function parseSides
2639
- @desc Converts a string of directional CSS shorthand values into an object with the values expanded.
2640
- @param {String|Number} sides The CSS shorthand string to expand.
4752
+ Converts a string of directional CSS shorthand values into an object with the values expanded.
4753
+ @param sides The CSS shorthand string to expand.
2641
4754
  */ function parseSides(sides) {
2642
4755
  let values;
2643
4756
  if (typeof sides === "number") values = [
2644
- sides
4757
+ `${sides}`
2645
4758
  ];
2646
4759
  else values = sides.split(/\s+/);
2647
4760
  if (values.length === 1) values = [
@@ -2665,32 +4778,22 @@
2665
4778
  }
2666
4779
 
2667
4780
  /**
2668
- @function prefix
2669
- @desc Returns the appropriate CSS vendor prefix, given the current browser.
2670
- */ function prefix() {
2671
- if ("-webkit-transform" in document.body.style) return "-webkit-";
2672
- else if ("-moz-transform" in document.body.style) return "-moz-";
2673
- else if ("-ms-transform" in document.body.style) return "-ms-";
2674
- else if ("-o-transform" in document.body.style) return "-o-";
2675
- else return "";
4781
+ Returns `true` if the HTML or body element has either the "dir" HTML attribute or the "direction" CSS property set to "rtl".
4782
+ */ function rtl() {
4783
+ return document.documentElement.dir === "rtl" || document.body.dir === "rtl" || getComputedStyle(document.documentElement).direction === "rtl" || getComputedStyle(document.body).direction === "rtl";
2676
4784
  }
2677
4785
 
2678
4786
  /**
2679
- @function rtl
2680
- @desc Returns `true` if the HTML or body element has either the "dir" HTML attribute or the "direction" CSS property set to "rtl".
2681
- */ var rtl = (()=>select("html").attr("dir") === "rtl" || select("body").attr("dir") === "rtl" || select("html").style("direction") === "rtl" || select("body").style("direction") === "rtl");
2682
-
2683
- /**
2684
- @function stylize
2685
- @desc Applies each key/value in an object as a style.
2686
- @param {D3selection} elem The D3 element to apply the styles to.
2687
- @param {Object} styles An object of key/value style pairs.
4787
+ Applies each key/value in an object as a style.
4788
+ @param e The d3 selection to apply styles to.
4789
+ @param s An object of key/value style pairs.
2688
4790
  */ function stylize(e, s = {}) {
2689
4791
  for(const k in s)if (({}).hasOwnProperty.call(s, k)) e.style(k, s[k]);
2690
4792
  }
2691
4793
 
2692
4794
  exports.assign = assign;
2693
4795
  exports.attrize = attrize;
4796
+ exports.backgroundColor = backgroundColor;
2694
4797
  exports.date = date;
2695
4798
  exports.elem = elem;
2696
4799
  exports.fontExists = fontExists;
@@ -2698,7 +4801,6 @@
2698
4801
  exports.inViewport = inViewport;
2699
4802
  exports.isObject = isObject;
2700
4803
  exports.parseSides = parseSides;
2701
- exports.prefix = prefix;
2702
4804
  exports.rtl = rtl;
2703
4805
  exports.stylize = stylize;
2704
4806
  exports.textWidth = textWidth;