# SoulDoc usage examples

This guide demonstrates the public API of SoulDoc `1.0.3`.

SoulDoc can describe a document before or after geometry is calculated.
Printable HTML and SVG require no runtime dependencies. DOCX, PDF, and XLSX
are optional renderers.

## 1. Local installation

Install the package from the source directory:

```powershell
pip install -e D:\TenTags-all\SoulDoc
```

SoulDoc is then available to any Python project:

```python
import souldoc

print(souldoc.__version__)
```

Expected output:

```text
1.0.3
```

To run an example without installing the package, temporarily add `src` to
`PYTHONPATH`:

```powershell
$env:PYTHONPATH = "D:\TenTags-all\SoulDoc\src"
python example.py
```

## 2. Create an empty positioned document

```python
from souldoc import SoulDocument

document = SoulDocument(
    name="My document",
    sheet_name="Page",
    orientation="portrait",
)

print(document.orientation)
print(document.width_mm)
print(document.height_mm)
print(document.pages)
```

Expected output:

```text
portrait
210.0
297.0
[]
```

Use landscape orientation when required:

```python
document = SoulDocument(
    name="Landscape document",
    orientation="landscape",
)

print(document.width_mm, document.height_mm)
```

Expected output:

```text
297.0 210.0
```

## 3. Create a page with a positioned text box

```python
from souldoc import (
    SoulBox,
    SoulContentKind,
    SoulDocument,
    SoulPage,
)

document = SoulDocument(
    name="Contract",
    sheet_name="Page",
    orientation="portrait",
    pages=[
        SoulPage(
            number=1,
            width_mm=210,
            height_mm=297,
            boxes=[
                SoulBox(
                    id="title",
                    left_mm=20,
                    top_mm=15,
                    width_mm=170,
                    height_mm=25,
                    z_index=1,
                    content="Contract",
                    content_kind=SoulContentKind.TEXT,
                    style=(
                        "font-size:24pt;"
                        "font-weight:bold;"
                        "text-align:center;"
                    ),
                )
            ],
        )
    ],
)

title = document.pages[0].boxes[0]
print(title.page, title.left_mm, title.top_mm)
```

A box created inside `SoulPage` inherits that page number unless its own
`page` argument is explicitly set.

## 4. Control stacking with `z_index`

```python
from souldoc import SoulRecord, document_from_records

records = [
    SoulRecord(
        source_id="foreground",
        geometry={
            "portrait": {
                "page": 1,
                "left_mm": 20,
                "top_mm": 20,
                "width_mm": 100,
                "height_mm": 30,
                "z_index": 10,
            }
        },
        value="Foreground",
    ),
    SoulRecord(
        source_id="background",
        geometry={
            "portrait": {
                "page": 1,
                "left_mm": 15,
                "top_mm": 15,
                "width_mm": 110,
                "height_mm": 40,
                "z_index": 0,
            }
        },
        value="Background",
    ),
]

document = document_from_records(
    records,
    name="Layered document",
    orientation="portrait",
)

print([box.id for box in document.pages[0].boxes])
```

SoulDoc orders pages by page number, boxes by `z_index`, and boxes with the
same `z_index` by `source_id`.

## 5. Build a document from dictionaries

Creating `SoulRecord` objects manually is optional:

```python
from souldoc import document_from_dicts

document = document_from_dicts(
    [
        {
            "id": "customer",
            "name": {
                "portrait": {
                    "page": 1,
                    "left_mm": 20,
                    "top_mm": 30,
                    "width_mm": 80,
                    "height_mm": 12,
                }
            },
            "data": "Original value",
            "value": "Calculated value",
            "style": "font-weight:bold",
        }
    ],
    name="Imported document",
)

box = document.pages[0].boxes[0]
print(box.id)
print(box.content)
```

When `value` is not empty, it takes precedence over `data`.

## 6. Set explicit content

The `content` field has the highest priority:

```python
from souldoc import SoulRecord, document_from_records

record = SoulRecord(
    source_id="status",
    geometry={
        "portrait": {
            "page": 1,
            "left_mm": 15,
            "top_mm": 15,
            "width_mm": 60,
            "height_mm": 10,
        }
    },
    data="Source",
    value="Calculated",
    content="Final",
)

document = document_from_records([record])
print(document.pages[0].boxes[0].content)
```

