Перейти к основному содержимому
Мобилка25ГЛАВА 25Иконочный шрифт: свои иконки одним файломчетыре svgkit_icons.ttfF101F102F103F1041932 байта на всё

Иконочный шрифт: свои иконки одним файлом

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Последний пункт списка звучит неожиданно: «Создайте шрифт из представленных иконок». Не «добавьте иконки», а именно создайте шрифт.

Задание не выдуманное и не бессмысленное. Иконочный шрифт — старый и до сих пор живой приём: все иконки лежат в одном файле, каждая доступна как обычный символ, красится цветом текста и масштабируется вместе с ним. Никаких отдельных размеров под каждую плотность экрана.

В этой главе мы соберём такой шрифт из четырёх SVG и подключим его в Compose. Всё ниже — настоящий прогон: файлы собраны, проверены и скомпилированы.

Из SVG в шрифт​

ЧЕТЫРЕ SVG → ОДИН ФАЙЛ ШРИФТА → ЧЕТЫРЕ СИМВОЛАsvg/cart.svg, search.svg…по файлу на иконкусборщикfantasticonимя файла станет именем глифаkit_icons.ttf1932 байтавсе четыре в одном файлесимвол'\uF104'иконка как обычный текстшрифт красится цветом текста и масштабируется как текст — отдельных размеров не нужнозато он одноцветный: многоцветную иллюстрацию так не положишьчетыре иконки уместились в 1932 байта — меньше одной картинки
По файлу на иконку — один файл шрифта — четыре символа

Исходники — обычные SVG, по файлу на иконку:

svg/
cart.svg
heart.svg
search.svg
user.svg

Сборка одной командой:

npx fantasticon@4.1.0 svg -o out -n kit_icons -t ttf -g json --font-height 1000 --normalize
✔ 4 SVGs found in svg
✔ Generated out/kit_icons.ttf
✔ Generated out/kit_icons.json
Done
Нажми на часть выражения

Что получилось:

-rw-r--r-- 1932 kit_icons.ttf
-rw-r--r-- 81 kit_icons.json

Четыре иконки в 1932 байтах — меньше, чем одна картинка среднего размера.

Совет

Если сборщик ругается Invalid option outputDir: out is not a directory — папку вывода надо создать самому: mkdir -p out. Сборщик её не создаёт, и сообщение об этом не говорит.

Проверяем, что собралось​

Верить сборщику на слово не стоит — файл шрифта можно открыть и посмотреть:

import fontforge
f = fontforge.open('out/kit_icons.ttf')
print('семейство:', f.familyname, '| глифов:', len(list(f.glyphs())))
for g in f.glyphs():
if g.unicode > 0:
print(' U+%04X %-8s ширина %d' % (g.unicode, g.glyphname, g.width))
print('единиц на em:', f.em)
семейство: kit_icons | глифов: 5
U+F101 user ширина 1000
U+F102 search ширина 1000
U+F103 heart ширина 1000
U+F104 cart ширина 1000
единиц на em: 1000

Четыре глифа на местах, имена совпадают с именами файлов, ширина у всех одинаковая. Пятый глиф — служебный пустой, он есть в любом шрифте.

Интересный факт

Одинаковая ширина у всех иконок — не случайность и не мелочь. Если одна окажется шире, ряд иконок в панели поедет, а выравнивание по сетке развалится. Именно это делает флаг --normalize: приводит все исходники к одной высоте и ширине.

Отвечено вопросов: 0 из 3
Чем иконочный шрифт удобнее набора картинок?
Откуда берутся имена глифов?
Зачем всем иконкам одинаковая ширина?
Отвечено верно: 0 из 3

Почему коды начинаются с F1​

ПОЧЕМУ КОДЫ НАЧИНАЮТСЯ С F1U+0041Aзанято латиницейU+0410Азанято кириллицейU+F101userобласть для частного использования — свободнаU+F104cartникакой шрифт мира сюда не претендуетвозьми обычную букву — и текст «Cart» превратился бы в иконки на любом устройстведиапазон U+E000…U+F8FF зарезервирован именно под такие случаи
Диапазон U+E000…U+F8FF зарезервирован под такие случаи

