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
+ }
0 commit comments