Как стать автором
Обновить
2758.74
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Как я запрограммировала собственный рукописный шрифт

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров26K
Автор оригинала: Amy Goodchild

У меня нередко бывает, что я решу не увлекаться слишком сильно какой-то затеей, но в итоге всё равно в неё погружаюсь. Так было и на этот раз.

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

▍ Блочный шрифт


Предыдущая моя статья (англ.) была посвящена разработке печатной версии алфавита. Если коротко, то реализовала я его так:

  • Создала код для определения ключевых точек на пути написания каждой буквы (~10 точек на букву).
  • Сгладила эти пути с помощью алгоритма Чайкина.
  • Преобразовала пути в контуры букв с переменной толщиной.
  • Отрисовала полученные контуры с помощью библиотеки p5js.

Вот, что в итоге получилось:


Пути написания букв я определяла вручную, прописывая и подгоняя их позиции в коде, пока буквы не будут выглядеть нужным образом. Когда же дело коснулось программирования слитного письма, я упростила этот процесс.

▍ Дизайн букв


Для простоты работы я создала в редакторе p5js инструмент, упрощающий вывод ключевых точек в путях.

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

  • Кликами мыши отмечаются ключевые точки пути, который принимает форму кривой Чайкина.
  • Нажатием p происходит переход в режим редактирования.
  • Точки поочерёдно перетаскиваются в нужные позиции.
  • Нажатием «Ввод» готовый путь выводится в консоль.

Я создала для каждой буквы по 2-3 варианта.


Итоговый путь написания выглядит так:

[{x:0.7,y:22.5},{x:8.2,y:18.1},{x:8.9,y:11.2},{x:3.7,y:11.4},{x:1.7,y:18.9},{x:8.4,y:22.4},{x:17.7,y:22.0}] 

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

Клавиши w/a/s/d используются для размещения изображения в нужной точке, а r/e — для его приближения или отдаления. Размытая e на изображении выше выступает образцом.


Числа на этом листе бумаги представляют координаты x и y, обеспечивающие попадание образца в окно создания буквы.

После разметки всех путей, их выравнивания и преобразования в контуры с переменной толщиной (подробнее об этом в прошлой статье), отдельные буквы получились такими:


▍ Соединение букв


Иногда соединять между собой буквы легко — достаточно просто перейти сразу от одного пути ключевых точек к следующему, после чего алгоритм Чайкина разом их все объединит. Но некоторые пары букв связывать проблематично.

Возьмём, к примеру, пару na. На изображении ниже красным обозначена последняя точка написания n, а зелёным — первая точка a. Первая находится внизу, а вторая вверху, в результате чего объединяющая линия проходит по диагонали через a, делая её похожей на e.

Ещё один пример. В паре ti буква t заканчивается как раз над базовой линией, и написание i начинается оттуда, создавая неестественный выступ.


Чтобы исправить эти нюансы, в первом случае можно добавить в начало a дополнительную точку, а во втором удалить две последние точки из t.


Но такое изменение букв подойдёт не для всех сценариев.

Например, если a находится в начале слова, дополнительная точка приведёт к появлению лишнего хвоста, а если перед a будет идти w, то получится линия, проходящая через a иначе. Что касается t, то в случае перехода в k она деформируется.


Точки в начале и конце контуров букв должны менять своё положение в зависимости от того, какая буква идёт следующей.

Сначала я пробовала выделить конкретные «проблемные» пары и отдельно прописать для них правила, но в итоге решила этот вопрос иначе, добавив в начало и конец каждого пути одно число, определяющее один из четырёх возможных случаев:

  • Невозможность соединения со следующей буквой (0).
  • Соединение в районе базовой линии (1).
  • Соединение чуть выше базовой линии (2).
  • Соединение в районе x-height (3)

Вот несколько примеров:



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