Expected output:

```text
Final
```

Content selection follows this order:

```text
content -> value -> data
```

## 7. Provide portrait and landscape geometry

One source record may contain coordinates for both orientations:

```python
from souldoc import document_from_dicts

records = [
    {
        "id": "title",
        "name": {
            "portrait": {
                "page": 1,
                "left_mm": 20,
                "top_mm": 20,
                "width_mm": 170,
                "height_mm": 15,
            },
            "landscape": {
                "page": 1,
                "left_mm": 30,
                "top_mm": 10,
                "width_mm": 237,
                "height_mm": 15,
            },
        },
        "value": "Report",
    }
]

portrait = document_from_dicts(records, orientation="portrait")
landscape = document_from_dicts(records, orientation="landscape")

portrait_box = portrait.pages[0].boxes[0]
landscape_box = landscape.pages[0].boxes[0]

print(portrait_box.left_mm, portrait_box.width_mm)
print(landscape_box.left_mm, landscape_box.width_mm)
```

If geometry for the selected orientation is absent, SoulDoc falls back to
`portrait`, then to flat geometry stored directly in the `name` mapping.

## 8. Detect content kinds

```python
from souldoc import detect_content_kind

examples = [
    "Plain text",
    "<b>Formatted text</b>",
    "<table><tr><td>A</td></tr></table>",
    '<img src="resource:logo">',
    "$x^2 + y^2$",
]

for value in examples:
    print(detect_content_kind(value).value)
```

Expected output:

```text
text
html
table
image
math
```

## 9. Preserve whitespace and Unicode

SoulDoc does not normalize text content:

```python
from souldoc import SoulRecord, document_from_records

text = "  first line\n    indented line\nthird line"

document = document_from_records(
    [
        SoulRecord(
            source_id="multiline",
            geometry={
                "portrait": {
                    "page": 1,
                    "left_mm": 10,
                    "top_mm": 10,
                    "width_mm": 100,
                    "height_mm": 40,
                }
            },
            content=text,
        )
    ]
)

assert document.pages[0].boxes[0].content == text
```

Each renderer is responsible for preserving meaningful spaces and line breaks
in its native output.

## 10. Store metadata and unknown source fields

Metadata is available to editors, integrations, and specialized renderers:

```python
from souldoc import SoulRecord, document_from_dicts, document_from_records

document = document_from_records(
    [
        SoulRecord(
            source_id="total",
            geometry={
                "portrait": {
                    "page": 1,
                    "left_mm": 130,
                    "top_mm": 240,
                    "width_mm": 60,
                    "height_mm": 12,
                }
            },
            value="$1,250.00",
            metadata={
                "role": "grand-total",
                "currency": "USD",
            },
        )
    ]
)

print(document.pages[0].boxes[0].metadata["role"])
```

Unknown dictionary fields are preserved under `metadata["source_extra"]`:

```python
document = document_from_dicts(
    [
        {
            "id": "custom",
            "name": {
                "page": 1,
                "left_mm": 10,
                "top_mm": 10,
                "width_mm": 50,
                "height_mm": 10,
            },
            "value": "Value",
            "application_flag": True,
        }
    ]
)

metadata = document.pages[0].boxes[0].metadata
assert metadata["source_extra"]["application_flag"] is True
```

## 11. Register binary resources

```python
from souldoc import SoulRecord, SoulResource, document_from_records

resources = {
    "company-logo": SoulResource(
        id="company-logo",
        mime_type="image/png",
        source="data:image/png;base64,...",
        filename="logo.png",
    )
}

document = document_from_records(
    [
        SoulRecord(
            source_id="logo",
            geometry={
                "portrait": {
                    "page": 1,
                    "left_mm": 15,
                    "top_mm": 10,
                    "width_mm": 35,
                    "height_mm": 15,
                }
            },
            resource_id="company-logo",
        )
    ],
    resources=resources,
)

logo = document.resources["company-logo"]
print(logo.mime_type)
```

`source` may contain a data URL, local path, remote identifier, or any
application-defined locator. SoulDoc Core does not open files or perform
network requests. The renderer or application-provided resource resolver
interprets the value.

