@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 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
- [![Live Demo](./docs/image.png)](https://speajus.github.io/markdown-to-pdf/)
5
+ -[![Live Demo](./docs/image.png)]([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 | 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 |
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
- | [marked](https://github.com/markedjs/marked) | Markdown parsing and tokenization |
168
- | [pdfkit](https://pdfkit.org/) | PDF document generation |
169
- | [@resvg/resvg-js](https://github.com/nicolo-ribaudo/resvg-js) | SVG-to-PNG rasterization for image embedding |
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
- | `npm run build` | Compile TypeScript to `dist/` |
176
- | `npm run generate` | Generate sample PDFs from `samples/*.md` into `output/` |
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
- /** Derive the italic variant of a PDFKit built-in font name. */
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 = italicVariant(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
- let font = theme.body.font;
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 ? 'Helvetica-Oblique' : theme.body.font;
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.12",
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
- "test": "tsx --test test/*.test.ts && tsx samples/generate.ts && tsx ./src/cli.ts README.md README.pdf"
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",