@speajus/markdown-to-pdf 1.0.18 → 1.0.20

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
@@ -411,6 +411,21 @@ async function renderMarkdownToPdf(markdown, options) {
411
411
  doc.moveDown(0.5);
412
412
  break;
413
413
  }
414
+ case 'html': {
415
+ const htmlText = (tok.text ?? tok.raw ?? '').trim();
416
+ if (/^<br\s*\/?>$/i.test(htmlText)) {
417
+ doc.moveDown(0.5);
418
+ }
419
+ else {
420
+ // Non-br HTML: render as text (fall through to default behavior)
421
+ const raw = tok.text ?? tok.raw ?? '';
422
+ if (raw) {
423
+ applyBodyFont(insideBold, insideItalic);
424
+ renderText(raw, { continued: cont, underline: false, strike: false });
425
+ }
426
+ }
427
+ break;
428
+ }
414
429
  default: {
415
430
  const raw = tok.text ?? tok.raw ?? '';
416
431
  if (raw) {
@@ -449,6 +464,7 @@ async function renderMarkdownToPdf(markdown, options) {
449
464
  if (linkUrl) {
450
465
  doc.link(imgX, imgY, displayWidth, displayHeight, linkUrl);
451
466
  }
467
+ doc.y = imgY + displayHeight;
452
468
  doc.moveDown(0.5);
453
469
  }
454
470
  catch {
@@ -504,39 +520,101 @@ async function renderMarkdownToPdf(markdown, options) {
504
520
  }
505
521
  doc.y = savedY;
506
522
  }
523
+ /**
524
+ * Extract plain text from cell tokens, converting <br> HTML tokens to \n
525
+ * so that heightOfString can measure the full multiline content.
526
+ */
527
+ function cellPlainText(cell) {
528
+ if (!cell.tokens || cell.tokens.length === 0)
529
+ return cell.text;
530
+ function extract(tokens) {
531
+ let result = '';
532
+ for (const tok of tokens) {
533
+ if (tok.type === 'br') {
534
+ result += '\n';
535
+ }
536
+ else if (tok.type === 'html') {
537
+ const raw = (tok.text ?? tok.raw ?? '').trim();
538
+ if (/^<br\s*\/?>$/i.test(raw)) {
539
+ result += '\n';
540
+ }
541
+ else {
542
+ result += tok.text ?? tok.raw ?? '';
543
+ }
544
+ }
545
+ else if (tok.tokens && tok.tokens.length > 0) {
546
+ result += extract(tok.tokens);
547
+ }
548
+ else {
549
+ result += tok.text ?? tok.raw ?? '';
550
+ }
551
+ }
552
+ return result;
553
+ }
554
+ return extract(cell.tokens);
555
+ }
556
+ /**
557
+ * Measure the height a cell needs given its content and available width.
558
+ */
559
+ function measureCellHeight(cell, cellWidth, bold) {
560
+ const text = cellPlainText(cell);
561
+ const font = bold
562
+ ? safeFont(resolveFont(theme.body.font, true, false))
563
+ : safeFont(theme.body.font);
564
+ doc.font(font).fontSize(theme.body.fontSize);
565
+ return doc.heightOfString(text, { width: cellWidth });
566
+ }
507
567
  async function renderTable(table) {
508
568
  const colCount = table.header.length;
509
569
  if (colCount === 0)
510
570
  return;
511
571
  const cellPad = theme.table.cellPadding;
512
572
  const colWidth = contentWidth / colCount;
513
- const rowH = theme.body.fontSize + cellPad * 2 + 4;
514
- const textInsetY = (rowH - theme.body.fontSize) / 2;
515
- ensureSpace(rowH * 2);
573
+ const minRowH = theme.body.fontSize + cellPad * 2 + 4;
574
+ const textWidth = colWidth - cellPad * 2;
575
+ ensureSpace(minRowH * 2);
516
576
  const startX = margins.left;
517
577
  let y = doc.y;
518
- // Header row
578
+ // ── Measure header row height ──
579
+ let headerH = minRowH;
580
+ let maxHeaderTextHeight = 0;
581
+ for (let c = 0; c < colCount; c++) {
582
+ const h = measureCellHeight(table.header[c], textWidth, true);
583
+ maxHeaderTextHeight = Math.max(maxHeaderTextHeight, h);
584
+ headerH = Math.max(headerH, h + cellPad * 2 + 4);
585
+ }
586
+ const headerTextInsetY = (headerH - maxHeaderTextHeight) / 2;
587
+ // Header row background
519
588
  doc.save();
520
- doc.rect(startX, y, contentWidth, rowH).fill(theme.table.headerBackground);
589
+ doc.rect(startX, y, contentWidth, headerH).fill(theme.table.headerBackground);
521
590
  doc.restore();
522
591
  for (let c = 0; c < colCount; c++) {
523
592
  const cellX = startX + c * colWidth;
524
- await renderCellTokens(table.header[c], cellX + cellPad, y + textInsetY, colWidth - cellPad * 2, table.align[c] || 'left', true);
593
+ await renderCellTokens(table.header[c], cellX + cellPad, y + headerTextInsetY, textWidth, table.align[c] || 'left', true);
525
594
  }
526
595
  // Header border
527
596
  doc.save();
528
597
  doc.strokeColor(theme.table.borderColor).lineWidth(0.5);
529
- doc.rect(startX, y, contentWidth, rowH).stroke();
598
+ doc.rect(startX, y, contentWidth, headerH).stroke();
530
599
  for (let c = 1; c < colCount; c++) {
531
600
  const cx = startX + c * colWidth;
532
- doc.moveTo(cx, y).lineTo(cx, y + rowH).stroke();
601
+ doc.moveTo(cx, y).lineTo(cx, y + headerH).stroke();
533
602
  }
534
603
  doc.restore();
535
- y += rowH;
604
+ y += headerH;
536
605
  // Body rows
537
606
  const zebraColor = theme.table.zebraColor ?? '#f9f9f9';
538
607
  for (let r = 0; r < table.rows.length; r++) {
539
608
  const row = table.rows[r];
609
+ // ── Measure row height ──
610
+ let rowH = minRowH;
611
+ let maxRowTextHeight = 0;
612
+ for (let c = 0; c < colCount; c++) {
613
+ const h = measureCellHeight(row[c], textWidth, false);
614
+ maxRowTextHeight = Math.max(maxRowTextHeight, h);
615
+ rowH = Math.max(rowH, h + cellPad * 2 + 4);
616
+ }
617
+ const textInsetY = (rowH - maxRowTextHeight) / 2;
540
618
  doc.y = y; // sync doc.y BEFORE ensureSpace check
541
619
  ensureSpace(rowH);
542
620
  y = doc.y; // re-sync AFTER possible page break
@@ -548,7 +626,7 @@ async function renderMarkdownToPdf(markdown, options) {
548
626
  }
549
627
  for (let c = 0; c < colCount; c++) {
550
628
  const cellX = startX + c * colWidth;
551
- await renderCellTokens(row[c], cellX + cellPad, y + textInsetY, colWidth - cellPad * 2, table.align[c] || 'left', false);
629
+ await renderCellTokens(row[c], cellX + cellPad, y + textInsetY, textWidth, table.align[c] || 'left', false);
552
630
  }
553
631
  doc.save();
554
632
  doc.strokeColor(theme.table.borderColor).lineWidth(0.5);
@@ -610,6 +688,39 @@ async function renderMarkdownToPdf(markdown, options) {
610
688
  case 'code': {
611
689
  const t = token;
612
690
  const cs = theme.code.block;
691
+ // ── Mermaid diagrams ──────────────────────────────────────────────
692
+ if (t.lang === 'mermaid') {
693
+ try {
694
+ const mermaidTheme = theme.mermaid;
695
+ const { renderMermaidToPng } = await import('./mermaid-renderer.js');
696
+ const pngBuf = await renderMermaidToPng(t.text, mermaidTheme);
697
+ const img = doc.openImage(pngBuf);
698
+ const maxHeight = doc.page.height - margins.top - margins.bottom;
699
+ let displayWidth = Math.min(img.width, contentWidth);
700
+ let displayHeight = img.height * (displayWidth / img.width);
701
+ if (displayHeight > maxHeight) {
702
+ displayHeight = maxHeight;
703
+ displayWidth = img.width * (displayHeight / img.height);
704
+ }
705
+ ensureSpace(displayHeight + 10);
706
+ const imgX = theme.imageAlign === 'center'
707
+ ? margins.left + (contentWidth - displayWidth) / 2
708
+ : doc.x;
709
+ const imgY = doc.y;
710
+ doc.image(pngBuf, imgX, imgY, { width: displayWidth, height: displayHeight });
711
+ // Advance past the image — doc.image() does not move doc.y
712
+ doc.y = imgY + displayHeight;
713
+ doc.moveDown(0.5);
714
+ }
715
+ catch (err) {
716
+ // Fallback: render as plain code block on error
717
+ ensureSpace(20);
718
+ resetBodyFont();
719
+ doc.text(`[Mermaid diagram error: ${err.message}]`);
720
+ doc.moveDown(0.3);
721
+ }
722
+ break;
723
+ }
613
724
  // Use syntax highlighting when a language is specified and highlighting is enabled
614
725
  if (syntaxHighlight && t.lang) {
615
726
  const lines = t.text.split('\n');
@@ -743,6 +854,14 @@ async function renderMarkdownToPdf(markdown, options) {
743
854
  for (const token of tokens) {
744
855
  await renderToken(token);
745
856
  }
857
+ // Clean up mermaid jsdom window if it was used
858
+ try {
859
+ const { cleanupMermaid } = await import('./mermaid-renderer.js');
860
+ await cleanupMermaid();
861
+ }
862
+ catch {
863
+ // mermaid-renderer may not be available in browser builds
864
+ }
746
865
  doc.end();
747
866
  return new Promise((resolve) => {
748
867
  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.18",
3
+ "version": "1.0.20",
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"