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;
|
||||
|
||||
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;
|
||||
|
||||
|
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