/*
WISHLIST:
Hyphenation when wrapping
Pretty wrapping
Justify text stretching hyphens
*/

//This code begins with initialization of variables and transitions to defining the functions which contain almost all of the code. At the very end of the file is the code which actually triggers these functions when the page is opened.

//INITIALIZATION

//amount of rows and columns to be used in the newspaper "grid"
//currently, values other than 3 in both directions are not tested or fully supported because my newspaper doesn't use them, but this can be updated if that changes in the future (UPDATE 6/28/25: working on it!)
var doccols = 3;
var docrows = 3;

//This utility function is used to load XML files from the server upon which this script is hosted. The theme file contains many of the parameters for how the program generates a newspaper format. The newspaper file is the input to the program. The purpose of the program is to render a print edition of a newspaper based on the content and layout instructions in the newspaper XML file, for printing or conversion to a PDF.
//This function from Dave Burton, https://stackoverflow.com/questions/36921947/read-a-server-side-file-using-javascript
function loadFile(filePath) {
  var result = null;
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", filePath, false);
  xmlhttp.send();
  if (xmlhttp.status==200) {
    result = xmlhttp;
  }
  return result;
}

//https://stackoverflow.com/questions/273789/javascript-string-indexof-allowing-regular-expressions
function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}

//https://stackoverflow.com/questions/273789/javascript-string-indexof-allowing-regular-expressions
function regexLastIndexOf(stringToWorkWith, regex) {
    regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : ""));
    var lastIndexOf = -1;
    var nextStop = 0;
    var result;
    while((result = regex.exec(stringToWorkWith)) != null) {
        lastIndexOf = result.index;
        regex.lastIndex = ++nextStop;
    }
    return lastIndexOf;
}

//Used later on when content inside the XML file needs to be converted back from a JavaScript XML DOM to text, namely to import a template from the theme XML file into the document body to act as the foundation for a page to be rendered on.
var serializer = new XMLSerializer();

//Newspaper and theme XML files, stored as JavaScript XML DOM documents.
var newspaper = null;
var theme = null;

var trs; //list containing each row (tr element) in the grid in which newspaper content is laid out, from the DOM of the document being rendered
var tdgrid; //two-dimensional array of cells (td elements) from the grid
var inputgrid; //equivalent two-dimensional array, but of each cell's corresponding "BLOCK" XML element from the input newspaper XML file

var map; //this is an element in the theme XML file, the other (and usually non-varying) input to the program. The map contains definitions for every custom element (for example, a byline which is rendered as a paragraph <p> with class byline) and attribute which can be used in the newspaper XML file to define content to render in blocks or articles. If an element used is not listed in the map, an HTML element of the same name will be rendered. Also included is the CSS selector for which elements in a wide block should be rendered across multiple cells, like a headline with two columns of text underneath it 

var loadingpagenum = 0; //global variable storing the page number of the page currently being loaded, which is defined in the newspaper XML file. There is not support for one file with multiple pages of the same page number.

var displaypage = -1; //Variable for an UNIMPLEMENTED feature which would allow only one page of a newspaper to be displayed using an URL parameter (i.e. "?page=5;"). -1 would indicate that all pages should be loaded. 

var overrects = []; //07/02/25 For list of bounding rectangles of overlays on a page

//utility function to load a CSS style sheet into the document
function loadStylesheet(sheetpath) {
  var head = document.getElementsByTagName("head")[0];
  var link = document.createElement("link");
  link.rel = "stylesheet";
  link.type = "text/css";
  link.href = sheetpath;
  head.appendChild(link);
}

//utility function to load a script into the document
function loadScript(scriptpath) {
  var body = document.getElementsByTagName("body")[0];
  var script = document.createElement("script");
  script.src = scriptpath;
  body.appendChild(script);
}

//This function is used with the HTML template for each page type from theme.xml (for example, a front page vs. a page further on where the header is much smaller and contains different content). It allows use of variables in said template, for example to place the date of the newspaper in the header of each page. The format for a variable is !! followed by four letters and a semicolon, and the values are defined as custom tags in the newspaper XML file, preferably in the <head> thereof.
function vars(html) {
  pos = 0;
  while (true) {
    pos = html.indexOf("!!",pos);
    if (pos < 0) break;
    semic = html.indexOf(";",pos);
    if ((semic - pos) == 6) {
      token = html.substring(pos, semic+1);
      id = token.substring(2,6).toLowerCase();
      
      //Special provision for a variable called !!page; to return the page number of the page being currently rendered, instead of a newspaper-wide value.
      if (id === "page") {
          html = html.replace(token, loadingpagenum);
          pos++;
          continue;
      }
      
      try {
        val = newspaper.getElementsByTagName(id)[0].childNodes[0].nodeValue;
        html = html.replace(token, val);
      } catch {}
    }
    pos += 1;
  }
  return html;
}

//This function initalizes the document before content is loaded, requiring the path of the newspaper and theme XML files (to load and parse into DOM documents to be used in the rest of the program)
function loadNewspaper(newspath, themepath) {
  newspaper = loadFile(newspath).responseXML;
  theme = loadFile(themepath).responseXML;
  
  //set title
  titles = newspaper.getElementsByTagName("title");
  if (titles.length != 0) {
      document.title = titles[0].childNodes[0].textContent;
  }

  //load any <default> elements in the theme file, which can be used to set a default value for one of the variables found by the vars() function. The <default> adds an element to the bottom of the newspaper file, so it will only be the definition of a variable if no definition is found above it, i.e. it wasn't defined at all.
  try {
    defaults = theme.getElementsByTagName("default")[0];
    defaults.childNodes.forEach((node) => {var innode = newspaper.importNode(node, true);newspaper.getElementsByTagName("newspaper")[0].appendChild(innode);});
  } catch {}
  
  //load map
  map = theme.getElementsByTagName("map")[0];
  
  
  var stylesheets = Array.prototype.slice.call(theme.getElementsByTagName("css"));
  stylesheets.forEach((elem) => {loadStylesheet(elem.childNodes[0].nodeValue);});
  
}

//THE BELOW IS CONCERNED WITH RENDERING PAGES *BEFORE* ARTICLES ARE LOADED ON TO THEM. THIS CAN INCLUDE HEADLINES, IMAGES, NOTES, ETC., BUT NOT ARTICLE CONTENT, BECAUSE ARTICLE CONTENT CAN BE LOADED OVER MULTIPLE BLOCKS AND PAGES DEPENDING ON WHERE SPACE IS ALLOCATED TO THE ARTICLES AND HOW MUCH SPACE IS LEFT IN EACH COLUMN AFTER THIS STUFF IS ALL FULLY LOADED

//Adds the classes which define borders between articles to content in the document based on each cell's corresponding input <block> from the newspaper XML file
function renderSkeleton(block, td, x, y, w, l) {
  if (block == null) return null; //Doesn't run this function over a cell where there is no input block from the newspaper XML file, i.e. there is no content defined for it. The CSS and JavaScript which renders the borders is liable to break if used on empty cells, and it might also create borders around cells that are "empty" because a different block actually overflows into them, like above a cell that is the second row of a two-row article.
  
  //render vertical borders
  if (x > 1) {
    td.classList.add("ldiv");
  }
  console.log();
  if ((x + w - 1) < doccols) {
    td.classList.add("rdiv");
  }

  //render horizontal borders
  //if (y > 1) {
    if (x == 1 && (tdgrid[y-1].filter(x => x===null).length >= inputgrid[y-1].filter(x => x===null).length)) {
      if (y > 1) trs[y-1].classList.add("lineabove");
      //trs[y-1].classList.add("linebelow");
    } else {
      if (y > 1) td.classList.add("partlineabove");
      //td.classList.add("partlinebelow");
      if(w == 1) {
        td.classList.add("onecol");
      } else {
        td.style.setProperty("--pbcols",w);
      }
    }
  //}
  
  //ADDITION 08/20/24
  /*if (y > 1) {
      var testy = y-1;
      while (testy !== 0) {
          var testtd = tdgrid[testy-1][x-1];
          if (trs[testy-1].classList.contains("linebelow")) {
              trs[y-1].classList.add("lineabove");
              td.classList.remove("partlineabove");
              break;
          }
          if (testtd) {
              if (testtd.classList.contains("partlinebelow")) {
              var testcols = testtd.style.getPropertyValue("--pbcols");
              if (testcols) {
                  if ((testcols * 1) > w) {td.style.setProperty("--pbcols", testcols); break;}
              }
          }}
          testy = testy - 1;
      }
  }*/
  if (x > 1) {
      var testx = x-1;
      while (testx !== 0) {
          var cll = tdgrid[y-1][testx-1];
          if (cll) { if (cll.classList.contains("partlineabove")) {
              if ((cll.style.getPropertyValue("--pbcols") >= (x-testx)) || (cll.classList.contains("onecol") && (x - testx) == 1)) {
                  var cls = 1;
                  if (cll.style.getPropertyValue("--pbcols")) cls = cll.style.getPropertyValue("--pbcols");
                  var curr = 1;
                  if (td.style.getPropertyValue("--pbcols")) curr = td.style.getPropertyValue("--pbcols");
                  cll.style.setProperty("--pbcols", parseInt(curr) + parseInt(cls));
                  cll.classList.remove("onecol");
                  td.classList.remove("partlineabove");
              } else {
                  break;
              }
          }}
          testx = testx - 1;
      }
  }
  
  
  if (block.getAttribute("breakabove") == "true") {
    trs[y-1].classList.add("hardabove");
  }

}

var exclitems = []; //items which were rendered into the long content block (for example, the headlines and images which stretch across multiple columns of text) and shouldn't be duplicated into the <div>s of class "dco" ("div container") which contain content in each column

