jsPDF doesn't work well with the latest version of Firefox. It does not let me to download the PDF. Is there a fix on this? I tried downloading the latest version of jsPDF.
EDIT:
My FF version is 32.0.3
I don't get any error messages.
This is the code that "downloads" the pdf. It works well in IE and Chrome:
So I think it has nothing to do with the code. What I want to know is how can I download the pdf in Firefox.
function appendDataToPDF(div, doc, top)
{
html2canvas(div, {
background: '#fff',
onrendered: function(canvas) {
var img = canvas.toDataURL();
doc.addImage(img, 'JPEG', 10, top, parseInt($(div).css("width"), 10), parseInt($(div).css("height"), 10));
if(top > 240)
{
doc.addPage();
top = 27;
}
div = $(div).next();
if(div.length === 0)
{
doc.save('doc.pdf');
}
else
{
if(div.get(0).nodeName === 'BR')
div = $(div).next();
appendDataToPDF(div, doc, top);
}
}
});
}
The issue lies within the execution of the doc.save(). The document is not ready yet, when the save() command is invoked. Try to set a timeout and it should work.
setTimeout(function() {
doc.save(filename);
}, timeout);
I just placed an alert() after the doc.save() function and now the download works. You could try this solution if you run into the same problem.
One thread is going on related this issue, may be you will get fix here.
https://github.com/parallax/jsPDF/issues/3391
Related
I have a dynamic page which depending on some information will create more or less sections. I want to loop through those, make a canvas out of them and bring those sections to a pdf.
I had the page previously running with this code, but i had to change the sections.
var pagina = 0;
var paginas_totales = parseInt('<?= $pagina ?>');
var doc = new jsPDF({
orientation: "landscape",
unit: "mm",
format: [297, 238]
});
$('.generate').on('click', function() {
nextStep();
});
function generaDescarga() {
pagina++;
if(pagina == paginas_totales) {
doc.save('Report <?= html_entity_decode($dataSampling['samp_name']) ?>.pdf');
window.scrollTo(0,0)
} else {
nextStep()
}
}
var j = -1;
function nextStep(){
j++;
if(j >= paginas_totales) return;
$('#loader .contenedor_loader span').text(`Creando página ${j + 1} / ${paginas_totales}`)
//Fix imagen cut
document.querySelector(".page-"+j).scrollIntoView()
setTimeout(() => {
html2canvas(
document.querySelector(".page-"+j),
{
// allowTaint: true,
backgroundColor:"#0d3451",
scale: 2,
userCORS: true,
onrendered: function(canvas) {
var imgData = canvas.toDataURL("image/jpeg", 1.0);
doc.addImage(imgData, 'JPEG', 0, 0);
if (j != paginas_totales - 1) {
doc.addPage();
}
generaDescarga();
}
}
).then(canvas => {// document.body.appendChild(canvas)}
);
}, 10);
}
From here i got plenty of errors, most of them because the asyncronity of the library of html2canvas where i am struggling.
My actual code looks like this
var paginas_totales = parseInt('<?= $pagina ?>');
rendered =0;
$('.generate').on('click', function() {
showLoading();
var doc = new jsPDF({orientation: "landscape",unit: "pt",format: [1125, 900]});
//Generation of empty structure
for (let i = 1; i < paginas_totales; i++) {doc.addPage();}
doc.save('Report <?= $dataSampling['samp_name'] ?>.pdf');
for (let i = 0; i <paginas_totales ; i++) {
var div_renderizar = document.querySelector(".page-"+i);
if (div_renderizar != null){
generateCanvas( doc, i, div_renderizar ,function() {
rendered++;
if (rendered >= paginas_totales){
doc.save('Report.pdf');
hideLoading();
}
});
}else{
console.log("Failing page "+i);
hideLoading();
}
}
});
function generateCanvas(doc, i, div_renderizar, callback){
html2canvas(
div_renderizar,
{ allowTaint: true,
scale: 2,
userCORS: true
}
).then(function (canvas){
let imgData = canvas.toDataURL("image/jpeg", 1.0);
doc.setPage(i);
doc.addImage(imgData, 'JPEG', 0, 0);
}).catch( error => {
console.error("Falló generateCanvas=> "+error);
console.log(new Error().stack);
hideLoading();
});
callback();
}
Where im getting blank pages in the pdf.
At this point i've already managed to get a pdf with all pages but i get it in a random* order, which i guess is due the asyncronity of the library (lighter pages go first)
I've tried adding "async" and "await".
I've updated the libraries to a CDN for both html2canvas and jsPDF
I've tried lots of tutorials like
https://solveforum.com/forums/threads/solved-html2canvas-and-jspdf-rendering-multiple-divs-blank-pdfs.197029/
https://www.freakyjolly.com/html2canvas-multipage-pdf-tutorial/
but most of them focus on static page or pagination depending on height
I've tried different approaches suggested on other SO pages like
HTML2Canvas creating multiple divs
html2canvas screenshot keeps turning up blank (this one because it was generating blank pages)
But the ORDER on the pages go crazy and the log isn't helping, is just telling me that the error is in this function, or in the .min external file, but not what i need.
So what i need is
Create pdf object
Loop for elements like $(".page-"+i) from 0 to last index
Add page for element
Create canvas for that element and insert that IN ORDER into the pdf
Download/Preview the results
The html structure is working perfect, having some pdfs being correctly generated proves this.
Both html2canvas and jsPDF are correctly installed/configured
The issue im sure its between point 2 and 4.
Hope its clear enough. Thank you for all help, really stuck on this, i have tried a lot of approaches, but i wanted to be readable.
I have six floors of a building drawn on a canvas with fabric.js. In order to show only one floor, I do this:
building.selectFloor = function(floorId){
for (var i = 0; i < floors.length; i++){
if (floorId == floors[i].name){
floors[i].visible = true;
}else{
floors[i].visible = false;
}
}
canvas.renderAll();
};
But nothing changes - all floors are still visible. The floors set to visible = false don't disappear until renderAll() is called later on a window resize:
$(window).resize(setSize);
setSize();
function setSize(){
viewportWidth = $(window).width();
viewportHeight = $(window).height();
$appContainer.css({ "width": viewportWidth, "height": viewportHeight});
canvas.setDimensions({ width: viewportWidth, height: viewportHeight });
if (canvas.building){
canvas.building.center();
canvas.building.scaleX = canvas.building.scaleY = (viewportWidth + viewportHeight) / (1920 + 1080);
canvas.renderAll();
}
}
but then they do disappear. Why is one renderAll() working, and the other not? I've tried wrapping the non-functioning renderAll() in a window.timeOut to see if a delay helps, but no luck.
Ok - so I figured it out in the end: building is a group which appears to be getting cached by some internal fabric magic, and the cached version is being rendered instead of the updated version. To render the updated version you have to do this
canvas.building.set('dirty', true);
canvas.renderAll();
Hope that helps someone on down the line.
EDIT
Turns out there's an easier solution still:
canvas.building.objectCaching = false
There is a new version that changed the format to:
canvas.requestRenderAll();
This worked for me in later versions:
// for one canvas
myCanvas.objectCaching = false;
// for all canvas
fabric.Object.prototype.objectCaching = false;
// in both case it still needs to be re-rendered
myCanvas.renderAll();
ps: using version 4.2.0
You have to update (at least in the new version) object properties with object.set({...}) or set object.dirty = true in order to make the cache system recognize the change in the object properties. Disabling cache might not be the best idea, it has been included for a reason. canvas.requestRenderAll(); then works as intended.
I am using highcharts to display several graphs on a webpage which display fine.
I have an export function that tries to combine the charts into a pdf. I am getting the svg of the chart and converting it to a jpeg image to be included in a pdf created by jsPDF.
Here is the code I am using to generate the images:
if ($('.chart').length > 0) {
var chartSVG = $('.chart').highcharts().getSVG(),
chartImg = new Image();
chartImg.src = "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(chartSVG)));
var chartCanvas = document.createElement("canvas");
chartCanvas.width = 600;
chartCanvas.height = 400;
chartCanvas.getContext("2d").drawImage(chartImg, 0, 0, 600, 400);
var chartImgData = chartCanvas.toDataURL("image/jpeg");
}
This works perfectly in Chrome but in Firefox it just returns a black image.
Does anyone know what might be going wrong or has seen a similar issue?
Thanks for your help.
UPDATE
I've updated the code but now no image is appended to the pdf document, either in Chrome or Firefox.
if ($('.sales').length > 0) {
var chartSVG = $('.sales').highcharts().getSVG(),
chartImg = new Image();
chartImg.onload = function () {
var chartCanvas = document.createElement("canvas");
chartCanvas.width = 600;
chartCanvas.height = 400;
chartCanvas.getContext("2d").drawImage(chartImg, 0, 0, 600, 400);
var chartImgData = chartCanvas.toDataURL("image/jpeg");
}
chartImg.src = "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(chartSVG)));
}
Not sure if I have the code in the correct place.
If I log 'chartImgData' to the console, both browsers generate a dataURI, but Firefox's version differs to Chromes.
UPDATE
Fixed the issue with black images. Now i'm struggling with how to return multiple images - how to nest multiple callbacks or is there another way?
Example: jsfiddle.net/wmuk489c/2
SOLVED
Thanks for your help #RobertLangson. fiddle updated with final working code should anyone need it: http://jsfiddle.net/wmuk489c/3/
FURTHER ISSUES:
My charts are dynamic and so may not always be present. I need to get an image from each graph that exists. If the graph does not exist, the 'getSVG' function fails, see example: http://jsfiddle.net/wmuk489c/4/
How should the img.onload work if the chart doesn't exist? The first chart in the callback may not be present either, so how would this work? Is there a better way to get the images?
setting chartImg.src causes an asynchronous load so you then need to do this...
chartImg.onload = function() {
var chartCanvas = document.createElement("canvas");
chartCanvas.width = 600;
chartCanvas.height = 400;
chartCanvas.getContext("2d").drawImage(chartImg, 0, 0, 600, 400);
var chartImgData = chartCanvas.toDataURL("image/jpeg");
doc.addImage(chartImgData, 'JPEG', 0, 0, 200, 100);
// You can only do this bit after you've added the image so it needs
// to be in the callback too
doc.save('test.pdf');
}
chartImg.src = ...
You've a race condition otherwise and I imagine you just happen to get away with it with the Chrome browser on your PC.
Here's your fiddle fixed.
I'm trying to use the following sticky div script which works fine in all browsers except chrome. I've tested it so far on IE6, 7, 8 and Firefox.
Has anybody a brief idea on why?
below is the JavaScript code...
var offsetfromedge=0 //offset from window edge when content is "docked". Change if desired.
var dockarray=new Array() //array to cache dockit instances
var dkclear=new Array() //array to cache corresponding clearinterval pointers
function dockit(el, duration){
this.source=document.all? document.all[el] : document.getElementById(el);
this.source.height=this.source.offsetHeight;
this.docheight=truebody().clientHeight;
this.duration=duration;
this.pagetop=0;
this.elementoffset=this.getOffsetY();
dockarray[dockarray.length]=this;
var pointer=eval(dockarray.length-1);
var dynexpress='dkclear['+pointer+']=setInterval("dockornot(dockarray['+pointer+'])",100);';
dynexpress=(this.duration>0)? dynexpress+'setTimeout("clearInterval(dkclear['+pointer+']); dockarray['+pointer+'].source.style.top=0", duration*1000)' : dynexpress;
eval(dynexpress);
}
dockit.prototype.getOffsetY=function(){
var totaloffset=parseInt(this.source.offsetTop);
var parentEl=this.source.offsetParent;
while (parentEl!=null){
totaloffset+=parentEl.offsetTop;
parentEl=parentEl.offsetParent;
}
return totaloffset;
}
function dockornot(obj){
obj.pagetop=truebody().scrollTop;
if (obj.pagetop>obj.elementoffset) //detect upper offset
obj.source.style.top=obj.pagetop-obj.elementoffset+offsetfromedge+"px";
else if (obj.pagetop+obj.docheight<obj.elementoffset+parseInt(obj.source.height)) //lower offset
obj.source.style.top=obj.pagetop+obj.docheight-obj.source.height-obj.elementoffset-offsetfromedge+"px";
else
obj.source.style.top=0;
}
function truebody(){
return (document.compatMode && document.compatMode!="BackCompat")? document.documentElement : document.body
}
and I'm using the following tutorial
http://www.dynamicdrive.com/dynamicindex17/dockcontent.htm
try to put the JS code at the end of the html file and not into the head tags, sometimes browser load the pages sequentially and sometimes that makes some error
i'm using phantom js to screen shot a page
http://code.google.com/p/phantomjs/wiki/QuickStart#Rendering
it has a feature called clipRect
http://code.google.com/p/phantomjs/wiki/Interface#clipRect_(object)
can someone show me how i would modify the following code to us clipRect so i only get a partial screenshot and not the whole thing?
if (phantom.state.length === 0) {
if (phantom.args.length !== 2) {
console.log('Usage: rasterize.js URL filename');
phantom.exit();
} else {
var address = phantom.args[0];
phantom.state = 'rasterize';
phantom.viewportSize = { width: 600, height: 600 };
phantom.open(address);
}
} else {
var output = phantom.args[1];
phantom.sleep(200);
phantom.render(output);
phantom.exit();
}
If you are trying to get a screenshot of a particular element, you could get the necessary information for clipRect from getBoundingClientRect as per the bottom of this article:
page.clipRect = page.evaluate(function() {
return document.getElementById(THE_ELEMENT_YOU_WANT).getBoundingClientRect();
});
From the fine manual:
clipRect (object)
This property defines the rectangular area of the web page to be rasterized when render() is invoked. If no clipping rectangle is set, render() will process the entire web page.
Example: phantom.clipRect = { top: 14, left: 3, width: 400, height: 300 }
So try setting clipRect right before you call render:
var output = phantom.args[1];
phantom.sleep(200);
phantom.clipRect = { top: 14, left: 3, width: 400, height: 300 }
phantom.render(output);
phantom.exit();
You'd have to figure out where the upper left corner (top and left) is and how big (width and height) you want the clipping rectangle to be.
You can probably set the clipRect any time before render() is called but start with that and see what happens.
What was happening is i was using brew and it was installing v 1.0.0 where clipRect and almost every other function wasn't supported as v 1.0.0 is the oldest version.
If you follow these instructions: http://code.google.com/p/phantomjs/wiki/BuildInstructions#Mac_OS_X
then right click on the complied file and click show/view contents (on mac) then copy the executable bin/phantomjs.app/Contents/MacOS/phantomjs to some directory in your PATH.
Feel free to post on here i'm monitoring this and i can help if needed.