endnotes and some new styling tags
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Mon, 1 Jun 2026 09:15:32 +0000 (09:15 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Mon, 1 Jun 2026 09:15:32 +0000 (09:15 +0000)
RT-style-JS_public.tar [new file with mode: 0644]
developer/authored/RT/element/citation.js [new file with mode: 0644]
developer/authored/RT/element/constraint.js [new file with mode: 0644]
developer/authored/RT/element/crossref.js [new file with mode: 0644]
developer/authored/RT/element/symbol.js [new file with mode: 0644]
developer/authored/RT/layout/article_tech_ref.js
developer/authored/RT/tool/to_pdf2.py [new file with mode: 0644]

diff --git a/RT-style-JS_public.tar b/RT-style-JS_public.tar
new file mode 100644 (file)
index 0000000..3967e2d
Binary files /dev/null and b/RT-style-JS_public.tar differ
diff --git a/developer/authored/RT/element/citation.js b/developer/authored/RT/element/citation.js
new file mode 100644 (file)
index 0000000..5c725f6
--- /dev/null
@@ -0,0 +1,56 @@
+window.StyleRT = window.StyleRT || {};
+
+window.StyleRT.citation = function(){
+  const citations = document.querySelectorAll('rt-cite');
+  if(citations.length === 0) return;
+
+  const article = document.querySelector('rt-article');
+  if(!article) return;
+
+  // 1. Ensure the H1 is a direct child of the article so the TOC can see it
+  let endnotesHeader = document.getElementById('endnotes-header');
+  if (!endnotesHeader) {
+    endnotesHeader = document.createElement('h1');
+    endnotesHeader.id = 'endnotes-header';
+    endnotesHeader.innerText = 'Endnotes';
+    article.appendChild(endnotesHeader);
+  }
+
+  // 2. Locate or generate the endnotes list container
+  let endnoteContainer = document.querySelector('rt-endnotes');
+  if(!endnoteContainer) {
+    endnoteContainer = document.createElement('rt-endnotes');
+    article.appendChild(endnoteContainer);
+  }
+  
+  // 3. Ensure the list structure exists
+  if(!endnoteContainer.querySelector('ol')) {
+    endnoteContainer.innerHTML = '<ol></ol>';
+  }
+  
+  const list = endnoteContainer.querySelector('ol');
+
+  // Process each inline citation
+  citations.forEach((cite, index) => {
+    const refNum = index + 1;
+    const refText = cite.getAttribute('ref') || cite.innerHTML;
+    
+    // Transform the inline tag into a superscript link
+    cite.innerHTML = `<sup><a href="#note-${refNum}" id="cite-${refNum}">[${refNum}]</a></sup>`;
+    cite.style.cursor = 'pointer';
+    cite.style.color = 'var(--rt-brand-link)';
+    cite.style.textDecoration = 'none';
+
+    // Append the corresponding entry into the endnotes list
+    const li = document.createElement('li');
+    li.id = `note-${refNum}`;
+    li.innerHTML = `${refText} <a href="#cite-${refNum}" style="text-decoration:none;">&#8617;</a>`;
+    list.appendChild(li);
+  });
+  
+  // Style the container
+  endnoteContainer.style.display = 'block';
+  endnoteContainer.style.marginTop = '1rem';
+  endnoteContainer.style.borderTop = '1px solid var(--rt-surface-3)';
+  endnoteContainer.style.paddingTop = '1rem';
+};
diff --git a/developer/authored/RT/element/constraint.js b/developer/authored/RT/element/constraint.js
new file mode 100644 (file)
index 0000000..dc4b596
--- /dev/null
@@ -0,0 +1,13 @@
+// developer/authored/RT/element/constraint.js
+window.StyleRT = window.StyleRT || {};
+
+window.StyleRT.constraint = function(){
+  document.querySelectorAll('rt-constraint').forEach( (el) => {
+    el.style.display = 'block';
+    el.style.borderLeft = '4px solid var(--rt-state-warning)';
+    el.style.backgroundColor = 'var(--rt-surface-1)';
+    el.style.padding = '1rem';
+    el.style.margin = '1.5rem 0';
+    el.style.color = 'var(--rt-content-main)';
+  });
+};
diff --git a/developer/authored/RT/element/crossref.js b/developer/authored/RT/element/crossref.js
new file mode 100644 (file)
index 0000000..f0b2c60
--- /dev/null
@@ -0,0 +1,15 @@
+// developer/authored/RT/element/crossref.js
+window.StyleRT = window.StyleRT || {};
+
+window.StyleRT.crossref = function(){
+  document.querySelectorAll('rt-crossref').forEach( (el) => {
+    el.style.color = 'var(--rt-brand-link)';
+    el.style.textDecoration = 'underline';
+    el.style.cursor = 'pointer';
+    el.style.fontWeight = '500';
+    
+    // Note: To make this fully context-aware across soft limits,
+    // this module will eventually need to hook into the page 
+    // registry built by paginate_by_element.js.
+  });
+};
diff --git a/developer/authored/RT/element/symbol.js b/developer/authored/RT/element/symbol.js
new file mode 100644 (file)
index 0000000..748ad75
--- /dev/null
@@ -0,0 +1,10 @@
+// developer/authored/RT/element/symbol.js
+window.StyleRT = window.StyleRT || {};
+
+window.StyleRT.symbol = function(){
+  document.querySelectorAll('rt-symbol').forEach( (el) => {
+    el.style.fontFamily = '"Courier New", Courier, monospace';
+    el.style.fontWeight = '600';
+    el.style.padding = '0 0.1em';
+  });
+};
index 0ac7fec..a663f0f 100644 (file)
@@ -32,6 +32,7 @@
   let is_layout_locked = true;
 
   // 5. Declare Dependencies
+  RT.include('RT/element/citation');
   RT.include('RT/core/utility');
   RT.include('RT/element/math');
   RT.include('RT/element/code');
   RT.include('RT/element/TOC');
   RT.include('RT/element/title');
   RT.include('RT/element/theme_selector');
+  RT.include('RT/element/symbol');
+  RT.include('RT/element/constraint');
+  RT.include('RT/element/crossref');
+
+
   RT.include('RT/layout/paginate_by_element');
   RT.include('RT/layout/page_fixed_glow');
+  RT.include('RT/layout/paginate_by_element');
+
   RT.include('RT/core/body_visibility_visible');
 
+
   // 6. The Typography Layout
   RT.article = function(){
     RT.config = RT.config || {};
   };
 
   // 7. The Execution Sequence
-  function run_semantics() {
-    debug.log('scroll'`4. run_semantics starting.`);
+  function run_semantics(){
+    debug.log('scroll' ,`4. run_semantics starting.`);
     if(RT.theme) RT.theme();     
+    if(RT.citation) RT.citation();
     RT.article(); 
     if(RT.title) RT.title(); 
     if(RT.term) RT.term();
     if(RT.math) RT.math();
     if(RT.code) RT.code();
+    if(RT.symbol) RT.symbol();
+    if(RT.constraint) RT.constraint();
+    if(RT.crossref) RT.crossref();
 
-    if(window.MathJax && MathJax.Hub && MathJax.Hub.Queue){
+    if( window.MathJax && MathJax.Hub && MathJax.Hub.Queue ){
       MathJax.Hub.Queue( ["Typeset" ,MathJax.Hub] ,run_layout );
     }else{
       run_layout();
diff --git a/developer/authored/RT/tool/to_pdf2.py b/developer/authored/RT/tool/to_pdf2.py
new file mode 100644 (file)
index 0000000..a493d27
--- /dev/null
@@ -0,0 +1,479 @@
+import re
+import os
+import glob
+from weasyprint import HTML
+
+def process_rt_html(html_str, out_file):
+    # Fix escaped dollar signs
+    html_str = html_str.replace('\\$', '$')
+
+    # Remove hardcoded page breaks that cause blank pages
+    html_str = re.sub(r'<div\s+style=["\']page-break-[^>]*["\']>\s*</div>', '', html_str, flags=re.IGNORECASE)
+
+    css = """
+    <style>
+    @page {
+        size: A4;
+        margin: 25mm 20mm;
+        background-color: #faf9f6;
+    }
+    *, *::before, *::after { box-sizing: border-box; }
+    body {
+        font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+        font-size: 11pt;
+        color: #2c3e50;
+        line-height: 1.6;
+        margin: 0;
+        padding: 0;
+        background-color: #faf9f6;
+    }
+    h1, h2, h3 {
+        color: #1a252f;
+        page-break-after: avoid;
+        font-family: 'Georgia', serif;
+    }
+    h1 {
+        font-size: 16pt;
+        border-bottom: 2px solid #d4af37;
+        padding-bottom: 5px;
+        margin-top: 2em;
+    }
+    h2 {
+        font-size: 14pt;
+        margin-top: 1.5em;
+        color: #2c3e50;
+    }
+    h3 {
+        font-size: 12pt;
+        margin-top: 1.2em;
+        color: #34495e;
+    }
+    .rt-title-block {
+        text-align: center;
+        margin: -25mm -20mm 30px -20mm;
+        padding: 35px 20mm;
+        background-color: #1a252f;
+        color: #ecf0f1;
+    }
+    .rt-title-block h1 {
+        color: #d4af37;
+        border: none;
+        font-size: 20pt;
+        margin: 0 0 15px 0;
+        padding: 0;
+    }
+    .rt-meta {
+        font-family: 'Georgia', serif;
+        font-size: 11pt;
+        color: #bdc3c7;
+        font-style: italic;
+    }
+    .rt-copyright {
+        font-size: 9pt;
+        color: #95a5a6;
+        margin-top: 10px;
+    }
+    .rt-term {
+        border-bottom: 1px dashed #7f8c8d;
+        font-style: italic;
+        color: #2980b9;
+    }
+    .rt-term-plain {
+        font-style: normal;
+    }
+    .rt-neologism {
+        font-weight: bold;
+        color: #c0392b;
+    }
+    .rt-neologism-plain {
+        font-weight: normal;
+    }
+    .toc {
+        background-color: #ffffff;
+        border-left: 4px solid #d4af37;
+        padding: 20px;
+        margin: 25px 0;
+        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+        /* Ensure the TOC breaks naturally, without forcing an empty page */
+    }
+    .toc-title {
+        font-family: 'Georgia', serif;
+        font-size: 14pt;
+        font-weight: bold;
+        color: #1a252f;
+        margin-bottom: 15px;
+    }
+    .toc ul {
+        list-style: none;
+        padding-left: 0;
+        margin: 0;
+    }
+    .toc li {
+        margin-bottom: 8px;
+    }
+    .toc-h1 { font-weight: bold; margin-top: 12px; }
+    .toc-h2 { margin-left: 20px; font-size: 10pt; color: #34495e; }
+    .toc-h3 { margin-left: 40px; font-size: 10pt; color: #7f8c8d; }
+    table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 25px 0;
+        background-color: #ffffff;
+    }
+    th, td {
+        border: 1px solid #ecf0f1;
+        padding: 12px;
+        text-align: left;
+    }
+    th {
+        background-color: #f4f6f7;
+        color: #1a252f;
+        font-weight: bold;
+    }
+    tr:nth-child(even) {
+        background-color: #fafbfc;
+    }
+    ul, ol {
+        margin: 15px 0;
+        padding-left: 25px;
+    }
+    li {
+        margin-bottom: 10px;
+    }
+    /* Let's remove this completely for this specific case to see if it lets the table and TOC share a page */
+    /* .content-start {
+        page-break-before: always;
+    } */
+    </style>
+    """
+
+    # Inject CSS
+    html_str = html_str.replace("</head>", css + "</head>")
+    
+    # Process Title Block
+    title_match = re.search(r'<RT-title\s+([^>]+)>(.*?)</RT-title>', html_str, re.DOTALL | re.IGNORECASE)
+    if title_match:
+        attrs = title_match.group(1)
+        title = re.search(r'title="(.*?)"', attrs).group(1) if 'title="' in attrs else 'Document'
+        author = re.search(r'author="(.*?)"', attrs).group(1) if 'author="' in attrs else ''
+        date = re.search(r'date="(.*?)"', attrs).group(1) if 'date="' in attrs else ''
+        copyright_txt = re.search(r'copyright="(.*?)"', attrs).group(1) if 'copyright="' in attrs else ''
+        
+        title_block = f'''
+        <div class="rt-title-block">
+            <h1>{title}</h1>
+            <div class="rt-meta"><span class="author">{author}</span> | <span class="date">{date}</span></div>
+            <div class="rt-copyright">{copyright_txt}</div>
+        </div>
+        '''
+        html_str = html_str[:title_match.start()] + title_block + html_str[title_match.end():]
+        
+    # Process TOC
+    toc_match = re.search(r'<RT-TOC\s+level="(.*?)"></RT-TOC>', html_str, re.IGNORECASE)
+    if toc_match:
+        level = toc_match.group(1)
+        headings = []
+        if '-' in level:
+            headings = re.findall(r'<h([123])>(.*?)</h[123]>', html_str, re.IGNORECASE)
+        else:
+            headings = re.findall(rf'<h({level})>(.*?)</h{level}>', html_str, re.IGNORECASE)
+            
+        toc_html = '<div class="toc"><div class="toc-title">Table of Contents</div><ul>'
+        for lvl, text in headings:
+            # exclude COVER SHEET from TOC
+            if "COVER SHEET" in text:
+                continue
+            toc_html += f'<li class="toc-h{lvl}">{text}</li>'
+        toc_html += '</ul></div>'
+        
+        html_str = html_str[:toc_match.start()] + toc_html + html_str[toc_match.end():]
+
+    # Term Replacement (First occurrence)
+    seen_terms = set()
+    def term_replace(match):
+        text = match.group(1)
+        l_text = text.lower()
+        if l_text not in seen_terms:
+            seen_terms.add(l_text)
+            return f'<span class="rt-term">{text}</span>'
+        else:
+            return f'<span class="rt-term-plain">{text}</span>'
+            
+    html_str = re.sub(r'<RT-term>(.*?)</RT-term>', term_replace, html_str)
+    
+    # Neologism Replacement (First occurrence)
+    seen_neos = set()
+    def neo_replace(match):
+        text = match.group(1)
+        l_text = text.lower()
+        if l_text not in seen_neos:
+            seen_neos.add(l_text)
+            return f'<span class="rt-neologism">{text}</span>'
+        else:
+            return f'<span class="rt-neologism-plain">{text}</span>'
+            
+    html_str = re.sub(r'<RT-neologism>(.*?)</RT-neologism>', neo_replace, html_str)
+
+    HTML(string=html_str).write_pdf(out_file)
+
+# Locate the uploaded file
+uploaded_files = glob.glob('/mnt/data/user_uploads/*abstract.html')
+if not uploaded_files:
+    uploaded_files = glob.glob('/mnt/data/uploads/*abstract.html')
+if not uploaded_files:
+    # Look for it anywhere under /mnt/data
+    for root, dirs, files in os.walk('/mnt/data'):
+        for file in files:
+            if file == 'abstract.html':
+                uploaded_files.append(os.path.join(root, file))
+
+if uploaded_files:
+    filepath = uploaded_files[0]
+    with open(filepath, 'r', encoding='utf-8') as f:
+        html_content = f.read()
+    process_rt_html(html_content, 'abstract.pdf')
+    print("PDF successfully generated.")
+else:
+    print("Could not find abstract.html in the data directory.")
+
+Code output
+Traceback (most recent call last):
+  File "<xbox-string>", line 1, in <module>
+    import bs4
+ModuleNotFoundError: No module named 'bs4'
+
+Python
+import re
+import os
+import glob
+from weasyprint import HTML
+
+def process_rt_html(html_str, out_file):
+    html_str = html_str.replace('\\$', '$')
+    html_str = re.sub(r'<div\s+style=["\']page-break-[^>]*["\']>\s*</div>', '', html_str, flags=re.IGNORECASE)
+
+    css = """
+    <style>
+    @page {
+        size: A4;
+        margin: 25mm 20mm;
+        background-color: #faf9f6;
+    }
+    *, *::before, *::after { box-sizing: border-box; }
+    body {
+        font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+        font-size: 11pt;
+        color: #2c3e50;
+        line-height: 1.6;
+        margin: 0;
+        padding: 0;
+        background-color: #faf9f6;
+    }
+    h1, h2, h3 {
+        color: #1a252f;
+        page-break-after: avoid;
+        font-family: 'Georgia', serif;
+    }
+    h1 {
+        font-size: 16pt;
+        border-bottom: 2px solid #d4af37;
+        padding-bottom: 5px;
+        margin-top: 2em;
+    }
+    h2 {
+        font-size: 14pt;
+        margin-top: 1.5em;
+        color: #2c3e50;
+    }
+    h3 {
+        font-size: 12pt;
+        margin-top: 1.2em;
+        color: #34495e;
+    }
+    .rt-title-block {
+        text-align: center;
+        margin: -25mm -20mm 30px -20mm;
+        padding: 35px 20mm;
+        background-color: #1a252f;
+        color: #ecf0f1;
+    }
+    .rt-title-block h1 {
+        color: #d4af37;
+        border: none;
+        font-size: 20pt;
+        margin: 0 0 15px 0;
+        padding: 0;
+    }
+    .rt-meta {
+        font-family: 'Georgia', serif;
+        font-size: 11pt;
+        color: #bdc3c7;
+        font-style: italic;
+    }
+    .rt-copyright {
+        font-size: 9pt;
+        color: #95a5a6;
+        margin-top: 10px;
+    }
+    .rt-term {
+        border-bottom: 1px dashed #7f8c8d;
+        font-style: italic;
+        color: #2980b9;
+    }
+    .rt-term-plain {
+        font-style: normal;
+    }
+    .rt-neologism {
+        font-weight: bold;
+        color: #c0392b;
+    }
+    .rt-neologism-plain {
+        font-weight: normal;
+    }
+    .toc {
+        background-color: #ffffff;
+        border-left: 4px solid #d4af37;
+        padding: 20px;
+        margin: 25px 0;
+        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+        /* Ensure no forced break before TOC */
+        page-break-before: auto;
+    }
+    .toc-title {
+        font-family: 'Georgia', serif;
+        font-size: 14pt;
+        font-weight: bold;
+        color: #1a252f;
+        margin-bottom: 15px;
+    }
+    .toc ul {
+        list-style: none;
+        padding-left: 0;
+        margin: 0;
+    }
+    .toc li {
+        margin-bottom: 8px;
+    }
+    .toc-h1 { font-weight: bold; margin-top: 12px; }
+    .toc-h2 { margin-left: 20px; font-size: 10pt; color: #34495e; }
+    .toc-h3 { margin-left: 40px; font-size: 10pt; color: #7f8c8d; }
+    table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 25px 0;
+        background-color: #ffffff;
+    }
+    th, td {
+        border: 1px solid #ecf0f1;
+        padding: 12px;
+        text-align: left;
+    }
+    th {
+        background-color: #f4f6f7;
+        color: #1a252f;
+        font-weight: bold;
+    }
+    tr:nth-child(even) {
+        background-color: #fafbfc;
+    }
+    ul, ol {
+        margin: 15px 0;
+        padding-left: 25px;
+    }
+    li {
+        margin-bottom: 10px;
+    }
+    .content-start {
+        page-break-before: always;
+    }
+    </style>
+    """
+
+    html_str = html_str.replace("</head>", css + "</head>")
+    
+    title_match = re.search(r'<RT-title\s+([^>]+)>(.*?)</RT-title>', html_str, re.DOTALL | re.IGNORECASE)
+    if title_match:
+        attrs = title_match.group(1)
+        title = re.search(r'title="(.*?)"', attrs).group(1) if 'title="' in attrs else 'Document'
+        author = re.search(r'author="(.*?)"', attrs).group(1) if 'author="' in attrs else ''
+        date = re.search(r'date="(.*?)"', attrs).group(1) if 'date="' in attrs else ''
+        copyright_txt = re.search(r'copyright="(.*?)"', attrs).group(1) if 'copyright="' in attrs else ''
+        
+        title_block = f'''
+        <div class="rt-title-block">
+            <h1>{title}</h1>
+            <div class="rt-meta"><span class="author">{author}</span> | <span class="date">{date}</span></div>
+            <div class="rt-copyright">{copyright_txt}</div>
+        </div>
+        '''
+        html_str = html_str[:title_match.start()] + title_block + html_str[title_match.end():]
+        
+    toc_match = re.search(r'<RT-TOC\s+level="(.*?)"></RT-TOC>', html_str, re.IGNORECASE)
+    if toc_match:
+        level = toc_match.group(1)
+        headings = []
+        if '-' in level:
+            headings = re.findall(r'<h([123])>(.*?)</h[123]>', html_str, re.IGNORECASE)
+        else:
+            headings = re.findall(rf'<h({level})>(.*?)</h{level}>', html_str, re.IGNORECASE)
+            
+        toc_html = '<div class="toc"><div class="toc-title">Table of Contents</div><ul>'
+        for lvl, text in headings:
+            if "COVER SHEET" in text:
+                continue
+            toc_html += f'<li class="toc-h{lvl}">{text}</li>'
+        toc_html += '</ul></div>'
+        
+        # Add page break AFTER the TOC instead of before it
+        toc_html += '<div class="content-start"></div>'
+        
+        html_str = html_str[:toc_match.start()] + toc_html + html_str[toc_match.end():]
+
+    seen_terms = set()
+    def term_replace(match):
+        text = match.group(1)
+        l_text = text.lower()
+        if l_text not in seen_terms:
+            seen_terms.add(l_text)
+            return f'<span class="rt-term">{text}</span>'
+        else:
+            return f'<span class="rt-term-plain">{text}</span>'
+            
+    html_str = re.sub(r'<RT-term>(.*?)</RT-term>', term_replace, html_str)
+    
+    seen_neos = set()
+    def neo_replace(match):
+        text = match.group(1)
+        l_text = text.lower()
+        if l_text not in seen_neos:
+            seen_neos.add(l_text)
+            return f'<span class="rt-neologism">{text}</span>'
+        else:
+            return f'<span class="rt-neologism-plain">{text}</span>'
+            
+    html_str = re.sub(r'<RT-neologism>(.*?)</RT-neologism>', neo_replace, html_str)
+
+    HTML(string=html_str).write_pdf(out_file)
+
+uploaded_files = glob.glob('/mnt/data/user_uploads/*abstract.html')
+if not uploaded_files:
+    uploaded_files = glob.glob('/mnt/data/uploads/*abstract.html')
+if not uploaded_files:
+    for root, dirs, files in os.walk('/mnt/data'):
+        for file in files:
+            if file == 'abstract.html':
+                uploaded_files.append(os.path.join(root, file))
+
+if uploaded_files:
+    filepath = uploaded_files[0]
+    with open(filepath, 'r', encoding='utf-8') as f:
+        html_content = f.read()
+    
+    # One last aggressive check to ensure we absolutely strip ANY style="page-break..." strings
+    # in case the earlier regex missed a specific formatting
+    html_content = re.sub(r'style="[^"]*page-break-after:\s*always;?[^"]*"', '', html_content, flags=re.IGNORECASE)
+
+    process_rt_html(html_content, 'abstract_no_gap.pdf')
+    print("PDF successfully generated.")
+else:
+    print("Could not find abstract.html in the data directory.")