//renders all the elements placed within a "block" in the newspaper XML file to a cell (the td) in a certain position on the document (defined using its x and y coordinates on the grid and its length and width)
function renderElements(block, td, x, y, w, l) {
    
    //07/01/25—TODO: Add to Article Loading Too?
    let boxes = block.getElementsByTagName("box");
    for (let box of boxes) {
        renderElement(box, td);
        exclitems.push(box);
    }
    
  if (w != 1) {
      var longcont = td.ownerDocument.createElement("div");
      longcont.classList.add("longcont");
      var query = map.getElementsByTagName("longcontselector")[0].childNodes[0].nodeValue;
      var longitems = block.querySelectorAll(query);
      for (const longelem of longitems) {
          if (longelem.hasAttribute("col") && longelem.getAttribute("col") != "long") continue;
          renderElement(longelem, longcont);
          exclitems.push(longelem);
      }
      td.appendChild(longcont);
  }
  var col = 1;
  while (col <= w) {
    dco = td.ownerDocument.createElement("div");
    dco.classList.add("dco");
    if (block.hasAttribute("article")) {
      dco.classList.add("article-"+block.getAttribute("article"));
    }
    var selector = "block > [col='"+col+"']";
    if (col === 1) selector = selector + ", block > *:not([col])";
    var dcoitems = block.querySelectorAll(selector);
    renderElementGroup(dcoitems, dco);
    td.appendChild(dco);
    
    //addition 06/29/25
    dco.style.marginLeft = (dco.getBoundingClientRect() * (td.getElementsByClassName("dco").length - 1)) + "px";
    //console.log(( * (td.getElementsByClassName("dco").length - 1)) + "px THING!!");
    //alert(window.getComputedStyle(dco).getPropertyValue("--cwidth"))
    
    col += 1;
  }
  exclitems = [];
  
}

//taking an iterable group of elements from the DOM document for the newspaper XML file, renders each one to a particular destination in the document to display
function renderElementGroup(group, dest) {
  try {
    group.forEach((node) => {if (((node.nodeType === node.ELEMENT_NODE) || (node.nodeType === node.TEXT_NODE)) && !(exclitems.includes(node))) {renderElement(node, dest);}});
  } catch {}
}

//wrapper function around renderElementGroup
function renderChildElements(parent, dest) {
  renderElementGroup(parent.childNodes, dest);
}

//renders an individual element of the newspaper XML file into a destination on the page, using the map to translate between custom elements and HTML elements
function renderElement(node, dest) {
  
  if (node.nodeType === node.TEXT_NODE) {
    var tele = dest.ownerDocument.createTextNode(node.nodeValue);
    dest.appendChild(tele);
    return;
  }
  
  var tag = node.tagName;
  var classes = [];
  try {
    mapping = map.querySelector("tag[name='"+tag+"']");
    if (mapping != null) {
      tag = mapping.getAttribute("val");
      classes = mapping.getAttribute("class").split(" ");
    }
  } catch {}
  
  var ele = dest.ownerDocument.createElement(tag);
  ele.classList.add(...classes);
  
  //handle attributes
  attribs = node.attributes;
  for (const attrib of attribs) {
    var name = attrib.name;
    var value = attrib.value;
    
    var prop = map.querySelector("tag[name='"+node.tagName+"'] prop[name='"+name+"']:not([val]), tag[name='"+node.tagName+"'] prop[name='"+name+"'][val='"+value+"']");
    
    if (prop === null) prop = map.querySelector("map > prop[name='"+name+"']:not([val]), map > prop[name='"+name+"'][val='"+value+"']");
    
    
    if (prop == null) {
      ele.setAttribute(name,value);
      continue;
    }
    if (prop.hasAttribute("unit")) {
      value = value + prop.getAttribute("unit");
    }
    if (prop.hasAttribute("style")) {
      ele.style.setProperty(prop.getAttribute("style"), value);
    }
    if (prop.hasAttribute("attr")) {
      ele.setAttribute(prop.getAttribute("attr"), value);
    }
    if (prop.hasAttribute("class")) {
      ele.classList.add(prop.getAttribute("class"));
    }
    
  }
  
  renderChildElements(node, ele);
  dest.appendChild(ele);
}

//renders a block by first rendering the layout of the dividers around it and then rendering the content within it; this DOES NOT render articles into the blocks
function renderBlock(block, td, x, y, w, l) {
  renderSkeleton(block, td, x, y, w, l);
  renderElements(block, td, x, y, w, l);
}

