While reveal.js is a feature-rich tool, one thing that it lacks is the ability to export to a PDF that is user-friendly and cross-browser compatible. There is built-in export functionality, but it only works in Chrome and requires the user to modify the URL query string. Our customer required Firefox compatibility so we needed to engineer a way to allow PDF export that is available with the click of a button, and works in Firefox. The customer didn’t need the ability to export the presentation in a format used for editing (i.e. preserving all the HTML/CSS etc…) they just needed to export something they could present and email (i.e. a slideshow).
What to do?
So whenever I am presented with a problem that doesn’t have an obvious solution, one of the first things I do is look at work that has already been done in the past to see if the problem (or a similar problem) has already been solved. In this case, one of our engineers (Wayne Eull) had already worked on a capability which would capture a screenshot of an area of our web application and create a JPEG of it to use as a thumbnail. So the idea arose amongst the team that what is a slideshow, but a simple collection of images. If we can just capture a screenshot of each slide, we could package them back into a PDF and provide the capability to the customer.
html2canvas is the tool that we were using to capture “screenshots”. I put that in quotation marks because what html2canvas does is that it attempts to capture the information in the DOM and transfer it to an HTML5 Canvas. The canvas object has the useful capability of exporting its contents to an image. So the plan of attack was to step through each slide in the presentation, create a JPEG image of it, and send the images off to a PDF generation tool to package them together into a PDF.
The first action we had to take was to create a container at the top level of our application to hold the contents of a slide that is going to have a screenshot taken of it. This was needed because our application has headers/footers/etc… that affect the size of the actual presentation area. This would cause html2canvas to capture a canvas that was scaled incorrectly. By transferring the contents to a top level div, this fixes the scaling issue. To accomplish this we just add an empty div (with class=”ui snapshot”) into our HTML at the top level.
The first ‘gotcha’ that we came across was somewhat unique to our application, but could trip up some users who are using Semantic UI. We use the dimmer module for various things (modal transitions, loading widgets, etc…), and it was discovered that this could interfere with the work of html2canvas. We had to first remove the dimmable class. If we did not, occasionally the images that would be captured would have a large black bar covering the bottom third or so of the image.
Before we begin to capture slides, we want to rewind the presentation back to the start so that we can step through all the slides. We accomplish this by using lodash to clone the current state and preserve all information about it, then set the slide indices to 0. We will also save off the original presentation state so that we can revert to it when we are finished.
So now that we are at the start of the presentation, we are ready to capture our first slide. To capture a slide, we copy the current slide contents into the container div using jquery.
After the contents have been cloned into our div, we can capture the image for the slide.
You’ll notice we are using the lodash
defer method here. The
defer method defers invoking the method until the current call stack has cleared. We would occasionally run into timing issues that could affect whether everything was in place before we captured the slide. In our application we had a lot going on during this process, so you may or may not need this.
Once you have captured the first slide, start stepping through each slide of the presentation. The images of each slide are placed into the imagesToCapture array. Use the reveal.js
next method to step through and capture each slide.
When all slides have been captured, use one of the various PDF generation tools out there that can be used to build a PDF from the images. In this example, we will use jspdf.
Once we have completed building our PDF, we need to remember to clean everything up and revert the presentation back to its original state.
Putting it all Together
Here is an example that pulls together the different steps into one place.
Bonus Points - Canvas within a Canvas
One of the more interesting features of reveal.js is that basically anything that can be displayed in a webpage can be integrated into the slides of the presentation. One of the components that users are allowed to incorporate into presentations in our application is a Cesium widget. This posed an extra problem for us. We needed to determine a way to include the contents of the cesium widget as well, as the html2canvas tool wouldn’t capture the contents of it. To accomplish this, we had to access the contents of the cesium canvas, determine its position on the slide, and then stitch one canvas into another at the correct location. This code is similar to the above code, but adds in handling for the cesium widget.
reveal.js is a really awesome tool for building and showing presentations. This little fix provides users with a bit of a band-aid to solve one of the holes in their functionality. Hopefully it can help someone out that needs it.