Using react i want to generate dynamic PDFs
the content of the PDF is very dynamic, a combo of texts, tables, columns, stacks … and nested tables with stacks and so on …
Before I inserting a new title like
{ text: "Overview", style: "header" }
before inserting this text i would check first how much is left of the page and check after I add that item is the remaining space going to be less than 1/3 of the whole height.
So imagine the page height is 800 after I add the item (and there’s items before that) the rest of the page will be 200 knowing 200 < ( 800 / 3 ) in that case i should push.
{ text: "", pageBreak: "before" }
after it I push my item. so what comes after too will be also in the new page and visually will looks nice.
I tried to reach that with the help of ChatGPT but not even close the the results.
my function seems not taking on count the margin coming from the content array. or if i change the font size or margin it all breaks.
here’s my function:
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
pdfMake.vfs = pdfFonts.pdfMake.vfs;
const PAGE_HEIGHT = 842; // A4 page height in points
const THRESHOLD = PAGE_HEIGHT * (2 / 3); // 2/3 of the page height
const MIN_SPACE = PAGE_HEIGHT * (1 / 3); // 1/3 of the page height
function getContentHeight(content: any): number {
const [, top, , bottom] = content?.margin ?? content?._margin ?? [0, 0, 0, 0];
// Calculate the height based on content type
if (typeof content === "string") {
return Math.ceil(content.split("\n").length * 15) + top + bottom; // Approx. 15 points per line of text
}
if (Array.isArray(content)) {
// Calculate height for an array of content (like columns)
return (
content.reduce((acc, item) => acc + getContentHeight(item), 0) +
top +
bottom
);
}
if (content.table) {
// Estimate table height: assume each row is 20 points
return content.table.body.length * 20 + top + bottom; // Adjust as necessary
}
if (content.image) {
// Assume images have a fixed height; adjust according to your images
return 150 + top + bottom; // Example height for images
}
// Handle other types, such as columns or nested tables
if (content.columns) {
return (
content.columns.reduce(
(acc: number, column: any) => acc + getContentHeight(column),
0
) +
top +
bottom
);
}
// Handle nested tables
if (content.table && content.table.body) {
return content.table.body.reduce((acc: any, row: any) => {
return (
acc +
row.reduce(
(rowAcc: any, cell: any) => rowAcc + getContentHeight(cell),
0
) +
top +
bottom
);
}, 0);
}
return 0; // Default return if no height can be calculated
}
function generatePDF(contents: any, styles: any, fileName = "document.pdf") {
const docDefinition: any = {
content: [],
styles,
};
const CONTENT_HEIGHTS: any[] = []; // To keep track of content heights
// Loop through each content item in the array
contents.forEach((content: any) => {
const contentHeight = getContentHeight(content); // Calculate height of the new content
// Calculate the current total height of all content
const currentHeight = CONTENT_HEIGHTS.reduce(
(acc, height) => acc + height,
0
);
// Check if this content is a header
const isHeader = content.style === "header";
// Calculate the remaining space on the current page
const remainingSpace = PAGE_HEIGHT - currentHeight;
// Check if adding the current content would exceed the threshold
if (currentHeight + contentHeight > THRESHOLD) {
// If exceeds the threshold, check if the header can fit
if (isHeader && remainingSpace >= MIN_SPACE) {
// If it's a header and there's enough space, add it to the current page
docDefinition.content.push(content);
CONTENT_HEIGHTS.push(contentHeight);
} else {
// If it's a header and there's not enough space, or it's not a header, push to a new page
docDefinition.content.push({ text: "", pageBreak: "before" });
CONTENT_HEIGHTS.length = 0; // Reset content heights for the new page
}
} else {
// If the content fits within the threshold, add it to the current page
docDefinition.content.push(content);
CONTENT_HEIGHTS.push(contentHeight);
}
});
// Generate and download the PDF
pdfMake.createPdf(docDefinition).download(fileName);
}
export default generatePDF;
and an example of my content array is:
const content = [
{
text: "Will Summary",
style: "header",
margin: [0, 50, 0, 0],
fontSize: 28,
},
{
text: "1 October 2024",
style: "paragraph",
margin: [0, 0, 0, 50],
},
{ text: "Overview", style: "header" },
{
columns: [
{
text: "This Document list all the assets for confurmation purpos only, and it doesnt serve any other purposes like authorty or managing any of the listed assets.",
style: "paragraph",
width: "70%",
},
],
},
{ text: "How to read this document", style: "header" },
{
columns: [
{
text: "Confirm the listed information are correct. as this informations will be used later to generate the Will.",
style: "paragraph",
width: "70%",
},
],
},
{ text: "Generated for", style: "header", margin: [0, 30, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{ style: "light_text", text: "Full name", margin: [0, 0, 0, 5] },
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Birthday", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Email", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{
text: " Identification NO",
style: "text",
margin: [3, 0, 0, 0],
},
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Address", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Marital Status", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Phone number", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
// { text: "", pageBreak: "after" },
{ text: "Executors", style: "header", margin: [0, 20, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " First Executor", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{ style: "light_text", text: "Full name", margin: [0, 0, 0, 5] },
{
style: "light_text",
text: "Passport: 123456",
margin: [0, 0, 0, 5],
},
],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Second Executor", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Third Executor", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Forth Executor", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "-",
margin: [0, 0, 0, 5],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
{ text: "Guardians", style: "header", margin: [0, 20, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " First Guardian", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{ style: "light_text", text: "Full name", margin: [0, 0, 0, 5] },
{
style: "light_text",
text: "Passport: 123456",
margin: [0, 0, 0, 5],
},
],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
{ text: "Witnesses", style: "header", margin: [0, 20, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " First Witness", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{ style: "light_text", text: "Full name", margin: [0, 0, 0, 5] },
{
style: "light_text",
text: "Passport: 123456",
margin: [0, 0, 0, 5],
},
],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
{ text: "Pets", style: "header", margin: [0, 20, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Pet", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{ style: "light_text", text: "Pet name", margin: [0, 0, 0, 5] },
{
style: "light_text",
text: "Pet type",
margin: [0, 0, 0, 5],
},
],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
{ text: "Assets", style: "header", margin: [0, 20, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150,
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150, // Use '*' to fill the remaining width
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150, // Use '*' to fill the remaining width
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150, // Use '*' to fill the remaining width
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150, // Use '*' to fill the remaining width
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
{ text: "Gifts", style: "header", margin: [0, 20, 0, 10] },
{
table: {
widths: ["25%", "75%"],
body: [
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150,
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
[
{
columns: [
{ image: uncheckedBox, width: 7, margin: [0, 2, 0, 0] },
{ text: " Asset name", style: "text", margin: [3, 0, 0, 0] },
],
margin: [0, 0, 0, 5],
},
{
stack: [
{
style: "light_text",
text: "Type: type here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Institution: Institution here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Account Number: Account Number here",
margin: [0, 0, 0, 5],
},
{
style: "light_text",
text: "Currency: (MYR) Malaysian Ringgit",
margin: [0, 0, 0, 5],
},
{
// Draw a rectangle (the bottom border)
canvas: [
{
type: "rect",
x: 0, // Start from the left
y: -5, // Adjust this value to position it
w: 150, // Use '*' to fill the remaining width
h: 1, // Height of the border
color: "#d0d0d0", // Color of the border
},
],
margin: [0, 10, 0, 0], // Optional margin
},
],
},
],
],
},
layout: "noBorders", // This removes all borders from the table
},
];
My content can be more or less, a very dynamic
The current results
You need to sign in to view this answers