# RichRender Engine — API Reference

**Version:** 0.6-dev
**Date:** March 2026
**Status:** Core engine complete, 79/79 tests passing

---

## Executive Summary

RichRender is a compact binary format for rich text, with a modular JavaScript engine that handles encoding, inflation (rendering to HTML), interactive editing, and format conversion.

### The Problem

Rich text on the web relies on HTML — a verbose, parser-heavy format designed for documents, not data. A bold red word costs 50+ bytes in HTML. Email clients, chat systems, IoT devices, and editors all pay this tax. TinyMCE (the dominant open-source rich text editor) ships 500KB+ minified just to manipulate it.

### The Solution

RichRender encodes the same bold red word in 5 bytes: one control character, two style-pip bytes, and the text. The format uses bitwise flags — bold is a single bit, color is a 4-bit index, font size is a 3-bit index. No parsing, no tree walking, no configuration files.

The engine is six JavaScript files totaling **20KB minified** (6KB gzipped). It provides:

- A universal inflator that renders any RichRender stream to semantic HTML
- An editor data model with cursor operations and a bitwise painting API
- Format converters between wire format and editor format
- Optional MDR (Markdown) import

### Performance

| Bundle | Minified | Gzipped |
|--------|----------|---------|
| Read-only viewer | 9.7KB | 3.7KB |
| Interactive editor engine | 15.9KB | 5.4KB |
| Full stack with MDR | 20KB | 5.9KB |

For comparison: TinyMCE is 500KB+ minified. CKEditor is 300KB+.

### Four Tiers, One Format

RichRender is one format at four depths. Each tier is a superset of the one below:

| Tier | What It Handles | Use Case |
|------|----------------|----------|
| **Bit-Wise** | Style pips only (bytes 1–3). Bold, italic, colors, sizes, headings. | AI chat, IoT, email, smart devices |
| **Lite-Wire** | Adds block/inline elements: paragraphs, lists, links, blockquotes. | TinyMCE-competitive editors |
| **Wire Format** | Full element tables, meta regions, preamble, custom palettes. | Complete document authoring |
| **Editor** | Per-character pip arrays for cursor-addressable editing. | Interactive editors |

A Bit-Wise stream is valid Lite-Wire. A Lite-Wire stream is valid Wire Format. One inflator reads all tiers.

---

## File Structure

```
richrender-core.js    Constants, element tables, stream builder, disassembler
rr-inflate.js         Universal inflator (any tier → Render-HTML)
rr-editor.js          Editor format: EditorDoc, painting API
rr-convert.js         Format converters (wire ↔ editor ↔ HTML)
mdr-to-rich.js        MDR Markdown → RichRender encoder
rich-to-html.js       Bit-Wise-only inflator (minimal/legacy)
```

**Cherry-pick by use case:**
- Read-only viewer: `core` + `rr-inflate`
- Interactive editor: `core` + `rr-inflate` + `rr-editor` + `rr-convert`
- MDR import: add `mdr-to-rich`

---

## API Reference

### richrender-core.js — `RR`

The shared foundation. All other modules depend on this.

#### Control Characters

| Constant | Value | Meaning |
|----------|-------|---------|
| `RR.SOH` | 0x01 | Block element OPEN |
| `RR.STX` | 0x02 | Style span OPEN |
| `RR.ETX` | 0x03 | Style span CLOSE |
| `RR.EOT` | 0x04 | Block element CLOSE |
| `RR.SO` | 0x0E | Inline element OPEN |
| `RR.SI` | 0x0F | Inline element CLOSE |
| `RR.META_OPEN` | 0xFE | Meta region OPEN |
| `RR.META_CLOSE` | 0xFF | Meta region CLOSE |
| `RR.META_ATTR` | 0x00 | Meta type: attributes |
| `RR.META_COMMENT` | 0x01 | Meta type: comment |

#### Style-Pip Byte One — Flags

