Wpf 在后台线程上渲染字体并将其加载到FontCache?
我试图显示一个字体选择器列表,与Blend中的列表类似: 与Blend一样,当FontFamilies未加载到FontCache中时,我会看到性能问题 似乎我所付出的代价是实际渲染给定FontSize的FontFamily并将其保存到FontCache所需的时间。一旦渲染字体在缓存中,问题就会消失 我尝试在后台线程上迭代Fonts.SystemFontFamilies集合,并向UI发送一个调用,该调用会导致隐藏的文本块更新(这会导致字体呈现) 当然,由于分派调用是连续发生的,它只会冲击UI,我会得到阻塞UI的相同净结果,直到所有字体都呈现并加载到FontCacheWpf 在后台线程上渲染字体并将其加载到FontCache?,wpf,fonts,backgroundworker,Wpf,Fonts,Backgroundworker,我试图显示一个字体选择器列表,与Blend中的列表类似: 与Blend一样,当FontFamilies未加载到FontCache中时,我会看到性能问题 似乎我所付出的代价是实际渲染给定FontSize的FontFamily并将其保存到FontCache所需的时间。一旦渲染字体在缓存中,问题就会消失 我尝试在后台线程上迭代Fonts.SystemFontFamilies集合,并向UI发送一个调用,该调用会导致隐藏的文本块更新(这会导致字体呈现) 当然,由于分派调用是连续发生的,它只会冲击UI,我
有人能很好地解决这个问题吗?他们似乎没有在Blend中找到解决方案,所以我认为这不是一个好的解决方案。给你一些想法 想法1:完全在后台线程上提取字体。 字体实际上被加载到系统字体缓存(进程间),然后部分字体信息被复制到特定于线程的字体缓存中。填充系统字体缓存可能会带来足够好的速度提升。这可以通过一个低优先级后台线程来完成,该线程在应用程序启动时立即开始运行。因此,当用户下拉字体列表时,系统字体缓存应该已完全填充 Idea 2:自己缓存渲染的字体几何体 不要使用文本块,而是在组合框的数据模板中使用ContentPresenter对象,并将内容绑定到PriorityBinding。较低的优先级将使用默认字体生成文本块,较高的优先级将是IsAsync绑定,该绑定将使用适当的参数创建GlyphRun,对其调用BuildGeometry(),并返回路径对象内的几何体。创建的几何体对象可以缓存并再次返回,以便将来访问同一字体 这样做的结果是,项目最初将以默认字体显示,并在加载字体并创建其几何体后立即呈现为样式化字体。请注意,这可以与在单独的线程中预先填充缓存的代码结合使用 Idea 2的代码如下所示:
<ComboBox ItemsSource="{Binding MyFontObjects}">
<ComboBox.ItemTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<PriorityBinding>
<Binding IsAsync="True" Path="BuildStyledFontName" />
<Binding Path="BuildTextBlock" />
</PriorityBinding>
... close all tags ...
public class MyFontObject
{
public FontFamily Font { get; set; }
public object BuildTextBlock
{
get { return new TextBlock { Text = GetFamilyName(Font) } }
}
public object BuildStyledFontName
{
get
{
return new Path { Data = GetStyledFontGeometryUsingCache() };
}
}
private Geometry GetStyledFontGeometryUsingCache()
{
Geometry geo;
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
lock(_fontGeometryBuildLock)
{
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
geo = BuildStyledFontGeometry();
lock(_fontGeometryCache)
_fontGeometryCache[Font] = geo;
}
}
static object _fontGeometryCache = new Dictionary<FontFamily, Geometry>();
static object _fontGeometryBuildLock = new object();
private Geometry BuildStyledFontGeometry()
{
var run = new GlyphRun
{
Characters = GetFamilyName(Font),
GlyphTypeface = GetGlyphTypeface(Font),
}
return run.BuildGeometry();
}
... GetFamilyName ...
... GetGlyphTypeface ...
// Call from low priority background thread spawned at app startup
publc static void PrefillCache()
{
foreach(FontFamily font in Fonts.SystemFontFamilies)
new MyFontObject { Font = font }.GetStyledFontGeometryUsingCache();
}
}
... 关闭所有标签。。。
其中MyFontObjets是一个IEnumerable对象,类似于:
<ComboBox ItemsSource="{Binding MyFontObjects}">
<ComboBox.ItemTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<PriorityBinding>
<Binding IsAsync="True" Path="BuildStyledFontName" />
<Binding Path="BuildTextBlock" />
</PriorityBinding>
... close all tags ...
public class MyFontObject
{
public FontFamily Font { get; set; }
public object BuildTextBlock
{
get { return new TextBlock { Text = GetFamilyName(Font) } }
}
public object BuildStyledFontName
{
get
{
return new Path { Data = GetStyledFontGeometryUsingCache() };
}
}
private Geometry GetStyledFontGeometryUsingCache()
{
Geometry geo;
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
lock(_fontGeometryBuildLock)
{
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
geo = BuildStyledFontGeometry();
lock(_fontGeometryCache)
_fontGeometryCache[Font] = geo;
}
}
static object _fontGeometryCache = new Dictionary<FontFamily, Geometry>();
static object _fontGeometryBuildLock = new object();
private Geometry BuildStyledFontGeometry()
{
var run = new GlyphRun
{
Characters = GetFamilyName(Font),
GlyphTypeface = GetGlyphTypeface(Font),
}
return run.BuildGeometry();
}
... GetFamilyName ...
... GetGlyphTypeface ...
// Call from low priority background thread spawned at app startup
publc static void PrefillCache()
{
foreach(FontFamily font in Fonts.SystemFontFamilies)
new MyFontObject { Font = font }.GetStyledFontGeometryUsingCache();
}
}
公共类MyFontObject
{
公共字体系列字体{get;set;}
公共对象构建文本块
{
获取{返回新文本块{Text=GetFamilyName(字体)}
}
公共对象BuildStyledFontName
{
得到
{
返回新路径{Data=GetStyledFontGeometryUsingCache()};
}
}
私有几何图形GetStyledFontGeometryUsingCache()
{
几何地理;
锁(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font,out geo)返回geo;
锁(_fontgometrybuildlock)
{
锁(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font,out geo)返回geo;
geo=BuildStyledFontGeometry();
锁(_fontGeometryCache)
_fontGeometryCache[Font]=geo;
}
}
静态对象_fontGeometryCache=新字典();
静态对象_fontgometrybuildlock=新对象();
私有几何图形BuildStyledFontGeometry()
{
var run=新的GlyphRun
{
Characters=GetFamilyName(字体),
GlyphTypeface=GetGlyphTypeface(字体),
}
返回run.BuildGeometry();
}
…GetFamilyName。。。
…字体。。。
//从应用程序启动时生成的低优先级后台线程调用
publc static void PrefillCache()
{
foreach(Fonts.SystemFontFamilies中的FontFamily字体)
新的MyFontObject{Font=Font}.GetStyledFontGeometryUsingCache();
}
}
请注意,缓存中的几何体对象可以通过将其转换为PathGeometry,然后再转换为PathGeometry mini语言中的字符串来保存到磁盘。这将允许使用单个文件读取和解析来填充字体几何体缓存,因此您唯一看到延迟的时间是首次运行应用程序时,或者使用大量新字体。给你一些想法 想法1:完全在后台线程上提取字体。 字体实际加载到系统字体缓存中(进程间)然后,部分字体信息被复制到特定于线程的字体缓存中。填充系统字体缓存可能会带来足够好的速度提升。这可以通过一个低优先级的后台线程来完成,该线程在应用程序启动时就开始运行。因此,当用户下拉字体列表时应完全填充系统字体缓存 Idea 2:自己缓存渲染的字体几何体 不要使用TextBlock,而是在组合框的DataTemplate中使用ContentPresenter对象,将内容绑定到PriorityBinding。较低的优先级将使用默认字体生成TextBlock,较高的优先级将是IsAsync绑定,该绑定将使用适当的参数创建GlyphRun,调用BuildGeometry()然后返回路径对象中的几何体。创建的几何体对象可以缓存并再次返回,以便将来访问同一字体 这样做的结果是,项目最初将以默认字体显示,并在加载字体并创建其几何体后立即呈现为样式化字体。请注意,这可以与在单独线程中预先填充缓存的代码结合使用 Idea 2的代码如下所示:
<ComboBox ItemsSource="{Binding MyFontObjects}">
<ComboBox.ItemTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<PriorityBinding>
<Binding IsAsync="True" Path="BuildStyledFontName" />
<Binding Path="BuildTextBlock" />
</PriorityBinding>
... close all tags ...
public class MyFontObject
{
public FontFamily Font { get; set; }
public object BuildTextBlock
{
get { return new TextBlock { Text = GetFamilyName(Font) } }
}
public object BuildStyledFontName
{
get
{
return new Path { Data = GetStyledFontGeometryUsingCache() };
}
}
private Geometry GetStyledFontGeometryUsingCache()
{
Geometry geo;
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
lock(_fontGeometryBuildLock)
{
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
geo = BuildStyledFontGeometry();
lock(_fontGeometryCache)
_fontGeometryCache[Font] = geo;
}
}
static object _fontGeometryCache = new Dictionary<FontFamily, Geometry>();
static object _fontGeometryBuildLock = new object();
private Geometry BuildStyledFontGeometry()
{
var run = new GlyphRun
{
Characters = GetFamilyName(Font),
GlyphTypeface = GetGlyphTypeface(Font),
}
return run.BuildGeometry();
}
... GetFamilyName ...
... GetGlyphTypeface ...
// Call from low priority background thread spawned at app startup
publc static void PrefillCache()
{
foreach(FontFamily font in Fonts.SystemFontFamilies)
new MyFontObject { Font = font }.GetStyledFontGeometryUsingCache();
}
}