Skip to content

Commit c19a2c7

Browse files
authored
Merge pull request #6483 from EnterpriseDB/bugfix/josh/pdf-svg-compat
PDF rendering misc bugs
2 parents a50b521 + 3356292 commit c19a2c7

File tree

5 files changed

+481
-12
lines changed

5 files changed

+481
-12
lines changed

scripts/pdf/cleanup_combined_markdown.mjs

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import GithubSlugger from "github-slugger";
1414
import yaml from "js-yaml";
1515
import { visitParents } from "unist-util-visit-parents";
1616
import remarkMdxEmbeddedHast from "./lib/mdast-embedded-hast.mjs";
17+
import rehypeParse from "rehype-parse";
18+
import fs from 'node:fs/promises';
19+
import { optimize } from 'svgo';
1720
import toVfile from "to-vfile";
1821
const { read, write } = toVfile;
1922

@@ -29,6 +32,8 @@ const formatErrorPath = (path, line, column) => {
2932
: `${path}:${line}:${column}`;
3033
};
3134

35+
const imageExts = [".png", ".svg", ".jpg", ".jpeg", ".gif"];
36+
3237
(async () => {
3338
if (!process.argv[2]) {
3439
console.log(`usage:
@@ -69,7 +74,7 @@ function cleanup() {
6974
const originalRE =
7075
/<span data-original-path='(?<originalPath>.+?):(?<originalStartLine>\d+)'><\/span>/;
7176
const docsUrl = "https://www.enterprisedb.com/docs";
72-
return (tree, file) => {
77+
return async (tree, file) => {
7378
const docsLocations = /product_docs\/docs|advocacy_docs/;
7479
const thisProductPath = path.dirname(file.path).split(docsLocations)[1];
7580
const isVersioned = file.path.includes("product_docs/")
@@ -92,6 +97,10 @@ function cleanup() {
9297
const slugger = new GithubSlugger();
9398

9499
const getSlugForOrigPathAndSlug = (origPath, origSlug) => {
100+
const ext = path.posix.extname(origPath);
101+
const isImageUrl = imageExts.includes(ext);
102+
if (isImageUrl) return "";
103+
95104
const normalizedPath = origPath
96105
.replace(/\/?$/, "")
97106
.replace(/\.mdx?$/, "");
@@ -144,7 +153,7 @@ function cleanup() {
144153

145154
visitParents(
146155
tree,
147-
["jsx-hast", "element", "link", "heading", "code", "admonition", "table"],
156+
["jsx-hast", "element", "link", "image", "heading", "code", "admonition", "table"],
148157
(node, ancestors) => {
149158
if (node.type === "jsx-hast") {
150159
let { originalPath, originalStartLine } =
@@ -218,7 +227,14 @@ function cleanup() {
218227
) {
219228
node.properties.href = normalizeUrl(node.properties.href);
220229
}
221-
if (node.type === "link") {
230+
else if (
231+
node.type === "element" &&
232+
node.tagName === "img" &&
233+
node.properties.src
234+
) {
235+
node.properties.src = normalizeUrl(node.properties.src);
236+
}
237+
if (node.type === "link" || node.type === "image") {
222238
node.url = normalizeUrl(node.url);
223239
}
224240

@@ -234,7 +250,8 @@ function cleanup() {
234250
const siblings = ancestors[ancestors.length - 1].children;
235251
const idx = siblings.indexOf(node);
236252
siblings.splice(idx + 1, 0, { type: "code", lang: "output", value: output });
237-
return idx + 2;
253+
siblings.splice(idx, 0, { type: "jsx", value: '<div class="next-pre-has-output"></div>' });
254+
return idx + 3;
238255
}
239256
}
240257

@@ -307,8 +324,19 @@ function cleanup() {
307324
slugMap[""] = "";
308325
}
309326

327+
function urlToFileUrl(url)
328+
{
329+
if (
330+
url.startsWith(thisProductUrl)
331+
) {
332+
const dest = new URL(url.replace(thisProductUrl+'/', ''), `file://${path.dirname(path.resolve(file.path))}/`);
333+
return dest.toString();
334+
}
335+
}
336+
337+
let svgNodes = [];
310338
let lineOffset = 0;
311-
visitParents(tree, ["link", "element", "heading"], (node) => {
339+
visitParents(tree, ["link", "image", "element", "heading"], (node) => {
312340
try {
313341
if (node.type === "heading" && node.data?.originalFile) {
314342
currentOriginalFile = node.data.originalFile;
@@ -322,7 +350,21 @@ function cleanup() {
322350
node.properties.href
323351
)
324352
node.properties.href = mapUrlToSlug(node.properties.href);
353+
else if (
354+
node.type === "element" &&
355+
node.tagName === "img" &&
356+
node.properties.src
357+
) {
358+
node.properties.src = urlToFileUrl(node.properties.src);
359+
if (node.properties.src.endsWith(".svg"))
360+
svgNodes.push(node);
361+
}
325362
else if (node.type === "link") node.url = mapUrlToSlug(node.url);
363+
else if (node.type === "image") {
364+
node.url = urlToFileUrl(node.url);
365+
if (node.url.endsWith(".svg"))
366+
svgNodes.push(node);
367+
}
326368
} catch (e) {
327369
console.log(
328370
`${e.severity === 1 ? "⚠️⚠️ " : "⚠️ "} ${formatErrorPath(
@@ -334,5 +376,54 @@ function cleanup() {
334376
);
335377
}
336378
});
379+
380+
for (let node of svgNodes) {
381+
await convertSvgNode(node);
382+
}
337383
};
338384
}
385+
386+
const svgoPlugins = [
387+
{
388+
name: 'preset-default',
389+
params: {
390+
overrides: {
391+
// disable plugins
392+
removeTitle: false,
393+
removeDesc: false,
394+
},
395+
},
396+
},
397+
'removeXMLNS',
398+
];
399+
400+
// adapted from https://github.com/alvinometric/remark-inline-svg to suit the specific needs of this pipeline
401+
async function convertSvgNode(node) {
402+
const url = new URL(node.url || node.properties.src);
403+
const image = await fs.readFile(url.pathname, 'utf-8');
404+
const result = optimize(image, { path: url.pathname, multipass: true, plugins: svgoPlugins });
405+
406+
if (node.properties.src) {
407+
let hast = unified()
408+
.use(rehypeParse, {
409+
emitParseErrors: true,
410+
verbose: true,
411+
fragment: true,
412+
})
413+
.parse(result.data);
414+
node.tagName = "figure";
415+
let style = node.properties.style;
416+
if (node.properties.width)
417+
style = `${style ? style + '; ' : ''}width: ${node.properties.width}`;
418+
delete node.properties;
419+
if (style)
420+
node.properties = {style};
421+
node.children = hast.children;
422+
}
423+
else {
424+
node.type = "jsx";
425+
node.value = result.data;
426+
delete node.url;
427+
}
428+
429+
}

scripts/pdf/generate_pdf.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,9 @@ def main(args):
5555
"pandoc",
5656
mdx_file,
5757
"--from=gfm",
58-
"--self-contained",
5958
"--highlight-style=tango",
6059
f"--include-in-header={BASE_DIR / 'pdf-script.html'}",
61-
f"--css={BASE_DIR / 'pdf-styles.css'}",
60+
f"--css=file://{BASE_DIR / 'pdf-styles.css'}",
6261
f"--resource-path={':'.join((str(p) for p in resource_search_paths))}",
6362
f"--output={html_file}",
6463
]
@@ -109,6 +108,7 @@ def main(args):
109108
"wkhtmltopdf",
110109
"--log-level",
111110
"error",
111+
"--enable-local-file-access",
112112
"--title",
113113
title,
114114
"--margin-top",
@@ -133,6 +133,12 @@ def main(args):
133133
if args.open_pdf:
134134
run(["open", pdf_file])
135135

136+
except:
137+
# if ANY exception is thrown, treat (likely incomplete) output as suspect and remove
138+
# don't remove HTML files, since likely we'll want those for debugging if --html is specified
139+
pdf_file.unlink(missing_ok=True)
140+
pdf_hash_file.unlink(missing_ok=True)
141+
raise
136142
finally:
137143
mdx_file.unlink(missing_ok=True)
138144
if not args.generate_html_only:

0 commit comments

Comments
 (0)