4

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?

6
  • Are you sure pdfDoc.internal.pageSize.width or ...height is returning a value or this value is > 0 ? You can try at least some fixes values to test it. Commented Nov 17, 2022 at 8:26
  • I tried values like pdfDoc.addSvgAsImage(svgStr, 0, 200, 500, 500); still the same Commented Nov 17, 2022 at 8:29
  • Have you used your step-through debugger to troubleshoot? If not, why not? Commented Nov 17, 2022 at 8:31
  • This has nothing to do with react Commented Nov 17, 2022 at 12:18
  • Yes, doing this in react project, I thought I would add react code, but it is not necessary, thanks! Commented Nov 17, 2022 at 12:35

3 Answers 3

4

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"); } 
Sign up to request clarification or add additional context in comments.

1 Comment

You are right, I need to await the Promise, but the image is not fitting correctly in the pdf even though the width and height changes are made
1

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"); }); } 

2 Comments

If you are rasterizing a vector image to then use it within a format that is a vector format at heart (pdf), then this is hardly the right way, though it might be one way of doing it ;-) The original approach is the right way, but you would need to debug it a bit to figure out why it fails.
After looking at the JSPDF code, they too are rasterizing the SVG, which is a bit of a shame, to be honest. They could have gotten crystal clear vectors as well. Not sure why they went that route. Maybe the simplest/cheapest?
0

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().

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.