@speajus/markdown-to-pdf 1.0.12 → 1.0.14
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 +19 -19
- package/README.pdf +0 -0
- package/dist/browser.d.ts +4 -1
- package/dist/browser.js +3 -0
- package/dist/cli.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/renderer.js +88 -11
- package/dist/types.d.ts +27 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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) | [Live Demo](https://speajus.github.io/markdown-to-pdf/)
|
|
4
4
|
|
|
5
|
-
[](https://speajus.github.io/markdown-to-pdf/)
|
|
5
|
+
-[]([https://speajus.github.io/markdown-to-pdf/](https://speajus.github.io/markdown-to-pdf/))
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -128,16 +128,16 @@ await generatePdf(markdown, {
|
|
|
128
128
|
|
|
129
129
|
The full `ThemeConfig` interface exposes styles for:
|
|
130
130
|
|
|
131
|
-
| Section
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
131
|
+
| Section | Configurable properties |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| headings | Font, size, and color for each level (h1–h6) |
|
|
134
|
+
| body | Font, size, color, and line gap |
|
|
135
|
+
| code.inline | Font, size, color, and background color |
|
|
136
|
+
| code.block | Font, size, color, background color, and padding |
|
|
137
|
+
| blockquote | Border color, border width, italic flag, and indent |
|
|
138
|
+
| table | Header background, border color, cell padding, and zebra color |
|
|
139
|
+
| linkColor | Color for hyperlink text |
|
|
140
|
+
| horizontalRuleColor | Color for --- dividers |
|
|
141
141
|
|
|
142
142
|
## Project Structure
|
|
143
143
|
|
|
@@ -163,18 +163,18 @@ The full `ThemeConfig` interface exposes styles for:
|
|
|
163
163
|
## Dependencies
|
|
164
164
|
|
|
165
165
|
| Package | Purpose |
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| marked | Markdown parsing and tokenization |
|
|
168
|
+
| pdfkit | PDF document generation |
|
|
169
|
+
| @resvg/resvg-js | SVG-to-PNG rasterization for image embedding |
|
|
170
170
|
|
|
171
171
|
## Scripts
|
|
172
172
|
|
|
173
173
|
| Command | Description |
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| npm run build | Compile TypeScript to dist/ |
|
|
176
|
+
| npm run generate | Generate sample PDFs from samples/*.md into output/ |
|
|
177
177
|
|
|
178
178
|
## License
|
|
179
179
|
|
|
180
|
-
ISC
|
|
180
|
+
ISC
|
package/README.pdf
CHANGED
|
Binary file
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-specific entry point that excludes Node.js dependencies
|
|
3
|
+
*/
|
|
1
4
|
export { createBrowserImageRenderer } from "./browser-image-renderer.js";
|
|
2
5
|
export { createBrowserColorEmojiRenderer } from './color-emoji.js';
|
|
3
|
-
export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, ThemeConfig, PdfOptions, ColorEmojiRenderer, } from './types.js';
|
|
6
|
+
export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, ThemeConfig, PdfOptions, ColorEmojiRenderer, CustomFontDefinition, } from './types.js';
|
|
4
7
|
export { defaultTheme, defaultPageLayout, defaultSyntaxHighlightTheme } from './styles.js';
|
|
5
8
|
export { themes, modernTheme, academicTheme, minimalTheme, oceanTheme } from './themes/index.js';
|
|
6
9
|
export { renderMarkdownToPdf } from "./renderer.js";
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderMarkdownToPdf = exports.oceanTheme = exports.minimalTheme = exports.academicTheme = exports.modernTheme = exports.themes = exports.defaultSyntaxHighlightTheme = exports.defaultPageLayout = exports.defaultTheme = exports.createBrowserColorEmojiRenderer = exports.createBrowserImageRenderer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Browser-specific entry point that excludes Node.js dependencies
|
|
6
|
+
*/
|
|
4
7
|
var browser_image_renderer_js_1 = require("./browser-image-renderer.js");
|
|
5
8
|
Object.defineProperty(exports, "createBrowserImageRenderer", { enumerable: true, get: function () { return browser_image_renderer_js_1.createBrowserImageRenderer; } });
|
|
6
9
|
var color_emoji_js_1 = require("./color-emoji.js");
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,9 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const index_js_1 = require("./index.js");
|
|
11
11
|
async function readMdWritePdf(inputPath, outputPath, extraOptions) {
|
|
12
|
+
if (inputPath === outputPath) {
|
|
13
|
+
throw new Error(`input path can not be the same as output path.`);
|
|
14
|
+
}
|
|
12
15
|
console.log(`Converting ${inputPath} → ${outputPath}`);
|
|
13
16
|
const resolvedInput = path_1.default.resolve(inputPath);
|
|
14
17
|
const markdown = fs_1.default.readFileSync(resolvedInput, "utf-8");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, ThemeConfig, PdfOptions, ColorEmojiRenderer, } from './types.js';
|
|
1
|
+
export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, ThemeConfig, PdfOptions, ColorEmojiRenderer, CustomFontDefinition, } from './types.js';
|
|
2
2
|
export { defaultTheme, defaultPageLayout, defaultSyntaxHighlightTheme } from './styles.js';
|
|
3
3
|
export { renderMarkdownToPdf as generatePdf, renderMarkdownToPdf, } from "./renderer.js";
|
|
4
4
|
export { createBrowserImageRenderer } from './browser-image-renderer.js';
|
package/dist/renderer.js
CHANGED
|
@@ -67,6 +67,26 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
67
67
|
// fall through without emoji support.
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
// ── Custom font registration ─────────────────────────────────────────────
|
|
71
|
+
// Register user-supplied font families so they can be referenced by name
|
|
72
|
+
// in ThemeConfig font fields. Each variant is registered with a suffix:
|
|
73
|
+
// name (regular), name-Bold, name-Italic, name-BoldItalic
|
|
74
|
+
// Missing variants fall back: boldItalic → bold → regular, italic → regular.
|
|
75
|
+
const customFontNames = new Set();
|
|
76
|
+
if (options?.customFonts) {
|
|
77
|
+
for (const cf of options.customFonts) {
|
|
78
|
+
try {
|
|
79
|
+
doc.registerFont(cf.name, cf.regular);
|
|
80
|
+
doc.registerFont(`${cf.name}-Bold`, cf.bold ?? cf.regular);
|
|
81
|
+
doc.registerFont(`${cf.name}-Italic`, cf.italic ?? cf.regular);
|
|
82
|
+
doc.registerFont(`${cf.name}-BoldItalic`, cf.boldItalic ?? cf.bold ?? cf.regular);
|
|
83
|
+
customFontNames.add(cf.name);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Font registration failed — skip this font gracefully.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
70
90
|
// ── Color emoji pre-render ─────────────────────────────────────────────
|
|
71
91
|
// When a colorEmoji renderer is provided, pre-scan the entire markdown for
|
|
72
92
|
// every unique emoji and convert them all to PNG buffers up-front. This
|
|
@@ -92,8 +112,38 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
92
112
|
doc.addPage();
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
|
-
/**
|
|
115
|
+
/** Check whether `font` (or its base name) is a registered custom font. */
|
|
116
|
+
function isCustomFont(font) {
|
|
117
|
+
if (customFontNames.has(font))
|
|
118
|
+
return true;
|
|
119
|
+
// Also match variant names like "Roboto-Bold"
|
|
120
|
+
const dash = font.lastIndexOf('-');
|
|
121
|
+
if (dash > 0)
|
|
122
|
+
return customFontNames.has(font.substring(0, dash));
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/** Return the base (registered) name of a custom font, stripping any variant suffix. */
|
|
126
|
+
function customFontBase(font) {
|
|
127
|
+
if (customFontNames.has(font))
|
|
128
|
+
return font;
|
|
129
|
+
const dash = font.lastIndexOf('-');
|
|
130
|
+
if (dash > 0) {
|
|
131
|
+
const base = font.substring(0, dash);
|
|
132
|
+
if (customFontNames.has(base))
|
|
133
|
+
return base;
|
|
134
|
+
}
|
|
135
|
+
return font;
|
|
136
|
+
}
|
|
137
|
+
/** Derive the italic variant of a font name. */
|
|
96
138
|
function italicVariant(font) {
|
|
139
|
+
// Custom fonts use Name-Italic / Name-BoldItalic
|
|
140
|
+
if (isCustomFont(font)) {
|
|
141
|
+
const base = customFontBase(font);
|
|
142
|
+
if (font.endsWith('-Bold'))
|
|
143
|
+
return `${base}-BoldItalic`;
|
|
144
|
+
return `${base}-Italic`;
|
|
145
|
+
}
|
|
146
|
+
// Built-in Times family
|
|
97
147
|
if (font.startsWith('Times'))
|
|
98
148
|
return font.replace(/-Bold$/, '-BoldItalic').replace(/^Times-Roman$/, 'Times-Italic');
|
|
99
149
|
// Helvetica / Courier families use "Oblique"
|
|
@@ -101,22 +151,49 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
101
151
|
return font + 'Oblique';
|
|
102
152
|
return font + '-Oblique';
|
|
103
153
|
}
|
|
154
|
+
/** Resolve the correct font variant (regular / bold / italic / bold-italic). */
|
|
155
|
+
function resolveFont(baseFont, bold, italic) {
|
|
156
|
+
if (isCustomFont(baseFont)) {
|
|
157
|
+
const base = customFontBase(baseFont);
|
|
158
|
+
if (bold && italic)
|
|
159
|
+
return `${base}-BoldItalic`;
|
|
160
|
+
if (bold)
|
|
161
|
+
return `${base}-Bold`;
|
|
162
|
+
if (italic)
|
|
163
|
+
return `${base}-Italic`;
|
|
164
|
+
return base;
|
|
165
|
+
}
|
|
166
|
+
// Built-in PDFKit fonts — strip existing variant suffix before applying,
|
|
167
|
+
// so that e.g. 'Helvetica-Bold' + bold doesn't produce 'Helvetica-Bold-Bold'.
|
|
168
|
+
if (baseFont.startsWith('Times')) {
|
|
169
|
+
if (bold && italic)
|
|
170
|
+
return 'Times-BoldItalic';
|
|
171
|
+
if (bold)
|
|
172
|
+
return 'Times-Bold';
|
|
173
|
+
if (italic)
|
|
174
|
+
return 'Times-Italic';
|
|
175
|
+
return baseFont;
|
|
176
|
+
}
|
|
177
|
+
// Helvetica / Courier families: strip any existing variant suffix first
|
|
178
|
+
const family = baseFont.replace(/-(Bold|Oblique|BoldOblique)$/, '');
|
|
179
|
+
if (bold && italic)
|
|
180
|
+
return `${family}-BoldOblique`;
|
|
181
|
+
if (bold)
|
|
182
|
+
return `${family}-Bold`;
|
|
183
|
+
if (italic)
|
|
184
|
+
return `${family}-Oblique`;
|
|
185
|
+
return family;
|
|
186
|
+
}
|
|
104
187
|
function applyBodyFont(bold, italic) {
|
|
105
188
|
if (headingCtx) {
|
|
106
189
|
// Inside a heading — use heading style as the base.
|
|
107
190
|
let font = headingCtx.font;
|
|
108
|
-
if (italic)
|
|
109
|
-
font =
|
|
191
|
+
if (bold || italic)
|
|
192
|
+
font = resolveFont(font, bold, italic);
|
|
110
193
|
doc.font(font).fontSize(headingCtx.fontSize).fillColor(headingCtx.color);
|
|
111
194
|
return;
|
|
112
195
|
}
|
|
113
|
-
|
|
114
|
-
if (bold && italic)
|
|
115
|
-
font = 'Helvetica-BoldOblique';
|
|
116
|
-
else if (bold)
|
|
117
|
-
font = 'Helvetica-Bold';
|
|
118
|
-
else if (italic)
|
|
119
|
-
font = 'Helvetica-Oblique';
|
|
196
|
+
const font = resolveFont(theme.body.font, bold, italic);
|
|
120
197
|
doc.font(font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
|
|
121
198
|
}
|
|
122
199
|
function resetBodyFont() {
|
|
@@ -684,7 +761,7 @@ async function renderMarkdownToPdf(markdown, options) {
|
|
|
684
761
|
for (const child of t.tokens) {
|
|
685
762
|
if (child.type === 'paragraph') {
|
|
686
763
|
const p = child;
|
|
687
|
-
const font = bq.italic ?
|
|
764
|
+
const font = bq.italic ? italicVariant(theme.body.font) : theme.body.font;
|
|
688
765
|
doc.font(font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
|
|
689
766
|
doc.text('', textX, doc.y, { width: textWidth });
|
|
690
767
|
await renderInlineTokens(p.tokens, false, false, bq.italic);
|
package/dist/types.d.ts
CHANGED
|
@@ -73,6 +73,25 @@ export interface ThemeConfig {
|
|
|
73
73
|
*/
|
|
74
74
|
syntaxHighlight?: SyntaxHighlightTheme;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* A custom font definition providing font data for registration with PDFKit.
|
|
78
|
+
*
|
|
79
|
+
* Supply at minimum a `name` and `regular` buffer. Missing bold / italic /
|
|
80
|
+
* bold-italic variants fall back: boldItalic → bold → regular,
|
|
81
|
+
* italic → regular, bold → regular.
|
|
82
|
+
*/
|
|
83
|
+
export interface CustomFontDefinition {
|
|
84
|
+
/** The name to use in ThemeConfig font fields (e.g. 'Roboto'). */
|
|
85
|
+
name: string;
|
|
86
|
+
/** Regular weight font data. */
|
|
87
|
+
regular: Buffer;
|
|
88
|
+
/** Bold variant (falls back to regular). */
|
|
89
|
+
bold?: Buffer;
|
|
90
|
+
/** Italic variant (falls back to regular). */
|
|
91
|
+
italic?: Buffer;
|
|
92
|
+
/** Bold-italic variant (falls back to bold or regular). */
|
|
93
|
+
boldItalic?: Buffer;
|
|
94
|
+
}
|
|
76
95
|
/** Converts a single emoji string to a PNG `Buffer`. */
|
|
77
96
|
export type ColorEmojiRenderer = (emoji: string) => Promise<Buffer>;
|
|
78
97
|
export interface PdfOptions {
|
|
@@ -146,4 +165,12 @@ export interface PdfOptions {
|
|
|
146
165
|
* monochrome font or the body font.
|
|
147
166
|
*/
|
|
148
167
|
colorEmoji?: ColorEmojiRenderer;
|
|
168
|
+
/**
|
|
169
|
+
* Custom font definitions to register with PDFKit.
|
|
170
|
+
*
|
|
171
|
+
* Each entry provides font data (as `Buffer`s) for a named font family
|
|
172
|
+
* with optional bold, italic, and bold-italic variants. The `name` can
|
|
173
|
+
* then be used in any `ThemeConfig` font field (e.g. `body.font`).
|
|
174
|
+
*/
|
|
175
|
+
customFonts?: CustomFontDefinition[];
|
|
149
176
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speajus/markdown-to-pdf",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "A new project created with Intent by Augment.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
"README.md"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
+
"docs:dev": "cd docs && $npm_execpath dev",
|
|
32
33
|
"build": "tsc && mkdir -p dist/fonts && cp src/fonts/* dist/fonts/",
|
|
33
34
|
"generate": "tsx samples/generate.ts",
|
|
34
|
-
"
|
|
35
|
+
"readme": "pnpm tsx ./src/cli.ts README.md README.pdf",
|
|
36
|
+
"test": "tsx --test test/*.test.ts && tsx samples/generate.ts && pnpm run readme"
|
|
35
37
|
},
|
|
36
38
|
"keywords": [
|
|
37
39
|
"markdown",
|