Monday, June 13, 2016

How to use PDFMake to generate PDF under IE, Safari, and Android

PDFMake can be used to create pdf on client side. It worked nice under Chrome and Firefox, however, it doesn't work under IE, Safari, and Android. The reason it doesn't work because PDFMake simply created the pdf using a popup window to display it.

So to make it work, another javascript lib called PDF.js can be used. The PDF.js itself is a pdf viewer, and we can create a modal dialog to contain the pdf viewer, instead of using popup window.

Note PDF.js is not working with Safari, to get it working on Safari,
  1. compatibility.js must be included.
  2. PDFJS.workerSrc must be assigned.
<script type="text/javascript" src="compatibility.js"></script>
<script type="text/javascript" src="pdf.js"></script>

<!-- NEED THIS for Safari Mac to render work -->
<script type="text/javascript">
    // Specify the main script used to create a new PDF.JS web worker.  
    // In production, change this to point to the combined `pdf.js` file.  
    PDFJS.workerSrc = 'pdf.worker.js';  
</script>
HTML part:
The pdf.js actually use modal to display pdf content.
<div id="containerPDFViewer" class="modal modal-info fade" role="dialog">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
  <button type="button" class="close" data-dismiss="modal">&times;</button>
  <h4 class="modal-title">@GeneralResources.PDFViewer</h4>
            </div>
            <div class="modal-body">
  <canvas id="pdfviewer"></canvas>
            </div>
        </div>
        <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
</div>
Javascript part:
//save the pdf into base64 strings
var pdfstr;
try {
    pdfMake.createPdf(docDefinition).getDataUrl(function (result) {   
        pdfstr = result;
        var pdfAsArray = convertDataURIToBinary(pdfstr);
        PDFJS.getDocument(pdfAsArray).then(function getPdf(pdf) {
            //  
            // Fetch the first page
            //  
            pdf.getPage(1).then(function getPdfPage(page) {
                var scale = 1.4;
                var viewport = page.getViewport(scale);
                //
                // Prepare canvas using PDF page dimensions
                //
                var canvas = $("#pdfviewer").get(0);
                var context = canvas.getContext('2d');
                canvas.height = viewport.height;
                canvas.width = viewport.width;           
                //
                // Render PDF page into canvas context
                //
                var renderContext = {
                    canvasContext: context,
                    viewport: viewport
                };
                page.render(renderContext);
                $('#containerPDFViewer').modal({ backdrop: 'static' }); //disable backdrop so user need to make choice
            });
        });
    });
}
catch (e) {     
    throw e;
}

var BASE64_MARKER = ';base64,';
function convertDataURIToBinary(dataURI) {
    var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
    var base64 = dataURI.substring(base64Index);
    var raw = window.atob(base64);
    var rawLength = raw.length;
    var array = new Uint8Array(new ArrayBuffer(rawLength));

    for (var i = 0; i < rawLength; i++) {
        array[i] = raw.charCodeAt(i);
    }
    return array;
}

Note the 1st parameter of getDataUrl is a call back function, will will be executed after getDataUrl is done. We need to put pdf.js code inside the call back function.

Here we didn't use getBase64 because it only return Base64 string, and getDataUrl actually returned Base64 string with leading pdf string. I have tested the code, the getBase64 failed to call convertDataURIToBinary() as there is base64 code error, but getDataUrl is OK.

Note as I tested, the pdf content will be displayed within the modal, but the effect is not as good as normal pdf viewer. In addition, even pdf.js still has issue in IE and Safari.

As result, for supporting IE and Safari, we should not use PDFMake and pdf.js.


References:
http://gonehybrid.com/how-to-create-and-display-a-pdf-file-in-your-ionic-app/
http://stackoverflow.com/questions/17022052/pdf-js-not-working-with-safari
http://stackoverflow.com/questions/12092633/pdf-js-rendering-a-pdf-file-using-a-base64-file-source-instead-of-url
https://developer.tizen.org/community/tip-tech/displaying-pdf-files-pdf.js-library


1 comment:

  1. Hey Frank,
    thank you so much for your nice article. I am currently using your lines in my project and it works great. But I have one huge problem: the & sign. I am calling some content from mysql db in utf8 format and putting that stuff into the pdf via your script. Signs like 'ä', 'ö' and even the 'ß' as used in germany are no prob. But the '&'. I red a few articles about base64 and atob in javascript, but I am not that good in js. May you help me? Have a great day.
    Greetings
    Chris

    ReplyDelete