您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

473 行
15 KiB

using System;
using System.Collections.Generic;
using UIWidgets.foundation;
using UIWidgets.service;
using UIWidgets.ui;
using UnityEngine;
using Canvas = UIWidgets.ui.Canvas;
using Rect = UIWidgets.ui.Rect;
namespace UIWidgets.painting
{
public class TextPainter
{
private TextSpan _text;
private TextAlign _textAlign;
private TextDirection? _textDirection;
private double _textScaleFactor;
private Paragraph _layoutTemplate;
private Paragraph _paragraph;
private bool _needsLayout = true;
private int _maxLines;
private string _ellipsis;
private double _lastMinWidth;
private double _lastMaxWidth;
public TextPainter(TextSpan text,
TextAlign textAlign = TextAlign.left,
TextDirection textDirection = TextDirection.ltr,
double textScaleFactor = 1.0,
int maxLines = 0,
string ellipsis = "")
{
_text = text;
_textAlign = textAlign;
_textDirection = textDirection;
_textScaleFactor = textScaleFactor;
_maxLines = maxLines;
_ellipsis = ellipsis;
}
public double textScaleFactor
{
get { return _textScaleFactor; }
set
{
if (_textScaleFactor == value)
return;
_textScaleFactor = value;
_paragraph = null;
_layoutTemplate = null;
_needsLayout = true;
}
}
public string ellipsis
{
get { return _ellipsis; }
set
{
if (_ellipsis == value)
{
return;
}
_ellipsis = value;
_paragraph = null;
_needsLayout = true;
}
}
public TextSpan text
{
get { return _text; }
set
{
if (text.Equals(value))
{
return;
}
if (!Equals(_text == null ? null : _text.style, value == null ? null : value.style))
{
_layoutTemplate = null;
}
_text = value;
_paragraph = null;
_needsLayout = true;
}
}
public Size size
{
get
{
Debug.Assert(!_needsLayout);
return new Size(width, height);
}
}
public TextDirection? textDirection
{
get { return _textDirection; }
set
{
if (textDirection == value)
{
return;
}
_textDirection = value;
_paragraph = null;
_layoutTemplate = null;
_needsLayout = true;
}
}
public TextAlign textAlign
{
get { return _textAlign; }
set
{
if (_textAlign == value)
{
return;
}
_textAlign = value;
_paragraph = null;
_needsLayout = true;
}
}
public bool didExceedMaxLines
{
get
{
Debug.Assert(!_needsLayout);
return _paragraph.didExceedMaxLines;
}
}
public int maxLines
{
get { return _maxLines; }
set
{
if (_maxLines == value)
{
return;
}
_maxLines = value;
_paragraph = null;
_needsLayout = true;
}
}
public double minIntrinsicWidth
{
get
{
Debug.Assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.minIntrinsicWidth);
}
}
public double maxIntrinsicWidth
{
get
{
Debug.Assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.maxIntrinsicWidth);
}
}
public double height
{
get
{
Debug.Assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.height);
}
}
public double width
{
get
{
Debug.Assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.width);
}
}
public double computeDistanceToActualBaseline(TextBaseline baseline)
{
Debug.Assert(!_needsLayout);
switch (baseline)
{
case TextBaseline.alphabetic:
return _paragraph.alphabeticBaseline;
case TextBaseline.ideographic:
return _paragraph.ideographicBaseline;
}
return 0.0;
}
public void layout(double minWidth = 0.0, double maxWidth = double.PositiveInfinity)
{
Debug.Assert(text != null, "TextPainter.text must be set to a non-null value before using the TextPainter.");
Debug.Assert(textDirection != null, "TextPainter.textDirection must be set to a non-null value before using the TextPainter.");
if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth)
{
return;
}
_needsLayout = false;
if (_paragraph == null)
{
var builder = new ParagraphBuilder(_createParagraphStyle());
_text.build(builder, textScaleFactor);
_paragraph = builder.build();
}
_lastMinWidth = minWidth;
_lastMaxWidth = maxWidth;
_paragraph.layout(new ParagraphConstraints(maxWidth));
if (minWidth != maxWidth)
{
var newWidth = MathUtils.clamp(maxIntrinsicWidth, minWidth, maxWidth);
if (newWidth != width)
{
_paragraph.layout(new ParagraphConstraints(newWidth));
}
}
}
public void paint(Canvas canvas, Offset offset)
{
Debug.Assert(!_needsLayout);
_paragraph.paint(canvas, offset);
}
public Offset getOffsetForCaret(TextPosition position, Rect caretPrototype)
{
D.assert(!_needsLayout);
var offset = position.offset;
if (offset > 0)
{
var prevCodeUnit = _text.codeUnitAt(offset);
if (prevCodeUnit == null) // out of upper bounds
{
var rectNextLine = _paragraph.getNextLineStartRect();
if (rectNextLine != null)
{
return new Offset(rectNextLine.start, rectNextLine.top);
}
}
}
switch (position.affinity)
{
case TextAffinity.upstream:
return _getOffsetFromUpstream(offset, caretPrototype) ??
_getOffsetFromDownstream(offset, caretPrototype) ?? _emptyOffset;
case TextAffinity.downstream:
return _getOffsetFromDownstream(offset, caretPrototype) ??
_getOffsetFromUpstream(offset, caretPrototype) ?? _emptyOffset;
}
return null;
}
public Paragraph.LineRange getLineRange(int lineNumber)
{
D.assert(!_needsLayout);
return _paragraph.getLineRange(lineNumber);
}
public Paragraph.LineRange getLineRange(TextPosition textPosition)
{
return getLineRange(getLineIndex(textPosition));
}
public List<TextBox> getBoxesForSelection(TextSelection selection)
{
D.assert(!_needsLayout);
var results = _paragraph.getRectsForRange(selection.start, selection.end);
return results;
}
public TextPosition getPositionForOffset(Offset offset) {
D.assert(!_needsLayout);
var result = _paragraph.getGlyphPositionAtCoordinate(offset.dx, offset.dy);
return new TextPosition(result.position, result.affinity);
}
public TextRange getWordBoundary(TextPosition position)
{
D.assert(!_needsLayout);
var range = _paragraph.getWordBoundary(position.offset);
return new TextRange(range.start, range.end);
}
public TextPosition getPositionVerticalMove(TextPosition position, int move)
{
D.assert(!_needsLayout);
var offset = getOffsetForCaret(position, Rect.zero);
var lineIndex = Math.Min(Math.Max(_paragraph.getLine(position) + move, 0), _paragraph.getLineCount());
var targetLineStart = _paragraph.getLineRange(lineIndex).start;
var newLineOffset = getOffsetForCaret(new TextPosition(targetLineStart), Rect.zero);
return getPositionForOffset(new Offset(offset.dx, newLineOffset.dy));
}
public int getLineIndex(TextPosition position)
{
D.assert(!_needsLayout);
return _paragraph.getLine(position);
}
public int getLineCount()
{
D.assert(!_needsLayout);
return _paragraph.getLineCount();
}
public TextPosition getWordRight(TextPosition position)
{
D.assert(!_needsLayout);
var offset = position.offset;
while(true)
{
var range = _paragraph.getWordBoundary(offset);
if (range.end == range.start)
{
break;
}
if (!char.IsWhiteSpace((char)(text.codeUnitAt(range.start)??0)))
{
return new TextPosition(range.end);
}
offset = range.end;
}
return new TextPosition(offset, position.affinity);
}
public TextPosition getWordLeft(TextPosition position)
{
D.assert(!_needsLayout);
var offset = Math.Max(position.offset - 1, 0);
while(true)
{
var range = _paragraph.getWordBoundary(offset);
if (!char.IsWhiteSpace((char)(text.codeUnitAt(range.start)??0)))
{
return new TextPosition(range.start);
}
offset = Math.Max(range.start - 1, 0);
if (offset == 0)
{
break;
}
}
return new TextPosition(offset, position.affinity);
}
private ParagraphStyle _createParagraphStyle(TextDirection defaultTextDirection = TextDirection.ltr)
{
if (_text.style == null)
{
return new ParagraphStyle(
textAlign: textAlign,
textDirection: textDirection ?? defaultTextDirection,
maxLines: maxLines,
ellipsis: ellipsis
);
}
return _text.style.getParagraphStyle(textAlign, textDirection ?? defaultTextDirection,
ellipsis, maxLines, textScaleFactor);
}
public double preferredLineHeight
{
get
{
if (_layoutTemplate == null)
{
var builder = new ParagraphBuilder(
_createParagraphStyle(TextDirection.ltr)
); // direction doesn't matter, text is just a space
if (text != null && text.style != null)
{
builder.pushStyle(text.style);
}
builder.addText(" ");
_layoutTemplate = builder.build();
_layoutTemplate.layout(new ParagraphConstraints(double.PositiveInfinity));
}
return _layoutTemplate.height;
}
}
private double _applyFloatingPointHack(double layoutValue)
{
return Math.Ceiling(layoutValue);
}
private Offset _getOffsetFromUpstream(int offset, Rect caretPrototype) {
var prevCodeUnit = _text.codeUnitAt(offset - 1);
if (prevCodeUnit == null)
return null;
var prevRuneOffset = _isUtf16Surrogate((int)prevCodeUnit) ? offset - 2 : offset - 1;
var boxes = _paragraph.getRectsForRange(prevRuneOffset, offset);
if (boxes.Count == 0)
return null;
var box = boxes[0];
var caretEnd = box.end;
var dx = box.direction == TextDirection.rtl ? caretEnd : caretEnd - caretPrototype.width;
return new Offset(dx, box.top);
}
private Offset _getOffsetFromDownstream(int offset, Rect caretPrototype) {
var nextCodeUnit = _text.codeUnitAt(offset);
if (nextCodeUnit == null)
return null;
var nextRuneOffset = _isUtf16Surrogate((int)nextCodeUnit) ? offset + 2 : offset + 1;
var boxes = _paragraph.getRectsForRange(offset, nextRuneOffset);
if (boxes.Count == 0)
return null;
var box = boxes[0];
var caretStart = box.start;
var dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart;
return new Offset(dx, box.top);
}
private Offset _emptyOffset
{
get
{
D.assert(!_needsLayout);
switch (textAlign)
{
case TextAlign.left:
return Offset.zero;
case TextAlign.right:
return new Offset(width, 0.0);
case TextAlign.center:
return new Offset(width / 2.0, 0.0);
case TextAlign.justify:
if (textDirection == TextDirection.rtl)
{
return new Offset(width, 0.0);
}
return Offset.zero;
}
return null;
}
}
private static bool _isUtf16Surrogate(int value)
{
return (value & 0xF800) == 0xD800;
}
}
}