[0,{x:12.2,y:13.2},{x:13.5,y:11.0},{x:6.2,y:8.4},{x:1.1,y:13.0},{x:1.8,y:19.0},{x:7.0,y:23.4},{x:15.2,y:23.6},{x:18.4,y:22.1},1],

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


Здесь также видны некоторые отклонения, вызванные наличием у каждой буквы нескольких путей и их редактированием в зависимости от того, какая буква идёт следующей. В идеале для каждой буквы у меня должно было получиться не менее 5-6 вариантов путей написания, но я стремилась к балансу, чтобы не раздувать размер файла.

▍ Создание слов


При создании слова:

  • Для каждой буквы из 2-3 вариантов выбирается базовый путь.
  • Информация о концах пути передаётся в смежные буквы (изначально должны быть выбраны все пути букв, поскольку в некоторых случаях разные их варианты для одной и той же буквы имеют разные конечные точки).
  • Смежные базовые пути сонастраиваются. Например, если высота предыдущей буквы равна 2, из начала этого пути удаляется одна точка, или если стартовая высота следующей буквы равна 1, в определённое место добавляется дополнительная точка.

Функции корректировки букв порой получаются сложными. Вот пример для буквы q:

// ip = путь 
// pc = информация о конце пути предыдущей буквы 
// nc = информация о начале следующей буквы
// n = индекс пути, выбранного для этой буквы
adjust: (ip, pc, nc, n) => {
  // Случайным образом добавляет в конец разрыв, равный 70% этой буквы.
  if (rand() < 0.7 ) ip.splice(-1, 1, 0);

   // Если из 4 возможных вариантов для этого пути был выбран [2], 
   if (n < 2) {

     // а предыдущая буква заканчивается на 3, заменить первые две точки на другую точку.
     if (pc == 3) ip.splice(1, 2, {x:10,y:12});

     // В противном случае, если это не 0, добавить точку в начало.
     else if (pc > 0) ip.splice(1, 0, {x:10,y:20});
  }

  // Если между этим символом и следующим нет разрыва (0),
  if (nc > 0 && ip[ip.length-1] != 0){
    // заменить последние две точки другой точкой. 
    ip.splice(-3, 2, {x:16,y:34})
  }
}

Но зачастую они откровенно короткие. Например, вот функция для буквы n:

adjust: (ip, pc, nc) => {
  // Если следующая буква начнётся с 3, на выбор создать разрыв или переместить последнюю точку. 
  if (nc == 3) rand() < 0.3 ? ip.splice(-1, 1, 0) : ip.splice(-2, 1, {x:17,y:23.8})
}

Затем базовые пути всех букв объединяются. При этом программа игнорирует 1,2 и 3 в их путях, но при встрече 0 создаёт разрыв, начиная новый путь.

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


Забавы ради приведу для сравнения два текста: один, полученный программным путём на плоттере, и второй написанный мной вручную.


▍ Сколько он весит?


При создании блочного шрифта код для обработки букв составил 9,7 КБ. В случае же рукописного после прогона через минификатор сейчас он весит 26,1 КБ.

Так получилось в основном из-за присутствия у каждой буквы нескольких вариантов путей, а также функции для корректировки крайних точек соседних букв. Но мне удалось сэкономить. Уверена, что можно добиться ещё большей экономии — хоть я и не профессиональный программист, но кое-какие идеи у меня есть.

Например, сейчас буквы построены на основе предустановленного размера шрифта 20 и последующего изменения этого размера. В итоге множество точек определены как, например x: 14.5. Если же сменить базовый размер шрифта на 200, то точку можно будет определить как 145, удалив один символ (десятичный разделитель). Это изменение нужно вносить осторожно, так что пока я его отложила.

▍ Как я всё это использую?


Основная цель — это заголовки, подписи и заметки на диаграммах, с которыми я работала. Но мне также очень нравится просто играться с самим текстом.

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


Больше фото




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

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
Теги:
Хабы:
Всего голосов 102: ↑100 и ↓2+127
Комментарии59

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds