Skip to content

Latest commit

 

History

History
169 lines (138 loc) · 8.71 KB

vector_graphics.md

File metadata and controls

169 lines (138 loc) · 8.71 KB

vector_graphics, or flutter_svg 2.0

Starting in version 2.0, flutter_svg migrated to using the vector_graphics_compiler as an SVG parsing backend and the vector_graphics runtime package for widget/render object responsibilities.

The vector_graphics packages provide a compiler runtime that has no dependencies on dart:ui, and so can run completely as a CLI application or in a background isolate. It produces a binary file that has no versioning or compatibility guarantees - meaning that the output of the compiler is only suitable for use with the runtime and codec with the same exact git hash. This allows for a compact, optimized, and further optimizable format.

The ideal way to use that package is to convert your assets at compile time and bundle them with your application, or store them on a server in a location that is matched precisely to git hash of the package that produced them. This can be challenging with the current state of the flutter_tools and Dart build systems, as well as for developers who lack the resources to version and control all of their network vector graphic assets. This package is meant to help bridge the gap, as well as to help existing flutter_svg users benefit from more optimizations and an easier migration path.

Why vector_graphics?

SVG, as specified, is a nightmare. Parsing XML is slow, especially in comparison to binary formats like Flat/Protocol Buffers and the like. SVG itself pulls in many other web standards like CSS, JavaScript, and HTML. Even if you restrict yourself to some static subset of SVG, the specification allows for a wide latitude of insane but valid things. For example, the specification does nothing to prevent or discourage an editor from inserting dozens of <g> nodes, each having a transform, that eventually transform their sole child back to the identity matrix. And because the specification states that each group node can have other inheritable attributes attached, each node must be examined and some suitable data structure(s) must be allocated to account for how to pass on those heritable attributes if any arise. On a slow, lost-cost mobile device, this cuts into valuable frame-time if done on the UI isolate. And this doesn't even mention that design tool may have snuck in any number of masks, which as specified require at least two render target switches but frequently are used in place of what could be achieved with a much cheaper clip.

The basic problem is that SVG gives design tools a very large number of ways to specify a drawing, and design tools are generally not written to produce SVG that will be fast to render or fast to parse. Existing SVG optimization tools tend to focus on network download size, and may lack internal capabilities to examine the relationships between nodes to decide when and how they can be optimized.

The vector_graphics suite addresses this by focusing less on being fast at parsing and more on being able to produce something that will be fast when it finally gets to the UI isolate - and faster when Flutter's rasterizer goes to render it. Some of the optimizations available that suite are currently only available when directly using the vector_graphics package, both because of challenges around packaging FFI libraries for various platforms and because some of the optimization algorithms may be more expensive than just taking the penalty of not using them.

Additionally, the design that flutter_svg was using took inspiration from package:flutter's image related classes. This turned out to be a mistake. There is no intention for flutter_svg to ever support animated/multi-frame SVGs, and the way that Flutter ties together byte acquisition and image decoding makes things a bit complicated. Instead, vector_graphics introduces the concept of a BytesLoader class, which knows how to obtain encoded bytes to feed to the runtime via an asynchronous method that optionally receives a BuildContext. See the loaders.dart file in this package for examples.

As of this writing, the optimizations that are available include:

  • Useless element/attribute pruning.
  • Inheritance removal.
  • Paint/path deduplication.
  • Transform pre-calculation/collapsing.
  • Elimination of XML parsing on the UI isolate.
  • Elimination of UTF-8 and Base 64 decoding (i.e. for embedded images) on the UI isolate.
  • Path dashing is done completely on a background isolate.
  • More use of 32-bit floats instead of 64-bit doubles, which saves on memory with good visual fidelity.

What changes?

Compared to prior releases of flutter_svg, the following changes can be expected:

  • Loss of support for text elements that use dx or dy attributes, which already was implemented only partially and incorrectly.
  • Support for out of order defs and element references.
  • Support for patterns.
  • Gradients render slightly differently. This is likely due to a combination of some precision differences and pre-calculating transforms.
  • Some shapes, such as circles and rounded rects, render slightly differently. This is due to precision differences and pre-calculation of curves.
  • Golden tests may now show SVGs they did not used to show. this is because more parsing and rendering is completely synchronous in tests, whereas previously it was always at least partially async.
  • Outside of tests (where the extra async makes life more confusing) and web (which does not have isolates), parsing happens in a separate isolates.

What do I need to change in my code?

precachePicture

This API doesn't exist anymore. If you were using it to pre-fetch bytes from some PictureProvider, instead write a custom BytesLoader implementation (or use an existing one) and use vg.loadPicture. However, there is currently no provided widget to make drawing that picture easy, but generally speaking you can use a CustomPainter.

PictureProviders

If you had a custom PictureProvider, you will now want a custom BytesLoader. Consider overriding SvgLoader<T>, which takes care of doing the right thing with isolates for you and only requires you to implement a function to obtain the SVG string that will be run in a non-UI isolate. See more examples in loaders.dart in this package.

PictureCache

Pictures are no longer cached, however the byte data for vector_graphics output is cached in the Cache object. This is similar to but not quite the same as the old picture cache. It is available via svg.cache.

Widget changes

The SvgPicture.clipBehavior property is deprecated (no-op).

The SvgPicture.cacheColorFilter property is deprecated (no-op).

The SvgPicture.* constructors still have color and colorBlendMode properties, which are deprecated. Instead use the colorFilter property. To achieve the old behavior of the color property, use ColorFilter.mode(color, BlendMode.srcIn).

The SvgPicture.pictureProvider property has been removed. Use the loader property instead, if you must.

Golden testing

Please limit your golden tests. In particular, try to make sure that you avoid testing the same SVG over and over again in multiple different golden tests, and try to make sure that you only have as many golden files that as can be reviewed by you and your team if they all changed in a single update. In general, this is somewhere around 10-20 golden files per reviwer, and that is generously assuming that each reviewer will carefully examine the differences in those 10-20 files. Assume that the reviewer will have no idea what the author of the test intended, even if the reviewer authored the test. If your team is 5 people, that means somewhere between 50-100 goldens.

Anecdotally, I have missed important regressions in golden tests I wrote because I wrote them several years ago and just forgot, and I was only looking for change X which I verified but missed changes Y and Z that I hadn't thought about in over a year. I have also worked with partner projects that use flutter_svg that have tens of thousands of golden images directly or indirectly involveing SVGs and it is a nightmare for everyone when some minor change comes along that touches most of them. Very frequently in such projects, it becomes obvious that no one has ever looked at a large number of these golden tests, and they actually harm test coverage because they falsely make broken code appear to have coverage via a test that requires a human to double check its result.

With that said, most golden testing should "just work," except if your SVGs have images in them. In that case, "real" async work needs to get done, and at some point the test in question will have to call

await tester.runAsync(() => vg.waitForPendingDecodes());
await tester.pump();

to make sure that the image decode(s) finish and the placeholder widget is replaced with the actual picture.