EmojiFont.Build leaves TMP's emoji fallback list empty. EmojiCheck fails when that list is non-empty, when the global fallback lacks Emoji.asset, or when a glyph is served by a font other than Emoji.asset or the body font; reads the Pirates product names from the shipped catalog; renders the emoji-test.txt corpus and reports what Twemoji lacks; counts only used atlas pages (TMP doubles the array capacity, so trailing slots are null by design). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
77 lines
3.4 KiB
C#
77 lines
3.4 KiB
C#
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<Font>(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<TMP_FontAsset>(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_Asset>();
|
|
TMP_Settings.fallbackFontAssets = new List<TMP_FontAsset> { 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;
|
|
}
|
|
}
|