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