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 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 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(); 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 } }