Flutter_svg: Specifying `viewBox`, `width`, and `height` leads to incorrect stroke-width and font-size rendering

Created on 10 Sep 2018  Â·  27Comments  Â·  Source: dnfield/flutter_svg

Sketch produced this SVG.

It renders in Sketch like this:

image

It renders in Chrome like this:

image

It renders in Flutter like this:

image

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

... 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>
bug

All 27 comments

Not entirely sure why this is happening yet iwth just a quick look, but it does seem off. Will continue to investigate.

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.

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....

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.

The basic issue seems to be there needs to be some special handling for stroke widths and font sizes in this scenario.

I think I finally figured this one out. Hoping to have a fix soon.

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.

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

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.

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

Here's a thin one and a broader one, for comparison.

image

image

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.

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?

If you specify the width of the svg, it's going to be the actual width of the drawing in pixels.

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.

This feels like a bug to me. the alignment property isn't doing what it should be in this case.

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

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

In Chrome:

image

<?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>

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. :(

Naively, I'd think the viewBox would say what the default clipping rectangle and size of an svg shoudl be.

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.

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.

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.

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.

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.

I've reverted the changes I made to width/height processing - this still needs to be figured out though.

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?

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
Was this page helpful?
0 / 5 - 0 ratings

Related issues

alectogeek picture alectogeek  Â·  5Comments

ChristianKleineidam picture ChristianKleineidam  Â·  5Comments

kentcb picture kentcb  Â·  4Comments

barak-innov picture barak-innov  Â·  4Comments

ifiokjr picture ifiokjr  Â·  5Comments