mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Add complex rich text test cases and fix them (#22948)
This commit is contained in:
parent
8619a81e89
commit
3a035ee044
@ -188,6 +188,7 @@ class TextLayoutService {
|
||||
if (span is PlaceholderSpan) {
|
||||
// TODO(mdebbar): Do placeholders affect min/max intrinsic width?
|
||||
} else if (span is FlatTextSpan) {
|
||||
spanometer.currentSpan = span;
|
||||
final LineBreakResult nextBreak = currentLine.findNextBreak(span.end);
|
||||
|
||||
// For the purpose of max intrinsic width, we don't care if the line
|
||||
@ -251,6 +252,9 @@ class LineSegment {
|
||||
|
||||
/// The width of the trailing white space in the segment.
|
||||
double get widthOfTrailingSpace => widthIncludingSpace - width;
|
||||
|
||||
/// Whether this segment is made of only white space.
|
||||
bool get isSpaceOnly => start.index == end.indexWithoutTrailingSpaces;
|
||||
}
|
||||
|
||||
/// Builds instances of [EngineLineMetrics] for the given [paragraph].
|
||||
@ -358,8 +362,12 @@ class LineBuilder {
|
||||
void _addSegment(LineSegment segment) {
|
||||
_segments.add(segment);
|
||||
|
||||
// Add the width of previous trailing space.
|
||||
width += widthOfTrailingSpace + segment.width;
|
||||
// Adding a space-only segment has no effect on `width` because it doesn't
|
||||
// include trailing white space.
|
||||
if (!segment.isSpaceOnly) {
|
||||
// Add the width of previous trailing space.
|
||||
width += widthOfTrailingSpace + segment.width;
|
||||
}
|
||||
widthIncludingSpace += segment.widthIncludingSpace;
|
||||
end = segment.end;
|
||||
}
|
||||
@ -370,17 +378,39 @@ class LineBuilder {
|
||||
LineSegment _popSegment() {
|
||||
final LineSegment poppedSegment = _segments.removeLast();
|
||||
|
||||
double widthOfPrevTrailingSpace;
|
||||
if (_segments.isEmpty) {
|
||||
widthOfPrevTrailingSpace = 0.0;
|
||||
width = 0.0;
|
||||
widthIncludingSpace = 0.0;
|
||||
end = start;
|
||||
} else {
|
||||
widthOfPrevTrailingSpace = lastSegment.widthOfTrailingSpace;
|
||||
widthIncludingSpace -= poppedSegment.widthIncludingSpace;
|
||||
end = lastSegment.end;
|
||||
}
|
||||
|
||||
width = width - poppedSegment.width - widthOfPrevTrailingSpace;
|
||||
widthIncludingSpace -= poppedSegment.widthIncludingSpace;
|
||||
// Now, let's figure out what to do with `width`.
|
||||
|
||||
// Popping a space-only segment has no effect on `width`.
|
||||
if (!poppedSegment.isSpaceOnly) {
|
||||
// First, we subtract the width of the popped segment.
|
||||
width -= poppedSegment.width;
|
||||
|
||||
// Second, we subtract all trailing spaces from `width`. There could be
|
||||
// multiple trailing segments that are space-only.
|
||||
double widthOfTrailingSpace = 0.0;
|
||||
int i = _segments.length - 1;
|
||||
while (i >= 0 && _segments[i].isSpaceOnly) {
|
||||
// Since the segment is space-only, `widthIncludingSpace` contains
|
||||
// the width of the space and nothing else.
|
||||
widthOfTrailingSpace += _segments[i].widthIncludingSpace;
|
||||
i--;
|
||||
}
|
||||
if (i >= 0) {
|
||||
// Having `i >= 0` means in the above loop we stopped at a
|
||||
// non-space-only segment. We should also subtract its trailing spaces.
|
||||
widthOfTrailingSpace += _segments[i].widthOfTrailingSpace;
|
||||
}
|
||||
width -= widthOfTrailingSpace;
|
||||
}
|
||||
}
|
||||
|
||||
return poppedSegment;
|
||||
}
|
||||
|
||||
152
lib/web_ui/test/text/layout_service_rich_test.dart
Normal file
152
lib/web_ui/test/text/layout_service_rich_test.dart
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.12
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import 'layout_service_helper.dart';
|
||||
|
||||
const ui.Color white = ui.Color(0xFFFFFFFF);
|
||||
const ui.Color black = ui.Color(0xFF000000);
|
||||
const ui.Color red = ui.Color(0xFFFF0000);
|
||||
const ui.Color green = ui.Color(0xFF00FF00);
|
||||
const ui.Color blue = ui.Color(0xFF0000FF);
|
||||
|
||||
final EngineParagraphStyle ahemStyle = EngineParagraphStyle(
|
||||
fontFamily: 'ahem',
|
||||
fontSize: 10,
|
||||
);
|
||||
|
||||
ui.ParagraphConstraints constrain(double width) {
|
||||
return ui.ParagraphConstraints(width: width);
|
||||
}
|
||||
|
||||
CanvasParagraph rich(
|
||||
EngineParagraphStyle style,
|
||||
void Function(CanvasParagraphBuilder) callback,
|
||||
) {
|
||||
final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
|
||||
callback(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() async {
|
||||
await ui.webOnlyInitializeTestDomRenderer();
|
||||
|
||||
test('measures spans in the same line correctly', () {
|
||||
final CanvasParagraph paragraph = rich(ahemStyle, (builder) {
|
||||
builder.pushStyle(EngineTextStyle.only(fontSize: 12.0));
|
||||
// 12.0 * 6 = 72.0 (with spaces)
|
||||
// 12.0 * 5 = 60.0 (without spaces)
|
||||
builder.addText('Lorem ');
|
||||
|
||||
builder.pushStyle(EngineTextStyle.only(fontSize: 13.0));
|
||||
// 13.0 * 6 = 78.0 (with spaces)
|
||||
// 13.0 * 5 = 65.0 (without spaces)
|
||||
builder.addText('ipsum ');
|
||||
|
||||
builder.pushStyle(EngineTextStyle.only(fontSize: 11.0));
|
||||
// 11.0 * 5 = 55.0
|
||||
builder.addText('dolor');
|
||||
})..layout(constrain(double.infinity));
|
||||
|
||||
expect(paragraph.maxIntrinsicWidth, 205.0);
|
||||
expect(paragraph.minIntrinsicWidth, 65.0); // "ipsum"
|
||||
expect(paragraph.width, double.infinity);
|
||||
expectLines(paragraph, [
|
||||
l('Lorem ipsum dolor', 0, 17, hardBreak: true, width: 205.0, left: 0.0),
|
||||
]);
|
||||
});
|
||||
|
||||
test('breaks lines correctly at the end of spans', () {
|
||||
final CanvasParagraph paragraph = rich(ahemStyle, (builder) {
|
||||
builder.addText('Lorem ');
|
||||
builder.pushStyle(EngineTextStyle.only(fontSize: 15.0));
|
||||
builder.addText('sit ');
|
||||
builder.pop();
|
||||
builder.addText('.');
|
||||
})..layout(constrain(60.0));
|
||||
|
||||
expect(paragraph.maxIntrinsicWidth, 130.0);
|
||||
expect(paragraph.minIntrinsicWidth, 50.0); // "Lorem"
|
||||
expect(paragraph.width, 60.0);
|
||||
expectLines(paragraph, [
|
||||
l('Lorem ', 0, 6, hardBreak: false, width: 50.0, left: 0.0),
|
||||
l('sit ', 6, 10, hardBreak: false, width: 45.0, left: 0.0),
|
||||
l('.', 10, 11, hardBreak: true, width: 10.0, left: 0.0),
|
||||
]);
|
||||
});
|
||||
|
||||
test('breaks lines correctly in the middle of spans', () {
|
||||
final CanvasParagraph paragraph = rich(ahemStyle, (builder) {
|
||||
builder.addText('Lorem ipsum ');
|
||||
builder.pushStyle(EngineTextStyle.only(fontSize: 11.0));
|
||||
builder.addText('sit dolor');
|
||||
})..layout(constrain(100.0));
|
||||
|
||||
expect(paragraph.maxIntrinsicWidth, 219.0);
|
||||
expect(paragraph.minIntrinsicWidth, 55.0); // "dolor"
|
||||
expect(paragraph.width, 100.0);
|
||||
expectLines(paragraph, [
|
||||
l('Lorem ', 0, 6, hardBreak: false, width: 50.0, left: 0.0),
|
||||
l('ipsum sit ', 6, 16, hardBreak: false, width: 93.0, left: 0.0),
|
||||
l('dolor', 16, 21, hardBreak: true, width: 55.0, left: 0.0),
|
||||
]);
|
||||
});
|
||||
|
||||
test('handles space-only spans', () {
|
||||
final CanvasParagraph paragraph = rich(ahemStyle, (builder) {
|
||||
builder.pushStyle(EngineTextStyle.only(color: red));
|
||||
builder.addText('Lorem ');
|
||||
builder.pop();
|
||||
builder.pushStyle(EngineTextStyle.only(color: blue));
|
||||
builder.addText(' ');
|
||||
builder.pushStyle(EngineTextStyle.only(color: green));
|
||||
builder.addText(' ');
|
||||
builder.pushStyle(EngineTextStyle.only(color: black));
|
||||
builder.addText('ipsum');
|
||||
});
|
||||
paragraph.layout(constrain(80.0));
|
||||
|
||||
expect(paragraph.maxIntrinsicWidth, 160.0);
|
||||
expect(paragraph.minIntrinsicWidth, 50.0); // "Lorem" or "ipsum"
|
||||
expect(paragraph.width, 80.0);
|
||||
expectLines(paragraph, [
|
||||
l('Lorem ', 0, 11, hardBreak: false, width: 50.0, widthWithTrailingSpaces: 110.0, left: 0.0),
|
||||
l('ipsum', 11, 16, hardBreak: true, width: 50.0, left: 0.0),
|
||||
]);
|
||||
});
|
||||
|
||||
test('should not break at span end if it is not a line break', () {
|
||||
final CanvasParagraph paragraph = rich(ahemStyle, (builder) {
|
||||
builder.pushStyle(EngineTextStyle.only(color: red));
|
||||
builder.addText('Lorem');
|
||||
builder.pop();
|
||||
builder.pushStyle(EngineTextStyle.only(color: blue));
|
||||
builder.addText(' ');
|
||||
builder.pushStyle(EngineTextStyle.only(color: black));
|
||||
builder.addText('ip');
|
||||
builder.pushStyle(EngineTextStyle.only(color: green));
|
||||
builder.addText('su');
|
||||
builder.pushStyle(EngineTextStyle.only(color: white));
|
||||
builder.addText('m');
|
||||
})..layout(constrain(50.0));
|
||||
|
||||
expect(paragraph.maxIntrinsicWidth, 110.0);
|
||||
expect(paragraph.minIntrinsicWidth, 50.0); // "Lorem" or "ipsum"
|
||||
expect(paragraph.width, 50.0);
|
||||
expectLines(paragraph, [
|
||||
l('Lorem ', 0, 6, hardBreak: false, width: 50.0, left: 0.0),
|
||||
l('ipsum', 6, 11, hardBreak: true, width: 50.0, left: 0.0),
|
||||
]);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user