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

8

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?

Divinity answered 13/10, 2012 at 18:57 Comment(3)
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.Camm
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: https://mcmap.net/q/41992/-lt-embed-gt-vs-lt-object-gt/…Johnathanjohnathon
Why do you even need to grab data with js? Can't you just open new window with location=<url_of_your_xmlhttprequest>?Sagittarius
V
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();
Vouge answered 1/8, 2015 at 14:40 Comment(11)
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().Ezarras
@Ezarras 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()Vouge
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: https://mcmap.net/q/42081/-how-to-create-a-file-in-memory-for-user-to-download-but-not-through-serverEzarras
No, not an issue to remove element following calling .click() on elementVouge
to me the latest chrome on windows seems ignoring the a.click(), and the same for firefox on linuxStuder
@Studer Are popup blockers enabled at the browsers? See also How to download a file without using <a> element with download attribute or a server?Vouge
will it work if the response type at api side is application/json or application/binary??Graduated
@JyotiDuhan Why would a .pdf be served with an application/json content type?Vouge
Yes, got it now. It should be binary. Thanks :)Graduated
Adding responseType to blob worked for me in AngularJS.Richman
If you run into issues with IE complaining about the responseType, check out this answer for the fix.Helicograph
A
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);
}
Abiotic answered 15/9, 2016 at 16:29 Comment(7)
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)Veneaux
Using a simple anchor is not an option in our case, AJAX was necessary.Abiotic
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.Milurd
@Milurd it's also not 2016 anymore. Time changes all things.Abiotic
I am trying to use downloadUrl for displaying PDF on the browser. The PDF is just blank white pages. Any idea why?Kingwood
@Kingwood do you got any solution for this in my case also I am getting blank pdf but with correct number of pagesFerren
Hey @Shivamkaushal, I don't remember the solution I got but I wrote this answer around the same time - https://mcmap.net/q/42084/-print-lt-div-id-quot-printarea-quot-gt-lt-div-gt-only 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 iframeKingwood
A
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.

Abbyabbye answered 12/2, 2013 at 20:7 Comment(2)
Worked in Android browser?Mindymine
@Alexandre, This solution doesn't scale.Pussy
C
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.

Crosspiece answered 27/7, 2015 at 20:0 Comment(0)
O
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);

?>
Ovi answered 2/5, 2013 at 19:15 Comment(1)
-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.Turoff
S
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!
});
Southerly answered 3/8, 2015 at 13:2 Comment(0)
C
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.

Castanon answered 14/10, 2012 at 0:8 Comment(0)
B
0

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

Regards!

Beatification answered 28/7, 2015 at 16:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.