node js pdf dynamic

Introduction

Have you ever scoured the internet, searching for a solution to generate dynamic PDFs in Node.js? If you’re tired of “Hello World” PDFs and need a real-world solution, you’re in the right place. In this post, we’ll take you through the process of creating dynamic and visually appealing PDFs. We’ll demonstrate how to incorporate local images, pull data from a JSON source, and design complex tables with reusable components, all in Node.js.

Talk is cheap show me what you gonna make

So as can you see the pdf is what we developer develop in day today life, i wont be sharing the css part but will share the html and code used in developing this, the main challenge faced in developing this was rendering the local image and the image from remote url.

Requirements

Before we dive in, let’s cover the essentials. To create dynamic PDFs in Node.js, we’ll need two key packages: ejs for generating HTML content from templates and puppeteer for handling headless browser automation. You can install these packages with the following command:

npm i --save ejs puppeteer 

To keep things practical, we’ll use a sample JSON structure, but you can adapt this to your own data as needed. Here’s an example of the JSON structure we’ll use:

{
    "status": 1,
    "msg": "lead of car details",
    "data": {
        "VEHICLE_GENERAL_DETAILS": {
            "make": "",
            "model": "",
             ...
        },
        "VEHICLE_REGISTRATION_DETAILS": {
            "registrationType": "",
            "registrationNumber": "",
            ...
        },
        "VEHICLE_EXTERIORS": {
            "bumperFront": "",
            "headLamps": "",
            ...
        },
        "VEHICLE_INTERIORS": {
            "sterring": "",
            "dashboard": "",
            ...
        },
        "all": ["imgUrl",""],
    }
}

This JSON contains vehicle details, which will be used to populate our dynamic PDF.

Creating the Template (template.ejs)

<html>
<head>
  <meta charset="utf8">
  <title>Farman Ali</title>
  <style>
  </style>

</head>
<div id="pageHeader-first" class="header-first">
  <div class="left">
    <table class="header-table">
      <tr>
        <td><img class="logo" src="<%= Logo %>" /></td>
        <td class="info">
          <p>Vehicle Inspection Report</p>
          <p class="subtext">Certificate No.: <%= generateCertificateNumber(json.VEHICLE_GENERAL_DETAILS.id) %>
          </p>
        </td>
      </tr>
    </table>
    <table class="table1">
      <tr>
        <th>
          <div class="head-car"><img src="<%= json.all[0] %>" /></div>
          <span class="org-badge">Commercial</span>
          <div>
            <%= capitalizeFirst(json.VEHICLE_GENERAL_DETAILS.make) + ' ' +
              capitalizeFirst(json.VEHICLE_GENERAL_DETAILS.model) + ' ' +
              capitalizeFirst(json.VEHICLE_GENERAL_DETAILS.fuel_type) %>
          </div>
        </th>
      </tr>
      <tr>
        <td>
          <div class="location-tr">
            <span class="location"><img src="<%= iLocation %>" />
              <%= capitalizeFirst(json.VEHICLE_GENERAL_DETAILS.city_name) + ', ' +
                capitalizeFirst(json.VEHICLE_GENERAL_DETAILS.state_name) %>
            </span>
            <span class="cost"><img src="<%= iRupee %>" />
              <%= formatNumber(json.VEHICLE_EXTRA_DETAILS.expectation_price) %>
            </span>
          </div>
        </td>
      </tr>
    </table>
  </div>
  <div class="right">
    <table class="icon-table">
      <tr>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iRto %>" />
            <p>
              <%= valueName(json.VEHICLE_REGISTRATION_DETAILS.registration_type) %>
            </p>
            <p class="subtxt">Registration Type</p>
            <span class="divider"></span>
          </div>
        </td>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iRegNo %>" />
            <p>
              <%= valueName(json.VEHICLE_REGISTRATION_DETAILS.registration_number) %>
            </p>
            <p class="subtxt">Registration No</p>
          </div>
        </td>
      </tr>
      <tr>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iCalendar %>" />
            <p>
              <%= (json.VEHICLE_REGISTRATION_DETAILS.registration_date) %>
            </p>
            <p class="subtxt">Reg. Date</p>
            <span class="divider"></span>
          </div>
        </td>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iFuel %>" />
            <p>
              <%= valueName(json.VEHICLE_GENERAL_DETAILS.fuel_type) %>
            </p>
            <p class="subtxt">Fuel Type</p>
          </div>
        </td>
      </tr>
      <tr>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iColor %>" />
            <p>
              <%= valueName(json.VEHICLE_GENERAL_DETAILS.colour) %>
            </p>
            <p class="subtxt">Colour</p>
            <span class="divider"></span>
          </div>
        </td>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iCar %>" />
            <p>
              <%= valueName(json.VEHICLE_GENERAL_DETAILS.body_type) %>
            </p>
            <p class="subtxt">Body Type</p>
          </div>
        </td>
      </tr>
      <tr>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iManual %>" />
            <p>
              <%= valueName(json.VEHICLE_GENERAL_DETAILS.transmission) %>
            </p>
            <p class="subtxt">Transmission</p>
            <span class="divider"></span>
          </div>
        </td>
        <td class="infostruct">
          <div>
            <img class="icon" src="<%= iUser %>" />
            <p>
              <%= valueName(json.VEHICLE_REGISTRATION_DETAILS.num_of_owners) %>
            </p>
            <p class="subtxt">No. of Owner</p>
          </div>
        </td>
      </tr>
    </table>
  </div>
</div>

<div class="page">
  <% generateCarTable("Exteriors",circleCar,json.VEHICLE_GENERAL_DETAILS,'exterior'); %>
    <% renderKeyValues(json.VEHICLE_EXTERIORS,keyValue); %>
      <% generateCarTable("Exteriors",circleCar,json.VEHICLE_IMAGES,'exterior'); %>
        <% renderKeyValues(json.VEHICLE_EXTERIORS,keyValue); %>
          <% generateCarTable("Interiors",circleCar,json.VEHICLE_IMAGES,'interior'); %>
            <% renderKeyValues(json.VEHICLE_INTERIORS,keyValue); %>
              <% generateImages(json.all) %>
</div>
<table class="table5">
  <tr>
    <td class="qr-code">
      <img src="<%= iQrCode %>" />
      <p>Use your smart phone to scan the QR code and get detailed inspection report</p>
    </td>
    <td class="approved">
      <p>Approved by</p>
      <p>Farman ali <br /> License No: 83719 <br /> www.farmanali.in </p>
    </td>
  </tr>
</table>
<% 

  function generateCarTable(title, circleCar,image,image_name) { %>
        <table class="table3">
          <tr>
            <td colspan="3">
              <div class="badge">
                <%= title %>
              </div>
            </td>
          </tr>
          <% const imageArr=getImagesByName(image,image_name) 
          if(imageArr.length){ %>
          <tr>
            <td>
              <img src="<%= imageArr[0] ? imageArr[0] : Logo %>" />
            </td>
            <td>
              <img src="<%= imageArr[1] ? imageArr[1] : Logo %>" class="mb-2" /> <br />
              <img src="<%= imageArr[2] ? imageArr[2] : Logo %>" />
            </td>
            <td>
              <div class="circle-border circle5">
                <div class="circle">
                  <div class="img-wrp">
                    <img src="<%= circleCar %>" />
                    8/10
                  </div>
                </div>
              </div>
            </td>
          </tr>
      </table>
  <% } } 
     function generateImages(allImages){ %>
      <table class="table6">
        <tr>
          <% let colCount=0; 
           const numCols=2; 
           for (const img of allImages) { %>
            <td class="w50">
              <img class="allImg" src="<%= img %>" />
            </td>
            <% colCount++; 
            if (colCount===2) { 
                colCount=0; %>
        </tr>
        <tr>
          <% } } %>
        </tr>
      </table>
      <% } 
      
    function renderKeyValues(obj) { %>
      <table class="table4">
        <tr>
          <% let colCount=0; 
           const numCols=3;
            for(const key in obj) { 
              %>
                <td>
                  <%= key %>
                </td>
                <td class="<%= getStatusClass(obj[key]) %>">
                  <%= valueName(obj[key]) %>
                </td>
            <% 
                colCount++; 
                if (colCount===3) { 
                colCount=0; 
            %>
        </tr>
        <tr>
          <% } } 
           if(colCount> 0){
                const emptyCells = numCols - (colCount % numCols);
                for (let i = 0; i < emptyCells; i++) { %>
                  <td></td>
                  <% 
                  } 
            } %>
        </tr>
      </table>
      <% } %>

