У меня нередко бывает, что я решу не увлекаться слишком сильно какой-то затеей, но в итоге всё равно в неё погружаюсь. Так было и на этот раз.
Недавно я создала на 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 ?
Комментарии (30)
ArkadiyMak
24.05.2024 13:34+10Вариации, лигатуры, выбор из нескольких глифов в зависимости от соседних - всё это поддерживается в OpenType Layout, зачем было городить какой-то огород с JavaScript?
aabzel
24.05.2024 13:34Теперь осталось сгенерировать GCODE, включить плоттер и можно делать сувениры
LuchS-lynx
24.05.2024 13:34смех смехом, но я встречался в строительстве, когда акты/журналы через подобные решения заполнялись ручкой с чернилами "от руки". Т.к. при строительстве ведется огромное число исполнительных документов, часть из которых все еще пишется и подписывается вручную, то...
Zenitchik
24.05.2024 13:34Дело в том, что часто бывает быстрее вписать что-то от руки, чем бежать к компьютеру, править файл, потом бежать к принтеру за распечаткой.
Neitr
24.05.2024 13:34Да уж, такой почерк и не стоило цифровать. Раз уж столько сил потрачено, оцифровала бы в шрифт какой то красивый почерк чей то
corvair
24.05.2024 13:34+1В 24-игольчатых матричных принтерах Epson, кроме самых бюджетных типа LQ-400, издавна имеется встроенный шрифт Script, неплохо для своего времени и технологии имитирующий рукопись. Недавно встретил такой на 9-игольчатом принтере Facit E-445 (Citizen Swift 9).
Единственная проблема - я никогда не встречал этот шрифт в русифицированном виде.
Overgod
24.05.2024 13:34+5Очень интересно.
Только практический смысл в чем? Заменить хорошо читаемый печатный шрифт на менее читаемый рукописный?
Ну разве что как элемент дизайна для чего-нибудь сойдёт.
Mingun
24.05.2024 13:34+1Для сдачи конспектов по философии в 96-страничной тетради. Обязательно от руки!
NikolayTheSquid
24.05.2024 13:34Это всё хорошо. Только вот на хабре теперь каждая третья статья - перевод. А из интересных - каждая первая. Что по уникальному контенту?
Lurch4
24.05.2024 13:34Энтузиазм это хорошо.
"Съешь ещё этих мягких французских булок, да выпей же чаю." :-)
moroz69off
24.05.2024 13:34Съешь же этих мягких французских булок, да выпей ещё чаю
Найдёте 5 разДичей - получити пиченью...
fiego
24.05.2024 13:34+1Есть у меня полу законченный проект на похожую тему. Тоже рукописный текст, только списком кривых Безье, про шум я даже не подумал, предназначалось для микроконтроллера. Тут шрифт сделан руками, с координатами точек из головы. https://youtu.be/GvBGo-hvBrQ?si=qeyWuOZi-O-ngp8k
Впоследствии, я сделал редактор, в котором такие шрифты делать можно, но рисование даже просто всех латинских букв меня так утомило, что до русских уже не дошёл. Соединения букв делаются отдельными кривыми.
Zenitchik
Мда... А я, когда мне понадобился шрифт для заметок на чертежах, написал на PostScript шрифт ЕСКД-Б )))
Зачем вообще на чертежах рукописный шрифт?
DimPal
Там в оргигинале не чертежи.
The main purpose for this handwriting is for the titles, labels and scribbled notes on these diagrams I’ve been working on
Bright_Translate Автор
Я посмотрел на фактические картинки, на которые ведёт ссылка. Решил, что под понятие чертежа в общем смысле они подходят. Как вариант, схемы. А вы бы как назвали подобные изображения?
yappari
Чем не устроило слово "диаграмма"?
Bright_Translate Автор
Диаграмма как-то иначе для меня выглядеть должна. Столбчатая, линейная, к примеру. Но не "блок-схема", как на фото автора. Да, у нее есть там круговая типа диаграмма, но в более общем смысле это все же чертежи или схемы. Или диаграммы и чертежи (схемы).
yappari
Как-то всё смешалось. Не имею целью никого переубеждать, но не могу не уточнить. Если совсем упрощённо, то диаграмма это более общее понятие. В него входят в т.ч. графики (те самые упомянутые зависимости чего-то от чего-то, столбчатые, линейчатые и прочие), а также различные схемы (процессов, взаимодействий, соединений и т.п.). Чертежи же это прежде всего изображения физических объектов в неком масштабе, цель которых - дать представление о строении (форме, составе) или внешнем виде этих объектов. При этом схемы, оформленные в соответствии с требованиями стандарта на документацию (например, того же ГОСТа) уже могут считаться чертежами. Но как правило, схемы, которые на чертежах, идут комплектом к другим чертежам или документации, т.е. не самодостаточны.
Другой трактовки "чертежей" за всё время работы с документацией мне как-то не попадалось.
Bright_Translate Автор
Я выбирал слово чертеж, опираясь на его основной смысл - начертание чего-либо. В этом плане, что может быть обобщенней. Не знал, что диаграммы так трактуются, буду иметь в виду. Исправлю, так и быть.
Zenitchik
Не только, например, понятие "номограмма" определяется как "вид чертежа". Следовательно графики - это тоже чертежи.