| Constant | Value | Meaning |
|----------|-------|---------|
| `RR.PIP_NESTED` | 0x01 | Nested span (bit 0) |
| `RR.PIP_BOLD` | 0x02 | Bold (bit 1) |
| `RR.PIP_ITALIC` | 0x04 | Italic (bit 2) |
| `RR.PIP_STRIKETHROUGH` | 0x08 | Strikethrough (bit 3) |
| `RR.PIP_UNDERLINE` | 0x10 | Underline (bit 4) |
| `RR.PIP_VARIANT_MASK` | 0x60 | Variant field mask (bits 6–5) |
| `RR.PIP_SUPER` | 0x20 | Superscript (01) |
| `RR.PIP_SUB` | 0x40 | Subscript (10) |
| `RR.PIP_HIGHLIGHT` | 0x60 | Highlight (11) |
| `RR.PIP_CONTINUATION` | 0x80 | Byte two follows (bit 7) |
| `RR.PIP_STYLE_MASK` | 0x7E | All style bits (excluding nested/continuation) |

#### Style-Pip Byte Two — Color and Font

| Constant | Value | Meaning |
|----------|-------|---------|
| `RR.P2_CONTINUATION` | 0x80 | Byte three follows |
| `RR.P2_BG_MASK` | 0x60 | Background color mask (bits 6–5) |
| `RR.P2_BG_NONE` | 0x00 | No background |
| `RR.P2_BG_YELLOW` | 0x20 | Yellow highlight |
| `RR.P2_BG_GREEN` | 0x40 | Green highlight |
| `RR.P2_BG_PINK` | 0x60 | Pink highlight |
| `RR.P2_FG_MASK` | 0x1E | Foreground color mask (bits 4–1) |
| `RR.P2_FG_SHIFT` | 1 | Shift right to get color index 0–15 |
| `RR.P2_FONT_BIT` | 0x01 | 0=serif, 1=sans-serif |

#### Style-Pip Byte Three — Size and Extras

| Constant | Value | Meaning |
|----------|-------|---------|
| `RR.P3_CONTINUATION` | 0x80 | Byte four follows (preamble-defined) |
| `RR.P3_SIZE_MASK` | 0x70 | Font size mask (bits 6–4) |
| `RR.P3_SIZE_SHIFT` | 4 | Shift right to get size index 0–7 |
| `RR.P3_DOUBLE_UNDERLINE` | 0x04 | Double underline (bit 2) |
| `RR.P3_OVERLINE` | 0x02 | Overline (bit 1) |
| `RR.P3_SMALLCAPS` | 0x01 | Small-caps (bit 0) |

#### Default Palettes

```javascript
RR.DEFAULT_FG[0..15]    // Foreground colors: inherit, black, dark gray, red,
                        // maroon, orange, green, dark green, blue, navy,
                        // purple, teal, brown, light gray, white, gold

RR.DEFAULT_BG[0..3]     // Background colors: none, yellow, green, pink

RR.DEFAULT_SIZES[0..7]  // Font sizes: inherit, 2em, 1.5em, 1.17em, 1em,
                        // 0.85em, 0.75em, 0.6em
```

#### Element Tables

```javascript
RR.BLOCK.p          // 15 — paragraph
RR.BLOCK.h1         //  9 — through h6 (14)
RR.BLOCK.blockquote // 17
RR.BLOCK.ul         // 18
RR.BLOCK.ol         // 19
RR.BLOCK.li         // 20
RR.BLOCK.hr         // 40
// ... 43 block elements total (see BLOCK object)

RR.INLINE.code      //  1
RR.INLINE.a         // 15
// ... 26 inline elements total (see INLINE object)

RR.BLOCK_NAMES[idx]   // Reverse lookup: index → tag name
RR.INLINE_NAMES[idx]  // Reverse lookup: index → tag name
```

#### Heading Patterns

```javascript
RR.HEADING_PATTERNS  // { 1:'h1', 2:'h2', 3:'h3', 4:'h4' }
                     // Inflator matches: size index + bold flag → heading tag
```

#### RichStream — Byte Stream Builder

```javascript
var s = new RR.RichStream();

s.write(byte)               // Write a single byte (number) or Uint8Array
s.writeText(string)         // Write UTF-8 encoded text
s.openBlock(idx)            // SOH + block index
s.closeBlock()              // EOT
s.openInline(idx)           // SO + inline index
s.closeInline()             // SI
s.openStyle(pip)            // STX + pip byte
s.closeStyle(resumePip?)    // ETX + optional resuming pip
s.meta(type, content)       // META_OPEN + type + text + META_CLOSE
s.toBytes()                 // → Uint8Array
```

#### Debugging Utilities

