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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/enums/mdTokenType.d.ts +18 -0
  4. package/dist/enums/mdTokenType.js +19 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +3 -0
  7. package/dist/parser/MdTextParser.d.ts +8 -0
  8. package/dist/parser/MdTextParser.js +91 -0
  9. package/dist/renderer/MdTextRender.d.ts +10 -0
  10. package/dist/renderer/MdTextRender.js +57 -0
  11. package/dist/renderer/components/heading.d.ts +8 -0
  12. package/dist/renderer/components/heading.js +18 -0
  13. package/dist/renderer/components/index.d.ts +5 -0
  14. package/dist/renderer/components/index.js +5 -0
  15. package/dist/renderer/components/list.d.ts +5 -0
  16. package/dist/renderer/components/list.js +12 -0
  17. package/dist/renderer/components/listItem.d.ts +5 -0
  18. package/dist/renderer/components/listItem.js +32 -0
  19. package/dist/renderer/components/paragraph.d.ts +8 -0
  20. package/dist/renderer/components/paragraph.js +44 -0
  21. package/dist/renderer/components/rawItem.d.ts +5 -0
  22. package/dist/renderer/components/rawItem.js +16 -0
  23. package/dist/types/index.d.ts +2 -0
  24. package/dist/types/index.js +2 -0
  25. package/dist/types/parsedElement.d.ts +20 -0
  26. package/dist/types/parsedElement.js +1 -0
  27. package/dist/types/renderOption.d.ts +37 -0
  28. package/dist/types/renderOption.js +1 -0
  29. package/dist/types/wordInfo.d.ts +4 -0
  30. package/dist/types/wordInfo.js +1 -0
  31. package/dist/utils/doc-helpers.d.ts +3 -0
  32. package/dist/utils/doc-helpers.js +3 -0
  33. package/dist/utils/handlePageBreak.d.ts +6 -0
  34. package/dist/utils/handlePageBreak.js +11 -0
  35. package/dist/utils/justifyText.d.ts +15 -0
  36. package/dist/utils/justifyText.js +53 -0
  37. package/package.json +63 -0
  38. package/src/enums/mdTokenType.ts +18 -0
  39. package/src/index.ts +4 -0
  40. package/src/parser/MdTextParser.ts +98 -0
  41. package/src/renderer/MdTextRender.ts +110 -0
  42. package/src/renderer/components/heading.ts +30 -0
  43. package/src/renderer/components/index.ts +5 -0
  44. package/src/renderer/components/list.ts +28 -0
  45. package/src/renderer/components/listItem.ts +62 -0
  46. package/src/renderer/components/paragraph.ts +79 -0
  47. package/src/renderer/components/rawItem.ts +35 -0
  48. package/src/types/index.ts +2 -0
  49. package/src/types/parsedElement.ts +15 -0
  50. package/src/types/renderOption.ts +38 -0
  51. package/src/types/wordInfo.ts +4 -0
  52. package/src/utils/doc-helpers.ts +6 -0
  53. package/src/utils/handlePageBreak.ts +13 -0
  54. package/src/utils/justifyText.ts +103 -0
@@ -0,0 +1,3 @@
1
+ import jsPDF from 'jspdf';
2
+ import { RenderOption } from '../types';
3
+ export declare const getCharHight: (doc: jsPDF, options: RenderOption) => number;
@@ -0,0 +1,3 @@
1
+ export const getCharHight = (doc, options) => {
2
+ return doc.getTextDimensions('H').h * options.page.defaultLineHeightFactor;
3
+ };
@@ -0,0 +1,6 @@
1
+ import jsPDF from 'jspdf';
2
+ import { RenderOption } from '../types';
3
+ /**
4
+ * Handles page breaks when content overflows.
5
+ */
6
+ export declare const HandlePageBreaks: (doc: jsPDF, options: RenderOption) => void;
@@ -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,4 @@
1
+ import { MdTextRender } from './renderer/MdTextRender';
2
+ import { MdTextParser } from './parser/MdTextParser';
3
+
4
+ export { MdTextRender, MdTextParser };
@@ -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,2 @@
1
+ export * from './parsedElement';
2
+ export * from './renderOption';
@@ -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
+ };