Skip to content

Instantly share code, notes, and snippets.

@rjozefowicz
Last active March 22, 2024 22:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rjozefowicz/fa0618fce8e6ae28ff5c46b65ba7defd to your computer and use it in GitHub Desktop.
Save rjozefowicz/fa0618fce8e6ae28ff5c46b65ba7defd to your computer and use it in GitHub Desktop.
Paged.js demo + Puppeteer

HTML to export - includes Paged.js polyfill:

<!DOCTYPE HTML>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
</head>
<style>
    @page cover {
        margin: 0;

        @bottom-center {
            content: none;
        }
    }

    @media print {
        #list-toc-generated {
            list-style: none;
            padding-left: 0px;
        }

        #list-toc-generated .toc-element a::after {
            content: target-counter(attr(href), page);
            float: right;
        }

        #list-toc-generated .toc-element-level-1 {
            margin-top: 15px;
            font-weight: bold;
        }

        #list-toc-generated {
            counter-reset: counterTocLevel1;
        }

        #list-toc-generated .toc-element-level-1 {
            counter-increment: counterTocLevel1;
            counter-reset: counterTocLevel2;
        }

        #list-toc-generated .toc-element-level-2 {
            counter-increment: counterTocLevel2;
        }

        #list-toc-generated {
            overflow-x: hidden;
        }

        #list-toc-generated .toc-element::after {
            content: ".................................................................................................................................................";
            float: left;
            width: 0;
            padding-left: 5px;
            letter-spacing: 2px;
        }

        #list-toc-generated .toc-element {
            display: flex;
        }

        #list-toc-generated .toc-element a::after {
            position: absolute;
            right: 0;
            background-color: white;
            padding-left: 6px;
        }

        #list-toc-generated .toc-element a {
            right: 0;
        }
    }

    @media screen {
        body {
            background-color: whitesmoke;
        }

        .pagedjs_pages {
            display: flex;
            width: var(--pagedjs-width);
            flex: 0;
            flex-wrap: wrap;
            margin: 0 auto;
        }

        .pagedjs_page {
            background-color: white;
            box-shadow: 0 0 0 2px #888;
            margin: 0;
            flex-shrink: 0;
            flex-grow: 0;
            margin-top: 10mm;
        }
        
        #pagedjs_interface_header {
            position: fixed;
            background-color: #888;
            width: 100vw;
            height: 30px;
            top: 0;
            left: 0;

        }
    }

    body {
        counter-reset: titleLevel1;
    }

    h2 {
        counter-increment: titleLevel1;
        counter-reset: titleLevel2;
    }

    h2::before {
        content: counter(titleLevel1) ". ";
    }

    h3 {
        counter-increment: titleLevel2;
    }

    h3::before {
        content: counter(titleLevel1) ". "counter(titleLevel2) ". ";
    }

    @media print {
        @page {
            size: A4;

            @bottom-right {
                content: counter(page);
            }
        }

        .cover {
            break-after: always;
        }

        h1.chapter-title {
            break-before: page;
        }
    }

    .cover {
        page: cover;
        width: 100%;
        height: 100%;
    }

    .cover h1 {
        padding-top: 50%;
        text-align: center;
    }

    @page cover {
        margin: 0;

        @bottom-center {
            content: none;
        }
    }
</style>

<script>
    function createToc(config) {
        const content = config.content;
        const tocElement = config.tocElement;
        const titleElements = config.titleElements;

        let tocElementDiv = content.querySelector(tocElement);
        let tocUl = document.createElement("ul");
        tocUl.id = "list-toc-generated";
        tocElementDiv.appendChild(tocUl);

        // add class to all title elements
        let tocElementNbr = 0;
        for (var i = 0; i < titleElements.length; i++) {

            let titleHierarchy = i + 1;
            let titleElement = content.querySelectorAll(titleElements[i]);


            titleElement.forEach(function (element) {

                // add classes to the element
                element.classList.add("title-element");
                element.setAttribute("data-title-level", titleHierarchy);

                // add id if doesn't exist
                tocElementNbr++;
                idElement = element.id;
                if (idElement == '') {
                    element.id = 'title-element-' + tocElementNbr;
                }
                let newIdElement = element.id;

            });

        }

        // create toc list
        let tocElements = content.querySelectorAll(".title-element");

        for (var i = 0; i < tocElements.length; i++) {
            let tocElement = tocElements[i];

            let tocNewLi = document.createElement("li");

            // Add class for the hierarcy of toc
            tocNewLi.classList.add("toc-element");
            tocNewLi.classList.add("toc-element-level-" + tocElement.dataset.titleLevel);

            // Keep class of title elements
            let classTocElement = tocElement.classList;
            for (var n = 0; n < classTocElement.length; n++) {
                if (classTocElement[n] != "title-element") {
                    tocNewLi.classList.add(classTocElement[n]);
                }
            }

            // Create the element
            tocNewLi.innerHTML = '<a href="#' + tocElement.id + '">' + tocElement.innerHTML + '</a>';
            tocUl.appendChild(tocNewLi);
        }

    }

    class handlers extends Paged.Handler {
        constructor(chunker, polisher, caller) {
            super(chunker, polisher, caller);
        }

        beforeParsed(content) {
            createToc({
                content: content,
                tocElement: '#my-toc-content',
                titleElements: ['.manual-payload h1.chapter-title']
            });
        }

    }
    Paged.registerHandlers(handlers);
</script>

<body>

    <!-- cover -->
    <div class="cover">
        <h1>AWS CLI</h1>
    </div>

    <!-- ToC -->
    <div id="table-of-content">
        <h1>Table of content</h1>
        <nav id="my-toc-content">

        </nav>
    </div>

    <!-- payload -->
    <div class="manual-payload">
        <article id="chapter1">
            <h1 class="chapter-title">1. AWS CLI</h2>
                <p>tadam #1</p>
        </article>

        <article id="chapter1.1">
            <h1 class="chapter-title">1.1 Installation</h2>
                <p>tadam #1.1</p>
        </article>

        <article id="chapter2">
            <h1 class="chapter-title">2. Aurora</h2>
                <p>tadam #2</p>
        </article>
    </div>

</html>

then execute puppeteer part to save it as PDF:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('file:///input.html', {waitUntil: 'networkidle2'});
  await page.pdf({
    path: 'output.pdf', 
    format: 'A4'
  });

  await browser.close();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment