[ATG / UI Toolkit] Deprecation of characterLookupTable in TextCore: read-only access to glyph metrics

Hi,
Prior to Unity 6000.5,
TextCore.FontAsset.characterLookupTable allowed direct access to a glyph’s metrics (GlyphMetrics: width, height, bearings, advance). In 6000.5 this path is marked as deprecated, presumably tied to Advanced Text Generator (ATG) not supporting glyph metric modification.

I understand why mutation should be restricted, but that’s a different concern from read-only access. Read-only glyph metrics at the TextCore level are essential for building custom UI Toolkit controls that need cell-based layout — terminals, monospaced character grids, tile-based editors — where exact advance/bearing values per glyph are required to position content with pixel precision, without routing through the full ATG generation pipeline.

Deprecating the only public path to this data, without providing a read-only replacement, forces users toward reflection over internal fields or maintaining a parallel metrics cache — both fragile across Unity versions.

Has anyone on the team looked at this, or is a replacement planned? Any pointers would be appreciated.

Thanks in advance,

The underlying table use a manual approaches for parsing the font data that uses a lot of memory, are not needed for ATG as harfbuzz and FreeType parse the data of the font directly, and they have been proven to contains surprise over the years. The API being obsolete reflect that we do not recommend adding dependency to that api, but you are right that we possibly don’t offer alternatives for all workflows.

Can you share a bit more about how you are using the information? Reading the information of the font pre-shaping could lead to surprise as it’s not the final size on screen and for fonts in general it is usually better to rely on the final glyph size, but monospaces fonts tend to have less ligatures and substitution so it’s probably work well with an ascii subset. For example, that table would not take into account the different tags (font size, spacing) that could change the width of a glyph.

Hi Simon,

Thanks for the detailed explanation, that makes sense.

My use case: I’m building a monospaced text grid (terminal-style UI), where I need to know the fixed cell size (width/height in pixels) that a character will occupy on screen, before actually laying out the text, so I can size/position the grid cells accordingly.

Here’s roughly what I’m doing today:

/// <summary>
/// Computes the terminal cell size and the device-space font size that must be passed to
/// <c>MeshGenerationContext.DrawText</c> so a drawn glyph's advance lands exactly on the cell.
/// </summary>
/// <remarks>
/// <c>UIR.MeshGenerator.DrawText</c> rounds the font size it receives to the nearest device
/// pixel (<c>fontSize = (int)Mathf.Round(fontSize)</c>, verified in
/// <c>UnityEngine.UIElementsModule.dll</c> 6000.3.21f1), and the mesh it produces is later
/// divided by <c>scaledPixelsPerPoint</c> back into panel points. So the real glyph advance,
/// in panel points, is <c>horizontalAdvance * deviceFontSize / (faceInfo.pointSize *
/// pixelsPerPoint)</c> - not <c>horizontalAdvance * fontSize / faceInfo.pointSize</c> as a
/// naive calculation would give. Whenever <c>fontSize * pixelsPerPoint</c> isn't an integer
/// (i.e. almost always, since the panel is set to ScaleWithScreenSize), that difference
/// compounds across columns and text runs drift off the cell grid. Deriving both the cell
/// size and <c>deviceFontSize</c> from the same rounded value keeps every glyph exactly on
/// the grid regardless of <c>scaledPixelsPerPoint</c>.
/// </remarks>
private void CalculateCellMetrics(FontAsset font, char character, float fontSize, float pixelsPerPoint,
            out Vector2 cellSize, out float deviceFontSize)
        {
            if (font == null || !font.HasCharacter(character, false, true))
            {
                cellSize = Vector2.zero;
                deviceFontSize = 0f;
                return;
            }

            var glyph = font.characterLookupTable[character].glyph;
            var faceInfo = font.faceInfo;

            deviceFontSize = Mathf.Round(fontSize * pixelsPerPoint);

            float scale = deviceFontSize / (faceInfo.pointSize * pixelsPerPoint);

            float advance = glyph.metrics.horizontalAdvance * scale;
            float height = faceInfo.lineHeight * scale;

            cellSize = new Vector2(advance, height);
        }

Where pixelsPerPoint is scaledPixelsPerPoint and character is a fixed reference glyph (e.g. ‘A’ or ‘M’), since the font is monospaced and all glyphs should share the same advance width.