```javascript
RR.describePip(b1, b2?, b3?)   // → "bold+italic+fg:3+sans" (human-readable)
RR.hexDump(bytes, perLine?)     // → hex dump string
RR.disassemble(bytes)           // → annotated disassembly with indentation
```

---

### rr-inflate.js — `rrInflate(bytes)`

Universal inflator. Reads any RichRender byte stream and produces Render-HTML.

```javascript
var html = rrInflate(bytes);   // Uint8Array → HTML string
```

**Handles all control characters:** STX/ETX (style spans), SOH/EOT (blocks), SO/SI (inlines), META (attributes, comments).

**Heading detection:** Size + bold pattern → `<h1>` through `<h4>`.

**Double underline:** Overrides single underline when both set.

**Highlight:** Byte-one variant → `<mark>` with default yellow. Byte-two bg overrides color.

**Round-trip classes:** Every output element carries `mdr-` prefixed classes:

| Class | Source |
|-------|--------|
| `mdr-b`, `mdr-i`, `mdr-u`, `mdr-s` | Byte one flags |
| `mdr-sup`, `mdr-sub`, `mdr-hl` | Byte one variant |
| `mdr-fg`, `mdr-bg`, `mdr-font` | Byte two |
| `mdr-sz`, `mdr-sc`, `mdr-ol`, `mdr-du` | Byte three |
| `mdr-set-h1` … `mdr-set-h4` | Heading presets |
| `mdr-p`, `mdr-ul`, `mdr-li`, etc. | Block elements |
| `mdr-code`, `mdr-a`, etc. | Inline elements |

**Meta attributes:** Parsed as `key=value` pairs (newline-separated). Emitted as HTML attributes.

---

### rr-editor.js — `RREditor.EditorDoc`

Per-character pip array for cursor-addressable editing. All style operations are bitwise math.

#### Creating a Document

```javascript
var doc = RREditor.EditorDoc.fromText('hello world');
var doc = new RREditor.EditorDoc(charArray);  // from existing char array
```

Each character is `{ ch: string, p1: number, p2: number, p3: number }`.

#### Cursor Operations

```javascript
doc.length()                    // → number of characters
doc.text(from?, to?)            // → plain text string
doc.charAt(pos)                 // → { ch, p1, p2, p3 } or null
doc.insertAt(pos, text, p1?, p2?, p3?)  // insert text with optional style
doc.deleteRange(from, to)       // delete characters in range
doc.slice(from, to)             // → new EditorDoc (deep copy)
```

#### Painting — Byte One Flags

```javascript
doc.applyFlag(from, to, flag)   // Set bits:   pip |= flag
doc.removeFlag(from, to, flag)  // Clear bits:  pip &= ~flag
doc.toggleFlag(from, to, flag)  // Toggle: if all set → remove, else apply
doc.hasFlag(pos, flag)          // → boolean
doc.rangeHasFlag(from, to, flag) // → boolean (all chars in range)
```

Flags: `RR.PIP_BOLD`, `RR.PIP_ITALIC`, `RR.PIP_UNDERLINE`, `RR.PIP_STRIKETHROUGH`

#### Painting — Byte One Variant

```javascript
doc.setVariant(from, to, variant)  // RR.PIP_SUPER, RR.PIP_SUB, RR.PIP_HIGHLIGHT
doc.clearVariant(from, to)         // Reset to normal
```

#### Painting — Byte Two (Color, Font)

```javascript
doc.setFgColor(from, to, index)  // 0–15 (see DEFAULT_FG)
doc.getFgColor(pos)              // → index
doc.setBgColor(from, to, index)  // 0–3 (see DEFAULT_BG)
doc.getBgColor(pos)              // → index
doc.setFont(from, to, sans)      // true=sans-serif, false=serif
```

#### Painting — Byte Three (Size, Extras)

```javascript
doc.setSize(from, to, index)     // 0–7 (see DEFAULT_SIZES)
doc.getSize(pos)                 // → index
doc.applyP3Flag(from, to, flag)  // RR.P3_SMALLCAPS, RR.P3_OVERLINE, RR.P3_DOUBLE_UNDERLINE
doc.removeP3Flag(from, to, flag)
doc.toggleP3Flag(from, to, flag)
```

#### Style Query

