Фактор DirectX

Кто боится последовательностей глифов?

Чарльз Петцольд

Исходный код можно скачать по ссылке.

Чарльз ПетцольдС концепцией глифов символов (character glyphs) я впервые столкнулся в Windows Presentation Foundation (WPF) несколько лет назад. В полиграфии понятие «глиф» относится к графическому представлению символа, а не к его функции в конкретном письменном языке, на что указывает Unicode-значение символа. Я знал, что в WPF элемент Glyphs и класс GlyphRun представляют альтернативный способ отображения текста, но мне казалось, что они добавляют лишний уровень сложности. Самое странное — ссылка на нужный шрифт осуществляется не по названию семейства шрифтов, а по пути к реальному файлу шрифта. Я занимаюсь программированием для Windows со времен ее бета-версий 1.0 и еще никогда не ссылался на шрифт по имени его файла. Так просто не делали.

Я скоропалительно счел, что Glyphs и GlyphRun были слишком эзотерическими для «мейнстримовых» программистов вроде меня. И лишь годы спустя, когда я начал работать с Microsoft XML Paper Specification (XPS), мне стал понятен смысл глифов.

Основная особенность

Обычно, когда программа запрашивает какой-то шрифт от Windows, она использует название семейства шрифтов, например Century Schoolbook, и атрибуты, такие как italic (курсив) или bold (полужирный). В период выполнения Windows находит в системе пользователя файл со шрифтом, который соответствует названию семейства. Курсивное или полужирное начертание может быть либо встроенным в этот шрифт, либо синтезируемым системой. Для разметки текста Windows или само приложение обращается к информации о метрике шрифта, которая описывает размеры символов этого шрифта.

Что будет, если два разных пользователя имеют два разных файла шрифтов в своих системах, которые содержат шрифт с названием семейства Century Schoolbook, но немного отличаются по метрикам? На самом деле это не проблема. Поскольку текст форматируется и размечается в период выполнения, визуально эти шрифты могут слегка различаться, но это не имеет особого значения. Прикладные программы достаточно гибки в этом плане.

Однако XPS-документы — это другая история. XPS во многом подобно PDF описывает документ с фиксированными страницами. Весь текст на каждой странице уже размечен и находится в точных позициях. Если такой документ визуализировать с использованием шрифта, слегка отличающегося от шрифта, который применялся для дизайна страницы, он будет выглядеть неправильно. Вот почему XPS-документы часто содержат встроенные файлы шрифтов и почему XPS-страницы используют элемент Glyphs для ссылки на эти файлы шрифтов и для визуализации текста. Тем самым полностью устраняется любая неоднозначность.

Обычно мы ссылаемся на текстовые символы по их кодам. И, как правило, нам не нужно ничего знать о точном глифе, который отображается вследствие использования кода символа в конкретном шрифте. Но иногда важно иметь больший контроль над самими глифами.

Вы могли предположить, что между кодами символов и глифами в конкретном шрифте существует соответствие «один к одному», и обычно так и есть. Но существуют очень важные исключения: некоторые файлы шрифтов содержат лигатуры, которые являются едиными глифами, соответствующими парам символов вроде «fi» или «fl». Некоторые нелатинские наборы символов требуют нескольких глифов для рендеринга одного символа. Кроме того, иногда файлы шрифтов содержат альтернативные глифы для определенных символов, например ноль с диагональным перечеркиванием, малые заглавные буквы, используемые для нижнего регистра или буквы со стилизованными завитушками (stylistic swashes). Эти стилистические альтернативы являются особенностью конкретного файла шрифта, а не самого шрифта. Вот почему столь важно получение нужного файла шрифта.

Если вы пишете приложение Windows Store для Windows 8, то можете использовать эти стилистические альтернативы через Windows Runtime API. В главе 16 моей книги «Programming Windows, 6th Edition» (O’Reilly Media, 2012) есть программа TypographyDemo, которая демонстрирует, как это делается. Но нижележащая поддержка глифов в Windows 8 реализуется в DirectX, и их реальные возможности в наибольшей мере раскрываются в этой среде.

Поддержка глифов в DirectX

Глифы в DirectX отображаются с участием нескольких интерфейсов, методов и структур.

Начнем с интерфейса ID2D1RenderTarget, который содержит базовые методы для отображения двухмерной графики и текста. В этом интерфейсе определен метод DrawGlyphRun; ему нужно передать структуру типа DWRITE_GLYPH_RUN, которая описывает отображаемые глифы, а также ссылается на объект типа IDWriteFontFace.

Один из способов получить объект IDWriteFontFace — вызов метода CreateFontFace в IDWriteFactory. Этот метод требует объект IDWriteFontFile, получаемый через метод CreateFontFileReference в IDWriteFactory.

Метод CreateFontFileReference ссылается на файл шрифта, используя путь и имя файла. Однако, если вы применяете DirectX в приложении Windows Store, оно, по-видимому, не получит полный и свободный доступ к жесткому диску, и, как правило, вы не сможете обращаться к шрифтам в таком стиле. Любой файл шрифта, на который вы ссылаетесь в CreateFontFileReference, скорее всего будет определен как ресурс и включен в пакет приложения.

Но не всякий файл шрифта можно включить в пакет приложения. Если шрифт является частью приложения, у вас должна быть лицензия на распространение этого шрифта. К счастью, Microsoft лицензировала несколько файлов шрифтов от Ascender Corp., чтобы вы могли распространять их со своими приложениями. Эти файлы шрифтов использовались в основном для XNA-приложений, но представляют интерес и с точки зрения глифов.

В сопутствующем этой статье исходном коде есть программа GlyphDump на основе шаблона DirectX App (XAML) из Visual Studio Express 2013 Preview. Этот шаблон создает приложение, которое осуществляет рендеринг DirectX-графики на SwapChainPanel.

Я создал новый фильтр (и соответствующую папку) в проекте GlyphDump с именем Fonts и добавит 10 файлов шрифтом, лицензированных от Ascender Corp. ListBox в DirectXPage.xaml явным образом перечисляет названия семейств этих шрифтов, причем свойства Tag ссылаются на имена файлов. При выборе одного из них в списке программа конструирует путь и имя файла шрифта так:

Package::Current->InstalledLocation->Path +
  "\\Fonts\\" + filename;

Затем класс GlyphDumpRenderer использует этот путь для создания объектов IDWriteFontFile и IDWriteFontFace.

Остальная работа выполняется в методе Render, как показано на рис. 1.

Рис. 1. Метод Render в GlyphDump

bool GlyphDumpRenderer::Render()
{
  if (!m_needsRedraw)
      return false;
  ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
  context->SaveDrawingState(m_stateBlock.Get());
  context->BeginDraw();
  context->Clear(ColorF(ColorF::White));
  context->SetTransform(m_deviceResources->GetOrientationTransform2D());
  if (m_fontFace != nullptr)
  {
    Windows::Foundation::Size outputBounds = m_deviceResources->GetOutputBounds();
    uint16 glyphCount = m_fontFace->GetGlyphCount();
    int rows = (glyphCount + 16) / 16;
    float boxHeight = outputBounds.Height / (rows + 1);
    float boxWidth = outputBounds.Width / 17;
    // Определяем всю структуру, кроме glyphIndices
    DWRITE_GLYPH_RUN glyphRun;
    glyphRun.fontFace = m_fontFace.Get();
    glyphRun.fontEmSize = 0.75f * min(boxHeight, boxWidth);
    glyphRun.glyphCount = 1;
    glyphRun.glyphAdvances = nullptr;
    glyphRun.glyphOffsets = nullptr;
    glyphRun.isSideways = false;
    glyphRun.bidiLevel = 0;
    for (uint16 index = 0; index < glyphCount; index++)
    {
      glyphRun.glyphIndices = &index;
      context->DrawGlyphRun(Point2F(((index) % 16 + 0.5f) * boxWidth,
                                    ((index) / 16 + 1) * boxHeight),
                                    &glyphRun,
                                    m_blackBrush.Get());
    }
  }
  // Здесь мы игнорируем D2DERR_RECREATE_TARGET. Эта ошибка
  // указывает, что устройство потеряно. Ошибка будет
  // обработана при следующем вызове Present.
  HRESULT hr = context->EndDraw();
  if (hr != D2DERR_RECREATE_TARGET)
  {
    DX::ThrowIfFailed(hr);
  }
  context->RestoreDrawingState(m_stateBlock.Get());
  m_needsRedraw = false;
  return true;
}

