Skip to content

Expose public API for drawing with font files #20088

@imagitama

Description

@imagitama

Is your feature request related to a problem? Please describe.

Story

As an Avalonia user (who mixes canvas drawing and axaml) I want to draw some text on a canvas using a font file that I load at runtime because my Avalonia app allows my end users to provide their own font files.

Issue

When I draw some FormattedText I cannot load a font file from the filesystem:

using var stream = File.OpenRead("path/to/font.ttf");

var myFont = new Typeface(stream);

var text = new FormattedText(
   "Hello world!",
    CultureInfo.InvariantCulture,
    FlowDirection.LeftToRight,
    myFont,
    64,
    Colors.White
);
                                    
ctx.DrawText(text, new Point(0, 0));

I can do it with reflection though:

static class RuntimeGlyphFactory
{
    private static readonly ConstructorInfo GlyphCtor;

    static RuntimeGlyphFactory()
    {
        var skiaAssembly = AppDomain.CurrentDomain
            .GetAssemblies()
            .FirstOrDefault(a => a.GetName().Name == "Avalonia.Skia")
            ?? throw new Exception("Avalonia.Skia assembly not loaded");

        var glyphType = skiaAssembly.GetType("Avalonia.Skia.GlyphTypefaceImpl", throwOnError: true) ?? throw new Exception("Type not found");

        GlyphCtor = glyphType.GetConstructor(
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
            binder: null,
            types: new[] { typeof(SKTypeface), typeof(FontSimulations) },
            modifiers: null
        ) ?? throw new Exception("Could not find GlyphTypefaceImpl(SKTypeface, FontSimulations) constructor.");
    }

    public static IGlyphTypeface Create(SKTypeface skTypeface)
    {
        return (IGlyphTypeface)GlyphCtor.Invoke([skTypeface, FontSimulations.None]);
    }
}

public class CustomFontCollection : FontCollectionBase
{
    public override Uri Key => new Uri("fonts:Custom");
     
    public override bool TryGetGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
    {
        glyphTypeface = null;

        if (_glyphTypefacesByFamilyName.TryGetValue(familyName, out IGlyphTypeface? value))
        {
            glyphTypeface = value;
            return true;
        }

        return false;
    }
    
    public string RegisterFontFile(string absolutePath)
    {
        var familyName = Path.GetFileNameWithoutExtension(absolutePath);
        var uri = $"fonts:Custom#{familyName}";

        var family = new FontFamily(uri);
        _families.Add(family);

        using var stream = File.OpenRead(absolutePath);
        var skTypeface = SKTypeface.FromStream(stream);

        var actualFamilyName = skTypeface.FamilyName;

        var glyphTypeface = RuntimeGlyphFactory.Create(skTypeface);

        _glyphTypefacesByFamilyName[familyName] = glyphTypeface;

        return familyName;
    }
}
    
FontManager.Current.AddFontCollection(myCollection);

myCollection.RegisterFontFile("path/to/font.ttf");

ctx.DrawText(text, ...);

Describe the solution you'd like

Expose a public API to do this so I don't have to rely on fragile/poor performing reflection.

Describe alternatives you've considered

Using Reflection.

Creating an SVG and using SKTypeface. But creating a bitmap every frame is very bad for performance.

Additional context

Related: #20087

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions