From 3dad3ec5772c07283810c6cf5e8aedc6122ab5eb Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Mon, 29 Jun 2026 06:25:17 +0000 Subject: [PATCH] Core/RT-Style_make.js -> Core/RT-Manuscript_make.js --- ...RT-Style_make.js => RT-Manuscript_make.js} | 0 .../Manuscript.copy/Core/stage_manager.js | 129 ++++-- .../Manuscript.copy/Core/state_manager-0.js | 178 ++++++++ .../Manuscript.copy/Core/theme_make.js | 2 +- ...le_locator.js => RT-Manuscript_locator.js} | 6 +- .../Manuscript.copy/Document/manual.html | 3 +- .../Layout/article_tech_ref.js | 78 ++-- .../Manuscript.copy/Layout/paginate-0.js | 419 ++++++++++++++++++ .../Manuscript.copy/Layout/paginate.js | 401 +++++++++++++++++ .../Layout/paginate_by_element.js | 399 ----------------- .../Manuscript.copy/Locator/direct.js | 8 +- .../Manuscript.copy/Locator/immediate.js | 25 +- 12 files changed, 1155 insertions(+), 493 deletions(-) rename developer/authored/Manuscript.copy/Core/{RT-Style_make.js => RT-Manuscript_make.js} (100%) create mode 100644 developer/authored/Manuscript.copy/Core/state_manager-0.js rename developer/authored/Manuscript.copy/Document/{RT-Style_locator.js => RT-Manuscript_locator.js} (53%) create mode 100644 developer/authored/Manuscript.copy/Layout/paginate-0.js create mode 100644 developer/authored/Manuscript.copy/Layout/paginate.js delete mode 100644 developer/authored/Manuscript.copy/Layout/paginate_by_element.js diff --git a/developer/authored/Manuscript.copy/Core/RT-Style_make.js b/developer/authored/Manuscript.copy/Core/RT-Manuscript_make.js similarity index 100% rename from developer/authored/Manuscript.copy/Core/RT-Style_make.js rename to developer/authored/Manuscript.copy/Core/RT-Manuscript_make.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-Style_locator.js b/developer/authored/Manuscript.copy/Document/RT-Manuscript_locator.js similarity index 53% rename from developer/authored/Manuscript.copy/Document/RT-Style_locator.js rename to developer/authored/Manuscript.copy/Document/RT-Manuscript_locator.js index 4498e6b..87e0fe4 100644 --- a/developer/authored/Manuscript.copy/Document/RT-Style_locator.js +++ b/developer/authored/Manuscript.copy/Document/RT-Manuscript_locator.js @@ -4,10 +4,10 @@ There are 4 variations for RT-Style-locator.js: URL_only - used by websites - indirect - used by Harmony projects, except for RT-style + indirect - used by Harmony projects, except for RT-Manuscript - 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 + 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 || {}; diff --git a/developer/authored/Manuscript.copy/Document/manual.html b/developer/authored/Manuscript.copy/Document/manual.html index 386e691..22da8cb 100644 --- a/developer/authored/Manuscript.copy/Document/manual.html +++ b/developer/authored/Manuscript.copy/Document/manual.html @@ -3,9 +3,10 @@ RT Style System: Reference Manual - + 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('