When box content is empty, `resource_id` automatically selects the image
content kind. An `<img src="resource:...">` fragment is not required.

## 12. Serialize and restore JSON

```python
from souldoc import dumps, loads

json_text = dumps(document)
restored = loads(json_text)

assert restored.name == document.name
assert restored.pages[0].boxes[0].resource_id == "company-logo"
```

SoulDoc emits a versioned envelope containing the schema name, schema version,
producer information, and document.

Unicode is written without ASCII escaping:

```python
from souldoc import SoulRecord, document_from_records, dumps

document = document_from_records(
    [
        SoulRecord(
            source_id="greeting",
            geometry={
                "page": 1,
                "left_mm": 10,
                "top_mm": 10,
                "width_mm": 80,
                "height_mm": 10,
            },
            content="Hello, world! 🌍",
        )
    ]
)

assert "🌍" in dumps(document)
```

Dictionary helpers are also available:

```python
from souldoc import document_from_dict, document_to_dict

payload = document_to_dict(document)
restored = document_from_dict(payload)
```

## 13. Use strict validation

Strict mode rejects invalid input:

```python
from souldoc import (
    SoulDocValidationError,
    SoulRecord,
    document_from_records,
)

invalid = SoulRecord(
    source_id="invalid",
    geometry={
        "portrait": {
            "page": 0,
            "left_mm": 10,
            "top_mm": 10,
            "width_mm": -5,
            "height_mm": 10,
        }
    },
    value="Invalid",
)

try:
    document_from_records([invalid], strict=True)
except SoulDocValidationError as error:
    print(error)
```

Strict mode validates orientation, numeric types, finite coordinates, positive
dimensions, page numbers, resource identifiers, resource references, and
document page numbering.

## 14. Import legacy data in compatibility mode

`strict=False` is the default and is intended for legacy or external input:

```python
from souldoc import SoulRecord, document_from_records

document = document_from_records(
    [
        SoulRecord(
            source_id="legacy",
            geometry={
                "portrait": {
                    "page": "1",
                    "left_mm": "10.5",
                    "top_mm": "20",
                    "width_mm": "80",
                    "height_mm": "12",
                }
            },
            value="Legacy value",
        )
    ],
    strict=False,
)

box = document.pages[0].boxes[0]
print(box.left_mm, box.width_mm)
```

Malformed geometry JSON is skipped in compatibility mode instead of stopping
the entire import.

Validate an object that was constructed directly:

```python
from souldoc import validate_document

errors = validate_document(document, strict=False)
for error in errors:
    print(error)

validate_document(document, strict=True)
```

## 15. Handle future schema versions

SoulDoc rejects unknown future JSON schema versions by default:

```python
from souldoc import SoulDocSchemaError, loads

future_json = """
{
  "schema": "souldoc",
  "schema_version": 999,
  "document": {
    "name": "Future document",
    "orientation": "portrait",
    "pages": []
  }
}
"""

try:
    loads(future_json)
except SoulDocSchemaError as error:
    print(error)
```

Compatibility mode is useful for exploratory reads:

```python
document = loads(future_json, strict=False)
print(document.schema_version)
```

## 16. Adapt an ORM or application model

SoulDoc has no ORM dependency. Convert application objects to `SoulRecord`:

```python
from souldoc import SoulRecord, document_from_records


def record_from_model(item):
    return SoulRecord(
        source_id=item.id,
        geometry=item.geometry,
        data=item.source_text,
        value=item.calculated_text,
        style=item.css,
        metadata={"application_type": item.type},
        resource_id=item.resource_id,
    )


records = [record_from_model(item) for item in database_items]
document = document_from_records(records, name="Imported document")
```

The application performs database queries. SoulDoc receives ordinary Python
data only.

## 17. Export editable DOCX

Install the optional renderer:

```bash
pip install "souldoc[docx]"
```

Render a positioned or layout document:

```python
from pathlib import Path
from souldoc.docx import render_docx

docx_bytes = render_docx(document)
Path("document.docx").write_bytes(docx_bytes)
```

Positioned text blocks become Word text boxes. Text, hyperlinks, and nested
tables remain editable.

Provide a controlled resource loader when images are not embedded as data
URLs:

