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);
}