rapier-markdown-kit 1.1.101 → 1.1.104

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.
Files changed (60) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +213 -53
  3. package/dist/agent/vendor/hyphen-en-us/LICENSE +14 -0
  4. package/dist/agent/vendor/hyphen-en-us/SOURCE.json +33 -0
  5. package/dist/agent/vendor/hyphen-en-us/hyph-en-us.hyp.txt +14 -0
  6. package/dist/agent/vendor/hyphen-en-us/hyph-en-us.pat.txt +4938 -0
  7. package/dist/agent/vendor/hyphen-en-us/patterns.js +25 -0
  8. package/dist/agent/vendor/pretext/LICENSE +208 -0
  9. package/dist/agent/vendor/pretext/NOTICE +26 -13
  10. package/dist/agent/vendor/pretext/SOURCE.json +213 -45
  11. package/dist/agent/vendor/pretext/analysis.js +675 -1224
  12. package/dist/agent/vendor/pretext/entry-geometry.js +183 -0
  13. package/dist/agent/vendor/pretext/gecko-line-breaks.js +688 -0
  14. package/dist/agent/vendor/pretext/generated/engine-break-data.js +57 -0
  15. package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +80 -0
  16. package/dist/agent/vendor/pretext/graphemes.js +98 -0
  17. package/dist/agent/vendor/pretext/han-kerning.js +253 -0
  18. package/dist/agent/vendor/pretext/layout.js +352 -290
  19. package/dist/agent/vendor/pretext/line-break.js +1154 -588
  20. package/dist/agent/vendor/pretext/line-breaks.js +892 -0
  21. package/dist/agent/vendor/pretext/line-text.js +81 -39
  22. package/dist/agent/vendor/pretext/measurement.js +1105 -176
  23. package/dist/agent/vendor/pretext/prepare.js +807 -0
  24. package/dist/agent/vendor/pretext/rich-inline.js +1242 -273
  25. package/dist/agent/will.mjs +2 -0
  26. package/dist/kit/assets.mjs +5 -1
  27. package/dist/kit/conformance/run.mjs +4 -4
  28. package/dist/kit/export-fonts.mjs +26 -11
  29. package/dist/kit/index.mjs +1 -0
  30. package/dist/kit/ledger/carried.mjs +9 -7
  31. package/dist/kit/ledger/data.mjs +73 -13
  32. package/dist/kit/ledger/format.mjs +166 -53
  33. package/dist/kit/ledger/hash.mjs +75 -18
  34. package/dist/kit/ledger/history.mjs +143 -115
  35. package/dist/kit/ledger/inverse-source.mjs +158 -19
  36. package/dist/kit/ledger/journal-records.mjs +292 -67
  37. package/dist/kit/model.mjs +1 -1
  38. package/dist/kit/pdf-glyphs.mjs +4 -4
  39. package/dist/kit/render-markdown.mjs +259 -122
  40. package/dist/kit/render-sanitize.mjs +24 -4
  41. package/dist/kit/render-work.mjs +99 -20
  42. package/dist/kit/render.mjs +101 -65
  43. package/dist/kit/typography.mjs +2 -0
  44. package/dist/layout/hyphenation.mjs +127 -0
  45. package/dist/layout/line-plan.mjs +88 -96
  46. package/dist/layout/model.mjs +61 -80
  47. package/dist/layout/paragraph-fit.mjs +191 -0
  48. package/dist/layout/preparation.mjs +373 -0
  49. package/dist/layout/prepared-paragraph.mjs +223 -0
  50. package/dist/layout/typography.mjs +345 -111
  51. package/dist/packages/rapier-diff/index.mjs +288 -196
  52. package/dist/spec/document-fonts.mjs +13 -4
  53. package/dist/spec/md-assets.mjs +149 -30
  54. package/dist/spec/md-layout.mjs +5 -2
  55. package/dist/spec/md-marks.mjs +218 -3
  56. package/dist/spec/md-source.mjs +122 -0
  57. package/package.json +2 -1
  58. package/style.css +9 -0
  59. package/dist/agent/vendor/pretext/bidi.js +0 -151
  60. package/dist/agent/vendor/pretext/generated/bidi-data.js +0 -3831
@@ -0,0 +1,892 @@
1
+ /*! Pretext Unreleased (bac1774c0) | MIT; BSD/Unicode/ICU notices in LICENSE | Copyright (c) 2026 Pretext contributors */
2
+ // Break opportunities as Chrome and Safari find them: ports of Blink's and WebKit's
3
+ // line-break scans over their own pair tables and ICU line rules, which
4
+ // scripts/generate-engine-break-data.ts writes to src/generated/engine-break-data.ts.
5
+ // The tables' bundle cost is accepted (RESEARCH.md, Decisions Log, 2026-09-23).
6
+ //
7
+ // Sources, cited as file:line:
8
+ // - ICU 78.2 as vendored in Chromium 152, under third_party/icu/source/common. Chrome
9
+ // 153 runs the same code. apple-rbbi.cpp and apple-brkiter.cpp are Apple's
10
+ // ICU-76142.5.1.200 sources, behind libicucore 78.1 on macOS 26.5.2 and macOS 27.
11
+ // - Blink in Chromium 152, under third_party/blink/renderer/platform/text/:
12
+ // tbi.cc = text_break_iterator.cc, tbi.h = text_break_iterator.h,
13
+ // tbi_icu.cc = text_break_iterator_icu.cc, gen.cc = character_property_data_generator.cc.
14
+ // - WebKit safari-7625.1.29.11-branch (Safari 27.0), under Source/. Only Safari 27's rules
15
+ // are ported, not Safari 26's (RESEARCH.md, Decisions Log, 2026-09-16):
16
+ // BP.h = WebCore/rendering/BreakablePositions.h,
17
+ // IIB = WebCore/layout/formattingContexts/inline/InlineItemsBuilder.cpp,
18
+ // IFU = WebCore/layout/formattingContexts/inline/InlineFormattingUtils.cpp,
19
+ // TU = WebCore/layout/formattingContexts/inline/text/TextUtil.cpp,
20
+ // TBI.h = WTF/wtf/text/TextBreakIterator.h, TBIICU.h = WTF/wtf/text/icu/TextBreakIteratorICU.h.
21
+ //
22
+ // Deliberate differences:
23
+ // - ICU runs forward from the start of the text, without its boundary cache, reverse
24
+ // table or dictionary engines. Inside runs of Thai, Lao, Khmer and Myanmar letters,
25
+ // Intl.Segmenter word boundaries stand in for the dictionaries.
26
+ // - Blink restarts ICU at each line start, which drops the context before the line
27
+ // (tbi.h:159-163, tbi_icu.cc:771-810). These scans read each text once.
28
+ // - Chrome opens line_normal_cj.brk for zh content, and for content without a language
29
+ // under a Chinese UI. The scan takes preparation's language, which on a page without one
30
+ // is the language V8 shows as its default locale, Chrome's UI language (getBlinkLineBreaks).
31
+ // Content-Language headers and an element's own lang aren't read.
32
+ // - WebKit splits items where bidi levels change (IIB:637-775), and swaps a Han-script
33
+ // locale for the user's first Chinese language (FontDescription.cpp:74-83, 107-113).
34
+ // Pretext resolves no bidi levels here and takes preparation's language as it is.
35
+
36
+ import {
37
+ appleQuoteRemaps,
38
+ blinkLinePairsPacked,
39
+ classMaps,
40
+ classRemapsPacked,
41
+ classRunsVarints,
42
+ jointClassCount,
43
+ ruleTables,
44
+ webkitLinePairsPacked,
45
+
46
+
47
+ } from './generated/engine-break-data.js'
48
+
49
+ // What a scan marks at a position of its text, as bits. A line may start at a BREAK. The WebKit
50
+ // scan marks a FORCED_BREAK after a U+2028 or U+2029 that starts an item; the Gecko scan marks a
51
+ // CLUSTER_START where a cluster starts without a break, and a SOFT_HYPHEN_BREAK with the BREAK
52
+ // right after a soft hyphen. A rich-inline paragraph's analysis marks an ITEM_START where one of
53
+ // its items starts, which starts a segment whether or not a line may start there.
54
+ export const BREAK = 1
55
+ export const CLUSTER_START = 2
56
+ export const FORCED_BREAK = 4
57
+ export const SOFT_HYPHEN_BREAK = 8
58
+ export const ITEM_START = 16
59
+
60
+ // Page languages whose line-break rules differ in some engine. Every other
61
+ // language, an empty or missing one, and no document read as root.
62
+
63
+
64
+ // The primary language subtag, ASCII case-insensitively, up to `-`, `_` or the
65
+ // end. No allocation: preparation calls this once per text.
66
+ export function getBreakLanguage(tag ) {
67
+ if (tag === null || tag.length < 2) return 'root'
68
+ if (tag.length > 2 && tag.charCodeAt(2) !== 0x2D && tag.charCodeAt(2) !== 0x5F) return 'root'
69
+ const first = tag.charCodeAt(0) | 0x20
70
+ const second = tag.charCodeAt(1) | 0x20
71
+ if (first === 0x6A && second === 0x61) return 'ja'
72
+ if (first === 0x6B && second === 0x6F) return 'ko'
73
+ if (first === 0x7A && second === 0x68) return 'zh'
74
+ return 'root'
75
+ }
76
+
77
+ // The generated tables of bytes ship packed, in base64: the unpacked length, then runs of literal
78
+ // bytes, each followed by a copy of earlier bytes (length - 4, then distance back), every count a
79
+ // little-endian base-128 varint. Packing keeps the tables a page parses small; a page unpacks only
80
+ // its engine's tables.
81
+ export function unpackTable(packed ) {
82
+ const input = atob(packed)
83
+ let at = 0
84
+ const varint = () => {
85
+ let value = 0
86
+ let scale = 1
87
+ let byte
88
+ do {
89
+ byte = input.charCodeAt(at++)
90
+ value += (byte & 0x7f) * scale
91
+ scale *= 0x80
92
+ } while (byte >= 0x80)
93
+ return value
94
+ }
95
+ const bytes = new Uint8Array(varint())
96
+ let out = 0
97
+ while (at < input.length) {
98
+ for (let n = varint(); n > 0; n--) bytes[out++] = input.charCodeAt(at++)
99
+ if (at >= input.length) break
100
+ const length = varint() + 4
101
+ const from = out - varint()
102
+ // A copy that overlaps the bytes it writes repeats them, so it goes one byte at a time.
103
+ if (from + length <= out) bytes.copyWithin(out, from, from + length)
104
+ else for (let k = 0; k < length; k++) bytes[out + k] = bytes[from + k]
105
+ out += length
106
+ }
107
+ if (out !== bytes.length) throw new Error('A packed table unpacked to the wrong length')
108
+ return bytes
109
+ }
110
+
111
+ // The little-endian base-128 varints a table in base64 holds. Tables of varints ship without
112
+ // unpackTable's packing, which saves them nothing once the bundle is compressed.
113
+ export function unpackVarints(base64 ) {
114
+ const input = atob(base64)
115
+ const values = new Int32Array(input.length)
116
+ let count = 0
117
+ for (let i = 0; i < input.length;) {
118
+ let byte = input.charCodeAt(i++)
119
+ let value = byte & 0x7f
120
+ for (let shift = 7; byte >= 0x80; shift += 7) {
121
+ byte = input.charCodeAt(i++)
122
+ value |= (byte & 0x7f) << shift
123
+ }
124
+ values[count++] = value
125
+ }
126
+ return values.subarray(0, count)
127
+ }
128
+
129
+ // A code point's class in one of the generated maps, in two loads: `index` has, for every 256 code
130
+ // points up to U+10FFFF, their block of `values`. Code points that share a class through a whole
131
+ // block share one block.
132
+
133
+
134
+ // For c in U+0000..U+10FFFF, which is every code point a scan decodes from UTF-16: `index` has no
135
+ // entry above that, where the engines' tries give their error value (unicode/ucptrie.h:615-620;
136
+ // getLineBreakClass in src/gecko-line-breaks.ts gives ICU4X's itself).
137
+ export function getClass(table , c ) {
138
+ return table.values[(table.index[c >> 8] << 8) | (c & 0xff)]
139
+ }
140
+
141
+ // The maps ship as one list of runs over U+0000..U+10FFFF, each run a length - 1 and a joint class:
142
+ // the joint classes are the classes all the maps together tell apart, so code points that the
143
+ // engines class alike are stored once. `remap` has one map's class for each joint class, and
144
+ // `blocks` is how many blocks its table takes. Throws for a list that doesn't end at U+10FFFF, an
145
+ // empty one included.
146
+ export function unpackClassRuns(runs , remap , blocks ) {
147
+ const index = new Uint16Array(0x1100)
148
+ const values = new Uint8Array(blocks << 8)
149
+ // The block each class fills whole, once a run covers one.
150
+ const whole = new Int32Array(0x100).fill(-1)
151
+ let count = 0
152
+ let c = 0
153
+ let end = 0
154
+ let value = -1
155
+ for (let r = 0; r <= runs.length; r += 2) {
156
+ // Runs of joint classes that are one class in this map make one run, [c, end), written once
157
+ // the next run's class differs or no run is left.
158
+ const following = r < runs.length ? remap[runs[r + 1] ] : -1
159
+ if (following !== value) {
160
+ while (c < end) {
161
+ const block = c >> 8
162
+ if ((c & 0xff) === 0 && end - c >= 0x100) {
163
+ if (whole[value] < 0) {
164
+ whole[value] = count
165
+ values.fill(value, count << 8, ++count << 8)
166
+ }
167
+ index.fill(whole[value] , block, end >> 8)
168
+ c = end & ~0xff
169
+ } else {
170
+ // A block that several runs share is its own: the first of them starts it.
171
+ if ((c & 0xff) === 0) index[block] = count++
172
+ const stop = Math.min(end, (block + 1) << 8)
173
+ const shift = (index[block] - block) << 8
174
+ values.fill(value, c + shift, stop + shift)
175
+ c = stop
176
+ }
177
+ }
178
+ value = following
179
+ }
180
+ if (r < runs.length) end += runs[r] + 1
181
+ }
182
+ if (end !== 0x110000) throw new Error('The class runs don\'t cover U+0000..U+10FFFF')
183
+ return { index, values }
184
+ }
185
+
186
+ // The run list and the remaps, kept for the other maps the engine reads.
187
+ let classRuns = null
188
+ let classRemaps = null
189
+
190
+ export function unpackClasses(map ) {
191
+ const [row, blocks] = classMaps[map]
192
+ classRuns ??= unpackVarints(classRunsVarints)
193
+ classRemaps ??= unpackTable(classRemapsPacked)
194
+ return unpackClassRuns(classRuns, classRemaps.subarray(row * jointClassCount), blocks)
195
+ }
196
+
197
+ // A regular expression built at its first use, for the ones most text never reaches. Where V8 or
198
+ // SpiderMonkey parses a literal with a \p{...} class of a general category or a script, it builds the
199
+ // class's set, in a function that never runs too, so each such literal costs every page as it loads.
200
+ // One that every text tests stays a literal (RESEARCH.md, Keeping Work Bounded, JavaScript Engines).
201
+ export function lazyRegExp(source , flags ) {
202
+ let built = null
203
+ return () => built ??= new RegExp(source, flags)
204
+ }
205
+
206
+ // Unicode properties the scans read with RegExp's \p{...}, as two bits per property and code point:
207
+ // whether it was tested, and whether the code point has it. A scan tests a property the first time it
208
+ // asks about a code point, so a page builds and runs only the tests its engine's scan makes. Code
209
+ // points below U+10000 keep their bits in one table, the others in a map.
210
+ const PROPERTY_TESTS = [
211
+ lazyRegExp(String.raw`^[\p{L}\p{N}]$`, 'u'),
212
+ lazyRegExp(String.raw`^\p{M}$`, 'u'),
213
+ lazyRegExp(String.raw`^\p{P}$`, 'u'),
214
+ lazyRegExp(String.raw`^[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]$`, 'u'),
215
+ lazyRegExp(String.raw`^\p{Default_Ignorable_Code_Point}$`, 'u'),
216
+ lazyRegExp(String.raw`^\p{Emoji}$`, 'u'),
217
+ lazyRegExp(String.raw`^\p{sc=Hangul}$`, 'u'),
218
+ ]
219
+ export const LETTER_OR_NUMBER = 0
220
+ export const MARK = 1
221
+ export const PUNCTUATION = 2
222
+ export const PUNCTUATION_BUT_DASH_OR_CONNECTOR = 3
223
+ export const DEFAULT_IGNORABLE = 4
224
+ export const EMOJI = 5
225
+ export const HANGUL = 6
226
+ let bmpProperties = null
227
+ const astralProperties = new Map ()
228
+
229
+ export function hasProperty(cp , property ) {
230
+ const bmp = bmpProperties ??= new Uint16Array(0x10000)
231
+ const has = 1 << (property * 2)
232
+ let bits = cp < 0x10000 ? bmp[cp] : astralProperties.get(cp) ?? 0
233
+ if ((bits & has << 1) === 0) {
234
+ bits |= has << 1 | (PROPERTY_TESTS[property] ().test(String.fromCodePoint(cp)) ? has : 0)
235
+ if (cp < 0x10000) bmp[cp] = bits
236
+ else astralProperties.set(cp, bits)
237
+ }
238
+ return (bits & has) !== 0
239
+ }
240
+
241
+ // The scans read word boundaries only inside runs of Thai, Lao, Khmer and Myanmar
242
+ // letters, where no locale changes them. They ask for the segmenter only when such a
243
+ // run shows up (Chrome's and Safari's scans also in Tai Le, New Tai Lue, Tai Tham, Tai
244
+ // Viet and Ahom runs), so other text prepares without Intl.Segmenter.
245
+ let wordSegmenter = null
246
+
247
+ export function getWordSegmenter() {
248
+ return wordSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'word' })
249
+ }
250
+
251
+ export function clearWordSegmenter() {
252
+ wordSegmenter = null
253
+ }
254
+
255
+ // --- ICU's rule-based iterator over compiled line rules ---
256
+
257
+ export const START_STATE = 1 // rbbi.cpp:48
258
+ const STOP_STATE = 0 // rbbi.cpp:51
259
+ const ACCEPTING_UNCONDITIONAL = 1 // rbbidata.h:127
260
+
261
+ // ICU's compiled rules (RBBIDataHeader, rbbidata.h:67-94) as the forward iterator reads them: the
262
+ // state table's sizes (RBBIStateTable, rbbidata.h:134-148), its rows of fAccepting, fLookAhead,
263
+ // fTagsIdx and fNextState[catCount] (rbbidata.h:98-125), 16 bits each, and the category of every
264
+ // code point, which ICU keeps in a UCPTrie.
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+ // A state table ships as its rows' differences from rows it repeats. The rows start as zeros or
275
+ // as `base`, another table's rows of the same shape. Then, for each row that differs from how it
276
+ // starts: the rows skipped since the last such row, how many rows back the row it copies is, or 0
277
+ // for none, how many cells differ after that, and for each the cells skipped since the last one
278
+ // and its value.
279
+ export function unpackStateRows(width , states , base , differences ) {
280
+ const rows = new Uint16Array(states * width)
281
+ if (base !== null) rows.set(base)
282
+ for (let i = 0, row = -width; i < differences.length;) {
283
+ row += (differences[i++] + 1) * width
284
+ const back = differences[i++] * width
285
+ if (back > 0) rows.copyWithin(row, row - back, row - back + width)
286
+ let at = row - 1
287
+ for (let n = differences[i++] ; n > 0; n--) {
288
+ at += 1 + differences[i++]
289
+ rows[at] = differences[i++]
290
+ }
291
+ }
292
+ return rows
293
+ }
294
+
295
+ // A table's rows. The rows of the table they start from are unpacked for it and not kept.
296
+ function unpackTableRows(table ) {
297
+ const [catCount, , , states, base, differences] = ruleTables[table]
298
+ return unpackStateRows(catCount + 3, states, base === null ? null : unpackTableRows(base), unpackVarints(differences))
299
+ }
300
+
301
+ const breakRules = {}
302
+
303
+ // A line or character table's rules, unpacked the first time a scan asks for them.
304
+ export function getBreakRules(table ) {
305
+ let rules = breakRules[table]
306
+ if (rules === undefined) {
307
+ const [catCount, dictCategoriesStart, lookAheadResultsSize] = ruleTables[table]
308
+ rules = breakRules[table] = {
309
+ catCount, dictCategoriesStart, rowWidth: catCount + 3, rows: unpackTableRows(table), lookAheadResultsSize,
310
+ classes: unpackClasses(table),
311
+ }
312
+ }
313
+ return rules
314
+ }
315
+
316
+ // Apple's quotation remap for a locale: code points the line rules read as another category
317
+ // (apple-rbbi.cpp:1061-1084).
318
+
319
+ const NO_OVERRIDES = { chars: [], categories: [] }
320
+
321
+ // ICU's handleNext() (rbbi.cpp:779-952) from each boundary to the next over the whole text:
322
+ // flags[b] = 1 at every boundary 0 < b <= text.length, and at Intl.Segmenter word boundaries
323
+ // strictly inside each run of dictionary characters of a segment that ICU would give to a
324
+ // dictionary: one with a character in a dictionary category since its start (rbbi.cpp:854,
325
+ // rbbi_cache.cpp:486-489). The line rules say $dictionary = [$SA]. The text is read like
326
+ // utext_next32() over UTF-16 (utext.cpp:272-308), with unpaired surrogates as code points. ICU
327
+ // allocates the look-ahead slots uninitialized (rbbi.cpp:122-129) and never resets them between
328
+ // calls, since the rules read a slot only after setting it; here they start at -1 for each text.
329
+ export function markRuleBoundaries(r , text , flags , overrides = NO_OVERRIDES) {
330
+ const rows = r.rows
331
+ const width = r.rowWidth
332
+ const dictionaryStart = r.dictCategoriesStart
333
+ const length = text.length
334
+ const matches = new Int32Array(r.lookAheadResultsSize).fill(-1)
335
+ const overrideChars = overrides.chars
336
+ const overrideCount = overrideChars.length
337
+
338
+ for (let start = 0; start < length;) { // rbbi.cpp:809-813
339
+ let dictionaryCharCount = 0
340
+ let result = start
341
+ let boundary = -1
342
+ let pos = start
343
+ let c = text.charCodeAt(pos++)
344
+ if ((c & 0xfc00) === 0xd800 && pos < length) {
345
+ const trail = text.charCodeAt(pos)
346
+ if ((trail & 0xfc00) === 0xdc00) { pos++; c = ((c - 0xd800) << 10) + trail - 0xdc00 + 0x10000 }
347
+ }
348
+ let atEnd = false
349
+ let ended = false
350
+ let row = START_STATE * width
351
+ let category = 0
352
+
353
+ for (;;) {
354
+ if (atEnd) { // rbbi.cpp:832-843
355
+ if (ended) break
356
+ ended = true
357
+ category = 1
358
+ } else { // rbbi.cpp:850-855, with Apple's overrides (apple-rbbi.cpp:1061-1084)
359
+ let overridden = false
360
+ for (let i = 0; i < overrideCount; i++) {
361
+ if (c === overrideChars[i]) { category = overrides.categories[i] ; overridden = true; break }
362
+ }
363
+ if (!overridden) {
364
+ category = getClass(r.classes, c)
365
+ if (category >= dictionaryStart) dictionaryCharCount++
366
+ }
367
+ }
368
+ const state = rows[row + 3 + category] // rbbi.cpp:874-877
369
+ row = state * width
370
+
371
+ const accepting = rows[row] // rbbi.cpp:880-896
372
+ if (accepting === ACCEPTING_UNCONDITIONAL) {
373
+ result = pos
374
+ } else if (accepting > ACCEPTING_UNCONDITIONAL && matches[accepting] >= 0) {
375
+ boundary = matches[accepting]
376
+ break
377
+ }
378
+
379
+ const rule = rows[row + 1] // rbbi.cpp:904-910
380
+ if (rule > ACCEPTING_UNCONDITIONAL) matches[rule] = pos
381
+
382
+ if (state === STOP_STATE) break // rbbi.cpp:912-917
383
+
384
+ if (!ended) { // rbbi.cpp:923-929
385
+ if (pos >= length) {
386
+ atEnd = true
387
+ } else {
388
+ c = text.charCodeAt(pos++)
389
+ if ((c & 0xfc00) === 0xd800 && pos < length) {
390
+ const trail = text.charCodeAt(pos)
391
+ if ((trail & 0xfc00) === 0xdc00) { pos++; c = ((c - 0xd800) << 10) + trail - 0xdc00 + 0x10000 }
392
+ }
393
+ }
394
+ }
395
+ }
396
+
397
+ if (boundary < 0) {
398
+ boundary = result
399
+ if (result === start) { // rbbi.cpp:937-942
400
+ boundary = start + 1
401
+ if ((text.charCodeAt(start) & 0xfc00) === 0xd800 && boundary < length &&
402
+ (text.charCodeAt(boundary) & 0xfc00) === 0xdc00) boundary++
403
+ }
404
+ }
405
+ flags[boundary] = 1
406
+ if (dictionaryCharCount > 0) markDictionaryWords(r, text, start, boundary, flags)
407
+ start = boundary // rbbi.cpp:945
408
+ }
409
+ }
410
+
411
+ // --- Line tables ---
412
+
413
+
414
+
415
+ // Line_Break=SA for one code point: the line rules' dictionary categories.
416
+ function isComplexContext(rules , c ) {
417
+ return getClass(rules.classes, c) >= rules.dictCategoriesStart
418
+ }
419
+
420
+ function markDictionaryWords(rules , text , start , end , flags ) {
421
+ let runStart = -1
422
+ for (let i = start; i <= end;) {
423
+ let dictionary = false
424
+ let size = 1
425
+ if (i < end) {
426
+ let c = text.charCodeAt(i)
427
+ if ((c & 0xfc00) === 0xd800 && i + 1 < text.length && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
428
+ c = ((c - 0xd800) << 10) + text.charCodeAt(i + 1) - 0xdc00 + 0x10000
429
+ size = 2
430
+ }
431
+ dictionary = isComplexContext(rules, c)
432
+ }
433
+ if (dictionary) {
434
+ if (runStart < 0) runStart = i
435
+ } else if (runStart >= 0) {
436
+ if (i - runStart > 1) {
437
+ for (const segment of getWordSegmenter().segment(text.slice(runStart, i))) {
438
+ if (segment.index > 0) flags[runStart + segment.index] = 1
439
+ }
440
+ }
441
+ runStart = -1
442
+ }
443
+ i += size
444
+ }
445
+ }
446
+
447
+ const SPACE = 0x20
448
+ const TAB = 0x09
449
+ const LF = 0x0a
450
+ const ZWSP = 0x200b
451
+ const LINE_SEPARATOR = 0x2028
452
+ const PARAGRAPH_SEPARATOR = 0x2029
453
+ const IDEOGRAPHIC_SPACE = 0x3000
454
+
455
+ // --- Blink ---
456
+
457
+ let blinkPairs = null
458
+ let blinkDefaultLocale
459
+
460
+ // Blink's default language as Intl shows it (getBlinkLineBreaks), read once.
461
+ export function getBlinkDefaultLocale() {
462
+ return blinkDefaultLocale ??= new Intl.DateTimeFormat().resolvedOptions().locale
463
+ }
464
+
465
+ const NO_BREAK = 0
466
+ const CAN_BREAK = 1
467
+ const UNKNOWN = 2
468
+
469
+ // tbi.h:203-205
470
+ function isBlinkBreakableSpace(c ) {
471
+ return c === SPACE || c === TAB || c === LF
472
+ }
473
+
474
+ // Context::ShouldBreakFast, tbi.cc:216-260, with soft hyphens enabled (hyphens: manual).
475
+ // The pair table is Chromium's generated kFastLineBreakTable for U+0021..U+00FF
476
+ // (gen.cc:422-551).
477
+ function shouldBreakFast(pairs , lastLast , last , ch ) {
478
+ if (last < 0x21 || ch < 0x21) return NO_BREAK
479
+ if (last === 0x2d) {
480
+ if (ch <= 0x7f) {
481
+ if (ch >= 0x30 && ch <= 0x39) {
482
+ const lower = lastLast | 0x20
483
+ return (lastLast >= 0x30 && lastLast <= 0x39) || (lower >= 0x61 && lower <= 0x7a) ? CAN_BREAK : NO_BREAK
484
+ }
485
+ } else {
486
+ return UNKNOWN
487
+ }
488
+ }
489
+ if (last <= 0xff && ch <= 0xff) {
490
+ const x = ch - 0x21
491
+ return (pairs[(last - 0x21) * 28 + (x >> 3)] & (1 << (x & 7))) !== 0 ? CAN_BREAK : NO_BREAK
492
+ }
493
+ return UNKNOWN
494
+ }
495
+
496
+ // ShouldKeepAfterKeepAll, tbi.cc:157-165, per UTF-16 code unit.
497
+ function shouldKeepAfterKeepAll(rules , lastLast , last , ch ) {
498
+ const pre = hasProperty(last, MARK) ? lastLast : last
499
+ return hasProperty(pre, LETTER_OR_NUMBER) && !isComplexContext(rules, pre) &&
500
+ hasProperty(ch, LETTER_OR_NUMBER) && !isComplexContext(rules, ch)
501
+ }
502
+
503
+ // Where a line may start in text Blink collapsed as Pretext does: flags[i] = 1 for
504
+ // 0 < i < text.length. NextBreakablePosition (tbi.cc:270-387) for LineBreakType kNormal
505
+ // or kKeepAll and BreakSpaceType kAfterSpaceRun, asked at every offset. Each answer
506
+ // depends only on the two units before the offset, the unit at it, and whether ICU has
507
+ // a boundary there.
508
+ // Under line-break: auto, Chrome opens ICU's line iterator with the plain locale
509
+ // (line_breaker.cc:57-71 in core/layout/inline; LazyLineBreakIterator::LocaleWithKeyword,
510
+ // tbi.h:274-288; tbi_icu.cc:59-94), and ICU's break-iterator data picks the table by locale,
511
+ // falling back by truncation (brkiter.cpp:57-144, 433-452): data/brkitr/zh.txt:6 and
512
+ // zh_Hant.txt:6 map `line` to line_normal_cj.brk, and root.txt:8, ja.txt:6 and ko.txt:6 to
513
+ // line_normal.brk.
514
+ // Content without a language opens the table of Blink's default language, the renderer's
515
+ // --lang, which is Chrome's UI language (tbi_icu.cc:71-75, text_break_iterator_internal_icu.cc:31-45,
516
+ // platform/language.cc:94-99). Chrome also makes it the renderer's ICU default locale, which
517
+ // V8's Intl reads (ui/base/l10n/l10n_util.cc:392-398, chrome/app/chrome_main_delegate.cc:1474-1476),
518
+ // so preparation gives the scan that language from Intl for a page without one
519
+ // (getPreparationLanguage in src/measurement.ts); navigator.language follows the accept
520
+ // languages instead. Without a document the scan reads root.
521
+ export function getBlinkLineBreaks(text , keepAll , language ) {
522
+ const length = text.length
523
+ const breaks = new Uint8Array(length + 1)
524
+ if (length < 2) return breaks
525
+ const pairs = blinkPairs ??= unpackTable(blinkLinePairsPacked)
526
+ const table = getBreakLanguage(language) === 'zh' ? 'line_normal_cj' : 'line_normal'
527
+ const rules = getBreakRules(`chromium/${table}`)
528
+ let icu = null
529
+ let lastLast = 0
530
+ let last = text.charCodeAt(0)
531
+ for (let i = 1; i < length; lastLast = last, last = text.charCodeAt(i), i++) {
532
+ const ch = text.charCodeAt(i)
533
+ if (isBlinkBreakableSpace(ch)) continue // tbi.cc:284-291
534
+ if (isBlinkBreakableSpace(last)) { breaks[i] = 1; continue }
535
+ const fast = shouldBreakFast(pairs, lastLast, last, ch)
536
+ if (fast === CAN_BREAK) { breaks[i] = 1; continue } // tbi.cc:305-309
537
+ if (keepAll && shouldKeepAfterKeepAll(rules, lastLast, last, ch)) continue // tbi.cc:338-344
538
+ if (fast === NO_BREAK) continue // tbi.cc:346-348
539
+ // tbi.cc:350-383: ICU's first boundary after i - 1 is i exactly when i is a boundary,
540
+ // since the unit before it isn't a space here.
541
+ if (icu === null) {
542
+ icu = new Uint8Array(length + 1)
543
+ markRuleBoundaries(rules, text, icu)
544
+ }
545
+ if (icu[i] === 1) breaks[i] = 1
546
+ }
547
+ return breaks
548
+ }
549
+
550
+ // --- WebKit ---
551
+
552
+ // BreakClass, BP.h:80-102. PI and PF mark opening and closing quotation marks, always
553
+ // together with QU.
554
+ const AL = 1
555
+ const ID = 2
556
+ const CM = 4
557
+ const OP = 8
558
+ const CP = 16
559
+ const CL = 32
560
+ const GL = 64
561
+ const QU = 128
562
+ const SP = 256
563
+ const PI = 512
564
+ const PF = 1024
565
+ const WEIRD = 32768
566
+
567
+ let webkitPairs = null
568
+
569
+ const webkitLineRules = new Map ()
570
+
571
+ // BP.h:125-139 with NoBreakSpaceBehavior::Normal.
572
+ function isWebKitBreakableSpace(c ) {
573
+ return c === SPACE || c === LF || c === TAB || c === LINE_SEPARATOR || c === PARAGRAPH_SEPARATOR
574
+ }
575
+
576
+ function isASCIIDigit(c ) {
577
+ return c >= 0x30 && c <= 0x39
578
+ }
579
+
580
+ function isASCIIAlpha(c ) {
581
+ const lower = c | 0x20
582
+ return lower >= 0x61 && lower <= 0x7a
583
+ }
584
+
585
+ // BP.h:330-539. The 0x3000..0x303F switch reads only the low five bits, so it answers for
586
+ // 0x3020..0x303F too.
587
+ function classify(c ) {
588
+ switch (c >> 7) {
589
+ case 0:
590
+ switch (c >> 4) {
591
+ case 0: return WEIRD
592
+ case 1: return CM
593
+ case 2: return c === 0x20 ? SP : c === 0x22 || c === 0x27 ? QU : c === 0x28 ? OP : c === 0x29 ? CP : WEIRD
594
+ case 3: return c <= 0x39 ? AL : WEIRD
595
+ case 4: return AL
596
+ case 5: return c <= 0x5a ? AL : c === 0x5b ? OP : c === 0x5d ? CP : WEIRD
597
+ case 6: return AL
598
+ default: return c <= 0x7a ? AL : c === 0x7b ? OP : c === 0x7d ? CL : WEIRD
599
+ }
600
+ case 1:
601
+ if (c === 0xa0) return GL
602
+ if (c > 0xc0) return AL
603
+ if (c === 0xa1 || c === 0xbf) return OP
604
+ if (c === 0xab) return QU | PI
605
+ if (c === 0xbb) return QU | PF
606
+ return WEIRD
607
+ case 2: case 3: case 4: return AL
608
+ case 5: return c === 0x2c8 || c === 0x2cc || c === 0x2df ? WEIRD : AL
609
+ case 6:
610
+ if (c === 0x34f || (c >= 0x35c && c <= 0x362)) return GL
611
+ if (c < 0x370) return CM
612
+ if (c === 0x37e) return WEIRD
613
+ return AL
614
+ case 7: case 8: case 10: return AL
615
+ case 9: return c >= 0x483 && c <= 0x489 ? CM : AL
616
+ case 11:
617
+ if (c <= 0x588 || c >= 0x5c8) return AL
618
+ if (c >= 0x591 && c <= 0x5bd) return CM
619
+ return c === 0x5bf || c === 0x5c1 || c === 0x5c2 || c === 0x5c4 || c === 0x5c5 || c === 0x5c7 ? CM : WEIRD
620
+ case 64:
621
+ if (c === 0x2018 || c === 0x201c) return QU | PI
622
+ if (c === 0x2019 || c === 0x201d) return QU | PF
623
+ return WEIRD
624
+ default:
625
+ if (c >= 0x2e80 && c <= 0xa4cf) {
626
+ if ((c & 0xff00) === 0x3000) {
627
+ if (c <= 0x303f) {
628
+ switch (c & 0x1f) {
629
+ case 0x01: case 0x02: case 0x09: case 0x0b: case 0x0d: case 0x0f: case 0x11: case 0x15: case 0x17:
630
+ case 0x19: case 0x1b: case 0x1e: case 0x1f:
631
+ return CL
632
+ case 0x08: case 0x0a: case 0x0c: case 0x0e: case 0x10: case 0x16: case 0x14: case 0x18: case 0x1a:
633
+ case 0x1d:
634
+ return OP
635
+ default:
636
+ return WEIRD
637
+ }
638
+ }
639
+ return WEIRD
640
+ }
641
+ if ((c & 0xfff0) === 0x31f0) return WEIRD
642
+ if ((c & 0xfff8) === 0x3248) return AL
643
+ if ((c & 0xffc0) === 0x4dc0) return AL
644
+ if (c === 0xa015) return WEIRD
645
+ return ID
646
+ }
647
+ if (c >= 0xac00 && c <= 0xd7af) return ID
648
+ if (c >= 0xf900 && c <= 0xfaff) return ID
649
+ return WEIRD
650
+ }
651
+ }
652
+
653
+ // CachedLineBreakIteratorFactory (TBI.h:236-351) with two characters of prior context
654
+ // (TBI.h:239-290), and ubrk_following over the prior context followed by the text
655
+ // (TBIICU.h:99-124, 136-142).
656
+
657
+
658
+
659
+
660
+
661
+
662
+
663
+
664
+
665
+
666
+ function createFactory(text , line ) {
667
+ return { text, secondToLast: 0, last: 0, priorLength: 0, line, nextBoundary: null }
668
+ }
669
+
670
+ // The first ICU boundary after `location`, or -1. `location` is -1 only with a prior
671
+ // context; WebKit's unsigned arithmetic then lands on the last prior character
672
+ // (TBIICU.h:136-142).
673
+ function following(f , location ) {
674
+ let next = f.nextBoundary
675
+ if (next === null) {
676
+ const prior = f.priorLength === 2
677
+ ? String.fromCharCode(f.secondToLast, f.last)
678
+ : f.priorLength === 1 ? String.fromCharCode(f.last) : ''
679
+ const text = prior + f.text
680
+ const flags = new Uint8Array(text.length + 1)
681
+ markRuleBoundaries(f.line.rules, text, flags, f.line.overrides)
682
+ next = new Int32Array(text.length + 2)
683
+ next[text.length + 1] = -1
684
+ for (let p = text.length; p >= 0; p--) next[p] = flags[p] === 1 ? p : next[p + 1]
685
+ f.nextBoundary = next
686
+ }
687
+ const o = location + f.priorLength + 1
688
+ if (o >= next.length) return -1
689
+ const b = next[o]
690
+ return b < 0 ? -1 : b - f.priorLength
691
+ }
692
+
693
+ // BP.h:142-255 for LineBreakRules::Normal, WordBreakBehavior::Normal and
694
+ // NoBreakSpaceBehavior::Normal. During the fast-forward over units where ICU agrees
695
+ // (BP.h:241-249) the characters before are not read again.
696
+ function nextBreakablePosition(pairs , f , startPosition ) {
697
+ const s = f.text
698
+ const length = s.length
699
+ if (startPosition === 0 && f.priorLength === 0) {
700
+ if (length <= 1) return length
701
+ startPosition++
702
+ }
703
+ let beforeBefore = startPosition > 1 ? s.charCodeAt(startPosition - 2) : f.secondToLast
704
+ let before = startPosition > 0 ? s.charCodeAt(startPosition - 1) : f.last
705
+ let beforeType = 0
706
+ let after = 0
707
+ let afterType = 0
708
+ let nextBreak = -1
709
+ for (let i = startPosition; i < length; beforeBefore = before, before = after, beforeType = afterType, i++) {
710
+ after = s.charCodeAt(i)
711
+ afterType = 0
712
+ if (isWebKitBreakableSpace(after)) return i
713
+ // ASCII rapid lookup.
714
+ if (before === 0x2d && isASCIIDigit(after)) {
715
+ if (isASCIIDigit(beforeBefore) || isASCIIAlpha(beforeBefore)) return i
716
+ continue
717
+ }
718
+ if (before <= 0xff && after <= 0xff) {
719
+ if (before >= 0x21 && after >= 0x21) {
720
+ const x = after - 0x21
721
+ if ((pairs[(before - 0x21) * 28 + (x >> 3)] & (1 << (x & 7))) !== 0) return i
722
+ }
723
+ continue
724
+ }
725
+ // Non-ASCII rapid lookup.
726
+ if (beforeType === 0) beforeType = classify(before)
727
+ afterType = classify(after)
728
+ const pair = beforeType | afterType
729
+ if ((pair & ~(SP | AL | QU | PI | PF)) === 0) continue
730
+ if ((pair | AL) === (ID | AL)) return i
731
+ if ((pair & (GL | QU)) !== 0 && (pair & WEIRD) === 0) {
732
+ // A quotation mark next to East Asian text breaks before it when it opens and
733
+ // after it when it closes (LB19a).
734
+ if ((pair & ID) !== 0 && (pair & QU) !== 0 && ((afterType & PI) !== 0 || (beforeType & PF) !== 0)) return i
735
+ continue
736
+ }
737
+ if (afterType === CM) {
738
+ afterType = beforeType
739
+ continue
740
+ }
741
+ if ((pair & WEIRD) === 0 && (pair & (CL | CP | OP)) !== 0) {
742
+ if (afterType === CL || afterType === CP || beforeType === OP) continue
743
+ if ((pair & ID) !== 0) return i
744
+ }
745
+ // ICU lookup.
746
+ if (nextBreak < i) nextBreak = following(f, i - 1)
747
+ if (nextBreak >= 0 && i < nextBreak) {
748
+ for (const max = Math.min(nextBreak, length - 1); i < max; beforeBefore = before, before = after, beforeType = afterType, i++) {
749
+ const lookahead = s.charCodeAt(i + 1)
750
+ if (lookahead <= 0xff && !isASCIIAlpha(lookahead)) break
751
+ }
752
+ }
753
+ if (i === nextBreak && !isWebKitBreakableSpace(before)) return i
754
+ }
755
+ return length
756
+ }
757
+
758
+ // BP.h:258-274: keep-all breaks at spaces, before ZWSP and after U+3000, and with
759
+ // punctuation breaks after any punctuation but the text's last character.
760
+ function nextBreakableSpace(s , startPosition , punctuationBreaks ) {
761
+ for (let i = startPosition; i < s.length; i++) {
762
+ const c = s.charCodeAt(i)
763
+ if (isWebKitBreakableSpace(c) || c === ZWSP) return i
764
+ if (c === IDEOGRAPHIC_SPACE) return i + 1
765
+ if (punctuationBreaks && hasProperty(c, PUNCTUATION_BUT_DASH_OR_CONNECTOR) && i + 1 < s.length) return i + 1
766
+ }
767
+ return s.length
768
+ }
769
+
770
+ // ubrk_open(UBRK_LINE, locale) in libicucore (TBIICU.h:63-67): line_normal.brk for ja and
771
+ // ko, line_cj.brk for zh and line.brk otherwise, plus the locale's quotation remap
772
+ // (apple-brkiter.cpp:458-473, apple-rbbi.cpp:406-487), looked up with ICU's parent fallback
773
+ // under ICU's case: lowercase language, title-case script, uppercase region (uloc_getName).
774
+ export function getWebKitLineRules(language ) {
775
+ const locale = language ?? ''
776
+ let line = webkitLineRules.get(locale)
777
+ if (line !== undefined) return line
778
+ const breakLanguage = getBreakLanguage(locale)
779
+ const rules = getBreakRules(breakLanguage === 'ja' || breakLanguage === 'ko' ? 'apple/line_normal' : breakLanguage === 'zh' ? 'apple/line_cj' : 'apple/line')
780
+ const subtags = locale.split(/[-_]/)
781
+ let name = subtags[0] .toLowerCase()
782
+ for (let k = 1; k < subtags.length; k++) {
783
+ const subtag = subtags[k]
784
+ name += '_' + (subtag.length === 4 ? subtag[0] .toUpperCase() + subtag.slice(1).toLowerCase() : subtag.toUpperCase())
785
+ }
786
+ let remap = appleQuoteRemaps[name]
787
+ while (remap === undefined) {
788
+ const cut = name.lastIndexOf('_')
789
+ name = cut < 0 ? '' : name.slice(0, cut)
790
+ remap = appleQuoteRemaps[name]
791
+ }
792
+ const chars = []
793
+ const categories = []
794
+ for (let k = 0; k < remap.length; k += 2) {
795
+ chars.push(remap[k] )
796
+ categories.push(getClass(rules.classes, remap[k + 1] === 0 ? 0x7b : 0x7d))
797
+ }
798
+ line = { rules, overrides: { chars, categories } }
799
+ webkitLineRules.set(locale, line)
800
+ return line
801
+ }
802
+
803
+ const TEXT = 0
804
+ const WHITESPACE = 1
805
+ const SOFT_LINE_BREAK = 2
806
+
807
+ // Where a line may start in a text node's source: flags[i] = 1 for 0 < i < source.length.
808
+ // These are the soft wrap opportunities between the items InlineItemsBuilder::build
809
+ // makes (IIB:122-130), as the soft wrap index loop finds them (IFU:456-510): every item
810
+ // boundary that isn't next to a forced break. A U+2028 or U+2029 that starts an item
811
+ // forces a break after it, marked FORCED_BREAK; one that ICU's fast-forward passed stays
812
+ // inside a text item and doesn't.
813
+ export function getWebKitLineBreaks(
814
+ source ,
815
+ preserveNewlines ,
816
+ keepAll ,
817
+ language ,
818
+ ) {
819
+ const pairs = webkitPairs ??= unpackTable(webkitLinePairsPacked)
820
+ const f = createFactory(source, getWebKitLineRules(language))
821
+ const length = source.length
822
+ const breaks = new Uint8Array(length + 1)
823
+ // WebKit stores a text holding a code unit above U+00FF in 16 bits, where keep-all also
824
+ // breaks after punctuation.
825
+ let sixteenBit = false
826
+ for (let i = 0; keepAll && i < length && !sixteenBit; i++) sixteenBit = source.charCodeAt(i) > 0xff
827
+ let previousKind = -1
828
+ // handleTextContent, IIB:924-1051, for hyphens manual and nbsp-mode normal.
829
+ for (let position = 0; position < length;) {
830
+ let kind = WHITESPACE
831
+ let end = position
832
+ const c = source.charCodeAt(position)
833
+ if (c === LINE_SEPARATOR || c === PARAGRAPH_SEPARATOR || (preserveNewlines && c === LF)) {
834
+ // handleSegmentBreak, IIB:954-962.
835
+ kind = SOFT_LINE_BREAK
836
+ end++
837
+ if (c !== LF) breaks[end] = FORCED_BREAK
838
+ } else {
839
+ // handleWhitespace, IIB:963-992, with moveToNextNonWhitespacePosition (IIB:55-73).
840
+ for (; end < length; end++) {
841
+ const c = source.charCodeAt(end)
842
+ if (c !== SPACE && c !== TAB && (preserveNewlines || c !== LF)) break
843
+ }
844
+ if (end === position) {
845
+ // handleNonWhitespace, IIB:1012-1038, with moveToNextBreakablePosition (IIB:75-87),
846
+ // TU:398-422 with line-break auto (LineMode Default, TU:450-466) and BP.h:287-300.
847
+ kind = TEXT
848
+ end = length
849
+ for (let p = position; p < length; p++) {
850
+ const next = keepAll ? nextBreakableSpace(source, p, sixteenBit) : nextBreakablePosition(pairs, f, p)
851
+ if (next !== position) { end = next; break }
852
+ }
853
+ }
854
+ }
855
+ // IFU:336-355, 406-418: the scan split same-level items, and wrapping is allowed next
856
+ // to white space.
857
+ if (previousKind >= 0 && previousKind !== SOFT_LINE_BREAK && kind !== SOFT_LINE_BREAK) breaks[position] = 1
858
+ previousKind = kind
859
+ position = end
860
+ }
861
+ return breaks
862
+ }
863
+
864
+ // TU:374-396: whether a line may start where the next inline box starts, from a fresh
865
+ // factory on that box with the previous box's last two characters as prior context.
866
+ // hyphens: manual, so a trailing soft hyphen doesn't block the break. Keep-all reads no
867
+ // prior context (TU:403-407, BP.h:288-300): the box starts at a break only where it
868
+ // starts with a breakable space or ZWSP. The soft wrap index loop skips a soft line break
869
+ // item (IFU:456-510), a preserved line feed or a line or paragraph separator
870
+ // (IIB:954-962), so no break comes before one, and wrapping is allowed next to a
871
+ // white-space item (IFU:406-418), which a box's preserved spaces and tabs make
872
+ // (IIB:963-992).
873
+ export function getWebKitBreakBetweenItems(previous , next , keepAll , language ) {
874
+ const last = previous.charCodeAt(previous.length - 1)
875
+ const first = next.charCodeAt(0)
876
+ if (first === LF || first === LINE_SEPARATOR || first === PARAGRAPH_SEPARATOR) return false
877
+ if (last === SPACE || last === TAB || first === SPACE || first === TAB) return true
878
+ if (keepAll) return nextBreakableSpace(next, 0, false) === 0
879
+ const pairs = webkitPairs ??= unpackTable(webkitLinePairsPacked)
880
+ const f = createFactory(next, getWebKitLineRules(language))
881
+ const n = previous.length
882
+ f.secondToLast = n > 1 ? previous.charCodeAt(n - 2) : 0
883
+ f.last = n > 0 ? previous.charCodeAt(n - 1) : 0
884
+ f.priorLength = f.last === 0 ? 0 : f.secondToLast === 0 ? 1 : 2
885
+ return nextBreakablePosition(pairs, f, 0) === 0
886
+ }
887
+
888
+ // canBreakBefore, InlineContentBreaker.cpp:124-137, for line-break auto: whether a line
889
+ // that holds only an overflowing first character ends before this code unit.
890
+ export function canWebKitLineStartWith(unit ) {
891
+ return unit === 0x5c || (unit !== 0xa0 && unit !== 0x2010 && unit !== 0x2013 && !hasProperty(unit, PUNCTUATION_BUT_DASH_OR_CONNECTOR))
892
+ }