Карта символов, которую выдал сборщик:

{
"user": 61697,
"search": 61698,
"heart": 61699,
"cart": 61700
}

В шестнадцатеричном виде это U+F101–U+F104 — область для частного использования. Юникод специально оставил диапазон U+E000…U+F8FF пустым: туда никогда не попадёт ни буква, ни знак препинания, ни эмодзи.

Смысл в том, чтобы не пересечься с настоящим текстом. Посади иконку на код буквы A — и слово «Cart», написанное этим шрифтом, превратилось бы в набор картинок.

Важно

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

Подключаем в Compose​

Файл кладётся в ресурсы:

ui/src/main/res/font/kit_icons.ttf
Совет

Имя файла в res/font подчиняется правилам ресурсов Android: только строчные латинские буквы, цифры и подчёркивание. kit_icons.ttf годится, kit-icons.ttf или kitIcons.ttf — нет, сборка упадёт.

Дальше — семейство шрифта и перечисление иконок:

val KitIconFont = FontFamily(Font(R.font.kit_icons))

enum class KitIcon(val glyph: Char) {
User('\uF101'),
Search('\uF102'),
Heart('\uF103'),
Cart('\uF104'),
}
Нажми на часть выражения

И сам компонент:

@Composable
fun KitIconGlyph(
icon: KitIcon,
modifier: Modifier = Modifier,
size: TextUnit = 24.sp,
tint: Color = LocalContentColor.current,
description: String? = null,
) {
Text(
text = icon.glyph.toString(),
modifier = modifier.clearAndSetSemantics {
if (description != null) contentDescription = description
},
fontFamily = KitIconFont,
fontSize = size,
color = tint,
)
}
Нажми на часть выражения
Отвечено вопросов: 0 из 3
Почему коды иконок берут из диапазона U+E000…U+F8FF?
Почему имя файла шрифта kit_icons.ttf, а не kitIcons.ttf?
Почему размер иконки задают в sp, а не в dp?
Отвечено верно: 0 из 3

Иконка со смыслом и иконка-украшение​

ИКОНКА СО СМЫСЛОМ И ИКОНКА-УКРАШЕНИЕНесёт смыслкнопка с одной корзиной и без подписиdescription = "Корзина"диктор прочитает — иначе кнопка немаяУкрашениета же корзина рядом со словом «Корзина»description = nullдиктор пропустит: смысл уже в текстепустое описание хуже обоих вариантов: диктор прочитает символ из частной области как мусоррешает не иконка, а то, есть ли рядом текст с тем же смыслом
Решает не иконка, а то, есть ли рядом текст с тем же смыслом

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

Описание — когда иконка единственный носитель смысла:

KitIconGlyph(icon = KitIcon.Cart, description = "Корзина")

Кнопка с одной корзиной и без подписи без описания становится немой: диктор скажет «кнопка» и всё.

Явное «ничего» — когда рядом есть текст с тем же смыслом:

Row {
KitIconGlyph(icon = KitIcon.Cart) // description = null
Text("Корзина")
}

Здесь описание только помешало бы: диктор прочитал бы «Корзина. Корзина».

Важно

Худший вариант — не задать ничего и не убрать символ из дерева доступности. Тогда диктор попытается прочитать сам глиф. Именно это и делает clearAndSetSemantics: убирает символ, а описание ставит только если оно есть.

Когда иконочный шрифт не подходит​

Приём хороший, но не всеобщий. Три случая, когда берут обычные векторные ресурсы:

  • иконка многоцветная — шрифт одноцветный по устройству, цвет у него один на весь глиф;
  • иконка анимируется — шрифт не умеет ни морфинга, ни частичной отрисовки;
  • иконок три штуки — заводить сборку шрифта ради трёх значков дороже, чем положить три файла.
Интересный факт

Отраслевой ход последних лет — обратный: Material перешёл с иконочного шрифта на векторные ресурсы, потому что те поддерживают несколько цветов и переменные начертания. Так что знать приём стоит, а применять — когда он прямо требуется заданием или когда иконок действительно много и все одноцветные.

Типичные ошибки​

Ошибка 1. Папка вывода не создана.

Invalid option outputDir: out is not a directory

mkdir -p out перед сборкой.

Ошибка 2. Имя файла с дефисом или заглавными.

res/font принимает только строчные, цифры и подчёркивание. Сборка падает на этапе ресурсов.

Ошибка 3. Коды вписаны в разметку прямо.

Text(text = "\uF104", fontFamily = KitIconFont)

Работает, но через месяц никто не помнит, что такое F104. Перечисление с именами — одна строка кода и никакой магии.

Ошибка 4. Размер в dp.

Иконка перестанет расти вместе с системным шрифтом, а рядом стоящий текст вырастет — значок окажется меньше подписи.

Ошибка 5. Нет ни описания, ни его отсутствия.

Диктор читает символ из частной области как мусор.

Ошибка 6. Иконки разной ширины.

Без --normalize исходники приходят как есть, и ряд значков в панели съезжает.

Ошибка 7. Шрифт пересобран, а коды не проверены.

Сборщик раздаёт коды по порядку обхода файлов. Добавили иконку с именем, которое встало в середину списка, — и все последующие коды сдвинулись. Карту символов после каждой пересборки берут заново.

Отвечено вопросов: 0 из 3
Добавили новую иконку и пересобрали шрифт. Что проверить?
Когда иконочный шрифт НЕ подходит?
Что делает clearAndSetSemantics у иконки?
Отвечено верно: 0 из 3

Шпаргалка по главе​

Что нужноКак
Собрать шрифтnpx fantasticon svg -o out -n kit_icons -t ttf -g json --font-height 1000 --normalize
Создать папку выводаmkdir -p out — сборщик её не создаёт
Проверить результатfontforge -script или любой просмотрщик шрифтов
Куда положитьres/font/kit_icons.ttf, имя только строчными
СемействоFontFamily(Font(R.font.kit_icons))
Имена вместо кодовenum class KitIcon(val glyph: Char)
Размерв sp, не в dp
ЦветLocalContentColor.current по умолчанию
ДоступностьclearAndSetSemantics + описание или его осознанное отсутствие

Слова главы​

ТерминЧто это
Иконочный шрифтШрифт, где вместо букв нарисованы значки
ГлифОдно начертание в шрифте; у нас — одна иконка
Область частного использованияU+E000…U+F8FF: коды, которые Юникод оставил свободными
Единиц на emВнутренняя точность шрифта; 1000 — принятое значение
Дерево доступностиТо, что читает экранный диктор
УкрашениеИконка, дублирующая соседний текст; диктор её пропускает

Цель: скорость ≥ 95 зн/мин, точность ≥ 90%

mkdir -p out

Что должен уметь после главы​

  • собрать иконочный шрифт из набора SVG одной командой;
  • проверить получившийся файл: имена глифов, коды и ширину;
  • объяснить, почему коды берут из области частного использования;
  • подключить шрифт в Compose и завести имена вместо кодов;
  • отличить иконку со смыслом от украшения и задать доступность;
  • назвать три случая, когда иконочный шрифт не подходит.

Проверка по главе​

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

Экзамен по блоку: Библиотека компонентов

Сводная проверка по 7 главам блока: 25 вопросов, 10:00 на всё, по одному вопросу, без возврата. Пересдавать можно сколько угодно раз.

Готовность: экзамены глав сданы 0 из 7. Блок можно сдавать и так, но шанс выше после всех глав.

Куда дальше: проверенные ресурсы​

  • fantasticon — сборщик, которым собран шрифт этой главы.
  • Шрифты в Compose — FontFamily, ресурсы шрифтов и их правила именования.
  • Private Use Area — почему диапазон свободен и как им пользоваться.
  • Доступность в Compose — семантика, описания и когда их не задают.

Видео по теме

ВидеоКак создать иконочный шрифт из SVGWebmassa
ВидеоСоздание иконочного шрифта для версткиWebDesign Master
ВидеоКак создать для сайта свой ИКОНОЧНЫЙ ШРИФТ за 5 минутПРО ФРОНТЕНД
ВидеоUse Custom Fonts in Jetpack Compose Apps!Charfaoui Younes
ВидеоSVG или иконочный шрифт ? (font awesome, icomoon)От 0 до 1

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.