Задача
СкопированоВ работе разработчика много рутинных задач. Например, подготовка шрифтов. В зависимости от дизайна, требований к скорости загрузки, возможной поддержки нескольких языков и прочих условий, эта работа может занимать много времени.
Существует множество практических советов, как реализовать эту задачу максимально круто. Но все они сводятся к большому количеству рутинных действий. Причём ежедневно подготовкой шрифтов никто не занимается. Нужно время на то, чтобы вспомнить, как использовать инструменты, наверняка пару раз что-то пойдёт не так. Я предлагаю максимально сосредоточиться на процессе разработки, а не на скучных и однообразных действиях. Если есть возможность что-то автоматизировать, то я удержаться не могу.
Готовое решение
СкопированоВам понадобится:
- Терминал, который поддерживает команды Unix-подобных операционных систем: терминал на macOS или Linux, WSL под Windows.
- Node.js — среда выполнения для языка JavaScript, о которой подробнее написано в статье «Что такое Node.js».
- Glyphhanger — популярный инструмент для работы со шрифтами, который можно установить командой
npm install
.- g glyphhanger
Я использую заранее заготовленный скрипт для проектов. Например, если в проекте нужно использовать несколько вариаций шрифтов Fira с поддержкой латиницы, кириллицы, кода и математических выражений, я использую вот такой скрипт font.sh:
# Основной скрипт для генерации файлов шрифтовGENERATE_FONT() { for i in "${!SUBSETNAMES[@]}"; do for j in "${!FORMATS[@]}"; do for k in "${!FILENAMES[@]}"; do INPUT="$INPUTPATH/${FILENAMES[k]}-subset.${FORMATS[j]}" OUTPUT="$OUTPUTPATH/${FILENAMES[k]}-${SUBSETNAMES[i]}.${FORMATS[j]}" echo "----------------\n$INPUT -> $OUTPUT\n----------------\n" glyphhanger --whitelist="${SUBSETCODES[i]}" --formats="${FORMATS[j]}" --subset="$INPUTPATH/${FILENAMES[k]}.$EXTENSION" --css mv $(echo "$INPUT") $(echo "$OUTPUT") done done done}# Глобальные переменные# Набор названий для сабсетовSUBSETNAMES=("Latin" "LatinSupplement" "LatinExtendedA" "LatinExtendedB" "GreekCoptic" "Cyrilic" "CyrilicSupplement")# Набор диапазонов кодов глифов для каждого из сабсетовSUBSETCODES=("0000−007F" "0080−00FF" "0100−017F" "0180−024F" "0370−03FF" "0400−04FF" "0500−052F")FORMATS=("woff" "woff2")# Набор значений переменных для шрифта FiraSansFILENAMES=("FiraSans-Black" "FiraSans-BlackItalic" "FiraSans-Bold" "FiraSans-BoldItalic" "FiraSans-ExtraBold" "FiraSans-ExtraBoldItalic" "FiraSans-ExtraLight" "FiraSans-LightItalic" "FiraSans-Italic" "FiraSans-Light" "FiraSans-LightItalic" "FiraSans-Medium" "FiraSans-MediumItalic" "FiraSans-Regular" "FiraSans-SemiBold" "FiraSans-SemiBoldItalic" "FiraSans-Thin" "FiraSans-ThinItalic")EXTENSION="ttf"INPUTPATH="./service/fonts/Fira-Sans"OUTPUTPATH="./src/fonts/Fira-Sans"GENERATE_FONT# Набор значений переменных для шрифта FiraCodeFILENAMES=("FiraCode-Bold" "FiraCode-Light" "FiraCode-Medium" "FiraCode-Regular" "FiraCode-SemiBold")EXTENSION="ttf"INPUTPATH="./service/fonts/Fira-Code"OUTPUTPATH="./src/fonts/Fira-Code"GENERATE_FONT# Набор значений переменных для шрифта FiraMathFILENAMES=("FiraMath-Regular")EXTENSION="otf"INPUTPATH="./service/fonts/Fira-Math"OUTPUTPATH="./src/fonts/Fira-Math"GENERATE_FONT
# Основной скрипт для генерации файлов шрифтов GENERATE_FONT() { for i in "${!SUBSETNAMES[@]}"; do for j in "${!FORMATS[@]}"; do for k in "${!FILENAMES[@]}"; do INPUT="$INPUTPATH/${FILENAMES[k]}-subset.${FORMATS[j]}" OUTPUT="$OUTPUTPATH/${FILENAMES[k]}-${SUBSETNAMES[i]}.${FORMATS[j]}" echo "----------------\n$INPUT -> $OUTPUT\n----------------\n" glyphhanger --whitelist="${SUBSETCODES[i]}" --formats="${FORMATS[j]}" --subset="$INPUTPATH/${FILENAMES[k]}.$EXTENSION" --css mv $(echo "$INPUT") $(echo "$OUTPUT") done done done } # Глобальные переменные # Набор названий для сабсетов SUBSETNAMES=("Latin" "LatinSupplement" "LatinExtendedA" "LatinExtendedB" "GreekCoptic" "Cyrilic" "CyrilicSupplement") # Набор диапазонов кодов глифов для каждого из сабсетов SUBSETCODES=("0000−007F" "0080−00FF" "0100−017F" "0180−024F" "0370−03FF" "0400−04FF" "0500−052F") FORMATS=("woff" "woff2") # Набор значений переменных для шрифта FiraSans FILENAMES=("FiraSans-Black" "FiraSans-BlackItalic" "FiraSans-Bold" "FiraSans-BoldItalic" "FiraSans-ExtraBold" "FiraSans-ExtraBoldItalic" "FiraSans-ExtraLight" "FiraSans-LightItalic" "FiraSans-Italic" "FiraSans-Light" "FiraSans-LightItalic" "FiraSans-Medium" "FiraSans-MediumItalic" "FiraSans-Regular" "FiraSans-SemiBold" "FiraSans-SemiBoldItalic" "FiraSans-Thin" "FiraSans-ThinItalic") EXTENSION="ttf" INPUTPATH="./service/fonts/Fira-Sans" OUTPUTPATH="./src/fonts/Fira-Sans" GENERATE_FONT # Набор значений переменных для шрифта FiraCode FILENAMES=("FiraCode-Bold" "FiraCode-Light" "FiraCode-Medium" "FiraCode-Regular" "FiraCode-SemiBold") EXTENSION="ttf" INPUTPATH="./service/fonts/Fira-Code" OUTPUTPATH="./src/fonts/Fira-Code" GENERATE_FONT # Набор значений переменных для шрифта FiraMath FILENAMES=("FiraMath-Regular") EXTENSION="otf" INPUTPATH="./service/fonts/Fira-Math" OUTPUTPATH="./src/fonts/Fira-Math" GENERATE_FONT
Такой скрипт позволяет любому участнику проекта быстро сгенерировать или обновить набор шрифтов проекта. Исходники лежат в _ ./service/fonts/<Название-Шрифта>, скрипт создаст нужные шрифты и положит в папку _ ./src/fonts/<Название-Шрифта>.
Установите права на исполнение:
chmod 755 font.sh
chmod 755 font.sh
Запустить скрипт можно так:
sh font.sh
sh font.sh
Разбор решения
СкопированоОбычно формирование набора шрифтов происходит по следующему алгоритму:
- Анализ контента сайта на предмет выбора наборов символов с точки зрения максимальной оптимизации загрузки страницы.
- Выделение набора гарнитур по макету сайта
- Формирование набора имён для файлов
- Загрузка исходных файлов шрифтов с максимальным набором символов
- Генерация набора файлов шрифтов на основе исходных файлов
Набор символов определяется на основе контента. Стремиться нужно к тому, чтобы загружался только набор тех символов и шрифтов, которые реально используются на странице. Для определения набора символов можно использовать всё ту же утилиту Glyphhanger:
glyphhanger http://doka.guide/
glyphhanger http://doka.guide/
Так можно узнать, какие символы используются на странице сайта http://doka.guide/. Локальные файлы тоже можно посмотреть:
glyphhanger ./test.html
glyphhanger ./test.html
Принятым стандартом для формирования файлов шрифтов является использование таблицы символов UTF-8 или UTF-16. Каждый символ обозначается кодом в формате шестнадцатеричного числа. Диапазоны записываются через тире. Есть наборы символов, которые в основном встречаются в текстах на том или ином языке. Можно использовать уже определённые заранее наборы через закреплённые названия в соответствии со стандартами. Именно такой подход используется в моём скрипте. Есть неплохой сайт, чтобы наглядно увидеть разные наборы символов и соответствующие им названия.
Основа скрипта
СкопированоОсновой для работы является функция, которая генерирует набор файлов с помощью утилиты Glyphhanger:
# Функция для генерации шрифтовGENERATE_FONT() { # Цикл для прохода по всем именам сабсетов for i in "${!SUBSETNAMES[@]}"; do # Цикл для прохода по всем форматам for j in "${!FORMATS[@]}"; do # Цикл для прохода по всем именам файлов шрифтов for k in "${!FILENAMES[@]}"; do # Формирование имени файла на входе INPUT="$INPUTPATH/${FILENAMES[k]}-subset.${FORMATS[j]}" # Формирование имени файла на выходе OUTPUT="$OUTPUTPATH/${FILENAMES[k]}-${SUBSETNAMES[i]}.${FORMATS[j]}" # Вывод в терминал информации о сформированных файлах echo "----------------\n$INPUT -> $OUTPUT\n----------------\n" # Генерация шрифта с помощью glyphhanger glyphhanger --whitelist="${SUBSETCODES[i]}" --formats="${FORMATS[j]}" --subset="$INPUTPATH/${FILENAMES[k]}.$EXTENSION" --css # Перемещение сформированного файла в новое место mv $(echo "$INPUT") $(echo "$OUTPUT") done done done}
# Функция для генерации шрифтов GENERATE_FONT() { # Цикл для прохода по всем именам сабсетов for i in "${!SUBSETNAMES[@]}"; do # Цикл для прохода по всем форматам for j in "${!FORMATS[@]}"; do # Цикл для прохода по всем именам файлов шрифтов for k in "${!FILENAMES[@]}"; do # Формирование имени файла на входе INPUT="$INPUTPATH/${FILENAMES[k]}-subset.${FORMATS[j]}" # Формирование имени файла на выходе OUTPUT="$OUTPUTPATH/${FILENAMES[k]}-${SUBSETNAMES[i]}.${FORMATS[j]}" # Вывод в терминал информации о сформированных файлах echo "----------------\n$INPUT -> $OUTPUT\n----------------\n" # Генерация шрифта с помощью glyphhanger glyphhanger --whitelist="${SUBSETCODES[i]}" --formats="${FORMATS[j]}" --subset="$INPUTPATH/${FILENAMES[k]}.$EXTENSION" --css # Перемещение сформированного файла в новое место mv $(echo "$INPUT") $(echo "$OUTPUT") done done done }
Вы всегда сможете вместо этой утилиты использовать какую-то другую. По аналогии, можно автоматически сжать картинки или подготовить картинки в разных форматах и размерах с помощью Squoosh CLI.
Подходы к реализации
СкопированоТакую функцию можно использовать в интерактивных скриптах, которые взаимодействуют с командной строкой. Достаточно считать аргументы, в которых передаются значения, например, имена директорий для исходных и конечных файлов. Обработать аргументы можно так:
# Формирует пустой массив для аргументовPOSITIONAL_ARGS=()# Запускает цикл прохода по всем аргументамwhile [[ $# -gt 0 ]]; do case $1 in # Считывание первого аргумента по короткому ключу -i или длинному ключу --input-path -i|--input-path) INPUT_PATH="$2" shift # ключ shift # значение ;; # Считывание первого аргумента по короткому ключу -o или длинному ключу --output-path -o|--output-path) OUPUT_PATH="$2" shift # ключ shift # значение ;; # Обработка ситуации, когда ключ неизвестен -*|--*) echo "Неизвестный ключ $1" exit 1 ;; *) # Сохранение значений POSITIONAL_ARGS+=("$1") # сохранить значения аргументов shift # последний аргумент ;; esacdone# восстановление позиции аргументовset -- "${POSITIONAL_ARGS[@]}"# Для примера просто выведем аргументы на экранecho "INPUT_PATH = ${INPUT_PATH}"echo "OUPUT_PATH = ${OUPUT_PATH}"# Если аргумент не известен, то надо об этом рассказатьif [[ -n $1 ]]; then echo "Аргумент не известен:" tail -1 "$1"fi
# Формирует пустой массив для аргументов POSITIONAL_ARGS=() # Запускает цикл прохода по всем аргументам while [[ $# -gt 0 ]]; do case $1 in # Считывание первого аргумента по короткому ключу -i или длинному ключу --input-path -i|--input-path) INPUT_PATH="$2" shift # ключ shift # значение ;; # Считывание первого аргумента по короткому ключу -o или длинному ключу --output-path -o|--output-path) OUPUT_PATH="$2" shift # ключ shift # значение ;; # Обработка ситуации, когда ключ неизвестен -*|--*) echo "Неизвестный ключ $1" exit 1 ;; *) # Сохранение значений POSITIONAL_ARGS+=("$1") # сохранить значения аргументов shift # последний аргумент ;; esac done # восстановление позиции аргументов set -- "${POSITIONAL_ARGS[@]}" # Для примера просто выведем аргументы на экран echo "INPUT_PATH = ${INPUT_PATH}" echo "OUPUT_PATH = ${OUPUT_PATH}" # Если аргумент не известен, то надо об этом рассказать if [[ -n $1 ]]; then echo "Аргумент не известен:" tail -1 "$1" fi
Теперь можно запустить командой:
sh script.sh -i digits -o alphabet
sh script.sh -i digits -o alphabet
Если есть возможность облегчить себе труд в будущем, то надо обязательно это сделать. В будущем вы сэкономите очень много времени.