Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
25 changes: 9 additions & 16 deletions lib/web_ui/lib/src/engine/text/line_breaker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ List<LineBreakFragment> breakLinesUsingV8BreakIterator(String text, JSString jsT
iterator.adoptText(jsText);
iterator.first();
while (iterator.next() != -1) {
final LineBreakType type = _getV8BreakType(text, iterator);

final int fragmentEnd = iterator.current().toInt();
int trailingNewlines = 0;
int trailingSpaces = 0;
Expand Down Expand Up @@ -115,6 +113,15 @@ List<LineBreakFragment> breakLinesUsingV8BreakIterator(String text, JSString jsT
}
}

final LineBreakType type;
if (trailingNewlines > 0) {
type = LineBreakType.mandatory;
} else if (fragmentEnd == text.length) {
type = LineBreakType.endOfText;
} else {
type = LineBreakType.opportunity;
}

breaks.add(LineBreakFragment(
fragmentStart,
fragmentEnd,
Expand All @@ -132,20 +139,6 @@ List<LineBreakFragment> breakLinesUsingV8BreakIterator(String text, JSString jsT
return breaks;
}

/// Gets break type from v8BreakIterator.
LineBreakType _getV8BreakType(String text, DomV8BreakIterator iterator) {
final int fragmentEnd = iterator.current().toInt();

// I don't know why v8BreakIterator uses the type "none" to mean "soft break".
if (iterator.breakType() != 'none') {
return LineBreakType.mandatory;
}
if (fragmentEnd == text.length) {
return LineBreakType.endOfText;
}
return LineBreakType.opportunity;
}

class LineBreakFragment extends TextFragment {
const LineBreakFragment(super.start, super.end, this.type, {
required this.trailingNewlines,
Expand Down
40 changes: 40 additions & 0 deletions lib/web_ui/test/html/text/line_breaker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,46 @@ void testMain() {
}
});
});

group('v8BreakIterator hard line breaks', () {
List<Line> split(String text) {
return V8LineBreakFragmenter(text)
.fragment()
.map((LineBreakFragment fragment) => Line.fromLineBreakFragment(text, fragment))
.toList();
}

test('thai text with hard line breaks', () {
const String thaiText = '\u0E1A\u0E38\u0E1C\u0E25\u0E01\u0E32\u0E23';
expect(split(thaiText), <Line>[
Line('\u0E1A\u0E38', opportunity),
Line('\u0E1C\u0E25', opportunity),
Line('\u0E01\u0E32\u0E23', endOfText),
]);
expect(split('$thaiText\n'), <Line>[
Line('\u0E1A\u0E38', opportunity),
Line('\u0E1C\u0E25', opportunity),
Line('\u0E01\u0E32\u0E23\n', mandatory, nl: 1, sp: 1),
Line('', endOfText),
]);
});

test('khmer text with hard line breaks', () {
const String khmerText =
'\u179B\u1792\u17D2\u179C\u17BE\u17B2\u17D2\u1799';
expect(split(khmerText), <Line>[
Line('\u179B', opportunity),
Line('\u1792\u17D2\u179C\u17BE', opportunity),
Line('\u17B2\u17D2\u1799', endOfText),
]);
expect(split('$khmerText\n'), <Line>[
Line('\u179B', opportunity),
Line('\u1792\u17D2\u179C\u17BE', opportunity),
Line('\u17B2\u17D2\u1799\n', mandatory, nl: 1, sp: 1),
Line('', endOfText),
]);
});
}, skip: domIntl.v8BreakIterator == null);
}

typedef CreateLineBreakFragmenter = LineBreakFragmenter Function(String text);
Expand Down