@d3plus/dom 3.0.16 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -77
- package/es/index.js +1 -1
- package/es/src/D3Selection.js +4 -0
- package/es/src/assign.js +6 -8
- package/es/src/attrize.js +3 -4
- package/es/src/backgroundColor.js +14 -0
- package/es/src/date.js +4 -6
- package/es/src/elem.js +11 -18
- package/es/src/fontExists.js +5 -6
- package/es/src/getSize.js +20 -15
- package/es/src/inViewport.js +5 -7
- package/es/src/isObject.js +6 -6
- package/es/src/parseSides.js +3 -4
- package/es/src/rtl.js +3 -5
- package/es/src/stylize.js +3 -4
- package/es/src/textWidth.js +32 -27
- package/package.json +18 -9
- package/types/index.d.ts +14 -0
- package/types/src/D3Selection.d.ts +28 -0
- package/types/src/assign.d.ts +11 -0
- package/types/src/attrize.d.ts +7 -0
- package/types/src/backgroundColor.d.ts +7 -0
- package/types/src/date.d.ts +5 -0
- package/types/src/elem.d.ts +18 -0
- package/types/src/fontExists.d.ts +6 -0
- package/types/src/getSize.d.ts +5 -0
- package/types/src/inViewport.d.ts +6 -0
- package/types/src/isObject.d.ts +5 -0
- package/types/src/parseSides.d.ts +11 -0
- package/types/src/prefix.d.ts +5 -0
- package/types/src/rtl.d.ts +4 -0
- package/types/src/stylize.d.ts +7 -0
- package/types/src/textWidth.d.ts +7 -0
- package/umd/d3plus-dom.full.js +2321 -219
- package/umd/d3plus-dom.full.js.map +1 -1
- package/umd/d3plus-dom.full.min.js +185 -133
- package/umd/d3plus-dom.js +107 -217
- package/umd/d3plus-dom.js.map +1 -1
- package/umd/d3plus-dom.min.js +75 -75
- package/es/src/prefix.js +0 -10
package/umd/d3plus-dom.full.js
CHANGED
|
@@ -4,140 +4,33 @@
|
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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
|
-
|
|
118
|
-
@
|
|
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
|
-
|
|
126
|
-
@
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@param
|
|
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
|
-
|
|
169
|
-
@
|
|
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})
|
|
211
|
-
const monthSuffix = new RegExp(/^(-*\d{1,4})
|
|
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
|
-
|
|
2453
|
-
@
|
|
2454
|
-
@param
|
|
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
|
-
|
|
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(
|
|
2473
|
-
const elem =
|
|
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,
|
|
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 (
|
|
2480
|
-
else elem.exit().call(attrize,
|
|
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 (
|
|
2483
|
-
else update.call(attrize,
|
|
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
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
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
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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
|
-
|
|
2532
|
-
@
|
|
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(
|
|
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
|
-
|
|
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")
|
|
2590
|
-
val -= parseFloat(elem.style("margin-right")
|
|
2591
|
-
val -= parseFloat(elem.style("padding-left")
|
|
2592
|
-
val -= parseFloat(elem.style("padding-right")
|
|
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")
|
|
2595
|
-
val -= parseFloat(elem.style("margin-bottom")
|
|
2596
|
-
val -= parseFloat(elem.style("padding-top")
|
|
2597
|
-
val -= parseFloat(elem.style("padding-bottom")
|
|
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 =
|
|
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")
|
|
2605
|
-
val -= parseFloat(select(element).style("padding-bottom")
|
|
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
|
-
|
|
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
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
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.
|
|
2631
|
-
const pageY = window.
|
|
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
|
-
|
|
2639
|
-
@
|
|
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
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
-
|
|
2680
|
-
@
|
|
2681
|
-
|
|
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;
|