We have a ReactJS web app which uses an iframe to embed PDF in the page. We download the PDF as a byte array, then store it as a blob URL. It works in all browsers except Safari. We use createURLObject which creates a local url like blob:http://domain/guid.
If I grab this URL and open it in another tab, it shows the PDF. Not in Safari. It redirects to favorites with a random Guid and fails.
It seems Safari has issues with blobs or PDFs.
I tried changing it from a blob url to a data URL using a file reader. Only difference is that Safari can render the data url in another tab but not in an iframe.
The iframe loads but the body is empty with the message 'no supported plugin found'. Im running out of idea and think Safari just sucks
downloadPDf(customerId) {
this.api.downloadContract(customerId)
.then(response => {
const pdfData = new Blob(response.data.conent, 'application/pdf');
reader.onload = () => {
this.setState({dataUrl : reader.result });
}
reader.readAsDataURL(pdf);
}
Then in the JSX:
<iframe src={this.state.dataUrl} .... />
Once the file reader is loaded, the result will have a data URL containing the RAW data for the PDF.
It looks like something like this: data:application/pdf:byte64:<FILE_CONTENT>
This wont render in the iframe, but pasting it into a new tab will actually render the PDF correctly.
Our original code relied on Blob URLS which is like this:
downloadPDf(customerId) {
this.api.downloadContract(customerId)
.then(response => {
const pdfData = new Blob(response.data.conent, 'application/pdf');
const dataUrl = window.URL.CreateURLObject(blob);
}
This will create a local blob url which looks like: blob:http://localhost/
Pasting this into another tab does not work in Safari. It works in Edge, Chrome, and FireFox.
The downside of using the data URL is the file reader and a callback.
Related
I am working on different ways of displaying a PDF to get better on a project at work. I am able to insert a url to a PDF into an iframe element and it displays the PDF fine. Sometimes we have a use case where the front end receives a pdf as application/pdf instead of a url in a json object.
For this, I turn the application/pdf into a blob and create a url for the blob. This worked great on everything except Android, so I am testing out some methods with iFrame.
I would like to take this sample pdf http://www.pdf995.com/samples/pdf.pdf, turn it into a blob, and insert the blob url in the src of an iframe element for the purposes of testing blobs as iframe sources on Android Chrome browsers.
function App() {
const samplePdf = "http://www.pdf995.com/samples/pdf.pdf"
const blob = new Blob([samplePdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob)
return (
<>
<h1>iFrame Rendering of PDF Blob</h1>
<iframe title="pdf" src={url} style={{ height: '1250px', width: '100%' }}></iframe>
</>
);
}
export default App;
This is what renders in the React app
What am I missing to get the content of the pdf to display? React is not a requirement, just seemed an easy way to start a quick practice project.
You need the data to construct the blob while the URL just points to the data you need. Let's go & get it:
const getLocalPdfUrl = async () => {
const url = 'http://www.pdf995.com/samples/pdf.pdf';
const response = await fetch(url);
const blob = await response.blob();
return URL.createObjectURL(blob);
};
This function returns a Promise that will (hopefully) resolve with the URL you can use to construct the iframe. It's async, so, don't forget to wait for the promise to resolve.
Testing note
Fetching external resources from the frontend is restricted by CORS, so, pdf995.com's link will not work. It's also not a trivial task to find a dummy PDF document that would allow fetching itself from the FE.
To test if it works, I'd propose to place the PDF file in the /public folder (or similar) & serve it on the same locslhost as the app.
I need to fetch a PDF file from s3.amazonaws.com and when I query it using Postman (or paste directly into the browser), it loads fine. However when I try to generate the file path for it (to pass to a viewer later), it didn't work:
fetch(<S3URL>).then(res => res.blob()).then(blob => {
// THIS STEP DOES NOT WORK
let myBlob = new Blob(blob, {type: 'application/pdf'});
// expect something like 'www.mysite.com/my-file.pdf'
let PDFLink = window.URL.createObjectURL(myBlob);
return PDFLink;
}
I'm using Autodesk's Forge PDF viewer and it works perfectly fine for local PDF files:
let myPDFLink = 'public/my-file.pdf';
Autodesk.Viewing.Initializer(options, () => {
viewer = new Autodesk.Viewing.Private.GuiViewer3D(document.getElementById('forgeViewer'));
viewer.start();
viewer.loadExtension('Autodesk.PDF').then( () => {
viewer.loadModel(myPDFLink, viewer); // <-- works fine here
});
});
// from https://github.com/wallabyway/offline-pdf-markup
So, how do I go from the S3 URL (e.g. s3.amazonaws.com/com.autodesk.oss-persistent/0d/ff/c4/2dfd1860d1...) to something the PDF viewer can understand (i.e. has .pdf extension in the URL)?
I know for JSON files I need to do res.json() to extract the JSON content, but for PDFs, what should I do with the res object?
Note: I don't have control over the S3 URL. Autodesk generates a temporary S3 link whenever I want to download documents from their BIM360 portal.
I tried a lot of options and the only way I could display a PDF fetched via API calls is by using an object element:
<object data='<PDF link>' type='application/pdf'>
Converting the downloaded blob to base64 doesn't work. Putting the PDF link in an iframe doesn't work either (it still downloads instead of displaying). All the options I have read only work if the PDFs are part of the frontend application (i.e. local files, not something fetched from a remote server).
I'm using embed tag to show pdf file which is downloaded from server. Through embed i can download/print/rotate its contents. The problem is download my pdf file with FILENAME through download feature in embed tag.
HTML code:
<embed name="plugin" [src]="selectedDocumentSource | safe" type="application/pdf">
First approach
selectedDocumentSource is a link to file directly from server. For that i have in response header:
Content-Disposition: inline; filename="MyDocumentName.pdf"
The problem of this approach is: this GET is done by browser. Without any extension, and if possible, using typescript, how i could append the authorization header in it?
I'm using angular, but this browser request (and only this one) is not getting intercepted (maybe it runs outside angular or something).
Second approach
I download file instead browser.
showDocument(): void {
const reader = new FileReader();
reader.onload = () => {
this.selectedDocumentSource = reader.result;
};
this.service.getDocument( docId ).subscribe( {
next: (res: Blob) => reader.readAsDataURL( res )
} );
}
selectedDocumentSource is a DataURL. Filename attribute are not documented in this specification (althought i tried append fileName, filename, name attributes).
When user clicks in download icon (provided by browser), browser prompts where to save "download.pdf" file instead "MyDocumentName.pdf" expected.
Any ideas?
As per the Chrome version >=60 the PDF view functionality by any top-frame navigations options like
<A HREF=”data:…”>
window.open(“data:…”)
window.location = “data:…”
has been blocked by Google for which the discussion can be found at Google Groups. Now the problem is how to display the PDF on web without explicitly or forcibly making PDF to download. My old code looked as below via window.open to view the PDF data
dataFactory.getPDFData(id, authToken)
.then(function(res) {
window.open("data:application/pdf," + escape(res.data));
},function(error){
//Some Code
}).finally(function(){
//Some Code
});
In above I extract the PDF data from server and display it. But since window.open is blocked by Chrome and as suggested by one of the expert over here to use <iframe> to open the PDF data and I tried but it's not working. It always says Failed to Load PDF Data as below
The updated JS code for the <iframe> looks as below:
dataFactory.getPDFData(id, authToken)
.then(function(res) {
$scope.pdfData = res.data;
},function(error){
//Some Code
}).finally(function(){
//Some Code
});
And the HTML looks as below:
<iframe src="data:application/pdf;base64,pdfData" height="100%" width="100%"></iframe>
How can I proceed and bring back the original PDF view functionality? I searched over other stack questions but out of luck on how to resolve this. May be I did something wrong or missed something with the iframe code but it's not working out.
After unable to find the desired result I came up with below approach to resolve the issue.
Instead of opening the PDF on new page what I did is as soon as user clicks on the Print button PDF file gets downloaded automatically. Below is the source for same.
//File Name
var fileName = "Some File Name Here";
var binaryData = [];
binaryData.push(serverResponse.data); //Normal pdf binary data won't work so needs to push under an array
//To convert the PDF binary data to file so that it gets downloaded
var file = window.URL.createObjectURL(new Blob(binaryData, {type: "application/pdf"}));
var fileURL = document.createElement("fileURL");
fileURL.href = file;
fileURL.download = serverResponse.name || fileName;
document.body.appendChild(fileURL);
fileURL.click();
//To remove the inserted element
window.onfocus = function () {
document.body.removeChild(fileURL)
}
In your old code :
"data:application/pdf," + escape(res.data)
In the new :
your iframe src is like "data:application/pdf;base64,pdfData"
Try to remove base64 from the src, it seems to be already present in the value of 'pdfdata'.
I'm trying to implement a pdf print feature based on PDFKit in my web site.
I created a PDF using PDFKit and referenced it via a blobURL with an iframe.
But if I print the iframe, it is empty. (Firefox and Chrome works fine)
IE prints the following warning:
One or more blob urls were revoked by closing the blob for which they were created. these urls will no longer resolve as the data backing the url has been freed.
Here is my code:
var url = this.pdfStream.toBlobURL('application/pdf');
if (navigator.userAgent.match(/firefox/i) !== null){ //Firefox
window.open(url, '_blank');
} else{
$(document.body).append('<iframe id="printPreview" name="printPreview">');
$('iframe#printPreview').url = this.pdfStream.toBlobURL('application/pdf');
$('iframe#printPreview').attr('src', url);
$('iframe#printPreview').load(function(){
window.frames["printPreview"].focus();
window.frames["printPreview"].print();
});
}
Has anybody a workaround for this?
I already tried to open the BlobURL in a new window, but it also fails with IE. I also tried to store the blobURL in a window property, but it also fails.
Thanks!