Skip to content

Commit 79dd14f

Browse files
committed
chore: handle long text rendring with same indentation
1 parent 32926ec commit 79dd14f

File tree

5 files changed

+176
-126
lines changed

5 files changed

+176
-126
lines changed

package.json

Lines changed: 65 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,68 @@
11
{
2-
"name": "jspdf-md-renderer",
3-
"version": "1.6.1",
4-
"description": "A jsPDF utility to render Markdown directly into formatted PDFs with custom designs.",
5-
"main": "dist/index.js",
6-
"module": "dist/index.mjs",
7-
"types": "dist/index.d.ts",
8-
"exports": {
9-
".": {
10-
"import": "./dist/index.mjs",
11-
"require": "./dist/index.js",
12-
"types": "./dist/index.d.ts"
2+
"name": "jspdf-md-renderer",
3+
"version": "1.6.1",
4+
"description": "A jsPDF utility to render Markdown directly into formatted PDFs with custom designs.",
5+
"main": "dist/index.js",
6+
"module": "dist/index.mjs",
7+
"types": "dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"import": "./dist/index.mjs",
11+
"require": "./dist/index.js",
12+
"types": "./dist/index.d.ts"
13+
},
14+
"./types": "./dist/types/index.d.ts"
1315
},
14-
"./types": "./dist/types/index.d.ts"
15-
},
16-
"files": [
17-
"dist",
18-
"types",
19-
"LICENSE",
20-
"README.md"
21-
],
22-
"scripts": {
23-
"dev": "npm run format && npm run lint:fix && npm run build",
24-
"build": "rimraf dist && vite build",
25-
"lint": "eslint src/**",
26-
"lint:fix": "eslint src/** --fix",
27-
"format": "prettier --write .",
28-
"test": "echo \"Error: no test specified\" && exit 1",
29-
"prepare": "npm run build",
30-
"watch": "vite build --watch"
31-
},
32-
"repository": {
33-
"type": "git",
34-
"url": "git+https://github.com/JeelGajera/jspdf-md-renderer.git"
35-
},
36-
"keywords": [
37-
"jspdf",
38-
"markdown",
39-
"pdf",
40-
"renderer"
41-
],
42-
"author": "Jeel Gajera <jeelgajera200@gmail.com>",
43-
"license": "MIT",
44-
"bugs": {
45-
"url": "https://github.com/JeelGajera/jspdf-md-renderer/issues"
46-
},
47-
"homepage": "https://github.com/JeelGajera/jspdf-md-renderer#readme",
48-
"dependencies": {
49-
"jspdf": "^3.0.1",
50-
"jspdf-md-renderer": "file:",
51-
"marked": "^15.0.3"
52-
},
53-
"devDependencies": {
54-
"@eslint/js": "^9.16.0",
55-
"@types/node": "^22.10.2",
56-
"@typescript-eslint/eslint-plugin": "^8.18.0",
57-
"@typescript-eslint/parser": "^8.18.0",
58-
"eslint": "^9.16.0",
59-
"eslint-config-prettier": "^10.1.2",
60-
"eslint-plugin-prettier": "^5.2.1",
61-
"globals": "^16.0.0",
62-
"prettier": "^3.4.2",
63-
"rimraf": "^6.0.1",
64-
"typescript": "^5.7.2",
65-
"typescript-eslint": "^8.18.0",
66-
"vite": "^6.2.4",
67-
"vite-plugin-dts": "^4.5.3"
68-
}
16+
"files": [
17+
"dist",
18+
"types",
19+
"LICENSE",
20+
"README.md"
21+
],
22+
"scripts": {
23+
"dev": "npm run format && npm run lint:fix && npm run build",
24+
"build": "rimraf dist && vite build",
25+
"lint": "eslint src/**",
26+
"lint:fix": "eslint src/** --fix",
27+
"format": "prettier --write .",
28+
"test": "echo \"Error: no test specified\" && exit 1",
29+
"watch": "vite build --watch"
30+
},
31+
"repository": {
32+
"type": "git",
33+
"url": "git+https://github.com/JeelGajera/jspdf-md-renderer.git"
34+
},
35+
"keywords": [
36+
"jspdf",
37+
"markdown",
38+
"pdf",
39+
"renderer"
40+
],
41+
"author": "Jeel Gajera <jeelgajera200@gmail.com>",
42+
"license": "MIT",
43+
"bugs": {
44+
"url": "https://github.com/JeelGajera/jspdf-md-renderer/issues"
45+
},
46+
"homepage": "https://github.com/JeelGajera/jspdf-md-renderer#readme",
47+
"dependencies": {
48+
"jspdf": "^3.0.1",
49+
"jspdf-md-renderer": "file:",
50+
"marked": "^15.0.3"
51+
},
52+
"devDependencies": {
53+
"@eslint/js": "^9.16.0",
54+
"@types/node": "^22.10.2",
55+
"@typescript-eslint/eslint-plugin": "^8.18.0",
56+
"@typescript-eslint/parser": "^8.18.0",
57+
"eslint": "^9.16.0",
58+
"eslint-config-prettier": "^10.1.2",
59+
"eslint-plugin-prettier": "^5.2.1",
60+
"globals": "^16.0.0",
61+
"prettier": "^3.4.2",
62+
"rimraf": "^6.0.1",
63+
"typescript": "^5.7.2",
64+
"typescript-eslint": "^8.18.0",
65+
"vite": "^6.2.4",
66+
"vite-plugin-dts": "^4.5.3"
67+
}
6968
}

src/renderer/MdTextRender.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const MdTextRender = async (
2929
options: RenderOption,
3030
) => {
3131
const parsedElements = await MdTextParser(text);
32-
console.log(parsedElements);
32+
// console.log(parsedElements);
3333
let cursor: Cursor = {
3434
x: options.cursor.x,
3535
y: options.cursor.y,
@@ -120,7 +120,7 @@ export const MdTextRender = async (
120120
element,
121121
cursor,
122122
indent,
123-
options
123+
options,
124124
);
125125
break;
126126
case MdTokenType.Raw:
@@ -135,7 +135,7 @@ export const MdTextRender = async (
135135
renderElement,
136136
start,
137137
ordered,
138-
justify
138+
justify,
139139
);
140140
break;
141141
default:

src/renderer/components/inlineText.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ const renderInlineText = (
7777
// render first line
7878
doc.text(
7979
firstLine,
80-
cursor.x + (indent >= 2 ? indent + 2 * spaceMultiplier(style) : 0),
80+
cursor.x +
81+
(indent >= 2 ? indent + 2 * spaceMultiplier(style) : 0),
8182
cursor.y,
8283
{
8384
baseline: 'top',
@@ -86,7 +87,7 @@ const renderInlineText = (
8687
);
8788

8889
// update cursor position
89-
cursor.x = indent;
90+
cursor.x = options.page.xpading + indent;
9091
cursor.y += getCharHight(doc, options);
9192

9293
// render rest of the content in the next line with up to indent

src/renderer/components/listItem.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const renderListItem = (
7777
element.type === MdTokenType.List
7878
? indentLevel
7979
: indentLevel + 1;
80-
80+
8181
parentElementRenderer(
8282
subItem,
8383
newIndentLevel,
@@ -93,7 +93,7 @@ const renderListItem = (
9393
subItem,
9494
cursor,
9595
baseIndent,
96-
options
96+
options,
9797
);
9898
}
9999

@@ -103,26 +103,35 @@ const renderListItem = (
103103
}
104104
} else if (element.content) {
105105
// handle text with line breaks page break & multiple lines texts
106-
const textLines = doc.splitTextToSize(
107-
element.content,
108-
options.page.maxContentWidth - baseIndent - cursor.x,
109-
);
110-
// Render text
111-
doc.text(
112-
textLines,
113-
cursor.x + baseIndent,
114-
cursor.y,
115-
{
106+
const bulletX = cursor.x + baseIndent;
107+
const bulletWidth = doc.getTextWidth(bullet);
108+
const textMaxWidth =
109+
options.page.maxContentWidth - baseIndent - bulletWidth;
110+
// Split the content into lines, accounting for the bullet only on the first line
111+
const textLines = doc.splitTextToSize(element.content, textMaxWidth);
112+
// Render first line with bullet
113+
if (textLines.length > 0) {
114+
doc.text(textLines[0], bulletX + bulletWidth, cursor.y, {
116115
baseline: 'top',
117-
maxWidth: options.page.maxContentWidth - baseIndent - cursor.x,
118-
},
119-
);
120-
// Update cursor position\
121-
cursor.y += getCharHight(doc, options) * textLines.length;
122-
cursor.x = options.page.xmargin + baseIndent;
123-
// Move the cursor forward by the text width
124-
const contentWidth = doc.getTextWidth(element.content);
125-
cursor.x += contentWidth;
116+
maxWidth: textMaxWidth,
117+
});
118+
// Render bullet (already rendered above, but for clarity)
119+
// doc.text(bullet, bulletX, cursor.y, { baseline: 'top' });
120+
// Render wrapped lines (if any)
121+
for (let i = 1; i < textLines.length; i++) {
122+
cursor.y += getCharHight(doc, options);
123+
doc.text(textLines[i], bulletX + bulletWidth, cursor.y, {
124+
baseline: 'top',
125+
maxWidth: textMaxWidth,
126+
});
127+
}
128+
// Update cursor position
129+
cursor.y += getCharHight(doc, options);
130+
cursor.x = options.page.xmargin + baseIndent;
131+
// Move the cursor forward by the text width (optional, but keep for compatibility)
132+
const contentWidth = doc.getTextWidth(element.content);
133+
cursor.x += contentWidth;
134+
}
126135
}
127136

128137
return cursor;

src/renderer/components/rawItem.ts

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,47 +37,88 @@ const renderRawItem = (
3737
}
3838
} else {
3939
const indent = indentLevel * options.page.indent;
40-
const bullet = hasRawBullet ? (ordered ? `${start}. ` : '\u2022 ') : ''; // unicode for bullet point
41-
const lines = doc.splitTextToSize(
42-
bullet + element.content,
43-
options.page.maxContentWidth - indent,
44-
);
45-
if (
46-
cursor.y + lines.length * getCharHight(doc, options) >=
47-
options.page.maxContentHeight
48-
) {
49-
HandlePageBreaks(doc, options);
50-
cursor.y = options.page.topmargin;
51-
}
52-
53-
if (justify) {
54-
cursor.y =
55-
justifyText(
56-
doc,
57-
bullet + element.content,
58-
cursor.x + indent,
40+
const bullet = hasRawBullet ? (ordered ? `${start}. ` : '\u2022 ') : '';
41+
if (hasRawBullet && bullet) {
42+
// Align all wrapped lines with the first line's text (after bullet)
43+
const bulletWidth = doc.getTextWidth(bullet);
44+
const textMaxWidth =
45+
options.page.maxContentWidth - indent - bulletWidth;
46+
const textLines = doc.splitTextToSize(
47+
element.content || '',
48+
textMaxWidth,
49+
);
50+
if (textLines.length > 0) {
51+
// Render bullet
52+
doc.text(bullet, cursor.x + indent, cursor.y, {
53+
baseline: 'top',
54+
});
55+
// Render first line
56+
doc.text(
57+
textLines[0],
58+
cursor.x + indent + bulletWidth,
5959
cursor.y,
60-
options.page.maxContentWidth - indent,
61-
options.page.defaultLineHeightFactor,
62-
).y + getCharHight(doc, options);
63-
// handle x
64-
cursor.x = options.page.xpading;
60+
{
61+
baseline: 'top',
62+
maxWidth: textMaxWidth,
63+
},
64+
);
65+
// Render wrapped lines
66+
for (let i = 1; i < textLines.length; i++) {
67+
cursor.x = options.page.xpading;
68+
cursor.y += getCharHight(doc, options);
69+
doc.text(
70+
textLines[i],
71+
cursor.x + indent + bulletWidth,
72+
cursor.y,
73+
{
74+
baseline: 'top',
75+
maxWidth: textMaxWidth,
76+
},
77+
);
78+
}
79+
// Update cursor position
80+
cursor.y += getCharHight(doc, options);
81+
cursor.x = options.page.xpading + indent;
82+
const contentWidth = doc.getTextWidth(element.content || '');
83+
cursor.x += contentWidth;
84+
}
6585
} else {
66-
// Print text
67-
doc.text(bullet + element.content, cursor.x + indent, cursor.y, {
68-
baseline: 'top',
69-
});
70-
// Move x forward
71-
cursor.x += doc.getTextWidth(bullet + element.content);
72-
// Handle page break
86+
const lines = doc.splitTextToSize(
87+
element.content || '',
88+
options.page.maxContentWidth - indent,
89+
);
7390
if (
74-
cursor.x >=
75-
options.page.xpading + options.page.maxContentWidth
91+
cursor.y + lines.length * getCharHight(doc, options) >=
92+
options.page.maxContentHeight
7693
) {
7794
HandlePageBreaks(doc, options);
78-
cursor.x = options.page.xpading;
7995
cursor.y = options.page.topmargin;
8096
}
97+
if (justify) {
98+
cursor.y =
99+
justifyText(
100+
doc,
101+
element.content || '',
102+
cursor.x + indent,
103+
cursor.y,
104+
options.page.maxContentWidth - indent,
105+
options.page.defaultLineHeightFactor,
106+
).y + getCharHight(doc, options);
107+
cursor.x = options.page.xpading;
108+
} else {
109+
doc.text(element.content || '', cursor.x + indent, cursor.y, {
110+
baseline: 'top',
111+
});
112+
cursor.x += doc.getTextWidth(element.content || '');
113+
if (
114+
cursor.x >=
115+
options.page.xpading + options.page.maxContentWidth
116+
) {
117+
HandlePageBreaks(doc, options);
118+
cursor.x = options.page.xpading;
119+
cursor.y = options.page.topmargin;
120+
}
121+
}
81122
}
82123
}
83124
return cursor;

0 commit comments

Comments
 (0)