├── golden
├── 0.png
├── 1.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
├── 9.png
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
├── 16.png
├── 17.png
├── 18.png
├── 19.png
├── 20.png
├── 21.png
├── 22.png
├── 23.png
├── 24.png
├── 25.png
├── 26.png
├── 27.png
├── 28.png
├── 29.png
├── 30.png
├── 31.png
├── 32.png
├── 33.png
├── 34.png
├── 35.png
├── 36.png
├── 37.png
├── 38.png
├── 39.png
├── 40.png
├── 41.png
├── 42.png
└── 43.png
├── .gitignore
├── lib
├── path_drawing.dart
└── src
│ ├── parse_path.dart
│ ├── trim_path.dart
│ └── dash_path.dart
├── example
├── README.md
├── pubspec.yaml
├── .gitignore
├── analysis_options.yaml
├── .metadata
└── lib
│ └── main.dart
├── .metadata
├── .travis.yml
├── test
├── render_path_test.dart
├── trim_path_test.dart
└── dash_path_test.dart
├── pubspec.yaml
├── path_drawing.iml
├── LICENSE
├── CHANGELOG.md
├── path_drawing_android.iml
├── README.md
├── tool
└── path_to_image.dart
└── analysis_options.yaml
/golden/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/0.png
--------------------------------------------------------------------------------
/golden/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/1.png
--------------------------------------------------------------------------------
/golden/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/2.png
--------------------------------------------------------------------------------
/golden/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/3.png
--------------------------------------------------------------------------------
/golden/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/4.png
--------------------------------------------------------------------------------
/golden/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/5.png
--------------------------------------------------------------------------------
/golden/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/6.png
--------------------------------------------------------------------------------
/golden/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/7.png
--------------------------------------------------------------------------------
/golden/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/8.png
--------------------------------------------------------------------------------
/golden/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/9.png
--------------------------------------------------------------------------------
/golden/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/10.png
--------------------------------------------------------------------------------
/golden/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/11.png
--------------------------------------------------------------------------------
/golden/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/12.png
--------------------------------------------------------------------------------
/golden/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/13.png
--------------------------------------------------------------------------------
/golden/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/14.png
--------------------------------------------------------------------------------
/golden/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/15.png
--------------------------------------------------------------------------------
/golden/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/16.png
--------------------------------------------------------------------------------
/golden/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/17.png
--------------------------------------------------------------------------------
/golden/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/18.png
--------------------------------------------------------------------------------
/golden/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/19.png
--------------------------------------------------------------------------------
/golden/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/20.png
--------------------------------------------------------------------------------
/golden/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/21.png
--------------------------------------------------------------------------------
/golden/22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/22.png
--------------------------------------------------------------------------------
/golden/23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/23.png
--------------------------------------------------------------------------------
/golden/24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/24.png
--------------------------------------------------------------------------------
/golden/25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/25.png
--------------------------------------------------------------------------------
/golden/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/26.png
--------------------------------------------------------------------------------
/golden/27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/27.png
--------------------------------------------------------------------------------
/golden/28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/28.png
--------------------------------------------------------------------------------
/golden/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/29.png
--------------------------------------------------------------------------------
/golden/30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/30.png
--------------------------------------------------------------------------------
/golden/31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/31.png
--------------------------------------------------------------------------------
/golden/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/32.png
--------------------------------------------------------------------------------
/golden/33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/33.png
--------------------------------------------------------------------------------
/golden/34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/34.png
--------------------------------------------------------------------------------
/golden/35.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/35.png
--------------------------------------------------------------------------------
/golden/36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/36.png
--------------------------------------------------------------------------------
/golden/37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/37.png
--------------------------------------------------------------------------------
/golden/38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/38.png
--------------------------------------------------------------------------------
/golden/39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/39.png
--------------------------------------------------------------------------------
/golden/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/40.png
--------------------------------------------------------------------------------
/golden/41.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/41.png
--------------------------------------------------------------------------------
/golden/42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/42.png
--------------------------------------------------------------------------------
/golden/43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dnfield/flutter_path_drawing/HEAD/golden/43.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 | .idea/
7 |
8 | build/
9 | coverage/
10 |
11 | .flutter-plugins
12 |
13 | pubspec.lock
14 |
--------------------------------------------------------------------------------
/lib/path_drawing.dart:
--------------------------------------------------------------------------------
1 | export 'package:path_drawing/src/dash_path.dart';
2 | export 'package:path_drawing/src/parse_path.dart';
3 | export 'package:path_drawing/src/trim_path.dart';
4 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 1d915bacc0077bdd38869d480f12c71377b43157
8 | channel: master
9 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: A new Flutter project.
3 | publish_to: none
4 |
5 | environment:
6 | sdk: '>=2.12.0-0 <3.0.0'
7 | flutter: '>=1.24.0-7.0 <2.0.0'
8 |
9 | dependencies:
10 | path_drawing:
11 | path: ../
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 | flutter_lints: ^2.0.1
19 |
20 | flutter:
21 | uses-material-design: true
22 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 |
31 | ios/
32 | android/
33 | linux/
34 | macos/
35 | web/
36 | windows/
37 |
38 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - linux
3 | sudo: false
4 | addons:
5 | apt:
6 | # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18
7 | sources:
8 | - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version
9 | packages:
10 | - libstdc++6
11 | - fonts-droid
12 | before_script:
13 | - git clone https://github.com/flutter/flutter.git -b dev --single-branch
14 | - ./flutter/bin/flutter doctor
15 | - gem install coveralls-lcov
16 | script:
17 | - ./flutter/bin/flutter analyze
18 | - ./flutter/bin/flutter test --coverage
19 | after_success:
20 | - coveralls-lcov coverage/lcov.info
21 | cache:
22 | directories:
23 | - $HOME/.pub-cache
24 |
--------------------------------------------------------------------------------
/test/render_path_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:typed_data';
3 |
4 | import 'package:path/path.dart';
5 |
6 | import 'package:test/test.dart';
7 |
8 | import '../tool/path_to_image.dart';
9 |
10 | void main() {
11 | test('Path rendering matches golden files', () async {
12 | for (int i = 0; i < paths.length; i++) {
13 | final Uint8List bytes = await getPathPngBytes(paths[i]);
14 | final File golden = File(join(
15 | dirname(Platform.script.path),
16 | dirname(Platform.script.path).endsWith('test') ? '..' : '',
17 | 'golden',
18 | '$i.png',
19 | ));
20 | golden.writeAsBytesSync(bytes);
21 | final Uint8List goldenBytes = await golden.readAsBytes();
22 |
23 | expect(bytes, orderedEquals(goldenBytes));
24 | }
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: path_drawing
2 | version: 1.0.1
3 | description: >
4 | A flutter library to help with (Canvas) Path creation and manipulation
5 | homepage: https://github.com/dnfield/flutter_path_drawing
6 |
7 | dependencies:
8 | vector_math: ^2.1.0
9 | meta: ^1.3.0
10 | path_parsing: ^1.0.1
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | path: ^1.8.0
16 | test: ^1.16.0
17 | flutter_test:
18 | sdk: flutter
19 |
20 |
21 | # For information on the generic Dart part of this file, see the
22 | # following page: https://www.dartlang.org/tools/pub/pubspec
23 |
24 | # The following section is specific to Flutter.
25 | flutter:
26 | # There's no need to include these as we don't use them.
27 | # This fixes an issue when our plugin is imported into a project that also
28 | # uses no material icons.
29 | uses-material-design: false
30 |
31 | environment:
32 | sdk: '>=2.12.0 <3.0.0'
33 | flutter: '>=1.24.0-7.0'
34 |
--------------------------------------------------------------------------------
/path_drawing.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Dan Field
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/test/trim_path_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' show Path, Rect;
2 |
3 | import 'package:flutter_test/flutter_test.dart';
4 | import 'package:path_drawing/path_drawing.dart';
5 |
6 | void main() {
7 | test('TrimPath tests', () {
8 | final Path singleSegmentLine = Path()..lineTo(10.0, 10.0);
9 | final Path multiSegmentLine = Path()
10 | ..lineTo(10.0, 10.0)
11 | ..moveTo(50.0, 10.0)
12 | ..lineTo(20.0, 20.0);
13 |
14 | expect(trimPath(singleSegmentLine, 0.5).getBounds(),
15 | const Rect.fromLTRB(5, 5, 10, 10));
16 | expect(trimPath(singleSegmentLine, 1.0).getBounds(), Rect.zero);
17 | expect(trimPath(singleSegmentLine, 0.0).getBounds(),
18 | singleSegmentLine.getBounds());
19 | expect(trimPath(singleSegmentLine, 0.5, origin: PathTrimOrigin.end),
20 | isNotNull);
21 | expect(trimPath(singleSegmentLine, 0.5, firstOnly: false), isNotNull);
22 |
23 | expect(trimPath(multiSegmentLine, 0.5, firstOnly: false).getBounds(),
24 | const Rect.fromLTRB(5, 5, 35, 20));
25 | expect(trimPath(multiSegmentLine, 0.5).getBounds(),
26 | const Rect.fromLTRB(5, 5, 10, 10));
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/lib/src/parse_path.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' show Path;
2 |
3 | import 'package:path_parsing/path_parsing.dart';
4 |
5 | /// Creates a [Path] object from an SVG data string.
6 | ///
7 | /// Passing an empty string will result in an empty path.
8 | Path parseSvgPathData(String svg) {
9 | if (svg == '') {
10 | return Path();
11 | }
12 |
13 | final SvgPathStringSource parser = SvgPathStringSource(svg);
14 | final FlutterPathProxy path = FlutterPathProxy();
15 | final SvgPathNormalizer normalizer = SvgPathNormalizer();
16 | for (PathSegmentData seg in parser.parseSegments()) {
17 | normalizer.emitSegment(seg, path);
18 | }
19 | return path.path;
20 | }
21 |
22 | /// A [PathProxy] that takes the output of the path parsing library
23 | /// and maps it to a dart:ui [Path].
24 | class FlutterPathProxy extends PathProxy {
25 | FlutterPathProxy({Path? p}) : path = p ?? Path();
26 |
27 | final Path path;
28 |
29 | @override
30 | void close() {
31 | path.close();
32 | }
33 |
34 | @override
35 | void cubicTo(
36 | double x1, double y1, double x2, double y2, double x3, double y3) {
37 | path.cubicTo(x1, y1, x2, y2, x3, y3);
38 | }
39 |
40 | @override
41 | void lineTo(double x, double y) {
42 | path.lineTo(x, y);
43 | }
44 |
45 | @override
46 | void moveTo(double x, double y) {
47 | path.moveTo(x, y);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/dash_path_test.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: prefer_const_constructors
2 |
3 | import 'dart:ui' show Path;
4 |
5 | import 'package:path_drawing/path_drawing.dart';
6 |
7 | import 'package:test/test.dart';
8 |
9 | void main() {
10 | test('CircularList tests', () {
11 | final List ints = [1, 2, 3];
12 |
13 | final CircularIntervalList list = CircularIntervalList(ints);
14 |
15 | expect(list.next, 1);
16 | expect(list.next, 2);
17 | expect(list.next, 3);
18 | expect(list.next, 1);
19 | expect(list.next, 2);
20 | expect(list.next, 3);
21 | });
22 |
23 | test('DashPath tests', () {
24 | final Path singleSegmentLine = Path()..lineTo(10.0, 10.0);
25 | final CircularIntervalList dashArray =
26 | CircularIntervalList([1.0, 5.0]);
27 |
28 | expect(dashPath(singleSegmentLine, dashArray: dashArray), isNotNull);
29 | expect(
30 | dashPath(
31 | singleSegmentLine,
32 | dashArray: dashArray,
33 | dashOffset: DashOffset.percentage(5.0),
34 | ),
35 | isNotNull,
36 | );
37 | });
38 |
39 | group('DashOffset supports value equality', () {
40 | test('absolute', () {
41 | expect(
42 | DashOffset.absolute(20),
43 | equals(DashOffset.absolute(20)),
44 | );
45 | });
46 |
47 | test('percentage', () {
48 | expect(
49 | DashOffset.percentage(0.2),
50 | equals(DashOffset.percentage(0.2)),
51 | );
52 | });
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.0.1
4 |
5 | - Bump path parsing dependency to fix bugs in path parsing. Update goldens.
6 | - Analysis cleanup.
7 | - Remove unnecessary platform folders from example.
8 |
9 | ## 1.0.0
10 |
11 | - Stable release.
12 |
13 | ## 0.5.1+1
14 |
15 | - Make `DashOffset` comparable.
16 |
17 | ## 0.5.1
18 |
19 | - Bump path_parsing dependency.
20 |
21 | ## 0.5.0
22 |
23 | - Stable nullsafe release.
24 |
25 | ## 0.5.0-nullsafety.0
26 |
27 | - Null safe migration, general modernization
28 |
29 | ## 0.4.1+1
30 |
31 | - Set uses-material-design to false.
32 |
33 | ## 0.4.1
34 |
35 | - Consume latest path_parsing version.
36 |
37 | ## 0.4.0
38 |
39 | - Implement path trimming routine
40 | - Update example to demonstrate dash paths and trim paths.
41 | - Remove `new` keyword.
42 |
43 | ## 0.3.1
44 |
45 | - Consume updated version of parsing library
46 | - Fix layout to conform to newer Flutter package requirements.
47 |
48 | ## 0.3.0
49 |
50 | - Split out parsing logic into separate package that does not depend on Flutter
51 | - Consume latest version of package, which fixes bug in smooth curve parsing
52 |
53 | ## 0.2.x
54 |
55 | - 0.2.4: Fix bug in exponent validation logic
56 | - 0.2.3: Fix bugs in _decomposeCubic - `Offset.scale` and `Offset.translate` related
57 | - 0.2.2: Fix handling of null dashOffset
58 | - 0.2.1: Add support for dashed paths, update docs
59 | - 0.2.0: _not published_
60 |
61 | ## 0.1.x
62 |
63 | - 0.1.1: Fix bug in matrix translate logic
64 | - 0.1.0: Initial release
65 |
--------------------------------------------------------------------------------
/path_drawing_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
8 | channel: master
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
17 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
18 | - platform: android
19 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
20 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
21 | - platform: ios
22 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
23 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
24 | - platform: linux
25 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
26 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
27 | - platform: macos
28 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
29 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
30 | - platform: web
31 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
32 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
33 | - platform: windows
34 | create_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
35 | base_revision: 8e77cb4341d8635a0ead34bf17ca80fb74d5ead9
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # path_drawing
2 |
3 | [](https://pub.dev/packages/path_drawing)
4 |
5 | A Flutter library to assist with creating and manipulating paths.
6 |
7 | Currently supports parsing a `Path` from an SVG path data string
8 | (including normalizing the path commands to be amenable to Flutter's exposed
9 | Path methods).
10 |
11 | Dash paths has an initial implementation that relies on flutter 0.3.6 at a minimum.
12 |
13 | Planned for future release(s):
14 |
15 | - Trim paths
16 |
17 | ## Example
18 |
19 | Parse some path from svg string:
20 |
21 | ```dart
22 | import 'package:path_drawing/path_drawing.dart';
23 |
24 | final trianglePath = parseSvgPathData('M150 0 L75 200 L225 200 Z');
25 | ```
26 |
27 | Create [CustomPainter](https://api.flutter.dev/flutter/rendering/CustomPainter-class.html):
28 |
29 | ```dart
30 | class FilledPathPainter extends CustomPainter {
31 | const FilledPathPainter({
32 | @required this.path,
33 | @required this.color,
34 | });
35 |
36 | final Path path;
37 | final Color color;
38 |
39 | @override
40 | bool shouldRepaint(FilledPathPainter oldDelegate) =>
41 | oldDelegate.path != path || oldDelegate.color != color;
42 |
43 | @override
44 | void paint(Canvas canvas, Size size) {
45 | canvas.drawPath(
46 | path,
47 | Paint()
48 | ..color = color
49 | ..style = PaintingStyle.fill,
50 | );
51 | }
52 |
53 | @override
54 | bool hitTest(Offset position) => path.contains(position);
55 | }
56 | ```
57 |
58 | Use it inside [CustomPaint](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html):
59 |
60 | ```dart
61 | class MyWidget extends StatelessWidget {
62 | @override
63 | Widget build(BuildContext context) {
64 | return GestureDetector(
65 | onTap: () => print('tap'),
66 | child: CustomPaint(
67 | painter: FilledPathPainter(
68 | path: trianglePath,
69 | color: Colors.blue,
70 | ),
71 | ),
72 | );
73 | }
74 | }
75 | ```
76 |
77 | More examples can be found in [example folder](example/lib/main.dart)
78 |
--------------------------------------------------------------------------------
/lib/src/trim_path.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | /// The point on the path to trim from.
4 | enum PathTrimOrigin {
5 | /// Specifies that trimming should start from the first point in a segment.
6 | begin,
7 |
8 | /// Specifies that trimming should start from the last point in a segment.
9 | end
10 | }
11 |
12 | /// Trims `percentage` of the `source` [Path] away and returns a new path.
13 | ///
14 | /// The `percentage` parameter will be clamped between 0..1 and must not be null.
15 | ///
16 | /// Use the `firstOnly` parameter to specify whether this should apply only
17 | /// to the first segment of the path (and thus return only the first trimmed
18 | /// segment) or all segments of the path. Multi-segment paths (i.e. paths with a
19 | /// move verb) will all be trimmed if this is false; otherwise, a trimmed version
20 | /// of only the first path segment will be returned. It must not be null.
21 | ///
22 | /// The `origin` parameter allows the user to control which end of the path will be
23 | /// trimmed. It must not be null.
24 | ///
25 | /// If `source` is empty, an empty path will be returned.
26 | Path trimPath(
27 | Path source,
28 | double percentage, {
29 | bool firstOnly = true,
30 | PathTrimOrigin origin = PathTrimOrigin.begin,
31 | }) {
32 | assert(percentage != null); // ignore: unnecessary_null_comparison
33 | assert(firstOnly != null); // ignore: unnecessary_null_comparison
34 | assert(origin != null); // ignore: unnecessary_null_comparison
35 |
36 | percentage = percentage.clamp(0.0, 1.0);
37 | if (percentage == 1.0) {
38 | return Path();
39 | }
40 | if (percentage == 0.0) {
41 | return Path.from(source);
42 | }
43 | if (origin == PathTrimOrigin.end) {
44 | percentage = 1.0 - percentage;
45 | }
46 |
47 | final Path dest = Path();
48 | for (final PathMetric metric in source.computeMetrics()) {
49 | switch (origin) {
50 | case PathTrimOrigin.end:
51 | dest.addPath(
52 | metric.extractPath(0.0, metric.length * percentage),
53 | Offset.zero,
54 | );
55 | break;
56 | case PathTrimOrigin.begin:
57 | dest.addPath(
58 | metric.extractPath(metric.length * percentage, metric.length),
59 | Offset.zero,
60 | );
61 | break;
62 | }
63 | if (firstOnly) {
64 | break;
65 | }
66 | }
67 |
68 | return dest;
69 | }
70 |
--------------------------------------------------------------------------------
/lib/src/dash_path.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | /// Creates a new path that is drawn from the segments of `source`.
4 | ///
5 | /// Dash intervals are controled by the `dashArray` - see [CircularIntervalList]
6 | /// for examples.
7 | ///
8 | /// `dashOffset` specifies an initial starting point for the dashing.
9 | ///
10 | /// Passing a `source` that is an empty path will return an empty path.
11 | Path dashPath(
12 | Path source, {
13 | required CircularIntervalList dashArray,
14 | DashOffset? dashOffset,
15 | }) {
16 | assert(dashArray != null); // ignore: unnecessary_null_comparison
17 |
18 | dashOffset = dashOffset ?? const DashOffset.absolute(0.0);
19 | // TODO: Is there some way to determine how much of a path would be visible today?
20 |
21 | final Path dest = Path();
22 | for (final PathMetric metric in source.computeMetrics()) {
23 | double distance = dashOffset._calculate(metric.length);
24 | bool draw = true;
25 | while (distance < metric.length) {
26 | final double len = dashArray.next;
27 | if (draw) {
28 | dest.addPath(metric.extractPath(distance, distance + len), Offset.zero);
29 | }
30 | distance += len;
31 | draw = !draw;
32 | }
33 | }
34 |
35 | return dest;
36 | }
37 |
38 | enum _DashOffsetType { Absolute, Percentage }
39 |
40 | /// Specifies the starting position of a dash array on a path, either as a
41 | /// percentage or absolute value.
42 | ///
43 | /// The internal value will be guaranteed to not be null.
44 | class DashOffset {
45 | /// Create a DashOffset that will be measured as a percentage of the length
46 | /// of the segment being dashed.
47 | ///
48 | /// `percentage` will be clamped between 0.0 and 1.0.
49 | DashOffset.percentage(double percentage)
50 | : _rawVal = percentage.clamp(0.0, 1.0),
51 | _dashOffsetType = _DashOffsetType.Percentage;
52 |
53 | /// Create a DashOffset that will be measured in terms of absolute pixels
54 | /// along the length of a [Path] segment.
55 | const DashOffset.absolute(double start)
56 | : _rawVal = start,
57 | _dashOffsetType = _DashOffsetType.Absolute;
58 |
59 | final double _rawVal;
60 | final _DashOffsetType _dashOffsetType;
61 |
62 | double _calculate(double length) {
63 | return _dashOffsetType == _DashOffsetType.Absolute
64 | ? _rawVal
65 | : length * _rawVal;
66 | }
67 |
68 | @override
69 | bool operator ==(Object other) {
70 | if (identical(this, other)) {
71 | return true;
72 | }
73 |
74 | return other is DashOffset &&
75 | other._rawVal == _rawVal &&
76 | other._dashOffsetType == _dashOffsetType;
77 | }
78 |
79 | @override
80 | int get hashCode => Object.hash(_rawVal, _dashOffsetType);
81 | }
82 |
83 | /// A circular array of dash offsets and lengths.
84 | ///
85 | /// For example, the array `[5, 10]` would result in dashes 5 pixels long
86 | /// followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would
87 | /// result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap,
88 | /// a 10 pixel dash, etc.
89 | ///
90 | /// Note that this does not quite conform to an [Iterable], because it does
91 | /// not have a moveNext.
92 | class CircularIntervalList {
93 | CircularIntervalList(this._vals);
94 |
95 | final List _vals;
96 | int _idx = 0;
97 |
98 | T get next {
99 | if (_idx >= _vals.length) {
100 | _idx = 0;
101 | }
102 | return _vals[_idx++];
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tool/path_to_image.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 | import 'dart:math' show max;
4 | import 'dart:typed_data';
5 | import 'dart:ui';
6 |
7 | import 'package:path/path.dart';
8 |
9 | import 'package:path_drawing/path_drawing.dart';
10 |
11 | const List paths = [
12 | 'M100,200 L3,4',
13 | 'M100,200 l3,4',
14 | 'M100,200 H3',
15 | 'M100,200 h3',
16 | 'M100,200 V3',
17 | 'M100,200 v3',
18 | 'M100,200 C3,4,5,6,7,8',
19 | 'M100,200 c3,4,5,6,7,8',
20 | 'M100,200 S3,4,5,6',
21 | 'M100,200 s3,4,5,6',
22 | 'M100,200 Q3,4,5,6',
23 | 'M100,200 q3,4,5,6',
24 | 'M100,200 T3,4',
25 | 'M100,200 t3,4',
26 | 'M100,200 A3,4,5,0,0,6,7',
27 | 'M100,200 A3,4,5,1,0,6,7',
28 | 'M100,200 A3,4,5,0,1,6,7',
29 | 'M100,200 A3,4,5,1,1,6,7',
30 | 'M100,200 a3,4,5,0,0,6,7',
31 | 'M100,200 a3,4,5,0,1,6,7',
32 | 'M100,200 a3,4,5,1,0,6,7',
33 | 'M100,200 a3,4,5,1,1,6,7',
34 | 'M100,200 a3,4,5,006,7',
35 | 'M100,200 a3,4,5,016,7',
36 | 'M100,200 a3,4,5,106,7',
37 | 'M100,200 a3,4,5,116,7',
38 | '''M19.0281,19.40466 20.7195,19.40466 20.7195,15.71439 24.11486,15.71439 24.11486,14.36762 20.7195,14.36762
39 | 20.7195,11.68641 24.74134,11.68641 24.74134,10.34618 19.0281,10.34618 z''',
40 | 'M100,200 a0,4,5,0,0,10,0 a4,0,5,0,0,0,10 a0,0,5,0,0,-10,0 z',
41 | 'M.1 .2 L.3 .4 .5 .6',
42 | 'M1,1h2,3',
43 | 'M1,1H2,3',
44 | 'M1,1v2,3',
45 | 'M1,1V2,3',
46 | 'M1,1c2,3 4,5 6,7 8,9 10,11 12,13',
47 | 'M1,1C2,3 4,5 6,7 8,9 10,11 12,13',
48 | 'M1,1s2,3 4,5 6,7 8,9',
49 | 'M1,1S2,3 4,5 6,7 8,9',
50 | 'M1,1q2,3 4,5 6,7 8,9',
51 | 'M1,1Q2,3 4,5 6,7 8,9',
52 | 'M1,1t2,3 4,5',
53 | 'M1,1T2,3 4,5',
54 | 'M1,1a2,3,4,0,0,5,6 7,8,9,0,0,10,11',
55 | 'M1,1A2,3,4,0,0,5,6 7,8,9,0,0,10,11',
56 | 'm18 11.8a.41.41 0 0 1 .24.08l.59.43h.05.72a.4.4 0 0 1 .39.28l.22.69a.08.08 0 0 0 0 0l.58.43a.41.41 0 0 1 .15.45l-.22.68a.09.09 0 0 0 0 .07l.22.68a.4.4 0 0 1 -.15.46l-.58.42a.1.1 0 0 0 0 0l-.22.68a.41.41 0 0 1 -.38.29h-.79l-.58.43a.41.41 0 0 1 -.24.08.46.46 0 0 1 -.24-.08l-.58-.43h-.06-.72a.41.41 0 0 1 -.39-.28l-.22-.68a.1.1 0 0 0 0 0l-.58-.43a.42.42 0 0 1 -.15-.46l.23-.67v-.02l-.29-.68a.43.43 0 0 1 .15-.46l.58-.42a.1.1 0 0 0 0-.05l.27-.69a.42.42 0 0 1 .39-.28h.78l.58-.43a.43.43 0 0 1 .25-.09m0-1a1.37 1.37 0 0 0 -.83.27l-.34.25h-.43a1.42 1.42 0 0 0 -1.34 1l-.13.4-.35.25a1.42 1.42 0 0 0 -.51 1.58l.13.4-.13.4a1.39 1.39 0 0 0 .52 1.59l.34.25.13.4a1.41 1.41 0 0 0 1.34 1h.43l.34.26a1.44 1.44 0 0 0 .83.27 1.38 1.38 0 0 0 .83-.28l.35-.24h.43a1.4 1.4 0 0 0 1.33-1l.13-.4.35-.26a1.39 1.39 0 0 0 .51-1.57l-.13-.4.13-.41a1.4 1.4 0 0 0 -.51-1.56l-.35-.25-.13-.41a1.4 1.4 0 0 0 -1.34-1h-.42l-.34-.26a1.43 1.43 0 0 0 -.84-.28z',
57 | ];
58 | final Paint blackStrokePaint = Paint()
59 | ..color = const Color.fromARGB(255, 0, 0, 0)
60 | ..strokeWidth = 1.0
61 | ..style = PaintingStyle.stroke;
62 | final Paint whiteFillPaint = Paint()
63 | ..color = const Color.fromARGB(255, 255, 255, 255)
64 | ..style = PaintingStyle.fill;
65 |
66 | Future getPathPngBytes(String pathString) async {
67 | final PictureRecorder rec = PictureRecorder();
68 | final Canvas canvas = Canvas(rec);
69 |
70 | final Path p = parseSvgPathData(pathString);
71 |
72 | final Rect bounds = p.getBounds();
73 | const double scaleFactor = 5.0;
74 | canvas.scale(scaleFactor);
75 | canvas.drawPaint(whiteFillPaint);
76 |
77 | canvas.drawPath(p, blackStrokePaint);
78 |
79 | final Picture pict = rec.endRecording();
80 |
81 | final int imgWidth =
82 | (max(bounds.width, bounds.right) * 2 * scaleFactor).ceil();
83 | final int imgHeight =
84 | (max(bounds.height, bounds.bottom) * 2 * scaleFactor).ceil();
85 |
86 | final Image image = await pict.toImage(imgWidth, imgHeight);
87 | final ByteData bytes = (await image.toByteData(format: ImageByteFormat.png))!;
88 |
89 | return bytes.buffer.asUint8List();
90 | }
91 |
92 | Future main() async {
93 | for (int i = 0; i < paths.length; i++) {
94 | final String pathName =
95 | join(dirname(Platform.script.path), 'golden', '$i.png');
96 | final File output = File(pathName);
97 | await output.writeAsBytes(await getPathPngBytes(paths[i]));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # Specify analysis options.
2 | #
3 | # Until there are meta linter rules, each desired lint must be explicitly enabled.
4 | # See: https://github.com/dart-lang/linter/issues/288
5 | #
6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/
7 | # See the configuration guide for more
8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer
9 | #
10 | # There are four similar analysis options files in the flutter repos:
11 | # - analysis_options.yaml (this file)
12 | # - packages/flutter/lib/analysis_options_user.yaml
13 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml
14 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml
15 | #
16 | # This file contains the analysis options used by Flutter tools, such as IntelliJ,
17 | # Android Studio, and the `flutter analyze` command.
18 | #
19 | # The flutter/plugins repo contains a copy of this file, which should be kept
20 | # in sync with this file.
21 |
22 | analyzer:
23 | strong-mode:
24 | implicit-dynamic: false
25 | errors:
26 | # treat missing required parameters as a warning (not a hint)
27 | missing_required_param: warning
28 | # treat missing returns as a warning (not a hint)
29 | missing_return: warning
30 | # allow having TODOs in the code
31 | todo: ignore
32 | exclude:
33 | # for Travis - don't try to analyze the flutter repo
34 | - 'flutter/**'
35 |
36 |
37 | linter:
38 | rules:
39 | # these rules are documented on and in the same order as
40 | # the Dart Lint rules page to make maintenance easier
41 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml
42 | - always_declare_return_types
43 | - always_put_control_body_on_new_line
44 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
45 | - always_require_non_null_named_parameters
46 | - always_specify_types
47 | - annotate_overrides
48 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types
49 | # - avoid_bool_literals_in_conditional_expressions # not yet tested
50 | # - avoid_catches_without_on_clauses # we do this commonly
51 | # - avoid_catching_errors # we do this commonly
52 | - avoid_classes_with_only_static_members
53 | - avoid_empty_else
54 | - avoid_function_literals_in_foreach_calls
55 | - avoid_init_to_null
56 | - avoid_null_checks_in_equality_operators
57 | # - avoid_positional_boolean_parameters # not yet tested
58 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
59 | - avoid_relative_lib_imports
60 | - avoid_renaming_method_parameters
61 | - avoid_return_types_on_setters
62 | # - avoid_returning_null # we do this commonly
63 | # - avoid_returning_this # https://github.com/dart-lang/linter/issues/842
64 | # - avoid_setters_without_getters # not yet tested
65 | # - avoid_single_cascade_in_expression_statements # not yet tested
66 | - avoid_slow_async_io
67 | # - avoid_types_as_parameter_names # https://github.com/dart-lang/linter/pull/954/files
68 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types
69 | # - avoid_unused_constructor_parameters # https://github.com/dart-lang/linter/pull/847
70 | - await_only_futures
71 | - camel_case_types
72 | - cancel_subscriptions
73 | # - cascade_invocations # not yet tested
74 | # - close_sinks # https://github.com/flutter/flutter/issues/5789
75 | # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153
76 | # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
77 | - control_flow_in_finally
78 | - directives_ordering
79 | - empty_catches
80 | - empty_constructor_bodies
81 | - empty_statements
82 | - hash_and_equals
83 | - implementation_imports
84 | # - invariant_booleans # https://github.com/flutter/flutter/issues/5790
85 | - iterable_contains_unrelated_type
86 | # - join_return_with_assignment # not yet tested
87 | - library_names
88 | - library_prefixes
89 | - list_remove_unrelated_type
90 | # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791
91 | - no_adjacent_strings_in_list
92 | - no_duplicate_case_values
93 | - non_constant_identifier_names
94 | # - omit_local_variable_types # opposite of always_specify_types
95 | # - one_member_abstracts # too many false positives
96 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792
97 | - overridden_fields
98 | - package_api_docs
99 | - package_names
100 | - package_prefixed_library_names
101 | # - parameter_assignments # we do this commonly
102 | - prefer_adjacent_string_concatenation
103 | - prefer_asserts_in_initializer_lists
104 | - prefer_collection_literals
105 | - prefer_conditional_assignment
106 | - prefer_const_constructors
107 | - prefer_const_constructors_in_immutables
108 | - prefer_const_declarations
109 | - prefer_const_literals_to_create_immutables
110 | # - prefer_constructors_over_static_methods # not yet tested
111 | - prefer_contains
112 | # - prefer_equal_for_default_values # not yet tested
113 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
114 | - prefer_final_fields
115 | - prefer_final_locals
116 | - prefer_foreach
117 | # - prefer_function_declarations_over_variables # not yet tested
118 | - prefer_initializing_formals
119 | # - prefer_interpolation_to_compose_strings # not yet tested
120 | - prefer_is_empty
121 | - prefer_is_not_empty
122 | - prefer_single_quotes
123 | - prefer_typing_uninitialized_variables
124 | - recursive_getters
125 | - slash_for_doc_comments
126 | - sort_constructors_first
127 | - sort_unnamed_constructors_first
128 | - test_types_in_equals
129 | - throw_in_finally
130 | # - type_annotate_public_apis # subset of always_specify_types
131 | - type_init_formals
132 | # - unawaited_futures # https://github.com/flutter/flutter/issues/5793
133 | - unnecessary_brace_in_string_interps
134 | - unnecessary_getters_setters
135 | # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498
136 | - unnecessary_null_aware_assignments
137 | - unnecessary_null_in_if_null_operators
138 | - unnecessary_overrides
139 | - unnecessary_parenthesis
140 | # - unnecessary_statements # not yet tested
141 | - unnecessary_this
142 | - unrelated_type_equality_checks
143 | - use_rethrow_when_possible
144 | # - use_setters_to_change_properties # not yet tested
145 | # - use_string_buffers # https://github.com/dart-lang/linter/pull/664
146 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
147 | - valid_regexps
148 | - unnecessary_new
149 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:path_drawing/path_drawing.dart';
3 |
4 | void main() => runApp(const MyApp());
5 |
6 | class MyApp extends StatelessWidget {
7 | const MyApp({Key? key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return MaterialApp(
12 | title: 'Flutter Demo',
13 | theme: ThemeData(
14 | primarySwatch: Colors.blue,
15 | ),
16 | home: const MyHomePage(title: 'Flutter Demo Home Page'),
17 | );
18 | }
19 | }
20 |
21 | class MyHomePage extends StatefulWidget {
22 | const MyHomePage({Key? key, required this.title}) : super(key: key);
23 |
24 | final String title;
25 |
26 | @override
27 | State createState() => _MyHomePageState();
28 | }
29 |
30 | class _MyHomePageState extends State {
31 | late int index;
32 | late double _trimPercent;
33 | late PathTrimOrigin _trimOrigin;
34 |
35 | @override
36 | void initState() {
37 | super.initState();
38 | index = 0;
39 | _trimPercent = 0.2;
40 | _trimOrigin = PathTrimOrigin.begin;
41 | }
42 |
43 | String get currPath => paths[index];
44 |
45 | void nextPath() {
46 | setState(() => index = index >= paths.length - 1 ? 0 : index + 1);
47 | }
48 |
49 | void prevPath() {
50 | setState(() => index = index == 0 ? paths.length - 1 : index - 1);
51 | }
52 |
53 | void setTrimPercent(double value) {
54 | setState(() {
55 | _trimPercent = value;
56 | });
57 | }
58 |
59 | void toggleTrimOrigin(PathTrimOrigin? value) {
60 | setState(() {
61 | switch (_trimOrigin) {
62 | case PathTrimOrigin.begin:
63 | _trimOrigin = PathTrimOrigin.end;
64 | break;
65 | case PathTrimOrigin.end:
66 | _trimOrigin = PathTrimOrigin.begin;
67 | break;
68 | }
69 | });
70 | }
71 |
72 | @override
73 | Widget build(BuildContext context) {
74 | return DefaultTabController(
75 | length: 3,
76 | child: Scaffold(
77 | appBar: AppBar(
78 | title: Text(widget.title),
79 | bottom: const TabBar(
80 | tabs: [
81 | Tab(text: 'Path Trim'),
82 | Tab(text: 'Path Dash'),
83 | Tab(text: 'Path Parse'),
84 | ],
85 | ),
86 | ),
87 | body: TabBarView(
88 | children: [
89 | Stack(
90 | children: [
91 | CustomPaint(
92 | painter: TrimPathPainter(_trimPercent, _trimOrigin)),
93 | Align(
94 | alignment: Alignment.bottomCenter,
95 | child: Column(
96 | mainAxisAlignment: MainAxisAlignment.end,
97 | children: [
98 | Slider(
99 | value: _trimPercent,
100 | onChanged: (double value) => setTrimPercent(value),
101 | ),
102 | RadioListTile(
103 | title: Text(PathTrimOrigin.begin.toString()),
104 | value: PathTrimOrigin.begin,
105 | groupValue: _trimOrigin,
106 | onChanged: toggleTrimOrigin,
107 | ),
108 | RadioListTile(
109 | title: Text(PathTrimOrigin.end.toString()),
110 | value: PathTrimOrigin.end,
111 | groupValue: _trimOrigin,
112 | onChanged: toggleTrimOrigin,
113 | ),
114 | ],
115 | ),
116 | ),
117 | ],
118 | ),
119 | CustomPaint(painter: DashPathPainter()),
120 | Stack(
121 | children: [
122 | CustomPaint(painter: PathTestPainter(currPath)),
123 | GestureDetector(
124 | onTap: nextPath,
125 | ),
126 | ],
127 | ),
128 | ],
129 | ),
130 | ),
131 | );
132 | }
133 | }
134 |
135 | const List paths = [
136 | 'm18 11.8a.41.41 0 0 1 .24.08l.59.43h.05.72a.4.4 0 0 1 .39.28l.22.69a.08.08 0 0 0 0 0l.58.43a.41.41 0 0 1 .15.45l-.22.68a.09.09 0 0 0 0 .07l.22.68a.4.4 0 0 1 -.15.46l-.58.42a.1.1 0 0 0 0 0l-.22.68a.41.41 0 0 1 -.38.29h-.79l-.58.43a.41.41 0 0 1 -.24.08.46.46 0 0 1 -.24-.08l-.58-.43h-.06-.72a.41.41 0 0 1 -.39-.28l-.22-.68a.1.1 0 0 0 0 0l-.58-.43a.42.42 0 0 1 -.15-.46l.23-.67v-.02l-.29-.68a.43.43 0 0 1 .15-.46l.58-.42a.1.1 0 0 0 0-.05l.27-.69a.42.42 0 0 1 .39-.28h.78l.58-.43a.43.43 0 0 1 .25-.09m0-1a1.37 1.37 0 0 0 -.83.27l-.34.25h-.43a1.42 1.42 0 0 0 -1.34 1l-.13.4-.35.25a1.42 1.42 0 0 0 -.51 1.58l.13.4-.13.4a1.39 1.39 0 0 0 .52 1.59l.34.25.13.4a1.41 1.41 0 0 0 1.34 1h.43l.34.26a1.44 1.44 0 0 0 .83.27 1.38 1.38 0 0 0 .83-.28l.35-.24h.43a1.4 1.4 0 0 0 1.33-1l.13-.4.35-.26a1.39 1.39 0 0 0 .51-1.57l-.13-.4.13-.41a1.4 1.4 0 0 0 -.51-1.56l-.35-.25-.13-.41a1.4 1.4 0 0 0 -1.34-1h-.42l-.34-.26a1.43 1.43 0 0 0 -.84-.28z',
137 | '''M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z''',
138 | 'M100,200 L3,4',
139 | 'M100,200 l3,4',
140 | 'M100,200 H3',
141 | 'M100,200 h3',
142 | 'M100,200 V3',
143 | 'M100,200 v3',
144 | 'M100,200 C3,4,5,6,7,8',
145 | 'M100,200 c3,4,5,6,7,8',
146 | 'M100,200 S3,4,5,6',
147 | 'M100,200 s3,4,5,6',
148 | 'M100,200 Q3,4,5,6',
149 | 'M100,200 q3,4,5,6',
150 | 'M100,200 T3,4',
151 | 'M100,200 t3,4',
152 | 'M100,200 A3,4,5,0,0,6,7',
153 | 'M100,200 A3,4,5,1,0,6,7',
154 | 'M100,200 A3,4,5,0,1,6,7',
155 | 'M100,200 A3,4,5,1,1,6,7',
156 | 'M100,200 a3,4,5,0,0,6,7',
157 | 'M100,200 a3,4,5,0,1,6,7',
158 | 'M100,200 a3,4,5,1,0,6,7',
159 | 'M100,200 a3,4,5,1,1,6,7',
160 | 'M100,200 a3,4,5,006,7',
161 | 'M100,200 a3,4,5,016,7',
162 | 'M100,200 a3,4,5,106,7',
163 | 'M100,200 a3,4,5,116,7',
164 | '''M19.0281,19.40466 20.7195,19.40466 20.7195,15.71439 24.11486,15.71439 24.11486,14.36762 20.7195,14.36762
165 | 20.7195,11.68641 24.74134,11.68641 24.74134,10.34618 19.0281,10.34618 z''',
166 | 'M100,200 a0,4,5,0,0,10,0 a4,0,5,0,0,0,10 a0,0,5,0,0,-10,0 z',
167 | 'M.1 .2 L.3 .4 .5 .6',
168 | 'M1,1h2,3',
169 | 'M1,1H2,3',
170 | 'M1,1v2,3',
171 | 'M1,1V2,3',
172 | 'M1,1c2,3 4,5 6,7 8,9 10,11 12,13',
173 | 'M1,1C2,3 4,5 6,7 8,9 10,11 12,13',
174 | 'M1,1s2,3 4,5 6,7 8,9',
175 | 'M1,1S2,3 4,5 6,7 8,9',
176 | 'M1,1q2,3 4,5 6,7 8,9',
177 | 'M1,1Q2,3 4,5 6,7 8,9',
178 | 'M1,1t2,3 4,5',
179 | 'M1,1T2,3 4,5',
180 | 'M1,1a2,3,4,0,0,5,6 7,8,9,0,0,10,11',
181 | 'M1,1A2,3,4,0,0,5,6 7,8,9,0,0,10,11',
182 | ];
183 |
184 | final Paint black = Paint()
185 | ..color = Colors.black
186 | ..strokeWidth = 1.0
187 | ..style = PaintingStyle.stroke;
188 |
189 | class TrimPathPainter extends CustomPainter {
190 | TrimPathPainter(this.percent, this.origin);
191 |
192 | final double percent;
193 | final PathTrimOrigin origin;
194 |
195 | final Path p = Path()
196 | ..moveTo(10.0, 10.0)
197 | ..lineTo(100.0, 100.0)
198 | ..quadraticBezierTo(125.0, 20.0, 200.0, 100.0);
199 |
200 | @override
201 | bool shouldRepaint(TrimPathPainter oldDelegate) =>
202 | oldDelegate.percent != percent;
203 |
204 | @override
205 | void paint(Canvas canvas, Size size) {
206 | canvas.drawPath(trimPath(p, percent, origin: origin), black);
207 | }
208 | }
209 |
210 | class DashPathPainter extends CustomPainter {
211 | final Path p = Path()
212 | ..moveTo(10.0, 10.0)
213 | ..lineTo(100.0, 100.0)
214 | ..quadraticBezierTo(125.0, 20.0, 200.0, 100.0)
215 | ..addRect(const Rect.fromLTWH(0.0, 0.0, 50.0, 50.0));
216 |
217 | @override
218 | bool shouldRepaint(DashPathPainter oldDelegate) => true;
219 |
220 | @override
221 | void paint(Canvas canvas, Size size) {
222 | canvas.drawPath(
223 | dashPath(
224 | p,
225 | dashArray: CircularIntervalList(
226 | [5.0, 2.5],
227 | ),
228 | ),
229 | black);
230 | }
231 | }
232 |
233 | class PathTestPainter extends CustomPainter {
234 | PathTestPainter(String path) : p = parseSvgPathData(path);
235 |
236 | final Path p;
237 |
238 | @override
239 | bool shouldRepaint(PathTestPainter oldDelegate) => true;
240 |
241 | @override
242 | void paint(Canvas canvas, Size size) {
243 | canvas.drawPath(p, black);
244 | }
245 | }
246 |
--------------------------------------------------------------------------------