Metadata-Version: 2.4
Name: xllayout
Version: 1.2.0
Summary: Composable framework for building styled Excel reports with openpyxl and matplotlib.
Author: Juan Román Hernández
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: openpyxl<4,>=3.1
Provides-Extra: charts
Requires-Dist: matplotlib<4,>=3.8; extra == "charts"
Requires-Dist: Pillow<12,>=10; extra == "charts"

# xllayout
Composable framework to build styled Excel reports with `openpyxl` and `matplotlib`.

## Philosophy
`xllayout` is split into three layers:
1. `Theme`: centralized visual tokens (fonts, fills, borders, number formats, spacing).
2. `Components`: reusable blocks (`TitleBlock`, `KpiCard`, `TableBlock`, `MatplotlibChart`, etc.).
3. `Layouts`: sheet composition using absolute canvas coordinates or `Grid12`.

This lets you place components at any coordinate without relying on cursor-based writing.

## Structure
```text
xllayout/
  pyproject.toml
  xllayout/
    __init__.py
    core/
      canvas.py
      cell_style.py
      grid.py
      style_engine.py
      theme.py
      formats.py
      utils.py
    components/
      title.py
      text.py
      section.py
      kpi.py
      table.py
      chart.py
    layouts/
      summary.py
    writer/
      workbook.py
  examples/
    build_summary.py
```

## Install
Editable (recommended for development):
```bash
pip install -e ".[charts]"
```

Standard install:
```bash
pip install ".[charts]"
```

- Base dependency: `openpyxl`
- Optional extra `charts`: `matplotlib`, `Pillow`

## Quick API
```python
from xllayout.core.theme import LightTheme
from xllayout.layouts.summary import ExecutiveSummaryData
from xllayout.core.style_engine import StyleRegistry, StyleRule, StyleSelector
from xllayout.core.cell_style import CellStyleSpec
from xllayout.writer.workbook import ReportWriter

style_registry = StyleRegistry()
style_registry.add_rule(
    StyleRule(
        name="table_headers_center",
        selector=StyleSelector(role="table_header"),
        style=CellStyleSpec(align_horizontal="center", wrap_text=True),
        priority=10,
    )
)

writer = ReportWriter(
    theme=LightTheme(),
    detect_collisions=True,
    style_registry=style_registry,
)
wb = writer.new_workbook()
writer.build_summary_sheet(wb, data)  # data: ExecutiveSummaryData
wb.save("report.xlsx")
```

## Table Cell Styling (v1.1.0)
`TableBlock` now supports per-column text/cell styling:

```python
from xllayout.components.table import TableBlock
from xllayout.core.cell_style import CellStyleSpec

table = TableBlock(
    headers=["id", "descripcion", "valor"],
    rows=[[1, "Texto largo que debe envolver en varias lineas", 1200]],
    col_widths=[10, 34, 14],
    number_formats_by_col={3: "#,##0"},
    body_style_by_col={
        1: CellStyleSpec(align_horizontal="center", bold=True),
        2: CellStyleSpec(wrap_text=True, align_vertical="top"),
        3: CellStyleSpec(align_horizontal="right", font_color="#1B4D8A"),
    },
    header_style=CellStyleSpec(
        bold=True,
        align_horizontal="center",
        fill_color="#E2EAF3",
        border_style="thin",
        border_color="#D0D7DE",
    ),
    header_row_height=24,
    default_row_height=32,
)
```

Supported style attributes (`CellStyleSpec`):
- font: `font_name`, `font_size`, `bold`, `italic`, `underline`, `font_color`
- cell: `fill_color`, `border_style`, `border_color`
- alignment: `align_horizontal`, `align_vertical`, `wrap_text`, `shrink_to_fit`, `text_rotation`
- format: `number_format`

## Global Style Engine (v1.2.0)
You can apply style rules to any component region using roles like:
- `title`, `title_subtitle`
- `section_header`
- `kpi_card`, `kpi_label`, `kpi_value`
- `table_header`, `table_body`, `table_empty`
- `description`, `chart_area`

Rules are evaluated by `priority`, then merged into the existing cell style.

```python
from xllayout.core.style_engine import StyleRegistry, StyleRule, StyleSelector
from xllayout.core.cell_style import CellStyleSpec

registry = StyleRegistry()
registry.set_role_style("table_body", CellStyleSpec(font_size=9))
registry.add_rule(
    StyleRule(
        name="program_column_wrap",
        selector=StyleSelector(role="table_body", column_name="program_name"),
        style=CellStyleSpec(wrap_text=True, align_vertical="top"),
        priority=20,
    )
)
registry.add_rule(
    StyleRule(
        name="critical_rows",
        selector=StyleSelector(role="table_body", col=8),
        style=CellStyleSpec(font_color="#9C1C1C", bold=True),
        priority=30,
    )
)

writer = ReportWriter(theme=LightTheme(), style_registry=registry)
```

## Canvas vs Grid12
Absolute placement:
```python
from xllayout.core.canvas import SheetCanvas

canvas = SheetCanvas(ws, theme, detect_collisions=True)
canvas.put(component, row=5, col=3)
```

Grid placement:
```python
from xllayout.core.grid import Grid12

grid = Grid12(canvas)  # 12 tracks
grid.put(component, y=4, x=1, w=6, h=5)
```

If `detect_collisions=True`, overlapping components raise `ValueError`.

## Build a New Layout
1. Create a typed input dataclass under `layouts/`.
2. In `build(ws)`, initialize `SheetCanvas` and optionally `Grid12`.
3. Place components with `canvas.put(...)` or `grid.put(...)`.
4. Expose a facade method in `ReportWriter`.

## Run the example
```bash
python examples/build_summary.py
```

Output:
```text
examples/output/summary_report_example.xlsx
```

## Versioning and publishing
- Uses SemVer (`1.0.0`, `1.0.1`, `1.1.0`, `1.2.0`, ...).
- Recommended for teams: publish to a private PyPI-compatible registry.
