Default (No attribute):
Context Aware. Looks backwards for the nearest heading H(N).
Targets H(N+1). Stops at the next H(N).
+
+First heading 1 1
+ First heading 2 2
+ Next heading 2 2
+Next heading 2 3
+
*/
+
window.StyleRT = window.StyleRT || {};
window.StyleRT.TOC = function(){
TOC_seq.forEach( (container ,TOC_index) => {
container.style.display = 'block';
- // 1. Determine Target Level
- const attr_level = parseInt( container.getAttribute('level') );
- let target_level;
-
- if( !isNaN(attr_level) ){
- // EXPLICIT MODE
- target_level = attr_level;
- if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} explicit target: H${target_level}`);
- } else {
- // IMPLICIT / CONTEXT MODE
- let context_level = 0; // Default 0 (Root)
- let prev = container.previousElementSibling;
- while(prev){
- const match = prev.tagName.match(/^H([1-6])$/);
- if(match){
- context_level = parseInt( match[1] );
- break;
- }
- prev = prev.previousElementSibling;
- }
- target_level = context_level + 1;
- if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} context implied target: H${target_level}`);
+ // 1. Parse attribute: single number N or range A-B
+ const attr_val = container.getAttribute('level');
+ let start_level, end_level;
+
+ if (attr_val) {
+ const rangeMatch = attr_val.match(/^(\d)-(\d)$/);
+ if (rangeMatch) {
+ const a = parseInt(rangeMatch[1]);
+ const b = parseInt(rangeMatch[2]);
+ if (a >= 1 && a <= 6 && b >= 1 && b <= 6 && a <= b) {
+ start_level = a;
+ end_level = b;
+ if (debug.log) debug.log('TOC', `TOC #${TOC_index} range: H${a}-H${b}`);
+ } else {
+ if (debug.log) debug.log('TOC', `Invalid range "${attr_val}" → implicit mode`);
+ }
+ } else {
+ const single = parseInt(attr_val);
+ if (!isNaN(single) && single >= 1 && single <= 6) {
+ start_level = single;
+ end_level = single;
+ if (debug.log) debug.log('TOC', `TOC #${TOC_index} single level: H${single}`);
+ } else {
+ if (debug.log) debug.log('TOC', `Invalid level "${attr_val}" → implicit mode`);
+ }
+ }
+ }
+
+ // 2. Implicit mode (no attribute or invalid)
+ if (start_level === undefined || end_level === undefined) {
+ let context_level = 0;
+ let prev = container.previousElementSibling;
+ while (prev) {
+ const match = prev.tagName.match(/^H([1-6])$/);
+ if (match) {
+ context_level = parseInt(match[1]);
+ break;
+ }
+ prev = prev.previousElementSibling;
+ }
+ const target_level = Math.min(context_level + 1, 6);
+ start_level = target_level;
+ end_level = target_level;
+ if (debug.log) debug.log('TOC', `TOC #${TOC_index} implicit target: H${target_level}`);
}
- // Stop condition: Stop if we hit a heading that is a "parent" or "sibling" of the context.
- // Mathematically: Stop if found_level < target_level.
- const stop_threshold = target_level;
+ // 3. Collect all matching headings until a higher-level heading stops us
+ const headings = [];
+ let next_el = container.nextElementSibling;
+ while (next_el) {
+ const match = next_el.tagName.match(/^H([1-6])$/);
+ if (match) {
+ const found_level = parseInt(match[1]);
+
+ // Stop if we hit a heading that is a parent of the lowest level we collect
+ if (found_level < start_level) break;
+
+ // Collect if within the requested range
+ if (found_level >= start_level && found_level <= end_level) {
+ // Ensure it has an id
+ if (!next_el.id) {
+ next_el.id = `TOC-ref-${TOC_index}-${found_level}-${headings.length}`;
+ }
+ headings.push({ el: next_el, level: found_level });
+ }
+ }
+ next_el = next_el.nextElementSibling;
+ }
- // 2. Setup Container
+ // 4. Build the container (title + list)
container.innerHTML = '';
const title = document.createElement('h1');
- // Title logic: If targeting H1, the element serves as a Main TOC. Otherwise the element serves as a Section TOC.
- title.textContent = target_level === 1 ? 'Table of Contents' : 'Section Contents';
+ title.textContent = start_level === 1 ? 'Table of Contents' : 'Section Contents';
title.style.textAlign = 'center';
container.appendChild(title);
- const list = document.createElement('ul');
- list.style.listStyle = 'none';
- list.style.paddingLeft = '0';
- container.appendChild(list);
+ if (headings.length === 0) return; // nothing to show
- // 3. Scan Forward
- let next_el = container.nextElementSibling;
- while(next_el){
- const match = next_el.tagName.match(/^H([1-6])$/);
- if(match){
- const found_level = parseInt( match[1] );
+ // Top-level list
+ const topList = document.createElement('ul');
+ topList.style.listStyle = 'none';
+ topList.style.paddingLeft = '0';
+ container.appendChild(topList);
+
+ // Stack of <ul> elements; index 0 = top-level list
+ const listStack = [topList];
+
+ for (const item of headings) {
+ // Depth relative to start_level
+ const depth = item.level - start_level; // 0 = top-level, 1 = sub-level, etc.
- // STOP Logic:
- // If we are looking for H2s, we stop if we hit an H1 (level 1).
- // If we are looking for H1s, we stop if we hit nothing (level 0).
- if(found_level < target_level){
+ // Ensure we have the correct nesting depth
+ while (listStack.length - 1 > depth) {
+ // Pop until we are at the right depth
+ listStack.pop();
+ }
+
+ // If we need to go deeper, open new sub-lists inside the last <li>
+ while (listStack.length - 1 < depth) {
+ const parentList = listStack[listStack.length - 1];
+ const lastLi = parentList.lastElementChild;
+ if (lastLi) {
+ const subList = document.createElement('ul');
+ subList.style.listStyle = 'none';
+ subList.style.paddingLeft = '1.5rem'; // indentation for nested items
+ lastLi.appendChild(subList);
+ listStack.push(subList);
+ } else {
+ // No parent <li> yet – stay at current depth (flatten)
break;
}
+ }
- // COLLECT Logic:
- if(found_level === target_level){
- if(!next_el.id) next_el.id = `TOC-ref-${TOC_index}-${found_level}-${list.children.length}`;
-
- const li = document.createElement('li');
- li.style.marginBottom = '0.5rem';
+ // Create the <li> for this heading
+ const li = document.createElement('li');
+ li.style.marginBottom = '0.5rem';
- const a = document.createElement('a');
- a.href = `#${next_el.id}`;
- a.textContent = next_el.textContent;
- a.style.textDecoration = 'none';
- a.style.color = 'inherit';
- a.style.display = 'block';
+ const a = document.createElement('a');
+ a.href = `#${item.el.id}`;
+ a.textContent = item.el.textContent;
+ a.style.textDecoration = 'none';
+ a.style.color = 'inherit';
+ a.style.display = 'block';
- a.onmouseover = () => a.style.color = 'var(--rt-brand-primary)';
- a.onmouseout = () => a.style.color = 'inherit';
+ a.onmouseover = () => a.style.color = 'var(--rt-brand-primary)';
+ a.onmouseout = () => a.style.color = 'inherit';
- li.appendChild(a);
- list.appendChild(li);
- }
- }
- next_el = next_el.nextElementSibling;
+ li.appendChild(a);
+ // Add to the current deepest list
+ listStack[listStack.length - 1].appendChild(li);
}
});
};
(function(){
const RT = window.StyleRT = window.StyleRT || {};
- // 1. Declare Dependencies with updated core/ and layout/ paths
+ // 1. Declare Dependencies
RT.include('RT/core/utility');
RT.include('RT/element/math');
RT.include('RT/element/code');
RT.include('RT/layout/page_fixed_glow');
RT.include('RT/core/body_visibility_visible');
- // 2. The Typography Layout (Adapted from the original)
+ // 2. The Typography Layout
RT.article = function(){
RT.config = RT.config || {};
RT.config.article = {
style.color = "var(--rt-content-main)";
}
- // CSS injection for headers, lists, and layout mapped from theme variables
- // 1. Adjust paginator capacity to fit the physical page minus padding
window.StyleRT = window.StyleRT || {};
window.StyleRT.config = window.StyleRT.config || {};
window.StyleRT.config.page = window.StyleRT.config.page || {};
- // 1056px - 96px (padding) = 960px usable. 920px leaves room for the footer.
- window.StyleRT.config.page.height_limit = 920;
+ window.StyleRT.config.page.height_limit = 900;
- // 2. CSS injection for headers, lists, and layout mapped from theme variables
const style_node = document.createElement("style");
style_node.innerHTML = `
rt-article {
font-family: 'Noto Sans JP', Arial, sans-serif;
background-color: var(--rt-surface-0);
color: var(--rt-content-main);
- /* Force width so inline JS doesn't widen the measurement area */
max-width: 46.875rem !important;
box-sizing: border-box !important;
}
- /* PRE-PAGINATION: Mimic page padding so text line-wraps accurately during measurement */
rt-article:not(:has(rt-page)) {
padding: 3rem !important;
}
- /* POST-PAGINATION: Remove article padding so we don't double-pad the generated pages */
rt-article:has(rt-page) {
padding: 0 !important;
}
rt-article rt-page {
+ position: relative;
display: block;
padding: 3rem;
margin: 1.25rem auto;
box-shadow: 0 0 0.625rem var(--rt-brand-primary);
}
- /* --- HEADER CASCADE --- */
rt-article h1 {
font-size: 1.5rem;
text-align: center;
margin-left: 8ch;
}
- /* --- BODY TEXT (Flush Left) --- */
rt-article p,
rt-article ul,
rt-article ol {
margin-bottom: 0.5rem;
}
- /* --- CODE FORMATTING --- */
rt-article rt-code {
font-family: 'Courier New', Courier, monospace;
background-color: var(--rt-surface-code);
// 3. The Execution Sequence
const run_semantics = function(){
+ // Override browser scroll memory
+ if ('scrollRestoration' in history) {
+ history.scrollRestoration = 'manual';
+ }
+
+ // Capture the scroll position right before the page unloads
+ window.addEventListener('beforeunload', () => {
+ sessionStorage.setItem('RT_scroll_position', window.scrollY);
+ });
+
if(RT.theme) RT.theme();
RT.article();
- // Call the newly renamed element functions
if(RT.title) RT.title();
if(RT.term) RT.term();
if(RT.math) RT.math();
if(RT.code) RT.code();
- // Check for MathJax typesetting
if(window.MathJax && MathJax.Hub && MathJax.Hub.Queue){
MathJax.Hub.Queue( ["Typeset" ,MathJax.Hub] ,run_layout );
}else{
if(RT.paginate_by_element) RT.paginate_by_element();
if(RT.page) RT.page();
if(RT.body_visibility_visible) RT.body_visibility_visible();
+
+ // Yield to the browser's rendering pipeline.
+ // The DOM has been mutated, but the browser needs a moment to recalculate
+ // the physical geometry before we apply a scroll coordinate.
+ setTimeout(() => {
+ if (window.location.hash) {
+ const target = document.getElementById(window.location.hash.substring(1));
+ if (target) {
+ target.scrollIntoView();
+ }
+ } else {
+ const saved_y = sessionStorage.getItem('RT_scroll_position');
+ if (saved_y !== null) {
+ window.scrollTo(0, parseInt(saved_y, 10));
+ } else {
+ window.scrollTo(0, 0);
+ }
+ }
+ }, 50);
};
// 4. Bind to DOM Ready
- // This replaces the need to put a script at the bottom of the HTML body
document.addEventListener( 'DOMContentLoaded' ,run_semantics );
})();
-/*
- Layout Paginator: paginate_by_element
-*/
-window.StyleRT = window.StyleRT || {};
-
-window.StyleRT.paginate_by_element = function(){
+window.StyleRT.paginate_by_element = function () {
const RT = window.StyleRT;
const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
- const page_height_limit = page_conf.height_limit || 1000;
+ const page_height_limit = page_conf.height_limit || 1000;
- const article_seq = document.querySelectorAll("RT-article");
- if(article_seq.length === 0){
- RT.debug.error('pagination' ,'No <RT-article> elements found. Pagination aborted.');
+ const article_seq = document.querySelectorAll('RT-article');
+ if (article_seq.length === 0) {
+ RT.debug.error('pagination', 'No <RT-article> elements found. Pagination aborted.');
return;
}
+ // ---------- helpers ----------
+ const get_el_height = (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);
+ };
+
+ let measureContainer = null;
+ const 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;
+ };
+
+ const measureFragment = (frag) => {
+ const container = getMeasureContainer();
+ container.appendChild(frag);
+ const h = get_el_height(frag);
+ container.removeChild(frag);
+ return h;
+ };
+
+ const isSplittable = (el) => {
+ const tag = el.tagName;
+
+ // Support for custom RT-TOC wrapper
+ if (tag === 'RT-TOC') {
+ const list = el.querySelector('ul, ol');
+ if (!list) return null;
+
+ const items = Array.from(list.children).filter(c => c.tagName === 'LI');
+ if (items.length === 0) return null;
+
+ const itemHeights = items.map(li => get_el_height(li));
+
+ const emptyClone = el.cloneNode(true);
+ const listInClone = emptyClone.querySelector('ul, ol');
+ if (listInClone) listInClone.innerHTML = '';
+ const overhead = get_el_height(emptyClone);
+
+ el._splitInfo = { type: 'toc', itemHeights, overhead, offset: 0 };
+ return makeTOCSplitter(el, el._splitInfo);
+ }
+
+ 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 => get_el_height(li));
+
+ const emptyClone = el.cloneNode(false);
+ const overhead = get_el_height(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 ? get_el_height(thead) : 0;
+ const rowHeights = rows.map(row => get_el_height(row));
+
+ const emptyClone = el.cloneNode(false);
+ if (thead) {
+ const theadClone = thead.cloneNode(true);
+ emptyClone.appendChild(theadClone);
+ }
+ const tbodyClone = document.createElement('tbody');
+ emptyClone.appendChild(tbodyClone);
+ const overhead = get_el_height(emptyClone) - theadHeight;
+
+ el._splitInfo = { type: 'table', rowHeights, overhead, theadHeight, offset: 0 };
+ return makeTableSplitter(el, el._splitInfo);
+ }
+
+ return null;
+ };
+
+ function makeTOCSplitter(el, info) {
+ return (remaining) => {
+ const list = el.querySelector('ul, ol');
+ const children = Array.from(list.children).filter(c => c.tagName === 'LI');
+ const start = info.offset;
+
+ let bestCount = 0;
+ let bestHeight = 0;
+
+ const tempClone = el.cloneNode(true);
+ const tempList = tempClone.querySelector('ul, ol');
+ tempList.innerHTML = '';
+
+ for (let i = 0; i < children.length; i++) {
+ const itemClone = children[i].cloneNode(true);
+ tempList.appendChild(itemClone);
+ const fragHeight = measureFragment(tempClone);
+ 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(true);
+ const firstList = first.querySelector('ul, ol');
+ firstList.innerHTML = '';
+ for (let i = 0; i < bestCount; i++) {
+ firstList.appendChild(children[i].cloneNode(true));
+ }
+
+ const rest = el.cloneNode(true);
+ const restList = rest.querySelector('ul, ol');
+ restList.innerHTML = '';
+ for (let i = bestCount; i < children.length; i++) {
+ restList.appendChild(children[i].cloneNode(true));
+ }
+
+ // Clean up the title heading in the continuation fragment
+ const title = rest.querySelector('h1, h2, h3, h4, h5, h6');
+ if (title) {
+ title.remove();
+ }
+
+ // Recalculate overhead since the title is now gone
+ const emptyRest = rest.cloneNode(true);
+ const emptyRestList = emptyRest.querySelector('ul, ol');
+ if (emptyRestList) {
+ emptyRestList.innerHTML = '';
+ }
+ const restOverhead = measureFragment(emptyRest);
+
+ rest._splitInfo = {
+ type: 'toc',
+ itemHeights: info.itemHeights,
+ overhead: restOverhead,
+ offset: start + bestCount
+ };
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ 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));
+ }
+
+ const rest = el.cloneNode(false);
+ for (let i = bestCount; i < children.length; i++) {
+ rest.appendChild(children[i].cloneNode(true));
+ }
+
+ 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;
+ const relevantRows = rows.slice(start, start + rows.length);
+
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempTable = createShell();
+ const tempBody = tempTable.querySelector('tbody');
+ for (let i = 0; i < relevantRows.length; i++) {
+ tempBody.appendChild(relevantRows[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(relevantRows[i].cloneNode(true));
+ }
+
+ const rest = createShell();
+ const restBody = rest.querySelector('tbody');
+ for (let i = bestCount; i < relevantRows.length; i++) {
+ restBody.appendChild(relevantRows[i].cloneNode(true));
+ }
+
+ rest._splitInfo = {
+ type: 'table',
+ rowHeights: info.rowHeights,
+ overhead: info.overhead,
+ theadHeight: info.theadHeight,
+ offset: start + bestCount
+ };
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ // ---------- main pagination loop ----------
let article_index = 0;
- while(true){
- if(article_index === article_seq.length) break;
+ while (article_index < article_seq.length) {
const article = article_seq[article_index];
-
- const raw_element_seq = Array.from(article.children).filter( el =>
- !['SCRIPT' ,'STYLE' ,'RT-PAGE'].includes(el.tagName)
+
+ const raw_element_seq = Array.from(article.children).filter(el =>
+ !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName)
);
- if(raw_element_seq.length > 0){
- const page_seq = [];
- let current_batch_seq = [];
- let current_h = 0;
+ if (raw_element_seq.length === 0) {
+ article_index++;
+ continue;
+ }
- const get_el_height = (el) => {
- const rect = el.getBoundingClientRect();
- const style = window.getComputedStyle(el);
- const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
- return (rect.height || 0) + (margin || 0);
- };
+ const page_seq = [];
+ let current_batch_seq = [];
+ let current_h = 0;
- let i = 0;
- while(true){
- if(i === raw_element_seq.length) break;
- const el = raw_element_seq[i];
- const h = get_el_height(el);
-
- if(current_h + h > page_height_limit && current_batch_seq.length > 0){
- let backtrack_seq = [];
- let backtrack_h = 0;
-
- // Backtrack to rescue any widowed headings at the end of the batch
- while(true){
- if(current_batch_seq.length === 0) break;
- const last_el = current_batch_seq[current_batch_seq.length - 1];
- if(!/^H[1-6]/.test(last_el.tagName)) break;
-
- const popped_el = current_batch_seq.pop();
- backtrack_seq.unshift(popped_el);
- backtrack_h += get_el_height(popped_el);
- }
+ 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(current_batch_seq.length > 0){
+ raw_element_seq.splice(i, 1, rest);
+ } 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 = backtrack_seq;
- current_h = backtrack_h;
- }else{
- // Fallback for an extreme case where the entire page was a cascade of headings
- page_seq.push(backtrack_seq);
current_batch_seq = [];
current_h = 0;
+ raw_element_seq[i] = rest;
}
}
-
- current_batch_seq.push(el);
- current_h += h;
-
- i++;
+ continue;
}
- if(current_batch_seq.length > 0){
- page_seq.push(current_batch_seq);
- }
+ const h = get_el_height(el);
- article.innerHTML = '';
-
- let p = 0;
- while(true){
- if(p === page_seq.length) break;
- const batch_seq = page_seq[p];
- const page_el = document.createElement('rt-page');
- page_el.id = `page-${p + 1}`;
-
- let item_idx = 0;
- while(true){
- if(item_idx === batch_seq.length) break;
- page_el.appendChild(batch_seq[item_idx]);
- item_idx++;
+ if (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 += get_el_height(popped);
}
-
- article.appendChild(page_el);
- p++;
- }
- if(RT.debug){
- RT.debug.log('pagination' ,`Article paginated into ${page_seq.length} pages.`);
+ 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++;
+ }
+
+ if (RT.debug) {
+ RT.debug.log('pagination', `Article paginated into ${page_seq.length} pages.`);
}
article_index++;
}
+
+ if (measureContainer && measureContainer.parentNode) {
+ measureContainer.remove();
+ measureContainer = null;
+ }
};
Default (No attribute):
Context Aware. Looks backwards for the nearest heading H(N).
Targets H(N+1). Stops at the next H(N).
+
+First heading 1 1
+ First heading 2 2
+ Next heading 2 2
+Next heading 2 3
+
*/
+
window.StyleRT = window.StyleRT || {};
window.StyleRT.TOC = function(){
TOC_seq.forEach( (container ,TOC_index) => {
container.style.display = 'block';
- // 1. Determine Target Level
- const attr_level = parseInt( container.getAttribute('level') );
- let target_level;
-
- if( !isNaN(attr_level) ){
- // EXPLICIT MODE
- target_level = attr_level;
- if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} explicit target: H${target_level}`);
- } else {
- // IMPLICIT / CONTEXT MODE
- let context_level = 0; // Default 0 (Root)
- let prev = container.previousElementSibling;
- while(prev){
- const match = prev.tagName.match(/^H([1-6])$/);
- if(match){
- context_level = parseInt( match[1] );
- break;
- }
- prev = prev.previousElementSibling;
- }
- target_level = context_level + 1;
- if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} context implied target: H${target_level}`);
+ // 1. Parse attribute: single number N or range A-B
+ const attr_val = container.getAttribute('level');
+ let start_level, end_level;
+
+ if (attr_val) {
+ const rangeMatch = attr_val.match(/^(\d)-(\d)$/);
+ if (rangeMatch) {
+ const a = parseInt(rangeMatch[1]);
+ const b = parseInt(rangeMatch[2]);
+ if (a >= 1 && a <= 6 && b >= 1 && b <= 6 && a <= b) {
+ start_level = a;
+ end_level = b;
+ if (debug.log) debug.log('TOC', `TOC #${TOC_index} range: H${a}-H${b}`);
+ } else {
+ if (debug.log) debug.log('TOC', `Invalid range "${attr_val}" → implicit mode`);
+ }
+ } else {
+ const single = parseInt(attr_val);
+ if (!isNaN(single) && single >= 1 && single <= 6) {
+ start_level = single;
+ end_level = single;
+ if (debug.log) debug.log('TOC', `TOC #${TOC_index} single level: H${single}`);
+ } else {
+ if (debug.log) debug.log('TOC', `Invalid level "${attr_val}" → implicit mode`);
+ }
+ }
+ }
+
+ // 2. Implicit mode (no attribute or invalid)
+ if (start_level === undefined || end_level === undefined) {
+ let context_level = 0;
+ let prev = container.previousElementSibling;
+ while (prev) {
+ const match = prev.tagName.match(/^H([1-6])$/);
+ if (match) {
+ context_level = parseInt(match[1]);
+ break;
+ }
+ prev = prev.previousElementSibling;
+ }
+ const target_level = Math.min(context_level + 1, 6);
+ start_level = target_level;
+ end_level = target_level;
+ if (debug.log) debug.log('TOC', `TOC #${TOC_index} implicit target: H${target_level}`);
}
- // Stop condition: Stop if we hit a heading that is a "parent" or "sibling" of the context.
- // Mathematically: Stop if found_level < target_level.
- const stop_threshold = target_level;
+ // 3. Collect all matching headings until a higher-level heading stops us
+ const headings = [];
+ let next_el = container.nextElementSibling;
+ while (next_el) {
+ const match = next_el.tagName.match(/^H([1-6])$/);
+ if (match) {
+ const found_level = parseInt(match[1]);
+
+ // Stop if we hit a heading that is a parent of the lowest level we collect
+ if (found_level < start_level) break;
+
+ // Collect if within the requested range
+ if (found_level >= start_level && found_level <= end_level) {
+ // Ensure it has an id
+ if (!next_el.id) {
+ next_el.id = `TOC-ref-${TOC_index}-${found_level}-${headings.length}`;
+ }
+ headings.push({ el: next_el, level: found_level });
+ }
+ }
+ next_el = next_el.nextElementSibling;
+ }
- // 2. Setup Container
+ // 4. Build the container (title + list)
container.innerHTML = '';
const title = document.createElement('h1');
- // Title logic: If targeting H1, the element serves as a Main TOC. Otherwise the element serves as a Section TOC.
- title.textContent = target_level === 1 ? 'Table of Contents' : 'Section Contents';
+ title.textContent = start_level === 1 ? 'Table of Contents' : 'Section Contents';
title.style.textAlign = 'center';
container.appendChild(title);
- const list = document.createElement('ul');
- list.style.listStyle = 'none';
- list.style.paddingLeft = '0';
- container.appendChild(list);
+ if (headings.length === 0) return; // nothing to show
- // 3. Scan Forward
- let next_el = container.nextElementSibling;
- while(next_el){
- const match = next_el.tagName.match(/^H([1-6])$/);
- if(match){
- const found_level = parseInt( match[1] );
+ // Top-level list
+ const topList = document.createElement('ul');
+ topList.style.listStyle = 'none';
+ topList.style.paddingLeft = '0';
+ container.appendChild(topList);
+
+ // Stack of <ul> elements; index 0 = top-level list
+ const listStack = [topList];
+
+ for (const item of headings) {
+ // Depth relative to start_level
+ const depth = item.level - start_level; // 0 = top-level, 1 = sub-level, etc.
- // STOP Logic:
- // If we are looking for H2s, we stop if we hit an H1 (level 1).
- // If we are looking for H1s, we stop if we hit nothing (level 0).
- if(found_level < target_level){
+ // Ensure we have the correct nesting depth
+ while (listStack.length - 1 > depth) {
+ // Pop until we are at the right depth
+ listStack.pop();
+ }
+
+ // If we need to go deeper, open new sub-lists inside the last <li>
+ while (listStack.length - 1 < depth) {
+ const parentList = listStack[listStack.length - 1];
+ const lastLi = parentList.lastElementChild;
+ if (lastLi) {
+ const subList = document.createElement('ul');
+ subList.style.listStyle = 'none';
+ subList.style.paddingLeft = '1.5rem'; // indentation for nested items
+ lastLi.appendChild(subList);
+ listStack.push(subList);
+ } else {
+ // No parent <li> yet – stay at current depth (flatten)
break;
}
+ }
- // COLLECT Logic:
- if(found_level === target_level){
- if(!next_el.id) next_el.id = `TOC-ref-${TOC_index}-${found_level}-${list.children.length}`;
-
- const li = document.createElement('li');
- li.style.marginBottom = '0.5rem';
+ // Create the <li> for this heading
+ const li = document.createElement('li');
+ li.style.marginBottom = '0.5rem';
- const a = document.createElement('a');
- a.href = `#${next_el.id}`;
- a.textContent = next_el.textContent;
- a.style.textDecoration = 'none';
- a.style.color = 'inherit';
- a.style.display = 'block';
+ const a = document.createElement('a');
+ a.href = `#${item.el.id}`;
+ a.textContent = item.el.textContent;
+ a.style.textDecoration = 'none';
+ a.style.color = 'inherit';
+ a.style.display = 'block';
- a.onmouseover = () => a.style.color = 'var(--rt-brand-primary)';
- a.onmouseout = () => a.style.color = 'inherit';
+ a.onmouseover = () => a.style.color = 'var(--rt-brand-primary)';
+ a.onmouseout = () => a.style.color = 'inherit';
- li.appendChild(a);
- list.appendChild(li);
- }
- }
- next_el = next_el.nextElementSibling;
+ li.appendChild(a);
+ // Add to the current deepest list
+ listStack[listStack.length - 1].appendChild(li);
}
});
};
(function(){
const RT = window.StyleRT = window.StyleRT || {};
- // 1. Declare Dependencies with updated core/ and layout/ paths
+ // 1. Declare Dependencies
RT.include('RT/core/utility');
RT.include('RT/element/math');
RT.include('RT/element/code');
RT.include('RT/layout/page_fixed_glow');
RT.include('RT/core/body_visibility_visible');
- // 2. The Typography Layout (Adapted from the original)
+ // 2. The Typography Layout
RT.article = function(){
RT.config = RT.config || {};
RT.config.article = {
style.color = "var(--rt-content-main)";
}
- // CSS injection for headers, lists, and layout mapped from theme variables
- // 1. Adjust paginator capacity to fit the physical page minus padding
window.StyleRT = window.StyleRT || {};
window.StyleRT.config = window.StyleRT.config || {};
window.StyleRT.config.page = window.StyleRT.config.page || {};
- // 1056px - 96px (padding) = 960px usable. 920px leaves room for the footer.
- window.StyleRT.config.page.height_limit = 920;
+ window.StyleRT.config.page.height_limit = 900;
- // 2. CSS injection for headers, lists, and layout mapped from theme variables
const style_node = document.createElement("style");
style_node.innerHTML = `
rt-article {
font-family: 'Noto Sans JP', Arial, sans-serif;
background-color: var(--rt-surface-0);
color: var(--rt-content-main);
- /* Force width so inline JS doesn't widen the measurement area */
max-width: 46.875rem !important;
box-sizing: border-box !important;
}
- /* PRE-PAGINATION: Mimic page padding so text line-wraps accurately during measurement */
rt-article:not(:has(rt-page)) {
padding: 3rem !important;
}
- /* POST-PAGINATION: Remove article padding so we don't double-pad the generated pages */
rt-article:has(rt-page) {
padding: 0 !important;
}
rt-article rt-page {
+ position: relative;
display: block;
padding: 3rem;
margin: 1.25rem auto;
box-shadow: 0 0 0.625rem var(--rt-brand-primary);
}
- /* --- HEADER CASCADE --- */
rt-article h1 {
font-size: 1.5rem;
text-align: center;
margin-left: 8ch;
}
- /* --- BODY TEXT (Flush Left) --- */
rt-article p,
rt-article ul,
rt-article ol {
margin-bottom: 0.5rem;
}
- /* --- CODE FORMATTING --- */
rt-article rt-code {
font-family: 'Courier New', Courier, monospace;
background-color: var(--rt-surface-code);
// 3. The Execution Sequence
const run_semantics = function(){
+ // Override browser scroll memory
+ if ('scrollRestoration' in history) {
+ history.scrollRestoration = 'manual';
+ }
+
+ // Capture the scroll position right before the page unloads
+ window.addEventListener('beforeunload', () => {
+ sessionStorage.setItem('RT_scroll_position', window.scrollY);
+ });
+
if(RT.theme) RT.theme();
RT.article();
- // Call the newly renamed element functions
if(RT.title) RT.title();
if(RT.term) RT.term();
if(RT.math) RT.math();
if(RT.code) RT.code();
- // Check for MathJax typesetting
if(window.MathJax && MathJax.Hub && MathJax.Hub.Queue){
MathJax.Hub.Queue( ["Typeset" ,MathJax.Hub] ,run_layout );
}else{
if(RT.paginate_by_element) RT.paginate_by_element();
if(RT.page) RT.page();
if(RT.body_visibility_visible) RT.body_visibility_visible();
+
+ // Yield to the browser's rendering pipeline.
+ // The DOM has been mutated, but the browser needs a moment to recalculate
+ // the physical geometry before we apply a scroll coordinate.
+ setTimeout(() => {
+ if (window.location.hash) {
+ const target = document.getElementById(window.location.hash.substring(1));
+ if (target) {
+ target.scrollIntoView();
+ }
+ } else {
+ const saved_y = sessionStorage.getItem('RT_scroll_position');
+ if (saved_y !== null) {
+ window.scrollTo(0, parseInt(saved_y, 10));
+ } else {
+ window.scrollTo(0, 0);
+ }
+ }
+ }, 50);
};
// 4. Bind to DOM Ready
- // This replaces the need to put a script at the bottom of the HTML body
document.addEventListener( 'DOMContentLoaded' ,run_semantics );
})();
-/*
- Layout Paginator: paginate_by_element
-*/
-window.StyleRT = window.StyleRT || {};
-
-window.StyleRT.paginate_by_element = function(){
+window.StyleRT.paginate_by_element = function () {
const RT = window.StyleRT;
const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
- const page_height_limit = page_conf.height_limit || 1000;
+ const page_height_limit = page_conf.height_limit || 1000;
- const article_seq = document.querySelectorAll("RT-article");
- if(article_seq.length === 0){
- RT.debug.error('pagination' ,'No <RT-article> elements found. Pagination aborted.');
+ const article_seq = document.querySelectorAll('RT-article');
+ if (article_seq.length === 0) {
+ RT.debug.error('pagination', 'No <RT-article> elements found. Pagination aborted.');
return;
}
+ // ---------- helpers ----------
+ const get_el_height = (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);
+ };
+
+ let measureContainer = null;
+ const 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;
+ };
+
+ const measureFragment = (frag) => {
+ const container = getMeasureContainer();
+ container.appendChild(frag);
+ const h = get_el_height(frag);
+ container.removeChild(frag);
+ return h;
+ };
+
+ const isSplittable = (el) => {
+ const tag = el.tagName;
+
+ // Support for custom RT-TOC wrapper
+ if (tag === 'RT-TOC') {
+ const list = el.querySelector('ul, ol');
+ if (!list) return null;
+
+ const items = Array.from(list.children).filter(c => c.tagName === 'LI');
+ if (items.length === 0) return null;
+
+ const itemHeights = items.map(li => get_el_height(li));
+
+ const emptyClone = el.cloneNode(true);
+ const listInClone = emptyClone.querySelector('ul, ol');
+ if (listInClone) listInClone.innerHTML = '';
+ const overhead = get_el_height(emptyClone);
+
+ el._splitInfo = { type: 'toc', itemHeights, overhead, offset: 0 };
+ return makeTOCSplitter(el, el._splitInfo);
+ }
+
+ 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 => get_el_height(li));
+
+ const emptyClone = el.cloneNode(false);
+ const overhead = get_el_height(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 ? get_el_height(thead) : 0;
+ const rowHeights = rows.map(row => get_el_height(row));
+
+ const emptyClone = el.cloneNode(false);
+ if (thead) {
+ const theadClone = thead.cloneNode(true);
+ emptyClone.appendChild(theadClone);
+ }
+ const tbodyClone = document.createElement('tbody');
+ emptyClone.appendChild(tbodyClone);
+ const overhead = get_el_height(emptyClone) - theadHeight;
+
+ el._splitInfo = { type: 'table', rowHeights, overhead, theadHeight, offset: 0 };
+ return makeTableSplitter(el, el._splitInfo);
+ }
+
+ return null;
+ };
+
+ function makeTOCSplitter(el, info) {
+ return (remaining) => {
+ const list = el.querySelector('ul, ol');
+ const children = Array.from(list.children).filter(c => c.tagName === 'LI');
+ const start = info.offset;
+
+ let bestCount = 0;
+ let bestHeight = 0;
+
+ const tempClone = el.cloneNode(true);
+ const tempList = tempClone.querySelector('ul, ol');
+ tempList.innerHTML = '';
+
+ for (let i = 0; i < children.length; i++) {
+ const itemClone = children[i].cloneNode(true);
+ tempList.appendChild(itemClone);
+ const fragHeight = measureFragment(tempClone);
+ 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(true);
+ const firstList = first.querySelector('ul, ol');
+ firstList.innerHTML = '';
+ for (let i = 0; i < bestCount; i++) {
+ firstList.appendChild(children[i].cloneNode(true));
+ }
+
+ const rest = el.cloneNode(true);
+ const restList = rest.querySelector('ul, ol');
+ restList.innerHTML = '';
+ for (let i = bestCount; i < children.length; i++) {
+ restList.appendChild(children[i].cloneNode(true));
+ }
+
+ // Clean up the title heading in the continuation fragment
+ const title = rest.querySelector('h1, h2, h3, h4, h5, h6');
+ if (title) {
+ title.remove();
+ }
+
+ // Recalculate overhead since the title is now gone
+ const emptyRest = rest.cloneNode(true);
+ const emptyRestList = emptyRest.querySelector('ul, ol');
+ if (emptyRestList) {
+ emptyRestList.innerHTML = '';
+ }
+ const restOverhead = measureFragment(emptyRest);
+
+ rest._splitInfo = {
+ type: 'toc',
+ itemHeights: info.itemHeights,
+ overhead: restOverhead,
+ offset: start + bestCount
+ };
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ 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));
+ }
+
+ const rest = el.cloneNode(false);
+ for (let i = bestCount; i < children.length; i++) {
+ rest.appendChild(children[i].cloneNode(true));
+ }
+
+ 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;
+ const relevantRows = rows.slice(start, start + rows.length);
+
+ let bestCount = 0;
+ let bestHeight = 0;
+ const tempTable = createShell();
+ const tempBody = tempTable.querySelector('tbody');
+ for (let i = 0; i < relevantRows.length; i++) {
+ tempBody.appendChild(relevantRows[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(relevantRows[i].cloneNode(true));
+ }
+
+ const rest = createShell();
+ const restBody = rest.querySelector('tbody');
+ for (let i = bestCount; i < relevantRows.length; i++) {
+ restBody.appendChild(relevantRows[i].cloneNode(true));
+ }
+
+ rest._splitInfo = {
+ type: 'table',
+ rowHeights: info.rowHeights,
+ overhead: info.overhead,
+ theadHeight: info.theadHeight,
+ offset: start + bestCount
+ };
+
+ return { first, rest, firstHeight: bestHeight };
+ };
+ }
+
+ // ---------- main pagination loop ----------
let article_index = 0;
- while(true){
- if(article_index === article_seq.length) break;
+ while (article_index < article_seq.length) {
const article = article_seq[article_index];
-
- const raw_element_seq = Array.from(article.children).filter( el =>
- !['SCRIPT' ,'STYLE' ,'RT-PAGE'].includes(el.tagName)
+
+ const raw_element_seq = Array.from(article.children).filter(el =>
+ !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName)
);
- if(raw_element_seq.length > 0){
- const page_seq = [];
- let current_batch_seq = [];
- let current_h = 0;
+ if (raw_element_seq.length === 0) {
+ article_index++;
+ continue;
+ }
- const get_el_height = (el) => {
- const rect = el.getBoundingClientRect();
- const style = window.getComputedStyle(el);
- const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
- return (rect.height || 0) + (margin || 0);
- };
+ const page_seq = [];
+ let current_batch_seq = [];
+ let current_h = 0;
- let i = 0;
- while(true){
- if(i === raw_element_seq.length) break;
- const el = raw_element_seq[i];
- const h = get_el_height(el);
-
- if(current_h + h > page_height_limit && current_batch_seq.length > 0){
- let backtrack_seq = [];
- let backtrack_h = 0;
-
- // Backtrack to rescue any widowed headings at the end of the batch
- while(true){
- if(current_batch_seq.length === 0) break;
- const last_el = current_batch_seq[current_batch_seq.length - 1];
- if(!/^H[1-6]/.test(last_el.tagName)) break;
-
- const popped_el = current_batch_seq.pop();
- backtrack_seq.unshift(popped_el);
- backtrack_h += get_el_height(popped_el);
- }
+ 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(current_batch_seq.length > 0){
+ raw_element_seq.splice(i, 1, rest);
+ } 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 = backtrack_seq;
- current_h = backtrack_h;
- }else{
- // Fallback for an extreme case where the entire page was a cascade of headings
- page_seq.push(backtrack_seq);
current_batch_seq = [];
current_h = 0;
+ raw_element_seq[i] = rest;
}
}
-
- current_batch_seq.push(el);
- current_h += h;
-
- i++;
+ continue;
}
- if(current_batch_seq.length > 0){
- page_seq.push(current_batch_seq);
- }
+ const h = get_el_height(el);
- article.innerHTML = '';
-
- let p = 0;
- while(true){
- if(p === page_seq.length) break;
- const batch_seq = page_seq[p];
- const page_el = document.createElement('rt-page');
- page_el.id = `page-${p + 1}`;
-
- let item_idx = 0;
- while(true){
- if(item_idx === batch_seq.length) break;
- page_el.appendChild(batch_seq[item_idx]);
- item_idx++;
+ if (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 += get_el_height(popped);
}
-
- article.appendChild(page_el);
- p++;
- }
- if(RT.debug){
- RT.debug.log('pagination' ,`Article paginated into ${page_seq.length} pages.`);
+ 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++;
+ }
+
+ if (RT.debug) {
+ RT.debug.log('pagination', `Article paginated into ${page_seq.length} pages.`);
}
article_index++;
}
+
+ if (measureContainer && measureContainer.parentNode) {
+ measureContainer.remove();
+ measureContainer = null;
+ }
};