Workflow for TextMesh Pro sprite glyphs?

I have a folder with sprites which I want to use as glyphs in TextMesh Pro textboxes. From time to time, new sprites are added to that folder. I want to organize a workflow in such way that every time I add a sprite called xxx, I could use it as in TextMesh.

I tried to create a Sprite Atlas from folder, but I cannot find any way to export the Sprite Atlas into a texture. Currently, I need to place all new sprites manually onto a big texture and re-create the entire TextMesh Sprite Asset file. How can I improve my workflow? (1 - automatically convert an atlas into a texture [Sprite Asset is not created if an atlas is selected, it wants specifically a texture], 2 - refresh the Sprite Asset file so it reflects changes in an underlying texture)

Finally came out with this solution.

using System.IO;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore;

namespace Assets.Scripts.Utilities
{
    public class Glypher : MonoBehaviour
    {
        public int GlyphWidth = 32;
        public int GlyphHeight = 32;
        public int GlyphsInRow = 8;

        public TMP_SpriteAsset Asset;

        public void RepackGlyphs()
        {
            Debug.Log("Repacking glyphs...");            
            var path = Path.Combine(Directory.GetCurrentDirectory(), @"Assets\Resources\Glyphs");
            var outputPath = Path.Combine(Directory.GetCurrentDirectory(), @"Assets\Resources\GlyphBuild	exture.png");
            var files = Directory.EnumerateFiles(path, "*.png").ToList();
            var rowNumber = (files.Count - 1) / GlyphsInRow + 1;
            var completeWidth = GlyphsInRow * GlyphWidth;
            var completeHeight = GlyphHeight * rowNumber;
            var image = new Texture2D(completeWidth, completeHeight, TextureFormat.BGRA32, false);
            var yIndex = rowNumber - 1;
            var xIndex = 0;
            Asset?.spriteGlyphTable.Clear();
            Asset?.spriteCharacterTable.Clear();
            uint index = 0;
            foreach (var glyph in files)
            {
                var texture = new Texture2D(GlyphWidth, GlyphHeight);
                using (var file = new FileStream(glyph, FileMode.Open))
                {
                    using (var memstream = new MemoryStream())
                    {
                        file.CopyTo(memstream);
                        var tbytes = memstream.ToArray();
                        texture.LoadImage(tbytes);
                    }
                }
                var pixels = texture.GetPixels32();
                image.SetPixels32(xIndex * GlyphWidth, yIndex * GlyphHeight, GlyphWidth, GlyphHeight, pixels);

                var spriteGlyph = new TMP_SpriteGlyph
                {
                    glyphRect = new GlyphRect { width = GlyphWidth, height = GlyphHeight - 1, x = xIndex * GlyphWidth, y = yIndex * GlyphHeight },
                    metrics = new GlyphMetrics { width = GlyphWidth, height = GlyphHeight - 1, horizontalBearingY = 28, horizontalBearingX = 0, horizontalAdvance = 32 },
                    index = index
                };
                Asset?.spriteGlyphTable.Add(spriteGlyph);

                var spriteCharacter = new TMP_SpriteCharacter(0, spriteGlyph)
                {
                    name = Path.GetFileNameWithoutExtension(glyph),
                    glyphIndex = index++
                };
                Asset?.spriteCharacterTable.Add(spriteCharacter);

                xIndex++;
                if (xIndex >= GlyphsInRow)
                {
                    xIndex = 0;
                    yIndex--;
                }
            }

            var bytes = image.EncodeToPNG();
            using (var stream = new FileStream(outputPath, FileMode.Create))
            {
                stream.Write(bytes, 0, bytes.Length);
            }

            if (Asset == null)
                Debug.LogWarning("Right-click the created texture, Create > TextMeshPro > Sprite Asset and specify the resulting asset in Glypher and in TMP Settings, then repack glyphs again");
            else
                Debug.Log("Repacking glyphs done!");
        }
    }
}

Then, I have a custom editor to run this script from a button click:

using UnityEditor;
using UnityEngine;

namespace Assets.Scripts.Utilities
{
    [CustomEditor(typeof(Glypher))]
    public class GlypherEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            DrawDefaultInspector();

            var glypher = (Glypher)target;
            if (GUILayout.Button("Repack glyphs"))
            {
                glypher.RepackGlyphs();
            }
        }
    }
}

Finally, I create the sprite asset manually - but only for the first time. Then I specify this asset in Glypher component properties (I have a separate GameObject for devtime utilities, and this GameObject can be safely removed on game startup) AND in TMP Settings (possibly there’s a way to do that programmatically also, but since it’s a one-time action, I didn’t investigate this possibility). Every time new pngs are added to Assets\Resources\Glyphs folder, I then click Repack Glyphs on Glypher inspector, and my glyph table gets updated. Names of the glyphs are taken from file names. A few notes:

  1. This code relies completely on
    assumption that all your glyphs are
    the same size. If it’s not 32*32,
    you have to specify it in
    GlyphWidth&GlyphHeight properties of
    the Glypher.
  2. Possibly a SpriteAtlas can be used
    to pack textures more efficiently,
    but again - I didn’t run into
    limitations yet, so didn’t check
    this possibility.
  3. For the first run you may leave texture property unset - it will create the texture and instruct you of further actions in the log.

Hope it’s helpful for anyone else.