//---------------------------------------------- // 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; /// /// Helper class containing functionality related to using dynamic fonts. /// 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; } /// /// 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. /// 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 /// /// Recalculate the 'final' values. /// static public void Update() { Update(true); } /// /// Recalculate the 'final' values. /// 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 } /// /// Prepare to use the specified text. /// static public void Prepare(string text) { #if DYNAMIC_FONT if (dynamicFont != null) dynamicFont.RequestCharactersInTexture(text, finalSize, fontStyle); #endif } /// /// Get the specified symbol. /// static public BMSymbol GetSymbol(string text, int index, int textLength) { return (bitmapFont != null) ? bitmapFont.MatchSymbol(text, index, textLength) : null; } /// /// Get the width of the specified glyph. Returns zero if the glyph could not be retrieved. /// 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; } /// /// Get the specified glyph. /// 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 mColors = new BetterList(); static float mAlpha = 1f; #if DYNAMIC_FONT static CharacterInfo mTempChar; #endif /// /// Parse Aa syntax alpha encoded in the string. /// [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); } /// /// Parse a RrGgBb color encoded in the string. /// [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public Color ParseColor(string text, int offset) { return ParseColor24(text, offset); } /// /// Parse a RrGgBb color encoded in the string. /// [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); } /// /// Parse a RrGgBbAa color encoded in the string. /// [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); } /// /// The reverse of ParseColor -- encodes a color in RrGgBb format. /// [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public string EncodeColor(Color c) { return EncodeColor24(c); } /// /// Convenience function that wraps the specified text block in a color tag. /// [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public string EncodeColor(string text, Color c) { return "[c][" + EncodeColor24(c) + "]" + text + "[-][/c]"; } /// /// The reverse of ParseAlpha -- encodes a color in Aa format. /// [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); } /// /// The reverse of ParseColor24 -- encodes a color in RrGgBb format. /// [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public string EncodeColor24(Color c) { int i = 0xFFFFFF & (NGUIMath.ColorToInt(c) >> 8); return NGUIMath.DecimalToHex24(i); } /// /// The reverse of ParseColor32 -- encodes a color in RrGgBb format. /// [System.Diagnostics.DebuggerHidden] [System.Diagnostics.DebuggerStepThrough] static public string EncodeColor32(Color c) { int i = NGUIMath.ColorToInt(c); return NGUIMath.DecimalToHex32(i); } /// /// Parse an embedded symbol, such as [FFAA00] (set color) or [-] (undo color change). Returns whether the index was adjusted. /// 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); } /// /// Whether the specified character falls under the 'hex' character category (0-9, A-F). /// [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'); } /// /// Parse the symbol, if possible. Returns 'true' if the 'index' was adjusted. /// Advanced symbol support originally contributed by Rudy Pangestu. /// static public bool ParseSymbol(string text, ref int index, BetterList 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; } /// /// Runs through the specified string and removes all color-encoding symbols. /// 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; } /// /// Align the vertices to be right or center-aligned given the line width specified by NGUIText.lineWidth. /// static public void Align(BetterList 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; } } } /// /// Get the index of the closest character within the provided list of values. /// Meant to be used with the arrays created by PrintExactCharacterPositions(). /// static public int GetExactCharacterIndex(BetterList verts, BetterList 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; } /// /// 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(). /// static public int GetApproximateCharacterIndex(BetterList verts, BetterList 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]; } /// /// Whether the specified character is a space. /// [DebuggerHidden] [DebuggerStepThrough] static bool IsSpace(int ch) { return (ch == ' ' || ch == 0x200a || ch == 0x200b || ch == '\u2009'); } /// /// Convenience function that ends the line by either appending a new line character or replacing a space with one. /// [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'); } /// /// Convenience function that ends the line by replacing a space with a newline character. /// [DebuggerHidden] [DebuggerStepThrough] static void ReplaceSpaceWithNewline(ref StringBuilder s) { int i = s.Length - 1; if (i > 0 && IsSpace(s[i])) s[i] = '\n'; } /// /// Get the printed size of the specified string. The returned value is in pixels. /// 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 mSizes = new BetterList(); /// /// Calculate the character index offset required to print the end of the specified text. /// 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; } /// /// Get the end of line that would fit into a field of given width. /// static public string GetEndOfLineThatFits(string text) { int textLength = text.Length; int offset = CalculateOffsetToFit(text); return text.Substring(offset, textLength - offset); } /// /// Text wrapping functionality. The 'width' and 'height' should be in pixels. /// static public bool WrapText(string text, out string finalText, bool wrapLineColors = false) { return WrapText(text, out finalText, false, wrapLineColors); } /// /// Text wrapping functionality. The 'width' and 'height' should be in pixels. /// 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 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); } } /// /// Print the specified text into the buffers. /// static public void Print(string text, BetterList verts, BetterList uvs, BetterList 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 /// /// Print the specified text into the buffers. /// static public void Print(string text, BetterList verts, BetterList uvs, BetterList 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 }; /// /// Print character positions and indices into the specified buffer. Meant to be used with the "find closest vertex" calculations. /// static public void PrintApproximateCharacterPositions(string text, BetterList verts, BetterList 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); } /// /// 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. /// static public void PrintExactCharacterPositions(string text, BetterList verts, BetterList 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); } /// /// Print the caret and selection vertices. Note that it's expected that 'text' has been stripped clean of symbols. /// static public void PrintCaretAndSelection(string text, int start, int end, BetterList caret, BetterList 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); } } }