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

Недавно я создала на 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)


  1. Zenitchik
    24.05.2024 13:34
    +2

    Мда... А я, когда мне понадобился шрифт для заметок на чертежах, написал на PostScript шрифт ЕСКД-Б )))

    Зачем вообще на чертежах рукописный шрифт?


    1. DimPal
      24.05.2024 13:34
      +13

      Там в оргигинале не чертежи.

      The main purpose for this handwriting is for the titles, labels and scribbled notes on these diagrams I’ve been working on


      1. Bright_Translate Автор
        24.05.2024 13:34

        Я посмотрел на фактические картинки, на которые ведёт ссылка. Решил, что под понятие чертежа в общем смысле они подходят. Как вариант, схемы. А вы бы как назвали подобные изображения?


        1. yappari
          24.05.2024 13:34
          +5

          Чем не устроило слово "диаграмма"?


          1. Bright_Translate Автор
            24.05.2024 13:34

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

            Diagrams автора
            Diagrams автора
            Diagrams автора
            Diagrams автора
            Диаграмма
            Диаграмма


            1. yappari
              24.05.2024 13:34
              +1

              Как-то всё смешалось. Не имею целью никого переубеждать, но не могу не уточнить. Если совсем упрощённо, то диаграмма это более общее понятие. В него входят в т.ч. графики (те самые упомянутые зависимости чего-то от чего-то, столбчатые, линейчатые и прочие), а также различные схемы (процессов, взаимодействий, соединений и т.п.). Чертежи же это прежде всего изображения физических объектов в неком масштабе, цель которых - дать представление о строении (форме, составе) или внешнем виде этих объектов. При этом схемы, оформленные в соответствии с требованиями стандарта на документацию (например, того же ГОСТа) уже могут считаться чертежами. Но как правило, схемы, которые на чертежах, идут комплектом к другим чертежам или документации, т.е. не самодостаточны.

              Другой трактовки "чертежей" за всё время работы с документацией мне как-то не попадалось.


              1. Bright_Translate Автор
                24.05.2024 13:34

                Я выбирал слово чертеж, опираясь на его основной смысл - начертание чего-либо. В этом плане, что может быть обобщенней. Не знал, что диаграммы так трактуются, буду иметь в виду. Исправлю, так и быть.


              1. Zenitchik
                24.05.2024 13:34
                +1

                 Чертежи же это прежде всего изображения физических объектов в неком масштабе

                Не только, например, понятие "номограмма" определяется как "вид чертежа". Следовательно графики - это тоже чертежи.


  1. ArkadiyMak
    24.05.2024 13:34
    +10

    Вариации, лигатуры, выбор из нескольких глифов в зависимости от соседних - всё это поддерживается в OpenType Layout, зачем было городить какой-то огород с JavaScript?


    1. Gummilion
      24.05.2024 13:34
      +2

      Да, думал, в конце будет .otf-файл, а тут опять троллейбус из буханки.


  1. aabzel
    24.05.2024 13:34

    Теперь осталось сгенерировать GCODE, включить плоттер и можно делать сувениры


    1. LuchS-lynx
      24.05.2024 13:34

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


      1. Zenitchik
        24.05.2024 13:34

        Дело в том, что часто бывает быстрее вписать что-то от руки, чем бежать к компьютеру, править файл, потом бежать к принтеру за распечаткой.


    1. sgrey
      24.05.2024 13:34
      +2

      Уже сделали


  1. Neitr
    24.05.2024 13:34

    Да уж, такой почерк и не стоило цифровать. Раз уж столько сил потрачено, оцифровала бы в шрифт какой то красивый почерк чей то


    1. evoq
      24.05.2024 13:34
      +12

      Преувеличиваете. Почерк точно выше среднего по мне. У программистов уж точно, привыкли к клавиатуре


      1. Zenitchik
        24.05.2024 13:34

        Отнюдь! Мне глаз резанул уже наклон в другую сторону.


  1. Daddy_Cool
    24.05.2024 13:34

    del


  1. corvair
    24.05.2024 13:34
    +1

    В 24-игольчатых матричных принтерах Epson, кроме самых бюджетных типа LQ-400, издавна имеется встроенный шрифт Script, неплохо для своего времени и технологии имитирующий рукопись. Недавно встретил такой на 9-игольчатом принтере Facit E-445 (Citizen Swift 9).

    Единственная проблема - я никогда не встречал этот шрифт в русифицированном виде.


  1. Overgod
    24.05.2024 13:34
    +5

    Очень интересно.

    Только практический смысл в чем? Заменить хорошо читаемый печатный шрифт на менее читаемый рукописный?

    Ну разве что как элемент дизайна для чего-нибудь сойдёт.


    1. Mingun
      24.05.2024 13:34
      +1

      Для сдачи конспектов по философии в 96-страничной тетради. Обязательно от руки!


  1. NikolayTheSquid
    24.05.2024 13:34

    Это всё хорошо. Только вот на хабре теперь каждая третья статья - перевод. А из интересных - каждая первая. Что по уникальному контенту?


    1. slonopotamus
      24.05.2024 13:34
      +1

      Всё в ваших руках!


  1. Lurch4
    24.05.2024 13:34

    Энтузиазм это хорошо.

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


    1. moroz69off
      24.05.2024 13:34

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

      Найдёте 5 разДичей - получити пиченью...


      1. moroz69off
        24.05.2024 13:34

        Спасибо, Дорогой Друг, за минус!
        Кто бы ты ни был... Большое спасибо!


      1. Zenitchik
        24.05.2024 13:34

        Для буквопечатающей связи нужны хорошие э/м реле дать цифры 0123456789


  1. fiego
    24.05.2024 13:34
    +1

    Есть у меня полу законченный проект на похожую тему. Тоже рукописный текст, только списком кривых Безье, про шум я даже не подумал, предназначалось для микроконтроллера. Тут шрифт сделан руками, с координатами точек из головы. https://youtu.be/GvBGo-hvBrQ?si=qeyWuOZi-O-ngp8k

    Впоследствии, я сделал редактор, в котором такие шрифты делать можно, но рисование даже просто всех латинских букв меня так утомило, что до русских уже не дошёл. Соединения букв делаются отдельными кривыми.


    1. moroz69off
      24.05.2024 13:34

      Етто ВЫ естчо до ки(тайской) каллиграфии даже не приступили...


  1. fiego
    24.05.2024 13:34
    +2

    Вот что нарисовать успел