In a web application I'm using JSPDF to convert the html to pdf. All works fine, except for the images. After a while, I noticed that it adds images that point to a local resource; instead, it does not add images that point to an online resource, and leaves in the place of the image an empty space, as if he expected it but could not load it.
For example: <img src="img/house.jpg"/> is correctly added.
<img src="https://myurl.com/house.jpg"/> is not correctly added; there is an empty space instead of the image.
How can I solve it? Maybe store the image temporarily in local? I tried using addImage() but it is very hard to use, not only because I change the scale factor of pdf, but primarily because the content of the pdf is dynamic, and I do not know what size the images will have or their exact position.
You need to make sure the image(s) is/are loaded before addIMage(). The following code is what I used to convert multiple images online to a PDF file. It will rotate the image(s) based on the orientation of the images/page and set proper margin. Note that this code is for image only, not the Html page with embedded image(s), but the concept of img.onload remains the same.
As for image rotation, if you see a blank page after rotation, it could simply be that the image is out of bounds. See this answer for details.
function exportPdf(urls) {
let pdf = new jsPDF('l', 'mm', 'a4');
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const pageRatio = pageWidth / pageHeight;
for (let i = 0; i < urls.length; i++) {
let img = new Image();
img.src = urls[i];
img.onload = function () {
const imgWidth = this.width;
const imgHeight = this.height;
const imgRatio = imgWidth / imgHeight;
if (i > 0) { pdf.addPage(); }
pdf.setPage(i + 1);
if (imgRatio >= 1) {
const wc = imgWidth / pageWidth;
if (imgRatio >= pageRatio) {
pdf.addImage(img, 'JPEG', 0, (pageHeight - imgHeight / wc) / 2, pageWidth, imgHeight / wc, null, 'NONE');
}
else {
const pi = pageRatio / imgRatio;
pdf.addImage(img, 'JPEG', (pageWidth - pageWidth / pi) / 2, 0, pageWidth / pi, (imgHeight / pi) / wc, null, 'NONE');
}
}
else {
const wc = imgWidth / pageHeight;
if (1 / imgRatio > pageRatio) {
const ip = (1 / imgRatio) / pageRatio;
const margin = (pageHeight - ((imgHeight / ip) / wc)) / 4;
pdf.addImage(img, 'JPEG', (pageWidth - (imgHeight / ip) / wc) / 2, -(((imgHeight / ip) / wc) + margin), pageHeight / ip, (imgHeight / ip) / wc, null, 'NONE', -90);
}
else {
pdf.addImage(img, 'JPEG', (pageWidth - imgHeight / wc) / 2, -(imgHeight / wc), pageHeight, imgHeight / wc, null, 'NONE', -90);
}
}
if (i == urls.length - 1) {
pdf.save('Photo.pdf');
}
}
}
}
If this is a bit hard to follow, you can also use .addPage([imgWidth, imgHeight]), which is more straightforward. The downside of this method is that the first page is fixed by new jsPDF(). See this answer for details. You can use window.open(pdf.output('bloburl')) to debug.
Related
I'm using canvas for this animation. The animation is working fine on localhost but on the live servers it's taking too much time.
This is because I'm using almost 3000 frames for this animation, all frames are important. How can I increase the loading speed on the live server?
I have attached the code. Please review it and help me if I'm wrong somewhere.
const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");
const frameCount = 2999;
const currentFrame = index => (`compressed/${index.toString().padStart(9, '720_0000')}.jpg`)
const preloadImages = () => {
for (let i = 1; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
}
};
const img = new Image()
img.src = currentFrame(1);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
img.onload = function() {
scaleToFill(this);
}
function scaleToFill(img) {
var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
var x = (canvas.width / 2) - (img.width / 2) * scale;
var y = (canvas.height / 2) - (img.height / 2) * scale;
context.drawImage(img, x, y, img.width * scale, img.height * scale);
}
const updateImage = index => {
img.src = currentFrame(index);
context.drawImage(img, 0, 0);
}
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
preloadImages()
<canvas id="hero-lightpass"></canvas>
There might be some edge cases where you still need individual images, and the suggestions in the comments (using video or Sprite sheets) will not do, if you can use one of those option you should, it will simplify a lot...
I'm going to focus on that edge case where we have a ton of images
Great examples how to do things like that are online maps:
Google maps (https://www.google.com/maps)
OpenStreetMap (https://www.openstreetmap.org/)
Those maps have a lot of images, but they do not download all at once, they download and draw only what is needed, in your case you can preload maybe 50 (you have to experiment to see what best) and download the 51, 52 ... as you are drawing the first you have preloaded.
Back to the maps; They use "small" map tiles to paint one big mosaic, here are a couple of tiles:
https://a.tile.openstreetmap.org/6/16/25.png
https://b.tile.openstreetmap.org/6/15/26.png
You can see they are coming from different servers a & b that is to speed up download, browsers have limits on how many images are downloaded from one server at a time, for more details see:
http://kb.mozillazine.org/Network.http.max-connections-per-server
In a nutshell:
Download and draw only what is needed
Use multiple servers to host your images
I am using html2canvas and jsPDF to export my page to PDF document. As the page has long content I am using this code to create it:
const onExportClick = (e) => {
e.preventDefault();
const quotes = document.body;
html2canvas(quotes).then(
(canvas) => {
//! MAKE YOUR PDF
const pdf = new jsPDF("p", "pt", "A4");
for (let i = 0; i <= quotes.clientHeight / 980; i++) {
//! This is all just html2canvas stuff
const srcImg = canvas;
const sX = 0;
const sY = 980 * i; // start 980 pixels down for every new page
const sWidth = 900;
const sHeight = 980;
const dX = 0;
const dY = 0;
const dWidth = 900;
const dHeight = 980;
const onePageCanvas = document.createElement("canvas");
onePageCanvas.setAttribute("width", 900);
onePageCanvas.setAttribute("height", 980);
const ctx = onePageCanvas.getContext("2d");
// details on this usage of this function:
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images#Slicing
ctx.drawImage(
srcImg,
sX,
sY,
sWidth,
sHeight,
dX,
dY,
dWidth,
dHeight
);
// document.body.appendChild(canvas);
const canvasDataURL = onePageCanvas.toDataURL("image/png", 1.0);
const width = onePageCanvas.width;
const height = onePageCanvas.clientHeight;
//! If we're on anything other than the first page,
// add another page
if (i > 0) {
pdf.addPage(612, 791); //8.5" x 11" in pts (in*72)
}
//! now we declare that we're working on that page
pdf.setPage(i + 1);
//! now we add content to that page!
pdf.addImage(canvasDataURL, "PNG", 0, 0, width, height);
}
//! after the for loop is finished running, we save the pdf.
pdf.save("Test.pdf");
}
);
};
The problem is that document looks like this (for example first 2 pages from PDF):
In the website it is like this (stuff from first 2 pages from PDF):
So basically it's kind of zoomed too much. How can I make it fit to PDF page?
You have initialized the jsPDF to use 'pt' as default
const pdf = new jsPDF("p", "pt", "A4");
But the image that you get is in pixel. And the below line of code treats the width and heights as pt(points)
pdf.addImage(canvasDataURL, "PNG", 0, 0, width, height);
1 Point = 1.333333 Pixel
1 px = 0.75 point
Hopefully this should do the trick:
pdf.addImage(canvasDataURL, "PNG", 0, 0, width * 0.75, height * 0.75);
So in your case the image is getting bigger. What you have to do is just multiply your width/height( which is in pixel ) by 0.75 to get the dimension in points.
You should also factor in the additional margins that you give around the image and make sure to subtract the same and convert that number to pixel else the image might look cropped.
API Reference:
https://artskydj.github.io/jsPDF/docs/module-addImage.html#~addImage
Parameters:
width number
width of the image (in units declared at inception of PDF document)
height number
height of the Image (in units declared at inception of PDF document)
This is what I feel is the problem. Maybe you can give it a try. Hope it helps!
I want to draw a serialized DOM element in the canvas. It works perfect on
Chrome, Firefox, Safari and Opera but i have problems on Edge (and IE11 (this not crucial)).
This is how I handle the canvas drawing:
this._$svgElement = this._props._svgContainer.find('svg');
this._initSVGWidth = this._$svgElement.width();
this._initSVGHeight = this._$svgElement.height();
var svgURL = new XMLSerializer().serializeToString(this._$svgElement[0]);
var svgString = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgURL);
var newImageWidth, newImageHeight;
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
this._img = new Image();
this._img.onload = function () {
if (bowser.msie) {
newImageHeight = (this._initSVGHeight / this._initSVGWidth) * this._canvas.width;
newImageWidth = this._canvas.width;
if (newImageHeight >= this._canvas.height) {
newImageHeight = this._canvas.height;
newImageWidth = (this._initSVGWidth / this._initSVGHeight) * this._canvas.height;
}
} else {
newImageHeight = this._img.height / this._img.width * this._canvas.width;
newImageWidth = this._canvas.width;
if (newImageHeight > this._canvas.height) {
newImageHeight = this._canvas.height;
newImageWidth = this._img.width / this._img.height * this._canvas.height;
}
}
//original image values (never changed)
this._imgData = {
x: (this._canvas.width / 2 - newImageWidth / 2),
y: (this._canvas.height / 2 - newImageHeight / 2),
w: newImageWidth,
h: newImageHeight
};
this._ctx.drawImage(this._img, this._imgData.x, this._imgData.y, newImageWidth, newImageHeight);
}.bind(this);
this._img.src = svgString;
The result is a strange offset on the microsoft browsers:
Edge browser (wrong)
For Example: Chrome Browser (how it should be)
This is the SVG Test Source: https://s.cdpn.io/3/kiwi.svg
I don't know what the issue is here. Because even if I try to draw it with drawImage(this._img, 0, 0) the resulting image is cut off.
I'm thankful for any suggestions because we are out of any ideas.
I have the following JavaScript code, that when called should render an image and a subtitle into an HTML5 canvas:
var SplashScreen = function SplashScreen(imagePath, subtitle, callback) {
var up = false;
this.step = function (dt) {
if (!Game.keys['fire']) up = true;
if (up && Game.keys['fire'] && callback) callback();
};
this.draw = function (ctx) {
ctx.fillStyle = "#FFFFFF";
splashImage = new Image();
splashImage.src = imagePath;
splashImage.onload = function () {
ctx.drawImage(splashImage, Game.width / 2 - 420 / 2, Game.height / 2 - 100, 420, 263);
}
ctx.font = "bold 20px 'SF Collegiate'";
var measure2 = ctx.measureText(subtitle);
ctx.fillText(subtitle, Game.width / 2 - measure2.width / 2, Game.height / 2 + 200);
};
};
The "subtitle" renders fine. But the "splashImage" does not. I see that the image is successfully loaded by looking at the Network tab in Chrome. So it appears that the image is found but never renders.
Any ideas on where my syntax is wrong?
UPDATE: Must be something in the rest of the code ...
If I change:
splashImage.onload = function () {
ctx.drawImage(splashImage, Game.width / 2 - 420 / 2, Game.height / 2 - 100, 420, 263);
}
to
ctx.drawImage(splashImage, Game.width / 2 - 420 / 2, Game.height / 2 - 100, 420, 263);
it works a-okay! Odd...
If adding the width and height parameters fixed the problem, it could be that your image does not have the size you expected, or perhaps it does not even have an intrinsic size, as is sometimes the case with SVG files. If that is not what is going on, it could be a bug in the browser. Please file a bug if that is the case. You can submit bugs against Chrome here: crbug.com/newissue
I have a table(columns aligned differently) which is to be saved as pdf.I converted the table to image using html2canvas and then saved the image into pdf usng jspdf. It works well if the size of the image is less than or equal to page size of pdf but if the image size is bigger than the page size then it saves only first page of pdf(which has only a part of the image) and rest of the image is not displayed/saved in pdf. here the javascript code I used.
$("#btnVC_saveGLSummary").click(function () {
html2canvas($("#tblSaveAsPdf1"), {
onrendered: function (canvas) {
var myImage = canvas.toDataURL("image/jpeg");
var d = new Date().toISOString().slice(0, 19).replace(/-/g, "");
filename = 'report_' + d + '.pdf';
var doc = new jsPDF();
doc.addImage(myImage, 'JPEG', 12, 10);
doc.save(filename);
}
});
});
Any ideas how to get the remaining part of the image on the second page of pdf.
It works for html2canvas and jspdf.
var imgData = canvas.toDataURL('image/png');
var imgWidth = 210;
var pageHeight = 295;
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var doc = new jsPDF('p', 'mm');
var position = 10; // give some top padding to first page
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position += heightLeft - imgHeight; // top padding for other pages
doc.addPage();
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.save( 'file.pdf');
I use this method
function makePDF(){
var quotes = document.getElementById('container-fluid');
html2canvas(quotes, {
onrendered: function(canvas) {
//! MAKE YOUR PDF
var pdf = new jsPDF('p', 'pt', 'a4');
for (var i = 0; i <= quotes.clientHeight/980; i++) {
//! This is all just html2canvas stuff
var srcImg = canvas;
var sX = 0;
var sY = 1120*i; // start 980 pixels down for every new page
var sWidth = 778;
var sHeight = 1120;
var dX = 0;
var dY = 0;
var dWidth = 778;
var dHeight = 1120;
window.onePageCanvas = document.createElement("canvas");
onePageCanvas.setAttribute('width', 778);
onePageCanvas.setAttribute('height', 1120);
var ctx = onePageCanvas.getContext('2d');
// details on this usage of this function:
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images#Slicing
ctx.drawImage(srcImg,sX,sY,sWidth,sHeight,dX,dY,dWidth,dHeight);
// document.body.appendChild(canvas);
var canvasDataURL = onePageCanvas.toDataURL("image/png", 1.0);
var width = onePageCanvas.width;
var height = onePageCanvas.clientHeight;
//! If we're on anything other than the first page,
// add another page
if (i > 0) {
pdf.addPage(595, 842); //8.5" x 11" in pts (in*72)
}
//! now we declare that we're working on that page
pdf.setPage(i+1);
//! now we add content to that page!
pdf.addImage(canvasDataURL, 'PNG', 0, 0, (width*.72), (height*.71));
}
//! after the for loop is finished running, we save the pdf.
pdf.save('Test3.pdf');
}
});
}
I hope it will be helpful
Inspired by another stack overflow response
This was answered further up in a similar way, but I felt I wanted to give an example leaving out unnecessary code:
You can easily run through all pages and put 'watermark' texts or other items on every page like this:
let pdf = new jspdf();
// do something that creates multiple pages
for (let pageNumber = 1; pageNumber <= pdf.getNumberOfPages(); pageNumber++) {
pdf.setPage(pageNumber)
pdf.addImage('myImgOnEveryPage.png', 10,10,20,20);
pdf.text('My text on every page, 12, 12);
}
(JSPDF version 2.1.1)
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 1025, sectionWidth, 1250, 0, 0, 1800, 950);
var image2 = new Image();
image2 = Canvas2Image.convertToJPEG(canvas);
image2Data = image2.src;
doc.addImage(image2Data, 'JPEG', -105, 5, 440, 325);
Basically, you get the context of your canvas and then you can use the drawImage function to create a new Image that is a portion of your original canvas. The parameters of drawImage are:
drawImage(img, startX, startY, originalW, originalH, destX, dextY, destW, destH);
Where startX and Y are your starting locations for the clipping on the original images, original W and H are the height and width of your clipping (on the original picture), basically how much to clip, destX and Y are where on your PDF to put the new clipping and destH and W define how big the clipping is when placed on the canvas (they stretch the image once you've clipped it)Hope this helped, Cheers
You can easily do this by adding small tweak in your own code.
$("#btnVC_saveGLSummary").click(function () {
html2canvas($("#tblSaveAsPdf1"), {
onrendered: function (canvas) {
var myImage = canvas.toDataURL("image/jpeg");
var d = new Date().toISOString().slice(0, 19).replace(/-/g, "");
filename = 'report_' + d + '.pdf';
var doc = new jsPDF();
var totalPages = doc.internal.getNumberOfPages();
for(let i = 1; i < totalPages; i++) {
doc.setPage(i);
doc.addImage(myImage, 'JPEG', 12, 10);
}
doc.save(filename);
}
});
});