mirror of
https://github.com/astefanutti/decktape.git
synced 2024-11-22 12:22:10 +03:00
feat(esm): migrate this package to a pure ESM
BREAKING CHANGE: Please run node 12.20 or higher BREAKING CHANGE: This package is now a pure esm, please [read this](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
This commit is contained in:
parent
866daced5e
commit
056002fcdc
519
decktape.js
519
decktape.js
@ -2,22 +2,22 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk'),
|
||||
crypto = require('crypto'),
|
||||
Font = require('fonteditor-core').Font,
|
||||
fs = require('fs'),
|
||||
os = require('os'),
|
||||
parser = require('./libs/nomnom'),
|
||||
path = require('path'),
|
||||
puppeteer = require('puppeteer'),
|
||||
URI = require('urijs'),
|
||||
util = require('util');
|
||||
import chalk from 'chalk';
|
||||
import chalkTemplate from 'chalk-template';
|
||||
import crypto from 'crypto';
|
||||
import { Font } from 'fonteditor-core';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import parser from './libs/nomnom.js';
|
||||
import path from 'path';
|
||||
import puppeteer from 'puppeteer';
|
||||
import URI from 'urijs';
|
||||
import util from 'util';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const { PDFDocument, PDFName, ParseSpeeds, decodePDFRawStream } = require('pdf-lib');
|
||||
import { PDFDocument, PDFName, ParseSpeeds, decodePDFRawStream } from 'pdf-lib';
|
||||
|
||||
const { delay, pause } = require('./libs/util');
|
||||
|
||||
const plugins = loadAvailablePlugins(path.join(path.dirname(__filename), 'plugins'));
|
||||
import { delay, pause } from './libs/util.js';
|
||||
|
||||
parser.script('decktape').options({
|
||||
url : {
|
||||
@ -184,7 +184,8 @@ parser.command('version')
|
||||
.root(true)
|
||||
.help('Display decktape package version')
|
||||
.callback(_ => {
|
||||
console.log(require('./package.json').version);
|
||||
const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url)));
|
||||
console.log(pkg.version);
|
||||
process.exit();
|
||||
});
|
||||
parser.nocommand()
|
||||
@ -198,19 +199,10 @@ parser.command('automatic')
|
||||
`Iterates over the available plugins, picks the compatible one for presentation at the
|
||||
specified <url> and uses it to export and write the PDF into the specified <filename>.`
|
||||
);
|
||||
Object.entries(plugins).forEach(([id, plugin]) => {
|
||||
const command = parser.command(id);
|
||||
if (typeof plugin.options === 'object') {
|
||||
command.options(plugin.options);
|
||||
}
|
||||
if (typeof plugin.help === 'string') {
|
||||
command.help(plugin.help);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: should be deactivated as well when it does not execute in a TTY context
|
||||
if (os.name === 'windows') parser.nocolors();
|
||||
|
||||
const options = parser.parse(process.argv.slice(2));
|
||||
|
||||
const color = type => {
|
||||
switch (type) {
|
||||
@ -226,6 +218,18 @@ process.on('unhandledRejection', error => {
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const plugins = await loadAvailablePlugins(path.join(path.dirname(fileURLToPath(import.meta.url)), 'plugins'));
|
||||
|
||||
Object.entries(plugins).forEach(([id, plugin]) => {
|
||||
const command = parser.command(id);
|
||||
if (typeof plugin.options === 'object') {
|
||||
command.options(plugin.options);
|
||||
}
|
||||
if (typeof plugin.help === 'string') {
|
||||
command.help(plugin.help);
|
||||
}
|
||||
});
|
||||
const options = parser.parse(process.argv.slice(2));
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless : true,
|
||||
@ -255,9 +259,9 @@ process.on('unhandledRejection', error => {
|
||||
.on('requestfailed', request => {
|
||||
// do not output warning for cancelled requests
|
||||
if (request.failure() && request.failure().errorText === 'net::ERR_ABORTED') return;
|
||||
console.log(chalk`\n{keyword('orange') Unable to load resource from URL: ${request.url()}}`);
|
||||
console.log(chalkTemplate`\n{keyword('orange') Unable to load resource from URL: ${request.url()}}`);
|
||||
})
|
||||
.on('pageerror', error => console.log(chalk`\n{red Page error: ${error.message}}`));
|
||||
.on('pageerror', error => console.log(chalkTemplate`\n{red Page error: ${error.message}}`));
|
||||
|
||||
console.log('Loading page', options.url, '...');
|
||||
const load = page.waitForNavigation({ waitUntil: 'load', timeout: options.urlLoadTimeout });
|
||||
@ -275,274 +279,275 @@ process.on('unhandledRejection', error => {
|
||||
.then(_ => exportSlides(plugin, page, pdf))
|
||||
.then(async context => {
|
||||
await writePdf(options.filename, pdf);
|
||||
console.log(chalk`{green \nPrinted {bold ${context.exportedSlides}} slides}`);
|
||||
console.log(chalkTemplate`{green \nPrinted {bold ${context.exportedSlides}} slides}`);
|
||||
browser.close();
|
||||
process.exit();
|
||||
}))
|
||||
.catch(error => {
|
||||
console.log(chalk`{red \n${error}}`);
|
||||
console.log(chalkTemplate`{red \n${error}}`);
|
||||
browser.close();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
async function writePdf(filename, pdf) {
|
||||
const pdfDir = path.dirname(filename);
|
||||
try {
|
||||
fs.accessSync(pdfDir, fs.constants.F_OK);
|
||||
} catch {
|
||||
fs.mkdirSync(pdfDir, { recursive: true });
|
||||
async function writePdf(filename, pdf) {
|
||||
const pdfDir = path.dirname(filename);
|
||||
try {
|
||||
fs.accessSync(pdfDir, fs.constants.F_OK);
|
||||
} catch {
|
||||
fs.mkdirSync(pdfDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(filename, await pdf.save({ addDefaultPage: false }));
|
||||
}
|
||||
fs.writeFileSync(filename, await pdf.save({ addDefaultPage: false }));
|
||||
}
|
||||
|
||||
function loadAvailablePlugins(pluginsPath) {
|
||||
return fs.readdirSync(pluginsPath).reduce((plugins, pluginPath) => {
|
||||
const [, plugin] = pluginPath.match(/^(.*)\.js$/);
|
||||
if (plugin && fs.statSync(path.join(pluginsPath, pluginPath)).isFile()) {
|
||||
plugins[plugin] = require('./plugins/' + plugin);
|
||||
}
|
||||
return plugins;
|
||||
}, {});
|
||||
}
|
||||
async function loadAvailablePlugins(pluginsPath) {
|
||||
const plugins = await fs.promises.readdir(pluginsPath);
|
||||
const entries = await Promise.all(plugins.map(async pluginPath => {
|
||||
const [, plugin] = pluginPath.match(/^(.*)\.js$/);
|
||||
if (plugin && (await fs.promises.stat(path.join(pluginsPath, pluginPath))).isFile()) {
|
||||
return [plugin, await import (`./plugins/${pluginPath}`)];
|
||||
}
|
||||
}));
|
||||
return Object.fromEntries(entries.filter(Boolean));
|
||||
}
|
||||
|
||||
async function createPlugin(page) {
|
||||
let plugin;
|
||||
if (!options.command || options.command === 'automatic') {
|
||||
plugin = await createActivePlugin(page);
|
||||
if (!plugin) {
|
||||
console.log('No supported DeckTape plugin detected, falling back to generic plugin');
|
||||
plugin = plugins['generic'].create(page, options);
|
||||
async function createPlugin(page) {
|
||||
let plugin;
|
||||
if (!options.command || options.command === 'automatic') {
|
||||
plugin = await createActivePlugin(page);
|
||||
if (!plugin) {
|
||||
console.log('No supported DeckTape plugin detected, falling back to generic plugin');
|
||||
plugin = plugins['generic'].create(page, options);
|
||||
}
|
||||
} else {
|
||||
plugin = plugins[options.command].create(page, options);
|
||||
if (!await plugin.isActive()) {
|
||||
throw Error(`Unable to activate the ${plugin.getName()} DeckTape plugin for the address: ${options.url}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
plugin = plugins[options.command].create(page, options);
|
||||
if (!await plugin.isActive()) {
|
||||
throw Error(`Unable to activate the ${plugin.getName()} DeckTape plugin for the address: ${options.url}`);
|
||||
console.log(chalkTemplate`{cyan {bold ${plugin.getName()}} plugin activated}`);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
async function createActivePlugin(page) {
|
||||
for (let id in plugins) {
|
||||
if (id === 'generic') continue;
|
||||
const plugin = plugins[id].create(page, options);
|
||||
if (await plugin.isActive()) return plugin;
|
||||
}
|
||||
}
|
||||
console.log(chalk`{cyan {bold ${plugin.getName()}} plugin activated}`);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
async function createActivePlugin(page) {
|
||||
for (let id in plugins) {
|
||||
if (id === 'generic') continue;
|
||||
const plugin = plugins[id].create(page, options);
|
||||
if (await plugin.isActive()) return plugin;
|
||||
async function configurePage(plugin, page) {
|
||||
if (!options.size) {
|
||||
options.size = typeof plugin.size === 'function' ? await plugin.size() : { width: 1280, height: 720 };
|
||||
}
|
||||
await page.setViewport(options.size);
|
||||
}
|
||||
}
|
||||
|
||||
async function configurePage(plugin, page) {
|
||||
if (!options.size) {
|
||||
options.size = typeof plugin.size === 'function' ? await plugin.size() : { width: 1280, height: 720 };
|
||||
async function configurePlugin(plugin) {
|
||||
if (typeof plugin.configure === 'function') {
|
||||
await plugin.configure();
|
||||
}
|
||||
}
|
||||
await page.setViewport(options.size);
|
||||
}
|
||||
|
||||
async function configurePlugin(plugin) {
|
||||
if (typeof plugin.configure === 'function') {
|
||||
await plugin.configure();
|
||||
}
|
||||
}
|
||||
|
||||
async function exportSlides(plugin, page, pdf) {
|
||||
const context = {
|
||||
progressBarOverflow : 0,
|
||||
currentSlide : 1,
|
||||
exportedSlides : 0,
|
||||
pdfFonts : {},
|
||||
pdfXObjects : {},
|
||||
totalSlides : await plugin.slideCount(),
|
||||
};
|
||||
// TODO: support a more advanced "fragment to pause" mapping
|
||||
// for special use cases like GIF animations
|
||||
// TODO: support plugin optional promise to wait until a particular mutation
|
||||
// instead of a pause
|
||||
if (options.slides && !options.slides[context.currentSlide]) {
|
||||
process.stdout.write('\r' + await progressBar(plugin, context, { skip: true }));
|
||||
} else {
|
||||
await pause(options.pause);
|
||||
await exportSlide(plugin, page, pdf, context);
|
||||
}
|
||||
const maxSlide = options.slides ? Math.max(...Object.keys(options.slides)) : Infinity;
|
||||
let hasNext = await hasNextSlide(plugin, context);
|
||||
while (hasNext && context.currentSlide < maxSlide) {
|
||||
await nextSlide(plugin, context);
|
||||
await pause(options.pause);
|
||||
async function exportSlides(plugin, page, pdf) {
|
||||
const context = {
|
||||
progressBarOverflow : 0,
|
||||
currentSlide : 1,
|
||||
exportedSlides : 0,
|
||||
pdfFonts : {},
|
||||
pdfXObjects : {},
|
||||
totalSlides : await plugin.slideCount(),
|
||||
};
|
||||
// TODO: support a more advanced "fragment to pause" mapping
|
||||
// for special use cases like GIF animations
|
||||
// TODO: support plugin optional promise to wait until a particular mutation
|
||||
// instead of a pause
|
||||
if (options.slides && !options.slides[context.currentSlide]) {
|
||||
process.stdout.write('\r' + await progressBar(plugin, context, { skip: true }));
|
||||
} else {
|
||||
await pause(options.pause);
|
||||
await exportSlide(plugin, page, pdf, context);
|
||||
}
|
||||
hasNext = await hasNextSlide(plugin, context);
|
||||
}
|
||||
// Flush consolidated fonts
|
||||
Object.values(context.pdfFonts).forEach(({ ref, font }) => {
|
||||
pdf.context.assign(ref, pdf.context.flateStream(font.write({ type: 'ttf', hinting: true })));
|
||||
});
|
||||
return context;
|
||||
}
|
||||
|
||||
async function exportSlide(plugin, page, pdf, context) {
|
||||
process.stdout.write('\r' + await progressBar(plugin, context));
|
||||
|
||||
const buffer = await page.pdf({
|
||||
width : options.size.width,
|
||||
height : options.size.height,
|
||||
printBackground : true,
|
||||
pageRanges : '1',
|
||||
displayHeaderFooter : false,
|
||||
timeout : options.bufferTimeout,
|
||||
});
|
||||
await printSlide(pdf, await PDFDocument.load(buffer, { parseSpeed: ParseSpeeds.Fastest }), context);
|
||||
context.exportedSlides++;
|
||||
|
||||
if (options.screenshots) {
|
||||
for (let resolution of options.screenshotSizes || [options.size]) {
|
||||
await page.setViewport(resolution);
|
||||
// Delay page rendering to wait for the resize event to complete,
|
||||
// e.g. for impress.js (may be needed to be configurable)
|
||||
await pause(1000);
|
||||
await page.screenshot({
|
||||
path: path.join(options.screenshotDirectory, options.filename
|
||||
.replace('.pdf', `_${context.currentSlide}_${resolution.width}x${resolution.height}.${options.screenshotFormat}`)),
|
||||
fullPage: false,
|
||||
omitBackground: true,
|
||||
});
|
||||
await page.setViewport(options.size);
|
||||
await pause(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function printSlide(pdf, slide, context) {
|
||||
const duplicatedEntries = [];
|
||||
const [page] = await pdf.copyPages(slide, [0]);
|
||||
pdf.addPage(page);
|
||||
// Traverse the page to consolidate duplicates
|
||||
parseResources(page.node);
|
||||
// And delete all the collected duplicates
|
||||
duplicatedEntries.forEach(ref => pdf.context.delete(ref));
|
||||
|
||||
function parseResources(dictionary) {
|
||||
const resources = dictionary.get(PDFName.Resources);
|
||||
if (resources.has(PDFName.XObject)) {
|
||||
const xObject = resources.get(PDFName.XObject);
|
||||
xObject.entries().forEach(entry => parseXObject(entry, xObject));
|
||||
}
|
||||
if (resources.has(PDFName.Font)) {
|
||||
resources.get(PDFName.Font).entries().forEach(parseFont);
|
||||
}
|
||||
}
|
||||
|
||||
function parseXObject([name, entry], xObject) {
|
||||
const object = page.node.context.lookup(entry);
|
||||
const subtype = object.dict.get(PDFName.of('Subtype'));
|
||||
if (subtype === PDFName.of('Image')) {
|
||||
const digest = crypto.createHash('SHA1').update(object.contents).digest('hex');
|
||||
if (!context.pdfXObjects[digest]) {
|
||||
context.pdfXObjects[digest] = entry;
|
||||
const maxSlide = options.slides ? Math.max(...Object.keys(options.slides)) : Infinity;
|
||||
let hasNext = await hasNextSlide(plugin, context);
|
||||
while (hasNext && context.currentSlide < maxSlide) {
|
||||
await nextSlide(plugin, context);
|
||||
await pause(options.pause);
|
||||
if (options.slides && !options.slides[context.currentSlide]) {
|
||||
process.stdout.write('\r' + await progressBar(plugin, context, { skip: true }));
|
||||
} else {
|
||||
xObject.set(name, context.pdfXObjects[digest]);
|
||||
duplicatedEntries.push(entry);
|
||||
await exportSlide(plugin, page, pdf, context);
|
||||
}
|
||||
} else {
|
||||
parseResources(object.dict);
|
||||
hasNext = await hasNextSlide(plugin, context);
|
||||
}
|
||||
};
|
||||
// Flush consolidated fonts
|
||||
Object.values(context.pdfFonts).forEach(({ ref, font }) => {
|
||||
pdf.context.assign(ref, pdf.context.flateStream(font.write({ type: 'ttf', hinting: true })));
|
||||
});
|
||||
return context;
|
||||
}
|
||||
|
||||
function parseFont([_, entry]) {
|
||||
const object = page.node.context.lookup(entry);
|
||||
const subtype = object.get(PDFName.of('Subtype'));
|
||||
// See "Introduction to Font Data Structures" from PDF specification
|
||||
if (subtype === PDFName.of('Type0')) {
|
||||
// TODO: properly support composite fonts with multiple descendants
|
||||
const descendant = page.node.context.lookup(object.get(PDFName.of('DescendantFonts')).get(0));
|
||||
if (descendant.get(PDFName.of('Subtype')) === PDFName.of('CIDFontType2')) {
|
||||
const descriptor = page.node.context.lookup(descendant.get(PDFName.of('FontDescriptor')));
|
||||
const ref = descriptor.get(PDFName.of('FontFile2'));
|
||||
const file = page.node.context.lookup(ref);
|
||||
if (!file) {
|
||||
// The font has already been processed and removed
|
||||
return;
|
||||
}
|
||||
const bytes = decodePDFRawStream(file).decode();
|
||||
const font = Font.create(Buffer.from(bytes), { type: 'ttf', hinting: true });
|
||||
// Some fonts happen to have no metadata, which is required by fonteditor
|
||||
if (!font.data.name) {
|
||||
font.data.name = {};
|
||||
}
|
||||
// PDF font name does not contain sub family on Windows 10,
|
||||
// so a more robust key is computed from the font metadata
|
||||
const id = descriptor.get(PDFName.of('FontName')).value() + ' - ' + fontMetadataKey(font.data.name);
|
||||
if (context.pdfFonts[id]) {
|
||||
const f = context.pdfFonts[id].font;
|
||||
font.data.glyf.forEach((g, i) => {
|
||||
if (g.contours && g.contours.length > 0) {
|
||||
if (!f.data.glyf[i] || !f.data.glyf[i].contours || f.data.glyf[i].contours.length === 0) {
|
||||
mergeGlyph(f, i, g);
|
||||
}
|
||||
} else if (g.compound) {
|
||||
if (!f.data.glyf[i] || typeof f.data.glyf[i].compound === 'undefined') {
|
||||
mergeGlyph(f, i, g);
|
||||
}
|
||||
}
|
||||
});
|
||||
descriptor.set(PDFName.of('FontFile2'), context.pdfFonts[id].ref);
|
||||
duplicatedEntries.push(ref);
|
||||
async function exportSlide(plugin, page, pdf, context) {
|
||||
process.stdout.write('\r' + await progressBar(plugin, context));
|
||||
|
||||
const buffer = await page.pdf({
|
||||
width : options.size.width,
|
||||
height : options.size.height,
|
||||
printBackground : true,
|
||||
pageRanges : '1',
|
||||
displayHeaderFooter : false,
|
||||
timeout : options.bufferTimeout,
|
||||
});
|
||||
await printSlide(pdf, await PDFDocument.load(buffer, { parseSpeed: ParseSpeeds.Fastest }), context);
|
||||
context.exportedSlides++;
|
||||
|
||||
if (options.screenshots) {
|
||||
for (let resolution of options.screenshotSizes || [options.size]) {
|
||||
await page.setViewport(resolution);
|
||||
// Delay page rendering to wait for the resize event to complete,
|
||||
// e.g. for impress.js (may be needed to be configurable)
|
||||
await pause(1000);
|
||||
await page.screenshot({
|
||||
path: path.join(options.screenshotDirectory, options.filename
|
||||
.replace('.pdf', `_${context.currentSlide}_${resolution.width}x${resolution.height}.${options.screenshotFormat}`)),
|
||||
fullPage: false,
|
||||
omitBackground: true,
|
||||
});
|
||||
await page.setViewport(options.size);
|
||||
await pause(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function printSlide(pdf, slide, context) {
|
||||
const duplicatedEntries = [];
|
||||
const [page] = await pdf.copyPages(slide, [0]);
|
||||
pdf.addPage(page);
|
||||
// Traverse the page to consolidate duplicates
|
||||
parseResources(page.node);
|
||||
// And delete all the collected duplicates
|
||||
duplicatedEntries.forEach(ref => pdf.context.delete(ref));
|
||||
|
||||
function parseResources(dictionary) {
|
||||
const resources = dictionary.get(PDFName.Resources);
|
||||
if (resources.has(PDFName.XObject)) {
|
||||
const xObject = resources.get(PDFName.XObject);
|
||||
xObject.entries().forEach(entry => parseXObject(entry, xObject));
|
||||
}
|
||||
if (resources.has(PDFName.Font)) {
|
||||
resources.get(PDFName.Font).entries().forEach(parseFont);
|
||||
}
|
||||
}
|
||||
|
||||
function parseXObject([name, entry], xObject) {
|
||||
const object = page.node.context.lookup(entry);
|
||||
const subtype = object.dict.get(PDFName.of('Subtype'));
|
||||
if (subtype === PDFName.of('Image')) {
|
||||
const digest = crypto.createHash('SHA1').update(object.contents).digest('hex');
|
||||
if (!context.pdfXObjects[digest]) {
|
||||
context.pdfXObjects[digest] = entry;
|
||||
} else {
|
||||
context.pdfFonts[id] = { ref: ref, font: font };
|
||||
xObject.set(name, context.pdfXObjects[digest]);
|
||||
duplicatedEntries.push(entry);
|
||||
}
|
||||
} else {
|
||||
parseResources(object.dict);
|
||||
}
|
||||
};
|
||||
|
||||
function parseFont([_, entry]) {
|
||||
const object = page.node.context.lookup(entry);
|
||||
const subtype = object.get(PDFName.of('Subtype'));
|
||||
// See "Introduction to Font Data Structures" from PDF specification
|
||||
if (subtype === PDFName.of('Type0')) {
|
||||
// TODO: properly support composite fonts with multiple descendants
|
||||
const descendant = page.node.context.lookup(object.get(PDFName.of('DescendantFonts')).get(0));
|
||||
if (descendant.get(PDFName.of('Subtype')) === PDFName.of('CIDFontType2')) {
|
||||
const descriptor = page.node.context.lookup(descendant.get(PDFName.of('FontDescriptor')));
|
||||
const ref = descriptor.get(PDFName.of('FontFile2'));
|
||||
const file = page.node.context.lookup(ref);
|
||||
if (!file) {
|
||||
// The font has already been processed and removed
|
||||
return;
|
||||
}
|
||||
const bytes = decodePDFRawStream(file).decode();
|
||||
const font = Font.create(Buffer.from(bytes), { type: 'ttf', hinting: true });
|
||||
// Some fonts happen to have no metadata, which is required by fonteditor
|
||||
if (!font.data.name) {
|
||||
font.data.name = {};
|
||||
}
|
||||
// PDF font name does not contain sub family on Windows 10,
|
||||
// so a more robust key is computed from the font metadata
|
||||
const id = descriptor.get(PDFName.of('FontName')).value() + ' - ' + fontMetadataKey(font.data.name);
|
||||
if (context.pdfFonts[id]) {
|
||||
const f = context.pdfFonts[id].font;
|
||||
font.data.glyf.forEach((g, i) => {
|
||||
if (g.contours && g.contours.length > 0) {
|
||||
if (!f.data.glyf[i] || !f.data.glyf[i].contours || f.data.glyf[i].contours.length === 0) {
|
||||
mergeGlyph(f, i, g);
|
||||
}
|
||||
} else if (g.compound) {
|
||||
if (!f.data.glyf[i] || typeof f.data.glyf[i].compound === 'undefined') {
|
||||
mergeGlyph(f, i, g);
|
||||
}
|
||||
}
|
||||
});
|
||||
descriptor.set(PDFName.of('FontFile2'), context.pdfFonts[id].ref);
|
||||
duplicatedEntries.push(ref);
|
||||
} else {
|
||||
context.pdfFonts[id] = { ref: ref, font: font };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function mergeGlyph(font, index, glyf) {
|
||||
if (font.data.glyf.length <= index) {
|
||||
for (let i = font.data.glyf.length; i < index; i++) {
|
||||
font.data.glyf.push({ contours: Array(0), advanceWidth: 0, leftSideBearing: 0 });
|
||||
function mergeGlyph(font, index, glyf) {
|
||||
if (font.data.glyf.length <= index) {
|
||||
for (let i = font.data.glyf.length; i < index; i++) {
|
||||
font.data.glyf.push({ contours: Array(0), advanceWidth: 0, leftSideBearing: 0 });
|
||||
}
|
||||
font.data.glyf.push(glyf);
|
||||
} else {
|
||||
font.data.glyf[index] = glyf;
|
||||
}
|
||||
font.data.glyf.push(glyf);
|
||||
} else {
|
||||
font.data.glyf[index] = glyf;
|
||||
}
|
||||
|
||||
function fontMetadataKey(font) {
|
||||
const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'preferredFamily', 'preferredSubFamily', 'uniqueSubFamily'];
|
||||
return Object.entries(font)
|
||||
.filter(([key, _]) => keys.includes(key))
|
||||
.reduce((r, [k, v], i) => r + (i > 0 ? ',' : '') + k + '=' + v, '');
|
||||
}
|
||||
}
|
||||
|
||||
function fontMetadataKey(font) {
|
||||
const keys = ['fontFamily', 'fontSubFamily', 'fullName', 'preferredFamily', 'preferredSubFamily', 'uniqueSubFamily'];
|
||||
return Object.entries(font)
|
||||
.filter(([key, _]) => keys.includes(key))
|
||||
.reduce((r, [k, v], i) => r + (i > 0 ? ',' : '') + k + '=' + v, '');
|
||||
async function hasNextSlide(plugin, context) {
|
||||
if (typeof plugin.hasNextSlide === 'function') {
|
||||
return await plugin.hasNextSlide();
|
||||
} else {
|
||||
return context.currentSlide < context.totalSlides;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function hasNextSlide(plugin, context) {
|
||||
if (typeof plugin.hasNextSlide === 'function') {
|
||||
return await plugin.hasNextSlide();
|
||||
} else {
|
||||
return context.currentSlide < context.totalSlides;
|
||||
async function nextSlide(plugin, context) {
|
||||
context.currentSlide++;
|
||||
return plugin.nextSlide();
|
||||
}
|
||||
}
|
||||
|
||||
async function nextSlide(plugin, context) {
|
||||
context.currentSlide++;
|
||||
return plugin.nextSlide();
|
||||
}
|
||||
|
||||
// TODO: add progress bar, duration, ETA and file size
|
||||
async function progressBar(plugin, context, { skip } = { skip : false }) {
|
||||
const cols = [];
|
||||
const index = await plugin.currentSlideIndex();
|
||||
cols.push(`${skip ? 'Skipping' : 'Printing'} slide `);
|
||||
cols.push(`#${index}`.padEnd(8));
|
||||
cols.push(' (');
|
||||
cols.push(`${context.currentSlide}`.padStart(context.totalSlides ? context.totalSlides.toString().length : 3));
|
||||
cols.push('/');
|
||||
cols.push(context.totalSlides || ' ?');
|
||||
cols.push(') ...');
|
||||
// erase overflowing slide fragments
|
||||
cols.push(' '.repeat(Math.max(context.progressBarOverflow - Math.max(index.length + 1 - 8, 0), 0)));
|
||||
context.progressBarOverflow = Math.max(index.length + 1 - 8, 0);
|
||||
return cols.join('');
|
||||
}
|
||||
// TODO: add progress bar, duration, ETA and file size
|
||||
async function progressBar(plugin, context, { skip } = { skip : false }) {
|
||||
const cols = [];
|
||||
const index = await plugin.currentSlideIndex();
|
||||
cols.push(`${skip ? 'Skipping' : 'Printing'} slide `);
|
||||
cols.push(`#${index}`.padEnd(8));
|
||||
cols.push(' (');
|
||||
cols.push(`${context.currentSlide}`.padStart(context.totalSlides ? context.totalSlides.toString().length : 3));
|
||||
cols.push('/');
|
||||
cols.push(context.totalSlides || ' ?');
|
||||
cols.push(') ...');
|
||||
// erase overflowing slide fragments
|
||||
cols.push(' '.repeat(Math.max(context.progressBarOverflow - Math.max(index.length + 1 - 8, 0), 0)));
|
||||
context.progressBarOverflow = Math.max(index.length + 1 - 8, 0);
|
||||
return cols.join('');
|
||||
}
|
||||
})();
|
||||
|
364
libs/nomnom.js
364
libs/nomnom.js
@ -1,13 +1,12 @@
|
||||
var chalk = require('chalk');
|
||||
import chalk from 'chalk';
|
||||
|
||||
function ArgParser() {
|
||||
this.commands = {}; // expected commands
|
||||
this.specs = {}; // option specifications
|
||||
}
|
||||
|
||||
ArgParser.prototype = {
|
||||
class ArgParser {
|
||||
constructor() {
|
||||
this.commands = {}; // expected commands
|
||||
this.specs = {}; // option specifications
|
||||
}
|
||||
/* Add a command to the expected commands */
|
||||
command : function(name) {
|
||||
command(name) {
|
||||
var command;
|
||||
if (name) {
|
||||
command = this.commands[name] = {
|
||||
@ -23,114 +22,99 @@ ArgParser.prototype = {
|
||||
|
||||
// facilitates command('name').options().cb().help()
|
||||
var chain = {
|
||||
options : function(specs) {
|
||||
options: function (specs) {
|
||||
command.specs = specs;
|
||||
return chain;
|
||||
},
|
||||
opts : function(specs) {
|
||||
opts: function (specs) {
|
||||
// old API
|
||||
return this.options(specs);
|
||||
},
|
||||
option : function(name, spec) {
|
||||
option: function (name, spec) {
|
||||
command.specs[name] = spec;
|
||||
return chain;
|
||||
},
|
||||
callback : function(cb) {
|
||||
callback: function (cb) {
|
||||
command.cb = cb;
|
||||
return chain;
|
||||
},
|
||||
help : function(help) {
|
||||
help: function (help) {
|
||||
command.help = help;
|
||||
return chain;
|
||||
},
|
||||
usage : function(usage) {
|
||||
usage: function (usage) {
|
||||
command._usage = usage;
|
||||
return chain;
|
||||
},
|
||||
root : function(root) {
|
||||
root: function (root) {
|
||||
command.root = root;
|
||||
return chain;
|
||||
}
|
||||
};
|
||||
return chain;
|
||||
},
|
||||
|
||||
nocommand : function() {
|
||||
}
|
||||
nocommand() {
|
||||
return this.command();
|
||||
},
|
||||
|
||||
options : function(specs) {
|
||||
}
|
||||
options(specs) {
|
||||
this.specs = specs;
|
||||
return this;
|
||||
},
|
||||
|
||||
opts : function(specs) {
|
||||
}
|
||||
opts(specs) {
|
||||
// old API
|
||||
return this.options(specs);
|
||||
},
|
||||
|
||||
globalOpts : function(specs) {
|
||||
}
|
||||
globalOpts(specs) {
|
||||
// old API
|
||||
return this.options(specs);
|
||||
},
|
||||
|
||||
option : function(name, spec) {
|
||||
}
|
||||
option(name, spec) {
|
||||
this.specs[name] = spec;
|
||||
return this;
|
||||
},
|
||||
|
||||
usage : function(usage) {
|
||||
}
|
||||
usage(usage) {
|
||||
this._usage = usage;
|
||||
return this;
|
||||
},
|
||||
|
||||
printer : function(print) {
|
||||
}
|
||||
printer(print) {
|
||||
this.print = print;
|
||||
return this;
|
||||
},
|
||||
|
||||
script : function(script) {
|
||||
}
|
||||
script(script) {
|
||||
this._script = script;
|
||||
return this;
|
||||
},
|
||||
|
||||
scriptName : function(script) {
|
||||
}
|
||||
scriptName(script) {
|
||||
// old API
|
||||
return this.script(script);
|
||||
},
|
||||
|
||||
help : function(help) {
|
||||
}
|
||||
help(help) {
|
||||
this._help = help;
|
||||
return this;
|
||||
},
|
||||
|
||||
colors: function() {
|
||||
}
|
||||
colors() {
|
||||
// deprecated - colors are on by default now
|
||||
return this;
|
||||
},
|
||||
|
||||
nocolors : function() {
|
||||
}
|
||||
nocolors() {
|
||||
this._nocolors = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
parseArgs : function(argv) {
|
||||
}
|
||||
parseArgs(argv) {
|
||||
// old API
|
||||
return this.parse(argv);
|
||||
},
|
||||
|
||||
nom : function(argv) {
|
||||
}
|
||||
nom(argv) {
|
||||
return this.parse(argv);
|
||||
},
|
||||
|
||||
parse : function(argv) {
|
||||
this.print = this.print || function(str, code) {
|
||||
}
|
||||
parse(argv) {
|
||||
this.print = this.print || function (str, code) {
|
||||
console.log(str);
|
||||
process.exit(code || 0);
|
||||
};
|
||||
this._help = this._help || "";
|
||||
this._script = this._script || process.argv[0] + " "
|
||||
+ require('path').basename(process.argv[1]);
|
||||
+ require('path').basename(process.argv[1]);
|
||||
this.specs = this.specs || {};
|
||||
|
||||
var argv = argv || process.argv.slice(2);
|
||||
@ -140,72 +124,72 @@ ArgParser.prototype = {
|
||||
var commandExpected = Object.keys(this.commands).length > 0;
|
||||
|
||||
if (commandExpected) {
|
||||
if (command) {
|
||||
if (command.root) {
|
||||
this.specs = command.specs;
|
||||
} else {
|
||||
Object.assign(this.specs, command.specs);
|
||||
if (command) {
|
||||
if (command.root) {
|
||||
this.specs = command.specs;
|
||||
} else {
|
||||
Object.assign(this.specs, command.specs);
|
||||
}
|
||||
this.subcommand = true;
|
||||
this._script += " " + command.name;
|
||||
if (command.help) {
|
||||
this._help = command.help;
|
||||
}
|
||||
this.specs.command = {
|
||||
hidden: true,
|
||||
name: 'command',
|
||||
position: 0,
|
||||
help: command.help
|
||||
};
|
||||
}
|
||||
else if (!this.fallback) {
|
||||
return this.print(this._script + ": command expected", 1);
|
||||
}
|
||||
else {
|
||||
// no command but command expected e.g. 'git -v'
|
||||
var helpStringBuilder = {
|
||||
list: function () {
|
||||
return 'one of: ' + Object.entries(this.commands)
|
||||
.filter(([key, cmd]) => !cmd.root)
|
||||
.map(([key, cmd]) => key).join(', ');
|
||||
},
|
||||
twoColumn: function () {
|
||||
// find the longest command name to ensure horizontal alignment
|
||||
var maxLength = Math.max(...Object.values(this.commands).map(cmd => cmd.name.length));
|
||||
// create the two column text strings
|
||||
var cmdHelp = Object.entries(this.commands).map(([name, cmd]) => {
|
||||
var diff = maxLength - name.length;
|
||||
var pad = new Array(diff + 4).join(" ");
|
||||
return " " + [name, pad, cmd.help].join(" ");
|
||||
});
|
||||
return "\n" + cmdHelp.join("\n");
|
||||
}
|
||||
this.subcommand = true;
|
||||
this._script += " " + command.name;
|
||||
if (command.help) {
|
||||
this._help = command.help;
|
||||
}
|
||||
this.specs.command = {
|
||||
hidden: true,
|
||||
name: 'command',
|
||||
position: 0,
|
||||
help: command.help
|
||||
};
|
||||
}
|
||||
else if (!this.fallback) {
|
||||
return this.print(this._script + ": command expected", 1);
|
||||
}
|
||||
else {
|
||||
// no command but command expected e.g. 'git -v'
|
||||
var helpStringBuilder = {
|
||||
list : function() {
|
||||
return 'one of: ' + Object.entries(this.commands)
|
||||
.filter(([key, cmd]) => !cmd.root)
|
||||
.map(([key, cmd]) => key).join(', ');
|
||||
},
|
||||
twoColumn : function() {
|
||||
// find the longest command name to ensure horizontal alignment
|
||||
var maxLength = Math.max(...Object.values(this.commands).map(cmd => cmd.name.length));
|
||||
// create the two column text strings
|
||||
var cmdHelp = Object.entries(this.commands).map(([name, cmd]) => {
|
||||
var diff = maxLength - name.length;
|
||||
var pad = new Array(diff + 4).join(" ");
|
||||
return " " + [ name, pad, cmd.help ].join(" ");
|
||||
});
|
||||
return "\n" + cmdHelp.join("\n");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// if there are a small number of commands and all have help strings,
|
||||
// display them in a two column table; otherwise use the brief version.
|
||||
// The arbitrary choice of "20" comes from the number commands git
|
||||
// displays as "common commands"
|
||||
var helpType = 'list';
|
||||
if (Object.keys(this.commands).length <= 20) {
|
||||
if (Object.values(this.commands).every(cmd => cmd.help)) {
|
||||
helpType = 'twoColumn';
|
||||
}
|
||||
// if there are a small number of commands and all have help strings,
|
||||
// display them in a two column table; otherwise use the brief version.
|
||||
// The arbitrary choice of "20" comes from the number commands git
|
||||
// displays as "common commands"
|
||||
var helpType = 'list';
|
||||
if (Object.keys(this.commands).length <= 20) {
|
||||
if (Object.values(this.commands).every(cmd => cmd.help)) {
|
||||
helpType = 'twoColumn';
|
||||
}
|
||||
}
|
||||
|
||||
this.specs.command = {
|
||||
name: 'command',
|
||||
position: 0,
|
||||
help: helpStringBuilder[helpType].call(this)
|
||||
};
|
||||
this.specs.command = {
|
||||
name: 'command',
|
||||
position: 0,
|
||||
help: helpStringBuilder[helpType].call(this)
|
||||
};
|
||||
|
||||
if (this.fallback) {
|
||||
Object.assign(this.specs, this.fallback.specs);
|
||||
this._help = this.fallback.help;
|
||||
} else {
|
||||
this.specs.command.required = true;
|
||||
}
|
||||
}
|
||||
if (this.fallback) {
|
||||
Object.assign(this.specs, this.fallback.specs);
|
||||
this._help = this.fallback.help;
|
||||
} else {
|
||||
this.specs.command.required = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.specs.length === undefined) {
|
||||
@ -228,7 +212,7 @@ ArgParser.prototype = {
|
||||
|
||||
/* parse the args */
|
||||
var that = this;
|
||||
args.reduce(function(arg, val) {
|
||||
args.reduce(function (arg, val) {
|
||||
/* positional */
|
||||
if (arg.isValue) {
|
||||
positionals.push(arg.value);
|
||||
@ -237,20 +221,20 @@ ArgParser.prototype = {
|
||||
var last = arg.chars.pop();
|
||||
|
||||
/* -cfv */
|
||||
(arg.chars).forEach(function(ch) {
|
||||
(arg.chars).forEach(function (ch) {
|
||||
that.setOption(options, ch, true);
|
||||
});
|
||||
|
||||
/* -v key */
|
||||
if (!that.opt(last).flag) {
|
||||
if (val.isValue) {
|
||||
that.setOption(options, last, val.value);
|
||||
return Arg(); // skip next turn - swallow arg
|
||||
}
|
||||
else {
|
||||
that.print("'-" + (that.opt(last).name || last) + "'"
|
||||
+ " expects a value\n\n" + that.getUsage(), 1);
|
||||
}
|
||||
if (val.isValue) {
|
||||
that.setOption(options, last, val.value);
|
||||
return Arg(); // skip next turn - swallow arg
|
||||
}
|
||||
else {
|
||||
that.print("'-" + (that.opt(last).name || last) + "'"
|
||||
+ " expects a value\n\n" + that.getUsage(), 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* -v */
|
||||
@ -291,25 +275,25 @@ ArgParser.prototype = {
|
||||
if (!command && this.fallback)
|
||||
positionals.unshift(undefined);
|
||||
|
||||
positionals.forEach(function(pos, index) {
|
||||
positionals.forEach(function (pos, index) {
|
||||
this.setOption(options, index, pos);
|
||||
}, this);
|
||||
|
||||
options._ = positionals;
|
||||
|
||||
this.specs.forEach(function(opt) {
|
||||
this.specs.forEach(function (opt) {
|
||||
if (opt.default !== undefined && options[opt.name] === undefined) {
|
||||
this.setOption(options, opt.name, opt.default);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// exit if required arg isn't present
|
||||
this.specs.forEach(function(opt) {
|
||||
this.specs.forEach(function (opt) {
|
||||
if (opt.required && options[opt.name] === undefined) {
|
||||
var msg = opt.name + " argument is required";
|
||||
msg = this._nocolors ? msg : chalk.red(msg);
|
||||
var msg = opt.name + " argument is required";
|
||||
msg = this._nocolors ? msg : chalk.red(msg);
|
||||
|
||||
this.print("\n" + msg + "\n" + this.getUsage(), 1);
|
||||
this.print("\n" + msg + "\n" + this.getUsage(), 1);
|
||||
}
|
||||
}, this);
|
||||
|
||||
@ -321,9 +305,8 @@ ArgParser.prototype = {
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
getUsage : function() {
|
||||
}
|
||||
getUsage() {
|
||||
if (this.command && this.command._usage) {
|
||||
return this.command._usage;
|
||||
}
|
||||
@ -360,7 +343,7 @@ ArgParser.prototype = {
|
||||
}
|
||||
|
||||
// assume there are no gaps in the specified pos. args
|
||||
positionals.forEach(function(pos) {
|
||||
positionals.forEach(function (pos) {
|
||||
str += " ";
|
||||
var posStr = pos.string;
|
||||
if (!posStr) {
|
||||
@ -396,14 +379,14 @@ ArgParser.prototype = {
|
||||
}
|
||||
var longest = Math.max(...positionals.map(pos => pos.name.length));
|
||||
|
||||
positionals.forEach(function(pos) {
|
||||
positionals.forEach(function (pos) {
|
||||
var posStr = pos.string || pos.name;
|
||||
str += posStr + spaces(longest - posStr.length) + " ";
|
||||
if (!this._nocolors) {
|
||||
str += chalk.grey(pos.help || "")
|
||||
str += chalk.grey(pos.help || "");
|
||||
}
|
||||
else {
|
||||
str += (pos.help || "")
|
||||
str += (pos.help || "");
|
||||
}
|
||||
str += "\n";
|
||||
}, this);
|
||||
@ -422,13 +405,13 @@ ArgParser.prototype = {
|
||||
|
||||
longest = Math.max(...options.map(opt => opt.string.length));
|
||||
|
||||
options.forEach(function(opt) {
|
||||
options.forEach(function (opt) {
|
||||
if (!opt.hidden) {
|
||||
str += " " + opt.string + spaces(longest - opt.string.length) + " ";
|
||||
|
||||
var defaults = (opt.default != null ? " [" + opt.default + "]" : "");
|
||||
var help = opt.help ? opt.help + defaults : "";
|
||||
str += this._nocolors ? help: chalk.grey(help);
|
||||
str += this._nocolors ? help : chalk.grey(help);
|
||||
|
||||
str += "\n";
|
||||
}
|
||||
@ -440,58 +423,59 @@ ArgParser.prototype = {
|
||||
}
|
||||
return str;
|
||||
}
|
||||
};
|
||||
opt(arg) {
|
||||
// get the specified opt for this parsed arg
|
||||
var match = Opt({});
|
||||
this.specs.forEach(function (opt) {
|
||||
if (opt.matches(arg)) {
|
||||
match = opt;
|
||||
}
|
||||
});
|
||||
return match;
|
||||
}
|
||||
setOption(options, arg, value) {
|
||||
var option = this.opt(arg);
|
||||
if (option.callback) {
|
||||
var message = option.callback(value);
|
||||
|
||||
ArgParser.prototype.opt = function(arg) {
|
||||
// get the specified opt for this parsed arg
|
||||
var match = Opt({});
|
||||
this.specs.forEach(function(opt) {
|
||||
if (opt.matches(arg)) {
|
||||
match = opt;
|
||||
if (typeof message == "string") {
|
||||
this.print(message, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
return match;
|
||||
};
|
||||
|
||||
ArgParser.prototype.setOption = function(options, arg, value) {
|
||||
var option = this.opt(arg);
|
||||
if (option.callback) {
|
||||
var message = option.callback(value);
|
||||
|
||||
if (typeof message == "string") {
|
||||
this.print(message, 1);
|
||||
if (option.type != "string") {
|
||||
try {
|
||||
// infer type by JSON parsing the string
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
}
|
||||
|
||||
if (option.type != "string") {
|
||||
try {
|
||||
// infer type by JSON parsing the string
|
||||
value = JSON.parse(value)
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
if (option.transform) {
|
||||
value = option.transform(value);
|
||||
}
|
||||
|
||||
if (option.transform) {
|
||||
value = option.transform(value);
|
||||
}
|
||||
var name = option.name || arg;
|
||||
if (option.choices && option.choices.indexOf(value) == -1) {
|
||||
this.print(name + " must be one of: " + option.choices.join(", "), 1);
|
||||
}
|
||||
|
||||
var name = option.name || arg;
|
||||
if (option.choices && option.choices.indexOf(value) == -1) {
|
||||
this.print(name + " must be one of: " + option.choices.join(", "), 1);
|
||||
}
|
||||
|
||||
if (option.list) {
|
||||
if (!options[name]) {
|
||||
options[name] = [value];
|
||||
if (option.list) {
|
||||
if (!options[name]) {
|
||||
options[name] = [value];
|
||||
}
|
||||
else {
|
||||
options[name].push(value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
options[name].push(value);
|
||||
options[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
options[name] = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* an arg is an item that's actually parsed from the command line
|
||||
@ -585,4 +569,4 @@ var createParser = function() {
|
||||
return new ArgParser();
|
||||
}
|
||||
|
||||
module.exports = createParser();
|
||||
export default createParser();
|
||||
|
44
libs/util.js
44
libs/util.js
@ -1,45 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports.delay = delay => value => new Promise(resolve => setTimeout(resolve, delay, value));
|
||||
export const delay = delay => value => new Promise(resolve => setTimeout(resolve, delay, value));
|
||||
|
||||
module.exports.pause = ms => module.exports.delay(ms)();
|
||||
export const pause = ms => delay(ms)();
|
||||
|
||||
module.exports.wait = ms => () => module.exports.delay(ms);
|
||||
|
||||
// Can be removed when Node 8 becomes a requirement
|
||||
|
||||
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
||||
if (!String.prototype.padStart) {
|
||||
String.prototype.padStart = function padStart(targetLength, padString) {
|
||||
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
|
||||
padString = String(padString || ' ');
|
||||
if (this.length > targetLength) {
|
||||
return String(this);
|
||||
} else {
|
||||
targetLength = targetLength - this.length;
|
||||
if (targetLength > padString.length) {
|
||||
padString += padString.repeat(targetLength / padString.length);
|
||||
}
|
||||
return padString.slice(0, targetLength) + String(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
|
||||
if (!String.prototype.padEnd) {
|
||||
String.prototype.padEnd = function padEnd(targetLength, padString) {
|
||||
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
|
||||
padString = String(padString || ' ');
|
||||
if (this.length > targetLength) {
|
||||
return String(this);
|
||||
} else {
|
||||
targetLength = targetLength - this.length;
|
||||
if (targetLength > padString.length) {
|
||||
padString += padString.repeat(targetLength / padString.length);
|
||||
}
|
||||
return String(this) + padString.slice(0, targetLength);
|
||||
}
|
||||
};
|
||||
}
|
||||
export const wait = ms => () => delay(ms);
|
||||
|
53
npm-shrinkwrap.json
generated
53
npm-shrinkwrap.json
generated
@ -9,7 +9,8 @@
|
||||
"version": "3.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"chalk": "~5.1.2",
|
||||
"chalk-template": "^0.4.0",
|
||||
"fonteditor-core": "2.1.10",
|
||||
"pdf-lib": "1.17.1",
|
||||
"puppeteer": "18.2.1",
|
||||
@ -162,6 +163,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz",
|
||||
"integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk-template": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
|
||||
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk-template?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk-template/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
@ -775,12 +801,27 @@
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz",
|
||||
"integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ=="
|
||||
},
|
||||
"chalk-template": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
|
||||
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
"chalk": "^4.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
|
@ -6,6 +6,7 @@
|
||||
"homepage": "https://github.com/astefanutti/decktape",
|
||||
"license": "MIT",
|
||||
"main": "decktape.js",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"decktape": "decktape.js"
|
||||
},
|
||||
@ -20,7 +21,8 @@
|
||||
"url": "https://github.com/astefanutti/decktape/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"chalk": "~5.1.2",
|
||||
"chalk-template": "^0.4.0",
|
||||
"fonteditor-core": "2.1.10",
|
||||
"pdf-lib": "1.17.1",
|
||||
"puppeteer": "18.2.1",
|
||||
@ -28,6 +30,6 @@
|
||||
"urijs": "1.19.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": ">=12.20"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
exports.help =
|
||||
export const help =
|
||||
`Requires the bespoke-extern module to expose the Bespoke.js API to a global variable named
|
||||
'bespoke' and provides access to the collection of deck instances via 'bespoke.decks
|
||||
and the most recent deck via 'bespoke.deck'.`;
|
||||
|
||||
exports.create = page => new Bespoke(page);
|
||||
export const create = page => new Bespoke(page);
|
||||
|
||||
class Bespoke {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Deck(page);
|
||||
export const create = page => new Deck(page);
|
||||
|
||||
class Deck {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new DZSlides(page);
|
||||
export const create = page => new DZSlides(page);
|
||||
|
||||
class DZSlides {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Flowtime(page);
|
||||
export const create = page => new Flowtime(page);
|
||||
|
||||
class Flowtime {
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
// and detects changes to the DOM. The deck is considered over when no change
|
||||
// is detected afterward.
|
||||
|
||||
const { pause } = require('../libs/util');
|
||||
import { pause } from '../libs/util.js';
|
||||
|
||||
exports.options = {
|
||||
export const options = {
|
||||
key : {
|
||||
default : 'ArrowRight',
|
||||
metavar : '<key>',
|
||||
@ -23,15 +23,15 @@ exports.options = {
|
||||
},
|
||||
};
|
||||
|
||||
exports.help =
|
||||
export const help =
|
||||
`Emulates the end-user interaction by pressing the key with the specified --key option
|
||||
and iterates over the presentation as long as:
|
||||
- Any change to the DOM is detected by observing mutation events targeting the body element
|
||||
and its subtree,
|
||||
- Nor the number of slides exported has reached the specified --max-slides option.
|
||||
The --key option must be one of the 'KeyboardEvent' keys and defaults to [${exports.options.key.default}].`;
|
||||
The --key option must be one of the 'KeyboardEvent' keys and defaults to [${options.key.default}].`;
|
||||
|
||||
exports.create = (page, options) => new Generic(page, options);
|
||||
export const create = (page, options) => new Generic(page, options);
|
||||
|
||||
class Generic {
|
||||
constructor(page, options) {
|
||||
@ -39,8 +39,8 @@ class Generic {
|
||||
this.options = options;
|
||||
this.currentSlide = 1;
|
||||
this.isNextSlideDetected = false;
|
||||
this.key = this.options.key || exports.options.key.default;
|
||||
this.media = this.options.media || exports.options.media.default;
|
||||
this.key = this.options.key || options.key.default;
|
||||
this.media = this.options.media || options.media.default;
|
||||
}
|
||||
|
||||
getName() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Impress(page);
|
||||
export const create = page => new Impress(page);
|
||||
|
||||
class Impress {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Inspire(page);
|
||||
export const create = page => new Inspire(page);
|
||||
|
||||
class Inspire {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new NueDeck(page);
|
||||
export const create = page => new NueDeck(page);
|
||||
|
||||
class NueDeck {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Remark(page);
|
||||
export const create = page => new Remark(page);
|
||||
|
||||
class Remark {
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const URI = require('urijs');
|
||||
import URI from 'urijs';
|
||||
|
||||
exports.create = page => new Reveal(page);
|
||||
export const create = page => new Reveal(page);
|
||||
|
||||
class Reveal {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new RISE(page);
|
||||
export const create = page => new RISE(page);
|
||||
|
||||
class RISE {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Shower(page);
|
||||
export const create = page => new Shower(page);
|
||||
|
||||
class Shower {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Shower(page);
|
||||
export const create = page => new Shower(page);
|
||||
|
||||
class Shower {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new Slidy(page);
|
||||
export const create = page => new Slidy(page);
|
||||
|
||||
class Slidy {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
exports.create = page => new WebSlides(page);
|
||||
export const create = page => new WebSlides(page);
|
||||
|
||||
class WebSlides {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user