В книге Урсулы Ле Гуин “Волшебник Земноморья” магия требовала знания “истинного имени” того, с чем маг работает. Думаю, любой программист согласится, что идея здравая. URLи, UUIDы и прочие уникальные идентификаторы объектов — это то, с чем мы имеем дело постоянно. И, так же как в волшебном мире, эти истинные имена бывает не так-то просто узнать. По крайней мере для имен шрифтов это так.
Мне нужно было реализовать в нашем программном продукте экспорт текстовых блоков в PDF. Для экспорта используются проприетарные библиотеки Adobe PDF Library (http://datalogics.com/products/pdfl/) и надстройка над ней DLI (Datalogics Library Interface). Не буду углубляться в эти библиотеки, думаю они мало кому интересны. Но полагаю, что проблема, с которой я столкнулся, общая для любой реализации PDF экспорта.
Каждый шрифт (возьмем, например, Arial) имеет 4 различных начертания — обычный, жирный, наклонный и жирный наклонный. Т.е. Arial, Arial Bold, Arial Italic и Arial Bold Italic. Каждое начертание хранится в отдельном TTF файле или в отдельной секции TTC файла. И если мы хотим вывести в PDF файл наклонный или жирный шрифт, мы должны в вызове соответствующей функции явно указать “Arial Italic” или “Arial Bold”. Но в текстовом блоке, который мы экспортируем, указано, что его шрифт “Arial” и отдельно заданы атрибуты Bold и Italic. И EnumFontsFamiliesEx возвращает нам только имя “Arial” и все! В Как же получить нужную нам строку “Arial Italic”?
Очевидное решение — просто приписать строчку “Italic” к имени шрифта — работает не всегда. Например, оно не работает со шрифтом “Lucida Sans Typewriter”. PDF библиотека выдает ошибку, если мы передаем “Lucida Sans Typewriter Italic”.
Ключ к решению (pun intended) — HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionFonts. Достаточно посмотреть в содержимое этого ключа и становится ясно, что нужно было передать “Lucida Sans Typewriter Oblique”. Тогда все работает.
Формат записей в этом ключе нигде не документирован, но вроде бы очевиден:
«Arial (TrueType)»=«arial.ttf»
«Arial Italic (TrueType)»=«ariali.ttf»
«Arial Bold (TrueType)»=«arialbd.ttf»
«Arial Bold Italic (TrueType)»=«arialbi.ttf»
«Batang & BatangChe & Gungsuh & GungsuhChe (TrueType)»=«batang.ttc»
…
«Mangal (TrueType)»=«mangal.ttf»
«Mangal Bold (TrueType)»=«mangalb.ttf»
«Meiryo & Meiryo Italic & Meiryo UI & Meiryo UI Italic (TrueType)»=«meiryo.ttc»
«Meiryo Bold & Meiryo Bold Italic & Meiryo UI Bold & Meiryo UI Bold Italic (TrueType)»=«meiryob.ttc»
«MS Gothic & MS PGothic & MS UI Gothic (TrueType)»=«msgothic.ttc»
…
«Lucida Sans Typewriter Regular (TrueType)»=«LTYPE.TTF»
«Lucida Sans Typewriter Bold (TrueType)»=«LTYPEB.TTF»
«Lucida Sans Typewriter Bold Oblique (TrueType)»=«LTYPEBO.TTF»
«Lucida Sans Typewriter Oblique (TrueType)»=«LTYPEO.TTF»
Видно, что для TTC коллекций шрифты, содержащиеся в них, указаны через “ & “.
Алгоритм для установления соответствия между общим именем шрифта и именами начертаний получается такой: для каждого имени начертания отрезаем по одному слову с конца, пока остаток не совпадет с каким-нибудь именем, полученным из EnumFontsFamiliesEx. Кроме того отрезанные слова проверяем на совпадение со словами “Bold”, “Ilalic”, “Semibold”, “Oblique” и запоминаем соответвтвующий атрибут для этого начертания. Например для семейства“Lucida Sans Typewriter”:
Lucida Sans Typewriter Regular -> Lucida Sans Typewriter
Lucida Sans Typewriter Bold -> Lucida Sans Typewriter
Lucida Sans Typewriter Oblique -> Lucida Sans Typewriter
Lucida Sans Typewriter Bold Oblique -> Lucida Sans Typewriter Bold -> Lucida Sans Typewriter
Теперь если нужно вывести шрифт “Lucida Sans Typewriter” жирным и наклонным, то мы знаем, что этому начертанию соответствуюет имя “Lucida Sans Typewriter Bold Oblique” и передаем это имя в PDF библиотеку.
Тут, правда, поджидает еще одна неприятность. Например шрифт “Mangal” имеет только жирное начертание (“Mangal Bold”), а вот наклонного у него нет. Хотя мы можем поставить атрибут “наклонный” этому шрифту и Windows GDI в этом случае самостоятельно исказит имеющееся начертание при выводе на экран. При экспорте в PDF же придется проделать это самостоятельно. PDF библиотека может позволить задать матрицу преобразования при выводе текста. Например, в моем случае, это выглядело так:
ASFixedMatrix fontSkew;
if (bSimulateItalic)
{
double angle = 15;
fontSkew.a = fixedOne; // x scale
fontSkew.b = fixedZero; // rotate & skew
fontSkew.c = FloatToASFixed(tan(_PI * angle / 180)); // rotate & skew
fontSkew.d = fixedOne; // y scale
fontSkew.h = 0; // x translation
fontSkew.v = 0; // y translation
dlpdfcontentfontskew(..., &fontSkew);
}
Для имитации жирного шрифта красивого решения я не нашел. Я просто печатаю строку, которую надо вывести жирным, несколько раз с небольшим сдвигом. Визуально все выглядит нормально, но расстраивает, что текст в PDF файле дублируется.
Но и это еще не конец. Продукт, который мы разрабатываем, имеет японскую версию. Поэтому корректной работе с азиатскими шрифтами уделяется особое внимание. И тут вылезают еще две проблемы:
- Шрифт с именем “MS Pゴシック” не присутствует в HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionFonts и получается, что мы не можем узнать имена начертаний для этого шрифта.
- PDF библиотека вообще не понимает имена шрифтов в Unicode.
Начнем с первой проблемы (хотя исторически все началось со второй, но для связности рассказа так проще). Гугл говорит нам, что шрифт “MS Pゴシック” — это на самом деле MS Gothic. Выясняется, что японское имя он приобретает, если в системе выставлена японская локаль. При этом в реестре, разумеется, он так и остается под именем MS Gothic. Это, оказывается, штатное поведение EnumFontsFamiliesEx. Вот цитата из документации на нее: «The fonts for many East Asian languages have two typeface names an English name and a localized name. EnumFonts, EnumFontFamilies, and EnumFontFamiliesEx return the English typeface name if the system locale does not match the language of the font».
Кстати, если уж мы узнаем, что “MS Pゴシック” это “MS Gothic”, то это решает и вторую проблему, по крайней мере для случая, когда в реестре хранится английское имя. Мы просто передадим в PDF библиотеку имя “MS Gothic” и все заработает. Остается установить это соответствие.
Для большинства начертаний из HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionFonts мы поставили в соответствие имена шрифтов из EnumFontsFamiliesEx. Но для некоторых начертаний пары не нашлось. Еще бы — в реестре у нас “MS Gothic”, а EnumFontsFamiliesEx вернул “MS Pゴシック”.
В этом случае остается только самостоятельно разобрать TTF/TTC файл и найти там соответствующее японское имя.
Разбор TTC/TTF файла — задача несложная. За работающий образец можно взять исходники проекта “ttt2eot” code.google.com/p/ttf2eot/. Сам формат TTF/TTC хорошо документирован на сайте Microsoft: www.microsoft.com/typography/otspec/. Обратить внимание надо на то, что все данные в TTF хранятся в big endian формате, так что все числа и Unicode-строки надо конвертировать перед использованием.
Свой код я, к сожалению, выложить не имею права, поэтому лишь напишу здесь, что искать.
Нас интересует таблица “name” www.microsoft.com/typography/otspec/name.htm. Выбираем записи с:
- nPlatformId=3: Windows. Я предполагаю, что если фонт установлен под Windows, то эти записи там быть должны. Возможно я не прав, но пусть вначале такой шрифт встретится, тогда и будем разбираться./li>
nNameId = 1: Font Family name. Up to four fonts can share the Font Family name, forming a font style linking group (regular, italic, bold, bold italic — as defined by OS/2.fsSelection bit settings). Т.е. это как раз то имя, которое возвращает EnumFontFamiliesEx.
nEncodingId = 0 — однобайтная ASCII строка или 1 — двухбайтная USC2 строка. Остальные кодировки можно игнорировать: cпецификация явно требует, чтобы хоть одна из этих двух кодировок присутствовала: “When building a Unicode font for Windows, the platform ID should be 3 and the encoding ID should be 1. When building a symbol font for Windows, the platform ID should be 3 and the encoding ID should be 0.”
Одно из найденных имен совпадет с каким-то именем из EnumFontFamiliesEx.
Например для начертания “Meiryo Bold Italic” разобрав meiryob.ttc мы узнаем, что этому начертанию соответствует имя “メイリオ” из EnumFontFamiliesEx.
Остается узнать, является ли это начертание жирным и наклонным. Напрашивается идея тоже взять эту информацию из шрифта, но, как выяснилось экспериментально, эти атрибуты в файле шрифта могут быть неверными. Поэтому возьмем их из имени начертания (“Meiryo Bold Italic”), как уже делали выше. Только отрезать слова будем до тех пор, пока остаток не совпадет с любым именем, извлеченным из TTF файла, а не из выдачи EnumFontFamiliesEx.
Таким образом, если надо экспортировать текстовый блок, набранный наклонным и жирным шрифтом с именем “メイリオ”, мы передаем в PDF библиотеку имя “Meiryo Bold Italic”. Профит!
Автор: milyin