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/.
127 lines
8.8 KiB
Markdown
127 lines
8.8 KiB
Markdown
# 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`.
|
||
|
||
## Тестовые сценарии (обязательно прогонять при правках)
|
||
|
||
```bash
|
||
# кириллица с б/й/р, точками и спецсимволами (б/й на 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` по ширине).
|
||
- Цвет чернил по умолчанию — синий. Палитры/градиенты намеренно убраны
|
||
(раньше были, но усложняли). Не возвращайте мульти-цвет без явной просьбы.
|