//renders a whole page of blocks, primarily by triggering the renderBlock function on each block and its destination <td> element after a lot of initialization to create the grid needed; DOES NOT RENDER ARTICLES
function loadPage(num) {
    
  trs = [];
  tdgrid = [];
  inputgrid = [];
    
  loadingpagenum = num;
  //load page template and style sheets
  var page = newspaper.querySelector("edition page[num='"+num+"']");
  var typename = page.getAttribute("type");
  var type = theme.querySelector("page[name='"+typename+"']");
  var html = serializer.serializeToString(type.querySelector("div"));
  html = vars(html);
  document.body.innerHTML = document.body.innerHTML + html;


  var dctrs = document.body.getElementsByClassName("dctr");
  var dctr = dctrs[dctrs.length - 1];

  dctr.style.setProperty("--PageNum", num);
  //7-17-24 interface with specific page loading for frames
  dctr.id = "pg"+num;

//06/28/25
var varth = parseInt(window.getComputedStyle(dctr).getPropertyValue("--th"));
var vartw = parseInt(window.getComputedStyle(dctr).getPropertyValue("--tw"));

if (isNaN(varth) || isNaN(vartw) || !varth || !vartw) {varth = 3; vartw = 3; dctr.style.setProperty("--tw","3"); dctr.style.setProperty("--th", "3");}
if (!(varth >= 1 && varth <= 6 && vartw >= 1 && vartw <= 6)) throw new Error("Improper grid dimensions");

docrows = varth;
doccols = vartw;

  //build table elements grid
  var tdrow = Array(docrows)
  var inputrow = Array(docrows)
  var x = 1;
  var y = 1;
  trs = dctr.getElementsByClassName("mctr")[0].childNodes[1].childNodes;
  
  //06/29/25
  if (trs.length == 1) for (let i=2; i <= docrows; i++) {
      let newt = document.createElement("tr"); newt.setAttribute("y",i); trs[0].parentNode.appendChild(newt);
  }
  
  var tr = trs[0];
  if (page.getAttribute("row1height")) tr.style.setProperty("--cheight",page.getAttribute("row1height"));
  
  while (y <= docrows) {
    //console.log(x+", "+y);
    var td = document.createElement("td");
    td.classList.add("x"+x);
    td.classList.add("y"+y);
    td.classList.add("mainlayout");

    //get input block, if any
    var inputblock = null;
    try {
      inputblock = page.querySelector("block[x='"+x+"'][y='"+y+"']");
    } catch {}
    
    //colspan handling, rows
    var col = 1;
    var w = 1;
    var l = 1;
    try{
      w = parseInt(inputblock.getAttribute("w"));
      if (w > 1) {
        col = w;
        td.colSpan = w;
      }
      l = parseInt(inputblock.getAttribute("l"));
      td.style.setProperty("--rows",l);
      
      //OCT 27 2025
      if(inputblock.getAttribute("style")) td.setAttribute("style",td.getAttribute("style") + inputblock.getAttribute("style"));
      
    } catch {}
    
    tr.appendChild(td);
    tdrow[x-1] = td;
    inputrow[x-1] = inputblock;
    x += col;
    if (x > doccols) {
      x = 1;
      y += 1;
      tr = trs[y-1];
      if (page.getAttribute("row"+y+"height")) tr.style.setProperty("--cheight",page.getAttribute("row"+y+"height"));
      tdgrid.push(tdrow);
      tdrow = Array(docrows);
      inputgrid.push(inputrow);
      inputrow = Array(docrows);
    }
  }
  console.log(tdgrid);
  console.log(inputgrid);

  //render each block
  x = 1;
  y = 1;
  while (y <= docrows) {
    try {
      renderBlock(inputgrid[y-1][x-1], tdgrid[y-1][x-1], x, y, parseInt(inputgrid[y-1][x-1].getAttribute("w")), parseInt(inputgrid[y-1][x-1].getAttribute("l")));
    } catch {}
    x += 1;
    if (x > doccols) {
        x = 1;
        y += 1;
    }
  }
  
  loadingpagenum = 0;

}

//THE BELOW CONCERNS THE RENDERING OF ACTUAL ARTICLE CONTENT, STORED IN THE HEAD OF THE NEWSPAPER XML FILE AND THEN REFERENCED IN EACH BLOCK IN SAID FILE THAT DEFINES AN ARTICLE SOURCE IN ITS ATTRIBUTES

//Creates the (See page X...) or (... from page X) text that must be inserted where an article is "jumped" between separate blocks, or returns nothing if the article is merely continuing on a different column of the same block
function getCutoffFill(from, to) {
    if (to == null) return [document.createTextNode(""), document.createTextNode("")];
    if (from.closest(".mainlayout") != to.closest(".mainlayout")) {
        var innerto = to.closest(".dctr").style.getPropertyValue("--PageNum"); 
        var too = document.createElement("jump-");
        too.innerHTML = innerto;
        var innerfro = from.closest(".dctr").style.getPropertyValue("--PageNum");
        var fro = document.createElement("jumpback-");
        fro.innerHTML = innerfro;
        return [too, fro];
    }
    return [document.createTextNode(""), document.createTextNode("")];
}

