Unverified Commit a282058d authored by Gary Qian's avatar Gary Qian Committed by GitHub

Use full textspan tree instead of top level textspan (#25574)

parent b17feefc
......@@ -420,6 +420,7 @@ class TextPainter {
// TODO(garyq): Use actual extended grapheme cluster length instead of
// an increasing cluster length amount to achieve deterministic performance.
Offset _getOffsetFromUpstream(int offset, Rect caretPrototype) {
final String flattenedText = _text.toPlainText();
final int prevCodeUnit = _text.codeUnitAt(max(0, offset - 1));
if (prevCodeUnit == null)
return null;
......@@ -427,7 +428,7 @@ class TextPainter {
final bool needsSearch = _isUtf16Surrogate(prevCodeUnit) || _text.codeUnitAt(offset) == _zwjUtf16;
int graphemeClusterLength = needsSearch ? 2 : 1;
List<TextBox> boxes = <TextBox>[];
while (boxes.isEmpty && _text.text != null) {
while (boxes.isEmpty && flattenedText != null) {
final int prevRuneOffset = offset - graphemeClusterLength;
boxes = _paragraph.getBoxesForRange(prevRuneOffset, offset);
// When the range does not include a full cluster, no boxes will be returned.
......@@ -436,7 +437,7 @@ class TextPainter {
// return empty boxes. We break and try from downstream instead.
if (!needsSearch)
break; // Only perform one iteration if no search is required.
if (prevRuneOffset < -_text.text.length)
if (prevRuneOffset < -flattenedText.length)
break; // Stop iterating when beyond the max length of the text.
// Multiply by two to log(n) time cover the entire text span. This allows
// faster discovery of very long clusters and reduces the possibility
......@@ -445,7 +446,7 @@ class TextPainter {
graphemeClusterLength *= 2;
continue;
}
final TextBox box = boxes[0];
final TextBox box = boxes.first;
final double caretEnd = box.end;
final double dx = box.direction == TextDirection.rtl ? caretEnd - caretPrototype.width : caretEnd;
return Offset(dx, box.top);
......@@ -456,15 +457,16 @@ class TextPainter {
// TODO(garyq): Use actual extended grapheme cluster length instead of
// an increasing cluster length amount to achieve deterministic performance.
Offset _getOffsetFromDownstream(int offset, Rect caretPrototype) {
final String flattenedText = _text.toPlainText();
// We cap the offset at the final index of the _text.
final int nextCodeUnit = _text.codeUnitAt(min(offset, _text.text == null ? 0 : _text.text.length - 1));
final int nextCodeUnit = _text.codeUnitAt(min(offset, flattenedText == null ? 0 : flattenedText.length - 1));
if (nextCodeUnit == null)
return null;
// Check for multi-code-unit glyphs such as emojis or zero width joiner
final bool needsSearch = _isUtf16Surrogate(nextCodeUnit) || nextCodeUnit == _zwjUtf16;
int graphemeClusterLength = needsSearch ? 2 : 1;
List<TextBox> boxes = <TextBox>[];
while (boxes.isEmpty && _text.text != null) {
while (boxes.isEmpty && flattenedText != null) {
final int nextRuneOffset = offset + graphemeClusterLength;
boxes = _paragraph.getBoxesForRange(offset, nextRuneOffset);
// When the range does not include a full cluster, no boxes will be returned.
......@@ -473,7 +475,7 @@ class TextPainter {
// return empty boxes. We break and try from upstream instead.
if (!needsSearch)
break; // Only perform one iteration if no search is required.
if (nextRuneOffset >= _text.text.length << 1)
if (nextRuneOffset >= flattenedText.length << 1)
break; // Stop iterating when beyond the max length of the text.
// Multiply by two to log(n) time cover the entire text span. This allows
// faster discovery of very long clusters and reduces the possibility
......@@ -482,7 +484,7 @@ class TextPainter {
graphemeClusterLength *= 2;
continue;
}
final TextBox box = boxes[0];
final TextBox box = boxes.last;
final double caretStart = box.start;
final double dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart;
return Offset(dx, box.top);
......
......@@ -29,6 +29,31 @@ void main() {
expect(caretOffset.dx, painter.width);
});
test('TextPainter null text test', () {
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr;
List<TextSpan> children = <TextSpan>[const TextSpan(text: 'B'), const TextSpan(text: 'C')];
painter.text = TextSpan(text: null, children: children);
painter.layout();
Offset caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero);
expect(caretOffset.dx, 0);
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero);
expect(caretOffset.dx, painter.width / 2);
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 2), ui.Rect.zero);
expect(caretOffset.dx, painter.width);
children = <TextSpan>[];
painter.text = TextSpan(text: null, children: children);
painter.layout();
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero);
expect(caretOffset.dx, 0);
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero);
expect(caretOffset.dx, 0);
});
test('TextPainter caret emoji test', () {
final TextPainter painter = TextPainter()
..textDirection = TextDirection.ltr;
......
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