Этот метод Render отображает все глифы в выбранном файле шрифтов в виде строк по 16 глифов в каждой, поэтому он пытается вычислить значение fontEmSize, достаточно малое для данного экрана. (Для некоторых шрифтов со множеством глифов этот вариант будет работать не слишком хорошо.) Обычно поле glyphIndices структуры DWRITE_GLYPH_RUN — это массив индексов глифов. Здесь я отображаю только один глиф одновременно.

Метод DrawGlyphRun требует точку координат, структуру DWRITE_GLYPH_RUN и кисть. Точка координат указывает, где должен быть расположен левый край базовой линии глифа. Заметьте: я сказал «базовой линии». Здесь проявляется отличие от большинства методов отображения текста, требующих указывать верхний левый угол первого символа.

На рис. 2 показан, возможно, самый интересный шрифт из этого пакета; его семейства называется Pescadero, а имя файла шрифта — Pesca.ttf.

Программа GlyphDump, показывающая шрифт Pescadero
Рис. 2. Программа GlyphDump, показывающая шрифт Pescadero

До написания этой программы я никогда не видел такого. Поначалу он выглядит похожим на традиционную ASCII-таблицу, но потом появляется серия глифов для числовых верхних и нижних индексов, целый набор лигатур различных видов плюс заглавные буквы с декоративными завитушками.

Очевидно, поле glyphIndices структуры DWRITE_GLYPH_RUN — это не то же самое, что код символа. Это индексы, которые ссылаются на глифы в файле шрифтов, и эти глифы даже не обязаны располагаться в каком-то мало-мальски рациональном порядке.

Текст с глифами

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

Это демонстрируется проектом FancyTitle. Здесь идея в том, что у вас есть программа, которая по большей части базируется на XAML, но вам нужен витиеватый заголовок с применением некоторых лигатур и завитушек из шрифта Pescadero. Проект включает файл Pesca.ttf, а XAML-файл определяет SwapChainPanel шириной 778 и высотой 54; эти значения я подобрал эмпирически на основе размера визуализированного текста.

Поскольку требования этого проекта к отображению просты, я удалил класс FancyTitleMain и классы визууализации, оставив класс DirectXPage для рендеринга в SwapChainPanel. (Однако мне пришлось слегка модифицировать DeviceResources, чтобы сделать IDeviceNotify интерфейсом ref-класса и чтобы DirectXPage мог реализовать IDeviceNotify для уведомления при потере устройства и его создании заново.)

Текстовый вывод, показанный на рис. 3, имеет 24 символа, но лишь 22 глифа. Лигатуры и завитушки вы распознаете по рис. 2.

Вывод FancyTitle
Рис. 3. Вывод FancyTitle

Я сделал DirectX-фон Alice Blue, чтобы вы могли увидеть, что SwapChainPanel чуть больше самого текста. Конечно, можно точно просчитать размер вывода, поскольку файл шрифта является частью приложения и обращение к глифам происходит напрямую.

Вы можете получить лигатуры и альтернативные глифы без применения DrawGlyphRun. Кроме того, их можно получить с меньшей точностью, используя элемент TextBlock в Windows Runtime, как демонстрирует программа TypographyDemo из моей книги по Windows 8. В DirectWrite применяется метод SetTypography из IDWriteTextLayout в сочетании с объектом IDWriteTypography, который в конечном счете ссылается на член обширного перечисления DWRITE_FONT_FEATURE_TAG. Но эти способы не столь определенны, как точно указание глифов.

Как получить курсив и полужирный с помощью DrawGlyphRun? Во многих случаях разные файлы шрифта содержат курсивное и полужирное начертания шрифта. Среди шрифтов, включенных в программу GlyphDump, есть пара шрифтов Bold и шрифт Light. Однако вы также можете имитировать наклонные и полужирные символы, указывая соответствующие флаги DWRITE_FONT_SIMULATIONS в CreateFontFace.

Продвижения и смещения

И GlyphDump, и FancyTitle устанавливают два поля в DWRITE_GLYPH_RUN в nullptr. Эти поля называются glyphAdvances и glyphOffsets.

Отображая массив глифов, вы указываете начальную позицию базовой линии первого символа слева. Для каждого последующего символа горизонтальная координата позиции автоматически увеличивается на основе ширины символа. (Аналогичный процесс происходит при отображении направленного в сторону текста.) Это приращение называется продвижением (advance).

