@speajus/markdown-to-pdf 1.0.2 → 1.0.3
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/README.md +1 -1
- package/README.pdf +0 -0
- package/dist/src/fonts/NotoEmoji-Regular.ttf +0 -0
- package/dist/src/fonts/NotoSans-Bold.ttf +0 -0
- package/dist/src/fonts/NotoSans-BoldItalic.ttf +0 -0
- package/dist/src/fonts/NotoSans-Italic.ttf +0 -0
- package/dist/src/fonts/NotoSans-Regular.ttf +0 -0
- package/package.json +25 -6
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.js +0 -24
- package/dist/src/index.d.ts +0 -5
- package/dist/src/index.js +0 -26
- package/dist/src/renderer.d.ts +0 -2
- package/dist/src/renderer.js +0 -423
- package/dist/src/styles.d.ts +0 -3
- package/dist/src/styles.js +0 -56
- package/dist/src/types.d.ts +0 -62
- package/dist/src/types.js +0 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Basic-Markdown-To-PDF
|
|
2
2
|
|
|
3
|
-
A lightweight TypeScript library that converts Markdown files into styled PDF documents. Built on [marked](https://github.com/markedjs/marked) for parsing and [PDFKit](https://pdfkit.org/) for PDF generation.
|
|
3
|
+
A lightweight TypeScript library that converts Markdown files into styled PDF documents. Built on [marked](https://github.com/markedjs/marked) for parsing and [PDFKit](https://pdfkit.org/) for PDF generation. [README.pdf](./README.pdf)
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
package/README.pdf
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speajus/markdown-to-pdf",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A new project created with Intent by Augment.",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
"node": {
|
|
9
|
+
"require": "./dist/src/index.js",
|
|
10
|
+
"import": "./dist/src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"browser": {
|
|
13
|
+
"require": "./dist/src/browser.js",
|
|
14
|
+
"import": "./dist/src/browser.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"files": [
|
|
8
18
|
"dist/src"
|
|
9
19
|
],
|
|
10
20
|
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"generate": "
|
|
13
|
-
"test": "
|
|
21
|
+
"build": "tsc && mkdir -p dist/src/fonts && cp src/fonts/* dist/src/fonts/",
|
|
22
|
+
"generate": "tsx samples/generate.ts",
|
|
23
|
+
"test": "tsx samples/generate.ts"
|
|
14
24
|
},
|
|
15
25
|
"keywords": [
|
|
16
26
|
"markdown",
|
|
@@ -27,12 +37,21 @@
|
|
|
27
37
|
"dependencies": {
|
|
28
38
|
"@resvg/resvg-js": "^2.6.2",
|
|
29
39
|
"marked": "^17.0.2",
|
|
30
|
-
"pdfkit": "^0.17.2"
|
|
40
|
+
"pdfkit": "^0.17.2",
|
|
41
|
+
"prismjs": "^1.30.0"
|
|
31
42
|
},
|
|
32
43
|
"devDependencies": {
|
|
33
44
|
"@types/node": "^25.2.3",
|
|
34
45
|
"@types/pdfkit": "^0.17.5",
|
|
35
|
-
"
|
|
46
|
+
"@types/prismjs": "^1.26.6",
|
|
47
|
+
"canvas": "^3.2.1",
|
|
48
|
+
"pdfjs-dist": "^3.11.174",
|
|
49
|
+
"tsx": "^4.21.0",
|
|
36
50
|
"typescript": "^5.9.3"
|
|
51
|
+
},
|
|
52
|
+
"pnpm": {
|
|
53
|
+
"overrides": {
|
|
54
|
+
"canvas": "^3.2.1"
|
|
55
|
+
}
|
|
37
56
|
}
|
|
38
57
|
}
|
package/dist/src/cli.d.ts
DELETED
package/dist/src/cli.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ts-node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const index_js_1 = require("./index.js");
|
|
9
|
-
async function main() {
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
if (args.length === 0) {
|
|
12
|
-
console.error('Usage: ts-node src/cli.ts <input.md> [output.pdf]');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
const inputPath = args[0];
|
|
16
|
-
const outputPath = args[1] ?? inputPath.replace(/\.md$/i, '.pdf');
|
|
17
|
-
console.log(`Converting ${inputPath} → ${outputPath}`);
|
|
18
|
-
await (0, index_js_1.generatePdf)(inputPath, outputPath);
|
|
19
|
-
console.log(`Done. PDF written to ${path_1.default.resolve(outputPath)}`);
|
|
20
|
-
}
|
|
21
|
-
main().catch((err) => {
|
|
22
|
-
console.error('Error:', err);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
});
|
package/dist/src/index.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, ThemeConfig, PdfOptions, } from './types.js';
|
|
2
|
-
export { renderMarkdownToPdf } from './renderer.js';
|
|
3
|
-
export { defaultTheme, defaultPageLayout } from './styles.js';
|
|
4
|
-
import type { PdfOptions } from './types.js';
|
|
5
|
-
export declare function generatePdf(inputPath: string, outputPath: string, options?: PdfOptions): Promise<void>;
|
package/dist/src/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.defaultPageLayout = exports.defaultTheme = exports.renderMarkdownToPdf = void 0;
|
|
7
|
-
exports.generatePdf = generatePdf;
|
|
8
|
-
var renderer_js_1 = require("./renderer.js");
|
|
9
|
-
Object.defineProperty(exports, "renderMarkdownToPdf", { enumerable: true, get: function () { return renderer_js_1.renderMarkdownToPdf; } });
|
|
10
|
-
var styles_js_1 = require("./styles.js");
|
|
11
|
-
Object.defineProperty(exports, "defaultTheme", { enumerable: true, get: function () { return styles_js_1.defaultTheme; } });
|
|
12
|
-
Object.defineProperty(exports, "defaultPageLayout", { enumerable: true, get: function () { return styles_js_1.defaultPageLayout; } });
|
|
13
|
-
const fs_1 = __importDefault(require("fs"));
|
|
14
|
-
const path_1 = __importDefault(require("path"));
|
|
15
|
-
const renderer_js_2 = require("./renderer.js");
|
|
16
|
-
async function generatePdf(inputPath, outputPath, options) {
|
|
17
|
-
const resolvedInput = path_1.default.resolve(inputPath);
|
|
18
|
-
const markdown = fs_1.default.readFileSync(resolvedInput, 'utf-8');
|
|
19
|
-
const basePath = path_1.default.dirname(resolvedInput);
|
|
20
|
-
const mergedOptions = { ...options, basePath: options?.basePath ?? basePath };
|
|
21
|
-
const buffer = await (0, renderer_js_2.renderMarkdownToPdf)(markdown, mergedOptions);
|
|
22
|
-
const dir = path_1.default.dirname(path_1.default.resolve(outputPath));
|
|
23
|
-
if (!fs_1.default.existsSync(dir))
|
|
24
|
-
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
25
|
-
fs_1.default.writeFileSync(path_1.default.resolve(outputPath), buffer);
|
|
26
|
-
}
|
package/dist/src/renderer.d.ts
DELETED
package/dist/src/renderer.js
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.renderMarkdownToPdf = renderMarkdownToPdf;
|
|
7
|
-
const styles_js_1 = require("./styles.js");
|
|
8
|
-
const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
9
|
-
const marked_1 = require("marked");
|
|
10
|
-
const resvg_js_1 = require("@resvg/resvg-js");
|
|
11
|
-
const stream_1 = require("stream");
|
|
12
|
-
const https_1 = __importDefault(require("https"));
|
|
13
|
-
const http_1 = __importDefault(require("http"));
|
|
14
|
-
const fs_1 = __importDefault(require("fs"));
|
|
15
|
-
const path_1 = __importDefault(require("path"));
|
|
16
|
-
function isSvg(buf) {
|
|
17
|
-
// Check for XML/SVG signature in the first 256 bytes
|
|
18
|
-
const head = buf.subarray(0, 256).toString('utf-8').trimStart();
|
|
19
|
-
return head.startsWith('<svg') || head.startsWith('<?xml');
|
|
20
|
-
}
|
|
21
|
-
function convertSvgToPng(svgData) {
|
|
22
|
-
const resvg = new resvg_js_1.Resvg(svgData, { font: { loadSystemFonts: true } });
|
|
23
|
-
const rendered = resvg.render();
|
|
24
|
-
return Buffer.from(rendered.asPng());
|
|
25
|
-
}
|
|
26
|
-
const FETCH_TIMEOUT_MS = 10000;
|
|
27
|
-
const MAX_REDIRECTS = 5;
|
|
28
|
-
function fetchImageBuffer(url, redirectCount = 0) {
|
|
29
|
-
if (redirectCount > MAX_REDIRECTS) {
|
|
30
|
-
return Promise.reject(new Error(`Too many redirects fetching ${url}`));
|
|
31
|
-
}
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const get = url.startsWith('https') ? https_1.default.get : http_1.default.get;
|
|
34
|
-
const req = get(url, (res) => {
|
|
35
|
-
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
36
|
-
res.resume(); // drain the response so the socket can be reused / freed
|
|
37
|
-
fetchImageBuffer(res.headers.location, redirectCount + 1).then(resolve, reject);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
|
|
41
|
-
res.resume();
|
|
42
|
-
reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const chunks = [];
|
|
46
|
-
res.on('data', (chunk) => chunks.push(chunk));
|
|
47
|
-
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
48
|
-
res.on('error', reject);
|
|
49
|
-
});
|
|
50
|
-
req.on('error', reject);
|
|
51
|
-
req.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
52
|
-
req.destroy(new Error(`Timeout fetching ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
async function renderMarkdownToPdf(markdown, options) {
|
|
57
|
-
const theme = options?.theme ?? styles_js_1.defaultTheme;
|
|
58
|
-
const layout = options?.pageLayout ?? styles_js_1.defaultPageLayout;
|
|
59
|
-
const basePath = options?.basePath ?? process.cwd();
|
|
60
|
-
const { margins } = layout;
|
|
61
|
-
const doc = new pdfkit_1.default({ size: layout.pageSize, margins });
|
|
62
|
-
const stream = new stream_1.PassThrough();
|
|
63
|
-
const chunks = [];
|
|
64
|
-
doc.pipe(stream);
|
|
65
|
-
stream.on('data', (chunk) => chunks.push(chunk));
|
|
66
|
-
const tokens = marked_1.marked.lexer(markdown);
|
|
67
|
-
const contentWidth = doc.page.width - margins.left - margins.right;
|
|
68
|
-
function ensureSpace(needed) {
|
|
69
|
-
if (doc.y + needed > doc.page.height - margins.bottom) {
|
|
70
|
-
doc.addPage();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function applyBodyFont(bold, italic) {
|
|
74
|
-
let font = theme.body.font;
|
|
75
|
-
if (bold && italic)
|
|
76
|
-
font = 'Helvetica-BoldOblique';
|
|
77
|
-
else if (bold)
|
|
78
|
-
font = 'Helvetica-Bold';
|
|
79
|
-
else if (italic)
|
|
80
|
-
font = 'Helvetica-Oblique';
|
|
81
|
-
doc.font(font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
|
|
82
|
-
}
|
|
83
|
-
function resetBodyFont() {
|
|
84
|
-
doc.font(theme.body.font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
|
|
85
|
-
}
|
|
86
|
-
function renderCodespan(text, continued) {
|
|
87
|
-
const cs = theme.code.inline;
|
|
88
|
-
doc.font(cs.font).fontSize(cs.fontSize);
|
|
89
|
-
const textW = doc.widthOfString(text);
|
|
90
|
-
const textH = doc.currentLineHeight();
|
|
91
|
-
const bgX = doc.x;
|
|
92
|
-
const bgY = doc.y;
|
|
93
|
-
doc.save();
|
|
94
|
-
doc.rect(bgX, bgY, textW + 4, textH + 2).fill(cs.backgroundColor);
|
|
95
|
-
doc.restore();
|
|
96
|
-
doc.font(cs.font).fontSize(cs.fontSize).fillColor(cs.color);
|
|
97
|
-
doc.text(text, bgX + 2, bgY + 1, { continued });
|
|
98
|
-
resetBodyFont();
|
|
99
|
-
}
|
|
100
|
-
function renderLink(tok, continued) {
|
|
101
|
-
doc.font(theme.body.font).fontSize(theme.body.fontSize).fillColor(theme.linkColor);
|
|
102
|
-
const linkText = tok.text || tok.href;
|
|
103
|
-
doc.text(linkText, { continued, underline: true, link: tok.href });
|
|
104
|
-
doc.fillColor(theme.body.color);
|
|
105
|
-
}
|
|
106
|
-
async function renderInlineTokens(inlineTokens, continued, insideBold = false, insideItalic = false) {
|
|
107
|
-
for (let i = 0; i < inlineTokens.length; i++) {
|
|
108
|
-
const isLast = i === inlineTokens.length - 1;
|
|
109
|
-
const cont = continued || !isLast;
|
|
110
|
-
const tok = inlineTokens[i];
|
|
111
|
-
switch (tok.type) {
|
|
112
|
-
case 'text': {
|
|
113
|
-
const t = tok;
|
|
114
|
-
if (t.tokens && t.tokens.length > 0) {
|
|
115
|
-
await renderInlineTokens(t.tokens, cont, insideBold, insideItalic);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
applyBodyFont(insideBold, insideItalic);
|
|
119
|
-
doc.text(t.text, { continued: cont });
|
|
120
|
-
}
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case 'strong': {
|
|
124
|
-
const t = tok;
|
|
125
|
-
await renderInlineTokens(t.tokens, cont, true, insideItalic);
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
case 'em': {
|
|
129
|
-
const t = tok;
|
|
130
|
-
await renderInlineTokens(t.tokens, cont, insideBold, true);
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
case 'codespan': {
|
|
134
|
-
renderCodespan(tok.text, cont);
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
case 'link': {
|
|
138
|
-
renderLink(tok, cont);
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
case 'image': {
|
|
142
|
-
await renderImage(tok);
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
case 'del': {
|
|
146
|
-
applyBodyFont(insideBold, insideItalic);
|
|
147
|
-
doc.text(tok.text, { continued: cont, strike: true });
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
case 'escape': {
|
|
151
|
-
applyBodyFont(insideBold, insideItalic);
|
|
152
|
-
doc.text(tok.text, { continued: cont });
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
case 'br': {
|
|
156
|
-
doc.moveDown(0.5);
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
default: {
|
|
160
|
-
const raw = tok.text ?? tok.raw ?? '';
|
|
161
|
-
if (raw) {
|
|
162
|
-
applyBodyFont(insideBold, insideItalic);
|
|
163
|
-
doc.text(raw, { continued: cont });
|
|
164
|
-
}
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
async function renderImage(tok) {
|
|
171
|
-
try {
|
|
172
|
-
let imgBuffer;
|
|
173
|
-
if (tok.href.startsWith('http://') || tok.href.startsWith('https://')) {
|
|
174
|
-
imgBuffer = await fetchImageBuffer(tok.href);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
// Local file path — resolve relative to the markdown file's directory
|
|
178
|
-
const imgPath = path_1.default.resolve(basePath, tok.href);
|
|
179
|
-
imgBuffer = fs_1.default.readFileSync(imgPath);
|
|
180
|
-
}
|
|
181
|
-
// Convert SVG to PNG since pdfkit doesn't support SVG natively
|
|
182
|
-
if (isSvg(imgBuffer)) {
|
|
183
|
-
imgBuffer = convertSvgToPng(imgBuffer);
|
|
184
|
-
}
|
|
185
|
-
// Read the image's intrinsic dimensions via pdfkit
|
|
186
|
-
// openImage exists at runtime but is missing from @types/pdfkit
|
|
187
|
-
const img = doc.openImage(imgBuffer);
|
|
188
|
-
const maxHeight = doc.page.height - margins.top - margins.bottom;
|
|
189
|
-
// Scale down to fit content area, but never scale up beyond natural size
|
|
190
|
-
let displayWidth = Math.min(img.width, contentWidth);
|
|
191
|
-
let displayHeight = img.height * (displayWidth / img.width);
|
|
192
|
-
// Also cap height to the printable area
|
|
193
|
-
if (displayHeight > maxHeight) {
|
|
194
|
-
displayHeight = maxHeight;
|
|
195
|
-
displayWidth = img.width * (displayHeight / img.height);
|
|
196
|
-
}
|
|
197
|
-
ensureSpace(displayHeight + 10);
|
|
198
|
-
doc.image(imgBuffer, { width: displayWidth, height: displayHeight });
|
|
199
|
-
doc.moveDown(0.5);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
ensureSpace(20);
|
|
203
|
-
resetBodyFont();
|
|
204
|
-
doc.text(`[Image: ${tok.text || 'image'}]`);
|
|
205
|
-
doc.moveDown(0.3);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
async function renderList(list, depth) {
|
|
209
|
-
const indent = margins.left + depth * 20;
|
|
210
|
-
for (let idx = 0; idx < list.items.length; idx++) {
|
|
211
|
-
const item = list.items[idx];
|
|
212
|
-
ensureSpace(theme.body.fontSize * 2);
|
|
213
|
-
resetBodyFont();
|
|
214
|
-
const bullet = list.ordered ? `${list.start + idx}.` : '•';
|
|
215
|
-
doc.text(bullet, indent, doc.y, { continued: true, width: contentWidth - depth * 20 });
|
|
216
|
-
doc.text(' ', { continued: true });
|
|
217
|
-
// Render item inline tokens
|
|
218
|
-
const itemTokens = item.tokens;
|
|
219
|
-
for (const child of itemTokens) {
|
|
220
|
-
if (child.type === 'text') {
|
|
221
|
-
const t = child;
|
|
222
|
-
if (t.tokens && t.tokens.length > 0) {
|
|
223
|
-
await renderInlineTokens(t.tokens, false);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
doc.text(t.text);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
else if (child.type === 'paragraph') {
|
|
230
|
-
await renderInlineTokens(child.tokens, false);
|
|
231
|
-
}
|
|
232
|
-
else if (child.type === 'list') {
|
|
233
|
-
await renderList(child, depth + 1);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
doc.moveDown(0.2);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
async function renderTable(table) {
|
|
240
|
-
const colCount = table.header.length;
|
|
241
|
-
if (colCount === 0)
|
|
242
|
-
return;
|
|
243
|
-
const cellPad = theme.table.cellPadding;
|
|
244
|
-
const colWidth = contentWidth / colCount;
|
|
245
|
-
const rowH = theme.body.fontSize + cellPad * 2 + 4;
|
|
246
|
-
const textInsetY = (rowH - theme.body.fontSize) / 2;
|
|
247
|
-
ensureSpace(rowH * 2);
|
|
248
|
-
const startX = margins.left;
|
|
249
|
-
let y = doc.y;
|
|
250
|
-
// Header row
|
|
251
|
-
doc.save();
|
|
252
|
-
doc.rect(startX, y, contentWidth, rowH).fill(theme.table.headerBackground);
|
|
253
|
-
doc.restore();
|
|
254
|
-
doc.font('Helvetica-Bold').fontSize(theme.body.fontSize).fillColor(theme.body.color);
|
|
255
|
-
for (let c = 0; c < colCount; c++) {
|
|
256
|
-
const cellX = startX + c * colWidth;
|
|
257
|
-
doc.text(table.header[c].text, cellX + cellPad, y + textInsetY, {
|
|
258
|
-
width: colWidth - cellPad * 2,
|
|
259
|
-
height: rowH,
|
|
260
|
-
align: table.align[c] || 'left',
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
// Header border
|
|
264
|
-
doc.save();
|
|
265
|
-
doc.strokeColor(theme.table.borderColor).lineWidth(0.5);
|
|
266
|
-
doc.rect(startX, y, contentWidth, rowH).stroke();
|
|
267
|
-
for (let c = 1; c < colCount; c++) {
|
|
268
|
-
const cx = startX + c * colWidth;
|
|
269
|
-
doc.moveTo(cx, y).lineTo(cx, y + rowH).stroke();
|
|
270
|
-
}
|
|
271
|
-
doc.restore();
|
|
272
|
-
y += rowH;
|
|
273
|
-
// Body rows
|
|
274
|
-
resetBodyFont();
|
|
275
|
-
for (const row of table.rows) {
|
|
276
|
-
ensureSpace(rowH);
|
|
277
|
-
for (let c = 0; c < colCount; c++) {
|
|
278
|
-
const cellX = startX + c * colWidth;
|
|
279
|
-
doc.text(row[c].text, cellX + cellPad, y + textInsetY, {
|
|
280
|
-
width: colWidth - cellPad * 2,
|
|
281
|
-
height: rowH,
|
|
282
|
-
align: table.align[c] || 'left',
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
doc.save();
|
|
286
|
-
doc.strokeColor(theme.table.borderColor).lineWidth(0.5);
|
|
287
|
-
doc.rect(startX, y, contentWidth, rowH).stroke();
|
|
288
|
-
for (let c = 1; c < colCount; c++) {
|
|
289
|
-
const cx = startX + c * colWidth;
|
|
290
|
-
doc.moveTo(cx, y).lineTo(cx, y + rowH).stroke();
|
|
291
|
-
}
|
|
292
|
-
doc.restore();
|
|
293
|
-
y += rowH;
|
|
294
|
-
}
|
|
295
|
-
doc.x = margins.left;
|
|
296
|
-
doc.y = y;
|
|
297
|
-
doc.moveDown(0.5);
|
|
298
|
-
resetBodyFont();
|
|
299
|
-
}
|
|
300
|
-
async function renderToken(token) {
|
|
301
|
-
switch (token.type) {
|
|
302
|
-
case 'heading': {
|
|
303
|
-
const t = token;
|
|
304
|
-
const key = `h${t.depth}`;
|
|
305
|
-
const style = theme.headings[key];
|
|
306
|
-
const spaceAbove = style.fontSize * 0.8;
|
|
307
|
-
const spaceBelow = style.fontSize * 0.3;
|
|
308
|
-
ensureSpace(spaceAbove + style.fontSize + spaceBelow);
|
|
309
|
-
doc.moveDown(spaceAbove / doc.currentLineHeight());
|
|
310
|
-
doc.font(style.font).fontSize(style.fontSize).fillColor(style.color);
|
|
311
|
-
doc.text(t.text);
|
|
312
|
-
doc.moveDown(spaceBelow / doc.currentLineHeight());
|
|
313
|
-
resetBodyFont();
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
case 'paragraph': {
|
|
317
|
-
const t = token;
|
|
318
|
-
ensureSpace(theme.body.fontSize * 2);
|
|
319
|
-
resetBodyFont();
|
|
320
|
-
await renderInlineTokens(t.tokens, false);
|
|
321
|
-
doc.moveDown(0.5);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
case 'code': {
|
|
325
|
-
const t = token;
|
|
326
|
-
const cs = theme.code.block;
|
|
327
|
-
const lines = t.text.split('\n');
|
|
328
|
-
const lineH = cs.fontSize * 1.4;
|
|
329
|
-
const blockH = lines.length * lineH + cs.padding * 2;
|
|
330
|
-
ensureSpace(blockH + 10);
|
|
331
|
-
const x = margins.left;
|
|
332
|
-
const y = doc.y;
|
|
333
|
-
doc.save();
|
|
334
|
-
doc.rect(x, y, contentWidth, blockH).fill(cs.backgroundColor);
|
|
335
|
-
doc.restore();
|
|
336
|
-
doc.font(cs.font).fontSize(cs.fontSize).fillColor(cs.color);
|
|
337
|
-
let textY = y + cs.padding;
|
|
338
|
-
for (const line of lines) {
|
|
339
|
-
doc.text(line, x + cs.padding, textY, { width: contentWidth - cs.padding * 2 });
|
|
340
|
-
textY += lineH;
|
|
341
|
-
}
|
|
342
|
-
doc.x = margins.left;
|
|
343
|
-
doc.y = y + blockH;
|
|
344
|
-
doc.moveDown(0.5);
|
|
345
|
-
resetBodyFont();
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
case 'blockquote': {
|
|
349
|
-
const t = token;
|
|
350
|
-
const bq = theme.blockquote;
|
|
351
|
-
ensureSpace(30);
|
|
352
|
-
const bqPadding = 6; // vertical padding above and below text
|
|
353
|
-
const startY = doc.y;
|
|
354
|
-
doc.y += bqPadding; // add top padding before text
|
|
355
|
-
const textX = margins.left + bq.borderWidth + bq.indent;
|
|
356
|
-
const textWidth = contentWidth - bq.borderWidth - bq.indent;
|
|
357
|
-
for (const child of t.tokens) {
|
|
358
|
-
if (child.type === 'paragraph') {
|
|
359
|
-
const p = child;
|
|
360
|
-
const font = bq.italic ? 'Helvetica-Oblique' : theme.body.font;
|
|
361
|
-
doc.font(font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
|
|
362
|
-
doc.text('', textX, doc.y, { width: textWidth });
|
|
363
|
-
await renderInlineTokens(p.tokens, false, false, bq.italic);
|
|
364
|
-
doc.moveDown(0.3);
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
await renderToken(child);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
doc.y += bqPadding; // add bottom padding after text
|
|
371
|
-
const endY = doc.y;
|
|
372
|
-
doc.save();
|
|
373
|
-
doc.rect(margins.left, startY, bq.borderWidth, endY - startY).fill(bq.borderColor);
|
|
374
|
-
doc.restore();
|
|
375
|
-
doc.x = margins.left;
|
|
376
|
-
doc.moveDown(0.3);
|
|
377
|
-
resetBodyFont();
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
case 'list': {
|
|
381
|
-
await renderList(token, 0);
|
|
382
|
-
doc.moveDown(0.3);
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
case 'hr': {
|
|
386
|
-
ensureSpace(20);
|
|
387
|
-
doc.moveDown(0.5);
|
|
388
|
-
const y = doc.y;
|
|
389
|
-
doc.save();
|
|
390
|
-
doc.strokeColor(theme.horizontalRuleColor).lineWidth(1)
|
|
391
|
-
.moveTo(margins.left, y)
|
|
392
|
-
.lineTo(margins.left + contentWidth, y)
|
|
393
|
-
.stroke();
|
|
394
|
-
doc.restore();
|
|
395
|
-
doc.y = y;
|
|
396
|
-
doc.moveDown(0.5);
|
|
397
|
-
resetBodyFont();
|
|
398
|
-
break;
|
|
399
|
-
}
|
|
400
|
-
case 'table': {
|
|
401
|
-
await renderTable(token);
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
case 'image': {
|
|
405
|
-
await renderImage(token);
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
case 'space':
|
|
409
|
-
case 'html':
|
|
410
|
-
break;
|
|
411
|
-
default:
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
// ── Main loop ─────────────────────────────────────────────────────────────
|
|
416
|
-
for (const token of tokens) {
|
|
417
|
-
await renderToken(token);
|
|
418
|
-
}
|
|
419
|
-
doc.end();
|
|
420
|
-
return new Promise((resolve) => {
|
|
421
|
-
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
422
|
-
});
|
|
423
|
-
}
|
package/dist/src/styles.d.ts
DELETED
package/dist/src/styles.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defaultPageLayout = exports.defaultTheme = void 0;
|
|
4
|
-
exports.defaultTheme = {
|
|
5
|
-
headings: {
|
|
6
|
-
h1: { font: 'Helvetica-Bold', fontSize: 28, color: '#1a1a1a', bold: true },
|
|
7
|
-
h2: { font: 'Helvetica-Bold', fontSize: 22, color: '#2a2a2a', bold: true },
|
|
8
|
-
h3: { font: 'Helvetica-Bold', fontSize: 18, color: '#3a3a3a', bold: true },
|
|
9
|
-
h4: { font: 'Helvetica-Bold', fontSize: 16, color: '#4a4a4a', bold: true },
|
|
10
|
-
h5: { font: 'Helvetica-Bold', fontSize: 14, color: '#5a5a5a', bold: true },
|
|
11
|
-
h6: { font: 'Helvetica-Bold', fontSize: 12, color: '#6a6a6a', bold: true },
|
|
12
|
-
},
|
|
13
|
-
body: {
|
|
14
|
-
font: 'Helvetica',
|
|
15
|
-
fontSize: 11,
|
|
16
|
-
color: '#333333',
|
|
17
|
-
lineGap: 4,
|
|
18
|
-
},
|
|
19
|
-
code: {
|
|
20
|
-
inline: {
|
|
21
|
-
font: 'Courier',
|
|
22
|
-
fontSize: 10,
|
|
23
|
-
color: '#c7254e',
|
|
24
|
-
backgroundColor: '#f9f2f4',
|
|
25
|
-
},
|
|
26
|
-
block: {
|
|
27
|
-
font: 'Courier',
|
|
28
|
-
fontSize: 9,
|
|
29
|
-
color: '#333333',
|
|
30
|
-
backgroundColor: '#f5f5f5',
|
|
31
|
-
padding: 8,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
blockquote: {
|
|
35
|
-
borderColor: '#3498db',
|
|
36
|
-
borderWidth: 3,
|
|
37
|
-
italic: true,
|
|
38
|
-
indent: 20,
|
|
39
|
-
},
|
|
40
|
-
linkColor: '#2980b9',
|
|
41
|
-
horizontalRuleColor: '#cccccc',
|
|
42
|
-
table: {
|
|
43
|
-
headerBackground: '#f0f0f0',
|
|
44
|
-
borderColor: '#cccccc',
|
|
45
|
-
cellPadding: 6,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
exports.defaultPageLayout = {
|
|
49
|
-
pageSize: 'LETTER',
|
|
50
|
-
margins: {
|
|
51
|
-
top: 50,
|
|
52
|
-
right: 50,
|
|
53
|
-
bottom: 50,
|
|
54
|
-
left: 50,
|
|
55
|
-
},
|
|
56
|
-
};
|
package/dist/src/types.d.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
export interface TextStyle {
|
|
2
|
-
font: string;
|
|
3
|
-
fontSize: number;
|
|
4
|
-
color: string;
|
|
5
|
-
lineGap?: number;
|
|
6
|
-
bold?: boolean;
|
|
7
|
-
italic?: boolean;
|
|
8
|
-
}
|
|
9
|
-
export interface PageLayout {
|
|
10
|
-
pageSize: string;
|
|
11
|
-
margins: {
|
|
12
|
-
top: number;
|
|
13
|
-
right: number;
|
|
14
|
-
bottom: number;
|
|
15
|
-
left: number;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export interface CodeStyle {
|
|
19
|
-
font: string;
|
|
20
|
-
fontSize: number;
|
|
21
|
-
color: string;
|
|
22
|
-
backgroundColor: string;
|
|
23
|
-
}
|
|
24
|
-
export interface CodeBlockStyle extends CodeStyle {
|
|
25
|
-
padding: number;
|
|
26
|
-
}
|
|
27
|
-
export interface BlockquoteStyle {
|
|
28
|
-
borderColor: string;
|
|
29
|
-
borderWidth: number;
|
|
30
|
-
italic: boolean;
|
|
31
|
-
indent: number;
|
|
32
|
-
}
|
|
33
|
-
export interface TableStyles {
|
|
34
|
-
headerBackground: string;
|
|
35
|
-
borderColor: string;
|
|
36
|
-
cellPadding: number;
|
|
37
|
-
}
|
|
38
|
-
export interface ThemeConfig {
|
|
39
|
-
headings: {
|
|
40
|
-
h1: TextStyle;
|
|
41
|
-
h2: TextStyle;
|
|
42
|
-
h3: TextStyle;
|
|
43
|
-
h4: TextStyle;
|
|
44
|
-
h5: TextStyle;
|
|
45
|
-
h6: TextStyle;
|
|
46
|
-
};
|
|
47
|
-
body: TextStyle;
|
|
48
|
-
code: {
|
|
49
|
-
inline: CodeStyle;
|
|
50
|
-
block: CodeBlockStyle;
|
|
51
|
-
};
|
|
52
|
-
blockquote: BlockquoteStyle;
|
|
53
|
-
linkColor: string;
|
|
54
|
-
horizontalRuleColor: string;
|
|
55
|
-
table: TableStyles;
|
|
56
|
-
}
|
|
57
|
-
export interface PdfOptions {
|
|
58
|
-
theme?: ThemeConfig;
|
|
59
|
-
pageLayout?: PageLayout;
|
|
60
|
-
/** Base directory for resolving relative image paths */
|
|
61
|
-
basePath?: string;
|
|
62
|
-
}
|
package/dist/src/types.js
DELETED