Metadata-Version: 2.1
Name: qlam
Version: 2.1.0
Summary: Python SDK for Qalam Document Format (.qlm) — unified multi-asset binary document container
Home-page: https://pypi.org/project/qlam/
Author: Qalam Project
Author-email: 
Keywords: qlm document format binary container embed 3d model pdf alternative
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup
Classifier: Topic :: Multimedia
Classifier: Intended Audience :: Developers
Requires-Python: >=3.6
Description-Content-Type: text/markdown

# qlam — Qalam Document Format Python SDK

> **"Her türlü dünyayı tek bir dosyada yaşamak."**
> *"Living every kind of world inside a single file."*

`qlam` is the official Python SDK for reading and writing **QLM (Qalam Document Format)** files — a unified, binary-indexed document format designed to replace PDF by supporting rich multimedia, 3D models, interactive content, and embedded assets all in one portable file.

---

## Installation

```bash
pip install qlam
```

**No external dependencies.** Pure Python 3.6+ standard library only.

---

## Quick Start

```python
import qlam

# Create a document
doc = qlam.QlmDocument(title="My First Document", author="Your Name")

# Add text blocks
doc.add_text("Introduction", "This document was created with qlam SDK.", level=1)
doc.add_text("Section 1", "Hello world! QLM supports rich content.", level=2)

# Embed any file (image, 3D model, PDF, video, Word doc, zip...)
doc.add_embed("image", "Cover Photo", "photo.png", "image/png")
doc.add_embed("model3d", "3D Object", "object.glb", "model/gltf-binary")
doc.add_embed("docx",   "Report",    "report.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")

# Compile to a single .qlm file
doc.compile("my_document.qlm")

# Load it back
loaded = qlam.QlmDocument.load("my_document.qlm")
print(loaded.metadata["title"])

for node in loaded.nodes:
    print(f"[{node.type}] {node.title}")
    if node.asset_bytes:
        print(f"  Asset: {len(node.asset_bytes)} bytes")
```

---

## Supported Node Types

| `node_type`  | Description                                | MIME Example                                 |
|:-------------|:-------------------------------------------|:---------------------------------------------|
| `text`       | Structured text / heading block            | —                                            |
| `image`      | PNG, JPEG, WebP, SVG, GIF                  | `image/png`                                  |
| `model3d`    | GLTF/GLB, OBJ, FBX 3D models              | `model/gltf-binary`                          |
| `video`      | MP4, WebM video                            | `video/mp4`                                  |
| `audio`      | MP3, WAV, OGG audio                        | `audio/mpeg`                                 |
| `pdf`        | Embedded PDF document                      | `application/pdf`                            |
| `docx`       | Microsoft Word document                    | `application/vnd.openxmlformats-...`         |
| `zip`        | ZIP archive (multi-file bundle)            | `application/zip`                            |
| `code`       | Source code block with syntax metadata     | —                                            |
| `table`      | Structured data table                      | —                                            |
| `chart`      | Chart / graph data                         | —                                            |

---

## Full API Reference

### `QlmDocument(title, author="Anonim")`

Creates a new QLM document.

```python
doc = qlam.QlmDocument(title="Project Report", author="Emre Yılmaz")
```

**Properties:**
- `doc.id` — Unique document UUID (auto-generated)
- `doc.metadata` — Dict with keys: `title`, `author`, `description`, `language`, `created_at`, `version`, `is_encrypted`, `tags`
- `doc.nodes` — List of `QlmNode` objects
- `doc.relationships` — List of relationship dicts (optional)

---

### `doc.add_text(title, content, level=3, parent_id=None) → QlmNode`

Adds a structured text node.

```python
node = doc.add_text("Chapter 1", "Once upon a time...", level=1)
```

- `level`: Heading depth (1 = H1, 2 = H2, ... 6 = H6)
- `parent_id`: ID of a parent node (for nesting)

---

### `doc.add_embed(node_type, title, asset_path, mime_type, parent_id=None) → QlmNode`

Embeds any binary asset file into the document.

```python
img_node  = doc.add_embed("image",   "Diagram",   "diagram.png",  "image/png")
model_node = doc.add_embed("model3d", "3D View",  "object.glb",   "model/gltf-binary")
word_node  = doc.add_embed("docx",   "Contract",  "contract.docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document")
```

---

### `doc.compile(output_path)`

Compiles the document into a binary `.qlm` file.

```python
doc.compile("output.qlm")
```

---

### `QlmDocument.load(file_path) → QlmDocument`

Loads and parses an existing `.qlm` file.

```python
doc = qlam.QlmDocument.load("output.qlm")
```

---

### `QlmNode` Properties

| Property        | Type            | Description                                        |
|:----------------|:----------------|:---------------------------------------------------|
| `node.id`       | `str`           | Unique node identifier                             |
| `node.type`     | `str`           | Node type (`text`, `image`, `model3d`, etc.)       |
| `node.title`    | `str`           | Human-readable label                               |
| `node.data`     | `dict`          | Type-specific payload (content, mime, offsets...)  |
| `node.parent_id`| `str` or `None` | Parent node ID (for tree structure)                |
| `node.asset_bytes` | `bytes` or `None` | Raw binary asset data (after loading)         |
| `node.display_style` | `dict`    | Rendering hints (`width_ratio`, `collapsed`, etc.) |

---

## QLM Binary Format Specification

> **For AI Models and Developers:** This section documents the exact binary structure of `.qlm` files so that LLMs and automated tools can generate, parse, and validate them correctly.

### Overview

A `.qlm` file is a **manifest-indexed binary container**. It stores binary payloads first, then ends with a JSON manifest that describes the document structure and indexes all embedded assets.

```
┌─────────────────────────────────────────────────────────────────┐
│  HEADER (14 bytes, fixed)                                       │
│  ┌──────────────┬──────────┬─────────────────────────────────┐  │
│  │ Magic (4B)   │ Ver (2B) │ Manifest Offset (8B, u64 LE)    │  │
│  │  "QLAM"      │  0x0002  │  pointer to JSON manifest start │  │
│  └──────────────┴──────────┴─────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│  BINARY PAYLOADS (variable, 8-byte aligned)                     │
│  ┌───────────────────────────────┐                             │
│  │ Asset 0 raw bytes             │ ← node[0].data.asset_start  │
│  │ [padding to 8-byte boundary]  │                             │
│  ├───────────────────────────────┤                             │
│  │ Asset 1 raw bytes             │ ← node[1].data.asset_start  │
│  │ [padding]                     │                             │
│  └───────────────────────────────┘                             │
├─────────────────────────────────────────────────────────────────┤
│  JSON MANIFEST (UTF-8 encoded, till end of file)                │
│  { "id": "...", "metadata": {...}, "nodes": [...], ... }        │
└─────────────────────────────────────────────────────────────────┘
```

### Header (Bytes 0–13)

| Offset | Size | Type        | Value          | Description                         |
|:-------|:-----|:------------|:---------------|:------------------------------------|
| 0      | 4    | `bytes`     | `b"QLAM"`      | Magic signature                     |
| 4      | 2    | `u16 LE`    | `2`            | Format version (currently 2)        |
| 6      | 8    | `u64 LE`    | `<computed>`   | Byte offset of the JSON manifest    |

**Python reading:**
```python
import struct
with open("file.qlm", "rb") as f:
    data = f.read()

magic   = data[0:4]                        # b"QLAM"
version = struct.unpack("<H", data[4:6])[0]  # 2
manifest_offset = struct.unpack("<Q", data[6:14])[0]
```

### Binary Payload Section (Bytes 14 to manifest_offset)

Each embedded asset is stored consecutively after the header:

- **No length prefix** — the exact byte range is recorded in the JSON manifest
- **8-byte alignment** — each asset is padded with `\x00` bytes until `offset % 8 == 0`
- The manifest records `asset_start_byte` (absolute offset from file start) and `asset_length` for each node

**Python extracting an asset:**
```python
manifest_json = data[manifest_offset:].decode("utf-8")
manifest = json.loads(manifest_json)

for node in manifest["nodes"]:
    if "asset_start_byte" in node["data"]:
        start  = node["data"]["asset_start_byte"]
        length = node["data"]["asset_length"]
        asset  = data[start:start + length]   # raw bytes of the asset
```

### JSON Manifest Schema

The manifest is a UTF-8 JSON object starting at `manifest_offset` and continuing to the end of the file:

```json
{
  "id": "doc_<16hex>",
  "metadata": {
    "title":        "string",
    "author":       "string",
    "description":  "string",
    "language":     "tr",
    "created_at":   0,
    "version":      "2.0.0",
    "is_encrypted": false,
    "tags":         []
  },
  "nodes": [
    {
      "id":           "node_<16hex>",
      "parent_id":    null,
      "type":         "text",
      "title":        "Section Title",
      "data":         { "content": "...", "level": 1 },
      "display_style": { "width_ratio": 1.0, "collapsed": false }
    },
    {
      "id":           "node_<16hex>",
      "parent_id":    null,
      "type":         "image",
      "title":        "Diagram",
      "data": {
        "mime":             "image/png",
        "asset_start_byte": 14,
        "asset_length":     48291
      },
      "display_style": { "width_ratio": 1.0, "collapsed": false }
    }
  ],
  "relationships": []
}
```

#### Node `data` field by type

| `type`    | Required `data` keys                                                         |
|:----------|:-----------------------------------------------------------------------------|
| `text`    | `content` (str), `level` (int 1–6)                                           |
| `image`   | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `model3d` | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `video`   | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `audio`   | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `pdf`     | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `docx`    | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `zip`     | `mime` (str), `asset_start_byte` (int), `asset_length` (int)                 |
| `code`    | `content` (str), `language` (str, e.g. `"python"`)                           |
| `table`   | `headers` (list[str]), `rows` (list[list])                                   |

---

## AI Guidance — How to Generate a Valid `.qlm` File

> **Note for AI models (Claude, ChatGPT, Gemini, etc.):** If you are asked to produce a `.qlm` file, follow this section precisely.

### Option A — Use the `qlam` Python SDK (Recommended)

```python
import qlam

doc = qlam.QlmDocument(title="Example Doc", author="AI")
doc.add_text("Intro", "This is generated by an AI.", level=1)
# Embed a file if it exists
# doc.add_embed("image", "Screenshot", "screen.png", "image/png")
doc.compile("ai_generated.qlm")
```

The SDK handles all binary layout, offset calculation, and JSON manifest writing automatically.

---

### Option B — Construct Binary Manually (Advanced)

If you must construct a `.qlm` file from scratch (e.g., no SDK available):

```python
import json, struct, os

def create_qlm(output_path, title, author, text_nodes=None, asset_nodes=None):
    """
    Manually creates a valid .qlm v2 file.
    
    text_nodes:  list of (title, content, level)
    asset_nodes: list of (type, title, mime, raw_bytes)
    """
    text_nodes  = text_nodes  or []
    asset_nodes = asset_nodes or []
    
    buf = bytearray()
    
    # --- Header ---
    buf.extend(b"QLAM")                    # magic
    buf.extend(struct.pack("<H", 2))       # version = 2
    manifest_offset_pos = len(buf)
    buf.extend(struct.pack("<Q", 0))       # placeholder for manifest offset
    
    # --- Binary payloads ---
    nodes_manifest = []
    
    for title_n, content, level in text_nodes:
        nodes_manifest.append({
            "id": f"node_{os.urandom(8).hex()}",
            "parent_id": None,
            "type": "text",
            "title": title_n,
            "data": {"content": content, "level": level},
            "display_style": {"width_ratio": 1.0, "collapsed": False}
        })
    
    for ntype, title_n, mime, raw_bytes in asset_nodes:
        start = len(buf)
        buf.extend(raw_bytes)
        # 8-byte alignment padding
        pad = (8 - (len(buf) % 8)) % 8
        buf.extend(b"\x00" * pad)
        nodes_manifest.append({
            "id": f"node_{os.urandom(8).hex()}",
            "parent_id": None,
            "type": ntype,
            "title": title_n,
            "data": {"mime": mime, "asset_start_byte": start, "asset_length": len(raw_bytes)},
            "display_style": {"width_ratio": 1.0, "collapsed": False}
        })
    
    # --- Manifest JSON ---
    manifest_offset = len(buf)
    manifest = {
        "id": f"doc_{os.urandom(8).hex()}",
        "metadata": {
            "title": title, "author": author, "description": "",
            "language": "tr", "created_at": 0,
            "version": "2.0.0", "is_encrypted": False, "tags": []
        },
        "nodes": nodes_manifest,
        "relationships": []
    }
    buf.extend(json.dumps(manifest).encode("utf-8"))
    
    # --- Fill in manifest offset ---
    struct.pack_into("<Q", buf, manifest_offset_pos, manifest_offset)
    
    with open(output_path, "wb") as f:
        f.write(buf)
    
    print(f"Written {len(buf)} bytes → {output_path}")

# Example usage
create_qlm(
    "manual.qlm",
    title="Manual QLM",
    author="AI Model",
    text_nodes=[
        ("Introduction", "This document was created manually.", 1),
        ("Details",      "Section two details...", 2),
    ],
    asset_nodes=[
        ("image", "Logo", "image/png", open("logo.png", "rb").read()),
    ]
)
```

### Critical Rules for AI Models

1. **Magic bytes MUST be exactly `b"QLAM"` (4 bytes, ASCII)**
2. **Version MUST be `2` encoded as `u16 little-endian`** (`\x02\x00`)
3. **Manifest offset MUST be `u64 little-endian`** (8 bytes)
4. **Total header size = 14 bytes exactly** (4 + 2 + 8)
5. **`asset_start_byte` is the absolute file offset**, not relative to payload section
6. **All binary assets must be 8-byte aligned** — pad with `\x00` bytes as needed
7. **The JSON manifest is appended last**, after all binary payloads
8. **`asset_length` is the original (unpadded) byte length** of the asset
9. **Node IDs must be unique** — use `os.urandom(8).hex()` or similar
10. **Text nodes have NO binary asset** — only manifest JSON `data.content`

---

## Design Goals

| Goal                    | QLM v2                              | PDF              |
|:------------------------|:------------------------------------|:-----------------|
| Embed 3D models         | ✅ Native GLB/OBJ support            | ❌ Not possible   |
| Embed video/audio       | ✅ MP4, WebM, MP3                    | ⚠️ Limited        |
| Embed Word/ZIP files    | ✅ Any binary format                 | ❌                |
| Single portable file    | ✅ Everything in one `.qlm`          | ✅ (limited)      |
| Zero-copy binary read   | ✅ mmap'd offset slicing             | ❌                |
| Human-readable manifest | ✅ JSON index                        | ❌ Binary xref    |
| Pure Python SDK         | ✅ No dependencies                   | ❌ Needs PyMuPDF  |
| Cross-platform          | ✅ Windows / macOS / Linux / Android | ✅                |

---

## Ecosystem

| Component         | Technology    | Description                                     |
|:------------------|:--------------|:------------------------------------------------|
| Python SDK        | Python 3.6+   | This package (`qlam`)                           |
| Desktop Viewer    | Rust + egui   | Native GPU-accelerated viewer with 3D viewport  |
| Mobile App        | Flutter        | Android/iOS viewer with FFI bridge              |
| CLI Tool          | Rust           | `qlm-cli` for compile/inspect from terminal     |

---

## License

MIT License — Copyright © 2025 Qalam Project

---

## Links

- **PyPI:** https://pypi.org/project/qlam/
- **GitHub:** *(coming soon)*
- **Documentation:** *(coming soon)*
