180 lines
5.9 KiB
C#
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
|
|
}
|
|
}
|