text: // Hooks for paged.js
  {
    // Utils
    let pandocMeta, pandocMetaToString;
    {
      let el = document.getElementById('pandoc-meta');
      pandocMeta = el ? JSON.parse(el.firstChild.data) : {};
    }
  
    pandocMetaToString = meta => {
      let el = document.createElement('div');
      el.innerHTML = meta;
      return el.innerText;
    };
  
    let isString = value => {
      return typeof value === 'string' || value instanceof String;
    };
  
    let isArray = value => {
      return value && typeof value === 'object' && value.constructor === Array;
    };
  
    // This hook is an attempt to fix https://github.com/rstudio/pagedown/issues/131
    // Sometimes, the {break-after: avoid;} declaration applied on headers
    // lead to duplicated headers. I hate this bug.
    // This is linked to the way the HTML source is written
    // When we have the \n character like this: <div>\n<h1>...</h1>
    // the header may be duplicated.
    // But, if we have <div><h1>...</h1> without any \n, the problem disappear
    // I think this handler can fix most of cases
    // Obviously, we cannot suppress all the \n in the HTML document
    // because carriage returns are important in <pre> elements.
    // Tested with Chrome 76.0.3809.100/Windows
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
        this.carriageReturn = String.fromCharCode(10);
      }
  
      checkNode(node) {
        if (!node) return;
        if (node.nodeType !== 3) return;
        if (node.textContent === this.carriageReturn) {
          node.remove();
        }
      }
  
      afterParsed(parsed) {
        let template = document.querySelector('template').content;
        const breakAfterAvoidElements = template.querySelectorAll('[data-break-after="avoid"], [data-break-before="avoid"]');
        for (let el of breakAfterAvoidElements) {
          this.checkNode(el.previousSibling);
          this.checkNode(el.nextSibling);
        }
      }
    });
  
    // This hook creates a list of abbreviations
    // Note: we also could implement this feature using a Pandoc filter
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
      }
      beforeParsed(content) {
        // Find the abbreviation nodes
        const abbrNodeList = content.querySelectorAll('abbr');
  
        // Return early if there is no abbreviation
        if (abbrNodeList.length === 0) return;
  
        // Store unique values of abbreviations, see https://github.com/rstudio/pagedown/issues/218
        let abbreviations = [];
        for (const {title, innerHTML} of abbrNodeList.values()) {
          if (abbreviations.find(el => el.title === title && el.innerHTML === innerHTML)) {
            continue;
          }
          abbreviations.push({title: title, innerHTML: innerHTML});
        }
  
        const loaTitle = pandocMeta['loa-title'] ? pandocMetaToString(pandocMeta['loa-title']) : 'List of Abbreviations';
        const loaId = 'LOA';
        const tocList = content.querySelector('.toc ul');
        let listOfAbbreviations = document.createElement('div');
        let descriptionList = document.createElement('dl');
        content.appendChild(listOfAbbreviations);
        listOfAbbreviations.id = loaId;
        listOfAbbreviations.classList.add('section', 'front-matter', 'level1', 'loa');
        listOfAbbreviations.innerHTML = '<h1>' + loaTitle + '</h1>';
        listOfAbbreviations.appendChild(descriptionList);
        for(let abbr of abbreviations) {
          if(!abbr.title) continue;
          let term = document.createElement('dt');
          let definition = document.createElement('dd');
          descriptionList.appendChild(term);
          descriptionList.appendChild(definition);
          term.innerHTML = abbr.innerHTML;
          definition.innerText = abbr.title;
        }
        if (tocList) {
          const loaTOCItem = document.createElement('li');
          loaTOCItem.innerHTML = '<a href="#' + loaId + '">' + loaTitle + '</a>';
          tocList.appendChild(loaTOCItem);
        }
      }
    });
  
    // This hook moves the sections of class front-matter in the div.front-matter-container
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
      }
  
      beforeParsed(content) {
        const frontMatter = content.querySelector('.front-matter-container');
        if (!frontMatter) return;
  
        // move front matter sections in the front matter container
        const frontMatterSections = content.querySelectorAll('.level1.front-matter');
        for (const section of frontMatterSections) {
          frontMatter.appendChild(section);
        }
  
        // add the class front-matter-ref to any <a></a> element
        // referring to an entry in the front matter
        const anchors = content.querySelectorAll('a[href^="#"]:not([href*=":"])');
        for (const a of anchors) {
          const ref = a.getAttribute('href').replace(/^#/, '');
          const element = content.getElementById(ref);
          if (frontMatter.contains(element)) a.classList.add('front-matter-ref');
        }
  
        // update the toc, lof and lot for front matter sections
        const frontMatterSectionsLinks = content.querySelectorAll('.toc .front-matter-ref, .lof .front-matter-ref, .lot .front-matter-ref');
        for (let i = frontMatterSectionsLinks.length - 1; i >= 0; i--) {
          const listItem = frontMatterSectionsLinks[i].parentNode;
          const list = listItem.parentNode;
          list.insertBefore(listItem, list.firstChild);
        }
      }
    });
  
    // This hook expands the links in the lists of figures and tables
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
      }
  
      beforeParsed(content) {
        const items = content.querySelectorAll('.lof li, .lot li');
        for (const item of items) {
          const anchor = item.firstChild;
          anchor.innerText = item.innerText;
          item.innerText = '';
          item.append(anchor);
        }
      }
    });
  
    // This hook adds spans for leading symbols
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
      }
  
      beforeParsed(content) {
        const anchors = content.querySelectorAll('.toc a, .lof a, .lot a');
        for (const a of anchors) {
          a.innerHTML = a.innerHTML + '<span class="leaders"></span>';
        }
      }
    });
  
    // This hook appends short titles spans
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
      }
  
      beforeParsed(content) {
      /* A factory returning a function that appends short titles spans.
         The text content of these spans are reused for running titles (see default.css).
         Argument: level - An integer between 1 and 6.
      */
      function appendShortTitleSpans(level) {
        return () => {
          const divs = Array.from(content.querySelectorAll('.level' + level));
  
          function addSpan(div) {
            const mainHeader = div.getElementsByTagName('h' + level)[0];
            if (!mainHeader) return;
            const mainTitle = mainHeader.textContent;
            const spanSectionNumber = mainHeader.getElementsByClassName('header-section-number')[0];
            const mainNumber = !!spanSectionNumber ? spanSectionNumber.textContent : '';
            const runningTitle = 'shortTitle' in div.dataset ? mainNumber + ' ' + div.dataset.shortTitle : mainTitle;
            const span = document.createElement('span');
            span.className = 'shorttitle' + level;
            span.innerText = runningTitle;
            span.style.display = "none";
            mainHeader.appendChild(span);
            if (level == 1 && div.querySelector('.level2') === null) {
              let span2 = document.createElement('span');
              span2.className = 'shorttitle2';
              span2.innerText = ' ';
              span2.style.display = "none";
              span.insertAdjacentElement('afterend', span2);
            }
          }
  
          for (const div of divs) {
            addSpan(div);
          }
        };
      }
  
      appendShortTitleSpans(1)();
      appendShortTitleSpans(2)();
      }
    });
  
    // Footnotes support
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
  
        this.splittedParagraphRefs = [];
      }
  
      beforeParsed(content) {
        // remove footnotes in toc, lof, lot
        // see https://github.com/rstudio/pagedown/issues/54
        let removeThese = content.querySelectorAll('.toc .footnote, .lof .footnote, .lot .footnote');
        for (const el of removeThese) {
          el.remove();
        }
  
        let footnotes = content.querySelectorAll('.footnote');
  
        for (let footnote of footnotes) {
          let parentElement = footnote.parentElement;
          let footnoteCall = document.createElement('a');
          let footnoteNumber = footnote.dataset.pagedownFootnoteNumber;
  
          footnoteCall.className = 'footnote-ref'; // same class as Pandoc
          footnoteCall.setAttribute('id', 'fnref' + footnoteNumber); // same notation as Pandoc
          footnoteCall.setAttribute('href', '#' + footnote.id);
          footnoteCall.innerHTML = '<sup>' + footnoteNumber +'</sup>';
          parentElement.insertBefore(footnoteCall, footnote);
  
          // Here comes a hack. Fortunately, it works with Chrome and FF.
          let handler = document.createElement('p');
          handler.className = 'footnoteHandler';
          parentElement.insertBefore(handler, footnote);
          handler.appendChild(footnote);
          handler.style.display = 'inline-block';
          handler.style.width = '100%';
          handler.style.float = 'right';
          handler.style.pageBreakInside = 'avoid';
        }
      }
  
      afterPageLayout(pageFragment, page, breakToken) {
        function hasItemParent(node) {
          if (node.parentElement === null) {
            return false;
          } else {
            if (node.parentElement.tagName === 'LI') {
              return true;
            } else {
              return hasItemParent(node.parentElement);
            }
          }
        }
        // If a li item is broken, we store the reference of the p child element
        // see https://github.com/rstudio/pagedown/issues/23#issue-376548000
        if (breakToken !== undefined) {
          if (breakToken.node.nodeName === "#text" && hasItemParent(breakToken.node)) {
            this.splittedParagraphRefs.push(breakToken.node.parentElement.dataset.ref);
          }
        }
      }
  
      afterRendered(pages) {
        for (let page of pages) {
          const footnotes = page.element.querySelectorAll('.footnote');
          if (footnotes.length === 0) {
            continue;
          }
  
          const pageContent = page.element.querySelector('.pagedjs_page_content');
          let hr = document.createElement('hr');
          let footnoteArea = document.createElement('div');
  
          pageContent.style.display = 'flex';
          pageContent.style.flexDirection = 'column';
  
          hr.className = 'footnote-break';
          hr.style.marginTop = 'auto';
          hr.style.marginBottom = 0;
          hr.style.marginLeft = 0;
          hr.style.marginRight = 'auto';
          pageContent.appendChild(hr);
  
          footnoteArea.className = 'footnote-area';
          pageContent.appendChild(footnoteArea);
  
          for (let footnote of footnotes) {
            let handler = footnote.parentElement;
  
            footnoteArea.appendChild(footnote);
            handler.parentNode.removeChild(handler);
  
            footnote.innerHTML = '<sup>' + footnote.dataset.pagedownFootnoteNumber + '</sup>' + footnote.innerHTML;
            footnote.style.fontSize = 'x-small';
            footnote.style.marginTop = 0;
            footnote.style.marginBottom = 0;
            footnote.style.paddingTop = 0;
            footnote.style.paddingBottom = 0;
            footnote.style.display = 'block';
          }
        }
  
        for (let ref of this.splittedParagraphRefs) {
          let paragraphFirstPage = document.querySelector('[data-split-to="' + ref + '"]');
          // We test whether the paragraph is empty
          // see https://github.com/rstudio/pagedown/issues/23#issue-376548000
          if (paragraphFirstPage.innerText === "") {
            paragraphFirstPage.parentElement.style.display = "none";
            let paragraphSecondPage = document.querySelector('[data-split-from="' + ref + '"]');
            paragraphSecondPage.parentElement.style.setProperty('list-style', 'inherit', 'important');
          }
        }
      }
    });
  
    // Support for "Chapter " label on section with class `.chapter`
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
  
        this.options = pandocMeta['chapter_name'];
  
        let styles;
        if (isString(this.options)) {
          this.options = pandocMetaToString(this.options);
          styles = `
          :root {
            --chapter-name-before: "${this.options}";
          }
          `;
        }
        if (isArray(this.options)) {
          this.options = this.options.map(pandocMetaToString);
          styles = `
          :root {
            --chapter-name-before: "${this.options[0]}";
            --chapter-name-after: "${this.options[1]}";
          }
          `;
        }
        if (styles) polisher.insert(styles);
      }
  
      beforeParsed(content) {
        const tocAnchors = content.querySelectorAll('.toc a[href^="#"]:not([href*=":"]');
        for (const anchor of tocAnchors) {
          const ref = anchor.getAttribute('href').replace(/^#/, '');
          const element = content.getElementById(ref);
          if (element.classList.contains('chapter')) {
            anchor.classList.add('chapter-ref');
          }
        }
      }
    });
  
    // Main text line numbering,
    // see https://github.com/rstudio/pagedown/issues/115
    // Original idea: Julien Taquet, thanks!
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
  
        // get the number-lines option from Pandoc metavariables
        this.options = pandocMeta['number-lines'];
        // quit early if the "number-lines" option is false or missing
        if (!this.options) return;
        // retrieve the selector if provided, otherwise use the default selector
        this.selector = this.options.selector ? pandocMetaToString(this.options.selector) : '.level1:not(.front-matter) h1, .level1 h2, .level1 h3, .level1 p';
  
        const styles = `
        :root {
          --line-numbers-padding-right: 10px;
          --line-numbers-font-size: 8pt;
        }
        .pagedown-linenumbers-container {
          position: absolute;
          margin-top: var(--pagedjs-margin-top);
          right: calc(var(--pagedjs-width) - var(--pagedjs-margin-left));
        }
        .maintextlinenumbers {
          position: absolute;
          right: 0;
          text-align: right;
          padding-right: var(--line-numbers-padding-right);
          font-size: var(--line-numbers-font-size);
        }
        `;
        polisher.insert(styles);
  
        this.resetLinesCounter();
      }
  
      appendLineNumbersContainer(page) {
        const pagebox = page.element.querySelector('.pagedjs_pagebox');
        const lineNumbersContainer = document.createElement('div');
        lineNumbersContainer.classList.add('pagedown-linenumbers-container');
  
        return pagebox.appendChild(lineNumbersContainer);
      }
  
      lineHeight(element) {
        // If the document stylesheet does not define a value for line-height,
        // Blink returns "normal". Therefore, parseInt may return NaN.
        return parseInt(getComputedStyle(element).lineHeight);
      }
  
      innerHeight(element) {
        let outerHeight = element.getBoundingClientRect().height;
        let {borderTopWidth,
             borderBottomWidth,
             paddingTop,
             paddingBottom} = getComputedStyle(element);
  
        borderTopWidth = parseFloat(borderTopWidth);
        borderBottomWidth = parseFloat(borderBottomWidth);
        paddingTop = parseFloat(paddingTop);
        paddingBottom = parseFloat(paddingBottom);
  
        return Math.round(outerHeight - borderTopWidth - borderBottomWidth - paddingTop - paddingBottom);
      }
  
      arrayOfInt(from, length) {
        // adapted from https://stackoverflow.com/a/50234108/6500804
        return Array.from(Array(length).keys(), n => n + from);
      }
  
      incrementLinesCounter(value) {
        this.linesCounter = this.linesCounter + value;
      }
  
      resetLinesCounter() {
        this.linesCounter = 0;
      }
  
      isDisplayMath(element) {
        const nodes = element.childNodes;
        if (nodes.length != 1) return false;
        return (nodes[0].nodeName === 'SPAN') && (nodes[0].classList.value === 'math display');
      }
  
      afterRendered(pages) {
        if (!this.options) return;
  
        for (let page of pages) {
          const lineNumbersContainer = this.appendLineNumbersContainer(page);
          const pageAreaY = page.area.getBoundingClientRect().y;
          let elementsToNumber = page.area.querySelectorAll(this.selector);
  
          for (let element of elementsToNumber) {
            // Do not add line numbers for display math environment
            if (this.isDisplayMath(element)) continue;
  
            // Try to retrieve line height
            const lineHeight = this.lineHeight(element);
            // Test against lineHeight method returns NaN
            if (!lineHeight) {
              console.warn('Failed to compute line height value on "' + page.id + '".');
              continue;
            }
  
            const innerHeight = this.innerHeight(element);
  
            // Number of lines estimation
            // There is no built-in method to detect the number of lines in a block.
            // The main caveat is that an actual line height can differ from
            // the line-height CSS property.
            // Mixed fonts, subscripts, superscripts, inline math... can increase
            // the actual line height.
            // Here, we divide the inner height of the block by the line-height
            // computed property and round to the floor to take into account that
            // sometimes the actual line height is greater than its property value.
            // This is far from perfect and can be easily broken especially by
            // inline math.
            const nLines = Math.floor(innerHeight / lineHeight);
  
            // do not add line numbers for void paragraphs
            if (nLines <= 0) continue;
  
            const linenumbers = document.createElement('div');
            lineNumbersContainer.appendChild(linenumbers);
            linenumbers.classList.add('maintextlinenumbers');
  
            const elementY = element.getBoundingClientRect().y;
            linenumbers.style.top = (elementY - pageAreaY) + 'px';
  
            const cs = getComputedStyle(element);
            const paddingTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth);
            linenumbers.style.paddingTop = paddingTop + 'px';
  
            linenumbers.style.lineHeight = cs.lineHeight;
  
            linenumbers.innerHTML = this.arrayOfInt(this.linesCounter + 1, nLines)
                                        .reduce((t, v) => t + '<br />' + v);
            this.incrementLinesCounter(nLines);
          }
  
          if (this.options['reset-page']) {
            this.resetLinesCounter();
          }
        }
      }
    });
  
    // Clean links to avoid impossible line breaking of long urls in a justified text
    // Author: Julien Taquet (Paged.js core team)
    // see https://github.com/spyrales/gouvdown/issues/37
    Paged.registerHandlers(class extends Paged.Handler {
      constructor(chunker, polisher, caller) {
        super(chunker, polisher, caller);
      }
      beforeParsed(content) {
        // add wbr to / in links
        const links = content.querySelectorAll('a[href^="http"], a[href^="www"]');
        links.forEach(link => {
          // Rerun to avoid large spaces.
          // Break after a colon or a double slash (//)
          // or before a single slash (/), a tilde (~), a period, a comma, a hyphen,
          // an underline (_), a question mark, a number sign, or a percent symbol.
          const content = link.textContent;
          if (!(link.childElementCount === 0 && content.match(/^http|^www/))) return;
          let printableUrl = content.replace(/\/\//g, "//\u003Cwbr\u003E");
          printableUrl = printableUrl.replace(/\,/g, ",\u003Cwbr\u003E");
          // put wbr around everything.
          printableUrl = printableUrl.replace(
            /(\/|\~|\-|\.|\,|\_|\?|\#|\%)/g,
            "\u003Cwbr\u003E$1"
          );
          // turn hyphen in non breaking hyphen
          printableUrl = printableUrl.replace(/\-/g, "\u003Cwbr\u003E&#x2011;");
          link.setAttribute("data-print-url", printableUrl);
          link.innerHTML = printableUrl;
        });
      }
    });
  
    // Repeat table headers on multiple pages
    // Authors: Julien Taquet, Lucas Maciuga and Tafael Caixeta, see https://gitlab.pagedmedia.org/tools/pagedjs/issues/84
    // TODO: remove this hook when Paged.js integrates this feature
    Paged.registerHandlers(class RepeatingTableHeadersHandler extends Paged.Handler {
  
      constructor(chunker, polisher, caller) {
          super(chunker, polisher, caller);
          this.splitTablesRefs = [];
      }
  
      afterPageLayout(pageElement, page, breakToken, chunker) {
          this.chunker = chunker;
          this.splitTablesRefs = [];
  
          if (breakToken) {
              const node = breakToken.node;
              const tables = this.findAllAncestors(node, "table");
              if (node.tagName === "TABLE") {
                  tables.push(node);
              }
  
              if (tables.length > 0) {
                  this.splitTablesRefs = tables.map(t => t.dataset.ref);
  
                  //checks if split inside thead and if so, set breakToken to next sibling element
                  let thead = node.tagName === "THEAD" ? node : this.findFirstAncestor(node, "thead");
                  if (thead) {
                      let lastTheadNode = thead.hasChildNodes() ? thead.lastChild : thead;
                      breakToken.node = this.nodeAfter(lastTheadNode, chunker.source);
                  }
  
                  this.hideEmptyTables(pageElement, node);
              }
          }
      }
  
      hideEmptyTables(pageElement, breakTokenNode) {
          this.splitTablesRefs.forEach(ref => {
              let table = pageElement.querySelector("[data-ref='" + ref + "']");
              if (table) {
                  let sourceBody = table.querySelector("tbody > tr");
                  if (!sourceBody || this.refEquals(sourceBody.firstElementChild, breakTokenNode)) {
                      table.style.visibility = "hidden";
                      table.style.position = "absolute";
                      let lineSpacer = table.nextSibling;
                      if (lineSpacer) {
                          lineSpacer.style.visibility = "hidden";
                          lineSpacer.style.position = "absolute";
                      }
                  }
              }
          });
      }
  
      refEquals(a, b) {
          return a && a.dataset && b && b.dataset && a.dataset.ref === b.dataset.ref;
      }
  
      findFirstAncestor(element, selector) {
          while (element.parentNode && element.parentNode.nodeType === 1) {
              if (element.parentNode.matches(selector)) {
                  return element.parentNode;
              }
              element = element.parentNode;
          }
          return null;
      }
  
      findAllAncestors(element, selector) {
          const ancestors = [];
          while (element.parentNode && element.parentNode.nodeType === 1) {
              if (element.parentNode.matches(selector)) {
                  ancestors.unshift(element.parentNode);
              }
              element = element.parentNode;
          }
          return ancestors;
      }
  
      // The addition of repeating Table Headers is done here because this hook is triggered before overflow handling
      layout(rendered, layout) {
          this.splitTablesRefs.forEach(ref => {
              const renderedTable = rendered.querySelector("[data-ref='" + ref + "']");
              if (renderedTable && renderedTable.hasAttribute("data-split-from")) {
                  // this event can be triggered multiple times
                  // added a flag repeated-headers to control when table headers already repeated in current page.
                  if (!renderedTable.getAttribute("repeated-headers")) {
                      const sourceTable = this.chunker.source.querySelector("[data-ref='" + ref + "']");
                      this.repeatColgroup(sourceTable, renderedTable);
                      this.repeatTHead(sourceTable, renderedTable);
                      renderedTable.setAttribute("repeated-headers", true);
                  }
              }
          });
      }
  
      repeatColgroup(sourceTable, renderedTable) {
          let colgroup = sourceTable.querySelectorAll("colgroup");
          let firstChild = renderedTable.firstChild;
          colgroup.forEach((colgroup) => {
              let clonedColgroup = colgroup.cloneNode(true);
              renderedTable.insertBefore(clonedColgroup, firstChild);
          });
      }
  
      repeatTHead(sourceTable, renderedTable) {
          let thead = sourceTable.querySelector("thead");
          if (thead) {
              let clonedThead = thead.cloneNode(true);
              renderedTable.insertBefore(clonedThead, renderedTable.firstChild);
          }
      }
  
      // the functions below are from pagedjs utils/dom.js
      nodeAfter(node, limiter) {
          if (limiter && node === limiter) {
              return;
          }
          let significantNode = this.nextSignificantNode(node);
          if (significantNode) {
              return significantNode;
          }
          if (node.parentNode) {
              while ((node = node.parentNode)) {
                  if (limiter && node === limiter) {
                      return;
                  }
                  significantNode = this.nextSignificantNode(node);
                  if (significantNode) {
                      return significantNode;
                  }
              }
          }
      }
  
      nextSignificantNode(sib) {
          while ((sib = sib.nextSibling)) {
              if (!this.isIgnorable(sib)) return sib;
          }
          return null;
      }
  
      isIgnorable(node) {
          return (node.nodeType === 8) || // A comment node
              ((node.nodeType === 3) && this.isAllWhitespace(node)); // a text node, all whitespace
      }
  
      isAllWhitespace(node) {
          return !(/[^\t\n\r ]/.test(node.textContent));
      }
    });
  
  }
  