//If tocut is an element that is overflowing its container, this function removes it or its children in reverse order until it no longer does, and then adds what was cut from the bottom of the element to a new destination dest.
function cutElement(tocut, dest) {
    console.log("Cutting element:")
    console.log(tocut);
    
    var replace = tocut.cloneNode();
    
    var endsjump = false;
    
    if (tocut.nodeType == tocut.TEXT_NODE) {
        var text = tocut.nodeValue;
        var cut = "";
        while (text != "") {
            //var lastspace = text.lastIndexOf(" ");
            var lastspace = regexLastIndexOf(text, /[\s\-–—]+/);
            cut = text.substring(lastspace)+cut;
            text = text.substring(0,lastspace);
            tocut.nodeValue = text;
            if (dest.scrollHeight <= dest.clientHeight) {
                replace.nodeValue = cut;
                return replace;
            }
        }
    }
    var i = tocut.childNodes.length - 1;
    var success = false;
    
    var original = tocut.innerHTML;
    
    while ((tocut.childNodes.length > 0) && !success) {
        if (i == -1) {
            if (endsjump) {
                var bugfix = tocut.childNodes[0]; //this variable is called bugfix because I added it while fixing a bug
                bugfix.remove();
                bugfix.classList.add("jumpalignseparateright");
                try {tocut.previousSibling.append(bugfix);} catch {}
            }
            break;
        }
        console.log("TOCUT CHILD NODES (i = " + i + ")");
        console.log(tocut.childNodes);
        var node = tocut.childNodes[i];
        if (node.nodeType == node.ELEMENT_NODE) {
            if (node.tagName.toLowerCase() === "jump-" && i == tocut.childNodes.length - 1) {
                endsjump = true;
                i--;
                continue;
            }
        }
        var nodeparent = node.parentElement;
        node.remove();
        if (dest.scrollHeight <= dest.clientHeight) {
            //this node is the node which overflows the paragraph
            if (endsjump) nodeparent.insertBefore(node, nodeparent.lastChild); else nodeparent.appendChild(node);
            node = cutElement(node, dest);
            success = true;
        }
        console.log("Prepending:");
        console.log(node);
        console.log(replace);
        replace.prepend(node);
        console.log(replace.innerHTML);
        i--;
    }
    
            console.log("Done cutting element:");
            console.log(tocut);
            console.log(replace);
    
    if (tocut.nodeType == tocut.ELEMENT_NODE && original != replace.innerHTML) {
        replace.classList.add("genite");
        tocut.classList.add("cutite");
    } else tocut.remove();
    
    return replace;
}

//This function contains the bulk of the logic for loading a given article into its destinations, spreading the content across however much space it needs if multiple columns or blocks are provided for it.
function loadArticle(article) {
    console.log("attempting to load article:");
    console.log(article);
    var name = article.getAttribute("name");
    var dests = document.getElementsByClassName("article-"+name);
    if (dests.length === 0) {
        console.log("article has no destinations.")
        return;
    }
    var elements = Array.from(article.childNodes);
    var i = 0;
    var ei = 0;
    var wrapped = false;
    var nextbackjump = null;
    var backjumpindex = 0;
    while (ei < elements.length) {
        if (i >= dests.length) {
            i = 0;
            ei += 1;
            wrapped = false;
            continue;
        }
        var element = elements[ei];
        var dest = dests[i];
        var textElement = false;
        try {textElement = (element.tagName.toLowerCase() === "p" || element.tagName.toLowerCase() === "ul" || element.tagName.toLowerCase() === "q" || element.tagName.toLowerCase() === "b");} catch {}
        
        var nextdest = null;
        if ((i + 1) < dests.length) nextdest = dests[i+1];
        var cutoff = getCutoffFill(dest, nextdest);
        renderElement(element, dest);
        if (wrapped) {
            console.log("logging children and backjumpindex")
            console.log(dest.children);
            console.log(backjumpindex);
            var fc = /*dest.firstChild*/dest.children[backjumpindex];
            
            if (((" "+fc.className+" ").indexOf(" genite ")) != -1) {
                fc.prepend(nextbackjump);
            } else {
                /*dest.prepend(nextbackjump);*/fc.parentNode.insertBefore(nextbackjump, fc);
            }
            nextbackjump = null;
        }
        
        //07/05/25
        tryDcoLap(dest);
        
        if (textElement) dest.lastChild.appendChild(cutoff[0]); else dest.appendChild(cutoff[0]); //IMPORTANT: adding the cutoff here must be reversed if loop continues w/o wrapping. Otherwise, a cut- element should also be provided.
        
        if (dest.scrollHeight > dest.clientHeight) {
            if (i >= (dests.length - 1)) {
                i = 0;
                ei+= 1;
                wrapped = false;
                continue;
            }
            if (textElement) {
                var tocut = dest.lastChild; //we already know that cutoff was inserted TO last child because of the if else statement for where the cutoff is inserted
                var replace = cutElement(tocut, dest);
                replace.classList.add("genpara", "genite");
                
                i += 1;
                var cutite = tocut;
                if (tocut.tagName.toLowerCase() === "ul") cutite = tocut.lastChild;
                if (((" "+cutite.className+" ").indexOf(" cutite ")) != -1) cutite.appendChild(document.createElement("cut-"));
                elements[ei] = replace;
                wrapped = true;
                nextbackjump = cutoff[1];
                if (i >= dests.length) continue;
                console.log("setting backjumpindex")
                console.log(dests[i].innerHTML);
                backjumpindex = dests[i].childElementCount
                continue;
            }
            if (wrapped) {
                i += 1;
                ei += 1;
                wrapped = false;
                continue;
            } //ELSE:
            dest.lastChild.previousSibling.remove();
            i += 1;
            wrapped = true;
            nextbackjump = cutoff[1];
            if (i >= dests.length) continue;
            console.log("setting backjumpindex")
            console.log(dests[i].innerHTML);
            backjumpindex = dests[i].childElementCount;
            continue;
        }
        cutoff[0].remove();
        ei += 1;
        wrapped = false;
        
    }
}

