Metadata-Version: 2.4
Name: pdfblox
Version: 1.0.0
Summary: Composable PDF building blocks (tables, headers, footers, title pages, tile grids) built on ReportLab.
Author-email: Aleksey Suvorov <cranesoft@protonmail.com>
License: MIT
Keywords: pdf,reportlab,report,building-blocks
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: reportlab>=4.0
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Dynamic: license-file

# pdfblox

Composable **PDF building blocks** for report generation, built on
[ReportLab](https://www.reportlab.com/). pdfblox turns structured data and
images into finished PDF reports — styled tables, page headers/footers,
title pages, and tile grids.

## Design

pdfblox is the **PDF-generation half** of a larger report-writing system. It
deliberately knows nothing about where data comes from:

```
┌──────────────────────────────────────────────────────────────┐
│  Integration wrapper  (owns .env / parameter injection)        │
│                                                                │
│   ┌─────────────────────┐         ┌────────────────────────┐  │
│   │  Data-source library │  data   │      pdfblox           │  │
│   │  (e.g. Some REST)    │ ──────▶ │  (this library)        │  │
│   │  fetch + render      │ images  │  build the PDF         │  │
│   └─────────────────────┘         └────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘
```

Consequences of this boundary:

- **One dependency: `reportlab`.** No `plotly`, no `kaleido`, no networking.
- **No `dotenv`, no `os.getenv`.** All configuration enters through explicit
  function arguments and `*Config` dataclasses. Environment/credential
  loading belongs to the integration layer above.
- **Images come in as bytes or flowables.** A logo is `logo_data: bytes` or a
  `logo_path`; a chart is a ready-made ReportLab `Image`. pdfblox never
  fetches or renders them.

## Documentation

- **[AI_REPORT_AUTHORING.md](AI_REPORT_AUTHORING.md)** — how to *author a report*
  with pdfblox (block reference, recipes, pitfalls, a full template). The guide
  to read when generating report scripts.
- **[CLAUDE.md](CLAUDE.md)** — how to *develop the library itself* (boundary
  rules, conventions, how to add a block).

## Installation

```bash
pip install -e .
```

## Building Blocks

| Block | Module | Key API |
|-------|--------|---------|
| Styled table | `pdfblox.tables` | `ColumnConfig`, `TableConfig`, `create_table` |
| Key/value table | `pdfblox.tables` | `KeyValueConfig`, `create_keyvalue_table` |
| Text & headings | `pdfblox.text` | `TextConfig`, `paragraph`, `heading` |
| Lists | `pdfblox.lists` | `ListConfig`, `bullet_list`, `numbered_list` |
| Image | `pdfblox.images` | `make_image` |
| Charts (native vector) | `pdfblox.charts` | `ChartConfig`, `bar_chart`, `line_chart`, `pie_chart`, `scatter_chart` |
| Layout primitives | `pdfblox.layout` | `spacer`, `divider`, `CalloutConfig`, `kpi_callout` |
| Page header | `pdfblox.header` | `HeaderConfig`, `draw_page_header` |
| Page footer | `pdfblox.footer` | `FooterConfig`, `draw_page_footer` |
| Title / cover page | `pdfblox.document` | `TitlePageConfig` |
| Tile grid | `pdfblox.document` | `TileGridConfig`, `build_tile_grid` |
| Document assembly | `pdfblox.document` | `PdfConfig`, `build_pdf`, `build_paginated_pdf` |
| Fluent builder | `pdfblox.report` | `Report` |
| Theme | `pdfblox.theme` | `Theme` |
| Theme presets | `pdfblox.themes` | `get_theme`, `available_themes`, `corporate_blue`, `slate_gray`, `forest_green`, `burgundy`, `teal`, `graphite` |
| Unicode fonts | `pdfblox.fonts` | `use_unicode_fonts`, `register_font` |
| Date / OS helpers | `pdfblox.utils` | `parse_timestamp`, `normalize_date`, `blank_to_none`, `open_file_in_windows` |

### One palette for the whole report: `Theme`

A `Theme` bundles fonts and colours and produces every block's config from
them. Pass it to `Report` and blocks are styled automatically; explicit
configs and per-call overrides still win.

```python
from reportlab.lib import colors
from pdfblox import Report, Theme

theme = Theme(primary_color=colors.HexColor("#7A0019"))  # brand colour

(
    Report("out.pdf",
           theme=theme,
           header_config=theme.header_config("Quarterly Report"),
           footer_config=theme.footer_config())
    .add_heading("Summary")          # styled from theme
    .add_paragraph("Body text.")     # styled from theme
    .add_callout("42", "Sensors")    # styled from theme
    .build()
)
```

### Charts

pdfblox draws **native vector charts** with ReportLab's own graphics engine —
no Plotly, kaleido, or matplotlib, and no extra dependency. Good for the
standard reporting charts; series colours come from the active `Theme`.

```python
from pdfblox import Report, get_theme

(
    Report("out.pdf", theme=get_theme("forest_green"))
    .add_bar_chart([[3, 5, 2], [4, 1, 6]], categories=["A", "B", "C"],
                   series_names=["Plan", "Actual"])
    .add_line_chart([1, 3, 2, 5], categories=["Q1", "Q2", "Q3", "Q4"])
    .add_pie_chart([30, 50, 20], labels=["North", "South", "East"], donut=True)
    .add_scatter_chart([(1, 2), (3, 4), (5, 3)])
    .build()
)
```

To embed a chart rendered elsewhere (e.g. a Plotly/server figure), render it to
PNG/SVG bytes in the data-source layer and place it with `make_image` instead —
pdfblox stays rendering-engine-free either way.

#### Built-in presets

Six ready-made palettes popular for reporting — `corporate_blue`,
`slate_gray`, `forest_green`, `burgundy`, `teal`, `graphite`. Look one up by
name (with optional overrides) or call its factory:

```python
from pdfblox import Report, get_theme, available_themes

print(available_themes())               # list preset names
theme = get_theme("slate_gray", base_font_size=11)

Report("out.pdf", theme=theme,
       header_config=theme.header_config("Status Report"),
       footer_config=theme.footer_config()).add_heading("Summary").build()
```

### Recommended entry point: the `Report` builder

```python
from pdfblox import Report, HeaderConfig, FooterConfig

(
    Report("out.pdf",
           header_config=HeaderConfig(title="Status"),
           footer_config=FooterConfig())
    .add_heading("Summary")
    .add_paragraph("All systems nominal.")
    .add_divider()
    .add_callout("42", "Active Sensors")
    .add_keyvalue_table([("Site", "North"), ("Status", "OK")])
    .build()
)
```

## Quick start

A paginated table report with a header and footer — no data source, just
inline data:

```python
from reportlab.lib.pagesizes import letter, landscape
from reportlab.lib.units import inch
from pdfblox import (
    ColumnConfig, PdfConfig, HeaderConfig, FooterConfig,
    build_paginated_pdf,
)

data = [
    {"name": "Sensor A", "value": 12.4, "status": "OK"},
    {"name": "Sensor B", "value": 99.1, "status": "ALERT"},
]

columns = [
    ColumnConfig("Instrument", 3 * inch, "name", alignment="LEFT"),
    ColumnConfig("Reading", 2 * inch, "value", formatter=lambda v: f"{v:.1f}"),
    ColumnConfig("Status", 2 * inch, "status"),
]

build_paginated_pdf(
    data,
    columns,
    "report.pdf",
    pdf_config=PdfConfig(page_size=landscape(letter)),
    header_config=HeaderConfig(title="Instrument Status", company_info=["Acme Co."]),
    footer_config=FooterConfig(),
)
```

For mixed content (charts, paragraphs, custom flowables) use `build_pdf`; for
a grid of chart tiles use `build_tile_grid` to produce per-page `Table`
flowables, then pass them to `build_pdf`. Add a cover page by passing a
`TitlePageConfig`.

## Tests

```bash
pip install -e ".[test]"
pytest
```

Each building block has a focused test under `tests/` that exercises it and,
where relevant, renders a real PDF into a temp directory.

## License

MIT — see [LICENSE](LICENSE).
