
Current Files:
Rich Render Core - JS:

/**
* RichRender Core
* Shared constants, element tables, pip utilities, and stream builder.
*/
var RR = (function () {
// ─── Control characters ──────────────────────────────────────────────────
var SOH = 0x01; // Block element OPEN
var STX = 0x02; // Style span OPEN
var ETX = 0x03; // Style span CLOSE
var EOT = 0x04; // Block element CLOSE
var SO = 0x0E; // Inline element OPEN
var SI = 0x0F; // Inline element CLOSE
var META_OPEN = 0xFE; // Meta region OPEN
var META_CLOSE = 0xFF; // Meta region CLOSE
// Meta region types
var META_ATTR = 0x00;
var META_COMMENT = 0x01;
// Set of all RichRender control bytes (for fast lookup)
var CONTROL = { 0x01:1, 0x02:1, 0x03:1, 0x04:1, 0x0E:1, 0x0F:1, 0xFE:1, 0xFF:1 };
// ═══════════════════════════════════════════════════════════════════════════
// STYLE-PIP BYTE ONE — flags (spec-defined, fixed)
// ═══════════════════════════════════════════════════════════════════════════
//
// bit 7 continuation 1 = byte two follows
// bit 6 variant-hi ┐ 00=normal, 01=super, 10=sub, 11=highlight
// bit 5 variant-lo ┘
// bit 4 underline
// bit 3 strikethrough
// bit 2 italic
// bit 1 bold
// bit 0 reserved must be 0
var PIP_BOLD = 0x02; // bit 1
var PIP_ITALIC = 0x04; // bit 2
var PIP_STRIKETHROUGH = 0x08; // bit 3
var PIP_UNDERLINE = 0x10; // bit 4
var PIP_VARIANT_MASK = 0x60; // bits 6–5
var PIP_SUPER = 0x20; // bits 6–5 = 01
var PIP_SUB = 0x40; // bits 6–5 = 10
var PIP_HIGHLIGHT = 0x60; // bits 6–5 = 11 (default yellow, byte two can refine)
var PIP_CONTINUATION = 0x80; // bit 7
// All style bits (excluding continuation)
var PIP_STYLE_MASK = PIP_BOLD | PIP_ITALIC | PIP_STRIKETHROUGH | PIP_UNDERLINE | PIP_VARIANT_MASK;
// Legacy alias
var PIP_VERT_MASK = PIP_VARIANT_MASK;
// ═══════════════════════════════════════════════════════════════════════════
// STYLE-PIP BYTE TWO — color and font (spec-defined, fixed)
// ═══════════════════════════════════════════════════════════════════════════
//
// bit 7 continuation 1 = byte three follows
// bit 6 bg-hi ┐ 00=none, 01=yellow, 10=green, 11=pink
// bit 5 bg-lo ┘
// bit 4 fg-3 (high) ┐
// bit 3 fg-2 │ foreground color index 0–15
// bit 2 fg-1 │
// bit 1 fg-0 (low) ┘
// bit 0 font 0=serif, 1=sans-serif
var P2_CONTINUATION = 0x80; // bit 7
var P2_BG_MASK = 0x60; // bits 6–5
var P2_BG_NONE = 0x00; // 00
var P2_BG_YELLOW = 0x20; // 01
var P2_BG_GREEN = 0x40; // 10
var P2_BG_PINK = 0x60; // 11
var P2_FG_MASK = 0x1E; // bits 4–1
var P2_FG_SHIFT = 1; // shift right 1 to get index 0–15
var P2_FONT_BIT = 0x01; // bit 0: 0=serif, 1=sans-serif
// ─── Default foreground palette (spec-defined, no preamble needed) ───────
var DEFAULT_FG = [
'', // 0 default/inherit
'#000000', // 1 black
'#555555', // 2 dark gray
'#CC0000', // 3 red
'#800000', // 4 maroon
'#DD6600', // 5 orange
'#008800', // 6 green
'#005500', // 7 dark green
'#0066CC', // 8 blue
'#003366', // 9 navy
'#7700AA', // 10 purple
'#008888', // 11 teal
'#885500', // 12 brown
'#AAAAAA', // 13 light gray
'#FFFFFF', // 14 white
'#CCAA00', // 15 gold
];
// ─── Default background palette (spec-defined) ──────────────────────────
var DEFAULT_BG = [
'', // 0 none/transparent
'#FFFF00', // 1 yellow highlight
'#90EE90', // 2 green highlight
'#FFB6C1', // 3 pink highlight
];
// ═══════════════════════════════════════════════════════════════════════════
// STYLE-PIP BYTE THREE — size and typographic extras (spec-defined, fixed)
// ═══════════════════════════════════════════════════════════════════════════
//
// bit 7 continuation 1 = byte four follows (preamble-defined)
// bit 6 size-hi ┐
// bit 5 size-mid │ font size index 0–7
// bit 4 size-lo ┘
// bit 3 reserved
// bit 2 double underline
// bit 1 overline
// bit 0 small-caps
var P3_CONTINUATION = 0x80; // bit 7
var P3_SIZE_MASK = 0x70; // bits 6–4
var P3_SIZE_SHIFT = 4; // shift right 4 to get index 0–7
var P3_RESERVED = 0x08; // bit 3
var P3_DOUBLE_UNDERLINE = 0x04; // bit 2
var P3_OVERLINE = 0x02; // bit 1
var P3_SMALLCAPS = 0x01; // bit 0
// ─── Default size table (spec-defined, no preamble needed) ──────────────
// Index 0 = default/inherit. Indices 1–7 = largest to smallest.
var DEFAULT_SIZES = [
'', // 0 default/inherit (1em)
'2em', // 1 ~h1
'1.5em', // 2 ~h2
'1.17em', // 3 ~h3
'1em', // 4 ~h4 (explicit body)
'0.85em', // 5 small / caption
'0.75em', // 6 x-small / fine print
'0.6em', // 7 xx-small / legal
];
// ─── Heading detection — three-byte pattern lookup ──────────────────────
// The format has no heading concept; these are recognized style combos.
// size index + bold → heading level (inflator emits <h1>–<h6>)
var HEADING_PATTERNS = {
1: 'h1', // size 1 + bold
2: 'h2', // size 2 + bold
3: 'h3', // size 3 + bold
4: 'h4', // size 4 + bold
};
// ─── Lite-Wire default element sets ───────────────────────────────────────
// These elements work out of the box with no preamble, like the color palette.
var LITEWIRE_BLOCKS = {
9:1, 10:1, 11:1, 12:1, 13:1, 14:1, // h1–h6
15:1, // p
17:1, // blockquote
18:1, // ul
19:1, // ol
20:1, // li
40:1, // hr
};
var LITEWIRE_INLINES = {
1:1, // code
15:1, // a
};
// Void (self-closing) block elements — no </tag> on EOT
var VOID_BLOCKS = { hr:1 };
// ─── Block element indices (Section 5.1) ─────────────────────────────────
var BLOCK = {
div:1, section:2, article:3, aside:4, nav:5,
header:6, footer:7, main:8,
h1:9, h2:10, h3:11, h4:12, h5:13, h6:14,
p:15, pre:16, blockquote:17,
ul:18, ol:19, li:20,
dl:21, dt:22, dd:23,
table:24, thead:25, tbody:26, tfoot:27, tr:28,
th:29, td:30, caption:31, colgroup:32, col:33,
figure:34, figcaption:35, details:36, summary:37,
fieldset:38, legend:39,
hr:40, form:41, dialog:42, pagebreak:43
};
// ─── Inline element indices (Section 5.2) ────────────────────────────────
var INLINE = {
code:1, kbd:2, mark:3, abbr:4, cite:5,
samp:6, 'var':7, ins:8, del:9, dfn:10,
time:11, span:12, em:13, strong:14, a:15,
q:16, bdi:17, bdo:18, data:19,
ruby:20, rt:21, rp:22, wbr:23,
output:24, meter:25, progress:26
};
// Reverse lookup
var BLOCK_NAMES = {};
for (var k in BLOCK) BLOCK_NAMES[BLOCK[k]] = k;
var INLINE_NAMES = {};
for (var k in INLINE) INLINE_NAMES[INLINE[k]] = k;
// ─── RichStream — byte stream builder ────────────────────────────────────
function RichStream() { this.bytes = []; }
RichStream.prototype.write = function (b) {
if (typeof b === 'number') this.bytes.push(b & 0xFF);
else if (b instanceof Uint8Array) for (var i = 0; i < b.length; i++) this.bytes.push(b[i]);
return this;
};
RichStream.prototype.writeText = function (str) {
var enc = new TextEncoder().encode(str);
for (var i = 0; i < enc.length; i++) this.bytes.push(enc[i]);
return this;
};
RichStream.prototype.openBlock = function (idx) { return this.write(SOH).write(idx); };
RichStream.prototype.closeBlock = function () { return this.write(EOT); };
RichStream.prototype.openInline = function (idx) { return this.write(SO).write(idx); };
RichStream.prototype.closeInline = function () { return this.write(SI); };
RichStream.prototype.openStyle = function (pip) { return this.write(STX).write(pip); };
RichStream.prototype.closeStyle = function (clearMask) {
this.write(ETX);
if (clearMask !== undefined) this.write(clearMask);
return this;
};
RichStream.prototype.meta = function (type, content) {
return this.write(META_OPEN).write(type).writeText(content).write(META_CLOSE);
};
RichStream.prototype.toBytes = function () { return new Uint8Array(this.bytes); };
// ─── Pip description (for debugging) ─────────────────────────────────────
function describePip(b, b2, b3) {
if (b === 0 && !b2 && !b3) return 'plain';
var parts = [];
if (b & PIP_BOLD) parts.push('bold');
if (b & PIP_ITALIC) parts.push('italic');
if (b & PIP_STRIKETHROUGH) parts.push('strike');
if (b & PIP_UNDERLINE) parts.push('underline');
var vert = b & PIP_VERT_MASK;
if (vert === PIP_SUPER) parts.push('super');
else if (vert === PIP_SUB) parts.push('sub');
else if (vert === PIP_HIGHLIGHT) parts.push('highlight');
if (b & PIP_CONTINUATION) parts.push('+cont');
if (b2) {
var fgIdx = (b2 & P2_FG_MASK) >> P2_FG_SHIFT;
if (fgIdx) parts.push('fg:' + fgIdx);
var bgIdx = (b2 & P2_BG_MASK) >> 5;
if (bgIdx) parts.push('bg:' + ['','yellow','green','pink'][bgIdx]);
if (b2 & P2_FONT_BIT) parts.push('sans');
if (b2 & P2_CONTINUATION) parts.push('+cont2');
}
if (b3) {
var sizeIdx = (b3 & P3_SIZE_MASK) >> P3_SIZE_SHIFT;
if (sizeIdx) parts.push('size:' + sizeIdx);
if (b3 & P3_SMALLCAPS) parts.push('small-caps');
if (b3 & P3_OVERLINE) parts.push('overline');
if (b3 & P3_DOUBLE_UNDERLINE) parts.push('dbl-underline');
if (b3 & P3_CONTINUATION) parts.push('+cont3');
}
return parts.join('+') || 'plain';
}
// ─── Hex dump ────────────────────────────────────────────────────────────
function hexDump(bytes, perLine) {
perLine = perLine || 16;
var lines = [];
for (var i = 0; i < bytes.length; i += perLine) {
var hex = [], ascii = '';
for (var j = 0; j < perLine; j++) {
if (i + j < bytes.length) {
var b = bytes[i + j];
hex.push(b.toString(16).padStart(2, '0'));
ascii += (b >= 0x20 && b < 0x7F) ? String.fromCharCode(b) : '.';
} else { hex.push(' '); ascii += ' '; }
}
lines.push(i.toString(16).padStart(6, '0') + ' ' +
hex.slice(0,8).join(' ') + ' ' + hex.slice(8).join(' ') + ' |' + ascii + '|');
}
return lines.join('\n');
}
// ─── Disassembler — annotated view of a byte stream ──────────────────────
function disassemble(bytes) {
var out = [], pos = 0, indent = 0;
var decoder = new TextDecoder('utf-8', { fatal: false });
function pad() { return ' '.repeat(indent); }
function hex(b) { return '0x' + b.toString(16).padStart(2, '0'); }
while (pos < bytes.length) {
var b = bytes[pos];
if (b === SOH) {
var idx = bytes[++pos];
out.push(pad() + 'SOH [' + (BLOCK_NAMES[idx] || idx) + ']');
indent++;
pos++;
} else if (b === EOT) {
indent = Math.max(0, indent - 1);
out.push(pad() + 'EOT');
pos++;
} else if (b === STX) {
var pip = bytes[++pos]; pos++;
var pip2 = 0, pip3 = 0;
if (pip & PIP_CONTINUATION) { pip2 = bytes[pos++]; }
if (pip2 & P2_CONTINUATION) { pip3 = bytes[pos++]; }
var hexStr = hex(pip) + (pip2 ? ' ' + hex(pip2) : '') + (pip3 ? ' ' + hex(pip3) : '');
out.push(pad() + 'STX [' + describePip(pip, pip2, pip3) + ' ' + hexStr + ']');
indent++;
} else if (b === ETX) {
indent = Math.max(0, indent - 1);
pos++; // consume ETX
// Read clear mask pip (always follows ETX)
if (pos < bytes.length) {
var cPip = bytes[pos++];
var cPip2 = 0, cPip3 = 0;
if ((cPip & PIP_CONTINUATION) && pos < bytes.length) { cPip2 = bytes[pos++]; }
if ((cPip2 & P2_CONTINUATION) && pos < bytes.length) { cPip3 = bytes[pos++]; }
if (cPip === 0) {
out.push(pad() + 'ETX [clear all]');
} else {
var cHex = hex(cPip) + (cPip2 ? ' ' + hex(cPip2) : '') + (cPip3 ? ' ' + hex(cPip3) : '');
out.push(pad() + 'ETX [clear ' + describePip(cPip, cPip2, cPip3) + ' ' + cHex + ']');
}
} else {
out.push(pad() + 'ETX');
}
} else if (b === SO) {
var idx = bytes[++pos];
out.push(pad() + 'SO [' + (INLINE_NAMES[idx] || idx) + ']');
indent++;
pos++;
} else if (b === SI) {
indent = Math.max(0, indent - 1);
out.push(pad() + 'SI');
pos++;
} else if (b === META_OPEN) {
var type = bytes[++pos];
pos++;
var metaBytes = [];
while (pos < bytes.length && bytes[pos] !== META_CLOSE) {
metaBytes.push(bytes[pos++]);
}
var metaText = decoder.decode(new Uint8Array(metaBytes));
var typeName = type === META_ATTR ? 'attr' : type === META_COMMENT ? 'comment' : hex(type);
out.push(pad() + 'META [' + typeName + '] ' + JSON.stringify(metaText));
if (pos < bytes.length) pos++; // skip META_CLOSE
} else {
// Text content — accumulate until we hit a control byte
var textBytes = [];
while (pos < bytes.length && !CONTROL[bytes[pos]] && bytes[pos] >= 0x09) {
textBytes.push(bytes[pos++]);
}
if (textBytes.length > 0) {
var text = decoder.decode(new Uint8Array(textBytes));
out.push(pad() + text.replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r'));
} else {
// Unknown byte
out.push(pad() + '??? ' + hex(b));
pos++;
}
}
}
return out.join('\n');
}
// ─── Public API ──────────────────────────────────────────────────────────
return {
SOH: SOH, STX: STX, ETX: ETX, EOT: EOT, SO: SO, SI: SI,
META_OPEN: META_OPEN, META_CLOSE: META_CLOSE,
META_ATTR: META_ATTR, META_COMMENT: META_COMMENT,
CONTROL: CONTROL,
PIP_BOLD: PIP_BOLD, PIP_ITALIC: PIP_ITALIC,
PIP_STRIKETHROUGH: PIP_STRIKETHROUGH, PIP_UNDERLINE: PIP_UNDERLINE,
PIP_VARIANT_MASK: PIP_VARIANT_MASK,
PIP_VERT_MASK: PIP_VERT_MASK, PIP_SUPER: PIP_SUPER, PIP_SUB: PIP_SUB,
PIP_HIGHLIGHT: PIP_HIGHLIGHT, PIP_CONTINUATION: PIP_CONTINUATION,
PIP_STYLE_MASK: PIP_STYLE_MASK,
P2_CONTINUATION: P2_CONTINUATION,
P2_BG_MASK: P2_BG_MASK, P2_BG_NONE: P2_BG_NONE,
P2_BG_YELLOW: P2_BG_YELLOW, P2_BG_GREEN: P2_BG_GREEN, P2_BG_PINK: P2_BG_PINK,
P2_FG_MASK: P2_FG_MASK, P2_FG_SHIFT: P2_FG_SHIFT,
P2_FONT_BIT: P2_FONT_BIT,
DEFAULT_FG: DEFAULT_FG, DEFAULT_BG: DEFAULT_BG,
P3_CONTINUATION: P3_CONTINUATION,
P3_SIZE_MASK: P3_SIZE_MASK, P3_SIZE_SHIFT: P3_SIZE_SHIFT,
P3_DOUBLE_UNDERLINE: P3_DOUBLE_UNDERLINE,
P3_OVERLINE: P3_OVERLINE, P3_SMALLCAPS: P3_SMALLCAPS,
DEFAULT_SIZES: DEFAULT_SIZES, HEADING_PATTERNS: HEADING_PATTERNS,
LITEWIRE_BLOCKS: LITEWIRE_BLOCKS, LITEWIRE_INLINES: LITEWIRE_INLINES,
VOID_BLOCKS: VOID_BLOCKS,
BLOCK: BLOCK, INLINE: INLINE,
BLOCK_NAMES: BLOCK_NAMES, INLINE_NAMES: INLINE_NAMES,
RichStream: RichStream,
describePip: describePip, hexDump: hexDump, disassemble: disassemble
};
})();
if (typeof module !== 'undefined') module.exports = RR;
Rich Render Converter - JS:

/**
* RichRender Format Converters
*
* Wire/Lite-Wire/Bit-Wise ↔ Editor format conversions.
*
* wireToEditor(bytes) — expand byte stream into per-character pip array
* editorToWire(doc) — compress editor doc into byte stream (run-length)
* editorToInflate(doc) — shortcut: editor → wire → HTML via rrInflate
*/
var RRConvert = (function () {
var decoder = new TextDecoder('utf-8', { fatal: false });
// ═══════════════════════════════════════════════════════════════════════
// WIRE → EDITOR
// Walk byte stream, track current pip state, expand text into tuples.
// Block/inline frames become marker characters (newlines for blocks).
// ═══════════════════════════════════════════════════════════════════════
function wireToEditor(bytes) {
var pos = 0, len = bytes.length;
var chars = [];
var pip1 = 0, pip2 = 0, pip3 = 0;
var blockStack = [];
while (pos < len) {
var b = bytes[pos];
// ── Block open (SOH) ──
if (b === RR.SOH) {
pos++;
var idx = bytes[pos++];
var tag = RR.BLOCK_NAMES[idx] || 'div';
// Skip meta attributes for now — store as marker
if (pos < len && bytes[pos] === RR.META_OPEN) {
pos++; // META_OPEN
var metaType = bytes[pos++];
// Consume meta content
while (pos < len && bytes[pos] !== RR.META_CLOSE) pos++;
if (pos < len) pos++; // META_CLOSE
}
// Push block marker with tag info
chars.push({ ch: '\n', p1: 0, p2: 0, p3: 0, block: tag, blockIdx: idx, blockOpen: true });
blockStack.push(idx);
continue;
}
// ── Block close (EOT) ──
if (b === RR.EOT) {
pos++;
var idx = blockStack.pop();
// Don't add a marker for void blocks or at end
continue;
}
// ── Inline open (SO) ──
if (b === RR.SO) {
pos++;
var idx = bytes[pos++];
var tag = RR.INLINE_NAMES[idx] || 'span';
var attrs = null;
if (pos < len && bytes[pos] === RR.META_OPEN) {
pos++;
var metaType = bytes[pos++];
if (metaType === RR.META_ATTR) {
var metaBytes = [];
while (pos < len && bytes[pos] !== RR.META_CLOSE) metaBytes.push(bytes[pos++]);
if (pos < len) pos++;
attrs = decoder.decode(new Uint8Array(metaBytes));
} else {
while (pos < len && bytes[pos] !== RR.META_CLOSE) pos++;
if (pos < len) pos++;
}
}
// Store inline open as a zero-width marker
chars.push({ ch: '', p1: pip1, p2: pip2, p3: pip3, inline: tag, inlineIdx: idx, inlineOpen: true, attrs: attrs });
continue;
}
// ── Inline close (SI) ──
if (b === RR.SI) {
pos++;
chars.push({ ch: '', p1: pip1, p2: pip2, p3: pip3, inlineClose: true });
continue;
}
// ── Style open (STX) — sets full style ──
if (b === RR.STX) {
pos++;
var p1 = bytes[pos++];
var p2 = 0, p3 = 0;
if (p1 & RR.PIP_CONTINUATION) p2 = bytes[pos++];
if (p2 & RR.P2_CONTINUATION) p3 = bytes[pos++];
pip1 = p1 & RR.PIP_STYLE_MASK;
pip2 = p2 & ~RR.P2_CONTINUATION;
pip3 = p3 & ~RR.P3_CONTINUATION;
continue;
}
// ── Style close (ETX) — clear mask ──
if (b === RR.ETX) {
pos++;
// Read clear mask pip (always follows ETX)
if (pos < len) {
var cm1 = bytes[pos++];
var cm2 = 0, cm3 = 0;
if (cm1 & RR.PIP_CONTINUATION && pos < len) { cm2 = bytes[pos++]; }
if (cm2 & RR.P2_CONTINUATION && pos < len) { cm3 = bytes[pos++]; }
if (cm1 === 0) {
// ETX [0x00] = clear all
pip1 = 0; pip2 = 0; pip3 = 0;
} else {
pip1 &= ~(cm1 & RR.PIP_STYLE_MASK);
if (cm2) pip2 &= ~(cm2 & ~RR.P2_CONTINUATION);
if (cm3) pip3 &= ~(cm3 & ~RR.P3_CONTINUATION);
}
}
continue;
}
// ── Meta region (standalone) ──
if (b === RR.META_OPEN) {
pos++;
var metaType = bytes[pos++];
while (pos < len && bytes[pos] !== RR.META_CLOSE) pos++;
if (pos < len) pos++;
continue;
}
// ── Text content ──
var start = pos;
while (pos < len && !RR.CONTROL[bytes[pos]]) {
pos++;
}
if (pos > start) {
var text = decoder.decode(bytes.slice(start, pos));
for (var i = 0; i < text.length; i++) {
chars.push({ ch: text[i], p1: pip1, p2: pip2, p3: pip3 });
}
}
}
return new RREditor.EditorDoc(chars);
}
// ═══════════════════════════════════════════════════════════════════════
// EDITOR → WIRE
// Walk character array, detect pip transitions, emit STX/ETX frames.
// Block/inline markers emit SOH/EOT and SO/SI.
// ═══════════════════════════════════════════════════════════════════════
function editorToWire(doc) {
var s = new RR.RichStream();
var chars = doc.chars;
// ── Style stack for nested spans ──
// Each entry: { p1, p2, p3 } — the cumulative style at that nesting depth
var styleStack = [];
function curStyle() {
return styleStack.length > 0 ? styleStack[styleStack.length - 1] : { p1: 0, p2: 0, p3: 0 };
}
function styleEqual(a, b) {
return a.p1 === b.p1 && a.p2 === b.p2 && a.p3 === b.p3;
}
function writePipBytes(p1, p2, p3) {
var outP1 = p1;
if (p2) outP1 |= RR.PIP_CONTINUATION;
s.write(outP1);
if (p2) {
var outP2 = p2;
if (p3) outP2 |= RR.P2_CONTINUATION;
s.write(outP2);
if (p3) s.write(p3);
}
}
function closeAll() {
while (styleStack.length > 0) {
var closing = styleStack.pop();
s.write(RR.ETX);
if (styleStack.length > 0) {
// Clear mask = bits to remove (difference between closing and outer)
var outer = styleStack[styleStack.length - 1];
var cm1 = (closing.p1 & RR.PIP_STYLE_MASK) & ~(outer.p1 & RR.PIP_STYLE_MASK);
var cm2 = closing.p2 & ~outer.p2;
var cm3 = closing.p3 & ~outer.p3;
writePipBytes(cm1, cm2, cm3);
} else {
// Closing last span — ETX [0x00] clear all
s.write(0x00);
}
}
}
// Determine if target style is "additive" over current (pure superset
// on byte-one flags, and any byte-two/three fields that change are
// additions, not replacements of existing non-zero fields).
function isAdditive(cur, tgt) {
var curFlags = cur.p1 & RR.PIP_STYLE_MASK;
var tgtFlags = tgt.p1 & RR.PIP_STYLE_MASK;
// All current flags must still be set in target
if ((tgtFlags & curFlags) !== curFlags) return false;
// Must actually add something
if (tgtFlags === curFlags && tgt.p2 === cur.p2 && tgt.p3 === cur.p3) return false;
// Byte two: if current has fg/bg/font, target must keep them
if (cur.p2) {
var curFg = cur.p2 & RR.P2_FG_MASK, tgtFg = tgt.p2 & RR.P2_FG_MASK;
var curBg = cur.p2 & RR.P2_BG_MASK, tgtBg = tgt.p2 & RR.P2_BG_MASK;
var curFont = cur.p2 & RR.P2_FONT_BIT, tgtFont = tgt.p2 & RR.P2_FONT_BIT;
if (curFg && tgtFg !== curFg) return false;
if (curBg && tgtBg !== curBg) return false;
if (curFont && tgtFont !== curFont) return false;
}
// Byte three: if current has size/extras, target must keep them
if (cur.p3) {
var curSz = cur.p3 & RR.P3_SIZE_MASK, tgtSz = tgt.p3 & RR.P3_SIZE_MASK;
if (curSz && tgtSz !== curSz) return false;
var curExtras = cur.p3 & 0x07, tgtExtras = tgt.p3 & 0x07;
if ((tgtExtras & curExtras) !== curExtras) return false;
}
return true;
}
// Check if target is a subset — some outer stack entry matches it
function findRevert(tgt) {
for (var k = styleStack.length - 2; k >= 0; k--) {
if (styleEqual(styleStack[k], tgt)) return k;
}
return -1;
}
function transitionTo(tgt) {
var cur = curStyle();
if (styleEqual(cur, tgt)) return; // no change
var tgtStyle = (tgt.p1 & RR.PIP_STYLE_MASK) | tgt.p2 | tgt.p3;
// ── Revert to unstyled ──
if (tgtStyle === 0) {
closeAll();
return;
}
// ── Currently unstyled → open flat ──
if (styleStack.length === 0) {
s.write(RR.STX);
writePipBytes(tgt.p1 & RR.PIP_STYLE_MASK, tgt.p2, tgt.p3);
styleStack.push({ p1: tgt.p1, p2: tgt.p2, p3: tgt.p3 });
return;
}
// ── Additive: nest inside current ──
if (isAdditive(cur, tgt)) {
s.write(RR.STX);
writePipBytes(tgt.p1 & RR.PIP_STYLE_MASK, tgt.p2, tgt.p3);
styleStack.push({ p1: tgt.p1, p2: tgt.p2, p3: tgt.p3 });
return;
}
// ── Revert: target matches an outer stack entry ──
var revertIdx = findRevert(tgt);
if (revertIdx >= 0) {
// Close spans down to that level, each ETX with clear mask
while (styleStack.length > revertIdx + 1) {
var closing = styleStack.pop();
s.write(RR.ETX);
if (styleStack.length > 0) {
var outer = styleStack[styleStack.length - 1];
var cm1 = (closing.p1 & RR.PIP_STYLE_MASK) & ~(outer.p1 & RR.PIP_STYLE_MASK);
var cm2 = closing.p2 & ~outer.p2;
var cm3 = closing.p3 & ~outer.p3;
writePipBytes(cm1, cm2, cm3);
} else {
s.write(0x00);
}
}
return;
}
// ── Otherwise: close all, open fresh ──
closeAll();
s.write(RR.STX);
writePipBytes(tgt.p1 & RR.PIP_STYLE_MASK, tgt.p2, tgt.p3);
styleStack.push({ p1: tgt.p1, p2: tgt.p2, p3: tgt.p3 });
}
// ── Look-ahead: does the current style resume after whitespace? ──
// Skips whitespace regardless of its own pip values —
// spaces/tabs/newlines render the same styled or unstyled.
function curStyleResumes(idx) {
var cur = curStyle();
var j = idx + 1;
while (j < chars.length) {
var cj = chars[j];
if (cj.blockOpen || cj.inlineOpen || cj.inlineClose) return false;
if (cj.ch !== ' ' && cj.ch !== '\n' && cj.ch !== '\t') break;
j++;
}
if (j >= chars.length) return false;
var cj = chars[j];
if (cj.blockOpen || cj.inlineOpen || cj.inlineClose) return false;
return cj.p1 === cur.p1 && cj.p2 === cur.p2 && cj.p3 === cur.p3;
}
function isWhitespace(ch) { return ch === ' ' || ch === '\n' || ch === '\t'; }
for (var i = 0; i < chars.length; i++) {
var c = chars[i];
// ── Block marker ──
if (c.blockOpen) {
closeAll();
s.openBlock(c.blockIdx);
continue;
}
if (c.block && !c.blockOpen) {
closeAll();
s.closeBlock();
continue;
}
// ── Inline marker ──
if (c.inlineOpen) {
s.openInline(c.inlineIdx);
if (c.attrs) {
s.meta(RR.META_ATTR, c.attrs);
}
continue;
}
if (c.inlineClose) {
s.closeInline();
continue;
}
// ── Block newline markers (no content) ──
if (c.ch === '\n' && c.block) {
continue;
}
// ── Style transition ──
var charStyle = (c.p1 & RR.PIP_STYLE_MASK) | c.p2 | c.p3;
if (charStyle !== 0) {
// Styled whitespace inside a span: if its style matches the current
// span or is unstyled, and the current style resumes, absorb it.
if (isWhitespace(c.ch) && styleStack.length > 0) {
var cur = curStyle();
var matchesCur = c.p1 === cur.p1 && c.p2 === cur.p2 && c.p3 === cur.p3;
if (matchesCur || curStyleResumes(i)) {
s.writeText(c.ch);
continue;
}
}
transitionTo({ p1: c.p1, p2: c.p2, p3: c.p3 });
s.writeText(c.ch);
} else if (styleStack.length > 0 && isWhitespace(c.ch)) {
// Unstyled whitespace inside a styled run: keep span open if same style resumes
if (curStyleResumes(i)) {
s.writeText(c.ch);
} else {
closeAll();
s.writeText(c.ch);
}
} else {
closeAll();
s.writeText(c.ch);
}
}
closeAll();
return s.toBytes();
}
// ═══════════════════════════════════════════════════════════════════════
// EDITOR → HTML (shortcut via inflate)
// ═══════════════════════════════════════════════════════════════════════
function editorToHtml(doc) {
return rrInflate(editorToWire(doc));
}
// ── Public API ─────────────────────────────────────────────────────────
return {
wireToEditor: wireToEditor,
editorToWire: editorToWire,
editorToHtml: editorToHtml
};
})();
if (typeof module !== 'undefined') module.exports = RRConvert;
Rich Render HTML Inflate - JS:

/**
* RichRender Universal Inflator
*
* Reads any RichRender byte stream — Bit-Wise, Lite-Wire, or Wire Format —
* and produces Render-HTML with mdr- classes for round-trip identification.
*
* Handles all control characters:
* STX/ETX — style spans (bytes 1–3: flags, color/font, size/extras)
* SOH/EOT — block elements (p, h1–h6, lists, blockquote, etc.)
* SO/SI — inline elements (code, a, etc.)
* META — attribute regions (href, etc.) and comments
*
* A Bit-Wise stream contains only STX/ETX — the block/inline handlers
* simply never fire. One inflator, all tiers.
*/
var rrInflate = (function () {
var decoder = new TextDecoder('utf-8', { fatal: false });
function escHtml(t) {
return t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
function escAttr(t) {
return t.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
}
// ── Pip reading ────────────────────────────────────────────────────────
function readPip(bytes, state) {
var p1 = bytes[state.pos++];
var p2 = 0, p3 = 0;
if (p1 & RR.PIP_CONTINUATION) { p2 = bytes[state.pos++]; }
if (p2 & RR.P2_CONTINUATION) { p3 = bytes[state.pos++]; }
return { p1: p1, p2: p2, p3: p3 };
}
// ── Heading detection ──────────────────────────────────────────────────
function detectHeading(pip, pip3) {
if (!(pip & RR.PIP_BOLD)) return null;
if (!pip3) return null;
var sizeIdx = (pip3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT;
return RR.HEADING_PATTERNS[sizeIdx] || null;
}
// ── Pip → HTML tags ────────────────────────────────────────────────────
function pipToTags(pip, pip2, pip3) {
var style = pip & RR.PIP_STYLE_MASK;
var opens = [], closes = [];
var vert = style & RR.PIP_VARIANT_MASK;
var hasDoubleUnderline = pip3 && (pip3 & RR.P3_DOUBLE_UNDERLINE);
// ── Heading pattern ──
var heading = detectHeading(pip, pip3);
if (heading) {
var hCss = [];
if (pip2) {
var fgIdx = (pip2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT;
if (fgIdx > 0 && RR.DEFAULT_FG[fgIdx]) hCss.push('color:' + RR.DEFAULT_FG[fgIdx]);
if (pip2 & RR.P2_FONT_BIT) hCss.push('font-family:sans-serif');
}
var hStyle = hCss.length > 0 ? ' style="' + hCss.join(';') + '"' : '';
opens.push('<' + heading + ' class="mdr-set-' + heading + '"' + hStyle + '>');
closes.unshift('</' + heading + '>');
if (style & RR.PIP_ITALIC) { opens.push('<em class="mdr-i">'); closes.unshift('</em>'); }
if (!hasDoubleUnderline && (style & RR.PIP_UNDERLINE)) { opens.push('<u class="mdr-u">'); closes.unshift('</u>'); }
if (style & RR.PIP_STRIKETHROUGH) { opens.push('<s class="mdr-s">'); closes.unshift('</s>'); }
if (pip3) {
if (pip3 & RR.P3_SMALLCAPS) { opens.push('<span class="mdr-sc" style="font-variant:small-caps">'); closes.unshift('</span>'); }
if (pip3 & RR.P3_OVERLINE) { opens.push('<span class="mdr-ol" style="text-decoration:overline">'); closes.unshift('</span>'); }
if (hasDoubleUnderline) { opens.push('<span class="mdr-du" style="text-decoration:underline double">'); closes.unshift('</span>'); }
}
return { open: opens.join(''), close: closes.join('') };
}
// ── Non-heading: byte two + three style span ──
var css = [], spanClasses = [];
if (pip2) {
var fgIdx = (pip2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT;
if (fgIdx > 0 && RR.DEFAULT_FG[fgIdx]) { css.push('color:' + RR.DEFAULT_FG[fgIdx]); spanClasses.push('mdr-fg'); }
if (vert !== RR.PIP_HIGHLIGHT && (pip2 & RR.P2_BG_MASK)) {
var bgIdx = (pip2 & RR.P2_BG_MASK) >> 5;
if (RR.DEFAULT_BG[bgIdx]) { css.push('background-color:' + RR.DEFAULT_BG[bgIdx]); spanClasses.push('mdr-bg'); }
}
if (pip2 & RR.P2_FONT_BIT) { css.push('font-family:sans-serif'); spanClasses.push('mdr-font'); }
}
if (pip3) {
var sizeIdx = (pip3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT;
if (sizeIdx > 0 && RR.DEFAULT_SIZES[sizeIdx]) { css.push('font-size:' + RR.DEFAULT_SIZES[sizeIdx]); spanClasses.push('mdr-sz'); }
}
if (css.length > 0) {
opens.push('<span class="' + spanClasses.join(' ') + '" style="' + css.join(';') + '">');
closes.unshift('</span>');
}
// Byte one: semantic tags
if (style & RR.PIP_BOLD) { opens.push('<strong class="mdr-b">'); closes.unshift('</strong>'); }
if (style & RR.PIP_ITALIC) { opens.push('<em class="mdr-i">'); closes.unshift('</em>'); }
if (hasDoubleUnderline) {
opens.push('<span class="mdr-du" style="text-decoration:underline double">'); closes.unshift('</span>');
} else if (style & RR.PIP_UNDERLINE) {
opens.push('<u class="mdr-u">'); closes.unshift('</u>');
}
if (style & RR.PIP_STRIKETHROUGH) { opens.push('<s class="mdr-s">'); closes.unshift('</s>'); }
if (vert === RR.PIP_SUPER) { opens.push('<sup class="mdr-sup">'); closes.unshift('</sup>'); }
else if (vert === RR.PIP_SUB) { opens.push('<sub class="mdr-sub">'); closes.unshift('</sub>'); }
else if (vert === RR.PIP_HIGHLIGHT) {
var hlBg = RR.DEFAULT_BG[1];
if (pip2 && (pip2 & RR.P2_BG_MASK)) {
var bgIdx = (pip2 & RR.P2_BG_MASK) >> 5;
hlBg = RR.DEFAULT_BG[bgIdx] || hlBg;
}
opens.push('<mark class="mdr-hl" style="background-color:' + hlBg + '">'); closes.unshift('</mark>');
}
// Byte three: typographic extras
if (pip3) {
if (pip3 & RR.P3_SMALLCAPS) { opens.push('<span class="mdr-sc" style="font-variant:small-caps">'); closes.unshift('</span>'); }
if (pip3 & RR.P3_OVERLINE) { opens.push('<span class="mdr-ol" style="text-decoration:overline">'); closes.unshift('</span>'); }
}
return { open: opens.join(''), close: closes.join('') };
}
// ── Meta region reader ─────────────────────────────────────────────────
function readMeta(bytes, state) {
var metaBytes = [];
while (state.pos < bytes.length && bytes[state.pos] !== RR.META_CLOSE) {
metaBytes.push(bytes[state.pos++]);
}
if (state.pos < bytes.length) state.pos++; // consume META_CLOSE
return decoder.decode(new Uint8Array(metaBytes));
}
// Parse "key=value\nkey2=value2" attribute string
function parseAttrs(str) {
var attrs = {};
var lines = str.split('\n');
for (var i = 0; i < lines.length; i++) {
var eq = lines[i].indexOf('=');
if (eq > 0) {
attrs[lines[i].substring(0, eq)] = lines[i].substring(eq + 1);
}
}
return attrs;
}
// Build HTML attribute string from parsed attrs
function attrsToHtml(attrObj) {
var s = '';
for (var k in attrObj) {
s += ' ' + escAttr(k) + '="' + escAttr(attrObj[k]) + '"';
}
return s;
}
// ── Main inflate function ──────────────────────────────────────────────
function inflate(bytes) {
var state = { pos: 0 };
var len = bytes.length;
var out = '';
var styleStack = []; // STX/ETX: { close, pip }
var blockStack = []; // SOH/EOT: tag name
var inlineStack = []; // SO/SI: tag name
while (state.pos < len) {
var b = bytes[state.pos];
// ── Block open (SOH) ──────────────────────────────────────────────
if (b === RR.SOH) {
state.pos++;
var idx = bytes[state.pos++];
var tag = RR.BLOCK_NAMES[idx] || 'div';
var attrStr = '';
// Check for meta attribute region immediately after index
if (state.pos < len && bytes[state.pos] === RR.META_OPEN) {
state.pos++; // consume META_OPEN
var metaType = bytes[state.pos++];
if (metaType === RR.META_ATTR) {
attrStr = attrsToHtml(parseAttrs(readMeta(bytes, state)));
} else {
readMeta(bytes, state); // consume and discard
}
}
out += '<' + tag + ' class="mdr-' + tag + '"' + attrStr + '>';
blockStack.push(tag);
continue;
}
// ── Block close (EOT) ─────────────────────────────────────────────
if (b === RR.EOT) {
state.pos++;
var tag = blockStack.pop();
if (tag && !RR.VOID_BLOCKS[tag]) {
out += '</' + tag + '>';
}
continue;
}
// ── Inline open (SO) ──────────────────────────────────────────────
if (b === RR.SO) {
state.pos++;
var idx = bytes[state.pos++];
var tag = RR.INLINE_NAMES[idx] || 'span';
var attrStr = '';
// Check for meta attribute region
if (state.pos < len && bytes[state.pos] === RR.META_OPEN) {
state.pos++;
var metaType = bytes[state.pos++];
if (metaType === RR.META_ATTR) {
attrStr = attrsToHtml(parseAttrs(readMeta(bytes, state)));
} else {
readMeta(bytes, state);
}
}
out += '<' + tag + ' class="mdr-' + tag + '"' + attrStr + '>';
inlineStack.push(tag);
continue;
}
// ── Inline close (SI) ─────────────────────────────────────────────
if (b === RR.SI) {
state.pos++;
var tag = inlineStack.pop();
if (tag) out += '</' + tag + '>';
continue;
}
// ── Style span open (STX) — full style ─────────────────────────────
if (b === RR.STX) {
state.pos++;
var pip = readPip(bytes, state);
var fullStyle = pip.p1 & RR.PIP_STYLE_MASK;
// Determine delta tags vs full tags based on enclosing style
var enclosingStyle = styleStack.length > 0 ? styleStack[styleStack.length - 1].pip : null;
var emitP1 = fullStyle;
// If nested inside another style, only emit the delta tags for byte one
if (enclosingStyle) {
emitP1 = fullStyle & ~(enclosingStyle.p1 & RR.PIP_STYLE_MASK);
}
var emitPip = emitP1 | (pip.p1 & RR.PIP_CONTINUATION);
// For byte two/three, emit only if different from enclosing
var emitP2 = pip.p2, emitP3 = pip.p3;
if (enclosingStyle) {
if (pip.p2 === enclosingStyle.p2) emitP2 = 0;
if (pip.p3 === enclosingStyle.p3) emitP3 = 0;
}
var tags = pipToTags(emitPip, emitP2, emitP3);
styleStack.push({ close: tags.close, pip: { p1: fullStyle, p2: pip.p2, p3: pip.p3 } });
out += tags.open;
continue;
}
// ── Style span close (ETX) — clear mask ──────────────────────────
if (b === RR.ETX) {
state.pos++;
var entry = styleStack.pop();
if (entry) out += entry.close;
// Read clear mask pip (always follows ETX)
if (state.pos < len) {
readPip(bytes, state); // consume clear mask (1–3 bytes)
}
continue;
}
// ── Standalone meta region ────────────────────────────────────────
if (b === RR.META_OPEN) {
state.pos++;
var metaType = bytes[state.pos++];
var content = readMeta(bytes, state);
if (metaType === RR.META_COMMENT) {
out += '<!-- ' + escHtml(content) + ' -->';
}
continue;
}
// ── Text content ──────────────────────────────────────────────────
var start = state.pos;
while (state.pos < len && !RR.CONTROL[bytes[state.pos]]) {
state.pos++;
}
if (state.pos > start) {
out += escHtml(decoder.decode(bytes.slice(start, state.pos)));
}
}
return out;
}
// ── Public API ─────────────────────────────────────────────────────────
return inflate;
})();
if (typeof module !== 'undefined') module.exports = { rrInflate: rrInflate };
Rich Render Editor - JS:

/**
* RichRender Editor Format
*
* Per-character pip array for cursor-addressable editing.
* Each position is { ch: string, p1: number, p2: number, p3: number }.
* Cursor maps directly to array index. No parsing, no tree walking.
*
* Painting API: apply/remove/toggle style bits on ranges.
* All operations are bitwise math on pip bytes.
*/
var RREditor = (function () {
// ── Document: array of { ch, p1, p2, p3 } ─────────────────────────────
function EditorDoc(chars) {
this.chars = chars || []; // [{ ch, p1, p2, p3 }, ...]
}
// ── Factory: create from plain text ────────────────────────────────────
EditorDoc.fromText = function (text) {
var chars = [];
for (var i = 0; i < text.length; i++) {
chars.push({ ch: text[i], p1: 0, p2: 0, p3: 0 });
}
return new EditorDoc(chars);
};
// ── Length ──────────────────────────────────────────────────────────────
EditorDoc.prototype.length = function () { return this.chars.length; };
// ── Plain text extraction ──────────────────────────────────────────────
EditorDoc.prototype.text = function (from, to) {
var start = from || 0;
var end = to !== undefined ? to : this.chars.length;
var out = '';
for (var i = start; i < end; i++) out += this.chars[i].ch;
return out;
};
// ── Cursor operations ──────────────────────────────────────────────────
EditorDoc.prototype.insertAt = function (pos, text, p1, p2, p3) {
p1 = p1 || 0; p2 = p2 || 0; p3 = p3 || 0;
var items = [];
for (var i = 0; i < text.length; i++) {
items.push({ ch: text[i], p1: p1, p2: p2, p3: p3 });
}
Array.prototype.splice.apply(this.chars, [pos, 0].concat(items));
return this;
};
EditorDoc.prototype.deleteRange = function (from, to) {
this.chars.splice(from, to - from);
return this;
};
EditorDoc.prototype.charAt = function (pos) {
return this.chars[pos] || null;
};
// ── Selection / slice ──────────────────────────────────────────────────
EditorDoc.prototype.slice = function (from, to) {
var sliced = this.chars.slice(from, to);
// Deep copy
var copy = [];
for (var i = 0; i < sliced.length; i++) {
var c = sliced[i];
copy.push({ ch: c.ch, p1: c.p1, p2: c.p2, p3: c.p3 });
}
return new EditorDoc(copy);
};
// ═══════════════════════════════════════════════════════════════════════
// PAINTING — bitwise style operations on ranges
// ═══════════════════════════════════════════════════════════════════════
// ── Byte one: flag painting ────────────────────────────────────────────
EditorDoc.prototype.applyFlag = function (from, to, flag) {
for (var i = from; i < to; i++) this.chars[i].p1 |= flag;
return this;
};
EditorDoc.prototype.removeFlag = function (from, to, flag) {
for (var i = from; i < to; i++) this.chars[i].p1 &= ~flag;
return this;
};
EditorDoc.prototype.toggleFlag = function (from, to, flag) {
// If ALL chars in range have the flag, remove it; otherwise apply it
var allSet = true;
for (var i = from; i < to; i++) {
if ((this.chars[i].p1 & flag) !== flag) { allSet = false; break; }
}
return allSet ? this.removeFlag(from, to, flag) : this.applyFlag(from, to, flag);
};
EditorDoc.prototype.hasFlag = function (pos, flag) {
var c = this.chars[pos];
return c ? (c.p1 & flag) === flag : false;
};
// ── Byte one: variant field (super/sub/highlight) ──────────────────────
// These are mutually exclusive in the variant mask
EditorDoc.prototype.setVariant = function (from, to, variant) {
for (var i = from; i < to; i++) {
this.chars[i].p1 = (this.chars[i].p1 & ~RR.PIP_VARIANT_MASK) | variant;
}
return this;
};
EditorDoc.prototype.clearVariant = function (from, to) {
return this.setVariant(from, to, 0);
};
// ── Byte two: foreground color ─────────────────────────────────────────
EditorDoc.prototype.setFgColor = function (from, to, colorIndex) {
for (var i = from; i < to; i++) {
var c = this.chars[i];
c.p2 = (c.p2 & ~RR.P2_FG_MASK) | ((colorIndex << RR.P2_FG_SHIFT) & RR.P2_FG_MASK);
this._syncContinuation(i);
}
return this;
};
EditorDoc.prototype.getFgColor = function (pos) {
var c = this.chars[pos];
return c ? (c.p2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT : 0;
};
// ── Byte two: background color ─────────────────────────────────────────
EditorDoc.prototype.setBgColor = function (from, to, bgIndex) {
for (var i = from; i < to; i++) {
var c = this.chars[i];
c.p2 = (c.p2 & ~RR.P2_BG_MASK) | ((bgIndex << 5) & RR.P2_BG_MASK);
this._syncContinuation(i);
}
return this;
};
EditorDoc.prototype.getBgColor = function (pos) {
var c = this.chars[pos];
return c ? (c.p2 & RR.P2_BG_MASK) >> 5 : 0;
};
// ── Byte two: font ─────────────────────────────────────────────────────
EditorDoc.prototype.setFont = function (from, to, sans) {
for (var i = from; i < to; i++) {
var c = this.chars[i];
if (sans) c.p2 |= RR.P2_FONT_BIT;
else c.p2 &= ~RR.P2_FONT_BIT;
this._syncContinuation(i);
}
return this;
};
// ── Byte three: font size ──────────────────────────────────────────────
EditorDoc.prototype.setSize = function (from, to, sizeIndex) {
for (var i = from; i < to; i++) {
var c = this.chars[i];
c.p3 = (c.p3 & ~RR.P3_SIZE_MASK) | ((sizeIndex << RR.P3_SIZE_SHIFT) & RR.P3_SIZE_MASK);
this._syncContinuation(i);
}
return this;
};
EditorDoc.prototype.getSize = function (pos) {
var c = this.chars[pos];
return c ? (c.p3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT : 0;
};
// ── Byte three: typographic extras ─────────────────────────────────────
EditorDoc.prototype.applyP3Flag = function (from, to, flag) {
for (var i = from; i < to; i++) {
this.chars[i].p3 |= flag;
this._syncContinuation(i);
}
return this;
};
EditorDoc.prototype.removeP3Flag = function (from, to, flag) {
for (var i = from; i < to; i++) {
this.chars[i].p3 &= ~flag;
this._syncContinuation(i);
}
return this;
};
EditorDoc.prototype.toggleP3Flag = function (from, to, flag) {
var allSet = true;
for (var i = from; i < to; i++) {
if (!(this.chars[i].p3 & flag)) { allSet = false; break; }
}
return allSet ? this.removeP3Flag(from, to, flag) : this.applyP3Flag(from, to, flag);
};
// ── Continuation bit sync ──────────────────────────────────────────────
// Automatically sets/clears continuation bits based on whether
// higher bytes have any non-zero content.
EditorDoc.prototype._syncContinuation = function (pos) {
var c = this.chars[pos];
// p3 has content? → p2 needs continuation
if (c.p3 & ~RR.P3_CONTINUATION) {
c.p2 |= RR.P2_CONTINUATION;
} else {
c.p2 &= ~RR.P2_CONTINUATION;
c.p3 = 0; // no content, clear entirely
}
// p2 has content? → p1 needs continuation
if (c.p2 & ~RR.P2_CONTINUATION) {
c.p1 |= RR.PIP_CONTINUATION;
} else if (!(c.p2 & RR.P2_CONTINUATION)) {
c.p1 &= ~RR.PIP_CONTINUATION;
c.p2 = 0; // no content, clear entirely
} else {
// p2 has no own content but has continuation (p3 has content)
c.p1 |= RR.PIP_CONTINUATION;
}
};
// ── Query: get style state at position ─────────────────────────────────
EditorDoc.prototype.styleAt = function (pos) {
var c = this.chars[pos];
if (!c) return null;
return {
bold: !!(c.p1 & RR.PIP_BOLD),
italic: !!(c.p1 & RR.PIP_ITALIC),
underline: !!(c.p1 & RR.PIP_UNDERLINE),
strikethrough: !!(c.p1 & RR.PIP_STRIKETHROUGH),
variant: c.p1 & RR.PIP_VARIANT_MASK,
fgColor: (c.p2 & RR.P2_FG_MASK) >> RR.P2_FG_SHIFT,
bgColor: (c.p2 & RR.P2_BG_MASK) >> 5,
sans: !!(c.p2 & RR.P2_FONT_BIT),
size: (c.p3 & RR.P3_SIZE_MASK) >> RR.P3_SIZE_SHIFT,
smallCaps: !!(c.p3 & RR.P3_SMALLCAPS),
overline: !!(c.p3 & RR.P3_OVERLINE),
doubleUnder: !!(c.p3 & RR.P3_DOUBLE_UNDERLINE),
p1: c.p1, p2: c.p2, p3: c.p3
};
};
// ── Query: is range uniform for a given style? ─────────────────────────
EditorDoc.prototype.rangeHasFlag = function (from, to, flag) {
for (var i = from; i < to; i++) {
if ((this.chars[i].p1 & flag) !== flag) return false;
}
return true;
};
// ── Public API ─────────────────────────────────────────────────────────
return { EditorDoc: EditorDoc };
})();
if (typeof module !== 'undefined') module.exports = RREditor;
RichMarkdown - CSS:

/**
* RichMarkdown CSS
* Companion stylesheet for richmd.js
* Override defaults via CSS custom properties on :root or any ancestor element.
*/
/* ─── Variables ──────────────────────────────────────────────────────────────── */
:root {
--mdr-font-family: inherit;
--mdr-font-size: 1rem;
--mdr-line-height: 1.5em;
--mdr-color: inherit;
--mdr-heading-font: inherit;
--mdr-heading-weight: bold;
--mdr-h1-size: 2em;
--mdr-h2-size: 1.5em;
--mdr-h3-size: 1.25em;
--mdr-h4-size: 1.1em;
--mdr-h5-size: 1em;
--mdr-h6-size: 0.9em;
--mdr-code-font: monospace;
--mdr-code-bg: rgba(0,0,0,0.06);
--mdr-code-radius: 3px;
--mdr-pre-bg: rgba(0,0,0,0.04);
--mdr-pre-radius: 4px;
--mdr-pre-padding: 1em;
--mdr-blockquote-border: 3px solid currentColor;
--mdr-blockquote-opacity: 0.6;
--mdr-blockquote-padding: 0 0 0 1em;
--mdr-link-color: inherit;
--mdr-link-decoration: underline;
--mdr-hr-border: 1px solid currentColor;
--mdr-hr-opacity: 0.2;
--mdr-hr-margin: 1.5em 0;
--mdr-table-border: 1px solid rgba(0,0,0,0.15);
--mdr-table-cell-padding: 0.4em 0.75em;
--mdr-th-bg: rgba(0,0,0,0.05);
--mdr-task-gap: 0.4em;
--mdr-task-done-opacity: 0.5;
--mdr-collapse-bg-opacity: 0.1;
--mdr-en-size: 0.85em;
--mdr-en-opacity: 0.75;
--mdr-en-divider-margin: 2em 0 1em;
}
/* ─── Paragraphs ─────────────────────────────────────────────────────────────── */
.mdr-p {
margin: 0;
padding: 0;
font-family: var(--mdr-font-family);
font-size: var(--mdr-font-size);
line-height: var(--mdr-line-height);
color: var(--mdr-color);
}
.mdr-empty {
display: block;
height: var(--mdr-line-height);
}
/* ─── Headings ───────────────────────────────────────────────────────────────── */
.mdr-h1, .mdr-h2, .mdr-h3,
.mdr-h4, .mdr-h5, .mdr-h6 {
font-family: var(--mdr-heading-font);
font-weight: var(--mdr-heading-weight);
line-height: 1.2;
margin: 0;
padding: 0;
}
.mdr-h1 { font-size: var(--mdr-h1-size); }
.mdr-h2 { font-size: var(--mdr-h2-size); }
.mdr-h3 { font-size: var(--mdr-h3-size); }
.mdr-h4 { font-size: var(--mdr-h4-size); }
.mdr-h5 { font-size: var(--mdr-h5-size); }
.mdr-h6 { font-size: var(--mdr-h6-size); }
/* ─── Inline formatting ──────────────────────────────────────────────────────── */
.mdr-b { font-weight: bold; }
.mdr-i { font-style: italic; }
.mdr-u { text-decoration: underline; }
.mdr-s { text-decoration: line-through; }
.mdr-del { text-decoration: line-through; color: var(--mdr-del-color, #e06c75); background: var(--mdr-del-bg, rgba(224, 108, 117, 0.1)); }
.mdr-ins { text-decoration: none; color: var(--mdr-ins-color, #61afef); background: var(--mdr-ins-bg, rgba(97, 175, 239, 0.1)); }
.mdr-mark { background: var(--mdr-mark-bg, rgba(229, 192, 123, 0.3)); }
.mdr-sup, .mdr-sub { font-size: 0.75em; line-height: 0; position: relative; vertical-align: baseline; }
.mdr-sup { top: -0.5em; }
.mdr-sub { bottom: -0.25em; }
/* ─── Abbreviations ─────────────────────────────────────────────────────────── */
.mdr-abbr {
text-decoration: underline dotted;
text-underline-offset: 0.15em;
cursor: help;
}
/* ─── Keyboard input ────────────────────────────────────────────────────────── */
.mdr-kbd {
font-family: var(--mdr-code-font);
font-size: 0.85em;
padding: 0.1em 0.4em;
border: 1px solid rgba(0,0,0,0.2);
border-radius: 3px;
background: rgba(0,0,0,0.04);
box-shadow: 0 1px 0 rgba(0,0,0,0.1);
}
/* ─── Code ───────────────────────────────────────────────────────────────────── */
.mdr-code {
font-family: var(--mdr-code-font);
font-size: 0.9em;
background: var(--mdr-code-bg);
border-radius: var(--mdr-code-radius);
padding: 0.1em 0.3em;
}
.mdr-pre {
font-family: var(--mdr-code-font);
font-size: 0.9em;
background: var(--mdr-pre-bg);
border-radius: var(--mdr-pre-radius);
padding: var(--mdr-pre-padding);
overflow-x: auto;
margin: 0;
white-space: pre;
}
.mdr-pre .mdr-code {
background: none;
border-radius: 0;
padding: 0;
font-size: inherit;
}
/* ─── Links ──────────────────────────────────────────────────────────────────── */
.mdr-a {
color: var(--mdr-link-color);
text-decoration: var(--mdr-link-decoration);
}
.mdr-anchor {
font-size: 0.75em;
color: var(--mdr-anchor-color, rgba(128,128,128,0.6));
text-decoration: none;
font-family: var(--mdr-code-font);
vertical-align: super;
scroll-margin-top: 6em;
}
/* ─── Blockquote ─────────────────────────────────────────────────────────────── */
.mdr-blockquote {
margin: 0;
padding: var(--mdr-blockquote-padding);
border-left: var(--mdr-blockquote-border);
opacity: var(--mdr-blockquote-opacity);
font-style: italic;
}
/* ─── Alignment ─────────────────────────────────────────────────────────────── */
.mdr-align-center { text-align: center; }
.mdr-align-right { text-align: right; }
/* ─── Horizontal rule ────────────────────────────────────────────────────────── */
.mdr-hr {
border: none;
border-top: var(--mdr-hr-border);
opacity: var(--mdr-hr-opacity);
margin: var(--mdr-hr-margin);
}
/* ─── Lists ──────────────────────────────────────────────────────────────────── */
.mdr-ul, .mdr-ol {
margin: 0;
padding-left: 1.5em;
}
.mdr-li {
line-height: var(--mdr-line-height);
margin: 0;
padding: 0;
}
/* ─── Task lists ─────────────────────────────────────────────────────────────── */
.mdr-task, .mdr-task-done {
margin: 0;
padding: 0;
display: flex;
align-items: baseline;
gap: var(--mdr-task-gap);
line-height: var(--mdr-line-height);
list-style: none;
}
.mdr-task::before {
content: "☐";
flex-shrink: 0;
}
.mdr-task.mdr-task-done::before {
content: "☑";
}
.mdr-task.mdr-task-done {
opacity: var(--mdr-task-done-opacity);
text-decoration: line-through;
}
/* ─── Tables ─────────────────────────────────────────────────────────────────── */
.mdr-table {
border-collapse: collapse;
width: 100%;
margin: 0;
font-size: var(--mdr-font-size);
}
.mdr-caption {
font-weight: bold;
margin-bottom: 0.5em;
text-align: left;
}
/* PM schema uses data-caption attr (content hole can't have siblings) */
.mdr-table[data-caption]::before {
content: attr(data-caption);
display: table-caption;
caption-side: top;
font-weight: bold;
margin-bottom: 0.5em;
text-align: left;
}
.mdr-th, .mdr-td {
border: var(--mdr-table-border);
padding: var(--mdr-table-cell-padding);
text-align: left;
vertical-align: top;
}
.mdr-th {
background: var(--mdr-th-bg);
font-weight: bold;
}
/* Table styles */
.mdr-table-layout,
.mdr-table-layout .mdr-th,
.mdr-table-layout .mdr-td {
border: none;
}
.mdr-table-layout .mdr-th {
background: none;
font-weight: normal;
}
.mdr-table-striped .mdr-th {
background: var(--mdr-th-bg);
}
.mdr-table-striped tbody tr:nth-child(even) .mdr-td {
background: rgba(0,0,0,0.03);
}
/* ─── Collapse ───────────────────────────────────────────────────────────────── */
.mdr-collapse {
margin: 0;
}
.mdr-collapse summary {
list-style: none;
cursor: pointer;
user-select: none;
}
.mdr-collapse summary::-webkit-details-marker {
display: none;
}
.mdr-collapse-summary::before {
content: "";
display: inline-block;
width: 1.8em;
height: 1em;
margin-right: 0.4em;
vertical-align: middle;
background-color: currentColor;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 19'%3E%3Cg transform='translate(-8.44 -194.91)'%3E%3Cpath opacity='0.15' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath fill='none' stroke='%23000' stroke-width='0.6' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath d='M13.35 212.9c-2.48-.02-3.84-1.3-3.86-3.6-.03-2.3 0-8.13-.05-9.67-.04-1.54 1.23-3.69 3.6-3.68 2.37.01 25.09-.04 26.8-.05 1.71-.01 3.6 1.33 3.58 3.01-.01 1.68.02 7.36.01 10.82.04 2.3-1.93 3.16-4.03 3.16-2.11 0-23.57.03-26.05.01zm28.38-12.59c-.03-1.42-.16-2.8-3.15-2.82-2.99-.02-22.69-.03-24.86-.02-2.17.01-2.53.78-2.55 2.96-.02 2.19.02 6.23-.03 8.28-.05 2.06 1.02 2.47 2.84 2.52 1.82.05 15.39.05 24.99.01 1.86-.01 2.71-.64 2.72-2.31 0-1.67.08-5.93.03-8.64zm-28.51 9.42c-.03-.03-.06-1.48-.06-3.21 0-2.34.03-3.18.1-3.25.14-.14 1.04-.13 1.16.01.07.08.09 1.01.07 3.29l-.02 3.17-.6.02c-.33.01-.62 0-.65-.03zm4.55-.41c-.04-.06-.63-1.24-1.39-2.86-.96-2.06-1.37-3.02-1.35-3.18.02-.18.12-.28.44-.46.5-.27 1.39-1.16 1.59-1.6.16-.35.47-.52.84-.44.29.06 6.23 2.63 6.5 2.81.29.14.15.26.05.52-.08.21-.21.47-.29.56-.14.17-.17.16-3.43-1.26-1.81-.79-3.33-1.42-3.37-1.4-.04.01 1.26 1.26 2.89 2.77 1.63 1.51 2.56 2.42 2.56 2.45 0 .02-.16.23-.35.46-.22.26-.41.41-.5.39-.08-.01-1-.85-2.55-2.28-1.54-1.43-2.86-2.63-2.92-2.66-.07-.04-.11-.03-.11.04 0 .06.61 1.39 1.34 2.95.73 1.57 1.26 2.69 1.24 2.74-.03.09-.9.57-1.03.54-.12-.03-.11-.05-.15-.1zm10.31-3.76c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm4.9-.08c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm5.07 0c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm-24.85-4.19c-.03-.07-.05-.45-.05-.85 0-1.22.4-1.61 1.63-1.61 1.01 0 .99-.01.99.71 0 .7-.01.71-.77.71h-.5v.45c0 .7-.02.72-.68.72-.45 0-.59-.03-.63-.13zm4.36-1.14c-.14-.14-.13-1.11.01-1.23.08-.07 1.23-.09 4.12-.07l4.01.02v.67.67l-4.02.02c-3.06.02-4.04 0-4.12-.08z'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / contain;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 19'%3E%3Cg transform='translate(-8.44 -194.91)'%3E%3Cpath opacity='0.15' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath fill='none' stroke='%23000' stroke-width='0.6' d='M9.57 204.44l1.06-7.41 14.81-.92 16.27 1.17 1.2 7.06-1.13 7.24-16.19 1.06-14.59-1.24z'/%3E%3Cpath d='M13.35 212.9c-2.48-.02-3.84-1.3-3.86-3.6-.03-2.3 0-8.13-.05-9.67-.04-1.54 1.23-3.69 3.6-3.68 2.37.01 25.09-.04 26.8-.05 1.71-.01 3.6 1.33 3.58 3.01-.01 1.68.02 7.36.01 10.82.04 2.3-1.93 3.16-4.03 3.16-2.11 0-23.57.03-26.05.01zm28.38-12.59c-.03-1.42-.16-2.8-3.15-2.82-2.99-.02-22.69-.03-24.86-.02-2.17.01-2.53.78-2.55 2.96-.02 2.19.02 6.23-.03 8.28-.05 2.06 1.02 2.47 2.84 2.52 1.82.05 15.39.05 24.99.01 1.86-.01 2.71-.64 2.72-2.31 0-1.67.08-5.93.03-8.64zm-28.51 9.42c-.03-.03-.06-1.48-.06-3.21 0-2.34.03-3.18.1-3.25.14-.14 1.04-.13 1.16.01.07.08.09 1.01.07 3.29l-.02 3.17-.6.02c-.33.01-.62 0-.65-.03zm4.55-.41c-.04-.06-.63-1.24-1.39-2.86-.96-2.06-1.37-3.02-1.35-3.18.02-.18.12-.28.44-.46.5-.27 1.39-1.16 1.59-1.6.16-.35.47-.52.84-.44.29.06 6.23 2.63 6.5 2.81.29.14.15.26.05.52-.08.21-.21.47-.29.56-.14.17-.17.16-3.43-1.26-1.81-.79-3.33-1.42-3.37-1.4-.04.01 1.26 1.26 2.89 2.77 1.63 1.51 2.56 2.42 2.56 2.45 0 .02-.16.23-.35.46-.22.26-.41.41-.5.39-.08-.01-1-.85-2.55-2.28-1.54-1.43-2.86-2.63-2.92-2.66-.07-.04-.11-.03-.11.04 0 .06.61 1.39 1.34 2.95.73 1.57 1.26 2.69 1.24 2.74-.03.09-.9.57-1.03.54-.12-.03-.11-.05-.15-.1zm10.31-3.76c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm4.9-.08c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm5.07 0c-1.4-.5-.82-2.79.71-2.26 1.53.53.7 2.72-.71 2.26zm-24.85-4.19c-.03-.07-.05-.45-.05-.85 0-1.22.4-1.61 1.63-1.61 1.01 0 .99-.01.99.71 0 .7-.01.71-.77.71h-.5v.45c0 .7-.02.72-.68.72-.45 0-.59-.03-.63-.13zm4.36-1.14c-.14-.14-.13-1.11.01-1.23.08-.07 1.23-.09 4.12-.07l4.01.02v.67.67l-4.02.02c-3.06.02-4.04 0-4.12-.08z'/%3E%3C/g%3E%3C/svg%3E") no-repeat center / contain;
transition: transform 0.15s;
}
.mdr-collapse[open] .mdr-collapse-summary::before {
transform: rotate(90deg);
}
.mdr-collapse-body {
padding-left: 1.2em;
display: none;
}
.mdr-collapse[open] > .mdr-collapse-body {
display: block;
}
/* ─── Endnotes ──────────────────────────────────────────────────────────────── */
.mdr-en-ref {
text-decoration: none;
font-size: 0.75em;
vertical-align: super;
}
.mdr-endnotes {
font-size: var(--mdr-en-size);
opacity: var(--mdr-en-opacity);
}
.mdr-en {
margin: 0;
padding: 0;
line-height: var(--mdr-line-height);
}
/* In WYSIWYG, PM toDOM only outputs the content hole — use pseudo-elements
to display the endnote number and return arrow visually. */
.mdr-en[data-en]::before {
content: attr(data-en);
font-size: 0.75em;
vertical-align: super;
font-weight: bold;
margin-right: 0.3em;
}
.mdr-en[data-en]::after {
content: ' \21A9';
font-size: 0.85em;
opacity: 0.5;
margin-left: 0.3em;
}
.mdr-en-back {
text-decoration: none;
margin-left: 0.3em;
}
.mdr-hr.mdr-en-separator {
margin: var(--mdr-en-divider-margin);
width: 30%;
margin-left: 0;
}
/* ─── Callout ────────────────────────────────────────────────────────────────── */
.mdr-callout {
display: block;
padding: 0.75em 1em;
margin: 0.5em 0;
background: var(--mdr-callout-bg, rgba(0,0,0,0.04));
border-left: 3px solid var(--mdr-callout-border, currentColor);
border-radius: var(--mdr-code-radius);
}
/* Block callouts show their type as a label */
div.mdr-callout::before {
display: block;
font-weight: bold;
font-size: 0.85em;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.3em;
}
.mdr-callout-note { --mdr-callout-border: #4a90d9; --mdr-callout-bg: rgba(74,144,217,0.08); }
.mdr-callout-note::before { content: "Note"; color: #4a90d9; }
.mdr-callout-warning { --mdr-callout-border: #e0a030; --mdr-callout-bg: rgba(224,160,48,0.08); }
.mdr-callout-warning::before { content: "Warning"; color: #e0a030; }
.mdr-callout-tip { --mdr-callout-border: #50b060; --mdr-callout-bg: rgba(80,176,96,0.08); }
.mdr-callout-tip::before { content: "Tip"; color: #50b060; }
.mdr-callout-caution { --mdr-callout-border: #d04040; --mdr-callout-bg: rgba(208,64,64,0.08); }
.mdr-callout-caution::before { content: "Caution"; color: #d04040; }
.mdr-callout-important { --mdr-callout-border: #8b5cf6; --mdr-callout-bg: rgba(139,92,246,0.08); }
.mdr-callout-important::before { content: "Important"; color: #8b5cf6; }
/* ─── Page break (print) ─────────────────────────────────────────────────────── */
.mdr-hr.mdr-page-break {
display: none;
}
@media print {
.mdr-hr.mdr-page-break {
display: block;
page-break-after: always;
border: none;
opacity: 0;
margin: 0;
}
}