//------------------------------------------------- // NGUI: Next-Gen UI kit // Copyright © 2011-2019 Tasharen Entertainment Inc //------------------------------------------------- // Dynamic font support contributed by the NGUI community members: // Unisip, zh4ox, Mudwiz, Nicki, DarkMagicCK. using UnityEngine; using System.Collections.Generic; /// /// Generic interface for the NGUI's font implementations. Added in order to support both /// old style (prefab-based) and new style (scriptable object-based) fonts. /// public interface INGUIFont { /// /// Access to the BMFont class directly. /// BMFont bmFont { get; set; } /// /// Original width of the font's texture in pixels. /// int texWidth { get; set; } /// /// Original height of the font's texture in pixels. /// int texHeight { get; set; } /// /// Whether the font has any symbols defined. /// bool hasSymbols { get; } /// /// List of symbols within the font. /// List symbols { get; set; } /// /// Atlas used by the font, if any. /// INGUIAtlas atlas { get; set; } /// /// Convenience method that returns the chosen sprite inside the atlas. /// UISpriteData GetSprite (string spriteName); /// /// Get or set the material used by this font. /// Material material { get; set; } /// /// Whether the font is using a premultiplied alpha material. /// bool premultipliedAlphaShader { get; } /// /// Whether the font is a packed font. /// bool packedFontShader { get; } /// /// Convenience function that returns the texture used by the font. /// Texture2D texture { get; } /// /// Offset and scale applied to all UV coordinates. /// Rect uvRect { get; set; } /// /// Sprite used by the font, if any. /// string spriteName { get; set; } /// /// Whether this is a valid font. /// bool isValid { get; } /// /// Pixel-perfect size of this font. /// int defaultSize { get; set; } /// /// Retrieves the sprite used by the font, if any. /// UISpriteData sprite { get; } /// /// Setting a replacement atlas value will cause everything using this font to use the replacement font instead. /// Suggested use: set up all your widgets to use a dummy font that points to the real font. Switching that font to /// another one (for example an eastern language one) is then a simple matter of setting this field on your dummy font. /// INGUIFont replacement { get; set; } /// /// Checks the replacement references, returning the deepest-most font. /// INGUIFont finalFont { get; } /// /// Whether the font is dynamic. /// bool isDynamic { get; } /// /// Get or set the dynamic font source. /// Font dynamicFont { get; set; } /// /// Get or set the dynamic font's style. /// FontStyle dynamicFontStyle { get; set; } /// /// Helper function that determines whether the font uses the specified one, taking replacements into account. /// bool References (INGUIFont font); /// /// Refresh all labels that use this font. /// void MarkAsChanged (); /// /// Forcefully update the font's sprite reference. /// void UpdateUVRect (); /// /// Retrieve the symbol at the beginning of the specified sequence, if a match is found. /// BMSymbol MatchSymbol (string text, int offset, int textLength); /// /// Add a new symbol to the font. /// void AddSymbol (string sequence, string spriteName); /// /// Remove the specified symbol from the font. /// void RemoveSymbol (string sequence); /// /// Change an existing symbol's sequence to the specified value. /// void RenameSymbol (string before, string after); /// /// Whether the specified sprite is being used by the font. /// bool UsesSprite (string s); } /// /// NGUI Font contains everything needed to be able to print text. /// [ExecuteInEditMode] public class NGUIFont : ScriptableObject, INGUIFont { [HideInInspector][SerializeField] Material mMat; [HideInInspector][SerializeField] Rect mUVRect = new Rect(0f, 0f, 1f, 1f); [HideInInspector][SerializeField] BMFont mFont = new BMFont(); [HideInInspector][SerializeField] Object mAtlas; [HideInInspector][SerializeField] Object mReplacement; // List of symbols, such as emoticons like ":)", ":(", etc [HideInInspector][SerializeField] List mSymbols = new List(); // Used for dynamic fonts [HideInInspector][SerializeField] Font mDynamicFont; [HideInInspector][SerializeField] int mDynamicFontSize = 16; [HideInInspector][SerializeField] FontStyle mDynamicFontStyle = FontStyle.Normal; // Cached value [System.NonSerialized] UISpriteData mSprite = null; [System.NonSerialized] int mPMA = -1; [System.NonSerialized] int mPacked = -1; /// /// Access to the BMFont class directly. /// public BMFont bmFont { get { var rep = replacement; return (rep != null) ? rep.bmFont : mFont; } set { var rep = replacement; if (rep != null) rep.bmFont = value; else mFont = value; } } /// /// Original width of the font's texture in pixels. /// public int texWidth { get { var rep = replacement; return (rep != null) ? rep.texWidth : ((mFont != null) ? mFont.texWidth : 1); } set { var rep = replacement; if (rep != null) rep.texWidth = value; else if (mFont != null) mFont.texWidth = value; } } /// /// Original height of the font's texture in pixels. /// public int texHeight { get { var rep = replacement; return (rep != null) ? rep.texHeight : ((mFont != null) ? mFont.texHeight : 1); } set { var rep = replacement; if (rep != null) rep.texHeight = value; else if (mFont != null) mFont.texHeight = value; } } /// /// Whether the font has any symbols defined. /// public bool hasSymbols { get { var rep = replacement; return (rep != null) ? rep.hasSymbols : (mSymbols != null && mSymbols.Count != 0); } } /// /// List of symbols within the font. /// public List symbols { get { var rep = replacement; return (rep != null) ? rep.symbols : mSymbols; } set { var rep = replacement; if (rep != null) rep.symbols = value; else mSymbols = value; } } /// /// Atlas used by the font, if any. /// public INGUIAtlas atlas { get { var rep = replacement; if (rep != null) return rep.atlas; return mAtlas as INGUIAtlas; } set { var rep = replacement; if (rep != null) { rep.atlas = value; } else if (mAtlas as INGUIAtlas != value) { mPMA = -1; mAtlas = value as UnityEngine.Object; if (value != null) { mMat = value.spriteMaterial; if (sprite != null) mUVRect = uvRect; } else { mAtlas = null; mMat = null; } MarkAsChanged(); } } } /// /// Convenience method that returns the chosen sprite inside the atlas. /// public UISpriteData GetSprite (string spriteName) { var ia = atlas; if (ia != null) return ia.GetSprite(spriteName); return null; } /// /// Get or set the material used by this font. /// public Material material { get { var rep = replacement; if (rep != null) return rep.material; var ia = mAtlas as INGUIAtlas; if (ia != null) return ia.spriteMaterial; if (mMat != null) { if (mDynamicFont != null && mMat != mDynamicFont.material) { mMat.mainTexture = mDynamicFont.material.mainTexture; } return mMat; } if (mDynamicFont != null) { return mDynamicFont.material; } return null; } set { var rep = replacement; if (rep != null) { rep.material = value; } else if (mMat != value) { mPMA = -1; mMat = value; MarkAsChanged(); } } } /// /// Whether the font is using a premultiplied alpha material. /// [System.Obsolete("Use premultipliedAlphaShader instead")] public bool premultipliedAlpha { get { return premultipliedAlphaShader; } } /// /// Whether the font is using a premultiplied alpha material. /// public bool premultipliedAlphaShader { get { var rep = replacement; if (rep != null) return rep.premultipliedAlphaShader; var ia = mAtlas as INGUIAtlas; if (ia != null) return ia.premultipliedAlpha; if (mPMA == -1) { Material mat = material; mPMA = (mat != null && mat.shader != null && mat.shader.name.Contains("Premultiplied")) ? 1 : 0; } return (mPMA == 1); } } /// /// Whether the font is a packed font. /// public bool packedFontShader { get { var rep = replacement; if (rep != null) return rep.packedFontShader; if (mAtlas != null) return false; if (mPacked == -1) { Material mat = material; mPacked = (mat != null && mat.shader != null && mat.shader.name.Contains("Packed")) ? 1 : 0; } return (mPacked == 1); } } /// /// Convenience function that returns the texture used by the font. /// public Texture2D texture { get { var rep = replacement; if (rep != null) return rep.texture; Material mat = material; return (mat != null) ? mat.mainTexture as Texture2D : null; } } /// /// Offset and scale applied to all UV coordinates. /// public Rect uvRect { get { var rep = replacement; if (rep != null) return rep.uvRect; return (mAtlas != null && sprite != null) ? mUVRect : new Rect(0f, 0f, 1f, 1f); } set { var rep = replacement; if (rep != null) { rep.uvRect = value; } else if (sprite == null && mUVRect != value) { mUVRect = value; MarkAsChanged(); } } } /// /// Sprite used by the font, if any. /// public string spriteName { get { var rep = replacement; return (rep != null) ? rep.spriteName : mFont.spriteName; } set { var rep = replacement; if (rep != null) { rep.spriteName = value; } else if (mFont.spriteName != value) { mFont.spriteName = value; MarkAsChanged(); } } } /// /// Whether this is a valid font. /// public bool isValid { get { return mDynamicFont != null || mFont.isValid; } } [System.Obsolete("Use defaultSize instead")] public int size { get { return defaultSize; } set { defaultSize = value; } } /// /// Pixel-perfect size of this font. /// public int defaultSize { get { var rep = replacement; if (rep != null) return rep.defaultSize; if (isDynamic || mFont == null) return mDynamicFontSize; return mFont.charSize; } set { var rep = replacement; if (rep != null) rep.defaultSize = value; else mDynamicFontSize = value; } } /// /// Retrieves the sprite used by the font, if any. /// public UISpriteData sprite { get { var rep = replacement; if (rep != null) return rep.sprite; var ia = mAtlas as INGUIAtlas; if (mSprite == null && ia != null && mFont != null && !string.IsNullOrEmpty(mFont.spriteName)) { mSprite = ia.GetSprite(mFont.spriteName); if (mSprite == null) mSprite = ia.GetSprite(name); if (mSprite == null) mFont.spriteName = null; else UpdateUVRect(); for (int i = 0, imax = mSymbols.Count; i < imax; ++i) symbols[i].MarkAsChanged(); } return mSprite; } } /// /// Setting a replacement atlas value will cause everything using this font to use the replacement font instead. /// Suggested use: set up all your widgets to use a dummy font that points to the real font. Switching that font to /// another one (for example an eastern language one) is then a simple matter of setting this field on your dummy font. /// public INGUIFont replacement { get { if (mReplacement == null) return null; return mReplacement as INGUIFont; } set { INGUIFont rep = value; if (rep == this as INGUIFont) rep = null; if (mReplacement as INGUIFont != rep) { if (rep != null && rep.replacement == this as INGUIFont) rep.replacement = null; if (mReplacement != null) MarkAsChanged(); mReplacement = rep as UnityEngine.Object; if (rep != null) { mPMA = -1; mMat = null; mFont = null; mDynamicFont = null; } MarkAsChanged(); } } } /// /// Checks the replacement references, returning the deepest-most font. /// public INGUIFont finalFont { get { INGUIFont fnt = this; for (int i = 0; i < 10; ++i) { var rep = fnt.replacement; if (rep != null) fnt = rep; } return fnt; } } /// /// Whether the font is dynamic. /// public bool isDynamic { get { var rep = replacement; return (rep != null) ? rep.isDynamic : (mDynamicFont != null); } } /// /// Get or set the dynamic font source. /// public Font dynamicFont { get { var rep = replacement; return (rep != null) ? rep.dynamicFont : mDynamicFont; } set { var rep = replacement; if (rep != null) { rep.dynamicFont = value; } else if (mDynamicFont != value) { if (mDynamicFont != null) material = null; mDynamicFont = value; MarkAsChanged(); } } } /// /// Get or set the dynamic font's style. /// public FontStyle dynamicFontStyle { get { var rep = replacement; return (rep != null) ? rep.dynamicFontStyle : mDynamicFontStyle; } set { var rep = replacement; if (rep != null) { rep.dynamicFontStyle = value; } else if (mDynamicFontStyle != value) { mDynamicFontStyle = value; MarkAsChanged(); } } } /// /// Trim the glyphs, making sure they never go past the trimmed texture bounds. /// void Trim () { Texture tex = null; var ia = mAtlas as INGUIAtlas; if (ia != null) tex = ia.texture; if (tex != null && mSprite != null) { Rect full = NGUIMath.ConvertToPixels(mUVRect, texture.width, texture.height, true); Rect trimmed = new Rect(mSprite.x, mSprite.y, mSprite.width, mSprite.height); int xMin = Mathf.RoundToInt(trimmed.xMin - full.xMin); int yMin = Mathf.RoundToInt(trimmed.yMin - full.yMin); int xMax = Mathf.RoundToInt(trimmed.xMax - full.xMin); int yMax = Mathf.RoundToInt(trimmed.yMax - full.yMin); mFont.Trim(xMin, yMin, xMax, yMax); } } /// /// Helper function that determines whether the font uses the specified one, taking replacements into account. /// public bool References (INGUIFont font) { if (font == null) return false; if (font == this as INGUIFont) return true; var rep = replacement; return (rep != null) ? rep.References(font) : false; } /// /// Refresh all labels that use this font. /// public void MarkAsChanged () { #if UNITY_EDITOR NGUITools.SetDirty(this); #endif var rep = replacement; if (rep != null) rep.MarkAsChanged(); mSprite = null; var labels = NGUITools.FindActive(); for (int i = 0, imax = labels.Length; i < imax; ++i) { var lbl = labels[i]; if (lbl.enabled && NGUITools.GetActive(lbl.gameObject) && NGUITools.CheckIfRelated(this, lbl.bitmapFont as INGUIFont)) { var fnt = lbl.bitmapFont; lbl.bitmapFont = null; lbl.bitmapFont = fnt; } } // Clear all symbols for (int i = 0, imax = symbols.Count; i < imax; ++i) symbols[i].MarkAsChanged(); } /// /// Forcefully update the font's sprite reference. /// public void UpdateUVRect () { if (mAtlas == null) return; Texture tex = null; var ia = mAtlas as INGUIAtlas; if (ia != null) tex = ia.texture; if (tex != null) { mUVRect = new Rect( mSprite.x - mSprite.paddingLeft, mSprite.y - mSprite.paddingTop, mSprite.width + mSprite.paddingLeft + mSprite.paddingRight, mSprite.height + mSprite.paddingTop + mSprite.paddingBottom); mUVRect = NGUIMath.ConvertToTexCoords(mUVRect, tex.width, tex.height); #if UNITY_EDITOR // The font should always use the original texture size if (mFont != null) { float tw = (float)mFont.texWidth / tex.width; float th = (float)mFont.texHeight / tex.height; if (tw != mUVRect.width || th != mUVRect.height) { //Debug.LogWarning("Font sprite size doesn't match the expected font texture size.\n" + // "Did you use the 'inner padding' setting on the Texture Packer? It must remain at '0'.", this); mUVRect.width = tw; mUVRect.height = th; } } #endif // Trimmed sprite? Trim the glyphs if (mSprite.hasPadding) Trim(); } } /// /// Retrieve the specified symbol, optionally creating it if it's missing. /// BMSymbol GetSymbol (string sequence, bool createIfMissing) { var s = symbols; for (int i = 0, imax = s.Count; i < imax; ++i) { BMSymbol sym = s[i]; if (sym.sequence == sequence) return sym; } if (createIfMissing) { BMSymbol sym = new BMSymbol(); sym.sequence = sequence; s.Add(sym); return sym; } return null; } /// /// Retrieve the symbol at the beginning of the specified sequence, if a match is found. /// public BMSymbol MatchSymbol (string text, int offset, int textLength) { var rep = replacement; if (rep != null) return rep.MatchSymbol(text, offset, textLength); // No symbols present int count = mSymbols.Count; if (count == 0) return null; textLength -= offset; // Run through all symbols for (int i = 0; i < count; ++i) { var sym = mSymbols[i]; // If the symbol's length is longer, move on int symbolLength = sym.length; if (symbolLength == 0 || textLength < symbolLength) continue; var match = true; // Match the characters for (int c = 0; c < symbolLength; ++c) { if (text[offset + c] != sym.sequence[c]) { match = false; break; } } // Match found if (match && sym.Validate(atlas)) return sym; } return null; } /// /// Add a new symbol to the font. /// public void AddSymbol (string sequence, string spriteName) { var rep = replacement; if (rep != null) { rep.AddSymbol(sequence, spriteName); return; } BMSymbol symbol = GetSymbol(sequence, true); symbol.spriteName = spriteName; MarkAsChanged(); } /// /// Remove the specified symbol from the font. /// public void RemoveSymbol (string sequence) { var rep = replacement; if (rep != null) { rep.RemoveSymbol(sequence); return; } BMSymbol symbol = GetSymbol(sequence, false); if (symbol != null) symbols.Remove(symbol); MarkAsChanged(); } /// /// Change an existing symbol's sequence to the specified value. /// public void RenameSymbol (string before, string after) { var rep = replacement; if (rep != null) { rep.RenameSymbol(before, after); return; } BMSymbol symbol = GetSymbol(before, false); if (symbol != null) symbol.sequence = after; MarkAsChanged(); } /// /// Whether the specified sprite is being used by the font. /// public bool UsesSprite (string s) { if (!string.IsNullOrEmpty(s)) { if (s.Equals(spriteName)) return true; var symbols = this.symbols; for (int i = 0, imax = symbols.Count; i < imax; ++i) { var sym = symbols[i]; if (s.Equals(sym.spriteName)) return true; } } return false; } }