wpftexttext-renderingglyphdrawingcontext

Handling DrawGlyphRun missing characters in WPF


I am using drawingContext.DrawGlyphRun in my scenario, as I need the best performance I can get as I am rendering a large amount of text at lots of different positions on screen.

However, I have come into a slight snag. The font I am using is missing some unicode characters for codes such as ✓, ∞

It looks like drawingContext.DrawText has a very handy feature where a fallback font is used where characters are missing, such that you still at least get something rendered.

So now I am wondering how I could achieve something similar without losing all the performance gains I got from switching to DrawGlyphRun

Seems like I would need to break up the call in multiple parts

"My string with ∞ unsupported character"

[My string with ][∞][ unsupported character]

So 3 calls to drawingContext.DrawGlyphRun, with the 2nd one using a different font :(

And then I would also need to compute the position offsets so it all aligns correctly.

Other than changing the font (something I can't really do) am I missing an obvious easy way of doing this?


Solution

  • I managed to get this working by using the Global User Interface fontfamily. Which a collection of typefaces that are used as fallbacks when a character code is not found in the target font.

    I decided to cache the typeface for these missing character codes.

    private readonly Dictionary<int, GlyphTypeface?> _fallbackGlyphMapping = [];
    
    private bool TryGetFallbackGlyphTypeface(int code, [NotNullWhen(true)] out GlyphTypeface? glyphTypeface)
    {
        if (!_fallbackGlyphMapping.TryGetValue(code, out glyphTypeface))
        {
            _globalFontFamily ??= new FontFamily("Global User Interface");
    
            foreach (var map in _globalFontFamily.FamilyMaps)
            {
                var typeface = new Typeface(new FontFamily(map.Target), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
                if (typeface.TryGetGlyphTypeface(out GlyphTypeface? tempGlyphTypeface))
                {
                    if (tempGlyphTypeface.CharacterToGlyphMap.TryGetValue(code, out var index))
                    {
                        _fallbackGlyphMapping[code] = tempGlyphTypeface;
                        glyphTypeface = tempGlyphTypeface;
                        return true;
                    }
                }
            }
    
            // couldn't locate
            _fallbackGlyphMapping[code] = null;
            return false;
        }
    
        return glyphTypeface != null;
    }