```python
from souldoc.docx import render_docx


def resolve_resource(source):
    if source == "assets/company-logo":
        return image_bytes, "image/png"
    return None


docx_bytes = render_docx(
    document,
    resource_resolver=resolve_resource,
)
```

A resolver may return `str`, `bytes`, `(bytes, mime_type)`, or `None`.

## 18. Convert TenTags to semantic HTML

The parser is part of the dependency-free core:

```python
from souldoc import parse_tentags, tentags_to_html

source = (
    "TABLE("
    "scale(A1=2,3;C1=1,2)"
    "data(A1=<b>Name</b>;B1=Amount;C1=Status;"
    "A2=<cm>Service</cm>;B2=100;C2=Ready)"
    ")"
)

table = parse_tentags(source)
html = tentags_to_html(source)

print(table.row_scales)
print(table.column_scales)
print(html)
```

`parse_tentags()` returns a neutral `TenTagsTable`; `tentags_to_html()` returns
semantic HTML understood by every SoulDoc renderer.

TenTags represents physical row and column merges with tags on each
participating cell:

```python
source = (
    "data("
    "A1=<cm><rm>Combined</rm></cm>;"
    "B1=<cm><rm></rm></cm>;"
    "A2=<cm><rm></rm></cm>;"
    "B2=<cm><rm></rm></cm>"
    ")"
)

html = tentags_to_html(source)
```

The DOCX renderer converts the resulting `rowspan` and `colspan` into native,
editable Word merged cells.

TenTags source may also be combined with evaluated PyCells output:

```python
source = "data(<cm>Formula A</cm>,<cm>Formula B</cm>)"
evaluated = (
    "<table><tbody><tr>"
    "<td>Calculated A</td><td>Calculated B</td>"
    "</tr></tbody></table>"
)

html = tentags_to_html(
    source,
    evaluated_html=evaluated,
)
```

The source supplies structure and styling while evaluated HTML supplies cell
values.

## 19. Build an automatically laid-out document

```python
from souldoc import (
    Column,
    EdgeInsets,
    SoulLayoutDocument,
    SoulStyle,
    Table,
    Text,
    layout_document,
)

layout_source = SoulLayoutDocument(
    name="Automatic report",
    margin=EdgeInsets.symmetric(vertical=15, horizontal=18),
    header=[
        Text(
            "Company report",
            style=SoulStyle(font_size_pt=9, color="#666666"),
        )
    ],
    footer=[
        Text(
            "Confidential",
            style=SoulStyle(
                font_size_pt=8,
                color="#777777",
                text_align="center",
            ),
        )
    ],
    children=[
        Column(
            style=SoulStyle(gap_mm=5),
            children=[
                Text(
                    "Quarterly results",
                    style=SoulStyle(
                        font_size_pt=24,
                        font_weight="bold",
                    ),
                ),
                Text(
                    "Long text wraps to the available width and continues "
                    "on the next page when required."
                ),
                Table(
                    rows=[
                        ["Product", "Quantity", "Amount"],
                        ["Service A", "12", "$1,200"],
                        ["Service B", "8", "$960"],
                    ],
                    header_rows=1,
                    repeat_header=True,
                ),
            ],
        )
    ],
)

document = layout_document(layout_source)
print(len(document.pages))
```

The header and footer repeat on each generated page. A table can split between
pages and repeat its header rows.

Combine flow components with absolute positioning:

```python
from souldoc import Absolute, SoulLayoutDocument, Text

layout_source = SoulLayoutDocument(
    children=[
        Text("This paragraph participates in normal flow."),
        Absolute(
            child=Text("Watermark"),
            left_mm=70,
            top_mm=130,
            width_mm=70,
            height_mm=15,
            z_index=-1,
        ),
        Text("This paragraph follows the first flow paragraph."),
    ]
)
```

`Absolute` does not advance the flow cursor. `Row`, `Column`, and `Grid`
calculate child geometry, while ordinary `Text` and `Table` components may
continue on later pages.

Use a font-aware text measurer when exact installed-font metrics are required:

```python
from souldoc import PillowTextMeasurer, layout_document

measurer = PillowTextMeasurer(
    font_resolver=lambda style: "C:/Windows/Fonts/arial.ttf"
)
document = layout_document(layout_source, text_measurer=measurer)
```

