pretext-pdf 1.0.8 → 1.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +945 -869
  2. package/README.md +1 -1
  3. package/UPSTREAM.md +112 -0
  4. package/dist/cli.js +19 -19
  5. package/dist/measure-text.d.ts +1 -1
  6. package/dist/measure-text.d.ts.map +1 -1
  7. package/dist/measure-text.js +1 -1
  8. package/dist/measure-text.js.map +1 -1
  9. package/dist/rich-text.js +1 -1
  10. package/dist/rich-text.js.map +1 -1
  11. package/dist/vendor/pretext/analysis.d.ts +35 -0
  12. package/dist/vendor/pretext/analysis.d.ts.map +1 -0
  13. package/dist/vendor/pretext/analysis.js +1162 -0
  14. package/dist/vendor/pretext/analysis.js.map +1 -0
  15. package/dist/vendor/pretext/bidi.d.ts +2 -0
  16. package/dist/vendor/pretext/bidi.d.ts.map +1 -0
  17. package/dist/vendor/pretext/bidi.js +176 -0
  18. package/dist/vendor/pretext/bidi.js.map +1 -0
  19. package/dist/vendor/pretext/generated/bidi-data.d.ts +5 -0
  20. package/dist/vendor/pretext/generated/bidi-data.d.ts.map +1 -0
  21. package/dist/vendor/pretext/generated/bidi-data.js +980 -0
  22. package/dist/vendor/pretext/generated/bidi-data.js.map +1 -0
  23. package/dist/vendor/pretext/layout.d.ts +75 -0
  24. package/dist/vendor/pretext/layout.d.ts.map +1 -0
  25. package/dist/vendor/pretext/layout.js +448 -0
  26. package/dist/vendor/pretext/layout.js.map +1 -0
  27. package/dist/vendor/pretext/line-break.d.ts +43 -0
  28. package/dist/vendor/pretext/line-break.d.ts.map +1 -0
  29. package/dist/vendor/pretext/line-break.js +908 -0
  30. package/dist/vendor/pretext/line-break.js.map +1 -0
  31. package/dist/vendor/pretext/line-text.d.ts +5 -0
  32. package/dist/vendor/pretext/line-text.d.ts.map +1 -0
  33. package/dist/vendor/pretext/line-text.js +69 -0
  34. package/dist/vendor/pretext/line-text.js.map +1 -0
  35. package/dist/vendor/pretext/measurement.d.ts +31 -0
  36. package/dist/vendor/pretext/measurement.d.ts.map +1 -0
  37. package/dist/vendor/pretext/measurement.js +251 -0
  38. package/dist/vendor/pretext/measurement.js.map +1 -0
  39. package/dist/vendor/pretext/rich-inline.d.ts +53 -0
  40. package/dist/vendor/pretext/rich-inline.d.ts.map +1 -0
  41. package/dist/vendor/pretext/rich-inline.js +327 -0
  42. package/dist/vendor/pretext/rich-inline.js.map +1 -0
  43. package/package.json +212 -208
