-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Open
Labels
Description
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