Как писать конспекты, если ты программист

от автора

Когда в техническом вузе преподаватель заставляет студентов писать конспекты от руки, получается что-то вот такое:

Фотка двух листов конспекта

Это – результат работы программы, генерирующей рукописный текст пользовательским почерком. Она может менять толщину пера и цвет пасты, писать буквы слитно или раздельно, поддерживает письмо на множестве разных языков и потенциально способна переносить слова по слогам на многих из них. Написано на C++/Qt, есть версии под Windows и Linux. Дальше будет небольшой разбор рукописного письма, описание разных способов его имитации, разбор наиболее интересных моментов работы программы и ссылка на репозиторий.

Почему программная генерация индивидуального рукописного письма – это сложно? Дело в том, что рукописное письмо очень изменчиво, здесь нет такой жёсткой предопределённости, как при печати. Для правдоподобной имитации рукописного письма надо учитывать как минимум вот такие его особенности:

  • Начертание букв зависит от того, какие буквы стоят рядом
  • Для каждого символа добавляются рандомные и не очень искажения, из-за чего абсолютно идентичные символы в тексте обычно не встречаются.
  • Слова могут быть написаны слитно или раздельно. Также можно совмещать оба варианта написания.
  • Меняются ширина, высота и наклон, как у символов, так и у строк.

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

Как это обычно делают?

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

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

А как сделать лучше?

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

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

Я не первый, кто решил, что программная генерация — это хорошая идея, и написал свою программу для создания рукописей. Мне известны программы Синяк и Handwriter, а также сервис Писец. У них, тем не менее, есть определённые недостатки, которые сподвигли меня на написание собственной программы: у Синяка, например, лишь один глиф на символ и некоторые проблемы с юзабилити, Handwriter платный, а Писец не позволяет создать свой шрифт.

Векторный или растровый шрифт?

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

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

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

Так что в моей программе используется векторная графика. Алгоритм создания шрифта теперь такой: пользователь открывает его любимый векторный редактор, рисует нужные глифы (желательно с помощью планшета), сохраняет их, а потом загружает в программу. Минусы у такого решения следующие:

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

Но появляются разные бонусы:

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

Размещение символов на листе

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

Скриншот редактора шрифтов
Жёлтый прямоугольник – как раз та часть символа, которая находится в пределах строки

В редакторе шрифтов как раз можно задать положение символа относительно строки и других символов, указывая границы символа жёлтой рамкой. А ещё тут, разумеется, ставятся в соответствие символы и конкретные глифы.

Данные для слитного написания

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

Частичная автоматизация создания шрифта

Указывать всё это вручную для каждого символа – застрелиться можно. Надо как-то упростить процесс создания своего шрифта.

Во-первых, есть автоматическая загрузка глифов. При сохранении глифа из любимого векторного редактора достаточно назвать файл по определённому шаблону, и программа отнесёт его к нужному символу. Шаблон такой: сам символ, затем, если нужно, номер; можно разделить их нижним подчёркиванием. И, поскольку Windows не видит большой разницы между заглавными и строчными буквами, для заглавных нужно добавить префикс «UP_». Правда, такой трюк работает не со всеми символами, т.к. далеко не всё можно использовать в имени файла, поэтому вместо запрещённых символов можно писать их название.

Во-вторых, при слитном написании соединительная линия, как правило, входит в начало первой линии буквы, а выходит из конца последней линии. Поскольку, как правило, векторные редакторы сохраняют информацию о линиях в той последовательности, в которой линии были нарисованы, мы знаем, какая линия была нарисована первой, а какая – последней. То есть большую часть работы по расстановке круглых указателей может взять на себя программа. И берёт.

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

Перенос слов по слогам

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

Такие правила уже есть для русского языка. Они не идеально точные, но якобы покрывают 99% всех переносов. Также, полагаю, их можно разработать и для некоторых других языков. Но не для всех. Например, в английском для переноса слов по слогам потребуется куда более сложный алгоритм, т.к. слова переносятся по звучанию, а не по написанию.

Вот так выглядят правила для русского языка:

  • «Х-ЛЛ»
  • «Г-ГЛ»
  • «ГС-СГ»
  • «СГ-СГ»
  • «ГС-ССГ»
  • «ГСС-ССГ»

Здесь Л – любая буква, Г – гласная, С – согласная, Х – буква из набора «йьъ». Чтобы предоставить пользователям возможность изменять правила, не модифицируя исходный код, я вынес их в отдельный файл:

Файл с правилами переноса
Я позволил себе несколько модифицировать оригинальные правила, чтобы не допустить отрыва одной буквы от слова.

Прочие возможности

Векторная графика даёт возможность изменять параметры линий, в частности, скруглять их края и сглаживать углы, так почему бы этим не воспользоваться? Кроме того, не вижу причин как-то ограничивать пользователя в возможности настроить параметры листа и шрифта. Примерное представление об основных настройках можно получить, посмотрев на скриншот:

Скриншот настроек

Ещё немного фоток конспекта

Кликабельно:

Фото 1

Фото 2

Фото 3

Исходники

Как и обещал – ссылка на репозиторий: https://github.com/aizenbit/Scribbler

ссылка на оригинал статьи https://habrahabr.ru/post/316404/