Шейдеры для рендеринга текста. Уменьшение размера TrueType шрифтов

В этом Уроке мы рассмотрим, как использовать Type1 или TrueType шрифты так, что бы можно было использовать не только стандартные шрифты. Другой особенностью является то что, вы сможете выбрать шрифт, кодировку, которая позволит Вам использовать не только западные но и другие Языки (стандартные шрифты имеют слишком мало доступных символов).

Существует два способа использования нового шрифта: вложенные шрифты в PDF или подключаемые. Если указанный Вами шрифт не встроен, то он будет искаться в системе. Преимущество подключаемых шрифтов заключается в том, что файл PDF гораздо легче весит, а с другой стороны, если он не доступен, то используется подстановка стандартного шрифта. Поэтому желательно, чтобы шрифт был установлен на клиентских системах. Если файл будет просматриваться не только на вашем локальном компьютере, то обязательно нужно его вставить в PDF.

Что бы добавить новые TrueTypes шрифты, нужно сделать три шага:

  • Сгенерировать файл метрики (.afm)
  • Сгенерировать файл определения шрифта(.php)
  • Объявить шрифт в сценарии

Для Type1, теоретически генерировать файл AFM не является необходимым, так как обычно файл поставляется вместе со шрифтом. В случае, если у вас есть только метрический файл в формате PFM, ты Вы сможете воспользоваться конвертером который предоставлен на сайте FPDF — http://www.fpdf.org/fr/dl.php?id=34 .

Генерация файла метрики

Первым шагом для TrueType является создание AFM-файла. Для этого нужно воспользоваться утилитой . Бинарный файл для Windows доступен по следующему адресу — http://www.fpdf.org/fr/dl.php?id=22 .
Чтобы воспользоваться утилитой ttf2pt1 в командной строке следует ввести следующее:
ttf2pt1 -a font.ttf font

Возьмем например шрифт Comic Sans MS Обычный:
ttf2pt1 -a c:\windows\fonts\comic.ttf comic

С помощью этой утилиты создаются два файла один из которых является файл с расширением.afm который собственно нам и нужен.

Генерация файла для определения шрифта

Вторым шагом является создание PHP файла, который содержит всю необходимую информацию для FPDF. Для того чтобы чтобы это сделать, в каталоге font/makefont Вы сможете найти дополнительный скрипт в фале makefont.php , который содержит следующие функции:
MakeFont(string fontfile, string afmfile [, string enc [, array patch [, string type]]])

Значения которые принимает метод, в качестве параметров:

  • fontfile — Путь к файлу с расширением.ttf или.pfb.
  • afmfile — Путь к файлу с расширением.afm.
  • enc — Название используемой кодировки. По умолчанию cp1252.
  • patch Дополнительные изменения касающиеся кодировки. По умолчанию пуст.
  • type — Тип шрифта (TrueType или Type1). По умолчанию TrueType.

Первым параметром должно быть имя и путь к шрифту. Расширение должно быть.ttf или.pfb. Если у вас есть шрифт Type1 в ASCII формате с расширением.pfa, Вы можете преобразовать его в двоичном формате с помощью утилиты t1utils .

Ранее сгенерированный файл AFM

Кодировка определяет связь между кодом (от 0 до 255) и характер. Первые 128 являются фиксированными и соответствуют ASCII, а следующие являются переменными. Кодировки хранятся в.map файлах. Кодировки бывают следующие:

  • cp1250 (Central Europe)
  • cp1251 (Cyrillic)
  • cp1252 (Western Europe)
  • cp1253 (Greek)
  • cp1254 (Turkish)
  • cp1255 (Hebrew)
  • cp1257 (Baltic)
  • cp1258 (Vietnamese)
  • cp874 (Thai)
  • ISO-8859-1 (Western Europe)
  • ISO-8859-2 (Central Europe)
  • ISO-8859-4 (Baltic)
  • ISO-8859-5 (Cyrillic)
  • ISO-8859-7 (Greek)
  • ISO-8859-9 (Turkish)
  • ISO-8859-11 (Thai)
  • ISO-8859-15 (Western Europe)
  • ISO-8859-16 (Central Europe)
  • KOI8-R (Russian)
  • KOI8-U (Ukrainian)

Шрифт который Вы выберете должен содержать символы, соответствующие выбранной кодировке.
В особенных случаях когда символы шрифта не содержат литеры, такие, как Symbol или ZapfDingbats, нужно передать пустую строку.
Кодировки, которые начинаются с СР, используются в ОС Windows. Linux системы обычно используют ISO.
Примечание : стандартные шрифты используют кодировку cp1252 .

Четвертый параметр дает возможность изменять кодировку. Иногда Вы можете добавить несколько символов. Так, например, ISO-8859-1 не содержит символ евро. Чтобы добавить его на позицию 164, нужно передать — array(164=>’Euro’) .

Последний параметр используется для передачи типа шрифта, в случае, если он не встроены (то есть если первый параметр пуст).

После того как Вы заполнили все параметры функции, Вы можете создать новый файл подключив при этом makefont.php, или просто добавить вызов функции непосредственно внутрь основного файла. После исполнения функции будет создано несколько файлов:.php и.afm. При желании Вы можете переименовать файл. Помимо этого скрипт создает файл с расширением.z, который является сжатым (за исключением случаев, когда функция сжатия недоступна, она требует Zlib). Вы можете переименовать и его тоже, но в этом случае Вы должны изменить переменную $file в.php файле, с соответствующим именем.

MakeFont("c:\\windows\\fonts\\comic.ttf" , "comic.afm" , "cp1252" ) ;

MakeFont("c:\\windows\\fonts\\comic.ttf","comic.afm","cp1252");

Выше приведенный пример создаст два файла: comic.php и comic.z .

Когда Вы получите эти файлы, их нужно скопировать в каталог с шрифтами. Если файл шрифта не получился сжатым то скопируйте файлы с расширением .ttf или .pfb , вместо .z .

Примечание: для шрифтов TTF, Вы можете не делать этого в ручную а скачать эти файлы с помощью утилиты по этому адресу: http://fpdf.fruit-lab.de/ . Я думаю что использование данного скрипта не составит у Вас больших трудностей, но все таки: Нужно выбрать файл TTF с компьютера, и потом при нажатии на единственную кнопку получите нужные файлы для FPDF.

Объявление шрифта в сценарии

Последний шаг является наиболее простым. Вам просто нужно вызвать AddFont () метод. Например:

$pdf->AddFont("Comic");

Теперь шрифт можно использовать. Если бы Вы выбрали другой шрифт, например Comic Sans MS Bold (comicbd.ttf), то нужно объявить его так:

$pdf -> AddFont ("Comic" , "B" , "comicbd.php" ) ;

$pdf->AddFont("Comic","B","comicbd.php");

Пример

Давайте посмотрим, маленький полностью рабочий пример. Будет использоваться шрифт Calligrapher, который Вы можете скачать на сайте — http://www.abstractfonts.com/ (сайт, предлагает большое количество бесплатных TrueType шрифтов). Ссылка для загрузки шрифта — http://www.abstractfonts.com/download/52 . Первым шагом является генерация AFM-файла:
ttf2pt1 -a calligra.ttf calligra

которая дает calligra.afm (и calligra.t1a, который можно удалить). Затем мы создаем файл определения:

require ("font/makefont/makefont.php" ) ; MakeFont("calligra.ttf" , "calligra.afm" ) ;

require("font/makefont/makefont.php"); MakeFont("calligra.ttf","calligra.afm");

Вызов функции даст следующие сообщения:
Warning: character Euro is missing
Warning: character eth is missing
Font file compressed (calligra.z)
Font definition file generated (calligra.php)

Символ Euro отсутствует, так как слишком старый. Другие символы также отсутствуют, однако они нам не понадобятся.
Теперь можно скопировать два файла в директорию и написать сценарий:

require ("fpdf.php" ) ; $pdf = new FPDF() ; $pdf -> AddFont ("Calligrapher" , "" , "calligra.php" ) ; $pdf -> AddPage () ; $pdf -> SetFont ("Calligrapher" , "" , 35 ) ; $pdf -> Cell (0 , 10 , "Enjoy new fonts with FPDF!" ) ; $pdf -> Output () ;

require("fpdf.php"); $pdf=new FPDF(); $pdf->AddFont("Calligrapher","","calligra.php"); $pdf->AddPage(); $pdf->SetFont("Calligrapher","",35); $pdf->Cell(0,10,"Enjoy new fonts with FPDF!"); $pdf->Output();

Вот что должно получиться в итоге:

О символе евро

