Skip to content

Commit c7828b7

Browse files
sayanichopraJeelGajera
authored andcommitted
feat(rendering): add support for code block rendering with bg
1 parent 295515b commit c7828b7

File tree

5 files changed

+97
-32
lines changed

5 files changed

+97
-32
lines changed
Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
// export const mdString = "# jsPDF Markdown Renderer\r\n\r\nA jsPDF utility to render Markdown directly into formatted PDFs with custom designs.\r\n\r\n## Table of Contents\r\n\r\n- [Installation](#installation)\r\n- [Usage](#usage)\r\n- [API](#api)\r\n- [Examples](#examples)\r\n- [Contributing](#contributing)\r\n- [License](#license)\r\n\r\n## Installation\r\n\r\nTo install the library, you can use npm:\r\n\r\n```sh\r\nnpm install jspdf-md-renderer\r\n```\r\n\r\n## Usage\r\n\r\n### Basic Example\r\n\r\nHere is a basic example of how to use the library to generate a PDF from Markdown content:\r\n\r\n```ts\r\nimport { jsPDF } from 'jspdf';\r\nimport { MdTextRender } from 'jspdf-md-renderer';\r\n\r\nconst mdString = `\r\n# Main Title\r\n\r\nThis is a brief introduction paragraph. It sets the tone for the document and introduces the main topic in a concise manner.\r\n\r\n## Section 1: Overview\r\n\r\nHere 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.\r\n\r\n## Section 2: Lists and Examples\r\n\r\nThis section showcases how to create simple and nested lists.\r\n\r\n### Simple List\r\n\r\n- Item 1\r\n- Item 2\r\n- Item 3\r\n\r\n### Nested List\r\n\r\n1. First Level 1\r\n - First Level 2\r\n - First Level 3\r\n2. Second Level 1\r\n - Second Level 2\r\n - Another Second Level 2\r\n - Nested deeper\r\n\r\n### Mixed List Example\r\n\r\n- Topic 1\r\n 1. Subtopic 1.1\r\n 2. Subtopic 1.2\r\n- Topic 2\r\n - Subtopic 2.1\r\n - Subtopic 2.2\r\n 1. Nested Subtopic 2.2.1\r\n 2. Nested Subtopic 2.2.2\r\n\r\n`;\r\n\r\nconst generatePDF = async () => {\r\n const doc = new jsPDF({\r\n unit: 'mm',\r\n format: 'a4',\r\n orientation: 'portrait',\r\n });\r\n\r\n const options = {\r\n cursor: { x: 10, y: 10 },\r\n page: {\r\n format: 'a4',\r\n unit: 'mm',\r\n orientation: 'portrait',\r\n maxContentWidth: 190,\r\n maxContentHeight: 277,\r\n lineSpace: 1.5,\r\n defaultLineHeightFactor: 1.2,\r\n defaultFontSize: 12,\r\n defaultTitleFontSize: 14,\r\n topmargin: 10,\r\n xpading: 10,\r\n xmargin: 10,\r\n indent: 10,\r\n },\r\n font: {\r\n bold: { name: 'helvetica', style: 'bold' },\r\n regular: { name: 'helvetica', style: 'normal' },\r\n light: { name: 'helvetica', style: 'light' },\r\n },\r\n endCursorYHandler: (y) => {\r\n console.log('End cursor Y position:', y);\r\n },\r\n };\r\n\r\n await MdTextRender(doc, mdString, options);\r\n doc.save('example.pdf');\r\n};\r\n\r\ngeneratePDF();\r\n```\r\n\r\n## API\r\n\r\n### `MdTextRender`\r\n\r\nRenders parsed markdown text into a jsPDF document.\r\n\r\n#### Parameters\r\n\r\n- `doc`: The jsPDF document instance.\r\n- `text`: The markdown content to render.\r\n- `options`: The render options (fonts, page margins, etc.).\r\n\r\n### `MdTextParser`\r\n\r\nParses markdown into tokens and converts to a custom parsed structure.\r\n\r\n#### Parameters\r\n\r\n- `text`: The markdown content to parse.\r\n\r\n#### Returns\r\n\r\n- `Promise<ParsedElement[]>`: Parsed markdown elements.\r\n\r\n\r\n## Supported Markdown Elements\r\n\r\nThe following Markdown elements are currently supported by `jspdf-md-renderer`:\r\n\r\n- **Headings**: `#`, `##`, `###`, etc.\r\n- **Paragraphs**\r\n- **Lists**:\r\n - Unordered lists: `-`, `*`, `+`\r\n - Ordered lists: `1.`, `2.`, `3.`, etc.\r\n\r\n\r\n## Examples\r\n\r\nYou can find more examples in the [examples](examples/test-pdf-gen) directory.\r\n\r\n## Contributing\r\n\r\nContributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) first.\r\n\r\n## License\r\n\r\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\r\n"
2-
// export const mdString = "# Sample Markdown Document\n\n## Headings\n\n# Heading Level 1\n## Heading Level 2\n### Heading Level 3\n#### Heading Level 4\n##### Heading Level 5\n###### Heading Level 6\n\n---\n\n## Emphasis\n\n- *Italic* text using asterisks.\n- _Italic_ text using underscores.\n- **Bold** text using double asterisks.\n- __Bold__ text using double underscores.\n- ***Bold and Italic*** text using triple asterisks.\n\n---\n\n## Lists\n\n### Unordered List\n- Item 1\n - Subitem 1.1\n - Subitem 1.2\n- Item 2\n\n### Ordered List\n5. Item 1\n6. Item 2\n 7. Subitem 2.1\n 2. Subitem 2.2\n\n---\n\n## Links and Images\n\n[OpenAI](https://openai.com)\n\n![Sample Image](https://via.placeholder.com/150 \"Placeholder Image\")\n\n---\n\n## Code\n\n### Inline Code\nThis is `inline code`.\n\n### Code Block\n```javascript\nfunction greet(name) {\n return `Hello, ${name}!`;\n}\nconsole.log(greet('Markdown'));\n```"
3-
export const mdString = `
4-
# Main Title
2+
export const mdString = "# Sample Markdown Document\n\n## Headings\n\n# Heading Level 1\n## Heading Level 2\n### Heading Level 3\n#### Heading Level 4\n##### Heading Level 5\n###### Heading Level 6\n\n---\n\n## Emphasis\n\n- *Italic* text using asterisks.\n- _Italic_ text using underscores.\n- **Bold** text using double asterisks.\n- __Bold__ text using double underscores.\n- ***Bold and Italic*** text using triple asterisks.\n\n---\n\n## Lists\n\n### Unordered List\n- Item 1\n - Subitem 1.1\n - Subitem 1.2\n- Item 2\n\n### Ordered List\n5. Item 1\n6. Item 2\n 7. Subitem 2.1\n 2. Subitem 2.2\n\n---\n\n## Links and Images\n\n[OpenAI](https://openai.com)\n\n![Sample Image](https://via.placeholder.com/150 \"Placeholder Image\")\n\n---\n\n## Code\n\n### Inline Code\nThis is `inline code`.\n\n### Code Block\n```javascript\nfunction greet(name) {\n return `Hello, ${name}!`;\n}\nconsole.log(greet('Markdown'));\n```"
3+
// export const mdString = `
4+
// # Main Title
55

6-
This is a brief introduction paragraph. It sets the tone for the document and introduces the main topic in a concise manner.
6+
// This is a brief introduction paragraph. It sets the tone for the document and introduces the main topic in a concise manner.
77

8-
## Section 1: Overview
8+
// ## Section 1: Overview
99

10-
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.
10+
// 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.
1111

12-
## Section 2: Lists and Examples
12+
// ## Section 2: Lists and Examples
1313

14-
This section showcases how to create simple and nested lists.
14+
// This section showcases how to create simple and nested lists.
1515

16-
### Simple List
16+
// ### Simple List
1717

18-
- Item 1
19-
- Item 2
20-
- Item 3
18+
// - Item 1
19+
// - Item 2
20+
// - Item 3
2121

22-
### Nested List
22+
// ### Nested List
2323

24-
1. First Level 1
25-
- First Level 2
26-
- First Level 3
27-
2. Second Level 1
28-
- Second Level 2
29-
- Another Second Level 2
30-
- Nested deeper
24+
// 1. First Level 1
25+
// - First Level 2
26+
// - First Level 3
27+
// 2. Second Level 1
28+
// - Second Level 2
29+
// - Another Second Level 2
30+
// - Nested deeper
3131

32-
### Mixed List Example
32+
// ### Mixed List Example
3333

