blob: 8a9b52925487fd0b45ebe684a8d7e3fae44f0780 [file] [log] [blame]
import 'dart:async';
import 'dart:convert';
import 'dart:io' show File;
import 'dart:typed_data';
import 'dart:ui' show Picture;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart' show AssetBundle;
import 'package:flutter/widgets.dart';
import 'parser.dart';
import 'src/picture_provider.dart';
import 'src/picture_stream.dart';
import 'src/render_picture.dart';
import 'src/unbounded_color_filtered.dart';
import 'src/vector_drawable.dart';
/// Instance for [Svg]'s utility methods, which can produce a [DrawableRoot]
/// or [PictureInfo] from [String] or [Uint8List].
final Svg svg = Svg._();
/// A utility class for decoding SVG data to a [DrawableRoot] or a [PictureInfo].
///
/// These methods are used by [SvgPicture], but can also be directly used e.g.
/// to create a [DrawableRoot] you manipulate or render to your own [Canvas].
/// Access to this class is provided by the exported [svg] member.
class Svg {
Svg._();
/// A global override flag for [SvgPicture.cacheColorFilter].
///
/// If this is null, the value in [SvgPicture.cacheColorFilter] is used. If it
/// is not null, it will override that value.
bool? cacheColorFilterOverride;
/// Produces a [PictureInfo] from a [Uint8List] of SVG byte data (assumes UTF8 encoding).
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// The `colorFilter` property will be applied to any [Paint] objects used during drawing.
///
/// The [key] will be used for debugging purposes.
Future<PictureInfo> svgPictureDecoder(
Uint8List raw,
bool allowDrawingOutsideOfViewBox,
ColorFilter? colorFilter,
String key,
) async {
final DrawableRoot svgRoot = await fromSvgBytes(raw, key);
final Picture pic = svgRoot.toPicture(
clipToViewBox: allowDrawingOutsideOfViewBox == true ? false : true,
colorFilter: colorFilter,
);
return PictureInfo(
picture: pic,
viewport: svgRoot.viewport.viewBoxRect,
size: svgRoot.viewport.size,
);
}
/// Produces a [PictureInfo] from a [String] of SVG data.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// The `colorFilter` property will be applied to any [Paint] objects used during drawing.
///
/// The [key] will be used for debugging purposes.
Future<PictureInfo> svgPictureStringDecoder(
String raw,
bool allowDrawingOutsideOfViewBox,
ColorFilter? colorFilter,
String key) async {
final DrawableRoot svg = await fromSvgString(raw, key);
return PictureInfo(
picture: svg.toPicture(
clipToViewBox: allowDrawingOutsideOfViewBox == true ? false : true,
colorFilter: colorFilter,
size: svg.viewport.viewBox,
),
viewport: svg.viewport.viewBoxRect,
size: svg.viewport.size,
);
}
/// Produces a [Drawableroot] from a [Uint8List] of SVG byte data (assumes UTF8 encoding).
///
/// The [key] will be used for debugging purposes.
Future<DrawableRoot> fromSvgBytes(Uint8List raw, String key) async {
// TODO(dnfield): do utf decoding in another thread?
// Might just have to live with potentially slow(ish) decoding, this is causing errors.
// See: https://github.com/dart-lang/sdk/issues/31954
// See: https://github.com/flutter/flutter/blob/bf3bd7667f07709d0b817ebfcb6972782cfef637/packages/flutter/lib/src/services/asset_bundle.dart#L66
// if (raw.lengthInBytes < 20 * 1024) {
return fromSvgString(utf8.decode(raw), key);
// } else {
// final String str =
// await compute(_utf8Decode, raw, debugLabel: 'UTF8 decode for SVG');
// return fromSvgString(str);
// }
}
// String _utf8Decode(Uint8List data) {
// return utf8.decode(data);
// }
/// Creates a [DrawableRoot] from a string of SVG data.
///
/// The `key` is used for debugging purposes.
Future<DrawableRoot> fromSvgString(String rawSvg, String key) async {
final SvgParser parser = SvgParser();
return await parser.parse(rawSvg, key: key);
}
}
/// Prefetches an SVG Picture into the picture cache.
///
/// Returns a [Future] that will complete when the first image yielded by the
/// [PictureProvider] is available or failed to load.
///
/// If the image is later used by an [SvgPicture], it will probably be loaded
/// faster. The consumer of the image does not need to use the same
/// [PictureProvider] instance. The [PictureCache] will find the picture
/// as long as both pictures share the same key.
///
/// The `onError` argument can be used to manually handle errors while precaching.
///
/// See also:
///
/// * [PictureCache], which holds images that may be reused.
Future<void> precachePicture(
PictureProvider provider,
BuildContext? context, {
Rect? viewBox,
ColorFilter? colorFilterOverride,
Color? color,
BlendMode? colorBlendMode,
PictureErrorListener? onError,
}) {
final PictureConfiguration config = createLocalPictureConfiguration(
context,
viewBox: viewBox,
colorFilterOverride: colorFilterOverride,
color: color,
colorBlendMode: colorBlendMode,
);
final Completer<void> completer = Completer<void>();
PictureStream? stream;
void listener(PictureInfo? picture, bool synchronous) {
completer.complete();
stream?.removeListener(listener);
}
void errorListener(Object exception, StackTrace stackTrace) {
if (onError != null) {
onError(exception, stackTrace);
} else {
FlutterError.reportError(FlutterErrorDetails(
context: ErrorDescription('picture failed to precache'),
library: 'SVG',
exception: exception,
stack: stackTrace,
silent: true,
));
}
completer.complete();
stream?.removeListener(listener);
}
stream = provider.resolve(config, onError: errorListener)
..addListener(listener, onError: errorListener);
return completer.future;
}
/// A widget that will parse SVG data into a [Picture] using a [PictureProvider].
///
/// The picture will be cached using the [PictureCache], incorporating any color
/// filtering used into the key (meaning the same SVG with two different `color`
/// arguments applied would be two cache entries).
class SvgPicture extends StatefulWidget {
/// Instantiates a widget that renders an SVG picture using the `pictureProvider`.
///
/// Either the [width] and [height] arguments should be specified, or the
/// widget should be placed in a context that sets tight layout constraints.
/// Otherwise, the image dimensions will change as the image is loaded, which
/// will result in ugly layout changes.
///
/// If `matchTextDirection` is set to true, the picture will be flipped
/// horizontally in [TextDirection.rtl] contexts.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// A custom `placeholderBuilder` can be specified for cases where decoding or
/// acquiring data may take a noticeably long time, e.g. for a network picture.
///
/// The `semanticsLabel` can be used to identify the purpose of this picture for
/// screen reading software.
///
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
const SvgPicture(
this.pictureProvider, {
Key? key,
this.width,
this.height,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.matchTextDirection = false,
this.allowDrawingOutsideViewBox = false,
this.placeholderBuilder,
this.semanticsLabel,
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.colorFilter,
this.cacheColorFilter = false,
}) : super(key: key);
/// Instantiates a widget that renders an SVG picture from an [AssetBundle].
///
/// The key will be derived from the `assetName`, `package`, and `bundle`
/// arguments. The `package` argument must be non-null when displaying an SVG
/// from a package and null otherwise. See the `Assets in packages` section for
/// details.
///
/// Either the [width] and [height] arguments should be specified, or the
/// widget should be placed in a context that sets tight layout constraints.
/// Otherwise, the image dimensions will change as the image is loaded, which
/// will result in ugly layout changes.
///
/// If `matchTextDirection` is set to true, the picture will be flipped
/// horizontally in [TextDirection.rtl] contexts.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// A custom `placeholderBuilder` can be specified for cases where decoding or
/// acquiring data may take a noticeably long time.
///
/// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
/// [ColorFilter] on any [Paint]s created for this drawing.
///
/// ## Assets in packages
///
/// To create the widget with an asset from a package, the [package] argument
/// must be provided. For instance, suppose a package called `my_icons` has
/// `icons/heart.svg` .
///
/// Then to display the image, use:
///
/// ```dart
/// SvgPicture.asset('icons/heart.svg', package: 'my_icons')
/// ```
///
/// Assets used by the package itself should also be displayed using the
/// [package] argument as above.
///
/// If the desired asset is specified in the `pubspec.yaml` of the package, it
/// is bundled automatically with the app. In particular, assets used by the
/// package itself must be specified in its `pubspec.yaml`.
///
/// A package can also choose to have assets in its 'lib/' folder that are not
/// specified in its `pubspec.yaml`. In this case for those images to be
/// bundled, the app has to specify which ones to include. For instance a
/// package named `fancy_backgrounds` could have:
///
/// ```
/// lib/backgrounds/background1.svg
/// lib/backgrounds/background2.svg
/// lib/backgrounds/background3.svg
///```
///
/// To include, say the first image, the `pubspec.yaml` of the app should
/// specify it in the assets section:
///
/// ```yaml
/// assets:
/// - packages/fancy_backgrounds/backgrounds/background1.svg
/// ```
///
/// The `lib/` is implied, so it should not be included in the asset path.
///
///
/// See also:
///
/// * [AssetPicture], which is used to implement the behavior when the scale is
/// omitted.
/// * [ExactAssetPicture], which is used to implement the behavior when the
/// scale is present.
/// * <https://flutter.io/assets-and-images/>, an introduction to assets in
/// Flutter.
///
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
SvgPicture.asset(
String assetName, {
Key? key,
this.matchTextDirection = false,
AssetBundle? bundle,
String? package,
this.width,
this.height,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.allowDrawingOutsideViewBox = false,
this.placeholderBuilder,
Color? color,
BlendMode colorBlendMode = BlendMode.srcIn,
this.semanticsLabel,
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.cacheColorFilter = false,
}) : pictureProvider = ExactAssetPicture(
allowDrawingOutsideViewBox == true
? svgStringDecoderOutsideViewBox
: svgStringDecoder,
assetName,
bundle: bundle,
package: package,
colorFilter: svg.cacheColorFilterOverride ?? cacheColorFilter
? _getColorFilter(color, colorBlendMode)
: null,
),
colorFilter = _getColorFilter(color, colorBlendMode),
super(key: key);
/// Creates a widget that displays a [PictureStream] obtained from the network.
///
/// The [url] argument must not be null.
///
/// Either the [width] and [height] arguments should be specified, or the
/// widget should be placed in a context that sets tight layout constraints.
/// Otherwise, the image dimensions will change as the image is loaded, which
/// will result in ugly layout changes.
///
/// If `matchTextDirection` is set to true, the picture will be flipped
/// horizontally in [TextDirection.rtl] contexts.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// A custom `placeholderBuilder` can be specified for cases where decoding or
/// acquiring data may take a noticeably long time, such as high latency scenarios.
///
/// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
/// [ColorFilter] on any [Paint]s created for this drawing.
///
/// All network images are cached regardless of HTTP headers.
///
/// An optional `headers` argument can be used to send custom HTTP headers
/// with the image request.
///
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
SvgPicture.network(
String url, {
Key? key,
Map<String, String>? headers,
this.width,
this.height,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.matchTextDirection = false,
this.allowDrawingOutsideViewBox = false,
this.placeholderBuilder,
Color? color,
BlendMode colorBlendMode = BlendMode.srcIn,
this.semanticsLabel,
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.cacheColorFilter = false,
}) : pictureProvider = NetworkPicture(
allowDrawingOutsideViewBox == true
? svgByteDecoderOutsideViewBox
: svgByteDecoder,
url,
headers: headers,
colorFilter: svg.cacheColorFilterOverride ?? cacheColorFilter
? _getColorFilter(color, colorBlendMode)
: null,
),
colorFilter = _getColorFilter(color, colorBlendMode),
super(key: key);
/// Creates a widget that displays a [PictureStream] obtained from a [File].
///
/// The [file] argument must not be null.
///
/// Either the [width] and [height] arguments should be specified, or the
/// widget should be placed in a context that sets tight layout constraints.
/// Otherwise, the image dimensions will change as the image is loaded, which
/// will result in ugly layout changes.
///
/// If `matchTextDirection` is set to true, the picture will be flipped
/// horizontally in [TextDirection.rtl] contexts.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// A custom `placeholderBuilder` can be specified for cases where decoding or
/// acquiring data may take a noticeably long time.
///
/// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
/// [ColorFilter] on any [Paint]s created for this drawing.
///
/// On Android, this may require the
/// `android.permission.READ_EXTERNAL_STORAGE` permission.
///
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
SvgPicture.file(
File file, {
Key? key,
this.width,
this.height,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.matchTextDirection = false,
this.allowDrawingOutsideViewBox = false,
this.placeholderBuilder,
Color? color,
BlendMode colorBlendMode = BlendMode.srcIn,
this.semanticsLabel,
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.cacheColorFilter = false,
}) : pictureProvider = FilePicture(
allowDrawingOutsideViewBox == true
? svgByteDecoderOutsideViewBox
: svgByteDecoder,
file,
colorFilter: svg.cacheColorFilterOverride ?? cacheColorFilter
? _getColorFilter(color, colorBlendMode)
: null,
),
colorFilter = _getColorFilter(color, colorBlendMode),
super(key: key);
/// Creates a widget that displays a [PictureStream] obtained from a [Uint8List].
///
/// The [bytes] argument must not be null.
///
/// Either the [width] and [height] arguments should be specified, or the
/// widget should be placed in a context that sets tight layout constraints.
/// Otherwise, the image dimensions will change as the image is loaded, which
/// will result in ugly layout changes.
///
/// If `matchTextDirection` is set to true, the picture will be flipped
/// horizontally in [TextDirection.rtl] contexts.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// A custom `placeholderBuilder` can be specified for cases where decoding or
/// acquiring data may take a noticeably long time.
///
/// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
/// [ColorFilter] on any [Paint]s created for this drawing.
///
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
SvgPicture.memory(
Uint8List bytes, {
Key? key,
this.width,
this.height,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.matchTextDirection = false,
this.allowDrawingOutsideViewBox = false,
this.placeholderBuilder,
Color? color,
BlendMode colorBlendMode = BlendMode.srcIn,
this.semanticsLabel,
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.cacheColorFilter = false,
}) : pictureProvider = MemoryPicture(
allowDrawingOutsideViewBox == true
? svgByteDecoderOutsideViewBox
: svgByteDecoder,
bytes,
colorFilter: svg.cacheColorFilterOverride ?? cacheColorFilter
? _getColorFilter(color, colorBlendMode)
: null,
),
colorFilter = _getColorFilter(color, colorBlendMode),
super(key: key);
/// Creates a widget that displays a [PictureStream] obtained from a [String].
///
/// The [bytes] argument must not be null.
///
/// Either the [width] and [height] arguments should be specified, or the
/// widget should be placed in a context that sets tight layout constraints.
/// Otherwise, the image dimensions will change as the image is loaded, which
/// will result in ugly layout changes.
///
/// If `matchTextDirection` is set to true, the picture will be flipped
/// horizontally in [TextDirection.rtl] contexts.
///
/// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
/// if set to true, it will not clip the canvas used internally to the view box,
/// meaning the picture may draw beyond the intended area and lead to undefined
/// behavior or additional memory overhead.
///
/// A custom `placeholderBuilder` can be specified for cases where decoding or
/// acquiring data may take a noticeably long time.
///
/// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
/// [ColorFilter] on any [Paint]s created for this drawing.
///
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
SvgPicture.string(
String bytes, {
Key? key,
this.width,
this.height,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.matchTextDirection = false,
this.allowDrawingOutsideViewBox = false,
this.placeholderBuilder,
Color? color,
BlendMode colorBlendMode = BlendMode.srcIn,
this.semanticsLabel,
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.cacheColorFilter = false,
}) : pictureProvider = StringPicture(
allowDrawingOutsideViewBox == true
? svgStringDecoderOutsideViewBox
: svgStringDecoder,
bytes,
colorFilter: svg.cacheColorFilterOverride ?? cacheColorFilter
? _getColorFilter(color, colorBlendMode)
: null,
),
colorFilter = _getColorFilter(color, colorBlendMode),
super(key: key);
/// The default placeholder for a SVG that may take time to parse or
/// retrieve, e.g. from a network location.
static WidgetBuilder defaultPlaceholderBuilder =
(BuildContext ctx) => const LimitedBox();
static ColorFilter? _getColorFilter(Color? color, BlendMode colorBlendMode) =>
color == null ? null : ColorFilter.mode(color, colorBlendMode);
/// A [PictureInfoDecoder] for [Uint8List]s that will clip to the viewBox.
static final PictureInfoDecoder<Uint8List> svgByteDecoder =
(Uint8List bytes, ColorFilter? colorFilter, String key) =>
svg.svgPictureDecoder(bytes, false, colorFilter, key);
/// A [PictureInfoDecoder] for strings that will clip to the viewBox.
static final PictureInfoDecoder<String> svgStringDecoder =
(String data, ColorFilter? colorFilter, String key) =>
svg.svgPictureStringDecoder(data, false, colorFilter, key);
/// A [PictureInfoDecoder] for [Uint8List]s that will not clip to the viewBox.
static final PictureInfoDecoder<Uint8List> svgByteDecoderOutsideViewBox =
(Uint8List bytes, ColorFilter? colorFilter, String key) =>
svg.svgPictureDecoder(bytes, true, colorFilter, key);
/// A [PictureInfoDecoder] for [String]s that will not clip to the viewBox.
static final PictureInfoDecoder<String> svgStringDecoderOutsideViewBox =
(String data, ColorFilter? colorFilter, String key) =>
svg.svgPictureStringDecoder(data, true, colorFilter, key);
/// If specified, the width to use for the SVG. If unspecified, the SVG
/// will take the width of its parent.
final double? width;
/// If specified, the height to use for the SVG. If unspecified, the SVG
/// will take the height of its parent.
final double? height;
/// How to inscribe the picture into the space allocated during layout.
/// The default is [BoxFit.contain].
final BoxFit fit;
/// How to align the picture within its parent widget.
///
/// The alignment aligns the given position in the picture to the given position
/// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
/// -1.0) aligns the image to the top-left corner of its layout bounds, while a
/// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
/// picture with the bottom right corner of its layout bounds. Similarly, an
/// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
/// middle of the bottom edge of its layout bounds.
///
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
/// [AlignmentDirectional]), then a [TextDirection] must be available
/// when the picture is painted.
///
/// Defaults to [Alignment.center].
///
/// See also:
///
/// * [Alignment], a class with convenient constants typically used to
/// specify an [AlignmentGeometry].
/// * [AlignmentDirectional], like [Alignment] for specifying alignments
/// relative to text direction.
final AlignmentGeometry alignment;
/// The [PictureProvider] used to resolve the SVG.
final PictureProvider pictureProvider;
/// The placeholder to use while fetching, decoding, and parsing the SVG data.
final WidgetBuilder? placeholderBuilder;
/// If true, will horizontally flip the picture in [TextDirection.rtl] contexts.
final bool matchTextDirection;
/// If true, will allow the SVG to be drawn outside of the clip boundary of its
/// viewBox.
final bool allowDrawingOutsideViewBox;
/// The [Semantics.label] for this picture.
///
/// The value indicates the purpose of the picture, and will be
/// read out by screen readers.
final String? semanticsLabel;
/// Whether to exclude this picture from semantics.
///
/// Useful for pictures which do not contribute meaningful information to an
/// application.
final bool excludeFromSemantics;
/// The content will be clipped (or not) according to this option.
///
/// See the enum [Clip] for details of all possible options and their common
/// use cases.
///
/// Defaults to [Clip.hardEdge], and must not be null.
final Clip clipBehavior;
/// The color filter, if any, to apply to this widget.
final ColorFilter? colorFilter;
/// Whether to cache the picture with the [colorFilter] applied or not.
///
/// This value should be set to true if the same SVG will be rendered with
/// multiple colors, but false if it will always (or almost always) be
/// rendered with the same [colorFilter].
///
/// If [Svg.cacheColorFilterOverride] is not null, it will override this value
/// for all widgets, regardless of what is specified for an individual widget.
///
/// This defaults to false and must not be null.
final bool cacheColorFilter;
@override
State<SvgPicture> createState() => _SvgPictureState();
}
class _SvgPictureState extends State<SvgPicture> {
PictureInfo? _picture;
PictureStream? _pictureStream;
bool _isListeningToStream = false;
@override
void didChangeDependencies() {
_resolveImage();
if (TickerMode.of(context)) {
_listenToStream();
} else {
_stopListeningToStream();
}
super.didChangeDependencies();
}
@override
void didUpdateWidget(SvgPicture oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.pictureProvider != oldWidget.pictureProvider) {
_resolveImage();
}
}
@override
void reassemble() {
_resolveImage(); // in case the image cache was flushed
super.reassemble();
}
void _resolveImage() {
final PictureStream newStream = widget.pictureProvider
.resolve(createLocalPictureConfiguration(context));
assert(newStream != null); // ignore: unnecessary_null_comparison
_updateSourceStream(newStream);
}
void _handleImageChanged(PictureInfo? imageInfo, bool synchronousCall) {
setState(() {
_picture = imageInfo;
});
}
// Update _pictureStream to newStream, and moves the stream listener
// registration from the old stream to the new stream (if a listener was
// registered).
void _updateSourceStream(PictureStream newStream) {
if (_pictureStream?.key == newStream.key) {
return;
}
if (_isListeningToStream)
_pictureStream!.removeListener(_handleImageChanged);
_pictureStream = newStream;
if (_isListeningToStream) {
_pictureStream!.addListener(_handleImageChanged);
}
}
void _listenToStream() {
if (_isListeningToStream) {
return;
}
_pictureStream!.addListener(_handleImageChanged);
_isListeningToStream = true;
}
void _stopListeningToStream() {
if (!_isListeningToStream) {
return;
}
_pictureStream!.removeListener(_handleImageChanged);
_isListeningToStream = false;
}
@override
void dispose() {
assert(_pictureStream != null);
_stopListeningToStream();
super.dispose();
}
@override
Widget build(BuildContext context) {
Widget _maybeWrapWithSemantics(Widget child) {
if (widget.excludeFromSemantics) {
return child;
}
return Semantics(
container: widget.semanticsLabel != null,
image: true,
label: widget.semanticsLabel == null ? '' : widget.semanticsLabel,
child: child,
);
}
Widget _maybeWrapColorFilter(Widget child) {
// The color filter is already applied by the provider, or there is no
// color filter to apply at all.
if (widget.pictureProvider.colorFilter != null ||
widget.colorFilter == null) {
return child;
}
return UnboundedColorFiltered(
colorFilter: widget.colorFilter,
child: child,
);
}
if (_picture != null) {
final Rect viewport = Offset.zero & _picture!.viewport.size;
double? width = widget.width;
double? height = widget.height;
if (width == null && height == null) {
width = viewport.width;
height = viewport.height;
} else if (height != null) {
width = height / viewport.height * viewport.width;
} else if (width != null) {
height = width / viewport.width * viewport.height;
}
return _maybeWrapWithSemantics(
_maybeWrapColorFilter(
SizedBox(
width: width,
height: height,
child: FittedBox(
fit: widget.fit,
alignment: widget.alignment,
clipBehavior: widget.clipBehavior,
child: SizedBox.fromSize(
size: viewport.size,
child: RawPicture(
_picture,
matchTextDirection: widget.matchTextDirection,
allowDrawingOutsideViewBox: widget.allowDrawingOutsideViewBox,
),
),
),
),
),
);
}
return _maybeWrapWithSemantics(
widget.placeholderBuilder == null
? _getDefaultPlaceholder(context, widget.width, widget.height)
: widget.placeholderBuilder!(context),
);
}
Widget _getDefaultPlaceholder(
BuildContext context, double? width, double? height) {
if (width != null || height != null) {
return SizedBox(width: width, height: height);
}
return SvgPicture.defaultPlaceholderBuilder(context);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(
DiagnosticsProperty<PictureStream>('stream', _pictureStream),
);
}
}