this.srivastava
this.srivastava

Reputation: 1061

Unable to save SVG to PDF using jsPDF

I'm trying to download PDF with SVG content using jsPDF library, it is able to download the file, but there is no content inside it, it is empty PDF.

This is my code:

const downloadPDF = (goJSDiagram) => {
  const svg = goJSDiagram.makeSvg({scale: 1, background: "white"});
  const svgStr = new XMLSerializer().serializeToString(svg);
  const pdfDoc = new jsPDF();
  pdfDoc.addSvgAsImage(svgStr, 0, 0, pdfDoc.internal.pageSize.width, pdfDoc.internal.pageSize.height)
  pdfDoc.save(props.model[0].cName?.split(" (")[0] + ".pdf");
}

When I do console.log(svgStr), I can see the SVG XML string. What changes should I make to render the content inside PDF?

Upvotes: 4

Views: 3223

Answers (3)

doc.addSvgAsImage(imgData, 20, 50, 17, 20 );
doc.save('filename');

save() method is executed earlier than doc.addImage(...)

It worked for me when I put doc.save() into small setTimeout(). The other solution would be to convert SVG to JPEG and use addImage() method instead of addSvgAsImage().

Upvotes: 0

this.srivastava
this.srivastava

Reputation: 1061

After lot of searching, I found the right way to do this, though the content rendered is little blurred.

  const waitForImage = imgElem => new Promise(resolve => imgElem.complete ? resolve() : imgElem.onload = imgElem.onerror = resolve);

  const downloadPDF = async (goJSDiagram) => {
    const svg = goJSDiagram.makeSvg({scale: 1, background: "white"});
    const svgStr = new XMLSerializer().serializeToString(svg);
    const img = document.createElement('img');
    img.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);

    waitForImage(img)
      .then(_ => {
        const canvas = document.createElement('canvas');
        canvas.width = 500;
        canvas.height = 500;
        canvas.getContext('2d').drawImage(img, 0, 0, 500, 500);
        const pdfDoc = new jsPDF('p', 'pt', 'a4');
        pdfDoc.addImage(canvas.toDataURL('image/png', 1.0), 0, 200, 500, 500);
        pdfDoc.save(props.model[0].cName?.split(" (")[0] + ".pdf");
      });
  }

Upvotes: 1

oligofren
oligofren

Reputation: 22923

I think I know what is going on after getting a good hint from this Github issue:

There's the issue that addSvgAsImage() is asynchronous

You are not awaiting the call to finish before calling save! That means you are trying to save before the SVG has started rendering to the PDF.

See the quite simple code in question:

  jsPDFAPI.addSvgAsImage = function(
  // ... bla bla
 return loadCanvg()
      .then(
        function(canvg) {
          return canvg.fromString(ctx, svg, options);
        },
        function() {
          return Promise.reject(new Error("Could not load canvg."));
        }
      )
      .then(function(instance) {
        return instance.render(options);
      })
      .then(function() {
        doc.addImage(
          canvas.toDataURL("image/jpeg", 1.0),
          x,
          y,
          w,
          h,
          compression,
          rotation
        );
      });

As you see, it is just a chain of Thenables. So you simply need to await the Promise, which means your code would look something like this in ES2015+:

const downloadPDF = async (goJSDiagram) => {
  const svg = goJSDiagram.makeSvg({scale: 1, background: "white"});
  const svgStr = new XMLSerializer().serializeToString(svg);
  const pdfDoc = new jsPDF();
  await pdfDoc.addSvgAsImage(svgStr, 0, 0, pdfDoc.internal.pageSize.width, pdfDoc.internal.pageSize.height)
  pdfDoc.save(props.model[0].cName?.split(" (")[0] + ".pdf");
}

Upvotes: 4

Related Questions