Выбор шрифта
Интерфейс IDWriteFontSet4 предоставляет методы выбора шрифтов из набора шрифтов. Эти методы позволяют перейти в модель семейства шрифтов типографических шрифтов при сохранении совместимости с существующими приложениями, документами и шрифтами.
Выбор шрифта (иногда называемый сопоставлением шрифтов или сопоставление шрифтов) — это процесс выбора доступных шрифтов, которые лучше всего соответствуют входным параметрам, переданным приложением. Входные параметры иногда называются логическим шрифтом. Логический шрифт содержит имя семейства шрифтов и другие атрибуты, указывающие на конкретный шрифт в семействе. Алгоритм выбора шрифта соответствует логическому шрифту ("шрифту, который требуется") доступному физическому шрифту ("шрифт, который у вас есть").
Семейство шрифтов представляет собой именованную группу шрифтов, которые имеют общий дизайн, но могут отличаться в таких атрибутах, как вес. Модель семейства шрифтов определяет, какие атрибуты можно использовать для отличия шрифтов в семействе. Новая модель семейства шрифтов типографических шрифтов имеет множество преимуществ по сравнению с двумя предыдущими моделями семейства шрифтов, используемыми в Windows. Но изменение моделей семейства шрифтов создает возможности для путаницы и проблем совместимости. Методы, предоставляемые интерфейсом IDWriteFontSet4, реализуют гибридный подход, который предлагает преимущества модели семейства типографических шрифтов при устранении проблем совместимости.
В этом разделе сравниваются старые модели семейства шрифтов с моделью семейства шрифтов типографических шрифтов; он объясняет проблемы совместимости, связанные с изменением моделей семейства шрифтов; и, наконец, объясняет, как эти проблемы можно преодолеть с помощью методов [IDWriteFontSet4](/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset4).
Модель семейства шрифтов RBIZ
Де-факто модель семейства шрифтов, используемая в экосистеме приложений GDI, иногда называется моделью четырех шрифтов или моделью RBIZ. Каждое семейство шрифтов в этой модели обычно имеет не более четырех шрифтов. Метка "RBIZ" поставляется из соглашения об именовании, используемого для некоторых файлов шрифтов, например:
Имя файла | Стиль шрифта |
---|---|
verdana.ttf | Регулярный |
verdanab.ttf | Смелый |
verdanai.ttf | Курсив |
verdanaz.ttf | Полужирный курсив |
При использовании GDI входные параметры, используемые для выбора шрифта, определяются структурой LOGFONT, которая включает имя семейства (lfFaceName
), вес (lfWeight
) и курсив (lfItalic
) полей. Поле lfItalic
имеет значение TRUE или FALSE. GDI позволяет lfWeight
поле быть любым значением в диапазоне FW_THIN (100) FW_BLACK (900), но по историческим причинам шрифты уже давно разработаны таким образом, что в одном семействе шрифтов GDI не более двух весов.
Популярные пользовательские интерфейсы приложений с раннего начала включали курсивную кнопку (чтобы включить курсив и отключить) и полужирную кнопку (переключиться между обычными и полужирными весами). Использование этих двух кнопок для выбора шрифтов в семействе предполагает модель RBIZ. Таким образом, несмотря на то, что GDI поддерживает более двух весов, разработчики шрифтов с поддержкой совместимости приложений устанавливают имя семейства GDI (openType name ID 1) таким образом, что было согласовано с моделью RBIZ.
Например, предположим, что вы хотите добавить более тяжелый вес "Черный" в семейство шрифтов Arial. Логически этот шрифт является частью семейства Arial, поэтому его можно выбрать, задав lfFaceName
значение "Arial" и lfWeight
значение FW_BLACK. Однако пользователю приложения не нужно выбирать между тремя весами с помощью полужирной кнопки с двумя состояниями. Решением было дать новому шрифту другое семейное имя, чтобы пользователь мог выбрать его, выбрав "Arial Black" в списке семейств шрифтов. Аналогичным образом, нет способа выбрать один из разных ширин в одном семействе шрифтов, используя только полужирные и курсивные кнопки, поэтому узкие версии Arial имеют разные имена семейств в модели RBIZ. Таким образом, у нас есть "Arial", "Arial Black" и "Arial Narrow" шрифты в модели RBIZ, несмотря на то, что типографически эти все принадлежат в одной семье.
В этих примерах можно увидеть, как ограничения модели семейства шрифтов могут повлиять на группирование шрифтов в семейства. Так как семейства шрифтов определяются по имени, это означает, что один и тот же шрифт может иметь разные имена семейств в зависимости от используемой модели семейства шрифтов.
DirectWrite не поддерживает модель семейства шрифтов RBIZ напрямую, но предоставляет методы преобразования в модель RBIZ, например IDWriteGdiInterop::CreateFontFromLOGFONT и IDWriteGdiInterop::ConvertFontToLOGFONT. Вы также можете получить имя семейства шрифта RBIZ, вызвав метод IDWriteFont::GetInformationalStrings и указав DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.
Модель семейства шрифтов в стиле weight-stretch
Модель семейства шрифтов в стиле весового стиля — это исходная модель семейства шрифтов, используемая DirectWrite до появления модели семейства шрифтов типографических шрифтов. Он также называется наклоном ширины веса (WWS). В модели WWS шрифты в одной семье могут отличаться тремя свойствами: вес (DWRITE_FONT_WEIGHT), stretch (DWRITE_FONT_STRETCH) и стилем (DWRITE_FONT_STYLE).
Модель WWS является более гибкой, чем модель RBIZ двумя способами. Во-первых, шрифты в одной семье можно различать по растяжениям (или ширине), а также по весу и стилю (регулярным, курсивным или косым). Во-вторых, в одной семье может быть более двух весов. Эта гибкость достаточна, чтобы позволить всем вариантам Arial быть включены в одну и ту же семейство WWS. В следующей таблице сравниваются свойства шрифта RBIZ и WWS для выбора шрифтов Arial:
Полное имя | Имя семейства RBIZ | lfWeight | lfItalic | Имя семейства WWS | Вес | Натягивать | Стиль |
---|---|---|---|---|---|---|---|
Arial | Arial | 400 | 0 | Arial | 400 | 5 | 0 |
Ариальный полужирный | Arial | 700 | 0 | Arial | 700 | 5 | 0 |
Arial Black | Arial Black | 900 | 0 | Arial | 900 | 5 | 0 |
Arial Narrow | Arial Narrow | 400 | 0 | Arial | 400 | 3 | 0 |
Ариальный узкий полужирный | Arial Narrow | 700 | 0 | Arial | 700 | 3 | 0 |
Как видно, "Arial Narrow" имеет те же lfWeight
и lfItalic
значения, что и "Arial", поэтому он имеет другое имя семейства RBIZ, чтобы избежать неоднозначности. "Arial Black" имеет другое имя семьи RBIZ, чтобы избежать более двух весов в семье "Arial". Напротив, все эти шрифты находятся в одном семействе стилей веса.
Тем не менее, модель стиля весовой растяжения не является открытой. Если два шрифта имеют одинаковый вес, растяжение и стиль, но отличаются по-другому (например, оптический размер), то они не могут быть включены в одно семейство шрифтов WWS. Это приводит нас к модели семейства шрифтов типографических шрифтов.
Модель семейства шрифтов типографических шрифтов
В отличие от своих предшественников, модель семейства шрифтов типографических шрифтов открытой. Он поддерживает любое количество осей вариантов в семействе шрифтов.
Если вы считаете параметры выбора шрифта координатами в пространстве конструктора, модель многомерной координаты определяет трехмерную систему координат с весом, растяжением и стилем в виде осей. Каждый шрифт в семействе WWS должен иметь уникальное расположение, определенное его координатами вдоль этих трех осей. Чтобы выбрать шрифт, укажите имя семейства WWS и вес, растяжение и параметры стиля.
В отличие от этого, модель семейства шрифтов типографических шрифтов имеет пространство N-мерного дизайна. Конструктор шрифтов может определить любое количество осей конструктора, каждое из которых определяется четырехзначным тегом тегом оси. Расположение заданного шрифта в пространстве N-мерного конструктора определяется массивом значений осей , где каждое значение оси состоит из тега оси и значения с плавающей запятой. Чтобы выбрать шрифт, укажите типографическое имя семейства и массив значений осей (DWRITE_FONT_AXIS_VALUE структуры).
Хотя число осей шрифта открыто завершено, существует несколько зарегистрированных осей со стандартными значениями, а значения веса, растяжения и стиля можно сопоставить с зарегистрированными значениями осей. DWRITE_FONT_WEIGHT можно сопоставить со значением оси "wght" (DWRITE_FONT_AXIS_TAG_WEIGHT) . DWRITE_FONT_STRETCH можно сопоставить со значением оси wdth (DWRITE_FONT_AXIS_TAG_WIDTH) . DWRITE_FONT_STYLE можно сопоставить с сочетанием значений оси "ital" и "slnt" (DWRITE_FONT_AXIS_TAG_ITALIC и DWRITE_FONT_AXIS_TAG_SLANT).
Другая зарегистрированная ось — "opsz" (DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE). Оптическое семейство шрифтов, например Sitka, включает шрифты, которые отличаются вдоль оси "opsz", то есть они предназначены для использования в разных размерах точек. Модель семейства шрифтов WWS не имеет оптической оси размера, поэтому семейство шрифтов Sitka должно быть разделено на несколько семейств шрифтов WWS: "Sitka Small", "Sitka Text", "Sitka Subheading" и т. д. Каждое семейство шрифтов WWS соответствует другому оптическому размеру, и пользователю остается указать правое имя семейства WWS для заданного размера шрифта. При использовании модели семейства шрифтов типографических шрифтов пользователь может просто выбрать "Sitka", а приложение может автоматически задать значение оси opsz на основе размера шрифта.
Выбор типографических шрифтов и переменных шрифтов
Концепция осей вариантов часто связана с переменными шрифтами, но она также применяется к статическим шрифтам. В таблице STAT (атрибуты стиля) OpenType объявляется, какие оси конструктора имеют шрифт и значения этих осей. Эта таблица необходима для шрифтов переменных, но также относится к статическим шрифтам.
API DirectWrite предоставляет значения осей "wght", "wdth", "ital" и "slnt" для каждого шрифта, даже если они отсутствуют в таблице STAT или нет таблицы STAT. Эти значения по возможности являются производными от таблицы STAT. В противном случае они являются производными от веса шрифта, растяжения шрифта и стиля шрифта.
Оси шрифтов могут быть переменными или не переменными. Статический шрифт имеет только оси, отличные от переменных, в то время как шрифт переменной может иметь оба. Чтобы использовать шрифт переменной, необходимо создать экземпляре переменной, в котором все оси переменной привязаны к определенным значениям. Интерфейс IDWriteFontFace представляет статический шрифт или конкретный экземпляр шрифта переменной. Можно создать произвольный экземпляр шрифта переменной с указанными значениями оси. Кроме того, шрифт переменной может объявлять именованные экземпляры в таблице STAT с предопределенными сочетаниями значений осей. Именованные экземпляры позволяют использовать шрифт переменной так же, как коллекцию статических шрифтов. При перечислении элементов IDWriteFontFamily или IDWriteFontSetсуществует один элемент для каждого статического шрифта и для каждого именованного экземпляра шрифта переменной.
Алгоритм сопоставления шрифтов для типографических шрифтов сначала выбирает потенциальных кандидатов соответствия на основе имени семейства. Если кандидаты соответствия включают шрифты переменных, все кандидаты соответствия для одного и того же шрифта переменной свернуты в один кандидат соответствия, в котором каждая ось переменной назначается определенное значение как можно ближе к запрошенному значению этой оси. Если для оси переменной нет запрошенного значения, оно назначается значение по умолчанию для этой оси. Затем порядок кандидатов соответствия определяется путем сравнения значений их оси с запрошенными значениями оси.
Например, рассмотрим семейство типографических типов Sitka в Windows. Sitka представляет собой оптическое семейство шрифтов, то есть имеет ось "opsz". В Windows 11 Sitka реализуется как два шрифта переменной со следующими значениями оси. Обратите внимание, что opsz
и wght
оси являются переменными, а другие оси являются не переменными.
Имя файла | "opsz" | "wght" | "wdth" | "ital" | "slnt" |
---|---|---|---|---|---|
SitkaVF.ttf | 6-27.5 | 400-700 | 100 | 0 | 0 |
SitkaVF-Italic.ttf | 6-27.5 | 400-700 | 100 | 1 | -12 |
Предположим, что запрошенные значения осей opsz:12 wght:475 wdth:100 ital:0 slnt:0
. Для каждого шрифта переменной мы создадим ссылку на шрифт переменной экземпляр, в котором каждая ось переменной назначается определенное значение. А именно, opsz
и wght
оси имеют значение 12
и 475
соответственно. Это дает следующие соответствующие шрифты, причем не курсивный шрифт ранжирован первым, так как это лучше подходит для ital
и slnt
осей:
SitkaVF.ttf opsz:12 wght:475 wdth:100 ital:0 slnt0
SitkaVF-Italic.ttf opsz:12 wght:475 wdth:100 ital:1 slnt:-12
В приведенном выше примере соответствующие шрифты являются произвольными экземплярами шрифтов переменных. Нет именованного экземпляра Sitka с весом 475. В отличие от этого, алгоритм сопоставления по стилю веса возвращает только именованные экземпляры.
Порядок сопоставления шрифтов
Существуют различные перегруженные методы GetMatchingFonts для модели семейства шрифтов в стиле весов (IDWriteFontFamily::GetMatchingFonts) и типографической модели семейства шрифтов (IDWriteFontCollection2::GetMatchingFonts). В обоих случаях выходные данные — это список соответствующих шрифтов в порядке убывания приоритета на основе того, насколько хорошо каждый кандидат соответствует входным свойствам. В этом разделе описывается определение приоритета.
В модели стиля stretch-stretch входные параметры — это вес шрифта (DWRITE_FONT_WEIGHT), растяжение шрифта (DWRITE_FONT_STRETCH) и стиль шрифта (DWRITE_FONT_STYLE). Алгоритм поиска лучшего совпадения был задокументирован в техническом документе 2006 года под названием "Модель выбора шрифтов WPF" Михаилом Леоновым и Дэвидом Брауном. См. раздел "Сопоставление лица из списка лиц кандидата". В этой статье речь шла о Windows Presentation Foundation (WPF), но DirectWrite позже использовал тот же подход.
Алгоритм использует понятие вектора атрибута шрифта ,, который для заданного сочетания веса, растяжения и стиля вычисляется следующим образом:
FontAttributeVector.X = (stretch - 5) * 1100;
FontAttributeVector.Y = style * 700;
FontAttributeVector.Z = (weight - 400) * 5;
Обратите внимание, что каждая координата вектора нормализуется путем вычитания значения "обычный" для соответствующего атрибута и умножения на константу. Умножения компенсируют тот факт, что диапазоны входных значений для веса, растяжения и стиля очень отличаются. В противном случае вес (100.999) будет доминировать над стилем (0.2).
Для каждого кандидата соответствия расстояние вектора и точка продукта вычисляются между вектором атрибута шрифта соответствия и вектором входного атрибута шрифта. При сравнении двух кандидатов совпадений кандидат с меньшим расстоянием вектора является лучшим совпадением. Если расстояние совпадает, кандидат с меньшей точкой продукта лучше подходит. Если продукт точки также совпадает, расстояния вдоль осей X, Y и Z сравниваются в этом порядке.
Сравнение расстояний достаточно интуитивно понятно, но использование dot product в качестве вторичной меры может потребовать некоторого объяснения. Предположим, что входной вес полуболд (600), а два кандидата — черные (900) и полусветные (300). Расстояние каждого кандидата от входного веса совпадает, но черный вес находится в том же направлении от источника (то есть 400 или нормально), поэтому он будет иметь меньший точечный продукт.
Алгоритм сопоставления типографических типов — это обобщение одного для стиля весового растяжения. Каждое значение оси рассматривается как координата в векторе атрибута N-мерного шрифта. Для каждого кандидата соответствия расстояние вектора и точка продукта вычисляются между вектором атрибута шрифта соответствия и вектором входного атрибута шрифта. Кандидат с меньшим расстоянием вектора является лучшим совпадением. Если расстояние совпадает, кандидат с меньшей точкой продукта лучше подходит. Если точечный продукт также совпадает, присутствие в указанной семействе стилей весовой растяжения можно использовать в качестве разбиения галстука.
Чтобы вычислить расстояние вектора и точка продукта, вектор атрибута шрифта соответствующего кандидата и вектор атрибута входного шрифта должны иметь одинаковые оси. Таким образом, любое отсутствующее значение оси в любом векторе заполняется путем замены стандартного значения для этой оси. Координаты вектора нормализуются путем вычитания стандартного (или "нормального") значения соответствующей оси и умножения результата на умножение на ось. Ниже приведены умножения и стандартные значения для каждой оси:
Ось | Множитель | Стандартное значение |
---|---|---|
"wght" | 5 | 400 |
"wdth" | 55 | 100 |
"ital" | 1400 | 0 |
"slnt" | 35 | 0 |
"opsz" | 1 | 12 |
другой | 1 | 0 |
Умножения соответствуют тем, которые используются алгоритмом стиля весовой растяжения, но масштабируются по мере необходимости. Например, обычная ширина равна 100, что эквивалентно растяжениям 5. Это дает умножение 55 и 1100. Атрибут устаревшего стиля (0.2) запутывает курсив и косость, которые в типографической модели разлагаются на ось "ital" (0.1) и "slnt" (-90.90.90). Выбранные умножители для этих двух осей дают эквивалентные результаты устаревшему алгоритму, если предполагается, что по умолчанию 20-градусный косой для косых шрифтов.
Выбор шрифта и оптический размер шрифта
Приложение, использующее модель семейства шрифтов типографических шрифтов, может реализовать оптическое изменение размера, указав значение оси opsz
в качестве параметра выбора шрифта. Например, приложение обработки слов может указать значение оси opsz
, равное размеру шрифта в точках. В этом случае пользователь может выбрать "Sitka" в качестве семейства шрифтов, а приложение автоматически выберет экземпляр Sitka с правильным значением оси opsz
. В модели WWS каждый оптический размер предоставляется в виде другого имени семьи, и это зависит от пользователя, чтобы выбрать правильный.
В теории, можно реализовать автоматическое оптическое изменение размера в модели стиля весовой растяжения, переопределив значение оси opsz
как отдельный шаг после выбора шрифта. Однако это работает только в том случае, если первый соответствующий шрифт является переменным шрифтом с переменной opsz
оси. Указание opsz
в качестве параметра выбора шрифта работает одинаково хорошо для статических шрифтов. Например, семейство шрифтов Sitka реализуется как переменные шрифты в Windows 11, но как коллекция статических шрифтов в Windows 10. Статические шрифты имеют разные, не перекрывающиеся opsz
диапазоны осей (они объявляются как диапазоны для целей выбора шрифтов, но они не являются переменными осями). Указание opsz
в качестве параметра выбора шрифта позволяет выбрать правильный статический шрифт для выбранного оптического размера.
Преимущества выбора типографического шрифта и проблемы совместимости
Модель выбора типографических шрифтов имеет несколько преимуществ по сравнению с более ранними моделями, но в чистой форме она имеет некоторые потенциальные проблемы совместимости. В этом разделе описываются преимущества и проблемы совместимости. В следующем разделе описывается модель выбора гибридного шрифта, которая сохраняет преимущества при устранении проблем совместимости.
Преимущества модели семейства типографических шрифтов:
Шрифты можно сгруппировать в семейства, как это предназначено конструктором, а не разделить на подфамили из-за ограничений модели семейства шрифтов.
Приложение может автоматически выбрать правильное значение оси
opsz
на основе размера шрифта, а не предоставлять пользователю разные оптические размеры в виде различных семейств шрифтов.Можно выбрать произвольные экземпляры шрифтов переменных. Например, если шрифт переменной поддерживает весовые значения в непрерывном диапазоне 100–900, типографическая модель может выбрать любой вес в этом диапазоне. Старые модели семейства шрифтов могут выбирать только ближайший вес из именованных экземпляров, определенных шрифтом.
Проблемы совместимости с моделью выбора типографического шрифта:
Некоторые старые шрифты не могут быть выбраны однозначно с помощью только типографических имен семейства и значений оси.
Существующие документы могут ссылаться на шрифты по имени семейства WWS или имени семейства RBIZ. Пользователи также могут ожидать использования имен семейств WWS и RBIZ. Например, документ может указать "Подзаголовок Sitka" (имя семейства WWS) вместо "Sitka" (типографическое имя семейства).
Библиотека или платформа могут применять модель семейства шрифтов типографических шрифтов, чтобы воспользоваться преимуществами автоматического оптического изменения размера, но не предоставить API для определения произвольных значений осей. Даже если предоставляется новый API, платформа может потребоваться работать с существующими приложениями, которые указывают только параметры веса, растяжения и стиля.
Проблема совместимости со старыми шрифтами возникает из-за того, что концепция типографического семейства предопределяет концепцию значений осей шрифтов, которые были введены вместе с переменными шрифтами в OpenType 1.8. До OpenType 1.8 имя типографической семьи просто выразило намерение конструктора о том, что набор шрифтов был связан, но без гарантии, что эти шрифты могут быть программно дифференцироваться на основе их свойств. В качестве гипотетического примера предположим, что все следующие шрифты имеют типографическое имя семейства "Устаревшая":
Полное имя | Семья WWS | Вес | Натягивать | Стиль | Типо семейство | wght | wdth | идаль | slnt |
---|---|---|---|---|---|---|---|---|---|
Наследство | Наследство | 400 | 5 | 0 | Наследство | 400 | 100 | 0 | 0 |
Устаревший полужирный | Наследство | 700 | 5 | 0 | Наследство | 700 | 100 | 0 | 0 |
Устаревшее черное | Наследство | 900 | 5 | 0 | Наследство | 900 | 100 | 0 | 0 |
Устаревшие программные | Устаревшие программные | 400 | 5 | 0 | Наследство | 400 | 100 | 0 | 0 |
Устаревший мягкий полужирный | Устаревшие программные | 700 | 5 | 0 | Наследство | 700 | 100 | 0 | 0 |
Устаревшее мягкое черное | Устаревшие программные | 900 | 5 | 0 | Наследство | 900 | 100 | 0 | 0 |
Типографическая семья "Устаревшая" имеет три веса, и каждый вес имеет регулярные и мягкие варианты. Если бы они были новыми шрифтами, они могут быть реализованы как объявление оси soft design. Однако эти шрифты предопределяют OpenType 1.8, поэтому их единственные оси дизайна являются производными от веса, растяжения и стиля. Для каждого веса семейство шрифтов имеет два шрифта с одинаковыми значениями осей, поэтому однозначно выбрать шрифт в этой семье невозможно, используя только значения осей.
Алгоритм выбора гибридных шрифтов
API выбора шрифтов, описанные в следующем разделе, используют алгоритм выбора гибридных шрифтов, который сохраняет преимущества выбора типографического шрифта при устранении проблем совместимости.
Выбор гибридного шрифта обеспечивает мост от старых моделей семейства шрифтов, позволяя сопоставлять значения шрифта, растяжения шрифта и стиля шрифта соответствующим значениям оси шрифта. Это помогает устранить проблемы с совместимостью документов и приложений.
Кроме того, алгоритм выбора гибридных шрифтов позволяет указать имя семейства типографических типов, имя семейства стилей весов, имя семейства GDI/RBIZ или полное имя шрифта. Сопоставление выполняется одним из следующих способов в порядке убывания приоритета:
Имя соответствует типографической семье (например, Sitka). Сопоставление происходит в семействе типографических типов и используются все запрошенные значения осей. Если имя также соответствует подсемейству WWS (т. е. одно меньше семейства типографических), то членство в подсемействе WWS используется в качестве разбиения связи.
Имя соответствует семейству WWS (например, Sitka Text). Сопоставление происходит в семействе WWS и запрашиваемые значения осей, отличные от "wght", "wdth", "ital" и "slnt", игнорируются.
Имя соответствует семейству GDI (например, Schrift Condensed). Сопоставление происходит в семействе RBIZ и запрашиваемых значений осей, отличных от "wght", "ital" и "slnt", игнорируются.
Имя соответствует полному имени (например, Schrift Bold Condensed). Возвращается шрифт, соответствующий полному имени. Запрошенные значения оси игнорируются. Сопоставление по полному имени шрифта допускается, так как GDI поддерживает его.
В предыдущем разделе описана неоднозначная типографическая семья с именем "Устаревшая версия". Гибридный алгоритм позволяет избежать неоднозначности путем указания "Устаревший" или "Устаревший обратимый" в качестве имени семейства. Если указан параметр Legacy Soft, то неоднозначность отсутствует, так как сопоставление происходит только в семействе WWS. Если "Устаревшая" спецификация, все шрифты в семействе типографических типов считаются подходящими кандидатами, но неоднозначность избегается с помощью членства в семействе WWS "Устаревшая" в качестве разбиения галстука.
Предположим, что документ задает имя семейства и вес, растяжение и параметры стиля, но не значения оси. Приложение может сначала преобразовать вес, растяжение, стиль и размер шрифта в значения оси, вызвав IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. Затем приложение может передать значения имени семейства и оси в IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts возвращает список соответствующих шрифтов в порядке приоритета, и результат подходит, является ли указанное имя семейства типографическим именем, именем семейства в стиле весов, именем семейства RBIZ или полным именем. Если указанная семья имеет ось opsz, то соответствующий оптический размер автоматически выбирается на основе размера шрифта.
Предположим, что документ указывает вес, растяжение и стиль, а также также задает значения осей. В этом случае явные значения оси также можно передать в IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues, а производные значения осей, возвращаемые методом, будут содержать только оси шрифтов, которые не были явно указаны. Таким образом, значения осей, указанные явно документом (или приложением), имеют приоритет над значениями осей, производными от веса, растяжения, стиля и размера шрифта.
API выбора гибридных шрифтов
Модель выбора гибридного шрифта реализуется следующими методами IDWriteFontSet4:
Метод IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues преобразует размер шрифта, вес, растяжение и параметры стиля в соответствующие значения оси. Все явные значения оси, передаваемые клиентом, исключаются из производных значений оси.
Метод IDWriteFontSet4::GetMatchingFonts возвращает приоритетный список соответствующих шрифтов с именем семейства и массивом значений осей. Как описано выше, параметр имени семейства может быть типографическим именем семейства, именем семейства WWS, именем семейства RBIZ или полным именем. (Полное имя определяет определенный стиль шрифта, например "Arial Полужирный курсив". GetMatchingFonts поддерживает сопоставление по полному имени для большей comaptibiltiy с GDI, что также позволяет.
Следующие другие API DirectWrite также используют алгоритм выбора гибридных шрифтов:
IDWriteTextLayout, если модель семейства типографических шрифтов включена путем вызова IDWriteTextLayout4::SetFontAxisValues или IDWriteTextLayout4::SetAutomaticFontAxes
Примеры использования API выбора шрифтов
В этом разделе показано полное консольное приложение, демонстрирующее методы IDWriteFontSet4::GetMatchingFonts и IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. Сначала давайте добавим некоторые заголовки:
#include <dwrite_core.h>
#include <wil/com.h>
#include <iostream>
#include <string>
#include <vector>
Метод IDWriteFontSet4::GetMatchingFonts возвращает список шрифтов в порядке приоритета, соответствующий указанным значениям имени семейства и оси. Следующая функция MatchAxisValues выводит параметры для IDWriteFontSet4::GetMatchingFonts и список соответствующих шрифтов в возвращаемом наборе шрифтов.
// Forward declarations of overloaded output operators used by MatchAxisValues.
std::wostream& operator<<(std::wostream& out, DWRITE_FONT_AXIS_VALUE const& axisValue);
std::wostream& operator<<(std::wostream& out, IDWriteFontFile* fileReference);
std::wostream& operator<<(std::wostream& out, IDWriteFontFaceReference1* faceReference);
//
// MatchAxisValues calls IDWriteFontSet4::GetMatchingFonts with the
// specified parameters and outputs the matching fonts.
//
void MatchAxisValues(
IDWriteFontSet4* fontSet,
_In_z_ WCHAR const* familyName,
std::vector<DWRITE_FONT_AXIS_VALUE> const& axisValues,
DWRITE_FONT_SIMULATIONS allowedSimulations
)
{
// Write the input parameters.
std::wcout << L"GetMatchingFonts(\"" << familyName << L"\", {";
for (DWRITE_FONT_AXIS_VALUE const& axisValue : axisValues)
{
std::wcout << L' ' << axisValue;
}
std::wcout << L" }, " << allowedSimulations << L"):\n";
// Get the matching fonts for the specified family name and axis values.
wil::com_ptr<IDWriteFontSet4> matchingFonts;
THROW_IF_FAILED(fontSet->GetMatchingFonts(
familyName,
axisValues.data(),
static_cast<UINT32>(axisValues.size()),
allowedSimulations,
&matchingFonts
));
// Output the matching font face references.
UINT32 const fontCount = matchingFonts->GetFontCount();
for (UINT32 fontIndex = 0; fontIndex < fontCount; fontIndex++)
{
wil::com_ptr<IDWriteFontFaceReference1> faceReference;
THROW_IF_FAILED(matchingFonts->GetFontFaceReference(fontIndex, &faceReference));
std::wcout << L" " << faceReference.get() << L'\n';
}
std::wcout << std::endl;
}
Приложение может иметь параметры веса, растяжения и стиля вместо значений оси (или в дополнение к значениям оси). Например, приложению может потребоваться работать с документами, ссылающимися на шрифты с помощью RBIZ или параметров стиля stretch-stretch. Даже если приложение добавляет поддержку указания произвольных значений оси, может потребоваться также поддерживать старые параметры. Для этого приложение может вызывать IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues перед вызовом IDWriteFontSet4::GetMatchingFonts.
Следующая функция MatchFont принимает параметры веса, растяжения, стиля и размера шрифта в дополнение к значениям оси. Он перенаправляет эти параметры в метод IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues для вычисления производных значений оси, которые добавляются к значениям входной оси. Он передает объединенные значения оси приведенной выше функции MatchAxisValues.
struct FontStyleParams
{
FontStyleParams() {}
FontStyleParams(DWRITE_FONT_WEIGHT fontWeight) : fontWeight{ fontWeight } {}
FontStyleParams(float fontSize) : fontSize{ fontSize } {}
DWRITE_FONT_WEIGHT fontWeight = DWRITE_FONT_WEIGHT_NORMAL;
DWRITE_FONT_STRETCH fontStretch = DWRITE_FONT_STRETCH_NORMAL;
DWRITE_FONT_STYLE fontStyle = DWRITE_FONT_STYLE_NORMAL;
float fontSize = 0.0f;
};
//
// MatchFont calls IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues to convert
// the input parameters to axis values and then calls MatchAxisValues.
//
void MatchFont(
IDWriteFactory7* factory,
_In_z_ WCHAR const* familyName,
FontStyleParams styleParams = {},
std::vector<DWRITE_FONT_AXIS_VALUE> axisValues = {},
DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE
)
{
wil::com_ptr<IDWriteFontSet2> fontSet2;
THROW_IF_FAILED(factory->GetSystemFontSet(/*includeDownloadableFonts*/ false, &fontSet2));
wil::com_ptr<IDWriteFontSet4> fontSet;
THROW_IF_FAILED(fontSet2->QueryInterface(&fontSet));
// Ensure the total number of axis values can't overflow a UINT32.
size_t const inputAxisCount = axisValues.size();
if (inputAxisCount > UINT32_MAX - DWRITE_STANDARD_FONT_AXIS_COUNT)
{
THROW_HR(E_INVALIDARG);
}
// Reserve space at the end of axisValues vector for the derived axis values.
// The maximum number of derived axis values is DWRITE_STANDARD_FONT_AXIS_COUNT.
axisValues.resize(inputAxisCount + DWRITE_STANDARD_FONT_AXIS_COUNT);
// Convert the weight, stretch, style, and font size to derived axis values.
UINT32 derivedAxisCount = fontSet->ConvertWeightStretchStyleToFontAxisValues(
/*inputAxisValues*/ axisValues.data(),
static_cast<UINT32>(inputAxisCount),
styleParams.fontWeight,
styleParams.fontStretch,
styleParams.fontStyle,
styleParams.fontSize,
/*out*/ axisValues.data() + inputAxisCount
);
// Resize the vector based on the actual number of derived axis values returned.
axisValues.resize(inputAxisCount + derivedAxisCount);
// Pass the combined axis values (explicit and derived) to MatchAxisValues.
MatchAxisValues(fontSet.get(), familyName, axisValues, allowedSimulations);
}
Следующая функция демонстрирует результаты вызова приведенной выше функции MatchFont с некоторыми примерами входных данных:
void TestFontSelection(IDWriteFactory7* factory)
{
// Request "Cambria" with bold weight, with and without allowing simulations.
// - Matches a typographic/WWS family.
// - Result includes all fonts in the family ranked by priority.
// - Useful because Cambria Bold might have fewer glyphs than Cambria Regular.
// - Simulations may be applied if allowed.
MatchFont(factory, L"Cambria", DWRITE_FONT_WEIGHT_BOLD);
MatchFont(factory, L"Cambria", DWRITE_FONT_WEIGHT_BOLD, {}, DWRITE_FONT_SIMULATIONS_NONE);
// Request "Cambria Bold".
// - Matches the full name of one static font.
MatchFont(factory, L"Cambria Bold");
// Request "Bahnschrift" with bold weight.
// - Matches a typographic/WWS family.
// - Returns an arbitrary instance of the variable font.
MatchFont(factory, L"Bahnschrift", DWRITE_FONT_WEIGHT_BOLD);
// Request "Segoe UI Variable" with two different font sizes.
// - Matches a typographic family name only (not a WWS family name).
// - Font size maps to "opsz" axis value (Note conversion from DIPs to points.)
// - Returns an arbitrary instance of the variable font.
MatchFont(factory, L"Segoe UI Variable", 16.0f);
MatchFont(factory, L"Segoe UI Variable", 32.0f);
// Same as above with an explicit opsz axis value.
// The explicit value is used instead of an "opsz" value derived from the font size.
MatchFont(factory, L"Segoe UI Variable", 16.0f, { { DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE, 32.0f } });
// Request "Segoe UI Variable Display".
// - Matches a WWS family (NOT a typographic family).
// - The input "opsz" value is ignored; the optical size of the family is used.
MatchFont(factory, L"Segoe UI Variable Display", 16.0f);
// Request "Segoe UI Variable Display Bold".
// - Matches the full name of a variable font instance.
// - All input axes are ignored; the axis values of the matching font are used.
MatchFont(factory, L"Segoe UI Variable Display Bold", 16.0f);
}
Ниже приведены выходные данные приведенной выше функции TestFontSelection :
GetMatchingFonts("Cambria", { wght:700 wdth:100 ital:0 slnt:0 }, 3):
cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
cambria.ttc wght:400 wdth:100 ital:0 slnt:0 BOLDSIM
cambriaz.ttf wght:700 wdth:100 ital:1 slnt:-12.4
cambriai.ttf wght:400 wdth:100 ital:1 slnt:-12.4 BOLDSIM
GetMatchingFonts("Cambria", { wght:700 wdth:100 ital:0 slnt:0 }, 0):
cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
cambriaz.ttf wght:700 wdth:100 ital:1 slnt:-12.4
cambria.ttc wght:400 wdth:100 ital:0 slnt:0
cambriai.ttf wght:400 wdth:100 ital:1 slnt:-12.4
GetMatchingFonts("Cambria Bold", { wght:400 wdth:100 ital:0 slnt:0 }, 3):
cambriab.ttf wght:700 wdth:100 ital:0 slnt:0
GetMatchingFonts("Bahnschrift", { wght:700 wdth:100 ital:0 slnt:0 }, 3):
bahnschrift.ttf wght:700 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
SegUIVar.ttf opsz:12 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { wght:400 wdth:100 ital:0 slnt:0 opsz:24 }, 3):
SegUIVar.ttf opsz:24 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable", { opsz:32 wght:400 wdth:100 ital:0 slnt:0 }, 3):
SegUIVar.ttf opsz:32 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable Display", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
SegUIVar.ttf opsz:36 wght:400 wdth:100 ital:0 slnt:0
GetMatchingFonts("Segoe UI Variable Display Bold", { wght:400 wdth:100 ital:0 slnt:0 opsz:12 }, 3):
SegUIVar.ttf opsz:36 wght:700 wdth:100 ital:0 slnt:0
Ниже приведены реализации перегруженных операторов, объявленных выше. Они используются MatchAxisValues для записи значений входных осей и результирующей ссылки на шрифт лиц:
// Output a font axis value.
std::wostream& operator<<(std::wostream& out, DWRITE_FONT_AXIS_VALUE const& axisValue)
{
UINT32 tagValue = axisValue.axisTag;
WCHAR tagChars[5] =
{
static_cast<WCHAR>(tagValue & 0xFF),
static_cast<WCHAR>((tagValue >> 8) & 0xFF),
static_cast<WCHAR>((tagValue >> 16) & 0xFF),
static_cast<WCHAR>((tagValue >> 24) & 0xFF),
'\0'
};
return out << tagChars << L':' << axisValue.value;
}
// Output a file name given a font file reference.
std::wostream& operator<<(std::wostream& out, IDWriteFontFile* fileReference)
{
wil::com_ptr<IDWriteFontFileLoader> loader;
THROW_IF_FAILED(fileReference->GetLoader(&loader));
wil::com_ptr<IDWriteLocalFontFileLoader> localLoader;
if (SUCCEEDED(loader->QueryInterface(&localLoader)))
{
const void* fileKey;
UINT32 fileKeySize;
THROW_IF_FAILED(fileReference->GetReferenceKey(&fileKey, &fileKeySize));
WCHAR filePath[MAX_PATH];
THROW_IF_FAILED(localLoader->GetFilePathFromKey(fileKey, fileKeySize, filePath, MAX_PATH));
WCHAR* fileName = wcsrchr(filePath, L'\\');
fileName = (fileName != nullptr) ? fileName + 1 : filePath;
out << fileName;
}
return out;
}
// Output a font face reference.
std::wostream& operator<<(std::wostream& out, IDWriteFontFaceReference1* faceReference)
{
// Output the font file name.
wil::com_ptr<IDWriteFontFile> fileReference;
THROW_IF_FAILED(faceReference->GetFontFile(&fileReference));
std::wcout << fileReference.get();
// Output the face index if nonzero.
UINT32 const faceIndex = faceReference->GetFontFaceIndex();
if (faceIndex != 0)
{
out << L'#' << faceIndex;
}
// Output the axis values.
UINT32 const axisCount = faceReference->GetFontAxisValueCount();
std::vector<DWRITE_FONT_AXIS_VALUE> axisValues(axisCount);
THROW_IF_FAILED(faceReference->GetFontAxisValues(axisValues.data(), axisCount));
for (DWRITE_FONT_AXIS_VALUE const& axisValue : axisValues)
{
out << L' ' << axisValue;
}
// Output the simulations.
DWRITE_FONT_SIMULATIONS simulations = faceReference->GetSimulations();
if (simulations & DWRITE_FONT_SIMULATIONS_BOLD)
{
out << L" BOLDSIM";
}
if (simulations & DWRITE_FONT_SIMULATIONS_OBLIQUE)
{
out << L" OBLIQUESIM";
}
return out;
}
Чтобы выкрутить пример, выполните следующие функции синтаксического анализа командной строки и основную функцию :
char const g_usage[] =
"ParseCmdLine <args>\n"
"\n"
" -test Test sample font selection parameters.\n"
" <familyname> Sets the font family name.\n"
" -size <value> Sets the font size in DIPs.\n"
" -bold Sets weight to bold (700).\n"
" -weight <value> Sets a weight in the range 100-900.\n"
" -italic Sets style to DWRITE_FONT_STYLE_ITALIC.\n"
" -oblique Sets style to DWRITE_FONT_STYLE_OBLIQUE.\n"
" -stretch <value> Sets a stretch in the range 1-9.\n"
" -<axis>:<value> Sets an axis value (for example, -opsz:24).\n"
" -nosim Disallow font simulations.\n"
"\n";
struct CmdArgs
{
std::wstring familyName;
FontStyleParams styleParams;
std::vector<DWRITE_FONT_AXIS_VALUE> axisValues;
DWRITE_FONT_SIMULATIONS allowedSimulations = DWRITE_FONT_SIMULATIONS_BOLD | DWRITE_FONT_SIMULATIONS_OBLIQUE;
bool test = false;
};
template<typename T>
_Success_(return)
bool ParseEnum(_In_z_ WCHAR const* arg, long minValue, long maxValue, _Out_ T* result)
{
WCHAR* endPtr;
long value = wcstol(arg, &endPtr, 10);
*result = static_cast<T>(value);
return value >= minValue && value <= maxValue && *endPtr == L'\0';
}
_Success_(return)
bool ParseFloat(_In_z_ WCHAR const* arg, _Out_ float* value)
{
WCHAR* endPtr;
*value = wcstof(arg, &endPtr);
return *arg != L'\0' && *endPtr == L'\0';
}
bool ParseCommandLine(int argc, WCHAR** argv, CmdArgs& cmd)
{
for (int argIndex = 1; argIndex < argc; argIndex++)
{
WCHAR const* arg = argv[argIndex];
if (*arg != L'-')
{
if (!cmd.familyName.empty())
return false;
cmd.familyName = argv[argIndex];
}
else if (!wcscmp(arg, L"-test"))
{
cmd.test = true;
}
else if (!wcscmp(arg, L"-size"))
{
if (++argIndex == argc)
return false;
if (!ParseFloat(argv[argIndex], &cmd.styleParams.fontSize))
return false;
}
else if (!wcscmp(arg, L"-bold"))
{
cmd.styleParams.fontWeight = DWRITE_FONT_WEIGHT_BOLD;
}
else if (!wcscmp(arg, L"-weight"))
{
if (++argIndex == argc)
return false;
if (!ParseEnum(argv[argIndex], 100, 900, &cmd.styleParams.fontWeight))
return false;
}
else if (!wcscmp(arg, L"-italic"))
{
cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_ITALIC;
}
else if (!wcscmp(arg, L"-oblique"))
{
cmd.styleParams.fontStyle = DWRITE_FONT_STYLE_OBLIQUE;
}
else if (!wcscmp(arg, L"-stretch"))
{
if (++argIndex == argc)
return false;
if (!ParseEnum(argv[argIndex], 1, 9, &cmd.styleParams.fontStretch))
return false;
}
else if (wcslen(arg) > 5 && arg[5] == L':')
{
// Example: -opsz:12
DWRITE_FONT_AXIS_VALUE axisValue;
axisValue.axisTag = DWRITE_MAKE_FONT_AXIS_TAG(arg[1], arg[2], arg[3], arg[4]);
if (!ParseFloat(arg + 6, &axisValue.value))
return false;
cmd.axisValues.push_back(axisValue);
}
else if (!wcscmp(arg, L"-nosim"))
{
cmd.allowedSimulations = DWRITE_FONT_SIMULATIONS_NONE;
}
else
{
return false;
}
}
return true;
}
int __cdecl wmain(int argc, WCHAR** argv)
{
CmdArgs cmd;
if (!ParseCommandLine(argc, argv, cmd))
{
std::cerr << "Invalid command. Type TestFontSelection with no arguments for usage.\n";
return 1;
}
if (cmd.familyName.empty() && !cmd.test)
{
std::cout << g_usage;
return 0;
}
wil::com_ptr<IDWriteFactory7> factory;
THROW_IF_FAILED(DWriteCoreCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory7),
(IUnknown**)&factory
));
if (!cmd.familyName.empty())
{
MatchFont(
factory.get(),
cmd.familyName.c_str(),
cmd.styleParams,
std::move(cmd.axisValues),
cmd.allowedSimulations
);
}
if (cmd.test)
{
TestFontSelection(factory.get());
}
}