How to build PDF file from binary string returned from a web-service using javascript

115,767

Solution 1

Is there any solution like building a pdf file on file system in order to let the user download it?

Try setting responseType of XMLHttpRequest to blob , substituting download attribute at a element for window.open to allow download of response from XMLHttpRequest as .pdf file

var request = new XMLHttpRequest();
request.open("GET", "/path/to/pdf", true); 
request.responseType = "blob";
request.onload = function (e) {
    if (this.status === 200) {
        // `blob` response
        console.log(this.response);
        // create `objectURL` of `this.response` : `.pdf` as `Blob`
        var file = window.URL.createObjectURL(this.response);
        var a = document.createElement("a");
        a.href = file;
        a.download = this.response.name || "detailPDF";
        document.body.appendChild(a);
        a.click();
        // remove `a` following `Save As` dialog, 
        // `window` regains `focus`
        window.onfocus = function () {                     
          document.body.removeChild(a)
        }
    };
};
request.send();

Solution 2

I realize this is a rather old question, but here's the solution I came up with today:

doSomethingToRequestData().then(function(downloadedFile) {
  // create a download anchor tag
  var downloadLink      = document.createElement('a');
  downloadLink.target   = '_blank';
  downloadLink.download = 'name_to_give_saved_file.pdf';

  // convert downloaded data to a Blob
  var blob = new Blob([downloadedFile.data], { type: 'application/pdf' });

  // create an object URL from the Blob
  var URL = window.URL || window.webkitURL;
  var downloadUrl = URL.createObjectURL(blob);

  // set object URL as the anchor's href
  downloadLink.href = downloadUrl;

  // append the anchor to document body
  document.body.append(downloadLink);

  // fire a click event on the anchor
  downloadLink.click();

  // cleanup: remove element and revoke object URL
  document.body.removeChild(downloadLink);
  URL.revokeObjectURL(downloadUrl);
}

Solution 3

I changed this:

var htmlText = '<embed width=100% height=100%'
                 + ' type="application/pdf"'
                 + ' src="data:application/pdf,'
                 + escape(pdfText)
                 + '"></embed>'; 

to

var htmlText = '<embed width=100% height=100%'
                 + ' type="application/pdf"'
                 + ' src="data:application/pdf;base64,'
                 + escape(pdfText)
                 + '"></embed>'; 

and it worked for me.

Solution 4

The answer of @alexandre with base64 does the trick.

The explanation why that works for IE is here

https://en.m.wikipedia.org/wiki/Data_URI_scheme

Under header 'format' where it says

Some browsers (Chrome, Opera, Safari, Firefox) accept a non-standard ordering if both ;base64 and ;charset are supplied, while Internet Explorer requires that the charset's specification must precede the base64 token.

Solution 5

Detect the browser and use Data-URI for Chrome and use PDF.js as below for other browsers.

PDFJS.getDocument(url_of_pdf)
.then(function(pdf) {
    return pdf.getPage(1);
})
.then(function(page) {
    // get a viewport
    var scale = 1.5;
    var viewport = page.getViewport(scale);
    // get or create a canvas
    var canvas = ...;
    canvas.width = viewport.width;
    canvas.height = viewport.height;

    // render a page
    page.render({
        canvasContext: canvas.getContext('2d'),
        viewport: viewport
    });
})
.catch(function(err) {
    // deal with errors here!
});
Share:
115,767
Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    I am trying to build a PDF file out of a binary stream which I receive as a response from an Ajax request.

    Via XmlHttpRequest I receive the following data:

    %PDF-1.4....
    .....
    ....hole data representing the file
    ....
    %% EOF
    

    What I tried so far was to embed my data via data:uri. Now, there's nothing wrong with it and it works fine. Unfortunately, it does not work in IE9 and Firefox. A possible reason may be that FF and IE9 have their problems with this usage of the data-uri.

    Now, I'm looking for any solution that works for all browsers. Here's my code:

    // responseText encoding 
    pdfText = $.base64.decode($.trim(pdfText));
    
    // Now pdfText contains %PDF-1.4 ...... data...... %%EOF
    
    var winlogicalname = "detailPDF";
    var winparams = 'dependent=yes,locationbar=no,scrollbars=yes,menubar=yes,'+
                'resizable,screenX=50,screenY=50,width=850,height=1050';
    
    var htmlText = '<embed width=100% height=100%'
                         + ' type="application/pdf"'
                         + ' src="data:application/pdf,'
                         + escape(pdfText)
                         + '"></embed>'; 
    
                    // Open PDF in new browser window
                    var detailWindow = window.open ("", winlogicalname, winparams);
                    detailWindow.document.write(htmlText);
                    detailWindow.document.close();
    

    As I have said, it works fine with Opera and Chrome (Safari hasn't been tested). Using IE or FF will bring up a blank new window.

    Is there any solution like building a PDF file on a file system in order to let the user download it? I need the solution that works in all browsers, at least in IE, FF, Opera, Chrome and Safari.

    I have no permission to edit the web-service implementation. So it had to be a solution at client-side. Any ideas?

  • isobretatel
    isobretatel about 11 years
    Worked in Android browser?
  • Juanjo
    Juanjo over 8 years
    -1. Interesting but is not what the user asked. He asked specifically from a web service to Javascript to do it in the client side, Do not assume the user even controls the web service or that he is using a server side language.
  • zezollo
    zezollo over 7 years
    Works fine for me too, except the final removeChild(a), what returned an error (something like "Node not found"). So I just didn't triggered this removal on window.onfocus and instead directly put document.body.removeChild(a) right after a.click().
  • guest271314
    guest271314 over 7 years
    @zezollo window gaining focus before click event? Could probably be replaced with document.body.appendChild(a);a.onclick = function() { window.onfocus = function () { document.body.removeChild(a); window.onfocus = null; } } a.click()
  • zezollo
    zezollo over 7 years
    No, the error shows up after closing the pdf file viewer (software outside browser, not pdf.js). I can't understand this error, because when inspecting the html code, it does include the a element at the right place. Anyway, is this a problem to remove the child right after the click? This works really fine. This is used here too: stackoverflow.com/a/18197341/3926735
  • guest271314
    guest271314 over 7 years
    No, not an issue to remove element following calling .click() on element
  • Endless
    Endless over 7 years
    Do you need to do it with ajax? You can simply put the url as href and download it directly (but only if it's a simple get request)
  • Chris Cashwell
    Chris Cashwell over 7 years
    Using a simple anchor is not an option in our case, AJAX was necessary.
  • manuel-84
    manuel-84 over 7 years
    to me the latest chrome on windows seems ignoring the a.click(), and the same for firefox on linux
  • guest271314
    guest271314 over 7 years
    @manuel-84 Are popup blockers enabled at the browsers? See also How to download a file without using <a> element with download attribute or a server?
  • Pacerier
    Pacerier over 6 years
    @Alexandre, This solution doesn't scale.
  • Jyoti Duhan
    Jyoti Duhan over 6 years
    will it work if the response type at api side is application/json or application/binary??
  • guest271314
    guest271314 over 6 years
    @JyotiDuhan Why would a .pdf be served with an application/json content type?
  • Jyoti Duhan
    Jyoti Duhan over 6 years
    Yes, got it now. It should be binary. Thanks :)
  • AKJ
    AKJ almost 6 years
    Adding responseType to blob worked for me in AngularJS.
  • Allen
    Allen over 4 years
    If you run into issues with IE complaining about the responseType, check out this answer for the fix.
  • Urasquirrel
    Urasquirrel about 4 years
    This doesn't seem to work consistently in React and other virtualized dom libraries where a library maintains your dom, and you shouldn't be directly interacting with it in this way.
  • Chris Cashwell
    Chris Cashwell about 4 years
    @Urasquirrel it's also not 2016 anymore. Time changes all things.