diff --git a/packages/vector_graphics/CHANGELOG.md b/packages/vector_graphics/CHANGELOG.md index db1ceebd..4dc76537 100644 --- a/packages/vector_graphics/CHANGELOG.md +++ b/packages/vector_graphics/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## 1.1.12 + +- [x] error when parsing an invalid svg string. +- [x] error when download svg string from network. +- [x] error when read svg file from asset. +- [x] expose ErrorWidgetBuilder. + ## 1.1.11+ - Relax package:http constraint. diff --git a/packages/vector_graphics/lib/src/listener.dart b/packages/vector_graphics/lib/src/listener.dart index d1d958a3..83adf8b6 100644 --- a/packages/vector_graphics/lib/src/listener.dart +++ b/packages/vector_graphics/lib/src/listener.dart @@ -55,7 +55,7 @@ final Map> _pendingDecodes = /// Decode a vector graphics binary asset into a [Picture]. /// /// Throws a [StateError] if the data is invalid. -Future decodeVectorGraphics( +Future decodeVectorGraphics( ByteData data, { required Locale? locale, required TextDirection? textDirection, diff --git a/packages/vector_graphics/lib/src/vector_graphics.dart b/packages/vector_graphics/lib/src/vector_graphics.dart index 290e8a78..7e638ff3 100644 --- a/packages/vector_graphics/lib/src/vector_graphics.dart +++ b/packages/vector_graphics/lib/src/vector_graphics.dart @@ -5,14 +5,14 @@ import 'dart:math' as math; import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; - import 'package:vector_graphics_codec/vector_graphics_codec.dart'; import 'html_render_vector_graphics.dart'; -import 'loader.dart'; import 'listener.dart'; +import 'loader.dart'; import 'render_object_selection.dart'; import 'render_vector_graphic.dart'; @@ -280,8 +280,7 @@ class _PictureData { @immutable class _PictureKey { - const _PictureKey( - this.cacheKey, this.locale, this.textDirection, this.clipViewbox); + const _PictureKey(this.cacheKey, this.locale, this.textDirection, this.clipViewbox); final Object cacheKey; final Locale? locale; @@ -307,10 +306,8 @@ class _VectorGraphicWidgetState extends State { Locale? locale; TextDirection? textDirection; - static final Map<_PictureKey, _PictureData> _livePictureCache = - <_PictureKey, _PictureData>{}; - static final Map<_PictureKey, Future<_PictureData>> _pendingPictures = - <_PictureKey, Future<_PictureData>>{}; + static final Map<_PictureKey, _PictureData?> _livePictureCache = <_PictureKey, _PictureData?>{}; + static final Map<_PictureKey, Future<_PictureData?>> _pendingPictures = <_PictureKey, Future<_PictureData?>>{}; @override void didChangeDependencies() { @@ -346,29 +343,38 @@ class _VectorGraphicWidgetState extends State { } } - Future<_PictureData> _loadPicture( - BuildContext context, _PictureKey key, BytesLoader loader) { + Future<_PictureData?> _loadPicture(BuildContext context, _PictureKey key, BytesLoader loader) { if (_pendingPictures.containsKey(key)) { return _pendingPictures[key]!; } - final Future<_PictureData> result = - loader.loadBytes(context).then((ByteData data) { - return decodeVectorGraphics( - data, - locale: key.locale, - textDirection: key.textDirection, - clipViewbox: key.clipViewbox, - loader: loader, - onError: (Object error, StackTrace? stackTrace) { - return _handleError( - error, - stackTrace, - ); - }, - ); - }).then((PictureInfo pictureInfo) { + + final Future<_PictureData?> result = loader.loadBytes(context).then((ByteData data) async { + if (data.lengthInBytes == 0) { + debugPrint('_VectorGraphicWidgetState.decodeVectorGraphics: empty'); + _handleError(const FormatException('Empty SVG xml content'), null); + return null; + } else { + return decodeVectorGraphics(data, + locale: key.locale, + textDirection: key.textDirection, + clipViewbox: key.clipViewbox, + loader: loader, onError: (Object error, StackTrace? stackTrace) { + debugPrintStack( + stackTrace: stackTrace, label: '_VectorGraphicWidgetState.decodeVectorGraphics.onError: $error'); + _handleError(error, stackTrace); + }); + } + }).onError((Object? error, StackTrace stackTrace) { + debugPrintStack(stackTrace: stackTrace, label: '_VectorGraphicWidgetState._loadPictureInfo.onError: $error'); + _handleError(error ?? '', stackTrace); + return null; + }).then((PictureInfo? pictureInfo) { + if (pictureInfo == null) { + return null; + } return _PictureData(pictureInfo, 0, key); }); + _pendingPictures[key] = result; result.whenComplete(() { _pendingPictures.remove(key); @@ -377,6 +383,9 @@ class _VectorGraphicWidgetState extends State { } void _handleError(Object error, StackTrace? stackTrace) { + if (!mounted) { + return; + } setState(() { _error = error; _stackTrace = stackTrace; @@ -386,8 +395,7 @@ class _VectorGraphicWidgetState extends State { void _loadAssetBytes() { // First check if we have an avilable picture and use this immediately. final Object loaderKey = widget.loader.cacheKey(context); - final _PictureKey key = - _PictureKey(loaderKey, locale, textDirection, widget.clipViewbox); + final _PictureKey key = _PictureKey(loaderKey, locale, textDirection, widget.clipViewbox); final _PictureData? data = _livePictureCache[key]; if (data != null) { data.count += 1; @@ -399,7 +407,10 @@ class _VectorGraphicWidgetState extends State { } // If not, then check if there is a pending load. final BytesLoader loader = widget.loader; - _loadPicture(context, key, loader).then((_PictureData data) { + _loadPicture(context, key, loader).then((_PictureData? data) { + if (data == null) { + return; + } data.count += 1; // The widget may have changed, requesting a new vector graphic before @@ -675,7 +686,7 @@ class VectorGraphicUtilities { /// /// It is the caller's responsibility to handle disposing the picture when /// they are done with it. - Future loadPicture( + Future loadPicture( BytesLoader loader, BuildContext? context, { bool clipViewbox = true, diff --git a/packages/vector_graphics/test/listener_test.dart b/packages/vector_graphics/test/listener_test.dart index 8f55f4d6..6816832e 100644 --- a/packages/vector_graphics/test/listener_test.dart +++ b/packages/vector_graphics/test/listener_test.dart @@ -43,31 +43,31 @@ void main() { }); test('decode without clip', () async { - final PictureInfo info = await decodeVectorGraphics( + final PictureInfo? info = await decodeVectorGraphics( vectorGraphicBuffer, locale: ui.PlatformDispatcher.instance.locale, textDirection: ui.TextDirection.ltr, clipViewbox: true, loader: const AssetBytesLoader('test'), ); - final ui.Image image = info.picture.toImageSync(15, 15); - final Uint32List imageBytes = - (await image.toByteData())!.buffer.asUint32List(); + expect(info, isNotNull); + final ui.Image image = info!.picture.toImageSync(15, 15); + final Uint32List imageBytes = (await image.toByteData())!.buffer.asUint32List(); expect(imageBytes.first, 0xFF000000); expect(imageBytes.last, 0x00000000); }, skip: kIsWeb); test('decode with clip', () async { - final PictureInfo info = await decodeVectorGraphics( + final PictureInfo? info = await decodeVectorGraphics( vectorGraphicBuffer, locale: ui.PlatformDispatcher.instance.locale, textDirection: ui.TextDirection.ltr, clipViewbox: false, loader: const AssetBytesLoader('test'), ); - final ui.Image image = info.picture.toImageSync(15, 15); - final Uint32List imageBytes = - (await image.toByteData())!.buffer.asUint32List(); + expect(info, isNotNull); + final ui.Image image = info!.picture.toImageSync(15, 15); + final Uint32List imageBytes = (await image.toByteData())!.buffer.asUint32List(); expect(imageBytes.first, 0xFF000000); expect(imageBytes.last, 0xFF000000); }, skip: kIsWeb); diff --git a/packages/vector_graphics/test/render_vector_graphics_test.dart b/packages/vector_graphics/test/render_vector_graphics_test.dart index 633440ff..9109a953 100644 --- a/packages/vector_graphics/test/render_vector_graphics_test.dart +++ b/packages/vector_graphics/test/render_vector_graphics_test.dart @@ -17,7 +17,7 @@ import 'package:vector_graphics_codec/vector_graphics_codec.dart'; import 'caching_test.dart'; void main() { - late PictureInfo pictureInfo; + late PictureInfo? pictureInfo; tearDown(() { // Since we don't always explicitly dispose render objects in unit tests, manually clear @@ -40,7 +40,7 @@ void main() { test('Rasterizes a picture to a draw image call', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -61,7 +61,7 @@ void main() { test('Multiple render objects with the same scale share a raster', () async { final RenderVectorGraphic renderVectorGraphicA = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -69,7 +69,7 @@ void main() { 1.0, ); final RenderVectorGraphic renderVectorGraphicB = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -90,7 +90,7 @@ void main() { test('disposing render object release raster', () async { final RenderVectorGraphic renderVectorGraphicA = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -98,7 +98,7 @@ void main() { 1.0, ); final RenderVectorGraphic renderVectorGraphicB = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -125,7 +125,7 @@ void main() { 'Multiple render objects with the same scale share a raster, different load order', () async { final RenderVectorGraphic renderVectorGraphicA = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -133,7 +133,7 @@ void main() { 1.0, ); final RenderVectorGraphic renderVectorGraphicB = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -157,7 +157,7 @@ void main() { test('Changing color filter does not re-rasterize', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -184,7 +184,7 @@ void main() { test('Changing device pixel ratio does re-rasterize and dispose old raster', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -209,7 +209,7 @@ void main() { test('Changing scale does re-rasterize and dispose old raster', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -234,7 +234,7 @@ void main() { test('The raster size is increased by the inverse picture scale', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -253,7 +253,7 @@ void main() { test('The raster size is increased by the device pixel ratio', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 2.0, @@ -272,7 +272,7 @@ void main() { test('The raster size is increased by the device pixel ratio and ratio', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 2.0, @@ -291,7 +291,7 @@ void main() { test('Changing size asserts if it is different from the picture size', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -312,7 +312,7 @@ void main() { test('Does not rasterize a picture when fully transparent', () async { final FixedOpacityAnimation opacity = FixedOpacityAnimation(0.0); final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -338,7 +338,7 @@ void main() { test('paints partially opaque picture', () async { final FixedOpacityAnimation opacity = FixedOpacityAnimation(0.5); final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -354,7 +354,7 @@ void main() { test('Disposing render object disposes picture', () async { final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -375,7 +375,7 @@ void main() { test('Removes listeners on detach, dispose, adds then on attach', () async { final FixedOpacityAnimation opacity = FixedOpacityAnimation(0.5); final RenderVectorGraphic renderVectorGraphic = RenderVectorGraphic( - pictureInfo, + pictureInfo!, 'test', null, 1.0, @@ -411,7 +411,7 @@ void main() { test('Color filter applies clip', () async { final RenderPictureVectorGraphic render = RenderPictureVectorGraphic( - pictureInfo, + pictureInfo!, const ui.ColorFilter.mode(Colors.green, ui.BlendMode.difference), null, );