parallax / jsPDF

Client-side JavaScript PDF generation for everyone.
https://parall.ax/products/jspdf
MIT License
29k stars 4.64k forks source link

addImage PNG fails >= 2.4.0 #3359

Open cobbrg opened 2 years ago

cobbrg commented 2 years ago

The following code exports a good PNG image to PDF in jsPDF version 2.3.1 and below. In version 2.4.0 and 2.5.0, the image is not displayed and Acrobat Reader reports "An error exists on this page". The image is displayed with a black background when viewed in the Edge PDF viewer (previously transparent).

where imageCanvas is an HTMLCanvasElement and pdf is a jsPDF

let img = imageCanvas.toDataURL("image/png", 1.0);
pdf.addImage(img, 'PNG', 20, 20, 560, 300);
pdf.save('testPNG.pdf');

JPEG and WEBP show an image when using 2.4.0 and 2.5.0, but the JPEG background is black and the WEBP image looks like an 8-bit conversion.

HackbrettXXX commented 2 years ago

Cannot reproduce. This works:

const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);

const pdf = new jsPDF({ unit: "pt", format: [200, 200] });
const img = canvas.toDataURL("image/png", 1.0);
pdf.addImage(img, "PNG", 20, 20, 100, 100);
pdf.save("testPNG.pdf");
cobbrg commented 2 years ago

Reproduced using the test code to display a red square. Works with 2.3.1, but not 2.4.0. PDF reader used is Adobe Acrobat DC 2021.011.20039. This is an Angular 7 project. The only package.json difference between the working and non-working projects is "jspdf": "^2.3.1" vs. "jspdf": "2.4.0". The only yarn.lock difference is the additional dependency on "@babel/runtime" "^7.14.0".

package.json and yarn.lock files attached. Thanks for investigating. package.json.txt yarn.lock.txt

HackbrettXXX commented 2 years ago

I've tested it with 2.4.0 and 2.5.0 and Adobe Acrobat DC 2021.011.20039. I can't reproduce it. I didn't test with Angular, although I think that shouldn't make a difference. Please provide a complete project where the bug is reproducible.

cobbrg commented 2 years ago

Sample project that reproduces the issue is attached. One project is configured to use jsPDF 2.4.0 in package.json. A PDF created by this project and opened in Adobe Acrobat displays "An error exists on this page. Acrobat may not display the page correctly. Please contact the person who created the PDF document to correct the problem." The other project changes the jsPDF version in package.json to 2.3.1 and the red square is displayed with no error in Adobe Acrobat. Thanks.

Edited to include separate builds for each version of jsPDF: jsPDFTest_2.3.1.zip jsPDFTest_2.4.0.zip

makeitraina commented 2 years ago

I also reproduced this issue using v2.5.1. I used a png url to verify that the issue was not with how I was using canvas.

const pngImg = new Image();
pngImg.src =
  'https://raw.githubusercontent.com/YourUserAccount/YourProject/master/DirectoryPath/Example.png?raw=true';
pdf.addImage(
  pngImg,
  'png',
  0,
  0,
  pngImg.width,
  pngImg.height,
);
pdf.save('pngImage.pdf')
gregoriogerardi-reflektion commented 2 years ago

I am facing this same issue with jspdf 2.4.0 and a canvas generated from html2canvas 1.3.3. It is only failing when opening the resulting pdf with Adobe Acrobat in both windows and mac os. If I open the pdf with any other viewer (like the chrome browser) it works fine

TiBeN commented 2 years ago

Not sure it is related but it seems jsPDF.addImage does not wait for image to be loaded. There is a related issue: https://github.com/parallax/jsPDF/issues/3376

I was able to workaround this by waiting Image.onload() before calling jsPDF.addImage()

Hope it'll help.

skiano commented 2 years ago

For me, it works when I call the save method, but if I try to output the document to a datauristring it fails when I make the canvas larger.

skiano commented 2 years ago

I'm not sure if this will help anyone else... but I was able to work around my version of this issue by:

  1. converting the canvas to a blob instead of a dataUrl using
  2. converting the blob to a url
  3. using that url in the pdf

It looks something like this

const doc = new jsPDF({});

canvas.toBlob((blob) => {
  const url = URL.createObjectURL(blob);
  doc.addImage(url, "PNG", 20, 20, 100, 100);
}, 'image/png');
felipecmonteiro commented 11 months ago

I'm not sure if this will help anyone else... but I was able to work around my version of this issue by:

  1. converting the canvas to a blob instead of a dataUrl using
  2. converting the blob to a url
  3. using that url in the pdf

It looks something like this

const doc = new jsPDF({});

canvas.toBlob((blob) => {
  const url = URL.createObjectURL(blob);
  doc.addImage(url, "PNG", 20, 20, 100, 100);
}, 'image/png');

This helped me, thanks! My implementation (using html2canvas and your code snippet) is below:

    html2canvas(element, {
      width: element.clientWidth,
      height: element.clientHeight,
      useCORS: true,
      allowTaint: false
    }).then((canvas: HTMLCanvasElement) => {
      let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
      let outputContext: CanvasRenderingContext2D = outputCanvas.getContext('2d');
      outputCanvas.width = this.width;
      outputCanvas.height = this.height;

      canvas.toBlob((blob) => {
        let imageObj = new Image();
        imageObj.width = this.width;
        imageObj.height = this.height;
        imageObj.style.objectFit = 'cover';
        imageObj.src = URL.createObjectURL(blob);
        imageObj.onload = () => {
          outputContext.drawImage(imageObj, 0, 0, this.width, this.height);
          if (callback) {
            callback(null, outputCanvas.toDataURL().replace('data:image/png;base64,', ''));
          }
        };
        imageObj.onerror = (error) => {
          callback(error);
        };
      });

I'm using:

Finally, I observed the black background issue even with this approach if I called outputContext.scale() prior to outputContext.drawImage().