10

I've looked around stackoverflow trying to find a way to do this for a while now, and can't find a suitable answer. I need to be able to load a PDF in either a new window or an iframe via a base64 encoded string and trigger a print preview of it immediately after loading it. I can easily load the PDF using both of those methods, but can't actually get it to show the print preview properly. Here is what I've tried:

  1. Using embed element in a new window. Calling window.print() is blank, even after the content is loaded.
  2. Using a hidden, dynamically created iframe with src="data:application/pdf;base64,JVBERi0..." and calling myFrame.contentWindow.print(). But this gives a CORS error. I'm not sure why, because I'm not loading a new domain through the iframe, just content.
  3. Open a new window with only an iframe element like the one in #2 and calling a print on the whole window. This also shows a blank white page.
  4. Open a new window with the data uri and print it. window.open('data:application/pdf;base64,JVBERi0...').print();. This doesn't work either, as it doesn't even show a print preview at all. I've also tried delaying it with a setTimeout but that doesn't do anything either.

At this point I'm very confused as to why none of these work, especially because in Chrome it display's custom menu bars like this:

Chrome Print Preview

And if I click the actual print icon there, the print preview is perfect. Whatever Chrome is doing when I click that button is exactly what I want to accomplish. Is there anyway to trigger that functionality? Or is there another way to accomplish what I want? And just to clarify, I only need this to work in Chrome, I don't need to worry about other browsers.

jtate
  • 2,612
  • 7
  • 25
  • 35
  • Did you managed to get it working ? I tried the same nothing seems to work – db306 May 19 '17 at 12:02
  • @db306 no, I honestly don't think it's possible, I've tried everything I could think of. The PDF viewer in Chrome is actually a plugin and I don't think it's possible to interact with it through javascript. I think it may be possible to accomplish this with PDF.js but I haven't had time to try and implement it – jtate May 19 '17 at 13:25
  • I actually managed to get it to print on the pop up window, however, the function would get fired too soon and there was no way I could add an event listener upon the pdf formatter given that each browser has a different reader. The only way I could get it to work is to save the pdf file on the server, serve the pdf on a hidden iframe and print once the iframe is loaded. Hope that sort of helps someone. Thanks – db306 May 21 '17 at 11:26
  • This works in chrome using iframe, but I am having trouble with Safari, it tries to print before the PDF is rendered and onload doesn't work – SKaul Jun 28 '18 at 22:02

2 Answers2

17

Here is a solution for point #3:

Open a new window with only an iframe element like the one in #2 and calling a print on the whole window. This also shows a blank white page.

In your case, it's throwing CORS error because it looks like for iframe src you are giving the base64String not the URL. Here is what you can do

  1. Take your base64String, convert it to a Blob
  2. Generate a URL from the Blob
  3. Provide the generated URL to iframe.
  4. After this you can print the content using iframe.contentWindow.print();

Here is the code to convert base64 to Blob

'use strict';

const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
    const byteCharacters = atob(b64Data);
    const byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        const slice = byteCharacters.slice(offset, offset + sliceSize),
            byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    const blob = new Blob(byteArrays, { type: contentType });
    return blob;
}


const contentType = "application/pdf",
    b64Data = "YourBase64PdfString", //Replace this with your base64String
    blob = this.b64toBlob(b64Data, contentType),
    blobUrl = URL.createObjectURL(blob);

Use blobUrl to the src of Iframe, once it's done, you can call print() on iframe as shown below

const iframeEle = document.getElementById("Iframe");
if (iframeEle) {
    iframeEle.contentWindow.print();
}

Hope this helps...

More details on base64 to Blob is here Creating a Blob from a base64 string in JavaScript

jlewkovich
  • 2,725
  • 2
  • 35
  • 49
ptchand
  • 186
  • 2
  • 3
  • This works perfectly. I just had to add a setTimeout(), so the browser has time to render the PDF. Otherwise it would just print a blank page. – Michael Jul 17 '18 at 11:54
  • 1
    This returns `Blocked a frame with origin "http://localhost:3000" from accessing a cross-origin frame.` error for me. I have `Allow-Origin=*` – Craig Howell Aug 06 '20 at 01:00
  • thanks for your solution this work , but i not understand what is sliceSize and for whats ?? – abolfazl_mehdi Dec 10 '20 at 09:58
5

you can use this,

function "printPreview(binaryPDFData)" to get print preview dialog of binary pdf data.

printPreview = (data, type = 'application/pdf') => {
    let blob = null;
    blob = this.b64toBlob(data, type);
    const blobURL = URL.createObjectURL(blob);
    const theWindow = window.open(blobURL);
    const theDoc = theWindow.document;
    const theScript = document.createElement('script');
    function injectThis() {
        window.print();
    }
    theScript.innerHTML = `window.onload = ${injectThis.toString()};`;
    theDoc.body.appendChild(theScript);
};

b64toBlob = (content, contentType) => {
    contentType = contentType || '';
    const sliceSize = 512;
     // method which converts base64 to binary
    const byteCharacters = window.atob(content); 

    const byteArrays = [];
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        const slice = byteCharacters.slice(offset, offset + sliceSize);
        const byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
    }
    const blob = new Blob(byteArrays, {
        type: contentType
    }); // statement which creates the blob
    return blob;
};
Manoj
  • 57
  • 1
  • 3
  • This is working perfect in Chrome and Firefox but not in IE. Is there any fix in IE. unable to add blob url to the window in IE. – Gowri Tumma Nov 20 '19 at 18:13
  • Anyway to get this to auto close after print? I have something similar working for images and I am able to use `window.close()` to achieve this but when I put `window.close()` in the code above it closes the window before I have a chance to print, even with a short setTimeout set. – Craig Howell Aug 06 '20 at 00:58