|  | // 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:test/test.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, isNot(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); } | 
|  |  | 
|  | expect(callback, throwsA(isA<AssertionError>())); | 
|  | }); | 
|  |  | 
|  | test('rounding hack disabled', () { | 
|  | const double fontSize = 1.25; | 
|  | const String text = '12345'; | 
|  |  | 
|  | expect((fontSize * text.length).truncate(), isNot(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)); | 
|  | } | 
|  | }); | 
|  |  | 
|  | test('kTextHeightNone unsets the height multiplier', () { | 
|  | const double fontSize = 10; | 
|  | final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize, height: 10)); | 
|  | builder.pushStyle(TextStyle(height: kTextHeightNone)); | 
|  | builder.addText('A'); | 
|  | final Paragraph paragraph = builder.build() | 
|  | ..layout(const ParagraphConstraints(width: 1000)); | 
|  | expect(paragraph.height, fontSize); | 
|  | }); | 
|  |  | 
|  | test('kTextHeightNone ParagraphStyle', () { | 
|  | const double fontSize = 10; | 
|  | final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize, height: kTextHeightNone)); | 
|  | builder.addText('A'); | 
|  | final Paragraph paragraph = builder.build() | 
|  | ..layout(const ParagraphConstraints(width: 1000)); | 
|  | expect(paragraph.height, fontSize); | 
|  | }); | 
|  |  | 
|  | test('kTextHeightNone StrutStyle', () { | 
|  | const double fontSize = 10; | 
|  | final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( | 
|  | fontSize: 100, | 
|  | strutStyle: StrutStyle(forceStrutHeight: true, height: kTextHeightNone, fontSize: fontSize), | 
|  | )); | 
|  | builder.addText('A'); | 
|  | final Paragraph paragraph = builder.build() | 
|  | ..layout(const ParagraphConstraints(width: 1000)); | 
|  | expect(paragraph.height, fontSize); | 
|  | }); | 
|  | } |