gpt4 book ai didi

c# - WPF:使用随 'AddFontMemResourceEx' 安装的字体仅用于进程

转载 作者:行者123 更新时间:2023-11-30 12:20:35 24 4
gpt4 key购买 nike

在 WPF 中,我们希望将 ttf 字体用作嵌入式资源,而无需将它们复制或安装到系统中,也无需实际将它们写入磁盘。没有内存泄漏问题。

没有详述的解决方案:

How to include external font in WPF application without installing it

由于围绕此的 WPF 内存泄漏,在这种情况下可用:

WPF TextBlock memory leak when using Font

通过 AddFontMemResourceEx 在 GDI 中可以仅从内存和进程安装字体.由于这会为进程安装字体,因此它也应该适用于 WPF,但似乎在通过 AddFontMemResourceEx 安装字体后获得的 FontFamily 周围存在问题。例如:

var font = new FontFamily("Roboto");

它的工作原理是它不会给出任何错误,但字体实际上并没有改变,一些行间距和其他指标发生了改变,但出于某种原因字体看起来与 Segoe UI 完全一样.

那么问题是,如何在 WPF 中使用通过 AddFontMemResourceEx 安装的字体?

PS:这里是 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);
}
}

此代码成功并显示如下日志消息:

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

支持将嵌入式资源加载为字节数组的代码:

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

这意味着安装嵌入式字体可以这样完成,assembly 是包含嵌入式字体资源的程序集,EMBEDDEDFONTNAMESPACE 是嵌入式资源的命名空间,例如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);
}

最佳答案

我不知道这是否正是您想要的,但我有一个解决方案,您可以在其中将字体用作解决方案中的 Resource

  1. 声明所有你想要的fonts作为Resource
  2. 制作一个名为 FontExplorer 的自定义 MarkupExtension
  3. 试试我的 XAML 示例

应用程序 启动并且第一次使用FontExplorer 时,它会缓存您作为资源拥有的所有字体。之后,每次您需要其中之一时,都会使用缓存将其归还。

Resources

public class FontExplorer : MarkupExtension
{
// ##############################################################################################################################
// Properties
// ##############################################################################################################################

#region Properties

// ##########################################################################################
// Public Properties
// ##########################################################################################

public string Key { get; set; }

// ##########################################################################################
// Private Properties
// ##########################################################################################

private static readonly Dictionary<string, FontFamily> _CachedFonts = new Dictionary<string, FontFamily>();

#endregion


// ##############################################################################################################################
// Constructor
// ##############################################################################################################################

#region Constructor

static FontExplorer()
{
foreach (FontFamily fontFamily in Fonts.GetFontFamilies(new Uri("pack://application:,,,/"), "./Fonts/"))
{
_CachedFonts.Add(fontFamily.FamilyNames.First().Value, fontFamily);
}
}

#endregion

// ##############################################################################################################################
// methods
// ##############################################################################################################################

#region methods

public override object ProvideValue(IServiceProvider serviceProvider)
{
return ReadFont();
}

private object ReadFont()
{
if (!string.IsNullOrEmpty(Key))
{
if (_CachedFonts.ContainsKey(Key))
return _CachedFonts[Key];
}

return new FontFamily("Comic Sans MS");
}

#endregion
}

<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Window.Style>
<Style TargetType="local:MainWindow">
<Setter Property="FontFamily" Value="{local:FontExplorer Key='Candle Mustard'}"/>
<Style.Triggers>
<Trigger Property="Switch" Value="True">
<Setter Property="FontFamily" Value="{local:FontExplorer Key=Roboto}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Style>
<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0">
<TextBlock Text="Hello World" FontFamily="{local:FontExplorer Key='Candle Mustard'}"/>
<TextBlock Text="Hello World" FontFamily="{local:FontExplorer Key=Roboto}"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
<TextBlock Text="Hello World"/>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="1" x:Name="Panel"/>
</Grid>
</Window>

public partial class MainWindow : Window
{
public bool Switch
{
get => (bool)GetValue(SwitchProperty);
set => SetValue(SwitchProperty, value);
}

/// <summary>
/// The <see cref="Switch"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty SwitchProperty = DependencyProperty.Register("Switch", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));


private readonly DispatcherTimer _Timer;

public MainWindow()
{
InitializeComponent();
_Timer = new DispatcherTimer();
_Timer.Interval = TimeSpan.FromMilliseconds(50);
_Timer.Tick += (sender, args) =>
{
Switch = !Switch;
Panel.Children.Add(new TextBlock {Text = "I'm frome code behind"});
if(Panel.Children.Count > 15)
Panel.Children.Clear();
};
_Timer.Start();
}


// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################

#region PropertyChanged

/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

#endregion
}

预览

如您在预览中所见,GC 完成工作后,内存使用量 从 83.2MB 减少到 82.9MB。

enter image description here

关于c# - WPF:使用随 'AddFontMemResourceEx' 安装的字体仅用于进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50964801/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com