In this template, we define the basic HTML structure and include your CSS styling. The real magic happens when you integrate the JSON data into the HTML.

To render dynamic content, we use functions like generateCarTable, renderKeyValues, and generateImages. These functions serve specific purposes:

  1. generateCarTable : This function facilitates the rendering of a common UI structure for displaying vehicle information. It includes images, ratings, and titles and can be reused for various sections.
  2. renderKeyValues : This function facilitates in rendering the table which shows all the keys and value of the object dynamically, it also has the logic of how much column should be display in a row, currently only 3 columns is display in the row but you can modify it according to your need
  3. generateImages: This function generates image displays for the PDF.

With these functions, you can create a modular, organized, and reusable template structure. This approach is particularly helpful when dealing with objects of similar structure but different data.

Creating the Node.js Script (index.js)

const puppeteer = require('puppeteer');
const ejs = require('ejs');
const fs = require('fs');
const path = require('path');
const dataJson = require('./data.json');

let browser;

// Sample data to inject into the EJS template
const data = {
  json: dataJson.data,
};

function camelize(str) {
  return str.replace(/-([a-z])/g, function (match, letter) {
    return letter.toUpperCase();
  });
}

(async () => {
  const images = ['i-car', 'car', 'Logo'];

  for (const image of images) {
    const contents =
      'data:image/jpeg;base64,' +
      fs.readFileSync('./images/' + image + '.png', { encoding: 'base64' });
    data[camelize(image)] = contents;
  }

  if (!browser) browser = await puppeteer.launch({ headless: true });

  const page = await browser.newPage();

  // Read the EJS template file
  const templateContent = fs.readFileSync('template.ejs', 'utf8');

  // Compile the EJS template with data (if needed)
  const html = ejs.render(templateContent, data);

  // Set the HTML content of the page
  await page.setContent(html);

  // Generate a PDF or take a screenshot
  await page.pdf({
    path: 'output.pdf',
    format: 'A4',
    margin: {
      top: '20px',
      right: '20px',
      bottom: '20px',
      left: '20px',
    },
  });

  // Close the browser
  await browser.close();
})();

Now, let’s move on to the Node.js script that generates the PDF (in the index.js file). Here’s the breakdown:

  1. We import the necessary packages, including puppeteer, ejs, fs (for file system operations), and path (for working with file paths).
  2. We create a global browser variable. This variable allows us to avoid creating a new browser instance every time we run the script, which can be resource-intensive.
  3. Using Puppeteer, we launch a headless browser, create a new page, and prepare it for manipulation.
  4. We read the EJS template file (template.ejs) and compile it with the sample data from the JSON.
  5. Images are loaded, converted to base64 format, and added to the data object. These images can be referenced in the template.
  6. The script generates a PDF with specified settings. In this example, it’s configured for A4 size with 20-pixel margins.
  7. Finally, we close the browser instance.

Wrapping Up

This tutorial demonstrated how to create dynamic PDFs in Node.js, complete with images and reusable components. The sample template and Node.js script provided here should help you tackle complex PDF generation tasks. Feel free to adapt the code to your specific needs and build impressive, real-world PDF documents in Node.js.

Happy Coding!!

Leave a Reply

Your email address will not be published. Required fields are marked *