yWorks/svg2pdf.js

Excess clipping of nested SVG

edemaine opened this issue · 8 comments

Describe the bug
Rendering this SVG with a nested <svg> tag ends up with a weird clipping box.

To Reproduce
Put the following into a .html file, then open in a web browser. (Warning: It will immediately trigger a download of file test.pdf)

<html>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="148.26817321777344 216.94358825683594 19.859024047851562 24.615386962890625" width="19.859024047851562px" height="24.615386962890625px">
<g transform="translate(147.1999969482422,236.63589477539062)"><g transform="translate(0.5961542100601207 -9.72823342590332) scale(19)"><svg width="1.038678" height="0.536075" viewBox="0 -442 878 453"><defs><path id="MJX-1-TEX-I-1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"/></defs><g transform="scale(1,-1)"><use xlink:href="#MJX-1-TEX-I-1D45A"/></g></svg></g></g>
</svg>
<script type="module">
const {jsPDF} = await import("https://cdn.skypack.dev/pin/jspdf@v2.5.0-qOUBjLwjpjtUHvljumfc/dist=es2020,mode=imports,min/optimized/jspdf.js")
const {svg2pdf} = await import("https://cdn.skypack.dev/pin/svg2pdf.js@v2.2.1-E39EA3q6JjbAU0rSktv0/mode=imports,min/optimized/svg2pdfjs.js")
const width = 19.859024047851562
const height = 24.615386962890625
const pdf = new jsPDF({format: [width, height], orientation: 'portrait'})
await svg2pdf(document.querySelector('svg'), pdf, {width, height})
pdf.save('test.pdf')
</script>
</html>

Expected behavior

The example works in the playground. It looks like this:

image

Is there something not yet released to NPM?

Screenshots
Instead the path is mostly clipped. Here are some Illustrator screenshots:

image

image

I'm not really sure where that rectangular bounding box is coming from. Perhaps a transform issue?

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser: Chrome
  • Version: 108.0.5359.100
yGuy commented

With "works in the playground", are you saying that the PDF is displayed properly in the browser, or that downloading the PDF and then opening it in illustrator also differs from your local test results?

What happens if you open the PDF in other PDF renderers (Acrobat, Firefox, etc.)? It seems the implementations do not fully agree on these details.

I did not see any discrepancy between Chrome, Illustrator, or Acrobat in this test case. The playground PDF works everywhere, while the buggy PDF works nowhere.

I was showing the Illustrator view because it makes it clear how things are getting clipped.

yGuy commented

The playground PDF works everywhere, while the buggy PDF works nowhere.

Sorry, I cannot reproduce, for me the PDF viewer in the playground shows the pdf, properly, this is in the current version of Edge. Also opening the PDF from the playground in Edge, again, properly shows the PDF. So the PDF viewers seem to disagree, but I take it at least the Adobe tools show the cropping behavior? What version of Chrome are you seeing the clipping behavior in?

The example works fine in the playground; I agree. What doesn't work is if you use the reproduction above with the .html file.

yGuy commented

Interesting. The playground (for some reason, that's not ideal, actually) uses version 2.0.0 of JsPDF which is a lot older than the version you are using. Maybe this is a regression? The svg2pdf version is a slightly newer, unreleased version, but should only differ in third party dependencies during build time.

Could you try your sampel with 2.0.0 and then maybe bisecting the versions to find the version that changes the behavior?

I dug into this and it turns out:

  • the example works in the playground and does not with the provided JS code, because the playground passes unit: 'pt' to the jsPDF constructor.
  • Passing 'pt' as unit has the effect that jsPDF's internal scaling factor is 1. If the option is omitted, the default unit is 'mm', resulting in a scaling factor of 72 / 25.4. As a consequence, the bounding box of the PDF form object (created by the <use>-tag) is wrong. This is definitively a bug in jsPDF:

https://github.com/parallax/jsPDF/blob/5d09af9135a2fe049c7d3c8b95df280d22e4a6db/src/jspdf.js#L5957-L5964

called by

https://github.com/parallax/jsPDF/blob/5d09af9135a2fe049c7d3c8b95df280d22e4a6db/src/jspdf.js#L5707-L5721

called by

https://github.com/parallax/jsPDF/blob/5d09af9135a2fe049c7d3c8b95df280d22e4a6db/src/jspdf.js#L5751-L5771

The width and height of the passed bounding box are scaled by the inverse scaling factor and x and y are not.
However, the correct behavior should be that x, y, width, height should all be scaled by the normal (not inverse) scaling factor, so the values passed to beginFormObject are interpreted with the correct unit.

To summarize:

  • Pass unit: 'pt' to the jsPDF constructor as workaround. This should not make a big difference, if you're not using the jsPDF API for other things as svg2pdf.
  • I've created an issue on jsPDF side: parallax/jsPDF#3627

Similar issue as #95.

Awesome, thanks @HackbrettXXX! Adding unit: 'pt' fixed this issue and #245.

Closing in favor of parallax/jsPDF#3627