You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
4.6 KiB
172 lines
4.6 KiB
// you may not need all of these but they come up a lot
|
|
import fs from "fs";
|
|
import assert from "assert";
|
|
import logging from '../lib/logging.js';
|
|
import glob from "fast-glob";
|
|
import path from "path";
|
|
import template from "lodash/template.js";
|
|
import PDFDocument from "pdfkit";
|
|
|
|
const log = logging.create(import.meta.url);
|
|
|
|
const title_box = { x: 17, y: 415, width: 405, align: "center" };
|
|
const summary_box = { width: 405, height: 540 };
|
|
const main_title = { align: "center", width: 1196, height: 883, x: 586, y: 99};
|
|
const main_box = { width: 928, height: 535, x: 720, y: 273};
|
|
|
|
|
|
export const description = "Generates a PDF presentation from an input .md file and a background template."
|
|
|
|
// your command uses the npm package commander's options format
|
|
export const options = [
|
|
["--heading-font <path>", "Font file to use for headings.", "static/fonts/VictorMono-Bold.ttf"],
|
|
["--body-font <path>", "Font file to use for body text.", "static/fonts/VictorMono-Medium.ttf"],
|
|
]
|
|
|
|
// put required options in the required variable
|
|
export const required = [
|
|
["--input <path>", "The input .md file for the generated presentation."],
|
|
["--output <path>", "The output .pdf file to write."],
|
|
["--template <path>", "The background template image to use."],
|
|
]
|
|
|
|
// handy function for checking things are good and aborting
|
|
const check = (test, fail_message) => {
|
|
if(!test) {
|
|
log.error(fail_message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
export const start_pdf = (opts) => {
|
|
const doc = new PDFDocument({
|
|
font: "Courier",
|
|
size: [1920, 1080],
|
|
info: {
|
|
"Title": "Test",
|
|
"Author": "Zed A. Shaw",
|
|
"Subject": "Test subject",
|
|
"Keywords": "test test test",
|
|
"CreationDate": new Date(),
|
|
"ModDate": new Date(),
|
|
}});
|
|
|
|
const out_stream = doc.pipe(fs.createWriteStream(opts.output));
|
|
|
|
doc.registerFont("heading", opts.headingFont);
|
|
doc.registerFont("body", opts.bodyFont);
|
|
|
|
return [doc, out_stream];
|
|
}
|
|
|
|
const write_page = (doc, template, content) => {
|
|
// place the background image
|
|
doc.image(template, 0, 0, {
|
|
width: 1920,
|
|
height: 1080
|
|
});
|
|
|
|
// create the left side constant summary
|
|
doc.fontSize(40);
|
|
doc.font("heading").text(content.title, title_box.x, title_box.y, title_box);
|
|
doc.fontSize(20);
|
|
doc.moveDown();
|
|
// the lesson summary
|
|
doc.font("body").text(content.summary, summary_box);
|
|
doc.fillColor("white");
|
|
|
|
|
|
if(content.type == "title-bullets") {
|
|
doc.fontSize(80);
|
|
// add the title/body text
|
|
doc.font("heading").text(content.slide_title, main_title.x, main_title.y, main_title);
|
|
|
|
doc.fontSize(40);
|
|
|
|
doc.list(content.slide_body, main_box.x, main_box.y, main_box);
|
|
|
|
} else if(content.type == "title-only") {
|
|
// it's a title only slide
|
|
doc.fontSize(80);
|
|
doc.font("heading").text(content.slide_title, main_title.x, main_title.y + (main_title.height / 3), main_title);
|
|
} else {
|
|
// this handles title-text slides, and anything else
|
|
doc.fontSize(80);
|
|
// add the title/body text
|
|
doc.font("heading").text(content.slide_title, main_title.x, main_title.y, main_title);
|
|
|
|
doc.fontSize(40);
|
|
doc.font("body").text(content.slide_body, main_box.x, main_box.y, main_box);
|
|
}
|
|
}
|
|
|
|
const next_page = (doc) => {
|
|
doc.addPage();
|
|
}
|
|
|
|
const end_pdf = (doc) => {
|
|
doc.end();
|
|
}
|
|
|
|
const parse_input = (input) => {
|
|
const data = fs.readFileSync(input).toString();
|
|
const result = [];
|
|
|
|
const [head_data, slides_data] = data.split("===");
|
|
const head = JSON.parse(head_data);
|
|
|
|
for(let slide of slides_data.split("---")) {
|
|
const split = slide.trim().split("\n");
|
|
const title = split.shift();
|
|
const body = split.join("\n").trim();
|
|
|
|
if(title || body) {
|
|
const page = {
|
|
slide_title: title,
|
|
slide_body: body,
|
|
...head
|
|
}
|
|
|
|
if(!body) {
|
|
page.type = "title-only";
|
|
} else if(body.startsWith("*")) {
|
|
page.type = "title-bullets";
|
|
page.slide_body = body.split("\n").map(l => l.slice(1).trim());
|
|
} else {
|
|
page.type = "title-text";
|
|
}
|
|
|
|
result.push(page);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export const main = async (opts) => {
|
|
try {
|
|
const [doc, out_stream] = start_pdf(opts);
|
|
|
|
out_stream.on("finish", () => process.exit(0));
|
|
|
|
const pages = parse_input(opts.input);
|
|
|
|
for(let page of pages) {
|
|
console.log("PAGE", page);
|
|
write_page(doc, opts.template, page);
|
|
next_page(doc);
|
|
}
|
|
|
|
write_page(doc, opts.template, {
|
|
"title": pages[0].title,
|
|
"summary": pages[0].summary,
|
|
"slide_title": "The End",
|
|
"slide_body": "See you soon.",
|
|
"type": "title-text"
|
|
});
|
|
|
|
end_pdf(doc);
|
|
} catch(error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|