rapier-markdown-kit 1.2.33 → 1.2.40
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/README.md +15 -9
- package/dist/agent/vendor/pretext/NOTICE +2 -2
- package/dist/agent/vendor/pretext/SOURCE.json +62 -78
- package/dist/agent/vendor/pretext/analysis.js +24 -6
- package/dist/agent/vendor/pretext/gecko-line-breaks.js +1 -1
- package/dist/agent/vendor/pretext/generated/engine-break-data.js +1 -1
- package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +1 -1
- package/dist/agent/vendor/pretext/graphemes.js +1 -1
- package/dist/agent/vendor/pretext/han-kerning.js +1 -1
- package/dist/agent/vendor/pretext/layout.js +14 -11
- package/dist/agent/vendor/pretext/line-break.js +170 -195
- package/dist/agent/vendor/pretext/line-breaks.js +1 -7
- package/dist/agent/vendor/pretext/line-text.js +2 -2
- package/dist/agent/vendor/pretext/measurement.js +454 -113
- package/dist/agent/vendor/pretext/prepare.js +164 -244
- package/dist/agent/vendor/pretext/rich-inline.js +57 -37
- package/dist/agent/will.mjs +73 -0
- package/dist/kit/ledger/data.mjs +2 -2
- package/dist/kit/ledger/format.mjs +86 -42
- package/dist/kit/ledger/hash.mjs +43 -2
- package/dist/kit/ledger/history.mjs +6 -0
- package/dist/kit/ledger/inverse-source.mjs +6 -2
- package/dist/kit/ledger/journal-records.mjs +4 -2
- package/dist/kit/ledger/merge.mjs +3 -17
- package/dist/kit/ledger/text-merge.mjs +28 -2
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-markdown.mjs +133 -10
- package/dist/kit/render-print.mjs +3 -3
- package/dist/kit/render-sanitize.mjs +66 -23
- package/dist/kit/render.mjs +10 -10
- package/dist/kit/wiki.mjs +34 -0
- package/dist/kit/will.d.mts +3 -0
- package/dist/layout/hyphenation.mjs +6 -4
- package/dist/layout/line-plan.mjs +96 -0
- package/dist/layout/model.mjs +2 -1
- package/dist/layout/paragraph-fit.mjs +4 -4
- package/dist/layout/prepared-paragraph.mjs +19 -2
- package/dist/layout/typography.mjs +57 -63
- package/dist/spec/document-fonts.mjs +1 -0
- package/dist/spec/document-settings.mjs +1 -0
- package/dist/spec/html-blocks.mjs +10 -7
- package/dist/spec/md-assets.mjs +52 -12
- package/package.json +1 -1
- package/style.css +25 -23
- package/dist/agent/vendor/pretext/entry-geometry.js +0 -183
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/*! Pretext
|
|
1
|
+
/*! Pretext 1.0.0 + main (0d102a5cd) | MIT; BSD/Unicode/ICU notices in LICENSE | Copyright (c) 2026 Pretext contributors */
|
|
2
2
|
import {
|
|
3
3
|
CONTROL,
|
|
4
|
+
DROPPED,
|
|
4
5
|
HARD_BREAK,
|
|
5
6
|
KIND_BITS,
|
|
6
7
|
OBJECT,
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
} from './analysis.js'
|
|
18
19
|
|
|
19
20
|
import { getEngineProfile } from './measurement.js'
|
|
20
|
-
import { getFreshLineEnd, getSegmentEntryWidth, } from './entry-geometry.js'
|
|
21
21
|
|
|
22
22
|
const BREAK_AFTER_KINDS = 1 << SPACE | 1 << ZERO_WIDTH_BREAK | 1 << SOFT_HYPHEN | 1 << PRESERVED_SPACE | 1 << TAB
|
|
23
23
|
|
|
@@ -25,8 +25,8 @@ const BREAK_AFTER_KINDS = 1 << SPACE | 1 << ZERO_WIDTH_BREAK | 1 << SOFT_HYPHEN
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
|
|
@@ -35,10 +35,6 @@ const BREAK_AFTER_KINDS = 1 << SPACE | 1 << ZERO_WIDTH_BREAK | 1 << SOFT_HYPHEN
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
38
|
|
|
43
39
|
|
|
44
40
|
|
|
@@ -97,7 +93,12 @@ const BREAK_AFTER_KINDS = 1 << SPACE | 1 << ZERO_WIDTH_BREAK | 1 << SOFT_HYPHEN
|
|
|
97
93
|
// it (setEmptyObjectFacts in src/rich-inline.ts): the width of the white space that ends the
|
|
98
94
|
// text before it as its last text frame's own, 0 where that text ends in other white space
|
|
99
95
|
// and -1 where it ends in none, and 1 where a line with a break before the object goes back
|
|
100
|
-
// to it, as what follows the object would stick out too
|
|
96
|
+
// to it, as what follows the object would stick out too;
|
|
97
|
+
// - `hangEnds`: where the engine takes the hang out of each text frame's own width (EngineProfile,
|
|
98
|
+
// hangsSpacesPerTextFrame), 1 at a zero-width space that follows preserved spaces inside its own
|
|
99
|
+
// item: it ends the run of spaces that hang, which count in full once it follows them in their
|
|
100
|
+
// frame (BreakAndMeasureText, gfxTextRun.cpp:1152-1160), where one that starts an item is in the
|
|
101
|
+
// frame after theirs and the run goes on past it.
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
|
|
@@ -109,6 +110,7 @@ const BREAK_AFTER_KINDS = 1 << SPACE | 1 << ZERO_WIDTH_BREAK | 1 << SOFT_HYPHEN
|
|
|
109
110
|
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
|
|
112
114
|
|
|
113
115
|
|
|
114
116
|
|
|
@@ -129,19 +131,29 @@ function breaksAfterKind(kind ) {
|
|
|
129
131
|
// paints none.
|
|
130
132
|
export function isDiscretionaryLineEnd(
|
|
131
133
|
segmentFlags ,
|
|
134
|
+
segmentCount ,
|
|
132
135
|
endSegmentIndex ,
|
|
133
136
|
endGraphemeIndex ,
|
|
134
137
|
) {
|
|
135
|
-
return endGraphemeIndex === 0 && endSegmentIndex > 0 && endSegmentIndex <
|
|
138
|
+
return endGraphemeIndex === 0 && endSegmentIndex > 0 && endSegmentIndex < segmentCount &&
|
|
136
139
|
(segmentFlags[endSegmentIndex - 1] & KIND_BITS) === SOFT_HYPHEN
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
// What a line's start leaves out, by a segment's flags: a collapsible space, a soft hyphen, and a
|
|
143
|
+
// soft hyphen that is a zero-width break (DROPPED). A zero-width space stays, at a chunk's start
|
|
144
|
+
// and after a wrap alike: every engine lays it out as text of no width, never as white space.
|
|
145
|
+
// Blink trails only U+0020 and U+0009 past a line's end and skips only a collapsible space at its
|
|
146
|
+
// start (IsBreakableSpace, line_breaker.cc:186-188; HandleTrailingSpaces, :2426-2534; HandleText,
|
|
147
|
+
// :1336-1353, Chromium 153); WebKit's white-space items are spaces, tabs and line feeds, and a
|
|
148
|
+
// text run of any length is content (InlineItemsBuilder.cpp:54-73; Line::Run::isContentful,
|
|
149
|
+
// InlineLine.h:129); Gecko trims and skips the characters of IsTrimmableSpace alone
|
|
150
|
+
// (nsTextFrame.cpp:904-942, 10935-10951). So the line a zero-width space starts is a line, a line
|
|
151
|
+
// tall, though nothing else is on it: where nothing follows it, where a hard break does, and
|
|
152
|
+
// where the word after it doesn't fit beside it and goes down whole, as from any line that has a
|
|
153
|
+
// break (walkPreparedComplexLines has where a line ends before one).
|
|
154
|
+
function consumesAtLineStart(flags ) {
|
|
155
|
+
const kind = flags & KIND_BITS
|
|
156
|
+
return kind === SPACE || kind === SOFT_HYPHEN || (flags & DROPPED) !== 0
|
|
145
157
|
}
|
|
146
158
|
|
|
147
159
|
// A tab's advance from `position` on the line: to the next stop, or to the one after where
|
|
@@ -186,20 +198,6 @@ export function getItemTabAdvance(prepared , items
|
|
|
186
198
|
return getTabAdvance(lineWidth - endEdge, stopAdvance, minimumAdvance, appUnits) + prepared.widths[index]
|
|
187
199
|
}
|
|
188
200
|
|
|
189
|
-
// Where a line that holds only an overflowing grapheme ends: after that grapheme and
|
|
190
|
-
// the graphemes after it that can't start a line, up to `endGraphemeIndex`.
|
|
191
|
-
function getOverflowingFirstGraphemeEnd(
|
|
192
|
-
prepared ,
|
|
193
|
-
segmentIndex ,
|
|
194
|
-
graphemeIndex ,
|
|
195
|
-
endGraphemeIndex ,
|
|
196
|
-
) {
|
|
197
|
-
const prohibitions = prepared.lineStartProhibitions?.[segmentIndex] ?? null
|
|
198
|
-
let end = graphemeIndex + 1
|
|
199
|
-
while (prohibitions !== null && end < endGraphemeIndex && prohibitions[end] === 1) end++
|
|
200
|
-
return end
|
|
201
|
-
}
|
|
202
|
-
|
|
203
201
|
function getTerminalLetterSpacing(
|
|
204
202
|
prepared ,
|
|
205
203
|
openingEdges ,
|
|
@@ -214,10 +212,11 @@ function getTerminalLetterSpacing(
|
|
|
214
212
|
|
|
215
213
|
if (endGraphemeIndex > 0) return (segmentFlags[endSegmentIndex] & SPACED) !== 0 ? letterSpacing : 0
|
|
216
214
|
|
|
217
|
-
|
|
215
|
+
const segmentCount = prepared.widths.length
|
|
216
|
+
if (isDiscretionaryLineEnd(segmentFlags, segmentCount, endSegmentIndex, endGraphemeIndex)) return 0
|
|
218
217
|
// A run of preserved spaces and tabs that hangs where the line wraps already
|
|
219
218
|
// charged the gap after the glyph before it. Gecko doesn't hang tabs.
|
|
220
|
-
if (endSegmentIndex > startSegmentIndex && endSegmentIndex <
|
|
219
|
+
if (endSegmentIndex > startSegmentIndex && endSegmentIndex < segmentCount && (segmentFlags[endSegmentIndex] & KIND_BITS) !== HARD_BREAK &&
|
|
221
220
|
(1 << (segmentFlags[endSegmentIndex - 1] & KIND_BITS) & hangingKinds) !== 0) return 0
|
|
222
221
|
|
|
223
222
|
for (let i = endSegmentIndex - 1; i >= startSegmentIndex; i--) {
|
|
@@ -257,15 +256,15 @@ export function normalizePreparedLineStart(
|
|
|
257
256
|
cursor ,
|
|
258
257
|
) {
|
|
259
258
|
const { segmentFlags } = prepared
|
|
260
|
-
const segmentCount =
|
|
259
|
+
const segmentCount = prepared.widths.length
|
|
261
260
|
let segmentIndex = cursor.segmentIndex
|
|
262
261
|
if (segmentIndex >= segmentCount) return false
|
|
263
262
|
if (cursor.graphemeIndex > 0) return true
|
|
264
263
|
|
|
265
264
|
let atChunkStart = segmentIndex === 0 || (segmentFlags[segmentIndex - 1] & KIND_BITS) === HARD_BREAK
|
|
266
265
|
while (true) {
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
266
|
+
const flags = segmentFlags[segmentIndex]
|
|
267
|
+
if ((flags & KIND_BITS) === HARD_BREAK) {
|
|
269
268
|
if (atChunkStart) {
|
|
270
269
|
cursor.segmentIndex = segmentIndex
|
|
271
270
|
cursor.graphemeIndex = 0
|
|
@@ -275,7 +274,7 @@ export function normalizePreparedLineStart(
|
|
|
275
274
|
cursor.segmentIndex = segmentIndex
|
|
276
275
|
cursor.graphemeIndex = 0
|
|
277
276
|
atChunkStart = true
|
|
278
|
-
} else if (consumesAtLineStart(
|
|
277
|
+
} else if (consumesAtLineStart(flags)) {
|
|
279
278
|
if (++segmentIndex >= segmentCount) return false
|
|
280
279
|
} else {
|
|
281
280
|
cursor.segmentIndex = segmentIndex
|
|
@@ -303,11 +302,10 @@ export function walkPreparedLinesRaw(
|
|
|
303
302
|
// stepPreparedLineGeometryFromStart(), walked chat 6 to 32% slower in Chrome and
|
|
304
303
|
// Firefox (RESEARCH.md, Keeping Work Bounded).
|
|
305
304
|
const { segmentFlags } = prepared
|
|
306
|
-
const segmentCount =
|
|
305
|
+
const segmentCount = prepared.widths.length
|
|
307
306
|
while (true) {
|
|
308
307
|
let startSegmentIndex = cursor.segmentIndex
|
|
309
|
-
|
|
310
|
-
while (startSegmentIndex < segmentCount && consumesAtLineStart(segmentFlags[startSegmentIndex] & KIND_BITS, atTextStart)) {
|
|
308
|
+
while (startSegmentIndex < segmentCount && consumesAtLineStart(segmentFlags[startSegmentIndex] )) {
|
|
311
309
|
startSegmentIndex++
|
|
312
310
|
}
|
|
313
311
|
if (startSegmentIndex >= segmentCount) return stats.lineCount
|
|
@@ -322,16 +320,16 @@ export function walkPreparedLinesRaw(
|
|
|
322
320
|
|
|
323
321
|
// layout()'s count: the simple stepper's lines as one numeric loop, with no
|
|
324
322
|
// cursor and no per-line call. Every segment boundary of a fast-path handle is
|
|
325
|
-
// a break, so an overflowing space
|
|
326
|
-
// starts the next one
|
|
327
|
-
//
|
|
328
|
-
// 2026-09-24).
|
|
323
|
+
// a break, so an overflowing space ends its line, which it hangs on, and any other
|
|
324
|
+
// segment starts the next one, a zero-width space too (stepPreparedSimpleLineGeometry).
|
|
325
|
+
// The full walker costs three to five times as much per segment, so one walker for
|
|
326
|
+
// all text was rejected (RESEARCH.md, Decisions Log, 2026-09-24).
|
|
329
327
|
export function countPreparedLines(prepared , maxWidth ) {
|
|
330
328
|
// The loop takes no overflow trims, which the stepper takes for a line's first segment.
|
|
331
329
|
if (!prepared.simpleLineWalkFastPath || prepared.overflowLineEndTrims !== null) {
|
|
332
330
|
return prepared.simpleLineCountFastPath ? countSteppedLines(prepared, maxWidth) : walkPreparedLinesRaw(prepared, maxWidth)
|
|
333
331
|
}
|
|
334
|
-
const { widths, segmentFlags, breakableFitAdvances,
|
|
332
|
+
const { widths, segmentFlags, breakableFitAdvances, breakableLineStartExtras, lineStartExtras, lineEndTrims } = prepared
|
|
335
333
|
const fitLimit = Math.max(0, maxWidth) + getEngineProfile().lineFitEpsilon
|
|
336
334
|
const segmentCount = widths.length
|
|
337
335
|
let count = 0
|
|
@@ -341,7 +339,6 @@ export function countPreparedLines(prepared , maxWidth )
|
|
|
341
339
|
let lineW = 0
|
|
342
340
|
let hasContent = false
|
|
343
341
|
|
|
344
|
-
// A ZWSP at the start of the text starts the first line.
|
|
345
342
|
for (let i = 0; i < segmentCount; i++) {
|
|
346
343
|
const kind = segmentFlags[i] & KIND_BITS
|
|
347
344
|
const w = widths[i]
|
|
@@ -356,8 +353,8 @@ export function countPreparedLines(prepared , maxWidth )
|
|
|
356
353
|
count++
|
|
357
354
|
lineW = 0
|
|
358
355
|
hasContent = false
|
|
359
|
-
if (kind
|
|
360
|
-
} else if (kind === SPACE
|
|
356
|
+
if (kind === SPACE) continue
|
|
357
|
+
} else if (kind === SPACE) {
|
|
361
358
|
continue
|
|
362
359
|
}
|
|
363
360
|
|
|
@@ -368,37 +365,12 @@ export function countPreparedLines(prepared , maxWidth )
|
|
|
368
365
|
hasContent = true
|
|
369
366
|
continue
|
|
370
367
|
}
|
|
371
|
-
// An overflowing breakable segment fills lines grapheme by grapheme.
|
|
372
|
-
// holding only an overflowing grapheme keeps the graphemes after it that
|
|
373
|
-
// can't start a line. A line that starts inside it where it has fresh-line
|
|
374
|
-
// geometry takes the tail or its fresh prefixes.
|
|
375
|
-
const prohibitions = lineStartProhibitions?.[i] ?? null
|
|
376
|
-
const entry = entryGeometry === null ? null : entryGeometry[i]
|
|
368
|
+
// An overflowing breakable segment fills lines grapheme by grapheme.
|
|
377
369
|
const startExtras = breakableLineStartExtras?.[i] ?? null
|
|
378
370
|
let g = 0
|
|
379
371
|
while (g < advances.length) {
|
|
380
|
-
if (g > 0 && entry !== null && entry.entries[g] !== null) {
|
|
381
|
-
const end = getFreshLineEnd(entry, g, advances.length, fitLimit)
|
|
382
|
-
if (end > advances.length) {
|
|
383
|
-
lineW += getSegmentEntryWidth(entry, g, advances.length)
|
|
384
|
-
hasContent = true
|
|
385
|
-
break
|
|
386
|
-
}
|
|
387
|
-
count++
|
|
388
|
-
g = end
|
|
389
|
-
continue
|
|
390
|
-
}
|
|
391
372
|
if (startExtras !== null) lineW += startExtras[g]
|
|
392
373
|
lineW += advances[g++]
|
|
393
|
-
if (prohibitions !== null && lineW > fitLimit) {
|
|
394
|
-
const kept = g
|
|
395
|
-
while (g < advances.length && prohibitions[g] === 1) lineW += advances[g++]
|
|
396
|
-
if (g > kept) {
|
|
397
|
-
count++
|
|
398
|
-
lineW = 0
|
|
399
|
-
continue
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
374
|
while (g < advances.length && lineW + advances[g] <= fitLimit) lineW += advances[g++]
|
|
403
375
|
if (g < advances.length) {
|
|
404
376
|
count++
|
|
@@ -421,13 +393,14 @@ export function countPreparedLines(prepared , maxWidth )
|
|
|
421
393
|
// its in the last bits (RESEARCH.md, Keeping Work Bounded).
|
|
422
394
|
function countSteppedLines(prepared , maxWidth ) {
|
|
423
395
|
const { segmentFlags } = prepared
|
|
396
|
+
const segmentCount = prepared.widths.length
|
|
424
397
|
const cursor = { segmentIndex: 0, graphemeIndex: 0 }
|
|
425
398
|
let count = 0
|
|
426
399
|
while (normalizePreparedLineStart(prepared, cursor)) {
|
|
427
400
|
const startSegmentIndex = cursor.segmentIndex
|
|
428
401
|
const startGraphemeIndex = cursor.graphemeIndex
|
|
429
402
|
stepPreparedSimpleLineGeometry(prepared, cursor, maxWidth)
|
|
430
|
-
if (cursor.graphemeIndex === 0 && cursor.segmentIndex <
|
|
403
|
+
if (cursor.graphemeIndex === 0 && cursor.segmentIndex < segmentCount && (segmentFlags[cursor.segmentIndex] & UNBROKEN) !== 0) {
|
|
431
404
|
cursor.segmentIndex = startSegmentIndex
|
|
432
405
|
cursor.graphemeIndex = startGraphemeIndex
|
|
433
406
|
walkPreparedComplexLines(prepared, cursor, maxWidth, undefined, null, true)
|
|
@@ -450,30 +423,34 @@ function countSteppedLines(prepared , maxWidth )
|
|
|
450
423
|
// spaces and tabs (nsLineBreaker.cpp:323, :586) and doesn't hang a tab, so where the white space
|
|
451
424
|
// runs into a tab, Firefox moves all of it to the next line with the tab. The walker asks once
|
|
452
425
|
// per run, so a long run costs one pass.
|
|
453
|
-
function staysAfterObject(segmentFlags , start , hangingKinds ) {
|
|
426
|
+
function staysAfterObject(segmentFlags , segmentCount , start , hangingKinds ) {
|
|
454
427
|
let after = start + 1
|
|
455
|
-
while (after <
|
|
456
|
-
return after ===
|
|
428
|
+
while (after < segmentCount && (1 << (segmentFlags[after] & KIND_BITS) & hangingKinds) !== 0) after++
|
|
429
|
+
return after === segmentCount || (segmentFlags[after] & KIND_BITS) !== TAB
|
|
457
430
|
}
|
|
458
431
|
|
|
459
432
|
// Whether a line that would end at the break before `breakSegmentIndex`, painting
|
|
460
433
|
// `breakWidth`, returns to an earlier opportunity: where it ends at a selected
|
|
461
434
|
// discretionary hyphen that doesn't fit. A return needs an overflow that isolated
|
|
462
435
|
// widths can show: the soft hyphens on the line may measure narrower joined than apart
|
|
463
|
-
// by less than the overflow.
|
|
436
|
+
// by less than the overflow. Those are the soft hyphens the line passes, whose text the
|
|
437
|
+
// engine shapes whole, and with `endKeepsJoin` the one the line ends at too
|
|
438
|
+
// (EngineProfile, unfitHyphenRetreat).
|
|
464
439
|
function returnsFromUnfitHyphen(
|
|
465
440
|
prepared ,
|
|
466
441
|
lineStartSegmentIndex ,
|
|
467
442
|
breakSegmentIndex ,
|
|
468
443
|
breakWidth ,
|
|
469
444
|
fitLimit ,
|
|
445
|
+
endKeepsJoin ,
|
|
470
446
|
) {
|
|
471
447
|
const { discretionaryHyphenContexts, segmentFlags } = prepared
|
|
472
448
|
const softHyphenIndex = breakSegmentIndex - 1
|
|
473
449
|
if (softHyphenIndex < lineStartSegmentIndex || (segmentFlags[softHyphenIndex] & KIND_BITS) !== SOFT_HYPHEN || breakWidth <= fitLimit) return false
|
|
474
450
|
const overflow = breakWidth - fitLimit
|
|
475
451
|
let narrowing = 0
|
|
476
|
-
|
|
452
|
+
const last = endKeepsJoin ? softHyphenIndex : softHyphenIndex - 1
|
|
453
|
+
if (discretionaryHyphenContexts !== null) for (let i = lineStartSegmentIndex; i <= last; i++) narrowing += discretionaryHyphenContexts[i]
|
|
477
454
|
return narrowing < overflow
|
|
478
455
|
}
|
|
479
456
|
|
|
@@ -497,7 +474,6 @@ function walkPreparedComplexLines(
|
|
|
497
474
|
widths,
|
|
498
475
|
segmentFlags,
|
|
499
476
|
breakableFitAdvances,
|
|
500
|
-
entryGeometry,
|
|
501
477
|
discretionaryHyphenWidth,
|
|
502
478
|
letterSpacing,
|
|
503
479
|
lineStartExtras,
|
|
@@ -506,7 +482,7 @@ function walkPreparedComplexLines(
|
|
|
506
482
|
tabStopAdvance,
|
|
507
483
|
minimumTabAdvance,
|
|
508
484
|
} = prepared
|
|
509
|
-
const segmentCount =
|
|
485
|
+
const segmentCount = widths.length
|
|
510
486
|
const items = prepared.items
|
|
511
487
|
const hyphenWidths = items === undefined ? null : items.hyphenWidths
|
|
512
488
|
const hyphenRooms = items === undefined ? null : items.hyphenRooms
|
|
@@ -524,8 +500,14 @@ function walkPreparedComplexLines(
|
|
|
524
500
|
// In Gecko a text frame's width leaves out its trailing spaces that overflow the line and
|
|
525
501
|
// keeps those that fit, whatever follows the frame (EngineProfile, hangsSpacesPerTextFrame),
|
|
526
502
|
// so in a rich-inline paragraph the run of preserved spaces that hang goes on, with what
|
|
527
|
-
// overflows, past a segment that takes no room: an object of width 0, a soft hyphen
|
|
528
|
-
// zero-width break
|
|
503
|
+
// overflows, past a segment that takes no room: an object of width 0, a soft hyphen, the
|
|
504
|
+
// zero-width break the Gecko scan makes of a soft hyphen after white space (DROPPED), and a
|
|
505
|
+
// zero-width space that starts an item, where the content before the run fits. The frame
|
|
506
|
+
// after the spaces' own then starts at the line's end, where a zero-width space fits; where
|
|
507
|
+
// that content is past the line's end, as a box wider than the line, the zero-width space
|
|
508
|
+
// starts the next line, as a break comes before it after white space
|
|
509
|
+
// (nsLineBreaker.cpp:321-330), and so does one inside the spaces' own item, which ends their
|
|
510
|
+
// run (ParagraphSegmentData, hangEnds).
|
|
529
511
|
const hangGoesOnPastEmpty = items !== undefined && engineProfile.hangsSpacesPerTextFrame
|
|
530
512
|
const testsHyphenBeforeAtomic = engineProfile.testsHyphenBeforeAtomic
|
|
531
513
|
const zeroWidthGlueTakesLine = engineProfile.zeroWidthGlueTakesLine
|
|
@@ -546,6 +528,9 @@ function walkPreparedComplexLines(
|
|
|
546
528
|
// the soft hyphen the line reaches before any of its opportunities has fit, since one
|
|
547
529
|
// without a hyphen fits where the text before it did.
|
|
548
530
|
const keepsFirstBreak = retreatsFromUnfitHyphen && engineProfile.unfitHyphenRetreat === 'full-width-or-first'
|
|
531
|
+
// Gecko's line keeps what the letters on the two sides of the soft hyphen it ends at
|
|
532
|
+
// measure narrower joined. Blink shapes the line's end again, without it.
|
|
533
|
+
const hyphenEndKeepsJoin = engineProfile.unfitHyphenRetreat === 'full-width'
|
|
549
534
|
|
|
550
535
|
let lastLineWidth = null
|
|
551
536
|
while (true) {
|
|
@@ -679,11 +664,11 @@ function walkPreparedComplexLines(
|
|
|
679
664
|
}
|
|
680
665
|
|
|
681
666
|
// A line that ends after a whole segment charges its advance and the letter
|
|
682
|
-
// spacing gap after it. Spaces and zero-width
|
|
683
|
-
//
|
|
684
|
-
//
|
|
667
|
+
// spacing gap after it. Spaces hang, and zero-width text owns no gap, though NEL
|
|
668
|
+
// does. Text that takes no letter spacing, such as zero-width glue or a zero-width
|
|
669
|
+
// space, fits like the line that still ends with the gap before it.
|
|
685
670
|
let fitAdvance = 0
|
|
686
|
-
if (letterSpacing !== 0 && !spaced && !breakAfter && kind !== CONTROL) {
|
|
671
|
+
if (letterSpacing !== 0 && !spaced && (!breakAfter || (kind === ZERO_WIDTH_BREAK && (flags & DROPPED) === 0)) && kind !== CONTROL) {
|
|
687
672
|
fitAdvance = gap + w
|
|
688
673
|
} else if (breakAfter ? kind === TAB : w !== 0 || kind === CONTROL) {
|
|
689
674
|
const contribution = w + (spaced ? letterSpacing : 0)
|
|
@@ -694,13 +679,14 @@ function walkPreparedComplexLines(
|
|
|
694
679
|
// text 2.8%, and Firefox 156 read within 1.5% either way (RESEARCH.md, Keeping Work
|
|
695
680
|
// Bounded, Work Done Only Where A Rule Applies; Decisions Log, 2026-10-08).
|
|
696
681
|
let hangs = (1 << kind & hangingKinds) !== 0
|
|
697
|
-
if (hangGoesOnPastEmpty && kind === ZERO_WIDTH_BREAK && hangEndSegmentIndex === i
|
|
682
|
+
if (hangGoesOnPastEmpty && kind === ZERO_WIDTH_BREAK && hangEndSegmentIndex === i &&
|
|
683
|
+
((flags & DROPPED) !== 0 || (hangStartWidth - hangEdgesWidth <= fitLimit && (items.hangEnds === null || items.hangEnds[i] === 0)))) hangs = true
|
|
698
684
|
if (hangs) {
|
|
699
685
|
if (hangEndSegmentIndex !== i) {
|
|
700
686
|
hangStartWidth = lineW - lineEndTrimmed + leadingSpacing
|
|
701
687
|
hangStays = false
|
|
702
688
|
if (items !== undefined && i > lineStartSegmentIndex && (segmentFlags[i - 1] & KIND_BITS) === OBJECT) {
|
|
703
|
-
hangStays = staysAfterObject(segmentFlags, i, hangingKinds)
|
|
689
|
+
hangStays = staysAfterObject(segmentFlags, segmentCount, i, hangingKinds)
|
|
704
690
|
// A run that goes down with its tab leaves from the break after the object.
|
|
705
691
|
if (!hangStays) {
|
|
706
692
|
pendingBreakSegmentIndex = i
|
|
@@ -794,8 +780,9 @@ function walkPreparedComplexLines(
|
|
|
794
780
|
}
|
|
795
781
|
// No engine lets content make a line narrower (findWholeLine in src/rich-inline.ts),
|
|
796
782
|
// so a segment of less than no advance, as a letter under a letter spacing more
|
|
797
|
-
// negative than it is wide, doesn't bring a line that overflows back.
|
|
798
|
-
|
|
783
|
+
// negative than it is wide, doesn't bring a line that overflows back. A zero-width
|
|
784
|
+
// space has no advance: what it fits with is the gap of the letter before it.
|
|
785
|
+
const narrowsOverflow = fitAdvance < 0 && kind !== ZERO_WIDTH_BREAK && lineW - lineEndTrimmed > fitLimit
|
|
799
786
|
overflows = (overflows || narrowsOverflow) && !(hangs && hangStays) && !placesEmptyObject
|
|
800
787
|
if (hangGoesOnPastEmpty && !overflows && hangEndSegmentIndex === i && kind === OBJECT && w === 0) {
|
|
801
788
|
hangEndSegmentIndex = i + 1
|
|
@@ -804,23 +791,63 @@ function walkPreparedComplexLines(
|
|
|
804
791
|
}
|
|
805
792
|
if (overflows) {
|
|
806
793
|
// A break segment hangs with the gap before it, after the content before
|
|
807
|
-
// it, which fits without its line-end trim. A collapsible space
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
//
|
|
814
|
-
//
|
|
794
|
+
// it, which fits without its line-end trim. A collapsible space hangs even
|
|
795
|
+
// after overflowing content that started the line, as the simple stepper
|
|
796
|
+
// does, and so does the zero-width break Firefox drops (DROPPED), there and
|
|
797
|
+
// after the start edge of its item's opening; a preserved space there starts
|
|
798
|
+
// the next line. A tab that doesn't hang goes to the next line, as text does.
|
|
799
|
+
//
|
|
800
|
+
// A zero-width space is content of no width, so it stays where the content
|
|
801
|
+
// before it fits, with the white space between them, which then ends no line,
|
|
802
|
+
// and with the gap after its last letter. Else the line ends before it and
|
|
803
|
+
// it starts the next (consumesAtLineStart). Blink sends text that follows
|
|
804
|
+
// content past the line's end to HandleOverflow, which ends the line at a
|
|
805
|
+
// break before that text (HandleText, line_breaker.cc:1355-1372; :4079-4305),
|
|
806
|
+
// and a line that trails spaces ends at the first character that is neither
|
|
807
|
+
// U+0020 nor U+0009 (HandleTrailingSpaces, :2447-2456, 2518-2523). WebKit fits
|
|
808
|
+
// it against what is left of the line, which isn't clamped at 0 (availableWidth,
|
|
809
|
+
// InlineLineBuilder.cpp:1172-1183, 1466), and keeps overflowing content only
|
|
810
|
+
// where it trims, hangs or is span tags alone (processOverflowingContent,
|
|
811
|
+
// InlineContentBreaker.cpp:160-199, 275-295). Gecko ends a line at its last
|
|
812
|
+
// break whose content fits, else at its first (BreakAndMeasureText,
|
|
813
|
+
// gfxTextRun.cpp:1091-1109), and a break comes before a zero-width space after
|
|
814
|
+
// white space (nsLineBreaker.cpp:321-330) and, by overflow-wrap, before any
|
|
815
|
+
// cluster on a line with no break yet (gfxTextRun.cpp:1068-1074). The break
|
|
816
|
+
// before it is the one the engine has there: after the white space, after an
|
|
817
|
+
// object, or, after a grapheme wider than its line, the one between graphemes
|
|
818
|
+
// that a line with no other break takes. A line-end trim doesn't keep it: a
|
|
819
|
+
// mark is halted and a U+3000 run hangs where the line ends after them, and
|
|
820
|
+
// with a zero-width space after them on the line they count whole (`字」` and
|
|
821
|
+
// one in 40px Hiragino Sans at 70px are two lines in Chrome 155, the second
|
|
822
|
+
// empty). Two places keep it past the line's end: after the start edge
|
|
823
|
+
// of its own item's opening, which the line took by the part it fits, as the
|
|
824
|
+
// engines keep a span's start with its first text; and right after an object
|
|
825
|
+
// in Gecko (EngineProfile, zeroWidthSpaceStaysAfterAtomic). The zero-width
|
|
826
|
+
// spaces after one that stays stay with it, as no break comes between two
|
|
827
|
+
// (UAX #14 LB7). Premise: widths measured apart. Blink kerns the letters on
|
|
828
|
+
// the two sides of a zero-width space, so a letter wider than its line by
|
|
829
|
+
// less than that kerning fits there, and Chrome keeps the zero-width space
|
|
830
|
+
// on its line (ENGINE_FOLLOWUPS.md, Line edges).
|
|
831
|
+
//
|
|
832
|
+
// Only a break segment can stay, so all of this is under that one test, and
|
|
833
|
+
// the profile's field is read where a zero-width space overflows right after
|
|
834
|
+
// an object: a line that ends at a segment that is no break pays nothing for
|
|
835
|
+
// the rule, and a walk reads nothing for it at its start (RESEARCH.md, Keeping
|
|
836
|
+
// Work Bounded, Work Done Only Where A Rule Applies).
|
|
815
837
|
const contentW = lineW - lineEndTrimmed
|
|
816
|
-
if (breakAfter
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
838
|
+
if (breakAfter) {
|
|
839
|
+
const afterObject = kind === ZERO_WIDTH_BREAK && (segmentFlags[i - 1] & KIND_BITS) === OBJECT
|
|
840
|
+
const stays = kind === ZERO_WIDTH_BREAK && (flags & DROPPED) === 0
|
|
841
|
+
? afterObject && (engineProfile.zeroWidthSpaceStaysAfterAtomic || (openingEdges !== null && openingEdges[i - 1] !== 0))
|
|
842
|
+
: (hangs || kind !== TAB) && (contentW <= fitLimit || (pendingBreakSegmentIndex < 0 && (kind === SPACE || kind === ZERO_WIDTH_BREAK)) || afterObject)
|
|
843
|
+
if (stays) {
|
|
844
|
+
endWidth = hangs ? hangStartWidth : contentW
|
|
845
|
+
lineW += advance
|
|
846
|
+
endSegmentIndex = i + 1
|
|
847
|
+
if (kind === ZERO_WIDTH_BREAK) while (endSegmentIndex < segmentCount && (segmentFlags[endSegmentIndex] & KIND_BITS) === ZERO_WIDTH_BREAK) endSegmentIndex++
|
|
848
|
+
endGraphemeIndex = 0
|
|
849
|
+
break decided
|
|
850
|
+
}
|
|
824
851
|
}
|
|
825
852
|
// Gecko's line returns from a tab that doesn't fit to its latest break (below), as
|
|
826
853
|
// BreakAndMeasureText keeps the last break whose line fits (gfxTextRun.cpp:1086-1101).
|
|
@@ -978,65 +1005,23 @@ function walkPreparedComplexLines(
|
|
|
978
1005
|
// A grapheme fits with the letter spacing after it.
|
|
979
1006
|
const fitAdvances = breakableFitAdvances[i]
|
|
980
1007
|
const fitCount = fitAdvances.length
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
// This branch reads a width for each question: in getFreshLineEnd(), the entry's
|
|
986
|
-
// admissionFit for whether the whole tail fits and the fresh prefixes for where
|
|
987
|
-
// the line ends where it doesn't; then the tail's fresh width, freshWhole, for the
|
|
988
|
-
// line that goes on. The line takes its first grapheme even where that doesn't
|
|
989
|
-
// fit, so it advances.
|
|
990
|
-
const end = getFreshLineEnd(entry , fillStart, fitCount, fitLimit)
|
|
991
|
-
hasContent = true
|
|
992
|
-
if (end <= fitCount) {
|
|
1008
|
+
for (let g = fillStart; g < fitCount; g++) {
|
|
1009
|
+
const baseGw = fitAdvances[g]
|
|
1010
|
+
if (!hasContent) {
|
|
1011
|
+
hasContent = true
|
|
993
1012
|
lineEndSegmentIndex = i
|
|
994
|
-
lineEndGraphemeIndex =
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
//
|
|
998
|
-
//
|
|
999
|
-
if (
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
lineEndSegmentIndex = i + 1
|
|
1007
|
-
lineEndGraphemeIndex = 0
|
|
1008
|
-
lineW = freshWhole - letterSpacing
|
|
1009
|
-
} else {
|
|
1010
|
-
for (let g = fillStart; g < fitCount; g++) {
|
|
1011
|
-
const baseGw = fitAdvances[g]
|
|
1012
|
-
if (!hasContent) {
|
|
1013
|
-
hasContent = true
|
|
1014
|
-
lineEndSegmentIndex = i
|
|
1015
|
-
lineEndGraphemeIndex = g + 1
|
|
1016
|
-
const startExtras = prepared.breakableLineStartExtras?.[i] ?? null
|
|
1017
|
-
lineW = startExtras === null ? baseGw : baseGw + startExtras[g]
|
|
1018
|
-
// A line that starts inside a padded rich-inline item, or fills its first
|
|
1019
|
-
// segment, pays the item's extraWidth (ParagraphSegmentData).
|
|
1020
|
-
if (g > 0 ? insideExtras !== null : fillExtras !== null) lineW += g > 0 ? insideExtras [i] : fillExtras [i]
|
|
1021
|
-
// A line that holds only this grapheme, overflowing, keeps the graphemes after
|
|
1022
|
-
// it that can't start a line, and ends.
|
|
1023
|
-
const end = lineW + letterSpacing > fitLimit
|
|
1024
|
-
? getOverflowingFirstGraphemeEnd(prepared, i, g, fitCount)
|
|
1025
|
-
: g + 1
|
|
1026
|
-
if (end > g + 1) {
|
|
1027
|
-
for (let k = g + 1; k < end; k++) lineW += fitAdvances[k] + letterSpacing
|
|
1028
|
-
endSegmentIndex = end === fitCount ? i + 1 : i
|
|
1029
|
-
endGraphemeIndex = end === fitCount ? 0 : end
|
|
1030
|
-
endWidth = lineW - lineEndTrimmed
|
|
1031
|
-
break decided
|
|
1032
|
-
}
|
|
1033
|
-
} else {
|
|
1034
|
-
const candidatePaintWidth = lineW + (baseGw + (g > fillStart ? letterSpacing : fillSpacing))
|
|
1035
|
-
if (candidatePaintWidth + letterSpacing > fitLimit) break decided
|
|
1036
|
-
lineW = candidatePaintWidth
|
|
1037
|
-
lineEndSegmentIndex = i
|
|
1038
|
-
lineEndGraphemeIndex = g + 1
|
|
1039
|
-
}
|
|
1013
|
+
lineEndGraphemeIndex = g + 1
|
|
1014
|
+
const startExtras = prepared.breakableLineStartExtras?.[i] ?? null
|
|
1015
|
+
lineW = startExtras === null ? baseGw : baseGw + startExtras[g]
|
|
1016
|
+
// A line that starts inside a padded rich-inline item, or fills its first
|
|
1017
|
+
// segment, pays the item's extraWidth (ParagraphSegmentData).
|
|
1018
|
+
if (g > 0 ? insideExtras !== null : fillExtras !== null) lineW += g > 0 ? insideExtras [i] : fillExtras [i]
|
|
1019
|
+
} else {
|
|
1020
|
+
const candidatePaintWidth = lineW + (baseGw + (g > fillStart ? letterSpacing : fillSpacing))
|
|
1021
|
+
if (candidatePaintWidth + letterSpacing > fitLimit) break decided
|
|
1022
|
+
lineW = candidatePaintWidth
|
|
1023
|
+
lineEndSegmentIndex = i
|
|
1024
|
+
lineEndGraphemeIndex = g + 1
|
|
1040
1025
|
}
|
|
1041
1026
|
}
|
|
1042
1027
|
if (hasContent && lineEndSegmentIndex === i && lineEndGraphemeIndex === fitCount) {
|
|
@@ -1063,7 +1048,7 @@ function walkPreparedComplexLines(
|
|
|
1063
1048
|
fitBreakSegmentIndex >= 0 &&
|
|
1064
1049
|
pendingBreakSegmentIndex === lineEndSegmentIndex &&
|
|
1065
1050
|
lineEndGraphemeIndex === 0 &&
|
|
1066
|
-
returnsFromUnfitHyphen(prepared, lineStartSegmentIndex, lineEndSegmentIndex, pendingBreakWidth, fitLimit)
|
|
1051
|
+
returnsFromUnfitHyphen(prepared, lineStartSegmentIndex, lineEndSegmentIndex, pendingBreakWidth, fitLimit, hyphenEndKeepsJoin)
|
|
1067
1052
|
) {
|
|
1068
1053
|
endSegmentIndex = fitBreakSegmentIndex
|
|
1069
1054
|
endGraphemeIndex = 0
|
|
@@ -1110,9 +1095,10 @@ function stepPreparedSimpleLineGeometry(
|
|
|
1110
1095
|
cursor ,
|
|
1111
1096
|
maxWidth ,
|
|
1112
1097
|
) {
|
|
1113
|
-
const { widths, segmentFlags, breakableFitAdvances,
|
|
1098
|
+
const { widths, segmentFlags, breakableFitAdvances, lineStartExtras, lineEndTrims, overflowLineEndTrims } = prepared
|
|
1099
|
+
const engineProfile = getEngineProfile()
|
|
1114
1100
|
// A negative width lays out as 0, as in the complex walker.
|
|
1115
|
-
const fitLimit = Math.max(0, maxWidth) +
|
|
1101
|
+
const fitLimit = Math.max(0, maxWidth) + engineProfile.lineFitEpsilon
|
|
1116
1102
|
const start = cursor.segmentIndex
|
|
1117
1103
|
|
|
1118
1104
|
// The first segment of the line, or the rest of one a line ended inside. One that
|
|
@@ -1124,23 +1110,11 @@ function stepPreparedSimpleLineGeometry(
|
|
|
1124
1110
|
const startTrim = overflowLineEndTrims !== null && startW - overflowLineEndTrims[start] <= fitLimit
|
|
1125
1111
|
? overflowLineEndTrims[start]
|
|
1126
1112
|
: startEndTrim
|
|
1127
|
-
// A line that starts inside a segment where it has fresh-line geometry takes the
|
|
1128
|
-
// tail, and goes on, or its fresh prefixes.
|
|
1129
|
-
const entry = cursor.graphemeIndex > 0 && entryGeometry !== null ? entryGeometry[start] : null
|
|
1130
1113
|
let lineW
|
|
1131
1114
|
// The line-end trim of the last segment, where only that trim let it fit. Every
|
|
1132
1115
|
// later segment overflows, so the line ends after it and paints that much less.
|
|
1133
1116
|
let endTrimmed = 0
|
|
1134
|
-
if (
|
|
1135
|
-
const segmentEnd = startAdvances .length
|
|
1136
|
-
const end = getFreshLineEnd(entry, cursor.graphemeIndex, segmentEnd, fitLimit)
|
|
1137
|
-
lineW = getSegmentEntryWidth(entry, cursor.graphemeIndex, Math.min(end, segmentEnd))
|
|
1138
|
-
if (end <= segmentEnd) {
|
|
1139
|
-
cursor.segmentIndex = end === segmentEnd ? start + 1 : start
|
|
1140
|
-
cursor.graphemeIndex = end === segmentEnd ? 0 : end
|
|
1141
|
-
return lineW
|
|
1142
|
-
}
|
|
1143
|
-
} else if (cursor.graphemeIndex > 0 || (startW - startTrim > fitLimit && startAdvances !== null)) {
|
|
1117
|
+
if (cursor.graphemeIndex > 0 || (startW - startTrim > fitLimit && startAdvances !== null)) {
|
|
1144
1118
|
const fitAdvances = startAdvances
|
|
1145
1119
|
const startExtras = prepared.breakableLineStartExtras?.[start] ?? null
|
|
1146
1120
|
let g = cursor.graphemeIndex + 1
|
|
@@ -1149,15 +1123,6 @@ function stepPreparedSimpleLineGeometry(
|
|
|
1149
1123
|
// the item's extraWidth (ParagraphSegmentData).
|
|
1150
1124
|
const items = prepared.items
|
|
1151
1125
|
if (items !== undefined && (g > 1 ? items.insideExtras !== null : items.fillExtras !== null)) lineW += g > 1 ? items.insideExtras [start] : items.fillExtras [start]
|
|
1152
|
-
// A line that holds only an overflowing grapheme keeps the graphemes after it
|
|
1153
|
-
// that can't start a line, and ends.
|
|
1154
|
-
const overflowEnd = lineW > fitLimit ? getOverflowingFirstGraphemeEnd(prepared, start, g - 1, fitAdvances.length) : g
|
|
1155
|
-
if (overflowEnd > g) {
|
|
1156
|
-
for (; g < overflowEnd; g++) lineW += fitAdvances[g]
|
|
1157
|
-
cursor.segmentIndex = g === fitAdvances.length ? start + 1 : start
|
|
1158
|
-
cursor.graphemeIndex = g === fitAdvances.length ? 0 : g
|
|
1159
|
-
return lineW
|
|
1160
|
-
}
|
|
1161
1126
|
for (; g < fitAdvances.length; g++) {
|
|
1162
1127
|
if (lineW + fitAdvances[g] > fitLimit) {
|
|
1163
1128
|
cursor.graphemeIndex = g
|
|
@@ -1171,16 +1136,26 @@ function stepPreparedSimpleLineGeometry(
|
|
|
1171
1136
|
}
|
|
1172
1137
|
|
|
1173
1138
|
// Every boundary of a fast-path handle is a break, so the line takes whole
|
|
1174
|
-
// segments until one overflows. An overflowing space
|
|
1175
|
-
//
|
|
1139
|
+
// segments until one overflows. An overflowing space hangs and ends the line. A
|
|
1140
|
+
// zero-width space, which is content of no width, overflows where the content
|
|
1141
|
+
// before it does, and then starts the next line, but right after an object where
|
|
1142
|
+
// the engine keeps it there (walkPreparedComplexLines has the rule). Before
|
|
1143
|
+
// other text, the line leaves out a space it ends with.
|
|
1176
1144
|
for (let i = start + 1; i < widths.length; i++) {
|
|
1177
1145
|
const w = widths[i]
|
|
1178
1146
|
const endTrim = lineEndTrims === null ? 0 : lineEndTrims[i]
|
|
1179
1147
|
if (lineW + w - endTrim > fitLimit) {
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1148
|
+
const kind = segmentFlags[i] & KIND_BITS
|
|
1149
|
+
let end = i
|
|
1150
|
+
if (kind === SPACE) {
|
|
1151
|
+
end = i + 1
|
|
1152
|
+
} else if (kind === ZERO_WIDTH_BREAK && engineProfile.zeroWidthSpaceStaysAfterAtomic && (segmentFlags[i - 1] & KIND_BITS) === OBJECT) {
|
|
1153
|
+
end = i + 1
|
|
1154
|
+
while (end < widths.length && (segmentFlags[end] & KIND_BITS) === ZERO_WIDTH_BREAK) end++
|
|
1155
|
+
}
|
|
1156
|
+
cursor.segmentIndex = end
|
|
1182
1157
|
cursor.graphemeIndex = 0
|
|
1183
|
-
return
|
|
1158
|
+
return end === i && breaksAfterKind(segmentFlags[i - 1] & KIND_BITS) ? lineW - widths[i - 1] : lineW - endTrimmed
|
|
1184
1159
|
}
|
|
1185
1160
|
lineW += w
|
|
1186
1161
|
endTrimmed = lineW > fitLimit ? endTrim : 0
|