В разных кодировках символ евро расположен на разных позициях да и вообще бывает он не во всех кодировках:

Кодировка

Позиция
128
136
128
128
128
128
128
128
128
отсутствует
отсутствует
отсутствует
отсутствует
отсутствует
отсутствует
Вместо ISO-8859-2, можно использовать ISO-8859-16, но эта кодировка содержит много различий. Поэтому здесь проще добавить в кодировку этот символ, как описано выше. То же самое верно и для других кодировок.

Соединение шрифтов под Windows

Если шрифт который Вы выбрали не доступен в том или ином стиле, Windows способен соединить его из обычной версии. Например, нет Comic Sans MS Italic, но он может быть построен из Comic Sans MS Regular. Эта функция может быть использована в файле PDF, но, к сожалению требует, чтобы обычный шрифт присутствовал в системе. Вот как это можно сделать:

  • Создать файл для определения обычного шрифта без вложения (можете, переименовать его, чтобы отразить необходимый стиль)
  • Откройте его и добавьте к переменной $name после запятой необходимый стиль (Italic, Bold or BoldItalic)

Например, для файла comici.php это будет выглядеть следующим образом:

$pdf->AddFont("Comic","I","comici.php");

Уменьшение размера TrueType шрифтов

Файлы шрифтов часто очень объемные по размеру(более 100, и даже 200 КБ), это связано с тем, что они содержат символы, которые соответствуют для многих кодировок. Zlib сжатие уменьшает их, но они остаются достаточно большими. НО все же есть методика, которая поможет еще уменьшить. Методика состоит в том что при преобразовании шрифта Type1 с помощью ttf2pt1 нужно указать кодировку которая Вам нужна, и все символы соответствующие другим кодировкам будут проигнорированы.
Например, шрифт arial.ttf который поставляется с Windows 98 весит 267KB (он содержит 1296 знаков). После сжатия будет 147. Давайте преобразуем его в Type1, сохраняя только символы нужные для кодировки cp1250:

Удачного Вам использования!

Продолжаем серию статей о мобильном геймдеве. В этой статье я расскажу как рендерить UTF-8 текст с помощью SDF Bitmap шрифтов, как эти шрифты создавать и как использовать эту технику для качественного рендеринга иконок.

SDF (Signed Distance Field ) - это изображение из оттенков серого, сгенерированное из контрастного черно-белого изображения, в котором уровень серого цвета означает дистанцию до ближайшей контрастной границы. Звучит запутанно, но на самом деле все очень просто.


Сам SDF шрифт выглядит так:



Давайте возьмем это изображение и изменим его уровни (levels) в фотошопе или любом другом графическом редакторе.



Выглядит уже лучше! У нас есть четкий шрифт со сглаживанием на краях.
Так же мы можем получить жирное или тонкое начертание. А вот получить Italic увы не получится .



Самый главный плюс SDF - это возможность увеличивать шрифт без заметных артефактов.



Как создавать SDF шрифт?

Прежде всего нужно создать самый обычный черно-белый bitmap шрифт. Сделать это можно в старом добром BMFont или в UBFG .


Для хорошего результата генерируйте шрифт размером 400pt, без сглаживания, с отступами 45x45x45x45 и размером картинки 4096x4096. Merging при таких размерах советую отключить т.к. скорее всего UBGF зависнет.


Экспортируем картинку в PNG без прозрачности, а формат описания желательно выбрать BMFont (для пущей совместимости).




convert font.png -filter Jinc (+clone -negate -morphology Distance Euclidean -level 50%,-50%) -morphology Distance Euclidean -compose Plus -composite -level 43%,57% -resize 12.5% font.png

На выходе мы получим картинку 512x512, которая даст нам в итоге весьма хороший результат.
Из файла с описанием нам нужно будет вытащить символы в unicode и их положение/размер (не забудьте разделить координаты на 8 т.к. мы уменьшали картинку). Какие именно символы надо экспортировать, я расскажу чуть ниже в разделе про UTF-8.


Минутку, в UBFG ведь есть встроенный Distance Field!
Да, есть. Но результат получается заметно хуже. Возможно в обновлениях авторы UBFG это поправят.

Шейдеры для рендеринга текста

Вертексный шейдер для вывода каждой буквы, символ за символом:


#ifdef DEFPRECISION precision mediump float; #endif attribute mediump vec2 Vertex; uniform highp mat4 MVP; uniform mediump vec2 cords; varying mediump vec2 outTexCord; void main(){ outTexCord=Vertex*cords+cords; gl_Position = MVP * vec4(Vertex*cords+cords, 0.0, 1.0); }

DEFPRECISION нужен для OpenGL ES.
В cords и cords передаем положение и скейл символа на экране.
А в cords и cords - координаты символа на текстуре шрифта.


Фрагментный шейдер


#ifdef DEFPRECISION precision mediump float; #endif varying mediump vec2 outTexCord; uniform lowp sampler2D tex0; uniform mediump vec4 color; uniform mediump vec2 params; void main(void){ float tx=texture2D(tex0, outTexCord).r; float a=min((tx-params.x)*params.y, 1.0); gl_FragColor=vec4(color.rgb,a*color.a); }

В color передаем цвет и прозрачность буквы.
А через params регулируем толщину и сглаживание краев шрифта.


Если можно регулировать толщину шрифта, то значит можно выводить и рамку!
Фрагментный шейдер текста с рамкой :


#ifdef DEFPRECISION precision mediump float; #endif varying mediump vec2 outTexCord; uniform lowp sampler2D tex0; uniform mediump vec4 color; uniform mediump vec4 params; uniform mediump vec3 borderColor; void main(void){ float tx=texture2D(tex0, outTexCord).r; float b=min((tx-params.z)*params.w, 1.0); float a=clamp((tx-params.x)*params.y, 0.0, 1.0); gl_FragColor=vec4(borderColor+(color.rgb-borderColor)*a, b*color.a); }

Дополнительно мы передаем толщину, сглаживание в params.zw и цвет рамки в borderColor .
Должен получиться вот такой результат:



Чтобы получить красивые края как при маленьких, так и при больших размерах текста, надо подобрать разные параметры контраста/сглаживания (params ) для маленького шрифта и для большого. Затем интерполировать их по текущему размеру.


На мой взгляд, для маленьких размеров хорошо подходит:

  • более жирное начертание
  • более сглаженные края
  • бордюр минимальный и размытый, чтобы не рябил

Для большого размера :

  • более тонкое начертание шрифта
  • края очень резкие
  • бордюр больше и резче

Иконки


В современном дизайне довольно популярными стали плоские иконки. Бесплатных векторных иконок полным полно . Все что нам нужно сделать - собрать черно-белый текстурный атлас из нужных иконок и точно так же прогнать его через ImageMagick!


В итоге мы можем хранить иконки в довольно низком разрешении, но получать хороший результат при скейле и вращении иконок!


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

UTF-8

В современных проектах никто уже не использует однобайтные кодировки. Все перешли на UTF-8, wchar, unicode. Мне например удобно работать со строками в UTF-8 char*.
UTF-8 легко раскодируется в unicode и отлично стыкуется с Java/String и NSString.


Ф-ция преобразования UTF-8 в Unicode:


static inline unsigned int UTF2Unicode(const unsigned char *txt, unsigned int &i){ unsigned int a=txt; if((a&0x80)==0)return a; if((a&0xE0)==0xC0){ a=(a&0x1F)<<6; a|=txt&0x3F; }else if((a&0xF0)==0xE0){ a=(a&0xF)<<12; a|=(txt&0x3F)<<6; a|=txt&0x3F; }else if((a&0xF8)==0xF0){ a=(a&0x7)<<18; a|=(a&0x3F)<<12; a|=(txt&0x3F)<<6; a|=txt&0x3F; } return a; }

Бонус! Изменяем реестр unicode символа.

static inline unsigned int uppercase(unsigned int a){ if(a>=97 && a<=122)return a-32; if(a>=224 && a<=223)return a-32; if(a>=1072 && a<=1103)return a-32; if(a>=1104 && a<=1119)return a-80; if((a%2)!=0){ if(a>=256 && a<=424)return a-1; if(a>=433 && a<=445)return a-1; if(a>=452 && a<=476)return a-1; if(a>=478 && a<=495)return a-1; if(a>=504 && a<=569)return a-1; if(a>=1120 && a<=1279)return a-1; } return a; } static inline unsigned int lowercase(unsigned int a){ if(a>=65 && a<=90)return a+32; if(a>=192 && a<=223)return a+32; if(a>=1040 && a<=1071)return a+32; if(a>=1024 && a<=1039)return a+80; if((a%2)==0){ if(a>=256 && a<=424)return a+1; if(a>=433 && a<=445)return a+1; if(a>=452 && a<=476)return a+1; if(a>=478 && a<=495)return a+1; if(a>=504 && a<=569)return a+1; if(a>=1120 && a<=1279)return a+1; } return a; }

