How

Let me introduce you to Kendo React PDF by Progress

Progress has created a library that allows for easily doing this. Setting it up is very straightforward if you follow their documents. I also am not entirely sure if it will remain open-use in the future, because it seems their software comes with a cost. Currently, the required packages are on NPM for the public, and don’t require you to pay anything for them, and I will assume that is intended. I would definitely pay for a license if you plan to use this in a for profit product.

There are limitations with this PDF Export library that you can find here. One of which is including svg tags or files. This is a huge limitation in my opinion, that I found a way around. The other limitations are definitely possible to get around in some way too.

The setup is a little all over the place, so here is the run down for getting set up immediately for making a resume template:

1. Install the required dependencies using Node.js v5.0.0 or later

npm install --save @progress/kendo-react-pdf @progress/kendo-drawing

2. Have your React app ready

3. Import the required packages. In my case, I didn’t use savePDF , but you can look up how that is used in their documentation.

import { PDFExport } from '@progress/kendo-react-pdf';

4. Put the PDFExport object into your React object. The props are pretty self explanatory. The ref is used later on for exporting the PDF. Everything inside will be put onto the exported PDF.

<PDFExport paperSize={'Letter'}

fileName="_____.pdf"

title=""

subject=""

keywords=""

ref={(r) => this.resume = r}>

<div>content</div>

</PDFExport>

5. Here is what it looks like so far:

Empty empty empty

6. As you can see, we basically did nothing. Let’s try visualizing the PDF a little better. So how wide or tall should we actually make it? Kendo uses 72 pixels per inch. Because I’m using the Letter size option we want to create an 8.5x11 inch sized div. This means we want to make our div 612 pixels wide, and 792 pixels tall. (8.5 * 72 = 612, 11 * 72 = 792). Let’s also add some styling to make it appear like a piece of paper.

<PDFExport paperSize={'Letter'}

fileName="_____.pdf"

title=""

subject=""

keywords=""

ref={(r) => this.resume = r}>

<div style={{

height: 792,

width: 612,

padding: 'none',

backgroundColor: 'white',

boxShadow: '5px 5px 5px black',

margin: 'auto',

overflowX: 'hidden',

overflowY: 'hidden'}}>

content

</div>

</PDFExport>

Looks like a piece of paper now

Is this the correct size? It is. Progress provides documentation on their sizes here. It might appear small on the page until you download it. I have not put time into figuring out scaling this to be based off width/height percentages instead of pixels, but I’m sure it isn’t too difficult as long as you do some math to figure out correct sizes.

7. Next let’s just add a button that calls the export functionality. I added some styling to move it to above the PDF.

// Add this method to the React

exportPDF = () => {

this.resume.save();

} // Add this to the render method

<button onClick={this.exportPDF}>download</button>

Download button!

If you aren’t interested in adding SVGs to your resume, you are basically done! You can start programming the rest of your resume in React, using all the styling and array mapping and stuff that you would normally use in a React application. If you encounter an issue, I encourage you to go out and try and find a way around it, then write about it!

If you do want SVGs, read on.

In my case, I used react-fontawesome, but you can just use raw SVG html. My procedure just required an extra step.

Turning SVGs into Canvas items into images

It is so convoluted to just get this to work correctly. Kendo doesn’t allow SVGs to be input, but they allow for images. I had to research ways to do this effectively and easily. I found a package that allows you to do this in a few steps! Here they are:

1. Install the canvg library. This is one of the pieces that will allow you to do this conversion.

npm install canvg

2. Import canvg (and ReactDOMServer if you’re using libraries like FontAwesome. ReactDOMServer should already be included with React). I also imported react-fontawesome and an icon.

import canvg from 'canvg';

import ReactDOMServer from 'react-dom/server';

import { faGithub } from '@fortawesome/free-brands-svg-icons';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

3. Try it out first. Put an SVG into your PDFExport object somewhere. It’ll just not appear when you try to download the PDF. It’ll appear just fine in React, though.

4. How do you fix it?

canvg(canvas, html) will take a reference to a canvas object and the html to the SVG (as a string) and put the SVG into the canvas.

will take a reference to a canvas object and the html to the SVG (as a string) and put the SVG into the canvas. Then you can use canvas.toDataURL(“image/png”) to return a base64 image object (read more here).

to return a base64 image object (read more here). If you are using a FontAwesomeIcon object, you must render it and get the html code for it. This is done using ReactDOMServer.renderToStaticMarkup(<FontAwesomeIcon icon={faGithub}/>);

