yWorks / svg2pdf.js

A javascript-only SVG to PDF conversion utility that runs in the browser. Brought to you by yWorks - the diagramming experts
MIT License
643 stars 96 forks source link

Excess clipping of nested SVG #244

Closed edemaine closed 11 months ago

edemaine commented 1 year ago

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):

yGuy commented 1 year ago

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.

edemaine commented 1 year ago

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 1 year ago

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?

edemaine commented 1 year ago

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 1 year ago

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?

HackbrettXXX commented 11 months ago

I dug into this and it turns out:

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:

HackbrettXXX commented 11 months ago

Similar issue as #95.

edemaine commented 11 months ago

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

Closing in favor of https://github.com/parallax/jsPDF/issues/3627