mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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.
This commit is contained in:
parent
394a736984
commit
f5bd8976de
@ -6,6 +6,8 @@ import 'dart:ui' show hashValues, TextAffinity, TextPosition;
|
|||||||
|
|
||||||
export 'dart:ui' show TextAffinity, TextPosition;
|
export 'dart:ui' show TextAffinity, TextPosition;
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// A range of characters in a string of text.
|
/// A range of characters in a string of text.
|
||||||
class TextRange {
|
class TextRange {
|
||||||
/// Creates a text range.
|
/// Creates a text range.
|
||||||
@ -15,7 +17,10 @@ class TextRange {
|
|||||||
///
|
///
|
||||||
/// Instead of creating an empty text range, consider using the [empty]
|
/// Instead of creating an empty text range, consider using the [empty]
|
||||||
/// constant.
|
/// constant.
|
||||||
const TextRange({ this.start, this.end });
|
const TextRange({
|
||||||
|
@required this.start,
|
||||||
|
@required this.end
|
||||||
|
});
|
||||||
|
|
||||||
/// A text range that starts and ends at offset.
|
/// A text range that starts and ends at offset.
|
||||||
const TextRange.collapsed(int offset)
|
const TextRange.collapsed(int offset)
|
||||||
@ -89,8 +94,8 @@ class TextSelection extends TextRange {
|
|||||||
///
|
///
|
||||||
/// The [baseOffset] and [extentOffset] arguments must not be null.
|
/// The [baseOffset] and [extentOffset] arguments must not be null.
|
||||||
const TextSelection({
|
const TextSelection({
|
||||||
int baseOffset,
|
@required int baseOffset,
|
||||||
int extentOffset,
|
@required int extentOffset,
|
||||||
this.affinity: TextAffinity.downstream,
|
this.affinity: TextAffinity.downstream,
|
||||||
this.isDirectional: false
|
this.isDirectional: false
|
||||||
}) : baseOffset = baseOffset,
|
}) : baseOffset = baseOffset,
|
||||||
@ -108,7 +113,7 @@ class TextSelection extends TextRange {
|
|||||||
///
|
///
|
||||||
/// The [offset] argument must not be null.
|
/// The [offset] argument must not be null.
|
||||||
const TextSelection.collapsed({
|
const TextSelection.collapsed({
|
||||||
int offset,
|
@required int offset,
|
||||||
this.affinity: TextAffinity.downstream
|
this.affinity: TextAffinity.downstream
|
||||||
}) : baseOffset = offset, extentOffset = offset, isDirectional = false, super.collapsed(offset);
|
}) : 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);
|
_textPainter.layout(minWidth: minWidth, maxWidth: wrap ? maxWidth : double.INFINITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _layoutTextWithConstraints(BoxConstraints constraints) {
|
||||||
|
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeMinIntrinsicWidth(double height) {
|
double computeMinIntrinsicWidth(double height) {
|
||||||
_layoutText();
|
_layoutText();
|
||||||
@ -147,7 +151,7 @@ class RenderParagraph extends RenderBox {
|
|||||||
assert(!needsLayout);
|
assert(!needsLayout);
|
||||||
assert(constraints != null);
|
assert(constraints != null);
|
||||||
assert(constraints.debugAssertIsValid());
|
assert(constraints.debugAssertIsValid());
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
_layoutTextWithConstraints(constraints);
|
||||||
return _textPainter.computeDistanceToActualBaseline(baseline);
|
return _textPainter.computeDistanceToActualBaseline(baseline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +163,7 @@ class RenderParagraph extends RenderBox {
|
|||||||
assert(debugHandleEvent(event, entry));
|
assert(debugHandleEvent(event, entry));
|
||||||
if (event is! PointerDownEvent)
|
if (event is! PointerDownEvent)
|
||||||
return;
|
return;
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
_layoutTextWithConstraints(constraints);
|
||||||
Offset offset = entry.localPosition.toOffset();
|
Offset offset = entry.localPosition.toOffset();
|
||||||
TextPosition position = _textPainter.getPositionForOffset(offset);
|
TextPosition position = _textPainter.getPositionForOffset(offset);
|
||||||
TextSpan span = _textPainter.text.getSpanForPosition(position);
|
TextSpan span = _textPainter.text.getSpanForPosition(position);
|
||||||
@ -171,7 +175,7 @@ class RenderParagraph extends RenderBox {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
_layoutTextWithConstraints(constraints);
|
||||||
// We grab _textPainter.size here because assigning to `size` will trigger
|
// We grab _textPainter.size here because assigning to `size` will trigger
|
||||||
// us to validate our intrinsic sizes, which will change _textPainter's
|
// us to validate our intrinsic sizes, which will change _textPainter's
|
||||||
// layout because the intrinsic size calculations are destructive.
|
// 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
|
// If you remove this call, make sure that changing the textAlign still
|
||||||
// works properly.
|
// works properly.
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
_layoutTextWithConstraints(constraints);
|
||||||
final Canvas canvas = context.canvas;
|
final Canvas canvas = context.canvas;
|
||||||
if (_hasVisualOverflow) {
|
if (_hasVisualOverflow) {
|
||||||
final Rect bounds = offset & size;
|
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
|
@override
|
||||||
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
SemanticsAnnotator get semanticsAnnotator => _annotate;
|
||||||
|
|
||||||
|
75
packages/flutter/test/rendering/paragraph_test.dart
Normal file
75
packages/flutter/test/rendering/paragraph_test.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// 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'));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user