diff --git a/.ci/coveralls.sh b/.ci/coveralls.sh new file mode 100755 index 00000000..9dd7e84f --- /dev/null +++ b/.ci/coveralls.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -x + +if [[ -z $COVERALLS_REPO_TOKEN ]]; then + echo "Skipping coveralls, user not authorized" + exit 0 +fi + +dart pub global activate coveralls +dart pub global run coveralls coverage/lcov.info diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 34c0d628..00000000 --- a/.cirrus.yml +++ /dev/null @@ -1,28 +0,0 @@ -container: - image: cirrusci/flutter:latest - -env: - CI_NAME: "CirrusCI" - CI_BUILD_NUMBER: $CIRRUS_TASK_ID - CI_BUILD_URL: "https://cirrus-ci.com/task/$CIRRUS_TASK_ID" - CI_BRANCH: $CIRRUS_BRANCH - CI_PULL_REQUEST: $CIRRUS_PR - COVERALLS_REPO_TOKEN: ENCRYPTED[61ba3fee193a9ed6116e0f61bbe26fe8c0452287e5dfd86c728e2f1f24327818d6c74c956d7b9cbf3bd6236489af0fd1] - -test_task: - matrix: - - name: stable - channel_script: | - flutter channel stable - flutter upgrade - - name: master - channel_script: | - flutter channel master - flutter upgrade - pub_cache: - folder: ~/.pub-cache - analyze_script: flutter analyze . - test_script: flutter test --coverage - coveralls_script: | - dart pub global activate coveralls - dart pub global run coveralls coverage/lcov.info diff --git a/.github/workflows/flutter_svg.yml b/.github/workflows/flutter_svg.yml new file mode 100644 index 00000000..4ab36135 --- /dev/null +++ b/.github/workflows/flutter_svg.yml @@ -0,0 +1,30 @@ +name: analyze and test + +on: + push: + branches: [master] + paths-ignore: + - '**/*.md' + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/flutter_svg + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - run: flutter --version + - run: flutter pub get + - run: dart format --set-exit-if-changed . + - run: flutter analyze . + - run: flutter test --coverage + # - uses: romeovs/lcov-reporter-action@v0.2.16 + # with: + # lcov-file: ./packages/vector_graphics_codec/coverage/lcov.info + # github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/flutter_svg_test.yml b/.github/workflows/flutter_svg_test.yml new file mode 100644 index 00000000..daeea37a --- /dev/null +++ b/.github/workflows/flutter_svg_test.yml @@ -0,0 +1,30 @@ +name: analyze and test flutter_svg_test + +on: + push: + branches: [master] + paths-ignore: + - '**/*.md' + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/flutter_svg_test + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - run: flutter --version + - run: flutter pub get + - run: dart format --set-exit-if-changed . + - run: flutter analyze . + - run: flutter test --coverage + # - uses: romeovs/lcov-reporter-action@v0.2.16 + # with: + # lcov-file: ./packages/vector_graphics_codec/coverage/lcov.info + # github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 8dd4cc08..a48af087 100644 --- a/.gitignore +++ b/.gitignore @@ -8,13 +8,12 @@ pubspec.lock .pub/ build/ ios/.generated/ -packages .flutter-plugins doc/api/ coverage/ .project # golden failure diffs -test/failures +**/test/failures # Flutter crash logs /flutter_*.log diff --git a/README.md b/README.md index 7976d434..44f2cbd4 100644 --- a/README.md +++ b/README.md @@ -1,196 +1,33 @@ # flutter_svg -[![Pub](https://img.shields.io/pub/v/flutter_svg.svg)](https://pub.dartlang.org/packages/flutter_svg) [![Build Status](https://travis-ci.org/dnfield/flutter_svg.svg?branch=master)](https://travis-ci.org/dnfield/flutter_svg) [![Coverage Status](https://coveralls.io/repos/github/dnfield/flutter_svg/badge.svg?branch=master)](https://coveralls.io/github/dnfield/flutter_svg?branch=master) +[![Pub](https://img.shields.io/pub/v/flutter_svg.svg)](https://pub.dartlang.org/packages/flutter_svg) [![Coverage Status](https://coveralls.io/repos/github/dnfield/flutter_svg/badge.svg?branch=master)](https://coveralls.io/github/dnfield/flutter_svg?branch=master) Flutter Logo which can be rendered by this package! -Draw SVG (and _some_ Android VectorDrawable (XML)) files on a Flutter Widget. +Draw SVG files using Flutter. -## Getting Started +## Overview -This is a Dart-native rendering library. Issues/PRs will be raised in Flutter -and flutter/engine as necessary for features that are not good candidates for -Dart implementations (especially if they're impossible to implement without -engine support). However, not everything that Skia can easily do needs to be -done by Skia; for example, the Path parsing logic here isn't much slower than -doing it in native, and Skia isn't always doing low level GPU accelerated work -where you might think it is (e.g. Dash Paths). +This library consists of two packages: [**flutter_svg**](https://github.com/dnfield/flutter_svg/tree/master/packages/flutter_svg) and [**flutter_svg_test**](https://github.com/dnfield/flutter_svg/tree/master/packages/flutter_svg_test). -All of the SVGs in the `assets/` folder (except the text related one(s)) now -have corresponding PNGs in the `golden/` folder that were rendered using -`flutter test tool/gen_golden.dart` and compared against their rendering output -in Chrome. Automated tests will continue to compare these to ensure code changes -do not break known-good renderings. +[flutter_svg](https://github.com/dnfield/flutter_svg/tree/master/packages/flutter_svg) provides a wrapper around Dart implementations of SVG parsing, including SVG path data. In particular, it provides efficient `BytesLoader` implementations for [`package:vector_graphics`](https://pub.dev/packages/vector_graphics). The package is easier to use but not as performant as using the `vector_graphics` and `vector_graphics_compiler` packages directly. Those packages allow you to do ahead-of-time compilation and optimization of SVGs, and offer some more performant rasterization strategies at runtime. -Basic usage (to create an SVG rendering widget from an asset): - -```dart -final String assetName = 'assets/image.svg'; -final Widget svg = SvgPicture.asset( - assetName, - semanticsLabel: 'Acme Logo' -); -``` - -You can color/tint the image like so: - -```dart -final String assetName = 'assets/up_arrow.svg'; -final Widget svgIcon = SvgPicture.asset( - assetName, - color: Colors.red, - semanticsLabel: 'A red up arrow' -); -``` - -The default placeholder is an empty box (`LimitedBox`) - although if a `height` -or `width` is specified on the `SvgPicture`, a `SizedBox` will be used instead -(which ensures better layout experience). There is currently no way to show an -Error visually, however errors will get properly logged to the console in debug -mode. - -You can also specify a placeholder widget. The placeholder will display during -parsing/loading (normally only relevant for network access). - -```dart -// Will print error messages to the console. -final String assetName = 'assets/image_that_does_not_exist.svg'; -final Widget svg = SvgPicture.asset( - assetName, -); - -final Widget networkSvg = SvgPicture.network( - 'https://site-that-takes-a-while.com/image.svg', - semanticsLabel: 'A shark?!', - placeholderBuilder: (BuildContext context) => Container( - padding: const EdgeInsets.all(30.0), - child: const CircularProgressIndicator()), -); -``` - -If you'd like to render the SVG to some other canvas, you can do something like: - -```dart -import 'package:flutter_svg/flutter_svg.dart'; -final String rawSvg = '''...'''; -final DrawableRoot svgRoot = await svg.fromSvgString(rawSvg, rawSvg); - -// If you only want the final Picture output, just use -final Picture picture = svgRoot.toPicture(); - -// Otherwise, if you want to draw it to a canvas: -// Optional, but probably normally desirable: scale the canvas dimensions to -// the SVG's viewbox -svgRoot.scaleCanvasToViewBox(canvas); - -// Optional, but probably normally desireable: ensure the SVG isn't rendered -// outside of the viewbox bounds -svgRoot.clipCanvasToViewBox(canvas); -svgRoot.draw(canvas, size); -``` - -The `SvgPicture` helps to automate this logic, and it provides some convenience -wrappers for getting assets from multiple sources and caching the resultant -`Picture`. _It does not render the data to an `Image` at any point_; you -certainly can do that in Flutter, but you then lose some of the benefit of -having a vector format to begin with. - -While I'm making every effort to avoid needlessly changing the API, it's not -guarnateed to be stable yet (hence the pre-1.0.0 version). To date, the biggest -change is deprecating the `SvgImage` widgets in favor of `SvgPicture` - it -became very confusing to maintain that name, as `Picture`s are the underlying -mechanism for rendering rather than `Image`s. - -See [main.dart](/../master/example/lib/main.dart) for a complete sample. - -## Check SVG compatibility - -As not all SVG features are supported by this library (see below), sometimes we have to dynamically -check if an SVG contains any unsupported features resulting in broken images. -You might also want to throw errors in tests, but only warn about them at runtime. -This can be done by using the snippet below: - -```dart -final SvgParser parser = SvgParser(); -try { - parser.parse(svgString, warningsAsErrors: true); - print('SVG is supported'); -} catch (e) { - print('SVG contains unsupported features'); -} -``` - -> Note: -> The library currently only detects unsupported elements (like the ` - - - - - - - - - - - - -'''; - final SvgParser parser = SvgParser(); - expect( - parser.parse(svgStr, warningsAsErrors: true), - throwsA(anything), - ); - }); - - test('Warns about unsupported elements by default', () async { - const String svgStr = ''' - - - svg/stick_figure - Created with Sketch. - - - - - - - - - - - - - -'''; - - final SvgParser parser = SvgParser(); - expect(await parser.parse(svgStr), isA()); - }); -} diff --git a/test/vector_drawable_test.dart b/test/vector_drawable_test.dart deleted file mode 100644 index b2c2dcfd..00000000 --- a/test/vector_drawable_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:typed_data'; -import 'dart:ui'; - -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('DrawableRoot can mergeStyle', () { - const DrawableStyle styleA = DrawableStyle( - groupOpacity: 0.5, - pathFillType: PathFillType.evenOdd, - ); - const DrawableStyle styleB = DrawableStyle( - pathFillType: PathFillType.nonZero, - ); - DrawableRoot root = DrawableRoot( - '', // No id - const DrawableViewport(Size(100, 100), Size(100, 100)), - [], - DrawableDefinitionServer(), - styleA, - ); - expect(root.style!.pathFillType, styleA.pathFillType); - root = root.mergeStyle(styleB); - expect(root.style!.pathFillType, styleB.pathFillType); - }); - - test('SvgPictureDecoder uses color filter properly', () async { - final PictureInfo info = await svg.svgPictureStringDecoder( - ''' - - - -''', - false, - const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color), - 'test', - ); - final Image image = await info.picture.toImage(2, 2); - final ByteData data = (await image.toByteData())!; - - const List expected = [ - 0, 48, 0, 255, // - 0, 48, 0, 255, - 0, 48, 0, 255, - 0, 48, 0, 255, - ]; - expect(data.buffer.asUint8List(), expected); - }); -} diff --git a/test/xml_svg_test.dart b/test/xml_svg_test.dart deleted file mode 100644 index f8cccfc9..00000000 --- a/test/xml_svg_test.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'dart:ui'; - -import 'package:test/test.dart'; -import 'package:xml/xml_events.dart'; - -import 'package:flutter_svg/src/svg/xml_parsers.dart'; -import 'package:flutter_svg/src/utilities/xml.dart'; - -void main() { - test('Xlink href tests', () { - final XmlStartElementEvent el = - parseEvents('').first - as XmlStartElementEvent; - - final XmlStartElementEvent elXlink = - parseEvents('') - .first as XmlStartElementEvent; - - expect(getHrefAttribute(el.attributes), 'http://localhost'); - expect(getHrefAttribute(elXlink.attributes), 'http://localhost'); - }); - - test('Attribute and style tests', () { - final XmlStartElementEvent el = - parseEvents('') - .first as XmlStartElementEvent; - - expect(getAttribute(el.attributes, 'stroke'), '#fff'); - expect(getAttribute(el.attributes, 'fill'), '#eee'); - expect(getAttribute(el.attributes, 'stroke-dashpattern'), '1 2'); - expect(getAttribute(el.attributes, 'stroke-opacity'), '1'); - expect(getAttribute(el.attributes, 'stroke-another'), ''); - expect(getAttribute(el.attributes, 'fill-opacity'), '.23'); - - expect(getAttribute(el.attributes, 'fill-opacity', checkStyle: false), ''); - expect(getAttribute(el.attributes, 'fill', checkStyle: false), '#eee'); - }); - - // if the parsing logic changes, we can simplify some methods. for now assert that whitespace in attributes is preserved - test('Attribute WhiteSpace test', () { - final XmlStartElementEvent xd = - parseEvents('').first - as XmlStartElementEvent; - - expect( - xd.attributes[0].value, - ' asdf', - reason: - 'XML Parsing implementation no longer preserves leading whitespace in attributes!', - ); - expect( - xd.attributes[1].value, - 'asdf ', - reason: - 'XML Parsing implementation no longer preserves trailing whitespace in attributes!', - ); - }); - - test('viewBox tests', () { - const Rect rect = Rect.fromLTWH(0.0, 0.0, 100.0, 100.0); - - final XmlStartElementEvent svgWithViewBox = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent svgWithViewBoxAndWidthHeight = - parseEvents('') - .first as XmlStartElementEvent; - final XmlStartElementEvent svgWithWidthHeight = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent svgWithViewBoxMinXMinY = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent svgWithNoSizeInfo = - parseEvents('').first as XmlStartElementEvent; - - expect(parseViewBox(svgWithViewBoxAndWidthHeight.attributes)!.size, - const Size(50, 50)); - expect(parseViewBox(svgWithViewBox.attributes)!.viewBoxRect, rect); - expect(parseViewBox(svgWithViewBox.attributes)!.viewBoxOffset, Offset.zero); - expect(parseViewBox(svgWithViewBoxAndWidthHeight.attributes)!.viewBoxRect, - rect); - expect(parseViewBox(svgWithWidthHeight.attributes)!.viewBoxRect, rect); - expect(parseViewBox(svgWithNoSizeInfo.attributes, nullOk: true), null); - expect(() => parseViewBox(svgWithNoSizeInfo.attributes), throwsStateError); - expect(parseViewBox(svgWithViewBoxMinXMinY.attributes)!.viewBoxRect, rect); - expect(parseViewBox(svgWithViewBoxMinXMinY.attributes)!.viewBoxOffset, - const Offset(-42.0, -56.0)); - }); - - test('TileMode tests', () { - final XmlStartElementEvent pad = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent reflect = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent repeat = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent invalid = - parseEvents('').first - as XmlStartElementEvent; - - final XmlStartElementEvent none = - parseEvents('').first as XmlStartElementEvent; - - expect(parseTileMode(pad.attributes), TileMode.clamp); - expect(parseTileMode(invalid.attributes), TileMode.clamp); - expect(parseTileMode(none.attributes), TileMode.clamp); - - expect(parseTileMode(reflect.attributes), TileMode.mirror); - expect(parseTileMode(repeat.attributes), TileMode.repeated); - }); - - test('@stroke-dashoffset tests', () { - final XmlStartElementEvent abs = - parseEvents('').first - as XmlStartElementEvent; - final XmlStartElementEvent pct = - parseEvents('').first - as XmlStartElementEvent; - - // TODO(dnfield): DashOffset is completely opaque right now, maybe expose the raw value? - expect(parseDashOffset(abs.attributes), isNotNull); - expect(parseDashOffset(pct.attributes), isNotNull); - }); - - test('font-weight tests', () { - expect(parseFontWeight('100'), FontWeight.w100); - expect(parseFontWeight('200'), FontWeight.w200); - expect(parseFontWeight('300'), FontWeight.w300); - expect(parseFontWeight('400'), FontWeight.w400); - expect(parseFontWeight('500'), FontWeight.w500); - expect(parseFontWeight('600'), FontWeight.w600); - expect(parseFontWeight('700'), FontWeight.w700); - expect(parseFontWeight('800'), FontWeight.w800); - expect(parseFontWeight('900'), FontWeight.w900); - - expect(parseFontWeight('normal'), FontWeight.normal); - expect(parseFontWeight('bold'), FontWeight.bold); - - expect(() => parseFontWeight('invalid'), throwsUnsupportedError); - }); -} diff --git a/tool/gen_golden.dart b/tool/gen_golden.dart deleted file mode 100644 index d5731d30..00000000 --- a/tool/gen_golden.dart +++ /dev/null @@ -1,73 +0,0 @@ -// There's probably some better way to do this, but for now run `flutter test tool/gen_golden.dart -// should exclude files that -// - aren't rendering properly -// - have text (this doesn't render properly in the host setup?) -// The golden files should then be visually compared against Chrome's rendering output for correctness. -// The comparison may have to be made more tolerant if we want to use other sources of rendering for comparison... - -import 'dart:io'; -import 'dart:typed_data'; -import 'dart:ui'; - -import 'package:path/path.dart' as path; - -import 'package:flutter_svg/svg.dart'; -import 'package:flutter_svg/src/vector_drawable.dart'; - -Future getSvgPngBytes(String svgData) async { - final PictureRecorder rec = PictureRecorder(); - final Canvas canvas = Canvas(rec); - - const Size size = Size(200.0, 200.0); - - final DrawableRoot svgRoot = - await svg.fromSvgString(svgData, 'GenGoldenTest'); - svgRoot.scaleCanvasToViewBox(canvas, size); - svgRoot.clipCanvasToViewBox(canvas); - - canvas.drawPaint(Paint()..color = const Color(0xFFFFFFFF)); - svgRoot.draw(canvas, svgRoot.viewport.viewBoxRect); - - final Picture pict = rec.endRecording(); - - final Image image = - await pict.toImage(size.width.toInt(), size.height.toInt()); - final ByteData bytes = (await image.toByteData(format: ImageByteFormat.png))!; - - return bytes.buffer.asUint8List(); -} - -Iterable getSvgFileNames() sync* { - final Directory dir = Directory('./example/assets'); - for (FileSystemEntity fe in dir.listSync(recursive: true)) { - if (fe is File && fe.path.toLowerCase().endsWith('.svg')) { - // Skip text based tests unless we're on Linux - these have - // subtle platform specific differences. - if (fe.path.toLowerCase().contains('text') && !Platform.isLinux) { - continue; - } - yield fe; - } - } -} - -String getGoldenFileName(String svgAssetPath) { - return svgAssetPath - .replaceAll('/example\/assets/', '/golden/') - .replaceAll('\\example\\assets\\', '\\golden\\') - .replaceAll('.svg', '.png'); -} - -Future main() async { - for (File fe in getSvgFileNames()) { - final String pathName = getGoldenFileName(fe.path); - - final Directory goldenDir = Directory(path.dirname(pathName)); - if (!goldenDir.existsSync()) { - goldenDir.createSync(recursive: true); - } - final File output = File(pathName); - print(pathName); - await output.writeAsBytes(await getSvgPngBytes(await fe.readAsString())); - } -} diff --git a/vector_graphics.md b/vector_graphics.md new file mode 100644 index 00000000..46a12682 --- /dev/null +++ b/vector_graphics.md @@ -0,0 +1,169 @@ +# `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 `` 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`. + +### `PictureProvider`s + +If you had a custom `PictureProvider`, you will now want a custom `BytesLoader`. +Consider overriding `SvgLoader`, 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 + +```dart +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.