@speajus/markdown-to-pdf 1.0.11 → 1.0.13

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
@@ -88,6 +88,7 @@ interface PdfOptions {
88
88
  syntaxHighlight?: boolean; // Enable syntax highlighting (default: true)
89
89
  lineNumbers?: boolean; // Show line numbers in code blocks (default: false)
90
90
  languages?: string[]; // Prism.js languages to load (default: all)
91
+ zebraStripes?: boolean; // Zebra-stripe table rows (default: true)
91
92
  }
92
93
  ```
93
94
 
@@ -134,7 +135,7 @@ The full `ThemeConfig` interface exposes styles for:
134
135
  | `code.inline` | Font, size, color, and background color |
135
136
  | `code.block` | Font, size, color, background color, and padding |
136
137
  | `blockquote` | Border color, border width, italic flag, and indent |
137
- | `table` | Header background, border color, and cell padding |
138
+ | `table` | Header background, border color, cell padding, and zebra color |
138
139
  | `linkColor` | Color for hyperlink text |
139
140
  | `horizontalRuleColor` | Color for `---` dividers |
140
141
 
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/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
@@ -23,6 +23,7 @@ async function renderMarkdownToPdf(markdown, options) {
23
23
  (0, highlight_prism_js_1.loadHighlightLanguages)(options?.languages);
24
24
  }
25
25
  const lineNumbers = options?.lineNumbers ?? false;
26
+ const zebraStripes = options?.zebraStripes !== false;
26
27
  const emojiFontOpt = options?.emojiFont ?? true;
27
28
  // Use provided image renderer or create default Node.js renderer
28
29
  const imageRenderer = options?.renderImage ?? defaults_js_1.DEFAULTS.renderImage(basePath);
@@ -66,6 +67,26 @@ async function renderMarkdownToPdf(markdown, options) {
66
67
  // fall through without emoji support.
67
68
  }
68
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
+ }
69
90
  // ── Color emoji pre-render ─────────────────────────────────────────────
70
91
  // When a colorEmoji renderer is provided, pre-scan the entire markdown for
71
92
  // every unique emoji and convert them all to PNG buffers up-front. This
@@ -91,8 +112,38 @@ async function renderMarkdownToPdf(markdown, options) {
91
112
  doc.addPage();
92
113
  }
93
114
  }
94
- /** 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. */
95
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
96
147
  if (font.startsWith('Times'))
97
148
  return font.replace(/-Bold$/, '-BoldItalic').replace(/^Times-Roman$/, 'Times-Italic');
98
149
  // Helvetica / Courier families use "Oblique"
@@ -100,22 +151,49 @@ async function renderMarkdownToPdf(markdown, options) {
100
151
  return font + 'Oblique';
101
152
  return font + '-Oblique';
102
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
+ }
103
187
  function applyBodyFont(bold, italic) {
104
188
  if (headingCtx) {
105
189
  // Inside a heading — use heading style as the base.
106
190
  let font = headingCtx.font;
107
- if (italic)
108
- font = italicVariant(font);
191
+ if (bold || italic)
192
+ font = resolveFont(font, bold, italic);
109
193
  doc.font(font).fontSize(headingCtx.fontSize).fillColor(headingCtx.color);
110
194
  return;
111
195
  }
112
- let font = theme.body.font;
113
- if (bold && italic)
114
- font = 'Helvetica-BoldOblique';
115
- else if (bold)
116
- font = 'Helvetica-Bold';
117
- else if (italic)
118
- font = 'Helvetica-Oblique';
196
+ const font = resolveFont(theme.body.font, bold, italic);
119
197
  doc.font(font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
120
198
  }
121
199
  function resetBodyFont() {
@@ -549,8 +627,16 @@ async function renderMarkdownToPdf(markdown, options) {
549
627
  doc.restore();
550
628
  y += rowH;
551
629
  // Body rows
552
- for (const row of table.rows) {
630
+ const zebraColor = theme.table.zebraColor ?? '#f9f9f9';
631
+ for (let r = 0; r < table.rows.length; r++) {
632
+ const row = table.rows[r];
553
633
  ensureSpace(rowH);
634
+ // Zebra stripe: fill even rows (0-indexed, so odd visual rows) with a tinted background
635
+ if (zebraStripes && r % 2 === 1) {
636
+ doc.save();
637
+ doc.rect(startX, y, contentWidth, rowH).fill(zebraColor);
638
+ doc.restore();
639
+ }
554
640
  for (let c = 0; c < colCount; c++) {
555
641
  const cellX = startX + c * colWidth;
556
642
  await renderCellTokens(row[c], cellX + cellPad, y + textInsetY, colWidth - cellPad * 2, table.align[c] || 'left', false);
@@ -675,7 +761,7 @@ async function renderMarkdownToPdf(markdown, options) {
675
761
  for (const child of t.tokens) {
676
762
  if (child.type === 'paragraph') {
677
763
  const p = child;
678
- const font = bq.italic ? 'Helvetica-Oblique' : theme.body.font;
764
+ const font = bq.italic ? italicVariant(theme.body.font) : theme.body.font;
679
765
  doc.font(font).fontSize(theme.body.fontSize).fillColor(theme.body.color);
680
766
  doc.text('', textX, doc.y, { width: textWidth });
681
767
  await renderInlineTokens(p.tokens, false, false, bq.italic);
package/dist/styles.js CHANGED
@@ -55,8 +55,8 @@ exports.defaultTheme = {
55
55
  inline: {
56
56
  font: 'Courier',
57
57
  fontSize: 10,
58
- color: '#c7254e',
59
- backgroundColor: '#f9f2f4',
58
+ color: '#1a1a1a',
59
+ backgroundColor: '#f0f0f0',
60
60
  },
61
61
  block: {
62
62
  font: 'Courier',
@@ -78,6 +78,7 @@ exports.defaultTheme = {
78
78
  headerBackground: '#f0f0f0',
79
79
  borderColor: '#cccccc',
80
80
  cellPadding: 6,
81
+ zebraColor: '#f9f9f9',
81
82
  },
82
83
  syntaxHighlight: exports.defaultSyntaxHighlightTheme,
83
84
  };
@@ -16,13 +16,13 @@ exports.modernTheme = {
16
16
  },
17
17
  body: { font: 'Helvetica', fontSize: 11, color: '#2d3436', lineGap: 5 },
18
18
  code: {
19
- inline: { font: 'Courier', fontSize: 10, color: '#e17055', backgroundColor: '#ffeaa7' },
19
+ inline: { font: 'Courier', fontSize: 10, color: '#2d3436', backgroundColor: '#e8e8e8' },
20
20
  block: { font: 'Courier', fontSize: 9, color: '#2d3436', backgroundColor: '#dfe6e9', padding: 10 },
21
21
  },
22
22
  blockquote: { borderColor: '#0d7377', borderWidth: 3, italic: true, indent: 20 },
23
23
  linkColor: '#0984e3',
24
24
  horizontalRuleColor: '#b2bec3',
25
- table: { headerBackground: '#dfe6e9', borderColor: '#b2bec3', cellPadding: 7 },
25
+ table: { headerBackground: '#dfe6e9', borderColor: '#b2bec3', cellPadding: 7, zebraColor: '#f0f5f5' },
26
26
  syntaxHighlight: {
27
27
  background: '#e8f0f2',
28
28
  gutter: '#636e72',
@@ -52,13 +52,13 @@ exports.academicTheme = {
52
52
  },
53
53
  body: { font: 'Times-Roman', fontSize: 12, color: '#1a1a2e', lineGap: 4 },
54
54
  code: {
55
- inline: { font: 'Courier', fontSize: 10, color: '#6c3483', backgroundColor: '#f4ecf7' },
55
+ inline: { font: 'Courier', fontSize: 10, color: '#1a1a2e', backgroundColor: '#ececec' },
56
56
  block: { font: 'Courier', fontSize: 9, color: '#1a1a2e', backgroundColor: '#f2f3f4', padding: 8 },
57
57
  },
58
58
  blockquote: { borderColor: '#6c3483', borderWidth: 2, italic: true, indent: 24 },
59
59
  linkColor: '#2e4057',
60
60
  horizontalRuleColor: '#aab7b8',
61
- table: { headerBackground: '#eaecee', borderColor: '#aab7b8', cellPadding: 6 },
61
+ table: { headerBackground: '#eaecee', borderColor: '#aab7b8', cellPadding: 6, zebraColor: '#f7f8f8' },
62
62
  syntaxHighlight: {
63
63
  background: '#f2f3f4',
64
64
  gutter: '#7f8c8d',
@@ -88,13 +88,13 @@ exports.minimalTheme = {
88
88
  },
89
89
  body: { font: 'Helvetica', fontSize: 10, color: '#444444', lineGap: 5 },
90
90
  code: {
91
- inline: { font: 'Courier', fontSize: 9, color: '#555555', backgroundColor: '#f7f7f7' },
91
+ inline: { font: 'Courier', fontSize: 9, color: '#333333', backgroundColor: '#eeeeee' },
92
92
  block: { font: 'Courier', fontSize: 9, color: '#444444', backgroundColor: '#fafafa', padding: 10 },
93
93
  },
94
94
  blockquote: { borderColor: '#cccccc', borderWidth: 2, italic: false, indent: 18 },
95
95
  linkColor: '#555555',
96
96
  horizontalRuleColor: '#e0e0e0',
97
- table: { headerBackground: '#fafafa', borderColor: '#e0e0e0', cellPadding: 8 },
97
+ table: { headerBackground: '#fafafa', borderColor: '#e0e0e0', cellPadding: 8, zebraColor: '#f5f5f5' },
98
98
  syntaxHighlight: {
99
99
  background: '#f8f8f8',
100
100
  gutter: '#999999',
@@ -124,13 +124,13 @@ exports.oceanTheme = {
124
124
  },
125
125
  body: { font: 'Helvetica', fontSize: 11, color: '#2c3e50', lineGap: 4 },
126
126
  code: {
127
- inline: { font: 'Courier', fontSize: 10, color: '#c0392b', backgroundColor: '#eaf2f8' },
127
+ inline: { font: 'Courier', fontSize: 10, color: '#1b2631', backgroundColor: '#e0e0e0' },
128
128
  block: { font: 'Courier', fontSize: 9, color: '#2c3e50', backgroundColor: '#eaf2f8', padding: 8 },
129
129
  },
130
130
  blockquote: { borderColor: '#2980b9', borderWidth: 3, italic: true, indent: 20 },
131
131
  linkColor: '#2471a3',
132
132
  horizontalRuleColor: '#aed6f1',
133
- table: { headerBackground: '#d4e6f1', borderColor: '#85c1e9', cellPadding: 6 },
133
+ table: { headerBackground: '#d4e6f1', borderColor: '#85c1e9', cellPadding: 6, zebraColor: '#eaf2f8' },
134
134
  syntaxHighlight: {
135
135
  background: '#eaf2f8',
136
136
  gutter: '#5d6d7e',
package/dist/types.d.ts CHANGED
@@ -34,6 +34,10 @@ export interface TableStyles {
34
34
  headerBackground: string;
35
35
  borderColor: string;
36
36
  cellPadding: number;
37
+ /** Background color for alternating (even) body rows when zebra striping is enabled.
38
+ * @default '#f9f9f9'
39
+ */
40
+ zebraColor?: string;
37
41
  }
38
42
  export interface TokenColors {
39
43
  [key: string]: string;
@@ -69,6 +73,25 @@ export interface ThemeConfig {
69
73
  */
70
74
  syntaxHighlight?: SyntaxHighlightTheme;
71
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
+ }
72
95
  /** Converts a single emoji string to a PNG `Buffer`. */
73
96
  export type ColorEmojiRenderer = (emoji: string) => Promise<Buffer>;
74
97
  export interface PdfOptions {
@@ -107,6 +130,14 @@ export interface PdfOptions {
107
130
  * @default false
108
131
  */
109
132
  lineNumbers?: boolean;
133
+ /**
134
+ * Enable zebra striping (alternating row backgrounds) on tables.
135
+ *
136
+ * The stripe color is controlled by `theme.table.zebraColor`.
137
+ *
138
+ * @default true
139
+ */
140
+ zebraStripes?: boolean;
110
141
  /**
111
142
  * Emoji font configuration.
112
143
  *
@@ -134,4 +165,12 @@ export interface PdfOptions {
134
165
  * monochrome font or the body font.
135
166
  */
136
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[];
137
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speajus/markdown-to-pdf",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "A new project created with Intent by Augment.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",