在后台线程上将字体渲染并加载到FontCache中?

时间:2009-11-17 00:00:14

标签: wpf fonts backgroundworker

我正在尝试显示类似于Blend中的字体选择器列表:

Blend Font Picker http://img691.imageshack.us/img691/60/blendfontpicker.png

与Blend一样,我在FontCache中未加载FontFamilies时会看到性能问题。

我付出的代价似乎是实际渲染给定FontSize的FontFamily并将其保存到FontCache中所需的时间。一旦渲染的字体在缓存中,问题就会消失。

我尝试在后台线程上迭代Fonts.SystemFontFamilies集合并调度对UI的调用,导致隐藏的TextBlock更新(这应该导致字体呈现)。

当然,由于调度调用是连续发生的,所以只需对UI进行调整,并获得阻塞UI的相同结果,直到所有字体都被渲染并加载到FontCache中。

有没有人能很好地解决这个问题?他们似乎没有在Blend找到它的解决方案所以我认为没有一个好的解决方案。

2 个答案:

答案 0 :(得分:3)

为您提出一些想法

想法1:完全在后台线程上获取字体。

字体实际上被加载到系统字体缓存中(进程间),然后部分字体信息被复制到特定于线程的字体缓存中。填充系统字体缓存可能会导致速度的充分提高。这可以通过低优先级后台线程来完成,该线程开始运行应用程序启动的瞬间。因此,当用户下拉字体列表时,系统字体缓存应该完全填充。

创意2:自己缓存渲染的字体几何

不使用TextBlocks,而是使用ComboBox的DataTemplate中的ContentPresenter对象以及绑定到PriorityBinding的内容。较低优先级将使用默认字体生成TextBlock,较高优先级将是IsAsync绑定,该绑定将使用适当的参数创建GlyphRun,在其上调用BuildGeometry(),并在Path对象内返回Geometry。创建的Geometry对象可以被缓存并再次返回,以便将来访问相同的字体。

结果将是项目最初将以默认字体显示,并在加载字体并创建其几何图形后立即呈现为样式字体。请注意,这可以与在单独的线程中预填充缓存的代码结合使用。

Idea 2的代码如下所示:

<ComboBox ItemsSource="{Binding MyFontObjects}">
  <ComboBox.ItemTemplate>
    <ContentPresenter>
      <ContentPresenter.Content>
        <PriorityBinding>
          <Binding IsAsync="True" Path="BuildStyledFontName" />
          <Binding Path="BuildTextBlock" />
        </PriorityBinding>
        ... close all tags ...

MyFontObjets将是IEnumerable对象,如下所示:

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

请注意,可以将缓存中的Geometry对象保存到磁盘,方法是将它们转换为PathGeometry,然后将其转换为PathGeometry迷你语言中的字符串。这将允许使用单个文件读取和放大来填充字体几何缓存。解析,所以你唯一一次看到任何延迟就是你第一次运行应用程序时,或者当你使用大量新字体运行它时。

答案 1 :(得分:0)

我的解决方案是不提前渲染所有字体,通过使用虚拟化面板显示字体列表,您只会加载适合屏幕的字体,它将首次减慢滚动速度,但它几乎不会引起注意用户。

查看http://www.bennedik.de/2007/10/wpf-fast-font-drop-down-list.html

顺便说一句,如果您使用VirtualizingStackPanel的组合,则必须在DataTemplate中设置TextBlock元素的宽度,否则在滚动期间下拉宽度将会改变。