svg.dart 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io' show File;
  4. import 'dart:typed_data';
  5. import 'dart:ui' show Picture;
  6. import 'package:flutter/foundation.dart';
  7. import 'package:flutter/services.dart' show AssetBundle;
  8. import 'package:flutter/widgets.dart';
  9. import 'parser.dart';
  10. import 'src/picture_provider.dart';
  11. import 'src/picture_stream.dart';
  12. import 'src/render_picture.dart';
  13. import 'src/vector_drawable.dart';
  14. /// Instance for [Svg]'s utility methods, which can produce a [DrawableRoot]
  15. /// or [PictureInfo] from [String] or [Uint8List].
  16. final Svg svg = Svg._();
  17. /// A utility class for decoding SVG data to a [DrawableRoot] or a [PictureInfo].
  18. ///
  19. /// These methods are used by [SvgPicture], but can also be directly used e.g.
  20. /// to create a [DrawableRoot] you manipulate or render to your own [Canvas].
  21. /// Access to this class is provided by the exported [svg] member.
  22. class Svg {
  23. Svg._();
  24. /// Produces a [PictureInfo] from a [Uint8List] of SVG byte data (assumes UTF8 encoding).
  25. ///
  26. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  27. /// if set to true, it will not clip the canvas used internally to the view box,
  28. /// meaning the picture may draw beyond the intended area and lead to undefined
  29. /// behavior or additional memory overhead.
  30. ///
  31. /// The `colorFilter` property will be applied to any [Paint] objects used during drawing.
  32. ///
  33. /// The [key] will be used for debugging purposes.
  34. Future<PictureInfo> svgPictureDecoder(
  35. Uint8List raw,
  36. bool allowDrawingOutsideOfViewBox,
  37. ColorFilter colorFilter,
  38. String key,
  39. ) async {
  40. final DrawableRoot svgRoot = await fromSvgBytes(raw, key);
  41. final Picture pic = svgRoot.toPicture(
  42. clipToViewBox: allowDrawingOutsideOfViewBox == true ? false : true,
  43. colorFilter: colorFilter,
  44. );
  45. return PictureInfo(
  46. picture: pic,
  47. viewport: svgRoot.viewport.viewBoxRect,
  48. size: svgRoot.viewport.size,
  49. );
  50. }
  51. /// Produces a [PictureInfo] from a [String] of SVG data.
  52. ///
  53. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  54. /// if set to true, it will not clip the canvas used internally to the view box,
  55. /// meaning the picture may draw beyond the intended area and lead to undefined
  56. /// behavior or additional memory overhead.
  57. ///
  58. /// The `colorFilter` property will be applied to any [Paint] objects used during drawing.
  59. ///
  60. /// The [key] will be used for debugging purposes.
  61. Future<PictureInfo> svgPictureStringDecoder(
  62. String raw,
  63. bool allowDrawingOutsideOfViewBox,
  64. ColorFilter colorFilter,
  65. String key) async {
  66. final DrawableRoot svg = await fromSvgString(raw, key);
  67. return PictureInfo(
  68. picture: svg.toPicture(
  69. clipToViewBox: allowDrawingOutsideOfViewBox == true ? false : true,
  70. colorFilter: colorFilter,
  71. size: svg.viewport.viewBox,
  72. ),
  73. viewport: svg.viewport.viewBoxRect,
  74. size: svg.viewport.size,
  75. );
  76. }
  77. /// Produces a [Drawableroot] from a [Uint8List] of SVG byte data (assumes UTF8 encoding).
  78. ///
  79. /// The [key] will be used for debugging purposes.
  80. Future<DrawableRoot> fromSvgBytes(Uint8List raw, String key) async {
  81. // TODO(dnfield): do utf decoding in another thread?
  82. // Might just have to live with potentially slow(ish) decoding, this is causing errors.
  83. // See: https://github.com/dart-lang/sdk/issues/31954
  84. // See: https://github.com/flutter/flutter/blob/bf3bd7667f07709d0b817ebfcb6972782cfef637/packages/flutter/lib/src/services/asset_bundle.dart#L66
  85. // if (raw.lengthInBytes < 20 * 1024) {
  86. return fromSvgString(utf8.decode(raw), key);
  87. // } else {
  88. // final String str =
  89. // await compute(_utf8Decode, raw, debugLabel: 'UTF8 decode for SVG');
  90. // return fromSvgString(str);
  91. // }
  92. }
  93. // String _utf8Decode(Uint8List data) {
  94. // return utf8.decode(data);
  95. // }
  96. /// Creates a [DrawableRoot] from a string of SVG data.
  97. ///
  98. /// The `key` is used for debugging purposes.
  99. Future<DrawableRoot> fromSvgString(String rawSvg, String key) async {
  100. final SvgParser parser = SvgParser();
  101. return await parser.parse(rawSvg, key: key);
  102. }
  103. }
  104. /// Prefetches an SVG Picture into the picture cache.
  105. ///
  106. /// Returns a [Future] that will complete when the first image yielded by the
  107. /// [PictureProvider] is available or failed to load.
  108. ///
  109. /// If the image is later used by an [SvgPicture], it will probably be loaded
  110. /// faster. The consumer of the image does not need to use the same
  111. /// [PictureProvider] instance. The [PictureCache] will find the picture
  112. /// as long as both pictures share the same key.
  113. ///
  114. /// The `onError` argument can be used to manually handle errors while precaching.
  115. ///
  116. /// See also:
  117. ///
  118. /// * [PictureCache], which holds images that may be reused.
  119. Future<void> precachePicture(
  120. PictureProvider provider,
  121. BuildContext context, {
  122. Rect viewBox,
  123. ColorFilter colorFilterOverride,
  124. Color color,
  125. BlendMode colorBlendMode,
  126. PictureErrorListener onError,
  127. }) {
  128. final PictureConfiguration config = createLocalPictureConfiguration(
  129. context,
  130. viewBox: viewBox,
  131. colorFilterOverride: colorFilterOverride,
  132. color: color,
  133. colorBlendMode: colorBlendMode,
  134. );
  135. final Completer<void> completer = Completer<void>();
  136. PictureStream stream;
  137. void listener(PictureInfo picture, bool synchronous) {
  138. completer.complete();
  139. stream?.removeListener(listener);
  140. }
  141. void errorListener(dynamic exception, StackTrace stackTrace) {
  142. if (onError != null) {
  143. onError(exception, stackTrace);
  144. } else {
  145. FlutterError.reportError(FlutterErrorDetails(
  146. context: ErrorDescription('picture failed to precache'),
  147. library: 'SVG',
  148. exception: exception,
  149. stack: stackTrace,
  150. silent: true,
  151. ));
  152. }
  153. completer.complete();
  154. stream?.removeListener(listener);
  155. }
  156. stream = provider.resolve(config, onError: errorListener)
  157. ..addListener(listener, onError: errorListener);
  158. return completer.future;
  159. }
  160. /// A widget that will parse SVG data into a [Picture] using a [PictureProvider].
  161. ///
  162. /// The picture will be cached using the [PictureCache], incorporating any color
  163. /// filtering used into the key (meaning the same SVG with two different `color`
  164. /// arguments applied would be two cache entries).
  165. class SvgPicture extends StatefulWidget {
  166. /// Instantiates a widget that renders an SVG picture using the `pictureProvider`.
  167. ///
  168. /// Either the [width] and [height] arguments should be specified, or the
  169. /// widget should be placed in a context that sets tight layout constraints.
  170. /// Otherwise, the image dimensions will change as the image is loaded, which
  171. /// will result in ugly layout changes.
  172. ///
  173. /// If `matchTextDirection` is set to true, the picture will be flipped
  174. /// horizontally in [TextDirection.rtl] contexts.
  175. ///
  176. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  177. /// if set to true, it will not clip the canvas used internally to the view box,
  178. /// meaning the picture may draw beyond the intended area and lead to undefined
  179. /// behavior or additional memory overhead.
  180. ///
  181. /// A custom `placeholderBuilder` can be specified for cases where decoding or
  182. /// acquiring data may take a noticeably long time, e.g. for a network picture.
  183. ///
  184. /// The `semanticsLabel` can be used to identify the purpose of this picture for
  185. /// screen reading software.
  186. ///
  187. /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
  188. const SvgPicture(
  189. this.pictureProvider, {
  190. Key key,
  191. this.width,
  192. this.height,
  193. this.fit = BoxFit.contain,
  194. this.alignment = Alignment.center,
  195. this.matchTextDirection = false,
  196. this.allowDrawingOutsideViewBox = false,
  197. this.placeholderBuilder,
  198. this.semanticsLabel,
  199. this.excludeFromSemantics = false,
  200. this.clipBehavior = Clip.hardEdge,
  201. }) : super(key: key);
  202. /// Instantiates a widget that renders an SVG picture from an [AssetBundle].
  203. ///
  204. /// The key will be derived from the `assetName`, `package`, and `bundle`
  205. /// arguments. The `package` argument must be non-null when displaying an SVG
  206. /// from a package and null otherwise. See the `Assets in packages` section for
  207. /// details.
  208. ///
  209. /// Either the [width] and [height] arguments should be specified, or the
  210. /// widget should be placed in a context that sets tight layout constraints.
  211. /// Otherwise, the image dimensions will change as the image is loaded, which
  212. /// will result in ugly layout changes.
  213. ///
  214. /// If `matchTextDirection` is set to true, the picture will be flipped
  215. /// horizontally in [TextDirection.rtl] contexts.
  216. ///
  217. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  218. /// if set to true, it will not clip the canvas used internally to the view box,
  219. /// meaning the picture may draw beyond the intended area and lead to undefined
  220. /// behavior or additional memory overhead.
  221. ///
  222. /// A custom `placeholderBuilder` can be specified for cases where decoding or
  223. /// acquiring data may take a noticeably long time.
  224. ///
  225. /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  226. /// [ColorFilter] on any [Paint]s created for this drawing.
  227. ///
  228. /// ## Assets in packages
  229. ///
  230. /// To create the widget with an asset from a package, the [package] argument
  231. /// must be provided. For instance, suppose a package called `my_icons` has
  232. /// `icons/heart.svg` .
  233. ///
  234. /// Then to display the image, use:
  235. ///
  236. /// ```dart
  237. /// SvgPicture.asset('icons/heart.svg', package: 'my_icons')
  238. /// ```
  239. ///
  240. /// Assets used by the package itself should also be displayed using the
  241. /// [package] argument as above.
  242. ///
  243. /// If the desired asset is specified in the `pubspec.yaml` of the package, it
  244. /// is bundled automatically with the app. In particular, assets used by the
  245. /// package itself must be specified in its `pubspec.yaml`.
  246. ///
  247. /// A package can also choose to have assets in its 'lib/' folder that are not
  248. /// specified in its `pubspec.yaml`. In this case for those images to be
  249. /// bundled, the app has to specify which ones to include. For instance a
  250. /// package named `fancy_backgrounds` could have:
  251. ///
  252. /// ```
  253. /// lib/backgrounds/background1.svg
  254. /// lib/backgrounds/background2.svg
  255. /// lib/backgrounds/background3.svg
  256. ///```
  257. ///
  258. /// To include, say the first image, the `pubspec.yaml` of the app should
  259. /// specify it in the assets section:
  260. ///
  261. /// ```yaml
  262. /// assets:
  263. /// - packages/fancy_backgrounds/backgrounds/background1.svg
  264. /// ```
  265. ///
  266. /// The `lib/` is implied, so it should not be included in the asset path.
  267. ///
  268. ///
  269. /// See also:
  270. ///
  271. /// * [AssetPicture], which is used to implement the behavior when the scale is
  272. /// omitted.
  273. /// * [ExactAssetPicture], which is used to implement the behavior when the
  274. /// scale is present.
  275. /// * <https://flutter.io/assets-and-images/>, an introduction to assets in
  276. /// Flutter.
  277. ///
  278. /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
  279. SvgPicture.asset(
  280. String assetName, {
  281. Key key,
  282. this.matchTextDirection = false,
  283. AssetBundle bundle,
  284. String package,
  285. this.width,
  286. this.height,
  287. this.fit = BoxFit.contain,
  288. this.alignment = Alignment.center,
  289. this.allowDrawingOutsideViewBox = false,
  290. this.placeholderBuilder,
  291. Color color,
  292. BlendMode colorBlendMode = BlendMode.srcIn,
  293. this.semanticsLabel,
  294. this.excludeFromSemantics = false,
  295. this.clipBehavior = Clip.hardEdge,
  296. }) : pictureProvider = ExactAssetPicture(
  297. allowDrawingOutsideViewBox == true
  298. ? svgStringDecoderOutsideViewBox
  299. : svgStringDecoder,
  300. assetName,
  301. bundle: bundle,
  302. package: package,
  303. colorFilter: _getColorFilter(color, colorBlendMode)),
  304. super(key: key);
  305. /// Creates a widget that displays a [PictureStream] obtained from the network.
  306. ///
  307. /// The [url] argument must not be null.
  308. ///
  309. /// Either the [width] and [height] arguments should be specified, or the
  310. /// widget should be placed in a context that sets tight layout constraints.
  311. /// Otherwise, the image dimensions will change as the image is loaded, which
  312. /// will result in ugly layout changes.
  313. ///
  314. /// If `matchTextDirection` is set to true, the picture will be flipped
  315. /// horizontally in [TextDirection.rtl] contexts.
  316. ///
  317. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  318. /// if set to true, it will not clip the canvas used internally to the view box,
  319. /// meaning the picture may draw beyond the intended area and lead to undefined
  320. /// behavior or additional memory overhead.
  321. ///
  322. /// A custom `placeholderBuilder` can be specified for cases where decoding or
  323. /// acquiring data may take a noticeably long time, such as high latency scenarios.
  324. ///
  325. /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  326. /// [ColorFilter] on any [Paint]s created for this drawing.
  327. ///
  328. /// All network images are cached regardless of HTTP headers.
  329. ///
  330. /// An optional `headers` argument can be used to send custom HTTP headers
  331. /// with the image request.
  332. ///
  333. /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
  334. SvgPicture.network(
  335. String url, {
  336. Key key,
  337. Map<String, String> headers,
  338. this.width,
  339. this.height,
  340. this.fit = BoxFit.contain,
  341. this.alignment = Alignment.center,
  342. this.matchTextDirection = false,
  343. this.allowDrawingOutsideViewBox = false,
  344. this.placeholderBuilder,
  345. Color color,
  346. BlendMode colorBlendMode = BlendMode.srcIn,
  347. this.semanticsLabel,
  348. this.excludeFromSemantics = false,
  349. this.clipBehavior = Clip.hardEdge,
  350. }) : pictureProvider = NetworkPicture(
  351. allowDrawingOutsideViewBox == true
  352. ? svgByteDecoderOutsideViewBox
  353. : svgByteDecoder,
  354. url,
  355. headers: headers,
  356. colorFilter: _getColorFilter(color, colorBlendMode)),
  357. super(key: key);
  358. /// Creates a widget that displays a [PictureStream] obtained from a [File].
  359. ///
  360. /// The [file] argument must not be null.
  361. ///
  362. /// Either the [width] and [height] arguments should be specified, or the
  363. /// widget should be placed in a context that sets tight layout constraints.
  364. /// Otherwise, the image dimensions will change as the image is loaded, which
  365. /// will result in ugly layout changes.
  366. ///
  367. /// If `matchTextDirection` is set to true, the picture will be flipped
  368. /// horizontally in [TextDirection.rtl] contexts.
  369. ///
  370. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  371. /// if set to true, it will not clip the canvas used internally to the view box,
  372. /// meaning the picture may draw beyond the intended area and lead to undefined
  373. /// behavior or additional memory overhead.
  374. ///
  375. /// A custom `placeholderBuilder` can be specified for cases where decoding or
  376. /// acquiring data may take a noticeably long time.
  377. ///
  378. /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  379. /// [ColorFilter] on any [Paint]s created for this drawing.
  380. ///
  381. /// On Android, this may require the
  382. /// `android.permission.READ_EXTERNAL_STORAGE` permission.
  383. ///
  384. /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
  385. SvgPicture.file(
  386. File file, {
  387. Key key,
  388. this.width,
  389. this.height,
  390. this.fit = BoxFit.contain,
  391. this.alignment = Alignment.center,
  392. this.matchTextDirection = false,
  393. this.allowDrawingOutsideViewBox = false,
  394. this.placeholderBuilder,
  395. Color color,
  396. BlendMode colorBlendMode = BlendMode.srcIn,
  397. this.semanticsLabel,
  398. this.excludeFromSemantics = false,
  399. this.clipBehavior = Clip.hardEdge,
  400. }) : pictureProvider = FilePicture(
  401. allowDrawingOutsideViewBox == true
  402. ? svgByteDecoderOutsideViewBox
  403. : svgByteDecoder,
  404. file,
  405. colorFilter: _getColorFilter(color, colorBlendMode)),
  406. super(key: key);
  407. /// Creates a widget that displays a [PictureStream] obtained from a [Uint8List].
  408. ///
  409. /// The [bytes] argument must not be null.
  410. ///
  411. /// Either the [width] and [height] arguments should be specified, or the
  412. /// widget should be placed in a context that sets tight layout constraints.
  413. /// Otherwise, the image dimensions will change as the image is loaded, which
  414. /// will result in ugly layout changes.
  415. ///
  416. /// If `matchTextDirection` is set to true, the picture will be flipped
  417. /// horizontally in [TextDirection.rtl] contexts.
  418. ///
  419. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  420. /// if set to true, it will not clip the canvas used internally to the view box,
  421. /// meaning the picture may draw beyond the intended area and lead to undefined
  422. /// behavior or additional memory overhead.
  423. ///
  424. /// A custom `placeholderBuilder` can be specified for cases where decoding or
  425. /// acquiring data may take a noticeably long time.
  426. ///
  427. /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  428. /// [ColorFilter] on any [Paint]s created for this drawing.
  429. ///
  430. /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
  431. SvgPicture.memory(
  432. Uint8List bytes, {
  433. Key key,
  434. this.width,
  435. this.height,
  436. this.fit = BoxFit.contain,
  437. this.alignment = Alignment.center,
  438. this.matchTextDirection = false,
  439. this.allowDrawingOutsideViewBox = false,
  440. this.placeholderBuilder,
  441. Color color,
  442. BlendMode colorBlendMode = BlendMode.srcIn,
  443. this.semanticsLabel,
  444. this.excludeFromSemantics = false,
  445. this.clipBehavior = Clip.hardEdge,
  446. }) : pictureProvider = MemoryPicture(
  447. allowDrawingOutsideViewBox == true
  448. ? svgByteDecoderOutsideViewBox
  449. : svgByteDecoder,
  450. bytes,
  451. colorFilter: _getColorFilter(color, colorBlendMode)),
  452. super(key: key);
  453. /// Creates a widget that displays a [PictureStream] obtained from a [String].
  454. ///
  455. /// The [bytes] argument must not be null.
  456. ///
  457. /// Either the [width] and [height] arguments should be specified, or the
  458. /// widget should be placed in a context that sets tight layout constraints.
  459. /// Otherwise, the image dimensions will change as the image is loaded, which
  460. /// will result in ugly layout changes.
  461. ///
  462. /// If `matchTextDirection` is set to true, the picture will be flipped
  463. /// horizontally in [TextDirection.rtl] contexts.
  464. ///
  465. /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  466. /// if set to true, it will not clip the canvas used internally to the view box,
  467. /// meaning the picture may draw beyond the intended area and lead to undefined
  468. /// behavior or additional memory overhead.
  469. ///
  470. /// A custom `placeholderBuilder` can be specified for cases where decoding or
  471. /// acquiring data may take a noticeably long time.
  472. ///
  473. /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  474. /// [ColorFilter] on any [Paint]s created for this drawing.
  475. ///
  476. /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
  477. SvgPicture.string(
  478. String bytes, {
  479. Key key,
  480. this.width,
  481. this.height,
  482. this.fit = BoxFit.contain,
  483. this.alignment = Alignment.center,
  484. this.matchTextDirection = false,
  485. this.allowDrawingOutsideViewBox = false,
  486. this.placeholderBuilder,
  487. Color color,
  488. BlendMode colorBlendMode = BlendMode.srcIn,
  489. this.semanticsLabel,
  490. this.excludeFromSemantics = false,
  491. this.clipBehavior = Clip.hardEdge,
  492. }) : pictureProvider = StringPicture(
  493. allowDrawingOutsideViewBox == true
  494. ? svgStringDecoderOutsideViewBox
  495. : svgStringDecoder,
  496. bytes,
  497. colorFilter: _getColorFilter(color, colorBlendMode)),
  498. super(key: key);
  499. /// The default placeholder for a SVG that may take time to parse or
  500. /// retrieve, e.g. from a network location.
  501. static WidgetBuilder defaultPlaceholderBuilder =
  502. (BuildContext ctx) => const LimitedBox();
  503. static ColorFilter _getColorFilter(Color color, BlendMode colorBlendMode) =>
  504. color == null
  505. ? null
  506. : ColorFilter.mode(color, colorBlendMode ?? BlendMode.srcIn);
  507. /// A [PictureInfoDecoder] for [Uint8List]s that will clip to the viewBox.
  508. static final PictureInfoDecoder<Uint8List> svgByteDecoder =
  509. (Uint8List bytes, ColorFilter colorFilter, String key) =>
  510. svg.svgPictureDecoder(bytes, false, colorFilter, key);
  511. /// A [PictureInfoDecoder] for strings that will clip to the viewBox.
  512. static final PictureInfoDecoder<String> svgStringDecoder =
  513. (String data, ColorFilter colorFilter, String key) =>
  514. svg.svgPictureStringDecoder(data, false, colorFilter, key);
  515. /// A [PictureInfoDecoder] for [Uint8List]s that will not clip to the viewBox.
  516. static final PictureInfoDecoder<Uint8List> svgByteDecoderOutsideViewBox =
  517. (Uint8List bytes, ColorFilter colorFilter, String key) =>
  518. svg.svgPictureDecoder(bytes, true, colorFilter, key);
  519. /// A [PictureInfoDecoder] for [String]s that will not clip to the viewBox.
  520. static final PictureInfoDecoder<String> svgStringDecoderOutsideViewBox =
  521. (String data, ColorFilter colorFilter, String key) =>
  522. svg.svgPictureStringDecoder(data, true, colorFilter, key);
  523. /// If specified, the width to use for the SVG. If unspecified, the SVG
  524. /// will take the width of its parent.
  525. final double width;
  526. /// If specified, the height to use for the SVG. If unspecified, the SVG
  527. /// will take the height of its parent.
  528. final double height;
  529. /// How to inscribe the picture into the space allocated during layout.
  530. /// The default is [BoxFit.contain].
  531. final BoxFit fit;
  532. /// How to align the picture within its parent widget.
  533. ///
  534. /// The alignment aligns the given position in the picture to the given position
  535. /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
  536. /// -1.0) aligns the image to the top-left corner of its layout bounds, while a
  537. /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
  538. /// picture with the bottom right corner of its layout bounds. Similarly, an
  539. /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
  540. /// middle of the bottom edge of its layout bounds.
  541. ///
  542. /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
  543. /// [AlignmentDirectional]), then a [TextDirection] must be available
  544. /// when the picture is painted.
  545. ///
  546. /// Defaults to [Alignment.center].
  547. ///
  548. /// See also:
  549. ///
  550. /// * [Alignment], a class with convenient constants typically used to
  551. /// specify an [AlignmentGeometry].
  552. /// * [AlignmentDirectional], like [Alignment] for specifying alignments
  553. /// relative to text direction.
  554. final AlignmentGeometry alignment;
  555. /// The [PictureProvider] used to resolve the SVG.
  556. final PictureProvider pictureProvider;
  557. /// The placeholder to use while fetching, decoding, and parsing the SVG data.
  558. final WidgetBuilder placeholderBuilder;
  559. /// If true, will horizontally flip the picture in [TextDirection.rtl] contexts.
  560. final bool matchTextDirection;
  561. /// If true, will allow the SVG to be drawn outside of the clip boundary of its
  562. /// viewBox.
  563. final bool allowDrawingOutsideViewBox;
  564. /// The [Semantics.label] for this picture.
  565. ///
  566. /// The value indicates the purpose of the picture, and will be
  567. /// read out by screen readers.
  568. final String semanticsLabel;
  569. /// Whether to exclude this picture from semantics.
  570. ///
  571. /// Useful for pictures which do not contribute meaningful information to an
  572. /// application.
  573. final bool excludeFromSemantics;
  574. /// The content will be clipped (or not) according to this option.
  575. ///
  576. /// See the enum [Clip] for details of all possible options and their common
  577. /// use cases.
  578. ///
  579. /// Defaults to [Clip.hardEdge], and must not be null.
  580. final Clip clipBehavior;
  581. @override
  582. State<SvgPicture> createState() => _SvgPictureState();
  583. }
  584. class _SvgPictureState extends State<SvgPicture> {
  585. PictureInfo _picture;
  586. PictureStream _pictureStream;
  587. bool _isListeningToStream = false;
  588. @override
  589. void didChangeDependencies() {
  590. _resolveImage();
  591. if (TickerMode.of(context)) {
  592. _listenToStream();
  593. } else {
  594. _stopListeningToStream();
  595. }
  596. super.didChangeDependencies();
  597. }
  598. @override
  599. void didUpdateWidget(SvgPicture oldWidget) {
  600. super.didUpdateWidget(oldWidget);
  601. if (widget.pictureProvider != oldWidget.pictureProvider) {
  602. _resolveImage();
  603. }
  604. }
  605. @override
  606. void reassemble() {
  607. _resolveImage(); // in case the image cache was flushed
  608. super.reassemble();
  609. }
  610. void _resolveImage() {
  611. final PictureStream newStream = widget.pictureProvider
  612. .resolve(createLocalPictureConfiguration(context));
  613. assert(newStream != null);
  614. _updateSourceStream(newStream);
  615. }
  616. void _handleImageChanged(PictureInfo imageInfo, bool synchronousCall) {
  617. setState(() {
  618. _picture = imageInfo;
  619. });
  620. }
  621. // Update _pictureStream to newStream, and moves the stream listener
  622. // registration from the old stream to the new stream (if a listener was
  623. // registered).
  624. void _updateSourceStream(PictureStream newStream) {
  625. if (_pictureStream?.key == newStream?.key) {
  626. return;
  627. }
  628. if (_isListeningToStream)
  629. _pictureStream.removeListener(_handleImageChanged);
  630. _pictureStream = newStream;
  631. if (_isListeningToStream) {
  632. _pictureStream.addListener(_handleImageChanged);
  633. }
  634. }
  635. void _listenToStream() {
  636. if (_isListeningToStream) {
  637. return;
  638. }
  639. _pictureStream.addListener(_handleImageChanged);
  640. _isListeningToStream = true;
  641. }
  642. void _stopListeningToStream() {
  643. if (!_isListeningToStream) {
  644. return;
  645. }
  646. _pictureStream.removeListener(_handleImageChanged);
  647. _isListeningToStream = false;
  648. }
  649. @override
  650. void dispose() {
  651. assert(_pictureStream != null);
  652. _stopListeningToStream();
  653. super.dispose();
  654. }
  655. @override
  656. Widget build(BuildContext context) {
  657. Widget _maybeWrapWithSemantics(Widget child) {
  658. if (widget.excludeFromSemantics) {
  659. return child;
  660. }
  661. return Semantics(
  662. container: widget.semanticsLabel != null,
  663. image: true,
  664. label: widget.semanticsLabel == null ? '' : widget.semanticsLabel,
  665. child: child,
  666. );
  667. }
  668. if (_picture != null) {
  669. final Rect viewport = Offset.zero & _picture.viewport.size;
  670. double width = widget.width;
  671. double height = widget.height;
  672. if (width == null && height == null) {
  673. width = viewport.width;
  674. height = viewport.height;
  675. } else if (height != null) {
  676. width = height / viewport.height * viewport.width;
  677. } else if (width != null) {
  678. height = width / viewport.width * viewport.height;
  679. }
  680. return _maybeWrapWithSemantics(
  681. SizedBox(
  682. width: width,
  683. height: height,
  684. child: FittedBox(
  685. fit: widget.fit,
  686. alignment: widget.alignment,
  687. clipBehavior: widget.clipBehavior,
  688. child: SizedBox.fromSize(
  689. size: viewport.size,
  690. child: RawPicture(
  691. _picture,
  692. matchTextDirection: widget.matchTextDirection,
  693. allowDrawingOutsideViewBox: widget.allowDrawingOutsideViewBox,
  694. ),
  695. ),
  696. ),
  697. ),
  698. );
  699. }
  700. return _maybeWrapWithSemantics(
  701. widget.placeholderBuilder == null
  702. ? _getDefaultPlaceholder(context, widget.width, widget.height)
  703. : widget.placeholderBuilder(context),
  704. );
  705. }
  706. Widget _getDefaultPlaceholder(
  707. BuildContext context, double width, double height) {
  708. if (width != null || height != null) {
  709. return SizedBox(width: width, height: height);
  710. }
  711. return SvgPicture.defaultPlaceholderBuilder(context);
  712. }
  713. @override
  714. void debugFillProperties(DiagnosticPropertiesBuilder description) {
  715. super.debugFillProperties(description);
  716. description.add(
  717. DiagnosticsProperty<PictureStream>('stream', _pictureStream),
  718. );
  719. }
  720. }