- PVSM.RU - https://www.pvsm.ru -

Недавно я создала на JavaScript собственный блочный шрифт, решив, что запрограммировать слитное рукописное письмо будет слишком сложно. Но не прошло и двух месяцев, как вот я здесь и готова рассказать о созданном мной механизме слитного письма. Пожалуй, в этом есть свой урок, но мы в его смысл углубляться не станем.
Предыдущая моя статья [1] (англ.) была посвящена разработке печатной версии алфавита. Если коротко, то реализовала я его так:
Вот, что в итоге получилось:

Пути написания букв я определяла вручную, прописывая и подгоняя их позиции в коде, пока буквы не будут выглядеть нужным образом. Когда же дело коснулось программирования слитного письма, я упростила этот процесс.
Для простоты работы я создала в редакторе p5js инструмент [2], упрощающий вывод ключевых точек в путях.
Он показывает образец буквы (для понимания масштаба и контекста) рядом с областью, в которой нужно построить новую букву. Само же построение происходит в несколько шагов:
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,{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 вариантов путей написания, но я стремилась к балансу, чтобы не раздувать размер файла.
При создании слова:
Функции корректировки букв порой получаются сложными. Вот пример для буквы 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, удалив один символ (десятичный разделитель). Это изменение нужно вносить осторожно, так что пока я его отложила.
Основная цель — это заголовки, подписи и заметки на чертежах [3], с которыми я работала. Но мне также очень нравится просто играться с самим текстом.
Одно из главных преимуществ использования запрограммированных путей написания текста вместо шрифта в том, что их можно корректировать — например, изменять положение букв, толщину отдельных из них и так далее.




Следующим делом я планирую встроить этот рукописный шрифт в те самые чертежи, но ещё я определённо хочу создать что-то для самого текста, так как он мне очень нравится, и здесь есть ещё масса возможностей.
Автор: Дмитрий Брайт
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/391664
Ссылки в тексте:
[1] Предыдущая моя статья: https://www.amygoodchild.com/blog/generating-the-alphabet
[2] инструмент: https://editor.p5js.org/amygoodchild/sketches/GZzkh4cWt
[3] чертежах: https://x.com/amygoodchild/status/1770433357777973273
[4] Источник: https://habr.com/ru/companies/ruvds/articles/816077/?utm_source=habrahabr&utm_medium=rss&utm_campaign=816077
Нажмите здесь для печати.