I get that this reads pre-shaping metrics and doesn’t account for things like per-tag size/spacing overrides — in my case that’s fine since I’m not applying rich text tags to this text, and I’m only using it for grid sizing, not final glyph placement.

Given that characterLookupTable is being deprecated, is there a recommended replacement API to get this same advance/lineHeight info for a single reference character in ATG, without going through the full shaping pipeline? Or is reading pre-shaping metrics for this specific “fixed grid cell size” use case still going to be supported some other way?

Thanks

I don’t think there is a replacement, but the old api should continue working until we have something else. It just causes some really high memory impact when attempting to load the CJK fonts from what I understood, so the recommendation to not use that without purpose still stand.

We think we can have some more lightweight api that don’t force loading everything into a managed table. Something like :

public partial class FontAsset
{
    public bool TryGetGlyphIndex(uint unicode, out uint glyphIndex);
    public bool TryGetGlyphMetrics(uint glyphIndex, out GlyphMetrics metrics);
}

It would expose correctly the font data but will also not work when attempting to fetch the glyph for substituted/ligated characters.

I’m wondering if we could add more primitive font information too. It seem that it would be more efficient and relevant for monospace fonts to read directly:
xAvgCharWidth or advanceWidthMax and isFixedPitch

We also have a few interrogations regarding bitmap font support : since the value will be rounded internally in harfbuzz/freetype before being used, just aligning differently the size manually might cause a different result compared to the underlying library. I think your example could lead us to add pixelsPerPoint or other optional parameter to get the glyph size in a more specific context.

A lot of parts are in motion with the transition from TextCore to ATG, so there is no guarantee of having a new api backported to 6.5, but that workflow should be covered before we remove the characterLookupTable.

That would be great, and really useful.

Actually, I’ve done a workaround using FontEngine to circumvent the deprecation. Your proposed API sounds like the perfect solution, though.

Here’s my workaround to obtain glyph metrics using FontEngine. I know it’s not ideal, but currently it’s the only way I’ve found to get this data:

private float GetFontAdvance(FontAsset fontAsset, char character)
        {
            var pointSize = fontAsset.faceInfo.pointSize;
            var font = fontAsset.sourceFontFile;

            var err = FontEngine.InitializeFontEngine();
            if (err != FontEngineError.Success)
            {
                throw new Exception($"Failed to initialize font engine: {err}");
            }

            err = FontEngine.LoadFontFace(font, Mathf.RoundToInt(pointSize));
            if (err != FontEngineError.Success)
            {
                throw new Exception($"Failed to load font face: {err}");
            }

            var unicodeValue = (uint)character;
            if (!FontEngine.TryGetGlyphWithUnicodeValue(unicodeValue, GlyphLoadFlags.LOAD_COMPUTE_METRICS,
                    out var glyph))
            {
                throw new Exception($"Failed to get glyph for character '{character}' (U+{unicodeValue:X4})");
            }

            FontEngine.UnloadFontFace();

            return glyph.metrics.horizontalAdvance;
}

Also, exposing the font’s unitsPerEm would be very useful, since it would let us calculate raw font advance:

advance = rawAdvance * (pointSize / unitsPerEm).

Then calculate with the same calculation showed in my last reply using pixelsPerPoint, etc.

private void CalculateCellMetrics(FontAsset font, char character, float fontSize, float pixelsPerPoint,
            out Vector2 cellSize, out float deviceFontSize)
        {
            if (font == null || !font.HasCharacter(character, false, true))
            {
                cellSize = Vector2.zero;
                deviceFontSize = 0f;
                return;
            }

            var fontAdvance = GetFontAdvance(font,character);
            var faceInfo = font.faceInfo;

            deviceFontSize = Mathf.Round(fontSize * pixelsPerPoint);

            float scale = deviceFontSize / (faceInfo.pointSize * pixelsPerPoint);

            float advance = fontAdvance * scale;
            float height = faceInfo.lineHeight * scale;

            cellSize = new Vector2(advance, height);
        }

Thanks

The unitsPerEm is present on FaceInfo, but it’s currently internal. We’ll make it public when we release the new API.

Are there any plans to support this new API in future Unity releases?

Hey! Yes, it actually landed in 6000.7.0b1 and should be part of the 6000.6.2f1 release.

Please keep us posted on how it goes with the APIs.