using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine.UI; using UnityEngine; using System.Collections; [AddComponentMenu("UI/Effects/Letter Spacing", 15)] public class LetterSpacing : BaseMeshEffect { private const string SupportedTagRegexPattersn = @"|||||||||"; [SerializeField] private bool useRichText; [SerializeField] private float m_spacing = 0f; protected LetterSpacing() { } #if UNITY_EDITOR protected override void OnValidate() { spacing = m_spacing; base.OnValidate(); } #endif public float spacing { get { return m_spacing; } set { if (m_spacing == value) return; m_spacing = value; if (graphic != null) graphic.SetVerticesDirty(); } } public override void ModifyMesh(VertexHelper vh) { if (!this.IsActive()) return; List list = new List(); vh.GetUIVertexStream(list); ModifyVertices(list); vh.Clear(); vh.AddUIVertexTriangleStream(list); } public void ModifyVertices(List verts) { if (!IsActive()) return; Text text = GetComponent(); string str = text.text; IList lineInfos = text.cachedTextGenerator.lines; for (int i = lineInfos.Count - 1; i > 0; i--) { str = str.Insert(lineInfos[i].startCharIdx, "\n"); str = str.Remove(lineInfos[i].startCharIdx - 1, 1); } string[] lines = str.Split('\n'); if (text == null) { Debug.LogWarning("LetterSpacing: Missing Text component"); return; } Vector3 pos; float letterOffset = spacing * (float)text.fontSize / 100f; float alignmentFactor = 0; int glyphIdx = 0; // 字符索引从文本开始,包括RichText标记和换行符 bool isRichText = useRichText && text.supportRichText; IEnumerator matchedTagCollection = null; // 当使用RichText时,它将收集所有标签 (index, length, value) Match currentMatchedTag = null; switch (text.alignment) { case TextAnchor.LowerLeft: case TextAnchor.MiddleLeft: case TextAnchor.UpperLeft: alignmentFactor = 0f; break; case TextAnchor.LowerCenter: case TextAnchor.MiddleCenter: case TextAnchor.UpperCenter: alignmentFactor = 0.5f; break; case TextAnchor.LowerRight: case TextAnchor.MiddleRight: case TextAnchor.UpperRight: alignmentFactor = 1f; break; } for (int lineIdx = 0; lineIdx < lines.Length; lineIdx++) { string line = lines[lineIdx]; int lineLength = line.Length; if (isRichText) { matchedTagCollection = GetRegexMatchedTagCollection(line, out lineLength); currentMatchedTag = null; if (matchedTagCollection.MoveNext()) { currentMatchedTag = (Match)matchedTagCollection.Current; } } float lineOffset = (lineLength - 1) * letterOffset * alignmentFactor; for (int charIdx = 0, actualCharIndex = 0; charIdx < line.Length; charIdx++, actualCharIndex++) { if (isRichText) { if (currentMatchedTag != null && currentMatchedTag.Index == charIdx) { // skip matched RichText tag charIdx += currentMatchedTag.Length - 1; // -1 because next iteration will increment charIdx actualCharIndex--; // tag is not an actual character, cancel counter increment on this iteration glyphIdx += currentMatchedTag.Length; // glyph index is not incremented in for loop so skip entire length // prepare next tag to detect currentMatchedTag = null; if (matchedTagCollection.MoveNext()) { currentMatchedTag = (Match)matchedTagCollection.Current; } continue; } } int idx1 = glyphIdx * 6 + 0; int idx2 = glyphIdx * 6 + 1; int idx3 = glyphIdx * 6 + 2; int idx4 = glyphIdx * 6 + 3; int idx5 = glyphIdx * 6 + 4; int idx6 = glyphIdx * 6 + 5; // Check for truncated text (doesn't generate verts for all characters) if (idx6 > verts.Count - 1) return; UIVertex vert1 = verts[idx1]; UIVertex vert2 = verts[idx2]; UIVertex vert3 = verts[idx3]; UIVertex vert4 = verts[idx4]; UIVertex vert5 = verts[idx5]; UIVertex vert6 = verts[idx6]; pos = Vector3.right * (letterOffset * actualCharIndex - lineOffset); vert1.position += pos; vert2.position += pos; vert3.position += pos; vert4.position += pos; vert5.position += pos; vert6.position += pos; verts[idx1] = vert1; verts[idx2] = vert2; verts[idx3] = vert3; verts[idx4] = vert4; verts[idx5] = vert5; verts[idx6] = vert6; glyphIdx++; } // Offset for carriage return character that still generates verts glyphIdx++; } } private IEnumerator GetRegexMatchedTagCollection(string line, out int lineLengthWithoutTags) { MatchCollection matchedTagCollection = Regex.Matches(line, SupportedTagRegexPattersn); lineLengthWithoutTags = 0; int tagsLength = 0; if (matchedTagCollection.Count > 0) { foreach (Match matchedTag in matchedTagCollection) { tagsLength += matchedTag.Length; } } lineLengthWithoutTags = line.Length - tagsLength; return matchedTagCollection.GetEnumerator(); } }