代码之家  ›  专栏  ›  技术社区  ›  Brad Cunningham

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

  •  0
  • Brad Cunningham  · 技术社区  · 15 年前

    我试图显示一个类似于Blend的字体选取器列表:

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

    和Blend一样,当fontFamilies未加载到fontCache中时,我会看到性能问题。

    似乎我要支付的惩罚是实际呈现给定fontsize的fontfamily并将其保存到fontcache所需的时间。一旦呈现的字体在缓存中,问题就消失了。

    我尝试在后台线程上迭代fonts.systemfontfamilies集合,并向UI发送一个调用,该调用将导致隐藏的文本块更新(这将导致字体呈现)。

    当然,由于分派调用是连续发生的,所以它只会加重UI的负担,而我会得到相同的阻塞UI的净结果,直到所有字体都呈现并加载到fontcache中。

    有人能很好地解决这个问题吗?他们似乎没有找到一个解决办法,在混合,所以我认为没有一个好的解决办法。

    2 回复  |  直到 15 年前
        1
  •  3
  •   Ray Burns    15 年前

    给你几个主意

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

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

    想法2:自己缓存渲染的字体几何图形

    不要使用文本块,而是在ComboBox的数据模板中使用ContentPresenter对象,内容绑定到PriorityBinding。较低的优先级将使用默认字体生成一个文本块,较高的优先级将是一个isasync绑定,该绑定将使用适当的参数创建一个glyphRun,对其调用buildGeometry(),并返回路径对象内的几何体。创建的几何图形对象可以缓存并再次返回,以便将来访问同一字体。

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

    想法2的代码如下所示:

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

    其中,myfontobjets将是大量这样的对象:

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

    请注意,通过将缓存中的几何图形对象转换为PathGeometry,然后再转换为PathGeometry Mini语言中的字符串,可以将其保存到磁盘。这将允许使用单个文件读取和分析来填充字体几何缓存,因此,您看到任何延迟的唯一时间是首次运行应用程序时,或使用大量新字体运行应用程序时。

        2
  •  0
  •   Nir    15 年前

    我的解决方案是不要提前渲染所有字体,通过使用字体列表的虚拟面板,您将只加载适合屏幕的字体,它将第一次减慢滚动速度,但用户几乎看不到。

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

    顺便说一句,如果将组合与virtualizingstackpanel一起使用,则必须在datatemplate中设置textBlock元素的宽度,否则在滚动期间下拉列表的宽度将发生更改。