rapier-markdown-kit 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 (55) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +102 -0
  3. package/dist/agent/vendor/pretext/LICENSE +21 -0
  4. package/dist/agent/vendor/pretext/NOTICE +15 -0
  5. package/dist/agent/vendor/pretext/SOURCE.json +85 -0
  6. package/dist/agent/vendor/pretext/analysis.js +1234 -0
  7. package/dist/agent/vendor/pretext/bidi.js +151 -0
  8. package/dist/agent/vendor/pretext/generated/bidi-data.js +3831 -0
  9. package/dist/agent/vendor/pretext/layout.js +308 -0
  10. package/dist/agent/vendor/pretext/line-break.js +636 -0
  11. package/dist/agent/vendor/pretext/line-text.js +44 -0
  12. package/dist/agent/vendor/pretext/measurement.js +189 -0
  13. package/dist/agent/vendor/pretext/package.json +3 -0
  14. package/dist/agent/vendor/pretext/rich-inline.js +291 -0
  15. package/dist/agent/will.mjs +167 -0
  16. package/dist/kit/assets.mjs +3 -0
  17. package/dist/kit/conformance/01-inline.expected.json +82 -0
  18. package/dist/kit/conformance/01-inline.md +10 -0
  19. package/dist/kit/conformance/02-width-x-align.expected.json +83 -0
  20. package/dist/kit/conformance/02-width-x-align.md +12 -0
  21. package/dist/kit/conformance/03-wrap-around-silhouette.expected.json +177 -0
  22. package/dist/kit/conformance/03-wrap-around-silhouette.md +12 -0
  23. package/dist/kit/conformance/04-wrap-box.expected.json +173 -0
  24. package/dist/kit/conformance/04-wrap-box.md +12 -0
  25. package/dist/kit/conformance/05-behind.expected.json +122 -0
  26. package/dist/kit/conformance/05-behind.md +12 -0
  27. package/dist/kit/conformance/06-front.expected.json +122 -0
  28. package/dist/kit/conformance/06-front.md +12 -0
  29. package/dist/kit/conformance/07-rotate-raster.expected.json +179 -0
  30. package/dist/kit/conformance/07-rotate-raster.md +12 -0
  31. package/dist/kit/conformance/08-drawing-rotation.expected.json +188 -0
  32. package/dist/kit/conformance/08-drawing-rotation.md +12 -0
  33. package/dist/kit/conformance/09-drawing-ring-interior.expected.json +229 -0
  34. package/dist/kit/conformance/09-drawing-ring-interior.md +12 -0
  35. package/dist/kit/conformance/10-both-sides.expected.json +221 -0
  36. package/dist/kit/conformance/10-both-sides.md +10 -0
  37. package/dist/kit/conformance/11-neighbour-skips-image.expected.json +160 -0
  38. package/dist/kit/conformance/11-neighbour-skips-image.md +13 -0
  39. package/dist/kit/conformance/12-heading-barrier.expected.json +164 -0
  40. package/dist/kit/conformance/12-heading-barrier.md +12 -0
  41. package/dist/kit/conformance/13-rtl-text.expected.json +179 -0
  42. package/dist/kit/conformance/13-rtl-text.md +8 -0
  43. package/dist/kit/conformance/README.md +99 -0
  44. package/dist/kit/conformance/measurer.mjs +0 -0
  45. package/dist/kit/conformance/run.mjs +123 -0
  46. package/dist/kit/index.mjs +6 -0
  47. package/dist/kit/layout.mjs +2 -0
  48. package/dist/kit/marks.mjs +2 -0
  49. package/dist/kit/model.mjs +3 -0
  50. package/dist/kit/will.mjs +2 -0
  51. package/dist/layout/model.mjs +364 -0
  52. package/dist/spec/md-assets.mjs +323 -0
  53. package/dist/spec/md-layout.mjs +117 -0
  54. package/dist/spec/md-marks.mjs +78 -0
  55. package/package.json +33 -0
