CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/jupyter/convert/html-to-pdf.ts
Views: 687
1
/*
2
Note that this doesn't actually use upstream nbconvert itself at all!
3
4
- pdf: takes html, then uses headless chrome via
5
chromium-browser or google-chrome, if installed to convert to pdf
6
7
NOTE: Firefox does *not* support printing to pdf in headless mode according to
8
https://stackoverflow.com/questions/48358556/firefox-headless-print-to-pdf-option
9
*/
10
11
import which from "which";
12
import { join, parse } from "path";
13
import { executeCode } from "@cocalc/backend/execute-code";
14
import { getLogger } from "@cocalc/project/logger";
15
16
const log = getLogger("jupyter:html-to-pdf");
17
18
// time google-chrome --headless --disable-gpu --no-sandbox --print-to-pdf=a.pdf --run-all-compositor-stages-before-draw --virtual-time-budget=10000 --disable-dev-shm-usage --disable-setuid-sandbox a.html
19
20
export default async function htmlToPDF(
21
path: string,
22
timeout: number = 30
23
): Promise<string> {
24
const { dir, name } = parse(path);
25
const outfile = join(dir, name + ".pdf");
26
27
const command = await getCommand();
28
const args = [
29
"--headless",
30
"--disable-gpu",
31
"--no-sandbox",
32
`--print-to-pdf=${outfile}`,
33
"--run-all-compositor-stages-before-draw",
34
// I added --disable-dev-shm-usage --disable-setuid-sandbox because printing large complicated documents was failing,
35
// and GPT-4 suggested these options. There are security implications, but that is OK given the application.
36
"--disable-dev-shm-usage",
37
"--disable-setuid-sandbox",
38
`--virtual-time-budget=${timeout * 1000}`,
39
path,
40
];
41
log.debug(`htmlToPDF: ${command} ${args.join(" ")}`);
42
const output = await executeCode({
43
command,
44
args,
45
err_on_exit: false,
46
timeout,
47
ulimit_timeout: true,
48
bash: true,
49
});
50
if (output.exit_code != 0) {
51
throw Error(output.stderr);
52
}
53
54
return outfile;
55
}
56
57
const COMMANDS = ["google-chrome", "chromium-browser"];
58
59
let cache: string = "";
60
async function getCommand(): Promise<string> {
61
if (cache) return cache;
62
for (const cmd of COMMANDS) {
63
try {
64
await which(cmd);
65
cache = cmd;
66
return cmd;
67
} catch (_err) {}
68
}
69
throw Error(
70
`one of ${COMMANDS.join(" or ")} must be installed to convert to PDF`
71
);
72
}
73
74