Вы можете получать продвижения, используемые DirectX для знаков ненулевой ширины (spacing characters), вызовом метода GetDesignGlyphMetrics из IDWriteFontFace. Результатом является массив структур DWRITE_GLYPH_METRICS — по одной для каждого индекса глифа, в котором вы заинтересованы. Поле advanceWidth этой структуры указывает продвижение относительно значению поля designUnitsPerEm структуры DWRITE_FONT_METRICS, возвращаемой методом GetMetrics в IDWriteFontFace (оно также включает вертикальные метрики, применимые ко всем глифам в рамках конкретного шрифта).

Или можно вызвать метод GetDesignGlyphAdvances интерфейса IDWriteFontFace1, который предоставляет продвижения относительно значения designUnitsPerEm. Разделите значения на designUnitsPerEm (которое часто представляет собой такое ровное значение, как 2048), а затем умножьте на ширину самой широкой буквы (em size), заданной в DWRITE_GLYPH_RUN.

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

Поле glyphOffsets — это массив структур DWRITE_GLYPH_OFFSET, по одной на каждый символ. Два поля — advanceOffset и ascenderOffset — указывают нужное смещение (вправо и вверх соответственно) относительно нормальной позиции символа. Этот механизм часто применяется в XPS-файлах для размещения множества глифов конкретного шрифта по всей странице.

Программа CenteredGlyphDump демонстрирует, как использовать поле glyphOffsets для отображения всего массива глифов из файла конкретного шрифта одним вызовом DrawGlyphRun:

context->DrawGlyphRun(Point2F(), &m_glyphRun, m_blackBrush.Get());

Координаты, передаваемые в DrawGlyphRun, — (0, 0), и glyphAdvances присваивается массив нулевых значений, чтобы запретить продвижение последующих глифов. Позиция каждого глифа полностью управляется glyphOffsets. Эта позиция основана на метриках глифа, чтобы центрировать каждый глиф в своем столбце. Результат показан на рис. 4.

Программа CenteredGlyphDump
Рис. 4. Программа CenteredGlyphDump

Собственный загрузчик шрифтов

Если файл шрифта является частью пакета приложения, то получить файловый путь для использования с CreateFontReference довольно легко. Но как быть, если ваш шрифт находится в XPS- или EPUB-пакете либо, возможно, в изолированном хранилище или в облаке?

Пока у вас есть возможность писать код, который обращается к файлу шрифта, до тех пор и DirectX может обращаться к нему. Вам потребуется предоставить два класса: один, который реализует IDWriteFontFileLoader, и другой, который реализует IDWriteFontFileStream. Обычно класс, реализующий IDWriteFontFileLoader, является синглтоном (singleton), который обращается ко всем файлам шрифтов, нужных вашему приложению, и назначает каждому из них свой ключ. Метод CreateStreamFromKey в вашей реализации IDWriteFontFileLoader возвращает экземпляр IDWriteFontStream для каждого файла шрифта.

Чтобы использовать эти два класса, вы сначала создаете единственный экземпляр класса, реализующего IDWriteFontFileLoader, и передаете его в RegisterFontFileLoader вашего объекта IDWriteFactory. Затем вместо вызова CreateFontFileReference для получения объекта IDWriteFontFile вызовите CreateCustomFontFileReference с этим экземпляром IDWriteFontFileLoader и ключом, идентифицирующим нужным вам файл шрифта.

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

Весьма вероятно, что вашей реализации IDWriteFontFileLoader понадобятся вызовы для файлового ввода-вывода, и в приложении Windows Store эти вызовы должны быть асинхронными. Поэтому имеет смысл определить в классе IDWriteFontFileLoader асинхронный метод, который загружает все шрифты в память, а в IDWriteFontFileStream сделать так, чтобы он просто возвращал указатели на эти блоки памяти. Этот подход я взял на вооружение в PrivateFontFileLoader и PrivateFontFileStream после внимательного изучения Direct2D Magazine App Sample и других примеров кода от Microsoft для Windows 8.1.

Чтобы ключ идентифицировал каждый файл, я использовал имя файла. По окончании асинхронного метода загрузки в PrivateFontFileLoader программа CenteredGlyphDump получает эти имена файлов для ListBox. Вот почему на рис. 4 отображаются только имена файлов. Программа остается в неведении о названиях семейств шрифтов, связанных с каждым файлом.

