Files
2025-01-25 04:38:09 +08:00

2896 lines
93 KiB
C#

//----------------------------------------------
// NGUI: Next-Gen UI kit
// Copyright © 2011-2015 Tasharen Entertainment
//----------------------------------------------
#if !UNITY_3_5
#define DYNAMIC_FONT
#endif
#define FUNCELL_MODIFIED
using UnityEngine;
using System.Text;
using System.Diagnostics;
/// <summary>
/// Helper class containing functionality related to using dynamic fonts.
/// </summary>
static public class NGUIText
{
public enum Alignment
{
Automatic,
Left,
Center,
Right,
Justified,
}
public enum SymbolStyle
{
None,
Normal,
Colored,
}
#if FUNCELL_MODIFIED
public enum ChineseRotType
{
None,
Left,
Right,
}
#endif
public class GlyphInfo
{
public Vector2 v0;
public Vector2 v1;
public Vector2 u0;
public Vector2 u1;
public Vector2 u2;
public Vector2 u3;
public float advance = 0f;
public int channel = 0;
}
/// <summary>
/// When printing text, a lot of additional data must be passed in. In order to save allocations,
/// this data is not passed at all, but is rather set in a single place before calling the functions that use it.
/// </summary>
static public string Language = "";
static public UIFont bitmapFont;
#if DYNAMIC_FONT
static public Font dynamicFont;
#endif
static public GlyphInfo glyph = new GlyphInfo();
static public int fontSize = 16;
static public float fontScale = 1f;
static public float pixelDensity = 1f;
static public FontStyle fontStyle = FontStyle.Normal;
static public Alignment alignment = Alignment.Left;
static public Color tint = Color.white;
static public int rectWidth = 1000000;
static public int rectHeight = 1000000;
static public int regionWidth = 1000000;
static public int regionHeight = 1000000;
static public int maxLines = 0;
static public bool gradient = false;
static public Color gradientBottom = Color.white;
static public Color gradientTop = Color.white;
static public bool encoding = false;
static public float spacingX = 0f;
static public float spacingY = 0f;
static public bool premultiply = false;
static public SymbolStyle symbolStyle;
static public int finalSize = 0;
static public float finalSpacingX = 0f;
static public float finalLineHeight = 0f;
static public float baseline = 0f;
static public bool useSymbols = false;
#if FUNCELL_MODIFIED
//是否是东方文字
static public bool isEastern = false;
static public bool isVie = false;
#endif
/// <summary>
/// Recalculate the 'final' values.
/// </summary>
static public void Update() { Update(true); }
/// <summary>
/// Recalculate the 'final' values.
/// </summary>
static public void Update(bool request)
{
finalSize = Mathf.RoundToInt(fontSize / pixelDensity);
finalSpacingX = spacingX * fontScale;
finalLineHeight = (fontSize + spacingY) * fontScale;
useSymbols = (bitmapFont != null && bitmapFont.hasSymbols) && encoding && symbolStyle != SymbolStyle.None;
#if DYNAMIC_FONT
Font font = dynamicFont;
if (font != null && request)
{
font.RequestCharactersInTexture(")_-", finalSize, fontStyle);
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7
if (!font.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.vert.height == 0f)
{
font.RequestCharactersInTexture("A", finalSize, fontStyle);
{
if (!font.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle))
{
baseline = 0f;
return;
}
}
}
float y0 = mTempChar.vert.yMax;
float y1 = mTempChar.vert.yMin;
#else
if (!font.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.maxY == 0f)
{
font.RequestCharactersInTexture("A", finalSize, fontStyle);
{
if (!font.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle))
{
baseline = 0f;
return;
}
}
}
float y0 = mTempChar.maxY;
float y1 = mTempChar.minY;
#endif
baseline = Mathf.Round(y0 + (finalSize - y0 + y1) * 0.5f);
}
#endif
}
/// <summary>
/// Prepare to use the specified text.
/// </summary>
static public void Prepare(string text)
{
#if DYNAMIC_FONT
if (dynamicFont != null)
dynamicFont.RequestCharactersInTexture(text, finalSize, fontStyle);
#endif
}
/// <summary>
/// Get the specified symbol.
/// </summary>
static public BMSymbol GetSymbol(string text, int index, int textLength)
{
return (bitmapFont != null) ? bitmapFont.MatchSymbol(text, index, textLength) : null;
}
/// <summary>
/// Get the width of the specified glyph. Returns zero if the glyph could not be retrieved.
/// </summary>
static public float GetGlyphWidth(int ch, int prev)
{
if (bitmapFont != null)
{
bool thinSpace = false;
if (ch == '\u2009')
{
thinSpace = true;
ch = ' ';
}
BMGlyph bmg = bitmapFont.bmFont.GetGlyph(ch);
if (bmg != null)
{
int adv = bmg.advance;
if (thinSpace) adv >>= 1;
return fontScale * ((prev != 0) ? adv + bmg.GetKerning(prev) : bmg.advance);
}
}
#if DYNAMIC_FONT
else if (dynamicFont != null)
{
if (dynamicFont.GetCharacterInfo((char)ch, out mTempChar, finalSize, fontStyle))
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7
return mTempChar.width * fontScale * pixelDensity;
#else
return mTempChar.advance * fontScale * pixelDensity;
#endif
}
#endif
return 0f;
}
/// <summary>
/// Get the specified glyph.
/// </summary>
static public GlyphInfo GetGlyph(int ch, int prev)
{
if (bitmapFont != null)
{
bool thinSpace = false;
if (ch == '\u2009')
{
thinSpace = true;
ch = ' ';
}
BMGlyph bmg = bitmapFont.bmFont.GetGlyph(ch);
if (bmg != null)
{
int kern = (prev != 0) ? bmg.GetKerning(prev) : 0;
glyph.v0.x = (prev != 0) ? bmg.offsetX + kern : bmg.offsetX;
glyph.v1.y = -bmg.offsetY;
glyph.v1.x = glyph.v0.x + bmg.width;
glyph.v0.y = glyph.v1.y - bmg.height;
glyph.u0.x = bmg.x;
glyph.u0.y = bmg.y + bmg.height;
glyph.u2.x = bmg.x + bmg.width;
glyph.u2.y = bmg.y;
glyph.u1.x = glyph.u0.x;
glyph.u1.y = glyph.u2.y;
glyph.u3.x = glyph.u2.x;
glyph.u3.y = glyph.u0.y;
int adv = bmg.advance;
if (thinSpace) adv >>= 1;
glyph.advance = adv + kern;
glyph.channel = bmg.channel;
if (fontScale != 1f)
{
glyph.v0 *= fontScale;
glyph.v1 *= fontScale;
glyph.advance *= fontScale;
}
return glyph;
}
}
#if DYNAMIC_FONT
else if (dynamicFont != null)
{
if (dynamicFont.GetCharacterInfo((char)ch, out mTempChar, finalSize, fontStyle))
{
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7
glyph.v0.x = mTempChar.vert.xMin;
glyph.v1.x = glyph.v0.x + mTempChar.vert.width;
glyph.v0.y = mTempChar.vert.yMax - baseline;
glyph.v1.y = glyph.v0.y - mTempChar.vert.height;
glyph.u0.x = mTempChar.uv.xMin;
glyph.u0.y = mTempChar.uv.yMin;
glyph.u2.x = mTempChar.uv.xMax;
glyph.u2.y = mTempChar.uv.yMax;
if (mTempChar.flipped)
{
glyph.u1 = new Vector2(glyph.u2.x, glyph.u0.y);
glyph.u3 = new Vector2(glyph.u0.x, glyph.u2.y);
}
else
{
glyph.u1 = new Vector2(glyph.u0.x, glyph.u2.y);
glyph.u3 = new Vector2(glyph.u2.x, glyph.u0.y);
}
glyph.advance = mTempChar.width;
glyph.channel = 0;
#else
glyph.v0.x = mTempChar.minX;
glyph.v1.x = mTempChar.maxX;
glyph.v0.y = mTempChar.maxY - baseline;
glyph.v1.y = mTempChar.minY - baseline;
glyph.u0 = mTempChar.uvTopLeft;
glyph.u1 = mTempChar.uvBottomLeft;
glyph.u2 = mTempChar.uvBottomRight;
glyph.u3 = mTempChar.uvTopRight;
glyph.advance = mTempChar.advance;
glyph.channel = 0;
#endif
glyph.v0.x = Mathf.Round(glyph.v0.x);
glyph.v0.y = Mathf.Round(glyph.v0.y);
glyph.v1.x = Mathf.Round(glyph.v1.x);
glyph.v1.y = Mathf.Round(glyph.v1.y);
float pd = fontScale * pixelDensity;
if (pd != 1f)
{
glyph.v0 *= pd;
glyph.v1 *= pd;
glyph.advance *= pd;
}
return glyph;
}
}
#endif
return null;
}
static Color mInvisible = new Color(0f, 0f, 0f, 0f);
static BetterList<Color> mColors = new BetterList<Color>();
static float mAlpha = 1f;
#if DYNAMIC_FONT
static CharacterInfo mTempChar;
#endif
/// <summary>
/// Parse Aa syntax alpha encoded in the string.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public float ParseAlpha(string text, int index)
{
int a = (NGUIMath.HexToDecimal(text[index + 1]) << 4) | NGUIMath.HexToDecimal(text[index + 2]);
return Mathf.Clamp01(a / 255f);
}
/// <summary>
/// Parse a RrGgBb color encoded in the string.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public Color ParseColor(string text, int offset) { return ParseColor24(text, offset); }
/// <summary>
/// Parse a RrGgBb color encoded in the string.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public Color ParseColor24(string text, int offset)
{
int r = (NGUIMath.HexToDecimal(text[offset]) << 4) | NGUIMath.HexToDecimal(text[offset + 1]);
int g = (NGUIMath.HexToDecimal(text[offset + 2]) << 4) | NGUIMath.HexToDecimal(text[offset + 3]);
int b = (NGUIMath.HexToDecimal(text[offset + 4]) << 4) | NGUIMath.HexToDecimal(text[offset + 5]);
float f = 1f / 255f;
return new Color(f * r, f * g, f * b);
}
/// <summary>
/// Parse a RrGgBbAa color encoded in the string.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public Color ParseColor32(string text, int offset)
{
int r = (NGUIMath.HexToDecimal(text[offset]) << 4) | NGUIMath.HexToDecimal(text[offset + 1]);
int g = (NGUIMath.HexToDecimal(text[offset + 2]) << 4) | NGUIMath.HexToDecimal(text[offset + 3]);
int b = (NGUIMath.HexToDecimal(text[offset + 4]) << 4) | NGUIMath.HexToDecimal(text[offset + 5]);
int a = (NGUIMath.HexToDecimal(text[offset + 6]) << 4) | NGUIMath.HexToDecimal(text[offset + 7]);
float f = 1f / 255f;
return new Color(f * r, f * g, f * b, f * a);
}
/// <summary>
/// The reverse of ParseColor -- encodes a color in RrGgBb format.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public string EncodeColor(Color c) { return EncodeColor24(c); }
/// <summary>
/// Convenience function that wraps the specified text block in a color tag.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public string EncodeColor(string text, Color c) { return "[c][" + EncodeColor24(c) + "]" + text + "[-][/c]"; }
/// <summary>
/// The reverse of ParseAlpha -- encodes a color in Aa format.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public string EncodeAlpha(float a)
{
int i = Mathf.Clamp(Mathf.RoundToInt(a * 255f), 0, 255);
return NGUIMath.DecimalToHex8(i);
}
/// <summary>
/// The reverse of ParseColor24 -- encodes a color in RrGgBb format.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public string EncodeColor24(Color c)
{
int i = 0xFFFFFF & (NGUIMath.ColorToInt(c) >> 8);
return NGUIMath.DecimalToHex24(i);
}
/// <summary>
/// The reverse of ParseColor32 -- encodes a color in RrGgBb format.
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public string EncodeColor32(Color c)
{
int i = NGUIMath.ColorToInt(c);
return NGUIMath.DecimalToHex32(i);
}
/// <summary>
/// Parse an embedded symbol, such as [FFAA00] (set color) or [-] (undo color change). Returns whether the index was adjusted.
/// </summary>
static public bool ParseSymbol(string text, ref int index)
{
int n = 1;
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethrough = false;
bool ignoreColor = false;
return ParseSymbol(text, ref index, null, false, ref n, ref bold, ref italic, ref underline, ref strikethrough, ref ignoreColor);
}
/// <summary>
/// Whether the specified character falls under the 'hex' character category (0-9, A-F).
/// </summary>
[System.Diagnostics.DebuggerHidden]
[System.Diagnostics.DebuggerStepThrough]
static public bool IsHex(char ch)
{
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
}
/// <summary>
/// Parse the symbol, if possible. Returns 'true' if the 'index' was adjusted.
/// Advanced symbol support originally contributed by Rudy Pangestu.
/// </summary>
static public bool ParseSymbol(string text, ref int index, BetterList<Color> colors, bool premultiply,
ref int sub, ref bool bold, ref bool italic, ref bool underline, ref bool strike, ref bool ignoreColor)
{
int length = text.Length;
if (index + 3 > length || text[index] != '[') return false;
if (text[index + 2] == ']')
{
if (text[index + 1] == '-')
{
if (colors != null && colors.size > 1)
colors.RemoveAt(colors.size - 1);
index += 3;
return true;
}
string sub3 = text.Substring(index, 3);
switch (sub3)
{
case "[b]":
bold = true;
index += 3;
return true;
case "[i]":
italic = true;
index += 3;
return true;
case "[u]":
underline = true;
index += 3;
return true;
case "[s]":
strike = true;
index += 3;
return true;
case "[c]":
ignoreColor = true;
index += 3;
return true;
}
}
if (index + 4 > length) return false;
if (text[index + 3] == ']')
{
string sub4 = text.Substring(index, 4);
switch (sub4)
{
case "[/b]":
bold = false;
index += 4;
return true;
case "[/i]":
italic = false;
index += 4;
return true;
case "[/u]":
underline = false;
index += 4;
return true;
case "[/s]":
strike = false;
index += 4;
return true;
case "[/c]":
ignoreColor = false;
index += 4;
return true;
default:
{
char ch0 = text[index + 1];
char ch1 = text[index + 2];
if (IsHex(ch0) && IsHex(ch1))
{
int a = (NGUIMath.HexToDecimal(ch0) << 4) | NGUIMath.HexToDecimal(ch1);
mAlpha = a / 255f;
index += 4;
return true;
}
}
break;
}
}
if (index + 5 > length) return false;
if (text[index + 4] == ']')
{
string sub5 = text.Substring(index, 5);
switch (sub5)
{
case "[sub]":
sub = 1;
index += 5;
return true;
case "[sup]":
sub = 2;
index += 5;
return true;
}
}
if (index + 6 > length) return false;
if (text[index + 5] == ']')
{
string sub6 = text.Substring(index, 6);
switch (sub6)
{
case "[/sub]":
sub = 0;
index += 6;
return true;
case "[/sup]":
sub = 0;
index += 6;
return true;
case "[/url]":
index += 6;
return true;
}
}
if (text[index + 1] == 'u' && text[index + 2] == 'r' && text[index + 3] == 'l' && text[index + 4] == '=')
{
int closingBracket = text.IndexOf(']', index + 4);
if (closingBracket != -1)
{
index = closingBracket + 1;
return true;
}
else
{
index = text.Length;
return true;
}
}
if (index + 8 > length) return false;
if (text[index + 7] == ']')
{
Color c = ParseColor24(text, index + 1);
if (EncodeColor24(c) != text.Substring(index + 1, 6).ToUpper())
return false;
if (colors != null)
{
c.a = colors[colors.size - 1].a;
if (premultiply && c.a != 1f)
c = Color.Lerp(mInvisible, c, c.a);
colors.Add(c);
}
index += 8;
return true;
}
if (index + 10 > length) return false;
if (text[index + 9] == ']')
{
Color c = ParseColor32(text, index + 1);
if (EncodeColor32(c) != text.Substring(index + 1, 8).ToUpper())
return false;
if (colors != null)
{
if (premultiply && c.a != 1f)
c = Color.Lerp(mInvisible, c, c.a);
colors.Add(c);
}
index += 10;
return true;
}
return false;
}
/// <summary>
/// Runs through the specified string and removes all color-encoding symbols.
/// </summary>
static public string StripSymbols(string text)
{
if (text != null)
{
for (int i = 0, imax = text.Length; i < imax;)
{
char c = text[i];
if (c == '[')
{
int sub = 0;
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethrough = false;
bool ignoreColor = false;
int retVal = i;
if (ParseSymbol(text, ref retVal, null, false, ref sub, ref bold, ref italic, ref underline, ref strikethrough, ref ignoreColor))
{
text = text.Remove(i, retVal - i);
imax = text.Length;
continue;
}
}
++i;
}
}
return text;
}
//yuqiang 2018/05/22 use for multi-language
static public string StripLanSymbol(string text)
{
if (string.IsNullOrEmpty(text))
return string.Empty;
if (string.IsNullOrEmpty(Language))
return text;
int offset = 0;
int startIndex = 0;
int endIndex = 0;
string startLanTag = Language;
string endLanTag = "/" + Language;
int lanLen = startLanTag.Length;
int endLanLen = endLanTag.Length;
int textLen = text.Length;
bool findStart = false;
bool findEnd = false;
StringBuilder sb = new StringBuilder();
for (offset = 0; offset < textLen; ++offset)
{
if (offset + lanLen + 1 >= textLen) break;
if (text[offset] == '[' && text[offset + lanLen + 1] == ']')
{
for (int i = 0; i < lanLen; ++i)
{
if (text[offset + i + 1] == startLanTag[i])
{
findStart = true;
}
else
{
findStart = false;
break;
}
}
if (findStart)
{
//skip ']'
offset += lanLen + 1 + 1;
startIndex = offset;
}
}
if (offset + endLanLen + 1 >= textLen) break;
if (findStart)
{
if (text[offset] == '[' && text[offset + endLanLen + 1] == ']')
{
for (int i = 0; i < endLanLen; ++i)
{
if (text[offset + i + 1] == endLanTag[i])
{
findEnd = true;
}
else
{
findEnd = false;
break;
}
}
}
if (findEnd)
{
endIndex = offset;
if (endIndex > 0 && endIndex >= startIndex)
{
sb.Append(text.Substring(startIndex, endIndex - startIndex));
}
startIndex = endIndex;
findEnd = false;
//跳到语言tag末尾
offset += endLanLen + 1 + 1;
continue;
}
}
}
if (sb.Length > 0)
return sb.ToString();
return text;
}
/// <summary>
/// Align the vertices to be right or center-aligned given the line width specified by NGUIText.lineWidth.
/// </summary>
static public void Align(BetterList<Vector3> verts, int indexOffset, float printedWidth, int elements = 4)
{
switch (alignment)
{
case Alignment.Right:
{
float padding = rectWidth - printedWidth;
if (padding < 0f) return;
#if UNITY_FLASH
for (int i = indexOffset; i < verts.size; ++i)
verts.buffer[i] = verts.buffer[i] + new Vector3(padding, 0f);
#else
for (int i = indexOffset; i < verts.size; ++i)
verts.buffer[i].x += padding;
#endif
break;
}
case Alignment.Center:
{
float padding = (rectWidth - printedWidth) * 0.5f;
if (padding < 0f) return;
// Keep it pixel-perfect
int diff = Mathf.RoundToInt(rectWidth - printedWidth);
int intWidth = Mathf.RoundToInt(rectWidth);
bool oddDiff = (diff & 1) == 1;
bool oddWidth = (intWidth & 1) == 1;
if ((oddDiff && !oddWidth) || (!oddDiff && oddWidth))
padding += 0.5f * fontScale;
#if UNITY_FLASH
for (int i = indexOffset; i < verts.size; ++i)
verts.buffer[i] = verts.buffer[i] + new Vector3(padding, 0f);
#else
for (int i = indexOffset; i < verts.size; ++i)
verts.buffer[i].x += padding;
#endif
break;
}
case Alignment.Justified:
{
// Printed text needs to reach at least 65% of the width in order to be justified
if (printedWidth < rectWidth * 0.65f) return;
// There must be some padding involved
float padding = (rectWidth - printedWidth) * 0.5f;
if (padding < 1f) return;
// There must be at least two characters
int chars = (verts.size - indexOffset) / elements;
if (chars < 1) return;
float progressPerChar = 1f / (chars - 1);
float scale = rectWidth / printedWidth;
for (int i = indexOffset + elements, charIndex = 1; i < verts.size; ++charIndex)
{
float x0 = verts.buffer[i].x;
float x1 = verts.buffer[i + elements / 2].x;
float w = x1 - x0;
float x0a = x0 * scale;
float x1a = x0a + w;
float x1b = x1 * scale;
float x0b = x1b - w;
float progress = charIndex * progressPerChar;
x1 = Mathf.Lerp(x1a, x1b, progress);
x0 = Mathf.Lerp(x0a, x0b, progress);
x0 = Mathf.Round(x0);
x1 = Mathf.Round(x1);
if (elements == 4)
{
#if UNITY_FLASH
verts.buffer[i] = verts.buffer[i] + new Vector3(x0, 0f);
verts.buffer[i+1] = verts.buffer[i+1] + new Vector3(x0, 0f);
verts.buffer[i+2] = verts.buffer[i+2] + new Vector3(x1, 0f);
verts.buffer[i+3] = verts.buffer[i+3] + new Vector3(x1, 0f);
i += elements;
#else
verts.buffer[i++].x = x0;
verts.buffer[i++].x = x0;
verts.buffer[i++].x = x1;
verts.buffer[i++].x = x1;
#endif
}
else if (elements == 2)
{
#if UNITY_FLASH
verts.buffer[i] = verts.buffer[i] + new Vector3(x0, 0f);
verts.buffer[i + 1] = verts.buffer[i + 1] + new Vector3(x1, 0f);
#else
verts.buffer[i++].x = x0;
verts.buffer[i++].x = x1;
#endif
}
else if (elements == 1)
{
#if UNITY_FLASH
verts.buffer[i] = verts.buffer[i] + new Vector3(x0, 0f);
#else
verts.buffer[i++].x = x0;
#endif
}
}
break;
}
}
}
/// <summary>
/// Get the index of the closest character within the provided list of values.
/// Meant to be used with the arrays created by PrintExactCharacterPositions().
/// </summary>
static public int GetExactCharacterIndex(BetterList<Vector3> verts, BetterList<int> indices, Vector2 pos)
{
for (int i = 0; i < indices.size; ++i)
{
int i0 = (i << 1);
int i1 = i0 + 1;
float x0 = verts[i0].x;
if (pos.x < x0) continue;
float x1 = verts[i1].x;
if (pos.x > x1) continue;
float y0 = verts[i0].y;
if (pos.y < y0) continue;
float y1 = verts[i1].y;
if (pos.y > y1) continue;
return indices[i];
}
return 0;
}
/// <summary>
/// Get the index of the closest vertex within the provided list of values.
/// This function first sorts by Y, and only then by X.
/// Meant to be used with the arrays created by PrintApproximateCharacterPositions().
/// </summary>
static public int GetApproximateCharacterIndex(BetterList<Vector3> verts, BetterList<int> indices, Vector2 pos)
{
// First sort by Y, and only then by X
float bestX = float.MaxValue;
float bestY = float.MaxValue;
int bestIndex = 0;
for (int i = 0; i < verts.size; ++i)
{
float diffY = Mathf.Abs(pos.y - verts[i].y);
if (diffY > bestY) continue;
float diffX = Mathf.Abs(pos.x - verts[i].x);
if (diffY < bestY)
{
bestY = diffY;
bestX = diffX;
bestIndex = i;
}
else if (diffX < bestX)
{
bestX = diffX;
bestIndex = i;
}
}
return indices[bestIndex];
}
/// <summary>
/// Whether the specified character is a space.
/// </summary>
[DebuggerHidden]
[DebuggerStepThrough]
static bool IsSpace(int ch) { return (ch == ' ' || ch == 0x200a || ch == 0x200b || ch == '\u2009'); }
/// <summary>
/// Convenience function that ends the line by either appending a new line character or replacing a space with one.
/// </summary>
[DebuggerHidden]
[DebuggerStepThrough]
static public void EndLine(ref StringBuilder s)
{
int i = s.Length - 1;
if (i > 0 && IsSpace(s[i])) s[i] = '\n';
else s.Append('\n');
}
/// <summary>
/// Convenience function that ends the line by replacing a space with a newline character.
/// </summary>
[DebuggerHidden]
[DebuggerStepThrough]
static void ReplaceSpaceWithNewline(ref StringBuilder s)
{
int i = s.Length - 1;
if (i > 0 && IsSpace(s[i])) s[i] = '\n';
}
/// <summary>
/// Get the printed size of the specified string. The returned value is in pixels.
/// </summary>
static public Vector2 CalculatePrintedSize(string text)
{
Vector2 v = Vector2.zero;
if (!string.IsNullOrEmpty(text))
{
// When calculating printed size, get rid of all symbols first since they are invisible anyway
if (encoding) text = StripSymbols(text);
// Ensure we have characters to work with
Prepare(text);
float x = 0f, y = 0f, maxX = 0f;
int textLength = text.Length, ch = 0, prev = 0;
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
// Start a new line
if (ch == '\n')
{
if (x > maxX) maxX = x;
x = 0f;
y += finalLineHeight;
continue;
}
// Skip invalid characters
if (ch < ' ') continue;
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol == null)
{
float w = GetGlyphWidth(ch, prev);
if (w != 0f)
{
w += finalSpacingX;
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x > maxX) maxX = x - finalSpacingX;
x = w;
y += finalLineHeight;
}
else x += w;
prev = ch;
}
}
else
{
float w = finalSpacingX + symbol.advance * fontScale;
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x > maxX) maxX = x - finalSpacingX;
x = w;
y += finalLineHeight;
}
else x += w;
i += symbol.sequence.Length - 1;
prev = 0;
}
}
v.x = ((x > maxX) ? x - finalSpacingX : maxX);
v.y = (y + finalLineHeight);
}
return v;
}
static BetterList<float> mSizes = new BetterList<float>();
/// <summary>
/// Calculate the character index offset required to print the end of the specified text.
/// </summary>
static public int CalculateOffsetToFit(string text)
{
if (string.IsNullOrEmpty(text) || regionWidth < 1) return 0;
Prepare(text);
int textLength = text.Length, ch = 0, prev = 0;
for (int i = 0, imax = text.Length; i < imax; ++i)
{
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol == null)
{
ch = text[i];
float w = GetGlyphWidth(ch, prev);
if (w != 0f) mSizes.Add(finalSpacingX + w);
prev = ch;
}
else
{
mSizes.Add(finalSpacingX + symbol.advance * fontScale);
for (int b = 0, bmax = symbol.sequence.Length - 1; b < bmax; ++b) mSizes.Add(0);
i += symbol.sequence.Length - 1;
prev = 0;
}
}
float remainingWidth = regionWidth;
int currentCharacterIndex = mSizes.size;
while (currentCharacterIndex > 0 && remainingWidth > 0)
remainingWidth -= mSizes[--currentCharacterIndex];
mSizes.Clear();
if (remainingWidth < 0) ++currentCharacterIndex;
return currentCharacterIndex;
}
/// <summary>
/// Get the end of line that would fit into a field of given width.
/// </summary>
static public string GetEndOfLineThatFits(string text)
{
int textLength = text.Length;
int offset = CalculateOffsetToFit(text);
return text.Substring(offset, textLength - offset);
}
/// <summary>
/// Text wrapping functionality. The 'width' and 'height' should be in pixels.
/// </summary>
static public bool WrapText(string text, out string finalText, bool wrapLineColors = false)
{
return WrapText(text, out finalText, false, wrapLineColors);
}
/// <summary>
/// Text wrapping functionality. The 'width' and 'height' should be in pixels.
/// </summary>
static public bool WrapText(string text, out string finalText, bool keepCharCount, bool wrapLineColors, bool useEllipsis = false)
{
if (regionWidth < 1 || regionHeight < 1 || finalLineHeight < 1f)
{
finalText = "";
return false;
}
float height = (maxLines > 0) ? Mathf.Min(regionHeight, finalLineHeight * maxLines) : regionHeight;
int maxLineCount = (maxLines > 0) ? maxLines : 1000000;
maxLineCount = Mathf.FloorToInt(Mathf.Min(maxLineCount, height / finalLineHeight) + 0.01f);
if (maxLineCount == 0)
{
finalText = "";
return false;
}
if (string.IsNullOrEmpty(text)) text = " ";
Prepare(text);
StringBuilder sb = new StringBuilder();
int textLength = text.Length;
float remainingWidth = regionWidth;
int start = 0, offset = 0, lineCount = 1, prev = 0;
bool lineIsEmpty = true;
bool fits = true;
#if FUNCELL_MODIFIED
bool isVie = NGUIText.isVie;
bool eastern = NGUIText.isEastern;
#else
bool eastern = false;
#endif
Color c = tint;
int subscriptMode = 0; // 0 = normal, 1 = subscript, 2 = superscript
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethrough = false;
bool ignoreColor = false;
if (!useSymbols) wrapLineColors = false;
if (wrapLineColors)
{
mColors.Add(c);
sb.Append("[");
sb.Append(NGUIText.EncodeColor(c));
sb.Append("]");
}
// Run through all characters
for (; offset < textLength; ++offset)
{
#region //normal
char ch = text[offset];
if (ch > 12287) eastern = true;
#if FUNCELL_MODIFIED
if (ch >= '\x0E00' && ch <= '\x0E7F') eastern = true;
if (isVie)
{
//如果是越南文
eastern = false;
}
#endif
// New line character -- start a new line
if (ch == '\n')
{
if (lineCount == maxLineCount) break;
remainingWidth = regionWidth;
// Add the previous word to the final string
if (start < offset) sb.Append(text.Substring(start, offset - start + 1));
else sb.Append(ch);
if (wrapLineColors)
{
for (int i = 0; i < mColors.size; ++i)
sb.Insert(sb.Length - 1, "[-]");
for (int i = 0; i < mColors.size; ++i)
{
sb.Append("[");
sb.Append(NGUIText.EncodeColor(mColors[i]));
sb.Append("]");
}
}
lineIsEmpty = true;
++lineCount;
start = offset + 1;
prev = 0;
continue;
}
// When encoded symbols such as [RrGgBb] or [-] are encountered, skip past them
if (encoding)
{
if (!wrapLineColors)
{
if (ParseSymbol(text, ref offset))
{
--offset;
continue;
}
}
else if (ParseSymbol(text, ref offset, mColors, premultiply, ref subscriptMode, ref bold,
ref italic, ref underline, ref strikethrough, ref ignoreColor))
{
if (ignoreColor)
{
c = mColors[mColors.size - 1];
c.a *= mAlpha * tint.a;
}
else
{
c = tint * mColors[mColors.size - 1];
c.a *= mAlpha;
}
for (int b = 0, bmax = mColors.size - 2; b < bmax; ++b)
c.a *= mColors[b].a;
--offset;
continue;
}
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, offset, textLength) : null;
// Calculate how wide this symbol or character is going to be
float glyphWidth;
if (symbol == null)
{
// Find the glyph for this character
float w = GetGlyphWidth(ch, prev);
if (w == 0f && !IsSpace(ch)) continue;
glyphWidth = finalSpacingX + w;
}
else glyphWidth = finalSpacingX + symbol.advance * fontScale;
// Reduce the width
remainingWidth -= glyphWidth;
// If this marks the end of a word, add it to the final string.
if (IsSpace(ch) && !eastern && start < offset)
{
int end = offset - start + 1;
// Last word on the last line should not include an invisible character
if (lineCount == maxLineCount && remainingWidth <= 0f && offset < textLength)
{
char cho = text[offset];
if (cho < ' ' || IsSpace(cho)) --end;
}
sb.Append(text.Substring(start, end));
lineIsEmpty = false;
start = offset + 1;
prev = ch;
}
// Doesn't fit?
if (Mathf.RoundToInt(remainingWidth) < 0)
{
// Can't start a new line
if (lineIsEmpty || lineCount == maxLineCount)
{
// Adds "..." at the end of text that doesn't fit. Contributed by Jason Nollan.
if (useEllipsis && lineCount == maxLineCount && offset > 1)
{
float ellipsisWidth = GetGlyphWidth('.', '.') * 3f;
if (ellipsisWidth < regionWidth)
{
remainingWidth += glyphWidth;
int tempOffset = offset;
int removeCount = 0;
while (tempOffset > 1 && remainingWidth < ellipsisWidth)
{
--tempOffset;
char prevCh = text[tempOffset - 1];
char characterToRemove = text[tempOffset];
bool isCaseWhereSpaceShouldBeInStringBuilderButIsnt = (remainingWidth == 0 && IsSpace(characterToRemove));
remainingWidth += GetGlyphWidth(characterToRemove, prevCh);
if (tempOffset < start && !isCaseWhereSpaceShouldBeInStringBuilderButIsnt)
++removeCount;
}
if (remainingWidth >= ellipsisWidth)
{
if (removeCount > 0)
sb.Length = Mathf.Max(0, sb.Length - removeCount);
sb.Append(text.Substring(start, Mathf.Max(0, tempOffset - start)));
while (sb.Length > 0 && IsSpace(sb[sb.Length - 1])) --sb.Length;
sb.Append("...");
++lineCount;
start = offset = tempOffset;
break;
}
}
}
// This is the first word on the line -- add it up to the character that fits
sb.Append(text.Substring(start, Mathf.Max(0, offset - start)));
bool space = IsSpace(ch);
if (!space && !eastern) fits = false;
if (wrapLineColors && mColors.size > 0) sb.Append("[-]");
if (lineCount++ == maxLineCount)
{
start = offset;
break;
}
if (keepCharCount) ReplaceSpaceWithNewline(ref sb);
else EndLine(ref sb);
if (wrapLineColors)
{
for (int i = 0; i < mColors.size; ++i)
sb.Insert(sb.Length - 1, "[-]");
for (int i = 0; i < mColors.size; ++i)
{
sb.Append("[");
sb.Append(NGUIText.EncodeColor(mColors[i]));
sb.Append("]");
}
}
// Start a brand-new line
lineIsEmpty = true;
if (space)
{
start = offset + 1;
remainingWidth = regionWidth;
}
else
{
start = offset;
remainingWidth = regionWidth - glyphWidth;
}
prev = 0;
}
else
{
// Revert the position to the beginning of the word and reset the line
lineIsEmpty = true;
remainingWidth = regionWidth;
offset = start - 1;
prev = 0;
if (lineCount++ == maxLineCount) break;
if (keepCharCount) ReplaceSpaceWithNewline(ref sb);
else EndLine(ref sb);
if (wrapLineColors)
{
for (int i = 0; i < mColors.size; ++i)
sb.Insert(sb.Length - 1, "[-]");
for (int i = 0; i < mColors.size; ++i)
{
sb.Append("[");
sb.Append(NGUIText.EncodeColor(mColors[i]));
sb.Append("]");
}
}
continue;
}
}
else prev = ch;
// Advance the offset past the symbol
if (symbol != null)
{
offset += symbol.length - 1;
prev = 0;
}
#endregion
}
if (start < offset) sb.Append(text.Substring(start, offset - start));
if (wrapLineColors && mColors.size > 0) sb.Append("[-]");
finalText = sb.ToString();
mColors.Clear();
return fits && ((offset == textLength) || (lineCount <= Mathf.Min(maxLines, maxLineCount)));
}
static Color32 s_c0, s_c1;
#if FUNCELL_MODIFIED
static private void AddVerts(bool isChinese, ChineseRotType chineseRotType, BetterList<Vector3> verts, Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
{
if (isChinese && chineseRotType != ChineseRotType.None)
{
if (chineseRotType == ChineseRotType.Left)
{
verts.Add(v2);
verts.Add(v3);
verts.Add(v4);
verts.Add(v1);
}
else
{
verts.Add(v4);
verts.Add(v1);
verts.Add(v2);
verts.Add(v3);
}
}
else
{
verts.Add(v1);
verts.Add(v2);
verts.Add(v3);
verts.Add(v4);
}
}
/// <summary>
/// Print the specified text into the buffers.
/// </summary>
static public void Print(string text, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols, ChineseRotType chineseRotType)
{
if (string.IsNullOrEmpty(text)) return;
int indexOffset = verts.size;
Prepare(text);
// Start with the white tint
mColors.Add(Color.white);
mAlpha = 1f;
int ch = 0, prev = 0;
float x = 0f, y = 0f, maxX = 0f;
float sizeF = finalSize;
Color gb = tint * gradientBottom;
Color gt = tint * gradientTop;
Color32 uc = tint;
int textLength = text.Length;
Rect uvRect = new Rect();
float invX = 0f, invY = 0f;
float sizePD = sizeF * pixelDensity;
// Advanced symbol support contributed by Rudy Pangestu.
bool subscript = false;
int subscriptMode = 0; // 0 = normal, 1 = subscript, 2 = superscript
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethrough = false;
bool ignoreColor = false;
const float sizeShrinkage = 0.75f;
float v0x;
float v1x;
float v1y;
float v0y;
float prevX = 0;
if (bitmapFont != null)
{
uvRect = bitmapFont.uvRect;
invX = uvRect.width / bitmapFont.texWidth;
invY = uvRect.height / bitmapFont.texHeight;
}
var isChinese = false;
if(chineseRotType != ChineseRotType.None)
{
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
if (ch >= 0x4e00 && ch <= 0x9fbb)
{
isChinese = true;
break;
}
}
}
if(isChinese && chineseRotType == ChineseRotType.Right)
{
var newText = new StringBuilder(textLength);
for(int i = textLength - 1; i >= 0; --i)
{
newText.Append(text[i]);
}
text = newText.ToString();
}
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
prevX = x;
// New line character -- skip to the next line
if (ch == '\n')
{
if (x > maxX) maxX = x;
if (alignment != Alignment.Left)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
x = 0;
y += finalLineHeight;
prev = 0;
continue;
}
// Invalid character -- skip it
if (ch < ' ')
{
prev = ch;
continue;
}
// Color changing symbol
if (encoding && ParseSymbol(text, ref i, mColors, premultiply, ref subscriptMode, ref bold,
ref italic, ref underline, ref strikethrough, ref ignoreColor))
{
Color fc;
if (ignoreColor)
{
fc = mColors[mColors.size - 1];
fc.a *= mAlpha * tint.a;
}
else
{
fc = tint * mColors[mColors.size - 1];
fc.a *= mAlpha;
}
uc = fc;
for (int b = 0, bmax = mColors.size - 2; b < bmax; ++b)
fc.a *= mColors[b].a;
if (gradient)
{
gb = gradientBottom * fc;
gt = gradientTop * fc;
}
--i;
continue;
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol != null)
{
v0x = x + symbol.offsetX * fontScale;
v1x = v0x + symbol.width * fontScale;
v1y = -(y + symbol.offsetY * fontScale);
v0y = v1y - symbol.height * fontScale;
// Doesn't fit? Move down to the next line
if (Mathf.RoundToInt(x + symbol.advance * fontScale) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
prevX = 0;
}
AddVerts(isChinese, chineseRotType, verts, new Vector3(v0x, v0y), new Vector3(v0x, v1y), new Vector3(v1x, v1y), new Vector3(v1x, v0y));
x += finalSpacingX + symbol.advance * fontScale;
i += symbol.length - 1;
prev = 0;
if (uvs != null)
{
Rect uv = symbol.uvRect;
float u0x = uv.xMin;
float u0y = uv.yMin;
float u1x = uv.xMax;
float u1y = uv.yMax;
uvs.Add(new Vector2(u0x, u0y));
uvs.Add(new Vector2(u0x, u1y));
uvs.Add(new Vector2(u1x, u1y));
uvs.Add(new Vector2(u1x, u0y));
}
if (cols != null)
{
if (symbolStyle == SymbolStyle.Colored)
{
for (int b = 0; b < 4; ++b) cols.Add(uc);
}
else
{
Color32 col = Color.white;
col.a = uc.a;
for (int b = 0; b < 4; ++b) cols.Add(col);
}
}
}
else // No symbol present
{
GlyphInfo glyph = GetGlyph(ch, prev);
if (glyph == null) continue;
prev = ch;
if (subscriptMode != 0)
{
glyph.v0.x *= sizeShrinkage;
glyph.v0.y *= sizeShrinkage;
glyph.v1.x *= sizeShrinkage;
glyph.v1.y *= sizeShrinkage;
if (subscriptMode == 1)
{
glyph.v0.y -= fontScale * fontSize * 0.4f;
glyph.v1.y -= fontScale * fontSize * 0.4f;
}
else
{
glyph.v0.y += fontScale * fontSize * 0.05f;
glyph.v1.y += fontScale * fontSize * 0.05f;
}
}
v0x = glyph.v0.x + x;
v0y = glyph.v0.y - y;
v1x = glyph.v1.x + x;
v1y = glyph.v1.y - y;
float w = glyph.advance;
if (finalSpacingX < 0f) w += finalSpacingX;
// Doesn't fit? Move down to the next line
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
prevX = 0;
}
if (IsSpace(ch))
{
if (underline)
{
ch = '_';
}
else if (strikethrough)
{
ch = '-';
}
}
// Advance the position
x += (subscriptMode == 0) ? finalSpacingX + glyph.advance :
(finalSpacingX + glyph.advance) * sizeShrinkage;
// No need to continue if this is a space character
if (IsSpace(ch)) continue;
// Texture coordinates
if (uvs != null)
{
if (bitmapFont != null)
{
glyph.u0.x = uvRect.xMin + invX * glyph.u0.x;
glyph.u2.x = uvRect.xMin + invX * glyph.u2.x;
glyph.u0.y = uvRect.yMax - invY * glyph.u0.y;
glyph.u2.y = uvRect.yMax - invY * glyph.u2.y;
glyph.u1.x = glyph.u0.x;
glyph.u1.y = glyph.u2.y;
glyph.u3.x = glyph.u2.x;
glyph.u3.y = glyph.u0.y;
}
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
uvs.Add(glyph.u0);
uvs.Add(glyph.u1);
uvs.Add(glyph.u2);
uvs.Add(glyph.u3);
}
}
// Vertex colors
if (cols != null)
{
if (glyph.channel == 0 || glyph.channel == 15)
{
if (gradient)
{
float min = sizePD + glyph.v0.y / fontScale;
float max = sizePD + glyph.v1.y / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
cols.Add(s_c0);
cols.Add(s_c1);
cols.Add(s_c1);
cols.Add(s_c0);
}
}
else
{
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(uc);
}
}
else
{
// Packed fonts come as alpha masks in each of the RGBA channels.
// In order to use it we need to use a special shader.
//
// Limitations:
// - Effects (drop shadow, outline) will not work.
// - Should not be a part of the atlas (eastern fonts rarely are anyway).
// - Lower color precision
Color col = uc;
col *= 0.49f;
switch (glyph.channel)
{
case 1: col.b += 0.51f; break;
case 2: col.g += 0.51f; break;
case 4: col.r += 0.51f; break;
case 8: col.a += 0.51f; break;
}
Color32 c = col;
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(c);
}
}
// Bold and italic contributed by Rudy Pangestu.
if (!bold)
{
if (!italic)
{
AddVerts(isChinese, chineseRotType, verts, new Vector3(v0x, v0y), new Vector3(v0x, v1y), new Vector3(v1x, v1y), new Vector3(v1x, v0y));
}
else // Italic
{
float slant = fontSize * 0.1f * ((v1y - v0y) / fontSize);
AddVerts(isChinese, chineseRotType, verts, new Vector3(v0x - slant, v0y), new Vector3(v0x + slant, v1y), new Vector3(v1x + slant, v1y), new Vector3(v1x - slant, v0y));
}
}
else // Bold
{
for (int j = 0; j < 4; ++j)
{
float a = mBoldOffset[j * 2];
float b = mBoldOffset[j * 2 + 1];
float slant = (italic ? fontSize * 0.1f * ((v1y - v0y) / fontSize) : 0f);
AddVerts(isChinese, chineseRotType, verts, new Vector3(v0x + a - slant, v0y + b), new Vector3(v0x + a + slant, v1y + b), new Vector3(v1x + a + slant, v1y + b), new Vector3(v1x + a - slant, v0y + b));
}
}
// Underline and strike-through contributed by Rudy Pangestu.
if (underline || strikethrough)
{
GlyphInfo dash = GetGlyph(strikethrough ? '-' : '_', prev);
if (dash == null) continue;
if (uvs != null)
{
if (bitmapFont != null)
{
dash.u0.x = uvRect.xMin + invX * dash.u0.x;
dash.u2.x = uvRect.xMin + invX * dash.u2.x;
dash.u0.y = uvRect.yMax - invY * dash.u0.y;
dash.u2.y = uvRect.yMax - invY * dash.u2.y;
}
float cx = (dash.u0.x + dash.u2.x) * 0.5f;
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
uvs.Add(new Vector2(cx, dash.u0.y));
uvs.Add(new Vector2(cx, dash.u2.y));
uvs.Add(new Vector2(cx, dash.u2.y));
uvs.Add(new Vector2(cx, dash.u0.y));
}
}
if (subscript && strikethrough)
{
v0y = (-y + dash.v0.y) * sizeShrinkage;
v1y = (-y + dash.v1.y) * sizeShrinkage;
}
else
{
v0y = (-y + dash.v0.y);
v1y = (-y + dash.v1.y);
}
if (bold)
{
for (int j = 0; j < 4; ++j)
{
float a = mBoldOffset[j * 2];
float b = mBoldOffset[j * 2 + 1];
AddVerts(isChinese, chineseRotType, verts, new Vector3(prevX + a, v0y + b), new Vector3(prevX + a, v1y + b), new Vector3(x + a, v1y + b), new Vector3(x + a, v0y + b));
}
}
else
{
AddVerts(isChinese, chineseRotType, verts, new Vector3(prevX, v0y), new Vector3(prevX, v1y), new Vector3(x, v1y), new Vector3(x, v0y));
}
if (gradient)
{
float min = sizePD + dash.v0.y / fontScale;
float max = sizePD + dash.v1.y / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
cols.Add(s_c0);
cols.Add(s_c1);
cols.Add(s_c1);
cols.Add(s_c0);
}
}
else
{
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(uc);
}
}
}
}
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
mColors.Clear();
}
#else
/// <summary>
/// Print the specified text into the buffers.
/// </summary>
static public void Print(string text, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
{
if (string.IsNullOrEmpty(text)) return;
int indexOffset = verts.size;
Prepare(text);
// Start with the white tint
mColors.Add(Color.white);
mAlpha = 1f;
int ch = 0, prev = 0;
float x = 0f, y = 0f, maxX = 0f;
float sizeF = finalSize;
Color gb = tint * gradientBottom;
Color gt = tint * gradientTop;
Color32 uc = tint;
int textLength = text.Length;
Rect uvRect = new Rect();
float invX = 0f, invY = 0f;
float sizePD = sizeF * pixelDensity;
// Advanced symbol support contributed by Rudy Pangestu.
bool subscript = false;
int subscriptMode = 0; // 0 = normal, 1 = subscript, 2 = superscript
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethrough = false;
bool ignoreColor = false;
const float sizeShrinkage = 0.75f;
float v0x;
float v1x;
float v1y;
float v0y;
float prevX = 0;
if (bitmapFont != null)
{
uvRect = bitmapFont.uvRect;
invX = uvRect.width / bitmapFont.texWidth;
invY = uvRect.height / bitmapFont.texHeight;
}
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
prevX = x;
// New line character -- skip to the next line
if (ch == '\n')
{
if (x > maxX) maxX = x;
if (alignment != Alignment.Left)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
x = 0;
y += finalLineHeight;
prev = 0;
continue;
}
// Invalid character -- skip it
if (ch < ' ')
{
prev = ch;
continue;
}
// Color changing symbol
if (encoding && ParseSymbol(text, ref i, mColors, premultiply, ref subscriptMode, ref bold,
ref italic, ref underline, ref strikethrough, ref ignoreColor))
{
Color fc;
if (ignoreColor)
{
fc = mColors[mColors.size - 1];
fc.a *= mAlpha * tint.a;
}
else
{
fc = tint * mColors[mColors.size - 1];
fc.a *= mAlpha;
}
uc = fc;
for (int b = 0, bmax = mColors.size - 2; b < bmax; ++b)
fc.a *= mColors[b].a;
if (gradient)
{
gb = gradientBottom * fc;
gt = gradientTop * fc;
}
--i;
continue;
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol != null)
{
v0x = x + symbol.offsetX * fontScale;
v1x = v0x + symbol.width * fontScale;
v1y = -(y + symbol.offsetY * fontScale);
v0y = v1y - symbol.height * fontScale;
// Doesn't fit? Move down to the next line
if (Mathf.RoundToInt(x + symbol.advance * fontScale) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
prevX = 0;
}
verts.Add(new Vector3(v0x, v0y));
verts.Add(new Vector3(v0x, v1y));
verts.Add(new Vector3(v1x, v1y));
verts.Add(new Vector3(v1x, v0y));
x += finalSpacingX + symbol.advance * fontScale;
i += symbol.length - 1;
prev = 0;
if (uvs != null)
{
Rect uv = symbol.uvRect;
float u0x = uv.xMin;
float u0y = uv.yMin;
float u1x = uv.xMax;
float u1y = uv.yMax;
uvs.Add(new Vector2(u0x, u0y));
uvs.Add(new Vector2(u0x, u1y));
uvs.Add(new Vector2(u1x, u1y));
uvs.Add(new Vector2(u1x, u0y));
}
if (cols != null)
{
if (symbolStyle == SymbolStyle.Colored)
{
for (int b = 0; b < 4; ++b) cols.Add(uc);
}
else
{
Color32 col = Color.white;
col.a = uc.a;
for (int b = 0; b < 4; ++b) cols.Add(col);
}
}
}
else // No symbol present
{
GlyphInfo glyph = GetGlyph(ch, prev);
if (glyph == null) continue;
prev = ch;
if (subscriptMode != 0)
{
glyph.v0.x *= sizeShrinkage;
glyph.v0.y *= sizeShrinkage;
glyph.v1.x *= sizeShrinkage;
glyph.v1.y *= sizeShrinkage;
if (subscriptMode == 1)
{
glyph.v0.y -= fontScale * fontSize * 0.4f;
glyph.v1.y -= fontScale * fontSize * 0.4f;
}
else
{
glyph.v0.y += fontScale * fontSize * 0.05f;
glyph.v1.y += fontScale * fontSize * 0.05f;
}
}
v0x = glyph.v0.x + x;
v0y = glyph.v0.y - y;
v1x = glyph.v1.x + x;
v1y = glyph.v1.y - y;
float w = glyph.advance;
if (finalSpacingX < 0f) w += finalSpacingX;
// Doesn't fit? Move down to the next line
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
prevX = 0;
}
if (IsSpace(ch))
{
if (underline)
{
ch = '_';
}
else if (strikethrough)
{
ch = '-';
}
}
// Advance the position
x += (subscriptMode == 0) ? finalSpacingX + glyph.advance :
(finalSpacingX + glyph.advance) * sizeShrinkage;
// No need to continue if this is a space character
if (IsSpace(ch)) continue;
// Texture coordinates
if (uvs != null)
{
if (bitmapFont != null)
{
glyph.u0.x = uvRect.xMin + invX * glyph.u0.x;
glyph.u2.x = uvRect.xMin + invX * glyph.u2.x;
glyph.u0.y = uvRect.yMax - invY * glyph.u0.y;
glyph.u2.y = uvRect.yMax - invY * glyph.u2.y;
glyph.u1.x = glyph.u0.x;
glyph.u1.y = glyph.u2.y;
glyph.u3.x = glyph.u2.x;
glyph.u3.y = glyph.u0.y;
}
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
uvs.Add(glyph.u0);
uvs.Add(glyph.u1);
uvs.Add(glyph.u2);
uvs.Add(glyph.u3);
}
}
// Vertex colors
if (cols != null)
{
if (glyph.channel == 0 || glyph.channel == 15)
{
if (gradient)
{
float min = sizePD + glyph.v0.y / fontScale;
float max = sizePD + glyph.v1.y / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
cols.Add(s_c0);
cols.Add(s_c1);
cols.Add(s_c1);
cols.Add(s_c0);
}
}
else
{
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(uc);
}
}
else
{
// Packed fonts come as alpha masks in each of the RGBA channels.
// In order to use it we need to use a special shader.
//
// Limitations:
// - Effects (drop shadow, outline) will not work.
// - Should not be a part of the atlas (eastern fonts rarely are anyway).
// - Lower color precision
Color col = uc;
col *= 0.49f;
switch (glyph.channel)
{
case 1: col.b += 0.51f; break;
case 2: col.g += 0.51f; break;
case 4: col.r += 0.51f; break;
case 8: col.a += 0.51f; break;
}
Color32 c = col;
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(c);
}
}
// Bold and italic contributed by Rudy Pangestu.
if (!bold)
{
if (!italic)
{
verts.Add(new Vector3(v0x, v0y));
verts.Add(new Vector3(v0x, v1y));
verts.Add(new Vector3(v1x, v1y));
verts.Add(new Vector3(v1x, v0y));
}
else // Italic
{
float slant = fontSize * 0.1f * ((v1y - v0y) / fontSize);
verts.Add(new Vector3(v0x - slant, v0y));
verts.Add(new Vector3(v0x + slant, v1y));
verts.Add(new Vector3(v1x + slant, v1y));
verts.Add(new Vector3(v1x - slant, v0y));
}
}
else // Bold
{
for (int j = 0; j < 4; ++j)
{
float a = mBoldOffset[j * 2];
float b = mBoldOffset[j * 2 + 1];
float slant = (italic ? fontSize * 0.1f * ((v1y - v0y) / fontSize) : 0f);
verts.Add(new Vector3(v0x + a - slant, v0y + b));
verts.Add(new Vector3(v0x + a + slant, v1y + b));
verts.Add(new Vector3(v1x + a + slant, v1y + b));
verts.Add(new Vector3(v1x + a - slant, v0y + b));
}
}
// Underline and strike-through contributed by Rudy Pangestu.
if (underline || strikethrough)
{
GlyphInfo dash = GetGlyph(strikethrough ? '-' : '_', prev);
if (dash == null) continue;
if (uvs != null)
{
if (bitmapFont != null)
{
dash.u0.x = uvRect.xMin + invX * dash.u0.x;
dash.u2.x = uvRect.xMin + invX * dash.u2.x;
dash.u0.y = uvRect.yMax - invY * dash.u0.y;
dash.u2.y = uvRect.yMax - invY * dash.u2.y;
}
float cx = (dash.u0.x + dash.u2.x) * 0.5f;
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
uvs.Add(new Vector2(cx, dash.u0.y));
uvs.Add(new Vector2(cx, dash.u2.y));
uvs.Add(new Vector2(cx, dash.u2.y));
uvs.Add(new Vector2(cx, dash.u0.y));
}
}
if (subscript && strikethrough)
{
v0y = (-y + dash.v0.y) * sizeShrinkage;
v1y = (-y + dash.v1.y) * sizeShrinkage;
}
else
{
v0y = (-y + dash.v0.y);
v1y = (-y + dash.v1.y);
}
if (bold)
{
for (int j = 0; j < 4; ++j)
{
float a = mBoldOffset[j * 2];
float b = mBoldOffset[j * 2 + 1];
verts.Add(new Vector3(prevX + a, v0y + b));
verts.Add(new Vector3(prevX + a, v1y + b));
verts.Add(new Vector3(x + a, v1y + b));
verts.Add(new Vector3(x + a, v0y + b));
}
}
else
{
verts.Add(new Vector3(prevX, v0y));
verts.Add(new Vector3(prevX, v1y));
verts.Add(new Vector3(x, v1y));
verts.Add(new Vector3(x, v0y));
}
if (gradient)
{
float min = sizePD + dash.v0.y / fontScale;
float max = sizePD + dash.v1.y / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
cols.Add(s_c0);
cols.Add(s_c1);
cols.Add(s_c1);
cols.Add(s_c0);
}
}
else
{
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(uc);
}
}
}
}
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
mColors.Clear();
}
#endif
static float[] mBoldOffset = new float[]
{
-0.25f, 0f, 0.25f, 0f,
0f, -0.25f, 0f, 0.25f
};
/// <summary>
/// Print character positions and indices into the specified buffer. Meant to be used with the "find closest vertex" calculations.
/// </summary>
static public void PrintApproximateCharacterPositions(string text, BetterList<Vector3> verts, BetterList<int> indices)
{
if (string.IsNullOrEmpty(text)) text = " ";
Prepare(text);
float x = 0f, y = 0f, maxX = 0f, halfSize = fontSize * fontScale * 0.5f;
int textLength = text.Length, indexOffset = verts.size, ch = 0, prev = 0;
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
verts.Add(new Vector3(x, -y - halfSize));
indices.Add(i);
if (ch == '\n')
{
if (x > maxX) maxX = x;
if (alignment != Alignment.Left)
{
Align(verts, indexOffset, x - finalSpacingX, 1);
indexOffset = verts.size;
}
x = 0;
y += finalLineHeight;
prev = 0;
continue;
}
else if (ch < ' ')
{
prev = 0;
continue;
}
if (encoding && ParseSymbol(text, ref i))
{
--i;
continue;
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol == null)
{
float w = GetGlyphWidth(ch, prev);
if (w != 0f)
{
w += finalSpacingX;
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX, 1);
indexOffset = verts.size;
}
x = w;
y += finalLineHeight;
}
else x += w;
verts.Add(new Vector3(x, -y - halfSize));
indices.Add(i + 1);
prev = ch;
}
}
else
{
float w = symbol.advance * fontScale + finalSpacingX;
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX, 1);
indexOffset = verts.size;
}
x = w;
y += finalLineHeight;
}
else x += w;
verts.Add(new Vector3(x, -y - halfSize));
indices.Add(i + 1);
i += symbol.sequence.Length - 1;
prev = 0;
}
}
if (alignment != Alignment.Left && indexOffset < verts.size)
Align(verts, indexOffset, x - finalSpacingX, 1);
}
/// <summary>
/// Print character positions and indices into the specified buffer.
/// This function's data is meant to be used for precise character selection, such as clicking on a link.
/// There are 2 vertices for every index: Bottom Left + Top Right.
/// </summary>
static public void PrintExactCharacterPositions(string text, BetterList<Vector3> verts, BetterList<int> indices)
{
if (string.IsNullOrEmpty(text)) text = " ";
Prepare(text);
float fullSize = fontSize * fontScale;
float x = 0f, y = 0f, maxX = 0f;
int textLength = text.Length, indexOffset = verts.size, ch = 0, prev = 0;
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
if (ch == '\n')
{
if (x > maxX) maxX = x;
if (alignment != Alignment.Left)
{
Align(verts, indexOffset, x - finalSpacingX, 2);
indexOffset = verts.size;
}
x = 0;
y += finalLineHeight;
prev = 0;
continue;
}
else if (ch < ' ')
{
prev = 0;
continue;
}
if (encoding && ParseSymbol(text, ref i))
{
--i;
continue;
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol == null)
{
float gw = GetGlyphWidth(ch, prev);
if (gw != 0f)
{
float w = gw + finalSpacingX;
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX, 2);
indexOffset = verts.size;
}
x = 0f;
y += finalLineHeight;
prev = 0;
--i;
continue;
}
indices.Add(i);
verts.Add(new Vector3(x, -y - fullSize));
verts.Add(new Vector3(x + w, -y));
prev = ch;
x += w;
}
}
else
{
float w = symbol.advance * fontScale + finalSpacingX;
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX, 2);
indexOffset = verts.size;
}
x = 0f;
y += finalLineHeight;
prev = 0;
--i;
continue;
}
indices.Add(i);
verts.Add(new Vector3(x, -y - fullSize));
verts.Add(new Vector3(x + w, -y));
i += symbol.sequence.Length - 1;
x += w;
prev = 0;
}
}
if (alignment != Alignment.Left && indexOffset < verts.size)
Align(verts, indexOffset, x - finalSpacingX, 2);
}
/// <summary>
/// Print the caret and selection vertices. Note that it's expected that 'text' has been stripped clean of symbols.
/// </summary>
static public void PrintCaretAndSelection(string text, int start, int end, BetterList<Vector3> caret, BetterList<Vector3> highlight)
{
if (string.IsNullOrEmpty(text)) text = " ";
Prepare(text);
int caretPos = end;
if (start > end)
{
end = start;
start = caretPos;
}
float x = 0f, y = 0f, maxX = 0f, fs = fontSize * fontScale;
int caretOffset = (caret != null) ? caret.size : 0;
int highlightOffset = (highlight != null) ? highlight.size : 0;
int textLength = text.Length, index = 0, ch = 0, prev = 0;
bool highlighting = false, caretSet = false;
Vector2 last0 = Vector2.zero;
Vector2 last1 = Vector2.zero;
for (; index < textLength; ++index)
{
// Print the caret
if (caret != null && !caretSet && caretPos <= index)
{
caretSet = true;
caret.Add(new Vector3(x - 1f, -y - fs));
caret.Add(new Vector3(x - 1f, -y));
caret.Add(new Vector3(x + 1f, -y));
caret.Add(new Vector3(x + 1f, -y - fs));
}
ch = text[index];
if (ch == '\n')
{
// Used for alignment purposes
if (x > maxX) maxX = x;
// Align the caret
if (caret != null && caretSet)
{
if (alignment != Alignment.Left) Align(caret, caretOffset, x - finalSpacingX);
caret = null;
}
if (highlight != null)
{
if (highlighting)
{
// Close the selection on this line
highlighting = false;
highlight.Add(last1);
highlight.Add(last0);
}
else if (start <= index && end > index)
{
// This must be an empty line. Add a narrow vertical highlight.
highlight.Add(new Vector3(x, -y - fs));
highlight.Add(new Vector3(x, -y));
highlight.Add(new Vector3(x + 2f, -y));
highlight.Add(new Vector3(x + 2f, -y - fs));
}
// Align the highlight
if (alignment != Alignment.Left && highlightOffset < highlight.size)
{
Align(highlight, highlightOffset, x - finalSpacingX);
highlightOffset = highlight.size;
}
}
x = 0;
y += finalLineHeight;
prev = 0;
continue;
}
else if (ch < ' ')
{
prev = 0;
continue;
}
if (encoding && ParseSymbol(text, ref index))
{
--index;
continue;
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, index, textLength) : null;
float w = (symbol != null) ? symbol.advance * fontScale : GetGlyphWidth(ch, prev);
if (w != 0f)
{
float v0x = x;
float v1x = x + w;
float v0y = -y - fs;
float v1y = -y;
if (Mathf.RoundToInt(v1x + finalSpacingX) > regionWidth)
{
if (x == 0f) return;
// Used for alignment purposes
if (x > maxX) maxX = x;
// Align the caret
if (caret != null && caretSet)
{
if (alignment != Alignment.Left) Align(caret, caretOffset, x - finalSpacingX);
caret = null;
}
if (highlight != null)
{
if (highlighting)
{
// Close the selection on this line
highlighting = false;
highlight.Add(last1);
highlight.Add(last0);
}
else if (start <= index && end > index)
{
// This must be an empty line. Add a narrow vertical highlight.
highlight.Add(new Vector3(x, -y - fs));
highlight.Add(new Vector3(x, -y));
highlight.Add(new Vector3(x + 2f, -y));
highlight.Add(new Vector3(x + 2f, -y - fs));
}
// Align the highlight
if (alignment != Alignment.Left && highlightOffset < highlight.size)
{
Align(highlight, highlightOffset, x - finalSpacingX);
highlightOffset = highlight.size;
}
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
}
x += w + finalSpacingX;
// Print the highlight
if (highlight != null)
{
if (start > index || end <= index)
{
if (highlighting)
{
// Finish the highlight
highlighting = false;
highlight.Add(last1);
highlight.Add(last0);
}
}
else if (!highlighting)
{
// Start the highlight
highlighting = true;
highlight.Add(new Vector3(v0x, v0y));
highlight.Add(new Vector3(v0x, v1y));
}
}
// Save what the character ended with
last0 = new Vector2(v1x, v0y);
last1 = new Vector2(v1x, v1y);
prev = ch;
}
}
// Ensure we always have a caret
if (caret != null)
{
if (!caretSet)
{
caret.Add(new Vector3(x - 1f, -y - fs));
caret.Add(new Vector3(x - 1f, -y));
caret.Add(new Vector3(x + 1f, -y));
caret.Add(new Vector3(x + 1f, -y - fs));
}
if (alignment != Alignment.Left)
Align(caret, caretOffset, x - finalSpacingX);
}
// Close the selection
if (highlight != null)
{
if (highlighting)
{
// Finish the highlight
highlight.Add(last1);
highlight.Add(last0);
}
else if (start < index && end == index)
{
// Happens when highlight ends on an empty line. Highlight it with a thin line.
highlight.Add(new Vector3(x, -y - fs));
highlight.Add(new Vector3(x, -y));
highlight.Add(new Vector3(x + 2f, -y));
highlight.Add(new Vector3(x + 2f, -y - fs));
}
// Align the highlight
if (alignment != Alignment.Left && highlightOffset < highlight.size)
Align(highlight, highlightOffset, x - finalSpacingX);
}
}
}