I'm using jsPdf to generate a PDF from an HTML output in react. The HTML contains base64 images taken by mobile devices. In the HTML they are displayed correctly, but in the PDF they are rotated in various directions. I've tried manually adding the images using jsPdf functions but the flow of page is incorrect.
I've also tried getting the orientation using EXIF, which is actually correct, but it seems like the transform: rotate() css is not supported.
Is there a way to force orientation when using jsPdf/html2canvas?
Are there alternative libraries I can use to solve?
report.html(contentRef.current, {
callback: function (doc) {
doc.setFontSize(8);
addGeneratedBy(doc);
addFooters(doc);
report.save(fileName);
setGenerating(false);
},
html2canvas: {
onclone: function (doc, element) {
let images = doc.querySelectorAll("img");
let targetRect = contentRef.current.getBoundingClientRect();
// 1: Normal (0° rotation)
// 3: Upside-down (180° rotation)
// 6: Rotated 90° counterclockwise (270° clockwise)
// 8: Rotated 90° clockwise (270° counterclockwise)
images.forEach((img) => {
let rect = img.getBoundingClientRect();
EXIF.getData(img, function () {
const orientation = EXIF.getTag(this, "Orientation");
console.log(orientation);
let deg = 0;
orientation == 3 && (deg = 180);
orientation == 6 && (deg = 270);
orientation == 8 && (deg = 90);
img.style.transform = `rotate(${deg}deg)`
//report.addImage(img, "PNG", 200, rect.y - targetRect.y, 100, rect.width, undefined, undefined, deg);
//img.remove();
});
// img.remove();
});
},
onrendered: function (canvas) {
}
},
margin: [95, 30, 30, 30],
autoPaging: 'text',
width: 500,
y: 470,
windowWidth: 1200
});
https://artskydj.github.io/jsPDF/docs/module-addImage.html
(inner) addImage(imageData, format, x, y, width, height, alias, compression, rotation)
In your commented attempt when using that strategy I see you used this:
report.addImage(
img, //imageData
"PNG", //format
200, //x
rect.y - targetRect.y, //y
100, //width
rect.width, //height
undefined, //alias
undefined, //compression
deg //rotation
);
Since in the comments you said that the elements generated automatically were not in sync with the image locations, there's the chance you are just dealing with position and size in the wrong way and it's worth trying an accurate approach.
In the end I also added a scalingFactor
to accomodate the size of the pictures. In comments you also said that when using the canvas strategy, the rotation wasn't needed anymore. I didn't expect that! Good to hear.
So please try changing that part with this:
const imgData = img.src; // Get base64 data of the image
const imgWidth = rect.width;
const imgHeight = rect.height;
const posX = rect.x - targetRect.x; // X position relative to the contentRef
const posY = rect.y - targetRect.y; // Y position relative to the contentRef
// Add the image to the PDF
doc.addImage(
imgData, //here I'm using the base64 data instead of the element itself (not needed but worth trying)
"PNG",
posX,
posY,
imgWidth,
imgHeight,
undefined,
"FAST", // Use compression for performance
deg
);
Since as you observed yourself, the strategy above won't take into account the scenario where the content will be rendered on different pages thus messing with absolute coords, you may also try creating a single <canvas>
element for each <img>
found and will replace the original element. Such canvas will embed the original image and will give us the opportunity to rotate it:
html2canvas: {
onclone: function (doc, element) {
const scalingFactor = .75;
let images = doc.querySelectorAll("img");
images.forEach((img) => {
EXIF.getData(img, function () {
const orientation = EXIF.getTag(this, "Orientation");
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
// Use the original image directly
const { width, height } = img;
// Set canvas dimensions based on rotation
if (orientation == 6 || orientation == 8) {
canvas.width = height * scalingFactor;
canvas.height = width * scalingFactor;
} else {
canvas.width = width * scalingFactor;
canvas.height = height * scalingFactor;
}
let deg = 0;
orientation == 3 && (deg = 180);
orientation == 6 && (deg = 270);
orientation == 8 && (deg = 90);
// Translate and rotate the context (in relation to its center and not top-left corner)
// *here you can play to find a better strategy
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate((deg * Math.PI) / 180);
ctx.drawImage(img, -width / 2, -height / 2);
// Replace the image with the canvas
img.parentNode.replaceChild(canvas, img);
});
});
},
}