本文已参与「新人创作礼」活动,一起开启掘金创作之路。
相关工程部署可参考
先了解下docsify生成pdf的原理,前面文章也有提到过:
docsify现将md碎片进行合并生成一个完整的md文档,再将完整的md交给docsify-server,生成在线的html网页,再由puppeteer启动一个虚拟的chrome浏览器,根据我们给pdf设置的尺寸进行一张一张截取,从而生成pdf。
要想为pdf注入css脚本,其实就是在这个虚拟浏览器对网页进行截取前,为网页注入css脚本,这就必然要使用到浏览器爬虫puppeteer的api。
原理和思路都很简单,直接上代码了:
-
style.css 新增文件 被注入的css脚本;
-
render.js 源文件 做了部分调整,已添加中文注释;
style.css
html {
counter-reset: chapter;
}
ul {
margin: 0 0 !important;
margin-top: -10px !important;
}
li>ul {
margin-top: 0px !important;
}
blockquote {
margin: 0 0 !important;
margin-top: -5px !important;
padding: 5px !important;
}
li blockquote {
margin-top: 10px !important;
padding-top: 10px !important;
}
a {
color: #20435C !important;
}
p,
li {
line-height: 20px !important;
margin-top: 5px !important;
}
h1 {
page-break-before: always;
margin-bottom: 50px !important;
}
h1:before {
counter-increment: chapter;
content: "Chapter "counter(chapter)"\A\A";
white-space: pre;
color: #20435C;
}
h2,
h3,
h4,
h5,
h6 {
margin: 10px 0 !important;
border-bottom: 0 !important;
padding: 0px !important;
}
#mulu {
font-size: 40px !important;
margin-bottom: 30px !important;
display: inline-block !important;
}
render.js
const path = require("path");
const fs = require("fs");
const puppeteer = require("puppeteer");
const logger = require("./logger.js");
const runSandboxScript = require("./run-sandbox-script.js");
const merge = require("easy-pdf-merge");
const renderPdf = async ({
mainMdFilename,
pathToStatic,
pathToPublic,
pdfOptions,
docsifyRendererPort,
emulateMedia,
facebookName,
facebook,
}) => {
const browser = await puppeteer.launch({
defaultViewport: {
width: 1200,
height: 1000,
},
});
try {
const mainMdFilenameWithoutExt = path.parse(mainMdFilename).name;
const docsifyUrl = `http://localhost:${docsifyRendererPort}/#/${pathToStatic}/${mainMdFilenameWithoutExt}`;
let pdfUrls = []
const page = await browser.newPage();
// 生成封面pdf
if (facebook) {
await page.goto(`http://localhost:${docsifyRendererPort}/${facebookName}`, { waitUntil: "networkidle0" });
await page.emulateMedia(emulateMedia);
await page.pdf({
...{
...pdfOptions,
margin: {
top: '0px',
right: '0px',
bottom: '0px',
left: '0px'
},
},
path: path.resolve(pathToStatic + "/facebook.pdf"),
});
pdfUrls.push(pathToStatic + '/facebook.pdf')
}
// 生成内容pdf
await page.goto(docsifyUrl, { waitUntil: "networkidle0" });
await page.addStyleTag({ path: 'style.css' })
const renderProcessingErrors = await runSandboxScript(page, {
mainMdFilenameWithoutExt,
pathToStatic,
});
if (renderProcessingErrors.length)
logger.warn("anchors processing errors", renderProcessingErrors);
await page.emulateMedia(emulateMedia);
//隐藏下载btn
await page.$eval('.download', function (el, value) {
return el.setAttribute('style', value)
}, 'display: none')
await page.pdf({
...pdfOptions,
path: path.resolve(pathToStatic + '/' + (facebook ? 'content.pdf' : pathToPublic)),
});
pdfUrls.push(pathToStatic + '/' + (facebook ? 'content.pdf' : pathToPublic))
await browser.close();
if (facebook) {
await mergeMultiplePDF(pdfUrls, pathToStatic + '/' + pathToPublic);
pdfUrls.map(url => fs.unlinkSync(url))
}
} catch (e) {
await browser.close();
throw e;
}
};
const mergeMultiplePDF = (pdfFiles, pdfPath) => {
return new Promise((resolve, reject) => {
merge(pdfFiles, pdfPath, async (err) => {
if (err) {
console.log(err);
reject(err)
}
console.log('PDF Merge Success!');
resolve()
});
});
};
const htmlToPdf = ({
mainMdFilename,
pathToStatic,
pathToPublic,
pdfOptions,
removeTemp,
docsifyRendererPort,
emulateMedia,
facebookName,
facebook,
}) => async () => {
const { closeProcess } = require("./utils.js")({ pathToStatic, removeTemp });
try {
return await renderPdf({
mainMdFilename,
pathToStatic,
pathToPublic,
pdfOptions,
docsifyRendererPort,
emulateMedia,
facebookName,
facebook,
});
} catch (err) {
logger.err("puppeteer renderer error:", err);
await closeProcess(1);
}
};
module.exports = config => ({
htmlToPdf: htmlToPdf(config),
});
不到之处,烦请指正~