//THIS IS CALLED AFTER THE NON-ARTICLE CONTENT OF EVERY PAGE IS LOADED WITH loadPage and then calls the "theme"'s custom scrips, which in the case of my own "theme" (very likely the only one ever to be developed) is the script.js file which primarily exists to create vertical dividers between articles, since I couldn't get it to look quite right in my CSS alone
//IT SETS AN EVENT SO THAT WHEN THAT IS FINISHED IT WILL CALL fillPages, WHICH BEGINS THE PROCESS OF ACTUALLY LOADING IN ARTICLE CONTENT
function finishPages() {

  document.body.addEventListener("themeLoaded", (event) => {fillPages();});
  
  //load scripts
  var scripts = Array.prototype.slice.call(theme.getElementsByTagName("script"));
  scripts.forEach((elem) => {loadScript(elem.childNodes[0].nodeValue);});

}

//render article content onto the page
function fillPages() {
    
    //07/02/25
    listOverrects();
    for (let dco of document.querySelectorAll("div.dco")) {tryDcoLap(dco);}
    
  var articles = newspaper.getElementsByTagName("article");
  console.log(articles);
  for (const article of articles) {
      try {loadArticle(article);} catch (err) {try{console.log("ERROR WHILE LOADING ARTICLE "+article.getAttribute("name")+":");}catch{} console.log(err);}
  } 
  //bugfix regarding the "jumps" which say which page to go to for a continuation of an article sometimes not being the last thing rendered because of an issue in my logic with them and the separate "cut" elements which keep the justified text from de-justifying on the last line before a break to a new column or block
  for (jump of document.querySelectorAll("p + jump-")) {
      console.log(jump);
      console.log(jump.previousElementSibling);
      console.log(jump.parentNode);
      ps = jump.previousElementSibling;
      jump.remove();
      ps.appendChild(jump);
      ps.appendChild(document.createElement("cut-"));
  }
  
}

//loads all pages and blocks and then sets the event so when the pages are fully loaded the theme script and then the article loading will be called
function loadAllNewspages(n, t) {
  loadNewspaper(n, t);
  
  //addition sept 6 2024
  var styl = newspaper.querySelector("globals style");
  if (styl) {
      document.body.style = styl.textContent;
  }
  //addition oct 17 2025
  var pgtypes = newspaper.querySelectorAll("globals page[name]");
  let cuspgs = theme.createElement("cuspgs");
  theme.getElementsByTagName("theme")[0].prepend(cuspgs);
  if(pgtypes) for (pgtype of pgtypes) {
      cuspgs.appendChild(pgtype);
  }
  
  for (pg of newspaper.getElementsByTagName("edition")[0].getElementsByTagName("page")) {
      loadPage(pg.getAttribute("num"));
  }
    
  //set to fill page on load of the above
  document.body.setAttribute("onload","finishPages()");
  
  //oct 2025
  document.body.setAttribute("lang","en-US");
}


//ACTUAL MAIN RUN CODE!!! Initialization in order to run loadAllNewspages

var n = "newspaper.xml"; //newspaper path
var t = "theme.xml"; //theme path

//for some experimental work with using this site within an iFrame and not immediately running it in order to instead load XML that isn't in a server-hosted file; not done yet
window.loadWith = function(news, them) {
    n = news;
    t = them;
    loadAllNewspages(n,t);
    
    Promise.all(Array.from(document.images).filter(img => !img.complete).map(img => new Promise(resolve => { img.onload = img.onerror = resolve; }))).then(() => {
    finishPages();
});
}

const params = new URLSearchParams(window.location.search);

if (!params.has("norun")) { //this flag tested for the aforementioned iFrame feature that is WIP
    
    if (params.has("theme")) t = params.get("theme");
    if (params.has("file")) n = params.get("file");
    
    if (params.has("pg")) {
        alert("direct page URL support \"coming soon\"");
    } else {
        loadAllNewspages(n,t);
    }

}