@@ -0,0 +1,636 @@
1
+ /*! Pretext 0.0.9 | MIT | Copyright (c) 2026 Pretext contributors */
2
+ import { getEngineProfile } from './measurement.js';
3
+ export function isDiscretionaryLineEnd(kinds, endSegmentIndex, endGraphemeIndex) {
4
+ return endGraphemeIndex === 0 && endSegmentIndex > 0 && endSegmentIndex < kinds.length && kinds[endSegmentIndex - 1] === 'soft-hyphen';
5
+ }
6
+ function consumesAtLineStart(kind) {
7
+ return kind === 'space' || kind === 'zero-width-break' || kind === 'soft-hyphen';
8
+ }
9
+ function breaksAfter(kind) {
10
+ return kind === 'space' || kind === 'preserved-space' || kind === 'tab' || kind === 'zero-width-break' || kind === 'soft-hyphen';
11
+ }
12
+ function normalizeLineStartSegmentIndex(prepared, segmentIndex, endSegmentIndex = prepared.widths.length) {
13
+ while(segmentIndex < endSegmentIndex){
14
+ const kind = prepared.kinds[segmentIndex];
15
+ if (!consumesAtLineStart(kind)) break;
16
+ segmentIndex++;
17
+ }
18
+ return segmentIndex;
19
+ }
20
+ function getTabAdvance(lineWidth, tabStopAdvance) {
21
+ if (tabStopAdvance <= 0) return 0;
22
+ const remainder = lineWidth % tabStopAdvance;
23
+ if (Math.abs(remainder) <= 1e-6) return tabStopAdvance;
24
+ return tabStopAdvance - remainder;
25
+ }
26
+ function getLeadingLetterSpacing(prepared, hasContent, segmentIndex) {
27
+ return prepared.letterSpacing !== 0 && hasContent && prepared.spacingGraphemeCounts[segmentIndex] > 0 ? prepared.letterSpacing : 0;
28
+ }
29
+ function getLineEndContribution(leadingSpacing, segmentContribution) {
30
+ return segmentContribution === 0 ? 0 : leadingSpacing + segmentContribution;
31
+ }
32
+ function getTabTrailingLetterSpacing(prepared, segmentIndex) {
33
+ return prepared.letterSpacing !== 0 && prepared.spacingGraphemeCounts[segmentIndex] > 0 ? prepared.letterSpacing : 0;
34
+ }
35
+ function getWholeSegmentFitContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth) {
36
+ const segmentContribution = kind === 'tab' ? segmentWidth + getTabTrailingLetterSpacing(prepared, segmentIndex) : prepared.lineEndFitAdvances[segmentIndex];
37
+ return getLineEndContribution(leadingSpacing, segmentContribution);
38
+ }
39
+ function getBreakOpportunityFitContribution(prepared, kind, segmentIndex, leadingSpacing) {
40
+ const segmentContribution = kind === 'tab' ? 0 : prepared.lineEndFitAdvances[segmentIndex];
41
+ return getLineEndContribution(leadingSpacing, segmentContribution);
42
+ }
43
+ function getLineEndPaintContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth) {
44
+ const segmentContribution = kind === 'tab' ? segmentWidth : prepared.lineEndPaintAdvances[segmentIndex];
45
+ return getLineEndContribution(leadingSpacing, segmentContribution);
46
+ }
47
+ function getBreakableGraphemeAdvance(prepared, hasContent, baseAdvance) {
48
+ return prepared.letterSpacing !== 0 && hasContent ? baseAdvance + prepared.letterSpacing : baseAdvance;
49
+ }
50
+ function getBreakableCandidateFitWidth(prepared, candidatePaintWidth) {
51
+ return prepared.letterSpacing === 0 ? candidatePaintWidth : candidatePaintWidth + prepared.letterSpacing;
52
+ }
53
+ function getNextPreferredBreakIndex(preferredBreaks, preferredBreakIndex, graphemeEnd) {
54
+ let lo = preferredBreakIndex;
55
+ if (lo >= preferredBreaks.length || preferredBreaks[lo] >= graphemeEnd) return lo;
56
+ let hi = preferredBreaks.length;
57
+ lo++;
58
+ while(lo < hi){
59
+ const mid = Math.floor((lo + hi) / 2);
60
+ if (preferredBreaks[mid] < graphemeEnd) lo = mid + 1;
61
+ else hi = mid;
62
+ }
63
+ return lo;
64
+ }
65
+ function getTerminalLetterSpacing(prepared, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
66
+ if (prepared.letterSpacing === 0) return 0;
67
+ if (endGraphemeIndex > 0) {
68
+ return prepared.spacingGraphemeCounts[endSegmentIndex] > 0 ? prepared.letterSpacing : 0;
69
+ }
70
+ if (isDiscretionaryLineEnd(prepared.kinds, endSegmentIndex, endGraphemeIndex)) return 0;
71
+ for(let i = endSegmentIndex - 1; i >= startSegmentIndex; i--){
72
+ const kind = prepared.kinds[i];
73
+ if (kind === 'space' || kind === 'zero-width-break' || kind === 'hard-break' || kind === 'soft-hyphen') continue;
74
+ if (i === startSegmentIndex && startGraphemeIndex > 0) {
75
+ return prepared.letterSpacing;
76
+ }
77
+ return prepared.spacingGraphemeCounts[i] > 0 ? prepared.letterSpacing : 0;
78
+ }
79
+ return 0;
80
+ }
81
+ function finalizeLinePaintWidth(prepared, width, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
82
+ return width + getTerminalLetterSpacing(prepared, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex);
83
+ }
84
+ function findChunkIndexForStart(prepared, segmentIndex) {
85
+ let lo = 0;
86
+ let hi = prepared.chunks.length;
87
+ while(lo < hi){
88
+ const mid = Math.floor((lo + hi) / 2);
89
+ if (segmentIndex < prepared.chunks[mid].consumedEndSegmentIndex) {
90
+ hi = mid;
91
+ } else {
92
+ lo = mid + 1;
93
+ }
94
+ }
95
+ return lo < prepared.chunks.length ? lo : -1;
96
+ }
97
+ function normalizeLineStartInChunk(prepared, chunkIndex, cursor) {
98
+ let segmentIndex = cursor.segmentIndex;
99
+ if (cursor.graphemeIndex > 0) return chunkIndex;
100
+ for(let currentChunkIndex = chunkIndex; currentChunkIndex < prepared.chunks.length; currentChunkIndex++){
101
+ const chunk = prepared.chunks[currentChunkIndex];
102
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex && segmentIndex === chunk.startSegmentIndex) {
103
+ cursor.segmentIndex = segmentIndex;
104
+ cursor.graphemeIndex = 0;
105
+ return currentChunkIndex;
106
+ }
107
+ if (segmentIndex < chunk.startSegmentIndex) segmentIndex = chunk.startSegmentIndex;
108
+ segmentIndex = normalizeLineStartSegmentIndex(prepared, segmentIndex, chunk.endSegmentIndex);
109
+ if (segmentIndex < chunk.endSegmentIndex) {
110
+ cursor.segmentIndex = segmentIndex;
111
+ cursor.graphemeIndex = 0;
112
+ return currentChunkIndex;
113
+ }
114
+ if (chunk.consumedEndSegmentIndex >= prepared.widths.length) return -1;
115
+ segmentIndex = chunk.consumedEndSegmentIndex;
116
+ cursor.segmentIndex = segmentIndex;
117
+ cursor.graphemeIndex = 0;
118
+ }
119
+ return -1;
120
+ }
121
+ export function normalizePreparedLineStart(prepared, cursor) {
122
+ if (cursor.segmentIndex >= prepared.widths.length) return -1;
123
+ const chunkIndex = findChunkIndexForStart(prepared, cursor.segmentIndex);
124
+ if (chunkIndex < 0) return -1;
125
+ return normalizeLineStartInChunk(prepared, chunkIndex, cursor);
126
+ }
127
+ function normalizeLineStartChunkIndexFromHint(prepared, chunkIndex, cursor) {
128
+ if (cursor.segmentIndex >= prepared.widths.length) return -1;
129
+ let nextChunkIndex = chunkIndex;
130
+ while(nextChunkIndex < prepared.chunks.length && cursor.segmentIndex >= prepared.chunks[nextChunkIndex].consumedEndSegmentIndex){
131
+ nextChunkIndex++;
132
+ }
133
+ if (nextChunkIndex >= prepared.chunks.length) return -1;
134
+ return normalizeLineStartInChunk(prepared, nextChunkIndex, cursor);
135
+ }
136
+ export function countPreparedLines(prepared, maxWidth) {
137
+ return walkPreparedLinesRaw(prepared, maxWidth);
138
+ }
139
+ function walkPreparedLinesSimple(prepared, maxWidth, onLine) {
140
+ const { widths, kinds, breakableFitAdvances, breakablePreferredBreaks } = prepared;
141
+ if (widths.length === 0) return 0;
142
+ const engineProfile = getEngineProfile();
143
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
144
+ const fitLimit = maxWidth + lineFitEpsilon;
145
+ let lineCount = 0;
146
+ let lineW = 0;
147
+ let hasContent = false;
148
+ let lineStartSegmentIndex = 0;
149
+ let lineStartGraphemeIndex = 0;
150
+ let lineEndSegmentIndex = 0;
151
+ let lineEndGraphemeIndex = 0;
152
+ let pendingBreakSegmentIndex = -1;
153
+ let pendingBreakPaintWidth = 0;
154
+ function clearPendingBreak() {
155
+ pendingBreakSegmentIndex = -1;
156
+ pendingBreakPaintWidth = 0;
157
+ }
158
+ function emitCurrentLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = lineW) {
159
+ lineCount++;
160
+ onLine?.(width, lineStartSegmentIndex, lineStartGraphemeIndex, endSegmentIndex, endGraphemeIndex);
161
+ lineW = 0;
162
+ hasContent = false;
163
+ clearPendingBreak();
164
+ }
165
+ function startLineAtSegment(segmentIndex, width) {
166
+ hasContent = true;
167
+ lineStartSegmentIndex = segmentIndex;
168
+ lineStartGraphemeIndex = 0;
169
+ lineEndSegmentIndex = segmentIndex + 1;
170
+ lineEndGraphemeIndex = 0;
171
+ lineW = width;
172
+ }
173
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
174
+ hasContent = true;
175
+ lineStartSegmentIndex = segmentIndex;
176
+ lineStartGraphemeIndex = graphemeIndex;
177
+ lineEndSegmentIndex = segmentIndex;
178
+ lineEndGraphemeIndex = graphemeIndex + 1;
179
+ lineW = width;
180
+ }
181
+ function appendWholeSegment(segmentIndex, width) {
182
+ if (!hasContent) {
183
+ startLineAtSegment(segmentIndex, width);
184
+ return;
185
+ }
186
+ lineW += width;
187
+ lineEndSegmentIndex = segmentIndex + 1;
188
+ lineEndGraphemeIndex = 0;
189
+ }
190
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
191
+ const fitAdvances = breakableFitAdvances[segmentIndex];
192
+ const preferredBreaks = breakablePreferredBreaks[segmentIndex] ?? null;
193
+ let preferredBreakIndex = preferredBreaks === null ? -1 : getNextPreferredBreakIndex(preferredBreaks, 0, startGraphemeIndex + 1);
194
+ let lastPreferredBreakEnd = -1;
195
+ let lastPreferredBreakWidth = 0;
196
+ let g = startGraphemeIndex;
197
+ while(g < fitAdvances.length){
198
+ const gw = fitAdvances[g];
199
+ if (!hasContent) {
200
+ startLineAtGrapheme(segmentIndex, g, gw);
201
+ } else if (lineW + gw > fitLimit) {
202
+ if (preferredBreaks !== null && lastPreferredBreakEnd > startGraphemeIndex) {
203
+ emitCurrentLine(segmentIndex, lastPreferredBreakEnd, lastPreferredBreakWidth);
204
+ g = lastPreferredBreakEnd;
205
+ preferredBreakIndex = getNextPreferredBreakIndex(preferredBreaks, preferredBreakIndex, g + 1);
206
+ lastPreferredBreakEnd = -1;
207
+ lastPreferredBreakWidth = 0;
208
+ continue;
209
+ }
210
+ emitCurrentLine();
211
+ startLineAtGrapheme(segmentIndex, g, gw);
212
+ } else {
213
+ lineW += gw;
214
+ lineEndSegmentIndex = segmentIndex;
215
+ lineEndGraphemeIndex = g + 1;
216
+ }
217
+ const graphemeEnd = g + 1;
218
+ if (preferredBreaks !== null && preferredBreaks[preferredBreakIndex] === graphemeEnd) {
219
+ lastPreferredBreakEnd = graphemeEnd;
220
+ lastPreferredBreakWidth = lineW;
221
+ preferredBreakIndex++;
222
+ }
223
+ g++;
224
+ }
225
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === fitAdvances.length) {
226
+ lineEndSegmentIndex = segmentIndex + 1;
227
+ lineEndGraphemeIndex = 0;
228
+ }
229
+ }
230
+ let i = 0;
231
+ while(i < widths.length){
232
+ if (!hasContent) {
233
+ i = normalizeLineStartSegmentIndex(prepared, i);
234
+ if (i >= widths.length) break;
235
+ }
236
+ const w = widths[i];
237
+ const kind = kinds[i];
238
+ const breakAfter = breaksAfter(kind);
239
+ if (!hasContent) {
240
+ if (w > fitLimit && breakableFitAdvances[i] !== null) {
241
+ appendBreakableSegmentFrom(i, 0);
242
+ } else {
243
+ startLineAtSegment(i, w);
244
+ }
245
+ if (breakAfter) {
246
+ pendingBreakSegmentIndex = i + 1;
247
+ pendingBreakPaintWidth = lineW - w;
248
+ }
249
+ i++;
250
+ continue;
251
+ }
252
+ const newW = lineW + w;
253
+ if (newW > fitLimit) {
254
+ if (breakAfter) {
255
+ appendWholeSegment(i, w);
256
+ emitCurrentLine(i + 1, 0, lineW - w);
257
+ i++;
258
+ continue;
259
+ }
260
+ if (pendingBreakSegmentIndex >= 0) {
261
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex || lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0) {
262
+ emitCurrentLine();
263
+ continue;
264
+ }
265
+ emitCurrentLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
266
+ continue;
267
+ }
268
+ if (w > fitLimit && breakableFitAdvances[i] !== null) {
269
+ emitCurrentLine();
270
+ appendBreakableSegmentFrom(i, 0);
271
+ i++;
272
+ continue;
273
+ }
274
+ emitCurrentLine();
275
+ continue;
276
+ }
277
+ appendWholeSegment(i, w);
278
+ if (breakAfter) {
279
+ pendingBreakSegmentIndex = i + 1;
280
+ pendingBreakPaintWidth = lineW - w;
281
+ }
282
+ i++;
283
+ }
284
+ if (hasContent) emitCurrentLine();
285
+ return lineCount;
286
+ }
287
+ export function walkPreparedLinesRaw(prepared, maxWidth, onLine) {
288
+ if (prepared.simpleLineWalkFastPath) return walkPreparedLinesSimple(prepared, maxWidth, onLine);
289
+ const cursor = {
290
+ segmentIndex: 0,
291
+ graphemeIndex: 0
292
+ };
293
+ const chunkIndex = normalizePreparedLineStart(prepared, cursor);
294
+ return walkPreparedComplexLines(prepared, cursor, chunkIndex, maxWidth, onLine).lineCount;
295
+ }
296
+ function stepPreparedChunkLineGeometry(prepared, cursor, chunkIndex, maxWidth) {
297
+ return walkPreparedComplexLines(prepared, cursor, chunkIndex, maxWidth, undefined, 1).lastLineWidth;
298
+ }
299
+ function walkPreparedComplexLines(prepared, cursor, chunkIndex, maxWidth, onLine, lineLimit = Number.POSITIVE_INFINITY) {
300
+ const { widths, kinds, breakableFitAdvances, breakablePreferredBreaks, discretionaryHyphenWidth } = prepared;
301
+ const engineProfile = getEngineProfile();
302
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
303
+ const fitLimit = maxWidth + lineFitEpsilon;
304
+ let lineStartSegmentIndex;
305
+ let lineStartGraphemeIndex;
306
+ let lineW;
307
+ let hasContent;
308
+ let lineEndSegmentIndex;
309
+ let lineEndGraphemeIndex;
310
+ let pendingBreakSegmentIndex;
311
+ let pendingBreakFitWidth;
312
+ let pendingBreakPaintWidth;
313
+ let pendingBreakKind;
314
+ function getCurrentLinePaintWidth() {
315
+ return pendingBreakKind === 'soft-hyphen' && pendingBreakSegmentIndex === lineEndSegmentIndex && lineEndGraphemeIndex === 0 ? pendingBreakPaintWidth : lineW;
316
+ }
317
+ function finishLine(endSegmentIndex = lineEndSegmentIndex, endGraphemeIndex = lineEndGraphemeIndex, width = getCurrentLinePaintWidth()) {
318
+ if (!hasContent) return null;
319
+ cursor.segmentIndex = endSegmentIndex;
320
+ cursor.graphemeIndex = endGraphemeIndex;
321
+ return finalizeLinePaintWidth(prepared, width, lineStartSegmentIndex, lineStartGraphemeIndex, endSegmentIndex, endGraphemeIndex);
322
+ }
323
+ function startLineAtSegment(segmentIndex, width) {
324
+ hasContent = true;
325
+ lineEndSegmentIndex = segmentIndex + 1;
326
+ lineEndGraphemeIndex = 0;
327
+ lineW = width;
328
+ }
329
+ function startLineAtGrapheme(segmentIndex, graphemeIndex, width) {
330
+ hasContent = true;
331
+ lineEndSegmentIndex = segmentIndex;
332
+ lineEndGraphemeIndex = graphemeIndex + 1;
333
+ lineW = width;
334
+ }
335
+ function appendWholeSegment(segmentIndex, advance) {
336
+ if (!hasContent) {
337
+ startLineAtSegment(segmentIndex, advance);
338
+ return;
339
+ }
340
+ lineW += advance;
341
+ lineEndSegmentIndex = segmentIndex + 1;
342
+ lineEndGraphemeIndex = 0;
343
+ }
344
+ function updatePendingBreakForWholeSegment(kind, breakAfter, segmentIndex, segmentWidth, leadingSpacing, advance) {
345
+ if (!breakAfter) return;
346
+ const fitAdvance = getBreakOpportunityFitContribution(prepared, kind, segmentIndex, leadingSpacing);
347
+ const paintAdvance = getLineEndPaintContribution(prepared, kind, segmentIndex, leadingSpacing, segmentWidth);
348
+ pendingBreakSegmentIndex = segmentIndex + 1;
349
+ pendingBreakFitWidth = lineW - advance + fitAdvance;
350
+ pendingBreakPaintWidth = lineW - advance + paintAdvance;
351
+ pendingBreakKind = kind;
352
+ }
353
+ function appendBreakableSegmentFrom(segmentIndex, startGraphemeIndex) {
354
+ const fitAdvances = breakableFitAdvances[segmentIndex];
355
+ const preferredBreaks = breakablePreferredBreaks[segmentIndex] ?? null;
356
+ let preferredBreakIndex = preferredBreaks === null ? -1 : getNextPreferredBreakIndex(preferredBreaks, 0, startGraphemeIndex + 1);
357
+ let lastPreferredBreakEnd = -1;
358
+ let lastPreferredBreakWidth = 0;
359
+ for(let g = startGraphemeIndex; g < fitAdvances.length; g++){
360
+ const baseGw = fitAdvances[g];
361
+ if (!hasContent) {
362
+ startLineAtGrapheme(segmentIndex, g, baseGw);
363
+ } else {
364
+ const gw = getBreakableGraphemeAdvance(prepared, true, baseGw);
365
+ const candidatePaintWidth = lineW + gw;
366
+ if (getBreakableCandidateFitWidth(prepared, candidatePaintWidth) > fitLimit) {
367
+ if (preferredBreaks !== null && lastPreferredBreakEnd > startGraphemeIndex) {
368
+ return finishLine(segmentIndex, lastPreferredBreakEnd, lastPreferredBreakWidth);
369
+ }
370
+ return finishLine();
371
+ }
372
+ lineW = candidatePaintWidth;
373
+ lineEndSegmentIndex = segmentIndex;
374
+ lineEndGraphemeIndex = g + 1;
375
+ }
376
+ const graphemeEnd = g + 1;
377
+ if (preferredBreaks !== null && preferredBreaks[preferredBreakIndex] === graphemeEnd) {
378
+ lastPreferredBreakEnd = graphemeEnd;
379
+ lastPreferredBreakWidth = lineW;
380
+ preferredBreakIndex++;
381
+ }
382
+ }
383
+ if (hasContent && lineEndSegmentIndex === segmentIndex && lineEndGraphemeIndex === fitAdvances.length) {
384
+ lineEndSegmentIndex = segmentIndex + 1;
385
+ lineEndGraphemeIndex = 0;
386
+ }
387
+ return null;
388
+ }
389
+ let lineCount = 0;
390
+ let lastLineWidth = null;
391
+ while(chunkIndex >= 0 && lineCount < lineLimit){
392
+ lineStartSegmentIndex = cursor.segmentIndex;
393
+ lineStartGraphemeIndex = cursor.graphemeIndex;
394
+ lineW = 0;
395
+ hasContent = false;
396
+ lineEndSegmentIndex = cursor.segmentIndex;
397
+ lineEndGraphemeIndex = cursor.graphemeIndex;
398
+ pendingBreakSegmentIndex = -1;
399
+ pendingBreakFitWidth = 0;
400
+ pendingBreakPaintWidth = 0;
401
+ pendingBreakKind = null;
402
+ const chunk = prepared.chunks[chunkIndex];
403
+ let lineWidth = null;
404
+ if (chunk.startSegmentIndex === chunk.endSegmentIndex) {
405
+ cursor.segmentIndex = chunk.consumedEndSegmentIndex;
406
+ cursor.graphemeIndex = 0;
407
+ lineWidth = 0;
408
+ } else {
409
+ lineLoop: for(let i = cursor.segmentIndex; i < chunk.endSegmentIndex; i++){
410
+ const kind = kinds[i];
411
+ const breakAfter = breaksAfter(kind);
412
+ const startGraphemeIndex = i === cursor.segmentIndex ? cursor.graphemeIndex : 0;
413
+ const leadingSpacing = getLeadingLetterSpacing(prepared, hasContent, i);
414
+ const w = kind === 'tab' ? getTabAdvance(lineW + leadingSpacing, prepared.tabStopAdvance) : widths[i];
415
+ const advance = leadingSpacing + w;
416
+ const fitAdvance = getWholeSegmentFitContribution(prepared, kind, i, leadingSpacing, w);
417
+ if (kind === 'soft-hyphen' && startGraphemeIndex === 0) {
418
+ if (hasContent) {
419
+ lineEndSegmentIndex = i + 1;
420
+ lineEndGraphemeIndex = 0;
421
+ if (i + 1 < chunk.endSegmentIndex) {
422
+ pendingBreakSegmentIndex = i + 1;
423
+ pendingBreakFitWidth = lineW + discretionaryHyphenWidth;
424
+ pendingBreakPaintWidth = lineW + discretionaryHyphenWidth;
425
+ pendingBreakKind = kind;
426
+ }
427
+ }
428
+ continue;
429
+ }
430
+ if (!hasContent) {
431
+ if (startGraphemeIndex > 0) {
432
+ const line = appendBreakableSegmentFrom(i, startGraphemeIndex);
433
+ if (line !== null) {
434
+ lineWidth = line;
435
+ break lineLoop;
436
+ }
437
+ } else if (fitAdvance > fitLimit && breakableFitAdvances[i] !== null) {
438
+ const line = appendBreakableSegmentFrom(i, 0);
439
+ if (line !== null) {
440
+ lineWidth = line;
441
+ break lineLoop;
442
+ }
443
+ } else {
444
+ startLineAtSegment(i, w);
445
+ }
446
+ updatePendingBreakForWholeSegment(kind, breakAfter, i, w, leadingSpacing, advance);
447
+ continue;
448
+ }
449
+ const newFitW = lineW + fitAdvance;
450
+ if (newFitW > fitLimit) {
451
+ const currentBreakFitWidth = lineW + getBreakOpportunityFitContribution(prepared, kind, i, leadingSpacing);
452
+ const currentBreakPaintWidth = lineW + getLineEndPaintContribution(prepared, kind, i, leadingSpacing, w);
453
+ if (breakAfter && currentBreakFitWidth <= fitLimit) {
454
+ appendWholeSegment(i, advance);
455
+ lineWidth = finishLine(i + 1, 0, currentBreakPaintWidth);
456
+ break lineLoop;
457
+ }
458
+ if (pendingBreakSegmentIndex >= 0 && pendingBreakFitWidth <= fitLimit) {
459
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex || lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0) {
460
+ lineWidth = finishLine();
461
+ break lineLoop;
462
+ }
463
+ lineWidth = finishLine(pendingBreakSegmentIndex, 0, pendingBreakPaintWidth);
464
+ break lineLoop;
465
+ }
466
+ lineWidth = finishLine();
467
+ break lineLoop;
468
+ }
469
+ appendWholeSegment(i, advance);
470
+ updatePendingBreakForWholeSegment(kind, breakAfter, i, w, leadingSpacing, advance);
471
+ }
472
+ if (lineWidth === null) {
473
+ lineWidth = pendingBreakSegmentIndex === chunk.consumedEndSegmentIndex && lineEndGraphemeIndex === 0 ? finishLine(chunk.consumedEndSegmentIndex, 0, pendingBreakPaintWidth) : finishLine(chunk.consumedEndSegmentIndex, 0, lineW);
474
+ }
475
+ }
476
+ if (lineWidth === null) break;
477
+ lastLineWidth = lineWidth;
478
+ lineCount++;
479
+ onLine?.(lineWidth, lineStartSegmentIndex, lineStartGraphemeIndex, cursor.segmentIndex, cursor.graphemeIndex);
480
+ if (lineCount < lineLimit) chunkIndex = normalizeLineStartChunkIndexFromHint(prepared, chunkIndex, cursor);
481
+ }
482
+ return {
483
+ lineCount,
484
+ lastLineWidth
485
+ };
486
+ }
487
+ function stepPreparedSimpleLineGeometry(prepared, cursor, maxWidth) {
488
+ const { widths, kinds, breakableFitAdvances, breakablePreferredBreaks } = prepared;
489
+ const engineProfile = getEngineProfile();
490
+ const lineFitEpsilon = engineProfile.lineFitEpsilon;
491
+ const fitLimit = maxWidth + lineFitEpsilon;
492
+ let lineW = 0;
493
+ let hasContent = false;
494
+ let lineEndSegmentIndex = cursor.segmentIndex;
495
+ let lineEndGraphemeIndex = cursor.graphemeIndex;
496
+ let pendingBreakSegmentIndex = -1;
497
+ let pendingBreakPaintWidth = 0;
498
+ for(let i = cursor.segmentIndex; i < widths.length; i++){
499
+ const kind = kinds[i];
500
+ const breakAfter = breaksAfter(kind);
501
+ const startGraphemeIndex = i === cursor.segmentIndex ? cursor.graphemeIndex : 0;
502
+ const breakableFitAdvance = breakableFitAdvances[i];
503
+ const w = widths[i];
504
+ if (!hasContent) {
505
+ if (startGraphemeIndex > 0 || w > fitLimit && breakableFitAdvance !== null) {
506
+ const fitAdvances = breakableFitAdvance;
507
+ const preferredBreaks = breakablePreferredBreaks[i] ?? null;
508
+ let preferredBreakIndex = preferredBreaks === null ? -1 : getNextPreferredBreakIndex(preferredBreaks, 0, startGraphemeIndex + 1);
509
+ let lastPreferredBreakEnd = -1;
510
+ let lastPreferredBreakWidth = 0;
511
+ const firstGraphemeWidth = fitAdvances[startGraphemeIndex];
512
+ hasContent = true;
513
+ lineW = firstGraphemeWidth;
514
+ lineEndSegmentIndex = i;
515
+ lineEndGraphemeIndex = startGraphemeIndex + 1;
516
+ if (preferredBreaks !== null && preferredBreaks[preferredBreakIndex] === lineEndGraphemeIndex) {
517
+ lastPreferredBreakEnd = lineEndGraphemeIndex;
518
+ lastPreferredBreakWidth = lineW;
519
+ preferredBreakIndex++;
520
+ }
521
+ for(let g = startGraphemeIndex + 1; g < fitAdvances.length; g++){
522
+ const gw = fitAdvances[g];
523
+ if (lineW + gw > fitLimit) {
524
+ if (preferredBreaks !== null && lastPreferredBreakEnd > startGraphemeIndex) {
525
+ cursor.segmentIndex = i;
526
+ cursor.graphemeIndex = lastPreferredBreakEnd;
527
+ return lastPreferredBreakWidth;
528
+ }
529
+ cursor.segmentIndex = lineEndSegmentIndex;
530
+ cursor.graphemeIndex = lineEndGraphemeIndex;
531
+ return lineW;
532
+ }
533
+ lineW += gw;
534
+ lineEndSegmentIndex = i;
535
+ lineEndGraphemeIndex = g + 1;
536
+ if (preferredBreaks !== null && preferredBreaks[preferredBreakIndex] === lineEndGraphemeIndex) {
537
+ lastPreferredBreakEnd = lineEndGraphemeIndex;
538
+ lastPreferredBreakWidth = lineW;
539
+ preferredBreakIndex++;
540
+ }
541
+ }
542
+ if (lineEndSegmentIndex === i && lineEndGraphemeIndex === fitAdvances.length) {
543
+ lineEndSegmentIndex = i + 1;
544
+ lineEndGraphemeIndex = 0;
545
+ }
546
+ } else {
547
+ hasContent = true;
548
+ lineW = w;
549
+ lineEndSegmentIndex = i + 1;
550
+ lineEndGraphemeIndex = 0;
551
+ }
552
+ if (breakAfter) {
553
+ pendingBreakSegmentIndex = i + 1;
554
+ pendingBreakPaintWidth = lineW - w;
555
+ }
556
+ continue;
557
+ }
558
+ if (lineW + w > fitLimit) {
559
+ if (breakAfter) {
560
+ cursor.segmentIndex = i + 1;
561
+ cursor.graphemeIndex = 0;
562
+ return lineW;
563
+ }
564
+ if (pendingBreakSegmentIndex >= 0) {
565
+ if (lineEndSegmentIndex > pendingBreakSegmentIndex || lineEndSegmentIndex === pendingBreakSegmentIndex && lineEndGraphemeIndex > 0) {
566
+ cursor.segmentIndex = lineEndSegmentIndex;
567
+ cursor.graphemeIndex = lineEndGraphemeIndex;
568
+ return lineW;
569
+ }
570
+ cursor.segmentIndex = pendingBreakSegmentIndex;
571
+ cursor.graphemeIndex = 0;
572
+ return pendingBreakPaintWidth;
573
+ }
574
+ cursor.segmentIndex = lineEndSegmentIndex;
575
+ cursor.graphemeIndex = lineEndGraphemeIndex;
576
+ return lineW;
577
+ }
578
+ lineW += w;
579
+ lineEndSegmentIndex = i + 1;
580
+ lineEndGraphemeIndex = 0;
581
+ if (breakAfter) {
582
+ pendingBreakSegmentIndex = i + 1;
583
+ pendingBreakPaintWidth = lineW - w;
584
+ }
585
+ }
586
+ if (!hasContent) return null;
587
+ cursor.segmentIndex = lineEndSegmentIndex;
588
+ cursor.graphemeIndex = lineEndGraphemeIndex;
589
+ return lineW;
590
+ }
591
+ export function stepPreparedLineGeometryFromChunk(prepared, cursor, chunkIndex, maxWidth) {
592
+ if (prepared.simpleLineWalkFastPath) {
593
+ return stepPreparedSimpleLineGeometry(prepared, cursor, maxWidth);
594
+ }
595
+ return stepPreparedChunkLineGeometry(prepared, cursor, chunkIndex, maxWidth);
596
+ }
597
+ export function stepPreparedLineGeometry(prepared, cursor, maxWidth) {
598
+ const chunkIndex = normalizePreparedLineStart(prepared, cursor);
599
+ if (chunkIndex < 0) return null;
600
+ return stepPreparedLineGeometryFromChunk(prepared, cursor, chunkIndex, maxWidth);
601
+ }
602
+ export function measurePreparedLineGeometry(prepared, maxWidth) {
603
+ if (prepared.widths.length === 0) {
604
+ return {
605
+ lineCount: 0,
606
+ maxLineWidth: 0
607
+ };
608
+ }
609
+ const cursor = {
610
+ segmentIndex: 0,
611
+ graphemeIndex: 0
612
+ };
613
+ let lineCount = 0;
614
+ let maxLineWidth = 0;
615
+ if (!prepared.simpleLineWalkFastPath) {
616
+ const chunkIndex = normalizePreparedLineStart(prepared, cursor);
617
+ lineCount = walkPreparedComplexLines(prepared, cursor, chunkIndex, maxWidth, (width)=>{
618
+ if (width > maxLineWidth) maxLineWidth = width;
619
+ }).lineCount;
620
+ return {
621
+ lineCount,
622
+ maxLineWidth
623
+ };
624
+ }
625
+ while(true){
626
+ const lineWidth = stepPreparedLineGeometry(prepared, cursor, maxWidth);
627
+ if (lineWidth === null) {
628
+ return {
629
+ lineCount,
630
+ maxLineWidth
631
+ };
632
+ }
633
+ lineCount++;
634
+ if (lineWidth > maxLineWidth) maxLineWidth = lineWidth;
635
+ }
636
+ }
@@ -0,0 +1,44 @@
1
+ /*! Pretext 0.0.9 | MIT | Copyright (c) 2026 Pretext contributors */
2
+ import { getSharedGraphemeSegmenter } from './analysis.js';
3
+ import { isDiscretionaryLineEnd } from './line-break.js';
4
+ let sharedLineTextCaches = new WeakMap();
5
+ function getSegmentGraphemeOffsets(segmentIndex, segments, cache) {
6
+ let offsets = cache.get(segmentIndex);
7
+ if (offsets !== undefined) return offsets;
8
+ offsets = [
9
+ 0
10
+ ];
11
+ const graphemeSegmenter = getSharedGraphemeSegmenter();
12
+ for (const gs of graphemeSegmenter.segment(segments[segmentIndex])){
13
+ offsets.push(gs.index + gs.segment.length);
14
+ }
15
+ cache.set(segmentIndex, offsets);
16
+ return offsets;
17
+ }
18
+ export function getLineTextCache(prepared) {
19
+ let cache = sharedLineTextCaches.get(prepared);
20
+ if (cache !== undefined) return cache;
21
+ cache = new Map();
22
+ sharedLineTextCaches.set(prepared, cache);
23
+ return cache;
24
+ }
25
+ export function buildLineTextFromRange(prepared, cache, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
26
+ let text = '';
27
+ for(let i = startSegmentIndex; i < endSegmentIndex; i++){
28
+ if (prepared.kinds[i] === 'soft-hyphen' || prepared.kinds[i] === 'hard-break') continue;
29
+ if (i === startSegmentIndex && startGraphemeIndex > 0) {
30
+ const offsets = getSegmentGraphemeOffsets(i, prepared.segments, cache);
31
+ text += prepared.segments[i].slice(offsets[startGraphemeIndex]);
32
+ } else {
33
+ text += prepared.segments[i];
34
+ }
35
+ }
36
+ if (endGraphemeIndex > 0) {
37
+ const offsets = getSegmentGraphemeOffsets(endSegmentIndex, prepared.segments, cache);
38
+ text += prepared.segments[endSegmentIndex].slice(offsets[startSegmentIndex === endSegmentIndex ? startGraphemeIndex : 0], offsets[endGraphemeIndex]);
39
+ }
40
+ return isDiscretionaryLineEnd(prepared.kinds, endSegmentIndex, endGraphemeIndex) ? text + '-' : text;
41
+ }
42
+ export function clearLineTextCaches() {
43
+ sharedLineTextCaches = new WeakMap();
44
+ }