Window.Open with PDF stream instead of PDF location - javascript

Based on the question Open PDF in new browser full window, it looks like I can use JavaScript to open a new window with a PDF file with the following code:
window.open('MyPDF.pdf', '_blank');
I'd like to do so on a return trip from the server by adding a byte array instead of the file name to use as the URL location in window.open
I'm currently returning PDF files like this:
Response.Clear();
Response.ContentType = "application/pdf";
Response.BinaryWrite(pdfByteArray);
Response.Flush();
Is there a way to open a new window with a PDF byte array in javascript.
Something like this:
var script = "window.open('" + pdfByteArray + "', '_blank');";
ScriptManager.RegisterClientScriptBlock(Parent.Page, typeof(Page), "pdf", script, true);

It looks like window.open will take a Data URI as the location parameter.
So you can open it like this from the question: Opening PDF String in new window with javascript:
window.open("data:application/pdf;base64, " + base64EncodedPDF);
Here's an runnable example in plunker, and sample pdf file that's already base64 encoded.
Then on the server, you can convert the byte array to base64 encoding like this:
string fileName = #"C:\TEMP\TEST.pdf";
byte[] pdfByteArray = System.IO.File.ReadAllBytes(fileName);
string base64EncodedPDF = System.Convert.ToBase64String(pdfByteArray);
NOTE: This seems difficult to implement in IE because the URL length is prohibitively small for sending an entire PDF.

Note: I have verified this in the latest version of IE, and other browsers like Mozilla and Chrome and this works for me. Hope it works for others as well.
if (data == "" || data == undefined) {
alert("Falied to open PDF.");
} else { //For IE using atob convert base64 encoded data to byte array
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
var byteCharacters = atob(data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], {
type: 'application/pdf'
});
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else { // Directly use base 64 encoded data for rest browsers (not IE)
var base64EncodedPDF = data;
var dataURI = "data:application/pdf;base64," + base64EncodedPDF;
window.open(dataURI, '_blank');
}
}

Adding to #Dinesh's answer to handle Not allowed to navigate top frame to data URL error in Chrome and Edge
if (data == "" || data == undefined) {
// Log Error: PDF data not available
} else {
var byteCharacters = atob(data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var file = new Blob([byteArray], { type: 'application/pdf;base64' });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
// For IE
window.navigator.msSaveOrOpenBlob(file, 'mypdf.pdf');
} else {
// For non-IE
var fileURL = URL.createObjectURL(file);
window.open(fileURL);
}
}

Related

