Skip to content

Commit

Permalink
initial pass at pdf build script
Browse files Browse the repository at this point in the history
  • Loading branch information
spaceninja committed Aug 11, 2023
1 parent be50acf commit 99bb72b
Show file tree
Hide file tree
Showing 7 changed files with 906 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DOCRAPTOR_API_KEY="SXrftL1JWvCnWw0AkI9e"
DOCRAPTOR_TEST="true"
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DOCRAPTOR_API_KEY=""
DOCRAPTOR_TEST="true"
126 changes: 126 additions & 0 deletions build-pdf.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';

import dotenv from 'dotenv';
import { globby } from 'globby';
import fetch from 'node-fetch';
import rehypeInline from 'rehype-inline';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import { unified } from 'unified';

dotenv.config();
const docraptorApiKey = process.env.DOCRAPTOR_API_KEY;
const docraptorTest = process.env.DOCRAPTOR_TEST;
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const distDir = path.join(currentDir, 'dist', 'pdf');
const pages = await globby('dist/**/*.html');

/**
* Inline Assets
* Uses rehype/unified to inline assets like CSS and JS.
*/
const inlineAssets = unified()
.use(rehypeParse, { fragment: false })
.use(rehypeInline, {
js: true,
css: true,
images: true,
imports: true,
svgElements: false,
})
.use(rehypeStringify);

/**
* Get an HTML file and prepare it for DocRaptor
*
* @param {string} htmlPath - the HTML file to load
* @returns {string} the contents of the HTML file with inlined CSS
*/
const getHTML = async (htmlPath) => {
// Grab the HTML file contents as a string
const rawHTML = await fs.readFile(htmlPath, 'utf8');
// Change the CSS URI to a path so it can be inlined
const updatedCSS = rawHTML.replace('/style.css', 'dist/style.css');
// Change any image URLs to paths so they can be inlined
const updatedImages = updatedCSS.replace('/images/', 'dist/images/');
// Inline the CSS
return String(await inlineAssets.process(updatedImages));
};

/**
* Generate a PDF using DocRaptor
*
* @see https://docraptor.com/documentation/api
* @param {string} html - passed to docraptor
* @param {string} slug - used for better error logging only
* @returns {Buffer}
*/
const getPDF = async (html, slug) => {
if (!docraptorApiKey) throw new Error('Missing DocRaptor API Key');
// Send HTML to DocRaptor to generate PDF
const pdfRes = await fetch('https://docraptor.com/docs', {
method: 'POST',
headers: {
Authorization: `Basic ${Buffer.from(docraptorApiKey).toString('base64')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
test: docraptorTest,
document_content: html,
type: 'pdf',
prince_options: {
profile: 'PDF/UA-1', // Adds accessibility features like tagging
},
}),
});
if (!pdfRes.ok)
throw new Error(
`${slug}: ${pdfRes.status} ${pdfRes.statusText} ${await pdfRes.text()}`,
);
// Extract the PDF from the response and return it
const blob = await pdfRes.blob();
return Buffer.from(await blob.arrayBuffer(), 'binary');
};

/**
* Create a PDF from an HTML file
*
* @param {string} htmlPath - the HTML file to load
*/
const generatePDF = async (htmlPath) => {
// Strip `dist/` and `/index.html` from htmlPath
let slug = htmlPath.slice(5, -11);
// Special case for the root HTML file
if (htmlPath === 'dist/index.html') slug = 'index';
// Create relative HTML path and PDF write destination
const htmlPathCWD = path.join(currentDir, htmlPath);
const pdfSlug = slug.replace('/', '-');
const pdfPath = path.join(distDir, `${pdfSlug}.pdf`);
// Load the HTML file
const html = await getHTML(htmlPathCWD);
// Generate the PDF with DocRaptor
const pdf = await getPDF(html, slug);
// Save the PDF to a file
await fs.writeFile(pdfPath, pdf);
console.log(`[PDF] Writing dist/pdf/${pdfSlug}.pdf`);
};

/**
* Build All PDFs
* This script will generate a PDF from every HTML file in the `/dist` folder by
* passing the HTML to DocRaptor and saving the files to `/dist/pdf`.
*/
const buildAllPDFs = async () => {
// Create the output directory if it doesn't exist
await fs.mkdir(distDir, { recursive: true });
// Loop over the list of pages to generate an individual PDF for each page
await Promise.all(
pages.map(async (page) => {
await generatePDF(page);
}),
);
};

buildAllPDFs();
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
"license": "UNLICENSED",
"scripts": {
"start": "yarn watch",
"build": "run-p build:*",
"build": "run-s build:*",
"build:content": "eleventy",
"build:css": "sass src:dist",
"build:pdf": "node build-pdf.mjs",
"watch": "run-p watch:*",
"watch:content": "eleventy --serve",
"watch:css": "sass --watch src:dist",
Expand All @@ -21,12 +22,19 @@
},
"devDependencies": {
"@11ty/eleventy": "2.0.1",
"dotenv": "^16.3.1",
"globby": "^13.2.2",
"markdown-it-footnote": "3.0.3",
"node-fetch": "^3.3.2",
"npm-run-all": "4.1.5",
"prettier": "3.0.1",
"rehype-inline": "^2.0.0",
"rehype-parse": "^8.0.4",
"rehype-stringify": "^9.0.3",
"sass": "1.65.1",
"stylelint": "15.10.2",
"stylelint-config-prettier": "9.0.5",
"stylelint-config-spaceninja": "13.0.1"
"stylelint-config-spaceninja": "13.0.1",
"unified": "^10.1.2"
}
}
1 change: 0 additions & 1 deletion src/_includes/layout.njk
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<link rel="icon" href="/favicon.svg">
<link rel="mask-icon" href="/favicon.svg" color="black">
<link rel="stylesheet" href="/style.css">
<link href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
</head>
<body class="{{ body_class }}">
<header class="header" id="top">
Expand Down
12 changes: 7 additions & 5 deletions src/_scss/_theme.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import 'https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;1,400;1,700&display=swap';

:root {
--color-bg: #fff;
--color-fg: #333;
Expand Down Expand Up @@ -37,8 +39,8 @@ a {
}

main {
margin: 0 auto;
margin-bottom: 2em;
margin-inline: auto;
max-width: 60ch;

// text-align: justify;
Expand All @@ -55,16 +57,16 @@ h3 {
}

p {
margin-block: 0.25em;
text-indent: 3ch;
margin: 0.25em 0;
text-indent: 2em;

&:first-of-type {
text-indent: 0;
}
}

.credits {
margin-block: 1em;
margin: 1em 0;
text-align: center;
text-indent: 0;
}
Expand Down Expand Up @@ -144,7 +146,7 @@ figcaption {
display: flex;
gap: 1em;
justify-content: space-between;
margin-block: 1em;
margin: 1em 0;
}

.chapter-nav__toc {
Expand Down
Loading

0 comments on commit 99bb72b

Please sign in to comment.