Jspdf: Browser, platform, save(), open(), server or link?

Created on 26 Feb 2016  路  7Comments  路  Source: MrRio/jsPDF

I was getting a lot of users failing at the doc.save() so I set up a test grid to see for each browser and platform which method was most reliable. In addition to the built in save() and open(), I implemented for iOS the workaround where you output base64 to the href of a link (div method) and a server method where chunks of base64 are sent to php to create a downloadable file on the server. (XHR data must be under 1megabyte encoded! )

The test grid is attached, pardon my penmanship. 'Gen' means the doc created, 'file' means server save worked, 'show' means open a new window with the server pdf. Entries with a check and 'if not block pop-ups' means popup blockers stop execution with no error thrown. Check=success, x=fail. I circled the defaults I now use for each case. My users can select the server method if their default fails. I do a window.open() test onload and instruct the user to turn off block pop-ups. Sniffing isn't 100% so unknown browsers or platforms default to server method.

Note on browser sniffing: on Chrome for iPad 'CrOS' can be absent from the userAgent string, so in iOS: Like Safari without Mobile=Chrome. IE is failed at base64 taints canvas bug. My Android 'Browser' has glitchy JPG output and crashes.

jspdfdownload

I hope this saves someone else time and helpdesk calls. If anyone wants to see my code contact me. Thank you for the great library, cheers!

Most helpful comment

Hope this helps!

var chunks=[];
var totalChunks=1;
var reportName="";
var reportFile="";
var myPageLimit=25;
var usePdfMethod="server";
function pdfToMethod(doc, reportName){
  var defaultMethod=pdfMethod[platform][browser];
  usePdfMethod=defaultMethod;
  if(user.pdfMethod != "default"){usePdfMethod=user.pdfMethod;}
  if(usePdfMethod=="server"){pdfToServer(doc, reportName);}
  if(usePdfMethod=="save"){pdfToSave(doc, reportName);}
  if(usePdfMethod=="open"){pdfToOpen(doc, reportName);}
  if(usePdfMethod=="div"){pdfToDiv(doc, reportName);}
  var tapImage="downloadpdf.png";
  if(usePdfMethod=="save"){tapImage="savepdf.png";}
  if(usePdfMethod=="open"){tapImage="openpdf.png";}

  document.getElementById("downloadpdfimg").src="webapp/nav/"+tapImage;
  }

function pdfToDiv(doc, reportName){
  var pdfData = doc.output('datauristring');
  document.getElementById('downloadpdf').style.display="block";
  var element = document.getElementById('pdfData');
  element.href = "/webapp/iospdf.html#" + pdfData;
  element.target = "reportName";
  }
function pdfToSave(doc, reportName){

  doc.save(reportName+'.pdf');
  }
function pdfToOpen(doc, reportName){
  doc.output('dataurlnewwindow');
  }

var chunkBytes=300000;
function pdfToServer(doc, calling){
  reportName=calling;
  reportFile="reports/" +reportName+"_"+btoa(reportName)+ ".pdf"
  console.log('pdfToServer '+calling+ '');
  var pdf=doc.output();
  chunks=[];
  while (pdf) {
    if (pdf.length < chunkBytes) {
      chunks.push(pdf);
      break;
      }
    else {
      chunks.push(pdf.substr(0, chunkBytes));
      pdf = pdf.substr(chunkBytes);
      }
    }
  console.log("chunks.length="+chunks.length);
  document.getElementById('uploadingpercent').innerHTML="0%";  
  document.getElementById('uploadingpdf').style.display="block";
  totalChunks=chunks.length;
  saveChunk();
  }
function saveChunk(){
  var chunkNum=totalChunks-chunks.length;
  console.log("saveChunk chunks.length="+chunks.length+" chunkNum="+chunkNum);
  var chunk=chunks.shift();
  var aChunk=btoa(chunk);
  console.log("aChunk.length="+aChunk.length);
  var data = new FormData();
  data.append("chunkNum", chunkNum);
  data.append("reportName", reportName);
  data.append("pdfData", aChunk);

  var xhr = new XMLHttpRequest();
  xhr.onload = ajaxSuccess;
  xhr.open( 'post', '/webapp/uploadpdf.php', true );
  xhr.send(data);
  }
function ajaxSuccess(){
  console.log('ajaxSuccess()');
  if(chunks.length>0){
    var prog=(totalChunks-chunks.length)/totalChunks;
    document.getElementById('uploadingpercent').innerHTML=Math.floor(prog*100)+"%";
    window.setTimeout("saveChunk()", 200);
    }
  else{
    console.log('pdfToServer completed '+reportFile);
    document.getElementById('downloadpdf').style.display="block";
    document.getElementById('uploadingpdf').style.display="none";
    var element = document.getElementById('pdfData');
    element.href = "javascript:openPdfFile(); ";
    element.target = "";
    }
  }


[uploadpdf.php]
<?php
 $rnd=base64_encode($_POST['reportName']);
 $chunkNum=$_POST['chunkNum'];
 $pdf = base64_decode($_POST['pdfData']);
 $fname = "reports/" .$_POST['reportName'] . "_" . $rnd . ".pdf"; // name the file
 if($chunkNum=="0"){$file = fopen($fname, 'w');} // open the file write
 else{$file = fopen($fname, 'a');}// append chunk
 fwrite($file, $pdf); //save data
 fclose($file);
 echo '{"file":"' . $fname . '", "chunkNum":"' . $chunkNum . '"}';
?>

All 7 comments

@jambots I know this is an old post, but I'd love to see your code for this, especially the js and php for your "server" method. Thanks in advance.

Hope this helps!

var chunks=[];
var totalChunks=1;
var reportName="";
var reportFile="";
var myPageLimit=25;
var usePdfMethod="server";
function pdfToMethod(doc, reportName){
  var defaultMethod=pdfMethod[platform][browser];
  usePdfMethod=defaultMethod;
  if(user.pdfMethod != "default"){usePdfMethod=user.pdfMethod;}
  if(usePdfMethod=="server"){pdfToServer(doc, reportName);}
  if(usePdfMethod=="save"){pdfToSave(doc, reportName);}
  if(usePdfMethod=="open"){pdfToOpen(doc, reportName);}
  if(usePdfMethod=="div"){pdfToDiv(doc, reportName);}
  var tapImage="downloadpdf.png";
  if(usePdfMethod=="save"){tapImage="savepdf.png";}
  if(usePdfMethod=="open"){tapImage="openpdf.png";}

  document.getElementById("downloadpdfimg").src="webapp/nav/"+tapImage;
  }

function pdfToDiv(doc, reportName){
  var pdfData = doc.output('datauristring');
  document.getElementById('downloadpdf').style.display="block";
  var element = document.getElementById('pdfData');
  element.href = "/webapp/iospdf.html#" + pdfData;
  element.target = "reportName";
  }
function pdfToSave(doc, reportName){

  doc.save(reportName+'.pdf');
  }
function pdfToOpen(doc, reportName){
  doc.output('dataurlnewwindow');
  }

var chunkBytes=300000;
function pdfToServer(doc, calling){
  reportName=calling;
  reportFile="reports/" +reportName+"_"+btoa(reportName)+ ".pdf"
  console.log('pdfToServer '+calling+ '');
  var pdf=doc.output();
  chunks=[];
  while (pdf) {
    if (pdf.length < chunkBytes) {
      chunks.push(pdf);
      break;
      }
    else {
      chunks.push(pdf.substr(0, chunkBytes));
      pdf = pdf.substr(chunkBytes);
      }
    }
  console.log("chunks.length="+chunks.length);
  document.getElementById('uploadingpercent').innerHTML="0%";  
  document.getElementById('uploadingpdf').style.display="block";
  totalChunks=chunks.length;
  saveChunk();
  }
function saveChunk(){
  var chunkNum=totalChunks-chunks.length;
  console.log("saveChunk chunks.length="+chunks.length+" chunkNum="+chunkNum);
  var chunk=chunks.shift();
  var aChunk=btoa(chunk);
  console.log("aChunk.length="+aChunk.length);
  var data = new FormData();
  data.append("chunkNum", chunkNum);
  data.append("reportName", reportName);
  data.append("pdfData", aChunk);

  var xhr = new XMLHttpRequest();
  xhr.onload = ajaxSuccess;
  xhr.open( 'post', '/webapp/uploadpdf.php', true );
  xhr.send(data);
  }
function ajaxSuccess(){
  console.log('ajaxSuccess()');
  if(chunks.length>0){
    var prog=(totalChunks-chunks.length)/totalChunks;
    document.getElementById('uploadingpercent').innerHTML=Math.floor(prog*100)+"%";
    window.setTimeout("saveChunk()", 200);
    }
  else{
    console.log('pdfToServer completed '+reportFile);
    document.getElementById('downloadpdf').style.display="block";
    document.getElementById('uploadingpdf').style.display="none";
    var element = document.getElementById('pdfData');
    element.href = "javascript:openPdfFile(); ";
    element.target = "";
    }
  }


[uploadpdf.php]
<?php
 $rnd=base64_encode($_POST['reportName']);
 $chunkNum=$_POST['chunkNum'];
 $pdf = base64_decode($_POST['pdfData']);
 $fname = "reports/" .$_POST['reportName'] . "_" . $rnd . ".pdf"; // name the file
 if($chunkNum=="0"){$file = fopen($fname, 'w');} // open the file write
 else{$file = fopen($fname, 'a');}// append chunk
 fwrite($file, $pdf); //save data
 fclose($file);
 echo '{"file":"' . $fname . '", "chunkNum":"' . $chunkNum . '"}';
?>

Making this the "Save-Method not Working" main-thread.

We are using FileSaver.js for having the browser saving covered. We dont need to manage this part any longer.

Not my intention to reopen a closed topic but can you add an snipet on how are you using FileSaver.js with jsPDF? I can't make it work on iOS - Safari

Hello,
we will not provide support for filesaving. This project is about pdf generation. For saving pdf, we have included filesaverjs. But nobody forces you to use filesaverjs. You can use any other javascript based file saving/downloading library to do the work.

And if you look in filesaverjs, a project which is only focused on filesaving, you will see, that they pointed out, that

iOS
saveAs must be run within a user interaction event such as onTouchDown or onClick; setTimeout will prevent saveAs from triggering. Due to restrictions in iOS saveAs opens in a new window instead of downloading, if you want this fixed please tell Apple how this WebKit bug is affecting you.

We will not develop a file saving javascript library, when others have professionally developed already better tested and reliable libraries.

@Uzlopak, if I understand correctly, JsPDF uses another library (file-saver) to perform downloading.
Still, the JsPDF offers the feature to download PDF files. If this feature doesn't work, should the bug be reported here, or in FileSaver.js?

Do you suggest that we implement FileSaver.js ourselves, or any such library, even though JsPDF has it as a dependency?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

centurianii picture centurianii  路  4Comments

mackersD picture mackersD  路  4Comments

NoFootDancer picture NoFootDancer  路  3Comments

Pinank picture Pinank  路  3Comments

allenksun picture allenksun  路  3Comments