Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
RogerCheng
Associate
Associate

Overview


 

Portable Document Format (PDF) is one of the most common document formats used for electronic documents in businesses due to its nature of being read-only, having rich formatting styles, and being compact in size, etc. Often enough, a business application needs to generate PDF documents based on business data and images (such as receipts, certificates, etc.). This blog post shows examples of how to generate a PDF document in business applications using Node.js.

SAP Business Technology Platform (BTP) provides a fast and easy way to create, run, manage, and scale business applications in the cloud. A business application in BTP typically includes a user interface, microservices that contain business logic, and technical operations. There are also backend systems such as Enterprise Resource Planning (ERP), Supply Chain Management, Blockchain network etc. that store the business data.

 


Typical Business Application


 

The JavaScript PDF document generation library for Node.js used in this example is PDFKit (https://www.npmjs.com/package/pdfkit). The documentation can be found at the PDFKit.org website (https://pdfkit.org/).

Other libraries used in this example are Request (https://www.npmjs.com/package/request) and axios (https://www.npmjs.com/package/axios). During implementation, you only need either Request or axios for requests/responses. However, for demonstration purpose, this article shows how these two libraries can be used to load an image from a web URL and display it in the PDF document.

The sample code shown in this article is partly based on another article Generating a PDF in Nodejs (https://levelup.gitconnected.com/generating-pdf-in-nodejs-201e8d9fa3d8).

 

Sample Code Using PDFKit Library


 

From the command line or terminal, add the dependency libraries after project initialization.
npm init
npm install pdfkit
npm install request
npm install axios

 

Create a Node module (RequestUtil.js) to handle requests. The doRequest function returns a Promise object which eventually will return a resolved state or a rejected state. In the sample code of this article, the doRequest function is called to get the content of an image file from a web URL.
"use strict";
const _REQUEST = require("request");

module.exports =
{
doRequest: doRequest
}

async function doRequest(requestPayload)
{
return new Promise(function(resolve, reject)
{
_REQUEST(requestPayload, function(err, response)
{
if (err)
{
reject(err);
}
else
{
resolve(response);
}
});
});
}

 

Create the main function (GenInvoice.js) to provide the sample invoice data to be displayed in the generated PDF document. The sample invoice data is defined as a JSON object. It is passed as an argument into InvoiceGenerator.js to generate the PDF document.
"use strict"
const _INVOICE_GENERATOR = require("./InvoiceGenerator");

const _kINVOICE_DATA =
{
invoiceNumber: "9900001",
dueDate: "March 31, 2021",
subtotal: 8000,
paid: 500,
memo: "Delivery expected in 7 days",
addresses:
{
billing:
{
name: "Santa Claus",
address: "1 Elf Street",
city: "Arctic City",
state: "Arctic Circle",
postalCode: "H0H 0H0",
country: "North Pole"
}
},
items:
[
{
itemCode: "12341",
itemDescription: "Best Run laptop computer",
quantity: 4,
price: 2985.00
},
{
itemCode: "12342",
itemDescription: "Best Run desktop computer",
quantity: 2,
price: 2295.00
}
]
};

let ig = new _INVOICE_GENERATOR(_kINVOICE_DATA);

void async function main()
{
await ig.generate();
}()

 

Create a class (InvoiceGenerator.js) for calling the PDFKit library to generate the PDF document.
"use strict"
const _AXIOS = require("axios");
const _FS = require("fs");
const _PDFKIT = require("pdfkit");
const _REQUEST = require("request");
const _REQUEST_UTIL = require("./RequestUtil");

class InvoiceGenerator
{
constructor(invoice)
{
this.invoice = invoice;
}

async generate()
{
let pdfkit = new _PDFKIT();
let pdfOutputFile = `./Invoice-${this.invoice.invoiceNumber}.pdf`;
// [COMMENT] The PDF file is to be written to the file system.
pdfkit.pipe(_FS.createWriteStream(pdfOutputFile));
await this.writeContent(pdfkit);
pdfkit.end();
}

async writeContent(pdfkit)
{
this.generateHeaders(pdfkit);
this.generateTable(pdfkit);
this.drawBoxes(pdfkit);
await this.displayImage(pdfkit);
this.generateBulletList(pdfkit);
this.generateNumberedList(pdfkit);
this.generateLetteredList(pdfkit);
this.generateMutliLevelList(pdfkit);
this.generateHyperlink(pdfkit);
this.generateFooter(pdfkit);
}

generateHeaders(pdfkit) {...}
generateTable(pdfkit) {...}
drawBoxes(pdfkit) {...}
async displayImage(pdfkit) {...}
// displayImageBase64(pdfkit) {...}
generateBulletList(pdfkit) {...}
generateNumberedList(pdfkit) {...}
generateLetteredList(pdfkit) {...}
generateMultiLevelList(pdfkit) {...}
generateHyperlink(pdfkit) {...}
generateFooter(pdfkit) {...}
}

module.exports = InvoiceGenerator;

 

Function generateHeaders()


This function generates the header section of the PDF document.
generateHeaders(pdfkit)
{
let billingAddress = this.invoice.addresses.billing;
pdfkit.image("./SAP.png", 25, 25, {width: 150})
.fillColor("#000")
.fontSize(20)
.text("INVOICE", 400, 25, {align: "right"})
.fontSize(10)
.text(`Invoice Number: ${this.invoice.invoiceNumber}`, {align: "right"})
.text(`Due Date: ${this.invoice.dueDate}`, {align: "right"})
.text(`Balance Due: €${this.invoice.subtotal - this.invoice.paid}`, {align: "right"});
// [COMMENT] A blank line between Balance Due and Billing Address.
pdfkit.moveDown();
pdfkit.text(`Billing Address:\n${billingAddress.name}`, {align: "right"})
.text(`${billingAddress.address}\n${billingAddress.city}`, {align: "right"})
.text(`${billingAddress.state} ${billingAddress.postalCode}`, {align: "right"})
.text(`${billingAddress.country}`, {align: "right"});
const _kPAGE_BEGIN = 25;
const _kPAGE_END = 580;
// [COMMENT] Draw a horizontal line.
pdfkit.moveTo(_kPAGE_BEGIN, 200)
.lineTo(_kPAGE_END, 200)
.stroke();
pdfkit.text(`Memo: ${this.invoice.memo}`, 50, 210);
pdfkit.moveTo(_kPAGE_BEGIN, 250)
.lineTo(_kPAGE_END, 250)
.stroke();
}

 


The Generated Header


 

Function generateTable()


This function generates the table that contains a list of items.
generateTable(pdfkit)
{
const _kTABLE_TOP_Y = 270;
const _kITEM_CODE_X = 50;
const _kDESCRIPTION_X = 100;
const _kQUANTITY_X = 250;
const _kPRICE_X = 300;
const _kAMOUNT_X = 350;
pdfkit.fontSize(10)
.text("Item Code", _kITEM_CODE_X, _kTABLE_TOP_Y, {bold: true, underline: true})
.text("Description", _kDESCRIPTION_X, _kTABLE_TOP_Y, {bold: true, underline: true})
.text("Quantity", _kQUANTITY_X, _kTABLE_TOP_Y, {bold: true, underline: true})
.text("Price", _kPRICE_X, _kTABLE_TOP_Y, {bold: true, underline: true})
.text("Amount", _kAMOUNT_X, _kTABLE_TOP_Y, {bold: true, underline: true});
let items = this.invoice.items;
for (let idx = 0; idx < items.length; idx++)
{
let item = items[idx];
let yCoord = _kTABLE_TOP_Y + 25 + (idx * 25);
pdfkit.fontSize(10)
.text(`${item.itemCode}`, _kITEM_CODE_X, yCoord)
.text(`${item.itemDescription}`, _kDESCRIPTION_X, yCoord)
.text(`${item.quantity}`, _kQUANTITY_X, yCoord)
.text(`€${item.price}`, _kPRICE_X, yCoord)
.text(`€${item.price * item.quantity}`, _kAMOUNT_X, yCoord);
}
}

 


The Generated Table


 

Function drawBoxes()


This function draws some boxes.
drawBoxes(pdfkit)
{
const _kTOP_H_LINE_X = 100;
const _kTOP_H_LINE_Y = 500;
const _kBOTTOM_H_LINE_X = 100;
const _kBOTTOM_H_LINE_Y = 525;
const _kH_LINE_LENGTH = 400;
// [COMMENT] Draw the 2 horizontal lines.
pdfkit.moveTo(_kTOP_H_LINE_X, _kTOP_H_LINE_Y)
.lineTo(_kH_LINE_LENGTH, _kTOP_H_LINE_Y)
.stroke();
pdfkit.moveTo(_kBOTTOM_H_LINE_X, _kBOTTOM_H_LINE_Y)
.lineTo(_kH_LINE_LENGTH, _kBOTTOM_H_LINE_Y)
.stroke();
const _kV_LINE_1_X = 100;
const _kV_LINE_2_X = 200;
const _kV_LINE_3_X = 300;
const _kV_LINE_4_X = 400;
const _kV_LINE_LENGTH = 25;
// [COMMENT] Draw the vertical lines.
pdfkit.moveTo(_kV_LINE_1_X, _kTOP_H_LINE_Y)
.lineTo(_kV_LINE_1_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
.stroke();
pdfkit.moveTo(_kV_LINE_2_X, _kTOP_H_LINE_Y)
.lineTo(_kV_LINE_2_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
.stroke();
pdfkit.moveTo(_kV_LINE_3_X, _kTOP_H_LINE_Y)
.lineTo(_kV_LINE_3_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
.stroke();
pdfkit.moveTo(_kV_LINE_4_X, _kTOP_H_LINE_Y)
.lineTo(_kV_LINE_4_X, _kTOP_H_LINE_Y + _kV_LINE_LENGTH)
.stroke();
// [COMMENT] Write the text “Box 1”, “Box 2”, and “Box 3” in the boxes.
pdfkit.fontSize(10)
.text("Box 1", _kV_LINE_1_X + 5, _kTOP_H_LINE_Y + 5)
.text("Box 2", _kV_LINE_2_X + 5, _kTOP_H_LINE_Y + 5)
.text("Box 3", _kV_LINE_3_X + 5, _kTOP_H_LINE_Y + 5)
}

 


The Drawn Boxes


 

Function displayImage() - Using Request


This function gets an image from a web URL using the Request library and displays it in the PDF document.
async displayImage(pdfkit)
{
let requestPayload =
{
url: "https://www.sap.com/dam/application/shared/photos/hero-product-category/sap-s4hana-custom-hero.jpg/_jcr_content/renditions/sap-s4hana-custom-hero_3198_1648.jpg.adapt.1599_824.false.false.false.false.jpg/1563795394722.jpg",
method: "GET",
encoding: null
}
let response = await _REQUEST_UTIL.doRequest(requestPayload);
let statusCode = response.statusCode;
let responseBody = response.body;
let imgBinary = Buffer.from(responseBody, "binary");
// [COMMENT] The image is encoded in base64 string.
let imgBase64 = imgBinary.toString("base64");
let img = Buffer.from(imgBase64, "base64");
pdfkit.addPage().text("Image").image(img, 100, 100, {width: 400});
}

 

Function displayImage() - Using axios


This function gets an image from a web URL using the axios library and displays it in the PDF document.
async displayImage(pdfkit)
{
let imageUrl = "https://www.sap.com/dam/application/shared/photos/hero-product-category/sap-s4hana-custom-hero.jpg/_jcr_content/renditions/sap-s4hana-custom-hero_3198_1648.jpg.adapt.1599_824.false.false.false.false.jpg/1563795394722.jpg";
let response = await _AXIOS.request(
{
method: "GET",
url: imageUrl,
responseEncoding: "binary"
});
let responseData = response.data;
let imgBinary = Buffer.from(responseData, "binary");
// [COMMENT] The image is encoded in base64 string.
let imgBase64 = imgBinary.toString("base64");
let img = Buffer.from(imgBase64, "base64");
pdfkit.addPage().text("Image").image(img, 100, 100, {width: 400});
}

 

Function displayImageBase64() – Image Encoded in Base64 String


This function displays an image encoded in base64 string in the PDF document. An image can be read from the file system, encoded in base64 and displayed in the PDF document.
displayImage(pdfkit)
{
// [COMMENT] The image is encoded in base64 string.
let imgBase64 = "/9j/4AAQSkZJRgABAgAA ... atrdbG1sbH0LY2Nn/9k=";
let img = Buffer.from(imgBase64, "base64");
pdfkit.addPage().text("Image").image(img, 100, 100, {width: 400});
}

 


The Displayed Image


 

Function generateBulletList()


This function generates a bullet list of items.
generateBulletList(pdfkit)
{
let theList = ["Item A", "Item B", "Item C"];
let xCoord = 100;
let yCoord = 400;
pdfkit.fontSize(10)
.fillColor("green")
.list(theList, xCoord, yCoord, {listType: "bullet"});
}

 


The Generated Bullet List


 

Function generateNumberedList()


This function generates a numbered list of items.
generateNumberedList(pdfkit)
{
let theList = ["Item A", "Item B", "Item C"];
let xCoord = 100;
let yCoord = 450;
pdfkit.fontSize(10)
.fillColor("purple")
.list(theList, xCoord, yCoord, {listType: "numbered"});
}

 


The Generated Numbered List


 

Function generateLetteredList()


This function generates a lettered list of items.
generateLetteredList(pdfkit)
{
let theList = ["Item A", "Item B", "Item C"];
let xCoord = 100;
let yCoord = 500;
pdfkit.fontSize(10)
.fillColor("purple")
.list(theList, xCoord, yCoord, {listType: "lettered"});
}

 


The Generated Lettered List


 

Function generateMultiLevelList()


This function generates a multi-level list of items.
generateMutliLevelList(pdfkit)
{
let theList = ["Item A", ["Item A1", "Item A2"], "Item B", "Item C"];
let xCoord = 100;
let yCoord = 550;
pdfkit.fontSize(10)
.fillColor("purple")
.list(theList, xCoord, yCoord, {listType: "bullet"});
}

 


The Generated Multi-Level List


 

Function generateHyperlink()


This function generates a clickable hyperlink.
generateHyperlink(pdfkit)
{
let linkText = "SAP.com";
let xCoord = 100;
let yCoord = 650;
pdfkit.fontSize(10)
.fillColor("blue")
.text(linkText, xCoord, yCoord);
let width = pdfkit.widthOfString(linkText);
let height = pdfkit.currentLineHeight();
pdfkit.underline(100, 650, width, height, {color: "blue"})
.link(100, 650, width, height, "https://www.sap.com");
}

The tooltip in the figure below shows the website URL of the hyperlink.

 


The Generated Clickable Hyperlink


 

Function generateFooter()


This function generates the footer section of the PDF document.
generateFooter(pdfkit)
{
pdfkit.fontSize(10)
.fillColor("black")
.text("Payment due upon receipt.", 50, 700, {align: "center"});
}

 


The Generated Footer


 

This is the sample of the generated PDF document.


The Generated PDF File


 

Summary


In this blog post, we introduced PDF generation using Node.js. There are other considerations in doing a project, such as template management, document repository management, etc. However, implementations of those properly depend on the business requirements. Whether there is a need to introduce an Enterprise Content Management (ECM) tool is beyond the scope of this blog post.

 

 

Your feedback and comments are most welcome. You may also want to check out the Q&A area of SAP BTP, Cloud Foundry Environment (https://answers.sap.com/tags/73555000100800000287).