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.
@@ -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(codeArg, infostringArg, escapedArg) {
23
- let text = '';
24
- let lang = '';
25
-
26
- if (typeof codeArg === 'object' && codeArg !== null) {
27
- text = codeArg.text || '';
28
- lang = codeArg.lang || '';
29
- } else {
30
- text = codeArg || '';
31
- lang = infostringArg || '';
32
- }
33
-
34
- if (lang === 'mermaid') {
35
- return `<div class="mermaid-container"><pre class="mermaid">${text}</pre></div>`;
36
- }
37
-
38
- if (text.includes('┌') && text.includes('└')) {
39
- return `<div class="ui-mockup-container"><pre class="ui-mockup"><code>${escapeHtml(text)}</code></pre></div>`;
40
- }
41
-
42
- return originalCodeRenderer.call(this, codeArg, infostringArg, escapedArg);
43
- };
44
-
45
- renderer.heading = function(text, level, raw, slugger) {
46
- const rawClean = raw ? raw.trim() : text;
47
- const slug = headingsMap.get(rawClean) || (slugger ? slugger.slug(raw) : `heading-${level}`);
48
- return `<h${level} id="${slug}">${text}</h${level}>`;
49
- };
50
-
51
- return renderer;
52
- }
53
-
54
- /**
55
- * Wraps section headers with section-group divs for clean page-break handling
56
- * @param {string} html
57
- * @returns {string}
58
- */
59
- function wrapSectionGroups(html) {
60
- let wrapped = html.replace(
61
- /<h3>(.*?)<\/h3>\s*(<div class="ui-mockup-container">|<table|<div class="mermaid-container">|<ul|<ol)/g,
62
- '<div class="section-group"><h3>$1</h3>$2'
63
- );
64
- return wrapped.replace(
65
- /(<\/table>|<\/div>|<\/ul>|<\/ol>)\s*(?=<h[1-4]>|<hr|\$)/g,
66
- '$1</div>'
67
- );
68
- }
69
-
70
- /**
71
- * Converts Markdown content into a fully styled HTML string
72
- * @param {string} rawMarkdownContent
73
- * @param {Object} options
74
- * @returns {string} HTML string
75
- */
76
- function parseMarkdownToHtml(rawMarkdownContent, options = {}) {
77
- // 1. Extract Front Matter & Metadata
78
- const { data: frontMatter, content: markdownContent } = parseFrontMatter(rawMarkdownContent);
79
- const titleMatch = markdownContent.match(/^#\s+(.+)$/m);
80
- const title = frontMatter.title || options.title || (titleMatch ? titleMatch[1].trim() : 'Converted Document');
81
- const subtitle = frontMatter.subtitle || options.subtitle || '';
82
- const author = frontMatter.author || options.author || '';
83
- const date = frontMatter.date || options.date || '';
84
-
85
- // 2. Merge options
86
- const mergedOptions = {
87
- theme: options.theme || frontMatter.theme || 'modern',
88
- font: options.font || frontMatter.font || 'Inter',
89
- pageSize: options.pageSize || frontMatter.pageSize || 'A4',
90
- orientation: options.orientation || frontMatter.orientation || (frontMatter.landscape ? 'landscape' : 'portrait'),
91
- margin: options.margin || frontMatter.margin || '14mm 12mm 16mm 12mm',
92
- toc: options.toc !== undefined ? options.toc : (frontMatter.toc || false),
93
- cover: options.cover !== undefined ? options.cover : (frontMatter.cover || false),
94
- header: options.header !== undefined ? options.header : (frontMatter.header || true),
95
- footer: options.footer !== undefined ? options.footer : (frontMatter.footer || true),
96
- mermaid: options.mermaid !== undefined ? options.mermaid : (frontMatter.mermaid !== false),
97
- katex: options.katex !== undefined ? options.katex : (frontMatter.katex !== false),
98
- customCss: options.customCss || '',
99
- cssFile: options.cssFile || frontMatter.cssFile || frontMatter.css
100
- };
101
-
102
- // 3. Preprocess Markdown (LaTeX, pagebreaks, alerts)
103
- const processedMd = preprocessMarkdown(markdownContent);
104
-
105
- // 4. Generate TOC & Cover Page
106
- let tocHtml = '';
107
- let headingsMap = new Map();
108
- if (mergedOptions.toc) {
109
- const tocResult = generateToc(processedMd);
110
- tocHtml = tocResult.tocHtml;
111
- headingsMap = tocResult.headingsMap;
112
- }
113
-
114
- const coverHtml = generateCoverPage({
115
- title,
116
- subtitle,
117
- author,
118
- date,
119
- cover: mergedOptions.cover
120
- });
121
-
122
- // 5. Read custom CSS file if specified
123
- let customCss = mergedOptions.customCss || '';
124
- if (mergedOptions.cssFile) {
125
- customCss += '\n' + readCustomCss(mergedOptions.cssFile);
126
- }
127
-
128
- // 6. Parse Markdown using custom renderer
129
- marked.setOptions({ gfm: true, breaks: true });
130
- const renderer = buildMarkedRenderer(headingsMap);
131
- marked.use({ renderer });
132
-
133
- let bodyHtml = marked.parse(processedMd);
134
- bodyHtml = sanitizeHtml(bodyHtml);
135
-
136
- // 7. Inject TOC, Cover Page & Section Groups
137
- if (tocHtml) {
138
- bodyHtml = bodyHtml.includes('</h1>')
139
- ? bodyHtml.replace('</h1>', '</h1>\n' + tocHtml)
140
- : tocHtml + bodyHtml;
141
- }
142
-
143
- if (coverHtml) {
144
- bodyHtml = coverHtml + bodyHtml;
145
- }
146
-
147
- bodyHtml = wrapSectionGroups(bodyHtml);
148
-
149
- // 8. Generate and return full HTML template
150
- return getHtmlTemplate({
151
- title,
152
- author,
153
- bodyHtml,
154
- options: {
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
+ };
@@ -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, '&amp;')
26
- .replace(/</g, '&lt;')
27
- .replace(/>/g, '&gt;')
28
- .replace(/"/g, '&quot;')
29
- .replace(/'/g, '&#039;');
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, '&amp;')
26
+ .replace(/</g, '&lt;')
27
+ .replace(/>/g, '&gt;')
28
+ .replace(/"/g, '&quot;')
29
+ .replace(/'/g, '&#039;');
30
+ }
31
+
32
+ module.exports = {
33
+ sanitizeHtml,
34
+ escapeHtml
35
+ };