Metadata-Version: 2.1
Name: qlam
Version: 2.1.2
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      |  |
|  +--------------+----------+-------------------------------+  |
+---------------------------------------------------------------+
|  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

| Feature | QLM v2 | PDF |
|:---|:---|:---|
| Embed 3D models | ✅ Native GLB/OBJ | ❌ Not supported |
| 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 offset slicing | ❌ |
| Human-readable manifest | ✅ JSON index | ❌ Binary xref |
| Pure Python SDK | ✅ No dependencies | ❌ Needs PyMuPDF |
| Cross-platform | ✅ Win / 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)*
