Продолжаем серию статей о мобильном геймдеве. В этой статье я расскажу как рендерить UTF-8 текст с помощью SDF Bitmap шрифтов, как эти шрифты создавать и как использовать эту технику для качественного рендеринга иконок.
SDF (Signed Distance Field
) - это изображение из оттенков серого, сгенерированное из контрастного черно-белого изображения, в котором уровень серого цвета означает дистанцию до ближайшей контрастной границы. Звучит запутанно, но на самом деле все очень просто.
Сам SDF шрифт выглядит так:
Давайте возьмем это изображение и изменим его уровни (levels) в фотошопе или любом другом графическом редакторе.
Выглядит уже лучше! У нас есть четкий шрифт со сглаживанием на краях.
Так же мы можем получить жирное или тонкое начертание. А вот получить Italic увы не получится
.
Самый главный плюс 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 это поправят.
Вертексный шейдер для вывода каждой буквы, символ за символом:
DEFPRECISION
нужен для OpenGL ES.
В cords
и cords
передаем положение и скейл символа на экране.
А в cords
и cords
- координаты символа на текстуре шрифта.
Фрагментный шейдер
В color
передаем цвет и прозрачность буквы.
А через params
регулируем толщину и сглаживание краев шрифта.
Если можно регулировать толщину шрифта, то значит можно выводить и рамку!
Фрагментный шейдер текста с рамкой
:
Дополнительно мы передаем толщину, сглаживание в params.zw
и цвет рамки в borderColor
.
Должен получиться вот такой результат:
Чтобы получить красивые края как при маленьких, так и при больших размерах текста, надо подобрать разные параметры контраста/сглаживания (params
) для маленького шрифта и для большого. Затем интерполировать их по текущему размеру.
На мой взгляд, для маленьких размеров хорошо подходит:
Для большого размера :
В современном дизайне довольно популярными стали плоские иконки. Бесплатных векторных иконок полным полно . Все что нам нужно сделать - собрать черно-белый текстурный атлас из нужных иконок и точно так же прогнать его через ImageMagick!
В итоге мы можем хранить иконки в довольно низком разрешении, но получать хороший результат при скейле и вращении иконок!
Бонусом можно легко добавить к иконкам градиент. Для этого надо просто повесить цвета на вертексы, а градиент получим за счет интерполяции между точками. Радиальный же градиент придется делать попиксельно в фрагментом шейдере.
В современных проектах никто уже не использует однобайтные кодировки. Все перешли на UTF-8, wchar, unicode. Мне например удобно работать со строками в UTF-8 char*.
UTF-8 легко раскодируется в unicode и отлично стыкуется с Java/String и NSString.
Ф-ция преобразования UTF-8 в Unicode:
Бонус! Изменяем реестр 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; }
В большинстве шрифтов, особенно креативных, есть только ascii и latin. Как же быть, если нам нужны, например, символы валют? Особенно актуально для in-app платежей, где какие только валюты не попадаются. Предлагаю следующую схему, которая очень хорошо себя зарекомендовала:
Как узнать какие символы есть в шрифте?
Тут на помощь нам приходит странная штука от Adobe - тада! - пустой шрифт !
Его можно использовать в CSS: font-family: Roboto, Adobe Blank;
Именно так получены таблички из картинки выше. Остается только скопировать нужные куски символов и вставить их в UBFG. В итоге мы получим несколько картинок 512х512, где каждая будет содержать столько символов, сколько в нее влезет.
Что за универсальный шрифт?
Допустим вы добавили битмапы для арабского, японского и китайского языков. Выйдет довольно много картинок. Не спешите их все загружать! Дождитесь когда вам действительно попадется символ из этого блока и подгрузите нужную текстуру.
Так же есть подвох в том, что все шрифты разного размера и разным baseline. При переходе с шрифта на шрифт текст будет скакать. Поэтому для каждого шрифта подберите параметры его относительного скейла и сдвига по Y. Учитывайте эти параметры при рендеринге каждого символа.
Я обещал плюшки!
Ловите готовый SDF шрифт Quivira уже порезанный на блоки!
В этом Уроке мы рассмотрим, как использовать Type1 или TrueType шрифты так, что бы можно было использовать не только стандартные шрифты. Другой особенностью является то что, вы сможете выбрать шрифт, кодировку, которая позволит Вам использовать не только западные но и другие Языки (стандартные шрифты имеют слишком мало доступных символов).
Существует два способа использования нового шрифта: вложенные шрифты в PDF или подключаемые. Если указанный Вами шрифт не встроен, то он будет искаться в системе. Преимущество подключаемых шрифтов заключается в том, что файл PDF гораздо легче весит, а с другой стороны, если он не доступен, то используется подстановка стандартного шрифта. Поэтому желательно, чтобы шрифт был установлен на клиентских системах. Если файл будет просматриваться не только на вашем локальном компьютере, то обязательно нужно его вставить в PDF.
Для 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]]])
Значения которые принимает метод, в качестве параметров:
Первым параметром должно быть имя и путь к шрифту. Расширение должно быть.ttf или.pfb. Если у вас есть шрифт Type1 в ASCII формате с расширением.pfa, Вы можете преобразовать его в двоичном формате с помощью утилиты t1utils .
Ранее сгенерированный файл AFM
Кодировка определяет связь между кодом (от 0 до 255) и характер. Первые 128 являются фиксированными и соответствуют ASCII, а следующие являются переменными. Кодировки хранятся в.map файлах. Кодировки бывают следующие:
Шрифт который Вы выберете должен содержать символы, соответствующие выбранной кодировке.
В особенных случаях когда символы шрифта не содержат литеры, такие, как 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, но, к сожалению требует, чтобы обычный шрифт присутствовал в системе. Вот как это можно сделать:
Например, для файла comici.php это будет выглядеть следующим образом: $pdf->AddFont("Comic","I","comici.php"); Уменьшение размера TrueType шрифтовФайлы шрифтов часто очень объемные по размеру(более 100, и даже 200 КБ), это связано с тем, что они содержат символы, которые соответствуют для многих кодировок. Zlib сжатие уменьшает их, но они остаются достаточно большими. НО все же есть методика, которая поможет еще уменьшить. Методика состоит в том что при преобразовании шрифта Type1 с помощью ttf2pt1 нужно указать кодировку которая Вам нужна, и все символы соответствующие другим кодировкам будут проигнорированы. Удачного Вам использования! |
) авторства Dr Markus Kuhn из Кембриджа. По сути это просто текст в кодировке UTF-8, однако вся соль в том, что в нем содержатся различные «фишки» кодировки, вроде combining characters . Как вы увидите, даже «простой текст» браузеры отображают кое-где по-разному и кое-где вообще не отображают. Сводная таблица прохождения теста для некоторых известных имен (под Windows 7 64-bit, шрифты по умолчанию):
Стоит заметить, что в Chrome, Firefox, IE моноширинным шрифтом по умолчанию является Courier New, в Opera - Consolas.
Firefox и Opera тоже все нашли, но отрендерили еще хуже. Amaya не нашел практически ничего.
Никаких претензий ко всем участникам.
Только Opera смогла показать круглые кавычки (curly apostrophes) корректно. Остальные с этим не справились. Впрочем, проблемы рядом с правым нижним углом рамки возникли у всех.
Идея в том, чтобы символы группировались, вместо того чтобы рисоваться по очереди. В общем, пока что не рисуются. Нужно заметить, что у Opera почти получилось - не поддался только знак вектора.
С греческим, грузинским и русским языками у браузеров все в порядке.
Сами символы нашли все браузеры. Однако внизу говорится, что при корректной отрисовке должно быть два ровных столбца. Этого не происходит.
IE и Amaya не нашли эфиопского языка.
Настоящие древние руны! Жаль, что Chrome и Amaya не нашли соответствующего шрифта.
Шрифт Брайля . Аналогично предыдущему тесту.
IE и Amaya не нашли некоторых символов и заменили их квадратами. Firefox странным образом совершил отступ на предпоследней строке.
Internet Explorer
Firefox
Opera
Amaya