@speajus/markdown-to-pdf 1.0.17 → 1.0.19

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
@@ -14,6 +14,7 @@ A lightweight TypeScript library that converts Markdown files into styled PDF do
14
14
  - **Tables** with header row highlighting and cell borders
15
15
  - **Links** rendered as clickable PDF hyperlinks
16
16
  - **Images** — local (PNG, JPEG) and remote (HTTP/HTTPS) with automatic SVG-to-PNG conversion via [@resvg/resvg-js](https://github.com/nicolo-ribaudo/resvg-js)
17
+ - **Mermaid diagrams** — render `mermaid` fenced code blocks as diagrams (flowchart, sequence, class, state, ER, gantt, pie, mindmap) via [@speajus/mermaid-to-svg](https://github.com/speajus/mermaid-to-svg)
17
18
  - **Horizontal rules**
18
19
  - **Automatic page breaks** when content exceeds the current page
19
20
  - **Fully themeable** — customize fonts, colors, spacing, page size, and margins
@@ -92,6 +93,50 @@ interface PdfOptions {
92
93
  }
93
94
  ```
94
95
 
96
+ ### Mermaid Diagrams
97
+
98
+ Fenced code blocks with the language `mermaid` are automatically rendered as diagrams:
99
+
100
+ ````markdown
101
+ ```mermaid
102
+ flowchart TD
103
+ A[Start] --> B{Decision}
104
+ B -->|Yes| C[Process A]
105
+ B -->|No| D[Process B]
106
+ C --> E[End]
107
+ D --> E
108
+ ```
109
+ ````
110
+
111
+ Mermaid theme colors are integrated with the PDF theme. Each built-in theme includes matching mermaid colors. You can also customize them:
112
+
113
+ ```typescript
114
+ await generatePdf(markdown, {
115
+ theme: {
116
+ ...defaultTheme,
117
+ // Use a built-in mermaid theme
118
+ mermaid: 'forest', // 'default' | 'dark' | 'forest' | 'neutral'
119
+ },
120
+ });
121
+
122
+ // Or provide custom mermaid colors
123
+ await generatePdf(markdown, {
124
+ theme: {
125
+ ...defaultTheme,
126
+ mermaid: {
127
+ background: '#ffffff',
128
+ primaryColor: '#4a90d9',
129
+ secondaryColor: '#b8d4f0',
130
+ lineColor: '#333333',
131
+ primaryTextColor: '#1a1a1a',
132
+ borderColor: '#4a90d9',
133
+ },
134
+ },
135
+ });
136
+ ```
137
+
138
+ Supported diagram types: flowchart, sequence, class, state, ER, gantt, pie, and mindmap.
139
+
95
140
  ### Page Layout
96
141
 
97
142
  ```typescript
@@ -138,19 +183,23 @@ The full `ThemeConfig` interface exposes styles for:
138
183
  | table | Header background, border color, cell padding, and zebra color |
139
184
  | linkColor | Color for hyperlink text |
140
185
  | horizontalRuleColor | Color for --- dividers |
186
+ | mermaid | Diagram colors: primary, secondary, text, line, border, background |
141
187
 
142
188
  ## Project Structure
143
189
 
144
190
  ```
145
191
  ├── src/
146
- │ ├── index.ts # Public API — generatePdf, renderMarkdownToPdf, exports
147
- │ ├── cli.ts # Command-line entry point
148
- │ ├── renderer.ts # Markdown-to-PDF rendering engine
149
- │ ├── styles.ts # Default theme and page layout
150
- └── types.ts # TypeScript interfaces for options and theming
192
+ │ ├── index.ts # Public API — generatePdf, renderMarkdownToPdf, exports
193
+ │ ├── cli.ts # Command-line entry point
194
+ │ ├── renderer.ts # Markdown-to-PDF rendering engine
195
+ │ ├── mermaid-renderer.ts # Mermaid diagram PNG rendering
196
+ ├── styles.ts # Default theme and page layout
197
+ │ ├── themes/index.ts # Built-in theme variants
198
+ │ └── types.ts # TypeScript interfaces for options and theming
151
199
  ├── samples/
152
200
  │ ├── generate.ts # Script to batch-generate sample PDFs
153
201
  │ ├── sample.md # Full-featured sample document
202
+ │ ├── mermaid.md # Mermaid diagram examples (all diagram types)
154
203
  │ ├── image.md # Image rendering tests (local, remote, SVG, broken)
155
204
  │ ├── logo.svg # Sample SVG image
156
205
  │ ├── logo.png # Sample PNG image
@@ -167,6 +216,7 @@ The full `ThemeConfig` interface exposes styles for:
167
216
  | marked | Markdown parsing and tokenization |
168
217
  | pdfkit | PDF document generation |
169
218
  | @resvg/resvg-js | SVG-to-PNG rasterization for image embedding |
219
+ | @speajus/mermaid-to-svg | Mermaid diagram rendering (parse → layout → SVG) |
170
220
 
171
221
  ## Scripts
172
222
 
package/README.pdf CHANGED
Binary file
@@ -96,6 +96,7 @@ function renderSvgToPng(svgBuffer) {
96
96
  img.src = url;
97
97
  });
98
98
  }
99
+ defaults_js_1.DEFAULTS.renderSvg = (svg) => renderSvgToPng(Buffer.from(svg));
99
100
  function isSvg(buf) {
100
101
  const head = buf.subarray(0, 256).toString('utf-8').trimStart();
101
102
  return head.startsWith('<svg') || head.startsWith('<?xml');
package/dist/browser.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Browser-specific entry point that excludes Node.js dependencies
3
3
  */
4
4
  export { createBrowserImageRenderer } from "./browser-image-renderer.js";
5
- export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, SpacingConfig, ThemeConfig, PdfOptions, CustomFontDefinition, } from './types.js';
5
+ export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, SpacingConfig, ThemeConfig, PdfOptions, CustomFontDefinition, MermaidThemeConfig, } from './types.js';
6
6
  export { defaultTheme, defaultPageLayout, defaultSyntaxHighlightTheme, defaultSpacing } from './styles.js';
7
7
  export { themes, modernTheme, academicTheme, minimalTheme, oceanTheme } from './themes/index.js';
8
8
  export { renderMarkdownToPdf } from "./renderer.js";
@@ -1,3 +1,8 @@
1
+ export type SvgOptions = {
2
+ width?: number;
3
+ height?: number;
4
+ };
1
5
  export declare const DEFAULTS: {
2
6
  renderImage: (basePath: string) => (imageUrl: string) => Promise<Buffer>;
7
+ renderSvg: (svg: string, options?: SvgOptions) => Promise<Buffer>;
3
8
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, SpacingConfig, ThemeConfig, PdfOptions, CustomFontDefinition, } from './types.js';
1
+ export type { TextStyle, PageLayout, CodeStyle, CodeBlockStyle, BlockquoteStyle, TableStyles, TokenColors, SyntaxHighlightTheme, SpacingConfig, ThemeConfig, PdfOptions, CustomFontDefinition, MermaidThemeConfig, } from './types.js';
2
2
  export { defaultTheme, defaultPageLayout, defaultSyntaxHighlightTheme, defaultSpacing } from './styles.js';
3
3
  export { renderMarkdownToPdf as generatePdf, renderMarkdownToPdf, } from "./renderer.js";
4
4
  export { createBrowserImageRenderer } from './browser-image-renderer.js';
@@ -0,0 +1,13 @@
1
+ import type { MermaidThemeConfig } from './types.js';
2
+ /**
3
+ * Render a mermaid diagram string to a PNG buffer.
4
+ *
5
+ * Uses `@speajus/mermaid-to-svg` to produce SVG, then `@resvg/resvg-js` to
6
+ * rasterise to PNG so PDFKit can embed it.
7
+ */
8
+ export declare function renderMermaidToPng(mermaidCode: string, themeConfig?: MermaidThemeConfig | 'default' | 'dark' | 'forest' | 'neutral'): Promise<Buffer>;
9
+ /**
10
+ * Call this once when you are done rendering all mermaid diagrams to
11
+ * release the jsdom window that `@speajus/mermaid-to-svg` creates internally.
12
+ */
13
+ export declare function cleanupMermaid(): Promise<void>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderMermaidToPng = renderMermaidToPng;
4
+ exports.cleanupMermaid = cleanupMermaid;
5
+ const defaults_js_1 = require("./defaults.js");
6
+ let mermaidModule = null;
7
+ async function loadMermaid() {
8
+ if (!mermaidModule) {
9
+ mermaidModule = await import('@speajus/mermaid-to-svg');
10
+ }
11
+ return mermaidModule;
12
+ }
13
+ /**
14
+ * Resolve a mermaid theme config (string name or partial object) into
15
+ * a `Theme` object from `@speajus/mermaid-to-svg`.
16
+ */
17
+ function resolveTheme(mermaidMod, config) {
18
+ if (!config || config === 'default')
19
+ return mermaidMod.defaultTheme;
20
+ if (config === 'dark')
21
+ return mermaidMod.darkTheme;
22
+ if (config === 'forest')
23
+ return mermaidMod.forestTheme;
24
+ if (config === 'neutral')
25
+ return mermaidMod.neutralTheme;
26
+ // Custom partial config — merge onto default
27
+ return mermaidMod.createTheme(config);
28
+ }
29
+ /**
30
+ * Render a mermaid diagram string to a PNG buffer.
31
+ *
32
+ * Uses `@speajus/mermaid-to-svg` to produce SVG, then `@resvg/resvg-js` to
33
+ * rasterise to PNG so PDFKit can embed it.
34
+ */
35
+ async function renderMermaidToPng(mermaidCode, themeConfig) {
36
+ const mod = await loadMermaid();
37
+ const theme = resolveTheme(mod, themeConfig);
38
+ const result = await mod.renderMermaid(mermaidCode, { theme });
39
+ // Convert SVG → PNG via resvg
40
+ return defaults_js_1.DEFAULTS.renderSvg(result.svg);
41
+ }
42
+ /**
43
+ * Call this once when you are done rendering all mermaid diagrams to
44
+ * release the jsdom window that `@speajus/mermaid-to-svg` creates internally.
45
+ */
46
+ async function cleanupMermaid() {
47
+ if (mermaidModule) {
48
+ mermaidModule.cleanup();
49
+ }
50
+ }
@@ -15,8 +15,8 @@ function isSvg(buf) {
15
15
  const head = buf.subarray(0, 256).toString('utf-8').trimStart();
16
16
  return head.startsWith('<svg') || head.startsWith('<?xml');
17
17
  }
18
- function convertSvgToPng(svgData) {
19
- const resvg = new resvg_js_1.Resvg(svgData, { font: { loadSystemFonts: true } });
18
+ function convertSvgToPng(svgData, options) {
19
+ const resvg = new resvg_js_1.Resvg(svgData, { font: { loadSystemFonts: true }, ...options });
20
20
  const rendered = resvg.render();
21
21
  return Buffer.from(rendered.asPng());
22
22
  }
@@ -60,7 +60,7 @@ function fetchImageBuffer(url, redirectCount = 0) {
60
60
  * @returns A function that takes an image URL/path and returns a Buffer
61
61
  */
62
62
  function createNodeImageRenderer(basePath = process.cwd()) {
63
- return async (imageUrl) => {
63
+ return async (imageUrl, options) => {
64
64
  let imgBuffer;
65
65
  if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
66
66
  // Remote image - fetch via HTTP/HTTPS
@@ -73,9 +73,10 @@ function createNodeImageRenderer(basePath = process.cwd()) {
73
73
  }
74
74
  // Convert SVG to PNG since pdfkit doesn't support SVG natively
75
75
  if (isSvg(imgBuffer)) {
76
- imgBuffer = convertSvgToPng(imgBuffer);
76
+ imgBuffer = convertSvgToPng(imgBuffer, options);
77
77
  }
78
78
  return imgBuffer;
79
79
  };
80
80
  }
81
81
  defaults_1.DEFAULTS.renderImage = createNodeImageRenderer;
82
+ defaults_1.DEFAULTS.renderSvg = async (svg) => convertSvgToPng(Buffer.from(svg));
package/dist/renderer.js CHANGED
@@ -537,7 +537,9 @@ async function renderMarkdownToPdf(markdown, options) {
537
537
  const zebraColor = theme.table.zebraColor ?? '#f9f9f9';
538
538
  for (let r = 0; r < table.rows.length; r++) {
539
539
  const row = table.rows[r];
540
+ doc.y = y; // sync doc.y BEFORE ensureSpace check
540
541
  ensureSpace(rowH);
542
+ y = doc.y; // re-sync AFTER possible page break
541
543
  // Zebra stripe: fill even rows (0-indexed, so odd visual rows) with a tinted background
542
544
  if (zebraStripes && r % 2 === 1) {
543
545
  doc.save();
@@ -608,6 +610,39 @@ async function renderMarkdownToPdf(markdown, options) {
608
610
  case 'code': {
609
611
  const t = token;
610
612
  const cs = theme.code.block;
613
+ // ── Mermaid diagrams ──────────────────────────────────────────────
614
+ if (t.lang === 'mermaid') {
615
+ try {
616
+ const mermaidTheme = theme.mermaid;
617
+ const { renderMermaidToPng } = await import('./mermaid-renderer.js');
618
+ const pngBuf = await renderMermaidToPng(t.text, mermaidTheme);
619
+ const img = doc.openImage(pngBuf);
620
+ const maxHeight = doc.page.height - margins.top - margins.bottom;
621
+ let displayWidth = Math.min(img.width, contentWidth);
622
+ let displayHeight = img.height * (displayWidth / img.width);
623
+ if (displayHeight > maxHeight) {
624
+ displayHeight = maxHeight;
625
+ displayWidth = img.width * (displayHeight / img.height);
626
+ }
627
+ ensureSpace(displayHeight + 10);
628
+ const imgX = theme.imageAlign === 'center'
629
+ ? margins.left + (contentWidth - displayWidth) / 2
630
+ : doc.x;
631
+ const imgY = doc.y;
632
+ doc.image(pngBuf, imgX, imgY, { width: displayWidth, height: displayHeight });
633
+ // Advance past the image — doc.image() does not move doc.y
634
+ doc.y = imgY + displayHeight;
635
+ doc.moveDown(0.5);
636
+ }
637
+ catch (err) {
638
+ // Fallback: render as plain code block on error
639
+ ensureSpace(20);
640
+ resetBodyFont();
641
+ doc.text(`[Mermaid diagram error: ${err.message}]`);
642
+ doc.moveDown(0.3);
643
+ }
644
+ break;
645
+ }
611
646
  // Use syntax highlighting when a language is specified and highlighting is enabled
612
647
  if (syntaxHighlight && t.lang) {
613
648
  const lines = t.text.split('\n');
@@ -741,6 +776,14 @@ async function renderMarkdownToPdf(markdown, options) {
741
776
  for (const token of tokens) {
742
777
  await renderToken(token);
743
778
  }
779
+ // Clean up mermaid jsdom window if it was used
780
+ try {
781
+ const { cleanupMermaid } = await import('./mermaid-renderer.js');
782
+ await cleanupMermaid();
783
+ }
784
+ catch {
785
+ // mermaid-renderer may not be available in browser builds
786
+ }
744
787
  doc.end();
745
788
  return new Promise((resolve) => {
746
789
  stream.on('end', () => resolve(Buffer.concat(chunks)));
package/dist/styles.js CHANGED
@@ -95,6 +95,7 @@ exports.defaultTheme = {
95
95
  spacing: { ...exports.defaultSpacing },
96
96
  imageAlign: 'left',
97
97
  emojiFont: 'twemoji',
98
+ mermaid: 'default',
98
99
  };
99
100
  exports.defaultPageLayout = {
100
101
  pageSize: 'LETTER',
@@ -42,6 +42,16 @@ exports.modernTheme = {
42
42
  spacing: { ...styles_js_1.defaultSpacing },
43
43
  imageAlign: 'left',
44
44
  emojiFont: 'twemoji',
45
+ mermaid: {
46
+ background: '#ffffff',
47
+ primaryColor: '#0d7377',
48
+ secondaryColor: '#b2dfdb',
49
+ tertiaryColor: '#e0f2f1',
50
+ primaryTextColor: '#2d3436',
51
+ secondaryTextColor: '#636e72',
52
+ lineColor: '#0984e3',
53
+ borderColor: '#14919b',
54
+ },
45
55
  };
46
56
  /** Academic — serif fonts, formal look inspired by LaTeX */
47
57
  exports.academicTheme = {
@@ -81,6 +91,16 @@ exports.academicTheme = {
81
91
  spacing: { ...styles_js_1.defaultSpacing },
82
92
  imageAlign: 'left',
83
93
  emojiFont: 'twemoji',
94
+ mermaid: {
95
+ background: '#ffffff',
96
+ primaryColor: '#eaecee',
97
+ secondaryColor: '#d5dbdb',
98
+ tertiaryColor: '#f2f3f4',
99
+ primaryTextColor: '#1a1a2e',
100
+ secondaryTextColor: '#7f8c8d',
101
+ lineColor: '#2e4057',
102
+ borderColor: '#6c3483',
103
+ },
84
104
  };
85
105
  /** Minimal — lots of whitespace, muted greys */
86
106
  exports.minimalTheme = {
@@ -120,6 +140,7 @@ exports.minimalTheme = {
120
140
  spacing: { ...styles_js_1.defaultSpacing },
121
141
  imageAlign: 'left',
122
142
  emojiFont: 'twemoji',
143
+ mermaid: 'neutral',
123
144
  };
124
145
  /** Ocean — deep blue palette */
125
146
  exports.oceanTheme = {
@@ -159,6 +180,16 @@ exports.oceanTheme = {
159
180
  spacing: { ...styles_js_1.defaultSpacing },
160
181
  imageAlign: 'left',
161
182
  emojiFont: 'twemoji',
183
+ mermaid: {
184
+ background: '#ffffff',
185
+ primaryColor: '#d4e6f1',
186
+ secondaryColor: '#aed6f1',
187
+ tertiaryColor: '#eaf2f8',
188
+ primaryTextColor: '#1b2631',
189
+ secondaryTextColor: '#5d6d7e',
190
+ lineColor: '#2471a3',
191
+ borderColor: '#2980b9',
192
+ },
162
193
  };
163
194
  /** Record of all built-in themes keyed by display name */
164
195
  exports.themes = {
package/dist/types.d.ts CHANGED
@@ -96,6 +96,42 @@ export interface ThemeConfig {
96
96
  * - `'none'` — disable emoji font; emoji render with the body font.
97
97
  */
98
98
  emojiFont?: 'twemoji' | 'openmoji' | 'noto' | 'none';
99
+ /**
100
+ * Mermaid diagram theme configuration.
101
+ *
102
+ * Controls how mermaid fenced code blocks are rendered as diagrams in the PDF.
103
+ * - `'default'` | `'dark'` | `'forest'` | `'neutral'` — use a built-in mermaid theme.
104
+ * - A `MermaidThemeConfig` object — provide custom colors for the diagram.
105
+ * - `undefined` — uses `'default'`.
106
+ */
107
+ mermaid?: MermaidThemeConfig | 'default' | 'dark' | 'forest' | 'neutral';
108
+ }
109
+ /**
110
+ * Custom mermaid diagram theme colors.
111
+ *
112
+ * Maps to the `Theme` interface from `@speajus/mermaid-to-svg`.
113
+ */
114
+ export interface MermaidThemeConfig {
115
+ /** Diagram background color */
116
+ background?: string;
117
+ /** Primary node fill color */
118
+ primaryColor?: string;
119
+ /** Secondary node fill color */
120
+ secondaryColor?: string;
121
+ /** Tertiary node fill color */
122
+ tertiaryColor?: string;
123
+ /** Primary text color */
124
+ primaryTextColor?: string;
125
+ /** Secondary text color */
126
+ secondaryTextColor?: string;
127
+ /** Line / arrow color */
128
+ lineColor?: string;
129
+ /** Node border color */
130
+ borderColor?: string;
131
+ /** Font family for diagram labels */
132
+ fontFamily?: string;
133
+ /** Base font size */
134
+ fontSize?: number;
99
135
  }
100
136
  /**
101
137
  * A custom font definition providing font data for registration with PDFKit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speajus/markdown-to-pdf",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "A new project created with Intent by Augment.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,6 +51,7 @@
51
51
  "type": "commonjs",
52
52
  "dependencies": {
53
53
  "@resvg/resvg-js": "^2.6.2",
54
+ "@speajus/mermaid-to-svg": "^0.1.5",
54
55
  "marked": "^17.0.2",
55
56
  "pdfkit": "github:jspears/pdfkit#support-color-emoji-google",
56
57
  "prismjs": "^1.30.0"
@@ -68,5 +69,6 @@
68
69
  "overrides": {
69
70
  "canvas": "^3.2.1"
70
71
  }
71
- }
72
+ },
73
+ "packageManager": "pnpm@9.2.0+sha512.98a80fd11c2e7096747762304106432b3ddc67dcf54b5a8c01c93f68a2cd5e05e6821849522a06fb76284d41a2660d5e334f2ee3bbf29183bf2e739b1dafa771"
72
74
  }