From: Thomas Walker Lynch Date: Mon, 29 Jun 2026 06:25:17 +0000 (+0000) Subject: Core/RT-Style_make.js -> Core/RT-Manuscript_make.js X-Git-Url: https://git.reasoningtechnology.com/machine%20fig.png?a=commitdiff_plain;h=3dad3ec5772c07283810c6cf5e8aedc6122ab5eb;p=RT-Style Core/RT-Style_make.js -> Core/RT-Manuscript_make.js --- diff --git a/developer/authored/Manuscript.copy/Core/RT-Manuscript_make.js b/developer/authored/Manuscript.copy/Core/RT-Manuscript_make.js new file mode 100644 index 0000000..fb3f58e --- /dev/null +++ b/developer/authored/Manuscript.copy/Core/RT-Manuscript_make.js @@ -0,0 +1,204 @@ +// 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(''); +}; + +window.RT.load('Core/stage_manager'); +window.RT.load('Core/theme_make'); +window.RT.load('Theme/manifest.js') diff --git a/developer/authored/Manuscript.copy/Core/RT-Style_make.js b/developer/authored/Manuscript.copy/Core/RT-Style_make.js deleted file mode 100644 index fb3f58e..0000000 --- a/developer/authored/Manuscript.copy/Core/RT-Style_make.js +++ /dev/null @@ -1,204 +0,0 @@ -// 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(''); -}; - -window.RT.load('Core/stage_manager'); -window.RT.load('Core/theme_make'); -window.RT.load('Theme/manifest.js') diff --git a/developer/authored/Manuscript.copy/Core/stage_manager.js b/developer/authored/Manuscript.copy/Core/stage_manager.js index de03a79..4c41214 100644 --- a/developer/authored/Manuscript.copy/Core/stage_manager.js +++ b/developer/authored/Manuscript.copy/Core/stage_manager.js @@ -1,16 +1,38 @@ -// 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"; @@ -88,57 +110,90 @@ RT.Element = RT.Element || new Set(); }); } - 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); })(); diff --git a/developer/authored/Manuscript.copy/Core/state_manager-0.js b/developer/authored/Manuscript.copy/Core/state_manager-0.js new file mode 100644 index 0000000..f28bf8b --- /dev/null +++ b/developer/authored/Manuscript.copy/Core/state_manager-0.js @@ -0,0 +1,178 @@ +/* + 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); + +})(); diff --git a/developer/authored/Manuscript.copy/Core/theme_make.js b/developer/authored/Manuscript.copy/Core/theme_make.js index 175978f..7edb5e1 100644 --- a/developer/authored/Manuscript.copy/Core/theme_make.js +++ b/developer/authored/Manuscript.copy/Core/theme_make.js @@ -113,7 +113,7 @@ RT.theme = (function() { })(); 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); diff --git a/developer/authored/Manuscript.copy/Document/RT-Manuscript_locator.js b/developer/authored/Manuscript.copy/Document/RT-Manuscript_locator.js new file mode 100644 index 0000000..87e0fe4 --- /dev/null +++ b/developer/authored/Manuscript.copy/Document/RT-Manuscript_locator.js @@ -0,0 +1,18 @@ +/* + 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(' + diff --git a/developer/authored/Manuscript.copy/Layout/article_tech_ref.js b/developer/authored/Manuscript.copy/Layout/article_tech_ref.js index 21c6034..e868ab8 100644 --- a/developer/authored/Manuscript.copy/Layout/article_tech_ref.js +++ b/developer/authored/Manuscript.copy/Layout/article_tech_ref.js @@ -1,3 +1,9 @@ +/* + 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 || {}; @@ -16,10 +22,25 @@ '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'); @@ -30,18 +51,6 @@ 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', { @@ -57,13 +66,8 @@ 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 = [ @@ -130,17 +134,39 @@ 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?"); } })(); diff --git a/developer/authored/Manuscript.copy/Layout/paginate-0.js b/developer/authored/Manuscript.copy/Layout/paginate-0.js new file mode 100644 index 0000000..c5d36ee --- /dev/null +++ b/developer/authored/Manuscript.copy/Layout/paginate-0.js @@ -0,0 +1,419 @@ +/* + Processes 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 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 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 = `${id}`; + + 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 = `${id}.${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; + } + }); + +})(); diff --git a/developer/authored/Manuscript.copy/Layout/paginate.js b/developer/authored/Manuscript.copy/Layout/paginate.js new file mode 100644 index 0000000..0a9a26a --- /dev/null +++ b/developer/authored/Manuscript.copy/Layout/paginate.js @@ -0,0 +1,401 @@ +/* + Processes 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 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 = `${id}`; + + 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 = `${id}.${html}`; + fn_container.appendChild(fn_line); + }); + + page.appendChild(fn_container); + }); + }); + + if (measureContainer && measureContainer.parentNode) { + measureContainer.remove(); + measureContainer = null; + } + }; +})(); diff --git a/developer/authored/Manuscript.copy/Layout/paginate_by_element.js b/developer/authored/Manuscript.copy/Layout/paginate_by_element.js deleted file mode 100644 index 19fa23c..0000000 --- a/developer/authored/Manuscript.copy/Layout/paginate_by_element.js +++ /dev/null @@ -1,399 +0,0 @@ -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 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 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 = `${id}`; - - 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 = `${id}.${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; - } -}; diff --git a/developer/authored/Manuscript.copy/Locator/direct.js b/developer/authored/Manuscript.copy/Locator/direct.js index f9457a5..11f7b82 100644 --- a/developer/authored/Manuscript.copy/Locator/direct.js +++ b/developer/authored/Manuscript.copy/Locator/direct.js @@ -28,11 +28,5 @@ window.RT = window.RT || {}; } document.write('