Salesforce Lightning: How to generate PDF with charts and images

Viswanathan Chandrababu
3 min readApr 9, 2021

Generating PDFs in Salesforce with charts, images and textual content is one of the hardest tasks to achieve. I am sure you searched a lot for native solution but at this point we don’t have any native solution.

Requirement

We had a requirement to generate PDF with charts, images, grid and textual content.

Data source for the charts and grids are from external API and PDF page size may vary from 25 to 50 pages.

Considering the requirement to generate PDF with a large number of pages, we initially searched for a server side apex solution, but it was clear to us after a lot of research that generating PDF with charts is not possible with Apex unless we use third party solutions (mostly paid).

After a lot of research we decided to settle with a client side solution using JS libraries.

Architecture

Salesforce PDF Generation Architecture
Salesforce PDF Generation Architecture

VF Page

<apex:page controller="PDFGenCtrl" action="{!loadPageData}"><!-- Chartjs lib -->
<apex:includeScript value="{!URLFOR($Resource.chartjs)}"/>
<!-- TODO - HTML --><!-- TODO - JavaScript --><!-- dom-to-image js lib -->
<apex:includeScript value="{!URLFOR($Resource.domtoimage)}"/>
<!-- dom-to-image jspdf -->
<apex:includeScript value="{!URLFOR($Resource.jspdf)}"/>
</apex:page>

This is a simple implementation of the above architecture. On page load we are calling “loadPageData” action from “PDFGenCtrl” apex class.

We included 3 required Open Source library

Chartjs — JavaScript library to render charts

Dom-to-Image — To convert rendered html / DOM node into image

jsPDF — To create PDF and attach contents(image, text) into it.

Finally we have placeholders to include custom HTML & JavaScript.

HTML

<div class="container"><button onclick="downloadPDF()" style="float:right;">
Download PDF
</button>
<!-- page 1 -->
<div class="page">
<canvas id="myChart"></canvas>
</div>
<!-- page 2 -->
<div class="page">
<img alt="img" src="/resource/image_content"
id="p2_img" width="300"/>
</div>
</div>

There are 3 parts in the above html.

“Download PDF” button — To generate PDF and download.

There are two divs with “page” CSS class, these two divs will convert into two pages of the pdf with chart and image.

JavaScript

<script>
// Read Page data
var pageData = '{!JSENCODE(pageData)}';
try{
pageData = JSON.parse(pageData);
var ctx = document.getElementById('myChart');
// Render chart in page 1
var myChart = new Chart(ctx, {
type: 'doughnut',
data: pageData.p1_data
});
// update image in page 2
var p2_img = document.getElementById('p2_img');
p2_img.src = pageData.p2_data;
}
catch(e){
}

function downloadPDF(){
// Select all div with 'page' class
var pages = document.getElementsByClassName('page');
var pageCount = 0;
var scale = 1;
//Create PDF
var pdf = new jsPDF('l','mm',[263,172]);
// Loop selected divs / pages
Array.from(pages).forEach((page, i) => {
if(i >0 && i< pages.length){
pdf.addPage();
}
// Convert each div.page node to image
domtoimage.toPng(page, {
width: page.offsetWidth*scale,
height: page.offsetHeight*scale,
style: {
transform: "scale("+ scale + ")",
transformOrigin: "top left",
width: page.offsetWidth + "px",
height: page.offsetHeight + "px"
}
}).then((blob) => {
pdf.setPage(i+1);
// Attach the converted blob / image into created pdf
pdf.addImage(blob, 'PNG', 0, 0, undefined, undefined, i, 'FAST');
pageCount++;
if(pageCount == pages.length){
//Download PDF
pdf.save("PDF_Report.pdf");
}
});
});
}
</script>

On page load, after apex action call we are parsing “pageData” to render a chart in first page and set image src in second page.

“downloadPDF” js function uses “dom-to-image” and “jsPDF” to generate PDF.

Apex

public class PDFGenCtrl { public String pageData {get;set;}

public PageReference loadPageData(){
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setEndpoint(‘http://demo3599847.mockable.io/pdf-data');
req.setMethod(‘GET’);
req.setHeader(‘Content-Type’, ‘application/json’);
try {
res = http.send(req);
}
catch(System.CalloutException e) {
System.debug(‘Callout error: ‘+ e);
}
pageData = res.getBody();
return null;
}
}

This is a simple Apex class to load page data from REST API and set the response to “pageData” property which can be used in the VF page.

Complete source code is available in github — https://github.com/csrviswa/Salesforce-PDF-Generation

--

--

Viswanathan Chandrababu

Full Stack Developer, Writer. Interested in Tech, Economics and Psychology