Repository: flutter
Issue: [flutter_svg] Specifying `viewBox`, `width`, and `height` leads to incorrect stroke-width and font-size rendering
Labels: package,p: flutter_svg

_Imported from https://github.com/dnfield/flutter_svg/issues/62_

Original report by @SteveAlexander on Sep 10, 2018

Sketch produced this SVG.

It renders in Sketch like this:

![image](https://user-images.githubusercontent.com/4321921/45322107-31ed4300-b53f-11e8-9eff-796f24dc95dd.png)

It renders in Chrome like this:

![image](https://user-images.githubusercontent.com/4321921/45322134-45001300-b53f-11e8-9ed8-3552b10c43ad.png)

It renders in Flutter like this:

![image](https://user-images.githubusercontent.com/4321921/45322146-4fbaa800-b53f-11e8-9cf6-ea7a141350fb.png)

If I go into the SVG code and reduce the element id Filter's stroke-width from 4 to 3, it looks like this:

![image](https://user-images.githubusercontent.com/4321921/45322202-7e388300-b53f-11e8-9012-ad848bf59ab8.png)

... which looks more like the other renderers do. I wonder if somehow flutter_svg is adding one to the stroke width?

```
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 51.2 (57519) - http://www.bohemiancoding.com/sketch -->
    <title>Close</title>
    <desc>Created with Sketch.</desc>

    <defs></defs>
    <g id="03.-Agenda" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
        <g id="Filter" transform="translate(-338.000000, -22.000000)" stroke="#1A1A1A" stroke-width="4">
            <g id="Close" transform="translate(347.000000, 31.000000) rotate(-45.000000) translate(-347.000000, -31.000000) translate(339.000000, 23.000000)">
                <path d="M0,8 L16,8" id="Line-2"></path>
                <path d="M8,0 L8,16" id="Line-2"></path>
            </g>
        </g>
    </g>
</svg>
```

---
Comment by @dnfield on Sep 11, 2018
Not entirely sure why this is happening yet iwth just a quick look, but it does seem off.  Will continue to investigate.

---
Comment by @dnfield on Oct 7, 2018
I haven't spent a ton of time looking at this, but I'm a little puzzled by it.  The width is definitely coming out as 4. I suspect something funny might be going on with the transforms, although translate/rotate shouldn't make it appear bigger.

---
Comment by @dnfield on Oct 7, 2018
It has something to do with the width and height attributes.

The way flutter_svg works is if there's a viewBox, it ignores width and height.  If you take width and height off this SVG, it renders the same as flutter_svg and in Chrome.

Checking spec....

---
Comment by @dnfield on Oct 7, 2018
Relevant part of spec:

> Unlike the ‘transform’ attribute (see effect of the ‘transform’ on sibling attributes), the automatic transformation that is created due to a ‘viewBox’ does not affect the ‘x’, ‘y’, ‘width’ and ‘height’ attributes (or in the case of the ‘marker’ element, the ‘markerWidth’ and ‘markerHeight’ attributes) on the element with the ‘viewBox’ attribute. Thus, in the example above which shows an ‘svg’ element which has attributes ‘width’, ‘height’ and ‘viewBox’, the ‘width’ and ‘height’ attributes represent values in the coordinate system that exists before the ‘viewBox’ transformation is applied. On the other hand, like the ‘transform’ attribute, it does establish a new coordinate system for all other attributes and for descendant elements.

It's not immediately obvious to me how to properly support this.  Some kind of scaling needs to be done, but I'm having trouble grasping the correct formula here (and also whether I've just made it impossible the way I'm (mis)using viewBox/width/height to make things fill the widget size).

I'll have to think about it some more.

---
Comment by @dnfield on Oct 7, 2018
The basic issue seems to be there needs to be some special handling for stroke widths and font sizes in this scenario.

---
Comment by @dnfield on Nov 5, 2018
I think I finally figured this one out.  Hoping to have a fix soon.

---
Comment by @dnfield on Nov 5, 2018
This is now fixed on master (d8e2b82) and I'm contemplating releasing it as v0.7.0, but I'd really like to hear your feedback about whether it's working the way you want it to.

There was a bug in the way I handled width/height previously.  I've corrected that, but it's a breaking change for anyone who was specifying width/height and wanted it to be that way.  My inclination is to go forward with this since it's closer to the spec, but I'm a little concerned about making lots of users upset that their SVGs no longer render correctly.

Let me know what you think.

---
Comment by @SteveAlexander on Nov 22, 2018
Initial feedback:

I have some work to do on my icons, or in my app, in order to accommodate the changes.

For example, the icons below align fine with the released version of flutter_svg, but are off-centre now.

![image](https://user-images.githubusercontent.com/4321921/48911693-57fe6000-ee6b-11e8-90e9-0303393493a1.png)

Next, I'll look into why that might be. Probably some combination of the SVGs we are using, and the way we're using them in our layout.

Personally, and on behalf of my team, we're very happy to make changes when the reason is an overall better system.



---
Comment by @SteveAlexander on Nov 22, 2018
Ok, seems that the area flutter_svg claims for the widget is twice as wide as the SVG wants to be.

```
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 51.2 (57519) - http://www.bohemiancoding.com/sketch -->
    <title>Fill 1</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="02.-Agenda" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="Agenda" transform="translate(-37.000000, -629.000000)" fill="#FFFFFF">
            <g id="Tab-bar" transform="translate(0.000000, 611.000000)">
                <polygon id="Fill-1" points="48 18 37 25.7 37 40 44.5 40 44.5 33.3989 51.5 33.3989 51.5 40 59 40 59 25.7"></polygon>
            </g>
        </g>
    </g>
</svg>
```

![image](https://user-images.githubusercontent.com/4321921/48913618-cc87cd80-ee70-11e8-8b6c-6be91011e6bb.png)


---
Comment by @SteveAlexander on Nov 22, 2018
Here's a thin one and a broader one, for comparison.

![image](https://user-images.githubusercontent.com/4321921/48913644-dd384380-ee70-11e8-80dd-27cc8858faf0.png)

![image](https://user-images.githubusercontent.com/4321921/48913649-e1646100-ee70-11e8-80d5-129ee1359186.png)



---
Comment by @dnfield on Nov 22, 2018
Yes, this is expected now.  I think it conforms to the spec more closely this way, but I'm not sure if it's really better for flutter specifically.

What do you think?

In general you probably don't want to explicitly set a width or height on a svg for use in Flutter.

---
Comment by @SteveAlexander on Nov 22, 2018
I'm not understanding how the width of the widget in flutter is twice as wide as the SVG. Is that what the spec wants?

---
Comment by @dnfield on Nov 22, 2018
If you specify the width of the svg, it's going to be the actual width of the drawing in pixels.  

---
Comment by @SteveAlexander on Nov 22, 2018
the code that renders these is:

```
            return SvgPicture.string(
              snapshot.data,
              color: iconColor,
              colorBlendMode: BlendMode.multiply,
              allowDrawingOutsideViewBox: true
            );
```

I'm not specifying a width or height in flutter.

There is a width and height in the SVG file (as in the earlier comment), but it is pretty much the bounding rectangle for the drawing.

---
Comment by @dnfield on Nov 24, 2018
This feels like a bug to me.  the alignment property isn't doing what it should be in this case.

---
Comment by @dnfield on Nov 24, 2018
I've pushed up some new changes to master that should fix the alignment issues.

I'm getting some more confidence in how this is working at this point, but would again like to hear some feedback from how this looks to you.  I'm still not sure I'm getting the right level of fidelity on your original example (the `x` icon).

---
Comment by @SteveAlexander on Nov 27, 2018
ok, so SVGs now appear centered correctly.

There does seem to be quite a bit of padding around the graphics, which I can't account for in the SVG itself.

The `x` icon is still a bit chunky.

In Flutter:

![image](https://user-images.githubusercontent.com/4321921/49081999-6ddf9c80-f240-11e8-91ab-7b100db367df.png)

 In Chrome:

![image](https://user-images.githubusercontent.com/4321921/49082015-7b952200-f240-11e8-807d-f5cf42d352a4.png)


```
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 51.2 (57519) - http://www.bohemiancoding.com/sketch -->
    <title>Close</title>
    <desc>Created with Sketch.</desc>

    <defs></defs>
    <g id="03.-Agenda" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
        <g id="Filter" transform="translate(-338.000000, -22.000000)" stroke="#1A1A1A" stroke-width="4">
            <g id="Close" transform="translate(347.000000, 31.000000) rotate(-45.000000) translate(-347.000000, -31.000000) translate(339.000000, 23.000000)">
                <path d="M0,8 L16,8" id="Line-2"></path>
                <path d="M8,0 L8,16" id="Line-2"></path>
            </g>
        </g>
    </g>
</svg>
```

---
Comment by @dnfield on Nov 27, 2018
Yes, I haven't quite been able to get that to look just right.  Unfortunately I think you're probably beat off avoiding having width or height explicitly specified.  :(

---
Comment by @SteveAlexander on Nov 27, 2018
Naively, I'd think the viewBox would say what the default clipping rectangle and size of an svg shoudl be.

---
Comment by @GoldenJoe on Dec 10, 2018
Haven't looked at the code, but I can guess the issue based on what I've seen in a few threads: device scale is being applied to the viewbox. The reason SVGs with dimension attributes look half size is because you're trying to draw on a canvas that is twice as large as it should be. Look at this:

https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox

Now the spec you referenced above:

> Unlike the ‘transform’ attribute (see effect of the ‘transform’ on sibling attributes), the automatic transformation that is created due to a ‘viewBox’ does not affect the ‘x’, ‘y’, ‘width’ and ‘height’ attributes (or in the case of the ‘marker’ element, the ‘markerWidth’ and ‘markerHeight’ attributes) on the element with the ‘viewBox’ attribute. Thus, in the example above which shows an ‘svg’ element which has attributes ‘width’, ‘height’ and ‘viewBox’, the ‘width’ and ‘height’ attributes represent values in the coordinate system that exists before the ‘viewBox’ transformation is applied. On the other hand, like the ‘transform’ attribute, it does establish a new coordinate system for all other attributes and for descendant elements.

Think of the viewbox as a picture frame, the dimensions the size of a picture, and the elements as the actual content of the picture. Suppose you have dimensions of (h:100, w:100). If the contents are a simple circle `<circle cx="50%" cy="50%" r="50" fill="black"/>` then you have a circle that fills the 100x100 square working area. AFTER that, you apply the transformation of a ViewBox. `viewBox="0 0 100 100"` Would not change the way it is drawn. The circle would still have a center at (50,50) with a radius of 50. `viewBox="-50 0 100 100"` means _move the viewbox left 50 units_. It would be like moving the picture frame left. Inside the picture frame,  you will see 50 units of blank space, then the circle which now has a global origin of (100, 50) but is otherwise the same. `viewBox="0 0 200 200"` would be like lifting the picture frame up to make the picture look half its size. The circle would now be drawn with an origin of (100,100) but a radius of 25. 

---
Comment by @dnfield on Dec 10, 2018
All the things you're discussing about the viewbox are supported - the only problem comes in when there is additional a width/height constraint. 

All of the scaling/transforming related to width/height happen after drawing.  But when you have both a width/height and a viewBox, there are additional transforms that are supposed to take place that I suspect I'm still not getting 100% right - and I suspect even if I were to get right, would still be counter-intuitive in a Flutter related context.

---
Comment by @GoldenJoe on Dec 10, 2018
There are only two ways to interpret this:

1. You render the elements in a canvas matching the view box first, then apply a scaling transformation the result to match the dimensions.

2. You render the elements in a canvas that matches the dimensions, then apply scaling/translation transformations according to the viewbox. 

If you think about it, it has to be number 2 because of how the Firefox examples work. 

But the bug we’re trying to fix is something else entirely. You’re taking dimensions (50,50) and a viewbox (0,0,50,50) and ending up with a render that has the icon squeezed into a 25x25 area in the center, with 12.5 units of padding. It’s not because of your ordering of dimension and viewbox transformations, it’s because of where you are applying a 2.0 scaling modifier when you compensate for the device’s resolution. It may be the viewbox, it may be dimensions. Unfortunately I can’t check the code where I am right now, but hopefully that explains my idea a bit better. 

---
Comment by @dnfield on Dec 11, 2018
What I'm doing is just drawing the elements to a canvas and then scaling it based on the viewbox. For Flutter, canvas doesn't have any pre-defined size. 

Either way I'm becoming more convinced that my current logic is incorrect for this issue and should be reverted.

---
Comment by @dnfield on Dec 11, 2018
I'vereverted a bit related to this in e445889, which should resolve the other issue. 

Whatever is done for this, it has to be handled in some other way.

---
Comment by @dnfield on Dec 17, 2018
I've reverted the changes I made to width/height processing - this still needs to be figured out though.

---
Comment by @lazingor on Dec 26, 2019
My understanding is that the `width` and `height` attributes on an SVG element play quite a different role to the `viewBox` attribute, if the `viewBox` attribute is present. The `viewBox` determines the internal coordinates that all the other coordinates specified within the SVG element are relative to. By contrast, `width` and `height` then specify how big the drawing is meant to be in its **ambient placement**.

So 
`<svg viewBox="0 0 1000 1000" width="50px" height="50px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="500" cy="500" r="100" />
</svg>`
should render as an SVG of size 50px by 50px, with a circle centred in the middle of radius 1/10 of the width of the box.

At the moment it seems to me that flutter_svg ignores the `width` and `height` attributes on the SVG element if the `viewBox` attribute is set. Is this intentional?

One can then specify the height manually by setting the `height` property in the SvgPicture constructor. But is there a way to pick this up automatically from `height` attribute of the SVG?

---
Comment by @KaMoKuMo on Jan 12, 2020
That is my understanding as well. Assuming the documents says:
`<svg viewBox="0 0 vBx vBy" width=x height=y...>`
You could 
1. Draw the picture in a Box with width=vBx, height=vBy.
2. Scale the width by a factor of x/vBx and the height by a factor of y/vBy


---
Comment by @AhmedLSayed9 on Jan 22, 2024
The issue has been around for a long time 😭