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` для проверки корректности
|
3. Claude вызывает `/mxl-validate` для проверки корректности
|
||||||
4. Claude вызывает `/mxl-info` для верификации структуры
|
4. Claude вызывает `/mxl-info` для верификации структуры
|
||||||
|
|
||||||
|
**Если макет создаётся по изображению** (скриншот, скан печатной формы) — сначала вызвать `/img-grid` для наложения сетки, по ней определить границы колонок и пропорции, затем использовать `"Nx"` ширины + `"page"` для автоматического расчёта размеров.
|
||||||
|
|
||||||
## JSON-схема DSL
|
## JSON-схема DSL
|
||||||
|
|
||||||
Полная спецификация формата: **`docs/mxl-dsl-spec.md`** (прочитать через Read tool перед написанием JSON).
|
Полная спецификация формата: **`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 } },
|
fonts: { name: { face, size, bold, italic, underline, strikeout } },
|
||||||
styles: { name: { font, align, valign, border, borderWidth, wrap, format } },
|
styles: { name: { font, align, valign, border, borderWidth, wrap, format } },
|
||||||
areas: [{ name, rows: [{ height, rowStyle, cells: [
|
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 позиция колонки
|
- `col` — 1-based позиция колонки
|
||||||
- `rowStyle` — автозаполнение пустот стилем (рамки по всей ширине)
|
- `rowStyle` — автозаполнение пустот стилем (рамки по всей ширине)
|
||||||
- Тип заполнения определяется автоматически: `param` → Parameter, `text` → Text, `template` → Template
|
- Тип заполнения определяется автоматически: `param` → Parameter, `text` → Text, `template` → Template
|
||||||
|
|||||||
@@ -119,6 +119,57 @@ function Parse-ColumnSpec {
|
|||||||
return $cols
|
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
|
# Build column width map: 1-based col -> width
|
||||||
$colWidthMap = @{}
|
$colWidthMap = @{}
|
||||||
if ($def.columnWidths) {
|
if ($def.columnWidths) {
|
||||||
@@ -671,6 +722,9 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
|||||||
# --- 9. Summary ---
|
# --- 9. Summary ---
|
||||||
|
|
||||||
Write-Host "[OK] Compiled: $OutputPath"
|
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 " Areas: $($namedItems.Count), Rows: $totalRowCount, Columns: $totalColumns"
|
||||||
Write-Host " Fonts: $($fontEntries.Count), Lines: $lineCount, Formats: $($formatRegistry.Count)"
|
Write-Host " Fonts: $($fontEntries.Count), Lines: $lineCount, Formats: $($formatRegistry.Count)"
|
||||||
Write-Host " Merges: $($merges.Count)"
|
Write-Host " Merges: $($merges.Count)"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|--------|--------|----------|------|
|
|--------|--------|----------|------|
|
||||||
| Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
|
| Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
|
||||||
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
|
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
|
||||||
|
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
|
||||||
|
|
||||||
## Требования
|
## Требования
|
||||||
|
|
||||||
@@ -53,7 +54,8 @@
|
|||||||
├── mxl-info/ # Анализ макета
|
├── mxl-info/ # Анализ макета
|
||||||
├── mxl-validate/ # Валидация макета
|
├── mxl-validate/ # Валидация макета
|
||||||
├── mxl-compile/ # Компиляция макета из JSON
|
├── mxl-compile/ # Компиляция макета из JSON
|
||||||
└── mxl-decompile/ # Декомпиляция макета в JSON
|
├── mxl-decompile/ # Декомпиляция макета в JSON
|
||||||
|
└── img-grid/ # Сетка для анализа изображений
|
||||||
docs/
|
docs/
|
||||||
├── epf-guide.md # Гайд: внешние обработки
|
├── epf-guide.md # Гайд: внешние обработки
|
||||||
├── mxl-guide.md # Гайд: табличный документ
|
├── mxl-guide.md # Гайд: табличный документ
|
||||||
|
|||||||
@@ -74,7 +74,8 @@
|
|||||||
| Поле | Обяз. | По умолч. | Описание |
|
| Поле | Обяз. | По умолч. | Описание |
|
||||||
|------|:-----:|-----------|----------|
|
|------|:-----:|-----------|----------|
|
||||||
| `columns` | да | — | Количество колонок |
|
| `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"`) |
|
| `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) |
|
||||||
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
|
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
|
||||||
| `styles` | нет | `{}` | Именованные стили |
|
| `styles` | нет | `{}` | Именованные стили |
|
||||||
|
|||||||
@@ -31,6 +31,22 @@ Claude напишет JSON-определение с областями, пар
|
|||||||
3. `/mxl-validate` → проверка корректности
|
3. `/mxl-validate` → проверка корректности
|
||||||
4. `/mxl-info` → верификация структуры (области, параметры)
|
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.
|
Быстрый обзор структуры макета без чтения тысяч строк XML.
|
||||||
|
|||||||
Reference in New Issue
Block a user