Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/core/text-rendering/TextLayoutEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,14 @@ export const wrapLine = (
continue;
}
const space = spaces[spaceIdx++] || '';
// Measure the real separator \u2014 multi-space runs collapse into one match.
// Single ' ' is the dominant separator \u2014 reuse the precomputed width.
// Multi-space/ZWSP runs collapse into one match; measure those for real.
const effectiveSpaceWidth =
space.length > 0 ? measureText(space, fontFamily, letterSpacing) : 0;
space === ' '
? spaceWidth
: space.length > 0
? measureText(space, fontFamily, letterSpacing)
: 0;
const totalWidth = currentLineWidth + effectiveSpaceWidth + wordWidth;

if (totalWidth < maxWidth) {
Expand Down
77 changes: 77 additions & 0 deletions src/core/text-rendering/tests/TextLayoutEngine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,83 @@ describe('SDF Text Utils', () => {
expect(lines).toHaveLength(1);
expect(lines[0]?.[0]).toBe(' hello world');
});

it('should count the full width of a multi-space separator when wrapping', () => {
// 'aa bb' really measures 60 (6 chars * 10). Counting the separator
// as a single spaceWidth (50) used to keep it on one line past maxWidth.
const result = wrapLine(
testMeasureText,
'aa bb',
'Arial',
55, // maxWidth — below the real 60, above the buggy 50
0,
10, // spaceWidth
'',
0,
'break-word',
10,
);

const [lines] = result;
expect(lines).toHaveLength(2);
expect(lines[0]).toEqual(['aa', 20, false, 0, 0]);
expect(lines[1]).toEqual(['bb', 20, false, 0, 0]);
});

it('should preserve a multi-space separator and its width when the line fits', () => {
const result = wrapLine(
testMeasureText,
'aa bb',
'Arial',
1000,
0,
10, // spaceWidth
'',
0,
'break-word',
10,
);

expect(result[0][0]).toEqual(['aa bb', 60, false, 0, 0]);
});

it('should treat a run of multiple ZWSPs as a zero-width separator', () => {
const result = wrapLine(
testMeasureText,
'aa\u200B\u200Bbb',
'Arial',
1000,
0,
10, // spaceWidth
'',
0,
'break-word',
10,
);

// Zero-width separator: no visible gap added, width is just the glyphs
expect(result[0][0]).toEqual(['aabb', 40, false, 0, 0]);
});

it('should use the precomputed spaceWidth for a single-space separator', () => {
// spaceWidth (4) deliberately differs from testMeasureText(' ') (10):
// the single-space fast path must reuse the precomputed value instead
// of re-measuring the separator.
const result = wrapLine(
testMeasureText,
'aa bb',
'Arial',
1000,
0,
4, // spaceWidth
'',
0,
'break-word',
10,
);

expect(result[0][0]).toEqual(['aa bb', 44, false, 0, 0]);
});
});

describe('wrapText', () => {
Expand Down
Loading