@@ -0,0 +1,908 @@
1
+ import { getEngineProfile } from './measurement.js';
2
+ function consumesAtLineStart(kind) {
3
+ return kind === 'space' || kind === 'zero-width-break' || kind === 'soft-hyphen';
4
+ }
5
+ function breaksAfter(kind) {
6
+ return (kind === 'space' ||
7
+ kind === 'preserved-space' ||
8
+ kind === 'tab' ||
9
+ kind === 'zero-width-break' ||
10
+ kind === 'soft-hyphen');
11
+ }
12
+ function isSimpleCollapsibleSpace(kind) {
13
+ return kind === 'space';
14
+ }
15
+ function normalizeLineStartSegmentIndex(prepared, segmentIndex, endSegmentIndex = prepared.widths.length) {
16
+ while (segmentIndex < endSegmentIndex) {
17
+ const kind = prepared.kinds[segmentIndex];
18
+ if (!consumesAtLineStart(kind))
19
+ break;
20
+ segmentIndex++;
21
+ }
22
+ return segmentIndex;
23
+ }
24
+ function getTabAdvance(lineWidth, tabStopAdvance) {
25
+ if (tabStopAdvance <= 0)
26
+ return 0;
27
+ const remainder = lineWidth % tabStopAdvance;
28
+ if (Math.abs(remainder) <= 1e-6)
29
+ return tabStopAdvance;
30
+ return tabStopAdvance - remainder;
31
+ }
32
+ function getLeadingLetterSpacing(prepared, hasContent, segmentIndex) {
33
+ return (prepared.letterSpacing !== 0 &&
34
+ hasContent &&
35
+ prepared.spacingGraphemeCounts[segmentIndex] > 0)
36
+ ? prepared.letterSpacing
37
+ : 0;
38
+ }
39
+ function getLineEndContribution(leadingSpacing, segmentContribution) {
40
+ return segmentContribution === 0 ? 0 : leadingSpacing + segmentContribution;
41
+ }
42
+ function getTabTrailingLetterSpacing(prepared, segmentIndex) {
43
+ return (prepared.letterSpacing !== 0 &&
44
+ prepared.spacingGraphemeCounts[segmentIndex] > 0)
45
+ ? prepared.letterSpacing
46
+ : 0;
47
+ }
48
+ function getWholeSegmentFitContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth) {
49
+ const segmentContribution = kind === 'tab'
50
+ ? segmentWidth + getTabTrailingLetterSpacing(prepared, segmentIndex)
51
+ : prepared.lineEndFitAdvances[segmentIndex];
52
+ return getLineEndContribution(leadingSpacing, segmentContribution);
53
+ }
54
+ function getBreakOpportunityFitContribution(prepared, kind, segmentIndex, leadingSpacing) {
55
+ const segmentContribution = kind === 'tab' ? 0 : prepared.lineEndFitAdvances[segmentIndex];
56
+ return getLineEndContribution(leadingSpacing, segmentContribution);
57
+ }
58
+ function getLineEndPaintContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth) {
59
+ const segmentContribution = kind === 'tab' ? segmentWidth : prepared.lineEndPaintAdvances[segmentIndex];
60
+ return getLineEndContribution(leadingSpacing, segmentContribution);
61
+ }
62
+ function getBreakableGraphemeAdvance(prepared, hasContent, baseAdvance) {
63
+ return prepared.letterSpacing !== 0 && hasContent
64
+ ? baseAdvance + prepared.letterSpacing
65
+ : baseAdvance;
66
+ }
67
+ function getBreakableCandidateFitWidth(prepared, candidatePaintWidth) {
68
+ return prepared.letterSpacing === 0
69
+ ? candidatePaintWidth
70
+ : candidatePaintWidth + prepared.letterSpacing;
71
+ }
72
+ function fitSoftHyphenBreak(graphemeFitAdvances, initialWidth, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, letterSpacing) {
73
+ let fitCount = 0;
74
+ let fittedWidth = initialWidth;
75
+ while (fitCount < graphemeFitAdvances.length) {
76
+ const nextWidth = fittedWidth + graphemeFitAdvances[fitCount] + letterSpacing;
77
+ const nextLineWidth = fitCount + 1 < graphemeFitAdvances.length
78
+ ? nextWidth + discretionaryHyphenWidth
79
+ : nextWidth;
80
+ if (nextLineWidth > maxWidth + lineFitEpsilon)
81
+ break;
82
+ fittedWidth = nextWidth;
83
+ fitCount++;
84
+ }
85
+ return { fitCount, fittedWidth };
86
+ }
87
+ function findChunkIndexForStart(prepared, segmentIndex) {
88
+ if (prepared.chunkBySegment !== null && segmentIndex >= 0 && segmentIndex < prepared.chunkBySegment.length) {
89
+ const c = prepared.chunkBySegment[segmentIndex];
90
+ return c < prepared.chunks.length ? c : -1;
91
+ }
92
+ let lo = 0;
93
+ let hi = prepared.chunks.length;
94
+ while (lo < hi) {
95
+ const mid = Math.floor((lo + hi) / 2);
96
+ if (segmentIndex < prepared.chunks[mid].consumedEndSegmentIndex) {
97
+ hi = mid;
98
+ }
99
+ else {
100
+ lo = mid + 1;
101
+ }
102
+ }
103
+ return lo < prepared.chunks.length ? lo : -1;
104
+ }
105
+ function normalizeLineStartInChunk(prepared, chunkIndex, cursor) {
106
+ let segmentIndex = cursor.segmentIndex;
107
+ if (cursor.graphemeIndex > 0)
108
+ return chunkIndex;
109
+ const chunk = prepared.chunks[chunkIndex];
110
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex && segmentIndex === chunk.startSegmentIndex) {
111
+ cursor.segmentIndex = segmentIndex;
112
+ cursor.graphemeIndex = 0;
113
+ return chunkIndex;
114
+ }
115
+ if (segmentIndex < chunk.startSegmentIndex)
116
+ segmentIndex = chunk.startSegmentIndex;
117
+ segmentIndex = normalizeLineStartSegmentIndex(prepared, segmentIndex, chunk.endSegmentIndex);
118
+ if (segmentIndex < chunk.endSegmentIndex) {
119
+ cursor.segmentIndex = segmentIndex;
120
+ cursor.graphemeIndex = 0;
121
+ return chunkIndex;
122
+ }
123
+ if (chunk.consumedEndSegmentIndex >= prepared.widths.length)
124
+ return -1;
125
+ cursor.segmentIndex = chunk.consumedEndSegmentIndex;
126
+ cursor.graphemeIndex = 0;
127
+ return chunkIndex + 1;
128
+ }
129
+ function normalizeLineStartChunkIndex(prepared, cursor) {
130
+ if (cursor.segmentIndex >= prepared.widths.length)
131
+ return -1;
132
+ const chunkIndex = findChunkIndexForStart(prepared, cursor.segmentIndex);
133
+ if (chunkIndex < 0)
134
+ return -1;
135
+ return normalizeLineStartInChunk(prepared, chunkIndex, cursor);
136
+ }
137
+ function normalizeLineStartChunkIndexFromHint(prepared, chunkIndex, cursor) {
138
+ if (cursor.segmentIndex >= prepared.widths.length)
139
+ return -1;
140
+ let nextChunkIndex = chunkIndex;
141
+ while (nextChunkIndex < prepared.chunks.length &&
142
+ cursor.segmentIndex >= prepared.chunks[nextChunkIndex].consumedEndSegmentIndex) {
143
+ nextChunkIndex++;
144
+ }
145
+ if (nextChunkIndex >= prepared.chunks.length)
146
+ return -1;
147
+ return normalizeLineStartInChunk(prepared, nextChunkIndex, cursor);
148
+ }
149
+ export function normalizeLineStart(prepared, start) {
150
+ const cursor = {
151
+ segmentIndex: start.segmentIndex,
152
+ graphemeIndex: start.graphemeIndex,
153
+ };
154
+ const chunkIndex = normalizeLineStartChunkIndex(prepared, cursor);
155
+ return chunkIndex < 0 ? null : cursor;
156
+ }
157
+ // Specialized hot-path counter that mirrors walkPreparedLines break semantics
158
+ // without tracking cursors/widths. Must stay aligned — see layout.test.ts
159
+ // "countPreparedLines stays aligned with the walked line counter".
160
+ export function countPreparedLines(prepared, maxWidth) {
161
+ return walkPreparedLinesRaw(prepared, maxWidth);
162
+ }
163
+ function walkPreparedLinesSimple(prepared, maxWidth, onLine) {
164
+ const { widths, kinds, breakableFitAdvances } = prepared;
165
+ if (widths.length === 0)
166
+ return 0;
167
+ const engineProfile = getEngineProfile();
168
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
169
+ const fitLimit = maxWidth + lineFitEpsilon;
170
+ let lineCount = 0;
171
+ let lineW = 0;
172
+ let hasContent = false;
173
+ let lineStartSegmentIndex = 0;
174
+ let lineStartGraphemeIndex = 0;
175
+ let lineEndSegmentIndex = 0;
176
+ let lineEndGraphemeIndex = 0;
177
+ let pendingBreakSegmentIndex = -1;
178
+ let pendingBreakPaintWidth = 0;
179
+ function clearPendingBreak() {
180
+ pendingBreakSegmentIndex = -1;
181
+ pendingBreakPaintWidth = 0;
182
+ }
183
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
184
+ lineCount++;
185
+ onLine?.(width, lineStartSegmentIndex, lineStartGraphemeIndex, endSegmentIndex, endGraphemeIndex);
186
+ lineW = 0;
187
+ hasContent = false;
188
+ clearPendingBreak();
189
+ }
190
+ function startLineAtSegment(segmentIndex, width) {
191
+ hasContent = true;
192
+ lineStartSegmentIndex = segmentIndex;
193
+ lineStartGraphemeIndex = 0;
194
+ lineEndSegmentIndex = segmentIndex + 1;
195
+ lineEndGraphemeIndex = 0;
196
+ lineW = width;
197
+ }
198
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
199
+ hasContent = true;
200
+ lineStartSegmentIndex = segmentIndex;
201
+ lineStartGraphemeIndex = graphemeIndex;
202
+ lineEndSegmentIndex = segmentIndex;
203
+ lineEndGraphemeIndex = graphemeIndex + 1;
204
+ lineW = width;
205
+ }
206
+ function appendWholeSegment(segmentIndex, width) {
207
+ if (!hasContent) {
208
+ startLineAtSegment(segmentIndex, width);
209
+ return;
210
+ }
211
+ lineW += width;
212
+ lineEndSegmentIndex = segmentIndex + 1;
213
+ lineEndGraphemeIndex = 0;
214
+ }
215
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
216
+ const fitAdvances = breakableFitAdvances[segmentIndex];
217
+ for (let g = startGraphemeIndex; g < fitAdvances.length; g++) {
218
+ const gw = fitAdvances[g];
219
+ if (!hasContent) {
220
+ startLineAtGrapheme(segmentIndex, g, gw);
221
+ }
222
+ else if (lineW + gw > fitLimit) {
223
+ emitCurrentLine();
224
+ startLineAtGrapheme(segmentIndex, g, gw);
225
+ }
226
+ else {
227
+ lineW += gw;
228
+ lineEndSegmentIndex = segmentIndex;
229
+ lineEndGraphemeIndex = g + 1;
230
+ }
231
+ }
232
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === fitAdvances.length) {
233
+ lineEndSegmentIndex = segmentIndex + 1;
234
+ lineEndGraphemeIndex = 0;
235
+ }
236
+ }
237
+ let i = 0;
238
+ while (i < widths.length) {
239
+ if (!hasContent) {
240
+ i = normalizeLineStartSegmentIndex(prepared, i);
241
+ if (i >= widths.length)
242
+ break;
243
+ }
244
+ const w = widths[i];
245
+ const kind = kinds[i];
246
+ const breakAfter = breaksAfter(kind);
247
+ if (!hasContent) {
248
+ if (w > fitLimit && breakableFitAdvances[i] !== null) {
249
+ appendBreakableSegmentFrom(i, 0);
250
+ }
251
+ else {
252
+ startLineAtSegment(i, w);
253
+ }
254
+ if (breakAfter) {
255
+ pendingBreakSegmentIndex = i + 1;
256
+ pendingBreakPaintWidth = lineW - w;
257
+ }
258
+ i++;
259
+ continue;
260
+ }
261
+ const newW = lineW + w;
262
+ if (newW > fitLimit) {
263
+ // CSS behavior: trailing collapsible space hangs past the line edge
264
+ // without triggering a line break — matches countPreparedLinesSimple.
265
+ // Update lineEndSegmentIndex so reconstruction includes the hanging space.
266
+ if (isSimpleCollapsibleSpace(kind)) {
267
+ lineEndSegmentIndex = i + 1;
268
+ lineEndGraphemeIndex = 0;
269
+ i++;
270
+ continue;
271
+ }
272
+ if (breakAfter) {
273
+ appendWholeSegment(i, w);
274
+ emitCurrentLine(i + 1, 0, lineW - w);
275
+ i++;
276
+ continue;
277
+ }
278
+ if (pendingBreakSegmentIndex >= 0) {
279
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex ||
280
+ (lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0)) {
281
+ emitCurrentLine();
282
+ continue;
283
+ }
284
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
285
+ continue;
286
+ }
287
+ if (w > fitLimit && breakableFitAdvances[i] !== null) {
288
+ emitCurrentLine();
289
+ appendBreakableSegmentFrom(i, 0);
290
+ i++;
291
+ continue;
292
+ }
293
+ emitCurrentLine();
294
+ continue;
295
+ }
296
+ appendWholeSegment(i, w);
297
+ if (breakAfter) {
298
+ pendingBreakSegmentIndex = i + 1;
299
+ pendingBreakPaintWidth = lineW - w;
300
+ }
301
+ i++;
302
+ }
303
+ if (hasContent)
304
+ emitCurrentLine();
305
+ return lineCount;
306
+ }
307
+ export function walkPreparedLinesRaw(prepared, maxWidth, onLine) {
308
+ if (prepared.simpleLineWalkFastPath) {
309
+ return walkPreparedLinesSimple(prepared, maxWidth, onLine);
310
+ }
311
+ const { widths, kinds, breakableFitAdvances, discretionaryHyphenWidth, chunks, } = prepared;
312
+ if (widths.length === 0 || chunks.length === 0)
313
+ return 0;
314
+ const engineProfile = getEngineProfile();
315
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
316
+ const fitLimit = maxWidth + lineFitEpsilon;
317
+ let lineCount = 0;
318
+ let lineW = 0;
319
+ let hasContent = false;
320
+ let lineStartSegmentIndex = 0;
321
+ let lineStartGraphemeIndex = 0;
322
+ let lineEndSegmentIndex = 0;
323
+ let lineEndGraphemeIndex = 0;
324
+ let pendingBreakSegmentIndex = -1;
325
+ let pendingBreakFitWidth = 0;
326
+ let pendingBreakPaintWidth = 0;
327
+ let pendingBreakKind = null;
328
+ function clearPendingBreak() {
329
+ pendingBreakSegmentIndex = -1;
330
+ pendingBreakFitWidth = 0;
331
+ pendingBreakPaintWidth = 0;
332
+ pendingBreakKind = null;
333
+ }
334
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
335
+ lineCount++;
336
+ onLine?.(width, lineStartSegmentIndex, lineStartGraphemeIndex, endSegmentIndex, endGraphemeIndex);
337
+ lineW = 0;
338
+ hasContent = false;
339
+ clearPendingBreak();
340
+ }
341
+ function startLineAtSegment(segmentIndex, width) {
342
+ hasContent = true;
343
+ lineStartSegmentIndex = segmentIndex;
344
+ lineStartGraphemeIndex = 0;
345
+ lineEndSegmentIndex = segmentIndex + 1;
346
+ lineEndGraphemeIndex = 0;
347
+ lineW = width;
348
+ }
349
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
350
+ hasContent = true;
351
+ lineStartSegmentIndex = segmentIndex;
352
+ lineStartGraphemeIndex = graphemeIndex;
353
+ lineEndSegmentIndex = segmentIndex;
354
+ lineEndGraphemeIndex = graphemeIndex + 1;
355
+ lineW = width;
356
+ }
357
+ function appendWholeSegment(segmentIndex, advance) {
358
+ if (!hasContent) {
359
+ startLineAtSegment(segmentIndex, advance);
360
+ return;
361
+ }
362
+ lineW += advance;
363
+ lineEndSegmentIndex = segmentIndex + 1;
364
+ lineEndGraphemeIndex = 0;
365
+ }
366
+ function updatePendingBreakForWholeSegment(kind, breakAfter, segmentIndex, segmentWidth, leadingSpacing, advance) {
367
+ if (!breakAfter)
368
+ return;
369
+ const fitAdvance = getBreakOpportunityFitContribution(prepared, kind, segmentIndex, leadingSpacing);
370
+ const paintAdvance = getLineEndPaintContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth);
371
+ pendingBreakSegmentIndex = segmentIndex + 1;
372
+ pendingBreakFitWidth = lineW - advance + fitAdvance;
373
+ pendingBreakPaintWidth = lineW - advance + paintAdvance;
374
+ pendingBreakKind = kind;
375
+ }
376
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
377
+ const fitAdvances = breakableFitAdvances[segmentIndex];
378
+ for (let g = startGraphemeIndex; g < fitAdvances.length; g++) {
379
+ const baseGw = fitAdvances[g];
380
+ if (!hasContent) {
381
+ startLineAtGrapheme(segmentIndex, g, baseGw);
382
+ }
383
+ else {
384
+ const gw = getBreakableGraphemeAdvance(prepared, true, baseGw);
385
+ const candidatePaintWidth = lineW + gw;
386
+ if (getBreakableCandidateFitWidth(prepared, candidatePaintWidth) > fitLimit) {
387
+ emitCurrentLine();
388
+ startLineAtGrapheme(segmentIndex, g, baseGw);
389
+ }
390
+ else {
391
+ lineW = candidatePaintWidth;
392
+ lineEndSegmentIndex = segmentIndex;
393
+ lineEndGraphemeIndex = g + 1;
394
+ }
395
+ }
396
+ }
397
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === fitAdvances.length) {
398
+ lineEndSegmentIndex = segmentIndex + 1;
399
+ lineEndGraphemeIndex = 0;
400
+ }
401
+ }
402
+ function continueSoftHyphenBreakableSegment(segmentIndex) {
403
+ if (pendingBreakKind !== 'soft-hyphen')
404
+ return false;
405
+ const fitWidths = breakableFitAdvances[segmentIndex];
406
+ if (fitWidths == null)
407
+ return false;
408
+ const { fitCount, fittedWidth } = fitSoftHyphenBreak(fitWidths, lineW, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, prepared.letterSpacing);
409
+ if (fitCount === 0)
410
+ return false;
411
+ lineW = fittedWidth;
412
+ lineEndSegmentIndex = segmentIndex;
413
+ lineEndGraphemeIndex = fitCount;
414
+ clearPendingBreak();
415
+ if (fitCount === fitWidths.length) {
416
+ lineEndSegmentIndex = segmentIndex + 1;
417
+ lineEndGraphemeIndex = 0;
418
+ return true;
419
+ }
420
+ emitCurrentLine(segmentIndex, fitCount, fittedWidth + discretionaryHyphenWidth);
421
+ appendBreakableSegmentFrom(segmentIndex, fitCount);
422
+ return true;
423
+ }
424
+ function emitEmptyChunk(chunk) {
425
+ lineCount++;
426
+ onLine?.(0, chunk.startSegmentIndex, 0, chunk.consumedEndSegmentIndex, 0);
427
+ clearPendingBreak();
428
+ }
429
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
430
+ const chunk = chunks[chunkIndex];
431
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex) {
432
+ emitEmptyChunk(chunk);
433
+ continue;
434
+ }
435
+ hasContent = false;
436
+ lineW = 0;
437
+ lineStartSegmentIndex = chunk.startSegmentIndex;
438
+ lineStartGraphemeIndex = 0;
439
+ lineEndSegmentIndex = chunk.startSegmentIndex;
440
+ lineEndGraphemeIndex = 0;
441
+ clearPendingBreak();
442
+ let i = chunk.startSegmentIndex;
443
+ while (i < chunk.endSegmentIndex) {
444
+ if (!hasContent) {
445
+ i = normalizeLineStartSegmentIndex(prepared, i, chunk.endSegmentIndex);
446
+ if (i >= chunk.endSegmentIndex)
447
+ break;
448
+ }
449
+ const kind = kinds[i];
450
+ const breakAfter = breaksAfter(kind);
451
+ const leadingSpacing = getLeadingLetterSpacing(prepared, hasContent, i);
452
+ const w = kind === 'tab'
453
+ ? getTabAdvance(lineW + leadingSpacing, prepared.tabStopAdvance)
454
+ : widths[i];
455
+ const advance = leadingSpacing + w;
456
+ const fitAdvance = getWholeSegmentFitContribution(prepared, kind, i, leadingSpacing, w);
457
+ if (kind === 'soft-hyphen') {
458
+ if (hasContent) {
459
+ lineEndSegmentIndex = i + 1;
460
+ lineEndGraphemeIndex = 0;
461
+ pendingBreakSegmentIndex = i + 1;
462
+ pendingBreakFitWidth = lineW + discretionaryHyphenWidth;
463
+ pendingBreakPaintWidth = lineW + discretionaryHyphenWidth;
464
+ pendingBreakKind = kind;
465
+ }
466
+ i++;
467
+ continue;
468
+ }
469
+ if (!hasContent) {
470
+ if (fitAdvance > fitLimit && breakableFitAdvances[i] !== null) {
471
+ appendBreakableSegmentFrom(i, 0);
472
+ }
473
+ else {
474
+ startLineAtSegment(i, w);
475
+ }
476
+ updatePendingBreakForWholeSegment(kind, breakAfter, i, w, leadingSpacing, advance);
477
+ i++;
478
+ continue;
479
+ }
480
+ const newFitW = lineW + fitAdvance;
481
+ if (newFitW > fitLimit) {
482
+ const currentBreakFitWidth = lineW + getBreakOpportunityFitContribution(prepared, kind, i, leadingSpacing);
483
+ const currentBreakPaintWidth = lineW + getLineEndPaintContribution(prepared, kind, i, leadingSpacing, w);
484
+ if (pendingBreakKind === 'soft-hyphen' &&
485
+ engineProfile.preferEarlySoftHyphenBreak &&
486
+ pendingBreakFitWidth <= fitLimit) {
487
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
488
+ continue;
489
+ }
490
+ if (pendingBreakKind === 'soft-hyphen' && continueSoftHyphenBreakableSegment(i)) {
491
+ i++;
492
+ continue;
493
+ }
494
+ if (breakAfter && currentBreakFitWidth <= fitLimit) {
495
+ appendWholeSegment(i, advance);
496
+ emitCurrentLine(i + 1, 0, currentBreakPaintWidth);
497
+ i++;
498
+ continue;
499
+ }
500
+ if (pendingBreakSegmentIndex >= 0 && pendingBreakFitWidth <= fitLimit) {
501
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex ||
502
+ (lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0)) {
503
+ emitCurrentLine();
504
+ continue;
505
+ }
506
+ const nextSegmentIndex = pendingBreakSegmentIndex;
507
+ emitCurrentLine(nextSegmentIndex, 0, pendingBreakPaintWidth);
508
+ i = nextSegmentIndex;
509
+ continue;
510
+ }
511
+ if (fitAdvance > fitLimit && breakableFitAdvances[i] !== null) {
512
+ emitCurrentLine();
513
+ appendBreakableSegmentFrom(i, 0);
514
+ i++;
515
+ continue;
516
+ }
517
+ emitCurrentLine();
518
+ continue;
519
+ }
520
+ appendWholeSegment(i, advance);
521
+ updatePendingBreakForWholeSegment(kind, breakAfter, i, w, leadingSpacing, advance);
522
+ i++;
523
+ }
524
+ if (hasContent) {
525
+ const finalPaintWidth = pendingBreakSegmentIndex === chunk.consumedEndSegmentIndex
526
+ ? pendingBreakPaintWidth
527
+ : lineW;
528
+ emitCurrentLine(chunk.consumedEndSegmentIndex, 0, finalPaintWidth);
529
+ }
530
+ }
531
+ return lineCount;
532
+ }
533
+ export function walkPreparedLines(prepared, maxWidth, onLine) {
534
+ if (onLine === undefined)
535
+ return walkPreparedLinesRaw(prepared, maxWidth);
536
+ return walkPreparedLinesRaw(prepared, maxWidth, (width, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) => {
537
+ onLine({
538
+ startSegmentIndex,
539
+ startGraphemeIndex,
540
+ endSegmentIndex,
541
+ endGraphemeIndex,
542
+ width,
543
+ });
544
+ });
545
+ }
546
+ function stepPreparedChunkLineGeometry(prepared, cursor, chunkIndex, maxWidth) {
547
+ const chunk = prepared.chunks[chunkIndex];
548
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex) {
549
+ cursor.segmentIndex = chunk.consumedEndSegmentIndex;
550
+ cursor.graphemeIndex = 0;
551
+ return 0;
552
+ }
553
+ const { widths, kinds, breakableFitAdvances, discretionaryHyphenWidth, } = prepared;
554
+ const engineProfile = getEngineProfile();
555
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
556
+ const fitLimit = maxWidth + lineFitEpsilon;
557
+ let lineW = 0;
558
+ let hasContent = false;
559
+ let lineEndSegmentIndex = cursor.segmentIndex;
560
+ let lineEndGraphemeIndex = cursor.graphemeIndex;
561
+ let pendingBreakSegmentIndex = -1;
562
+ let pendingBreakFitWidth = 0;
563
+ let pendingBreakPaintWidth = 0;
564
+ let pendingBreakKind = null;
565
+ function clearPendingBreak() {
566
+ pendingBreakSegmentIndex = -1;
567
+ pendingBreakFitWidth = 0;
568
+ pendingBreakPaintWidth = 0;
569
+ pendingBreakKind = null;
570
+ }
571
+ function finishLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
572
+ if (!hasContent)
573
+ return null;
574
+ cursor.segmentIndex = endSegmentIndex;
575
+ cursor.graphemeIndex = endGraphemeIndex;
576
+ return width;
577
+ }
578
+ function startLineAtSegment(segmentIndex, width) {
579
+ hasContent = true;
580
+ lineEndSegmentIndex = segmentIndex + 1;
581
+ lineEndGraphemeIndex = 0;
582
+ lineW = width;
583
+ }
584
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
585
+ hasContent = true;
586
+ lineEndSegmentIndex = segmentIndex;
587
+ lineEndGraphemeIndex = graphemeIndex + 1;
588
+ lineW = width;
589
+ }
590
+ function appendWholeSegment(segmentIndex, advance) {
591
+ if (!hasContent) {
592
+ startLineAtSegment(segmentIndex, advance);
593
+ return;
594
+ }
595
+ lineW += advance;
596
+ lineEndSegmentIndex = segmentIndex + 1;
597
+ lineEndGraphemeIndex = 0;
598
+ }
599
+ function updatePendingBreakForWholeSegment(kind, breakAfter, segmentIndex, segmentWidth, leadingSpacing, advance) {
600
+ if (!breakAfter)
601
+ return;
602
+ const fitAdvance = getBreakOpportunityFitContribution(prepared, kind, segmentIndex, leadingSpacing);
603
+ const paintAdvance = getLineEndPaintContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth);
604
+ pendingBreakSegmentIndex = segmentIndex + 1;
605
+ pendingBreakFitWidth = lineW - advance + fitAdvance;
606
+ pendingBreakPaintWidth = lineW - advance + paintAdvance;
607
+ pendingBreakKind = kind;
608
+ }
609
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
610
+ const fitAdvances = breakableFitAdvances[segmentIndex];
611
+ for (let g = startGraphemeIndex; g < fitAdvances.length; g++) {
612
+ const baseGw = fitAdvances[g];
613
+ if (!hasContent) {
614
+ startLineAtGrapheme(segmentIndex, g, baseGw);
615
+ }
616
+ else {
617
+ const gw = getBreakableGraphemeAdvance(prepared, true, baseGw);
618
+ const candidatePaintWidth = lineW + gw;
619
+ if (getBreakableCandidateFitWidth(prepared, candidatePaintWidth) > fitLimit) {
620
+ return finishLine();
621
+ }
622
+ lineW = candidatePaintWidth;
623
+ lineEndSegmentIndex = segmentIndex;
624
+ lineEndGraphemeIndex = g + 1;
625
+ }
626
+ }
627
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === fitAdvances.length) {
628
+ lineEndSegmentIndex = segmentIndex + 1;
629
+ lineEndGraphemeIndex = 0;
630
+ }
631
+ return null;
632
+ }
633
+ function maybeFinishAtSoftHyphen(segmentIndex) {
634
+ if (pendingBreakKind !== 'soft-hyphen' || pendingBreakSegmentIndex < 0)
635
+ return null;
636
+ const fitWidths = breakableFitAdvances[segmentIndex] ?? null;
637
+ if (fitWidths !== null) {
638
+ const { fitCount, fittedWidth } = fitSoftHyphenBreak(fitWidths, lineW, maxWidth, lineFitEpsilon, discretionaryHyphenWidth, prepared.letterSpacing);
639
+ if (fitCount === fitWidths.length) {
640
+ lineW = fittedWidth;
641
+ lineEndSegmentIndex = segmentIndex + 1;
642
+ lineEndGraphemeIndex = 0;
643
+ clearPendingBreak();
644
+ return null;
645
+ }
646
+ if (fitCount > 0) {
647
+ return finishLine(segmentIndex, fitCount, fittedWidth + discretionaryHyphenWidth);
648
+ }
649
+ }
650
+ if (pendingBreakFitWidth <= fitLimit) {
651
+ return finishLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
652
+ }
653
+ return null;
654
+ }
655
+ for (let i = cursor.segmentIndex; i < chunk.endSegmentIndex; i++) {
656
+ const kind = kinds[i];
657
+ const breakAfter = breaksAfter(kind);
658
+ const startGraphemeIndex = i === cursor.segmentIndex ? cursor.graphemeIndex : 0;
659
+ const leadingSpacing = getLeadingLetterSpacing(prepared, hasContent, i);
660
+ const w = kind === 'tab'
661
+ ? getTabAdvance(lineW + leadingSpacing, prepared.tabStopAdvance)
662
+ : widths[i];
663
+ const advance = leadingSpacing + w;
664
+ const fitAdvance = getWholeSegmentFitContribution(prepared, kind, i, leadingSpacing, w);
665
+ if (kind === 'soft-hyphen' && startGraphemeIndex === 0) {
666
+ if (hasContent) {
667
+ lineEndSegmentIndex = i + 1;
668
+ lineEndGraphemeIndex = 0;
669
+ pendingBreakSegmentIndex = i + 1;
670
+ pendingBreakFitWidth = lineW + discretionaryHyphenWidth;
671
+ pendingBreakPaintWidth = lineW + discretionaryHyphenWidth;
672
+ pendingBreakKind = kind;
673
+ }
674
+ continue;
675
+ }
676
+ if (!hasContent) {
677
+ if (startGraphemeIndex > 0) {
678
+ const line = appendBreakableSegmentFrom(i, startGraphemeIndex);
679
+ if (line !== null)
680
+ return line;
681
+ }
682
+ else if (fitAdvance > fitLimit && breakableFitAdvances[i] !== null) {
683
+ const line = appendBreakableSegmentFrom(i, 0);
684
+ if (line !== null)
685
+ return line;
686
+ }
687
+ else {
688
+ startLineAtSegment(i, w);
689
+ }
690
+ updatePendingBreakForWholeSegment(kind, breakAfter, i, w, leadingSpacing, advance);
691
+ continue;
692
+ }
693
+ const newFitW = lineW + fitAdvance;
694
+ if (newFitW > fitLimit) {
695
+ const currentBreakFitWidth = lineW + getBreakOpportunityFitContribution(prepared, kind, i, leadingSpacing);
696
+ const currentBreakPaintWidth = lineW + getLineEndPaintContribution(prepared, kind, i, leadingSpacing, w);
697
+ if (pendingBreakKind === 'soft-hyphen' &&
698
+ engineProfile.preferEarlySoftHyphenBreak &&
699
+ pendingBreakFitWidth <= fitLimit) {
700
+ return finishLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
701
+ }
702
+ const softBreakLine = maybeFinishAtSoftHyphen(i);
703
+ if (softBreakLine !== null)
704
+ return softBreakLine;
705
+ if (breakAfter && currentBreakFitWidth <= fitLimit) {
706
+ appendWholeSegment(i, advance);
707
+ return finishLine(i + 1, 0, currentBreakPaintWidth);
708
+ }
709
+ if (pendingBreakSegmentIndex >= 0 && pendingBreakFitWidth <= fitLimit) {
710
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex ||
711
+ (lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0)) {
712
+ return finishLine();
713
+ }
714
+ return finishLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
715
+ }
716
+ if (fitAdvance > fitLimit && breakableFitAdvances[i] !== null) {
717
+ const currentLine = finishLine();
718
+ if (currentLine !== null)
719
+ return currentLine;
720
+ const line = appendBreakableSegmentFrom(i, 0);
721
+ if (line !== null)
722
+ return line;
723
+ }
724
+ return finishLine();
725
+ }
726
+ appendWholeSegment(i, advance);
727
+ updatePendingBreakForWholeSegment(kind, breakAfter, i, w, leadingSpacing, advance);
728
+ }
729
+ if (pendingBreakSegmentIndex === chunk.consumedEndSegmentIndex && lineEndGraphemeIndex === 0) {
730
+ return finishLine(chunk.consumedEndSegmentIndex, 0, pendingBreakPaintWidth);
731
+ }
732
+ return finishLine(chunk.consumedEndSegmentIndex, 0, lineW);
733
+ }
734
+ function stepPreparedSimpleLineGeometry(prepared, cursor, maxWidth) {
735
+ const { widths, kinds, breakableFitAdvances } = prepared;
736
+ const engineProfile = getEngineProfile();
737
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
738
+ const fitLimit = maxWidth + lineFitEpsilon;
739
+ let lineW = 0;
740
+ let hasContent = false;
741
+ let lineEndSegmentIndex = cursor.segmentIndex;
742
+ let lineEndGraphemeIndex = cursor.graphemeIndex;
743
+ let pendingBreakSegmentIndex = -1;
744
+ let pendingBreakPaintWidth = 0;
745
+ for (let i = cursor.segmentIndex; i < widths.length; i++) {
746
+ const kind = kinds[i];
747
+ const breakAfter = breaksAfter(kind);
748
+ const startGraphemeIndex = i === cursor.segmentIndex ? cursor.graphemeIndex : 0;
749
+ const breakableFitAdvance = breakableFitAdvances[i];
750
+ const w = widths[i];
751
+ if (!hasContent) {
752
+ if (startGraphemeIndex > 0 || (w > fitLimit && breakableFitAdvance !== null)) {
753
+ const fitAdvances = breakableFitAdvance;
754
+ const firstGraphemeWidth = fitAdvances[startGraphemeIndex];
755
+ hasContent = true;
756
+ lineW = firstGraphemeWidth;
757
+ lineEndSegmentIndex = i;
758
+ lineEndGraphemeIndex = startGraphemeIndex + 1;
759
+ for (let g = startGraphemeIndex + 1; g < fitAdvances.length; g++) {
760
+ const gw = fitAdvances[g];
761
+ if (lineW + gw > fitLimit) {
762
+ cursor.segmentIndex = lineEndSegmentIndex;
763
+ cursor.graphemeIndex = lineEndGraphemeIndex;
764
+ return lineW;
765
+ }
766
+ lineW += gw;
767
+ lineEndSegmentIndex = i;
768
+ lineEndGraphemeIndex = g + 1;
769
+ }
770
+ if (lineEndSegmentIndex === i && lineEndGraphemeIndex === fitAdvances.length) {
771
+ lineEndSegmentIndex = i + 1;
772
+ lineEndGraphemeIndex = 0;
773
+ }
774
+ }
775
+ else {
776
+ hasContent = true;
777
+ lineW = w;
778
+ lineEndSegmentIndex = i + 1;
779
+ lineEndGraphemeIndex = 0;
780
+ }
781
+ if (breakAfter) {
782
+ pendingBreakSegmentIndex = i + 1;
783
+ pendingBreakPaintWidth = lineW - w;
784
+ }
785
+ continue;
786
+ }
787
+ if (lineW + w > fitLimit) {
788
+ // CSS behavior: trailing collapsible space hangs past the line edge
789
+ // without triggering a line break — matches countPreparedLinesSimple.
790
+ // Update lineEndSegmentIndex so reconstruction includes the hanging space.
791
+ if (isSimpleCollapsibleSpace(kind)) {
792
+ lineEndSegmentIndex = i + 1;
793
+ lineEndGraphemeIndex = 0;
794
+ continue;
795
+ }
796
+ if (breakAfter) {
797
+ cursor.segmentIndex = i + 1;
798
+ cursor.graphemeIndex = 0;
799
+ return lineW;
800
+ }
801
+ if (pendingBreakSegmentIndex >= 0) {
802
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex ||
803
+ (lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0)) {
804
+ cursor.segmentIndex = lineEndSegmentIndex;
805
+ cursor.graphemeIndex = lineEndGraphemeIndex;
806
+ return lineW;
807
+ }
808
+ cursor.segmentIndex = pendingBreakSegmentIndex;
809
+ cursor.graphemeIndex = 0;
810
+ return pendingBreakPaintWidth;
811
+ }
812
+ cursor.segmentIndex = lineEndSegmentIndex;
813
+ cursor.graphemeIndex = lineEndGraphemeIndex;
814
+ return lineW;
815
+ }
816
+ lineW += w;
817
+ lineEndSegmentIndex = i + 1;
818
+ lineEndGraphemeIndex = 0;
819
+ if (breakAfter) {
820
+ pendingBreakSegmentIndex = i + 1;
821
+ pendingBreakPaintWidth = lineW - w;
822
+ }
823
+ }
824
+ if (!hasContent)
825
+ return null;
826
+ cursor.segmentIndex = lineEndSegmentIndex;
827
+ cursor.graphemeIndex = lineEndGraphemeIndex;
828
+ return lineW;
829
+ }
830
+ export function layoutNextLineRange(prepared, start, maxWidth) {
831
+ const end = {
832
+ segmentIndex: start.segmentIndex,
833
+ graphemeIndex: start.graphemeIndex,
834
+ };
835
+ const chunkIndex = normalizeLineStartChunkIndex(prepared, end);
836
+ if (chunkIndex < 0)
837
+ return null;
838
+ const lineStartSegmentIndex = end.segmentIndex;
839
+ const lineStartGraphemeIndex = end.graphemeIndex;
840
+ const width = prepared.simpleLineWalkFastPath
841
+ ? stepPreparedSimpleLineGeometry(prepared, end, maxWidth)
842
+ : stepPreparedChunkLineGeometry(prepared, end, chunkIndex, maxWidth);
843
+ if (width === null)
844
+ return null;
845
+ return {
846
+ startSegmentIndex: lineStartSegmentIndex,
847
+ startGraphemeIndex: lineStartGraphemeIndex,
848
+ endSegmentIndex: end.segmentIndex,
849
+ endGraphemeIndex: end.graphemeIndex,
850
+ width,
851
+ };
852
+ }
853
+ export function stepPreparedLineGeometry(prepared, cursor, maxWidth) {
854
+ const chunkIndex = normalizeLineStartChunkIndex(prepared, cursor);
855
+ if (chunkIndex < 0)
856
+ return null;
857
+ if (prepared.simpleLineWalkFastPath) {
858
+ return stepPreparedSimpleLineGeometry(prepared, cursor, maxWidth);
859
+ }
860
+ return stepPreparedChunkLineGeometry(prepared, cursor, chunkIndex, maxWidth);
861
+ }
862
+ export function measurePreparedLineGeometry(prepared, maxWidth) {
863
+ if (prepared.widths.length === 0) {
864
+ return {
865
+ lineCount: 0,
866
+ maxLineWidth: 0,
867
+ };
868
+ }
869
+ const cursor = {
870
+ segmentIndex: 0,
871
+ graphemeIndex: 0,
872
+ };
873
+ let lineCount = 0;
874
+ let maxLineWidth = 0;
875
+ if (!prepared.simpleLineWalkFastPath) {
876
+ let chunkIndex = normalizeLineStartChunkIndex(prepared, cursor);
877
+ while (chunkIndex >= 0) {
878
+ const lineWidth = stepPreparedChunkLineGeometry(prepared, cursor, chunkIndex, maxWidth);
879
+ if (lineWidth === null) {
880
+ return {
881
+ lineCount,
882
+ maxLineWidth,
883
+ };
884
+ }
885
+ lineCount++;
886
+ if (lineWidth > maxLineWidth)
887
+ maxLineWidth = lineWidth;
888
+ chunkIndex = normalizeLineStartChunkIndexFromHint(prepared, chunkIndex, cursor);
889
+ }
890
+ return {
891
+ lineCount,
892
+ maxLineWidth,
893
+ };
894
+ }
895
+ while (true) {
896
+ const lineWidth = stepPreparedLineGeometry(prepared, cursor, maxWidth);
897
+ if (lineWidth === null) {
898
+ return {
899
+ lineCount,
900
+ maxLineWidth,
901
+ };
902
+ }
903
+ lineCount++;
904
+ if (lineWidth > maxLineWidth)
905
+ maxLineWidth = lineWidth;
906
+ }
907
+ }
908
+ //# sourceMappingURL=line-break.js.map