От символов к глифам

Конечно, отображение текста путем ссылки на глифы прекрасно работает, если вы точно знаете, какие глифы вам нужны, но можно ли выводить обычные Unicode-символы с помощью DrawGlyphRun?

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

Но коды символов, передаваемые GetGlyphIndices, находятся в такой форме, которая, возможно, уникальна для всей Windows. Вместо 16-битных кодов символов (как в случае нормальной работы со строками Unicode-символов) вам нужно создать массив 32-битных кодов символов. Как вы, вероятно, знаете, Unicode — это на самом деле набор 21-битных символов, но сами символы обычно хранятся как 16-битные значения (UTF-16), т. е. некоторые символы состоят из двух 16-битных значений. Но GetGlyphIndices ожидает передачи 32-битных значений. Если ваша строка символов не имеет кодов со значениями выше 0xFFFF, вы можете просто передавать значения из одного массива в другой.

Если вы имеете дело с обычными символами из латинского алфавита, то можете также предположить, что между символами и глифами существует соответствие «один к одному». Иначе вам могло бы понадобиться создание большего выходного массива для приема индексов.

Этот простой прием демонстрируется проектом HelloGlyphRun. На рис. 5 показан код, который загружает файл шрифта и настраивает структуру DWRITE_GLYPH_RUN. Метод Update модифицирует смещения глифов, чтобы добиться нечто вроде эффекта пульсации текста.

Рис. 5. Определение DWRITE_GLYPH_RUN из строки символов

// Преобразуем строку в индексы глифов
std::wstring str = L"Hello, Glyph Run!";
uint32 glyphCount = str.length();
std::vector<uint32> str32(glyphCount);
for (uint32 i = 0; i < glyphCount; i++)
     str32[i] = str[i];
m_glyphIndices = std::vector<uint16>(glyphCount);
m_fontFace->GetGlyphIndices(str32.data(), glyphCount, m_glyphIndices.data());
// Создаем массив для смещений (задаются при выполнении Update)
m_glyphOffsets = std::vector<DWRITE_GLYPH_OFFSET>(glyphCount);
// Получаем границы вывода
Windows::Foundation::Size outputBounds = m_deviceResources->GetOutputBounds();
// Определяем поля структуры DWRITE_GLYPH_RUN
m_glyphRun.fontFace = m_fontFace.Get();
m_glyphRun.fontEmSize = outputBounds.Width / 8; // эмпирически
m_glyphRun.glyphCount = glyphCount;
m_glyphRun.glyphIndices = m_glyphIndices.data();
m_glyphRun.glyphAdvances = nullptr;
m_glyphRun.glyphOffsets = m_glyphOffsets.data();
m_glyphRun.isSideways = false;
m_glyphRun.bidiLevel = 0;

Хотя вы, наверное, будете использовать DrawGlyphRun только с файлами шрифтов, которые вам хорошо известны, можно определить, какой тип глифов содержится в конкретном файле шрифта в период выполнения. IDWriteFontFace определяет метод TryGetFontTable, который обращается к таблицам в файле OpenFont. Используйте тег cmap для таблицы «символ к глифу» и GSUB для таблицы подстановки глифов, но будьте готовы потратить уйму времени на изучение спецификации OpenType, чтобы научиться успешно читать эти таблицы.

Глифы в системных шрифтах

Годится ли DrawGlyphRun только для предоставляемых вами файлов шрифтов? Поначалу кажется, что это именно так, но вы можете использовать их с системными шрифтами. Процедура такова. С помощью метода GetSystemFontCollection в IDWriteFactory получите объект IDWriteFontCollection. Этот объект позволяет найти все названия семейств, сопоставленных со шрифтами, которые установлены в системе. Метод GetFontFamily в IDWriteFontCollection возвращает объект типа IDWriteFontFamily. Из него можно вызвать GetMatchingFonts или GetFirstMatchingFont, чтобы скомбинировать семейство шрифтов с атрибутами italic, bold и stretch для получения IDWriteFont.

Получив объект IDWriteFont, вызовите CreateFontFace, чтобы получить объект IDWriteFontFace. Это объект того же типа, возвращавшегося CreateFontFace в более ранних программах! Располагая этим объектом, вы можете начать настройку структуры DWRITE_GLYPH_RUN для DrawGlyphRun.

