56

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?

nikiforovpizza
  • 487
  • 1
  • 7
  • 13
Sebb0
  • 581
  • 1
  • 4
  • 4
  • 2
    I think it would be better for everybody to write a service which downloads the pdf files to a folder, let the webserver serve the static files.Based on the path ,construct your URL for the file. – Srinivas Reddy Thatiparthy Oct 13 '12 at 19:02
  • 1
    Agreed with @SrinivasReddyThatiparthy. Download to a temporary file and serve up the file to the user. You could display it inline if definitely required by using a combination of object and embed tags - see this question: http://stackoverflow.com/questions/1244788/embed-vs-object/1244871#1244871 – mccannf Oct 13 '12 at 21:18
  • Why do you even need to grab data with js? Can't you just open new window with location=? – sander Aug 03 '15 at 13:35

8 Answers8

33

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();
guest271314
  • 1
  • 15
  • 104
  • 177
17

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);
}
Chris Cashwell
  • 22,308
  • 13
  • 63
  • 94
  • 1
    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) – Endless Sep 15 '16 at 16:53
  • Using a simple anchor is not an option in our case, AJAX was necessary. – Chris Cashwell Sep 16 '16 at 16:37
  • 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. – Urasquirrel Feb 24 '20 at 16:32
  • 2
    @Urasquirrel it's also not 2016 anymore. Time changes all things. – Chris Cashwell Feb 28 '20 at 15:12
  • I am trying to use `downloadUrl` for displaying PDF on the browser. The PDF is just blank white pages. Any idea why? – Tejas Aug 14 '22 at 04:13
  • @Tejas do you got any solution for this in my case also I am getting blank pdf but with correct number of pages – Shivam kaushal Dec 15 '22 at 19:13
  • Hey @Shivamkaushal, I don't remember the solution I got but I wrote this answer around the same time - https://stackoverflow.com/a/72896189/11058652 Maybe that might help you. Edit: Oh now I remember - I think I didn't use this kind of solution, I am simply serving the file from the server and displaying the PDF in an iframe – Tejas Dec 23 '22 at 08:11
5

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.

3

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.

Hace
  • 1,421
  • 12
  • 17
2

I work in PHP and use a function to decode the binary data sent back from the server. I extract the information an input to a simple file.php and view the file through my server and all browser display the pdf artefact.

<?php
   $data = 'dfjhdfjhdfjhdfjhjhdfjhdfjhdfjhdfdfjhdf==blah...blah...blah..'

   $data = base64_decode($data);
    header("Content-type: application/pdf");
    header("Content-Length:" . strlen($data ));
    header("Content-Disposition: inline; filename=label.pdf");
    print $data;
    exit(1);

?>
analyticalpicasso
  • 1,993
  • 8
  • 26
  • 45
Fabiolus
  • 53
  • 1
  • 2
  • 4
    -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. – Juanjo Sep 07 '15 at 15:01
2

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!
});
Ghostoy
  • 2,689
  • 19
  • 18
1

I saw another question on just this topic recently (streaming pdf into iframe using dataurl only works in chrome).

I've constructed pdfs in the ast and streamed them to the browser. I was creating them first with fdf, then with a pdf class I wrote myself - in each case the pdf was created from data retrieved from a COM object based on a couple of of GET params passed in via the url.

From looking at your data sent recieved in the ajax call, it looks like you're nearly there. I haven't played with the code for a couple of years now and didn't document it as well as I'd have cared to, but - I think all you need to do is set the target of an iframe to be the url you get the pdf from. Though this may not work - the file that oututs the pdf may also have to outut a html response header first.

In a nutshell, this is the output code I used:

//We send to a browser
header('Content-Type: application/pdf');
if(headers_sent())
    $this->Error('Some data has already been output, can\'t send PDF file');
header('Content-Length: '.strlen($this->buffer));
header('Content-Disposition: inline; filename="'.$name.'"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
ini_set('zlib.output_compression','0');
echo $this->buffer;

So, without seeing the full response text fro the ajax call I can't really be certain what it is, though I'm inclined to think that the code that outputs the pdf you're requesting may only be doig the equivalent of the last line in the above code. If it's code you have control over, I'd try setting the headers - then this way the browser can just deal with the response text - you don't have to bother doing a thing to it.

I simply constructed a url for the pdf I wanted (a timetable) then created a string that represented the html for an iframe of the desired sie, id etc that used the constructed url as it's src. As soon as I set the inner html of a div to the constructed html string, the browser asked for the pdf and then displayed it when it was received.

function  showPdfTt(studentId)
{
    var url, tgt;
    title = byId("popupTitle");
    title.innerHTML = "Timetable for " + studentId;
    tgt = byId("popupContent");
    url = "pdftimetable.php?";
    url += "id="+studentId;
    url += "&type=Student";
    tgt.innerHTML = "<iframe onload=\"centerElem(byId('box'))\" src='"+url+"' width=\"700px\" height=\"500px\"></iframe>";
}

EDIT: forgot to mention - you can send binary pdf's in this manner. The streams they contain don't need to be ascii85 or hex encoded. I used flate on all the streams in the pdf and it worked fine.

Community
  • 1
  • 1
enhzflep
  • 12,927
  • 2
  • 32
  • 51
0

You can use PDF.js to create PDF files from javascript... it's easy to code... hope this solve your doubt!!!

Regards!