Search code examples
javascriptjquerycanvasjspdfhtml2canvas

Why is jsPDF outputing only blank pages on larger documents?


I have a function that calls html2canvas. If the canvas is only 1 pdf 'page' large it works no problem. If it is larger than that I made this while statement to break up the canvas into smaller canvases that I am then adding 1 at a time to the PDF.

It works great for one page I have that generates about 5 'pages' in the PDF, but I have a different page that is probably 3 times larger that will not be generated and it is incredibly frustrating.

A PDF is being output but it is entirely blank with the correct number of pages if there had been any data. Why is it blank and how can I output the correct information? I've been beating my head at the wall for awhile now and any help would be greatly appreciated.

function exportHTMLToPDF( selector, pdfData ){
    html2canvas( $( selector ), {
        onrendered: function ( canvas ) {
            //Size variables
            var posFromTop = 0; //The position from the top
            var canvasLeft; 
            // width, height, and ratio (w x h) of a canvas 'page'
            var pageWidth, pageHeight, pageRatio;
            //Upright oritentation (portrait)
            if ( pdfData.orientation == 'p' ) {
                pageRatio = 0.77272727272;
            } else { //Landscape orientation
                pageRatio = 1.29411764706;
            }
            pageWidth = canvas.width;
            pageHeight = pageWidth / pageRatio;
            canvasLeft = canvas.height * pageWidth / pageWidth;
            //Set up the pdf
            var doc = jsPDF( pdfData.orientation, 'mm', [pageWidth, pageHeight] );
            //Get canvas context
            var ctx = canvas.getContext( '2d' );
            //Set that this is the first page so doc.addPage() isn't called
            var firstPage = true;
            //Get the next 'page'
            var imgData;
            //Create a second canvas to put the imgData in
            var c2 = document.createElement( 'canvas' );
            c2.setAttribute( 'width', pageWidth );
            c2.setAttribute( 'height', pageHeight );
            //Get a second canvas context to put the image on
            var ctx2 = c2.getContext( '2d' );
            //How many pages the pdf document will be
            var pages = canvasLeft / pageHeight;
            var imageURI;
            //While there are still more pages
            while ( pages >= 0 ) {
                //Convert the next part of the canvas to image data
                imgData = ctx.getImageData( 0, posFromTop, pageWidth, pageHeight );
                //Change the transparancy into white space
                for ( var i = 0; i < imgData.data.length; i += 4 ) {
                    if ( imgData.data[i + 3] == 0 ) {//If transparent
                        imgData.data[i] = 255;
                        imgData.data[i + 1] = 255;
                        imgData.data[i + 2] = 255;
                        imgData.data[i + 3] = 255;
                    }
                }
                //Put the image on the canvas
                ctx2.putImageData( imgData, 0, 0 );
                //If the document needs another page, add one
                firstPage ? firstPage = false : doc.addPage();

                //Turn the canvas with the newest image data into DataURL and put that 
                //on the pdf, with compression (the 'FAST' part)
                doc.addImage( c2.toDataURL( 'image/png' ), 'PNG', 0, 0, pageWidth, pageHeight, null, 'FAST' );
                pages--;
                posFromTop += pageHeight;
            }
            //Saves the pdf
            doc.save( pdfData.fileName + '.pdf' );
        }
    } );
}

Solution

  • It turns out that my selector was exceeding the maximum size for a canvas and thus was being rendered as blank. After some digging the maximum size (in chrome) was 268,435,456 pixels in area. The div I was attempting to create was a little over 270,000,000 pixels.

    My solution was to add a class firstHalf to the first half, and secondHalf to the second half, then alternate which was was shown.
    I then called exportHTMLToPDF twice while passing in the jsPDF doc so that it would use the same one.

    In a more systematic way having multiple "parts" to hide could be done with numbers instead. .part1, .part2, etc. then iterate through them with a passed in variable.

    function exportHTMLToPDF( selector, pdfData, doc ){
        var currentPart = pdfData.curPart;
        var lastPart = pdfData.lastPart;
        html2canvas( $(selector), {
            onrendered: function (canvas){
                //**Get page size data**//
                if( doc == null ){
                    doc = jsPDF( 'p', 'mm', [pageWidth, pageHeight] );
                }
                //**Add the current page to the doc**//
                //If this is the last page, save
                if( currentPart == lastPart ){
                    doc.save( pdfData.fileName + '.pdf' );
                } else {
                    //Else call this function again, for the next page
                    $('.part' + currentPart ).hide();
                    $('.part' + ( currentPart + 1 ).show();
                    pdfData.curPart = currentPart + 1;
                    exportHTMLToPDF( selector, pdfData, doc );
                }
            }
        } );
    }