//07/05/25
function dcoLapEle(ele, rect, overrect, keepright, index) {
    console.log(ele);
    /*try {if(ele.textContent.includes("actually started as just a musician")) alert("here!")} catch (error) {}*/
    let elrect = null;
    try {
        elrect = ele.getBoundingClientRect();
        if (elrect.right <= overrect.left || elrect.left >= overrect.right || elrect.bottom <= overrect.top || elrect.top >= overrect.bottom) return;
    } catch {}
    
    if (ele.nodeType == ele.ELEMENT_NODE) {
        if (ele.classList.contains("genite")) {
            if (ele.hasAttribute("genitechecked"+index)) return;
            ele.setAttribute("genitechecked"+index, "");
            ele.removeAttribute("checked"+index);
        }
        if (ele.hasAttribute("checked"+index)) return;
        ele.setAttribute("checked"+index, "");
        if (ele.innerText != "" || ele.classList.contains("dco")) {
            //var talign = window.getComputedStyle(ele).textAlign;
            //ele.style.textAlign = "left";
            let children = Array.from(ele.childNodes);
            for (child of children) {
                dcoLapEle(child, rect, overrect, keepright, index);
            }
            //ele.style.textAlign = talign;
            return;
        } else {
            let prev;
            if (keepright) {prev = parseFloat(window.getComputedStyle(ele).marginLeft);} else {prev = parseFloat(window.getComputedStyle(ele).marginRight);}
            if (isNaN(prev)) prev = 0;
            /*if (ele.tagName == "LI") {
                let prevv;
                if (keepright) {prevv = parseFloat(window.getComputedStyle(ele.parentElement).paddingLeft);} else {prevv = parseFloat(window.getComputedStyle(ele.parentElement).paddingRight);}
                if (isNaN(prevv)) prevv = 0;
                prev += prevv;
                
        }*/
            if (keepright) {ele.style.marginLeft = (prev + (overrect.right - elrect.left)) + "px";} else {ele.style.marginRight = (prev + (elrect.right - overrect.left)) + "px";}

        }
    } else if (ele.nodeType == ele.TEXT_NODE) {
        let range = document.createRange();
        let i = 0;
        let j = 0;
        //console.log("starting loop!");
        while (i != -1) {
            /*console.log(ele.nodeValue);
            console.log("I: " + i);
            console.log(j);
            console.log("Index: "+ele.nodeValue.indexOf(" ", 2));*/
            if (ele.nodeValue.length == 0) return;
            j = regexIndexOf(ele.nodeValue, /[\s\-–—]+/, i+1);
            if (i == 0) i = -1;
            range.setStart(ele, i+1);
            if (j != -1 && (ele.nodeValue.length >= j)) {range.setEnd(ele, j);} else {range.setEnd(ele, ele.nodeValue.length)};
            i = j;
            let wordrect = range.getBoundingClientRect();
            if (overrect.top - wordrect.bottom >= 2 || wordrect.top - overrect.bottom >= 2) continue;
            range.setEnd(ele, ele.nodeValue.length);
            break;
            /*if (wordrect.right <= overrect.left || wordrect.left >= overrect.right || wordrect.bottom <= overrect.top || wordrect.top >= overrect.bottom) continue;
            let span = document.createElement("span");
            range.surroundContents(span);
            span.style.backgroundColor = "blue";
            if (keepright) {
                span.style.marginLeft = (overrect.right - rect.left) + "px";
            } else {
                span.style.marginRight = (rect.right - overrect.left) + "px";
            }
            ele = span.nextSibling;
            i = 0;
            j = ele.nodeValue.indexOf(" ", 2);*/
        }
        let span = document.createElement("span");
        span.classList.add("wrapspan");
        range.surroundContents(span);
        if (span.innerHTML.trim() == "") {span.remove(); return;}
        span.style.display = "inline-block";
        //span.style.backgroundColor = "blue";
        if (span.previousSibling.nodeValue.trim() == "") {
            span.classList.add("indent");
            span.parentNode.classList.add("nindent");
        }
        //span.style.width = rect.width+"px"
        
        if (!(rect.right - overrect.right >= 50 || overrect.left - rect.left >= 50)) {
            let spacediv = document.createElement("div");
            spacediv.style.height = (overrect.bottom - span.getBoundingClientRect().top + 2) + "px";
            span.parentNode.insertBefore(spacediv, span);
            ele.parentNode.style.textAlignLast = "justify";
            return;
        }
        
        if (keepright) {
            span.style.paddingLeft = (overrect.right - rect.left) + "px";
        } else {
            span.style.paddingRight = (rect.right - overrect.left) + "px";
        }
        if (span.getBoundingClientRect().bottom > overrect.bottom) {
            let nele = span.firstChild;
            i = 0;
            j = 0;
            //console.log("starting loop!" + span.textContent);
            while (i != -1) {
                /*console.log(nele.nodeValue);
                console.log("I: " + i);
                console.log(j);
                console.log("Index: "+nele.nodeValue.indexOf(" ", 2));*/
                if (nele.nodeValue.length == 0) return;
                j = regexIndexOf(nele.nodeValue, /[\s\-–—]+/, i+1);
                if (i == 0) i = -1;
                range.setStart(nele, i+1);
                if (j != -1 && (nele.nodeValue.length >= j)) {range.setEnd(nele, j);} else {range.setEnd(nele, nele.nodeValue.length)};
                i = j;
                let wordrect = range.getBoundingClientRect();
                if (overrect.top - wordrect.bottom >= 2 || wordrect.top - overrect.bottom >= 2) {
                    range.setEnd(nele, nele.nodeValue.length);
                    break;
                }
            }
            
            console.log(range.toString());
            if (range.getBoundingClientRect().top < overrect.bottom) return;
            let spanspan = document.createElement("span");
            spanspan.classList.add("wrapspan");
            range.surroundContents(spanspan);
            if (spanspan.innerHTML.trim() == "") {spanspan.remove(); return;}
            span.style.textAlignLast = "justify";
            ele.parentNode.appendChild(spanspan);
        }
    } else return;
}

