Skip to content

Instantly share code, notes, and snippets.

@szkrd
Last active November 4, 2022 03:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save szkrd/34c7695551b0359c2d8ed822072c84fa to your computer and use it in GitHub Desktop.
Save szkrd/34c7695551b0359c2d8ed822072c84fa to your computer and use it in GitHub Desktop.
convert markdown to pdf using puppeteer
// first: `npm init -- yes && npm i -S showdown@1.9.1 github-markdown-css@5.1.0 serve@13.0.2 puppeteer-core`
// then: `node . --help`
const fs = require('fs');
const http = require('http');
const puppeteer = require('puppeteer-core');
const showdown = require('showdown');
const githubMarkdownCss = fs.readFileSync(require.resolve('github-markdown-css'), 'utf8');
const handler = require('serve-handler'); // without serve loading local files, like images, would not be possible
const die = (text = '', code = 1) => process.exit(~~console.error(text) + code);
const ifHasParam = (s = '') => process.argv.includes(s);
const paramValueOf = (s = '') => (process.argv.find(x => x.startsWith(`${s}=`)) || '').split('=')[1];
const asNum = (s = '') => (s || '').substr(0, 5).replace(/[^\d\.]/g, '');
const VERSION = '1.2.0';
const PDF_FORMAT = paramValueOf('--format') ?? 'A4';
const MARGIN_CM = parseFloat(asNum(paramValueOf('--margin') ?? '1')) || 0;
const PORT = parseInt(asNum(paramValueOf('--port')), 10) || 31234;
const DEBUG = ifHasParam('--debug');
let BROWSER_PATH = paramValueOf('--browser');
if (!BROWSER_PATH && process.platform === 'linux') BROWSER_PATH = '/usr/bin/chromium';
if (!BROWSER_PATH && process.platform === 'win32') BROWSER_PATH = 'C:/Program Files/Google/Chrome/Application/chrome.exe';
if (!BROWSER_PATH && process.platform === 'darwin') BROWSER_PATH = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
let info = '';
if (ifHasParam('--version')) info = `v${VERSION}`;
if (ifHasParam('--help')) info = 'Params (all optional):\n--help\n--version\n--port=31234\n--margin=1' +
'\n--debug\n--format=A4\n--browser=/usr/bin/chromium\n\nExample: node md2pdf sample.md';
if (info) process.exit(~~console.info(info));
if (process.argv.length <= 2) die('No filename specified, exiting...', 1);
const inputFileName = process.argv.slice(-1)[0];
const outputFileName = inputFileName.replace(/\.md$/i, '.pdf');
if (!fs.existsSync(inputFileName)) die(`Input file "${inputFileName}" not found.`, 2);
showdown.setFlavor('github');
const inputText = fs.readFileSync(inputFileName, 'utf8');
const renderedMd = (new showdown.Converter()).makeHtml(inputText);
const outputHtml = `<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8"><title></title><base href="http://127.0.0.1:${PORT}/" />
<style>${githubMarkdownCss}</style></head><body class="markdown-body">${renderedMd}</body></html>`;
let pup = { browser: null, page: null, server: null };
async function quitPup(code = 0, message = '') {
if (message) console.info(message);
await pup.browser.close();
pup.server.close();
process.exit(code);
}
async function handleExit() { await quitPup(0, 'SIGINT Closing Puppeteer and Serve.'); }
console.info(`Launching Serve at port ${PORT}`);
pup.server = http.createServer((request, response) => handler(request, response, { directoryListing: DEBUG }));
pup.server.listen(PORT, '127.0.0.1');
console.info('Launching Puppeteer');
(async () => {
process.on('SIGINT', handleExit);
const args = ['--disable-crash-reporter'];
const headless = !DEBUG;
const browser = pup.browser = await puppeteer.launch({ headless, printBackground: true, format: PDF_FORMAT, executablePath: BROWSER_PATH, args });
const page = pup.page = await browser.newPage();
await page.setContent(outputHtml);
if (DEBUG) return;
const m = `${MARGIN_CM}cm`;
await page.pdf({ printBackground : true, path: outputFileName, margin: { top: m, right: m, bottom: m, left: m } });
console.info('Closing Puppeteer');
await browser.close();
console.info('Closing Serve');
pup.server.close();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment