mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
Add img-grid skill and page auto-sizing to mxl-compile
- New skill /img-grid: overlays numbered grid on images to help
determine column proportions for MXL template generation
- Add "page" field to MXL DSL ("A4-landscape", "A4-portrait", or
number) that auto-calculates defaultWidth from column proportions
- Update DSL spec, mxl-compile SKILL.md, MXL guide, README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: img-grid
|
||||
description: Наложить пронумерованную сетку на изображение для определения пропорций колонок
|
||||
argument-hint: <ImagePath> [-c COLS]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
---
|
||||
|
||||
# /img-grid — Сетка для анализа макета
|
||||
|
||||
Накладывает пронумерованную сетку на изображение печатной формы. Позволяет точно определить границы колонок, их пропорции и span-ы для генерации макета табличного документа.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/img-grid <ImagePath> [-c COLS] [-o OUTPUT]
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|-----------|:------------:|--------------|-----------------------------------------------|
|
||||
| ImagePath | да | — | Путь к изображению (PNG, JPG) |
|
||||
| -c COLS | нет | 50 | Количество вертикальных делений |
|
||||
| -r ROWS | нет | авто | Количество горизонтальных делений (авто = квадратные ячейки) |
|
||||
| -o OUTPUT | нет | `<name>-grid.<ext>` | Путь для результата |
|
||||
|
||||
## Команда
|
||||
|
||||
```bash
|
||||
python .claude/skills/img-grid/scripts/overlay-grid.py "<ImagePath>" [-c 50] [-o "<OutputPath>"]
|
||||
```
|
||||
|
||||
Требуется Python 3 с библиотекой Pillow (`pip install Pillow`).
|
||||
|
||||
## Что делает
|
||||
|
||||
1. Рисует полупрозрачные вертикальные (красные) и горизонтальные (синие) линии
|
||||
2. Нумерует линии в отдельных полях сверху и слева (не перекрывает содержимое)
|
||||
3. Каждая 5-я и 10-я линия выделены ярче для удобства счёта
|
||||
|
||||
## Как использовать результат
|
||||
|
||||
### 1. Определить границы колонок
|
||||
|
||||
Посмотреть на изображение с сеткой и записать координаты вертикальных границ каждой колонки таблицы (в номерах grid-линий).
|
||||
|
||||
### 2. Найти базовую решётку
|
||||
|
||||
Если на форме несколько таблиц с разной раскладкой (например, шапка документа и основная таблица), объединить все граничные точки. Каждый сегмент между соседними границами — одна колонка MXL.
|
||||
|
||||
Пример для М-11:
|
||||
- Шапка: границы 0, 2, 4, 9, 14, 21, 28, 34, 40, 48
|
||||
- Таблица: границы 0, 2, 4, 11, 16, 19, 23, 28, 32, 36, 42, 48
|
||||
- Объединение: 0, 2, 4, 9, 11, 14, 16, 19, 21, 23, 28, 32, 34, 36, 40, 42, 48
|
||||
- Результат: **16 базовых колонок** с пропорциями 2, 2, 5, 2, 3, 2, 3, 2, 2, 5, 4, 2, 2, 4, 2, 6
|
||||
|
||||
### 3. Записать в JSON DSL
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 16,
|
||||
"page": "A4-landscape",
|
||||
"columnWidths": {
|
||||
"1": "2x", "2": "2x", "3": "5x", "4": "2x", "5": "3x",
|
||||
"6": "2x", "7": "3x", "8": "2x", "9": "2x", "10": "5x",
|
||||
"11": "4x", "12": "2x", "13": "2x", "14": "4x", "15": "2x", "16": "6x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Поле `"page"` позволяет компилятору автоматически вычислить абсолютные ширины из пропорций.
|
||||
|
||||
### 4. Скомпилировать
|
||||
|
||||
`/mxl-compile` → `/mxl-validate` → `/mxl-info`
|
||||
@@ -0,0 +1,112 @@
|
||||
"""Overlay a numbered grid on an image to help determine column/row proportions.
|
||||
|
||||
Usage: python overlay-grid.py <image> [-c COLS] [-r ROWS] [-o OUTPUT]
|
||||
|
||||
The grid helps an LLM count "squares" to determine exact column widths
|
||||
and positions when analyzing printed forms for MXL template generation.
|
||||
|
||||
Numbers are rendered in a dedicated margin band outside the image content,
|
||||
so they never overlap with the form and remain readable at any grid density.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
MARGIN_TOP = 20
|
||||
MARGIN_LEFT = 24
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Overlay numbered grid on image")
|
||||
parser.add_argument("image", help="Input image path")
|
||||
parser.add_argument("-c", "--cols", type=int, default=50,
|
||||
help="Number of vertical divisions (default: 50)")
|
||||
parser.add_argument("-r", "--rows", type=int, default=0,
|
||||
help="Number of horizontal divisions (0 = auto, match cell aspect ratio)")
|
||||
parser.add_argument("-o", "--output", help="Output path (default: <name>-grid.<ext>)")
|
||||
args = parser.parse_args()
|
||||
|
||||
src = Image.open(args.image).convert("RGBA")
|
||||
sw, sh = src.size
|
||||
|
||||
cols = args.cols
|
||||
step_x = sw / cols
|
||||
rows = args.rows
|
||||
if rows == 0:
|
||||
rows = round(sh / step_x)
|
||||
step_y = sh / rows
|
||||
|
||||
# Canvas with margins for labels
|
||||
cw = MARGIN_LEFT + sw
|
||||
ch = MARGIN_TOP + sh
|
||||
canvas = Image.new("RGBA", (cw, ch), (255, 255, 255, 255))
|
||||
canvas.paste(src, (MARGIN_LEFT, MARGIN_TOP))
|
||||
|
||||
overlay = Image.new("RGBA", (cw, ch), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(overlay)
|
||||
|
||||
# Font for labels in margin
|
||||
label_font_size = 12
|
||||
try:
|
||||
label_font = ImageFont.truetype("arial.ttf", label_font_size)
|
||||
except Exception:
|
||||
label_font = ImageFont.load_default()
|
||||
|
||||
# --- Vertical lines + numbers in top margin ---
|
||||
for i in range(cols + 1):
|
||||
x = MARGIN_LEFT + round(i * step_x)
|
||||
major = i % 10 == 0
|
||||
mid = i % 5 == 0
|
||||
|
||||
alpha = 160 if major else (110 if mid else 40)
|
||||
lw = 2 if major else 1
|
||||
draw.line([(x, MARGIN_TOP), (x, ch)], fill=(255, 0, 0, alpha), width=lw)
|
||||
|
||||
# Labels: always show multiples of 5; show all if spacing allows
|
||||
show_label = major or mid or step_x >= 20
|
||||
if show_label:
|
||||
label = str(i)
|
||||
bbox = label_font.getbbox(label)
|
||||
tw = bbox[2] - bbox[0]
|
||||
tx = x - tw // 2
|
||||
ty = 2
|
||||
color = (200, 0, 0, 255) if (major or mid) else (200, 0, 0, 180)
|
||||
draw.text((tx, ty), label, fill=color, font=label_font)
|
||||
|
||||
# --- Horizontal lines + numbers in left margin ---
|
||||
for j in range(rows + 1):
|
||||
y = MARGIN_TOP + round(j * step_y)
|
||||
major = j % 10 == 0
|
||||
mid = j % 5 == 0
|
||||
|
||||
alpha = 160 if major else (110 if mid else 20)
|
||||
lw = 2 if major else 1
|
||||
draw.line([(MARGIN_LEFT, y), (cw, y)], fill=(0, 0, 200, alpha), width=lw)
|
||||
|
||||
show_label = major or mid or step_y >= 20
|
||||
if show_label:
|
||||
label = str(j)
|
||||
bbox = label_font.getbbox(label)
|
||||
tw = bbox[2] - bbox[0]
|
||||
tx = MARGIN_LEFT - tw - 3
|
||||
ty = y - label_font_size // 2
|
||||
color = (0, 0, 200, 255) if (major or mid) else (0, 0, 200, 180)
|
||||
draw.text((tx, ty), label, fill=color, font=label_font)
|
||||
|
||||
result = Image.alpha_composite(canvas, overlay).convert("RGB")
|
||||
|
||||
if args.output:
|
||||
out = args.output
|
||||
else:
|
||||
name, ext = os.path.splitext(args.image)
|
||||
out = f"{name}-grid{ext}"
|
||||
|
||||
result.save(out)
|
||||
print(f"Grid: {cols} x {rows} cells")
|
||||
print(f"Cell size: {step_x:.1f} x {step_y:.1f} px")
|
||||
print(f"Image: {sw} x {sh} px")
|
||||
print(f"Saved: {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -39,6 +39,8 @@ powershell.exe -NoProfile -File .claude/skills/mxl-compile/scripts/mxl-compile.p
|
||||
3. Claude вызывает `/mxl-validate` для проверки корректности
|
||||
4. Claude вызывает `/mxl-info` для верификации структуры
|
||||
|
||||
**Если макет создаётся по изображению** (скриншот, скан печатной формы) — сначала вызвать `/img-grid` для наложения сетки, по ней определить границы колонок и пропорции, затем использовать `"Nx"` ширины + `"page"` для автоматического расчёта размеров.
|
||||
|
||||
## JSON-схема DSL
|
||||
|
||||
Полная спецификация формата: **`docs/mxl-dsl-spec.md`** (прочитать через Read tool перед написанием JSON).
|
||||
@@ -46,7 +48,7 @@ powershell.exe -NoProfile -File .claude/skills/mxl-compile/scripts/mxl-compile.p
|
||||
Краткая структура:
|
||||
|
||||
```
|
||||
{ columns, defaultWidth, columnWidths,
|
||||
{ columns, page, defaultWidth, columnWidths,
|
||||
fonts: { name: { face, size, bold, italic, underline, strikeout } },
|
||||
styles: { name: { font, align, valign, border, borderWidth, wrap, format } },
|
||||
areas: [{ name, rows: [{ height, rowStyle, cells: [
|
||||
@@ -56,6 +58,7 @@ powershell.exe -NoProfile -File .claude/skills/mxl-compile/scripts/mxl-compile.p
|
||||
```
|
||||
|
||||
Ключевые правила:
|
||||
- `page` — формат страницы (`"A4-landscape"`, `"A4-portrait"` или число). Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"`
|
||||
- `col` — 1-based позиция колонки
|
||||
- `rowStyle` — автозаполнение пустот стилем (рамки по всей ширине)
|
||||
- Тип заполнения определяется автоматически: `param` → Parameter, `text` → Text, `template` → Template
|
||||
|
||||
@@ -119,6 +119,57 @@ function Parse-ColumnSpec {
|
||||
return $cols
|
||||
}
|
||||
|
||||
# --- 4a. Auto-calculate defaultWidth from page format ---
|
||||
|
||||
$pageTargets = @{
|
||||
"A4-landscape" = 780
|
||||
"A4-portrait" = 540
|
||||
}
|
||||
|
||||
if ($def.page) {
|
||||
$pageName = "$($def.page)"
|
||||
$targetWidth = $null
|
||||
|
||||
if ($pageName -match '^\d+$') {
|
||||
$targetWidth = [int]$pageName
|
||||
} elseif ($pageTargets.ContainsKey($pageName)) {
|
||||
$targetWidth = $pageTargets[$pageName]
|
||||
} else {
|
||||
Write-Warning "Unknown page format '$pageName'. Known: $($pageTargets.Keys -join ', '), or a number."
|
||||
}
|
||||
|
||||
if ($targetWidth) {
|
||||
$totalUnits = 0.0
|
||||
$absoluteSum = 0
|
||||
$specifiedCols = @{}
|
||||
|
||||
if ($def.columnWidths) {
|
||||
foreach ($prop in $def.columnWidths.PSObject.Properties) {
|
||||
$val = "$($prop.Value)"
|
||||
$cols = Parse-ColumnSpec $prop.Name
|
||||
foreach ($c in $cols) {
|
||||
$specifiedCols[[int]$c] = $true
|
||||
if ($val -match '^([0-9.]+)x$') {
|
||||
$totalUnits += [double]$Matches[1]
|
||||
} else {
|
||||
$absoluteSum += [int]$val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($c = 1; $c -le $totalColumns; $c++) {
|
||||
if (-not $specifiedCols.ContainsKey($c)) {
|
||||
$totalUnits += 1.0
|
||||
}
|
||||
}
|
||||
|
||||
if ($totalUnits -gt 0) {
|
||||
$defaultWidth = [int][math]::Round(($targetWidth - $absoluteSum) / $totalUnits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build column width map: 1-based col -> width
|
||||
$colWidthMap = @{}
|
||||
if ($def.columnWidths) {
|
||||
@@ -671,6 +722,9 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
||||
# --- 9. Summary ---
|
||||
|
||||
Write-Host "[OK] Compiled: $OutputPath"
|
||||
if ($def.page) {
|
||||
Write-Host " Page: $pageName -> target $targetWidth, defaultWidth=$defaultWidth"
|
||||
}
|
||||
Write-Host " Areas: $($namedItems.Count), Rows: $totalRowCount, Columns: $totalColumns"
|
||||
Write-Host " Fonts: $($fontEntries.Count), Lines: $lineCount, Formats: $($formatRegistry.Count)"
|
||||
Write-Host " Merges: $($merges.Count)"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|--------|--------|----------|------|
|
||||
| Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
|
||||
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
|
||||
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
|
||||
|
||||
## Требования
|
||||
|
||||
@@ -53,7 +54,8 @@
|
||||
├── mxl-info/ # Анализ макета
|
||||
├── mxl-validate/ # Валидация макета
|
||||
├── mxl-compile/ # Компиляция макета из JSON
|
||||
└── mxl-decompile/ # Декомпиляция макета в JSON
|
||||
├── mxl-decompile/ # Декомпиляция макета в JSON
|
||||
└── img-grid/ # Сетка для анализа изображений
|
||||
docs/
|
||||
├── epf-guide.md # Гайд: внешние обработки
|
||||
├── mxl-guide.md # Гайд: табличный документ
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `columns` | да | — | Количество колонок |
|
||||
| `defaultWidth` | нет | 10 | Ширина колонок по умолчанию |
|
||||
| `page` | нет | — | Формат страницы: `"A4-landscape"` (780), `"A4-portrait"` (540) или число. Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"` |
|
||||
| `defaultWidth` | нет | 10 | Ширина колонок по умолчанию. Игнорируется если задан `page` и все колонки используют `"Nx"` |
|
||||
| `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) |
|
||||
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
|
||||
| `styles` | нет | `{}` | Именованные стили |
|
||||
|
||||
@@ -31,6 +31,22 @@ Claude напишет JSON-определение с областями, пар
|
||||
3. `/mxl-validate` → проверка корректности
|
||||
4. `/mxl-info` → верификация структуры (области, параметры)
|
||||
|
||||
### Создание макета по изображению
|
||||
|
||||
Если есть скриншот или скан печатной формы, `/img-grid` поможет точно определить пропорции колонок.
|
||||
|
||||
```
|
||||
> Вот изображение формы М-11. Создай макет по нему.
|
||||
```
|
||||
|
||||
Рабочий цикл:
|
||||
1. `/img-grid` → изображение с пронумерованной сеткой
|
||||
2. Claude считает координаты границ колонок по сетке
|
||||
3. Объединяет границы всех таблиц → базовая решётка
|
||||
4. Пишет JSON DSL с пропорциями `"Nx"` и `"page": "A4-landscape"`
|
||||
5. `/mxl-compile` автоматически вычисляет абсолютные ширины из пропорций и формата страницы
|
||||
6. `/mxl-validate` → `/mxl-info` → проверка
|
||||
|
||||
### Анализ существующего макета
|
||||
|
||||
Быстрый обзор структуры макета без чтения тысяч строк XML.
|
||||
|
||||
Reference in New Issue
Block a user