//07/03/25
function tryDcoLap(dco) {
    let rect = dco.getBoundingClientRect();
    //let overlaps = false;
    let i = 0;
    for (let overrect of overrects) {
        if (rect.right <= overrect.left || rect.left >= overrect.right || rect.bottom <= overrect.top || rect.top >= overrect.bottom) continue; //https://stackoverflow.com/questions/16005136/how-do-i-see-if-two-rectangles-intersect-in-javascript-or-pseudocode
        /*let wrap = !(rect.right <= overrect.right && rect.left >= overrect.left);*/
        let keepright = (rect.right - overrect.right) > (overrect.left - rect.left);
        dco.removeAttribute("checked"+i);
        dcoLapEle(dco, rect, overrect, keepright, i);
        
        i++;
        
        /*
        for (let ele of dco.getElementsByTagName("p")) {
            let elrect = ele.getBoundingClientRect();
            if (elrect.right <= overrect.left || elrect.left >= overrect.right || elrect.bottom <= overrect.top || elrect.top >= overrect.bottom) continue;
            ele.style.backgroundColor = "orange";
        }
        for (let ele of dco.querySelectorAll(":not(:has(*)):not(p)")) {
            if (ele.nodeType != ele.ELEMENT_NODE) continue;
            let elrect = ele.getBoundingClientRect();
            if (elrect.right <= overrect.left || elrect.left >= overrect.right || elrect.bottom <= overrect.top || elrect.top >= overrect.bottom) continue;
            let prev;
            if (keepright) {prev = parseFloat(window.getComputedStyle(ele).marginLeft);} else {prev = parseFloat(window.getComputedStyle(ele).marginRight);}
            if (isNaN(prev)) prev = 0;
            if (ele.tagName == "LI") {
                let prevv;
                if (keepright) {prevv = parseFloat(window.getComputedStyle(ele.parentElement).paddingLeft);} else {prevv = parseFloat(window.getComputedStyle(ele.parentElement).paddingRight);}
                if (isNaN(prevv)) prevv = 0;
                prev += prevv;
                
            }
            if (keepright) {ele.style.marginLeft = (prev + (overrect.right - elrect.left)) + "px";} else {ele.style.marginRight = (prev + (elrect.right - overrect.left)) + "px";}
        }*/
        
        /*for (ele of dco.querySelectorAll("[checked]")) {
            ele.removeAttribute("checked");
        }*/
    }
    
    /*//testing 07/02/25
    try{
        if (node.nodeValue.length < 10) return;
    let range = document.createRange();
    range.setStart(tele, 1);
    range.setEnd(tele, 3);
    let rect = range.getBoundingClientRect();
    let overlaps = false;
    for (let overrect of overrects) {
        console.log("rect "+rect.x+" "+rect.y+ " " + rect.width + " " + rect.height + "; overrect "+overrect.x);
        if (!(rect.right <= overrect.left || rect.left >= overrect.right || rect.bottom <= overrect.top || rect.top >= overrect.bottom)) overlaps = true; //https://stackoverflow.com/questions/16005136/how-do-i-see-if-two-rectangles-intersect-in-javascript-or-pseudocode
    }
    if (!overlaps) return;
    //alert("overlaps");
    let span = document.createElement("span");
    range.surroundContents(span);
    span.style.backgroundColor = "red";
    } catch (err) {alert(err);}*/
}

//07/02/25
function listOverrects () {
    for (let overlay of document.querySelectorAll("div.overlay")) {
        overrects.push(overlay.getBoundingClientRect());
    }
}