<p align="center">
  <img src="assets/icon.svg" alt="Lumora" width="128" height="128">
</p>

<h1 align="center">Lumora</h1>

<p align="center">
  A composable image generation and document rendering library for Python, powered by Pillow.
</p>

<p align="center">
  <a href="https://pypi.org/project/lumora/"><img src="https://img.shields.io/pypi/v/lumora.svg" alt="PyPI"></a>
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+"></a>
  <a href="https://github.com/krypton-byte/lumora/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License"></a>
  <a href="https://krypton-byte.github.io/lumora/playground/"><img src="https://img.shields.io/badge/try-playground-7c6ef0.svg" alt="Playground"></a>
  <a href="https://krypton-byte.github.io/lumora/docs/"><img src="https://img.shields.io/badge/docs-mkdocs-blue.svg" alt="Documentation"></a>
</p>

---

## Overview

**Lumora** lets you build complex, pixel-perfect images, multi-page PDF documents, and PPTX presentations entirely in Python — no browser, no template engine, no external rendering service.

Inspired by modern declarative UI toolkits (Jetpack Compose, Flutter), Lumora provides a composable widget tree where every element — containers, columns, rows, text, images — is sized, laid out, clipped, and rendered automatically.

### Key features

- **Composable layouts** — `Container`, `Column`, `Row`, `Stack`, `Expanded`, `Spacer`, `SizedBox`, `Padding`
- **Rich text** — `TextWidget`, `RichTextWidget` (inline HTML-like markup), `JustifiedTextWidget`, `HighlightTextWidget`
- **Advanced styling** — gradients (linear & radial), rounded corners, circles, strokes, shadows, text transforms
- **Visual effects** — blur, stroke outlines, glassmorphism corners, alpha gradients, custom effects
- **GPU acceleration** — optional Taichi-powered GPU backend (`lumora[gpu]`) with automatic CPU/NumPy fallback
- **Smart render caching** — content-addressable LRU cache with structural hashing; unchanged widgets return instantly on re-render (~17× speedup)
- **Design-resolution scaling** — define once at a virtual size, render to any resolution with LANCZOS resampling
- **PDF export** — multi-page documents with optional metadata via pikepdf
- **PPTX export** — PowerPoint presentations with auto-fitted slide dimensions via python-pptx
- **`@preview` decorator** — mark specific `Page` subclasses for selective rendering in the playground and VS Code extension
- **Pure Python** — depends on Pillow, NumPy, and python-pptx; pikepdf is an optional extra

---

## Monorepo structure

```
lumora/
├── packages/
│   ├── lumora/              # Core Python library (published to PyPI)
│   └── lumora-vscode/       # VS Code extension for live preview
├── docs/
│   ├── content/             # MkDocs documentation source
│   ├── playground/          # Browser-based playground (Vite + React)
│   └── landing/             # Landing page
├── examples/                # Example scripts
└── pyproject.toml           # Python project configuration
```

---

## Try it online