34-
- Topic 1
35-
1. Subtopic 1.1
36-
2. Subtopic 1.2
37-
- Topic 2
38-
- Subtopic 2.1
39-
- Subtopic 2.2
40-
1. Nested Subtopic 2.2.1
41-
2. Nested Subtopic 2.2.2
34+
// - Topic 1
35+
// 1. Subtopic 1.1
36+
// 2. Subtopic 1.2
37+
// - Topic 2
38+
// - Subtopic 2.1
39+
// - Subtopic 2.2
40+
// 1. Nested Subtopic 2.2.1
41+
// 2. Nested Subtopic 2.2.2
4242

43-
`;
43+
// `;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"lint:fix": "eslint src/** --fix",
2525
"format": "prettier --write .",
2626
"test": "echo \"Error: no test specified\" && exit 1",
27-
"prepare": "npm run build"
27+
"prepare": "npm run build",
28+
"watch": "tsc --watch"
2829
},
2930
"repository": {
3031
"type": "git",

src/renderer/MdTextRender.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
renderListItem,
1212
renderParagraph,
1313
renderRawItem,
14+
renderCodeBlock
1415
} from './components';
1516
import { getCharHight } from '../utils/doc-helpers';
1617

@@ -27,7 +28,7 @@ export const MdTextRender = async (
2728
options: RenderOption,
2829
) => {
2930
const parsedElements = await MdTextParser(text);
30-
console.log(parsedElements);
31+
// console.log(parsedElements);
3132

3233
let y = options.cursor.y;
3334
const x = options.cursor.x;
@@ -86,6 +87,17 @@ export const MdTextRender = async (
8687
case MdTokenType.Hr:
8788
y = renderHR(doc, y, options);
8889
break;
90+
case MdTokenType.Code:
91+
y = renderCodeBlock(
92+
doc,
93+
element,
94+
x,
95+
y,
96+
indentLevel,
97+
hasRawBullet,
98+
options,
99+
);
100+
break;
89101
case MdTokenType.Raw:
90102
case MdTokenType.Text:
91103
y = renderRawItem(

src/renderer/components/code.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import jsPDF from 'jspdf';
2+
import { ParsedElement } from '../../types';
3+
import { RenderOption } from '../../types';
4+
import { HandlePageBreaks } from '../../utils/handlePageBreak';
5+
import { getCharHight } from '../../utils/doc-helpers';
6+
7+
const renderCodeBlock = (
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+
if (
18+
y +
19+
doc.splitTextToSize(
20+
element.code ?? '',
21+
options.page.maxContentWidth - indent,
22+
).length *
23+
getCharHight(doc, options) -
24+
2 * getCharHight(doc, options) >=
25+
options.page.maxContentHeight
26+
) {
27+
HandlePageBreaks(doc, options);
28+
y = options.page.topmargin;
29+
}
30+
31+
const totalHeight = doc.splitTextToSize(
32+
element.code ?? '',
33+
options.page.maxContentWidth - indent,
34+
).length * getCharHight(doc, options) ;
35+
y+=options.page.lineSpace
36+
doc.setFillColor("#EEEEEE")
37+
doc.setDrawColor('#eee')
38+
doc.roundedRect(x,y-options.page.lineSpace,options.page.maxContentWidth,totalHeight,2,2,"FD")
39+
// doc.rect(x,y-options.page.lineSpace,options.page.maxContentWidth,totalHeight,"F")
40+
// doc.setTextColor('#')
41+
doc.setFontSize(10)
42+
doc.text(element.lang??'',x+options.page.maxContentWidth-doc.getTextWidth(element.lang??'')-options.page.lineSpace/2,y)
43+
doc.setFontSize(options.page.defaultFontSize)
44+
// y+=options.page.lineSpace
45+
doc.text(element.code ?? '', x+4, y);
46+
47+
y += totalHeight
48+
return y;
49+
};
50+
51+
export default renderCodeBlock;

src/renderer/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { default as renderList } from './list';
44
export { default as renderListItem } from './listItem';
55
export { default as renderRawItem } from './rawItem';
66
export { default as renderHR } from './hr';
7+
export { default as renderCodeBlock } from './code';

0 commit comments

Comments
 (0)