Print PDF directly from JavaScript - javascript

I am building a list of PDFs in HTML. In the list I'd like to include a download link and a print button/link. Is there some way to directly open the Print dialog for the PDF without the user seeing the PDF or opening a PDF viewer?
Some variation of downloading the PDF into a hidden iframe and triggering it to print with JavaScript?

Based on comments below, it no longer works in modern browsers
This question demonstrates an approach that might be helpful to you: Silent print an embedded PDF
It uses the <embed> tag to embed the PDF in the document:
<embed
type="application/pdf"
src="path_to_pdf_document.pdf"
id="pdfDocument"
width="100%"
height="100%" />
Then you call the .print() method on the element in Javascript when the PDF is loaded:
function printDocument(documentId) {
var doc = document.getElementById(documentId);
//Wait until PDF is ready to print
if (typeof doc.print === 'undefined') {
setTimeout(function(){printDocument(documentId);}, 1000);
} else {
doc.print();
}
}
You could place the embed in a hidden iframe and print it from there, giving you a seamless experience.

Here is a function to print a PDF from an iframe.
You just need to pass the URL of the PDF to the function. It will create an iframe and trigger print once the PDF is load.
Note that the function doesn't destroy the iframe. Instead, it reuses it each time the function is call. It's hard to destroy the iframe because it is needed until the printing is done, and the print method doesn't has callback support (as far as I know).
printPdf = function (url) {
var iframe = this._printIframe;
if (!this._printIframe) {
iframe = this._printIframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.style.display = 'none';
iframe.onload = function() {
setTimeout(function() {
iframe.focus();
iframe.contentWindow.print();
}, 1);
};
}
iframe.src = url;
}

You can use Print.js (npm install print-js). It's 128kB unpacked and you can find the docs at http://printjs.crabbly.com/.
It doesn't print on IE though, in those cases you'll have to download the PDF instead.
$http({
url: "",
method: "GET",
headers: {
"Content-type": "application/pdf"
},
responseType: "arraybuffer"
}).success(function (data, status, headers, config) {
var pdfFile = new Blob([data], {
type: "application/pdf"
});
var pdfUrl = URL.createObjectURL(pdfFile);
//window.open(pdfUrl);
printJS(pdfUrl);
//var printwWindow = $window.open(pdfUrl);
//printwWindow.print();
}).error(function (data, status, headers, config) {
alert("Sorry, something went wrong")
});

https://github.com/mozilla/pdf.js/
for a live demo http://mozilla.github.io/pdf.js/
it's probably what you want, but I can't see the point of this since modern browsers include such functionality, also it will run terribly slow on low-powered devices like mobile devices that, by the way, have their own optimized plugins and apps.

Cross browser solution for printing pdf from base64 string:
Chrome: print window is opened
FF: new tab with pdf is opened
IE11: open/save prompt is opened
.
const blobPdfFromBase64String = base64String => {
const byteArray = Uint8Array.from(
atob(base64String)
.split('')
.map(char => char.charCodeAt(0))
);
return new Blob([byteArray], { type: 'application/pdf' });
};
const isIE11 = !!(window.navigator && window.navigator.msSaveOrOpenBlob); // or however you want to check it
const printPDF = blob => {
try {
isIE11
? window.navigator.msSaveOrOpenBlob(blob, 'documents.pdf')
: printJS(URL.createObjectURL(blob)); // http://printjs.crabbly.com/
} catch (e) {
throw PDFError;
}
};
printPDF(blobPdfFromBase64String(base64String))
BONUS - Opening blob file in new tab for IE11
If you're able to do some preprocessing of the base64 string on the server you could expose it under some url and use the link in printJS :)

I used this function to download pdf stream from server.
function printPdf(url) {
var iframe = document.createElement('iframe');
// iframe.id = 'pdfIframe'
iframe.className='pdfIframe'
document.body.appendChild(iframe);
iframe.style.display = 'none';
iframe.onload = function () {
setTimeout(function () {
iframe.focus();
iframe.contentWindow.print();
URL.revokeObjectURL(url)
// document.body.removeChild(iframe)
}, 1);
};
iframe.src = url;
// URL.revokeObjectURL(url)
}

You can download the pdf file using fetch, and print it with Print.js
fetch("url").then(function (response) {
response.blob().then(function (blob) {
var reader = new FileReader();
reader.onload = function () {
//Remove the data:application/pdf;base64,
printJS({
printable: reader.result.substring(28),
type: 'pdf',
base64: true
});
};
reader.readAsDataURL(blob);
})
});

function printFile(url) {
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
// Use onload to make pdf preview work on firefox
iframe.onload = () => {
iframe.contentWindow.focus();
iframe.contentWindow.print();
};
}

It will be easy this way:
function PrintPdf (pdf) {
var iframe = document.createElement('iframe');
iframe.style.display = "none";
iframe.src = pdf;
document.body.appendChild(iframe);
iframe.contentWindow.focus();
iframe.contentWindow.print();
}

Simplification of #Nicolas BADIA's answer:
function printPDF (url)
{
let pdfFrame = document.body.appendChild(document.createElement('iframe'));
pdfFrame.style.display = 'none';
pdfFrame.onload = () => (void pdfFrame.contentWindow.print());
pdfFrame.src = url;
}

Related

URL.createObjectURL(blob) not creating proper URL in IE 11

I'm trying to render a pdf inside an iframe. It is working fine on Mozilla (v54) and Chrome (v59) but nothing happens in IE(v11) when I click on the link which loads the PDF. After debugging several times I found that the URL in Chrome/Firefox is blob:http://localhost:37444/5a8e7fed-cd61-4c58-904c-fad2ae169718 and in IE(v11) it is blob:B7395CB5-169D-471F-BB8F-AA90EAFB6DDB. Why is URL.createObjectURL(blob) not appending the http request in IE(v11)
function (iframe, url, headers) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = handler;
xhr.responseType = "arraybuffer";
headers.forEach(function (header) {
xhr.setRequestHeader(header[0], header[1]);
});
xhr.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) {
var blob = new Blob([xhr.response], { type: "application/pdf" });
var objectUrl = URL.createObjectURL(blob);
iframe.src = objectUrl;
} else {
console.error('XHR failed', this);
}
}
}
IE does not create a url for these blob objects because of security reasons i think.So using var objectUrl = URL.createObjectURL(blob);will not give you the source url which you can use inside iframe or embed tag.
I faced the same issue and searched a lot about the fix.But could not get the answer.Instead i solved it as following.
you can use the following for IE
if (bowser.msie && window.navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(file, fileName);
}else{
//do what you were doing for other than IE
}
the above IE code will prompt the user that whether he wants to save the file or directly open it.
User can click on button 'open' and then IE will show the PDF without downloading it in default reader.

Downloading a PDF using JavaScript that is Cross Browser Compatible

I can download a PDF using AngularJS in Chrome, but this doesn't appear to work in the latest FireFox, Internet Explorer 11 or Edge (assuming it doesn't work for IE10 either), and I know a shim is needed for IE9. Don't know if this the best shim for this if anyone has an opinion, but currently it doesn't seem to work. I tried it with a response type of blob and arraybuffer just in case that made a difference, and it doesn't.
All this counters what caniuse indicates about using the Blob URLs. Anyone have this working in IE9 and up, and the last couple versions of FF, and can point out what I'm doing wrong?
$http({
url: '/api/v1/download',
method: 'GET',
responseType: 'blob' // or 'arraybuffer'
}).then(function (response) {
// Use the Blob object to create an object URL to download the file
var url = URL.createObjectURL(response.data);
// var url = URL.createObjectURL(new Blob([response], {type: 'application/pdf'})); // arraybuffer version
// Create an anchor to perform download, but don't append to the DOM
anchor.href = downloadUrl;
anchor.download = filename;
anchor.target = '_blank';
anchor.click();
URL.revokeObjectURL(downloadUrl);
anchor = null;
}).catch(function (reason) {
console.log('FAIL', reason);
});
UPDATE
Currently the best (only) answer works for IE10, 11, Edge, FF, and continues to work with Chrome. IE9 won't work using this solution if anyone has another polyfill/shim/other/etc, and Safari doesn't support the download attribute so the solution in the chosen answer doesn't work in an SPA since it just redirects the current page so in both these cases I've just left TODO stubs.
This is an update to the posted answer with more information added in comments for anyone to use or hopefully add to so IE9 and Safari work as expected:
function performDownload(blob, filename) {
// IE9 has no API for handling downloads using Blob objects, and doesn't support the download attribute
if(isIE() == 9) {
// TODO: polyfill/shim/other... change response type to?
}
// Only works for IE10 and up, including Edge
else if (typeof window.navigator.msSaveBlob !== 'undefined') {
// Provides a prompt to save the file to a location of users choice
window.navigator.msSaveBlob(blob, filename);
}
// Browsers that adhere to current standards can implement downloads
// using the Blob object with the download anchor attribute
// ---
// NOTE: Edge 13+ is compliant with both these standards, but Edge 12
// does not support the download anchor attribute so all versions
// have been grouped to use the propriety `msSaveBlob` method
else {
// Use the Blob object to create an object URL to download the file
var URL = window.URL;
var downloadUrl = URL.createObjectURL(blob);
var anchor = document.createElement('a');
if(angular.isDefined(anchor.download)) {
anchor.href = downloadUrl;
anchor.download = filename;
anchor.target = '_blank';
document.body.appendChild(anchor); // Required by Firefox
anchor.click();
// Release the existing object URL, and the anchor
$timeout(function () {
URL.revokeObjectURL(downloadUrl);
document.body.removeChild(anchor);
anchor = null;
}, 100);
}
else {
// TODO: Safari does not support the download anchor attribute...
}
}
}
I've used this with success in both IE11 and Chrome:
function saveBlob(response, contentType, filename) {
let blob = new Blob([response.arrayBuffer()], { type: contentType });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround
window.navigator.msSaveBlob(blob, filename);
} else {
let URL = window.URL;
let downloadUrl = URL.createObjectURL(blob);
if (filename) {
let a = document.createElement('a');
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
// cleanup
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100);
}
}

How can I display the print option for a pdf stream returned by a wcf service?

My wcf service create a pdf stream and return it, through the ajax call I'm properly handling the display in a new window and the download of the pdf file.
There is an option that I need to add which is directly show the print options for the document.
I tried using iframe but the document preview doesn't properly show the pdf as expected.
This code works fine for download:
var blob = b64toBlob(base64PDF, { type: 'application/pdf;' });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = f + ".pdf";
link.click();
This is the code I'm trying to use to send the document to the printer:
var blob = b64toBlob(base64PDF, { type: 'application/pdf;' });
var iUrl = window.URL.createObjectURL(blob);
iUrl.download = f + ".pdf";
printPdf(iUrl);
function printPdf(url) {
var iframe = this._printIframe;
if (!this._printIframe) {
iframe = this._printIframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.style.display = 'none';
iframe.onload = function () {
setTimeout(function () {
iframe.focus();
iframe.contentWindow.print();
}, 1);
};
}
iframe.src = url;
}
I also tried few other approaches that are not any better.
var blob = b64toBlob(base64PDF, { type: 'application/pdf;' });
var iUrl = window.URL.createObjectURL(blob);
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = iUrl;
iframe.onload = function () {
setTimeout(function () {
iframe.focus();
iframe.contentWindow.print()
}, 1);
};
document.body.appendChild(iframe);
This code open the printing window but the document looks wrong, I basically get This:
Pdf print preview
As my pdf stream is a base64 string I thought I can create the blob directly as base 64, so I tried this code
var blob = new Blob([base64PDF], { type: 'application/pdf;base64,' })
var iUrl = window.URL.createObjectURL(blob);
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = iUrl;
iframe.onload = function () {
setTimeout(function () {
iframe.focus();
iframe.contentWindow.print()
}, 1);
};
document.body.appendChild(iframe);
Now nothing happen and I don't get the print preview page but in the console I get "Resource interpreted as Document but transferred with MIME type application/pdf:"

How to load a file into a html5 audio tag

How would a I load a audio file from a <input type="file"> tag into a audio tag?
I have tried :
<input type="file" id="file"></input>
<script>
var file = document.getElementById("file");
var audio = document.createElement("audio");
audio.src = file.value;
document.write(audio)
</script>
I believe it will satisfy your needs. First, the file input needs to be bound via JavaScript or jQuery (if you prefer). You can use Blob browser support
The following is a very basic example;
<input type="file" id="file"></input>
<audio id="audio" controls autoplay></audio>
We bind the #file for changes using AddEventListener as below
// Check for BlobURL support
var blob = window.URL || window.webkitURL;
if (!blob) {
console.log('Your browser does not support Blob URLs :(');
return;
}
document.getElementById('file').addEventListener('change', function(event){
consolePrint('change on input#file triggered');
var file = this.files[0],
fileURL = blob.createObjectURL(file);
console.log(file);
console.log('File name: '+file.name);
console.log('File type: '+file.type);
console.log('File BlobURL: '+ fileURL);
document.getElementById('audio').src = fileURL;
});
Or the other hand, here's a nice and more interactive example I created
<iframe style="height:600px;width:102.7%;margin:-10px;overflow:hidden;" src="//jsfiddle.net/adamazad/0oy5moph/embedded/result,js,html,css/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
var wrap = document.getElementById("wrap");
var file = document.getElementById("file");
var audio = document.createElement("audio");
file.onchange = function() {
audio.src = file.value;
wrap.appendChild(audio);
};
<div id="wrap">
<input type="file" id="file">
</div>
This is to realize the train of thought, but this also cannot achieve, because url that the upload is not local url, You can discuss with me in the comments section
I have developed an es6 code to load a file into an Audio element with a Promise that catches errors and resolves on success
//functions:
function setSrcObject(element, f) {
if ('srcObject' in element) {
try {
// eslint-disable-next-line no-param-reassign
element.srcObject = f; // this is the new way. only safary supports muliplt inputs, it is possible to put here media streams and files and blobs, but current new browsers support only media stream so need a fallback.
} catch (e) {
if (e.name !== 'TypeError') throw e;
// Avoid using this in new browsers, as it is going away.
// eslint-disable-next-line no-param-reassign
element.src = URL.createObjectURL(f);
}
}
// eslint-disable-next-line no-param-reassign
else element.src = URL.createObjectURL(f);
}
function loadAudioFile(audio, file) {
let onloadeddataResolve;
let onloadeddataReject;
// once
function onloadeddataEv() {
onloadeddataResolve();
audio.removeEventListener('loadeddata', onloadeddataEv);
audio.removeEventListener('error', onerrorEv);
}
function onerrorEv(e) {
onloadeddataReject(e.srcElement.error);
audio.removeEventListener('loadeddata', onloadeddataEv);
audio.removeEventListener('error', onerrorEv);
}
audio.addEventListener('loadeddata', onloadeddataEv);
audio.addEventListener('error', onerrorEv);
const promise = new Promise((resolve, reject) => {
onloadeddataResolve = resolve;
onloadeddataReject = reject;
}); // inversion of control promise
// try load it:
try {
// audio.src = url; // = http://example.org/myfile.mp3 or .ogg
setSrcObject(audio, file);
audio.load();
} catch (e) {
audio.removeEventListener('loadeddata', onloadeddataEv);
audio.removeEventListener('error', onerrorEv);
onloadeddataReject(e);
}
return promise;
}
//example:
let audio = new Audio(); // create audio element
audio.autoPlay=false;
filefield.oninput=async function test() {
try {
const f = filefield.files[0]; // take first file
await loadAudioFile(audio, f); // load the file
await audio.play();
} catch(e) { console.log(e.stack?e.stack:e) }
}
playpause.onclick = async () => { if( audio.paused) await audio.play(); else await audio.pause(); };
<input type="file" id="filefield" multiple="multiple" accept="audio/mpeg, audio/mp4, audio/ogg">
<input id="playpause" value="play pause" type="button">
this solution was a part of a playlist player object I have developed
https://jsfiddle.net/shimondoodkin/7zmhsg30/20/
related solution, play audio using web audio API
https://jsfiddle.net/shimondoodkin/251wx8ys/31/

Chrome Extension: Automatically download a screenshot taken with 'chrome.tabs.captureVisibleTab'

I'm new to the world of Chrome extensions / auto-downloading. I have a background page which takes a screenshot of the visible webpage with chrome.tabs.captureVisibleTab(). In my popup I have:
chrome.tabs.captureVisibleTab(null, {}, function (image) {
// Here I want to automatically download the image
});
I've done something similar with a blob before but I'm totally at a loss as to how to download an image as well as how to do it automatically.
In practice, I want my Chrome extension to screenshot + download the image automatically whenever a particular page is loaded (I'm guessing this will have to be achieved by having my content script talk to my background page, correct?)
Yes, as you said you can use Message Passing to get it done. By content scripts to detect the switch on particular pages, then chat with the background page in order to capture screenshot for that page. Your content script should send a message using chrome.runtime.sendMessage, and the background page should listen using chrome.runtime.onMessage.addListener:
Sample codes I created and tested it works with me:
Content script(myscript.js):
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
});
Background.js:
var screenshot = {
content : document.createElement("canvas"),
data : '',
init : function() {
this.initEvents();
},
saveScreenshot : function() {
var image = new Image();
image.onload = function() {
var canvas = screenshot.content;
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
// save the image
var link = document.createElement('a');
link.download = "download.png";
link.href = screenshot.content.toDataURL();
link.click();
screenshot.data = '';
};
image.src = screenshot.data;
},
initEvents : function() {
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.greeting == "hello") {
chrome.tabs.captureVisibleTab(null, {format : "png"}, function(data) {
screenshot.data = data;
screenshot.saveScreenshot();
});
}
});
}
};
screenshot.init();
Also keep in mind to register your content script's code and permissions in manifest file:
"permissions": ["<all_urls>","tabs"],
"content_scripts": [
{
"matches": ["http://www.particularpageone.com/*", "http://www.particularpagetwo.com/*"],
"js": ["myscript.js"]
}
]
It captures the screenshot and download the image automatically as .png whenever a particular page is loaded. Cheers!
Here's an alternative to message passing that should accomplish the same:
In my manifest.json
"manifest_version": 3,
"permissions": ["activeTab", "scripting"],
"background": {
"service_worker": "background.js"
},
...
In background.js
const download = (dataurl, filename) => {
const link = document.createElement("a");
link.href = dataurl;
link.download = filename;
link.click();
}
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.captureVisibleTab(async (dataUrl) => {
await chrome.scripting.executeScript({
func: download,
target: { tabId: tab.id },
args: [dataUrl, 'test.png'],
});
});
});

Categories