Files

180 lines
5.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
namespace AibisDream.Framework
{
public static class TypographyService
{
private static readonly HashSet<LocalizedTypography> Bindings = new();
private static ITypographyProfileLoader loader;
private static int switchVersion;
private static string currentProfileKey;
private static string latestRequestedProfileKey;
private static TypographyProfile currentProfile;
public static TypographyProfile CurrentProfile => currentProfile;
public static bool IsReady => currentProfile != null;
public static string CurrentProfileKey => currentProfileKey;
public static void ConfigureLoader(ITypographyProfileLoader profileLoader)
{
loader = profileLoader ?? throw new ArgumentNullException(nameof(profileLoader));
}
public static bool IsActiveFor(LocaleKind localeKind) =>
currentProfile != null &&
string.Equals(
currentProfileKey,
TypographyLocaleMapping.GetProfileKey(localeKind),
StringComparison.Ordinal);
public static TypographyStyle Resolve(TypographyRole role) => currentProfile?.Resolve(role);
public static bool Apply(TMP_Text text, TypographyRole role, bool applyMetrics = true)
{
if (text == null)
return false;
TypographyStyle style = Resolve(role);
if (style == null || !style.IsUsable)
return false;
ApplyStyle(text, style, applyMetrics, text.fontSize, text.lineSpacing, text.characterSpacing);
return true;
}
public static void Register(LocalizedTypography binding)
{
if (binding == null)
return;
Bindings.Add(binding);
if (currentProfile != null)
binding.ApplyProfile(currentProfile);
}
public static void Unregister(LocalizedTypography binding)
{
if (binding != null)
Bindings.Remove(binding);
}
public static async Task<bool> LoadAndActivateAsync(LocaleKind localeKind)
{
int requestVersion = ++switchVersion;
string profileKey = TypographyLocaleMapping.GetProfileKey(localeKind);
latestRequestedProfileKey = profileKey;
if (IsActiveFor(localeKind))
return true;
if (loader == null)
{
Debug.LogError("[TypographyService] 尚未配置 ITypographyProfileLoader。");
return false;
}
TypographyProfile loadedProfile;
try
{
loadedProfile = await loader.LoadAsync(profileKey);
}
catch (Exception exception)
{
Debug.LogError($"[TypographyService] 加载字体 Profile 失败:{profileKey}\n{exception}");
return false;
}
if (requestVersion != switchVersion)
{
if (!string.Equals(profileKey, currentProfileKey, StringComparison.Ordinal) &&
!string.Equals(profileKey, latestRequestedProfileKey, StringComparison.Ordinal))
{
loader.Release(profileKey);
}
return false;
}
if (loadedProfile == null)
{
Debug.LogError($"[TypographyService] 字体 Profile 不存在或加载失败:{profileKey}");
return false;
}
string previousKey = currentProfileKey;
loadedProfile.ApplyFallbacks();
currentProfile = loadedProfile;
currentProfileKey = profileKey;
RefreshBindings();
Canvas.ForceUpdateCanvases();
await Task.Yield();
if (!string.IsNullOrEmpty(previousKey) &&
!string.Equals(previousKey, profileKey, StringComparison.Ordinal))
{
loader.Release(previousKey);
}
return true;
}
internal static void ApplyStyle(
TMP_Text text,
TypographyStyle style,
bool applyMetrics,
float baseFontSize,
float baseLineSpacing,
float baseCharacterSpacing)
{
text.font = style.Font;
text.fontSharedMaterial = style.MaterialPreset != null
? style.MaterialPreset
: style.Font.material;
if (applyMetrics)
{
text.fontSize = baseFontSize * style.FontSizeMultiplier;
text.lineSpacing = baseLineSpacing + style.LineSpacingOffset;
text.characterSpacing = baseCharacterSpacing + style.CharacterSpacingOffset;
}
text.SetAllDirty();
if (text.gameObject.activeInHierarchy)
text.ForceMeshUpdate();
}
private static void RefreshBindings()
{
if (Bindings.Count == 0)
return;
var stale = new List<LocalizedTypography>();
foreach (LocalizedTypography binding in Bindings)
{
if (binding == null)
stale.Add(binding);
else
binding.ApplyProfile(currentProfile);
}
foreach (LocalizedTypography binding in stale)
Bindings.Remove(binding);
}
#if UNITY_EDITOR
public static void ResetForTests()
{
if (loader != null && !string.IsNullOrEmpty(currentProfileKey))
loader.Release(currentProfileKey);
loader = null;
currentProfile = null;
currentProfileKey = null;
latestRequestedProfileKey = null;
switchVersion++;
Bindings.Clear();
}
#endif
}
}