Skip to content

Commit

Permalink
Merge pull request #85 from mtmacdonald/refactor-pdf
Browse files Browse the repository at this point in the history
Refactor PDF code
  • Loading branch information
mtmacdonald committed Oct 24, 2023
2 parents bc3abac + 3ab221a commit c01e3c7
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 154 deletions.
162 changes: 8 additions & 154 deletions src/docgen/docgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ import chalk from 'chalk';
import path from 'path';
import cheerio from 'cheerio';
import moment from 'moment';
import { spawn, exec } from 'child_process';
import spawnArgs from 'spawn-args';
import MarkdownIt from 'markdown-it';
import imageSizeOf from 'image-size';
import { Spinner as cliSpinner } from 'cli-spinner';
import {
readFile,
writeFile,
copyDirectory,
removeDirectory,
cleanDirectory,
makeDirectory,
} from './fs/fs';
import { validateJSON } from './validation/validation';
import { checkPdfVersion, generatePdf } from './pdf/wkhtmltopdf/wkhtmltopdf';
import { version } from '../../package.json';

const markdown = new MarkdownIt('commonmark').enable('table');
Expand All @@ -29,7 +26,6 @@ markdown.validateLink = () => {

export function DocGen(process) {
let mainProcess = process;
let wkhtmltopdfVersion = 'wkhtmltopdf 0.12.6 (with patched qt)'; //output from wkhtmltopdf -V
let options;
let templates = {};
let meta = {};
Expand Down Expand Up @@ -78,7 +74,13 @@ export function DocGen(process) {
await loadMarkdown();
await processContent();
await writePages();
await checkPdfVersion();
await createRedirect();
if (options.pdf === true) {
await checkPdfVersion({ options, mainProcess });
await generatePdf({ options, meta, sortedPages, mainProcess });
} else {
console.log(chalk.green.bold('Done!'));
}
};

/*
Expand Down Expand Up @@ -624,141 +626,6 @@ export function DocGen(process) {
}
};

/*
wkthmltopdf options
*/

let pdfOptions = [
' --zoom 1.0',
' --image-quality 100',
' --print-media-type',
' --orientation portrait',
' --page-size A4',
' --margin-top 25',
' --margin-right 15',
' --margin-bottom 16',
' --margin-left 15',
' --header-spacing 5',
' --footer-spacing 5',
' --no-stop-slow-scripts',
];

let getPdfArguments = () => {
let pdfName = meta.parameters.name.toLowerCase() + '.pdf';
pdfOptions.push(' --enable-local-file-access');
pdfOptions.push(' --javascript-delay ' + options.pdfDelay); //code syntax highlight in wkhtmltopdf 0.12.2.1 fails without a delay (but why doesn't --no-stop-slow-scripts work?)
pdfOptions.push(
' --user-style-sheet ' + __dirname + '/../include/pdf-stylesheet.css',
);
pdfOptions.push(' --header-html ' + options.output + 'temp/pdfHeader.html');
pdfOptions.push(' --footer-html ' + options.output + 'temp/pdfFooter.html');
pdfOptions.push(' cover ' + options.output + 'temp/pdfCover.html');
pdfOptions.push(
' toc --xsl-style-sheet ' + __dirname + '/../include/pdf-contents.xsl',
);
let allPages = '';
for (let key in sortedPages) {
if (sortedPages.hasOwnProperty(key)) {
sortedPages[key].forEach((section) => {
section.pages.forEach((page) => {
let key = page.source;
let name = key.substr(0, page.source.lastIndexOf('.'));
let path = options.output + name + '.html';
allPages += ' ' + path;
});
});
}
}
let args = pdfOptions.join('');
args += allPages;
args += ' ' + options.output + pdfName;
return spawnArgs(args);
};

let checkPdfVersion = async () => {
if (options.pdf === true) {
//first check that wkhtmltopdf is installed
exec(options.wkhtmltopdfPath + ' -V', (error, stdout, stderr) => {
if (error) {
console.log(
chalk.red(
'Unable to call wkhtmltopdf. Is it installed and in path? See http://wkhtmltopdf.org',
),
);
if (options.verbose === true) {
console.log(chalk.red(error));
}
mainProcess.exit(1);
} else {
//warn if the version of wkhtmltopdf is not an expected version
let actualWkhtmltopdfVersion = stdout.trim();
if (actualWkhtmltopdfVersion !== wkhtmltopdfVersion) {
let warning =
'Warning: unexpected version of wkhtmltopdf, which may work but is not tested or supported';
let expectedVersion = ' expected version: ' + wkhtmltopdfVersion;
let detectedVersion =
' detected version: ' + actualWkhtmltopdfVersion;
console.log(chalk.yellow(warning));
console.log(chalk.yellow(expectedVersion));
console.log(chalk.yellow(detectedVersion));
}
generatePdf();
}
});
} else {
await cleanUp();
}
};

/*
call wkhtmltopdf as an external executable
*/

let generatePdf = async () => {
console.log(chalk.green('Creating the PDF copy (may take some time)'));
let args = getPdfArguments();
let wkhtmltopdf = spawn(options.wkhtmltopdfPath, args);
let spinner = new cliSpinner(chalk.green(' Processing... %s'));
spinner.setSpinnerString('|/-\\');

wkhtmltopdf.on('error', (error) => {
console.log(chalk.red('Error calling wkhtmltopdf to generate the PDF'));
if (options.verbose === true) {
console.log(chalk.red(error));
}
});

if (options.verbose !== true) {
spinner.start(); //only show spinner when verbose is off (otherwise show raw wkhtmltopdf output)
} else {
//pipe the output from wkhtmltopdf back to stdout
//however, wkhtmltpdf outputs to stderr, not stdout. See:
//https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1980
wkhtmltopdf.stderr.pipe(mainProcess.stdout);
}

wkhtmltopdf.stdout.on('data', (data) => {
//do nothing
});

wkhtmltopdf.stderr.on('data', (data) => {
//do nothing
});

wkhtmltopdf.on('close', async (code) => {
if (options.verbose !== true) {
spinner.stop();
console.log(''); //newline after spinner stops
}
if (code !== 0) {
let warning =
'wkhtmltopdf exited with a warning or error: try the -v option for details';
console.log(chalk.yellow(warning));
}
await cleanUp();
});
};

let createRedirect = async () => {
if (options.redirect) {
let parent = options.output.replace(/\/$/, ''); //trim any trailing slash
Expand All @@ -782,17 +649,4 @@ export function DocGen(process) {
}
}
};

/*
cleanup
*/

let cleanUp = async () => {
await createRedirect();
//remove temp files
if (options.pdf === true) {
await removeDirectory(options.output + 'temp');
}
console.log(chalk.green.bold('Done!'));
};
}
12 changes: 12 additions & 0 deletions src/docgen/execute/execute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { promisify } from 'util';
import { exec } from 'child_process';

const execPromise = promisify(exec);
export async function execute(command) {
try {
const { stdout, stderr } = await execPromise(command);
return {stdout, stderr}
} catch (e) {
console.error(e);
}
}
133 changes: 133 additions & 0 deletions src/docgen/pdf/wkhtmltopdf/wkhtmltopdf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import chalk from 'chalk';
import { Spinner as cliSpinner } from 'cli-spinner';
import { execute } from "../../execute/execute";
import { spawn } from "child_process";
import { removeDirectory } from "../../fs/fs";
import spawnArgs from 'spawn-args';

const wkhtmltopdfVersion = 'wkhtmltopdf 0.12.6 (with patched qt)'; //output from wkhtmltopdf -V

const pdfOptions = [
' --zoom 1.0',
' --image-quality 100',
' --print-media-type',
' --orientation portrait',
' --page-size A4',
' --margin-top 25',
' --margin-right 15',
' --margin-bottom 16',
' --margin-left 15',
' --header-spacing 5',
' --footer-spacing 5',
' --no-stop-slow-scripts',
];

const getPdfArguments = ({meta, options, sortedPages}) => {
let pdfName = meta.parameters.name.toLowerCase() + '.pdf';
pdfOptions.push(' --enable-local-file-access');
pdfOptions.push(' --javascript-delay ' + options.pdfDelay); //code syntax highlight in wkhtmltopdf 0.12.2.1 fails without a delay (but why doesn't --no-stop-slow-scripts work?)
pdfOptions.push(
' --user-style-sheet ' + __dirname + '/../../../include/pdf-stylesheet.css',
);
pdfOptions.push(' --header-html ' + options.output + 'temp/pdfHeader.html');
pdfOptions.push(' --footer-html ' + options.output + 'temp/pdfFooter.html');
pdfOptions.push(' cover ' + options.output + 'temp/pdfCover.html');
pdfOptions.push(
' toc --xsl-style-sheet ' + __dirname + '/../../../include/pdf-contents.xsl',
);
let allPages = '';
for (let key in sortedPages) {
if (sortedPages.hasOwnProperty(key)) {
sortedPages[key].forEach((section) => {
section.pages.forEach((page) => {
let key = page.source;
let name = key.substr(0, page.source.lastIndexOf('.'));
let path = options.output + name + '.html';
allPages += ' ' + path;
});
});
}
}
let args = pdfOptions.join('');
args += allPages;
args += ' ' + options.output + pdfName;
return spawnArgs(args);
};

export const checkPdfVersion = async ({options, mainProcess}) => {
try {
const {stdout} = await execute(options.wkhtmltopdfPath + ' -V');
//warn if the version of wkhtmltopdf is not an expected version
const actualWkhtmltopdfVersion = stdout.trim();
if (actualWkhtmltopdfVersion !== wkhtmltopdfVersion) {
const warning =
'Warning: unexpected version of wkhtmltopdf, which may work but is not tested or supported';
const expectedVersion = ' expected version: ' + wkhtmltopdfVersion;
const detectedVersion =
' detected version: ' + actualWkhtmltopdfVersion;
console.log(chalk.yellow(warning));
console.log(chalk.yellow(expectedVersion));
console.log(chalk.yellow(detectedVersion));
}
} catch (error) {
console.log(
chalk.red(
'Unable to call wkhtmltopdf. Is it installed and in path? See http://wkhtmltopdf.org',
),
);
if (options.verbose === true) {
console.log(chalk.red(error));
}
mainProcess.exit(1);
}
};

/*
call wkhtmltopdf as an external executable
*/

export const generatePdf = async ({options, meta, sortedPages, mainProcess}) => {
console.log(chalk.green('Creating the PDF copy (may take some time)'));
let args = getPdfArguments({options, meta, sortedPages});
let wkhtmltopdf = spawn(options.wkhtmltopdfPath, args);
let spinner = new cliSpinner(chalk.green(' Processing... %s'));
spinner.setSpinnerString('|/-\\');

wkhtmltopdf.on('error', (error) => {
console.log(chalk.red('Error calling wkhtmltopdf to generate the PDF'));
if (options.verbose === true) {
console.log(chalk.red(error));
}
});

if (options.verbose !== true) {
spinner.start(); //only show spinner when verbose is off (otherwise show raw wkhtmltopdf output)
} else {
//pipe the output from wkhtmltopdf back to stdout
//however, wkhtmltpdf outputs to stderr, not stdout. See:
//https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1980
wkhtmltopdf.stderr.pipe(mainProcess.stdout);
}

wkhtmltopdf.stdout.on('data', (data) => {
//do nothing
});

wkhtmltopdf.stderr.on('data', (data) => {
//do nothing
});

wkhtmltopdf.on('close', async (code) => {
if (options.verbose !== true) {
spinner.stop();
console.log(''); //newline after spinner stops
}
if (code !== 0) {
let warning =
'wkhtmltopdf exited with a warning or error: try the -v option for details';
console.log(chalk.yellow(warning));
}
await removeDirectory(options.output + 'temp', options.verbose);
console.log(chalk.green.bold('Done!'));
});
};

0 comments on commit c01e3c7

Please sign in to comment.