The **[Lumora Playground](https://krypton-byte.github.io/lumora/playground/)** lets you write and render Lumora pages directly in the browser — no installation required. Features include:

- Live code editor with Python syntax highlighting
- Instant rendering with render time display
- Zoom, pan, and fit controls for the preview
- Export to PDF (client-side via jsPDF) and PPTX
- Page reordering before export
- File save/open and shareable URLs
- Auto-saves your code between sessions
- Built-in examples to get started

---

## Installation

```bash
pip install lumora
```

For PDF metadata support (title, author, etc.):

```bash
pip install lumora[pikepdf]
```

For GPU-accelerated rendering via [Taichi](https://www.taichi-lang.org/):

```bash
pip install lumora[gpu]
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv add lumora            # core (includes python-pptx)
uv add lumora[pikepdf]   # with PDF metadata
uv add lumora[gpu]       # with GPU acceleration
```

### Requirements

- Python 3.12+
- Pillow >= 12.1
- NumPy >= 2.4
- python-pptx >= 0.6 *(included — for PPTX export)*
- pikepdf >= 10.3 *(optional — for PDF metadata)*
- Taichi >= 1.7 *(optional — for GPU acceleration)*

---

## Quick start

```python
from lumora.page import Page, preview
from lumora.layout import Container, Column, TextWidget
from lumora.config import RGBColor, Alignment, Margin
from lumora.font import TextStyle, FontWeight

title_style = TextStyle(
    font_size=72,
    font_family="Inter",
    font_weight=FontWeight.BOLD,
    paint=RGBColor(255, 255, 255),
)

body_style = TextStyle(
    font_size=32,
    font_family="Inter",
    paint=RGBColor(200, 200, 200),
)


@preview
class HelloPage(Page):
    design_width = 1920
    design_height = 1080

    def build(self):
        return Container(
            width=self.design_width,
            height=self.design_height,
            background=RGBColor(25, 25, 35),
            alignment=Alignment.CENTER,
            padding=Margin(60, 60, 60, 60),
            child=Column(
                spacing=20,
                children=[
                    TextWidget("Hello, Lumora!", style=title_style),
                    TextWidget(
                        "Composable image generation in pure Python.",
                        style=body_style,
                    ),
                ],
            ),
        )


page = HelloPage()
page.save("hello.png", width=1920)
```

### The `@preview` decorator

Mark any `Page` subclass with `@preview` to flag it for selective rendering in the [Playground](https://krypton-byte.github.io/lumora/playground/) and the [VS Code extension](#vs-code-extension). When `@preview` is present on any class, only decorated classes are rendered — otherwise all `Page` subclasses are rendered:

```python
from lumora.page import Page, preview

@preview
class DesignDraft(Page):
    """Only this page renders in the playground/VS Code."""
    ...

class ProductionPage(Page):
    """This page is skipped in preview tools."""
    ...
```

---

## Widget catalog

| Widget | Description |
|-|-|
| `Container` | Box with background, padding, alignment, clipping, effects |
| `Column` | Vertical layout with spacing and axis alignment |
| `Row` | Horizontal layout with spacing and axis alignment |
| `Stack` | Overlay children on top of each other |
| `Expanded` | Fill remaining space in a `Column` or `Row` |
| `Spacer` | Fixed-size empty space |
| `SizedBox` | Force explicit width/height on a child |
| `Padding` | Add insets around a child |
| `TextWidget` | Single-style text block |
| `RichTextWidget` | Inline markup: `<b>`, `<i>`, `<color>`, `<size>`, etc. |
| `JustifiedTextWidget` | Fully justified paragraph text |
| `HighlightTextWidget` | Text with highlighted background spans |
| `ImageWidget` | Render an image from file or PIL Image |
| `LineWidget` | Horizontal line / divider |
| `GlassContainer` | Frosted-glass effect container |

---

## Multi-page PDF

```python
from lumora.page import Document, DocumentMetadata, Format

doc = Document()
doc.add_page(CoverPage())
doc.add_page(ChartPage())
doc.add_page(SummaryPage())

# Without metadata — no pikepdf needed
doc.save("report.pdf", width=1920)

# With metadata — requires: pip install lumora[pikepdf]
doc.save(
    "report.pdf",
    Format.PDF,
    DocumentMetadata(title="Q4 Report", author="Analytics"),
    width=1920,
)
```

---

## PPTX (PowerPoint) export

Slide dimensions automatically match the image aspect ratio — no stretching.

```python
doc.save("slides.pptx", Format.PPTX)

# With metadata
doc.save(
    "slides.pptx",
    Format.PPTX,
    DocumentMetadata(title="Q4 Report", author="Analytics"),
    width=1920,
)

# At half resolution
doc.save("slides.pptx", Format.PPTX, scale=0.5)
```

---

## GPU acceleration

Lumora includes a dual compute backend. When `lumora[gpu]` is installed,
heavy pixel operations (gradients, blending, vignette, noise, etc.) are
offloaded to the GPU via [Taichi](https://www.taichi-lang.org/). Without
Taichi, the same operations run on the CPU using optimised NumPy.

```bash
pip install lumora[gpu]
```

Check the active backend at runtime:

```python
from lumora.backend import get_backend
print(get_backend())  # 'taichi' or 'numpy'
```

Taichi tries CUDA → Vulkan → Metal → OpenGL and finally falls back to
CPU if no GPU driver is available. Even in CPU mode, Taichi kernels
benefit from multi-threading and JIT compilation.

---

## Render caching

Lumora includes a **content-addressable render cache** that makes
re-rendering fast — especially during live-preview workflows where only
a few widgets change between refreshes.

Every widget computes a *structural hash* from its parameters and the
hashes of its children. When `render()` is called, Lumora checks the
cache first and returns the stored image immediately on a hit. Only
widgets whose content actually changed are re-rendered.

**Highlights:**

- **Automatic** — enabled by default, zero configuration needed.
- **Content-based** — even if widget objects are recreated (e.g. on each VS Code preview refresh), cache hits occur as long as the parameters match.
- **LRU + memory budget** — defaults to 256 MB; evicts least-recently-used entries when the budget is exceeded.
- **~17× speedup** on hot re-renders with partial changes (benchmarked on a 25-widget tree).

### Configuration

```python
from lumora.cache import RenderCache

# Raise memory budget to 512 MB
RenderCache.configure(max_mb=512)

# Disable caching entirely
RenderCache.configure(enabled=False)

# Inspect hit/miss statistics
print(RenderCache.instance().stats)
# {'hits': 19, 'misses': 1, 'hit_rate': 0.95, 'entries': 20,
#  'memory_mb': 42.0, 'max_memory_mb': 256.0}

# Clear all cached entries
RenderCache.instance().clear()
```

---

## Tooling

### Playground

The **[Lumora Playground](https://krypton-byte.github.io/lumora/playground/)** runs entirely in the browser via PyScript/Pyodide. Write, render, zoom/pan, and export — with auto-save and shareable URLs.

<p align="center">
  <img src="https://cdn.jsdelivr.net/gh/bgstanly/stream@157861b/output_playground.webp" alt="Lumora Playground Demo" width="720" />
</p>
<p align="center"><em>Lumora Playground — write and render Lumora pages directly in the browser</em></p>

### VS Code Extension

The **Lumora Preview** extension (`packages/lumora-vscode/`) provides live preview directly in VS Code with hot reload.

<p align="center">
  <img src="https://cdn.jsdelivr.net/gh/bgstanly/stream@157861b/output_vscode.webp" alt="Lumora VS Code Extension Demo" width="720" />
</p>
<p align="center"><em>Lumora VS Code Extension — live preview with hot reload</em></p>

Files must start with `# @lumora-file-preview` on the first line, and pages must be decorated with `@preview`:

```python
# @lumora-file-preview
from lumora.page import Page, preview
from lumora.layout import Container, TextWidget
from lumora.config import RGBColor, Alignment
from lumora.font import TextStyle, FontWeight


@preview
class MyPage(Page):
    design_width = 1920
    design_height = 1080

    def build(self):
        return Container(
            width=self.design_width,
            height=self.design_height,
            background=RGBColor(25, 25, 40),
            alignment=Alignment.CENTER,
            child=TextWidget(
                "Hello!",
                style=TextStyle(
                    font_size=80,
                    font_weight=FontWeight.BOLD,
                    paint=RGBColor(255, 255, 255),
                ),
            ),
        )
```

Install from the [Releases](https://github.com/krypton-byte/lumora/releases) page — download the `.vsix` file and install via `Ctrl+Shift+P` → "Extensions: Install from VSIX...".

---

## Development

```bash
# Clone the monorepo
git clone https://github.com/krypton-byte/lumora.git
cd lumora

# Install Python dependencies
uv sync --group docs --group dev

# Run examples
uv run python examples/01_hello_world.py

# Serve docs locally
uv run mkdocs serve -f docs/mkdocs.yml

# Build the playground
cd docs/playground && bun install && bun run dev

# Build the VS Code extension
cd packages/lumora-vscode && bun install && bun run compile
```

---

## Documentation

Full API documentation is available at **[krypton-byte.github.io/lumora/docs/](https://krypton-byte.github.io/lumora/docs/)**.

---

## License

MIT — see [LICENSE](LICENSE) for details.
