blob: d8acb74c50c34c8c2760e7c0ede9d4fbdb3bfb44 [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 'dart:ui';
import 'package:litetest/litetest.dart';
void main() {
// Ahem font uses a constant ideographic/alphabetic baseline ratio.
const double kAhemBaselineRatio = 1.25;
test('predictably lays out a single-line paragraph - Ahem', () {
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, 0.001),
);
}
});
test('predictably lays out a single-line paragraph - FlutterTest', () {
for (final double fontSize in <double>[10.0, 20.0, 30.0, 40.0]) {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'FlutterTest',
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, fontSize);
expect(paragraph.width, 400.0);
expect(paragraph.minIntrinsicWidth, fontSize * 4.0);
expect(paragraph.maxIntrinsicWidth, fontSize * 4.0);
expect(paragraph.alphabeticBaseline, fontSize * 0.75);
expect(paragraph.ideographicBaseline, fontSize);
}
});
test('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, 0.001),
);
}
});
test('getLineBoundary', () {
const double fontSize = 10.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(const ParagraphConstraints(width: fontSize * 5.0));
// Wraps to two lines.
expect(paragraph.height, closeTo(fontSize * 2.0, 0.001));
const TextPosition wrapPositionDown = TextPosition(
offset: 5,
);
TextRange line = paragraph.getLineBoundary(wrapPositionDown);
expect(line.start, 5);
expect(line.end, 9);
const TextPosition wrapPositionUp = TextPosition(
offset: 5,
affinity: TextAffinity.upstream,
);
line = paragraph.getLineBoundary(wrapPositionUp);
expect(line.start, 0);
expect(line.end, 5);
const TextPosition wrapPositionStart = TextPosition(
offset: 0,
);
line = paragraph.getLineBoundary(wrapPositionStart);
expect(line.start, 0);
expect(line.end, 5);
const TextPosition wrapPositionEnd = TextPosition(
offset: 9,
);
line = paragraph.getLineBoundary(wrapPositionEnd);
expect(line.start, 5);
expect(line.end, 9);
});
test('getLineBoundary RTL', () {
const double fontSize = 10.0;
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: fontSize,
textDirection: TextDirection.rtl,
));
builder.addText('القاهرةالقاهرة');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: fontSize * 5.0));
// Wraps to three lines.
expect(paragraph.height, closeTo(fontSize * 3.0, 0.001));
const TextPosition wrapPositionDown = TextPosition(
offset: 5,
);
TextRange line = paragraph.getLineBoundary(wrapPositionDown);
expect(line.start, 5);
expect(line.end, 10);
const TextPosition wrapPositionUp = TextPosition(
offset: 5,
affinity: TextAffinity.upstream,
);
line = paragraph.getLineBoundary(wrapPositionUp);
expect(line.start, 0);
expect(line.end, 5);
const TextPosition wrapPositionStart = TextPosition(
offset: 0,
);
line = paragraph.getLineBoundary(wrapPositionStart);
expect(line.start, 0);
expect(line.end, 5);
const TextPosition wrapPositionEnd = TextPosition(
offset: 9,
);
line = paragraph.getLineBoundary(wrapPositionEnd);
expect(line.start, 5);
expect(line.end, 10);
});
test('getLineBoundary empty line', () {
const double fontSize = 10.0;
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: fontSize,
textDirection: TextDirection.rtl,
));
builder.addText('Test\n\nAhem');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: fontSize * 5.0));
// Three lines due to line breaks, with the middle line being empty.
expect(paragraph.height, closeTo(fontSize * 3.0, 0.001));
const TextPosition emptyLinePosition = TextPosition(
offset: 5,
);
TextRange line = paragraph.getLineBoundary(emptyLinePosition);
expect(line.start, 5);
expect(line.end, 5);
// Since these are hard newlines, TextAffinity has no effect here.
const TextPosition emptyLinePositionUpstream = TextPosition(
offset: 5,
affinity: TextAffinity.upstream,
);
line = paragraph.getLineBoundary(emptyLinePositionUpstream);
expect(line.start, 5);
expect(line.end, 5);
const TextPosition endOfFirstLinePosition = TextPosition(
offset: 4,
);
line = paragraph.getLineBoundary(endOfFirstLinePosition);
expect(line.start, 0);
expect(line.end, 4);
const TextPosition startOfLastLinePosition = TextPosition(
offset: 6,
);
line = paragraph.getLineBoundary(startOfLastLinePosition);
expect(line.start, 6);
expect(line.end, 10);
});
test('getLineMetricsAt', () {
const double fontSize = 10.0;
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontSize: fontSize,
textDirection: TextDirection.rtl,
height: 2.0,
));
builder.addText('Test\npppp');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100.0));
final LineMetrics? line = paragraph.getLineMetricsAt(1);
expect(line?.hardBreak, isTrue);
expect(line?.ascent, 15.0);
expect(line?.descent, 5.0);
expect(line?.height, 20.0);
expect(line?.width, 4 * 10.0);
expect(line?.left, 100.0 - 40.0);
expect(line?.baseline, 20.0 + 15.0);
expect(line?.lineNumber, 1);
});
test('line number', () {
const double fontSize = 10.0;
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize));
builder.addText('Test\n\nTest');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100.0));
expect(paragraph.numberOfLines, 3);
expect(paragraph.getLineNumberAt(4), 0); // first LF
expect(paragraph.getLineNumberAt(5), 1); // second LF
expect(paragraph.getLineNumberAt(6), 2); // "T" in the second "Test"
});
test('empty paragraph', () {
const double fontSize = 10.0;
final Paragraph paragraph = ParagraphBuilder(ParagraphStyle(
fontSize: fontSize,
)).build();
paragraph.layout(const ParagraphConstraints(width: double.infinity));
expect(paragraph.getClosestGlyphInfoForOffset(Offset.zero), isNull);
expect(paragraph.getGlyphInfoAt(0), isNull);
expect(paragraph.getLineMetricsAt(0), isNull);
expect(paragraph.numberOfLines, 0);
expect(paragraph.getLineNumberAt(0), isNull);
expect(paragraph.getGlyphInfoAt(0), isNull);
expect(paragraph.getClosestGlyphInfoForOffset(Offset.zero), isNull);
});
test('OOB indices as input', () {
const double fontSize = 10.0;
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontSize: fontSize,
maxLines: 1,
ellipsis: 'BBB',
))..addText('A' * 100);
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));
expect(paragraph.numberOfLines, 1);
expect(paragraph.getLineMetricsAt(-1), isNull);
expect(paragraph.getLineMetricsAt(0)?.lineNumber, 0);
expect(paragraph.getLineMetricsAt(1), isNull);
expect(paragraph.getLineNumberAt(-1), isNull);
expect(paragraph.getLineNumberAt(0), 0);
expect(paragraph.getLineNumberAt(6), 0);
// The last 3 characters on the first line are ellipsized with BBB.
expect(paragraph.getLineMetricsAt(7), isNull);
expect(paragraph.getGlyphInfoAt(-1), isNull);
expect(paragraph.getGlyphInfoAt(0)?.graphemeClusterCodeUnitRange, const TextRange(start: 0, end: 1));
expect(paragraph.getGlyphInfoAt(6)?.graphemeClusterCodeUnitRange, const TextRange(start: 6, end: 7));
expect(paragraph.getGlyphInfoAt(7), isNull);
expect(paragraph.getGlyphInfoAt(200), isNull);
});
test('querying glyph info', () {
const double fontSize = 10.0;
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontSize: fontSize,
));
builder.addText('Test\nTest');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: double.infinity));
final GlyphInfo? bottomRight = paragraph.getClosestGlyphInfoForOffset(const Offset(99.0, 99.0));
final GlyphInfo? last = paragraph.getGlyphInfoAt(8);
expect(bottomRight, equals(last));
expect(bottomRight, notEquals(paragraph.getGlyphInfoAt(0)));
expect(bottomRight?.graphemeClusterLayoutBounds, const Rect.fromLTWH(30, 10, 10, 10));
expect(bottomRight?.graphemeClusterCodeUnitRange, const TextRange(start: 8, end: 9));
expect(bottomRight?.writingDirection, TextDirection.ltr);
});
test('painting a disposed paragraph does not crash', () {
final Paragraph paragraph = ParagraphBuilder(ParagraphStyle()).build();
paragraph.dispose();
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
void callback() { canvas.drawParagraph(paragraph, Offset.zero); }
if (assertStatementsEnabled) {
expectAssertion(callback);
} else {
expect(callback, throwsStateError);
}
});
test('rounding hack disabled', () {
const double fontSize = 1.25;
const String text = '12345';
assert((fontSize * text.length).truncate() != fontSize * text.length);
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize));
builder.addText(text);
final Paragraph paragraph = builder.build()
..layout(const ParagraphConstraints(width: text.length * fontSize));
expect(paragraph.maxIntrinsicWidth, text.length * fontSize);
switch (paragraph.computeLineMetrics()) {
case [LineMetrics(width: final double width)]:
expect(width, text.length * fontSize);
case final List<LineMetrics> metrics:
expect(metrics, hasLength(1));
}
});
}