Выбор шрифта
Интерфейс 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. Таким образом, в модели RBIZ у нас есть шрифты Arial, Arial Black и Arial Narrow, несмотря на то, что все они принадлежат к одной семье.
Из этих примеров можно увидеть, как ограничения модели семейства шрифтов могут повлиять на группирование шрифтов по семействам. Так как семейства шрифтов идентифицируются по имени, это означает, что один и тот же шрифт может иметь разные имена семейств в зависимости от используемой модели семейства шрифтов.
DirectWrite не поддерживает модель семейства шрифтов RBIZ напрямую, но предоставляет методы преобразования в модель RBIZ и из нее, такие как IDWriteGdiInterop::CreateFontFromLOGFONT и IDWriteGdiInterop::ConvertFontToLOGFONT. Вы также можете получить имя семейства RBIZ шрифта, вызвав его метод IDWriteFont::GetInformationalStrings и указав DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES.
Модель семейства шрифтов в стиле weight-stretch
Модель семейства шрифтов в стиле weight-stretch — это исходная модель семейства шрифтов, используемая DirectWrite до появления типографической модели семейства шрифтов. Он также известен как вес-ширина-наклон (WWS). В модели WWS шрифты в одном семействе могут отличаться тремя свойствами: вес (DWRITE_FONT_WEIGHT), растяжение (DWRITE_FONT_STRETCH) и стиль (DWRITE_FONT_STYLE).
Модель WWS более гибкая, чем модель RBIZ, в двух отношениях. Во-первых, шрифты одного семейства можно различать по растянутой (или ширине), а также по весу и стилю (обычный, курсив или наклон). Во-вторых, в одной семье может быть более двух весов. Такой гибкости достаточно, чтобы все варианты Arial были включены в одно семейство WWS. В следующей таблице сравниваются свойства шрифтов RBIZ и WWS для выбора шрифтов Arial:
Полное имя | Имя семейства RBIZ | lfWeight | lfItalic | WWS FamilyName | Вес | Stretch | Стиль |
---|---|---|---|---|---|---|---|
Arial | Arial | 400 | 0 | Arial | 400 | 5 | 0 |
Arial Полужирный (Arial Bold) | 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 Узкий Полужирный (Arial Narrow Bold) | 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 в зависимости от размера шрифта.
Выбор типографического шрифта и шрифты переменных
Концепция осей вариации часто связана с переменными шрифтами, но она также применяется к статическим шрифтам. Таблица OpenType STAT (атрибуты стиля) объявляет, какие оси структуры имеет шрифт, и значения этих осей. Эта таблица является обязательной для переменных шрифтов, но также относится к статическим шрифтам.
API DirectWrite предоставляет значения оси wght, wdth, ital и slnt для каждого шрифта, даже если они отсутствуют в таблице STAT или нет таблицы STAT. По возможности эти значения являются производными от таблицы STAT. В противном случае они являются производными от веса шрифта, растяжения шрифта и стиля шрифта.
Оси шрифта могут быть переменными или не переменными. Статический шрифт имеет только неперемеяемые оси, тогда как переменный шрифт может иметь и то, и другое. Чтобы использовать шрифт переменной, необходимо создать экземпляр шрифта переменной, в котором все оси переменных были привязаны к определенным значениям. Интерфейс IDWriteFontFace представляет статический шрифт или конкретный экземпляр шрифта переменной. Можно создать произвольный экземпляр шрифта переменной с указанными значениями оси. Кроме того, шрифт переменной может объявлять именованные экземпляры в таблице STAT с предопределенными сочетаниями значений осей. Именованные экземпляры позволяют шрифту переменной вести себя так же, как коллекция статических шрифтов. При перечислении элементов IDWriteFontFamily или IDWriteFontSet имеется один элемент для каждого статического шрифта и для каждого экземпляра шрифта именованной переменной.
Сначала типографический алгоритм сопоставления шрифтов выбирает потенциальных кандидатов на соответствие на основе имени семейства. Если кандидаты на совпадение содержат переменные шрифты, все кандидаты на соответствие для одного и того же шрифта переменной сворачиваются в один кандидат соответствия, в котором каждой оси переменной присваивается определенное значение, максимально близкое к запрошенному значению для этой оси. Если для оси переменных нет запрошенного значения, ей присваивается значение по умолчанию для этой оси. Затем порядок кандидатов на соответствие определяется путем сравнения значений их оси с запрошенными значениями оси.
Например, рассмотрим семейство typeographic 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 для модели семейства шрифтов в стиле weight-stretch (IDWriteFontFamily::GetMatchingFonts) и типографической модели семейства шрифтов (IDWriteFontCollection2::GetMatchingFonts). В обоих случаях выходные данные — это список совпадающих шрифтов в порядке убывания приоритета в зависимости от того, насколько хорошо каждый шрифт-кандидат соответствует входным свойствам. В этом разделе описывается, как определяется приоритет.
В модели с растяжением веса входными параметрами являются вес шрифта (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 сравниваются в этом порядке.
Сравнение расстояний достаточно интуитивно понятно, но использование точечного продукта в качестве дополнительной меры может потребовать некоторого объяснения. Предположим, что входной вес имеет значение semibold (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). Выбранные множители для этих двух осей дают эквивалентные результаты устаревшему алгоритму, если предполагается наклон по умолчанию на 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 типографическое имя семейства просто выражало намерение конструктора о том, что набор шрифтов был связан, но не гарантирует, что эти шрифты могут быть программно дифференцированы на основе их свойств. В качестве гипотетического примера предположим, что все следующие шрифты имеют типографическое имя семейства "Legacy":
Полное имя | Семейство WWS | Вес | Stretch | Стиль | Семейство опечаток | wght | wdth | Ital | slnt |
---|---|---|---|---|---|---|---|---|---|
Прежняя версия | Прежняя версия | 400 | 5 | 0 | Прежняя версия | 400 | 100 | 0 | 0 |
Устаревший полужирный | Прежняя версия | 700 | 5 | 0 | Прежняя версия | 700 | 100 | 0 | 0 |
Устаревшая черная | Прежняя версия | 900 | 5 | 0 | Прежняя версия | 900 | 100 | 0 | 0 |
Устаревшая версия Soft | Устаревшая версия Soft | 400 | 5 | 0 | Прежняя версия | 400 | 100 | 0 | 0 |
Устаревшая версия с мягким полужирным шрифтом | Устаревшая версия Soft | 700 | 5 | 0 | Прежняя версия | 700 | 100 | 0 | 0 |
Устаревшая мягкая черная | Устаревшая версия Soft | 900 | 5 | 0 | Прежняя версия | 900 | 100 | 0 | 0 |
Типографическое семейство "Legacy" имеет три веса, и каждый вес имеет регулярные и "мягкие" варианты. Если бы это были новые шрифты, их можно было бы реализовать как объявление оси проектирования SOFT. Однако эти шрифты предшествовали OpenType 1.8, поэтому их единственными осями оформления являются те, которые являются производными от веса, растяжения и стиля. Для каждого веса это семейство шрифтов имеет два шрифта с одинаковыми значениями оси, поэтому однозначно выбрать шрифт в этом семействе, используя только значения оси, невозможно.
Гибридный алгоритм выбора шрифтов
API выбора шрифтов, описанные в следующем разделе, используют гибридный алгоритм выбора шрифтов, который сохраняет преимущества выбора шрифта при одновременном устранении проблем совместимости шрифта.
Гибридный выбор шрифтов обеспечивает мост от старых моделей семейства шрифтов, позволяя сопоставлять вес шрифта, растяжение шрифта и значения стиля шрифта с соответствующими значениями оси шрифта. Это помогает устранить проблемы совместимости документов и приложений.
Кроме того, алгоритм выбора гибридного шрифта позволяет использовать указанное имя семейства как типографическое имя семейства, имя семейства в стиле weight-stretch, имя семейства GDI/RBIZ или полное имя шрифта. Сопоставление выполняется одним из следующих способов в порядке убывания приоритета:
Имя соответствует типографической семье (например, Sitka). Сопоставление происходит в семействе типографических данных, и используются все запрошенные значения оси. Если имя также соответствует подсемейству WWS (то есть одному меньше, чем типографическое семейство), то членство в подсемействе WWS используется в качестве разбиения связей.
Имя соответствует семейству WWS (например, Sitka Text). Сопоставление происходит в семействе WWS, и запрошенные значения оси, отличные от "wght", "wdth", "ital" и "slnt", игнорируются.
Имя соответствует семейству GDI (например, Bahnschrift Condensed). Сопоставление происходит в семействе RBIZ, а запрошенные значения оси, отличные от "wght", "ital" и "slnt", игнорируются.
Имя совпадает с полным именем (например, Bahnschrift Bold Condensed). Возвращается шрифт, соответствующий полному имени. Запрошенные значения оси игнорируются. Сопоставление по полному имени шрифта разрешено, так как GDI поддерживает его.
В предыдущем разделе описано неоднозначное типографическое семейство под названием "Legacy". Гибридный алгоритм позволяет избежать неоднозначности, указав в качестве имени семейства "Legacy" или "Legacy Soft". Если указан параметр Legacy Soft, неоднозначность отсутствует, так как сопоставление происходит только в семействе WWS. Если указано "Устаревшая версия", то все шрифты в семействе типографических считаются кандидатами на соответствие, но неоднозначности можно избежать, используя членство в семействе WWS "Legacy" в качестве средства разбиения связей.
Предположим, что в документе указаны имя семейства и параметры веса, растяжения и стиля, но не указаны значения оси. Приложение может сначала преобразовать вес, растяжение, стиль и размер шрифта в значения оси, вызвав IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues. Затем приложение может передать значения имени семейства и оси в IDWriteFontSet4::GetMatchingFonts. GetMatchingFonts возвращает список соответствующих шрифтов в порядке приоритета, и результат подходит независимо от того, является ли указанное имя семейства типографическим, именем семейства в стиле weight-stretch, именем семьи RBIZ или полным именем. Если указанное семейство имеет ось opsz, соответствующий оптический размер автоматически выбирается в зависимости от размера шрифта.
Предположим, что в документе указаны вес, растяжение и стиль, а также указаны значения оси. В этом случае явные значения оси также можно передать в IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues, а значения производных осей, возвращаемые методом, будут содержать только оси шрифтов, которые не были указаны явно. Таким образом, значения оси, явно указанные в документе (или приложении), имеют приоритет над значениями оси, производными от веса, растяжения, стиля и размера шрифта.
API гибридного выбора шрифтов
Гибридная модель выбора шрифтов реализуется с помощью следующих методов IDWriteFontSet4 :
Метод IDWriteFontSet4::ConvertWeightStretchStyleToFontAxisValues преобразует параметры размера, веса, растяжения и стиля шрифта в соответствующие значения оси. Все явные значения оси, передаваемые клиентом, исключаются из значений производных осей.
Метод IDWriteFontSet4::GetMatchingFonts возвращает приоритетный список соответствующих шрифтов, заданный именем семейства и массивом значений оси. Как описано выше, параметром family name может быть типографическое имя семейства, имя семьи WWS, имя семьи RBIZ или полное имя. (Полное имя определяет определенный стиль шрифта, например "Arial Bold Italic". GetMatchingFonts поддерживает сопоставление по полному имени для большей совместимости с 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 или параметров стиля weight-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;
}
Ниже приведены функции анализа командной строки и функция main.
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());
}
}