Skip to content

Commit 9e675ce

Browse files
committed
fix: improve line break preserving strategy
1 parent 055b100 commit 9e675ce

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

packages/layout-engine/layout-bridge/src/remeasure.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,8 +1333,21 @@ export function remeasureParagraph(
13331333

13341334
// Advance to next line start
13351335
if (explicitLineBreakRun >= 0) {
1336-
// Explicit break consumed as a line boundary, continue from run after break.
1337-
currentRun = explicitLineBreakRun + 1;
1336+
// Preserve trailing/manual break boundaries:
1337+
// - If this line started on the break, we've already emitted its empty-line boundary,
1338+
// so advance past it.
1339+
// - If this line ended before the break (text + break), only keep the break for the
1340+
// next iteration when it is trailing or followed by another break. Otherwise consume
1341+
// it immediately so mid-paragraph breaks don't create extra blank lines.
1342+
const emittedBreakBoundary =
1343+
startRun === explicitLineBreakRun && startChar === 0 && endRun === explicitLineBreakRun && endChar === 0;
1344+
if (emittedBreakBoundary) {
1345+
currentRun = explicitLineBreakRun + 1;
1346+
} else {
1347+
const nextRun = runs[explicitLineBreakRun + 1];
1348+
const preserveBoundaryForNextIteration = !nextRun || isLineBreakRun(nextRun);
1349+
currentRun = preserveBoundaryForNextIteration ? explicitLineBreakRun : explicitLineBreakRun + 1;
1350+
}
13381351
currentChar = 0;
13391352
} else {
13401353
currentRun = endRun;
@@ -1343,7 +1356,7 @@ export function remeasureParagraph(
13431356
if (currentRun >= runs.length) {
13441357
break;
13451358
}
1346-
if (currentChar >= runText(runs[currentRun]).length) {
1359+
if (!isLineBreakRun(runs[currentRun]) && currentChar >= runText(runs[currentRun]).length) {
13471360
currentRun += 1;
13481361
currentChar = 0;
13491362
}

packages/layout-engine/layout-bridge/test/remeasure.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,34 @@ describe('remeasureParagraph', () => {
898898
expect(measure.lines[2].toRun).toBe(4);
899899
});
900900

901+
it('preserves trailing explicit lineBreak as final empty line', () => {
902+
const block = createBlock([textRun('Hello'), { kind: 'lineBreak' } as Run]);
903+
const measure = remeasureParagraph(block, 200);
904+
905+
expect(measure.lines).toHaveLength(2);
906+
expect(measure.lines[0].fromRun).toBe(0);
907+
expect(measure.lines[0].toRun).toBe(0);
908+
// Final empty line should be anchored to trailing break run.
909+
expect(measure.lines[1].fromRun).toBe(1);
910+
expect(measure.lines[1].toRun).toBe(1);
911+
expect(measure.lines[1].toChar).toBe(0);
912+
});
913+
914+
it('preserves multiple trailing explicit lineBreak runs as multiple empty lines', () => {
915+
const block = createBlock([textRun('Hello'), { kind: 'lineBreak' } as Run, { kind: 'lineBreak' } as Run]);
916+
const measure = remeasureParagraph(block, 200);
917+
918+
expect(measure.lines).toHaveLength(3);
919+
expect(measure.lines[0].fromRun).toBe(0);
920+
expect(measure.lines[0].toRun).toBe(0);
921+
expect(measure.lines[1].fromRun).toBe(1);
922+
expect(measure.lines[1].toRun).toBe(1);
923+
expect(measure.lines[1].toChar).toBe(0);
924+
expect(measure.lines[2].fromRun).toBe(2);
925+
expect(measure.lines[2].toRun).toBe(2);
926+
expect(measure.lines[2].toChar).toBe(0);
927+
});
928+
901929
it('handles tabs followed immediately by line break', () => {
902930
const block = createBlock([textRun('A'), tabRun(), textRun('')]);
903931
const measure = remeasureParagraph(block, 200);

0 commit comments

Comments
 (0)