```javascript
doc.styleAt(pos)  // → { bold, italic, underline, strikethrough, variant,
                  //      fgColor, bgColor, sans, size, smallCaps,
                  //      overline, doubleUnder, p1, p2, p3 }
```

#### Continuation Auto-Sync

All byte-two and byte-three setters automatically maintain continuation bits. Setting a foreground color automatically sets `PIP_CONTINUATION` on byte one. Setting a font size automatically sets `P2_CONTINUATION` on byte two. Clearing all higher-byte content clears the continuation chain. The caller never manages continuation bits manually.

---

### rr-convert.js — `RRConvert`

Format converters between wire/Lite-Wire/Bit-Wise streams and the editor format.

```javascript
var doc = RRConvert.wireToEditor(bytes);   // Uint8Array → EditorDoc
var bytes = RRConvert.editorToWire(doc);   // EditorDoc → Uint8Array
var html = RRConvert.editorToHtml(doc);    // EditorDoc → HTML (shortcut)
```

**`wireToEditor`** expands a byte stream into per-character pip tuples. Style spans (STX/ETX) set the current pip state; text characters inherit it. Nested spans (PIP_NESTED) merge delta bits into the current state; resuming pips after ETX are consumed. Block markers become newline characters with metadata. Inline markers become zero-width entries with metadata.

**`editorToWire`** compresses an editor document into a byte stream. Detects pip transitions and emits optimal span structure:
- **Run-length:** Consecutive characters with identical pips share one span.
- **Nested spans:** When a style transition is additive (adds flags/colors without removing existing ones), emits a nested STX with only the delta bits and PIP_NESTED set. On revert, emits ETX with a resuming pip to restore the outer style.
- **Whitespace bridging:** Unstyled newlines and tabs inside a styled run keep the span open when the same style resumes after, avoiding redundant ETX/STX pairs.

**`editorToHtml`** is a convenience: `editorToWire` → `rrInflate`.

---

### mdr-to-rich.js — `mdrToRich(text)`

MDR (Markdown) to RichRender encoder. Currently handles byte-one style markers:

```javascript
var bytes = mdrToRich('This is **bold** and *italic*.');  // → Uint8Array
```

**Supported markers:**
| Marker | Style |
|--------|-------|
| `***text***` | Bold + Italic |
| `**text**` | Bold |
| `*text*` | Italic |
| `~~text~~` | Strikethrough |
| `__text__` | Underline |

Linear scanner — walks text left-to-right, toggles pip bits on marker boundaries. Handles nesting (italic inside bold), adjacent markers, and combined markers.

---

### rich-to-html.js — `richToHtml(bytes)`

Bit-Wise-only inflator. Handles STX/ETX style spans with all three pip bytes. Does NOT handle SOH/EOT blocks or SO/SI inlines.

**Use `rrInflate` instead** unless you need the smallest possible file for a Bit-Wise-only deployment. This file exists for size-constrained environments where block/inline support is unnecessary.

---

## Render-HTML Conventions

### Document Declaration

```html
<meta name="richrender" content="full" data-tier="bitwise|litewire|wire">
```

### Embedded Boundaries

```html
<div data-mdr="root" data-mdr-tier="bitwise">
  <!-- Clean Render-HTML, mdr- classes throughout -->
</div>
```

### Outside Boundaries

Three strategies for non-Render-HTML content (implementation guidance, not mandated):
1. **Ignore** — treat as opaque, no conversion
2. **Best-effort** — heuristic mapping to pips (lossy)
3. **Passthrough** — wrap in meta region, preserve for round-trip

---

## Tab Character

Tab (U+0009) is a normal text byte in the wire format — 0x09 sits outside the control character range. In the editor format it is stored as `{ ch: '\t', p1, p2, p3 }`.

**Render-HTML (TODO):** The inflator will emit `<span class="mdr-tab" style="display:inline-block;width:2em"></span>`. Not yet implemented in `rrInflate`. The `mdr-tab` class enables CSS customization. Default width is 2em, configurable via future config file.

**MDR round-trip:** No special syntax. The 0x09 byte survives encoding/decoding as plain text. The text editor is responsible for rendering; RichRender stores it faithfully.

---

## Editor Palette (Demo / Reference Implementation)

Interactive editors may maintain a **document-specific mutable palette** separate from the spec defaults. The demo editor (`richrender-demo.html`) implements this pattern.

### Concept

