Commit f5bd8976 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Expose some text metrics on RenderParagraph (#7456)

The underlying TextPainter is not exposed, so this patch exposes some
useful metrics that clients might want to read.
parent 394a7369
......@@ -6,6 +6,8 @@ import 'dart:ui' show hashValues, TextAffinity, TextPosition;
export 'dart:ui' show TextAffinity, TextPosition;
import 'package:meta/meta.dart';
/// A range of characters in a string of text.
class TextRange {
/// Creates a text range.
......@@ -15,7 +17,10 @@ class TextRange {
///
/// Instead of creating an empty text range, consider using the [empty]
/// constant.
const TextRange({ this.start, this.end });
const TextRange({
@required this.start,
@required this.end
});
/// A text range that starts and ends at offset.
const TextRange.collapsed(int offset)
......@@ -89,8 +94,8 @@ class TextSelection extends TextRange {
///
/// The [baseOffset] and [extentOffset] arguments must not be null.
const TextSelection({
int baseOffset,
int extentOffset,
@required int baseOffset,
@required int extentOffset,
this.affinity: TextAffinity.downstream,
this.isDirectional: false
}) : baseOffset = baseOffset,
......@@ -108,7 +113,7 @@ class TextSelection extends TextRange {
///
/// The [offset] argument must not be null.
const TextSelection.collapsed({
int offset,
@required int offset,
this.affinity: TextAffinity.downstream
}) : baseOffset = offset, extentOffset = offset, isDirectional = false, super.collapsed(offset);
......
......@@ -115,6 +115,10 @@ class RenderParagraph extends RenderBox {
_textPainter.layout(minWidth: minWidth, maxWidth: wrap ? maxWidth : double.INFINITY);
}
void _layoutTextWithConstraints(BoxConstraints constraints) {
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
}
@override
double computeMinIntrinsicWidth(double height) {
_layoutText();
......@@ -147,7 +151,7 @@ class RenderParagraph extends RenderBox {
assert(!needsLayout);
assert(constraints != null);
assert(constraints.debugAssertIsValid());
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
_layoutTextWithConstraints(constraints);
return _textPainter.computeDistanceToActualBaseline(baseline);
}
......@@ -159,7 +163,7 @@ class RenderParagraph extends RenderBox {
assert(debugHandleEvent(event, entry));
if (event is! PointerDownEvent)
return;
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
_layoutTextWithConstraints(constraints);
Offset offset = entry.localPosition.toOffset();
TextPosition position = _textPainter.getPositionForOffset(offset);
TextSpan span = _textPainter.text.getSpanForPosition(position);
......@@ -171,7 +175,7 @@ class RenderParagraph extends RenderBox {
@override
void performLayout() {
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
_layoutTextWithConstraints(constraints);
// We grab _textPainter.size here because assigning to `size` will trigger
// us to validate our intrinsic sizes, which will change _textPainter's
// layout because the intrinsic size calculations are destructive.
......@@ -222,7 +226,7 @@ class RenderParagraph extends RenderBox {
//
// If you remove this call, make sure that changing the textAlign still
// works properly.
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
_layoutTextWithConstraints(constraints);
final Canvas canvas = context.canvas;
if (_hasVisualOverflow) {
final Rect bounds = offset & size;
......@@ -245,6 +249,52 @@ class RenderParagraph extends RenderBox {
}
}
/// Returns the offset at which to paint the caret.
///
/// Valid only after [layout].
Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) {
assert(!needsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getOffsetForCaret(position, caretPrototype);
}
/// Returns a list of rects that bound the given selection.
///
/// A given selection might have more than one rect if this text painter
/// contains bidirectional text because logically contiguous text might not be
/// visually contiguous.
///
/// Valid only after [layout].
List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
assert(!needsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getBoxesForSelection(selection);
}
/// Returns the position within the text for the given pixel offset.
///
/// Valid only after [layout].
TextPosition getPositionForOffset(Offset offset) {
assert(!needsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getPositionForOffset(offset);
}
/// Returns the text range of the word at the given offset. Characters not
/// part of a word, such as spaces, symbols, and punctuation, have word breaks
/// on both sides. In such cases, this method will return a text range that
/// contains the given text position.
///
/// Word boundaries are defined more precisely in Unicode Standard Annex #29
/// <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
///
/// Valid only after [layout].
TextRange getWordBoundary(TextPosition position) {
assert(!needsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getWordBoundary(position);
}
@override
SemanticsAnnotator get semanticsAnnotator => _annotate;
......
// Copyright 2016 The Chromium 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' as ui show TextBox;
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
const String _kText = 'I polished up that handle so carefullee\nThat now I am the Ruler of the Queen\'s Navee!';
void main() {
test('getOffsetForCaret control test', () {
RenderParagraph paragraph = new RenderParagraph(new TextSpan(text: _kText));
layout(paragraph);
Rect caret = new Rect.fromLTWH(0.0, 0.0, 2.0, 20.0);
Offset offset5 = paragraph.getOffsetForCaret(new TextPosition(offset: 5), caret);
expect(offset5.dx, greaterThan(0.0));
Offset offset25 = paragraph.getOffsetForCaret(new TextPosition(offset: 25), caret);
expect(offset25.dx, greaterThan(offset5.dx));
Offset offset50 = paragraph.getOffsetForCaret(new TextPosition(offset: 50), caret);
expect(offset50.dy, greaterThan(offset5.dy));
});
test('getPositionForOffset control test', () {
RenderParagraph paragraph = new RenderParagraph(new TextSpan(text: _kText));
layout(paragraph);
TextPosition position20 = paragraph.getPositionForOffset(new Offset(20.0, 5.0));
expect(position20.offset, greaterThan(0.0));
TextPosition position40 = paragraph.getPositionForOffset(new Offset(40.0, 5.0));
expect(position40.offset, greaterThan(position20.offset));
TextPosition positionBelow = paragraph.getPositionForOffset(new Offset(5.0, 20.0));
expect(positionBelow.offset, greaterThan(position40.offset));
});
test('getBoxesForSelection control test', () {
RenderParagraph paragraph = new RenderParagraph(new TextSpan(text: _kText));
layout(paragraph);
List<ui.TextBox> boxes = paragraph.getBoxesForSelection(
new TextSelection(baseOffset: 5, extentOffset: 25)
);
expect(boxes.length, equals(1));
boxes = paragraph.getBoxesForSelection(
new TextSelection(baseOffset: 25, extentOffset: 50)
);
expect(boxes.length, equals(3));
});
test('getWordBoundary control test', () {
RenderParagraph paragraph = new RenderParagraph(new TextSpan(text: _kText));
layout(paragraph);
TextRange range5 = paragraph.getWordBoundary(new TextPosition(offset: 5));
expect(range5.textInside(_kText), equals('polished'));
TextRange range50 = paragraph.getWordBoundary(new TextPosition(offset: 50));
expect(range50.textInside(_kText), equals(' '));
TextRange range85 = paragraph.getWordBoundary(new TextPosition(offset: 75));
expect(range85.textInside(_kText), equals('Queen\'s'));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment