Как сбалансировать функции OpenType, ориентированные на сценарии, с другими функциями OpenType, использующими DirectWrite?

Полное раскрытие: я работаю над текстовым API моей библиотеки libui GUI. Это оборачивает DirectWrite в Windows, Core Text в OS X и Pango (который использует HarfBuzz для формирования OpenType) в других Unix-системах. Один из атрибутов форматирования текста, который я хочу указать, - это набор функций OpenType, которые используются всеми тремя; DirectWrite - это IDWriteTypography,

Теперь, когда вы рисуете текст с этими библиотеками, по умолчанию вы получаете несколько полезных функций OpenType, таких как стандартные лигатуры (liga) как лигатура f+i. Я думал, что это зависит от шрифта, но оказывается, что это зависит от сценария формируемого текста. Microsoft предоставляет рекомендации для всех сценариев, поддерживаемых OpenType (в разделе "Разработка для сценариев"), и я вижу довольно сложную логику для всего этого в самом HarfBuzz, чтобы подтвердить это.

В Core Text и Pango, если я включу другие атрибуты, они будут добавлены поверх этих значений по умолчанию. Но с DirectWrite, в частности IDWriteTextLayout::SetTypography()при этом удаляются значения по умолчанию:

DirectWrite удаляет функции OpenType по умолчанию, если вы явно указываете объект IDWriteTypography

Программа, которая производит этот вывод, может быть найдена здесь.

Очевидно, мой первый вариант - спросить, как получить функции по умолчанию в DirectWrite. Кто-то сделал это уже на этом сайте, и ответ, кажется, "нет".

Я предполагаю, что DirectWrite позволяет мне полностью контролировать список функций, применимых к некоторому тексту. Это хорошо, за исключением того, что я не могу сделать это с другими API, если я как-то явно не отключаю функции по умолчанию! Конечно, я не знаю, изменится ли этот список когда-либо, поэтому жесткое его кодирование может оказаться не самой лучшей идеей.

Даже если жесткое кодирование является опцией, я мог бы просто взять список HarfBuzz для каждого скрипта, но а) это довольно сложно б) есть несколько возможных формирователей для скрипта, в зависимости от (я думаю) совместимости версий (например, Мьянма).

Так почему бы не использовать списки HarfBuzz для воссоздания списка функций по умолчанию для DirectWrite? Кажется, в любом случае он хочет быть точным с другими шейперами, так что это должно сработать, верно? Ну, мне нужно сделать две вещи: выяснить, какой скрипт использовать, и выяснить, какие атрибуты использовать, какие символы для скрипта, где положение символа в слове имеет значение.

DirectWrite предоставляет интерфейс IDWriteTextAnalyzer Это обеспечивает возможности для формирования. Я мог бы использовать это, но кажется, что данные сценария возвращаются в DWRITE_SCRIPT_ANALYSIS структура и описание идентификатора сценария гласят: "Представление индекса, основанного на нулях, написанного системного сценария"

Это не помогает, поэтому я написал программу, которая просто выводит номера скриптов для текста, который я набираю. Запуск его на входной строке

لللللللللللللاااااااااالا abcd محمد ابن بطوطة‎‎ Отложения датского яруса

дает выход

0 - 26 script 3 shapes 0
26 - 5 script 49 shapes 0
31 - 14 script 3 shapes 0
45 - 2 script 1 shapes 1
47 - 25 script 22 shapes 0

Я не могу сопоставить эти номера скриптов ни с чем в каком-либо из заголовков Windows: если в каком-либо API есть определенное число для арабского, латинского или кириллического алфавита, они не соответствуют этим. И даже если я получу соответствие между сценарием и номером сценария, это все равно не даст мне данных для применения внутрисловных функций.

А как насчет Uniscribe? Ну, документация для аналога SCRIPT_ANALYSIS type говорит, что его идентификатор сценария является "[непрозрачным] значением", чье "значение для этого элемента не определено, и приложения не должны полагаться на то, что его значение будет одинаковым от одного выпуска к другому". И хотя я могу получить код языка для идентификации сценария, по-прежнему нет определенного значения, кроме LANG_ENGLISH для "западных" (латинских?) сценариев. Значения DirectWrite такие же, как у Uniscribe? И кажется, что я могу, по крайней мере, понять начальное и конечное состояния слов, посмотрев наfLinkBeforeа такжеfLinkAfterполя, но достаточно ли этого для правильного применения атрибутов к сценарию?

HarfBuzz имеет экспериментальный бэкэнд DirectWrite, который не предназначен для использования в реальных программах; Я еще не уверен, имеет ли он тот же самый признак функциональности, который я указал выше. Если я узнаю, я обновлю эту часть здесь.

Наконец, если я введу следующий эквивалентный тестовый пример с первым выше в чем-то вроде kaxaml:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>  
  <FlowDocumentPageViewer>
  <FlowDocument FontFamily="Constantia" FontSize="48">
  <Paragraph>
  afford afire aflight 1/4<LineBreak/>
  <Run Typography.Fraction="1">afford afire aflight 1/4</Run>
  </Paragraph>
  </FlowDocument>
  </FlowDocumentPageViewer>
  </Grid>
</Page>

Я вижу, что лигатуры применяются правильно, даже в последнем случае:

kaxaml показывает что я хочу работает отлично

(Дробь в конце просто чтобы доказать, что этот атрибут применяется.) Если я предполагаю, что XAML использует DirectWrite, то это подтверждает мой первый вариант (простое наложение моих пользовательских атрибутов поверх значений по умолчанию)... (Я делаю это предположение, основываясь на идее, что XAML предоставляет поразительно похожий API-интерфейс для Direct2D для рисования 2D-графики, и в нем заполнено множество дыр, где мне пришлось вручную писать много кода для склеивания, чтобы делать то же самое с vanilla Direct2D, поэтому я предполагаю, что все, что возможно в XAML, возможно с Direct2D и, соответственно, с DirectWrite, поскольку они были технически введены вместе...)

На данный момент я полностью потерян. Я хочу, по крайней мере, быть предсказуемым для разных платформ, и я не уверен, как программы вообще должны, не говоря уже о том, чтобы использовать функции OpenType напрямую или нет в любом случае. Неужели я не оправдываю ожидания от текстовых API-интерфейсов? Придется ли мне отбрасывать IDWriteTextLayout и делать все текстовое оформление и верстку самостоятельно, если я этого хочу?

Или мне нужно отказаться от поддержки Windows 7 и перейти на набор функций DirectWrite для обновления платформы? Или даже Windows 7 полностью?

2 ответа

Решение

После некоторых обсуждений с Питером Сиккингом и Эбрагимом Бягови я пошел и отладил более общую программу, которую я быстро построил, чтобы проверить вещи, и я выяснил, что происходит внутри.

Однако сначала я скажу, что это в равной степени относится к Uniscribe и DirectWrite.

Как оказалось, DirectWrite всегда предоставляет набор функций OpenType по умолчанию, независимо от того, какой набор функций я использую! Ситуация такова, что список предоставляемых функций по умолчанию отличается в зависимости от того, загружаю ли я свои собственные функции или нет, и в зависимости от механизма формирования. Для latn Скрипт в режиме горизонтальной записи и для английского языка, это делается с помощью "универсального движка".

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

locl
ccmp
rlig
rclt
calt
liga
clig

Если я предоставлю функции, общий движок будет использовать один и тот же список по умолчанию для всех сценариев:

locl
ccmp
rclt
rlig
mark
mkmk
dist

Так что я не знаю, что с этим делать. Я мог бы просто предоставить liga и несколько других я в коде libui (помечен как HACK конечно), но это все же странно. Я не уверен, что мотивация тоже. В любом случае, это объясняет поведение, которое я вижу.

Предположим, что ваш вопрос в целом касается программирования или, по крайней мере, касается программирования, я постараюсь дать ответы на некоторые из ваших вопросительных предложений.

Придется ли мне полностью отказаться от использования IDWriteTextLayout в моем коде, если я хочу иметь возможность добавлять типографские функции поверх значений по умолчанию?

Это зависит. Если интерфейс IDWriteTextLayout хорошо подходит для задач вашего проекта во всех отношениях, кроме простоты изменения типографских функций DirectWrite по умолчанию, узнайте, что вам следует делать с типографикой, и создайте экземпляр IDWriteTypography, соответствующий вашим потребностям. Разработка пользовательского текстового макета для программы может потребовать значительного времени и усилий, особенно если программа должна отображать двунаправленные тексты, сложные сценарии, встроенные объекты и т. Д.

Может случиться, что задачи вашего проекта требуют разработки механизма разметки текста по причинам, отличным от простого управления типографскими элементами, используемыми в отображаемом тексте. Например, ваш менеджер / клиент может попросить внедрить индивидуальные возможности переноса строк или алгоритм выравнивания глифа. В этом сценарии вы будете реализовывать метод IDWriteTextAnalizer::GetGlyphs. Этот метод имеет параметры DWRITE_TYPOGRAPHIC_FEATURES ** features, const UINT32 * featureRangeLengths, UINT32 featureRanges, и эти параметры позволяют заменять набор типографских возможностей "по умолчанию" для диапазона отображаемого текста (см. Мой ответ на другой вопрос " Что" параметры печати по умолчанию используются IDWriteTextLayout?). Только затронутые функции будут изменены; другие функции имеют свои значения по умолчанию. Более того, если вы опустите эти параметры в вызове GetGlyphs для следующего текстового диапазона (например, используйте значения NULL, NULL, 0), функции, измененные в предыдущем вызове GetGlyphs, не будут изменены вызовом для этого следующего диапазона.

документация для эквивалентного типа SCRIPT_ANALYSIS гласит, что его идентификатор сценария является "[непрозрачным] значением", чье "значение для этого элемента не определено, и приложения не должны полагаться на то, что его значение будет одинаковым от одного выпуска к другому". И хотя я могу получить языковой код, по которому можно будет идентифицировать скрипт, все еще нет определенного значения, кроме LANG_ENGLISH для "западных" (латинских?) Скриптов.

Строго говоря, это не вопросительный запрос, но я думаю, что вы недовольны тем, как определяются эти идентификаторы сценариев Unicode и как можно использовать API со столь неопределенно определенными структурами и константами.

Это может быть не по теме, но я рискну выдвинуть гипотезу о происхождении значений идентификатора сценария Unicode. По состоянию на 2010-07-17, Unicode, Inc. опубликовал версию Unicode 6.0. Стандарт содержал документ http://www.unicode.org/Public/6.0.0/ucd/PropertyValueAliases.txt с разделом, содержащим список сценариев. Список вышел так:

   # Script (sc)

   sc ; Arab      ; Arabic
   sc ; Armi      ; Imperial_Aramaic
   etc.

Арабский алфавит № 1, кириллический алфавит № 20, латинский алфавит № 47 в этом списке. Кроме того, в другом месте я видел этот список, начинающийся со сценариев Common и Inherited. Он помещает арабский алфавит на 3-е место, кириллицу на 22-е и латиницу на 49-е место. Эти ординалы вам знакомы, не так ли?

К счастью, нам не нужно полагаться на значения "идентификатора сценария Unicode"; нам нужны свойства скрипта, а не идентификаторы скрипта или сокращения. API является самосогласованным в том смысле, что он предоставляет фактические свойства сценария для текстового диапазона, когда мы передаем методу GetScriptProperties число, полученное из вызова AnalyzeScript.

Другие вопросы по тегам