This function will return html which can be put through the PDFExport as an img tag. Usage: <img src={outputFromCanvasToDataURL} />

There are multiple ways to do this, but I chose the most familiar to me. I created an invisible canvas object and created a reference to it, then called a function that does the conversion.

Note you want to scale your SVG up when you put it into the htmlString string, to make it super large and high quality. Then when putting it into the PDFExport object, make it small. This way, when you export it, the edges are nice and smooth, and not pixellated. The way this method will work is it takes the SVG as is (width and height declared by you) and translates that immediately into a picture. If your SVG is only set to be 20 pixels wide, your image is going to be very small.

To see an extra example, look at my finalized code under this next code block.

// I set a canvas loaded boolean in the constructor

constructor() {

super();

this.canvLoaded = false;

this.githubIcon = null;

} // function to convert SVG to image. htmlString is optional

convertSVGToImage = (htmlString) => {

// if FontAwesome, run this next part

let htmlString = ReactDOMServer.renderToStaticMarkup(

<FontAwesomeIcon icon={faGithub} />); // for both FontAwesome and regular SVG:

canvg(this.refs.canvas, htmlString);

this.githubIcon = this.refs.canvas.toDataURL('image/png')

} componentDidMount() {

this.convertSVGToImage();

} // Create a conditionally loaded canvas (will only need it once,

// assuming you store your image data somewhere after you run it

// through the function

render() {

return (

<div>

{/* invisible canvas, and note the reference */}

{!this.canvLoaded &&

<canvas ref="canvas" style={{ display: 'none' }}>

</canvas>

}

<PDFExport>...

{this.canvLoaded && <img src={this.githubIcon} />}

</PDFExport>

</div>);

}

This is essentially what I do. Except I store my data in an array that is iterated through, and I pass my canvas to the function. You can see this in the code below. Sorry for poor formatting. See the code with better formatting on github.

class App extends Component {

resume; constructor() {

super();

this.iconsToConvert = [

{

icon: faGithub,

alt: 'github icon'

},

{

icon: faMedium,

alt: 'medium icon'

}

]

this.canvLoaded = false;

} exportPDF = () => {

this.resume.save();

} convertSvgToImage = (arr) => {

let canv = this.refs.canvas;

if (!this.canvLoaded) {

this.canvLoaded = true;

canv.getContext("2d");

arr.forEach((d, i) => {

let htmlString = ReactDOMServer.renderToStaticMarkup(

<FontAwesomeIcon icon={d.icon} size={"3x"} style={{ color: '#005696', height: '500px', width: '500px' }} />

);

canvg(canv, htmlString);

d.icon = canv.toDataURL("image/png");

});

this.setState({});

}

} componentDidMount() {

this.convertSvgToImage(this.iconsToConvert);

} render() {

return (

<div style={{ height: '100vh', width: '100vw', paddingTop: 20, backgroundColor: 'gray' }}>

{!this.canvLoaded && <canvas ref="canvas" style={{ display: 'none' }}>

</canvas>}

<div style={{ textAlign: 'center', marginBottom: 10 }}><button onClick={this.exportPDF} style={{ margin: 'auto' }}>download</button></div>

<PDFExport paperSize={'Letter'}

fileName="_____.pdf"

title=""

subject=""

keywords=""

ref={(r) => this.resume = r}>

<div style={{

height: 792,

width: 612,

padding: 'none',

backgroundColor: 'white',

boxShadow: '5px 5px 5px black',

margin: 'auto',

overflowX: 'hidden',

overflowY: 'hidden'

}}>Hi!

{this.canvLoaded && this.iconsToConvert.map((iconObject, index) => {

return <img src={iconObject.icon} key={'img-' + index} alt={iconObject.alt} style={{ height: 25, width: 25 }} />

})}

</div>

</PDFExport>

</div>

);

}

}

What the output looks like

Final words / tl;dr

You can find the code file for this here (in src/App.js)

You can check out a full example on my website here (click on resume) with the code for it here (in src/components/Resume.js — careful though, my styling is very messy and I also perform the process outlined in this article in a very unoptimized way).

Questions or comments or suggestions? Leave them here! I’m constantly trying to learn more about the JavaScript ecosystem, and I might have a totally unconventional way of doing things. I’d love to hear what I could do to improve, or if my solution to this was woefully out of date or not. If you all find a better solution, I’d love to hear that too!

Links