Рендеринг и загрузка шрифтов в FontCache в фоновом потоке? - PullRequest
0 голосов
/ 17 ноября 2009

Я пытаюсь показать список выбора шрифтов, аналогичный списку в Blend:

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

Как и в случае с Blend, у меня возникают проблемы с производительностью, когда FontFamilies не загружаются в FontCache.

Кажется, что штраф, который я плачу, - это время, которое требуется для фактического рендеринга FontFamily для данного FontSize и сохранения его в FontCache. Как только визуализированный шрифт окажется в кеше, проблема исчезнет.

Я попытался выполнить итерацию коллекции Fonts.SystemFontFamilies в фоновом потоке и отправить вызов пользовательскому интерфейсу, который вызывает обновление скрытого TextBlock (что должно привести к визуализации шрифта).

Конечно, поскольку вызовы диспетчеризации происходят последовательно, он просто обрабатывает пользовательский интерфейс, и я получаю тот же чистый результат блокирующего пользовательского интерфейса, пока все шрифты не будут отрисованы и загружены в FontCache.

У кого-нибудь есть хорошее решение этой проблемы? Похоже, что они не нашли решения этой проблемы в Blend, поэтому я думаю, что нет хорошего решения.

Ответы [ 2 ]

3 голосов
/ 17 ноября 2009

пара идей для вас

Идея 1. Выполните выборку шрифта целиком в фоновом потоке.

Шрифты фактически загружаются в системный шрифт (межпроцессный), а затем часть информации о шрифте копируется в поток-кеш-шрифт. Вполне возможно, что заполнение кэша системных шрифтов приведет к достаточно хорошему увеличению скорости. Это может быть сделано фоновым потоком с низким приоритетом, который запускается сразу после запуска вашего приложения. Таким образом, к тому времени, когда пользователь опустит список шрифтов, кэш системного шрифта должен быть полностью заполнен.

Идея 2: Кэшировать геометрию визуализированного шрифта самостоятельно

Вместо использования TextBlocks, используйте объекты ContentPresenter в DataTemplate вашего ComboBox с содержимым, привязанным к PriorityBinding. При более низком приоритете будет создан TextBlock с использованием шрифта по умолчанию, а более высоким приоритетом будет привязка IsAsync, которая создаст GlyphRun с соответствующими параметрами, вызовет для него BuildGeometry () и вернет Geometry внутри объекта Path. Созданные объекты 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. Это позволило бы заполнить кэш геометрии шрифта, используя один файл read & parse, поэтому вы могли увидеть только одну задержку, когда вы впервые запустили приложение или запустили его с большим количеством новых шрифтов.

0 голосов
/ 17 ноября 2009

Мое решение состоит в том, чтобы не отображать все шрифты заранее, используя виртуализированную панель для списка шрифтов, вы будете загружать только те шрифты, которые помещаются на экран, это замедлит прокрутку в первый раз, но почти незаметно для пользователя. пользователь.

посмотрите на http://www.bennedik.de/2007/10/wpf-fast-font-drop-down-list.html

Кстати, если вы используете комбо с VirtualizingStackPanel, вам нужно будет установить ширину элемента TextBlock внутри DataTemplate, иначе ширина выпадающего меню изменится во время прокрутки.

...