improved paginator
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 13 Mar 2026 15:14:38 +0000 (15:14 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 13 Mar 2026 15:14:38 +0000 (15:14 +0000)
consumer/release/RT/layout/article_tech_ref.js
consumer/release/RT/layout/paginate_by_element.js
developer/authored/RT/layout/article_tech_ref.js
developer/authored/RT/layout/paginate_by_element.js

index 67fba92..8280bae 100644 (file)
     }
 
     // CSS injection for headers, lists, and layout mapped from theme variables
-// 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; 
+
+    // 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);
-        padding: 2rem;
+        /* 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 {
         display: block;
         padding: 3rem;
         margin: 1.25rem auto;
-        max-width: 46.875rem;
         background-color: var(--rt-surface-0);
         box-shadow: 0 0 0.625rem var(--rt-brand-primary);
       }
@@ -71,7 +90,7 @@
         font-size: 1.5rem;
         text-align: center;
         color: var(--rt-brand-primary);
-        font-weight: 500; /* Softened from the default heavy bold */
+        font-weight: 500;
         margin-top: 1.5rem;
         line-height: 1.15;
       }
         color: var(--rt-brand-tertiary);
         text-align: left;
         margin-top: 1.5rem;
-        margin-left: 4ch; /* Indented 4 characters */
+        margin-left: 4ch;
       }
       rt-article h4 {
         font-size: 1.05rem;
         font-weight: 600;
         text-align: left;
         margin-top: 1.25rem;
-        margin-left: 8ch; /* Indented 8 characters */
+        margin-left: 8ch;
       }
 
       /* --- BODY TEXT (Flush Left) --- */
index e085148..89595ca 100644 (file)
 */
 window.StyleRT = window.StyleRT || {};
 
-window.StyleRT.paginate_by_element = function() {
+window.StyleRT.paginate_by_element = function(){
   const RT = window.StyleRT;
-  
-  // Fix: Read safely without overwriting the config namespace
   const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
   const page_height_limit = page_conf.height_limit || 1000; 
 
   const article_seq = document.querySelectorAll("RT-article");
-  
-  // HURDLE: Error if no articles found to paginate
-  if(article_seq.length === 0) {
-    RT.debug.error('pagination', 'No <RT-article> elements found. Pagination aborted.');
+  if(article_seq.length === 0){
+    RT.debug.error('pagination' ,'No <RT-article> elements found. Pagination aborted.');
     return;
   }
 
-  article_seq.forEach( (article) => {
-    const raw_elements = Array.from(article.children).filter(el => 
-      !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName)
+  let article_index = 0;
+  while(true){
+    if(article_index === article_seq.length) break;
+    const article = article_seq[article_index];
+    
+    const raw_element_seq = Array.from(article.children).filter( el => 
+      !['SCRIPT' ,'STYLE' ,'RT-PAGE'].includes(el.tagName)
     );
 
-    if(raw_elements.length === 0) return;
+    if(raw_element_seq.length > 0){
+      const page_seq = [];
+      let current_batch_seq = [];
+      let current_h = 0;
 
-    const pages = [];
-    let current_batch = [];
-    let current_h = 0;
+      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 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);
-    };
+      let i = 0;
+      while(true){
+        if(i === raw_element_seq.length) break;
+        const el = raw_element_seq[i];
+        const h = get_el_height(el);
 
-    for (let i = 0; i < raw_elements.length; i++) {
-      const el = raw_elements[i];
-      const h = get_el_height(el);
-      const is_heading = /^H[1-6]/.test(el.tagName);
+        if(current_h + h > page_height_limit && current_batch_seq.length > 0){
+          let backtrack_seq = [];
+          let backtrack_h = 0;
 
-      let total_required_h = h;
-      if (is_heading && i + 1 < raw_elements.length) {
-        total_required_h += get_el_height(raw_elements[i + 1]);
-      }
+          // 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);
+          }
+
+          if(current_batch_seq.length > 0){
+            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;
+          }
+        }
 
-      if (current_h + total_required_h > page_height_limit && current_batch.length > 0) {
-        pages.push(current_batch);
-        current_batch = [];
-        current_h = 0;
+        current_batch_seq.push(el);
+        current_h += h;
+
+        i++;
       }
 
-      current_batch.push(el);
-      current_h += h;
-    }
+      if(current_batch_seq.length > 0){
+        page_seq.push(current_batch_seq);
+      }
 
-    if (current_batch.length > 0) pages.push(current_batch);
+      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++;
+        }
+        
+        article.appendChild(page_el);
+        p++;
+      }
 
-    article.innerHTML = ''; 
-    
-    pages.forEach( (list, index) => {
-      const page_el = document.createElement('rt-page');
-      page_el.id = `page-${index+1}`;
-      list.forEach(item => page_el.appendChild(item));
-      article.appendChild(page_el);
-    });
+      if(RT.debug){
+        RT.debug.log('pagination' ,`Article paginated into ${page_seq.length} pages.`);
+      }
+    }
 
-    if (RT.debug) RT.debug.log('pagination', `Article paginated into ${pages.length} pages.`);
-  });
+    article_index++;
+  }
 };
