</p>
<p>
- For RT-Style to work, the document will need to include a file that points it at the library, as discussed later in this manual.
+ For RT-Style to work, the document will need to include a file that points it at the library, as discussed in the <RT·Note·read key="env-setup" field="content"></RT·Note·read> section on page <RT·Note·read key="env-setup" field="page"></RT·Note·read>.
</p>
<p>
</RT·section>
<RT·section>
- <RT·name>Article Environment</RT·name>
+ <RT·name><RT·Note·write key="env-setup">Article Environment</RT·Note·write></RT·name>
<p>The standard operational header template for an article instance:</p>
<RT·code>
<RT·entry key="<RT·Counter·make>">
Initializes the named counter state machine.<br>
<div class="attr-list">
+
<br><strong>Attributes:</strong><br>
+
<code>counter</code>: Required state identifier.<br>
- <code>style</code>: Single style string, or comma-separated hierarchy defining formats at each nesting depth. The terminal format applies to all deeper nestings. Valid arguments: "NaturalNumber", "CountingNumber", "Roman", "roman", "Alpha", "alpha". The "outline" flag translates to a standard mixed document array. (Default: "NaturalNumber")<br>
- <code>on-first-step</code>: The initial base value. (Default: "0")<br>
- <code>separator</code>: String appended between depth sequence levels. (Default: ".")<br>
- <code>separator-placement</code>: "embedded" or "embedded-after". (Default: "embedded")<br>
- <code>mode</code>: "scoped" (content embedded between count boundaries receives the parent scope's value) or "milestone" (state carries forward sequentially irrespective of lexical depth).
+
+ <br><code>style</code>: Single style string, or comma-separated hierarchy defining formats at each nesting depth. The terminal format applies to all deeper nestings. Valid arguments: "NaturalNumber", "CountingNumber", "Roman", "roman", "Alpha", "alpha". The "outline" flag translates to a standard mixed document array. (Default: "NaturalNumber")<br>
+
+ <br><code>on-first-step</code>: The value returned the first time the counter is stepped. The default is style specific: "NaturalNumber"(0), "CountingNumber"(1), "Roman"(I), "roman"(i), "Alpha"(A), "alpha"(a).<br>
+
+ <br><code>separator</code>: String appended between depth sequence levels. (Default: ".")<br>
+
+ <br><code>separator-placement</code>: "embedded" or "embedded-after". (Default: "embedded")<br>
+
+ <br><code>mode</code>: "scoped" (content embedded between count boundaries receives the parent scope's value) or "milestone" (state carries forward sequentially irrespective of lexical depth).
+
</div>
</RT·entry>
<RT·entry key="<RT·Counter·step>">
<div class="attr-list">
<br><strong>Attributes:</strong><br>
<code>snapshot</code>: Required dictionary assignment key.<br>
- <code>key</code>: Sub-state query property. Resolves dot-notation parameters (e.g., "count.status", "count.list"). Querying "count" triggers the style formatter and separator join parameters. (Default: "count")
+ <code>key</code>: Sub-state query property. Resolves dot-notation parameters (e.g., "count.status", "count.list"). Querying "count" triggers the style formatter and separator join parameters. (Default: "count"). Reading an empty counter (before a first step) causes an error to the console.
</div>
</RT·entry>
</RT·dictionary>
/*
Processes <RT·counter·*> tags.
Calculates numbering, maintains the explicit status machine, manages snapshots, and outputs values for read tags.
- Supports two modes: 'scoped' (default) and 'milestone'.
+ Supports 'scoped' and 'milestone' modes. Includes DOM continuation suspension architecture.
*/
(function() {
RT.Counter = RT.Counter || {};
RT.dict_instance = RT.dict_instance || {};
RT.dict_snapshot = RT.dict_snapshot || {};
-
+ RT.dict_serial = RT.dict_serial || {};
+ RT.serial_id_allocator = RT.serial_id_allocator || 1;
class Count {
constructor() {
}
}
-
read(...path) {
if (path.length === 0) return undefined;
const key = path[0];
if (this.list && this.list.length > 0) {
const val = this.list.pop();
if (this.list.length === 0) {
- this.list = null; // Revert to strict null state if emptied
+ this.list = null;
}
return val;
}
c.list = this.list ? [...this.list] : null;
return c;
}
-
}
class CounterMachine {
this.style = ['NaturalNumber'];
this.separator = '.';
this.separator_placement = 'embedded';
- this.mode = 'scoped'; // 'scoped', 'milestone'
+ this.mode = 'scoped';
if (config) {
this.write(config);
}
this.style = parsed;
} else if (key === 'Count' || key === 'count') {
- // Support copying from another CounterMachine or Count object
const source_count = value instanceof CounterMachine ? value.count : (value instanceof Count ? value : null);
if (source_count) {
this.count = source_count.clone();
const current_status = this.count.read('status');
if (current_status === 'empty') {
- // Transition state strictly before mutating the list
this.count.write('status', 'preamble');
this.count.push(first_step_val !== undefined ? first_step_val : 0);
} else if (current_status === 'preamble') {
- this.count.push(0); // indent appends 0
+ this.count.push(0);
this.count.write('status', 'preamble');
} else if (current_status === 'between') {
this.count.increment();
if (!count_obj) return '';
const status = count_obj.read('status');
- if (status === 'empty') return '';
+ if (status === 'empty') {
+ console.error("RT-Manuscript Layout Error: Attempted to output an uninitialized empty counter.");
+ return '[Empty Counter]';
+ }
let active_list;
if (this.mode === 'scoped' && status === 'between') {
active_list = count_obj.read('list');
}
- // Safety check catches null or empty arrays
if (!active_list || active_list.length === 0) {
return '';
}
return count_str;
}
- // --- Atomic Type Conversions ---
-
to_NaturalNumber(num) { return num.toString(); }
from_NaturalNumber(val) {
const n = parseInt(val, 10);
from_roman(val) { return this.from_Roman(val.toUpperCase()); }
to_Roman(num) {
- let n = num + 1; // 0-indexed to 1-indexed
+ let n = num + 1;
if (n < 1) return n.toString();
const lookup = {M:1000, CM:900, D:500, CD:400, C:100, XC:90, L:50, XL:40, X:10, IX:9, V:5, IV:4, I:1};
let roman = '';
i++;
}
}
- return Math.max(0, num - 1); // 1-indexed to 0-indexed
+ return Math.max(0, num - 1);
}
to_Alpha(num) { return String.fromCharCode(65 + num); }
if (tag === 'rt·counter·make') {
const name = node.getAttribute('counter');
if (name) {
- const style_attr = node.getAttribute('style');
- const parsed_style = style_attr ? style_attr.split(',').map(s => s.trim()) : ['NaturalNumber'];
+ const continues_id = node.getAttribute('continues');
- RT.dict_instance[name] = new CounterMachine({
- style: parsed_style,
- separator: node.getAttribute('separator') || '.',
- separator_placement: node.getAttribute('separator-placement') || 'embedded',
- mode: node.getAttribute('mode') || 'scoped'
- });
-
- const on_first_step_str = node.getAttribute('on-first-step');
- if (on_first_step_str) {
- const top_style = RT.dict_instance[name].style[0];
- const method_name = `from_${top_style}`;
- const active_machine = RT.dict_instance[name];
- const initial_val = typeof active_machine[method_name] === 'function'
- ? active_machine[method_name](on_first_step_str)
- : active_machine.from_NaturalNumber(on_first_step_str);
-
- active_machine.first_step_val = initial_val;
+ if (continues_id && RT.dict_serial[continues_id]) {
+ RT.dict_instance[name] = RT.dict_serial[continues_id].clone();
+ } else {
+ const style_attr = node.getAttribute('style');
+ const parsed_style = style_attr ? style_attr.split(',').map(s => s.trim()) : ['NaturalNumber'];
+
+ RT.dict_instance[name] = new CounterMachine({
+ style: parsed_style,
+ separator: node.getAttribute('separator') || '.',
+ separator_placement: node.getAttribute('separator-placement') || 'embedded',
+ mode: node.getAttribute('mode') || 'scoped'
+ });
+
+ const on_first_step_str = node.getAttribute('on-first-step');
+ if (on_first_step_str) {
+ const top_style = RT.dict_instance[name].style[0];
+ const method_name = `from_${top_style}`;
+ const active_machine = RT.dict_instance[name];
+ const initial_val = typeof active_machine[method_name] === 'function'
+ ? active_machine[method_name](on_first_step_str)
+ : active_machine.from_NaturalNumber(on_first_step_str);
+
+ active_machine.first_step_val = initial_val;
+ }
}
+
+ const serial = node.getAttribute('serial') || String(RT.serial_id_allocator++);
+ node.setAttribute('serial', serial);
+ RT.dict_serial[serial] = RT.dict_instance[name];
}
} else if (tag === 'rt·counter·step') {
const name = node.getAttribute('counter');
+ const is_continuation = node.getAttribute('continuation') === 'true';
+
if (name && RT.dict_instance[name]) {
const active_machine = RT.dict_instance[name];
- active_machine.enter(active_machine.first_step_val);
- active_machine.first_step_val = undefined; // consume it
+ if (!is_continuation) {
+ active_machine.enter(active_machine.first_step_val);
+ active_machine.first_step_val = undefined;
+ }
machine_to_exit = active_machine;
}
} else if (tag === 'rt·counter·snapshot') {
}
if (machine_to_exit) {
- machine_to_exit.exit();
+ const is_continued = node.getAttribute('continued') === 'true';
+ if (!is_continued) {
+ machine_to_exit.exit();
+ } else {
+ // Cache the suspended state dynamically via the paginator's injected ID
+ const split_id = node.getAttribute('split-id');
+ if (split_id) {
+ RT.dict_serial[split_id] = machine_to_exit.clone();
+ }
+ }
}
}
}
};
- //------------------------------------------
- // on module load
- //
-
window.RT.counter = counter;
})();
return h;
}
-// =========================================================
+ // =========================================================
// Splitting Logic
// =========================================================
function isSplittable(el){
+
// Component Dictionary Execution
const componentId = el.getAttribute('data-rt-component');
if (componentId && window.RT.Component && window.RT.Component[componentId] && window.RT.Component[componentId].split) {
return (remaining) => window.RT.Component[componentId].split(el, remaining, measureFragment);
}
+ // Custom RT splitable attribute delegation
+ if (el.hasAttribute('splitable') && window.RT.Splitter && window.RT.Splitter[el.tagName.toLowerCase()]) {
+ return (remaining) => window.RT.Splitter[el.tagName.toLowerCase()](el, remaining, measureFragment, isSplittable);
+ }
+
// Native HTML Fallbacks
+
const tag = el.tagName;
if(tag === 'UL' || tag === 'OL'){
const items = Array.from(el.children).filter(c => c.tagName === 'LI');
}
+ // =========================================================
+ // RT ELEMENT SPLITTERS
+ // =========================================================
+ window.RT.Splitter = window.RT.Splitter || {};
+
+ window.RT.Splitter['rt·counter·step'] = function(el, remaining, measureFn, isSplittableFn) {
+ const children = Array.from(el.children);
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempContainer = el.cloneNode(false);
+ let splitChildResult = null;
+ let forcedBreak = false;
+
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+
+ // Break on explicit splitting break
+ if (child.tagName && child.tagName.toLowerCase() === 'rt·page-break') {
+ forcedBreak = true;
+ bestCount = i;
+ break;
+ }
+
+ tempContainer.appendChild(child.cloneNode(true));
+ const fragHeight = measureFn(tempContainer);
+
+ if (fragHeight <= remaining) {
+ bestCount = i + 1;
+ bestHeight = fragHeight;
+ } else {
+ tempContainer.removeChild(tempContainer.lastChild);
+ const childSplitter = isSplittableFn(child);
+ if (childSplitter) {
+ const childSplit = childSplitter(remaining - bestHeight);
+ if (childSplit && childSplit.first) {
+ splitChildResult = childSplit;
+ bestHeight += childSplit.firstHeight;
+ bestCount = i;
+ }
+ }
+ break;
+ }
+ }
+
+ if (bestCount === 0 && !splitChildResult && !forcedBreak) {
+ return { first: null, rest: el, firstHeight: 0 };
+ }
+
+ const first = el.cloneNode(false);
+ first.setAttribute('continued', 'true');
+ const splitId = 'split_' + Math.random().toString(36).substr(2, 9);
+ first.setAttribute('split-id', splitId);
+
+ for (let i = 0; i < bestCount; i++) {
+ first.appendChild(children[i].cloneNode(true));
+ }
+ if (splitChildResult) first.appendChild(splitChildResult.first);
+
+ let rest = null;
+ if (bestCount < children.length || splitChildResult || forcedBreak) {
+ rest = el.cloneNode(false);
+ rest.setAttribute('continuation', 'true');
+ if (splitChildResult && splitChildResult.rest) rest.appendChild(splitChildResult.rest);
+
+ const startIndex = forcedBreak ? bestCount + 1 : (splitChildResult ? bestCount + 1 : bestCount);
+ for (let i = startIndex; i < children.length; i++) {
+ rest.appendChild(children[i].cloneNode(true));
+ }
+
+ const makeTag = document.createElement('rt·counter·make');
+ makeTag.setAttribute('counter', el.getAttribute('counter'));
+ makeTag.setAttribute('continues', splitId);
+
+ rest = [makeTag, rest];
+ }
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+
// =========================================================
// PAGINATE 0: CHUNKING & INJECTING STRUCTURE
// =========================================================
current_h += firstHeight;
if(rest){
- raw_element_seq.splice(i ,1 ,rest);
+ if (Array.isArray(rest)) {
+ raw_element_seq.splice(i, 1, ...rest);
+ } else {
+ raw_element_seq.splice(i, 1, rest);
+ }
+ // Force page boundary push because element spanned boundary
+ page_seq.push(current_batch_seq);
+ current_batch_seq = [];
+ current_h = 0;
+ continue;
} else {
- raw_element_seq.splice(i ,1);
+ raw_element_seq.splice(i, 1);
+ continue;
}
} else {
if(current_batch_seq.length === 0){
const h = getElHeight(el);
const is_RT_page_break = el.tagName && el.tagName.toLowerCase() === 'rt·page-break';
+ const is_RT_page_break_primitive = el.tagName && el.tagName.toLowerCase() === 'rt·page-break-primitive';
// Explicit Page Break Logic - Execute immediately without backward traversal
- if(is_RT_page_break){
+ if(is_RT_page_break || is_RT_page_break_primitive){
if(current_batch_seq.length > 0){
page_seq.push(current_batch_seq);
current_batch_seq = [];
--- /dev/null
+/*
+ direct.js
+
+
+ We have four scenarios
+
+ immediate - used in the RT-style distribution itself (authored, consummer, staged)
+ direct - used in the RT-style project itself, but not in the distribution
+ indirect - the version all Harmony projects use
+ URL_only - always pulls style through a URL, a webserver must be present
+
+*/
+
+window.RT = window.RT || {};
+
+(function() {
+ const project_name = "RT-Style";
+ const path = window.location.pathname;
+ const project_root_index = path.indexOf('/' + project_name + '/');
+
+ if (project_root_index !== -1) {
+ // substring(0, x) excludes the trailing slash. We must prepend it to the payload.
+ const absolute_project_root = path.substring(0, project_root_index + project_name.length + 1);
+ window.RT.dirpr_library = absolute_project_root + "/consumer/Manuscript";
+ } else {
+ // Fallback for when served via local Python HTTP daemon from the project root
+ window.RT.dirpr_library = "../consumer/made/Manuscript";
+ }
+
+ document.write(
+ '<script src="'
+ + window.RT.dirpr_library
+ + '/Core/RT-Manuscript_make.js"'
+ + '><\/script>'
+ );
+
+})();
--- /dev/null
+<!--
+counter splits with continuations
+uses manual primitive page breaks and hard coded splits and continuations
+-->
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>Counter 3-Page Continuation Test</title>
+ <script src="RT-Manuscript_locator.js"></script>
+ <script>
+ window.RT.theme_preference('inverse_wheat');
+ window.RT.load('Layout/paginate');
+ window.RT.load('Layout/article_tech_ref');
+ </script>
+ </head>
+ <body>
+ <RT·theme-selector></RT·theme-selector>
+ <RT·article>
+
+ <!-- PAGE 1: Initial Scope Generation -->
+ <h1>Page 1: Initial Scope</h1>
+ <p>The state machine initializes. Scopes 1, 3, and 4 breach the page boundary and are suspended (<code>continued="true"</code>).</p>
+
+ <RT·counter·make counter="C" serial="1" style="outline" on-first-step="1"></RT·counter·make>
+
+ <RT·counter·step counter="C" continued="true"> <!-- Node 1: I -->
+ <RT·counter·snapshot counter="C" snapshot="n1"></RT·counter·snapshot>
+ <p>Node 1 execution: <strong><RT·counter·read snapshot="n1"></RT·counter·read></strong> (Expected: <strong>I</strong>)</p>
+
+ <RT·counter·step counter="C"> <!-- Node 2: I.A -->
+ <RT·counter·snapshot counter="C" snapshot="n2"></RT·counter·snapshot>
+ <p style="margin-left: 2rem;">Node 2 execution: <strong><RT·counter·read snapshot="n2"></RT·counter·read></strong> (Expected: <strong>I.A</strong>) - <em>Closes normally</em></p>
+ </RT·counter·step>
+
+ <RT·counter·step counter="C" continued="true"> <!-- Node 3: I.B -->
+ <RT·counter·snapshot counter="C" snapshot="n3"></RT·counter·snapshot>
+ <p style="margin-left: 2rem;">Node 3 execution: <strong><RT·counter·read snapshot="n3"></RT·counter·read></strong> (Expected: <strong>I.B</strong>)</p>
+
+ <RT·counter·step counter="C" continued="true"> <!-- Node 4: I.B.i -->
+ <RT·counter·snapshot counter="C" snapshot="n4"></RT·counter·snapshot>
+ <p style="margin-left: 4rem;">Node 4 execution: <strong><RT·counter·read snapshot="n4"></RT·counter·read></strong> (Expected: <strong>I.B.i</strong>)</p>
+ <p style="margin-left: 4rem; color: #B22222;">--- Page Boundary Reached ---</p>
+ </RT·counter·step>
+ </RT·counter·step>
+ </RT·counter·step>
+
+ <RT·page-break-primitive></RT·page-break-primitive>
+
+ <!-- PAGE 2: First Continuation -->
+ <h1>Page 2: First Continuation</h1>
+ <p>The graph is reassembled. Node 4 closes. Node 5 executes. The page boundary is breached again, forcing a second suspension of the outer scopes 1 and 3.</p>
+
+ <RT·counter·make counter="C" serial="2" continues="1"></RT·counter·make>
+
+ <!-- Node 1 Cont: Suspended again -->
+ <RT·counter·step counter="C" continuation="true" continued="true">
+
+ <!-- Node 3 Cont: Suspended again -->
+ <RT·counter·step counter="C" continuation="true" continued="true">
+
+ <!-- Node 4 Cont: Closes -->
+ <RT·counter·step counter="C" continuation="true">
+ <RT·counter·snapshot counter="C" snapshot="n4_cont"></RT·counter·snapshot>
+ <p style="margin-left: 4rem;">Node 4 continued payload. Current state: <strong><RT·counter·read snapshot="n4_cont"></RT·counter·read></strong> (Expected: <strong>I.B.i</strong>) - <em>Closes normally</em></p>
+ </RT·counter·step>
+
+ <RT·counter·step counter="C"> <!-- Node 5: I.B.ii -->
+ <RT·counter·snapshot counter="C" snapshot="n5"></RT·counter·snapshot>
+ <p style="margin-left: 4rem;">Node 5 execution: <strong><RT·counter·read snapshot="n5"></RT·counter·read></strong> (Expected: <strong>I.B.ii</strong>) - <em>Closes normally</em></p>
+ <p style="margin-left: 4rem; color: #B22222;">--- Page Boundary Reached ---</p>
+ </RT·counter·step>
+
+ </RT·counter·step>
+ </RT·counter·step>
+
+ <RT·page-break-primitive></RT·page-break-primitive>
+
+ <!-- PAGE 3: Second Continuation -->
+ <h1>Page 3: Second Continuation</h1>
+ <p>The final graph reassembly. Node 3 closes. Node 6 executes. Node 1 closes.</p>
+
+ <RT·counter·make counter="C" serial="3" continues="2"></RT·counter·make>
+
+ <!-- Node 1 Cont: Closes -->
+ <RT·counter·step counter="C" continuation="true">
+
+ <!-- Node 3 Cont: Closes -->
+ <RT·counter·step counter="C" continuation="true">
+ <RT·counter·snapshot counter="C" snapshot="n3_cont"></RT·counter·snapshot>
+ <p style="margin-left: 2rem;">Node 3 continued payload. Current state: <strong><RT·counter·read snapshot="n3_cont"></RT·counter·read></strong> (Expected: <strong>I.B</strong>) - <em>Closes normally</em></p>
+ </RT·counter·step>
+
+ <RT·counter·step counter="C"> <!-- Node 6: I.C -->
+ <RT·counter·snapshot counter="C" snapshot="n6"></RT·counter·snapshot>
+ <p style="margin-left: 2rem;">Node 6 execution: <strong><RT·counter·read snapshot="n6"></RT·counter·read></strong> (Expected: <strong>I.C</strong>) - <em>Closes normally</em></p>
+ </RT·counter·step>
+
+ <RT·counter·snapshot counter="C" snapshot="n1_cont"></RT·counter·snapshot>
+ <p>Node 1 continued payload. Current state: <strong><RT·counter·read snapshot="n1_cont"></RT·counter·read></strong> (Expected: <strong>I</strong>) - <em>Closes normally</em></p>
+
+ </RT·counter·step>
+
+ </RT·article>
+ </body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>Counter Split Continuation Test</title>
+ <script src="RT-Manuscript_locator.js"></script>
+ <script>
+ window.RT.theme_preference('inverse_wheat');
+ window.RT.load('Layout/paginate');
+ window.RT.load('Layout/article_tech_ref');
+ </script>
+ </head>
+ <body>
+ <RT·theme-selector></RT·theme-selector>
+ <RT·article>
+
+ <h1>Page 1: Pre-Split</h1>
+ <p>This counter scope initiates normally and is deliberately severed by an explicit structural split break.</p>
+
+ <RT·counter·make counter="C" style="outline" on-first-step="1"></RT·counter·make>
+
+ <RT·counter·step counter="C" splitable="true">
+ <RT·counter·snapshot counter="C" snapshot="n1_pre"></RT·counter·snapshot>
+ <p>Pre-split evaluation: <strong><RT·counter·read snapshot="n1_pre"></RT·counter·read></strong> (Expected: 1)</p>
+
+ <RT·page-break></RT·page-break>
+
+ <h1>Page 2: Post-Split Continuation</h1>
+ <RT·counter·snapshot counter="C" snapshot="n1_post"></RT·counter·snapshot>
+ <p>Post-split evaluation: <strong><RT·counter·read snapshot="n1_post"></RT·counter·read></strong> (Expected: 1)</p>
+ <p>The array extent remains static across the boundary because the continuation flag successfully suppressed the step increment.</p>
+
+ <RT·counter·step counter="C">
+ <RT·counter·snapshot counter="C" snapshot="n2_nested"></RT·counter·snapshot>
+ <p>Nested step evaluation: <strong><RT·counter·read snapshot="n2_nested"></RT·counter·read></strong> (Expected: 1.A)</p>
+ </RT·counter·step>
+
+ </RT·counter·step>
+
+ </RT·article>
+ </body>
+</html>