Блоки UTF-8

В большинстве шрифтов, особенно креативных, есть только ascii и latin. Как же быть, если нам нужны, например, символы валют? Особенно актуально для in-app платежей, где какие только валюты не попадаются. Предлагаю следующую схему, которая очень хорошо себя зарекомендовала:



Как узнать какие символы есть в шрифте?


Тут на помощь нам приходит странная штука от Adobe - тада! - пустой шрифт !
Его можно использовать в CSS: font-family: Roboto, Adobe Blank;
Именно так получены таблички из картинки выше. Остается только скопировать нужные куски символов и вставить их в UBFG. В итоге мы получим несколько картинок 512х512, где каждая будет содержать столько символов, сколько в нее влезет.


Что за универсальный шрифт?



Допустим вы добавили битмапы для арабского, японского и китайского языков. Выйдет довольно много картинок. Не спешите их все загружать! Дождитесь когда вам действительно попадется символ из этого блока и подгрузите нужную текстуру.


Так же есть подвох в том, что все шрифты разного размера и разным baseline. При переходе с шрифта на шрифт текст будет скакать. Поэтому для каждого шрифта подберите параметры его относительного скейла и сдвига по Y. Учитывайте эти параметры при рендеринге каждого символа.


Я обещал плюшки!
Ловите готовый SDF шрифт Quivira уже порезанный на блоки!

) авторства Dr Markus Kuhn из Кембриджа. По сути это просто текст в кодировке UTF-8, однако вся соль в том, что в нем содержатся различные «фишки» кодировки, вроде combining characters . Как вы увидите, даже «простой текст» браузеры отображают кое-где по-разному и кое-где вообще не отображают. Сводная таблица прохождения теста для некоторых известных имен (под Windows 7 64-bit, шрифты по умолчанию):

Стоит заметить, что в Chrome, Firefox, IE моноширинным шрифтом по умолчанию является Courier New, в Opera - Consolas.

Комментарии

Mathematics and sciences
Chrome не нашел часть символов и заменил их квадратами. IE все нашел, но выдал

Firefox и Opera тоже все нашли, но отрендерили еще хуже. Amaya не нашел практически ничего.

Linguistics and dictionaries

Никаких претензий ко всем участникам.

Nicer typography in plain text files

Только Opera смогла показать круглые кавычки (curly apostrophes) корректно. Остальные с этим не справились. Впрочем, проблемы рядом с правым нижним углом рамки возникли у всех.

Combining characters

Идея в том, чтобы символы группировались, вместо того чтобы рисоваться по очереди. В общем, пока что не рисуются. Нужно заметить, что у Opera почти получилось - не поддался только знак вектора.

Greek (in Polytonic), Georgian, Russian

С греческим, грузинским и русским языками у браузеров все в порядке.

Thai (UCS Level 2)

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

Ethiopian

IE и Amaya не нашли эфиопского языка.

Настоящие древние руны! Жаль, что Chrome и Amaya не нашли соответствующего шрифта.

Braille

Шрифт Брайля . Аналогично предыдущему тесту.

Compact font selection example text

IE и Amaya не нашли некоторых символов и заменили их квадратами. Firefox странным образом совершил отступ на предпоследней строке.

Greetings in various languages

Все браузеры справились с этим тестом.
Box drawing alignment
Chrome

Internet Explorer

Firefox

Opera

Amaya

Резюме

Amaya 11 я включил в тестирование, потому что думал, что раз это официальный браузер W3C, то и спецификациям он должен соответствовать наиболее полно. Однако он показал самый слабый результат. В тестах заслуженно лидирует Opera, но даже она набрала лишь две трети очков. Хоть современные браузеры и поддерживают юникод и большинство языков, соответствие стандартам явно хромает. Конечно, никто в реальной жизни не будет верстать рамки или круглые кавычки в UTF-8, и это не критично. Тем не менее, в погоне за новыми тегами HTML5 и производительностью Javascript, разработчики могли бы уделить больше внимания рендерингу текста в кодировке UTF-8.

Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: