Skip to content
Snippets Groups Projects
printer.js 6.01 KiB
Newer Older
const Paged = require('pagedjs');
const EventEmitter = require('events');
const puppeteer = require('puppeteer');
const util = require('util');
const fs = require('fs');
Fred Chasen's avatar
Fred Chasen committed
const fetch = require("node-fetch");

const path = require('path');

let dir = process.cwd();

Fred Chasen's avatar
Fred Chasen committed
// Find top most pagedjs
let pagedjsLocation = require.resolve("pagedjs/dist/paged.polyfill.js");
let paths = pagedjsLocation.split("node_modules");
let scriptPath = paths[0] + "node_modules" + paths[paths.length-1];

const PostProcesser = require('./postprocesser');

const PDF_SETTINGS = {
  printBackground: true,
  displayHeaderFooter: false,
  preferCSSPageSize: true,
  margin: {
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
  }
};

class Printer extends EventEmitter {
  constructor(headless, allowLocal) {
    super();
    this.headless = headless !== false;
    this.allowLocal = allowLocal;
    this.pages = [];
  }

  async setup() {
    const browser = await puppeteer.launch({
      headless: this.headless,
Fred Chasen's avatar
Fred Chasen committed
      args: this.allowLocal ? ['--allow-file-access-from-files', '--disable-dev-shm-usage', '--no-sandbox'] : ['--disable-dev-shm-usage', '--no-sandbox'],
      ignoreHTTPSErrors: true
    });

    return browser;
  }

  async render(input) {
    let resolver;
    let rendered = new Promise(function(resolve, reject) {
      resolver = resolve;
    });

    if (!this.browser) {
      this.browser = await this.setup();
    }

    const page = await this.browser.newPage();

Fred Chasen's avatar
Fred Chasen committed
    let uri, url, html;
    if (typeof input === "string") {
      try {
Fred Chasen's avatar
Fred Chasen committed
        uri = new URL(input);
        if (uri.protocol === "https:") {
          html = await fetch(input)
            .then(res => res.text())
        }
        url = input;
      } catch {
        let relativePath = path.resolve(dir, input);
        url = "file://" + relativePath;
      }
    } else {
      url = input.url;
      html = input.html;
    }

    if (html) {
      await page.setContent(html)
        .catch((e) => {
          console.error(e);
        });
Fred Chasen's avatar
Fred Chasen committed

      if (url) {
        await page.evaluate((url) => {
          let base = document.querySelector("base");
          if (!base) {
            base = document.createElement("base");
            document.querySelector("head").appendChild(base);
          }
          base.setAttribute("href", url);
        }, url);
      }

    } else {
      await page.goto(url)
        .catch((e) => {
          console.error(e);
        });
    }

    await page.evaluate(() => {
      window.PagedConfig = {
        auto: false
      }
    });

Fred Chasen's avatar
Fred Chasen committed


    await page.addScriptTag({
Fred Chasen's avatar
Fred Chasen committed
      path: scriptPath
    });

    // await page.exposeFunction('PuppeteerLogger', (msg) => {
    //   console.log(msg);
    // });

    await page.exposeFunction('onSize', (size) => {
      this.emit("size", size);
    });

    await page.exposeFunction('onPage', (page) => {
      // console.log("page", page.position + 1);

      this.pages.push(page);

      this.emit("page", page);
    });

    await page.exposeFunction('onRendered', (msg, width, height, orientation) => {
      this.emit("rendered", msg, width, height, orientation);
      resolver({msg, width, height, orientation});
    });

    await page.evaluate(() => {
      window.PagedPolyfill.on("page", (page) => {
        const { id, width, height, startToken, endToken, breakAfter, breakBefore, position } = page;

        const mediabox = page.element.getBoundingClientRect();
        const cropbox = page.pagebox.getBoundingClientRect();

        function getPointsValue(value) {
          return (Math.round(CSS.px(value).to("pt").value * 100) / 100);
        }

        let boxes = {
          media: {
            width: getPointsValue(mediabox.width),
            height: getPointsValue(mediabox.height),
            x: 0,
            y: 0
          },
          crop: {
            width: getPointsValue(cropbox.width),
            height: getPointsValue(cropbox.height),
            x: getPointsValue(cropbox.x) - getPointsValue(mediabox.x),
            y: getPointsValue(cropbox.y) - getPointsValue(mediabox.y)
          }
        }

        window.onPage({ id, width, height, startToken, endToken, breakAfter, breakBefore, position, boxes });
      });

      window.PagedPolyfill.on("size", (size) => {
        window.onSize(size);
      });

      window.PagedPolyfill.on("rendered", (flow) => {
        let msg = "Rendering " + flow.total + " pages took " + flow.performance + " milliseconds.";
        window.onRendered(msg, flow.width, flow.height, flow.orientation);
      });

      window.PagedPolyfill.preview();
    });

    await rendered;

    await page.waitForSelector(".pagedjs_pages");

    return page;
  }

  async pdf(input, options={}) {
    let page = await this.render(input);

    // Get metatags
    const meta = await page.evaluate(() => {
      let meta = {};
      let title = document.querySelector("title");
Fred Chasen's avatar
Fred Chasen committed
      if (title) {
        meta.title = title.textContent.trim();
      }
      let metaTags = document.querySelectorAll("meta");
      [...metaTags].forEach((tag) => {
        if (tag.name) {
          meta[tag.name] = tag.content;
        }
      })
      return meta;
    });

    let settings = {
      printBackground: true,
      displayHeaderFooter: false,
      preferCSSPageSize: options.width ? false : true,
      width: options.width,
      height: options.height,
      orientation: options.orientation,
      margin: {
        top: 0,
        right: 0,
        bottom: 0,
        left: 0,
      }
    }

    let pdf = await page.pdf(PDF_SETTINGS)
      .catch((e) => {
        console.error(e);
      });

    await page.close();

    this.emit("postprocessing");

    let post = new PostProcesser(pdf);
    post.metadata(meta);
    post.boxes(this.pages);
    pdf = post.save();

    return pdf;
  }

  async html(input, stayopen) {
    let page = await this.render(input);

    let content = await page.content()
      .catch((e) => {
        console.error(e);
      });

    await page.close();
    return content;
  }

  async preview(input) {
    let page = await this.render(input);
    return page;
  }

}

module.exports = Printer;