@amitdevx/md2pdf 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.0.1] - 2026-06-26
9
+
10
+ ### Added
11
+ - Core Markdown to PDF rendering engine using Playwright.
12
+ - Programmatic API `convert(options)`.
13
+ - CLI via `md2pdf <file>` command.
14
+ - AST-based parsing pipeline using `unified`, `remark`, and `rehype`.
15
+ - Default professional print typography and theme.
16
+ - Support for GitHub Flavored Markdown (tables, strikethrough).
17
+ - Resolution of local relative image paths.
18
+ - Comprehensive configuration for `tsup`, `vitest`, `eslint`, and `prettier`.
19
+ - GitHub Actions CI workflow for linting, building, and testing.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amit Divekar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # md2pdf
2
+
3
+ Production-quality Markdown to PDF rendering engine.
4
+
5
+ ## Overview
6
+
7
+ `md2pdf` is a modular, high-fidelity Markdown-to-PDF rendering engine for Node.js.
8
+
9
+ It was built because existing solutions often fail to handle complex CSS, rely on deprecated browser engines, or lack proper extensibility. `md2pdf` leverages the `unified` ecosystem (Remark/Rehype) for robust Abstract Syntax Tree (AST) manipulation and utilizes Playwright to drive headless Chromium. This ensures that the generated PDF perfectly reflects modern web standards, complete with professional typography, precise margins, and correct pagination.
10
+
11
+ ## Features (v0.0.1)
12
+
13
+ - **High-Fidelity Rendering:** Utilizes Chromium via Playwright for native print CSS capabilities.
14
+ - **Unified Pipeline:** Built entirely on `remark` and `rehype` ASTs for robustness.
15
+ - **Professional Typography:** Default theme is optimized for readability and print.
16
+ - **Core Markdown:** Supports headings, paragraphs, lists, bold, italics, blockquotes, code blocks, and local images.
17
+ - **GitHub Flavored Markdown:** Natively supports GFM tables and strikethrough.
18
+
19
+ ## Planned Roadmap
20
+
21
+ - Native Mermaid diagram execution
22
+ - KaTeX math rendering
23
+ - Obsidian compatibility (wiki links, embeds, callouts)
24
+ - Advanced CLI features (watch mode, theming)
25
+ - Extensible plugin system
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ # Install globally
31
+ npm install -g @amitdevx/md2pdf
32
+
33
+ # Or use locally within a project
34
+ npm install @amitdevx/md2pdf
35
+ ```
36
+
37
+ ## CLI Usage
38
+
39
+ Generate a PDF from a single Markdown file:
40
+
41
+ ```bash
42
+ md2pdf README.md
43
+ ```
44
+
45
+ Specify a custom output path:
46
+
47
+ ```bash
48
+ md2pdf input.md --output custom.pdf
49
+ ```
50
+
51
+ Convert an entire directory:
52
+
53
+ ```bash
54
+ md2pdf docs/
55
+ ```
56
+
57
+ ## Library Usage
58
+
59
+ You can embed the rendering engine directly in your Node.js applications.
60
+
61
+ ```typescript
62
+ import { convert } from '@amitdevx/md2pdf';
63
+
64
+ await convert({
65
+ input: 'README.md',
66
+ output: 'README.pdf'
67
+ });
68
+ ```
69
+
70
+ ## Development Setup
71
+
72
+ ```bash
73
+ git clone https://github.com/amitdevx/md2pdf.git
74
+ cd md2pdf
75
+ npm install
76
+ npx playwright install chromium
77
+ ```
78
+
79
+ ## Build Instructions
80
+
81
+ ```bash
82
+ npm run build
83
+ ```
84
+
85
+ ## Test Instructions
86
+
87
+ ```bash
88
+ npm run test
89
+ ```
90
+
91
+ ## Contributing
92
+
93
+ Please refer to `CONTRIBUTING.md` for our guidelines, branch naming conventions, and coding standards.
94
+
95
+ ## License
96
+
97
+ MIT License. See `LICENSE` for details.
98
+
99
+ ## Author
100
+
101
+ Amit Divekar
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var commander = require('commander');
5
+ var unified = require('unified');
6
+ var remarkParse = require('remark-parse');
7
+ var remarkGfm = require('remark-gfm');
8
+ var remarkRehype = require('remark-rehype');
9
+ var rehypeStringify = require('rehype-stringify');
10
+ var playwright = require('playwright');
11
+ var fs = require('fs/promises');
12
+ var path = require('path');
13
+ var ora = require('ora');
14
+ var pc = require('picocolors');
15
+ var fs2 = require('fs');
16
+
17
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
+
19
+ var remarkParse__default = /*#__PURE__*/_interopDefault(remarkParse);
20
+ var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
21
+ var remarkRehype__default = /*#__PURE__*/_interopDefault(remarkRehype);
22
+ var rehypeStringify__default = /*#__PURE__*/_interopDefault(rehypeStringify);
23
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
24
+ var path__default = /*#__PURE__*/_interopDefault(path);
25
+ var ora__default = /*#__PURE__*/_interopDefault(ora);
26
+ var pc__default = /*#__PURE__*/_interopDefault(pc);
27
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
28
+
29
+ async function parseMarkdown(markdown) {
30
+ const file = await unified.unified().use(remarkParse__default.default).use(remarkGfm__default.default).use(remarkRehype__default.default, { allowDangerousHtml: true }).use(rehypeStringify__default.default, { allowDangerousHtml: true }).process(markdown);
31
+ return String(file);
32
+ }
33
+
34
+ // src/renderer/index.ts
35
+ function renderHtmlTemplate(contentHtml, title = "Document") {
36
+ return `<!DOCTYPE html>
37
+ <html lang="en">
38
+ <head>
39
+ <meta charset="UTF-8">
40
+ <title>${title}</title>
41
+ <style>
42
+ /* Professional Typography and Print Defaults */
43
+ :root {
44
+ --text-main: #333;
45
+ --text-muted: #666;
46
+ --bg-main: #fff;
47
+ --border-color: #ddd;
48
+ --link-color: #0366d6;
49
+ --code-bg: #f6f8fa;
50
+ }
51
+
52
+ body {
53
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
54
+ line-height: 1.6;
55
+ color: var(--text-main);
56
+ background-color: var(--bg-main);
57
+ margin: 0;
58
+ padding: 0;
59
+ word-wrap: break-word;
60
+ }
61
+
62
+ .markdown-body {
63
+ padding: 2em;
64
+ max-width: 900px;
65
+ margin: 0 auto;
66
+ }
67
+
68
+ h1, h2, h3, h4, h5, h6 {
69
+ margin-top: 1.5em;
70
+ margin-bottom: 0.5em;
71
+ font-weight: 600;
72
+ line-height: 1.25;
73
+ color: #111;
74
+ page-break-after: avoid;
75
+ }
76
+
77
+ h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
78
+ h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
79
+ h3 { font-size: 1.5em; }
80
+
81
+ p, blockquote, ul, ol, dl, table, pre {
82
+ margin-top: 0;
83
+ margin-bottom: 16px;
84
+ }
85
+
86
+ a {
87
+ color: var(--link-color);
88
+ text-decoration: none;
89
+ }
90
+
91
+ a:hover {
92
+ text-decoration: underline;
93
+ }
94
+
95
+ blockquote {
96
+ padding: 0 1em;
97
+ color: var(--text-muted);
98
+ border-left: 0.25em solid var(--border-color);
99
+ }
100
+
101
+ code, kbd, pre {
102
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
103
+ font-size: 85%;
104
+ }
105
+
106
+ pre {
107
+ padding: 16px;
108
+ overflow: auto;
109
+ line-height: 1.45;
110
+ background-color: var(--code-bg);
111
+ border-radius: 6px;
112
+ page-break-inside: avoid;
113
+ }
114
+
115
+ pre code {
116
+ padding: 0;
117
+ margin: 0;
118
+ background-color: transparent;
119
+ border: 0;
120
+ }
121
+
122
+ code {
123
+ padding: 0.2em 0.4em;
124
+ margin: 0;
125
+ background-color: var(--code-bg);
126
+ border-radius: 6px;
127
+ }
128
+
129
+ table {
130
+ border-spacing: 0;
131
+ border-collapse: collapse;
132
+ width: 100%;
133
+ page-break-inside: avoid;
134
+ }
135
+
136
+ table th, table td {
137
+ padding: 6px 13px;
138
+ border: 1px solid var(--border-color);
139
+ }
140
+
141
+ table tr {
142
+ background-color: var(--bg-main);
143
+ border-top: 1px solid var(--border-color);
144
+ page-break-inside: avoid;
145
+ }
146
+
147
+ table tr:nth-child(2n) {
148
+ background-color: #f8f9fa;
149
+ }
150
+
151
+ img {
152
+ max-width: 100%;
153
+ height: auto;
154
+ box-sizing: content-box;
155
+ page-break-inside: avoid;
156
+ }
157
+
158
+ hr {
159
+ height: 0.25em;
160
+ padding: 0;
161
+ margin: 24px 0;
162
+ background-color: var(--border-color);
163
+ border: 0;
164
+ }
165
+
166
+ /* Print specific adjustments */
167
+ @media print {
168
+ body {
169
+ font-size: 11pt; /* Better readability for print */
170
+ }
171
+ .markdown-body {
172
+ padding: 0;
173
+ max-width: none;
174
+ }
175
+ a {
176
+ text-decoration: none;
177
+ color: #000;
178
+ }
179
+ }
180
+ </style>
181
+ </head>
182
+ <body>
183
+ <div class="markdown-body">
184
+ ${contentHtml}
185
+ </div>
186
+ </body>
187
+ </html>`;
188
+ }
189
+ async function generatePdf(options) {
190
+ const browser = await playwright.chromium.launch({
191
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
192
+ });
193
+ try {
194
+ const context = await browser.newContext();
195
+ const page = await context.newPage();
196
+ await page.setContent(options.html, { waitUntil: "networkidle" });
197
+ await page.evaluate(async () => {
198
+ await document.fonts.ready;
199
+ });
200
+ await page.pdf({
201
+ path: options.outputPath,
202
+ format: options.format || "A4",
203
+ printBackground: true,
204
+ margin: {
205
+ top: "20mm",
206
+ right: "20mm",
207
+ bottom: "20mm",
208
+ left: "20mm"
209
+ },
210
+ displayHeaderFooter: false
211
+ });
212
+ } finally {
213
+ await browser.close();
214
+ }
215
+ }
216
+ async function convert(options) {
217
+ const { input, output } = options;
218
+ const inputPath = path__default.default.resolve(process.cwd(), input);
219
+ const markdown = await fs__default.default.readFile(inputPath, "utf-8");
220
+ const dir = path__default.default.dirname(inputPath);
221
+ const processedMarkdown = markdown.replace(/!\[([^\]]*)\]\((?!http|data:)([^)]+)\)/g, (match, alt, src) => {
222
+ const absPath = path__default.default.resolve(dir, src);
223
+ return `![${alt}](file://${absPath})`;
224
+ });
225
+ const contentHtml = await parseMarkdown(processedMarkdown);
226
+ const title = path__default.default.basename(input, path__default.default.extname(input));
227
+ const html = renderHtmlTemplate(contentHtml, title);
228
+ const outputPath = path__default.default.resolve(process.cwd(), output);
229
+ await generatePdf({ html, outputPath });
230
+ }
231
+ var program = new commander.Command();
232
+ program.name("md2pdf").description("Production-quality Markdown to PDF rendering engine").version("0.0.1").argument("<input>", "Input markdown file").option("-o, --output <output>", "Output PDF file").action(async (input, options) => {
233
+ if (!fs2__default.default.existsSync(input)) {
234
+ console.error(pc__default.default.red(`Error: Input file '${input}' does not exist.`));
235
+ process.exit(1);
236
+ }
237
+ const output = options.output || input.replace(/\.md$/i, ".pdf");
238
+ const spinner = ora__default.default("Converting markdown to PDF...").start();
239
+ try {
240
+ await convert({ input, output });
241
+ spinner.succeed(pc__default.default.green(`Successfully generated ${output}`));
242
+ } catch (error) {
243
+ spinner.fail(pc__default.default.red("Failed to generate PDF"));
244
+ console.error(error);
245
+ process.exit(1);
246
+ }
247
+ });
248
+ program.parse(process.argv);
249
+ //# sourceMappingURL=index.cjs.map
250
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/parser/index.ts","../../src/renderer/index.ts","../../src/pdf/index.ts","../../src/core/index.ts","../../src/cli/index.ts"],"names":["unified","remarkParse","remarkGfm","remarkRehype","rehypeStringify","chromium","path","fs","Command","pc","ora"],"mappings":"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","file":"index.cjs","sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport remarkRehype from 'remark-rehype';\nimport rehypeStringify from 'rehype-stringify';\n\nexport async function parseMarkdown(markdown: string): Promise<string> {\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeStringify, { allowDangerousHtml: true })\n .process(markdown);\n \n return String(file);\n}\n","export function renderHtmlTemplate(contentHtml: string, title: string = 'Document'): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${title}</title>\n <style>\n /* Professional Typography and Print Defaults */\n :root {\n --text-main: #333;\n --text-muted: #666;\n --bg-main: #fff;\n --border-color: #ddd;\n --link-color: #0366d6;\n --code-bg: #f6f8fa;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n line-height: 1.6;\n color: var(--text-main);\n background-color: var(--bg-main);\n margin: 0;\n padding: 0;\n word-wrap: break-word;\n }\n\n .markdown-body {\n padding: 2em;\n max-width: 900px;\n margin: 0 auto;\n }\n\n h1, h2, h3, h4, h5, h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: #111;\n page-break-after: avoid;\n }\n\n h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h3 { font-size: 1.5em; }\n\n p, blockquote, ul, ol, dl, table, pre {\n margin-top: 0;\n margin-bottom: 16px;\n }\n\n a {\n color: var(--link-color);\n text-decoration: none;\n }\n \n a:hover {\n text-decoration: underline;\n }\n\n blockquote {\n padding: 0 1em;\n color: var(--text-muted);\n border-left: 0.25em solid var(--border-color);\n }\n\n code, kbd, pre {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n font-size: 85%;\n }\n\n pre {\n padding: 16px;\n overflow: auto;\n line-height: 1.45;\n background-color: var(--code-bg);\n border-radius: 6px;\n page-break-inside: avoid;\n }\n\n pre code {\n padding: 0;\n margin: 0;\n background-color: transparent;\n border: 0;\n }\n\n code {\n padding: 0.2em 0.4em;\n margin: 0;\n background-color: var(--code-bg);\n border-radius: 6px;\n }\n\n table {\n border-spacing: 0;\n border-collapse: collapse;\n width: 100%;\n page-break-inside: avoid;\n }\n\n table th, table td {\n padding: 6px 13px;\n border: 1px solid var(--border-color);\n }\n\n table tr {\n background-color: var(--bg-main);\n border-top: 1px solid var(--border-color);\n page-break-inside: avoid;\n }\n\n table tr:nth-child(2n) {\n background-color: #f8f9fa;\n }\n\n img {\n max-width: 100%;\n height: auto;\n box-sizing: content-box;\n page-break-inside: avoid;\n }\n\n hr {\n height: 0.25em;\n padding: 0;\n margin: 24px 0;\n background-color: var(--border-color);\n border: 0;\n }\n\n /* Print specific adjustments */\n @media print {\n body {\n font-size: 11pt; /* Better readability for print */\n }\n .markdown-body {\n padding: 0;\n max-width: none;\n }\n a {\n text-decoration: none;\n color: #000;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"markdown-body\">\n ${contentHtml}\n </div>\n</body>\n</html>`;\n}\n","import { chromium } from 'playwright';\n\nexport interface PdfOptions {\n html: string;\n outputPath: string;\n format?: 'A4' | 'Letter' | 'Legal';\n}\n\nexport async function generatePdf(options: PdfOptions): Promise<void> {\n const browser = await chromium.launch({\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n \n try {\n const context = await browser.newContext();\n const page = await context.newPage();\n \n // Set the HTML content\n await page.setContent(options.html, { waitUntil: 'networkidle' });\n \n // Ensure all web fonts are loaded\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n // Generate PDF\n await page.pdf({\n path: options.outputPath,\n format: options.format || 'A4',\n printBackground: true,\n margin: {\n top: '20mm',\n right: '20mm',\n bottom: '20mm',\n left: '20mm',\n },\n displayHeaderFooter: false,\n });\n } finally {\n await browser.close();\n }\n}\n","import { parseMarkdown } from '../parser/index.js';\nimport { renderHtmlTemplate } from '../renderer/index.js';\nimport { generatePdf } from '../pdf/index.js';\nimport { ConvertOptions } from '../types/index.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function convert(options: ConvertOptions): Promise<void> {\n const { input, output } = options;\n \n // 1. Read Markdown\n const inputPath = path.resolve(process.cwd(), input);\n const markdown = await fs.readFile(inputPath, 'utf-8');\n\n // 2. Parse to HTML (We also need to fix relative image paths to absolute)\n // For v0.0.1, we will replace relative image paths in markdown to absolute for Playwright\n const dir = path.dirname(inputPath);\n const processedMarkdown = markdown.replace(/!\\[([^\\]]*)\\]\\((?!http|data:)([^)]+)\\)/g, (match, alt, src) => {\n const absPath = path.resolve(dir, src);\n return `![${alt}](file://${absPath})`;\n });\n\n const contentHtml = await parseMarkdown(processedMarkdown);\n\n // 3. Render HTML with Theme\n const title = path.basename(input, path.extname(input));\n const html = renderHtmlTemplate(contentHtml, title);\n\n // 4. Generate PDF\n const outputPath = path.resolve(process.cwd(), output);\n await generatePdf({ html, outputPath });\n}\n","#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { convert } from '../core/index.js';\nimport ora from 'ora';\nimport pc from 'picocolors';\nimport fs from 'node:fs';\n\nconst program = new Command();\n\nprogram\n .name('md2pdf')\n .description('Production-quality Markdown to PDF rendering engine')\n .version('0.0.1')\n .argument('<input>', 'Input markdown file')\n .option('-o, --output <output>', 'Output PDF file')\n .action(async (input: string, options: { output?: string }) => {\n if (!fs.existsSync(input)) {\n console.error(pc.red(`Error: Input file '${input}' does not exist.`));\n process.exit(1);\n }\n\n const output = options.output || input.replace(/\\.md$/i, '.pdf');\n const spinner = ora('Converting markdown to PDF...').start();\n\n try {\n await convert({ input, output });\n spinner.succeed(pc.green(`Successfully generated ${output}`));\n } catch (error) {\n spinner.fail(pc.red('Failed to generate PDF'));\n console.error(error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { unified } from 'unified';
4
+ import remarkParse from 'remark-parse';
5
+ import remarkGfm from 'remark-gfm';
6
+ import remarkRehype from 'remark-rehype';
7
+ import rehypeStringify from 'rehype-stringify';
8
+ import { chromium } from 'playwright';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ import ora from 'ora';
12
+ import pc from 'picocolors';
13
+ import fs2 from 'fs';
14
+
15
+ async function parseMarkdown(markdown) {
16
+ const file = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeStringify, { allowDangerousHtml: true }).process(markdown);
17
+ return String(file);
18
+ }
19
+
20
+ // src/renderer/index.ts
21
+ function renderHtmlTemplate(contentHtml, title = "Document") {
22
+ return `<!DOCTYPE html>
23
+ <html lang="en">
24
+ <head>
25
+ <meta charset="UTF-8">
26
+ <title>${title}</title>
27
+ <style>
28
+ /* Professional Typography and Print Defaults */
29
+ :root {
30
+ --text-main: #333;
31
+ --text-muted: #666;
32
+ --bg-main: #fff;
33
+ --border-color: #ddd;
34
+ --link-color: #0366d6;
35
+ --code-bg: #f6f8fa;
36
+ }
37
+
38
+ body {
39
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
40
+ line-height: 1.6;
41
+ color: var(--text-main);
42
+ background-color: var(--bg-main);
43
+ margin: 0;
44
+ padding: 0;
45
+ word-wrap: break-word;
46
+ }
47
+
48
+ .markdown-body {
49
+ padding: 2em;
50
+ max-width: 900px;
51
+ margin: 0 auto;
52
+ }
53
+
54
+ h1, h2, h3, h4, h5, h6 {
55
+ margin-top: 1.5em;
56
+ margin-bottom: 0.5em;
57
+ font-weight: 600;
58
+ line-height: 1.25;
59
+ color: #111;
60
+ page-break-after: avoid;
61
+ }
62
+
63
+ h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
64
+ h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
65
+ h3 { font-size: 1.5em; }
66
+
67
+ p, blockquote, ul, ol, dl, table, pre {
68
+ margin-top: 0;
69
+ margin-bottom: 16px;
70
+ }
71
+
72
+ a {
73
+ color: var(--link-color);
74
+ text-decoration: none;
75
+ }
76
+
77
+ a:hover {
78
+ text-decoration: underline;
79
+ }
80
+
81
+ blockquote {
82
+ padding: 0 1em;
83
+ color: var(--text-muted);
84
+ border-left: 0.25em solid var(--border-color);
85
+ }
86
+
87
+ code, kbd, pre {
88
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
89
+ font-size: 85%;
90
+ }
91
+
92
+ pre {
93
+ padding: 16px;
94
+ overflow: auto;
95
+ line-height: 1.45;
96
+ background-color: var(--code-bg);
97
+ border-radius: 6px;
98
+ page-break-inside: avoid;
99
+ }
100
+
101
+ pre code {
102
+ padding: 0;
103
+ margin: 0;
104
+ background-color: transparent;
105
+ border: 0;
106
+ }
107
+
108
+ code {
109
+ padding: 0.2em 0.4em;
110
+ margin: 0;
111
+ background-color: var(--code-bg);
112
+ border-radius: 6px;
113
+ }
114
+
115
+ table {
116
+ border-spacing: 0;
117
+ border-collapse: collapse;
118
+ width: 100%;
119
+ page-break-inside: avoid;
120
+ }
121
+
122
+ table th, table td {
123
+ padding: 6px 13px;
124
+ border: 1px solid var(--border-color);
125
+ }
126
+
127
+ table tr {
128
+ background-color: var(--bg-main);
129
+ border-top: 1px solid var(--border-color);
130
+ page-break-inside: avoid;
131
+ }
132
+
133
+ table tr:nth-child(2n) {
134
+ background-color: #f8f9fa;
135
+ }
136
+
137
+ img {
138
+ max-width: 100%;
139
+ height: auto;
140
+ box-sizing: content-box;
141
+ page-break-inside: avoid;
142
+ }
143
+
144
+ hr {
145
+ height: 0.25em;
146
+ padding: 0;
147
+ margin: 24px 0;
148
+ background-color: var(--border-color);
149
+ border: 0;
150
+ }
151
+
152
+ /* Print specific adjustments */
153
+ @media print {
154
+ body {
155
+ font-size: 11pt; /* Better readability for print */
156
+ }
157
+ .markdown-body {
158
+ padding: 0;
159
+ max-width: none;
160
+ }
161
+ a {
162
+ text-decoration: none;
163
+ color: #000;
164
+ }
165
+ }
166
+ </style>
167
+ </head>
168
+ <body>
169
+ <div class="markdown-body">
170
+ ${contentHtml}
171
+ </div>
172
+ </body>
173
+ </html>`;
174
+ }
175
+ async function generatePdf(options) {
176
+ const browser = await chromium.launch({
177
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
178
+ });
179
+ try {
180
+ const context = await browser.newContext();
181
+ const page = await context.newPage();
182
+ await page.setContent(options.html, { waitUntil: "networkidle" });
183
+ await page.evaluate(async () => {
184
+ await document.fonts.ready;
185
+ });
186
+ await page.pdf({
187
+ path: options.outputPath,
188
+ format: options.format || "A4",
189
+ printBackground: true,
190
+ margin: {
191
+ top: "20mm",
192
+ right: "20mm",
193
+ bottom: "20mm",
194
+ left: "20mm"
195
+ },
196
+ displayHeaderFooter: false
197
+ });
198
+ } finally {
199
+ await browser.close();
200
+ }
201
+ }
202
+ async function convert(options) {
203
+ const { input, output } = options;
204
+ const inputPath = path.resolve(process.cwd(), input);
205
+ const markdown = await fs.readFile(inputPath, "utf-8");
206
+ const dir = path.dirname(inputPath);
207
+ const processedMarkdown = markdown.replace(/!\[([^\]]*)\]\((?!http|data:)([^)]+)\)/g, (match, alt, src) => {
208
+ const absPath = path.resolve(dir, src);
209
+ return `![${alt}](file://${absPath})`;
210
+ });
211
+ const contentHtml = await parseMarkdown(processedMarkdown);
212
+ const title = path.basename(input, path.extname(input));
213
+ const html = renderHtmlTemplate(contentHtml, title);
214
+ const outputPath = path.resolve(process.cwd(), output);
215
+ await generatePdf({ html, outputPath });
216
+ }
217
+ var program = new Command();
218
+ program.name("md2pdf").description("Production-quality Markdown to PDF rendering engine").version("0.0.1").argument("<input>", "Input markdown file").option("-o, --output <output>", "Output PDF file").action(async (input, options) => {
219
+ if (!fs2.existsSync(input)) {
220
+ console.error(pc.red(`Error: Input file '${input}' does not exist.`));
221
+ process.exit(1);
222
+ }
223
+ const output = options.output || input.replace(/\.md$/i, ".pdf");
224
+ const spinner = ora("Converting markdown to PDF...").start();
225
+ try {
226
+ await convert({ input, output });
227
+ spinner.succeed(pc.green(`Successfully generated ${output}`));
228
+ } catch (error) {
229
+ spinner.fail(pc.red("Failed to generate PDF"));
230
+ console.error(error);
231
+ process.exit(1);
232
+ }
233
+ });
234
+ program.parse(process.argv);
235
+ //# sourceMappingURL=index.js.map
236
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/parser/index.ts","../../src/renderer/index.ts","../../src/pdf/index.ts","../../src/core/index.ts","../../src/cli/index.ts"],"names":["fs"],"mappings":"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","file":"index.js","sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport remarkRehype from 'remark-rehype';\nimport rehypeStringify from 'rehype-stringify';\n\nexport async function parseMarkdown(markdown: string): Promise<string> {\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeStringify, { allowDangerousHtml: true })\n .process(markdown);\n \n return String(file);\n}\n","export function renderHtmlTemplate(contentHtml: string, title: string = 'Document'): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${title}</title>\n <style>\n /* Professional Typography and Print Defaults */\n :root {\n --text-main: #333;\n --text-muted: #666;\n --bg-main: #fff;\n --border-color: #ddd;\n --link-color: #0366d6;\n --code-bg: #f6f8fa;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n line-height: 1.6;\n color: var(--text-main);\n background-color: var(--bg-main);\n margin: 0;\n padding: 0;\n word-wrap: break-word;\n }\n\n .markdown-body {\n padding: 2em;\n max-width: 900px;\n margin: 0 auto;\n }\n\n h1, h2, h3, h4, h5, h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: #111;\n page-break-after: avoid;\n }\n\n h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h3 { font-size: 1.5em; }\n\n p, blockquote, ul, ol, dl, table, pre {\n margin-top: 0;\n margin-bottom: 16px;\n }\n\n a {\n color: var(--link-color);\n text-decoration: none;\n }\n \n a:hover {\n text-decoration: underline;\n }\n\n blockquote {\n padding: 0 1em;\n color: var(--text-muted);\n border-left: 0.25em solid var(--border-color);\n }\n\n code, kbd, pre {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n font-size: 85%;\n }\n\n pre {\n padding: 16px;\n overflow: auto;\n line-height: 1.45;\n background-color: var(--code-bg);\n border-radius: 6px;\n page-break-inside: avoid;\n }\n\n pre code {\n padding: 0;\n margin: 0;\n background-color: transparent;\n border: 0;\n }\n\n code {\n padding: 0.2em 0.4em;\n margin: 0;\n background-color: var(--code-bg);\n border-radius: 6px;\n }\n\n table {\n border-spacing: 0;\n border-collapse: collapse;\n width: 100%;\n page-break-inside: avoid;\n }\n\n table th, table td {\n padding: 6px 13px;\n border: 1px solid var(--border-color);\n }\n\n table tr {\n background-color: var(--bg-main);\n border-top: 1px solid var(--border-color);\n page-break-inside: avoid;\n }\n\n table tr:nth-child(2n) {\n background-color: #f8f9fa;\n }\n\n img {\n max-width: 100%;\n height: auto;\n box-sizing: content-box;\n page-break-inside: avoid;\n }\n\n hr {\n height: 0.25em;\n padding: 0;\n margin: 24px 0;\n background-color: var(--border-color);\n border: 0;\n }\n\n /* Print specific adjustments */\n @media print {\n body {\n font-size: 11pt; /* Better readability for print */\n }\n .markdown-body {\n padding: 0;\n max-width: none;\n }\n a {\n text-decoration: none;\n color: #000;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"markdown-body\">\n ${contentHtml}\n </div>\n</body>\n</html>`;\n}\n","import { chromium } from 'playwright';\n\nexport interface PdfOptions {\n html: string;\n outputPath: string;\n format?: 'A4' | 'Letter' | 'Legal';\n}\n\nexport async function generatePdf(options: PdfOptions): Promise<void> {\n const browser = await chromium.launch({\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n \n try {\n const context = await browser.newContext();\n const page = await context.newPage();\n \n // Set the HTML content\n await page.setContent(options.html, { waitUntil: 'networkidle' });\n \n // Ensure all web fonts are loaded\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n // Generate PDF\n await page.pdf({\n path: options.outputPath,\n format: options.format || 'A4',\n printBackground: true,\n margin: {\n top: '20mm',\n right: '20mm',\n bottom: '20mm',\n left: '20mm',\n },\n displayHeaderFooter: false,\n });\n } finally {\n await browser.close();\n }\n}\n","import { parseMarkdown } from '../parser/index.js';\nimport { renderHtmlTemplate } from '../renderer/index.js';\nimport { generatePdf } from '../pdf/index.js';\nimport { ConvertOptions } from '../types/index.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function convert(options: ConvertOptions): Promise<void> {\n const { input, output } = options;\n \n // 1. Read Markdown\n const inputPath = path.resolve(process.cwd(), input);\n const markdown = await fs.readFile(inputPath, 'utf-8');\n\n // 2. Parse to HTML (We also need to fix relative image paths to absolute)\n // For v0.0.1, we will replace relative image paths in markdown to absolute for Playwright\n const dir = path.dirname(inputPath);\n const processedMarkdown = markdown.replace(/!\\[([^\\]]*)\\]\\((?!http|data:)([^)]+)\\)/g, (match, alt, src) => {\n const absPath = path.resolve(dir, src);\n return `![${alt}](file://${absPath})`;\n });\n\n const contentHtml = await parseMarkdown(processedMarkdown);\n\n // 3. Render HTML with Theme\n const title = path.basename(input, path.extname(input));\n const html = renderHtmlTemplate(contentHtml, title);\n\n // 4. Generate PDF\n const outputPath = path.resolve(process.cwd(), output);\n await generatePdf({ html, outputPath });\n}\n","#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { convert } from '../core/index.js';\nimport ora from 'ora';\nimport pc from 'picocolors';\nimport fs from 'node:fs';\n\nconst program = new Command();\n\nprogram\n .name('md2pdf')\n .description('Production-quality Markdown to PDF rendering engine')\n .version('0.0.1')\n .argument('<input>', 'Input markdown file')\n .option('-o, --output <output>', 'Output PDF file')\n .action(async (input: string, options: { output?: string }) => {\n if (!fs.existsSync(input)) {\n console.error(pc.red(`Error: Input file '${input}' does not exist.`));\n process.exit(1);\n }\n\n const output = options.output || input.replace(/\\.md$/i, '.pdf');\n const spinner = ora('Converting markdown to PDF...').start();\n\n try {\n await convert({ input, output });\n spinner.succeed(pc.green(`Successfully generated ${output}`));\n } catch (error) {\n spinner.fail(pc.red('Failed to generate PDF'));\n console.error(error);\n process.exit(1);\n }\n });\n\nprogram.parse(process.argv);\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,227 @@
1
+ 'use strict';
2
+
3
+ var unified = require('unified');
4
+ var remarkParse = require('remark-parse');
5
+ var remarkGfm = require('remark-gfm');
6
+ var remarkRehype = require('remark-rehype');
7
+ var rehypeStringify = require('rehype-stringify');
8
+ var playwright = require('playwright');
9
+ var fs = require('fs/promises');
10
+ var path = require('path');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var remarkParse__default = /*#__PURE__*/_interopDefault(remarkParse);
15
+ var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
16
+ var remarkRehype__default = /*#__PURE__*/_interopDefault(remarkRehype);
17
+ var rehypeStringify__default = /*#__PURE__*/_interopDefault(rehypeStringify);
18
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
19
+ var path__default = /*#__PURE__*/_interopDefault(path);
20
+
21
+ // src/parser/index.ts
22
+ async function parseMarkdown(markdown) {
23
+ const file = await unified.unified().use(remarkParse__default.default).use(remarkGfm__default.default).use(remarkRehype__default.default, { allowDangerousHtml: true }).use(rehypeStringify__default.default, { allowDangerousHtml: true }).process(markdown);
24
+ return String(file);
25
+ }
26
+
27
+ // src/renderer/index.ts
28
+ function renderHtmlTemplate(contentHtml, title = "Document") {
29
+ return `<!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="UTF-8">
33
+ <title>${title}</title>
34
+ <style>
35
+ /* Professional Typography and Print Defaults */
36
+ :root {
37
+ --text-main: #333;
38
+ --text-muted: #666;
39
+ --bg-main: #fff;
40
+ --border-color: #ddd;
41
+ --link-color: #0366d6;
42
+ --code-bg: #f6f8fa;
43
+ }
44
+
45
+ body {
46
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
47
+ line-height: 1.6;
48
+ color: var(--text-main);
49
+ background-color: var(--bg-main);
50
+ margin: 0;
51
+ padding: 0;
52
+ word-wrap: break-word;
53
+ }
54
+
55
+ .markdown-body {
56
+ padding: 2em;
57
+ max-width: 900px;
58
+ margin: 0 auto;
59
+ }
60
+
61
+ h1, h2, h3, h4, h5, h6 {
62
+ margin-top: 1.5em;
63
+ margin-bottom: 0.5em;
64
+ font-weight: 600;
65
+ line-height: 1.25;
66
+ color: #111;
67
+ page-break-after: avoid;
68
+ }
69
+
70
+ h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
71
+ h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
72
+ h3 { font-size: 1.5em; }
73
+
74
+ p, blockquote, ul, ol, dl, table, pre {
75
+ margin-top: 0;
76
+ margin-bottom: 16px;
77
+ }
78
+
79
+ a {
80
+ color: var(--link-color);
81
+ text-decoration: none;
82
+ }
83
+
84
+ a:hover {
85
+ text-decoration: underline;
86
+ }
87
+
88
+ blockquote {
89
+ padding: 0 1em;
90
+ color: var(--text-muted);
91
+ border-left: 0.25em solid var(--border-color);
92
+ }
93
+
94
+ code, kbd, pre {
95
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
96
+ font-size: 85%;
97
+ }
98
+
99
+ pre {
100
+ padding: 16px;
101
+ overflow: auto;
102
+ line-height: 1.45;
103
+ background-color: var(--code-bg);
104
+ border-radius: 6px;
105
+ page-break-inside: avoid;
106
+ }
107
+
108
+ pre code {
109
+ padding: 0;
110
+ margin: 0;
111
+ background-color: transparent;
112
+ border: 0;
113
+ }
114
+
115
+ code {
116
+ padding: 0.2em 0.4em;
117
+ margin: 0;
118
+ background-color: var(--code-bg);
119
+ border-radius: 6px;
120
+ }
121
+
122
+ table {
123
+ border-spacing: 0;
124
+ border-collapse: collapse;
125
+ width: 100%;
126
+ page-break-inside: avoid;
127
+ }
128
+
129
+ table th, table td {
130
+ padding: 6px 13px;
131
+ border: 1px solid var(--border-color);
132
+ }
133
+
134
+ table tr {
135
+ background-color: var(--bg-main);
136
+ border-top: 1px solid var(--border-color);
137
+ page-break-inside: avoid;
138
+ }
139
+
140
+ table tr:nth-child(2n) {
141
+ background-color: #f8f9fa;
142
+ }
143
+
144
+ img {
145
+ max-width: 100%;
146
+ height: auto;
147
+ box-sizing: content-box;
148
+ page-break-inside: avoid;
149
+ }
150
+
151
+ hr {
152
+ height: 0.25em;
153
+ padding: 0;
154
+ margin: 24px 0;
155
+ background-color: var(--border-color);
156
+ border: 0;
157
+ }
158
+
159
+ /* Print specific adjustments */
160
+ @media print {
161
+ body {
162
+ font-size: 11pt; /* Better readability for print */
163
+ }
164
+ .markdown-body {
165
+ padding: 0;
166
+ max-width: none;
167
+ }
168
+ a {
169
+ text-decoration: none;
170
+ color: #000;
171
+ }
172
+ }
173
+ </style>
174
+ </head>
175
+ <body>
176
+ <div class="markdown-body">
177
+ ${contentHtml}
178
+ </div>
179
+ </body>
180
+ </html>`;
181
+ }
182
+ async function generatePdf(options) {
183
+ const browser = await playwright.chromium.launch({
184
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
185
+ });
186
+ try {
187
+ const context = await browser.newContext();
188
+ const page = await context.newPage();
189
+ await page.setContent(options.html, { waitUntil: "networkidle" });
190
+ await page.evaluate(async () => {
191
+ await document.fonts.ready;
192
+ });
193
+ await page.pdf({
194
+ path: options.outputPath,
195
+ format: options.format || "A4",
196
+ printBackground: true,
197
+ margin: {
198
+ top: "20mm",
199
+ right: "20mm",
200
+ bottom: "20mm",
201
+ left: "20mm"
202
+ },
203
+ displayHeaderFooter: false
204
+ });
205
+ } finally {
206
+ await browser.close();
207
+ }
208
+ }
209
+ async function convert(options) {
210
+ const { input, output } = options;
211
+ const inputPath = path__default.default.resolve(process.cwd(), input);
212
+ const markdown = await fs__default.default.readFile(inputPath, "utf-8");
213
+ const dir = path__default.default.dirname(inputPath);
214
+ const processedMarkdown = markdown.replace(/!\[([^\]]*)\]\((?!http|data:)([^)]+)\)/g, (match, alt, src) => {
215
+ const absPath = path__default.default.resolve(dir, src);
216
+ return `![${alt}](file://${absPath})`;
217
+ });
218
+ const contentHtml = await parseMarkdown(processedMarkdown);
219
+ const title = path__default.default.basename(input, path__default.default.extname(input));
220
+ const html = renderHtmlTemplate(contentHtml, title);
221
+ const outputPath = path__default.default.resolve(process.cwd(), output);
222
+ await generatePdf({ html, outputPath });
223
+ }
224
+
225
+ exports.convert = convert;
226
+ //# sourceMappingURL=index.cjs.map
227
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser/index.ts","../src/renderer/index.ts","../src/pdf/index.ts","../src/core/index.ts"],"names":["unified","remarkParse","remarkGfm","remarkRehype","rehypeStringify","chromium","path","fs"],"mappings":"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","file":"index.cjs","sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport remarkRehype from 'remark-rehype';\nimport rehypeStringify from 'rehype-stringify';\n\nexport async function parseMarkdown(markdown: string): Promise<string> {\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeStringify, { allowDangerousHtml: true })\n .process(markdown);\n \n return String(file);\n}\n","export function renderHtmlTemplate(contentHtml: string, title: string = 'Document'): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${title}</title>\n <style>\n /* Professional Typography and Print Defaults */\n :root {\n --text-main: #333;\n --text-muted: #666;\n --bg-main: #fff;\n --border-color: #ddd;\n --link-color: #0366d6;\n --code-bg: #f6f8fa;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n line-height: 1.6;\n color: var(--text-main);\n background-color: var(--bg-main);\n margin: 0;\n padding: 0;\n word-wrap: break-word;\n }\n\n .markdown-body {\n padding: 2em;\n max-width: 900px;\n margin: 0 auto;\n }\n\n h1, h2, h3, h4, h5, h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: #111;\n page-break-after: avoid;\n }\n\n h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h3 { font-size: 1.5em; }\n\n p, blockquote, ul, ol, dl, table, pre {\n margin-top: 0;\n margin-bottom: 16px;\n }\n\n a {\n color: var(--link-color);\n text-decoration: none;\n }\n \n a:hover {\n text-decoration: underline;\n }\n\n blockquote {\n padding: 0 1em;\n color: var(--text-muted);\n border-left: 0.25em solid var(--border-color);\n }\n\n code, kbd, pre {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n font-size: 85%;\n }\n\n pre {\n padding: 16px;\n overflow: auto;\n line-height: 1.45;\n background-color: var(--code-bg);\n border-radius: 6px;\n page-break-inside: avoid;\n }\n\n pre code {\n padding: 0;\n margin: 0;\n background-color: transparent;\n border: 0;\n }\n\n code {\n padding: 0.2em 0.4em;\n margin: 0;\n background-color: var(--code-bg);\n border-radius: 6px;\n }\n\n table {\n border-spacing: 0;\n border-collapse: collapse;\n width: 100%;\n page-break-inside: avoid;\n }\n\n table th, table td {\n padding: 6px 13px;\n border: 1px solid var(--border-color);\n }\n\n table tr {\n background-color: var(--bg-main);\n border-top: 1px solid var(--border-color);\n page-break-inside: avoid;\n }\n\n table tr:nth-child(2n) {\n background-color: #f8f9fa;\n }\n\n img {\n max-width: 100%;\n height: auto;\n box-sizing: content-box;\n page-break-inside: avoid;\n }\n\n hr {\n height: 0.25em;\n padding: 0;\n margin: 24px 0;\n background-color: var(--border-color);\n border: 0;\n }\n\n /* Print specific adjustments */\n @media print {\n body {\n font-size: 11pt; /* Better readability for print */\n }\n .markdown-body {\n padding: 0;\n max-width: none;\n }\n a {\n text-decoration: none;\n color: #000;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"markdown-body\">\n ${contentHtml}\n </div>\n</body>\n</html>`;\n}\n","import { chromium } from 'playwright';\n\nexport interface PdfOptions {\n html: string;\n outputPath: string;\n format?: 'A4' | 'Letter' | 'Legal';\n}\n\nexport async function generatePdf(options: PdfOptions): Promise<void> {\n const browser = await chromium.launch({\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n \n try {\n const context = await browser.newContext();\n const page = await context.newPage();\n \n // Set the HTML content\n await page.setContent(options.html, { waitUntil: 'networkidle' });\n \n // Ensure all web fonts are loaded\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n // Generate PDF\n await page.pdf({\n path: options.outputPath,\n format: options.format || 'A4',\n printBackground: true,\n margin: {\n top: '20mm',\n right: '20mm',\n bottom: '20mm',\n left: '20mm',\n },\n displayHeaderFooter: false,\n });\n } finally {\n await browser.close();\n }\n}\n","import { parseMarkdown } from '../parser/index.js';\nimport { renderHtmlTemplate } from '../renderer/index.js';\nimport { generatePdf } from '../pdf/index.js';\nimport { ConvertOptions } from '../types/index.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function convert(options: ConvertOptions): Promise<void> {\n const { input, output } = options;\n \n // 1. Read Markdown\n const inputPath = path.resolve(process.cwd(), input);\n const markdown = await fs.readFile(inputPath, 'utf-8');\n\n // 2. Parse to HTML (We also need to fix relative image paths to absolute)\n // For v0.0.1, we will replace relative image paths in markdown to absolute for Playwright\n const dir = path.dirname(inputPath);\n const processedMarkdown = markdown.replace(/!\\[([^\\]]*)\\]\\((?!http|data:)([^)]+)\\)/g, (match, alt, src) => {\n const absPath = path.resolve(dir, src);\n return `![${alt}](file://${absPath})`;\n });\n\n const contentHtml = await parseMarkdown(processedMarkdown);\n\n // 3. Render HTML with Theme\n const title = path.basename(input, path.extname(input));\n const html = renderHtmlTemplate(contentHtml, title);\n\n // 4. Generate PDF\n const outputPath = path.resolve(process.cwd(), output);\n await generatePdf({ html, outputPath });\n}\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,216 @@
1
+ import { unified } from 'unified';
2
+ import remarkParse from 'remark-parse';
3
+ import remarkGfm from 'remark-gfm';
4
+ import remarkRehype from 'remark-rehype';
5
+ import rehypeStringify from 'rehype-stringify';
6
+ import { chromium } from 'playwright';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+
10
+ // src/parser/index.ts
11
+ async function parseMarkdown(markdown) {
12
+ const file = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeStringify, { allowDangerousHtml: true }).process(markdown);
13
+ return String(file);
14
+ }
15
+
16
+ // src/renderer/index.ts
17
+ function renderHtmlTemplate(contentHtml, title = "Document") {
18
+ return `<!DOCTYPE html>
19
+ <html lang="en">
20
+ <head>
21
+ <meta charset="UTF-8">
22
+ <title>${title}</title>
23
+ <style>
24
+ /* Professional Typography and Print Defaults */
25
+ :root {
26
+ --text-main: #333;
27
+ --text-muted: #666;
28
+ --bg-main: #fff;
29
+ --border-color: #ddd;
30
+ --link-color: #0366d6;
31
+ --code-bg: #f6f8fa;
32
+ }
33
+
34
+ body {
35
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
36
+ line-height: 1.6;
37
+ color: var(--text-main);
38
+ background-color: var(--bg-main);
39
+ margin: 0;
40
+ padding: 0;
41
+ word-wrap: break-word;
42
+ }
43
+
44
+ .markdown-body {
45
+ padding: 2em;
46
+ max-width: 900px;
47
+ margin: 0 auto;
48
+ }
49
+
50
+ h1, h2, h3, h4, h5, h6 {
51
+ margin-top: 1.5em;
52
+ margin-bottom: 0.5em;
53
+ font-weight: 600;
54
+ line-height: 1.25;
55
+ color: #111;
56
+ page-break-after: avoid;
57
+ }
58
+
59
+ h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
60
+ h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
61
+ h3 { font-size: 1.5em; }
62
+
63
+ p, blockquote, ul, ol, dl, table, pre {
64
+ margin-top: 0;
65
+ margin-bottom: 16px;
66
+ }
67
+
68
+ a {
69
+ color: var(--link-color);
70
+ text-decoration: none;
71
+ }
72
+
73
+ a:hover {
74
+ text-decoration: underline;
75
+ }
76
+
77
+ blockquote {
78
+ padding: 0 1em;
79
+ color: var(--text-muted);
80
+ border-left: 0.25em solid var(--border-color);
81
+ }
82
+
83
+ code, kbd, pre {
84
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
85
+ font-size: 85%;
86
+ }
87
+
88
+ pre {
89
+ padding: 16px;
90
+ overflow: auto;
91
+ line-height: 1.45;
92
+ background-color: var(--code-bg);
93
+ border-radius: 6px;
94
+ page-break-inside: avoid;
95
+ }
96
+
97
+ pre code {
98
+ padding: 0;
99
+ margin: 0;
100
+ background-color: transparent;
101
+ border: 0;
102
+ }
103
+
104
+ code {
105
+ padding: 0.2em 0.4em;
106
+ margin: 0;
107
+ background-color: var(--code-bg);
108
+ border-radius: 6px;
109
+ }
110
+
111
+ table {
112
+ border-spacing: 0;
113
+ border-collapse: collapse;
114
+ width: 100%;
115
+ page-break-inside: avoid;
116
+ }
117
+
118
+ table th, table td {
119
+ padding: 6px 13px;
120
+ border: 1px solid var(--border-color);
121
+ }
122
+
123
+ table tr {
124
+ background-color: var(--bg-main);
125
+ border-top: 1px solid var(--border-color);
126
+ page-break-inside: avoid;
127
+ }
128
+
129
+ table tr:nth-child(2n) {
130
+ background-color: #f8f9fa;
131
+ }
132
+
133
+ img {
134
+ max-width: 100%;
135
+ height: auto;
136
+ box-sizing: content-box;
137
+ page-break-inside: avoid;
138
+ }
139
+
140
+ hr {
141
+ height: 0.25em;
142
+ padding: 0;
143
+ margin: 24px 0;
144
+ background-color: var(--border-color);
145
+ border: 0;
146
+ }
147
+
148
+ /* Print specific adjustments */
149
+ @media print {
150
+ body {
151
+ font-size: 11pt; /* Better readability for print */
152
+ }
153
+ .markdown-body {
154
+ padding: 0;
155
+ max-width: none;
156
+ }
157
+ a {
158
+ text-decoration: none;
159
+ color: #000;
160
+ }
161
+ }
162
+ </style>
163
+ </head>
164
+ <body>
165
+ <div class="markdown-body">
166
+ ${contentHtml}
167
+ </div>
168
+ </body>
169
+ </html>`;
170
+ }
171
+ async function generatePdf(options) {
172
+ const browser = await chromium.launch({
173
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
174
+ });
175
+ try {
176
+ const context = await browser.newContext();
177
+ const page = await context.newPage();
178
+ await page.setContent(options.html, { waitUntil: "networkidle" });
179
+ await page.evaluate(async () => {
180
+ await document.fonts.ready;
181
+ });
182
+ await page.pdf({
183
+ path: options.outputPath,
184
+ format: options.format || "A4",
185
+ printBackground: true,
186
+ margin: {
187
+ top: "20mm",
188
+ right: "20mm",
189
+ bottom: "20mm",
190
+ left: "20mm"
191
+ },
192
+ displayHeaderFooter: false
193
+ });
194
+ } finally {
195
+ await browser.close();
196
+ }
197
+ }
198
+ async function convert(options) {
199
+ const { input, output } = options;
200
+ const inputPath = path.resolve(process.cwd(), input);
201
+ const markdown = await fs.readFile(inputPath, "utf-8");
202
+ const dir = path.dirname(inputPath);
203
+ const processedMarkdown = markdown.replace(/!\[([^\]]*)\]\((?!http|data:)([^)]+)\)/g, (match, alt, src) => {
204
+ const absPath = path.resolve(dir, src);
205
+ return `![${alt}](file://${absPath})`;
206
+ });
207
+ const contentHtml = await parseMarkdown(processedMarkdown);
208
+ const title = path.basename(input, path.extname(input));
209
+ const html = renderHtmlTemplate(contentHtml, title);
210
+ const outputPath = path.resolve(process.cwd(), output);
211
+ await generatePdf({ html, outputPath });
212
+ }
213
+
214
+ export { convert };
215
+ //# sourceMappingURL=index.js.map
216
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser/index.ts","../src/renderer/index.ts","../src/pdf/index.ts","../src/core/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport remarkRehype from 'remark-rehype';\nimport rehypeStringify from 'rehype-stringify';\n\nexport async function parseMarkdown(markdown: string): Promise<string> {\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeStringify, { allowDangerousHtml: true })\n .process(markdown);\n \n return String(file);\n}\n","export function renderHtmlTemplate(contentHtml: string, title: string = 'Document'): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${title}</title>\n <style>\n /* Professional Typography and Print Defaults */\n :root {\n --text-main: #333;\n --text-muted: #666;\n --bg-main: #fff;\n --border-color: #ddd;\n --link-color: #0366d6;\n --code-bg: #f6f8fa;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n line-height: 1.6;\n color: var(--text-main);\n background-color: var(--bg-main);\n margin: 0;\n padding: 0;\n word-wrap: break-word;\n }\n\n .markdown-body {\n padding: 2em;\n max-width: 900px;\n margin: 0 auto;\n }\n\n h1, h2, h3, h4, h5, h6 {\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n font-weight: 600;\n line-height: 1.25;\n color: #111;\n page-break-after: avoid;\n }\n\n h1 { font-size: 2.25em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h2 { font-size: 1.75em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }\n h3 { font-size: 1.5em; }\n\n p, blockquote, ul, ol, dl, table, pre {\n margin-top: 0;\n margin-bottom: 16px;\n }\n\n a {\n color: var(--link-color);\n text-decoration: none;\n }\n \n a:hover {\n text-decoration: underline;\n }\n\n blockquote {\n padding: 0 1em;\n color: var(--text-muted);\n border-left: 0.25em solid var(--border-color);\n }\n\n code, kbd, pre {\n font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n font-size: 85%;\n }\n\n pre {\n padding: 16px;\n overflow: auto;\n line-height: 1.45;\n background-color: var(--code-bg);\n border-radius: 6px;\n page-break-inside: avoid;\n }\n\n pre code {\n padding: 0;\n margin: 0;\n background-color: transparent;\n border: 0;\n }\n\n code {\n padding: 0.2em 0.4em;\n margin: 0;\n background-color: var(--code-bg);\n border-radius: 6px;\n }\n\n table {\n border-spacing: 0;\n border-collapse: collapse;\n width: 100%;\n page-break-inside: avoid;\n }\n\n table th, table td {\n padding: 6px 13px;\n border: 1px solid var(--border-color);\n }\n\n table tr {\n background-color: var(--bg-main);\n border-top: 1px solid var(--border-color);\n page-break-inside: avoid;\n }\n\n table tr:nth-child(2n) {\n background-color: #f8f9fa;\n }\n\n img {\n max-width: 100%;\n height: auto;\n box-sizing: content-box;\n page-break-inside: avoid;\n }\n\n hr {\n height: 0.25em;\n padding: 0;\n margin: 24px 0;\n background-color: var(--border-color);\n border: 0;\n }\n\n /* Print specific adjustments */\n @media print {\n body {\n font-size: 11pt; /* Better readability for print */\n }\n .markdown-body {\n padding: 0;\n max-width: none;\n }\n a {\n text-decoration: none;\n color: #000;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"markdown-body\">\n ${contentHtml}\n </div>\n</body>\n</html>`;\n}\n","import { chromium } from 'playwright';\n\nexport interface PdfOptions {\n html: string;\n outputPath: string;\n format?: 'A4' | 'Letter' | 'Legal';\n}\n\nexport async function generatePdf(options: PdfOptions): Promise<void> {\n const browser = await chromium.launch({\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n \n try {\n const context = await browser.newContext();\n const page = await context.newPage();\n \n // Set the HTML content\n await page.setContent(options.html, { waitUntil: 'networkidle' });\n \n // Ensure all web fonts are loaded\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n // Generate PDF\n await page.pdf({\n path: options.outputPath,\n format: options.format || 'A4',\n printBackground: true,\n margin: {\n top: '20mm',\n right: '20mm',\n bottom: '20mm',\n left: '20mm',\n },\n displayHeaderFooter: false,\n });\n } finally {\n await browser.close();\n }\n}\n","import { parseMarkdown } from '../parser/index.js';\nimport { renderHtmlTemplate } from '../renderer/index.js';\nimport { generatePdf } from '../pdf/index.js';\nimport { ConvertOptions } from '../types/index.js';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function convert(options: ConvertOptions): Promise<void> {\n const { input, output } = options;\n \n // 1. Read Markdown\n const inputPath = path.resolve(process.cwd(), input);\n const markdown = await fs.readFile(inputPath, 'utf-8');\n\n // 2. Parse to HTML (We also need to fix relative image paths to absolute)\n // For v0.0.1, we will replace relative image paths in markdown to absolute for Playwright\n const dir = path.dirname(inputPath);\n const processedMarkdown = markdown.replace(/!\\[([^\\]]*)\\]\\((?!http|data:)([^)]+)\\)/g, (match, alt, src) => {\n const absPath = path.resolve(dir, src);\n return `![${alt}](file://${absPath})`;\n });\n\n const contentHtml = await parseMarkdown(processedMarkdown);\n\n // 3. Render HTML with Theme\n const title = path.basename(input, path.extname(input));\n const html = renderHtmlTemplate(contentHtml, title);\n\n // 4. Generate PDF\n const outputPath = path.resolve(process.cwd(), output);\n await generatePdf({ html, outputPath });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,2 @@
1
+ import { ConvertOptions } from '../types/index.js';
2
+ export declare function convert(options: ConvertOptions): Promise<void>;
@@ -0,0 +1,2 @@
1
+ export { convert } from './core/index.js';
2
+ export type { ConvertOptions } from './types/index.js';
@@ -0,0 +1 @@
1
+ export declare function parseMarkdown(markdown: string): Promise<string>;
@@ -0,0 +1,6 @@
1
+ export interface PdfOptions {
2
+ html: string;
3
+ outputPath: string;
4
+ format?: 'A4' | 'Letter' | 'Legal';
5
+ }
6
+ export declare function generatePdf(options: PdfOptions): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function renderHtmlTemplate(contentHtml: string, title?: string): string;
@@ -0,0 +1,5 @@
1
+ export interface ConvertOptions {
2
+ input: string;
3
+ output: string;
4
+ theme?: string;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@amitdevx/md2pdf",
3
+ "version": "0.0.1",
4
+ "description": "Production-quality Markdown to PDF rendering engine.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "bin": {
17
+ "md2pdf": "./dist/cli/index.js"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE",
23
+ "CHANGELOG.md"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "sideEffects": false,
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup && tsc --emitDeclarationOnly",
34
+ "dev": "tsup --watch",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "lint": "eslint src/ tests/",
38
+ "lint:fix": "eslint src/ tests/ --fix",
39
+ "typecheck": "tsc --noEmit",
40
+ "clean": "rm -rf dist/",
41
+ "prepare": "husky install || true",
42
+ "prepublishOnly": "npm run clean && npm run build && npm run test && npm run typecheck && npm run lint"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/amitdevx/md2pdf.git"
47
+ },
48
+ "homepage": "https://github.com/amitdevx/md2pdf#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/amitdevx/md2pdf/issues"
51
+ },
52
+ "keywords": [
53
+ "markdown",
54
+ "pdf",
55
+ "generator",
56
+ "playwright",
57
+ "unified",
58
+ "remark",
59
+ "rehype"
60
+ ],
61
+ "author": "Amit Divekar",
62
+ "license": "MIT",
63
+ "dependencies": {
64
+ "commander": "^11.1.0",
65
+ "ora": "^5.4.1",
66
+ "picocolors": "^1.0.0",
67
+ "playwright": "^1.40.0",
68
+ "rehype-stringify": "^10.0.0",
69
+ "remark-gfm": "^4.0.0",
70
+ "remark-parse": "^11.0.0",
71
+ "remark-rehype": "^11.0.0",
72
+ "unified": "^11.0.4"
73
+ },
74
+ "devDependencies": {
75
+ "@eslint/js": "^9.0.0",
76
+ "@types/node": "^20.10.0",
77
+ "eslint": "^9.0.0",
78
+ "husky": "^8.0.3",
79
+ "lint-staged": "^15.1.0",
80
+ "prettier": "^3.1.0",
81
+ "tsup": "^8.0.1",
82
+ "typescript": "^5.3.2",
83
+ "typescript-eslint": "^8.0.0",
84
+ "vitest": "^0.34.6"
85
+ },
86
+ "lint-staged": {
87
+ "*.ts": [
88
+ "eslint --fix",
89
+ "prettier --write"
90
+ ]
91
+ }
92
+ }