GitHub
5 年前
当前提交
4532e4a1
共有 30 个文件被更改,包括 1409 次插入 和 1781 次删除
-
19Runtime/painting/text_painter.cs
-
14Runtime/rendering/editable.cs
-
2Runtime/ui/painting/draw_cmd.cs
-
2Runtime/ui/painting/picture.cs
-
99Runtime/ui/painting/txt/mesh_generator.cs
-
164Runtime/ui/painting/txt/text_blob.cs
-
10Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs
-
2Runtime/ui/renderer/common/draw_cmd.cs
-
3Runtime/ui/renderer/common/picture.cs
-
1Runtime/ui/renderer/compositeCanvas/flow/instrumentation.cs
-
1Runtime/ui/renderer/compositeCanvas/flow/performance_overlay_layer.cs
-
48Runtime/ui/text.cs
-
33Runtime/ui/txt/emoji.cs
-
500Runtime/ui/txt/layout.cs
-
50Runtime/ui/txt/layout_utils.cs
-
623Runtime/ui/txt/linebreaker.cs
-
32Runtime/ui/txt/paint_record.cs
-
1001Runtime/ui/txt/paragraph.cs
-
2Runtime/ui/txt/paragraph_builder.cs
-
6Runtime/ui/txt/styled_runs.cs
-
5Runtime/ui/txt/text_buff.cs
-
19Runtime/ui/txt/word_separate.cs
-
59Runtime/ui/txt/wordbreaker.cs
-
1Samples/UIWidgetSample/MaterialThemeSample.cs
-
3Samples/UIWidgetsGallery/demo/animation/home.cs
-
28Tests/Editor/CanvasAndLayers.cs
-
149Samples/UIWidgetSample/BenchMarkLayout.cs
-
11Samples/UIWidgetSample/BenchMarkLayout.cs.meta
-
296Samples/UIWidgetSample/TextLayoutSample.unity
-
7Samples/UIWidgetSample/TextLayoutSample.unity.meta
|
|||
namespace Unity.UIWidgets.ui { |
|||
public class TextBlob { |
|||
|
|||
internal TextBlob(string text, int textOffset, int textSize, Vector2d[] positions, Rect bounds, TextStyle style) { |
|||
this.instanceId = ++_nextInstanceId; |
|||
this.positions = positions; |
|||
this.text = text; |
|||
this.textOffset = textOffset; |
|||
this.textSize = textSize; |
|||
this.style = style; |
|||
this.bounds = bounds; |
|||
} |
|||
|
|||
public Rect boundsInText { |
|||
get { |
|||
if (this._boundsInText == null) { |
|||
this._boundsInText = this.bounds.shift(new Offset(this.positions[0].x, this.positions[0].y)); |
|||
} |
|||
|
|||
return this._boundsInText; |
|||
} |
|||
} |
|||
|
|||
static long _nextInstanceId = 0; |
|||
internal readonly long instanceId; |
|||
internal readonly string text; |
|||
internal readonly int textOffset; |
|||
internal readonly int textSize; |
|||
internal readonly Vector2d[] positions; |
|||
internal readonly TextStyle style; |
|||
internal readonly Rect bounds; // bounds with positions[start] as origin
|
|||
|
|||
Rect _boundsInText; |
|||
} |
|||
|
|||
public class TextBlobBuilder { |
|||
TextStyle _style; |
|||
public Vector2d[] positions; |
|||
string _text; |
|||
int _textOffset; |
|||
int _size; |
|||
Rect _bounds; |
|||
|
|||
public void allocRunPos(painting.TextStyle style, string text, int offset, int size, float textScaleFactor = 1.0f) { |
|||
this.allocRunPos(TextStyle.applyStyle(null, style, textScaleFactor), text, offset, size); |
|||
} |
|||
|
|||
internal void allocRunPos(TextStyle style, string text, int offset, int size) { |
|||
this._style = style; |
|||
this._text = text; |
|||
this._textOffset = offset; |
|||
this._size = size; |
|||
if (this.positions == null || this.positions.Length < size) { |
|||
this.positions = new Vector2d[size]; |
|||
} |
|||
} |
|||
|
|||
public void setBounds(Rect bounds) { |
|||
this._bounds = bounds; |
|||
} |
|||
|
|||
public TextBlob make() { |
|||
var result = new TextBlob(this._text, this._textOffset, |
|||
this._size, this.positions, this._bounds, this._style); |
|||
this.positions = null; |
|||
return result; |
|||
} |
|||
} |
|||
namespace Unity.UIWidgets.ui { |
|||
public struct TextBlob { |
|||
internal TextBlob(string text, int textOffset, int textSize, float[] positionXs, |
|||
UnityEngine.Rect bounds, TextStyle style) { |
|||
this.instanceId = ++_nextInstanceId; |
|||
this._positionXs = positionXs; |
|||
this.text = text; |
|||
this.textOffset = textOffset; |
|||
this.textSize = textSize; |
|||
this.style = style; |
|||
this._bounds = bounds; |
|||
this._boundsInText = null; |
|||
} |
|||
|
|||
public Rect boundsInText { |
|||
get { |
|||
if (this._boundsInText == null) { |
|||
var pos = this.getPositionX(0); |
|||
this._boundsInText = Rect.fromLTWH(this._bounds.xMin + pos, this._bounds.yMin, |
|||
this._bounds.width, this._bounds.height); |
|||
} |
|||
|
|||
return this._boundsInText; |
|||
} |
|||
} |
|||
|
|||
public Rect shiftedBoundsInText(float dx, float dy) { |
|||
var pos = this.getPositionX(0); |
|||
return Rect.fromLTWH(this._bounds.xMin + pos + dx, this._bounds.yMin + dy, |
|||
this._bounds.width, this._bounds.height); |
|||
} |
|||
|
|||
public float getPositionX(int i) { |
|||
return this._positionXs[this.textOffset + i]; |
|||
} |
|||
|
|||
static long _nextInstanceId; |
|||
internal readonly long instanceId; |
|||
internal readonly string text; |
|||
internal readonly int textOffset; |
|||
internal readonly int textSize; |
|||
internal readonly TextStyle style; |
|||
readonly UnityEngine.Rect _bounds; // bounds with positions[start] as origin
|
|||
readonly float[] _positionXs; |
|||
|
|||
Rect _boundsInText; |
|||
} |
|||
|
|||
public struct TextBlobBuilder { |
|||
TextStyle _style; |
|||
float[] _positionXs; |
|||
string _text; |
|||
int _textOffset; |
|||
int _size; |
|||
UnityEngine.Rect _bounds; |
|||
|
|||
public void allocRunPos(painting.TextStyle style, string text, int offset, int size, |
|||
float textScaleFactor = 1.0f) { |
|||
this.allocRunPos(TextStyle.applyStyle(null, style, textScaleFactor), text, offset, size); |
|||
} |
|||
|
|||
internal void allocRunPos(TextStyle style, string text, int offset, int size) { |
|||
this._style = style; |
|||
this._text = text; |
|||
this._textOffset = offset; |
|||
this._size = size; |
|||
// Allocate a single buffer for all text blobs that share this text, to save memory and GC.
|
|||
// It is assumed that all of `text` is being used. This may cause great waste if a long text is passed
|
|||
// but only a small part of it is to be rendered, which is not the case for now.
|
|||
this.allocPos(text.Length); |
|||
} |
|||
|
|||
internal void allocPos(int size) { |
|||
if (this._positionXs == null || this._positionXs.Length < size) { |
|||
this._positionXs = new float[size]; |
|||
} |
|||
} |
|||
|
|||
public void setPositionX(int i, float positionX) { |
|||
this._positionXs[this._textOffset + i] = positionX; |
|||
} |
|||
|
|||
public void setPositionXs(float[] positionXs) { |
|||
this._positionXs = positionXs; |
|||
} |
|||
|
|||
public void setBounds(UnityEngine.Rect bounds) { |
|||
this._bounds = bounds; |
|||
} |
|||
|
|||
public TextBlob make() { |
|||
var result = new TextBlob(this._text, this._textOffset, |
|||
this._size, this._positionXs, this._bounds, this._style); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.foundation; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
|
|||
class Layout { |
|||
int _start; |
|||
int _count; |
|||
List<float> _advances = new List<float>(); |
|||
List<float> _positions = new List<float>(); |
|||
float _advance; |
|||
UnityEngine.Rect _bounds; |
|||
TabStops _tabStops; |
|||
|
|||
|
|||
public static float measureText(float offset, TextBuff buff, int start, int count, TextStyle style, |
|||
List<float> advances, int advanceOffset, TabStops tabStops) { |
|||
Layout layout = new Layout(); |
|||
layout.setTabStops(tabStops); |
|||
layout.doLayout(offset, buff, start, count, style); |
|||
if (advances != null) { |
|||
var layoutAdv = layout.getAdvances(); |
|||
for (int i = 0; i < count; i++) { |
|||
advances[i + advanceOffset] = layoutAdv[i]; |
|||
} |
|||
} |
|||
|
|||
return layout.getAdvance(); |
|||
} |
|||
|
|||
public void doLayout(float offset, TextBuff buff, int start, int count, TextStyle style) { |
|||
this._start = start; |
|||
this._count = count; |
|||
this._advances.reset(count); |
|||
this._positions.reset(count); |
|||
this._advance = 0; |
|||
this._bounds = default; |
|||
|
|||
Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; |
|||
|
|||
char startingChar = buff.text[buff.offset + start]; |
|||
if (char.IsHighSurrogate(startingChar) || EmojiUtils.isSingleCharEmoji(startingChar)) { |
|||
this.layoutEmoji(buff.text.Substring(buff.offset + start, count), style, font, start, count); |
|||
} |
|||
else { |
|||
font.RequestCharactersInTextureSafe(buff.text, style.UnityFontSize, style.UnityFontStyle); |
|||
|
|||
int wordstart = start == buff.size |
|||
? start |
|||
: LayoutUtils.getPrevWordBreakForCache(buff, start + 1); |
|||
int wordend; |
|||
for (int iter = start; iter < start + count; iter = wordend) { |
|||
wordend = LayoutUtils.getNextWordBreakForCache(buff, iter); |
|||
int wordCount = Mathf.Min(start + count, wordend) - iter; |
|||
this.layoutWord(offset, iter - start, buff.subBuff(wordstart, wordend - wordstart), |
|||
iter - wordstart, wordCount, style, font); |
|||
wordstart = wordend; |
|||
} |
|||
} |
|||
this._count = count; |
|||
} |
|||
|
|||
void layoutWord(float offset, int layoutOffset, |
|||
TextBuff buff, int start, int wordCount, TextStyle style, Font font) { |
|||
float wordSpacing = |
|||
wordCount == 1 && LayoutUtils.isWordSpace(buff.charAt(start)) ? style.wordSpacing : 0; |
|||
|
|||
float x = this._advance; |
|||
float letterSpace = style.letterSpacing; |
|||
float letterSpaceHalfLeft = letterSpace * 0.5f; |
|||
float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft; |
|||
|
|||
for (int i = 0; i < wordCount; i++) { |
|||
var ch = buff.charAt(start + i); |
|||
if (i == 0) { |
|||
x += letterSpaceHalfLeft + wordSpacing; |
|||
this._advances[i + layoutOffset] += letterSpaceHalfLeft + wordSpacing; |
|||
} |
|||
else { |
|||
this._advances[i - 1 + layoutOffset] += letterSpaceHalfRight; |
|||
this._advances[i + layoutOffset] += letterSpaceHalfLeft; |
|||
x += letterSpace; |
|||
} |
|||
|
|||
if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { |
|||
var minX = glyphInfo.minX + x; |
|||
var maxX = glyphInfo.maxX + x; |
|||
var minY = -glyphInfo.maxY; |
|||
var maxY = -glyphInfo.minY; |
|||
|
|||
if (this._bounds.width <= 0 || this._bounds.height <= 0) { |
|||
this._bounds = UnityEngine.Rect.MinMaxRect( |
|||
minX, minY, maxX, maxY); |
|||
} else { |
|||
if (minX < this._bounds.x) { |
|||
this._bounds.x = minX; |
|||
} |
|||
if (minY < this._bounds.y) { |
|||
this._bounds.y = minY; |
|||
} |
|||
if (maxX > this._bounds.xMax) { |
|||
this._bounds.xMax = maxX; |
|||
} |
|||
if (maxY > this._bounds.yMax) { |
|||
this._bounds.yMax = maxY; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this._positions[i + layoutOffset] = x; |
|||
float advance = glyphInfo.advance; |
|||
if (ch == '\t') { |
|||
advance = this._tabStops.nextTab((this._advance + offset)) - this._advance; |
|||
} |
|||
x += advance; |
|||
this._advances[i + layoutOffset] += advance; |
|||
if (i + 1 == wordCount) { |
|||
this._advances[i + layoutOffset] += letterSpaceHalfRight; |
|||
x += letterSpaceHalfRight; |
|||
} |
|||
} |
|||
|
|||
this._advance = x; |
|||
} |
|||
|
|||
void layoutEmoji(string text, TextStyle style, Font font, int start, int count) { |
|||
for (int i = 0; i < count; i++) { |
|||
char c = text[i]; |
|||
float x = this._advance; |
|||
if (EmojiUtils.isSingleCharNonEmptyEmoji(c) || char.IsHighSurrogate(c)) { |
|||
float letterSpace = style.letterSpacing; |
|||
float letterSpaceHalfLeft = letterSpace * 0.5f; |
|||
float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft; |
|||
|
|||
x += letterSpaceHalfLeft; |
|||
this._advances[i] += letterSpaceHalfLeft; |
|||
|
|||
var metrics = FontMetrics.fromFont(font, style.UnityFontSize); |
|||
|
|||
var minX = x; |
|||
var maxX = metrics.descent - metrics.ascent + x; |
|||
var minY = metrics.ascent; |
|||
var maxY = metrics.descent; |
|||
|
|||
if (this._bounds.width <= 0 || this._bounds.height <= 0) { |
|||
this._bounds = UnityEngine.Rect.MinMaxRect( |
|||
minX, minY, maxX, maxY); |
|||
} |
|||
else { |
|||
if (minX < this._bounds.x) { |
|||
this._bounds.x = minX; |
|||
} |
|||
|
|||
if (minY < this._bounds.y) { |
|||
this._bounds.y = minY; |
|||
} |
|||
|
|||
if (maxX > this._bounds.xMax) { |
|||
this._bounds.xMax = maxX; |
|||
} |
|||
|
|||
if (maxY > this._bounds.yMax) { |
|||
this._bounds.yMax = maxY; |
|||
} |
|||
} |
|||
|
|||
this._positions[i] = x; |
|||
float advance = style.fontSize; |
|||
x += advance; |
|||
|
|||
this._advances[i] += advance; |
|||
this._advances[i] += letterSpaceHalfRight; |
|||
x += letterSpaceHalfRight; |
|||
} |
|||
else { |
|||
this._advances[i] = 0; |
|||
this._positions[i] = x; |
|||
} |
|||
|
|||
this._advance = x; |
|||
} |
|||
} |
|||
|
|||
public void setTabStops(TabStops tabStops) { |
|||
this._tabStops = tabStops; |
|||
} |
|||
|
|||
public int nGlyphs() { |
|||
return this._count; |
|||
} |
|||
|
|||
public List<float> getAdvances() { |
|||
return this._advances; |
|||
} |
|||
|
|||
public float getAdvance() { |
|||
return this._advance; |
|||
} |
|||
|
|||
public float getX(int index) { |
|||
return this._positions[index]; |
|||
} |
|||
|
|||
public float getY(int index) { |
|||
return 0; |
|||
} |
|||
|
|||
public float getCharAdvance(int index) { |
|||
return this._advances[index]; |
|||
} |
|||
|
|||
public Rect getBounds() { |
|||
return Rect.fromLTWH(this._bounds.x, this._bounds.y, this._bounds.width, this._bounds.height); |
|||
} |
|||
} |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
static class Layout { |
|||
// Measure the length of the span of the text. Currently, this is only used to compute the length
|
|||
// of ellipsis, assuming that the ellipsis does not contain any tab, tab is not considered for simplicity
|
|||
public static float measureText(string text, TextStyle style) { |
|||
char startingChar = text[0]; |
|||
float totalWidth = 0; |
|||
if (char.IsHighSurrogate(startingChar) || EmojiUtils.isSingleCharEmoji(startingChar)) { |
|||
float advance = style.fontSize + style.letterSpacing; |
|||
for (int i = 0; i < text.Length; i++) { |
|||
char ch = text[i]; |
|||
if (char.IsHighSurrogate(ch) || EmojiUtils.isSingleCharNonEmptyEmoji(ch)) { |
|||
totalWidth += advance; |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; |
|||
font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); |
|||
for (int i = 0; i < text.Length; i++) { |
|||
char ch = text[i]; |
|||
if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { |
|||
totalWidth += glyphInfo.advance + style.letterSpacing; |
|||
} |
|||
else { |
|||
totalWidth += style.letterSpacing; |
|||
} |
|||
|
|||
if (LayoutUtils.isWordSpace(ch)) { |
|||
totalWidth += style.wordSpacing; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return totalWidth; |
|||
} |
|||
|
|||
public static int computeTruncateCount(float offset, string text, int start, int count, TextStyle style, |
|||
float advanceLimit, TabStops tabStops) { |
|||
char startingChar = text[start]; |
|||
float currentAdvance = offset; |
|||
if (char.IsHighSurrogate(startingChar) || EmojiUtils.isSingleCharEmoji(startingChar)) { |
|||
float advance = style.fontSize + style.letterSpacing; |
|||
for (int i = 0; i < count; i++) { |
|||
char ch = text[start + i]; |
|||
if (char.IsHighSurrogate(ch) || EmojiUtils.isSingleCharNonEmptyEmoji(ch)) { |
|||
currentAdvance += advance; |
|||
if (currentAdvance > advanceLimit) { |
|||
return count - i; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; |
|||
for (int i = 0; i < count; i++) { |
|||
char ch = text[start + i]; |
|||
if (ch == '\t') { |
|||
currentAdvance = tabStops.nextTab(currentAdvance); |
|||
} |
|||
else if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { |
|||
currentAdvance += glyphInfo.advance + style.letterSpacing; |
|||
} |
|||
else { |
|||
currentAdvance += style.letterSpacing; |
|||
} |
|||
|
|||
if (LayoutUtils.isWordSpace(ch)) { |
|||
currentAdvance += style.wordSpacing; |
|||
} |
|||
|
|||
if (currentAdvance > advanceLimit) { |
|||
return count - i; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
public static float computeCharWidths(float offset, string text, int start, int count, TextStyle style, |
|||
float[] advances, int advanceOffset, TabStops tabStops) { |
|||
char startingChar = text[start]; |
|||
float totalWidths = 0; |
|||
if (char.IsHighSurrogate(startingChar) || EmojiUtils.isSingleCharEmoji(startingChar)) { |
|||
float advance = style.fontSize + style.letterSpacing; |
|||
for (int i = 0; i < count; i++) { |
|||
char ch = text[start + i]; |
|||
if (char.IsHighSurrogate(ch) || EmojiUtils.isSingleCharNonEmptyEmoji(ch)) { |
|||
advances[i + advanceOffset] = advance; |
|||
totalWidths += advance; |
|||
} |
|||
else { |
|||
advances[i + advanceOffset] = 0; |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; |
|||
// TODO: it is kind of a waste to require the entire string for this style, but SubString causes alloc
|
|||
font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); |
|||
for (int i = 0; i < count; i++) { |
|||
char ch = text[start + i]; |
|||
if (ch == '\t') { |
|||
advances[i + advanceOffset] = tabStops.nextTab(offset + totalWidths) - (offset + totalWidths); |
|||
} |
|||
else if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { |
|||
advances[i + advanceOffset] = glyphInfo.advance + style.letterSpacing; |
|||
} |
|||
else { |
|||
advances[i + advanceOffset] = style.letterSpacing; |
|||
} |
|||
|
|||
if (LayoutUtils.isWordSpace(ch)) { |
|||
advances[i + advanceOffset] += style.wordSpacing; |
|||
} |
|||
|
|||
totalWidths += advances[i + advanceOffset]; |
|||
} |
|||
} |
|||
|
|||
return totalWidths; |
|||
} |
|||
|
|||
public static float doLayout(float offset, string text, int start, int count, TextStyle style, |
|||
float[] advances, float[] positions, TabStops tabStops, out UnityEngine.Rect bounds) { |
|||
float advance = 0; |
|||
Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; |
|||
|
|||
char startingChar = text[start]; |
|||
bounds = new UnityEngine.Rect(); |
|||
if (char.IsHighSurrogate(startingChar) || EmojiUtils.isSingleCharEmoji(startingChar)) { |
|||
advance = _layoutEmoji(text, start, count, style, font, advances, positions, ref bounds); |
|||
} |
|||
else { |
|||
// According to the logic of Paragraph.layout, it is assured that all the characters are requested
|
|||
// in the texture before (in computing line breaks), so skip it here for optimization.
|
|||
// The only exception is the ellipsis, which did not appear in line breaking. It is taken care of
|
|||
// only when needed.
|
|||
|
|||
// font.RequestCharactersInTextureSafe(buff.text, style.UnityFontSize, style.UnityFontStyle);
|
|||
|
|||
// int wordstart = start == buff.size
|
|||
// ? start
|
|||
// : LayoutUtils.getPrevWordBreakForCache(buff, start + 1);
|
|||
int wordend; |
|||
for (int iter = start; iter < start + count; iter = wordend) { |
|||
wordend = LayoutUtils.getNextWordBreak(text, iter, start + count); |
|||
advance = _layoutWord(offset, iter - start, text, iter, |
|||
wordend - iter, style, font, advances, positions, advance, |
|||
tabStops, ref bounds); |
|||
} |
|||
} |
|||
|
|||
// bounds relative to first character
|
|||
bounds.x -= positions[0]; |
|||
return advance; |
|||
} |
|||
|
|||
static float _layoutWord(float offset, int layoutOffset, |
|||
string text, int start, int wordCount, TextStyle style, Font font, float[] advances, |
|||
float[] positions, float initAdvance, TabStops tabStops, ref UnityEngine.Rect bounds) { |
|||
float wordSpacing = |
|||
wordCount == 1 && LayoutUtils.isWordSpace(text[start]) ? style.wordSpacing : 0; |
|||
|
|||
float x = initAdvance; |
|||
float letterSpace = style.letterSpacing; |
|||
float letterSpaceHalfLeft = letterSpace * 0.5f; |
|||
float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft; |
|||
|
|||
for (int i = 0; i < wordCount; i++) { |
|||
initAdvance = x; |
|||
var ch = text[start + i]; |
|||
if (i == 0) { |
|||
x += letterSpaceHalfLeft + wordSpacing; |
|||
advances[i + layoutOffset] = letterSpaceHalfLeft + wordSpacing; |
|||
} |
|||
else { |
|||
advances[i - 1 + layoutOffset] += letterSpaceHalfRight; |
|||
advances[i + layoutOffset] = letterSpaceHalfLeft; |
|||
|
|||
x += letterSpace; |
|||
} |
|||
|
|||
if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { |
|||
_updateBounds(glyphInfo, x, ref bounds); |
|||
} |
|||
|
|||
positions[i + layoutOffset] = x; |
|||
|
|||
float advance; |
|||
if (ch == '\t') { |
|||
advance = tabStops.nextTab(initAdvance + offset) - initAdvance - offset; |
|||
} |
|||
else { |
|||
advance = glyphInfo.advance; |
|||
} |
|||
|
|||
x += advance; |
|||
advances[i + layoutOffset] += advance; |
|||
|
|||
if (i + 1 == wordCount) { |
|||
advances[i + layoutOffset] += letterSpaceHalfRight; |
|||
x += letterSpaceHalfRight; |
|||
} |
|||
} |
|||
|
|||
return x; |
|||
} |
|||
|
|||
static float _layoutEmoji(string text, int start, int count, TextStyle style, Font font, float[] advances, |
|||
float[] positions, ref UnityEngine.Rect bounds) { |
|||
var metrics = FontMetrics.fromFont(font, style.UnityFontSize); |
|||
float x = 0; |
|||
for (int i = 0; i < count; i++) { |
|||
char c = text[start + i]; |
|||
if (EmojiUtils.isSingleCharNonEmptyEmoji(c) || char.IsHighSurrogate(c)) { |
|||
float letterSpace = style.letterSpacing; |
|||
float letterSpaceHalfLeft = letterSpace * 0.5f; |
|||
float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft; |
|||
|
|||
x += letterSpaceHalfLeft; |
|||
advances[i] = letterSpaceHalfLeft; |
|||
|
|||
|
|||
var minX = x; |
|||
var maxX = metrics.descent - metrics.ascent + x; |
|||
var minY = metrics.ascent; |
|||
var maxY = metrics.descent; |
|||
_updateBounds(minX, maxX, minY, maxY, ref bounds); |
|||
|
|||
positions[i] = x; |
|||
|
|||
float advance = style.fontSize; |
|||
x += advance; |
|||
|
|||
advances[i] += advance; |
|||
advances[i] += letterSpaceHalfRight; |
|||
|
|||
x += letterSpaceHalfRight; |
|||
} |
|||
else { |
|||
advances[i] = 0; |
|||
positions[i] = x; |
|||
} |
|||
} |
|||
|
|||
return x; |
|||
} |
|||
|
|||
static void _updateBounds(CharacterInfo glyphInfo, float x, ref UnityEngine.Rect bounds) { |
|||
var minX = glyphInfo.minX + x; |
|||
var maxX = glyphInfo.maxX + x; |
|||
var minY = -glyphInfo.maxY; |
|||
var maxY = -glyphInfo.minY; |
|||
_updateBounds(minX, maxX, minY, maxY, ref bounds); |
|||
} |
|||
|
|||
static void _updateBounds(float minX, float maxX, float minY, float maxY, ref UnityEngine.Rect bounds) { |
|||
if (bounds.width <= 0 || bounds.height <= 0) { |
|||
bounds.Set(minX, minY, maxX - minX, maxY - minY); |
|||
} |
|||
else { |
|||
if (minX < bounds.x) { |
|||
bounds.x = minX; |
|||
} |
|||
|
|||
if (minY < bounds.y) { |
|||
bounds.y = minY; |
|||
} |
|||
|
|||
if (maxX > bounds.xMax) { |
|||
bounds.xMax = maxX; |
|||
} |
|||
|
|||
if (maxY > bounds.yMax) { |
|||
bounds.yMax = maxY; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.InternalBridge; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
class TabStops { |
|||
int _tabWidth = int.MaxValue; |
|||
|
|||
Font _font; |
|||
|
|||
int _fontSize; |
|||
|
|||
const int kTabSpaceCount = 4; |
|||
|
|||
List<int> _stops = new List<int>(); |
|||
|
|||
public void set(List<int> stops, int tabWidth) { |
|||
this._stops.Clear(); |
|||
if (stops != null) { |
|||
this._stops.AddRange(stops); |
|||
} |
|||
|
|||
this._tabWidth = tabWidth; |
|||
} |
|||
|
|||
public void setFont(Font font, int size) { |
|||
if (this._font != font || this._fontSize != size) { |
|||
this._tabWidth = int.MaxValue; |
|||
} |
|||
|
|||
this._font = font; |
|||
this._fontSize = size; |
|||
} |
|||
|
|||
public float nextTab(float widthSoFar) { |
|||
for (int i = 0; i < this._stops.Count; i++) { |
|||
if (this._stops[i] > widthSoFar) { |
|||
return this._stops[i]; |
|||
} |
|||
} |
|||
|
|||
if (this._tabWidth == int.MaxValue) { |
|||
if (this._fontSize > 0) { |
|||
this._font.RequestCharactersInTextureSafe(" ", this._fontSize); |
|||
this._font.getGlyphInfo(' ', out var glyphInfo, this._fontSize, UnityEngine.FontStyle.Normal); |
|||
this._tabWidth = glyphInfo.advance * kTabSpaceCount; |
|||
} |
|||
} |
|||
|
|||
if (this._tabWidth == 0) { |
|||
return widthSoFar; |
|||
} |
|||
|
|||
return (Mathf.Floor(widthSoFar / this._tabWidth + 1) * this._tabWidth); |
|||
} |
|||
} |
|||
|
|||
class Candidate { |
|||
public int offset; |
|||
public int pre; |
|||
public float preBreak; |
|||
public float penalty; |
|||
|
|||
public float postBreak; |
|||
public int preSpaceCount; |
|||
public int postSpaceCount; |
|||
} |
|||
|
|||
class LineBreaker { |
|||
const float ScoreInfty = float.MaxValue; |
|||
const float ScoreDesperate = 1e10f; |
|||
|
|||
TextBuff _textBuf; |
|||
List<float> _charWidths = new List<float>(); |
|||
List<int> _breaks = new List<int>(); |
|||
List<float> _widths = new List<float>(); |
|||
WordBreaker _wordBreaker = new WordBreaker(); |
|||
float _width = 0.0f; |
|||
float _preBreak; |
|||
float _lineWidth; |
|||
int _lastBreak; |
|||
int _bestBreak; |
|||
float _bestScore; |
|||
int _spaceCount; |
|||
TabStops _tabStops; |
|||
int mFirstTabIndex; |
|||
List<Candidate> _candidates = new List<Candidate>(); |
|||
|
|||
public int computeBreaks() { |
|||
int nCand = this._candidates.Count; |
|||
if (nCand > 0 && (nCand == 1 || this._lastBreak != nCand - 1)) { |
|||
var cand = this._candidates[this._candidates.Count - 1]; |
|||
this._pushBreak(cand.offset, (cand.postBreak - this._preBreak)); |
|||
} |
|||
|
|||
return this._breaks.Count; |
|||
} |
|||
|
|||
public List<int> getBreaks() { |
|||
return this._breaks; |
|||
} |
|||
|
|||
public void resize(int size) { |
|||
if (this._charWidths.Count < size) { |
|||
NoAllocHelpersBridge<float>.ResizeList(this._charWidths, size); |
|||
} |
|||
} |
|||
|
|||
public void setText(string text, int textOffset, int textLength) { |
|||
this._textBuf = new TextBuff(text, textOffset, textLength); |
|||
this._wordBreaker.setText(this._textBuf); |
|||
this._wordBreaker.next(); |
|||
this._candidates.Clear(); |
|||
Candidate can = new Candidate { |
|||
offset = 0, postBreak = 0, preBreak = 0, postSpaceCount = 0, preSpaceCount = 0, pre = 0 |
|||
}; |
|||
this._candidates.Add(can); |
|||
this._lastBreak = 0; |
|||
this._bestBreak = 0; |
|||
this._bestScore = ScoreInfty; |
|||
this._preBreak = 0; |
|||
this.mFirstTabIndex = int.MaxValue; |
|||
this._spaceCount = 0; |
|||
} |
|||
|
|||
public void setLineWidth(float lineWidth) { |
|||
this._lineWidth = lineWidth; |
|||
} |
|||
|
|||
public float addStyleRun(TextStyle style, int start, int end) { |
|||
float width = 0.0f; |
|||
if (style != null) { |
|||
width = Layout.measureText(this._width - this._preBreak, this._textBuf, |
|||
start, end - start, style, |
|||
this._charWidths, start, this._tabStops); |
|||
} |
|||
|
|||
int current = this._wordBreaker.current(); |
|||
int afterWord = start; |
|||
int lastBreak = start; |
|||
|
|||
float lastBreakWidth = this._width; |
|||
float postBreak = this._width; |
|||
int postSpaceCount = this._spaceCount; |
|||
|
|||
for (int i = start; i < end; i++) { |
|||
char c = this._textBuf.charAt(i); |
|||
if (c == '\t') { |
|||
this._width = this._preBreak + this._tabStops.nextTab((this._width - this._preBreak)); |
|||
if (this.mFirstTabIndex == int.MaxValue) { |
|||
this.mFirstTabIndex = i; |
|||
} |
|||
} |
|||
else { |
|||
if (LayoutUtils.isWordSpace(c)) { |
|||
this._spaceCount += 1; |
|||
} |
|||
|
|||
this._width += this._charWidths[i]; |
|||
if (!LayoutUtils.isLineEndSpace(c)) { |
|||
postBreak = this._width; |
|||
postSpaceCount = this._spaceCount; |
|||
afterWord = i + 1; |
|||
} |
|||
} |
|||
|
|||
if (i + 1 == current) { |
|||
int wordStart = this._wordBreaker.wordStart(); |
|||
int wordEnd = this._wordBreaker.wordEnd(); |
|||
if (style != null || current == end || this._charWidths[current] > 0) { |
|||
this._addWordBreak(current, this._width, postBreak, this._spaceCount, postSpaceCount, 0); |
|||
} |
|||
|
|||
lastBreak = current; |
|||
lastBreakWidth = this._width; |
|||
current = this._wordBreaker.next(); |
|||
} |
|||
} |
|||
|
|||
return width; |
|||
} |
|||
|
|||
public void finish() { |
|||
this._wordBreaker.finish(); |
|||
this._width = 0; |
|||
this._candidates.Clear(); |
|||
this._widths.Clear(); |
|||
this._breaks.Clear(); |
|||
this._textBuf = default; |
|||
} |
|||
|
|||
public List<float> getWidths() { |
|||
return this._widths; |
|||
} |
|||
|
|||
public void setTabStops(TabStops tabStops) { |
|||
this._tabStops = tabStops; |
|||
} |
|||
|
|||
void _addWordBreak(int offset, float preBreak, float postBreak, int preSpaceCount, int postSpaceCount, |
|||
float penalty) { |
|||
|
|||
float width = this._candidates[this._candidates.Count - 1].preBreak; |
|||
if (postBreak - width > this._lineWidth) { |
|||
int i = this._candidates[this._candidates.Count - 1].offset; |
|||
width += this._charWidths[i++]; |
|||
for (; i < offset; i++) { |
|||
float w = this._charWidths[i]; |
|||
if (w > 0) { |
|||
this._addCandidate(new Candidate { |
|||
offset = i, |
|||
preBreak = width, |
|||
postBreak = width, |
|||
preSpaceCount = postSpaceCount, |
|||
postSpaceCount = postSpaceCount, |
|||
penalty = ScoreDesperate, |
|||
}); |
|||
width += w; |
|||
} |
|||
} |
|||
} |
|||
|
|||
this._addCandidate(new Candidate { |
|||
offset = offset, |
|||
preBreak = preBreak, |
|||
postBreak = postBreak, |
|||
preSpaceCount = preSpaceCount, |
|||
postSpaceCount = postSpaceCount, |
|||
penalty = penalty |
|||
}); |
|||
} |
|||
|
|||
|
|||
void _addCandidate(Candidate cand) { |
|||
int candIndex = this._candidates.Count; |
|||
this._candidates.Add(cand); |
|||
if (cand.postBreak - this._preBreak > this._lineWidth) { |
|||
if (this._bestBreak == this._lastBreak) { |
|||
this._bestBreak = candIndex; |
|||
} |
|||
this._pushGreedyBreak(); |
|||
} |
|||
|
|||
while (this._lastBreak != candIndex && cand.postBreak - this._preBreak > this._lineWidth) { |
|||
for (int i = this._lastBreak + 1; i < candIndex; i++) { |
|||
float penalty = this._candidates[i].penalty; |
|||
if (penalty <= this._bestScore) { |
|||
this._bestBreak = i; |
|||
this._bestScore = penalty; |
|||
} |
|||
} |
|||
if (this._bestBreak == this._lastBreak) { |
|||
this._bestBreak = candIndex; |
|||
} |
|||
this._pushGreedyBreak(); |
|||
} |
|||
|
|||
if (cand.penalty <= this._bestScore) { |
|||
this._bestBreak = candIndex; |
|||
this._bestScore = cand.penalty; |
|||
} |
|||
} |
|||
|
|||
void _pushGreedyBreak() { |
|||
var bestCandidate = this._candidates[this._bestBreak]; |
|||
this._pushBreak(bestCandidate.offset, (bestCandidate.postBreak - this._preBreak)); |
|||
this._bestScore = ScoreInfty; |
|||
this._lastBreak = this._bestBreak; |
|||
this._preBreak = bestCandidate.preBreak; |
|||
} |
|||
|
|||
void _pushBreak(int offset, float width) { |
|||
this._breaks.Add(offset); |
|||
this._widths.Add(width); |
|||
} |
|||
} |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
class TabStops { |
|||
int _tabWidth = int.MaxValue; |
|||
|
|||
Font _font; |
|||
|
|||
int _fontSize; |
|||
|
|||
int _spaceAdvance; |
|||
|
|||
const int kTabSpaceCount = 4; |
|||
|
|||
public void setFont(Font font, int size) { |
|||
if (this._font != font || this._fontSize != size) { |
|||
this._tabWidth = int.MaxValue; |
|||
} |
|||
|
|||
this._font = font; |
|||
// Recompute the advance of space (' ') if font size changes
|
|||
if (this._fontSize != size) { |
|||
this._fontSize = size; |
|||
this._font.RequestCharactersInTextureSafe(" ", this._fontSize); |
|||
this._font.getGlyphInfo(' ', out var glyphInfo, this._fontSize, UnityEngine.FontStyle.Normal); |
|||
this._spaceAdvance = glyphInfo.advance; |
|||
} |
|||
} |
|||
|
|||
public float nextTab(float widthSoFar) { |
|||
if (this._tabWidth == int.MaxValue) { |
|||
if (this._fontSize > 0) { |
|||
this._tabWidth = this._spaceAdvance * kTabSpaceCount; |
|||
} |
|||
} |
|||
|
|||
if (this._tabWidth == 0) { |
|||
return widthSoFar; |
|||
} |
|||
|
|||
return (Mathf.Floor(widthSoFar / this._tabWidth + 1) * this._tabWidth); |
|||
} |
|||
} |
|||
|
|||
struct Candidate { |
|||
public int offset; |
|||
public int pre; |
|||
public float preBreak; |
|||
public float penalty; |
|||
|
|||
public float postBreak; |
|||
public int preSpaceCount; |
|||
public int postSpaceCount; |
|||
} |
|||
|
|||
class LineBreaker { |
|||
const float ScoreInfty = float.MaxValue; |
|||
const float ScoreDesperate = 1e10f; |
|||
|
|||
int _lineLimit = 0; |
|||
|
|||
// Limit number of lines, 0 means no limit
|
|||
public int lineLimit { |
|||
get { return this._lineLimit; } |
|||
set { this._lineLimit = value; } |
|||
} |
|||
|
|||
public static LineBreaker instance { |
|||
get { |
|||
if (_instance == null) { |
|||
_instance = new LineBreaker(); |
|||
} |
|||
|
|||
return _instance; |
|||
} |
|||
} |
|||
|
|||
static LineBreaker _instance; |
|||
|
|||
public static int[] newLinePositions(string text, out int count) { |
|||
count = 0; |
|||
for (var i = 0; i < text.Length; i++) { |
|||
if (text[i] == '\n') { |
|||
count++; |
|||
} |
|||
} |
|||
|
|||
count++; |
|||
|
|||
if (_newLinePositions == null || _newLinePositions.Length < count) { |
|||
_newLinePositions = new int[count]; |
|||
} |
|||
|
|||
count = 0; |
|||
for (var i = 0; i < text.Length; i++) { |
|||
if (text[i] == '\n') { |
|||
_newLinePositions[count++] = i; |
|||
} |
|||
} |
|||
|
|||
_newLinePositions[count++] = text.Length; |
|||
|
|||
return _newLinePositions; |
|||
} |
|||
|
|||
static int[] _newLinePositions; |
|||
|
|||
TextBuff _textBuf; |
|||
float[] _charWidths; |
|||
List<int> _breaks = new List<int>(); |
|||
int _breaksCount = 0; |
|||
List<float> _widths = new List<float>(); |
|||
int _widthsCount = 0; |
|||
WordBreaker _wordBreaker = new WordBreaker(); |
|||
float _width = 0.0f; |
|||
float _preBreak; |
|||
float _lineWidth; |
|||
int _lastBreak; |
|||
int _bestBreak; |
|||
float _bestScore; |
|||
int _spaceCount; |
|||
TabStops _tabStops; |
|||
int mFirstTabIndex; |
|||
List<Candidate> _candidates = new List<Candidate>(); |
|||
int _candidatesCount = 0; |
|||
|
|||
public int computeBreaks() { |
|||
int nCand = this._candidatesCount; |
|||
if (nCand > 0 && (nCand == 1 || this._lastBreak != nCand - 1)) { |
|||
var cand = this._candidates[this._candidatesCount - 1]; |
|||
this._pushBreak(cand.offset, (cand.postBreak - this._preBreak)); |
|||
} |
|||
|
|||
return this._breaksCount; |
|||
} |
|||
|
|||
public int getBreaksCount() { |
|||
return this._breaksCount; |
|||
} |
|||
|
|||
public int getBreak(int i) { |
|||
return this._breaks[i]; |
|||
} |
|||
|
|||
public float getWidth(int i) { |
|||
return this._widths[i]; |
|||
} |
|||
|
|||
public void resize(int size) { |
|||
if (this._charWidths == null || this._charWidths.Length < size) { |
|||
this._charWidths = new float[LayoutUtils.minPowerOfTwo(size)]; |
|||
} |
|||
} |
|||
|
|||
public void setText(string text, int textOffset, int textLength) { |
|||
this._textBuf = new TextBuff(text, textOffset, textLength); |
|||
this._wordBreaker.setText(this._textBuf); |
|||
this._wordBreaker.next(); |
|||
this._candidatesCount = 0; |
|||
Candidate can = new Candidate { |
|||
offset = 0, postBreak = 0, preBreak = 0, postSpaceCount = 0, preSpaceCount = 0, pre = 0 |
|||
}; |
|||
this._addCandidateToList(can); |
|||
this._lastBreak = 0; |
|||
this._bestBreak = 0; |
|||
this._bestScore = ScoreInfty; |
|||
this._preBreak = 0; |
|||
this.mFirstTabIndex = int.MaxValue; |
|||
this._spaceCount = 0; |
|||
} |
|||
|
|||
public void setLineWidth(float lineWidth) { |
|||
this._lineWidth = lineWidth; |
|||
} |
|||
|
|||
public float addStyleRun(TextStyle style, int start, int end) { |
|||
float width = 0; |
|||
if (style != null) { |
|||
// Layout.measureText(this._width - this._preBreak, this._textBuf,
|
|||
// start, end - start, style,
|
|||
// this._charWidths, start, this._tabStops);
|
|||
width = Layout.computeCharWidths(this._width - this._preBreak, this._textBuf.text, |
|||
this._textBuf.offset + start, end - start, style, |
|||
this._charWidths, start, this._tabStops); |
|||
} |
|||
|
|||
int current = this._wordBreaker.current(); |
|||
float postBreak = this._width; |
|||
int postSpaceCount = this._spaceCount; |
|||
|
|||
for (int i = start; i < end; i++) { |
|||
char c = this._textBuf.charAt(i); |
|||
if (c == '\t') { |
|||
this._width = this._preBreak + this._tabStops.nextTab(this._width - this._preBreak); |
|||
if (this.mFirstTabIndex == int.MaxValue) { |
|||
this.mFirstTabIndex = i; |
|||
} |
|||
} |
|||
else { |
|||
if (LayoutUtils.isWordSpace(c)) { |
|||
this._spaceCount += 1; |
|||
} |
|||
|
|||
this._width += this._charWidths[i]; |
|||
if (!LayoutUtils.isLineEndSpace(c)) { |
|||
postBreak = this._width; |
|||
postSpaceCount = this._spaceCount; |
|||
} |
|||
} |
|||
|
|||
if (i + 1 == current) { |
|||
if (style != null || current == end || this._charWidths[current] > 0) { |
|||
this._addWordBreak(current, this._width, postBreak, this._spaceCount, postSpaceCount, 0); |
|||
} |
|||
|
|||
current = this._wordBreaker.next(); |
|||
} |
|||
} |
|||
|
|||
return width; |
|||
} |
|||
|
|||
public void finish() { |
|||
this._wordBreaker.finish(); |
|||
this._width = 0; |
|||
this._candidatesCount = 0; |
|||
this._breaksCount = 0; |
|||
this._widthsCount = 0; |
|||
this._textBuf = default; |
|||
} |
|||
|
|||
public int getWidthsCount() { |
|||
return this._widthsCount; |
|||
} |
|||
|
|||
public void setTabStops(TabStops tabStops) { |
|||
this._tabStops = tabStops; |
|||
} |
|||
|
|||
void _addWordBreak(int offset, float preBreak, float postBreak, int preSpaceCount, int postSpaceCount, |
|||
float penalty) { |
|||
float width = this._candidates[this._candidatesCount - 1].preBreak; |
|||
if (postBreak - width > this._lineWidth) { |
|||
this._addCandidatesInsideWord(width, offset, postSpaceCount); |
|||
} |
|||
|
|||
this._addCandidate(new Candidate { |
|||
offset = offset, |
|||
preBreak = preBreak, |
|||
postBreak = postBreak, |
|||
preSpaceCount = preSpaceCount, |
|||
postSpaceCount = postSpaceCount, |
|||
penalty = penalty |
|||
}); |
|||
} |
|||
|
|||
void _addCandidatesInsideWord(float width, int offset, int postSpaceCount) { |
|||
int i = this._candidates[this._candidatesCount - 1].offset; |
|||
width += this._charWidths[i++]; |
|||
for (; i < offset; i++) { |
|||
float w = this._charWidths[i]; |
|||
if (w > 0) { |
|||
this._addCandidate(new Candidate { |
|||
offset = i, |
|||
preBreak = width, |
|||
postBreak = width, |
|||
preSpaceCount = postSpaceCount, |
|||
postSpaceCount = postSpaceCount, |
|||
penalty = ScoreDesperate, |
|||
}); |
|||
width += w; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void _addCandidateToList(Candidate cand) { |
|||
if (this._candidates.Count == this._candidatesCount) { |
|||
this._candidates.Add(cand); |
|||
this._candidatesCount++; |
|||
} |
|||
else { |
|||
this._candidates[this._candidatesCount++] = cand; |
|||
} |
|||
} |
|||
|
|||
void _addCandidate(Candidate cand) { |
|||
int candIndex = this._candidatesCount; |
|||
this._addCandidateToList(cand); |
|||
if (cand.postBreak - this._preBreak > this._lineWidth) { |
|||
if (this._bestBreak == this._lastBreak) { |
|||
this._bestBreak = candIndex; |
|||
} |
|||
|
|||
this._pushGreedyBreak(); |
|||
} |
|||
|
|||
while (this._lastBreak != candIndex && cand.postBreak - this._preBreak > this._lineWidth) { |
|||
for (int i = this._lastBreak + 1; i < candIndex; i++) { |
|||
float penalty = this._candidates[i].penalty; |
|||
if (penalty <= this._bestScore) { |
|||
this._bestBreak = i; |
|||
this._bestScore = penalty; |
|||
} |
|||
} |
|||
|
|||
if (this._bestBreak == this._lastBreak) { |
|||
this._bestBreak = candIndex; |
|||
} |
|||
|
|||
this._pushGreedyBreak(); |
|||
} |
|||
|
|||
if (cand.penalty <= this._bestScore) { |
|||
this._bestBreak = candIndex; |
|||
this._bestScore = cand.penalty; |
|||
} |
|||
} |
|||
|
|||
void _pushGreedyBreak() { |
|||
var bestCandidate = this._candidates[this._bestBreak]; |
|||
this._pushBreak(bestCandidate.offset, bestCandidate.postBreak - this._preBreak); |
|||
this._bestScore = ScoreInfty; |
|||
this._lastBreak = this._bestBreak; |
|||
this._preBreak = bestCandidate.preBreak; |
|||
} |
|||
|
|||
void _pushBreak(int offset, float width) { |
|||
if (this.lineLimit == 0 || this._breaksCount < this.lineLimit) { |
|||
if (this._breaks.Count == this._breaksCount) { |
|||
this._breaks.Add(offset); |
|||
this._breaksCount++; |
|||
} |
|||
else { |
|||
this._breaks[this._breaksCount++] = offset; |
|||
} |
|||
|
|||
if (this._widths.Count == this._widthsCount) { |
|||
this._widths.Add(width); |
|||
this._widthsCount++; |
|||
} |
|||
else { |
|||
this._widths[this._widthsCount++] = width; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
1001
Runtime/ui/txt/paragraph.cs
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
using System.Collections.Generic; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.material; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
using FontStyle = Unity.UIWidgets.ui.FontStyle; |
|||
using Material = Unity.UIWidgets.material.Material; |
|||
|
|||
namespace UIWidgetsSample { |
|||
public class BenchMarkLayout : UIWidgetsSamplePanel { |
|||
protected override Widget createWidget() { |
|||
return new MaterialApp( |
|||
showPerformanceOverlay: false, |
|||
home: new Material( |
|||
child: new BenchMarkLayoutWidget()), |
|||
builder: (_, child) => { |
|||
return new Builder(builder: |
|||
context => { |
|||
return new MediaQuery( |
|||
data: MediaQuery.of(context).copyWith( |
|||
textScaleFactor: 1.0f |
|||
), |
|||
child: child); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
protected override void OnEnable() { |
|||
base.OnEnable(); |
|||
FontManager.instance.addFont(Resources.Load<Font>(path: "MaterialIcons-Regular"), "Material Icons"); |
|||
} |
|||
} |
|||
|
|||
class BenchMarkLayoutWidget : StatefulWidget { |
|||
public BenchMarkLayoutWidget(Key key = null) : base(key) { |
|||
} |
|||
|
|||
public override State createState() { |
|||
return new BenchMarkLayoutWidgetState(); |
|||
} |
|||
} |
|||
|
|||
class BenchMarkLayoutWidgetState : State<BenchMarkLayoutWidget> { |
|||
int width = 260; |
|||
bool visible = true; |
|||
|
|||
Widget richtext = new Container( |
|||
child: new RichText( |
|||
text: new TextSpan("", children: |
|||
new List<TextSpan>() { |
|||
new TextSpan("Real-time 3D revolutioni\t淡粉色的方式地方\tzes the animation pipeline "), |
|||
new TextSpan(style: new TextStyle(color: Color.fromARGB(255, 255, 0, 0)), |
|||
text: "for Disney Television Animation's\t “Baymax Dreams"), |
|||
new TextSpan("\t", style: new TextStyle(color: Colors.black)), |
|||
new TextSpan(" Unity Widgets"), |
|||
new TextSpan(" Text"), |
|||
new TextSpan("Real-time 3D revolutionizes the animation pipeline "), |
|||
new TextSpan(style: new TextStyle(color: Color.fromARGB(125, 255, 0, 0)), |
|||
text: "Transparent Red Text\n\n"), |
|||
new TextSpan("Bold Text Test Bold Textfs Test: FontWeight.w70\n\n"), |
|||
new TextSpan(style: new TextStyle(fontStyle: FontStyle.italic), |
|||
text: "This is FontStyle.italic Text This is FontStyle.italic Text\n\n"), |
|||
new TextSpan( |
|||
style: new TextStyle(fontStyle: FontStyle.italic, fontWeight: FontWeight.w700), |
|||
text: |
|||
"This is FontStyle.italic And 发撒放豆腐sad 发生的 Bold Text This is FontStyle.italic And Bold Text\n\n"), |
|||
new TextSpan(style: new TextStyle(fontSize: 18), |
|||
text: "FontSize 18: Get a named matrix value from the shader.\n\n"), |
|||
new TextSpan(style: new TextStyle(fontSize: 24), |
|||
text: "Emoji \ud83d\ude0a\ud83d\ude0b\t\ud83d\ude0d\ud83d\ude0e\ud83d\ude00"), |
|||
new TextSpan(style: new TextStyle(fontSize: 14), |
|||
text: "Emoji \ud83d\ude0a\ud83d\ude0b\ud83d\ude0d\ud83d\ude0e\ud83d\ude00 Emoji"), |
|||
new TextSpan(style: new TextStyle(fontSize: 18), |
|||
text: "Emoji \ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"), |
|||
new TextSpan(style: new TextStyle(fontSize: 18), |
|||
text: "\ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"), |
|||
new TextSpan(style: new TextStyle(fontSize: 18), |
|||
text: "\ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"), |
|||
new TextSpan(style: new TextStyle(fontSize: 18), |
|||
text: "\ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"), |
|||
new TextSpan(style: new TextStyle(fontSize: 18), |
|||
text: "\ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"), |
|||
new TextSpan(style: new TextStyle(fontSize: 24), |
|||
text: |
|||
"Emoji \ud83d\ude06\ud83d\ude1C\ud83d\ude18\ud83d\ude2D\ud83d\ude0C\ud83d\ude1E\n\n"), |
|||
new TextSpan(style: new TextStyle(fontSize: 14), |
|||
text: "FontSize 14"), |
|||
}) |
|||
) |
|||
); |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
Widget buttons = new Column( |
|||
mainAxisAlignment: MainAxisAlignment.end, |
|||
children: new List<Widget> { |
|||
new Text($"Width: {this.width}"), |
|||
new RaisedButton( |
|||
onPressed: () => { this.setState(() => { this.width += 10; }); }, |
|||
child: new Text("Add Width") |
|||
), |
|||
new Divider(), |
|||
new RaisedButton( |
|||
onPressed: () => { this.setState(() => { this.width -= 10; }); }, |
|||
child: new Text("Dec Width") |
|||
), |
|||
new Divider(), |
|||
new RaisedButton( |
|||
onPressed: () => { this.setState(() => { this.visible = true; }); }, |
|||
child: new Text("Show") |
|||
), |
|||
new Divider(), |
|||
new RaisedButton( |
|||
onPressed: () => { this.setState(() => { this.visible = false; }); }, |
|||
child: new Text("Hide") |
|||
) |
|||
} |
|||
); |
|||
Widget child = new Column( |
|||
children: new List<Widget> { |
|||
this.visible ? this.richtext : new Text(""), |
|||
this.visible |
|||
? new Text( |
|||
"Very Very Very Very Very Very Very Very Very Very Very Very Very Very\nVery Very Very Very Very Very Very Very Very Very Very Long Text", |
|||
maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.justify |
|||
) |
|||
: new Text("") |
|||
}); |
|||
child = new Stack( |
|||
children: new List<Widget> { |
|||
child, |
|||
buttons |
|||
} |
|||
); |
|||
child = new Container( |
|||
width: this.width, |
|||
color: Colors.black12, |
|||
child: child |
|||
); |
|||
child = new Center( |
|||
child: child |
|||
); |
|||
return child; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: cba57cf034e23904ab2f929598f4cb45 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!29 &1 |
|||
OcclusionCullingSettings: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 2 |
|||
m_OcclusionBakeSettings: |
|||
smallestOccluder: 5 |
|||
smallestHole: 0.25 |
|||
backfaceThreshold: 100 |
|||
m_SceneGUID: 00000000000000000000000000000000 |
|||
m_OcclusionCullingData: {fileID: 0} |
|||
--- !u!104 &2 |
|||
RenderSettings: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 9 |
|||
m_Fog: 0 |
|||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} |
|||
m_FogMode: 3 |
|||
m_FogDensity: 0.01 |
|||
m_LinearFogStart: 0 |
|||
m_LinearFogEnd: 300 |
|||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} |
|||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} |
|||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} |
|||
m_AmbientIntensity: 1 |
|||
m_AmbientMode: 0 |
|||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} |
|||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} |
|||
m_HaloStrength: 0.5 |
|||
m_FlareStrength: 1 |
|||
m_FlareFadeSpeed: 3 |
|||
m_HaloTexture: {fileID: 0} |
|||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} |
|||
m_DefaultReflectionMode: 0 |
|||
m_DefaultReflectionResolution: 128 |
|||
m_ReflectionBounces: 1 |
|||
m_ReflectionIntensity: 1 |
|||
m_CustomReflection: {fileID: 0} |
|||
m_Sun: {fileID: 0} |
|||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} |
|||
m_UseRadianceAmbientProbe: 0 |
|||
--- !u!157 &3 |
|||
LightmapSettings: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 11 |
|||
m_GIWorkflowMode: 1 |
|||
m_GISettings: |
|||
serializedVersion: 2 |
|||
m_BounceScale: 1 |
|||
m_IndirectOutputScale: 1 |
|||
m_AlbedoBoost: 1 |
|||
m_EnvironmentLightingMode: 0 |
|||
m_EnableBakedLightmaps: 1 |
|||
m_EnableRealtimeLightmaps: 1 |
|||
m_LightmapEditorSettings: |
|||
serializedVersion: 12 |
|||
m_Resolution: 2 |
|||
m_BakeResolution: 40 |
|||
m_AtlasSize: 1024 |
|||
m_AO: 0 |
|||
m_AOMaxDistance: 1 |
|||
m_CompAOExponent: 1 |
|||
m_CompAOExponentDirect: 0 |
|||
m_ExtractAmbientOcclusion: 0 |
|||
m_Padding: 2 |
|||
m_LightmapParameters: {fileID: 0} |
|||
m_LightmapsBakeMode: 1 |
|||
m_TextureCompression: 1 |
|||
m_FinalGather: 0 |
|||
m_FinalGatherFiltering: 1 |
|||
m_FinalGatherRayCount: 256 |
|||
m_ReflectionCompression: 2 |
|||
m_MixedBakeMode: 2 |
|||
m_BakeBackend: 1 |
|||
m_PVRSampling: 1 |
|||
m_PVRDirectSampleCount: 32 |
|||
m_PVRSampleCount: 512 |
|||
m_PVRBounces: 2 |
|||
m_PVREnvironmentSampleCount: 256 |
|||
m_PVREnvironmentReferencePointCount: 2048 |
|||
m_PVRFilteringMode: 1 |
|||
m_PVRDenoiserTypeDirect: 1 |
|||
m_PVRDenoiserTypeIndirect: 1 |
|||
m_PVRDenoiserTypeAO: 1 |
|||
m_PVRFilterTypeDirect: 0 |
|||
m_PVRFilterTypeIndirect: 0 |
|||
m_PVRFilterTypeAO: 0 |
|||
m_PVREnvironmentMIS: 1 |
|||
m_PVRCulling: 1 |
|||
m_PVRFilteringGaussRadiusDirect: 1 |
|||
m_PVRFilteringGaussRadiusIndirect: 5 |
|||
m_PVRFilteringGaussRadiusAO: 2 |
|||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5 |
|||
m_PVRFilteringAtrousPositionSigmaIndirect: 2 |
|||
m_PVRFilteringAtrousPositionSigmaAO: 1 |
|||
m_ShowResolutionOverlay: 1 |
|||
m_ExportTrainingData: 0 |
|||
m_LightingDataAsset: {fileID: 0} |
|||
m_UseShadowmask: 1 |
|||
--- !u!196 &4 |
|||
NavMeshSettings: |
|||
serializedVersion: 2 |
|||
m_ObjectHideFlags: 0 |
|||
m_BuildSettings: |
|||
serializedVersion: 2 |
|||
agentTypeID: 0 |
|||
agentRadius: 0.5 |
|||
agentHeight: 2 |
|||
agentSlope: 45 |
|||
agentClimb: 0.4 |
|||
ledgeDropHeight: 0 |
|||
maxJumpAcrossDistance: 0 |
|||
minRegionArea: 2 |
|||
manualCellSize: 0 |
|||
cellSize: 0.16666667 |
|||
manualTileSize: 0 |
|||
tileSize: 256 |
|||
accuratePlacement: 0 |
|||
debug: |
|||
m_Flags: 0 |
|||
m_NavMeshData: {fileID: 0} |
|||
--- !u!1 &1668830267 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1668830270} |
|||
- component: {fileID: 1668830269} |
|||
- component: {fileID: 1668830268} |
|||
m_Layer: 0 |
|||
m_Name: Main Camera |
|||
m_TagString: MainCamera |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!81 &1668830268 |
|||
AudioListener: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1668830267} |
|||
m_Enabled: 1 |
|||
--- !u!20 &1668830269 |
|||
Camera: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1668830267} |
|||
m_Enabled: 1 |
|||
serializedVersion: 2 |
|||
m_ClearFlags: 1 |
|||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} |
|||
m_projectionMatrixMode: 1 |
|||
m_GateFitMode: 2 |
|||
m_FOVAxisMode: 0 |
|||
m_SensorSize: {x: 36, y: 24} |
|||
m_LensShift: {x: 0, y: 0} |
|||
m_FocalLength: 50 |
|||
m_NormalizedViewPortRect: |
|||
serializedVersion: 2 |
|||
x: 0 |
|||
y: 0 |
|||
width: 1 |
|||
height: 1 |
|||
near clip plane: 0.3 |
|||
far clip plane: 1000 |
|||
field of view: 60 |
|||
orthographic: 0 |
|||
orthographic size: 5 |
|||
m_Depth: -1 |
|||
m_CullingMask: |
|||
serializedVersion: 2 |
|||
m_Bits: 4294967295 |
|||
m_RenderingPath: -1 |
|||
m_TargetTexture: {fileID: 0} |
|||
m_TargetDisplay: 0 |
|||
m_TargetEye: 3 |
|||
m_HDR: 1 |
|||
m_AllowMSAA: 1 |
|||
m_AllowDynamicResolution: 0 |
|||
m_ForceIntoRT: 0 |
|||
m_OcclusionCulling: 1 |
|||
m_StereoConvergence: 10 |
|||
m_StereoSeparation: 0.022 |
|||
--- !u!4 &1668830270 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1668830267} |
|||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 1, z: -10} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
--- !u!1 &1774196668 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 1774196670} |
|||
- component: {fileID: 1774196669} |
|||
m_Layer: 0 |
|||
m_Name: Directional Light |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!108 &1774196669 |
|||
Light: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1774196668} |
|||
m_Enabled: 1 |
|||
serializedVersion: 9 |
|||
m_Type: 1 |
|||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} |
|||
m_Intensity: 1 |
|||
m_Range: 10 |
|||
m_SpotAngle: 30 |
|||
m_InnerSpotAngle: 21.80208 |
|||
m_CookieSize: 10 |
|||
m_Shadows: |
|||
m_Type: 2 |
|||
m_Resolution: -1 |
|||
m_CustomResolution: -1 |
|||
m_Strength: 1 |
|||
m_Bias: 0.05 |
|||
m_NormalBias: 0.4 |
|||
m_NearPlane: 0.2 |
|||
m_CullingMatrixOverride: |
|||
e00: 1 |
|||
e01: 0 |
|||
e02: 0 |
|||
e03: 0 |
|||
e10: 0 |
|||
e11: 1 |
|||
e12: 0 |
|||
e13: 0 |
|||
e20: 0 |
|||
e21: 0 |
|||
e22: 1 |
|||
e23: 0 |
|||
e30: 0 |
|||
e31: 0 |
|||
e32: 0 |
|||
e33: 1 |
|||
m_UseCullingMatrixOverride: 0 |
|||
m_Cookie: {fileID: 0} |
|||
m_DrawHalo: 0 |
|||
m_Flare: {fileID: 0} |
|||
m_RenderMode: 0 |
|||
m_CullingMask: |
|||
serializedVersion: 2 |
|||
m_Bits: 4294967295 |
|||
m_RenderingLayerMask: 1 |
|||
m_Lightmapping: 4 |
|||
m_LightShadowCasterMode: 0 |
|||
m_AreaSize: {x: 1, y: 1} |
|||
m_BounceIntensity: 1 |
|||
m_ColorTemperature: 6570 |
|||
m_UseColorTemperature: 0 |
|||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} |
|||
m_UseBoundingSphereOverride: 0 |
|||
m_ShadowRadius: 0 |
|||
m_ShadowAngle: 0 |
|||
--- !u!4 &1774196670 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 1774196668} |
|||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} |
|||
m_LocalPosition: {x: 0, y: 3, z: 0} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 1 |
|||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} |
|
|||
fileFormatVersion: 2 |
|||
guid: 12e27c4b597519147a207be6204dc1f5 |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue