28

I'm having troubles in finding a solution for this: I retrieve a PDF blob from a SQL filestream field using Javascript in this way (it's a lightswitch project)

var blob = new Blob([screen.WebReportsPdfFilesStream.selectedItem.Pdf], { type: "application/pdf;base64" });

I have the blob and I can even convert it in a filestream or to base64("JVBERi0....." or "%PDF 1.6 ......", etc.)

No problem so far.

Now I need to display it in a viewer. I prefer the viewer to open in a new window but i'm open to embed it into my page somehow.

I'm wondering if I can directly pass the blob or the stream to the viewer and display the document. I've tried something like

PDFView.open(pdfAsArray, 0)

Nothing happens in the embedded viewer in this case.

The pdfAsArray is good since I can display it appending the stream to a canvas within the same page. I just want to display the viewer, not embed the PDF in a canvas, possibly in a new window.

Can anyone provide few lines of code on how to achieve that in Javascript?

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
Efrael
  • 557
  • 1
  • 4
  • 11

5 Answers5

28

I'm using PDFJS.version = '1.0.1040'; PDFJS.build = '997096f';

The code that worked for me to get base64 pdf data loaded was this:

function (base64Data) {
  var pdfData = base64ToUint8Array(base64Data);
  PDFJS.getDocument(pdfData).then(function (pdf) {
    pdf.getPage(1).then(function (page) {
      var scale = 1;
      var viewport = page.getViewport(scale);
      var canvas = document.getElementById('myCanvas');
      var context = canvas.getContext('2d');
      canvas.height = viewport.height;
      canvas.width = viewport.width;
      page.render({ canvasContext: context, viewport: viewport });
    });
  });

  function base64ToUint8Array(base64) {
    var raw = atob(base64);
    var uint8Array = new Uint8Array(raw.length);
    for (var i = 0; i < raw.length; i++) {
      uint8Array[i] = raw.charCodeAt(i);
    }
    return uint8Array;
  }
}

This function could be the success function of an api call promise. What I'm doing here is rendering the pdf onto a canvas element myCanvas.

<canvas id="myCanvas"></canvas>

This shows the first page of the pdf but has no functionality. I can see why the viewer is desirable. If I get this hooked up to the viewer (viewer.html / viewer.js) I will edit my answer.

EDIT: How to hook up the viewer

1 In bower.json, add "pdfjs-viewer": "1.0.1040"

2 Html:

<iframe id="pdfViewer" src="lib/pdfjs-viewer/web/viewer.html" style="width: 100%; height: 700px;" allowfullscreen="" webkitallowfullscreen=""></iframe>

3 Change the stupid default document in the viewer.js file:

var DEFAULT_URL = '';

4 Controller:

var pdfjsframe = document.getElementById('pdfViewer');
pdfjsframe.onload = function() {
  LoadPdfDocument();
};

$scope.myApiCallThatReturnsBase64PdfData.then(
  function(base64Data) {
    $scope.base64Data = base64Data;
    LoadPdfDocument();
  },
  function(failure) {

    //NotificationService.error(failure.Message);

  });

function LoadPdfDocument() {
  if ($scope.PdfDocumentLoaded)
    return;
  if (!$scope.base64Data)
    return;

  var pdfData = base64ToUint8Array($scope.base64Data);
  pdfjsframe.contentWindow.PDFViewerApplication.open(pdfData);

  $scope.PdfDocumentLoaded = true;
}

function base64ToUint8Array(base64) {
  var raw = atob(base64);
  var uint8Array = new Uint8Array(raw.length);
  for (var i = 0; i < raw.length; i++) {
    uint8Array[i] = raw.charCodeAt(i);
  }
  return uint8Array;
}
toddmo
  • 20,682
  • 14
  • 97
  • 107
  • I don't understand from where comes your variable '$scope', did I miss something in your explanation ? Because I think it's the right solution for this problem, but i have an undefined error on the $scope variable. Could you help me ? My problem could be different here, i try to retrieve the content of a PDF on server-side with PHP (a readfile from the PDF file), so i do my ajax call to retrieve the content, and after that, i want to put it in the viewer... Is it possible with your solution right here or not ? – Quentin Janon Nov 16 '15 at 16:55
  • 1
    @QuentinJanon, Sorry, `$scope` is from AngularJS. What you need to change is wherever you see `$scope`, make it a variable. Not a local variable inside the function, but make one outside any functions. – toddmo Nov 16 '15 at 19:14
  • 3
    Almost 1 year later, your answer saved my life man, thank you! =) – Gustavo Gabriel Aug 16 '16 at 15:09
  • @GustavoGabriel, wow lol. It seems like 5 minutes ago when I wrote this! – toddmo Aug 16 '16 at 16:23
  • 1
    Life saver. This answer should be included in the pdf.js docs, never seen such a poor documentation. Anyway, thanks!!!! – fgonzalez Aug 24 '16 at 07:31
  • @fgonzalez, The people who make the viewer to view everyone else's documentation have bad documentation. haha, figures :) – toddmo Aug 24 '16 at 16:10
  • I've successfully been able to get this to work in chrome, but have been having problems with IE11 (PDF invalid) do you know of any particular work arounds that I may be missing? – Ben L. Sep 20 '16 at 18:57
  • @BenL., actually pdfs natively work in Chrome (like so much else). So, originally I did this solution to support IE, which doesn't natively support pdf viewing. As a test, go to a popular or official site that uses pdfjs in IE 11 as a test to divide and conquer the problem, like https://mozilla.github.io/pdf.js/web/viewer.html – toddmo Sep 20 '16 at 23:18
  • @BenL., as another test, try a fresh unmodified copy of pdfjs with the default document and make sure that works too in your application. – toddmo Sep 20 '16 at 23:20
  • I am trying to understand why all of the this conversion is required. Can't you just set the `src` attribute of the `iframe` with the dataURI string? – Jeffrey A. Gochin Nov 17 '16 at 18:12
  • @JeffreyA.Gochin, the iframe src is the path to the viewer. How would a data uri work to show the viewer? – toddmo Nov 17 '16 at 19:46
  • In Chrome, Firefox and Safari there are native PDF viewers built in. Sorry, I guess this is the wrong topic for my question. This is focused on PDF.js, which I have tried to use, but I am looking for a solution that works offline and support DataURI's. – Jeffrey A. Gochin Nov 17 '16 at 21:45
  • 1
    @JeffreyA.Gochin, in the code example, `var pdfData` I believe contains the data after the `data:` in your uri (the actual data w/o header). Whether you use angular or just js, you should be able to adapt this code and load the viewer like that. Yes, IE sucks. – toddmo Nov 18 '16 at 16:37
  • @toddmo I can get the dataUri by simply using the HTML 5 file API. All I am looking for is a lightweight way to display pretty much any file type. Image files of course are a no-brainer; just use an `` tag. PDF not too bad in most browsers; you can use an ` – Jeffrey A. Gochin Nov 28 '16 at 01:08
  • I am getting this error: Uncaught TypeError: Cannot read property 'open' of undefined – Matt Dec 08 '16 at 19:57
  • 1
    @Matt, put a break in Chrome debugger on that line, and see what's not defined. pdfjsframe? contentWindow? PDFViewerApplication? The data comes in way after the document is loaded usually. Let me know and we'll go from there. – toddmo Dec 08 '16 at 22:36
  • 1
    @toddmo I apologize. Your code is correct. I was missing the `onload`. As a result, `PDFViewerApplication` was undefined. – Matt Dec 09 '16 at 19:09
  • 1
    Excellent answer! Only as a result of seeing this, I finally figured out how to implement it in asp.net mvc!! Thanks! – Thierry May 16 '20 at 01:20
  • 1
    @Thierry, Thanks! glad I could help! – toddmo May 16 '20 at 01:30
  • @toddmo Just wanted to let you know that your answer is still helping people. I thought I was up a creek without a paddle after days of research and banging my head against a wall, then I found this answer and was up and running in less than a few hours. If I ever see you in person, lunch is on me. – CF_HoneyBadger Jun 30 '21 at 15:32
14

If you've got an typed array (e.g. an Uint8Array), then the file can be opened using PDFView.open(typedarray, 0);.

If you've got a Blob or File object, then the data has to be converted to a typed array before you can view it:

var fr = new FileReader();
fr.onload = function() {
    var arraybuffer = this.result;
    var uint8array = new Uint8Array(arraybuffer);
    PDFView.open(uint8array, 0);
};   
fr.readAsArrayBuffer(blob);

Another method is to create a URL for the Blob/File object:

var url = URL.createObjectURL(blob);
PDFView.open(url, 0);

If the PDF Viewer is hosted at the same origin as your website that embeds the frame, then you can also view the PDF by passing the blob URL to the viewer:

var url = URL.createObjectURL(blob);
var viewerUrl = 'web/viewer.html?file=' + encodeURIComponent(url);
// TODO: Load the PDF.js viewer in a frame or new tab/window.
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Thank you Rob. I was aware of the first two pieces and even of the possibility to open a pdf.js returned pdfDocument with PDFView.load function. I did't know about the last piece of code var url = URL.createObjectURL(blob); var viewerUrl = 'web/viewer.html?file=' + encodeURIComponent(url); // TODO: Load the PDF.js viewer in a frame or new tab/window. I think this will address me to the right place, since my preference is to open the viewer in a new window instead of load the file in an embedded viewer – Efrael Jul 03 '14 at 09:42
  • So, this should work: var url = URL.createObjectURL(blob); var viewerUrl = 'Scripts/pdfViewer/web/viewer.html?file=' + encodeURIComponent(url); window.open(viewerUrl, "_blank"); – Efrael Jul 03 '14 at 09:48
12

I finally made the PDFView.open method working. Now if I embed the viewer into my page and call the open function as Rob suggested in the first 2 examples it works.

For those who are looking for this kind of solution I provide some lines of code here:

This is the code in my Lightswitch mainPage.lsml.js. The js scripts (pdf.js, viewer and Others) are referenced in the main html page of the Lightswitch project (Default.html); I assume it should work with any other html page not Lightswitch based.

myapp.MainPage.ShowPdf_execute = function (screen) {
// Write code here.
// Getting the stream from sql
var blob = new Blob([screen.WebReportsPdfFilesStream.selectedItem.Pdf], { type: "application/pdf;base64" });
// Pass the stream to an aspx page that makes some manipulations and returns a response
var formData = new FormData();
formData.tagName = pdfName;
formData.append(pdfName, blob);
var xhr = new XMLHttpRequest();
var url = "../OpenPdf.aspx";
xhr.open('POST', url, false);
xhr.onload = function (e) {
    var response = e.target.response;
        var pdfAsArray = convertDataURIToBinary("data:application/pdf;base64, " + response);
        var pdfDocument;
// Use PDFJS to render a pdfDocument from pdf array
    PDFJS.getDocument(pdfAsArray).then(function (pdf) {
        pdfDocument = pdf;
        var url = URL.createObjectURL(blob);
        PDFView.load(pdfDocument, 1.5)
    })
};
xhr.send(formData);  // multipart/form-data 
};

This is the convertDataURIToBinary function

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 (i = 0; i < rawLength; i++) {
    array[i] = raw.charCodeAt(i);
}
return array;
}

What is still missed is the possibility to pass the stream directly to the viewer.html page in order to open it in a new window and have a separate ui where make the rendering.

This code is still not working since I got an empty viewer with no document inside:

var blob = new Blob([screen.WebReportsPdfFilesStream.selectedItem.Pdf], { type: "application/pdf;base64" });
var url = URL.createObjectURL(blob);
var viewerUrl = 'Scripts/pdfViewer/web/viewer.html?file=' + encodeURIComponent(url);
window.open(viewerUrl);

Looks like the encodeURIComponent(url) is not passing to the viewer a good object to load into the viewer. Any idea or suggestion?

Maicon Mauricio
  • 2,052
  • 1
  • 13
  • 29
Efrael
  • 557
  • 1
  • 4
  • 11
  • Live saver. This solved my issue, and should be accepted as the correct answer. – lux Feb 25 '15 at 19:01
  • Hi @Efrael . Your solution is really cool, but I am not sure what is the **BASE64_MARKER**? Where to get it ? Or what I need to put in that variable, can you describe please ? :) – Oleg Klimenko Oct 26 '17 at 21:13
  • 1
    Just a string: var BASE64_MARKER = ';base64, '; – Efrael Oct 28 '17 at 09:54
5
var url = URL.createObjectURL(blob);

var viewerUrl = 'web/viewer.html?file=' + encodeURIComponent(url);

// TODO: Load the PDF.js viewer in a frame or new tab/window.

//-- Abobe code opens a new window but with errors : 'Missing PDF 
// blob://http://server-addr:port/converted-blob' 
cнŝdk
  • 31,391
  • 7
  • 56
  • 78
1

I am using viewer in an iframe;

 <iframe
    id="pdfIframe"
    src="pdfjs/web/viewer.html"
    style="width: 100%; height: 100%;"
  >
  </iframe>

And fetch API used as follows;

 fetch(pdfSourceUrl).then((response: Response) => {
            response.blob().then((blob) => {
              var url = URL.createObjectURL(blob);
              pdfIframe.src = `pdfjs/web/viewer.html?file=${url}`;
            });
          });

Eventually iframe src created as follows;

http://localhost:9000/pdfjs/web/viewer.html?file=blob:http://localhost:9000/14f6a2ec-ad25-40ab-9db8-560c15e90f6e

Teoman shipahi
  • 47,454
  • 15
  • 134
  • 158