Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs
T

249 lines
7.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// 将玩家可见文字按 Unicode 文本元素拆分。
/// 空白保留在布局中但不生成粒子;随机字符池直接忽略空白。
/// </summary>
public static class ExpressionTextTokenizer
{
public readonly struct UnitSequence
{
public UnitSequence(List<string> units, List<float> gapWeights)
{
Units = units;
GapWeights = gapWeights;
}
public IReadOnlyList<string> Units { get; }
public IReadOnlyList<float> GapWeights { get; }
public int Count => Units?.Count ?? 0;
}
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 List<string> BuildCharacterPool(IEnumerable<string> texts)
{
return BuildUnitPool(texts, ExpressionParticleUnitMode.Grapheme);
}
public static List<string> TokenizeText(
string text,
ExpressionParticleUnitMode mode)
{
return new List<string>(BuildLayoutUnits(text, mode).Units);
}
public static bool TryTokenizePool(
string pool,
ExpressionParticleUnitMode mode,
out List<string> units)
{
units = new List<string>();
if (string.IsNullOrWhiteSpace(pool) ||
pool.Contains("|") ||
pool.Contains(""))
{
return false;
}
units = TokenizeText(pool, mode);
return units.Count > 0;
}
public static List<string> BuildUnitPool(
IEnumerable<string> texts,
ExpressionParticleUnitMode mode)
{
var result = new List<string>();
if (texts == null)
return result;
foreach (string text in texts)
result.AddRange(TokenizeText(text, mode));
return result;
}
public static UnitSequence BuildLayoutUnits(
string text,
ExpressionParticleUnitMode mode)
{
return mode == ExpressionParticleUnitMode.Word
? BuildWordUnits(text)
: BuildGraphemeUnits(text);
}
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;
}
public static int FindUnitSequence(
IReadOnlyList<string> source,
IReadOnlyList<string> fragment)
{
return FindVisibleSequence(source, fragment);
}
private static UnitSequence BuildGraphemeUnits(string text)
{
var units = new List<string>();
var gaps = new List<float>();
if (string.IsNullOrEmpty(text))
return new UnitSequence(units, gaps);
float pendingGap = 0f;
TextElementEnumerator enumerator =
StringInfo.GetTextElementEnumerator(text);
while (enumerator.MoveNext())
{
string element = enumerator.GetTextElement();
if (string.IsNullOrWhiteSpace(element))
{
if (units.Count > 0)
pendingGap += 1f;
continue;
}
units.Add(element);
gaps.Add(units.Count == 1 ? 0f : pendingGap);
pendingGap = 0f;
}
return new UnitSequence(units, gaps);
}
private static UnitSequence BuildWordUnits(string text)
{
var units = new List<string>();
var gaps = new List<float>();
if (string.IsNullOrEmpty(text))
return new UnitSequence(units, gaps);
var word = new StringBuilder();
bool sawSeparatorAfterUnit = false;
foreach (char value in text)
{
if (char.IsWhiteSpace(value))
{
FlushWord(word, units, gaps, sawSeparatorAfterUnit);
if (units.Count > 0)
sawSeparatorAfterUnit = true;
continue;
}
word.Append(value);
}
FlushWord(word, units, gaps, sawSeparatorAfterUnit);
return new UnitSequence(units, gaps);
}
private static void FlushWord(
StringBuilder word,
List<string> units,
List<float> gaps,
bool hasLeadingSeparator)
{
if (word.Length == 0)
return;
units.Add(word.ToString());
gaps.Add(units.Count == 1 ? 0f : hasLeadingSeparator ? 1f : 0f);
word.Clear();
}
}
}