jspdf-md-renderer 1.2.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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/enums/mdTokenType.d.ts +18 -0
- package/dist/enums/mdTokenType.js +19 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/parser/MdTextParser.d.ts +8 -0
- package/dist/parser/MdTextParser.js +91 -0
- package/dist/renderer/MdTextRender.d.ts +10 -0
- package/dist/renderer/MdTextRender.js +57 -0
- package/dist/renderer/components/heading.d.ts +8 -0
- package/dist/renderer/components/heading.js +18 -0
- package/dist/renderer/components/index.d.ts +5 -0
- package/dist/renderer/components/index.js +5 -0
- package/dist/renderer/components/list.d.ts +5 -0
- package/dist/renderer/components/list.js +12 -0
- package/dist/renderer/components/listItem.d.ts +5 -0
- package/dist/renderer/components/listItem.js +32 -0
- package/dist/renderer/components/paragraph.d.ts +8 -0
- package/dist/renderer/components/paragraph.js +44 -0
- package/dist/renderer/components/rawItem.d.ts +5 -0
- package/dist/renderer/components/rawItem.js +16 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/parsedElement.d.ts +20 -0
- package/dist/types/parsedElement.js +1 -0
- package/dist/types/renderOption.d.ts +37 -0
- package/dist/types/renderOption.js +1 -0
- package/dist/types/wordInfo.d.ts +4 -0
- package/dist/types/wordInfo.js +1 -0
- package/dist/utils/doc-helpers.d.ts +3 -0
- package/dist/utils/doc-helpers.js +3 -0
- package/dist/utils/handlePageBreak.d.ts +6 -0
- package/dist/utils/handlePageBreak.js +11 -0
- package/dist/utils/justifyText.d.ts +15 -0
- package/dist/utils/justifyText.js +53 -0
- package/package.json +63 -0
- package/src/enums/mdTokenType.ts +18 -0
- package/src/index.ts +4 -0
- package/src/parser/MdTextParser.ts +98 -0
- package/src/renderer/MdTextRender.ts +110 -0
- package/src/renderer/components/heading.ts +30 -0
- package/src/renderer/components/index.ts +5 -0
- package/src/renderer/components/list.ts +28 -0
- package/src/renderer/components/listItem.ts +62 -0
- package/src/renderer/components/paragraph.ts +79 -0
- package/src/renderer/components/rawItem.ts +35 -0
- package/src/types/index.ts +2 -0
- package/src/types/parsedElement.ts +15 -0
- package/src/types/renderOption.ts +38 -0
- package/src/types/wordInfo.ts +4 -0
- package/src/utils/doc-helpers.ts +6 -0
- package/src/utils/handlePageBreak.ts +13 -0
- package/src/utils/justifyText.ts +103 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles page breaks when content overflows.
|
|
3
|
+
*/
|
|
4
|
+
export const HandlePageBreaks = (doc, options) => {
|
|
5
|
+
if (typeof options.pageBreakHandler === 'function') {
|
|
6
|
+
options.pageBreakHandler();
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
doc.addPage(options.page?.format, options.page?.orientation);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { WordInfo } from '../types/wordInfo';
|
|
3
|
+
export declare const writeLineJustify: (pdfGen: jsPDF, wordsInfo: WordInfo[], lineLength: number, lineNumber: number, xStart: number, yStart: number, lineHeight: number, textWidth: number, defaultLineHeightFactor: number) => void;
|
|
4
|
+
export declare const writeLastLineJustify: (wordsInfo: WordInfo[], pdfGen: jsPDF, xStart: number, yStart: number, lineNumber: number, lineHeight: number, textWidth: number, defaultLineHeightFactor: number) => void;
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param pdfGen jsPDF default Object reference
|
|
8
|
+
* @param text string text data
|
|
9
|
+
* @param xStart x point where to render
|
|
10
|
+
* @param yStart y point where to render
|
|
11
|
+
* @param textWidth text render area width
|
|
12
|
+
* @param defaultLineHeightFactor line height factor
|
|
13
|
+
* @returns end of y cursor point of justified render text data
|
|
14
|
+
*/
|
|
15
|
+
export declare const justifyText: (pdfGen: jsPDF, text: string, xStart: number, yStart: number, textWidth: number, defaultLineHeightFactor: number) => number;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// -------- Handle Justify Content for Custome Fonts
|
|
2
|
+
export const writeLineJustify = (pdfGen, wordsInfo, lineLength, lineNumber, xStart, yStart, lineHeight, textWidth, defaultLineHeightFactor) => {
|
|
3
|
+
const wordSpacing = (textWidth - lineLength) / (wordsInfo.length - 1);
|
|
4
|
+
let x = xStart;
|
|
5
|
+
const y = yStart + lineNumber * lineHeight;
|
|
6
|
+
for (const wordInfo of wordsInfo) {
|
|
7
|
+
pdfGen.text(wordInfo.text, x, y, {
|
|
8
|
+
align: 'justify',
|
|
9
|
+
lineHeightFactor: defaultLineHeightFactor,
|
|
10
|
+
maxWidth: textWidth,
|
|
11
|
+
});
|
|
12
|
+
x += wordInfo.wordLength + wordSpacing;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const writeLastLineJustify = (wordsInfo, pdfGen, xStart, yStart, lineNumber, lineHeight, textWidth, defaultLineHeightFactor) => {
|
|
16
|
+
const line = wordsInfo.map((x) => x.text).join(' ');
|
|
17
|
+
pdfGen.text(line, xStart, yStart + lineNumber * lineHeight, {
|
|
18
|
+
align: 'justify',
|
|
19
|
+
lineHeightFactor: defaultLineHeightFactor,
|
|
20
|
+
maxWidth: textWidth,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param pdfGen jsPDF default Object reference
|
|
26
|
+
* @param text string text data
|
|
27
|
+
* @param xStart x point where to render
|
|
28
|
+
* @param yStart y point where to render
|
|
29
|
+
* @param textWidth text render area width
|
|
30
|
+
* @param defaultLineHeightFactor line height factor
|
|
31
|
+
* @returns end of y cursor point of justified render text data
|
|
32
|
+
*/
|
|
33
|
+
export const justifyText = (pdfGen, text, xStart, yStart, textWidth, defaultLineHeightFactor) => {
|
|
34
|
+
const lineHeight = pdfGen.getTextDimensions('A').h * defaultLineHeightFactor;
|
|
35
|
+
const words = text.split(' ');
|
|
36
|
+
let lineNumber = 0;
|
|
37
|
+
let wordsInfo = [];
|
|
38
|
+
let lineLength = 0;
|
|
39
|
+
for (const word of words) {
|
|
40
|
+
const wordLength = pdfGen.getTextWidth(word + 'a');
|
|
41
|
+
if (wordLength + lineLength >= textWidth) {
|
|
42
|
+
writeLineJustify(pdfGen, wordsInfo, lineLength, lineNumber++, xStart, yStart, lineHeight, textWidth, defaultLineHeightFactor);
|
|
43
|
+
wordsInfo = [];
|
|
44
|
+
lineLength = 0;
|
|
45
|
+
}
|
|
46
|
+
wordsInfo.push({ text: word, wordLength });
|
|
47
|
+
lineLength += wordLength;
|
|
48
|
+
}
|
|
49
|
+
if (wordsInfo.length > 0) {
|
|
50
|
+
writeLastLineJustify(wordsInfo, pdfGen, xStart, yStart, lineNumber, lineHeight, textWidth, defaultLineHeightFactor);
|
|
51
|
+
}
|
|
52
|
+
return yStart + lineNumber * lineHeight;
|
|
53
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jspdf-md-renderer",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "A jsPDF utility to render Markdown directly into formatted PDFs with custom designs.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./types": "./dist/types/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"types"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "rimraf dist && tsc",
|
|
22
|
+
"lint": "eslint src/**",
|
|
23
|
+
"lint:fix": "eslint src/** --fix",
|
|
24
|
+
"format": "prettier --write .",
|
|
25
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
26
|
+
"prepare": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/JeelGajera/jspdf-md-renderer.git"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"jspdf",
|
|
34
|
+
"markdown",
|
|
35
|
+
"pdf",
|
|
36
|
+
"renderer"
|
|
37
|
+
],
|
|
38
|
+
"author": "Jeel Gajera <jeelgajera200@gmail.com>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/JeelGajera/jspdf-md-renderer/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/JeelGajera/jspdf-md-renderer#readme",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"jspdf": "^2.5.2",
|
|
46
|
+
"jspdf-md-renderer": "file:",
|
|
47
|
+
"marked": "^15.0.3"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@eslint/js": "^9.16.0",
|
|
51
|
+
"@types/node": "^22.10.2",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.18.0",
|
|
54
|
+
"eslint": "^9.16.0",
|
|
55
|
+
"eslint-config-prettier": "^9.1.0",
|
|
56
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
57
|
+
"globals": "^15.13.0",
|
|
58
|
+
"prettier": "^3.4.2",
|
|
59
|
+
"rimraf": "^6.0.1",
|
|
60
|
+
"typescript": "^5.7.2",
|
|
61
|
+
"typescript-eslint": "^8.18.0"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export enum MdTokenType {
|
|
2
|
+
Heading = 'heading',
|
|
3
|
+
Paragraph = 'paragraph',
|
|
4
|
+
List = 'list',
|
|
5
|
+
ListItem = 'list_item',
|
|
6
|
+
Blockquote = 'blockquote',
|
|
7
|
+
Code = 'code',
|
|
8
|
+
Table = 'table',
|
|
9
|
+
Html = 'html',
|
|
10
|
+
Hr = 'hr',
|
|
11
|
+
Image = 'image',
|
|
12
|
+
Link = 'link',
|
|
13
|
+
Strong = 'strong',
|
|
14
|
+
Em = 'em',
|
|
15
|
+
TableHeader = 'table_header',
|
|
16
|
+
TableCell = 'table_cell',
|
|
17
|
+
Raw = 'raw',
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { TokensList, marked } from 'marked';
|
|
3
|
+
import { MdTokenType } from '../enums/mdTokenType';
|
|
4
|
+
import { ParsedElement } from '../types/parsedElement';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses markdown into tokens and converts to a custom parsed structure.
|
|
8
|
+
*
|
|
9
|
+
* @param text - The markdown content to parse.
|
|
10
|
+
* @returns Parsed markdown elements.
|
|
11
|
+
*/
|
|
12
|
+
export const MdTextParser = async (text: string): Promise<ParsedElement[]> => {
|
|
13
|
+
const tokens = await marked.lexer(text, { async: true });
|
|
14
|
+
return convertTokens(tokens);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert the markdown tokens to ParsedElements.
|
|
19
|
+
*
|
|
20
|
+
* @param tokens - The list of markdown tokens.
|
|
21
|
+
* @returns Parsed elements in a custom structure.
|
|
22
|
+
*/
|
|
23
|
+
const convertTokens = (tokens: TokensList): ParsedElement[] => {
|
|
24
|
+
const parsedElements: ParsedElement[] = [];
|
|
25
|
+
tokens.forEach((token) => {
|
|
26
|
+
const handler = tokenHandlers[token.type];
|
|
27
|
+
if (handler) {
|
|
28
|
+
parsedElements.push(handler(token));
|
|
29
|
+
} else {
|
|
30
|
+
parsedElements.push({ type: MdTokenType.Raw, content: token.raw });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return parsedElements.map((element) =>
|
|
34
|
+
element.type === MdTokenType.Raw && element.content === '\n\n'
|
|
35
|
+
? { ...element, content: element.content.replace('\n\n', '\n') }
|
|
36
|
+
: element,
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Map each token type to its handler function.
|
|
42
|
+
*/
|
|
43
|
+
const tokenHandlers: Record<string, (token: any) => ParsedElement> = {
|
|
44
|
+
[MdTokenType.Heading]: (token) => ({
|
|
45
|
+
type: MdTokenType.Heading,
|
|
46
|
+
depth: token.depth,
|
|
47
|
+
content: token.text,
|
|
48
|
+
}),
|
|
49
|
+
[MdTokenType.Paragraph]: (token) => ({
|
|
50
|
+
type: MdTokenType.Paragraph,
|
|
51
|
+
content: token.text,
|
|
52
|
+
}),
|
|
53
|
+
[MdTokenType.List]: (token) => ({
|
|
54
|
+
type: MdTokenType.List,
|
|
55
|
+
items: token.items ? convertTokens(token.items) : [],
|
|
56
|
+
}),
|
|
57
|
+
[MdTokenType.ListItem]: (token) => ({
|
|
58
|
+
type: MdTokenType.ListItem,
|
|
59
|
+
content: token.text,
|
|
60
|
+
items: token.tokens ? convertTokens(token.tokens) : [],
|
|
61
|
+
}),
|
|
62
|
+
[MdTokenType.Code]: (token) => ({
|
|
63
|
+
type: MdTokenType.Code,
|
|
64
|
+
lang: token.lang,
|
|
65
|
+
code: token.text,
|
|
66
|
+
}),
|
|
67
|
+
[MdTokenType.Table]: (token) => ({
|
|
68
|
+
type: MdTokenType.Table,
|
|
69
|
+
header: token.header.map((header: any) => ({
|
|
70
|
+
type: MdTokenType.TableHeader,
|
|
71
|
+
content: header,
|
|
72
|
+
})),
|
|
73
|
+
rows: token.rows.map((row: any[]) =>
|
|
74
|
+
row.map((cell: any) => ({
|
|
75
|
+
type: MdTokenType.TableCell,
|
|
76
|
+
content: cell,
|
|
77
|
+
})),
|
|
78
|
+
),
|
|
79
|
+
}),
|
|
80
|
+
[MdTokenType.Image]: (token) => ({
|
|
81
|
+
type: MdTokenType.Image,
|
|
82
|
+
src: token.href,
|
|
83
|
+
alt: token.text,
|
|
84
|
+
}),
|
|
85
|
+
[MdTokenType.Link]: (token) => ({
|
|
86
|
+
type: MdTokenType.Link,
|
|
87
|
+
href: token.href,
|
|
88
|
+
text: token.text,
|
|
89
|
+
}),
|
|
90
|
+
[MdTokenType.Strong]: (token) => ({
|
|
91
|
+
type: MdTokenType.Strong,
|
|
92
|
+
content: token.text,
|
|
93
|
+
}),
|
|
94
|
+
[MdTokenType.Em]: (token) => ({
|
|
95
|
+
type: MdTokenType.Em,
|
|
96
|
+
content: token.text,
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { MdTokenType } from '../enums/mdTokenType';
|
|
3
|
+
import { MdTextParser } from '../parser/MdTextParser';
|
|
4
|
+
import { ParsedElement } from '../types/parsedElement';
|
|
5
|
+
import { RenderOption } from '../types/renderOption';
|
|
6
|
+
import { HandlePageBreaks } from '../utils/handlePageBreak';
|
|
7
|
+
import {
|
|
8
|
+
renderHeading,
|
|
9
|
+
renderList,
|
|
10
|
+
renderListItem,
|
|
11
|
+
renderParagraph,
|
|
12
|
+
renderRawItem,
|
|
13
|
+
} from './components';
|
|
14
|
+
import { getCharHight } from '../utils/doc-helpers';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders parsed markdown text into jsPDF document.
|
|
18
|
+
*
|
|
19
|
+
* @param doc - The jsPDF document.
|
|
20
|
+
* @param text - The markdown content to render.
|
|
21
|
+
* @param options - The render options (fonts, page margins, etc.).
|
|
22
|
+
*/
|
|
23
|
+
export const MdTextRender = async (
|
|
24
|
+
doc: jsPDF,
|
|
25
|
+
text: string,
|
|
26
|
+
options: RenderOption,
|
|
27
|
+
) => {
|
|
28
|
+
const parsedElements = await MdTextParser(text);
|
|
29
|
+
console.log(parsedElements);
|
|
30
|
+
console.log(doc);
|
|
31
|
+
|
|
32
|
+
let y = options.cursor.y;
|
|
33
|
+
const x = options.cursor.x;
|
|
34
|
+
|
|
35
|
+
const renderElement = (
|
|
36
|
+
element: ParsedElement,
|
|
37
|
+
indentLevel: number = 0,
|
|
38
|
+
hasRawBullet: boolean = false,
|
|
39
|
+
) => {
|
|
40
|
+
const indent = indentLevel * options.page.indent;
|
|
41
|
+
if (
|
|
42
|
+
y +
|
|
43
|
+
doc.splitTextToSize(
|
|
44
|
+
element.content ?? '',
|
|
45
|
+
options.page.maxContentWidth - indent,
|
|
46
|
+
).length *
|
|
47
|
+
getCharHight(doc, options) >=
|
|
48
|
+
options.page.maxContentHeight
|
|
49
|
+
) {
|
|
50
|
+
HandlePageBreaks(doc, options);
|
|
51
|
+
y = options.page.topmargin;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
switch (element.type) {
|
|
55
|
+
case MdTokenType.Heading:
|
|
56
|
+
y = renderHeading(doc, element, x, y, indent, options);
|
|
57
|
+
break;
|
|
58
|
+
case MdTokenType.Paragraph:
|
|
59
|
+
y = renderParagraph(doc, element, x, y, indent, options);
|
|
60
|
+
break;
|
|
61
|
+
case MdTokenType.List:
|
|
62
|
+
y = renderList(
|
|
63
|
+
doc,
|
|
64
|
+
element,
|
|
65
|
+
y,
|
|
66
|
+
indentLevel,
|
|
67
|
+
options,
|
|
68
|
+
renderElement,
|
|
69
|
+
);
|
|
70
|
+
break;
|
|
71
|
+
case MdTokenType.ListItem:
|
|
72
|
+
y = renderListItem(
|
|
73
|
+
doc,
|
|
74
|
+
element,
|
|
75
|
+
x,
|
|
76
|
+
y,
|
|
77
|
+
indentLevel,
|
|
78
|
+
options,
|
|
79
|
+
renderElement,
|
|
80
|
+
);
|
|
81
|
+
break;
|
|
82
|
+
case MdTokenType.Raw:
|
|
83
|
+
y = renderRawItem(
|
|
84
|
+
doc,
|
|
85
|
+
element,
|
|
86
|
+
x,
|
|
87
|
+
y,
|
|
88
|
+
indentLevel,
|
|
89
|
+
hasRawBullet,
|
|
90
|
+
options,
|
|
91
|
+
);
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
console.warn(
|
|
95
|
+
`Warning: Unsupported element type encountered: ${element.type}.
|
|
96
|
+
If you believe this element type should be supported, please create an issue at:
|
|
97
|
+
https://github.com/JeelGajera/jspdf-md-renderer/issues
|
|
98
|
+
with details of the element and expected behavior. Thank you for helping improve this library!`,
|
|
99
|
+
);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
return y;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const item of parsedElements) {
|
|
106
|
+
renderElement(item);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
options.endCursorYHandler(y);
|
|
110
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { ParsedElement } from '../../types/parsedElement';
|
|
3
|
+
import { RenderOption } from '../../types/renderOption';
|
|
4
|
+
import { getCharHight } from '../../utils/doc-helpers';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders heading elements.
|
|
8
|
+
*/
|
|
9
|
+
const renderHeading = (
|
|
10
|
+
doc: jsPDF,
|
|
11
|
+
element: ParsedElement,
|
|
12
|
+
x: number,
|
|
13
|
+
y: number,
|
|
14
|
+
indent: number,
|
|
15
|
+
options: RenderOption,
|
|
16
|
+
): number => {
|
|
17
|
+
const size = 6 - (element?.depth ?? 0) > 0 ? 6 - (element?.depth ?? 0) : 0;
|
|
18
|
+
// doc.setFont(options.font.regular.name, options.font.regular.style);
|
|
19
|
+
doc.setFontSize(options.page.defaultFontSize + size);
|
|
20
|
+
doc.text(element?.content ?? '', x + indent, y, {
|
|
21
|
+
align: 'left',
|
|
22
|
+
maxWidth: options.page.maxContentWidth - indent,
|
|
23
|
+
});
|
|
24
|
+
y += 1.5 * getCharHight(doc, options);
|
|
25
|
+
// doc.setFont(options.font.light.name, options.font.light.style);
|
|
26
|
+
doc.setFontSize(options.page.defaultFontSize);
|
|
27
|
+
return y;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default renderHeading;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as renderHeading } from './heading';
|
|
2
|
+
export { default as renderParagraph } from './paragraph';
|
|
3
|
+
export { default as renderList } from './list';
|
|
4
|
+
export { default as renderListItem } from './listItem';
|
|
5
|
+
export { default as renderRawItem } from './rawItem';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { ParsedElement } from '../../types/parsedElement';
|
|
3
|
+
import { RenderOption } from '../../types/renderOption';
|
|
4
|
+
import { getCharHight } from '../../utils/doc-helpers';
|
|
5
|
+
|
|
6
|
+
const renderList = (
|
|
7
|
+
doc: jsPDF,
|
|
8
|
+
element: ParsedElement,
|
|
9
|
+
y: number,
|
|
10
|
+
indentLevel: number,
|
|
11
|
+
options: RenderOption,
|
|
12
|
+
parentElementRenderer: (
|
|
13
|
+
element: ParsedElement,
|
|
14
|
+
indentLevel: number,
|
|
15
|
+
hasRawBullet?: boolean,
|
|
16
|
+
) => number,
|
|
17
|
+
): number => {
|
|
18
|
+
doc.setFontSize(options.page.defaultFontSize);
|
|
19
|
+
// doc.setFont(options.font.light.name, options.font.light.style);
|
|
20
|
+
for (const point of element?.items ?? []) {
|
|
21
|
+
y =
|
|
22
|
+
parentElementRenderer(point, indentLevel + 1, true) +
|
|
23
|
+
getCharHight(doc, options) * 0.2; // Recursively render nested list items
|
|
24
|
+
}
|
|
25
|
+
return y;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default renderList;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { ParsedElement } from '../../types/parsedElement';
|
|
3
|
+
import { RenderOption } from '../../types/renderOption';
|
|
4
|
+
import { justifyText } from '../../utils/justifyText';
|
|
5
|
+
import { HandlePageBreaks } from '../../utils/handlePageBreak';
|
|
6
|
+
import { getCharHight } from '../../utils/doc-helpers';
|
|
7
|
+
|
|
8
|
+
const renderListItem = (
|
|
9
|
+
doc: jsPDF,
|
|
10
|
+
element: ParsedElement,
|
|
11
|
+
x: number,
|
|
12
|
+
y: number,
|
|
13
|
+
indentLevel: number,
|
|
14
|
+
options: RenderOption,
|
|
15
|
+
parentElementRenderer: (
|
|
16
|
+
element: ParsedElement,
|
|
17
|
+
indentLevel: number,
|
|
18
|
+
hasRawBullet?: boolean,
|
|
19
|
+
) => number,
|
|
20
|
+
): number => {
|
|
21
|
+
const indent = indentLevel * options.page.indent;
|
|
22
|
+
if (
|
|
23
|
+
y +
|
|
24
|
+
doc.splitTextToSize(
|
|
25
|
+
element.content ?? '',
|
|
26
|
+
options.page.maxContentWidth - indent,
|
|
27
|
+
).length *
|
|
28
|
+
getCharHight(doc, options) -
|
|
29
|
+
2 * getCharHight(doc, options) >=
|
|
30
|
+
options.page.maxContentHeight
|
|
31
|
+
) {
|
|
32
|
+
HandlePageBreaks(doc, options);
|
|
33
|
+
y = options.page.topmargin;
|
|
34
|
+
}
|
|
35
|
+
if (!element.items && element.content) {
|
|
36
|
+
const lineHeight =
|
|
37
|
+
doc.getTextWidth(element.content) >
|
|
38
|
+
options.page.maxContentWidth - indent
|
|
39
|
+
? options.page.defaultLineHeightFactor
|
|
40
|
+
: options.page.defaultLineHeightFactor + 0.4;
|
|
41
|
+
y =
|
|
42
|
+
justifyText(
|
|
43
|
+
doc,
|
|
44
|
+
'\u2022 ' + element.content,
|
|
45
|
+
x + indent,
|
|
46
|
+
y,
|
|
47
|
+
options.page.maxContentWidth - indent,
|
|
48
|
+
lineHeight,
|
|
49
|
+
) + getCharHight(doc, options);
|
|
50
|
+
}
|
|
51
|
+
// Recursively render nested items if they exist
|
|
52
|
+
if (element.items && element.items.length > 0) {
|
|
53
|
+
for (const subItem of element.items) {
|
|
54
|
+
y =
|
|
55
|
+
parentElementRenderer(subItem, indentLevel + 1, true) +
|
|
56
|
+
getCharHight(doc, options) * 0.2;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return y;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default renderListItem;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { ParsedElement } from '../../types/parsedElement';
|
|
3
|
+
import { RenderOption } from '../../types/renderOption';
|
|
4
|
+
import { justifyText } from '../../utils/justifyText';
|
|
5
|
+
import { HandlePageBreaks } from '../../utils/handlePageBreak';
|
|
6
|
+
import { getCharHight } from '../../utils/doc-helpers';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders paragraph elements.
|
|
10
|
+
*/
|
|
11
|
+
const renderParagraph = (
|
|
12
|
+
doc: jsPDF,
|
|
13
|
+
element: ParsedElement,
|
|
14
|
+
x: number,
|
|
15
|
+
y: number,
|
|
16
|
+
indent: number,
|
|
17
|
+
options: RenderOption,
|
|
18
|
+
): number => {
|
|
19
|
+
doc.setFontSize(options.page.defaultFontSize);
|
|
20
|
+
// doc.setFont(options.font.light.name, options.font.light.style);
|
|
21
|
+
let content = element.content;
|
|
22
|
+
const lineHeight =
|
|
23
|
+
doc.getTextDimensions('A').h * options.page.defaultLineHeightFactor;
|
|
24
|
+
if (
|
|
25
|
+
y +
|
|
26
|
+
doc.splitTextToSize(
|
|
27
|
+
content ?? '',
|
|
28
|
+
options.page.maxContentWidth - indent,
|
|
29
|
+
).length *
|
|
30
|
+
lineHeight -
|
|
31
|
+
3 * lineHeight >=
|
|
32
|
+
options.page.maxContentHeight
|
|
33
|
+
) {
|
|
34
|
+
// ADD Possible text to Page bottom
|
|
35
|
+
const contentLeft: string[] = doc.splitTextToSize(
|
|
36
|
+
content ?? '',
|
|
37
|
+
options.page.maxContentWidth - indent,
|
|
38
|
+
);
|
|
39
|
+
const possibleContentLines: string[] = [];
|
|
40
|
+
const possibleContentY = y;
|
|
41
|
+
for (let j = 0; j < contentLeft.length; j++) {
|
|
42
|
+
if (y - 2 * lineHeight < options.page.maxContentHeight) {
|
|
43
|
+
possibleContentLines.push(contentLeft[j]);
|
|
44
|
+
y += options.page.lineSpace;
|
|
45
|
+
} else {
|
|
46
|
+
// set left content to move next page
|
|
47
|
+
if (j <= contentLeft.length - 1) {
|
|
48
|
+
content = contentLeft.slice(j).join('');
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (possibleContentLines.length > 0) {
|
|
54
|
+
y = justifyText(
|
|
55
|
+
doc,
|
|
56
|
+
possibleContentLines.join(' '),
|
|
57
|
+
x + indent,
|
|
58
|
+
possibleContentY,
|
|
59
|
+
options.page.maxContentWidth - indent,
|
|
60
|
+
options.page.defaultLineHeightFactor,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
HandlePageBreaks(doc, options);
|
|
64
|
+
y = options.page.topmargin;
|
|
65
|
+
}
|
|
66
|
+
y =
|
|
67
|
+
justifyText(
|
|
68
|
+
doc,
|
|
69
|
+
content ?? '',
|
|
70
|
+
x + indent,
|
|
71
|
+
y,
|
|
72
|
+
options.page.maxContentWidth - indent,
|
|
73
|
+
options.page.defaultLineHeightFactor,
|
|
74
|
+
) + getCharHight(doc, options);
|
|
75
|
+
|
|
76
|
+
return y;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default renderParagraph;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import jsPDF from 'jspdf';
|
|
2
|
+
import { ParsedElement } from '../../types/parsedElement';
|
|
3
|
+
import { RenderOption } from '../../types/renderOption';
|
|
4
|
+
import { HandlePageBreaks } from '../../utils/handlePageBreak';
|
|
5
|
+
import { getCharHight } from '../../utils/doc-helpers';
|
|
6
|
+
|
|
7
|
+
const renderRawItem = (
|
|
8
|
+
doc: jsPDF,
|
|
9
|
+
element: ParsedElement,
|
|
10
|
+
x: number,
|
|
11
|
+
y: number,
|
|
12
|
+
indentLevel: number,
|
|
13
|
+
hasRawBullet: boolean,
|
|
14
|
+
options: RenderOption,
|
|
15
|
+
): number => {
|
|
16
|
+
const indent = indentLevel * options.page.indent;
|
|
17
|
+
const bullet = hasRawBullet ? '\u2022 ' : ''; // unicode for bullet point
|
|
18
|
+
const lines = doc.splitTextToSize(
|
|
19
|
+
bullet + element.content,
|
|
20
|
+
options.page.maxContentWidth - indent,
|
|
21
|
+
);
|
|
22
|
+
if (
|
|
23
|
+
y + lines.length * getCharHight(doc, options) >=
|
|
24
|
+
options.page.maxContentHeight
|
|
25
|
+
) {
|
|
26
|
+
HandlePageBreaks(doc, options);
|
|
27
|
+
y = options.page.topmargin;
|
|
28
|
+
}
|
|
29
|
+
doc.text(lines, x + indent, y);
|
|
30
|
+
y += lines.length * getCharHight(doc, options);
|
|
31
|
+
|
|
32
|
+
return y;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default renderRawItem;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export type ParsedElement = {
|
|
3
|
+
type: string;
|
|
4
|
+
content?: string;
|
|
5
|
+
depth?: number;
|
|
6
|
+
items?: ParsedElement[];
|
|
7
|
+
lang?: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
src?: string;
|
|
10
|
+
alt?: string;
|
|
11
|
+
href?: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
header?: { type?: string; content?: any };
|
|
14
|
+
rows?: { type?: string; content?: any };
|
|
15
|
+
};
|