109 lines
3.6 KiB
C#
109 lines
3.6 KiB
C#
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
namespace AibisDream.MiniGame.Language
|
|
{
|
|
/// <summary>
|
|
/// 将玩家可见文字按 Unicode 文本元素拆分。空白保留在布局中,但不生成粒子。
|
|
/// 随机粒子字符池不使用本类,维持现有行为。
|
|
/// </summary>
|
|
public static class ExpressionTextTokenizer
|
|
{
|
|
public readonly struct Layout
|
|
{
|
|
public Layout(List<string> visibleElements, List<float> offsets)
|
|
{
|
|
VisibleElements = visibleElements;
|
|
Offsets = offsets;
|
|
}
|
|
|
|
public IReadOnlyList<string> VisibleElements { get; }
|
|
public IReadOnlyList<float> Offsets { get; }
|
|
public int Count => VisibleElements?.Count ?? 0;
|
|
}
|
|
|
|
public static List<string> GetVisibleElements(string text)
|
|
{
|
|
var result = new List<string>();
|
|
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 Layout BuildLayout(
|
|
string text,
|
|
float characterSpacing,
|
|
float whitespaceSpacingMultiplier)
|
|
{
|
|
var visible = new List<string>();
|
|
var positions = new List<float>();
|
|
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<string> source,
|
|
IReadOnlyList<string> 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;
|
|
}
|
|
}
|
|
}
|