K2F Content Operations - Detailed Guide
This guide covers advanced content reading and writing operations for K2F files.
Content includes (long documents)
Split large documents by semantic subtree, not by rendered page. In content/root.json, a container's children may mix normal nodes and include stubs:
{ "include": "content/ch01.json" }
Each referenced file holds one SemanticNode (often a container wrapping a chapter). Nested includes are allowed. Rules:
- Path must be under
content/, end in.json, and not becontent/root.json - Every file under
content/must be referenced (directly or transitively) or pack fails withUNEXPECTED_PATH - Compile and
content_hashuse the expanded tree; include stubs exist only on disk - Do not scan or sort all JSON in
content/— only explicit includes are loaded
styles/theme.json stays a single file (optional styles/tokens.json only).
Node Structure Deep Dive
Required Fields
Every node MUST have:
id: String (hierarchical identifier)role: String (must exist in theme)content: Object (one of:text,image,container,table,table_reference,code_block,math)
Optional Fields
variant: String | null (must exist in theme for the role)modifiers: Array (default: [])layout: Object | null (stack,grid,overlay, orcolumns)break_inside:"auto"|"avoid"(default"auto")keep_with_next: boolean (defaultfalse)column_span:"none"|"all"(default"none")preserve_whitespace: boolean | null (forcode_block, must betrueor omitted)- List fields when
roleislist_item:list_id,depth,marker_type
Content Types
1. Text Node
{
"id": "root.text1",
"role": "body",
"content": {
"type": "text",
"value": "Text content here"
},
"modifiers": []
}
2. Image Node
{
"id": "root.image1",
"role": "image",
"content": {
"type": "image",
"value": {
"src": "assets/images/photo.png",
"width": 300000, // 300pt in fixed-point
"height": 200000 // 200pt in fixed-point
}
}
}
Important: Image sizes MUST be specified in fixed-point Pt (1/1000 pt units). The engine does not inspect image binaries.
3. Container Node
{
"id": "root.section1",
"role": "section",
"content": {
"type": "container",
"value": {
"children": [
// Array of SemanticNode objects
]
}
},
"layout": {
"type": "stack",
"direction": "vertical",
"gap": 12000
}
}
4. Code Block Node
{
"id": "root.snippet",
"role": "code_block",
"content": {
"type": "code_block",
"value": ["fn main() {", " println!(\"hi\");", "}"]
},
"modifiers": [
{ "range": [0, 2], "type": "syntax_highlight", "intent": "keyword" }
]
}
See semantic_code_blocks.md. role and content.type must both be code_block; no layout; only syntax_highlight modifiers.
5. Display Math Node
{
"id": "root.eq1",
"role": "math",
"break_inside": "avoid",
"content": {
"type": "math",
"value": "\\frac{a}{b}"
}
}
Inline math: put U+FFFC in a text node and add { "type": "math", "intent": "<tex>", "range": [i, i+1] }.
6. Table Reference Node
{
"id": "root.table1",
"role": "table",
"content": {
"type": "table_reference",
"value": {
"source": "assets/data/revenue.csv",
"view_mode": "summary",
"width": 500000, // 500pt
"height": 300000 // 300pt
}
}
}
Important: Table sizes MUST be specified. The engine does not load external datasets for table_reference.
7. Strict Table Node (Native)
{
"id": "root.table2",
"role": "table",
"content": {
"type": "table",
"value": {
"column_widths": [{ "fr": 1 }, { "pt": 200000 }],
"header_rows": 0,
"gap": 0,
"data": {
"type": "inline",
"rows": [
[
{ "id": "root.table2.r0c0", "role": "body", "content": { "type": "text", "value": "A" } },
{ "id": "root.table2.r0c1", "role": "body", "content": { "type": "text", "value": "B" } }
]
]
}
}
}
}
Asset-backed rows: "data": { "type": "asset", "source": "assets/data/rows.json" }.
Full attribute details: attribute_reference.md.
Reading Nodes by ID Path
Simple Path
"root" → root node
Lookup by full id
Each node's id is the full dotted path (e.g. "root.section1.heading"), not just the last segment.
Algorithm (depth-first search)
- Start at the root node (
content/root.json). - If
node.id === target_id, return the node. - If the node is a container, recurse into each child in
content.value.children. - If no match, the id is missing (
UNKNOWN_IDat SDK/CLI time).
Do not split the path and match segment names — two siblings could share a suffix under different parents.
Writing Operations
Adding a New Node
- Determine parent container
- Generate unique ID:
parent_id.child_name - Create node with required fields
- Append to parent's
content.value.children
Example:
// Parent container
{
"id": "root.section1",
"content": {
"type": "container",
"value": {
"children": [
// Add new node here
{
"id": "root.section1.new_paragraph",
"role": "body",
"content": {
"type": "text",
"value": "New paragraph"
},
"modifiers": []
}
]
}
}
}
Updating Existing Node
- Find node by ID path
- Update desired fields
- Preserve structure (don't change
content.type)
Deleting a Node
- Find parent container
- Remove node from
content.value.childrenarray
Modifiers
Modifiers apply styling to text ranges. Max 50 modifiers per node.
Structure:
{
"modifiers": [
{
"range": [0, 5], // UTF-8 byte range [start, end)
"type": "emphasis", // Modifier type
"intent": "critical" // Intent (maps to theme)
}
]
}
Rules:
range:[start, end)— start inclusive, end exclusive- Range is UTF-8 byte offsets on character boundaries (not character indices)
- Run
python scripts/modifier_range.py --text "…" --find "…"instead of counting characters - Modifiers are sorted by start index
- Overlapping modifiers resolved by theme precedence
Closed modifier type enum:
emphasis- Bold/italic (intent:strong,emphasis, …)link- Underlined accent (intent usually a URL)underline/strikethroughsubscript/superscriptmath/syntax_highlight
There is no font-size, font-family, or color modifier type.
Layout Hints
Stack Layout
{
"layout": {
"type": "stack",
"direction": "vertical", // or "horizontal"
"gap": 12000, // Gap between children (Pt)
"align_items": "start", // "start" | "center" | "end" | "stretch"
"justify_content": "start", // "start" | "center" | "end"
"width": 500000, // Optional fixed width (Pt)
"height": 300000 // Optional fixed height (Pt)
}
}
Grid Layout
{
"layout": {
"type": "grid",
"columns": [
{ "pt": 200000 }, // Fixed width column (200pt)
{ "fr": 1 }, // Fractional column
{ "fr": 2 } // 2x fractional column
],
"rows": [
{ "pt": 100000 }, // Fixed height row (100pt)
{ "fr": 1 } // Fractional row
],
"gap": 12000, // Gap between cells (Pt)
"cell_align": {
"x": "stretch", // "start" | "center" | "end" | "stretch"
"y": "stretch" // "start" | "center" | "end" | "stretch"
}
}
}
Grid rules:
- Columns/rows: Array of
{pt: integer}or{fr: integer} frunits distribute remaining space proportionally- Children placed in document order (no auto-placement)
Overlay Layout
{
"layout": {
"type": "overlay",
"width": 500000, // Optional fixed width (Pt)
"height": 300000 // Optional fixed height (Pt)
}
}
Overlay rules:
- Children stacked visually (first = back, last = front)
- No gap or alignment options
- Used for layering (e.g., glass overlay over image)
Columns Layout
{
"layout": {
"type": "columns",
"count": 2,
"gap": 12000
}
}
Children pack left→right across equal-width columns (count 2..=4). Place title/abstract outside the columns container for full-width content. Child nodes may set "column_span": "all" to span the full content width (figures/tables). See attribute_reference.md.
Manifest Structure
Package manifest.json (no root; semantic tree is content/root.json):
{
"title": "Document Title",
"canvas_mode": "paged",
"page_config": {
"width": 595000,
"height": 842000,
"margin": [72000, 72000, 72000, 72000]
},
"engine_version": "0.1.0",
"running_blocks": [
{
"position": "footer",
"node": {
"id": "doc.footer",
"role": "running_footer",
"content": { "type": "text", "value": "Confidential" }
}
}
]
}
v0.1 canvas_mode is "paged" only. At compile time the engine may merge root with these fields into an in-memory Manifest.
Best Practices
- ID Naming: Use descriptive, hierarchical IDs:
root.section1.heading1 - Preserve Structure: Don't change
content.typewhen updating - Fixed-Point Math: All sizes in 1/1000 pt units (integers only)
- Validate: Check against
schema/nodes.schema.jsonbefore writing - Modifier Limits: Max 50 modifiers per node
- Required Fields: Always include
id,role,content
Common Errors to Avoid
- Decimal Pt values: Use integers (12000 not 12.0)
- Missing required fields: Always include id, role, content
- Invalid role: Role must exist in theme.json
- Invalid variant: Variant must exist in theme for that role
- Modifier overflow: Max 50 modifiers per node
- Invalid range: Modifier ranges must be valid UTF-8 byte offsets on character boundaries