Caroline Sosebee is a Software Engineer at ThreeWill. She has 30+ years of software development experience and a broad scope of general IT knowledge. At ThreeWill she has a proven track record of quality consultative and software development skills, focusing on the design and implementation of SharePoint solutions, both on-premises and in the Microsoft 365 cloud.
I’ve recently had the opportunity to begin learning SPFx, React, and Redux. It has been quite a journey so far, with lots of head-scratching, googling of really weird words, and many roadblocks that were soon (or more often not-so-soon) followed by an Aha! moment. Being a coder at heart, it’s been lots of fun learning new, challenging technology.
One of the tasks I got to work on recently had to do with generating a PDF file from an HTML5 Canvas object and then saving it to the file system. After doing some research, I found there are examples out there but most of them are based on converting the canvas object to a PNG file and then working some magic to place it into a pdf for displaying or saving.
After playing around with PNG, we felt there were a couple of major drawbacks to our application. The first is that PNG images containing text data tend to have some slight blurriness to them, which was unacceptable. The second drawback is the sheer size of the document that is created when using PNG images. My early tests showed that a one-page document with only two PNG images inserted, created a PDF file that was 2.8 MB in size. Our need was to generate 100 to 200 pages per document, each having 2-4 images on it. With this methodology, a 200-page document would be around a cool half gig in size. Not good at all.
So, we started looking into alternatives to PNG. We had already decided to use a library called fabric.js that sits on top of the HTML canvas element and provides an interactive object model. It has an API with converters for parsing canvas data into other formats, including SVG (scalable vector graphics) so we decided to give this a try. SVG by nature is a very clean rendering solution, and so we knew the images should be crisp and clear right out of the box. Once I got all the pieces working, I found that the PDFs were indeed crisp, and the file size was minuscule. A 300-page document, each page having 2 images inserted into it, was around 838kb. Yay!
Here is an example of PNG (left) and SVG (right) inserted into a PDF and then zoomed to 250%. Quite a difference.
Generating a PDF file using SVG images
My example here will show the pieces needed to get PDF generation up and running in a React component. I am in no way an expert in React so am not going to even attempt to dive into those details but will assume that you already know enough to create a component and get it to run, allowing you to follow these instructions. 😊 They will also likely translate into Angular or another language, but I personally have not done that myself.
Step 1 – Install library dependencies
There are several required library dependencies you need to install. There are likely equivalent ones that will also work, but these worked for me.
- Fabric – GitHub Fabric
- This is used to manipulate the canvas object and then pull that data
- Pdfkit – PDF Kit
- Used for generating PDF’s
- SVGtoPDF – NPM SVG to PDF
- This provides the tools to insert an SVG into a PDF file created with PDFKit
- Blob-stream – NPM Blob-Stream
- Used for streaming the pdf data to a blog object
- File-saver – GitHub File Saver
- Used to save the generated pdf back to the file system
Step 2 – Add a reference to load PDFKit library
PDFKit library is not loaded from an npm package but needs to be loaded via an external reference in the config.json file. I used the standalone package provided with PDFKit and placed it in a folder within the src folder. I then added a reference to it in the externals section, like so:
Note that any changes to config.json will require an sppkg package rebuild and redeploy if working and debugging within SharePoint.
Step 3 – Add libraries to the component
Step 4 – Put it all together
Add a function within your component that will be called to generate the PDF. Within this function, we’ll do the following:
• Have a reference to some json data
• Get a canvas object and set it to the correct size (using fabric library)
• Instantiate a new pdf document (using pdfkit library)
• Pipe the pdf object into a blob stream (using the blob-stream library)
• Load the json into the canvas (using fabric library)
• Get an SVG string from the canvas (using fabric library)
• Insert the SVG image into the PDF (using the svg-to-pdfkit library)
• Close the pdf document and finish the blob stream
• Convert the streamed data to a blob object and create an object URL from it
• Save the blob object to a local drive (using the file-saver library)
And that’s it! Now let’s break it down.
Get an instance of the canvas element in the UI and set the width and height (in pixels) to match the desired output image.
Create a new pdf document object using PDFKit and set some default values. Then pipe it to a blob stream so changes are automatically captured as the PDF file is manipulated.
Now we need to load some json data into the canvas then convert it to an SVG string. If getting json in the right format is a challenge, fabric.js can be your friend. It has functions that will save a canvas off in a number of formats, including json. Our project relies on this heavily for capturing the raw canvas data and saving it to SharePoint.
Next, there are some settings that need to be set before using SVGtoPDF to insert the image into the PDF document. PDFKit and SVGtoPDF both work in points while a canvas works in pixels, and paper is in inches (here in the US anyway 😊). This means that you may need to convert your pixels and inches to points. In my example, I’m hardcoding the values, so I don’t need to do any specific conversions, but this is not really normal. I highly recommend reading the documents for both packages in order to get the parameters set correctly for your application.
Now that we’ve inserted an image into the PDF document, it’s time to finish it all up. First, we have to close the PDF document, using ‘.end()’. We then stop the streaming so we can save it. Once we have our blob of data, there are two options as shown below. The first is to save it as a URL and open it in the browser. The second is to use FileSaver in order to save it to the local file system. I show doing both here.
And that is it! You can now generate a nice, crisp, small PDF from a large amount of canvas data successfully. I hope this helps someone!
Here is the full script – untested in this stripped-down model – that shows all the pieces put together. Run at your own risk! 😊