Error Converting Base64 data to File using JavaScript on Internet Explorer(0x800a01bd - JavaScript runtime error: Object doesn't support this action)

I am trying to convert base64 data to file using javascript on asp.net, but i am getting( 0x800a01bd - JavaScript runtime error: Object doesn't support this action) error on final stage while converting blob to file at final stage.
Here is my code:
function dataBaseURLtoFile(str) {
// extract content type and base64 payload from original string
var pos = str.indexOf(';base64,');
var type = str.substring(5, pos);
var b64 = str.substr(pos + 8);
// decode base64
var imageContent = atob(b64);
// create an ArrayBuffer and a view (as unsigned 8-bit)
var buffer = new ArrayBuffer(imageContent.length);
var view = new Uint8Array(buffer);
// fill the view, using the decoded base64
for (var n = 0; n < imageContent.length; n++) {
view[n] = imageContent.charCodeAt(n);
}
// convert ArrayBuffer to Blob
var blob = new Blob([buffer], { type: type });
//convert blob to file
var file = new File([blob], "name", { type: "image/jpeg", });
return file;
}
I try to check your code and found that issue is on line below.
var file = new File([blob], "name", { type: "image/jpeg", });
IE and Edge browser does not supports the File() constructor.
File.File() constructor
For IE and Edge browser you need to use any alternative way.
You can try to refer thread below may give you some helpful information about alternative ways.
Is there an alternative for File() constructor for Safari and IE?

Downloaded PDF looks empty although it contains some data

I'm trying to implement a PDF file download functionality with JavaScript.
As a response to a POST request I get a PDF file, in Chrome DevTools console it looks like (the oResult data container, fragment):
"%PDF-1.4↵%����↵4 0 obj↵<</Filter/FlateDecode/Length 986>>stream↵x��
Now I'm trying to initialize the download process:
let blob = new Blob([oResult], {type: "application/pdf"});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "tstPDF";
link.click();
As a result, upon a click on a button I get tstPDF.pdf, it contains the correct number of pages, but the PDF itself is empty, no content is displayed, although it is 6 KB.
When I test the Java server-side module, which generates the PDF, everything is working fine, it sends InputStream through ServletOutputStream. Thus I assume that the issue is somewhere on a client side, perhaps something with MIME, BLOB, encoding, or similar.
Why doesn't the generated PDF display any data?
I solved the issue.
The problem was in a way the data is delivered from the server to the client.
It is critical to assure that the server sends the data in Base64 encoding, otherwise the client side can't deserialize the PDF string back to the binary format. Below, you can find the full solution.
Server-side:
OutputStream pdfStream = PDFGenerator.pdfGenerate(data);
String pdfFileName = "test_pdf";
// represent PDF as byteArray for further serialization
byte[] byteArray = ((java.io.ByteArrayOutputStream) pdfStream).toByteArray();
// serialize PDF to Base64
byte[] encodedBytes = java.util.Base64.getEncoder().encode(byteArray);
response.reset();
response.addHeader("Pragma", "public");
response.addHeader("Cache-Control", "max-age=0");
response.setHeader("Content-disposition", "attachment;filename=" + pdfFileName);
response.setContentType("application/pdf");
// avoid "byte shaving" by specifying precise length of transferred data
response.setContentLength(encodedBytes.length);
// send to output stream
ServletOutputStream servletOutputStream = response.getOutputStream();
servletOutputStream.write(encodedBytes);
servletOutputStream.flush();
servletOutputStream.close();
Client side:
let binaryString = window.atob(data);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
let blob = new Blob([bytes], {type: "application/pdf"});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = pdfFileName;
link.click();
Reference topics:
How to convert a PDF generating in response.outputStream to a Base64 encoding
Download File from Bytes in JavaScript
Thanks to this. It really works.
BTW, here's how I do it using spring controller and ajax with pdf generated by jasper
The Controller:
public ResponseEntity<?> printPreview(#ModelAttribute("claim") Claim claim)
{
try
{
//Code to get the byte[] from jasper report.
ReportSource source = new ReportSource(claim);
byte[] report = reportingService.exportToByteArrayOutputStream(source);
//Conversion of bytes to Base64
byte[] encodedBytes = java.util.Base64.getEncoder().encode(report);
//Setting Headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("pdfFileName.pdf", "pdfFileName.pdf");
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
headers.setContentLength(encodedBytes.length);
return new ResponseEntity<>(encodedBytes, headers, HttpStatus.OK);
}
catch (Exception e)
{
LOG.error("Error on generating report", e);
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
The ajax:
$.ajax({
type: "POST",
url: "",
data: form.serialize(), //Data from my form
success: function(response)
{
let binaryString = window.atob(response);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
let blob = new Blob([bytes], {type: "application/pdf"});
let link = URL.createObjectURL(blob);
window.open(link, '_blank');
},
error: function()
{
}
});
This will load the pdf in new window.
References: Return generated pdf using spring MVC

Trigger print preview of base64 encoded PDF from javascript

I've looked around stackoverflow trying to find a way to do this for a while now, and can't find a suitable answer. I need to be able to load a PDF in either a new window or an iframe via a base64 encoded string and trigger a print preview of it immediately after loading it. I can easily load the PDF using both of those methods, but can't actually get it to show the print preview properly. Here is what I've tried:
Using embed element in a new window. Calling window.print() is blank, even after the content is loaded.
Using a hidden, dynamically created iframe with src="data:application/pdf;base64,JVBERi0..." and calling myFrame.contentWindow.print(). But this gives a CORS error. I'm not sure why, because I'm not loading a new domain through the iframe, just content.
Open a new window with only an iframe element like the one in #2 and calling a print on the whole window. This also shows a blank white page.
Open a new window with the data uri and print it. window.open('data:application/pdf;base64,JVBERi0...').print();. This doesn't work either, as it doesn't even show a print preview at all. I've also tried delaying it with a setTimeout but that doesn't do anything either.
At this point I'm very confused as to why none of these work, especially because in Chrome it display's custom menu bars like this:
And if I click the actual print icon there, the print preview is perfect. Whatever Chrome is doing when I click that button is exactly what I want to accomplish. Is there anyway to trigger that functionality? Or is there another way to accomplish what I want? And just to clarify, I only need this to work in Chrome, I don't need to worry about other browsers.
Here is a solution for point #3:
Open a new window with only an iframe element like the one in #2 and calling a print on the whole window. This also shows a blank white page.
In your case, it's throwing CORS error because it looks like for iframe src you are giving the base64String not the URL. Here is what you can do
Take your base64String, convert it to a Blob
Generate a URL from the Blob
Provide the generated URL to iframe.
After this you can print the content using iframe.contentWindow.print();
Here is the code to convert base64 to Blob
'use strict';
const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize),
byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, { type: contentType });
return blob;
}
const contentType = "application/pdf",
b64Data = "YourBase64PdfString", //Replace this with your base64String
blob = this.b64toBlob(b64Data, contentType),
blobUrl = URL.createObjectURL(blob);
Use blobUrl to the src of Iframe, once it's done, you can call print() on iframe as shown below
const iframeEle = document.getElementById("Iframe");
if (iframeEle) {
iframeEle.contentWindow.print();
}
Hope this helps...
More details on base64 to Blob is here Creating a Blob from a base64 string in JavaScript
you can use this,
function "printPreview(binaryPDFData)" to get print preview dialog of binary pdf data.
printPreview = (data, type = 'application/pdf') => {
let blob = null;
blob = this.b64toBlob(data, type);
const blobURL = URL.createObjectURL(blob);
const theWindow = window.open(blobURL);
const theDoc = theWindow.document;
const theScript = document.createElement('script');
function injectThis() {
window.print();
}
theScript.innerHTML = `window.onload = ${injectThis.toString()};`;
theDoc.body.appendChild(theScript);
};
b64toBlob = (content, contentType) => {
contentType = contentType || '';
const sliceSize = 512;
// method which converts base64 to binary
const byteCharacters = window.atob(content);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {
type: contentType
}); // statement which creates the blob
return blob;
};

Convert an Image DataUrl to Blob in JavaScript with Webkit Fallback?

I have an image represented as data URL. I want this image to be converted into a blob.
I use the following method for this purpose:
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var arrayBuffer = new ArrayBuffer(byteString.length);
var _ia = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteString.length; i++) {
_ia[i] = byteString.charCodeAt(i);
}
var dataView = new DataView(arrayBuffer);
var blob = new Blob([dataView], { type: mimeString });
return blob;
}
This function is working but as I found here: Blob support the blob sometimes need the webkit prefix to work. I know that for URL I could use the fallback with
var _URL = URL || webkitURL;
How do I have to include the webkit prefix version as a fallback for older browsers in my function? Do I have to use BlobBuilder() instead on Blob()?
Best way to feature detect support for Blob is try and catch
function supportsBlob() {
try {
return !!new Blob();
} catch (e) {
return false;
}
}

