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:

Performance

BundleMinifiedGzipped
Read-only viewer9.7KB3.7KB
Interactive editor engine15.9KB5.4KB
Full stack with MDR20KB5.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:

TierWhat It HandlesUse Case
Bit-WiseStyle pips only (bytes 1–3). Bold, italic, colors, sizes, headings.AI chat, IoT, email, smart devices
Lite-WireAdds block/inline elements: paragraphs, lists, links, blockquotes.TinyMCE-competitive editors
Wire FormatFull element tables, meta regions, preamble, custom palettes.Complete document authoring
EditorPer-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:


API Reference

richrender-core.js — RR

The shared foundation. All other modules depend on this.

Control Characters

ConstantValueMeaning
RR.SOH0x01Block element OPEN
RR.STX0x02Style span OPEN
RR.ETX0x03Style span CLOSE
RR.EOT0x04Block element CLOSE
RR.SO0x0EInline element OPEN
RR.SI0x0FInline element CLOSE
RR.META_OPEN0xFEMeta region OPEN
RR.META_CLOSE0xFFMeta region CLOSE
RR.META_ATTR0x00Meta type: attributes
RR.META_COMMENT0x01Meta type: comment

Style-Pip Byte One — Flags

ConstantValueMeaning
RR.PIP_NESTED0x01Nested span (bit 0)
RR.PIP_BOLD0x02Bold (bit 1)
RR.PIP_ITALIC0x04Italic (bit 2)
RR.PIP_STRIKETHROUGH0x08Strikethrough (bit 3)
RR.PIP_UNDERLINE0x10Underline (bit 4)
RR.PIP_VARIANT_MASK0x60Variant field mask (bits 6–5)
RR.PIP_SUPER0x20Superscript (01)
RR.PIP_SUB0x40Subscript (10)
RR.PIP_HIGHLIGHT0x60Highlight (11)
RR.PIP_CONTINUATION0x80Byte two follows (bit 7)
RR.PIP_STYLE_MASK0x7EAll style bits (excluding nested/continuation)

Style-Pip Byte Two — Color and Font

ConstantValueMeaning
RR.P2_CONTINUATION0x80Byte three follows
RR.P2_BG_MASK0x60Background color mask (bits 6–5)
RR.P2_BG_NONE0x00No background
RR.P2_BG_YELLOW0x20Yellow highlight
RR.P2_BG_GREEN0x40Green highlight
RR.P2_BG_PINK0x60Pink highlight
RR.P2_FG_MASK0x1EForeground color mask (bits 4–1)
RR.P2_FG_SHIFT1Shift right to get color index 0–15
RR.P2_FONT_BIT0x010=serif, 1=sans-serif

Style-Pip Byte Three — Size and Extras

ConstantValueMeaning
RR.P3_CONTINUATION0x80Byte four follows (preamble-defined)
RR.P3_SIZE_MASK0x70Font size mask (bits 6–4)
RR.P3_SIZE_SHIFT4Shift right to get size index 0–7
RR.P3_DOUBLE_UNDERLINE0x04Double underline (bit 2)
RR.P3_OVERLINE0x02Overline (bit 1)
RR.P3_SMALLCAPS0x01Small-caps (bit 0)

Default Palettes

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

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

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

RichStream — Byte Stream Builder

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

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.

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:

ClassSource
mdr-b, mdr-i, mdr-u, mdr-sByte one flags
mdr-sup, mdr-sub, mdr-hlByte one variant
mdr-fg, mdr-bg, mdr-fontByte two
mdr-sz, mdr-sc, mdr-ol, mdr-duByte three
mdr-set-h1 … mdr-set-h4Heading 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

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

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

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

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)

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)

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

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.

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:

editorToHtml is a convenience: editorToWire → rrInflate.


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

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

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

Supported markers:

MarkerStyle
***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

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

Embedded Boundaries

<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 PropertyPip Mapping
font-weight ≥ 700PIP_BOLD
font-style: italicPIP_ITALIC
text-decoration: underlinePIP_UNDERLINE
text-decoration: line-throughPIP_STRIKETHROUGH
colorNearest FG palette match or new editor slot
background-colorNearest BG palette match (near-white → transparent)
font-sizeNearest DEFAULT_SIZES match
font-familySerif/sans detection → P2_FONT_BIT

Color matching uses Euclidean RGB distance with:

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

Panels

PanelContent
Editorcontenteditable area, white-space: pre-wrap
ToolbarStyle buttons, color swatches (2 rows), font/size selectors, paint/clear tools
Wire viewHex dump of editorToWire() output
DisassemblyRR.disassemble() of wire bytes
Render-HTMLrrInflate() output with editor palette colors applied
PreviewLive HTML rendering of inflator output

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

FunctionPurpose
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:

SectionTestsWhat It Covers
One-Byte14Style flags vs MDR reference
Two-Byte12Color, font, highlight, background
Three-Byte15Size, headings, small-caps, overline, double underline
Lite-Wire15Paragraphs, lists, links, blockquotes, meta, mixed content
Editor23EditorDoc ops, painting, continuation sync, round-trip

All tests in richrender-test.html.