```
spec defaults:    [inherit, #000, #555, #CC0000, ...]   (immutable, 16 FG / 4 BG)
editor palette:   [  —,      —,    —,   #51653f, ...]   (mutable, sparse)
resolved:         [inherit, #000, #555, #51653f, ...]   (editor wins, default fallback)
```

`resolveFg(idx)` / `resolveBg(idx)` check the editor palette first, then fall back to `RR.DEFAULT_FG` / `RR.DEFAULT_BG`.

### Color Matching (paste pipeline)

When HTML is pasted, `getComputedStyle()` is read per text node and mapped to pip bytes:

| CSS Property | Pip Mapping |
|-------------|-------------|
| `font-weight ≥ 700` | `PIP_BOLD` |
| `font-style: italic` | `PIP_ITALIC` |
| `text-decoration: underline` | `PIP_UNDERLINE` |
| `text-decoration: line-through` | `PIP_STRIKETHROUGH` |
| `color` | Nearest FG palette match or new editor slot |
| `background-color` | Nearest BG palette match (near-white → transparent) |
| `font-size` | Nearest `DEFAULT_SIZES` match |
| `font-family` | Serif/sans detection → `P2_FONT_BIT` |

Color matching uses Euclidean RGB distance with:
- **Threshold:** 500 (below = match, above = claim new slot)
- **Chromatic guard:** Saturated colors (max−min channel spread > 30) cannot match achromatic colors (grays, black, white)
- **Transparent filter:** `rgba` with alpha ≈ 0, `transparent`, `inherit` → bg index 0

### Scope

The editor palette is per-document, editor-only state. It is not encoded in the wire format. Documents saved as wire streams use spec default colors (or preamble overrides). Future preamble extensions may bridge this gap.

---

## Interactive Demo — richrender-demo.html

Reference implementation of an interactive RichRender editor. Includes all engine files and demonstrates the full editing pipeline.

### Architecture

- **Input model:** `beforeinput`-driven. The browser never modifies the DOM directly. All input events are intercepted, applied to the EditorDoc, and re-rendered.
- **Source of truth:** `EditorDoc` (from `rr-editor.js`). The DOM is a projection of the doc state.
- **Caret tracking:** `selectionchange` event syncs DOM selection to a JS `caret` object. A `_rendering` guard prevents overwrite during re-render.
- **Rendering:** `editorDocToEditableHtml()` builds HTML with inline styles from pip bytes. Cursor markers (zero-width spans) are injected to preserve caret position across re-renders.

### Panels

| Panel | Content |
|-------|---------|
| Editor | contenteditable area, `white-space: pre-wrap` |
| Toolbar | Style buttons, color swatches (2 rows), font/size selectors, paint/clear tools |
| Wire view | Hex dump of `editorToWire()` output |
| Disassembly | `RR.disassemble()` of wire bytes |
| Render-HTML | `rrInflate()` output with editor palette colors applied |
| Preview | Live HTML rendering of inflator output |

### Key Functions (demo-specific, not part of the engine)

| Function | Purpose |
|----------|---------|
| `editorDocToEditableHtml(doc, cursor, selEnd)` | EditorDoc → HTML with cursor markers |
| `pipToInlineStyle(p1, p2, p3)` | Pip bytes → CSS inline style string |
| `cssToPips(computedStyle)` | CSS computed style → `{p1, p2, p3}` |
| `pasteHtmlToChars(html)` | HTML clipboard → per-character pip array |
| `matchFgColor(cssColor)` | CSS color → palette index (with chromatic guard) |
| `matchBgColor(cssColor)` | CSS background → palette index (with near-white filter) |
| `applyEditorPalette(html)` | Post-process rrInflate output with editor palette colors |
| `resolveFg(idx)` / `resolveBg(idx)` | Resolve palette index through editor→default chain |

---

## Test Coverage

79 tests across five sections:

| Section | Tests | What It Covers |
|---------|-------|----------------|
| One-Byte | 14 | Style flags vs MDR reference |
| Two-Byte | 12 | Color, font, highlight, background |
| Three-Byte | 15 | Size, headings, small-caps, overline, double underline |
| Lite-Wire | 15 | Paragraphs, lists, links, blockquotes, meta, mixed content |
| Editor | 23 | EditorDoc ops, painting, continuation sync, round-trip |

All tests in `richrender-test.html`.