Saving dataURL(base64) to file on PhoneGap (android)

I'm converting canvas to dataURL(base64) type and I wanted to save it to phone filesystem using PhoneGap's writer but without success (I get broken file which I cannot open) - here's some of my code:
var dataURL = document.getElementById("gen").toDataURL('image/png'); //substr() .replace('datadata:image/png;base64,', '');
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, gotFS, fail);
function gotFS(fileSystem) {
fileSystem.root.getFile("screenshot.png", {create: true, exclusive: false}, gotFileEntry, fail);
}
function gotFileEntry(fileEntry) {
fileEntry.createWriter(gotFileWriter, fail);
}
function gotFileWriter(writer) {
console.log("open and write");
writer.seek(0);
writer.write(dataURL);
console.log("close and save");
}
function fail(error) {
console.log(error.code);
}
var fileTransfer = new FileTransfer();
fileTransfer.download("/", screenshot.png,
function(entry) {
alert("download complete");
},
function(error) {
alert("download error source " + error.source);
alert("download error target " + error.target);
alert("upload error code" + error.code);
}
);
I've tried also other solution from stackoverflow, which was based on addtional java plugin but it hasn't work for me. Is there pure JS(with additional js libs) solution for it?
FileWriter’s write method does not take a base64 string.
According to the docs (http://docs.phonegap.com/en/edge/cordova_file_file.md.html#FileWriter)
text will be encoded as UTF-8 before being written. So your base64 string is being encoded before writing to the file so it’s not valid image data.
You have to pass your image data as a Blob or an ArrayBuffer. Note this only works on iOS and Android.
Have a look at Jeremy Banks’ b64toBlob function in this answer: https://stackoverflow.com/a/16245768
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
You can pass the resulting blob into the write method.
You may need to request larger file quota for the file system that is big enough to hold the image. Default is typically 5 mb but may vary in the different browsers as it's not standarized.
If the length of the data-uri exceeds this (which is likely considering it's a PNG file with 33% added 33% overhead as base64) the file won't save properly.
You can request quota using the quota API:
webkitStorageInfo.requestQuota(
webkitStorageInfo.PERSISTENT
newQuotaInBytes,
quotaCallback,
errorCallback);
More details can be found here.
You also have to take into account that you are getting a base64 encoded picture, so you cannot just save the as an images file an expect it to be a image.
So you have to Base64 decode that first before it will be an image. Maybe look at atob to do that
https://developer.mozilla.org/en-US/docs/Web/API/Window.atob

Categories