--- /dev/null
+// Core/loader.js
+
+window.RT = window.RT || {};
+window.RT.Module = window.RT.Module || new Set();
+
+// 2. Establish the Debug System
+window.RT.Debug = {
+ active_tokens: new Set([
+ 'scroll'
+ ]),
+
+ log: function(token, message) {
+ if (this.active_tokens.has(token)) {
+ console.log(`[RT:${token}]`, message);
+ }
+ },
+
+ warn: function(token, message) {
+ if (this.active_tokens.has(token)) {
+ console.warn(`[RT:${token}]`, message);
+ }
+ },
+
+ error: function(token, message) {
+ console.error(`[RT:${token}] CRITICAL:`, message);
+ },
+
+ enable: function(token) { this.active_tokens.add(token); console.log(`Enabled: ${token}`); },
+ disable: function(token) { this.active_tokens.delete(token); console.log(`Disabled: ${token}`); }
+};
+
+// 3. Establish the Utilities
+window.RT.Utility = {
+
+ String: {
+ to_roman: function(num) {
+ if (num < 1) return num.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 = '';
+ for (let i in lookup) {
+ while (num >= lookup[i]) {
+ roman += i;
+ num -= lookup[i];
+ }
+ }
+ return roman;
+ },
+
+ strip_common_indent: function(text, tag_indent = '') {
+ const raw_lines = text.split('\n');
+ const content_lines = raw_lines.filter(line => line.trim().length > 0);
+ let common_indent = '';
+
+ if (content_lines.length > 0) {
+ const first_match = content_lines[0].match(/^\s*/);
+ common_indent = first_match ? first_match[0] : '';
+
+ for (let i = 1; i < content_lines.length; i++) {
+ const line = content_lines[i];
+ let j = 0;
+ while (j < common_indent.length && j < line.length && common_indent[j] === line[j]) {
+ j++;
+ }
+ common_indent = common_indent.substring(0, j);
+ if (common_indent.length === 0) break;
+ }
+ }
+
+ let final_string = '';
+ if (common_indent.length > 0 && common_indent.startsWith(tag_indent)) {
+ const cleaned_lines = raw_lines.map(line => {
+ return line.startsWith(common_indent) ? line.replace(common_indent, '') : line;
+ });
+
+ if (cleaned_lines.length > 0 && cleaned_lines[0].length === 0) {
+ cleaned_lines.shift();
+ }
+ if (cleaned_lines.length > 0 && cleaned_lines[cleaned_lines.length - 1].trim().length === 0) {
+ cleaned_lines.pop();
+ }
+ final_string = cleaned_lines.join('\n');
+ } else {
+ final_string = text.trim();
+ }
+
+ return final_string;
+ }
+ },
+
+ Dom: {
+ measure_outer_height: function(el) {
+ const wasInDOM = el.parentNode !== null;
+ if (!wasInDOM) document.body.appendChild(el);
+ const rect = el.getBoundingClientRect();
+ const style = window.getComputedStyle(el);
+ const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ if (!wasInDOM) el.remove();
+ return (rect.height || 0) + (margin || 0);
+ },
+
+ is_block_content: function(element) {
+ return element.textContent.trim().includes('\n');
+ }
+ },
+
+ Font: {
+ measure_ink_ratio: function(target_font, ref_font = null) {
+ const debug = window.RT.Debug;
+ debug.log('layout', `Measuring ink ratio for ${target_font}`);
+
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ if (!ref_font) {
+ const bodyStyle = window.getComputedStyle(document.body);
+ ref_font = bodyStyle.fontFamily;
+ }
+
+ const get_metrics = (font) => {
+ ctx.font = '100px ' + font;
+ const metrics = ctx.measureText('M');
+ return {
+ ascent: metrics.actualBoundingBoxAscent,
+ descent: metrics.actualBoundingBoxDescent
+ };
+ };
+
+ const ref_m = get_metrics(ref_font);
+ const target_m = get_metrics(target_font);
+
+ const ratio = ref_m.ascent / target_m.ascent;
+
+ return {
+ ratio: ratio,
+ baseline_diff: ref_m.descent - target_m.descent
+ };
+ }
+ },
+
+ Color: {
+ extract_l: function(color_string) {
+ const str = String(color_string).trim();
+
+ if (!str.startsWith('oklch')) {
+ console.error(`[RT:color] Invalid format: Expected oklch color string, received '${str}'`);
+ return 0;
+ }
+
+ const match = str.match(/oklch\(\s*([\d.]+%?)/);
+ if (!match) {
+ console.error(`[RT:color] Parsing error: Could not extract lightness from '${str}'`);
+ return 0;
+ }
+
+ const l_value = match[1];
+ return l_value.includes('%') ? parseFloat(l_value) / 100 : parseFloat(l_value);
+ },
+
+ is_high_contrast: function(bg_color, text_color) {
+ const bg_l = this.extract_l(bg_color);
+ const text_l = this.extract_l(text_color);
+ return Math.abs(text_l - bg_l) >= 0.7;
+ },
+
+ is_readable: function(bg_color, text_color) {
+ const bg_l = this.extract_l(bg_color);
+ const text_l = this.extract_l(text_color);
+ return Math.abs(text_l - bg_l) >= 0.5;
+ },
+
+ is_light: function(color_string) {
+ return this.extract_l(color_string) > 0.75;
+ },
+
+ is_gray: function(color_string) {
+ const l = this.extract_l(color_string);
+ return l >= 0.25 && l <= 0.75;
+ },
+
+ is_dark: function(color_string) {
+ return this.extract_l(color_string) < 0.25;
+ }
+ }
+
+};
+
+
+window.RT.load = function(module_path) {
+ if (window.RT.Module.has(module_path)) {
+ return;
+ }
+ window.RT.Module.add(module_path);
+
+ let resolved_path = window.RT.dirpr_library + '/' + module_path;
+ if (!resolved_path.endsWith('.js')) {
+ resolved_path = resolved_path + '.js';
+ }
+
+ document.write('<script src="' + resolved_path + '"></script>');
+};
+
+window.RT.load('Core/stage_manager');
+window.RT.load('Core/theme_make');
+window.RT.load('Theme/manifest.js')
+++ /dev/null
-// Core/loader.js
-
-window.RT = window.RT || {};
-window.RT.Module = window.RT.Module || new Set();
-
-// 2. Establish the Debug System
-window.RT.Debug = {
- active_tokens: new Set([
- 'scroll'
- ]),
-
- log: function(token, message) {
- if (this.active_tokens.has(token)) {
- console.log(`[RT:${token}]`, message);
- }
- },
-
- warn: function(token, message) {
- if (this.active_tokens.has(token)) {
- console.warn(`[RT:${token}]`, message);
- }
- },
-
- error: function(token, message) {
- console.error(`[RT:${token}] CRITICAL:`, message);
- },
-
- enable: function(token) { this.active_tokens.add(token); console.log(`Enabled: ${token}`); },
- disable: function(token) { this.active_tokens.delete(token); console.log(`Disabled: ${token}`); }
-};
-
-// 3. Establish the Utilities
-window.RT.Utility = {
-
- String: {
- to_roman: function(num) {
- if (num < 1) return num.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 = '';
- for (let i in lookup) {
- while (num >= lookup[i]) {
- roman += i;
- num -= lookup[i];
- }
- }
- return roman;
- },
-
- strip_common_indent: function(text, tag_indent = '') {
- const raw_lines = text.split('\n');
- const content_lines = raw_lines.filter(line => line.trim().length > 0);
- let common_indent = '';
-
- if (content_lines.length > 0) {
- const first_match = content_lines[0].match(/^\s*/);
- common_indent = first_match ? first_match[0] : '';
-
- for (let i = 1; i < content_lines.length; i++) {
- const line = content_lines[i];
- let j = 0;
- while (j < common_indent.length && j < line.length && common_indent[j] === line[j]) {
- j++;
- }
- common_indent = common_indent.substring(0, j);
- if (common_indent.length === 0) break;
- }
- }
-
- let final_string = '';
- if (common_indent.length > 0 && common_indent.startsWith(tag_indent)) {
- const cleaned_lines = raw_lines.map(line => {
- return line.startsWith(common_indent) ? line.replace(common_indent, '') : line;
- });
-
- if (cleaned_lines.length > 0 && cleaned_lines[0].length === 0) {
- cleaned_lines.shift();
- }
- if (cleaned_lines.length > 0 && cleaned_lines[cleaned_lines.length - 1].trim().length === 0) {
- cleaned_lines.pop();
- }
- final_string = cleaned_lines.join('\n');
- } else {
- final_string = text.trim();
- }
-
- return final_string;
- }
- },
-
- Dom: {
- measure_outer_height: function(el) {
- const wasInDOM = el.parentNode !== null;
- if (!wasInDOM) document.body.appendChild(el);
- const rect = el.getBoundingClientRect();
- const style = window.getComputedStyle(el);
- const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
- if (!wasInDOM) el.remove();
- return (rect.height || 0) + (margin || 0);
- },
-
- is_block_content: function(element) {
- return element.textContent.trim().includes('\n');
- }
- },
-
- Font: {
- measure_ink_ratio: function(target_font, ref_font = null) {
- const debug = window.RT.Debug;
- debug.log('layout', `Measuring ink ratio for ${target_font}`);
-
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
-
- if (!ref_font) {
- const bodyStyle = window.getComputedStyle(document.body);
- ref_font = bodyStyle.fontFamily;
- }
-
- const get_metrics = (font) => {
- ctx.font = '100px ' + font;
- const metrics = ctx.measureText('M');
- return {
- ascent: metrics.actualBoundingBoxAscent,
- descent: metrics.actualBoundingBoxDescent
- };
- };
-
- const ref_m = get_metrics(ref_font);
- const target_m = get_metrics(target_font);
-
- const ratio = ref_m.ascent / target_m.ascent;
-
- return {
- ratio: ratio,
- baseline_diff: ref_m.descent - target_m.descent
- };
- }
- },
-
- Color: {
- extract_l: function(color_string) {
- const str = String(color_string).trim();
-
- if (!str.startsWith('oklch')) {
- console.error(`[RT:color] Invalid format: Expected oklch color string, received '${str}'`);
- return 0;
- }
-
- const match = str.match(/oklch\(\s*([\d.]+%?)/);
- if (!match) {
- console.error(`[RT:color] Parsing error: Could not extract lightness from '${str}'`);
- return 0;
- }
-
- const l_value = match[1];
- return l_value.includes('%') ? parseFloat(l_value) / 100 : parseFloat(l_value);
- },
-
- is_high_contrast: function(bg_color, text_color) {
- const bg_l = this.extract_l(bg_color);
- const text_l = this.extract_l(text_color);
- return Math.abs(text_l - bg_l) >= 0.7;
- },
-
- is_readable: function(bg_color, text_color) {
- const bg_l = this.extract_l(bg_color);
- const text_l = this.extract_l(text_color);
- return Math.abs(text_l - bg_l) >= 0.5;
- },
-
- is_light: function(color_string) {
- return this.extract_l(color_string) > 0.75;
- },
-
- is_gray: function(color_string) {
- const l = this.extract_l(color_string);
- return l >= 0.25 && l <= 0.75;
- },
-
- is_dark: function(color_string) {
- return this.extract_l(color_string) < 0.25;
- }
- }
-
-};
-
-
-window.RT.load = function(module_path) {
- if (window.RT.Module.has(module_path)) {
- return;
- }
- window.RT.Module.add(module_path);
-
- let resolved_path = window.RT.dirpr_library + '/' + module_path;
- if (!resolved_path.endsWith('.js')) {
- resolved_path = resolved_path + '.js';
- }
-
- document.write('<script src="' + resolved_path + '"></script>');
-};
-
-window.RT.load('Core/stage_manager');
-window.RT.load('Core/theme_make');
-window.RT.load('Theme/manifest.js')
-// Core/stage_manager.js
+/*
+ Core/stage_manager.js
+ Orchestrates the execution pipeline to resolve layout dependencies,
+ manages MathJax async typesetting, and handles scroll restoration.
+*/
-RT = RT || {};
-RT.Element = RT.Element || new Set();
+(function() {
-(function(){
- const debug = RT.Debug || { log: function(){} };
+ if (!window.RT) {
+ console.error("RT not defined - was RT-Manuscript_make run?");
+ return;
+ }
+
+ // Prevent duplicate initialization
+ if (window.RT.Element instanceof Set) {
+ console.warn("RT stage_manager already initialized. Aborting duplicate run.");
+ return;
+ }
+
+ // Phase queues/pointers
+ window.RT.Element = new Set();
+ window.RT.PageStyle = new Set();
+ window.RT.paginate = null;
+
+ const debug = window.RT.Debug || { log: function(){}, warn: function(){}, error: function(){} };
let target_y = 0;
let is_reload = false;
let is_layout_locked = false;
let scroll_timer;
+ // =========================================================
+ // SCROLL & LAYOUT LOCK UTILITIES
+ // =========================================================
+
function lock_layout() {
is_layout_locked = true;
document.documentElement.style.visibility = "hidden";
});
}
- function execute_pagination_and_scroll(){
- debug.log('scroll' ,`Pagination layout starting.`);
- if(RT.paginate_by_element) RT.paginate_by_element();
+ // =========================================================
+ // MASTER PIPELINE EXECUTION
+ // =========================================================
+
+ function run_pipeline() {
- let final_target = target_y;
- let use_hash = false;
- if (window.location.hash && !is_reload) {
- const hash_target = document.getElementById(window.location.hash.substring(1));
- if (hash_target) {
- use_hash = true;
+ // Phase 1: Base Elements
+ debug.log('stage_manager', 'Phase 1: Executing Element tasks');
+ if (window.RT.Element.size > 0) {
+ for (const element_fn of window.RT.Element) {
+ if (typeof element_fn === 'function') {
+ try { element_fn(); }
+ catch (e) { debug.error('stage_manager', "Element task failed: " + e); }
+ } else {
+ debug.warn('stage_manager', 'Invalid element in RT.Element Set: ' + element_fn);
}
+ }
}
- enforce_scroll(final_target ,use_hash ,0);
- }
-
- function process_elements_and_layout() {
- debug.log('lifecycle', 'Processing registered elements.');
+ // Define the continuation (Phases 2 & 3 + Scroll) to run after MathJax resolves
+ const run_post_math_phases = () => {
+ // Phase 2: Pagination
+ debug.log('stage_manager', 'Phase 2: Executing Pagination');
+ if (typeof window.RT.paginate === 'function') {
+ try { window.RT.paginate(); }
+ catch (e) { debug.error('stage_manager', "Pagination failed: " + e); }
+ } else {
+ debug.log('stage_manager', 'No pagination function registered. Skipping.');
+ }
- if (RT.Element instanceof Set && RT.Element.size > 0) {
- for (const element_fn of RT.Element) {
- if (typeof element_fn === 'function') {
- element_fn();
- } else {
- // Fallback warning if your debug utility is not available
- if (RT.Debug?.warn) {
- RT.Debug.warn('layout', 'Invalid element in RT.Element Set: ' + element_fn);
- } else {
- console.warn('Invalid element in RT.Element Set:', element_fn);
+ // Phase 3: Page Styling
+ debug.log('stage_manager', 'Phase 3: Executing PageStyle tasks');
+ if (window.RT.PageStyle.size > 0) {
+ for (const style_fn of window.RT.PageStyle) {
+ if (typeof style_fn === 'function') {
+ try { style_fn(); }
+ catch (e) { debug.error('stage_manager', "PageStyle task failed: " + e); }
}
}
}
- }
- if (window.MathJax && MathJax.Hub && MathJax.Hub.Queue) {
- MathJax.Hub.Queue(["Typeset", MathJax.Hub], execute_pagination_and_scroll);
+ // Final Step: Resolve Scroll Target
+ debug.log('scroll' ,`Pagination layout complete. Enforcing scroll target.`);
+ let final_target = target_y;
+ let use_hash = false;
+ if (window.location.hash && !is_reload) {
+ const hash_target = document.getElementById(window.location.hash.substring(1));
+ if (hash_target) {
+ use_hash = true;
+ }
+ }
+
+ enforce_scroll(final_target ,use_hash ,0);
+ };
+
+ // MathJax Intercept: Await typeset before paginating
+ if (window.MathJax) {
+ if (typeof window.MathJax.typesetPromise === 'function') {
+ window.MathJax.typesetPromise().then(run_post_math_phases).catch((err) => {
+ debug.error('stage_manager', 'MathJax typeset failed: ' + err);
+ run_post_math_phases();
+ });
+ } else if (window.MathJax.Hub && window.MathJax.Hub.Queue) {
+ window.MathJax.Hub.Queue(["Typeset", window.MathJax.Hub], run_post_math_phases);
+ } else {
+ run_post_math_phases();
+ }
} else {
- execute_pagination_and_scroll();
+ run_post_math_phases();
}
}
-
- // Initial Execution
+ // =========================================================
+ // INITIALIZATION
+ // =========================================================
+
lock_layout();
configure_history();
capture_scroll_target();
bind_window_events();
- document.addEventListener('DOMContentLoaded', process_elements_and_layout);
+ document.addEventListener('DOMContentLoaded', run_pipeline);
- // Safety Net: restore visibility on load if layout engine hangs
+ // Safety Net: restore visibility on load if the async layout engine hangs
window.addEventListener("load", unlock_layout);
})();
--- /dev/null
+/*
+ Core/stage_manager.js
+ Orchestrates the execution pipeline to resolve layout dependencies,
+ manages MathJax async typesetting, and handles scroll restoration.
+*/
+
+window.RT = window.RT || {};
+window.RT.Element = window.RT.Element || new Set();
+window.RT.PageStyle = window.RT.PageStyle || new Set();
+window.RT.paginate = null;
+
+(function(){
+ const debug = window.RT.Debug || { log: function(){}, warn: function(){}, error: function(){} };
+
+ let target_y = 0;
+ let is_reload = false;
+ let is_layout_locked = false;
+ let scroll_timer;
+
+ function lock_layout() {
+ is_layout_locked = true;
+ document.documentElement.style.visibility = "hidden";
+ }
+
+ function unlock_layout() {
+ if (!is_layout_locked) return;
+ is_layout_locked = false;
+
+ document.documentElement.style.visibility = "";
+ window.removeEventListener("load", unlock_layout);
+ document.dispatchEvent(new Event("RT_layout_complete"));
+ }
+
+ function configure_history(){
+ if ('scrollRestoration' in history) {
+ history.scrollRestoration = 'manual';
+ }
+ }
+
+ function capture_scroll_target(){
+ const raw_target = sessionStorage.getItem('RT_saved_y');
+ target_y = raw_target !== null ? parseInt(raw_target ,10) : 0;
+
+ if (window.performance) {
+ const nav_entries = performance.getEntriesByType("navigation");
+ if (nav_entries.length > 0) {
+ is_reload = (nav_entries[0].type === "reload");
+ } else if (performance.navigation) {
+ is_reload = (performance.navigation.type === 1);
+ }
+ }
+ }
+
+ function enforce_scroll(target ,use_hash ,attempts){
+ if (attempts > 15) {
+ unlock_layout();
+ return;
+ }
+
+ if (use_hash) {
+ const hash_target = document.getElementById(window.location.hash.substring(1));
+ if (hash_target) {
+ hash_target.scrollIntoView();
+ }
+ } else {
+ window.scrollTo(0 ,target);
+ }
+
+ let is_successful = use_hash ? true : (Math.abs(window.scrollY - target) < 5 || target === 0);
+ if (is_successful && document.body.scrollHeight > 1000) {
+ setTimeout(() => {
+ if (!use_hash && Math.abs(window.scrollY - target) >= 5) {
+ enforce_scroll(target ,use_hash ,attempts + 1);
+ } else {
+ unlock_layout();
+ }
+ }, 100);
+ } else {
+ setTimeout(() => enforce_scroll(target ,use_hash ,attempts + 1) ,50);
+ }
+ }
+
+ function bind_window_events(){
+ window.addEventListener('scroll' ,() => {
+ if (is_layout_locked) return;
+ clearTimeout(scroll_timer);
+ scroll_timer = setTimeout(() => {
+ sessionStorage.setItem('RT_saved_y' ,window.scrollY);
+ }, 200);
+ }, { passive: true });
+
+ window.addEventListener('beforeunload' ,() => {
+ lock_layout();
+ });
+ }
+
+ // =========================================================
+ // PIPELINE EXECUTION
+ // =========================================================
+
+ // Phase 2 & 3: Pagination and Page Styling
+ function execute_phase_2_and_3(){
+ debug.log('stage_manager', 'Phase 2: Executing Pagination');
+ if (typeof window.RT.paginate === 'function') {
+ try { window.RT.paginate(); }
+ catch (e) { debug.error('stage_manager', "Pagination failed: " + e); }
+ }
+
+ debug.log('stage_manager', 'Phase 3: Executing PageStyle tasks');
+ if (window.RT.PageStyle instanceof Set) {
+ window.RT.PageStyle.forEach(task => {
+ if (typeof task === 'function') {
+ try { task(); }
+ catch (e) { debug.error('stage_manager', "PageStyle task failed: " + e); }
+ }
+ });
+ }
+
+ // Now that final layout geometry exists, execute scroll mapping
+ debug.log('scroll' ,`Pagination layout complete. Enforcing scroll target.`);
+ let final_target = target_y;
+ let use_hash = false;
+ if (window.location.hash && !is_reload) {
+ const hash_target = document.getElementById(window.location.hash.substring(1));
+ if (hash_target) {
+ use_hash = true;
+ }
+ }
+
+ enforce_scroll(final_target ,use_hash ,0);
+ }
+
+ // Phase 1: Base Elements
+ function process_elements_and_layout() {
+ debug.log('stage_manager', 'Phase 1: Executing Element tasks');
+
+ if (window.RT.Element instanceof Set && window.RT.Element.size > 0) {
+ for (const element_fn of window.RT.Element) {
+ if (typeof element_fn === 'function') {
+ try { element_fn(); }
+ catch (e) { debug.error('stage_manager', "Element task failed: " + e); }
+ } else {
+ debug.warn('stage_manager', 'Invalid element in RT.Element Set: ' + element_fn);
+ }
+ }
+ }
+
+ // Check for MathJax (Handles both v3 Promises and v2 Hub Queues)
+ // We must wait for typesetting to finish before paginating, as equations change element heights.
+ if (window.MathJax) {
+ if (typeof window.MathJax.typesetPromise === 'function') {
+ window.MathJax.typesetPromise().then(execute_phase_2_and_3).catch((err) => {
+ debug.error('stage_manager', 'MathJax typeset failed: ' + err);
+ execute_phase_2_and_3();
+ });
+ } else if (window.MathJax.Hub && window.MathJax.Hub.Queue) {
+ MathJax.Hub.Queue(["Typeset", MathJax.Hub], execute_phase_2_and_3);
+ } else {
+ execute_phase_2_and_3();
+ }
+ } else {
+ execute_phase_2_and_3();
+ }
+ }
+
+
+ // Initial Execution Sequence
+ lock_layout();
+ configure_history();
+ capture_scroll_target();
+ bind_window_events();
+
+ document.addEventListener('DOMContentLoaded', process_elements_and_layout);
+
+ // Safety Net: restore visibility on load if the async layout engine hangs
+ window.addEventListener("load", unlock_layout);
+
+})();
})();
RT.theme_preference = function(author_pref, default_color = "#FF00FF") {
- const reader_pref = localStorage.getItem('RT-Style·theme_preference');
+ const reader_pref = localStorage.getItem('RT-Manuscript·theme_preference');
const theme_to_load = reader_pref ? reader_pref : author_pref;
RT.theme('load', theme_to_load, default_color);
--- /dev/null
+/*
+ immediate.js
+
+ There are 4 variations for RT-Style-locator.js:
+
+ URL_only - used by websites
+ indirect - used by Harmony projects, except for RT-Manuscript
+
+ immediate - used by RT-Manuscript in the distribution (authored, consummer, staged)
+ direct - used by RT-Manuscript project outside of the distribution, e.g. the main document directory
+*/
+
+window.RT = window.RT || {};
+
+(function() {
+ window.RT.dirpr_library = "..";
+ document.write('<script src="' + window.RT.dirpr_library + '/Core/RT-Style_make.js"><\/script>');
+})();
+++ /dev/null
-/*
- immediate.js
-
- There are 4 variations for RT-Style-locator.js:
-
- URL_only - used by websites
- indirect - used by Harmony projects, except for RT-style
-
- immediate - used by RT-style in the distribution (authored, consummer, staged)
- direct - used by RT-style project outside of the distribution, e.g. the main document directory
-*/
-
-window.RT = window.RT || {};
-
-(function() {
- window.RT.dirpr_library = "..";
- document.write('<script src="' + window.RT.dirpr_library + '/Core/RT-Style_make.js"><\/script>');
-})();
<head>
<meta charset="UTF-8">
<title>RT Style System: Reference Manual</title>
- <script src="RT-Style_locator.js"></script>
+ <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');
window.RT.load('Element/theme_selector');
</script>
+/*
+ Layout/article_tech_ref.js
+ Applies base technical document styling in Phase 1,
+ and applies structural page wrappers in Phase 3.
+*/
+
(function(){
const RT = window.RT = window.RT || {};
'crossref'
];
- // 2. The Extracted Styling Function
- function apply_article_styles() {
- const t = function(...path) { return window.RT.theme('read', ...path); };
+ // Shared utility functions
+ const t = function(...path) { return window.RT.theme('read', ...path); };
+
+ const apply = function(selector, rules) {
+ document.querySelectorAll(selector).forEach(el => {
+ for (let p in rules) {
+ if (typeof rules[p] === 'string' && rules[p].includes('!important')) {
+ el.style.setProperty(p.replace(/[A-Z]/g, m => "-" + m.toLowerCase()), rules[p].replace(' !important', ''), 'important');
+ } else {
+ el.style[p] = rules[p];
+ }
+ }
+ });
+ };
+ // =========================================================
+ // Phase 1: Base Element & Article Styling
+ // =========================================================
+ function apply_base_styles() {
const surface_0 = t('surface', '0');
const surface_code = t('surface', 'code');
const content_main = t('content', 'main');
const font_weight = is_dark === false ? "600" : "400";
- const apply = function(selector, rules) {
- document.querySelectorAll(selector).forEach(el => {
- for (let p in rules) {
- if (typeof rules[p] === 'string' && rules[p].includes('!important')) {
- el.style.setProperty(p.replace(/[A-Z]/g, m => "-" + m.toLowerCase()), rules[p].replace(' !important', ''), 'important');
- } else {
- el.style[p] = rules[p];
- }
- }
- });
- };
-
// Apply base geometry
apply('body, html, RT·article', { overflowAnchor: "none !important" });
apply('RT·article', {
boxSizing: "border-box !important"
});
+ // Default padding if pagination fails or is bypassed
apply('RT·article:not(:has(RT·page))', { padding: "3rem !important" });
- apply('RT·article:has(RT·page)', { padding: "0 !important" });
- apply('RT·article RT·page', {
- position: "relative", display: "block", padding: "3rem",
- margin: "1.25rem auto", backgroundColor: surface_0,
- boxShadow: `0 0 0.625rem ${brand_primary}`
- });
// Apply specific element scales
const element_styles = [
element_styles.forEach(rule => apply(rule[0], rule[1]));
}
+ // =========================================================
+ // Phase 3: Post-Pagination Styling
+ // =========================================================
+ function apply_page_styles() {
+ const surface_0 = t('surface', '0');
+ const brand_primary = t('brand', 'primary');
+
+ // Strip internal padding so the pages can dictate the margin/padding layout
+ apply('RT·article:has(RT·page)', { padding: "0 !important" });
+
+ // Style the actual page wrappers
+ apply('RT·article RT·page', {
+ position: "relative",
+ display: "block",
+ padding: "3rem",
+ margin: "1.25rem auto",
+ backgroundColor: surface_0,
+ boxShadow: `0 0 0.625rem ${brand_primary}`
+ });
+ }
+
//----------------------------------------
- // stuff done when article_tech_ref is loaded
+ // Execution & Registration
//
- // load the element files
+ // Load the element files
required_elements.forEach(name => RT.load('Element/' + name));
- if (RT.Element) {
- RT.Element.add(apply_article_styles);
+ if (RT.Element && RT.PageStyle) {
+ RT.Element.add(apply_base_styles);
+ RT.PageStyle.add(apply_page_styles);
} else {
- console.error("RT.Element not defined, was the state_manager run?");
+ console.error("RT.Element or RT.PageStyle not defined, was the stage_manager run?");
}
})();
--- /dev/null
+/*
+ Processes <RT·article> tags and paginates their contents.
+ Handles inline footnotes and element splitting to enforce page height limits.
+*/
+
+(function() {
+
+ if (!window.RT) {
+ console.error("RT not defined - was RT-Style_make run?");
+ return;
+ }
+ if (!window.RT.Element) {
+ console.error("RT.Element not defined - was the state_manager run?");
+ return;
+ }
+
+ RT.Element.add( function() {
+ const RT = window.RT;
+ const debug = RT.Debug || { log: function(){}, error: function(){} };
+ const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
+ const page_height_limit = page_conf.height_limit || 1000;
+
+ if (debug.log) debug.log('paginate', 'Running document pagination');
+
+ let measureContainer = null;
+
+ // =========================================================
+ // 1. DOM Measurement Utilities
+ // =========================================================
+ function getElHeight(el) {
+ const wasInDOM = el.parentNode !== null;
+ if (!wasInDOM) document.body.appendChild(el);
+ const rect = el.getBoundingClientRect();
+ const style = window.getComputedStyle(el);
+ const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ if (!wasInDOM) el.remove();
+ return (rect.height || 0) + (margin || 0);
+ }
+
+ function getMeasureContainer() {
+ if (measureContainer && measureContainer.parentNode) return measureContainer;
+ const article = document.querySelector('RT·article');
+ if (!article) {
+ const temp = document.createElement('div');
+ temp.style.visibility = 'hidden';
+ temp.style.position = 'absolute';
+ temp.style.width = '100%';
+ document.body.appendChild(temp);
+ measureContainer = temp;
+ return temp;
+ }
+ const container = document.createElement('div');
+ const articleStyle = window.getComputedStyle(article);
+ container.style.visibility = 'hidden';
+ container.style.position = 'absolute';
+ container.style.width = articleStyle.width;
+ container.style.fontFamily = articleStyle.fontFamily;
+ container.style.fontSize = articleStyle.fontSize;
+ container.style.lineHeight = articleStyle.lineHeight;
+ container.style.fontWeight = articleStyle.fontWeight;
+ document.body.appendChild(container);
+ measureContainer = container;
+ return container;
+ }
+
+ function measureFragment(frag) {
+ const container = getMeasureContainer();
+ container.appendChild(frag);
+ const h = getElHeight(frag);
+ container.removeChild(frag);
+ return h;
+ }
+
+ // =========================================================
+ // STEP 1: PREPARE FOOTNOTES (Strip and tag)
+ // =========================================================
+ const article_seq = document.querySelectorAll('RT·article');
+ if (article_seq.length === 0) {
+ debug.error('pagination', 'No <RT·article> elements found. Pagination aborted.');
+ return;
+ }
+
+ const footnote_registry = {};
+ let footnote_counter = 1;
+
+ Array.from(article_seq).forEach(article => {
+ // Bulletproof extraction: immune to XML/HTML case-sensitivity parsing quirks
+ const all_nodes = Array.from(article.querySelectorAll('*'));
+ const raw_footnotes = all_nodes.filter(node => node.tagName.toLowerCase() === 'RT·footnote');
+
+ raw_footnotes.forEach(fn => {
+ const id = footnote_counter++;
+ footnote_registry[id] = fn.innerHTML; // Save the payload
+
+ // Trim any standard HTML whitespace immediately preceding the tag
+ const prev = fn.previousSibling;
+ if (prev && prev.nodeType === Node.TEXT_NODE) {
+ prev.textContent = prev.textContent.replace(/\s+$/, '');
+ }
+
+ // Replace with a zero-height marker that rides along with the text
+ const marker = document.createElement('RT·fn-marker');
+ marker.setAttribute('data-id', id);
+
+ if (fn.parentNode) {
+ fn.parentNode.replaceChild(marker, fn);
+ }
+ });
+ });
+
+ // =========================================================
+ // Splitting Logic (Clean and undisturbed)
+ // =========================================================
+ function isSplittable(el) {
+ const tag = el.tagName;
+ if (tag === 'UL' || tag === 'OL') {
+ const items = Array.from(el.children).filter(c => c.tagName === 'LI');
+ if (items.length === 0) return null;
+
+ const itemHeights = items.map(li => getElHeight(li));
+ const emptyClone = el.cloneNode(false);
+ const overhead = getElHeight(emptyClone);
+
+ el._splitInfo = { type: 'list', itemHeights, overhead, offset: 0 };
+ return makeListSplitter(el, el._splitInfo);
+ }
+
+ if (tag === 'TABLE') {
+ const thead = el.querySelector('thead');
+ const tbody = el.querySelector('tbody');
+ const rows = tbody ? Array.from(tbody.rows) : Array.from(el.rows);
+ if (rows.length === 0) return null;
+
+ const theadHeight = thead ? getElHeight(thead) : 0;
+ const rowHeights = rows.map(row => getElHeight(row));
+
+ const emptyClone = el.cloneNode(false);
+ if (thead) emptyClone.appendChild(thead.cloneNode(true));
+ emptyClone.appendChild(document.createElement('tbody'));
+ const overhead = getElHeight(emptyClone) - theadHeight;
+
+ el._splitInfo = { type: 'table', rowHeights, overhead, theadHeight, offset: 0 };
+ return makeTableSplitter(el, el._splitInfo);
+ }
+ return null;
+ }
+
+ function makeListSplitter(el, info) {
+ return (remaining) => {
+ const children = Array.from(el.children).filter(c => c.tagName === 'LI');
+ const start = info.offset;
+
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempList = el.cloneNode(false);
+
+ for (let i = 0; i < children.length; i++) {
+ const itemClone = children[i].cloneNode(true);
+ tempList.appendChild(itemClone);
+ const fragHeight = measureFragment(tempList);
+ if (fragHeight <= remaining) {
+ bestCount = i + 1;
+ bestHeight = fragHeight;
+ } else {
+ tempList.removeChild(itemClone);
+ break;
+ }
+ }
+
+ if (bestCount === 0) return { first: null, rest: el, firstHeight: 0 };
+
+ const first = el.cloneNode(false);
+ for (let i = 0; i < bestCount; i++) {
+ first.appendChild(children[i].cloneNode(true));
+ }
+
+ let rest = null;
+ if (bestCount < children.length) {
+ rest = el.cloneNode(false);
+ for (let i = bestCount; i < children.length; i++) {
+ rest.appendChild(children[i].cloneNode(true));
+ }
+
+ if (el.tagName === 'OL') {
+ const currentStart = parseInt(el.getAttribute('start'), 10) || 1;
+ rest.setAttribute('start', currentStart + bestCount);
+ }
+
+ rest._splitInfo = {
+ type: 'list',
+ itemHeights: info.itemHeights,
+ overhead: info.overhead,
+ offset: start + bestCount
+ };
+ }
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ function makeTableSplitter(el, info) {
+ const thead = el.querySelector('thead');
+ const createShell = () => {
+ const shell = el.cloneNode(false);
+ if (thead) shell.appendChild(thead.cloneNode(true));
+ const newTbody = document.createElement('tbody');
+ shell.appendChild(newTbody);
+ return shell;
+ };
+
+ return (remaining) => {
+ const tbody = el.querySelector('tbody');
+ const rows = tbody ? Array.from(tbody.rows) : Array.from(el.rows);
+ const start = info.offset;
+
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempTable = createShell();
+ const tempBody = tempTable.querySelector('tbody');
+
+ for (let i = 0; i < rows.length; i++) {
+ tempBody.appendChild(rows[i].cloneNode(true));
+ const h = measureFragment(tempTable);
+ if (h <= remaining) {
+ bestCount = i + 1;
+ bestHeight = h;
+ } else {
+ tempBody.removeChild(tempBody.lastChild);
+ break;
+ }
+ }
+
+ if (bestCount === 0) return { first: null, rest: el, firstHeight: 0 };
+
+ const first = createShell();
+ const firstBody = first.querySelector('tbody');
+ for (let i = 0; i < bestCount; i++) {
+ firstBody.appendChild(rows[i].cloneNode(true));
+ }
+
+ let rest = null;
+ if (bestCount < rows.length) {
+ rest = createShell();
+ const restBody = rest.querySelector('tbody');
+ for (let i = bestCount; i < rows.length; i++) {
+ restBody.appendChild(rows[i].cloneNode(true));
+ }
+
+ rest._splitInfo = {
+ type: 'table',
+ rowHeights: info.rowHeights,
+ overhead: info.overhead,
+ theadHeight: info.theadHeight,
+ offset: start + bestCount
+ };
+ }
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ // =========================================================
+ // STEP 2: NORMAL PAGINATOR
+ // =========================================================
+ function paginateArticle(article) {
+ const raw_element_seq = Array.from(article.children).filter(el =>
+ !['SCRIPT', 'STYLE', 'RT·PAGE'].includes(el.tagName)
+ );
+
+ if (raw_element_seq.length === 0) return;
+
+ const page_seq = [];
+ let current_batch_seq = [];
+ let current_h = 0;
+ let i = 0;
+
+ while (i < raw_element_seq.length) {
+ const el = raw_element_seq[i];
+ const splitter = isSplittable(el);
+
+ if (splitter) {
+ const remaining = page_height_limit - current_h;
+ const { first, rest, firstHeight } = splitter(remaining);
+
+ if (first) {
+ current_batch_seq.push(first);
+ current_h += firstHeight;
+
+ if (rest) {
+ raw_element_seq.splice(i, 1, rest);
+ } else {
+ raw_element_seq.splice(i, 1);
+ }
+ } else {
+ if (current_batch_seq.length === 0) {
+ const frame = document.createElement('RT·scroll-frame');
+ frame.style.display = 'block';
+ frame.style.overflowY = 'auto';
+ frame.style.maxHeight = page_height_limit + 'px';
+ frame.appendChild(el);
+ current_batch_seq.push(frame);
+ i++;
+ } else {
+ page_seq.push(current_batch_seq);
+ current_batch_seq = [];
+ current_h = 0;
+ raw_element_seq[i] = rest || el;
+ }
+ }
+ continue;
+ }
+
+
+ // --- Ordinary (non-splittable) element ---
+ const h = getElHeight(el);
+ const is_RT_page_break = el.tagName && el.tagName.toLowerCase() === 'rt·page-break';
+
+ if( (is_RT_page_break || current_h + h > page_height_limit) && current_batch_seq.length > 0 ){
+ let backtrack_seq = [];
+ let backtrack_h = 0;
+
+ while (current_batch_seq.length > 0) {
+ const last = current_batch_seq[current_batch_seq.length - 1];
+ if (!/^H[1-6]/.test(last.tagName)) break;
+ const popped = current_batch_seq.pop();
+ backtrack_seq.unshift(popped);
+ backtrack_h += getElHeight(popped);
+ }
+
+ if (current_batch_seq.length > 0) {
+ page_seq.push(current_batch_seq);
+ current_batch_seq = backtrack_seq;
+ current_h = backtrack_h;
+ } else {
+ page_seq.push(backtrack_seq);
+ current_batch_seq = [];
+ current_h = 0;
+ }
+ }
+
+ current_batch_seq.push(el);
+ current_h += h;
+ i++;
+ }
+
+ if (current_batch_seq.length > 0) {
+ page_seq.push(current_batch_seq);
+ }
+
+ // Rebuild article with <RT·page> wrappers
+ article.innerHTML = '';
+ let p = 0;
+ while (p < page_seq.length) {
+ const batch = page_seq[p];
+ const page_el = document.createElement('RT·page');
+ page_el.id = `page-${p + 1}`;
+ batch.forEach(item => page_el.appendChild(item));
+ article.appendChild(page_el);
+ p++;
+ }
+ }
+
+ // Execute pagination
+ Array.from(article_seq).forEach(article => paginateArticle(article));
+
+ // =========================================================
+ // STEP 3: RESOLVE FOOTNOTES & EXPAND PAGES
+ // =========================================================
+ Array.from(article_seq).forEach(article => {
+ const rendered_pages = article.querySelectorAll('RT·page');
+
+ Array.from(rendered_pages).forEach(page => {
+ // Bulletproof extraction for the markers
+ const all_page_nodes = Array.from(page.querySelectorAll('*'));
+ const markers = all_page_nodes.filter(node => node.tagName.toLowerCase() === 'rt·fn-marker');
+
+ if (markers.length === 0) return;
+
+ // Construct the footer block for this page
+ const fn_container = document.createElement('div');
+ fn_container.className = 'RT·footnote-container';
+ fn_container.style.borderTop = '1px solid var(--RT·border-default)';
+ fn_container.style.marginTop = '2rem';
+ fn_container.style.paddingTop = '1rem';
+ fn_container.style.fontSize = '0.9em';
+
+ markers.forEach(marker => {
+ const id = marker.getAttribute('data-id');
+ const html = footnote_registry[id];
+
+ // Replace the invisible marker with the visible naked superscript link
+ const sup = document.createElement('sup');
+ sup.innerHTML = `<a href="#fn-${id}" id="fn-ref-${id}" style="color: var(--RT·brand-link); text-decoration: none;">${id}</a>`;
+
+ if (marker.parentNode) {
+ marker.parentNode.replaceChild(sup, marker);
+ }
+
+ // Append the actual text to the footer with a clean, print-ready number format
+ const fn_line = document.createElement('div');
+ fn_line.id = `fn-${id}`;
+ fn_line.style.marginBottom = '0.5rem';
+ fn_line.innerHTML = `<span style="padding-right: 0.5em; font-weight: 600;">${id}.</span>${html}`;
+ fn_container.appendChild(fn_line);
+ });
+
+ // Attach the footer. The page organically stretches to fit.
+ page.appendChild(fn_container);
+ });
+ });
+
+ // Cleanup
+ if (measureContainer && measureContainer.parentNode) {
+ measureContainer.remove();
+ measureContainer = null;
+ }
+ });
+
+})();
--- /dev/null
+/*
+ Processes <RT·article> tags and paginates their contents.
+ Handles inline footnotes and element splitting to enforce page height limits.
+*/
+
+(function() {
+
+ if (!window.RT) {
+ console.error("RT not defined - was RT-Style_make run?");
+ return;
+ }
+
+ window.RT.paginate = function() {
+ const RT = window.RT;
+ const debug = RT.Debug || { log: function(){}, error: function(){} };
+ const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
+ const page_height_limit = page_conf.height_limit || 1000;
+
+ if (debug.log) debug.log('paginate', 'Running document pagination');
+
+ let measureContainer = null;
+
+ // =========================================================
+ // 1. DOM Measurement Utilities
+ // =========================================================
+ function getElHeight(el) {
+ const wasInDOM = el.parentNode !== null;
+ if (!wasInDOM) document.body.appendChild(el);
+ const rect = el.getBoundingClientRect();
+ const style = window.getComputedStyle(el);
+ const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ if (!wasInDOM) el.remove();
+ return (rect.height || 0) + (margin || 0);
+ }
+
+ function getMeasureContainer() {
+ if (measureContainer && measureContainer.parentNode) return measureContainer;
+ const article = document.querySelector('RT·article');
+ if (!article) {
+ const temp = document.createElement('div');
+ temp.style.visibility = 'hidden';
+ temp.style.position = 'absolute';
+ temp.style.width = '100%';
+ document.body.appendChild(temp);
+ measureContainer = temp;
+ return temp;
+ }
+ const container = document.createElement('div');
+ const articleStyle = window.getComputedStyle(article);
+ container.style.visibility = 'hidden';
+ container.style.position = 'absolute';
+ container.style.width = articleStyle.width;
+ container.style.fontFamily = articleStyle.fontFamily;
+ container.style.fontSize = articleStyle.fontSize;
+ container.style.lineHeight = articleStyle.lineHeight;
+ container.style.fontWeight = articleStyle.fontWeight;
+ document.body.appendChild(container);
+ measureContainer = container;
+ return container;
+ }
+
+ function measureFragment(frag) {
+ const container = getMeasureContainer();
+ container.appendChild(frag);
+ const h = getElHeight(frag);
+ container.removeChild(frag);
+ return h;
+ }
+
+ // =========================================================
+ // STEP 1: PREPARE FOOTNOTES (Strip and tag)
+ // =========================================================
+ const article_seq = document.querySelectorAll('RT·article');
+ if (article_seq.length === 0) {
+ debug.error('pagination', 'No <RT·article> elements found. Pagination aborted.');
+ return;
+ }
+
+ const footnote_registry = {};
+ let footnote_counter = 1;
+
+ Array.from(article_seq).forEach(article => {
+ const all_nodes = Array.from(article.querySelectorAll('*'));
+ const raw_footnotes = all_nodes.filter(node => node.tagName.toLowerCase() === 'rt·footnote');
+
+ raw_footnotes.forEach(fn => {
+ const id = footnote_counter++;
+ footnote_registry[id] = fn.innerHTML;
+
+ const prev = fn.previousSibling;
+ if (prev && prev.nodeType === Node.TEXT_NODE) {
+ prev.textContent = prev.textContent.replace(/\s+$/, '');
+ }
+
+ const marker = document.createElement('RT·fn-marker');
+ marker.setAttribute('data-id', id);
+
+ if (fn.parentNode) {
+ fn.parentNode.replaceChild(marker, fn);
+ }
+ });
+ });
+
+ // =========================================================
+ // Splitting Logic
+ // =========================================================
+ function isSplittable(el) {
+ const tag = el.tagName;
+ if (tag === 'UL' || tag === 'OL') {
+ const items = Array.from(el.children).filter(c => c.tagName === 'LI');
+ if (items.length === 0) return null;
+
+ const itemHeights = items.map(li => getElHeight(li));
+ const emptyClone = el.cloneNode(false);
+ const overhead = getElHeight(emptyClone);
+
+ el._splitInfo = { type: 'list', itemHeights, overhead, offset: 0 };
+ return makeListSplitter(el, el._splitInfo);
+ }
+
+ if (tag === 'TABLE') {
+ const thead = el.querySelector('thead');
+ const tbody = el.querySelector('tbody');
+ const rows = tbody ? Array.from(tbody.rows) : Array.from(el.rows);
+ if (rows.length === 0) return null;
+
+ const theadHeight = thead ? getElHeight(thead) : 0;
+ const rowHeights = rows.map(row => getElHeight(row));
+
+ const emptyClone = el.cloneNode(false);
+ if (thead) emptyClone.appendChild(thead.cloneNode(true));
+ emptyClone.appendChild(document.createElement('tbody'));
+ const overhead = getElHeight(emptyClone) - theadHeight;
+
+ el._splitInfo = { type: 'table', rowHeights, overhead, theadHeight, offset: 0 };
+ return makeTableSplitter(el, el._splitInfo);
+ }
+ return null;
+ }
+
+ function makeListSplitter(el, info) {
+ return (remaining) => {
+ const children = Array.from(el.children).filter(c => c.tagName === 'LI');
+ const start = info.offset;
+
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempList = el.cloneNode(false);
+
+ for (let i = 0; i < children.length; i++) {
+ const itemClone = children[i].cloneNode(true);
+ tempList.appendChild(itemClone);
+ const fragHeight = measureFragment(tempList);
+ if (fragHeight <= remaining) {
+ bestCount = i + 1;
+ bestHeight = fragHeight;
+ } else {
+ tempList.removeChild(itemClone);
+ break;
+ }
+ }
+
+ if (bestCount === 0) return { first: null, rest: el, firstHeight: 0 };
+
+ const first = el.cloneNode(false);
+ for (let i = 0; i < bestCount; i++) {
+ first.appendChild(children[i].cloneNode(true));
+ }
+
+ let rest = null;
+ if (bestCount < children.length) {
+ rest = el.cloneNode(false);
+ for (let i = bestCount; i < children.length; i++) {
+ rest.appendChild(children[i].cloneNode(true));
+ }
+
+ if (el.tagName === 'OL') {
+ const currentStart = parseInt(el.getAttribute('start'), 10) || 1;
+ rest.setAttribute('start', currentStart + bestCount);
+ }
+
+ rest._splitInfo = {
+ type: 'list',
+ itemHeights: info.itemHeights,
+ overhead: info.overhead,
+ offset: start + bestCount
+ };
+ }
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ function makeTableSplitter(el, info) {
+ const thead = el.querySelector('thead');
+ const createShell = () => {
+ const shell = el.cloneNode(false);
+ if (thead) shell.appendChild(thead.cloneNode(true));
+ const newTbody = document.createElement('tbody');
+ shell.appendChild(newTbody);
+ return shell;
+ };
+
+ return (remaining) => {
+ const tbody = el.querySelector('tbody');
+ const rows = tbody ? Array.from(tbody.rows) : Array.from(el.rows);
+ const start = info.offset;
+
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempTable = createShell();
+ const tempBody = tempTable.querySelector('tbody');
+
+ for (let i = 0; i < rows.length; i++) {
+ tempBody.appendChild(rows[i].cloneNode(true));
+ const h = measureFragment(tempTable);
+ if (h <= remaining) {
+ bestCount = i + 1;
+ bestHeight = h;
+ } else {
+ tempBody.removeChild(tempBody.lastChild);
+ break;
+ }
+ }
+
+ if (bestCount === 0) return { first: null, rest: el, firstHeight: 0 };
+
+ const first = createShell();
+ const firstBody = first.querySelector('tbody');
+ for (let i = 0; i < bestCount; i++) {
+ firstBody.appendChild(rows[i].cloneNode(true));
+ }
+
+ let rest = null;
+ if (bestCount < rows.length) {
+ rest = createShell();
+ const restBody = rest.querySelector('tbody');
+ for (let i = bestCount; i < rows.length; i++) {
+ restBody.appendChild(rows[i].cloneNode(true));
+ }
+
+ rest._splitInfo = {
+ type: 'table',
+ rowHeights: info.rowHeights,
+ overhead: info.overhead,
+ theadHeight: info.theadHeight,
+ offset: start + bestCount
+ };
+ }
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ // =========================================================
+ // STEP 2: NORMAL PAGINATOR
+ // =========================================================
+ function paginateArticle(article) {
+ const raw_element_seq = Array.from(article.children).filter(el =>
+ !['SCRIPT', 'STYLE', 'RT·PAGE'].includes(el.tagName)
+ );
+
+ if (raw_element_seq.length === 0) return;
+
+ const page_seq = [];
+ let current_batch_seq = [];
+ let current_h = 0;
+ let i = 0;
+
+ while (i < raw_element_seq.length) {
+ const el = raw_element_seq[i];
+ const splitter = isSplittable(el);
+
+ if (splitter) {
+ const remaining = page_height_limit - current_h;
+ const { first, rest, firstHeight } = splitter(remaining);
+
+ if (first) {
+ current_batch_seq.push(first);
+ current_h += firstHeight;
+
+ if (rest) {
+ raw_element_seq.splice(i, 1, rest);
+ } else {
+ raw_element_seq.splice(i, 1);
+ }
+ } else {
+ if (current_batch_seq.length === 0) {
+ const frame = document.createElement('RT·scroll-frame');
+ frame.style.display = 'block';
+ frame.style.overflowY = 'auto';
+ frame.style.maxHeight = page_height_limit + 'px';
+ frame.appendChild(el);
+ current_batch_seq.push(frame);
+ i++;
+ } else {
+ page_seq.push(current_batch_seq);
+ current_batch_seq = [];
+ current_h = 0;
+ raw_element_seq[i] = rest || el;
+ }
+ }
+ continue;
+ }
+
+ const h = getElHeight(el);
+ const is_RT_page_break = el.tagName && el.tagName.toLowerCase() === 'rt·page-break';
+
+ if( (is_RT_page_break || current_h + h > page_height_limit) && current_batch_seq.length > 0 ){
+ let backtrack_seq = [];
+ let backtrack_h = 0;
+
+ while (current_batch_seq.length > 0) {
+ const last = current_batch_seq[current_batch_seq.length - 1];
+ if (!/^H[1-6]/.test(last.tagName)) break;
+ const popped = current_batch_seq.pop();
+ backtrack_seq.unshift(popped);
+ backtrack_h += getElHeight(popped);
+ }
+
+ if (current_batch_seq.length > 0) {
+ page_seq.push(current_batch_seq);
+ current_batch_seq = backtrack_seq;
+ current_h = backtrack_h;
+ } else {
+ page_seq.push(backtrack_seq);
+ current_batch_seq = [];
+ current_h = 0;
+ }
+ }
+
+ current_batch_seq.push(el);
+ current_h += h;
+ i++;
+ }
+
+ if (current_batch_seq.length > 0) {
+ page_seq.push(current_batch_seq);
+ }
+
+ article.innerHTML = '';
+ let p = 0;
+ while (p < page_seq.length) {
+ const batch = page_seq[p];
+ const page_el = document.createElement('RT·page');
+ page_el.id = `page-${p + 1}`;
+ batch.forEach(item => page_el.appendChild(item));
+ article.appendChild(page_el);
+ p++;
+ }
+ }
+
+ Array.from(article_seq).forEach(article => paginateArticle(article));
+
+ // =========================================================
+ // STEP 3: RESOLVE FOOTNOTES
+ // =========================================================
+ Array.from(article_seq).forEach(article => {
+ const rendered_pages = article.querySelectorAll('RT·page');
+
+ Array.from(rendered_pages).forEach(page => {
+ const all_page_nodes = Array.from(page.querySelectorAll('*'));
+ const markers = all_page_nodes.filter(node => node.tagName.toLowerCase() === 'rt·fn-marker');
+
+ if (markers.length === 0) return;
+
+ const fn_container = document.createElement('div');
+ fn_container.className = 'RT·footnote-container';
+ fn_container.style.borderTop = '1px solid var(--RT·border-default)';
+ fn_container.style.marginTop = '2rem';
+ fn_container.style.paddingTop = '1rem';
+ fn_container.style.fontSize = '0.9em';
+
+ markers.forEach(marker => {
+ const id = marker.getAttribute('data-id');
+ const html = footnote_registry[id];
+
+ const sup = document.createElement('sup');
+ sup.innerHTML = `<a href="#fn-${id}" id="fn-ref-${id}" style="color: var(--RT·brand-link); text-decoration: none;">${id}</a>`;
+
+ if (marker.parentNode) {
+ marker.parentNode.replaceChild(sup, marker);
+ }
+
+ const fn_line = document.createElement('div');
+ fn_line.id = `fn-${id}`;
+ fn_line.style.marginBottom = '0.5rem';
+ fn_line.innerHTML = `<span style="padding-right: 0.5em; font-weight: 600;">${id}.</span>${html}`;
+ fn_container.appendChild(fn_line);
+ });
+
+ page.appendChild(fn_container);
+ });
+ });
+
+ if (measureContainer && measureContainer.parentNode) {
+ measureContainer.remove();
+ measureContainer = null;
+ }
+ };
+})();
+++ /dev/null
-window.RT.paginate_by_element = function () {
- const RT = window.RT;
- const debug = RT.Debug || { log: function(){}, error: function(){} };
- const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
- const page_height_limit = page_conf.height_limit || 1000;
-
- let measureContainer = null;
-
- // =========================================================
- // 1. DOM Measurement Utilities
- // =========================================================
- function getElHeight(el) {
- const wasInDOM = el.parentNode !== null;
- if (!wasInDOM) document.body.appendChild(el);
- const rect = el.getBoundingClientRect();
- const style = window.getComputedStyle(el);
- const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
- if (!wasInDOM) el.remove();
- return (rect.height || 0) + (margin || 0);
- }
-
- function getMeasureContainer() {
- if (measureContainer && measureContainer.parentNode) return measureContainer;
- const article = document.querySelector('RT·article');
- if (!article) {
- const temp = document.createElement('div');
- temp.style.visibility = 'hidden';
- temp.style.position = 'absolute';
- temp.style.width = '100%';
- document.body.appendChild(temp);
- measureContainer = temp;
- return temp;
- }
- const container = document.createElement('div');
- const articleStyle = window.getComputedStyle(article);
- container.style.visibility = 'hidden';
- container.style.position = 'absolute';
- container.style.width = articleStyle.width;
- container.style.fontFamily = articleStyle.fontFamily;
- container.style.fontSize = articleStyle.fontSize;
- container.style.lineHeight = articleStyle.lineHeight;
- container.style.fontWeight = articleStyle.fontWeight;
- document.body.appendChild(container);
- measureContainer = container;
- return container;
- }
-
- function measureFragment(frag) {
- const container = getMeasureContainer();
- container.appendChild(frag);
- const h = getElHeight(frag);
- container.removeChild(frag);
- return h;
- }
-
- // =========================================================
- // STEP 1: PREPARE FOOTNOTES (Strip and tag)
- // =========================================================
- const article_seq = document.querySelectorAll('RT·article');
- if (article_seq.length === 0) {
- debug.error('pagination', 'No <RT·article> elements found. Pagination aborted.');
- return;
- }
-
- const footnote_registry = {};
- let footnote_counter = 1;
-
- Array.from(article_seq).forEach(article => {
- // Bulletproof extraction: immune to XML/HTML case-sensitivity parsing quirks
- const all_nodes = Array.from(article.querySelectorAll('*'));
- const raw_footnotes = all_nodes.filter(node => node.tagName.toLowerCase() === 'RT·footnote');
-
- raw_footnotes.forEach(fn => {
- const id = footnote_counter++;
- footnote_registry[id] = fn.innerHTML; // Save the payload
-
- // Trim any standard HTML whitespace immediately preceding the tag
- const prev = fn.previousSibling;
- if (prev && prev.nodeType === Node.TEXT_NODE) {
- prev.textContent = prev.textContent.replace(/\s+$/, '');
- }
-
- // Replace with a zero-height marker that rides along with the text
- const marker = document.createElement('RT·fn-marker');
- marker.setAttribute('data-id', id);
-
- if (fn.parentNode) {
- fn.parentNode.replaceChild(marker, fn);
- }
- });
- });
-
- // =========================================================
- // Splitting Logic (Clean and undisturbed)
- // =========================================================
- function isSplittable(el) {
- const tag = el.tagName;
- if (tag === 'UL' || tag === 'OL') {
- const items = Array.from(el.children).filter(c => c.tagName === 'LI');
- if (items.length === 0) return null;
-
- const itemHeights = items.map(li => getElHeight(li));
- const emptyClone = el.cloneNode(false);
- const overhead = getElHeight(emptyClone);
-
- el._splitInfo = { type: 'list', itemHeights, overhead, offset: 0 };
- return makeListSplitter(el, el._splitInfo);
- }
-
- if (tag === 'TABLE') {
- const thead = el.querySelector('thead');
- const tbody = el.querySelector('tbody');
- const rows = tbody ? Array.from(tbody.rows) : Array.from(el.rows);
- if (rows.length === 0) return null;
-
- const theadHeight = thead ? getElHeight(thead) : 0;
- const rowHeights = rows.map(row => getElHeight(row));
-
- const emptyClone = el.cloneNode(false);
- if (thead) emptyClone.appendChild(thead.cloneNode(true));
- emptyClone.appendChild(document.createElement('tbody'));
- const overhead = getElHeight(emptyClone) - theadHeight;
-
- el._splitInfo = { type: 'table', rowHeights, overhead, theadHeight, offset: 0 };
- return makeTableSplitter(el, el._splitInfo);
- }
- return null;
- }
-
- function makeListSplitter(el, info) {
- return (remaining) => {
- const children = Array.from(el.children).filter(c => c.tagName === 'LI');
- const start = info.offset;
-
- let bestCount = 0;
- let bestHeight = 0;
- const tempList = el.cloneNode(false);
-
- for (let i = 0; i < children.length; i++) {
- const itemClone = children[i].cloneNode(true);
- tempList.appendChild(itemClone);
- const fragHeight = measureFragment(tempList);
- if (fragHeight <= remaining) {
- bestCount = i + 1;
- bestHeight = fragHeight;
- } else {
- tempList.removeChild(itemClone);
- break;
- }
- }
-
- if (bestCount === 0) return { first: null, rest: el, firstHeight: 0 };
-
- const first = el.cloneNode(false);
- for (let i = 0; i < bestCount; i++) {
- first.appendChild(children[i].cloneNode(true));
- }
-
- let rest = null;
- if (bestCount < children.length) {
- rest = el.cloneNode(false);
- for (let i = bestCount; i < children.length; i++) {
- rest.appendChild(children[i].cloneNode(true));
- }
-
- if (el.tagName === 'OL') {
- const currentStart = parseInt(el.getAttribute('start'), 10) || 1;
- rest.setAttribute('start', currentStart + bestCount);
- }
-
- rest._splitInfo = {
- type: 'list',
- itemHeights: info.itemHeights,
- overhead: info.overhead,
- offset: start + bestCount
- };
- }
-
- return { first, rest, firstHeight: bestHeight };
- };
- }
-
- function makeTableSplitter(el, info) {
- const thead = el.querySelector('thead');
- const createShell = () => {
- const shell = el.cloneNode(false);
- if (thead) shell.appendChild(thead.cloneNode(true));
- const newTbody = document.createElement('tbody');
- shell.appendChild(newTbody);
- return shell;
- };
-
- return (remaining) => {
- const tbody = el.querySelector('tbody');
- const rows = tbody ? Array.from(tbody.rows) : Array.from(el.rows);
- const start = info.offset;
-
- let bestCount = 0;
- let bestHeight = 0;
- const tempTable = createShell();
- const tempBody = tempTable.querySelector('tbody');
-
- for (let i = 0; i < rows.length; i++) {
- tempBody.appendChild(rows[i].cloneNode(true));
- const h = measureFragment(tempTable);
- if (h <= remaining) {
- bestCount = i + 1;
- bestHeight = h;
- } else {
- tempBody.removeChild(tempBody.lastChild);
- break;
- }
- }
-
- if (bestCount === 0) return { first: null, rest: el, firstHeight: 0 };
-
- const first = createShell();
- const firstBody = first.querySelector('tbody');
- for (let i = 0; i < bestCount; i++) {
- firstBody.appendChild(rows[i].cloneNode(true));
- }
-
- let rest = null;
- if (bestCount < rows.length) {
- rest = createShell();
- const restBody = rest.querySelector('tbody');
- for (let i = bestCount; i < rows.length; i++) {
- restBody.appendChild(rows[i].cloneNode(true));
- }
-
- rest._splitInfo = {
- type: 'table',
- rowHeights: info.rowHeights,
- overhead: info.overhead,
- theadHeight: info.theadHeight,
- offset: start + bestCount
- };
- }
-
- return { first, rest, firstHeight: bestHeight };
- };
- }
-
- // =========================================================
- // STEP 2: NORMAL PAGINATOR
- // =========================================================
- function paginateArticle(article) {
- const raw_element_seq = Array.from(article.children).filter(el =>
- !['SCRIPT', 'STYLE', 'RT·PAGE'].includes(el.tagName)
- );
-
- if (raw_element_seq.length === 0) return;
-
- const page_seq = [];
- let current_batch_seq = [];
- let current_h = 0;
- let i = 0;
-
- while (i < raw_element_seq.length) {
- const el = raw_element_seq[i];
- const splitter = isSplittable(el);
-
- if (splitter) {
- const remaining = page_height_limit - current_h;
- const { first, rest, firstHeight } = splitter(remaining);
-
- if (first) {
- current_batch_seq.push(first);
- current_h += firstHeight;
-
- if (rest) {
- raw_element_seq.splice(i, 1, rest);
- } else {
- raw_element_seq.splice(i, 1);
- }
- } else {
- if (current_batch_seq.length === 0) {
- const frame = document.createElement('RT·scroll-frame');
- frame.style.display = 'block';
- frame.style.overflowY = 'auto';
- frame.style.maxHeight = page_height_limit + 'px';
- frame.appendChild(el);
- current_batch_seq.push(frame);
- i++;
- } else {
- page_seq.push(current_batch_seq);
- current_batch_seq = [];
- current_h = 0;
- raw_element_seq[i] = rest || el;
- }
- }
- continue;
- }
-
-
- // --- Ordinary (non-splittable) element ---
- const h = getElHeight(el);
- const is_RT_page_break = el.tagName && el.tagName.toLowerCase() === 'RT·page-break';
-
- if( (is_RT_page_break || current_h + h > page_height_limit) && current_batch_seq.length > 0 ){
- let backtrack_seq = [];
- let backtrack_h = 0;
-
- while (current_batch_seq.length > 0) {
- const last = current_batch_seq[current_batch_seq.length - 1];
- if (!/^H[1-6]/.test(last.tagName)) break;
- const popped = current_batch_seq.pop();
- backtrack_seq.unshift(popped);
- backtrack_h += getElHeight(popped);
- }
-
- if (current_batch_seq.length > 0) {
- page_seq.push(current_batch_seq);
- current_batch_seq = backtrack_seq;
- current_h = backtrack_h;
- } else {
- page_seq.push(backtrack_seq);
- current_batch_seq = [];
- current_h = 0;
- }
- }
-
- current_batch_seq.push(el);
- current_h += h;
- i++;
- }
-
- if (current_batch_seq.length > 0) {
- page_seq.push(current_batch_seq);
- }
-
- // Rebuild article with <RT·page> wrappers
- article.innerHTML = '';
- let p = 0;
- while (p < page_seq.length) {
- const batch = page_seq[p];
- const page_el = document.createElement('RT·page');
- page_el.id = `page-${p + 1}`;
- batch.forEach(item => page_el.appendChild(item));
- article.appendChild(page_el);
- p++;
- }
- }
-
- // Execute pagination
- Array.from(article_seq).forEach(article => paginateArticle(article));
-
- // =========================================================
- // STEP 3: RESOLVE FOOTNOTES & EXPAND PAGES
- // =========================================================
- Array.from(article_seq).forEach(article => {
- const rendered_pages = article.querySelectorAll('RT·page');
-
- Array.from(rendered_pages).forEach(page => {
- // Bulletproof extraction for the markers
- const all_page_nodes = Array.from(page.querySelectorAll('*'));
- const markers = all_page_nodes.filter(node => node.tagName.toLowerCase() === 'RT·fn-marker');
-
- if (markers.length === 0) return;
-
- // Construct the footer block for this page
- const fn_container = document.createElement('div');
- fn_container.className = 'RT·footnote-container';
- fn_container.style.borderTop = '1px solid var(--RT·border-default)';
- fn_container.style.marginTop = '2rem';
- fn_container.style.paddingTop = '1rem';
- fn_container.style.fontSize = '0.9em';
-
- markers.forEach(marker => {
- const id = marker.getAttribute('data-id');
- const html = footnote_registry[id];
-
- // Replace the invisible marker with the visible naked superscript link
- const sup = document.createElement('sup');
- sup.innerHTML = `<a href="#fn-${id}" id="fn-ref-${id}" style="color: var(--RT·brand-link); text-decoration: none;">${id}</a>`;
-
- if (marker.parentNode) {
- marker.parentNode.replaceChild(sup, marker);
- }
-
- // Append the actual text to the footer with a clean, print-ready number format
- const fn_line = document.createElement('div');
- fn_line.id = `fn-${id}`;
- fn_line.style.marginBottom = '0.5rem';
- fn_line.innerHTML = `<span style="padding-right: 0.5em; font-weight: 600;">${id}.</span>${html}`;
- fn_container.appendChild(fn_line);
- });
-
- // Attach the footer. The page organically stretches to fit.
- page.appendChild(fn_container);
- });
- });
-
- // Cleanup
- if (measureContainer && measureContainer.parentNode) {
- measureContainer.remove();
- measureContainer = null;
- }
-};
}
document.write('<script src="' + window.RT.dirpr_library + '/Core/loader.js"><\/script>');
-
- document.write(
- '<script>' +
- 'window.RT.load("Core/block_visibility_during_layout");' +
- 'window.RT.load("Element/theme_selector");' +
- '<\/script>'
- );
+
})();
/*
immediate.js
- We have four scenarios
+ There are 4 variations for RT-Style-locator.js:
- 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
+ URL_only - used by websites
+ indirect - used by Harmony projects, except for RT-style
+ immediate - used by RT-style in the distribution (authored, consummer, staged)
+ direct - used by RT-style project outside of the distribution, e.g. the main document directory
*/
window.RT = window.RT || {};
(function() {
- // We are the style library, so ...
window.RT.dirpr_library = "..";
-
- // 1. Inject the loader script
- document.write('<script src="' + window.RT.dirpr_library + '/Core/loader.js"><\/script>');
-
- // 2. Inject a secondary script block for the core dependencies
- document.write(
- '<script>' +
- 'window.RT.load("Core/utility");' +
- 'window.RT.load("Core/block_visibility_during_layout");' +
- 'window.RT.load("Theme");' +
- 'window.RT.load("Element/theme_selector");' +
- '<\/script>'
- );
+ document.write('<script src="' + window.RT.dirpr_library + '/Core/RT-Style_make.js"><\/script>');
})();