jspdf-md-renderer 4.0.0 → 4.1.0
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 +177 -269
- package/dist/index.d.mts +95 -3
- package/dist/index.d.ts +95 -3
- package/dist/index.js +747 -117
- package/dist/index.mjs +747 -118
- package/dist/index.umd.js +747 -117
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,173 +1,83 @@
|
|
|
1
1
|
# jsPDF Markdown Renderer
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
## What's New in v4
|
|
6
|
-
|
|
7
|
-
- Unified inline layout engine for consistent wrapping/alignment.
|
|
8
|
-
- Safe wrapping for long unbroken tokens (long URLs, long inline code).
|
|
9
|
-
- Heading scale controls (`heading.h1` ... `heading.h6`).
|
|
10
|
-
- Task list support (`- [x]` / `- [ ]`).
|
|
11
|
-
- Header/footer + page numbers.
|
|
12
|
-
- Spacing system (`spacing.*`) and richer style options (`codeBlock`, `blockquote`, `list`, `paragraph`).
|
|
3
|
+
A utility to render Markdown directly into formatted PDFs using `jsPDF`.
|
|
13
4
|
|
|
14
5
|
[](https://www.npmjs.com/package/jspdf-md-renderer)
|
|
15
6
|
[](https://opensource.org/licenses/MIT)
|
|
16
7
|
[](https://www.npmjs.com/package/jspdf-md-renderer)
|
|
17
8
|
|
|
18
|
-
##
|
|
9
|
+
## Highlights
|
|
19
10
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- [Contributing](#contributing)
|
|
26
|
-
- [License](#license)
|
|
11
|
+
- Rich markdown support (headings, lists, tables, images, code, blockquotes, links)
|
|
12
|
+
- Configurable typography, spacing, and block styling
|
|
13
|
+
- Header/footer and page-number support
|
|
14
|
+
- Safe inline layout and long-token wrapping
|
|
15
|
+
- Optional security enforcement for untrusted markdown
|
|
27
16
|
|
|
28
17
|
## Installation
|
|
29
18
|
|
|
30
|
-
To install the library, you can use npm:
|
|
31
|
-
|
|
32
19
|
```sh
|
|
33
20
|
npm install jspdf-md-renderer
|
|
34
21
|
```
|
|
35
22
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
### Basic Example
|
|
39
|
-
|
|
40
|
-
Here is a basic example of how to use the library to generate a PDF from Markdown content:
|
|
23
|
+
## Quick Start
|
|
41
24
|
|
|
42
25
|
```ts
|
|
43
|
-
import { jsPDF } from 'jspdf'
|
|
44
|
-
import { MdTextRender } from 'jspdf-md-renderer'
|
|
45
|
-
|
|
46
|
-
const mdString = `
|
|
47
|
-
# Main Title
|
|
48
|
-
|
|
49
|
-
This is a brief introduction paragraph. It sets the tone for the document and introduces the main topic in a concise manner.
|
|
50
|
-
|
|
51
|
-
## Section 1: Overview
|
|
52
|
-
|
|
53
|
-
Here is a medium-length paragraph that goes into more detail about the first section. It explains the context, provides background information, and sets up the discussion for the subsections.
|
|
54
|
-
|
|
55
|
-
## Section 2: Lists and Examples
|
|
26
|
+
import { jsPDF } from 'jspdf'
|
|
27
|
+
import { MdTextRender } from 'jspdf-md-renderer'
|
|
56
28
|
|
|
57
|
-
|
|
29
|
+
const markdown = `
|
|
30
|
+
# Project Report
|
|
58
31
|
|
|
59
|
-
|
|
32
|
+
This report includes **formatted markdown** content.
|
|
60
33
|
|
|
61
34
|
- Item 1
|
|
62
35
|
- Item 2
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`;
|
|
95
|
-
|
|
96
|
-
const generatePDF = async () => {
|
|
97
|
-
const doc = new jsPDF({
|
|
98
|
-
unit: 'mm',
|
|
99
|
-
format: 'a4',
|
|
100
|
-
orientation: 'portrait',
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const options = {
|
|
104
|
-
cursor: { x: 10, y: 10 },
|
|
105
|
-
page: {
|
|
106
|
-
format: 'a4',
|
|
107
|
-
unit: 'mm',
|
|
108
|
-
orientation: 'portrait',
|
|
109
|
-
maxContentWidth: 190,
|
|
110
|
-
maxContentHeight: 277,
|
|
111
|
-
lineSpace: 1.5,
|
|
112
|
-
defaultLineHeightFactor: 1.2,
|
|
113
|
-
defaultFontSize: 12,
|
|
114
|
-
defaultTitleFontSize: 14,
|
|
115
|
-
topmargin: 10,
|
|
116
|
-
xpading: 10,
|
|
117
|
-
xmargin: 10,
|
|
118
|
-
indent: 10,
|
|
119
|
-
},
|
|
120
|
-
font: {
|
|
121
|
-
bold: { name: 'helvetica', style: 'bold' },
|
|
122
|
-
regular: { name: 'helvetica', style: 'normal' },
|
|
123
|
-
light: { name: 'helvetica', style: 'light' },
|
|
124
|
-
},
|
|
125
|
-
heading: {
|
|
126
|
-
h1: 24,
|
|
127
|
-
h2: 20,
|
|
128
|
-
h3: 17,
|
|
129
|
-
h4: 15,
|
|
130
|
-
h5: 13,
|
|
131
|
-
h6: 12,
|
|
132
|
-
color: '#1A365D',
|
|
133
|
-
},
|
|
134
|
-
spacing: {
|
|
135
|
-
afterParagraph: 4,
|
|
136
|
-
afterHeading: 2,
|
|
137
|
-
afterCodeBlock: 4,
|
|
138
|
-
betweenListItems: 1,
|
|
139
|
-
},
|
|
140
|
-
footer: {
|
|
141
|
-
showPageNumbers: true,
|
|
142
|
-
align: 'right',
|
|
143
|
-
},
|
|
144
|
-
endCursorYHandler: (y) => {
|
|
145
|
-
console.log('End cursor Y position:', y);
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
await MdTextRender(doc, mdString, options);
|
|
150
|
-
doc.save('example.pdf');
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
generatePDF();
|
|
154
|
-
```
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
const doc = new jsPDF({ unit: 'mm', format: 'a4', orientation: 'portrait' })
|
|
39
|
+
|
|
40
|
+
await MdTextRender(doc, markdown, {
|
|
41
|
+
cursor: { x: 10, y: 10 },
|
|
42
|
+
page: {
|
|
43
|
+
format: 'a4',
|
|
44
|
+
unit: 'mm',
|
|
45
|
+
orientation: 'portrait',
|
|
46
|
+
maxContentWidth: 190,
|
|
47
|
+
maxContentHeight: 277,
|
|
48
|
+
lineSpace: 1.5,
|
|
49
|
+
defaultLineHeightFactor: 1.2,
|
|
50
|
+
defaultFontSize: 12,
|
|
51
|
+
defaultTitleFontSize: 14,
|
|
52
|
+
topmargin: 10,
|
|
53
|
+
xpading: 10,
|
|
54
|
+
xmargin: 10,
|
|
55
|
+
indent: 10,
|
|
56
|
+
},
|
|
57
|
+
font: {
|
|
58
|
+
bold: { name: 'helvetica', style: 'bold' },
|
|
59
|
+
regular: { name: 'helvetica', style: 'normal' },
|
|
60
|
+
light: { name: 'helvetica', style: 'light' },
|
|
61
|
+
},
|
|
62
|
+
endCursorYHandler: (y) => {
|
|
63
|
+
console.log('Final Y:', y)
|
|
64
|
+
},
|
|
65
|
+
})
|
|
155
66
|
|
|
156
|
-
|
|
67
|
+
doc.save('report.pdf')
|
|
68
|
+
```
|
|
157
69
|
|
|
158
|
-
|
|
70
|
+
## Browser Usage
|
|
159
71
|
|
|
160
|
-
|
|
72
|
+
### Bundler (Vite/Webpack/Rollup)
|
|
161
73
|
|
|
162
74
|
```ts
|
|
163
|
-
import { jsPDF } from 'jspdf'
|
|
164
|
-
import
|
|
165
|
-
import { MdTextRender } from 'jspdf-md-renderer'
|
|
75
|
+
import { jsPDF } from 'jspdf'
|
|
76
|
+
import 'jspdf-autotable'
|
|
77
|
+
import { MdTextRender } from 'jspdf-md-renderer'
|
|
166
78
|
```
|
|
167
79
|
|
|
168
|
-
###
|
|
169
|
-
|
|
170
|
-
Load dependencies first, then load `jspdf-md-renderer` UMD bundle.
|
|
80
|
+
### Script Tag (UMD)
|
|
171
81
|
|
|
172
82
|
```html
|
|
173
83
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
@@ -177,127 +87,48 @@ Load dependencies first, then load `jspdf-md-renderer` UMD bundle.
|
|
|
177
87
|
<script>
|
|
178
88
|
const { jsPDF } = window.jspdf;
|
|
179
89
|
const { MdTextRender } = window.JspdfMdRenderer;
|
|
180
|
-
|
|
181
|
-
(async () => {
|
|
182
|
-
const doc = new jsPDF();
|
|
183
|
-
await MdTextRender(doc, '# Hello from browser runtime');
|
|
184
|
-
doc.save('browser-runtime.pdf');
|
|
185
|
-
})();
|
|
186
90
|
</script>
|
|
187
91
|
```
|
|
188
92
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
### `MdTextParser`
|
|
204
|
-
|
|
205
|
-
Parses markdown into tokens and converts to a custom parsed structure.
|
|
206
|
-
|
|
207
|
-
#### Parameters
|
|
208
|
-
|
|
209
|
-
- `text`: The markdown content to parse.
|
|
210
|
-
|
|
211
|
-
#### Returns
|
|
212
|
-
|
|
213
|
-
- `Promise<ParsedElement[]>`: Parsed markdown elements.
|
|
214
|
-
|
|
215
|
-
## Supported Markdown Elements
|
|
216
|
-
|
|
217
|
-
The following Markdown elements are currently supported by `jspdf-md-renderer`:
|
|
218
|
-
|
|
219
|
-
- **Headings**: `#`, `##`, `###`, etc.
|
|
220
|
-
- **Paragraphs**
|
|
221
|
-
- **Lists**:
|
|
222
|
-
- Unordered lists: `-`, `*`, `+`
|
|
223
|
-
- Ordered lists: `1.`, `2.`, `3.`, etc.
|
|
224
|
-
- **Horizontal Rules**: `---`, `***`, `___`
|
|
225
|
-
- **Text Styles**:
|
|
226
|
-
- Bold: `**bold**` or `__bold__`
|
|
227
|
-
- Italic: `*italic*` or `_italic_`
|
|
228
|
-
- Bold Italic: `***bold italic***` or `___bold italic___`
|
|
229
|
-
- **Code Blocks** (fenced and indented):
|
|
230
|
-
````markdown
|
|
231
|
-
```js
|
|
232
|
-
console.log('Hello, world!');
|
|
233
|
-
```
|
|
234
|
-
````
|
|
235
|
-
- **Links**:
|
|
236
|
-
```markdown
|
|
237
|
-
[GitHub](https://github.com)
|
|
238
|
-
```
|
|
239
|
-
- **Blockquotes**:
|
|
240
|
-
```markdown
|
|
241
|
-
> This is a blockquote.
|
|
242
|
-
```
|
|
243
|
-
- **Images**:
|
|
244
|
-
```markdown
|
|
245
|
-

|
|
246
|
-
```
|
|
247
|
-
Images render at their **intrinsic (original) size** by default and scale down automatically if they exceed the available page width. You can control image dimensions and alignment using an optional attribute block `{...}` after the image syntax:
|
|
248
|
-
|
|
249
|
-
**Custom Attributes:**
|
|
250
|
-
| Attribute | Description | Example |
|
|
251
|
-
|-----------|-------------|---------|
|
|
252
|
-
| `width` or `w` | Image width in px | `{width=200}` or `{w=200}` |
|
|
253
|
-
| `height` or `h` | Image height in px | `{height=150}` or `{h=150}` |
|
|
254
|
-
| `align` | Alignment: `left`, `center`, `right` | `{align=center}` |
|
|
255
|
-
|
|
256
|
-
**Sizing Rules:**
|
|
257
|
-
- If only `width` is given, height is auto-calculated from aspect ratio
|
|
258
|
-
- If only `height` is given, width is auto-calculated from aspect ratio
|
|
259
|
-
- If both are given, the image uses exact dimensions (may distort if ratio differs)
|
|
260
|
-
- Images that exceed page bounds are always scaled down proportionally
|
|
261
|
-
|
|
262
|
-
**Examples:**
|
|
263
|
-
```markdown
|
|
264
|
-

|
|
265
|
-
{width=200}
|
|
266
|
-
{h=150 align=center}
|
|
267
|
-
{width=200 height=150 align=right}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
**Global Default Alignment:**
|
|
271
|
-
You can set a default alignment for all images via the `image` option:
|
|
272
|
-
```ts
|
|
273
|
-
const options = {
|
|
274
|
-
// ...other options
|
|
275
|
-
image: {
|
|
276
|
-
defaultAlign: 'center', // 'left' (default) | 'center' | 'right'
|
|
277
|
-
},
|
|
278
|
-
};
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
- **Inline Code**:
|
|
282
|
-
```markdown
|
|
283
|
-
This is an `inline code` example.
|
|
284
|
-
```
|
|
285
|
-
- **Tables**:
|
|
286
|
-
```markdown
|
|
287
|
-
| Header 1 | Header 2 | Header 3 |
|
|
288
|
-
| -------- | -------- | -------- |
|
|
289
|
-
| Row 1 | Data | Value |
|
|
290
|
-
| Row 2 | Data | Value |
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
## New Options (v4 quick reference)
|
|
93
|
+
## Supported Markdown
|
|
94
|
+
|
|
95
|
+
- Headings (`#` to `######`)
|
|
96
|
+
- Paragraphs
|
|
97
|
+
- Ordered/unordered/task lists
|
|
98
|
+
- Links
|
|
99
|
+
- Images (with optional `{width,height,align}` attributes)
|
|
100
|
+
- Tables
|
|
101
|
+
- Code blocks and inline code
|
|
102
|
+
- Blockquotes
|
|
103
|
+
- Horizontal rules
|
|
104
|
+
- Inline styles (bold/italic)
|
|
105
|
+
|
|
106
|
+
## Key Render Options
|
|
294
107
|
|
|
295
108
|
```ts
|
|
296
109
|
const options = {
|
|
297
|
-
heading: {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
110
|
+
heading: {
|
|
111
|
+
bold: true,
|
|
112
|
+
h1: 26,
|
|
113
|
+
h2: 22,
|
|
114
|
+
h3: 18,
|
|
115
|
+
bottomSpacing: 3,
|
|
116
|
+
},
|
|
117
|
+
list: {
|
|
118
|
+
bulletChar: '\u2022 ',
|
|
119
|
+
indentSize: 8,
|
|
120
|
+
itemSpacing: 0,
|
|
121
|
+
},
|
|
122
|
+
paragraph: {
|
|
123
|
+
bottomSpacing: 3,
|
|
124
|
+
color: '#111827',
|
|
125
|
+
},
|
|
126
|
+
blockquote: {
|
|
127
|
+
barColor: '#4A90D9',
|
|
128
|
+
barWidth: 2,
|
|
129
|
+
paddingLeft: 6,
|
|
130
|
+
backgroundColor: '#F8FAFC',
|
|
131
|
+
},
|
|
301
132
|
codeBlock: {
|
|
302
133
|
backgroundColor: '#F6F8FA',
|
|
303
134
|
borderColor: '#E1E4E8',
|
|
@@ -317,27 +148,104 @@ const options = {
|
|
|
317
148
|
afterList: 3,
|
|
318
149
|
afterTable: 3,
|
|
319
150
|
},
|
|
320
|
-
header: {
|
|
321
|
-
|
|
151
|
+
header: {
|
|
152
|
+
text: 'My Report',
|
|
153
|
+
align: 'center',
|
|
154
|
+
color: '#6b7280',
|
|
155
|
+
fontSize: 9,
|
|
156
|
+
},
|
|
157
|
+
footer: {
|
|
158
|
+
showPageNumbers: true,
|
|
159
|
+
align: 'right',
|
|
160
|
+
},
|
|
322
161
|
}
|
|
323
162
|
```
|
|
324
163
|
|
|
325
|
-
|
|
164
|
+
Behavior notes:
|
|
165
|
+
- Heading size fallback: `heading.hN` -> `page.defaultTitleFontSize`
|
|
166
|
+
- `heading.bold` defaults to `true`
|
|
167
|
+
- List spacing precedence: `spacing.betweenListItems` > `list.itemSpacing`
|
|
168
|
+
- Table width follows `page.maxContentWidth` for consistent block layout
|
|
326
169
|
|
|
327
|
-
|
|
328
|
-
- [Resume example](https://jeelgajera.github.io/jspdf-md-renderer/examples/resume)
|
|
329
|
-
- [Invoice example](https://jeelgajera.github.io/jspdf-md-renderer/examples/invoice)
|
|
330
|
-
- [Technical report example](https://jeelgajera.github.io/jspdf-md-renderer/examples/report)
|
|
331
|
-
- [Custom fonts guide](https://jeelgajera.github.io/jspdf-md-renderer/examples/custom-fonts)
|
|
170
|
+
## Security Controls (opt-in)
|
|
332
171
|
|
|
333
|
-
|
|
172
|
+
Security is disabled by default for backward compatibility.
|
|
334
173
|
|
|
335
|
-
|
|
174
|
+
```ts
|
|
175
|
+
const options = {
|
|
176
|
+
// ...other options
|
|
177
|
+
security: {
|
|
178
|
+
enabled: true,
|
|
179
|
+
violationMode: 'skip', // 'skip' | 'throw' | 'placeholder'
|
|
180
|
+
placeholderText: '[blocked]',
|
|
181
|
+
placeholderImageText: '[blocked image]',
|
|
182
|
+
|
|
183
|
+
// Link controls
|
|
184
|
+
allowedLinkProtocols: ['https:', 'http:', 'mailto:', 'tel:'],
|
|
185
|
+
disablePdfLinks: false,
|
|
186
|
+
|
|
187
|
+
// Image controls
|
|
188
|
+
allowRemoteImages: true,
|
|
189
|
+
allowedImageProtocols: ['https:', 'http:'],
|
|
190
|
+
allowedImageDomains: ['cdn.example.com'],
|
|
191
|
+
allowDataUrls: true,
|
|
192
|
+
allowSvgImages: true,
|
|
193
|
+
|
|
194
|
+
// SSRF controls
|
|
195
|
+
blockLocalhost: true,
|
|
196
|
+
blockPrivateIPs: true,
|
|
197
|
+
blockLinkLocalIPs: true,
|
|
198
|
+
blockMetadataIPs: true,
|
|
199
|
+
|
|
200
|
+
// Limits
|
|
201
|
+
maxMarkdownLength: 500000,
|
|
202
|
+
maxImageCount: 200,
|
|
203
|
+
maxImageSizeBytes: 10 * 1024 * 1024,
|
|
204
|
+
maxNestedDepth: 20,
|
|
205
|
+
renderTimeoutMs: 30000,
|
|
206
|
+
|
|
207
|
+
// Hooks
|
|
208
|
+
validateUrl: async (url, type) => true,
|
|
209
|
+
onSecurityViolation: (violation) => console.warn(violation),
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Security Details
|
|
215
|
+
|
|
216
|
+
- URL classes:
|
|
217
|
+
- explicit scheme (`https://...`) -> full protocol/domain/IP checks
|
|
218
|
+
- protocol-relative (`//host/path`) -> treated as external absolute URL
|
|
219
|
+
- relative path (`/a`, `./a`, `?q=1`, `#id`) -> allowed by default unless custom validator rejects
|
|
220
|
+
- `allowedImageDomains` semantics:
|
|
221
|
+
- `undefined` -> allow all domains
|
|
222
|
+
- `[]` -> deny all domains
|
|
223
|
+
- `maxImageSizeBytes` uses decoded bytes for data URLs
|
|
224
|
+
- `SecurityViolationError` is exported for throw-mode handling
|
|
225
|
+
|
|
226
|
+
Browser caveat:
|
|
227
|
+
- IP-level SSRF checks are best-effort in browser runtime due to DNS API limitations.
|
|
228
|
+
- For strict SSRF policy, fetch remote images through a trusted server-side proxy.
|
|
229
|
+
|
|
230
|
+
## API Exports
|
|
336
231
|
|
|
337
|
-
|
|
232
|
+
- `MdTextRender`
|
|
233
|
+
- `MdTextParser`
|
|
234
|
+
- `SecurityViolationError`
|
|
235
|
+
- `validateOptions`
|
|
236
|
+
- Types: `RenderOption`, `RenderSecurityOptions`, `SecurityViolation`, `ViolationMode`, and others
|
|
237
|
+
|
|
238
|
+
## Examples and Docs
|
|
239
|
+
|
|
240
|
+
- Docs site: [https://jeelgajera.github.io/jspdf-md-renderer/](https://jeelgajera.github.io/jspdf-md-renderer/)
|
|
241
|
+
- Resume example: [https://jeelgajera.github.io/jspdf-md-renderer/examples/resume](https://jeelgajera.github.io/jspdf-md-renderer/examples/resume)
|
|
242
|
+
- Invoice example: [https://jeelgajera.github.io/jspdf-md-renderer/examples/invoice](https://jeelgajera.github.io/jspdf-md-renderer/examples/invoice)
|
|
243
|
+
- Technical report example: [https://jeelgajera.github.io/jspdf-md-renderer/examples/report](https://jeelgajera.github.io/jspdf-md-renderer/examples/report)
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
338
246
|
|
|
339
|
-
|
|
247
|
+
Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
340
248
|
|
|
341
249
|
## License
|
|
342
250
|
|
|
343
|
-
|
|
251
|
+
MIT. See [LICENSE](LICENSE).
|
package/dist/index.d.mts
CHANGED
|
@@ -27,6 +27,94 @@
|
|
|
27
27
|
import { UserOptions } from "jspdf-autotable";
|
|
28
28
|
import jsPDF, { jsPDFOptions } from "jspdf";
|
|
29
29
|
|
|
30
|
+
//#region src/types/security.d.ts
|
|
31
|
+
type ViolationMode = 'skip' | 'throw' | 'placeholder';
|
|
32
|
+
type ViolationAction = 'skip' | 'placeholder';
|
|
33
|
+
type SecurityViolationType = 'link' | 'image' | 'markdown' | 'render';
|
|
34
|
+
type SecurityViolationCode = 'MARKDOWN_TOO_LARGE' | 'MAX_NESTED_DEPTH_EXCEEDED' | 'MAX_IMAGE_COUNT_EXCEEDED' | 'RENDER_TIMEOUT_EXCEEDED' | 'INVALID_URL' | 'LINK_PROTOCOL_BLOCKED' | 'IMAGE_PROTOCOL_BLOCKED' | 'IMAGE_DOMAIN_BLOCKED' | 'DATA_URL_BLOCKED' | 'SVG_BLOCKED' | 'LOCALHOST_BLOCKED' | 'PRIVATE_IP_BLOCKED' | 'LINK_LOCAL_IP_BLOCKED' | 'METADATA_IP_BLOCKED' | 'IMAGE_SIZE_EXCEEDED' | 'CUSTOM_VALIDATOR_BLOCKED';
|
|
35
|
+
interface SecurityViolation {
|
|
36
|
+
/** Machine-readable violation code. */
|
|
37
|
+
code: SecurityViolationCode;
|
|
38
|
+
/** High-level category of the violating input. */
|
|
39
|
+
type: SecurityViolationType;
|
|
40
|
+
/** Human-readable explanation of the violation. */
|
|
41
|
+
message: string;
|
|
42
|
+
/** Raw value that triggered the violation (URL, length, etc.). */
|
|
43
|
+
value?: string;
|
|
44
|
+
/** Optional execution context to help debugging (e.g. 'markdown-link'). */
|
|
45
|
+
context?: string;
|
|
46
|
+
/** ISO timestamp when the violation was recorded. */
|
|
47
|
+
timestamp: string;
|
|
48
|
+
}
|
|
49
|
+
declare class SecurityViolationError extends Error {
|
|
50
|
+
/** Structured violation payload used to construct this error. */
|
|
51
|
+
readonly violation: SecurityViolation;
|
|
52
|
+
constructor(violation: SecurityViolation);
|
|
53
|
+
}
|
|
54
|
+
interface RenderSecurityOptions {
|
|
55
|
+
/** Enables all built-in security checks. Default: false (opt-in). */
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
/** Allowed URI protocols for markdown links. Example: ['https:', 'mailto:']. */
|
|
58
|
+
allowedLinkProtocols?: string[];
|
|
59
|
+
/** If true, link text is rendered but PDF link actions are disabled. */
|
|
60
|
+
disablePdfLinks?: boolean;
|
|
61
|
+
/** Whether remote image fetching is allowed (http/https). */
|
|
62
|
+
allowRemoteImages?: boolean;
|
|
63
|
+
/** Allowed protocols for image URLs. Example: ['https:', 'http:']. */
|
|
64
|
+
allowedImageProtocols?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Optional domain allowlist for remote image hosts.
|
|
67
|
+
* - `undefined` (default): all domains are allowed.
|
|
68
|
+
* - `[]` (empty array): no domains are allowed.
|
|
69
|
+
* - `['example.com']`: only `example.com` and its subdomains are allowed.
|
|
70
|
+
*/
|
|
71
|
+
allowedImageDomains?: string[];
|
|
72
|
+
/** Whether inline data: image URLs are allowed. */
|
|
73
|
+
allowDataUrls?: boolean;
|
|
74
|
+
/** Whether SVG images are allowed. */
|
|
75
|
+
allowSvgImages?: boolean;
|
|
76
|
+
/** Blocks localhost image/link destinations when true. */
|
|
77
|
+
blockLocalhost?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Blocks private IPv4 ranges (10/8, 172.16/12, 192.168/16) when true.
|
|
80
|
+
* NOTE: In browser environments, IP-based checks cannot be enforced due to
|
|
81
|
+
* lack of DNS resolution APIs. Use a trusted server-side proxy for strict enforcement.
|
|
82
|
+
*/
|
|
83
|
+
blockPrivateIPs?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Blocks link-local IPv4 ranges (169.254/16) when true.
|
|
86
|
+
* NOTE: In browser environments, IP-based checks cannot be enforced due to
|
|
87
|
+
* lack of DNS resolution APIs. Use a trusted server-side proxy for strict enforcement.
|
|
88
|
+
*/
|
|
89
|
+
blockLinkLocalIPs?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Blocks known cloud metadata endpoints when true.
|
|
92
|
+
* NOTE: In browser environments, IP-based checks cannot be enforced due to
|
|
93
|
+
* lack of DNS resolution APIs. Use a trusted server-side proxy for strict enforcement.
|
|
94
|
+
*/
|
|
95
|
+
blockMetadataIPs?: boolean;
|
|
96
|
+
/** Maximum markdown input length in characters. */
|
|
97
|
+
maxMarkdownLength?: number;
|
|
98
|
+
/** Maximum number of markdown images allowed per render. */
|
|
99
|
+
maxImageCount?: number;
|
|
100
|
+
/** Maximum image payload size in bytes (fetched blob size or decoded data URL bytes). */
|
|
101
|
+
maxImageSizeBytes?: number;
|
|
102
|
+
/** Maximum supported markdown nesting depth. */
|
|
103
|
+
maxNestedDepth?: number;
|
|
104
|
+
/** Maximum total render time in milliseconds. */
|
|
105
|
+
renderTimeoutMs?: number;
|
|
106
|
+
/** Action taken when a violation occurs. */
|
|
107
|
+
violationMode?: ViolationMode;
|
|
108
|
+
/** Placeholder text used for blocked text-like content in placeholder mode. */
|
|
109
|
+
placeholderText?: string;
|
|
110
|
+
/** Placeholder text used for blocked images in placeholder mode. */
|
|
111
|
+
placeholderImageText?: string;
|
|
112
|
+
/** Optional custom URL validator. Return false to reject the URL. */
|
|
113
|
+
validateUrl?: (url: URL, type: 'link' | 'image') => boolean | Promise<boolean>;
|
|
114
|
+
/** Callback fired for every security violation, regardless of mode. */
|
|
115
|
+
onSecurityViolation?: (violation: SecurityViolation) => void;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
30
118
|
//#region src/types/renderOption.d.ts
|
|
31
119
|
type RenderOption = {
|
|
32
120
|
cursor: {
|
|
@@ -52,10 +140,13 @@ type RenderOption = {
|
|
|
52
140
|
bold: FontItem;
|
|
53
141
|
regular: FontItem;
|
|
54
142
|
light: FontItem;
|
|
143
|
+
italic?: FontItem;
|
|
144
|
+
boldItalic?: FontItem;
|
|
55
145
|
code?: FontItem;
|
|
56
146
|
};
|
|
57
147
|
heading?: {
|
|
58
|
-
/** Font size for h1-h6. Values are absolute (e.g. 22, 20, 18, 16, 14, 12). */
|
|
148
|
+
/** Whether headings should use bold font. Default: true */bold?: boolean; /** Font size for h1-h6. Values are absolute (e.g. 22, 20, 18, 16, 14, 12). */
|
|
149
|
+
h1?: number;
|
|
59
150
|
h2?: number;
|
|
60
151
|
h3?: number;
|
|
61
152
|
h4?: number;
|
|
@@ -72,7 +163,7 @@ type RenderOption = {
|
|
|
72
163
|
};
|
|
73
164
|
list?: {
|
|
74
165
|
/** Bullet character for unordered lists. Default: '\u2022 ' */bulletChar?: string; /** Extra indent per nesting level in doc units. Default: uses page.indent */
|
|
75
|
-
indentSize?: number; /** Vertical space between list items.
|
|
166
|
+
indentSize?: number; /** Vertical space between list items. Used when spacing.betweenListItems is not provided. */
|
|
76
167
|
itemSpacing?: number;
|
|
77
168
|
};
|
|
78
169
|
paragraph?: {
|
|
@@ -140,6 +231,7 @@ type RenderOption = {
|
|
|
140
231
|
};
|
|
141
232
|
pageBreakHandler?: (doc: jsPDF) => void;
|
|
142
233
|
endCursorYHandler: (y: number) => void;
|
|
234
|
+
security?: RenderSecurityOptions;
|
|
143
235
|
};
|
|
144
236
|
type Cursor = {
|
|
145
237
|
x: number;
|
|
@@ -332,4 +424,4 @@ declare enum MdTokenType {
|
|
|
332
424
|
//#region src/utils/options-validation.d.ts
|
|
333
425
|
declare const validateOptions: (options: RenderOption) => RenderOption;
|
|
334
426
|
//#endregion
|
|
335
|
-
export { MdTextParser, MdTextRender, MdTokenType, type ParsedElement, type RenderOption, type StyledLine, type StyledWordInfo, type TextStyle, renderInlineContent, renderPlainText, validateOptions };
|
|
427
|
+
export { MdTextParser, MdTextRender, MdTokenType, type ParsedElement, type RenderOption, type RenderSecurityOptions, type SecurityViolation, type SecurityViolationCode, SecurityViolationError, type StyledLine, type StyledWordInfo, type TextStyle, type ViolationAction, type ViolationMode, renderInlineContent, renderPlainText, validateOptions };
|