index 67fba92..8280bae 100644 (file)
     }
 
     // CSS injection for headers, lists, and layout mapped from theme variables
-// 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; 
+
+    // 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);
-        padding: 2rem;
+        /* 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 {
         display: block;
         padding: 3rem;
         margin: 1.25rem auto;
-        max-width: 46.875rem;
         background-color: var(--rt-surface-0);
         box-shadow: 0 0 0.625rem var(--rt-brand-primary);
       }
@@ -71,7 +90,7 @@
         font-size: 1.5rem;
         text-align: center;
         color: var(--rt-brand-primary);
-        font-weight: 500; /* Softened from the default heavy bold */
+        font-weight: 500;
         margin-top: 1.5rem;
         line-height: 1.15;
       }
         color: var(--rt-brand-tertiary);
         text-align: left;
         margin-top: 1.5rem;
-        margin-left: 4ch; /* Indented 4 characters */
+        margin-left: 4ch;
       }
       rt-article h4 {
         font-size: 1.05rem;
         font-weight: 600;
         text-align: left;
         margin-top: 1.25rem;
-        margin-left: 8ch; /* Indented 8 characters */
+        margin-left: 8ch;
       }
 
       /* --- BODY TEXT (Flush Left) --- */
index e085148..89595ca 100644 (file)
 */
 window.StyleRT = window.StyleRT || {};
 
-window.StyleRT.paginate_by_element = function() {
+window.StyleRT.paginate_by_element = function(){
   const RT = window.StyleRT;
-  
-  // Fix: Read safely without overwriting the config namespace
   const page_conf = (RT.config && RT.config.page) ? RT.config.page : {};
   const page_height_limit = page_conf.height_limit || 1000; 
 
   const article_seq = document.querySelectorAll("RT-article");
-  
-  // HURDLE: Error if no articles found to paginate
-  if(article_seq.length === 0) {
-    RT.debug.error('pagination', 'No <RT-article> elements found. Pagination aborted.');
+  if(article_seq.length === 0){
+    RT.debug.error('pagination' ,'No <RT-article> elements found. Pagination aborted.');
     return;
   }
 
-  article_seq.forEach( (article) => {
-    const raw_elements = Array.from(article.children).filter(el => 
-      !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName)
+  let article_index = 0;
+  while(true){
+    if(article_index === article_seq.length) break;
+    const article = article_seq[article_index];
+    
+    const raw_element_seq = Array.from(article.children).filter( el => 
+      !['SCRIPT' ,'STYLE' ,'RT-PAGE'].includes(el.tagName)
     );
 
-    if(raw_elements.length === 0) return;
+    if(raw_element_seq.length > 0){
+      const page_seq = [];
+      let current_batch_seq = [];
+      let current_h = 0;
 
-    const pages = [];
-    let current_batch = [];
-    let current_h = 0;
+      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 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);
-    };
+      let i = 0;
+      while(true){
+        if(i === raw_element_seq.length) break;
+        const el = raw_element_seq[i];
+        const h = get_el_height(el);
 
-    for (let i = 0; i < raw_elements.length; i++) {
-      const el = raw_elements[i];
-      const h = get_el_height(el);
-      const is_heading = /^H[1-6]/.test(el.tagName);
+        if(current_h + h > page_height_limit && current_batch_seq.length > 0){
+          let backtrack_seq = [];
+          let backtrack_h = 0;
 
-      let total_required_h = h;
-      if (is_heading && i + 1 < raw_elements.length) {
-        total_required_h += get_el_height(raw_elements[i + 1]);
-      }
+          // 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);
+          }
+
+          if(current_batch_seq.length > 0){
+            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;
+          }
+        }
 
-      if (current_h + total_required_h > page_height_limit && current_batch.length > 0) {
-        pages.push(current_batch);
-        current_batch = [];
-        current_h = 0;
+        current_batch_seq.push(el);
+        current_h += h;
+
+        i++;
       }
 
-      current_batch.push(el);
-      current_h += h;
-    }
+      if(current_batch_seq.length > 0){
+        page_seq.push(current_batch_seq);
+      }
 
-    if (current_batch.length > 0) pages.push(current_batch);
+      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++;
+        }
+        
+        article.appendChild(page_el);
+        p++;
+      }
 
-    article.innerHTML = ''; 
-    
-    pages.forEach( (list, index) => {
-      const page_el = document.createElement('rt-page');
-      page_el.id = `page-${index+1}`;
-      list.forEach(item => page_el.appendChild(item));
-      article.appendChild(page_el);
-    });
+      if(RT.debug){
+        RT.debug.log('pagination' ,`Article paginated into ${page_seq.length} pages.`);
+      }
+    }
 
-    if (RT.debug) RT.debug.log('pagination', `Article paginated into ${pages.length} pages.`);
-  });
+    article_index++;
+  }
 };