md-pdf-studio 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +208 -208
- package/bin/cli.js +186 -186
- package/package.json +61 -58
- package/src/browser/renderer.js +142 -142
- package/src/core/cover.js +40 -40
- package/src/core/frontmatter.js +54 -54
- package/src/core/index.js +80 -80
- package/src/core/parser.js +154 -163
- package/src/core/sanitizer.js +35 -35
- package/src/core/templates.js +192 -192
- package/src/core/themes/academic.js +20 -20
- package/src/core/themes/cover.js +14 -14
- package/src/core/themes/cyberpunk.js +21 -21
- package/src/core/themes/dark.js +21 -21
- package/src/core/themes/ebook.js +20 -20
- package/src/core/themes/github.js +21 -21
- package/src/core/themes/index.js +52 -52
- package/src/core/themes/minimal.js +20 -20
- package/src/core/themes/modern.js +34 -34
- package/src/core/toc.js +72 -72
- package/src/core/utils.js +85 -85
- package/src/index.js +2 -2
package/src/core/parser.js
CHANGED
|
@@ -1,163 +1,154 @@
|
|
|
1
|
-
const { marked } = require('marked');
|
|
2
|
-
const {
|
|
3
|
-
parseFrontMatter,
|
|
4
|
-
generateCoverPage,
|
|
5
|
-
preprocessMarkdown,
|
|
6
|
-
generateToc,
|
|
7
|
-
readCustomCss,
|
|
8
|
-
sanitizeHtml,
|
|
9
|
-
escapeHtml
|
|
10
|
-
} = require('./utils');
|
|
11
|
-
const { getHtmlTemplate } = require('./templates');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Builds marked custom renderer for diagrams, UI mockups, and headings
|
|
15
|
-
* @param {Map<string, string>} headingsMap
|
|
16
|
-
* @returns {marked.Renderer}
|
|
17
|
-
*/
|
|
18
|
-
function buildMarkedRenderer(headingsMap) {
|
|
19
|
-
const renderer = new marked.Renderer();
|
|
20
|
-
const originalCodeRenderer = renderer.code.bind(renderer);
|
|
21
|
-
|
|
22
|
-
renderer.code = function(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
bodyHtml
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
...mergedOptions,
|
|
156
|
-
customCss
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
module.exports = {
|
|
162
|
-
parseMarkdownToHtml
|
|
163
|
-
};
|
|
1
|
+
const { marked } = require('marked');
|
|
2
|
+
const {
|
|
3
|
+
parseFrontMatter,
|
|
4
|
+
generateCoverPage,
|
|
5
|
+
preprocessMarkdown,
|
|
6
|
+
generateToc,
|
|
7
|
+
readCustomCss,
|
|
8
|
+
sanitizeHtml,
|
|
9
|
+
escapeHtml
|
|
10
|
+
} = require('./utils');
|
|
11
|
+
const { getHtmlTemplate } = require('./templates');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds marked custom renderer for diagrams, UI mockups, and headings
|
|
15
|
+
* @param {Map<string, string>} headingsMap
|
|
16
|
+
* @returns {marked.Renderer}
|
|
17
|
+
*/
|
|
18
|
+
function buildMarkedRenderer(headingsMap) {
|
|
19
|
+
const renderer = new marked.Renderer();
|
|
20
|
+
const originalCodeRenderer = renderer.code.bind(renderer);
|
|
21
|
+
|
|
22
|
+
renderer.code = function(token) {
|
|
23
|
+
const text = token.text || '';
|
|
24
|
+
const lang = token.lang || '';
|
|
25
|
+
|
|
26
|
+
if (lang === 'mermaid') {
|
|
27
|
+
return `<div class="mermaid-container"><pre class="mermaid">${text}</pre></div>`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (text.includes('┌') && text.includes('└')) {
|
|
31
|
+
return `<div class="ui-mockup-container"><pre class="ui-mockup"><code>${escapeHtml(text)}</code></pre></div>`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return originalCodeRenderer.call(this, token);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
renderer.heading = function({ tokens, depth, text }) {
|
|
38
|
+
const slug = headingsMap.get(text.trim()) || `heading-${depth}`;
|
|
39
|
+
return `<h${depth} id="${slug}">${this.parser.parseInline(tokens)}</h${depth}>`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return renderer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wraps section headers with section-group divs for clean page-break handling
|
|
47
|
+
* @param {string} html
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
function wrapSectionGroups(html) {
|
|
51
|
+
let wrapped = html.replace(
|
|
52
|
+
/<h3>(.*?)<\/h3>\s*(<div class="ui-mockup-container">|<table|<div class="mermaid-container">|<ul|<ol)/g,
|
|
53
|
+
'<div class="section-group"><h3>$1</h3>$2'
|
|
54
|
+
);
|
|
55
|
+
return wrapped.replace(
|
|
56
|
+
/(<\/table>|<\/div>|<\/ul>|<\/ol>)\s*(?=<h[1-4]>|<hr|\$)/g,
|
|
57
|
+
'$1</div>'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Converts Markdown content into a fully styled HTML string
|
|
63
|
+
* @param {string} rawMarkdownContent
|
|
64
|
+
* @param {Object} options
|
|
65
|
+
* @returns {string} HTML string
|
|
66
|
+
*/
|
|
67
|
+
function parseMarkdownToHtml(rawMarkdownContent, options = {}) {
|
|
68
|
+
// 1. Extract Front Matter & Metadata
|
|
69
|
+
const { data: frontMatter, content: markdownContent } = parseFrontMatter(rawMarkdownContent);
|
|
70
|
+
const titleMatch = markdownContent.match(/^#\s+(.+)$/m);
|
|
71
|
+
const title = frontMatter.title || options.title || (titleMatch ? titleMatch[1].trim() : 'Converted Document');
|
|
72
|
+
const subtitle = frontMatter.subtitle || options.subtitle || '';
|
|
73
|
+
const author = frontMatter.author || options.author || '';
|
|
74
|
+
const date = frontMatter.date || options.date || '';
|
|
75
|
+
|
|
76
|
+
// 2. Merge options
|
|
77
|
+
const mergedOptions = {
|
|
78
|
+
theme: options.theme || frontMatter.theme || 'modern',
|
|
79
|
+
font: options.font || frontMatter.font || 'Inter',
|
|
80
|
+
pageSize: options.pageSize || frontMatter.pageSize || 'A4',
|
|
81
|
+
orientation: options.orientation || frontMatter.orientation || (frontMatter.landscape ? 'landscape' : 'portrait'),
|
|
82
|
+
margin: options.margin || frontMatter.margin || '14mm 12mm 16mm 12mm',
|
|
83
|
+
toc: options.toc !== undefined ? options.toc : (frontMatter.toc || false),
|
|
84
|
+
cover: options.cover !== undefined ? options.cover : (frontMatter.cover || false),
|
|
85
|
+
header: options.header !== undefined ? options.header : (frontMatter.header || true),
|
|
86
|
+
footer: options.footer !== undefined ? options.footer : (frontMatter.footer || true),
|
|
87
|
+
mermaid: options.mermaid !== undefined ? options.mermaid : (frontMatter.mermaid !== false),
|
|
88
|
+
katex: options.katex !== undefined ? options.katex : (frontMatter.katex !== false),
|
|
89
|
+
customCss: options.customCss || '',
|
|
90
|
+
cssFile: options.cssFile || frontMatter.cssFile || frontMatter.css
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 3. Preprocess Markdown (LaTeX, pagebreaks, alerts)
|
|
94
|
+
const processedMd = preprocessMarkdown(markdownContent);
|
|
95
|
+
|
|
96
|
+
// 4. Generate TOC & Cover Page
|
|
97
|
+
let tocHtml = '';
|
|
98
|
+
let headingsMap = new Map();
|
|
99
|
+
if (mergedOptions.toc) {
|
|
100
|
+
const tocResult = generateToc(processedMd);
|
|
101
|
+
tocHtml = tocResult.tocHtml;
|
|
102
|
+
headingsMap = tocResult.headingsMap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const coverHtml = generateCoverPage({
|
|
106
|
+
title,
|
|
107
|
+
subtitle,
|
|
108
|
+
author,
|
|
109
|
+
date,
|
|
110
|
+
cover: mergedOptions.cover
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// 5. Read custom CSS file if specified
|
|
114
|
+
let customCss = mergedOptions.customCss || '';
|
|
115
|
+
if (mergedOptions.cssFile) {
|
|
116
|
+
customCss += '\n' + readCustomCss(mergedOptions.cssFile);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 6. Parse Markdown using custom renderer
|
|
120
|
+
marked.setOptions({ gfm: true, breaks: true });
|
|
121
|
+
const renderer = buildMarkedRenderer(headingsMap);
|
|
122
|
+
marked.use({ renderer });
|
|
123
|
+
|
|
124
|
+
let bodyHtml = marked.parse(processedMd);
|
|
125
|
+
bodyHtml = sanitizeHtml(bodyHtml);
|
|
126
|
+
|
|
127
|
+
// 7. Inject TOC, Cover Page & Section Groups
|
|
128
|
+
if (tocHtml) {
|
|
129
|
+
bodyHtml = bodyHtml.includes('</h1>')
|
|
130
|
+
? bodyHtml.replace('</h1>', '</h1>\n' + tocHtml)
|
|
131
|
+
: tocHtml + bodyHtml;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (coverHtml) {
|
|
135
|
+
bodyHtml = coverHtml + bodyHtml;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
bodyHtml = wrapSectionGroups(bodyHtml);
|
|
139
|
+
|
|
140
|
+
// 8. Generate and return full HTML template
|
|
141
|
+
return getHtmlTemplate({
|
|
142
|
+
title,
|
|
143
|
+
author,
|
|
144
|
+
bodyHtml,
|
|
145
|
+
options: {
|
|
146
|
+
...mergedOptions,
|
|
147
|
+
customCss
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
parseMarkdownToHtml
|
|
154
|
+
};
|
package/src/core/sanitizer.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML Sanitizer & Escaping Utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Sanitizes HTML content by stripping dangerous inline script tags and inline handlers
|
|
7
|
-
* @param {string} html
|
|
8
|
-
* @returns {string} Clean HTML string
|
|
9
|
-
*/
|
|
10
|
-
function sanitizeHtml(html) {
|
|
11
|
-
if (!html) return '';
|
|
12
|
-
return html
|
|
13
|
-
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
14
|
-
.replace(/\s*on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Escapes special HTML characters
|
|
19
|
-
* @param {string} str
|
|
20
|
-
* @returns {string} Escaped string
|
|
21
|
-
*/
|
|
22
|
-
function escapeHtml(str) {
|
|
23
|
-
if (!str) return '';
|
|
24
|
-
return str
|
|
25
|
-
.replace(/&/g, '&')
|
|
26
|
-
.replace(/</g, '<')
|
|
27
|
-
.replace(/>/g, '>')
|
|
28
|
-
.replace(/"/g, '"')
|
|
29
|
-
.replace(/'/g, ''');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
module.exports = {
|
|
33
|
-
sanitizeHtml,
|
|
34
|
-
escapeHtml
|
|
35
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* HTML Sanitizer & Escaping Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sanitizes HTML content by stripping dangerous inline script tags and inline handlers
|
|
7
|
+
* @param {string} html
|
|
8
|
+
* @returns {string} Clean HTML string
|
|
9
|
+
*/
|
|
10
|
+
function sanitizeHtml(html) {
|
|
11
|
+
if (!html) return '';
|
|
12
|
+
return html
|
|
13
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
14
|
+
.replace(/\s*on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Escapes special HTML characters
|
|
19
|
+
* @param {string} str
|
|
20
|
+
* @returns {string} Escaped string
|
|
21
|
+
*/
|
|
22
|
+
function escapeHtml(str) {
|
|
23
|
+
if (!str) return '';
|
|
24
|
+
return str
|
|
25
|
+
.replace(/&/g, '&')
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/"/g, '"')
|
|
29
|
+
.replace(/'/g, ''');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
sanitizeHtml,
|
|
34
|
+
escapeHtml
|
|
35
|
+
};
|