blob: 681bb736c9d8deb0dae1445322c26db2b2c62e8f [file] [log] [blame]
// 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.
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide window;
void testEachMeasurement(String description, VoidCallback body, {bool? skip}) {
test('$description (dom measurement)', () async {
try {
TextMeasurementService.initialize(rulerCacheCapacity: 2);
WebExperiments.instance!.useCanvasText = false;
WebExperiments.instance!.useCanvasRichText = false;
return body();
} finally {
WebExperiments.instance!.useCanvasText = null;
WebExperiments.instance!.useCanvasRichText = null;
TextMeasurementService.clearCache();
}
}, skip: skip);
test('$description (canvas measurement)', () async {
try {
TextMeasurementService.initialize(rulerCacheCapacity: 2);
WebExperiments.instance!.useCanvasText = true;
WebExperiments.instance!.useCanvasRichText = false;
return body();
} finally {
WebExperiments.instance!.useCanvasText = null;
WebExperiments.instance!.useCanvasRichText = null;
TextMeasurementService.clearCache();
}
}, skip: skip);
}
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await webOnlyInitializeTestDomRenderer();
// Ahem font uses a constant ideographic/alphabetic baseline ratio.
const double kAhemBaselineRatio = 1.25;
testEachMeasurement('predictably lays out a single-line paragraph', () {
for (final double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: fontSize,
));
builder.addText('Test');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 400.0));
expect(paragraph.height, closeTo(fontSize, 0.001));
expect(paragraph.width, closeTo(400.0, 0.001));
expect(paragraph.minIntrinsicWidth, closeTo(fontSize * 4.0, 0.001));
expect(paragraph.maxIntrinsicWidth, closeTo(fontSize * 4.0, 0.001));
expect(paragraph.alphabeticBaseline, closeTo(fontSize * .8, 0.001));
expect(
paragraph.ideographicBaseline,
closeTo(paragraph.alphabeticBaseline * kAhemBaselineRatio, 3.0),
);
}
});
testEachMeasurement('predictably lays out a multi-line paragraph', () {
for (final double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: fontSize,
));
builder.addText('Test Ahem');
final Paragraph paragraph = builder.build();
paragraph.layout(ParagraphConstraints(width: fontSize * 5.0));
expect(
paragraph.height, closeTo(fontSize * 2.0, 0.001)); // because it wraps
expect(paragraph.width, closeTo(fontSize * 5.0, 0.001));
expect(paragraph.minIntrinsicWidth, closeTo(fontSize * 4.0, 0.001));
// TODO(yjbanov): see https://github.com/flutter/flutter/issues/21965
expect(paragraph.maxIntrinsicWidth, closeTo(fontSize * 9.0, 0.001));
expect(paragraph.alphabeticBaseline, closeTo(fontSize * .8, 0.001));
expect(
paragraph.ideographicBaseline,
closeTo(paragraph.alphabeticBaseline * kAhemBaselineRatio, 3.0),
);
}
});
testEachMeasurement('predictably lays out a single-line rich paragraph', () {
for (final double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: fontSize,
));
builder.addText('span1');
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
builder.addText('span2');
final Paragraph paragraph = builder.build();
paragraph.layout(ParagraphConstraints(width: fontSize * 10.0));
expect(paragraph.height, fontSize);
expect(paragraph.width, fontSize * 10.0);
expect(paragraph.minIntrinsicWidth, fontSize * 10.0);
expect(paragraph.maxIntrinsicWidth, fontSize * 10.0);
}
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50771
// TODO(nurhan): https://github.com/flutter/flutter/issues/46638
// TODO(nurhan): https://github.com/flutter/flutter/issues/50590
skip: browserEngine != BrowserEngine.blink);
testEachMeasurement('predictably lays out a multi-line rich paragraph', () {
for (final double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: fontSize,
));
builder.addText('12345 ');
builder.addText('67890 ');
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
builder.addText('bold');
final Paragraph paragraph = builder.build();
paragraph.layout(ParagraphConstraints(width: fontSize * 5.0));
expect(paragraph.height, fontSize * 3.0); // because it wraps
expect(paragraph.width, fontSize * 5.0);
expect(paragraph.minIntrinsicWidth, fontSize * 5.0);
expect(paragraph.maxIntrinsicWidth, fontSize * 16.0);
}
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/46638
// TODO(nurhan): https://github.com/flutter/flutter/issues/50590
// TODO(nurhan): https://github.com/flutter/flutter/issues/50771
skip: browserEngine != BrowserEngine.blink);
testEachMeasurement('getPositionForOffset single-line', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
));
builder.addText('abcd efg');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 1000));
// At the beginning of the line.
expect(
paragraph.getPositionForOffset(const Offset(0, 5)),
const TextPosition(offset: 0, affinity: TextAffinity.downstream),
);
// Below the line.
expect(
paragraph.getPositionForOffset(const Offset(0, 12)),
const TextPosition(offset: 8, affinity: TextAffinity.upstream),
);
// Above the line.
expect(
paragraph.getPositionForOffset(const Offset(0, -5)),
const TextPosition(offset: 0, affinity: TextAffinity.downstream),
);
// At the end of the line.
expect(
paragraph.getPositionForOffset(const Offset(80, 5)),
const TextPosition(offset: 8, affinity: TextAffinity.upstream),
);
// On the left side of "b".
expect(
paragraph.getPositionForOffset(const Offset(14, 5)),
const TextPosition(offset: 1, affinity: TextAffinity.downstream),
);
// On the right side of "b".
expect(
paragraph.getPositionForOffset(const Offset(16, 5)),
const TextPosition(offset: 2, affinity: TextAffinity.upstream),
);
});
test('getPositionForOffset multi-line', () {
// [Paragraph.getPositionForOffset] for multi-line text doesn't work well
// with dom-based measurement.
WebExperiments.instance!.useCanvasText = true;
WebExperiments.instance!.useCanvasRichText = false;
TextMeasurementService.initialize(rulerCacheCapacity: 2);
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
));
builder.addText('abcd\n');
builder.addText('abcdefg\n');
builder.addText('ab');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));
// First line: "abcd\n"
// At the beginning of the first line.
expect(
paragraph.getPositionForOffset(const Offset(0, 5)),
const TextPosition(offset: 0, affinity: TextAffinity.downstream),
);
// Above the first line.
expect(
paragraph.getPositionForOffset(const Offset(0, -15)),
const TextPosition(offset: 0, affinity: TextAffinity.downstream),
);
// At the end of the first line.
expect(
paragraph.getPositionForOffset(const Offset(50, 5)),
const TextPosition(offset: 4, affinity: TextAffinity.upstream),
);
// On the left side of "b" in the first line.
expect(
paragraph.getPositionForOffset(const Offset(14, 5)),
const TextPosition(offset: 1, affinity: TextAffinity.downstream),
);
// On the right side of "b" in the first line.
expect(
paragraph.getPositionForOffset(const Offset(16, 5)),
const TextPosition(offset: 2, affinity: TextAffinity.upstream),
);
// Second line: "abcdefg\n"
// At the beginning of the second line.
expect(
paragraph.getPositionForOffset(const Offset(0, 15)),
const TextPosition(offset: 5, affinity: TextAffinity.downstream),
);
// At the end of the second line.
expect(
paragraph.getPositionForOffset(const Offset(100, 15)),
const TextPosition(offset: 12, affinity: TextAffinity.upstream),
);
// On the left side of "e" in the second line.
expect(
paragraph.getPositionForOffset(const Offset(44, 15)),
const TextPosition(offset: 9, affinity: TextAffinity.downstream),
);
// On the right side of "e" in the second line.
expect(
paragraph.getPositionForOffset(const Offset(46, 15)),
const TextPosition(offset: 10, affinity: TextAffinity.upstream),
);
// Last (third) line: "ab"
// At the beginning of the last line.
expect(
paragraph.getPositionForOffset(const Offset(0, 25)),
const TextPosition(offset: 13, affinity: TextAffinity.downstream),
);
// At the end of the last line.
expect(
paragraph.getPositionForOffset(const Offset(100, 25)),
const TextPosition(offset: 15, affinity: TextAffinity.upstream),
);
// Below the last line.
expect(
paragraph.getPositionForOffset(const Offset(0, 32)),
const TextPosition(offset: 15, affinity: TextAffinity.upstream),
);
// On the left side of "b" in the last line.
expect(
paragraph.getPositionForOffset(const Offset(12, 25)),
const TextPosition(offset: 14, affinity: TextAffinity.downstream),
);
// On the right side of "a" in the last line.
expect(
paragraph.getPositionForOffset(const Offset(9, 25)),
const TextPosition(offset: 14, affinity: TextAffinity.upstream),
);
TextMeasurementService.clearCache();
WebExperiments.instance!.useCanvasText = null;
WebExperiments.instance!.useCanvasRichText = null;
});
test('getPositionForOffset multi-line centered', () {
WebExperiments.instance!.useCanvasText = true;
WebExperiments.instance!.useCanvasRichText = false;
TextMeasurementService.initialize(rulerCacheCapacity: 2);
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
));
builder.addText('abcd\n');
builder.addText('abcdefg\n');
builder.addText('ab');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));
// First line: "abcd\n"
// At the beginning of the first line.
expect(
paragraph.getPositionForOffset(const Offset(0, 5)),
const TextPosition(offset: 0, affinity: TextAffinity.downstream),
);
// Above the first line.
expect(
paragraph.getPositionForOffset(const Offset(0, -15)),
const TextPosition(offset: 0, affinity: TextAffinity.downstream),
);
// At the end of the first line.
expect(
paragraph.getPositionForOffset(const Offset(100, 5)),
const TextPosition(offset: 4, affinity: TextAffinity.upstream),
);
// On the left side of "b" in the first line.
expect(
// The line is centered so it's shifted to the right by "30.0px".
paragraph.getPositionForOffset(const Offset(30.0 + 14, 5)),
const TextPosition(offset: 1, affinity: TextAffinity.downstream),
);
// On the right side of "b" in the first line.
expect(
// The line is centered so it's shifted to the right by "30.0px".
paragraph.getPositionForOffset(const Offset(30.0 + 16, 5)),
const TextPosition(offset: 2, affinity: TextAffinity.upstream),
);
// Second line: "abcdefg\n"
// At the beginning of the second line.
expect(
paragraph.getPositionForOffset(const Offset(0, 15)),
const TextPosition(offset: 5, affinity: TextAffinity.downstream),
);
// At the end of the second line.
expect(
paragraph.getPositionForOffset(const Offset(100, 15)),
const TextPosition(offset: 12, affinity: TextAffinity.upstream),
);
// On the left side of "e" in the second line.
expect(
// The line is centered so it's shifted to the right by "15.0px".
paragraph.getPositionForOffset(const Offset(15.0 + 44, 15)),
const TextPosition(offset: 9, affinity: TextAffinity.downstream),
);
// On the right side of "e" in the second line.
expect(
// The line is centered so it's shifted to the right by "15.0px".
paragraph.getPositionForOffset(const Offset(15.0 + 46, 15)),
const TextPosition(offset: 10, affinity: TextAffinity.upstream),
);
// Last (third) line: "ab"
// At the beginning of the last line.
expect(
paragraph.getPositionForOffset(const Offset(0, 25)),
const TextPosition(offset: 13, affinity: TextAffinity.downstream),
);
// At the end of the last line.
expect(
paragraph.getPositionForOffset(const Offset(100, 25)),
const TextPosition(offset: 15, affinity: TextAffinity.upstream),
);
// Below the last line.
expect(
paragraph.getPositionForOffset(const Offset(0, 32)),
const TextPosition(offset: 15, affinity: TextAffinity.upstream),
);
// On the left side of "b" in the last line.
expect(
// The line is centered so it's shifted to the right by "40.0px".
paragraph.getPositionForOffset(const Offset(40.0 + 12, 25)),
const TextPosition(offset: 14, affinity: TextAffinity.downstream),
);
// On the right side of "a" in the last line.
expect(
// The line is centered so it's shifted to the right by "40.0px".
paragraph.getPositionForOffset(const Offset(40.0 + 9, 25)),
const TextPosition(offset: 14, affinity: TextAffinity.upstream),
);
TextMeasurementService.clearCache();
WebExperiments.instance!.useCanvasText = null;
WebExperiments.instance!.useCanvasRichText = null;
});
test('getWordBoundary', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle())
..addText('Lorem ipsum dolor');
final Paragraph paragraph = builder.build();
const TextRange loremRange = TextRange(start: 0, end: 5);
expect(paragraph.getWordBoundary(const TextPosition(offset: 0)), loremRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 1)), loremRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 2)), loremRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 3)), loremRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 4)), loremRange);
const TextRange firstSpace = TextRange(start: 5, end: 6);
expect(paragraph.getWordBoundary(const TextPosition(offset: 5)), firstSpace);
const TextRange ipsumRange = TextRange(start: 6, end: 11);
expect(paragraph.getWordBoundary(const TextPosition(offset: 6)), ipsumRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 7)), ipsumRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 8)), ipsumRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 9)), ipsumRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 10)), ipsumRange);
const TextRange secondSpace = TextRange(start: 11, end: 12);
expect(paragraph.getWordBoundary(const TextPosition(offset: 11)), secondSpace);
const TextRange dolorRange = TextRange(start: 12, end: 17);
expect(paragraph.getWordBoundary(const TextPosition(offset: 12)), dolorRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 13)), dolorRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 14)), dolorRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 15)), dolorRange);
expect(paragraph.getWordBoundary(const TextPosition(offset: 16)), dolorRange);
const TextRange endRange = TextRange(start: 17, end: 17);
expect(paragraph.getWordBoundary(const TextPosition(offset: 17)), endRange);
});
testEachMeasurement('getBoxesForRange returns a box', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.rtl,
));
builder.addText('abcd');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 1000));
expect(
paragraph.getBoxesForRange(1, 2).single,
const TextBox.fromLTRBD(
970,
0,
980,
10,
TextDirection.rtl,
),
);
});
testEachMeasurement('getBoxesForRange returns a box for rich text', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
));
builder.addText('abcd');
builder.pushStyle(TextStyle(fontWeight: FontWeight.bold));
builder.addText('xyz');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 1000));
expect(
paragraph.getBoxesForRange(1, 2).single,
const TextBox.fromLTRBD(0, 0, 0, 10, TextDirection.ltr),
);
});
testEachMeasurement(
'getBoxesForRange return empty list for zero-length range', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText('abcd');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 1000));
expect(paragraph.getBoxesForRange(0, 0), isEmpty);
});
testEachMeasurement('getBoxesForRange multi-line', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
));
builder.addText('abcd\n');
builder.addText('abcdefg\n');
builder.addText('ab');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));
// First line: "abcd\n"
// At the beginning of the first line.
expect(
paragraph.getBoxesForRange(0, 0),
<TextBox>[],
);
// At the end of the first line.
expect(
paragraph.getBoxesForRange(4, 4),
<TextBox>[],
);
// Between "b" and "c" in the first line.
expect(
paragraph.getBoxesForRange(2, 2),
<TextBox>[],
);
// The range "ab" in the first line.
expect(
paragraph.getBoxesForRange(0, 2),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 20.0, 10.0, TextDirection.ltr),
],
);
// The range "bc" in the first line.
expect(
paragraph.getBoxesForRange(1, 3),
const <TextBox>[
TextBox.fromLTRBD(10.0, 0.0, 30.0, 10.0, TextDirection.ltr),
],
);
// The range "d" in the first line.
expect(
paragraph.getBoxesForRange(3, 4),
const <TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// The range "\n" in the first line.
expect(
paragraph.getBoxesForRange(4, 5),
const <TextBox>[
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// The range "cd\n" in the first line.
expect(
paragraph.getBoxesForRange(2, 5),
const <TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// Second line: "abcdefg\n"
// At the beginning of the second line.
expect(
paragraph.getBoxesForRange(5, 5),
<TextBox>[],
);
// At the end of the second line.
expect(
paragraph.getBoxesForRange(12, 12),
<TextBox>[],
);
// The range "efg" in the second line.
expect(
paragraph.getBoxesForRange(9, 12),
const <TextBox>[
TextBox.fromLTRBD(40.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// The range "bcde" in the second line.
expect(
paragraph.getBoxesForRange(6, 10),
const <TextBox>[
TextBox.fromLTRBD(10.0, 10.0, 50.0, 20.0, TextDirection.ltr),
],
);
// The range "fg\n" in the second line.
expect(
paragraph.getBoxesForRange(10, 13),
const <TextBox>[
TextBox.fromLTRBD(50.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// Last (third) line: "ab"
// At the beginning of the last line.
expect(
paragraph.getBoxesForRange(13, 13),
<TextBox>[],
);
// At the end of the last line.
expect(
paragraph.getBoxesForRange(15, 15),
<TextBox>[],
);
// The range "a" in the last line.
expect(
paragraph.getBoxesForRange(14, 15),
const <TextBox>[
TextBox.fromLTRBD(10.0, 20.0, 20.0, 30.0, TextDirection.ltr),
],
);
// The range "ab" in the last line.
expect(
paragraph.getBoxesForRange(13, 15),
const <TextBox>[
TextBox.fromLTRBD(0.0, 20.0, 20.0, 30.0, TextDirection.ltr),
],
);
// Combine multiple lines
// The range "cd\nabc".
expect(
paragraph.getBoxesForRange(2, 8),
const <TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 30.0, 20.0, TextDirection.ltr),
],
);
// The range "\nabcd".
expect(
paragraph.getBoxesForRange(4, 9),
const <TextBox>[
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 40.0, 20.0, TextDirection.ltr),
],
);
// The range "d\nabcdefg\na".
expect(
paragraph.getBoxesForRange(3, 14),
const <TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 20.0, 10.0, 30.0, TextDirection.ltr),
],
);
// The range "abcd\nabcdefg\n".
expect(
paragraph.getBoxesForRange(0, 13),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// The range "abcd\nabcdefg\nab".
expect(
paragraph.getBoxesForRange(0, 15),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 20.0, 20.0, 30.0, TextDirection.ltr),
],
);
});
testEachMeasurement('getBoxesForRange with maxLines', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
maxLines: 2,
));
builder.addText('abcd\n');
builder.addText('abcdefg\n');
builder.addText('ab');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));
// First line: "abcd\n"
// At the beginning of the first line.
expect(
paragraph.getBoxesForRange(0, 0),
<TextBox>[],
);
// At the end of the first line.
expect(
paragraph.getBoxesForRange(4, 4),
<TextBox>[],
);
// Between "b" and "c" in the first line.
expect(
paragraph.getBoxesForRange(2, 2),
<TextBox>[],
);
// The range "ab" in the first line.
expect(
paragraph.getBoxesForRange(0, 2),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 20.0, 10.0, TextDirection.ltr),
],
);
// The range "bc" in the first line.
expect(
paragraph.getBoxesForRange(1, 3),
const <TextBox>[
TextBox.fromLTRBD(10.0, 0.0, 30.0, 10.0, TextDirection.ltr),
],
);
// The range "d" in the first line.
expect(
paragraph.getBoxesForRange(3, 4),
const <TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// The range "\n" in the first line.
expect(
paragraph.getBoxesForRange(4, 5),
const <TextBox>[
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// The range "cd\n" in the first line.
expect(
paragraph.getBoxesForRange(2, 5),
const <TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// Second line: "abcdefg\n"
// At the beginning of the second line.
expect(
paragraph.getBoxesForRange(5, 5),
<TextBox>[],
);
// At the end of the second line.
expect(
paragraph.getBoxesForRange(12, 12),
<TextBox>[],
);
// The range "efg" in the second line.
expect(
paragraph.getBoxesForRange(9, 12),
const <TextBox>[
TextBox.fromLTRBD(40.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// The range "bcde" in the second line.
expect(
paragraph.getBoxesForRange(6, 10),
const <TextBox>[
TextBox.fromLTRBD(10.0, 10.0, 50.0, 20.0, TextDirection.ltr),
],
);
// The range "fg\n" in the second line.
expect(
paragraph.getBoxesForRange(10, 13),
const <TextBox>[
TextBox.fromLTRBD(50.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// Last (third) line: "ab"
// At the beginning of the last line.
expect(
paragraph.getBoxesForRange(13, 13),
<TextBox>[],
);
// At the end of the last line.
expect(
paragraph.getBoxesForRange(15, 15),
<TextBox>[],
);
// The range "a" in the last line.
expect(
paragraph.getBoxesForRange(14, 15),
<TextBox>[],
);
// The range "ab" in the last line.
expect(
paragraph.getBoxesForRange(13, 15),
<TextBox>[],
);
// Combine multiple lines
// The range "cd\nabc".
expect(
paragraph.getBoxesForRange(2, 8),
const <TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 30.0, 20.0, TextDirection.ltr),
],
);
// The range "\nabcd".
expect(
paragraph.getBoxesForRange(4, 9),
const <TextBox>[
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 40.0, 20.0, TextDirection.ltr),
],
);
// The range "d\nabcdefg\na".
expect(
paragraph.getBoxesForRange(3, 14),
const <TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// The range "abcd\nabcdefg\n".
expect(
paragraph.getBoxesForRange(0, 13),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// The range "abcd\nabcdefg\nab".
expect(
paragraph.getBoxesForRange(0, 15),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
});
testEachMeasurement('getBoxesForRange includes trailing spaces', () {
const String text = 'abcd abcde ';
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText(text);
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.getBoxesForRange(0, text.length),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 120.0, 10.0, TextDirection.ltr),
],
);
});
testEachMeasurement('getBoxesForRange multi-line includes trailing spaces', () {
const String text = 'abcd\nabcde \nabc';
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText(text);
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(
paragraph.getBoxesForRange(0, text.length),
const <TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 20.0, 30.0, 30.0, TextDirection.ltr),
],
);
});
test('longestLine', () {
// [Paragraph.longestLine] is only supported by canvas-based measurement.
WebExperiments.instance!.useCanvasText = true;
WebExperiments.instance!.useCanvasRichText = false;
TextMeasurementService.initialize(rulerCacheCapacity: 2);
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText('abcd\nabcde abc');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 80.0));
expect(paragraph.longestLine, 50.0);
TextMeasurementService.clearCache();
WebExperiments.instance!.useCanvasText = null;
WebExperiments.instance!.useCanvasRichText = null;
});
testEachMeasurement('getLineBoundary (single-line)', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText('One single line');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 400.0));
// "One single line".length == 15
for (int i = 0; i < 15; i++) {
expect(
paragraph.getLineBoundary(TextPosition(offset: i)),
const TextRange(start: 0, end: 15),
reason: 'failed at offset $i',
);
}
});
test('getLineBoundary (multi-line)', () {
// [Paragraph.getLineBoundary] for multi-line paragraphs is only supported
// by canvas-based measurement.
WebExperiments.instance!.useCanvasText = true;
WebExperiments.instance!.useCanvasRichText = false;
TextMeasurementService.initialize(rulerCacheCapacity: 2);
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText('First line\n');
builder.addText('Second line\n');
builder.addText('Third line');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 400.0));
// "First line\n".length == 11
for (int i = 0; i < 11; i++) {
expect(
paragraph.getLineBoundary(TextPosition(offset: i)),
const TextRange(start: 0, end: 11),
reason: 'failed at offset $i',
);
}
// "Second line\n".length == 12
for (int i = 11; i < 23; i++) {
expect(
paragraph.getLineBoundary(TextPosition(offset: i)),
const TextRange(start: 11, end: 23),
reason: 'failed at offset $i',
);
}
// "Third line".length == 10
for (int i = 23; i < 33; i++) {
expect(
paragraph.getLineBoundary(TextPosition(offset: i)),
const TextRange(start: 23, end: 33),
reason: 'failed at offset $i',
);
}
TextMeasurementService.clearCache();
WebExperiments.instance!.useCanvasText = null;
WebExperiments.instance!.useCanvasRichText = null;
});
testEachMeasurement('width should be a whole integer', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
));
builder.addText('abc');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 30.8));
expect(paragraph.width, 30);
expect(paragraph.height, 10);
});
}