xxyxyz / flat

Generative infrastructure for Python
MIT License
125 stars 14 forks source link

add viewBox attribute to svg serializer #10

Closed aparrish closed 4 years ago

aparrish commented 4 years ago

As I understand the SVG standard, units in SVGs without viewBox attributes are interpreted as pixels. Flat exports SVGs without a viewBox attribute, which leads to unexpected behavior when using these SVGs with downstream tools. (For example, AxiDraw's Python library draws Flat-produced SVGs at three-quarter size, because it interprets Flat's units as pixels and defaults to rendering them at 96dpi.)

This pull request adds a viewBox attribute to all SVGs produced with Flat. The min-x and min-y values of the viewBox are both zero, and the width and the height viewBox are set to the width and the height of the Flat document. This helps ensure that downstream SVG implementations understand what the units inside of the svg tag are actually intended to mean!

sukop commented 4 years ago

Very good catch, thank you!

However, I don't understand that "unexpected" as it should always be 3/4 of size. Here's my thinking: <svg width="75pt" height="75pt"> = <svg width="100px" height="100px"> = <svg width="100" height="100"> = <svg width="100" height="100" viewBox="0 0 100 100">. But we want <svg width="100" height="100" viewBox="0 0 75 75">, i.e. <svg width="75pt" height="75pt" viewBox="0 0 75 75">. Perhaps a tool was ignoring that "pt" unit?

Also, could you please change the order of attributes to "width", "height", "viewBox"? I will then merge it right away.

I mean:

b'width="%spt" height="%spt" '
b'viewBox="0 0 %s %s">\n'

instead of

b'viewBox="0 0 %s %s" '
b'width="%spt" height="%spt">\n'
aparrish commented 4 years ago

Attribute order switched as requested!

I called the behavior "unexpected" because on a document whose units are set to (e.g.) in, I would expect (e.g.) .rectangle(0, 0, 5, 5) to draw a square 5" on each side. But according to the SVG standard, units inside an <svg> element without a viewBox must be interpreted as pixels, even if a unit of measurement is specified in the <svg>'s width and height element. Flat uses pts internally for everything (72pt = 1in), so a 5" rectangle would have a width of 360 in the generated SVG source code. Because of the missing viewBox, AxiDraw interprets this as 360 pixels, which it renders (somewhat arbitrarily) at 96dpi, and ends up drawing a 3.75" (= 5 0.75) square. (The 3/4 size thing results from the fact that (1/96) 72 = 0.75.)

I just tried this out in Inkscape and can verify that it interprets SVGs without a viewBox the same way that AxiDraw does. (In Inkscape, the document has the right size but clicking on any Flat-generated shape shows the units in px.)

sukop commented 4 years ago

Thank you again!

sukop commented 4 years ago

Not that it matters, because the end-result would be the same, but I believe the mechanism is slightly different. Every dimension without a unit is treated as "user unit" and viewBox establishes the transformation from absolute dimensions of outermost svg to these "unitless" values. If there is no viewBox, both initial viewport and user coordinate systems have to be identical, meaning an implicit viewBox is set up using CSS ratios from pt to px (1/72in, 1/96in). Which in turn means that viewBox is (always) 3/4 smaller (i.e. more "zoom out") because it is in px, and not in pt, but all the "unitless" values were meant to be in pt.