In this blog post, we’ll build a sample invoice in our React app using HTML and CSS, then generate a PDF file based on this content. Plus, we’ll control the generated page size exclusively through CSS.
Welcome to the Generating PDF in React blog post series!
In today’s blog post (part 2), we will build further upon this by covering a very common scenario that the KendoReact team sees as a frequent request: how to export an HTML invoice to PDF. Beyond some fancier HTML and CSS, we are also going to see how to export SVG elements (via charts) and how even more advanced React components like DropDowns can be included in our generated PDF files.
Additionally, we will see how we can dynamically change the paper size of the generated PDF file just through CSS. As extra credit, we will also see how we can embed our own custom fonts in our PDF files to ensure proper Unicode support.
Everything I’m covering in the blog post today is also covered in the “Generating PDF in React: Part 2” video on YouTube, which is recreating this Kendo UI for jQuery demo in a React application. So, if you prefer a more visual medium, you can watch that here, or head on over to the KendoReact Video section, where you’ll find additional links.
What we will be creating today is a sample layout of an invoice. If you’re not familiar with the terminology, it is essentially a document that highlights items purchased as a part of an order. While displaying these in our web apps makes a lot of sense, invoices are very often sent around in a PDF format when shared individuals who may not have access to said web app. This could include the company you are selling something to. So, generating a PDF from HTML to CSS becomes critical here.
The source code for everything we are doing here today, as well as the other parts of this series, can be found in this GitHub repo. Today’s blog post covers the code in the LayoutSample
component.
Let’s go ahead and copy in our HTML and CSS that we can use as a base to build upon. You can get a project up and running with create-react-app
, and then within your App.js
you can copy in the JSX section of the JS file, and you can copy the CSS in to App.css
and be off to the races.
For the sake of simplicity and to make it as easy as possible to share with everyone, here’s a StackBlitz project that shows off the HTML and JavaScript through App.js
and the CSS in style.css
. Since this can be a bit long, it’s just easier for you to fork this project, or copy and paste from each of the appropriate files.
Once you have this up and running, you can continue with the rest of the blog post. There are some details in the above markup that we will add to and cover in more detail, so don’t worry too much about what the CSS classes and HTML markup might be.
Now that we have this set, let’s continue by adding in some extra data visualizations in our React app.
In an attempt to make this invoice as fancy as possible, I want to add in some data visualization to make the end-result really pop. This lets me kill two birds with one stone. First, I get to add something that is visually pleasing to the example. And, second, I get to show off how to generate PDF files from HTML that include SVG elements.
Since we have been all about making things easy for us in these projects, I’ll lean on the KendoReact Charts library, as it lets me set up a chart with just a couple of lines of code instead of hand-coding SVG elements myself. Specifically, I want to add in a React Donut Chart in the invoice I’m setting up for PDF generation.
Looking at the KendoReact Chart Component Getting Started page, we can copy and paste the following command into our console and install the appropriate packages:
npm install --save @progress/kendo-react-charts @progress/kendo-drawing @progress/kendo-react-intl hammerjs @progress/kendo-licensing
We also have to install one of the KendoReact themes, which in this case will be the Material Theme. This can be done with the following npm install
:
npm install --save @progress/kendo-theme-material
And then we import the CSS file associated with the theme:
import "@progress/kendo-theme-material/dist/all.css";
Once this installation and setup is done, we have to import the proper pieces in to our React component:
import {
Chart,
ChartLegend,
ChartSeries,
ChartSeriesItem,
ChartSeriesLabels,
ChartCategoryAxis,
ChartCategoryAxisItem
} from "@progress/kendo-react-charts";
import "hammerjs";
import "@progress/kendo-theme-material/dist/all.css";
As a part of any chart, we need to have some sort of data as well, so let’s add this array somewhere in our application. In my case, I created invoice-data.json
and imported this file into my component, but feel free to add this wherever it feels natural for you.
[
{
"product": "Chicken",
"share": 0.175
},
{
"product": "Pork",
"share": 0.238
},
{
"product": "Turkey",
"share": 0.118
},
{
"product": "Kobe Beef",
"share": 0.052
},
{
"product": "Pickled Herring",
"share": 0.225
},
{
"product": "Bison",
"share": 0.192
}
]
This is also how imported the data into my component:
import sampleData from "./invoice-data.json";
Now, to add this in my project, I’m going to scan for the div with a className="pdf-chart"
prop set on it. From there, I will follow the KendoReact documentation to define my Donut Chart:
<div className="pdf-chart">
<Chart style={{ height: 280 }}>
<ChartSeries>
<ChartSeriesItem
type="donut"
data={sampleData}
categoryField="product"
field="share"
>
<ChartSeriesLabels
color="#fff"
background="none"
/>
</ChartSeriesItem>
</ChartSeries>
</Chart>
</div>
The React donut chart is pretty flexible, but even so the markup can start feeling intuitive quickly. First off, we define a <ChartSeriesItem>
to define a single Donut chart (the KendoReact Data Visualization package can support multiple series at once) where we wire up our data in the data
prop, and set our two fields in categoryField
and field
to the two fields found in our sample data. The <ChartSeriesLabel>
item is just there to let us display labels in the donut chart—which will just be white text in our case.
To have a quick look at the current state of our project, here’s the full thing running in StackBlitz:
Next, we will cover how we can use CSS classes and a DropDown to control the paper size of our invoice and ultimately the PDF file we are generating.
Let’s take some time and inspect the CSS we pasted at the beginning of this article or in one of the StackBlitz projects. Specifically this section right here:
/* Dimensions other than px and em should be divided by 1.33 for the proper PDF output */
.size-a4 {
width: 6.2in;
height: 8.7in;
}
.size-letter {
width: 6.3in;
height: 8.2in;
}
.size-executive {
width: 5.4in;
height: 7.8in;
font-size: 12px;
}
.size-executive .pdf-header {
margin-bottom: .1in;
}
From this we can see that we have three different page sizes that we can operate within: A4, Letter and Executive. Normally when we generate a PDF in React we have to rely on the KendoReact PDF Generator’s paperSize prop to define our desired paper size. However, if we know the measurements for the desired output, we can actually customize this 100% through CSS!
In our invoice, this is the element that we need to adjust and set a new CSS class when we want to update the size of the invoice:
<div className="pdf-page size-a4">
For this application, we have a requirement from our users that they want to be able to customize what type of paper that the PDF should be generated to, so let’s give our users a DropDown that can select from the available sizes and change the paper size on the fly.
Whenever we have a finite list of available options that we want users to select from (without having the ability to enter custom text), a select
element is usually a great way to go. However, since we already have KendoReact included in our project, we may as well go for a fancier version of this element and upgrade to the React DropDownList. To get this added in our application, we need to install the package using the following npm install command, found on the React DropDown’s package overview page:
npm install --save @progress/kendo-react-dropdowns @progress/kendo-react-intl @progress/kendo-licensing
Once we have that installed, we can import the DropDownList in our React component:
import { DropDownList } from "@progress/kendo-react-dropdowns";
Now, we need to define the list of available options to bind to our React DropDownList. Here’s the variable we can use:
const ddData = [
{ text: "A4", value: "size-a4" },
{ text: "Letter", value: "size-letter" },
{ text: "Executive", value: "size-executive" }
];
As you can see, we use the text
field to give us an easy-to-read string that we can present to the user, while the value
field for each item uses the CSS class name that we want to set for each option.
Something else that we need to set is some sort of state in our component that can keep track of the current value of the selected item—this is a React component, after all! So, we can do this by importing useState
from React and use it to define a variable. First, we do the proper import:
import { useRef, useState, useEffect } from 'react';
Then in our component we can set up the following:
const [layoutSelection, setLayoutSelection] = useState({ text: "A4", value: "size-a4"});
For those of you who haven’t used React Hooks yet, this may seem a bit confusing, but the layoutSelection
portion is the variable we can access in our app, and setLayoutSelection
can be called when we want to update our state. Additionally, so that we have a default item selected when our application loads, we set an initial state to be equal to that of our first option in our DropDownList, which is the A4 size.
Once we’ve done all of this, we can add the following code into the top of the application by finding the first <div class="box-col">
element at the top of our HTML:
<div className="box-col">
<h4>Select a Page Size</h4>
<DropDownList
data={ddData}
textField="text"
dataItemKey="value"
value={layoutSelection}
onChange={updatePageLayout}
>
</DropDownList>
</div>
As you can see, we pass in our ddData
variable to the DropDownList, then define the textField
to represent what the user will see and the dataItemKey
to be the underlying value. We set the initial value to our layoutSelection
variable, and finally we use the onChange
event to call a function which we will use to update our state. Since we haven’t defined that yet, let’s go ahead and do so:
const updatePageLayout = (event) => {
setLayoutSelection(event.target.value);
}
The last piece of the puzzle is to also update the CSS of the aforementioned div element that lets us control the dimensions of our invoice. Specifically, this element:
<div className="pdf-page size-a4">
In order to update the className
every time the state oflayoutSelection
changes, we can set the className prop to be equal to a string literal which grabs the current value of layoutSelection
with the following:
<div className={ `pdf-page ${ layoutSelection.value }` }>
Now every time our state is updated, this part of our code will also update to be one of the following strings:
Once we have compiled things and our app is up and running, you can select a value from the React DropDownList and see how the layout of the invoice changes with every choice!
Here’s an up-to-date StackBlitz project showcasing our progress so far.
The stage is set, so let’s move on to the last step in our journey and allow users to generate a PDF with varying sizes depending on the choice that our user has made!
Now that we have the overall invoice HTML and CSS squared away, along with a DropDown that users can interact with to change the layout of the invoice, let’s add PDF generation to our React app!
To do this, I’ll need the KendoReact PDF Generation package, which we also used in Part 1 of this blog post series, and I’ll also toss in the KendoReact Button to give us a pretty button to press in order to generate a PDF file from the HTML of our React app.
To get started on this step, I’ll just go ahead and run the following npm install:
npm install --save @progress/kendo-react-pdf @progress/kendo-drawing @progress/kendo-react-buttons @progress/kendo-licensing
And then import both components by adding this at the top of my React component:
import { Button } from '@progress/kendo-react-buttons';
import { PDFExport, savePDF } from '@progress/kendo-react-pdf';
First things first, I’m going to take the easiest approach to exporting to PDF by just wrapping my desired content with a <PDFExport>
component. Yup, it’s really that simple! In this case, I need to wrap around the div element that we just used to dynamically update the page size, which should leave me with the following:
<div className="page-container hidden-on-narrow">
<PDFExport ref={pdfExportComponent}>
<div className={ `pdf-page ${ layoutSelection.value }` }>
...
</div>
</PDFExport>
...
</div>
We use the ref
prop above in order to let us quickly reference this element when we need to kick off our generation. Thanks to our previous import of useRef
from React, we can just add the following line of code to our component:
const pdfExportComponent = useRef(null);
Then we can go ahead and add our React Button somewhere on the page. In this case, I think it makes sense to add it under the DropDownList and give it an appropriate header, so let’s tweak the top of the page to look like this:
<div className="box wide hidden-on-narrow">
<div class="box-col">
<h4>Select a Page Size</h4>
<DropDownList
data={ddData}
textField="text"
dataItemKey="value"
value={layoutSelection}
onChange={updatePageLayout}
>
</DropDownList>
</div>
<div className="box-col">
<h4>Export PDF</h4>
<Button primary={true} onClick={handleExportWithComponent}>Export</Button>
</div>
</div>
I included the React DropDownList code as well to give you a reference, but the biggest piece to look at here is the <Button>
and its onClick
event toward the bottom of the markup.
Since we have wrapped around the appropriate elements with <PDFExport>
, the event handler we defined here is what does all the magic to generate a PDF file:
const handleExportWithComponent =(event) => {
pdfExportComponent.current.save();
}
In the button’s onClick
event we find our PDFExport
component via the reference that we defined earlier and then use the .save()
function to take our HTML and generate a PDF file representing our content.
Here is the current version of the project in StackBlitz so you can see everything in a single project:
That’s all you need to do! Thanks to the KendoReact PDF Generator package, all we really have to do is identify our parent element for export, which can be anywhere in our markup as you just noticed, and wrap it with <PDFExport>
tags, and the React PDF Generator library takes care of all of the rest for us. Even the SVG element of the React donut chart is included without any additional code from our side.
One thing you may have noticed as you generate the PDF file and observe the end result in all its glory—and this is part of the extra credit—is that certain special characters (any letters with decoration in this example) may come out looking pretty odd. This is because, as a default, the KendoReact PDF Generator library relies on standard PDF fonts, which only support ASCII characters. This, however, is a problem we can work around by adding in our own fonts that support Unicode characters! Let’s do this now.
As the KendoReact embedded fonts in PDF files article mentions, we have to embed appropriate fonts in order to be able to handle characters that are not ASCII characters and instead require Unicode support to render.
A note to make here is that you need to ensure that you have the legal right to use the font that you are looking to embed. There are plenty of fonts with licensing that permits you to use them freely, but keep an eye out to ensure you’re not accidentally doing something you’re not supposed to do.
In our case, I’m going to import DejaVu Sans. My approach is to use the font found on the KendoReact CDN, but in your case you should either reference your own CDN or host the font files within your project.
When following the KendoReact PDF generator embedded fonts article, you’ll see that in order to take advantage of this font we need to use @font-face
within our CSS. We also need to find the .pdf-page
class and update this to use our new font in the font-family
CSS property.
Here’s the CSS that we can add to our component’s CSS file:
/*
Use the DejaVu Sans font for display and embedding in the PDF file.
The standard PDF fonts have no support for Unicode characters.
*/
.pdf-page {
position: relative;
margin: 0 auto;
padding: .4in .3in;
color: #333;
background-color: #fff;
box-shadow: 0 5px 10px 0 rgba(0,0,0,.3);
box-sizing: border-box;
font-family: "DejaVu Sans", "Arial", sans-serif;
}
/*
The example loads the DejaVu Sans from the Kendo UI CDN.
Other fonts have to be hosted from your application.
The official site of the Deja Vu Fonts project is
https://dejavu-fonts.github.io/.
*/
@font-face {
font-family: "DejaVu Sans";
src: url("https://kendo.cdn.telerik.com/2017.2.621/styles/fonts/DejaVu/DejaVuSans.ttf") format("truetype");
}
@font-face {
font-family: "DejaVu Sans";
font-weight: bold;
src: url("https://kendo.cdn.telerik.com/2017.2.621/styles/fonts/DejaVu/DejaVuSans-Bold.ttf") format("truetype");
}
@font-face {
font-family: "DejaVu Sans";
font-style: italic;
src: url("https://kendo.cdn.telerik.com/2017.2.621/styles/fonts/DejaVu/DejaVuSans-Oblique.ttf") format("truetype");
}
@font-face {
font-family: "DejaVu Sans";
font-weight: bold;
font-style: italic;
src: url("https://kendo.cdn.telerik.com/2017.2.621/styles/fonts/DejaVu/DejaVuSans-Oblique.ttf") format("truetype");
}
With that added, we now have a font that supports Unicode characters. If we go ahead and generate a new PDF file, we see that all of our original characters are included, and the generated PDF file looks that much cleaner.
Here’s the final StackBlitz project in all its glory:
This article was a little bit on the longer side, but as a quick recap we did the following:
That’s quite the journey right there! Between “Generating PDF in React: As Easy As 1-2-3” and this blog post, I hope that you see just how flexible and powerful the KendoReact PDF Generator library can be. With a single line of code, you can also start generating PDF files in your React apps!
Carl Bergenhem was the Product Manager for Kendo UI.