Skip to content

Commit 091f557

Browse files
CopilotYukaii
andcommitted
Replace markdown-pdf with puppeteer for PDF generation
Co-authored-by: Yukaii <4230968+Yukaii@users.noreply.github.com>
1 parent b27e41a commit 091f557

File tree

3 files changed

+175
-8
lines changed

3 files changed

+175
-8
lines changed

lib/note/noteActions.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
const fs = require('fs')
44
const path = require('path')
5-
const markdownpdf = require('markdown-pdf')
65
const shortId = require('shortid')
76
const querystring = require('querystring')
87
const moment = require('moment')
98
const { Pandoc } = require('@hackmd/pandoc.js')
9+
const { convertMarkdownToPDF } = require('../utils/markdown-to-pdf')
1010

1111
const config = require('../config')
1212
const logger = require('../logger')
@@ -64,7 +64,7 @@ function actionInfo (req, res, note) {
6464
res.send(data)
6565
}
6666

67-
function actionPDF (req, res, note) {
67+
async function actionPDF (req, res, note) {
6868
const url = config.serverURL || 'http://' + req.get('host')
6969
const body = note.content
7070
const extracted = Note.extractMeta(body)
@@ -78,14 +78,17 @@ function actionPDF (req, res, note) {
7878
}
7979
const pdfPath = config.tmpPath + '/' + Date.now() + '.pdf'
8080
content = content.replace(/\]\(\//g, '](' + url + '/')
81-
const markdownpdfOptions = {
82-
highlightCssPath: highlightCssPath
83-
}
84-
markdownpdf(markdownpdfOptions).from.string(content).to(pdfPath, function () {
81+
82+
try {
83+
await convertMarkdownToPDF(content, pdfPath, {
84+
highlightCssPath: highlightCssPath
85+
})
86+
8587
if (!fs.existsSync(pdfPath)) {
8688
logger.error('PDF seems to not be generated as expected. File doesn\'t exist: ' + pdfPath)
8789
return errorInternalError(req, res)
8890
}
91+
8992
const stream = fs.createReadStream(pdfPath)
9093
let filename = title
9194
// Be careful of special characters
@@ -100,7 +103,10 @@ function actionPDF (req, res, note) {
100103
fs.unlinkSync(pdfPath)
101104
})
102105
stream.pipe(res)
103-
})
106+
} catch (error) {
107+
logger.error('PDF generation failed:', error)
108+
return errorInternalError(req, res)
109+
}
104110
}
105111

106112
const outputFormats = {

lib/utils/markdown-to-pdf.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'use strict'
2+
3+
const puppeteer = require('puppeteer')
4+
const markdownit = require('markdown-it')
5+
const path = require('path')
6+
const fs = require('fs')
7+
8+
// Configure markdown-it similar to frontend
9+
function createMarkdownRenderer () {
10+
const md = markdownit('default', {
11+
html: true,
12+
breaks: true,
13+
linkify: true,
14+
typographer: true,
15+
highlight: function (str, lang) {
16+
if (lang && require('highlight.js').getLanguage(lang)) {
17+
try {
18+
return '<pre class="hljs"><code>' +
19+
require('highlight.js').highlight(lang, str, true).value +
20+
'</code></pre>'
21+
} catch (__) {}
22+
}
23+
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
24+
}
25+
})
26+
27+
// Add plugins commonly used in CodiMD
28+
md.use(require('markdown-it-abbr'))
29+
md.use(require('markdown-it-footnote'))
30+
md.use(require('markdown-it-deflist'))
31+
md.use(require('markdown-it-mark'))
32+
md.use(require('markdown-it-ins'))
33+
md.use(require('markdown-it-sub'))
34+
md.use(require('markdown-it-sup'))
35+
36+
return md
37+
}
38+
39+
async function convertMarkdownToPDF (markdown, outputPath, options = {}) {
40+
const md = createMarkdownRenderer()
41+
42+
// Convert markdown to HTML
43+
const htmlContent = md.render(markdown)
44+
45+
// Read highlight.js CSS
46+
const highlightCssPath = options.highlightCssPath || path.join(__dirname, '../../node_modules/highlight.js/styles/github-gist.css')
47+
let highlightCss = ''
48+
49+
if (fs.existsSync(highlightCssPath)) {
50+
highlightCss = fs.readFileSync(highlightCssPath, 'utf8')
51+
}
52+
53+
// Create full HTML document
54+
const fullHtml = `
55+
<!DOCTYPE html>
56+
<html>
57+
<head>
58+
<meta charset="UTF-8">
59+
<title>PDF Export</title>
60+
<style>
61+
${highlightCss}
62+
63+
body {
64+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
65+
line-height: 1.6;
66+
color: #333;
67+
max-width: 800px;
68+
margin: 0 auto;
69+
padding: 20px;
70+
}
71+
72+
pre {
73+
background-color: #f6f8fa;
74+
border-radius: 6px;
75+
padding: 16px;
76+
overflow: auto;
77+
}
78+
79+
code {
80+
background-color: #f6f8fa;
81+
border-radius: 3px;
82+
padding: 2px 4px;
83+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
84+
}
85+
86+
pre code {
87+
background-color: transparent;
88+
padding: 0;
89+
}
90+
91+
h1, h2, h3, h4, h5, h6 {
92+
margin-top: 24px;
93+
margin-bottom: 16px;
94+
font-weight: 600;
95+
line-height: 1.25;
96+
}
97+
98+
blockquote {
99+
padding: 0 1em;
100+
color: #6a737d;
101+
border-left: 0.25em solid #dfe2e5;
102+
margin: 0;
103+
}
104+
105+
table {
106+
border-collapse: collapse;
107+
width: 100%;
108+
}
109+
110+
th, td {
111+
border: 1px solid #dfe2e5;
112+
padding: 6px 13px;
113+
}
114+
115+
th {
116+
background-color: #f6f8fa;
117+
font-weight: 600;
118+
}
119+
</style>
120+
</head>
121+
<body>
122+
${htmlContent}
123+
</body>
124+
</html>`
125+
126+
// Launch puppeteer and generate PDF
127+
let browser = null
128+
try {
129+
browser = await puppeteer.launch({
130+
headless: 'new',
131+
args: ['--no-sandbox', '--disable-setuid-sandbox']
132+
})
133+
134+
const page = await browser.newPage()
135+
await page.setContent(fullHtml, { waitUntil: 'networkidle0' })
136+
137+
await page.pdf({
138+
path: outputPath,
139+
format: 'A4',
140+
margin: {
141+
top: '20px',
142+
right: '20px',
143+
bottom: '20px',
144+
left: '20px'
145+
},
146+
printBackground: true
147+
})
148+
149+
return true
150+
} catch (error) {
151+
throw error
152+
} finally {
153+
if (browser) {
154+
await browser.close()
155+
}
156+
}
157+
}
158+
159+
module.exports = {
160+
convertMarkdownToPDF
161+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"lodash": "^4.17.21",
6666
"lutim": "~1.0.2",
6767
"markdown-it": "~10.0.0",
68-
"markdown-pdf": "~9.0.0",
68+
"puppeteer": "^21.0.0",
6969
"method-override": "~3.0.0",
7070
"minimist": "^1.2.8",
7171
"minio": "^7.1.1",

0 commit comments

Comments
 (0)