WPF: Use la fuente instalada con 'AddFontMemResourceEx' solo para el proceso

En WPF nos gustaría usarttf fuentes como recursos integrados sin copiarlos o instalarlos en el sistema y sin escribirlos en el disco. Sin problemas de pérdida de memoria.

Ninguna de las soluciones detalladas en:

Cómo incluir una fuente externa en la aplicación WPF sin instalarla

son utilizables en este escenario debido a la pérdida de memoria WPF alrededor de esto:

érdida de memoria de @WPF TextBlock al usar Font

La instalación de fuentes desde la memoria y el proceso solo es posible en GDI a través deAddFontMemResourceEx. Dado que esto instala la fuente para el proceso, también debería funcionar para WPF, pero parece haber problemas en torno aFontFamily que obtenemos después de instalar la fuente a través deAddFontMemResourceEx. P.ej.

var font = new FontFamily("Roboto");

Esto funciona porque no da ningún error, pero la fuente no se cambia realmente, se cambian algunos espacios entre líneas y otras métricas, pero la fuente se ve exactamente comoSegoe UI por alguna razón

La pregunta es, entonces, ¿cómo es posible usar las fuentes instaladas conAddFontMemResourceEx en WPF?

PS: Aquí el 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);
    }
}

Este código tiene éxito con mensajes de registro como:

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

Código de soporte para cargar recursos incrustados 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;
    }
}

Lo que significa que la instalación de fuentes incrustadas se puede hacer como, conassembly siendo el ensamblado que contiene los recursos de fuente incrustados yEMBEDDEDFONTNAMESPACE ser el espacio de nombres de los recursos integrados, p. @SomeProject.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);
}

Respuestas a la pregunta(1)

Su respuesta a la pregunta