OiO.lk Blog pdf Generate Dynamic PDF documents ( React.js & pdfMake)
pdf

Generate Dynamic PDF documents ( React.js & pdfMake)


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

Exit mobile version