svgdotjs / svgdom

Straightforward DOM implementation to make SVG.js run headless on Node.js
MIT License
269 stars 53 forks source link

rbox() for SVGs with dimensions in non-pixel unit returns value in that unit instead of pixels #122

Closed charlie-s closed 5 months ago

charlie-s commented 5 months ago

I am processing some SVGs on the command line with svgdom/svg.js. Some of them use inches in their width/height, others don't define any unit and therefore default to pixels. For example:

<svg xmlns="http://www.w3.org/2000/svg" width="58in" height="71.69in" viewBox="0 0 4176 5161.48" version="1.1">
  <text transform="translate(2906.43 738.05)"><tspan x="0" y="0">TEST</tspan></text>

Calling rbox() on the tspan in the above string returns:

x: 40.52055555555555, y: 10.237678537293961, w: 0.8395182291666643, ...

These values are in inches, even through their translate and x/y values are in pixels. Do you know if this conversion is happening in svgdom, svg.js, or in a dependency? getBoundingClientRect doesn't explicitly say what its returned unit is, but the spec does say it's relative to the viewport (obviously). I can't find a way to determine if the context has "inch" defined somewhere in these cases. I know it's an edge case and I've seen this issue where you commented on something related. Just wondering if you had any ideas, short me of me just looking for the string "in" in the SVG's width attribute and brute forcing it.

You can see a reproduction of this here: https://stackblitz.com/edit/stackblitz-starters-7rensp?file=svgPixels.js

Fuzzyma commented 5 months ago

Svgdom can't really handle units properly. As far as I remember it just handles all numbers as if there is no unit. I chose to go that route because I wasn't sure how units are converted to pixels if you don't have a screen. Because it also depends on the Pixel density of the screen. If you have a retina display their are way more pixels in one inch than on a normal HD display.

For a solution: you could try to do the conversion yourself by looping through all the nodes and replace the inch unit with its px value. At least then you are in charge and know what to expect. However, it's a bit annoying to do that for all the different shapes (hello paths 🙄)

If you find a better solution (or have a good way to convert units) let me know!

// edit: if the inches only pop up as width and height of the svg itself, then all getBoundingClientRect values should also be in inches since the viewport subdivides the svg into its own (non px) units (called the userspace) anyway and you just assume that computers now use inches for their screen and not pixels. However that does not work if any other dimension of a shape is done with a unit. It only works for the one case that units are used for width and height of the root svg

// edit2: clarification: the units used in translate and x, y are not in px. They are done in userspace. So there is no conversion happening anywhere in svgdom. It just treats it as numbers. If you wouldn't put a unit as width and height you would get back the same numbers from getBoundingClientRect. This is possible because the viewbox defines the number of units in the space and not the width and height. Width and height are solely for displaying purposes when the svg is used in another context (e. G. On a web page).

charlie-s commented 5 months ago

Thanks for the feedback and details.

I ended up converting the SVGs to pixels as part of the workflow of getting them into my system. They were coming from Adobe Illustrator, so I was able to write a script for Illustrator that handles the conversion and export. I didn't want to go that route initially, for whatever reason, but now it's very clean and the SVGs I'm working with are all predictable now.

This lib is very helpful, I appreciate your work on it.