using System.Collections.Generic; using System.Globalization; namespace AibisDream.MiniGame.Language { /// /// 将玩家可见文字按 Unicode 文本元素拆分。 /// 空白保留在布局中但不生成粒子;随机字符池直接忽略空白。 /// public static class ExpressionTextTokenizer { public readonly struct Layout { public Layout(List visibleElements, List offsets) { VisibleElements = visibleElements; Offsets = offsets; } public IReadOnlyList VisibleElements { get; } public IReadOnlyList Offsets { get; } public int Count => VisibleElements?.Count ?? 0; } public static List GetVisibleElements(string text) { var result = new List(); if (string.IsNullOrEmpty(text)) return result; TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(text); while (enumerator.MoveNext()) { string element = enumerator.GetTextElement(); if (!string.IsNullOrWhiteSpace(element)) result.Add(element); } return result; } public static List BuildCharacterPool(IEnumerable texts) { var result = new List(); if (texts == null) return result; foreach (string text in texts) result.AddRange(GetVisibleElements(text)); return result; } public static Layout BuildLayout( string text, float characterSpacing, float whitespaceSpacingMultiplier) { var visible = new List(); var positions = new List(); if (string.IsNullOrEmpty(text)) return new Layout(visible, positions); float spacing = characterSpacing > 0f ? characterSpacing : 1f; float whitespaceAdvance = spacing * whitespaceSpacingMultiplier; float cursor = 0f; TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(text); while (enumerator.MoveNext()) { string element = enumerator.GetTextElement(); if (string.IsNullOrWhiteSpace(element)) { if (visible.Count > 0) cursor += whitespaceAdvance; continue; } visible.Add(element); positions.Add(cursor); cursor += spacing; } if (positions.Count > 0) { float center = (positions[0] + positions[positions.Count - 1]) * 0.5f; for (int i = 0; i < positions.Count; i++) positions[i] -= center; } return new Layout(visible, positions); } public static int FindVisibleSequence( IReadOnlyList source, IReadOnlyList fragment) { if (source == null || fragment == null || fragment.Count == 0 || fragment.Count > source.Count) return -1; int lastStart = source.Count - fragment.Count; for (int start = 0; start <= lastStart; start++) { bool matches = true; for (int i = 0; i < fragment.Count; i++) { if (!string.Equals(source[start + i], fragment[i], System.StringComparison.Ordinal)) { matches = false; break; } } if (matches) return start; } return -1; } } }