Files
second_constantine 9e81d7a3e0 handwrite.py: рукописный рендер кириллицы синими чернилами
CLI-утилита (Pillow) для превращения текста в картинку рукописного текста. Полная поддержка кириллицы, синие чернила с имитацией письма ручкой (дрейф давления, полупрозрачность, неровный край), корректное выравнивание (б/й/н на baseline, р/у/д с хвостами, точки внизу, спецсимволы), перенос по словам + сохранение переносов строк, тетрадная бумага, --list-fonts. run.sh: команды-обёртки (init/upd/render/lined/big/calm/plain/poetry/render-file/render-stdin/list-fonts/examples). Документация: README.md, AGENTS.md. Примеры в examples/.
2026-08-22 15:12:01 +03:00

8.8 KiB
Raw Permalink Blame History

AGENTS.md — hand-writter

Руководство для агентов (и людей), работающих с репозиторием hand-writter.

О проекте

handwrite.py — CLI-утилита на Python, превращающая текст в картинку рукописного текста с поддержкой кириллицы (полный русский алфавит, включая Ё, щ, ъ, э, ю, я). Единственная зависимость — Pillow (PIL).

Ключевые особенности:

  • Автоподбор рукописного шрифта из системы с проверкой покрытия кириллицы (без сторонних библиотек — только Pillow: рендер символа и сравнение с notdef-глифом). Приоритет: Marker Felt, затем Snell Roundhand, затем любой шрифт с кириллицей.
  • Синие чернила по умолчанию (#1a3b8c) с имитацией письма ручкой: коррелированный дрейф «давления» (тёмные/светлые участки), полупрозрачные чернила, неровный край мазка.
  • Выравнивание глифов — стандартное PIL text((x, line_top)) (через маску в _stamp_glyph). Все обычные буквы (а, о, н, б, й) стоят на одной baseline, буквы с нижними выносными (р, у, д, ц, щ) опускают хвосты ниже — строчная «р» ниже обычных букв, точки/запятые внизу, спецсимволы ($ ~ % @ …) — по дизайну шрифта. Не вводите ручное смещение «по ink_bottom» — оно ломает б/й/н и обрезает глифы.
  • Перенос по словам + сохранение \n из входного файла/STDIN.
  • Тетрадная «линейная» бумага (--lined), цвета чернил/бумаги/линий, синие палитры и градиенты оттенков.

Структура

hand-writter/
├── handwrite.py          # основной скрипт (точка входа)
├── requirements.txt      # Pillow>=9.0.0
├── run.sh                # обёртка-команды: init/upd/render/...
├── README.md             # пользовательская документация
├── AGENTS.md             # этот файл
└── examples/             # примеры вывода
    ├── blue_pen.png
    ├── blue_pen_lined.png
    ├── multiline.png
    └── sample_input.txt

Окружение и запуск

  • Python 3.8+ (в run.sh предполагается python3.13; если его нет — отредактируйте PY в run.sh или используйте системный python3).
  • Активация venv: source .venv/bin/activate (через ./run.sh init).
  • Прямой запуск: python3 handwrite.py "Текст" -o out.png.
  • Команды-обёртки: ./run.sh render "Текст" и др. (см. ниже).

Правила работы с кодом

  1. Не добавляйте новых зависимостей без крайней необходимости. Скрипт намеренно зависит только от Pillow — это упрощает установку в изолированных средах. Любая новая зависимость должна быть обоснована и добавлена в requirements.txt.

  2. Сохраняйте поддержку кириллицы. Любое изменение рендера/шрифтов должно проверяться на полном русском алфавите (см. CYRILLIC_TEST в handwrite.py). Не полагайтесь на «визуальную» проверку — шрифт может отрисовывать notdef для отсутствующих глифов; используйте _font_covers_cyrillic().

  3. Выравнивание — стандартное PIL. В _stamp_glyph глиф рисуется маской с origin в канвасе и композируется так, чтобы совпадать с text((x, line_top)). НЕ прижимайте ink_bottom вручную к baseline и НЕ сдвигайте глифы по bbox — это ломает б/й (обрезает) и делает их «слишком низкими». Канвас берётся по абсолютным границам bbox (включая отрицательные bbox[0]/bbox[1] для Ё, р). Знаки препинания рисуются с jitter=0 (чёткие точки), но тем же выравниванием.

  4. Сохраняйте переносы строк. wrap_text использует splitlines() — каждая строка входа становится отдельной строкой на картинке. Не «схлоптывайте» пустые строки без явного флага.

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

  6. Внесение изменений: после правок проверяйте

    • python3 -c "import ast; ast.parse(open('handwrite.py').read())" — синтаксис;
    • запуск на кириллице с точками/запятыми и многострочным файлом;
    • что точки-знаки остаются в нижней зоне строки.

Команды (run.sh)

Команда Описание
./run.sh init Создать venv и установить зависимости
./run.sh upd Обновить зависимости
./run.sh render "текст" Рендер текста-аргумента → out/handwriting.png
./run.sh lined "текст" Рендер на тетрадной бумаге
./run.sh big "текст" Крупный шрифт (64pt) на тёплой бумаге
./run.sh calm "текст" «Спокойный» почерк (jitter 0.3, seed 42)
./run.sh plain "текст" Ровный текст (без дрожания/поворота строк)
./run.sh render-file file.txt Рендер файла (с переносами строк)
./run.sh poetry file.txt Многострочный файл → тетрадь, крупный шрифт → out/poetry.png
./run.sh render-stdin Чтение из STDIN (echo "..." | ./run.sh render-stdin)
./run.sh list-fonts Показать доступные рукописные шрифты с кириллицей
./run.sh examples Перегенерировать примеры в examples/

Все команды рендера (render, lined, big, calm, plain, poetry, render-file, render-stdin) принимают доп. опции handwrite.py после --, например: ./run.sh render "Текст" -- --jitter 0.4 --seed 7 --lined.

Тестовые сценарии (обязательно прогонять при правках)

# кириллица с б/й/р, точками и спецсимволами (б/й на baseline, р ниже, точки внизу)
python3 handwrite.py "байты йогурт река. \$ ~ % @ # конец!" -o /tmp/t1.png

# многострочный файл (каждая строка файла → строка на картинке)
printf 'Строка 1.\nСтрока 2.\nСтрока 3.\n' > /tmp/m.txt
python3 handwrite.py -i /tmp/m.txt -o /tmp/t2.png --lined

# синий (по умолчанию) + тетрадь
python3 handwrite.py "Текст" --lined -o /tmp/t3.png

Чего не делать

  • Не коммитьте .DS_Store, .venv/, временные *.png вне examples/.
  • Не удаляйте examples/ — это демонстрация возможностей.
  • Не «упрощайте» проверку глифов кириллицы до textbbox/ширины — это ненадёжно (узкие буквы, например «г», неотличимы от notdef по ширине).
  • Цвет чернил по умолчанию — синий. Палитры/градиенты намеренно убраны (раньше были, но усложняли). Не возвращайте мульти-цвет без явной просьбы.