The default `ApproximateTextMeasurer` is deterministic and has no external
dependencies.

## 20. Use unified rendering and export

```python
from souldoc import render_document, save_document

html = render_document(layout_source, "html")
svg = render_document(layout_source, "svg")
json_text = render_document(layout_source, "json")

save_document(layout_source, "report.html")
save_document(layout_source, "report.svg")
save_document(layout_source, "report.json")
save_document(layout_source, "report.docx")
save_document(layout_source, "report.xlsx")
save_document(layout_source, "report.pdf")
```

`save_document()` infers the format from the file extension. Install
`souldoc[docx]` for DOCX, `souldoc[xlsx]` for XLSX, and `souldoc[pdf]` plus
`playwright install chromium` for PDF.

Add a TenTags table directly to automatic layout:

```python
from souldoc import SoulLayoutDocument, tentags_table

layout_source = SoulLayoutDocument(
    name="TenTags report",
    children=[
        tentags_table(
            "data(A1=<b>Name</b>;B1=Amount;"
            "A2=Service A;B2=1200)"
        )
    ],
)
```

TenTags first becomes semantic HTML with `rowspan` and `colspan`. The same
table then flows through the HTML, SVG, PDF, DOCX, and XLSX renderers.

## Core responsibilities and exclusions

SoulDoc Core intentionally does not perform database queries, download images,
sanitize HTML, calculate formulas, or process spreadsheet editor notes.

DOCX, PDF, and XLSX are separate optional renderer modules. The PDF renderer
starts local Chromium only when `render_pdf()` is explicitly called and blocks
network requests.

The fields `note` and `note_style` do not belong to SoulDoc models and are
ignored when legacy records are imported.

## 21. Render a PyCells `MD(...)` record

PyCells normally supplies the raw formula in `data` and evaluated HTML in
`value`. SoulDoc preserves both and renders the evaluated HTML:

```python
from souldoc import document_from_dicts, save_document

document = document_from_dicts(
    [
        {
            "id": "markdown",
            "name": {
                "page": 1,
                "left_mm": 15,
                "top_mm": 15,
                "width_mm": 180,
                "height_mm": 80,
            },
            "data": r'=MD("# Hello\n## This is Markdown\n- List item")',
            "value": (
                "<h1>Hello</h1>"
                "<h2>This is Markdown</h2>"
                "<ul><li>List item</li></ul>"
            ),
        }
    ],
    name="Markdown document",
)

save_document(document, "markdown.html")
save_document(document, "markdown.pdf")
save_document(document, "markdown.docx")
save_document(document, "markdown.xlsx")
```

When `value` is missing, SoulDoc converts the formula itself:

```python
from souldoc import markdown_formula_to_html

html = markdown_formula_to_html(
    r'=MD("# Hello\n## This is Markdown\n- List item")'
)

print(html)
```

Expected output:

```html
<h1>Hello</h1>
<h2>This is Markdown</h2>
<ul>
<li>List item</li>
</ul>
```

The canonical PyCells line-break escape is `\n`. SoulDoc also accepts `/n`
between Markdown blocks for compatibility with manually entered records.

Use an explicit content kind when Markdown is supplied without `MD(...)`:

```python
from souldoc import (
    Markdown,
    SoulContentKind,
    SoulLayoutDocument,
    SoulRecord,
    document_from_records,
)

record_document = document_from_records(
    [
        SoulRecord(
            source_id="markdown",
            geometry={
                "page": 1,
                "left_mm": 15,
                "top_mm": 15,
                "width_mm": 180,
                "height_mm": 80,
            },
            content="# Explicit Markdown\n\n- First\n- Second",
            content_kind=SoulContentKind.MARKDOWN,
        )
    ]
)

layout_document = SoulLayoutDocument(
    children=[
        Markdown("# Layout Markdown\n\nThis is **bold**.")
    ]
)
```

The Markdown source remains unchanged in `SoulDocument` and serialized JSON.
Renderers convert it when producing HTML, SVG, PDF, DOCX, or XLSX. An ordinary
`Text("# Plain text")` component is never interpreted as Markdown.
