using System.Collections.Generic; using TMPro; using UnityEditor; using UnityEngine; using UnityEngine.TextCore.LowLevel; // The one writer of the emoji font asset and of TMP Settings' emoji routing. The font file is the // output of tools/emoji/fetch_emoji_font.py; the asset is a dynamic COLOR font asset whose atlas pages // are cache, filled on first use at runtime and cleared here. Every TMP text reaches emoji through ONE // route, TMP Settings' global fallback list; the emoji fallback list stays empty, and no component names // an emoji font. (Measured: the global list alone resolves every single code point; the emoji list alone // leaves 200 text-presentation emoji unresolved. A second route could only disagree with the first.) public static class EmojiFont { public const string FontPath = "Assets/Fonts/Emoji/Twemoji.ttf"; public const string AssetPath = "Assets/Fonts/Emoji/Emoji.asset"; // Colour outlines (COLRv0) are rasterised at one size; 109 is the strike size a CBDT set needs. const int SamplingPointSize = 64; const int AtlasPadding = 4; const int AtlasSize = 2048; // Menu entry for interactive use; -executeMethod EmojiFont.Build for batchmode, exit 1 on failure. [MenuItem("Aetheria/Text/Build Emoji Font")] public static void Build() { var ok = TryBuild(out var message); if (ok) Debug.Log(message); else Debug.LogError(message); if (Application.isBatchMode) EditorApplication.Exit(ok ? 0 : 1); } static bool TryBuild(out string message) { var font = AssetDatabase.LoadAssetAtPath(FontPath); if (font == null) { message = $"EmojiFont: {FontPath} is missing; run tools/emoji/fetch_emoji_font.py."; return false; } // An existing asset keeps its GUID, so a second Build changes nothing the settings point at. var asset = AssetDatabase.LoadAssetAtPath(AssetPath); if (asset == null) { asset = TMP_FontAsset.CreateFontAsset(font, SamplingPointSize, AtlasPadding, GlyphRenderMode.COLOR, AtlasSize, AtlasSize, AtlasPopulationMode.Dynamic, enableMultiAtlasSupport: true); if (asset == null) { message = $"EmojiFont: TMP could not create a font asset from {FontPath}."; return false; } asset.name = "Emoji"; AssetDatabase.CreateAsset(asset, AssetPath); foreach (var page in asset.atlasTextures) { page.name = "Emoji Atlas"; AssetDatabase.AddObjectToAsset(page, asset); } asset.material.name = "Emoji Atlas Material"; AssetDatabase.AddObjectToAsset(asset.material, asset); } // The atlas pages are cache: Build leaves them empty, the same state EmojiCheck restores. asset.ClearFontAssetData(true); // One route: the global fallback. It also serves text-presentation emoji (U+263A without FE0F). TMP_Settings.emojiFallbackTextAssets = new List(); TMP_Settings.fallbackFontAssets = new List { asset }; TMP_Settings.defaultSpriteAsset = null; EditorUtility.SetDirty(asset); EditorUtility.SetDirty(TMP_Settings.instance); AssetDatabase.SaveAssets(); message = "EmojiFont: built " + AssetPath + " and made it TMP Settings' only emoji route (the global fallback)."; return true; } }