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/.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# handwrite.py
|
||||
|
||||
Python-скрипт, превращающий текст в картинку рукописного текста с **поддержкой
|
||||
кириллицы** (полный русский алфавит, включая `Ё`, `щ`, `ъ`, `э`, `ю`, `я`).
|
||||
Чернила — **синие**, с имитацией письма ручкой.
|
||||
|
||||
## Возможности
|
||||
|
||||
- 🖋️ **Синие чернила «как рукой»**: имитация письма ручкой — дрейф «давления»
|
||||
(коррелированные тёмные/светлые участки), полупрозрачные чернила, неровный
|
||||
край мазка
|
||||
- ✅ Полная поддержка кириллицы (автоподбор рукописного шрифта с проверкой
|
||||
наличия всех глифов; приоритет — `Marker Felt`)
|
||||
- 📐 Корректное выравнивание: обычные буквы (`а`, `о`, `н`, `б`, `й`) на одной
|
||||
базовой линии, буквы с нижними выносными (`р`, `у`, `д`, `ц`, `щ`) — с
|
||||
хвостами ниже, точки/запятые — внизу строки, спецсимволы (`$ ~ % @ # & …`) —
|
||||
по дизайну шрифта
|
||||
- 📝 Перенос по словам + сохранение `\n` из входного файла/STDIN
|
||||
- 📓 «Тетрадная» линейная бумага (`--lined`)
|
||||
- 🎨 Настройка цвета чернил/бумаги/линий, размера шрифта, полей
|
||||
- 📥 Ввод: аргументом, из файла (`-i`, с переносами строк), через STDIN
|
||||
- 🔒 Воспроизводимый результат через `--seed`
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt # нужна только Pillow
|
||||
```
|
||||
|
||||
или через обёртку (создаст venv):
|
||||
|
||||
```bash
|
||||
./run.sh init
|
||||
```
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
# текст аргументом
|
||||
python3 handwrite.py "Привет, мир!" -o hello.png
|
||||
|
||||
# тетрадная бумага
|
||||
python3 handwrite.py "Привет, мир!" -o hello.png --lined
|
||||
|
||||
# из файла с переносами строк (каждая строка файла → строка на картинке)
|
||||
python3 handwrite.py -i text.txt -o letter.png --lined
|
||||
|
||||
# через STDIN
|
||||
echo "Мама мыла раму" | python3 handwrite.py -o out.png
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|---|---|---|
|
||||
| `text` | — | Текст для рендера (позиционный). |
|
||||
| `-i, --input` | — | Файл с текстом (UTF-8); переносы строк сохраняются. `-` = STDIN, иначе при отсутствии текста — STDIN. |
|
||||
| `-o, --output` | `handwriting.png` | Выходной PNG. |
|
||||
| `--font` | авто | Путь к шрифту (`path` или `path:index` в `.ttc`). Иначе авто-подбор рукописного с кириллицей. |
|
||||
| `--font-size` | `44` | Размер шрифта (pt). |
|
||||
| `--line-spacing` | `1.6` | Множитель межстрочного интервала. |
|
||||
| `--margin` | `64` | Поля вокруг текста (px). |
|
||||
| `--chars-per-line` | `48` | Примерная ширина строки в символах для переноса. |
|
||||
| `--ink` | `#1a3b8c` | Цвет чернил (`#RRGGBB`). По умолчанию — синий. |
|
||||
| `--paper` | `#ffffff` | Цвет бумаги (`#RRGGBB`). |
|
||||
| `--lined` | выкл. | Рисовать «тетрадные» линии. |
|
||||
| `--lined-color` | `#cfe8ff` | Цвет линий бумаги. |
|
||||
| `--jitter` | `1.0` | Сила «рукописного» дрожания (`0` — ровный текст). |
|
||||
| `--no-rotate-lines` | выкл. | Отключить лёгкий поворот строк. |
|
||||
| `--seed` | случайный | Зерно ГПСЧ для воспроизводимости. |
|
||||
|
||||
## Примеры использования
|
||||
|
||||
```bash
|
||||
# крупный текст на тёплой бумаге
|
||||
python3 handwrite.py "Стихи на полях" -o poem.png --font-size 56 --paper "#fdf6e3" --lined
|
||||
|
||||
# «спокойный» почерк — меньше колебаний давления, фиксированный seed
|
||||
python3 handwrite.py "Ровный аккуратный текст" -o neat.png --jitter 0.3 --seed 42
|
||||
|
||||
# полностью ровный текст (без дрожания и поворота)
|
||||
python3 handwrite.py "Как печатный" -o plain.png --jitter 0 --no-rotate-lines
|
||||
|
||||
# другой синий оттенок чернил
|
||||
python3 handwrite.py "Глубокий синий" -o deep.png --ink "#0f2d78"
|
||||
|
||||
# широкий формат (длинные строки)
|
||||
python3 handwrite.py -i long_text.txt -o wide.png --chars-per-line 80 --margin 40
|
||||
|
||||
# другой рукописный шрифт (Snell Roundhand)
|
||||
python3 handwrite.py "Текст" --font "/System/Library/Fonts/Supplemental/SnellRoundhand.ttc:0"
|
||||
|
||||
# многострочный файл через STDIN
|
||||
cat text.txt | python3 handwrite.py -o out.png --lined
|
||||
|
||||
# тёмная бумага, светлые чернила (например, для презентации)
|
||||
python3 handwrite.py "Белым по тёмному" -o dark.png --ink "#cfe8ff" --paper "#1a2238"
|
||||
```
|
||||
|
||||
## Команды run.sh
|
||||
|
||||
Удобные обёртки для типовых задач (вывод — в `out/`):
|
||||
|
||||
| Команда | Описание |
|
||||
|---|---|
|
||||
| `./run.sh init` | Создать venv и установить зависимости |
|
||||
| `./run.sh upd` | Обновить зависимости |
|
||||
| `./run.sh render "текст"` | Рендер текста → `out/handwriting.png` |
|
||||
| `./run.sh lined "текст"` | Рендер на тетрадной бумаге → `out/handwriting.png` |
|
||||
| `./run.sh big "текст"` | Крупный шрифт (64pt) на тёплой бумаге |
|
||||
| `./run.sh calm "текст"` | «Спокойный» почерк (jitter 0.3, seed 42) |
|
||||
| `./run.sh plain "текст"` | Ровный текст (без дрожания/поворота) |
|
||||
| `./run.sh render-file file` | Рендер файла (с переносами строк) |
|
||||
| `./run.sh poetry file` | Многострочный файл → тетрадь, крупный шрифт |
|
||||
| `./run.sh render-stdin` | Чтение из STDIN (`echo "..." \| ./run.sh render-stdin`) |
|
||||
| `./run.sh list-fonts` | Показать доступные рукописные шрифты с кириллицей |
|
||||
| `./run.sh examples` | Перегенерировать примеры в `examples/` |
|
||||
|
||||
Доп. опции `handwrite.py` передаются после `--` (или просто в конце):
|
||||
|
||||
```bash
|
||||
./run.sh render "Текст" -- --jitter 0.4 --seed 7
|
||||
./run.sh render-file text.txt --chars-per-line 60
|
||||
./run.sh lined "Текст" -- --ink "#0f2d78"
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
```
|
||||
examples/
|
||||
├── blue_pen.png # синий текст «от руки»
|
||||
├── blue_pen_lined.png # синий текст на тетради
|
||||
├── multiline.png # из файла с переносами строк
|
||||
└── sample_input.txt # пример входного файла
|
||||
```
|
||||
|
||||
Перегенерировать: `./run.sh examples`.
|
||||
|
||||
## Подбор шрифта
|
||||
|
||||
Скрипт автоматически ищет рукописный шрифт с **полным покрытием кириллицы**:
|
||||
|
||||
1. Проверяет приоритетные системные шрифты (`Marker Felt`, затем `Snell Roundhand`).
|
||||
2. Сканирует системные каталоги, отбирая «рукописные» имена и проверяя все
|
||||
глифы кириллицы.
|
||||
3. В крайнем случае берёт любой шрифт с поддержкой кириллицы.
|
||||
|
||||
Проверка глифов — без сторонних библиотек (только Pillow): каждый символ
|
||||
рендерится и сравнивается с формой `notdef`-глифа. Узкие буквы (например «г»)
|
||||
неотличимы от `notdef` по ширине, поэтому сравнение делается по форме, а не по
|
||||
ширине.
|
||||
|
||||
## Требования
|
||||
|
||||
- Python 3.8+
|
||||
- Pillow (`pip install pillow`)
|
||||
|
||||
## Файлы проекта
|
||||
|
||||
```
|
||||
hand-writter/
|
||||
├── handwrite.py # основной скрипт
|
||||
├── requirements.txt # Pillow
|
||||
├── run.sh # команды-обёртки
|
||||
├── README.md # этот файл
|
||||
├── AGENTS.md # руководство для агентов/разработчиков
|
||||
└── examples/ # примеры вывода
|
||||
```
|
||||
|
||||
Подробности о внутреннем устройстве и правилах изменения кода — в `AGENTS.md`.
|
||||
Reference in New Issue
Block a user