WPF: Use a fonte instalada com 'AddFontMemResourceEx' apenas para processo

No WPF, gostaríamos de usarttf fontes como recursos incorporados sem copiá-los ou instalá-los no sistema e sem gravá-los no disco. Sem problemas de vazamento de memória.

Nenhuma das soluções detalhadas em:

Como incluir fonte externa no aplicativo WPF sem instalá-lo

são utilizáveis neste cenário devido ao vazamento de memória WPF em torno deste:

Vazamento de memória WPF TextBlock ao usar fonte

A instalação de fontes apenas da memória e do processo é possível no GDI viaAddFontMemResourceEx. Como isso instala a fonte do processo, também deve funcionar para o WPF, mas parece haver problemas em torno doFontFamily que obtemos após instalar a fonte viaAddFontMemResourceEx. Por exemplo.:

var font = new FontFamily("Roboto");

Isso funciona porque não há erros, mas a fonte não é realmente alterada, alguns espaçamentos de linha e outras métricas são alterados, mas a fonte se parece exatamente comSegoe UI por algum motivo.

A questão é, então, é e como é possível usar fontes instaladas comAddFontMemResourceEx no WPF?

PS: Aqui o código P / Invoke:

const string GdiDllName = "gdi32";
[DllImport(GdiDllName, ExactSpelling= true)]
private static extern IntPtr AddFontMemResourceEx(byte[] pbFont, int cbFont, IntPtr pdv, out uint pcFonts);

public static void AddFontMemResourceEx(string fontResourceName, byte[] bytes, Action<string> log)
{
    var handle = AddFontMemResourceEx(bytes, bytes.Length, IntPtr.Zero, out uint fontCount);
    if (handle == IntPtr.Zero)
    {
        log?.Invoke($"Font install failed for '{fontResourceName}'");
    }
    else
    {
        var message = $"Font installed '{fontResourceName}' with font count '{fontCount}'";
        log?.Invoke(message);
    }
}

Esse código é bem-sucedido com mensagens de log como:

Font installed 'Roboto-Regular.ttf' with font count '1'

Código de suporte para carregar recursos incorporados como matriz de bytes:

public static byte[] ReadResourceByteArray(Assembly assembly, string resourceName)
{
    using (var stream = assembly.GetManifestResourceStream(resourceName))
    {
        var bytes = new byte[stream.Length];
        int read = 0;
        while (read < bytes.Length)
        {
            read += stream.Read(bytes, read, bytes.Length - read);
        }
        if (read != bytes.Length)
        {
            throw new ArgumentException(
                $"Resource '{resourceName}' has unexpected length " +
                $"'{read}' expected '{bytes.Length}'");
        }
        return bytes;
    }
}

O que significa que a instalação de fontes incorporadas pode ser feita como, comassembly sendo o assembly que contém os recursos de fonte incorporados eEMBEDDEDFONTNAMESPACE sendo o espaço para nome dos recursos incorporados, por exemploSomeProject.Fonts:

var resourceNames = assembly.GetManifestResourceNames();

string Prefix = "EMBEDDEDFONTNAMESPACE" + ".";
var fontFileNameToResourceName = resourceNames.Where(n => n.StartsWith(Prefix))
    .ToDictionary(n => n.Replace(Prefix, string.Empty), n => n);

var fontFileNameToBytes = fontFileNameToResourceName
    .ToDictionary(p => p.Key, p => ReadResourceByteArray(assembly, p.Value));

foreach (var fileNameBytes in fontFileNameToBytes)
{
    AddFontMemResourceEx(fileNameBytes.Key, fileNameBytes.Value, log);
}

questionAnswers(0)

yourAnswerToTheQuestion