Сказанное демонстрируется в проекте SystemFontGlyphs. Этот проект использует GetSystemFontCollection в своем классе DirectXPage для заполнения ListBox названиями семейств системных шрифтов. При выборе какого-либо элемента из списка создается производный объект IDWriteFontFace, который передается рендеру.

Класс SystemFontGlyphsRenderer формирует структуру DWRITE_GLYPH_RUN на основе текста «Annoying vibrating text effect». Затем метод Update присваивает значениям массива смещений глифов случайно выбираемые числа в диапазоне от –3 до 3, создавая тем самым впечатление, будто все символы текстовой строки независимо вибрируют.

Концептуально IDWriteFont и IDWriteFontFace различаются несильно: IDWriteFont всегда представляет шрифт, который является частью набора шрифтов (например набора системных шрифтов), а IDWriteFontFace — не всегда. В IDWriteFontCollection есть метод GetFontFromFontFace, который принимает IDWriteFontFace и возвращает соответствующий IDWriteFont, но это работает, только если файл шрифта, сопоставленный с IDWriteFontFace, является частью набора шрифтов.

Пользовательские наборы шрифтов

Теперь вы знаете, как использовать DrawGlyphRun с файлами шрифтов, загружаемых вашим приложением, а также с системными шрифтами. А можно ли применять методы обычного вывода текста (DrawText и DrawTextLayout) с вашими файлами шрифтов?

Да, можно. Вспомните, что и DrawText, и DrawTextLayout требуют передачи объекта IDWriteTextFormat. Вы создаете его методом CreateTextFormat из IDWriteFactory, указывая как название семейства шрифтов, так и набор шрифтов.

Обычно вы подставляете nullptr вместо аргумента набора шрифтов, чтобы выбрать системные шрифты. Но вы можете создать собственный набор шрифтов, вызвав метод CreateCustomFontCollection из IDWriteFactory. Вы должны предоставить свой класс, реализующий интерфейс IDWriteFontCollectionLoader, и еще один класс, который реализует IDWriteFontFileEnumerator.

Это демонстрируется в программе ResourceFontLayout. Эта программа также включает реализации интерфейсов IDWriteFontFileLoader и IDWriteFontFileStream из CenteredGlyphDump. Получив список семейств шрифтов из этого пользовательского набора шрифтов, вы заметите, что несколько файлов шрифтов иногда объединяются в одно семейство шрифтов.

Приз в самом низу ящика

К этому моменту вам должна быть очевидна важность IDWriteFontFace. Вы можете создать объект IDWriteFontFace двумя способами: либо напрямую из файла шрифта, либо выбором конкретного шрифта из набора шрифтов. Затем вы ссылаетесь на этот IDWriteFontFace в структуре DWRITE_GLYPH_RUN или используете его для получения метрик глифов либо для доступа к таблицам в файле шрифта.

В IDWriteFontFace также определен метод GetGlyphRunOutline. Аргументы этого метода очень похожи на поля структуры DWRITE_GLYPH_RUN, но он принимает дополнительный аргумент — IDWriteGeometrySink, который представляет собой то же самое, что и ID2D1SimplifiedGeometrySink.

Это означает, что вы можете преобразовать текстовую строку в ID2D1PathGeometry, а затем выполнять ее рендеринг и манипулировать ее геометрией как угодно. На рис. 6 приведен экранный снимок из программы OutlinedCharacters, которая показывает геометрические элементы символов: сначала они штрихуются и заполняются, потом выполняется их рендеринг с точечным стилем (точки анимируются так, чтобы казалось, будто они перемещаются по символам) и, наконец, геометрические элементы расширяются и обводятся, что фактически означает оконтуривание контуров.

Программа OutlinedCharacters
Рис. 6. Программа OutlinedCharacters

И это я только начал…


Чарльз Петцольд (Charles Petzold) — давний «пишущий» редактор MSDN Magazine и автор книги «Programming Windows, 6th edition» (O’Reilly Media, 2012) о написании приложений для Windows 8. Его веб-сайт находится по адресу charlespetzold.com.

Выражаю благодарность за рецензирование статьи экспертам Microsoft Джиму Галасину (Jim Galasyn) и Джастину Пеньяну (Justin Panian).