├── .github └── workflows │ └── main.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ └── Icon-512.png │ ├── index.html │ └── manifest.json ├── lib ├── src │ ├── parser.dart │ ├── scanner.dart │ └── tokens.dart └── svg_path_parser.dart ├── pubspec.yaml └── test ├── parser_test.dart ├── scanner_test.dart └── utils.dart /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Flutter Test package 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | - uses: actions/checkout@v1 23 | - uses: actions/setup-java@v1 24 | with: 25 | java-version: '12.x' 26 | - uses: subosito/flutter-action@v1 27 | with: 28 | channel: stable 29 | - run: flutter pub get 30 | - run: flutter test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore pubspec.lock for packages 2 | /pubspec.lock 3 | 4 | # Miscellaneous 5 | *.class 6 | *.log 7 | *.pyc 8 | *.swp 9 | .DS_Store 10 | .atom/ 11 | .buildlog/ 12 | .history 13 | .svn/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | .packages 32 | .pub-cache/ 33 | .pub/ 34 | build/ 35 | 36 | # Android related 37 | **/android/**/gradle-wrapper.jar 38 | **/android/.gradle 39 | **/android/captures/ 40 | **/android/gradlew 41 | **/android/gradlew.bat 42 | **/android/local.properties 43 | **/android/**/GeneratedPluginRegistrant.java 44 | 45 | # iOS/XCode related 46 | **/ios/**/*.mode1v3 47 | **/ios/**/*.mode2v3 48 | **/ios/**/*.moved-aside 49 | **/ios/**/*.pbxuser 50 | **/ios/**/*.perspectivev3 51 | **/ios/**/*sync/ 52 | **/ios/**/.sconsign.dblite 53 | **/ios/**/.tags* 54 | **/ios/**/.vagrant/ 55 | **/ios/**/DerivedData/ 56 | **/ios/**/Icon? 57 | **/ios/**/Pods/ 58 | **/ios/**/.symlinks/ 59 | **/ios/**/profile 60 | **/ios/**/xcuserdata 61 | **/ios/.generated/ 62 | **/ios/Flutter/App.framework 63 | **/ios/Flutter/Flutter.framework 64 | **/ios/Flutter/Flutter.podspec 65 | **/ios/Flutter/Generated.xcconfig 66 | **/ios/Flutter/app.flx 67 | **/ios/Flutter/app.zip 68 | **/ios/Flutter/flutter_assets/ 69 | **/ios/Flutter/flutter_export_environment.sh 70 | **/ios/ServiceDefinitions.json 71 | **/ios/Runner/GeneratedPluginRegistrant.* 72 | 73 | # Exceptions to above rules. 74 | !**/ios/**/default.mode1v3 75 | !**/ios/**/default.mode2v3 76 | !**/ios/**/default.pbxuser 77 | !**/ios/**/default.perspectivev3 78 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 79 | -------------------------------------------------------------------------------- /.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: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.2] - 03-December-2023 2 | 3 | * Fix issue with shorthand control points calc 4 | 5 | ## [1.1.1] - 19-July-2021 6 | 7 | * Allow Flags without separators in cubic Bézier curves 8 | see details here: [issue#6](https://github.com/masterashu/svg_path_parser/issues/6) 9 | 10 | ## [1.1.0] - 19-July-2021 11 | 12 | * Fixed an issue where using an absolute command after 13 | a relative command will lead to wrong parsing behavior. 14 | See details here: [issue#7](https://github.com/masterashu/svg_path_parser/issues/7) 15 | 16 | ## [1.0.0] - 7-March-2021 17 | 18 | * Added null safety support 19 | * Fixed unable to parse the sequence `H 4` and similar. [issue](https://github.com/masterashu/svg_path_parser/issues/3) 20 | * Fix Typo in README 21 | 22 | ## [0.1.1] - 31-May-2020 23 | 24 | * Added flag failSilently to [parseSvgPath](https://pub.dev/documentation/svg_path_parser/latest/svg_path_parser/parseSvgPath.html) 25 | which will return an empty Path object if the provided path is invalid. 26 | 27 | ## [0.1.0] - 18-April-2020 28 | 29 | Changes: 30 | * Added example 31 | * Added Testcase for Parser 32 | * Added Docs for Parser 33 | 34 | ## [0.0.9] - 17-April-2020 35 | 36 | * Initial Development release. 37 | * Added Testcase 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ashutosh Chauhan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svg_path_parser 2 | [![Pub Version (including pre-releases)](https://img.shields.io/pub/v/svg_path_parser?include_prereleases)](https://pub.dev/packages/svg_path_parser) 3 | [![Flutter Test package](https://github.com/masterashu/svg_path_parser/workflows/Flutter%20Test%20package/badge.svg)](https://github.com/masterashu/svg_path_parser/actions) 4 | 5 | A Flutter/Dart utility to parse an SVG path into a equivalent Path object from `dart:ui` library. 6 | 7 | ## Getting Started 8 | 9 | Add this to your package's **pubspec.yaml** file: 10 | 11 | ```yaml 12 | dependencies: 13 | svg_path_parser: ^1.1.2 14 | ``` 15 | 16 | Now in your Dart code, you can use: 17 | 18 | ```dart 19 | import 'package:svg_path_parser/svg_path_parser.dart'; 20 | ``` 21 | 22 | You can use `parseSvgPath()` to parse a valid SVG path string to [Path](https://api.flutter.dev/flutter/dart-ui/Path-class.html) object; 23 | 24 | ```dart 25 | Path path = parseSvgPath('m.29 47.85 14.58 14.57 62.2-62.2h-29.02z'); 26 | ``` 27 | 28 | ## Examples 29 | View the [example](https://github.com/masterashu/svg_path_parser/tree/master/example) 30 | folder to see an example (drawing flutter logo using svg paths). 31 | 32 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /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 and should not be manually edited. 5 | 6 | version: 7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | [web demo](https://masterashu.github.io/demo_svg_editor/#/) 4 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 18 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 19 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXCopyFilesBuildPhase section */ 23 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 24 | isa = PBXCopyFilesBuildPhase; 25 | buildActionMask = 2147483647; 26 | dstPath = ""; 27 | dstSubfolderSpec = 10; 28 | files = ( 29 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 30 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 31 | ); 32 | name = "Embed Frameworks"; 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXCopyFilesBuildPhase section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 39 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 40 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 41 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 42 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 43 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 44 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 45 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 46 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 47 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 48 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 61 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 9740EEB11CF90186004384FC /* Flutter */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 3B80C3931E831B6300D905FE /* App.framework */, 72 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 73 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 74 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 75 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 76 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 77 | ); 78 | name = Flutter; 79 | sourceTree = ""; 80 | }; 81 | 97C146E51CF9000F007C117D = { 82 | isa = PBXGroup; 83 | children = ( 84 | 9740EEB11CF90186004384FC /* Flutter */, 85 | 97C146F01CF9000F007C117D /* Runner */, 86 | 97C146EF1CF9000F007C117D /* Products */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | 97C146EF1CF9000F007C117D /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 97C146EE1CF9000F007C117D /* Runner.app */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 97C146F01CF9000F007C117D /* Runner */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 102 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 103 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 104 | 97C147021CF9000F007C117D /* Info.plist */, 105 | 97C146F11CF9000F007C117D /* Supporting Files */, 106 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 107 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 108 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 109 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 110 | ); 111 | path = Runner; 112 | sourceTree = ""; 113 | }; 114 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | ); 118 | name = "Supporting Files"; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | 97C146ED1CF9000F007C117D /* Runner */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 127 | buildPhases = ( 128 | 9740EEB61CF901F6004384FC /* Run Script */, 129 | 97C146EA1CF9000F007C117D /* Sources */, 130 | 97C146EB1CF9000F007C117D /* Frameworks */, 131 | 97C146EC1CF9000F007C117D /* Resources */, 132 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 133 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = Runner; 140 | productName = Runner; 141 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | 97C146E61CF9000F007C117D /* Project object */ = { 148 | isa = PBXProject; 149 | attributes = { 150 | LastUpgradeCheck = 1020; 151 | ORGANIZATIONNAME = "The Chromium Authors"; 152 | TargetAttributes = { 153 | 97C146ED1CF9000F007C117D = { 154 | CreatedOnToolsVersion = 7.3.1; 155 | LastSwiftMigration = 1100; 156 | }; 157 | }; 158 | }; 159 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 160 | compatibilityVersion = "Xcode 3.2"; 161 | developmentRegion = en; 162 | hasScannedForEncodings = 0; 163 | knownRegions = ( 164 | en, 165 | Base, 166 | ); 167 | mainGroup = 97C146E51CF9000F007C117D; 168 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | 97C146ED1CF9000F007C117D /* Runner */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | 97C146EC1CF9000F007C117D /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 183 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 184 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 185 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXResourcesBuildPhase section */ 190 | 191 | /* Begin PBXShellScriptBuildPhase section */ 192 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 193 | isa = PBXShellScriptBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | inputPaths = ( 198 | ); 199 | name = "Thin Binary"; 200 | outputPaths = ( 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | shellPath = /bin/sh; 204 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 205 | }; 206 | 9740EEB61CF901F6004384FC /* Run Script */ = { 207 | isa = PBXShellScriptBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | inputPaths = ( 212 | ); 213 | name = "Run Script"; 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 219 | }; 220 | /* End PBXShellScriptBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | 97C146EA1CF9000F007C117D /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 228 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXSourcesBuildPhase section */ 233 | 234 | /* Begin PBXVariantGroup section */ 235 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 236 | isa = PBXVariantGroup; 237 | children = ( 238 | 97C146FB1CF9000F007C117D /* Base */, 239 | ); 240 | name = Main.storyboard; 241 | sourceTree = ""; 242 | }; 243 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | 97C147001CF9000F007C117D /* Base */, 247 | ); 248 | name = LaunchScreen.storyboard; 249 | sourceTree = ""; 250 | }; 251 | /* End PBXVariantGroup section */ 252 | 253 | /* Begin XCBuildConfiguration section */ 254 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 255 | isa = XCBuildConfiguration; 256 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_ANALYZER_NONNULL = YES; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 279 | CLANG_WARN_STRICT_PROTOTYPES = YES; 280 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SUPPORTED_PLATFORMS = iphoneos; 300 | TARGETED_DEVICE_FAMILY = "1,2"; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Profile; 304 | }; 305 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 306 | isa = XCBuildConfiguration; 307 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | CLANG_ENABLE_MODULES = YES; 311 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 312 | ENABLE_BITCODE = NO; 313 | FRAMEWORK_SEARCH_PATHS = ( 314 | "$(inherited)", 315 | "$(PROJECT_DIR)/Flutter", 316 | ); 317 | INFOPLIST_FILE = Runner/Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 319 | LIBRARY_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "$(PROJECT_DIR)/Flutter", 322 | ); 323 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 326 | SWIFT_VERSION = 5.0; 327 | VERSIONING_SYSTEM = "apple-generic"; 328 | }; 329 | name = Profile; 330 | }; 331 | 97C147031CF9000F007C117D /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = dwarf; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | ENABLE_TESTABILITY = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_DYNAMIC_NO_PIC = NO; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_OPTIMIZATION_LEVEL = 0; 369 | GCC_PREPROCESSOR_DEFINITIONS = ( 370 | "DEBUG=1", 371 | "$(inherited)", 372 | ); 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 380 | MTL_ENABLE_DEBUG_INFO = YES; 381 | ONLY_ACTIVE_ARCH = YES; 382 | SDKROOT = iphoneos; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | }; 385 | name = Debug; 386 | }; 387 | 97C147041CF9000F007C117D /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_NONNULL = YES; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 398 | CLANG_WARN_BOOL_CONVERSION = YES; 399 | CLANG_WARN_COMMA = YES; 400 | CLANG_WARN_CONSTANT_CONVERSION = YES; 401 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 409 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 411 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 412 | CLANG_WARN_STRICT_PROTOTYPES = YES; 413 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 414 | CLANG_WARN_UNREACHABLE_CODE = YES; 415 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 416 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 417 | COPY_PHASE_STRIP = NO; 418 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 419 | ENABLE_NS_ASSERTIONS = NO; 420 | ENABLE_STRICT_OBJC_MSGSEND = YES; 421 | GCC_C_LANGUAGE_STANDARD = gnu99; 422 | GCC_NO_COMMON_BLOCKS = YES; 423 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 424 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 425 | GCC_WARN_UNDECLARED_SELECTOR = YES; 426 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 427 | GCC_WARN_UNUSED_FUNCTION = YES; 428 | GCC_WARN_UNUSED_VARIABLE = YES; 429 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 430 | MTL_ENABLE_DEBUG_INFO = NO; 431 | SDKROOT = iphoneos; 432 | SUPPORTED_PLATFORMS = iphoneos; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | VALIDATE_PRODUCT = YES; 436 | }; 437 | name = Release; 438 | }; 439 | 97C147061CF9000F007C117D /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | ENABLE_BITCODE = NO; 447 | FRAMEWORK_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | INFOPLIST_FILE = Runner/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | LIBRARY_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 461 | SWIFT_VERSION = 5.0; 462 | VERSIONING_SYSTEM = "apple-generic"; 463 | }; 464 | name = Debug; 465 | }; 466 | 97C147071CF9000F007C117D /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | CLANG_ENABLE_MODULES = YES; 472 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 473 | ENABLE_BITCODE = NO; 474 | FRAMEWORK_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "$(PROJECT_DIR)/Flutter", 477 | ); 478 | INFOPLIST_FILE = Runner/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 480 | LIBRARY_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "$(PROJECT_DIR)/Flutter", 483 | ); 484 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 487 | SWIFT_VERSION = 5.0; 488 | VERSIONING_SYSTEM = "apple-generic"; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 97C147031CF9000F007C117D /* Debug */, 499 | 97C147041CF9000F007C117D /* Release */, 500 | 249021D3217E4FDB00AE95B9 /* Profile */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 97C147061CF9000F007C117D /* Debug */, 509 | 97C147071CF9000F007C117D /* Release */, 510 | 249021D4217E4FDB00AE95B9 /* Profile */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | /* End XCConfigurationList section */ 516 | }; 517 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 518 | } 519 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:svg_path_parser/svg_path_parser.dart'; 3 | 4 | void main() => runApp(MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | title: 'Flutter Demo', 11 | theme: ThemeData( 12 | primarySwatch: Colors.blue, 13 | ), 14 | home: MyHomePage(), 15 | ); 16 | } 17 | } 18 | 19 | /// A Stateful widget that paints flutter logo using [CustomPaint] and [Path]. 20 | class MyHomePage extends StatefulWidget { 21 | final paths = [ 22 | // ['m48.75 95.97-25.91-25.74 14.32-14.57 40.39 40.31z', Color(0xff02539a)], 23 | // ['m22.52 70.25 25.68-25.68h28.87l-39.95 39.95z', Color(0xd745d1fd)], 24 | // ['m.29 47.85 14.58 14.57 62.2-62.2h-29.02z', Color(0xff45d1fd)], 25 | // ['M2,5 S2,-2 4,5 S7,8 8,4', Colors.blue], 26 | [ 27 | 'M317.91,66.52a7.09,7.09,0,0,0-.39-.78,24.06,24.06,0,0,0-4-5.44c-1.57-1.77-2.86-3.76-4.44-5.51a22.88,22.88,0,0,0-2.74-2.56,24.47,24.47,0,0,1-2.53-1.82A72.29,72.29,0,0,0,297,44.62a40.37,40.37,0,0,0-8.35-4.9,85.76,85.76,0,0,0-12.1-4.89,72.32,72.32,0,0,0-10.17-2.32c-2.31-.31-4.6-.68-6.9-1a73.37,73.37,0,0,0-7.7-.67c-1.78-.05-3.56-.15-5.35-.21-.36,0-.62,0-.64-.51a31.17,31.17,0,0,0-.89-5.64c3.82.64,7.54,1.36,11.23,2.25,2.32.55,4.64,1.16,7,1.71a8.89,8.89,0,0,0,2.36.38c.4,0,.62-.14.64-.56,0-.22.07-.44.06-.65a18.6,18.6,0,0,0-1.21-6.48,14.3,14.3,0,0,0-5.08-6.27c-3.15-2.22-6.67-2.48-10.33-1.69-.23.05-.45.19-.76,0l.76-.78c3.49-3.36,6.85-6.85,10.54-10,.65-.55.61-.85-.15-1.07A26.82,26.82,0,0,0,252.43,0c-1.31,0-2.61,0-3.92.13a16.82,16.82,0,0,0-11,5.1,9.8,9.8,0,0,0-3,6.6,12.9,12.9,0,0,0,0,2.17,16.08,16.08,0,0,0-6.29-2.34,12.43,12.43,0,0,0-7.86,1.66,24.13,24.13,0,0,0-6.39,5.56,6.08,6.08,0,0,0-1.39,2.69c-.24,1-.12,1.18.89,1.12.61-.05,1.22-.18,1.84-.22,2-.13,3.94-.57,5.91-.8a33.31,33.31,0,0,1,5.63-.52c1,.06,2.06,0,3.09,0a3.41,3.41,0,0,1-.85.71,18.41,18.41,0,0,0-3.59,3,18,18,0,0,0-3.78,7.89c-.1.45-.22.7-.72.75-.24,0-.49.11-.74.15-2.07.4-4.13.79-6.2,1.22a97.69,97.69,0,0,0-9.59,2.86c-4.54,1.42-8.89,3.35-13.27,5.21A78.83,78.83,0,0,0,182,47.45a65.26,65.26,0,0,0-5.76,4.08A39,39,0,0,0,171.71,47a88.86,88.86,0,0,0-17.57-12,15.33,15.33,0,0,0,.69-3.48A8.21,8.21,0,0,0,153.5,26a5.14,5.14,0,0,0-4.8-2.38c-2.07.12-4.12.48-6.47.79.77-3.1,2-5.87,2.72-9l-.06,0-10.35,1.75a18.76,18.76,0,0,0-1.54,3.7c.66.5,1.46-.08,2.1.32.44.39.26.86.12,1.3l-1.41,4.33c-1.44-.43-2.9-.82-4.36-1.19l2.05-6.16c.17-.5.55-.95.32-1.64-2.3,0-4.5,1-6.81,1-1.38.73-3,.36-4.47,1.2a20,20,0,0,0-1.22,3.44c.76.47,1.66-.32,2.42.5a.07.07,0,0,1,0,.05,104.76,104.76,0,0,0-11-1.26c-1-2-3.16-2.68-5.54-1.49a9.75,9.75,0,0,0-1.94,1.32c-2.7,0-5.4.15-8.11.4-3.25.31-6.49.74-9.7,1.33-.74.13-1.47.29-2.2.44a10.08,10.08,0,0,0-4.94-5.35,11.49,11.49,0,0,0-9.17-.6,16.89,16.89,0,0,0-7.25,3.71c-1.44,1.18-2.81,2.45-4.23,3.69a6,6,0,0,0-5.13-2.79,6.37,6.37,0,0,0-5.75,3.52A20.5,20.5,0,0,0,37,26c1-1.53,2.07-2.84,3.11-4.13a72.7,72.7,0,0,1,7.62-8.26c1-.92,2-1.85,3-2.82a.83.83,0,0,0-.36-1.48c-.9-.28-1.81-.52-2.7-.81a8,8,0,0,0-4.4-.14,22.71,22.71,0,0,0-5.69,2.13,20.19,20.19,0,0,0-10.08,11.8c-.3.92-.5,1.88-.78,2.92l-.36-.52a16.37,16.37,0,0,0-4.31-4.43,27.13,27.13,0,0,0-7.42-3.6,11.25,11.25,0,0,0-10,1.54,13.65,13.65,0,0,0-4,4.69,9.82,9.82,0,0,0-.6,1.27c-.23.58-.11.87.43,1.12a3.19,3.19,0,0,0,.73.21c2.92.59,5.78,1.36,8.65,2.15,3,.83,6,1.94,9,2.83.19.06.47,0,.44.35-.05,0-.09.12-.15.14A28.76,28.76,0,0,0,11,35.16a17.94,17.94,0,0,0-7.1,9.15A26.5,26.5,0,0,0,2.43,51a19.18,19.18,0,0,0,.85,7.61,78.21,78.21,0,0,0,3.65,8.28,2,2,0,0,0,.21.38c.33.42.61.43,1,0A5.68,5.68,0,0,0,8.9,66c1.68-3.39,3.45-6.73,5.35-10C16,52.9,18,50,20,47.06c.13-.18.2-.41.54-.5-.05.26-.08.44-.13.61a15.85,15.85,0,0,0-.73,5.37A12.15,12.15,0,0,0,23.42,61a2.69,2.69,0,0,0,.71.5c.74.35,1,.27,1.17-.5s.33-1.49.49-2.23c1-4.54,2.1-9.06,3.43-13.51a15,15,0,0,1,1-2.85,21.27,21.27,0,0,1,1.05,3.65c.59,2.21,1.43,4.35,1.94,6.58a1.7,1.7,0,0,1-.46,1.73,84.33,84.33,0,0,0-7.44,9.81,25.35,25.35,0,0,0-2.06,3.47c-.37.82-.13,1.14.77,1.16a2.09,2.09,0,0,0,2.32-1.18c.06-.13.13-.28.39-.23.76,2.06,1.57,4.15,2.3,6.27,1.3,3.82,1.87,7.83,2.78,11.75.09.39,0,.62-.4.73-.78.2-1,.76-1.1,1.54-.07,1-.06,1.89,0,2.84,0,1.64-.51,2,2,2.18.42,0,.58.16.62.56a20.06,20.06,0,0,0,.3,2c1,4.32,1.45,8.73,2.18,13.09.45,2.72.82,5.45,1.3,8.17.57,3.14.91,6.31,1.32,9.48.27,2,.44,4.11.72,6.16.46,3.37,1,6.75,1.49,10.12a31.46,31.46,0,0,0,.87,4.59c.29.94.64,1.21,1.56,1.11,2.45-.24,4.92-.31,7.36-.65h.05a4.43,4.43,0,0,0-.81,2.46,1,1,0,0,0,.68,1,3.35,3.35,0,0,0,1.26.22,22.29,22.29,0,0,1,4.32.17,9,9,0,0,0,1.64.2,16.67,16.67,0,0,0,4.61-.71c1.59-.44,3.14-1,4.7-1.58.66-.24,1.28-.58,1.93-.85a2,2,0,0,0,.91-.74c.21-.34-.16-.56-.22-.84s-.06-.31-.09-.46c-.08-.41-.15-.82-.22-1.23.76-.06,1.52-.13,2.28-.17,1.22-.08,2.44-.18,3.67-.28l-.33-1.05a7.08,7.08,0,0,1-.36-2.62,2.49,2.49,0,0,1,1.56-2.4,2.43,2.43,0,0,1,2.94.72,21.15,21.15,0,0,1,2.91,4.54c.53,1.07,1,2.16,1.44,3.26,1.11-.16,2.2-.38,3.32-.46,1.28-.09,2.56-.26,3.83-.44a21.35,21.35,0,0,0,4.53-1.45,4,4,0,0,0,1.28-.88,1.07,1.07,0,0,0,.18-.19l1.15,0c1.22-.05,2.44-.08,3.66-.1a4.4,4.4,0,0,0,.58,1.49,2,2,0,0,0,.82.76q1.92-3.9,4.16-7.66a71.77,71.77,0,0,1,6.13-8.64A52.52,52.52,0,0,1,118,124a5.5,5.5,0,0,1,1.48-.95,1.49,1.49,0,0,0,.87-.59c.15-.29.48-.28.78-.3,2.32-.14,4.62-.45,6.92-.71,2.08-.23,4.17-.44,6.26-.54,1.12-.05,2.24-.13,3.36-.2-.1-1.51-.19-3-.28-4.54-.05-1-.24-2-.37-3s.09-1.2.95-1.32a15.24,15.24,0,0,1,2.61-.22,59.62,59.62,0,0,0,8.75-.76c3.37-.4,6.75-.85,10.12-1.29,3.57-.46,7.17-.41,10.76-.6,2.81-.15,5.63,0,8.44-.24,3.18-.31,6.37-.13,9.54-.3,2.77-.15,5.54-.08,8.3-.26,4.69-.31,9.4-.34,14.09-.57,5.13-.25,10.25-.07,15.38-.25,1.19,0,2.38-.22,3.58-.26,1.44,0,1.45,0,2,1.37a3.85,3.85,0,0,1,.32,1.76,71.88,71.88,0,0,0,0,7.34c0,.55,0,1.11,0,1.66.53,0,1.06.05,1.58,0,2.17,0,4.33.15,6.49.22a28.68,28.68,0,0,1,6.89,1,2.25,2.25,0,0,1,1.51,1.1c.56,1,1.29,1.89,1.89,2.87a96.81,96.81,0,0,1,5.94,11,95.26,95.26,0,0,1,5.59,15.27l.53.21c-.13.13-.25.26-.37.4.26,1,.51,2,.72,3,.61,2.85,1.35,5.67,2,8.51a2.29,2.29,0,0,1,.12.37.21.21,0,0,0,.17.2c.15,0,.23-.06.29-.2.18-.45.36-.91.57-1.35a32,32,0,0,1,5.84-8.34,28.07,28.07,0,0,1,4.09-3.69,3.29,3.29,0,0,1,1.87-.8c.73,0,1,.26.82.95a10.1,10.1,0,0,1-.65,1.65,85.86,85.86,0,0,0-3.93,10.48,40.18,40.18,0,0,0,8.13.71,24.42,24.42,0,0,0,2.59-.28c.52-.19,1.05-.37,1.58-.54a29.48,29.48,0,0,1,3.2-.83c.29-.12.58-.22.86-.35a3.42,3.42,0,0,0,1.07-.7c.64-.67.64-1-.15-1.46a39.64,39.64,0,0,0-7.65-3.47c-.65-.21-1.67,0-1.89-.61s.2-1.41.35-2.13a3.74,3.74,0,0,0,.12-.45c.09-.85.6-1.22,1.4-1.42a2.24,2.24,0,0,0,1.87-2.35,9.45,9.45,0,0,1,.42-2.18c.3-1,.55-2,.79-3,1.91,0,3.81-.09,5.72-.18.8,0,1.6,0,2.4,0a34.06,34.06,0,0,0,5.61-.64c.6-.09.76-.35.56-.88a2.18,2.18,0,0,1-.05-1c.1-1.3.22-2.59.35-3.89.17-1.66.17-3.34.32-5,.38-4.15,1-8.27,1.67-12.39.49-2.86.95-5.72,1.54-8.56s1.24-5.46,1.8-8.19c.37-1.84.64-3.71,1-5.56,1.1.1,1.21.06,1.54-1,.45-1.42.91-2.83,1.25-4.29.23-1,.1-2,.42-3,1.18-3.59,2.06-7.27,2.9-10.94.76-3.27,1.45-6.56,1.88-9.89.27,0,.35.19.49.3a1.44,1.44,0,0,0,1.68,0C318.06,67.59,318.16,67.22,317.91,66.52ZM39.07,47.71c-1.57-3-3.38-5.87-5.18-8.7,1-.14,8.73,2,11.46,3.26C43.16,44,41.11,45.81,39.07,47.71ZM70.51,28.26a5.46,5.46,0,0,1,3.36-1.15C72.74,27.48,71.62,27.86,70.51,28.26Zm78.28,3.2c0,.28-.09.55-.15.82l-.2-.1q-1.33-.6-2.67-1.17c.18-.53.36-1.07.55-1.61a3.32,3.32,0,0,1,2,0A1.94,1.94,0,0,1,148.79,31.46Zm89.81-.37c-1.23.08-2.45.16-3.88.16,1.32-1.83,2.35-3.7,3.72-5.48a9.51,9.51,0,0,1,.51,2.67,11.6,11.6,0,0,0,.14,2C239.19,30.86,239.06,31.06,238.6,31.09Z', 28 | // 'M317.91,66.52c.74.35,1,.27,1.17-.5s.33-1.49.49-2.23', 29 | // 'M-90,20a2.69,2.69,0,0,0,.71.5c.74.35,1,.27,1.17-.5s.33-1.49.49-2.23c1-4.54,2.1-9.06,3.43-13.51', 30 | // 'M -90 20 Q -89.06 20 -88.12 20 Q -87.875 18.885 -86 19 t 1.63 2.23 T -81 20 t -9 -3', 31 | // 'M -90 20 A 2.69 2.69 0 0 0 -89.29 20.5 C -88.55 20.85 -88.29 20.77 -88.12 20 S -87.79 18.51 -87.63 17.77 C -86.63 13.23 -85.53 8.71 -84.2 4.26', 32 | Colors.amber 33 | ], 34 | ]; 35 | @override 36 | _MyHomePageState createState() => _MyHomePageState(); 37 | } 38 | 39 | class _MyHomePageState extends State { 40 | bool showBorder = false; 41 | @override 42 | Widget build(BuildContext context) { 43 | return Scaffold( 44 | body: Transform.scale( 45 | scale: 2.0, 46 | child: GestureDetector( 47 | child: Center( 48 | child: Container( 49 | width: 100, 50 | height: 100, 51 | child: Stack( 52 | children: widget.paths.map((e) { 53 | return CustomPaint( 54 | painter: MyPainter( 55 | parseSvgPath(e[0] as String), e[1] as Color, 56 | showPath: showBorder)); 57 | }).toList(), 58 | ), 59 | ), 60 | ), 61 | behavior: HitTestBehavior.translucent, 62 | onTap: () { 63 | setState(() { 64 | // hide/show border 65 | showBorder = !showBorder; 66 | }); 67 | }, 68 | ), 69 | ), 70 | ); 71 | } 72 | } 73 | 74 | class MyPainter extends CustomPainter { 75 | final Path path; 76 | final Color color; 77 | final bool showPath; 78 | MyPainter(this.path, this.color, {this.showPath = true}); 79 | 80 | @override 81 | void paint(Canvas canvas, Size size) { 82 | var paint = Paint() 83 | ..color = color 84 | ..strokeWidth = 4.0; 85 | canvas.drawPath(path, paint); 86 | if (showPath) { 87 | var border = Paint() 88 | ..color = Colors.black 89 | ..strokeWidth = 0.2 90 | ..style = PaintingStyle.stroke; 91 | canvas.drawPath(path, border); 92 | } 93 | } 94 | 95 | @override 96 | bool shouldRepaint(CustomPainter oldDelegate) => true; 97 | } 98 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.16.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.3.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.11" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.1.4" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.7.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.1" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.8.2" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | svg_path_parser: 125 | dependency: "direct main" 126 | description: 127 | path: ".." 128 | relative: true 129 | source: path 130 | version: "1.1.2" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.2.0" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.4.9" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.2" 152 | sdks: 153 | dart: ">=2.17.0-0 <3.0.0" 154 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | publish_to: none 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: '>=2.12.0 <3.0.0' 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | svg_path_parser: 26 | path: ../../svg_path_parser 27 | 28 | 29 | dev_dependencies: 30 | flutter_test: 31 | sdk: flutter 32 | 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterashu/svg_path_parser/deb827e2c634ae78a3b0529218fe74c1d661071c/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | example 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/parser.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:svg_path_parser/src/scanner.dart'; 3 | import 'package:svg_path_parser/src/tokens.dart'; 4 | 5 | extension RelativeOffsets on Offset { 6 | relativeTranslate(Offset p, CoordinateType type) { 7 | if (type == CoordinateType.absolute) { 8 | return this; 9 | } else { 10 | return this + p; 11 | } 12 | } 13 | } 14 | 15 | /// A Parser that converts a SVG path to a [Path] object. 16 | class Parser { 17 | /// Creates a new [Parser] object. 18 | /// 19 | /// [source] should not be null. 20 | Parser(source) 21 | : _scanner = Scanner(source), 22 | path = Path(), 23 | _initialPoint = Offset.zero, 24 | _currentPoint = Offset.zero, 25 | _lastCommandArgs = [], 26 | _lastCommandControlPoints = []; 27 | 28 | /// Last command Parsed 29 | late CommandToken _lastCommand; 30 | 31 | /// List of Arguments of Previous Command 32 | List _lastCommandArgs; 33 | 34 | List _lastCommandControlPoints; 35 | 36 | /// The initial [Offset] where the [Path] object started from. 37 | Offset _initialPoint; 38 | 39 | /// The current [Offset] where the [Path] is currently at. 40 | Offset _currentPoint; 41 | 42 | /// The path object to be returned. 43 | Path path; 44 | 45 | /// The underlying [Scanner] which reads input source and emits [Token]s. 46 | final Scanner _scanner; 47 | 48 | /// Parses the SVG path. 49 | Path parse() { 50 | // Scan streamStart Token 51 | _parseStreamStart(); 52 | 53 | while (_scanner.peek()!.type != TokenType.streamEnd) { 54 | _parseCommand(); 55 | } 56 | 57 | _parseStreamEnd(); 58 | 59 | return this.path; 60 | } 61 | 62 | /// Parses the SVG path. 63 | Path parseDebug(int maxCommands) { 64 | // Scan streamStart Token 65 | _parseStreamStart(); 66 | 67 | while (_scanner.peek()!.type != TokenType.streamEnd) { 68 | _parseCommand(); 69 | } 70 | 71 | _parseStreamEnd(); 72 | 73 | return this.path; 74 | } 75 | 76 | /// Parses the stream start token. 77 | _parseStreamStart() { 78 | _scanner.scan(); 79 | } 80 | 81 | /// Parses the stream end token. 82 | _parseStreamEnd() { 83 | _scanner.scan(); 84 | } 85 | 86 | /// Parses a SVG path Command. 87 | _parseCommand() { 88 | Token token = _scanner.peek()!; 89 | // If extra arguments are encountered. Use the last command. 90 | if (!(token is CommandToken)) { 91 | // Subsequent pairs after first Move to are considered as implicit 92 | // Line to commands. https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands 93 | if (_lastCommand.type == TokenType.moveTo) { 94 | token = CommandToken(TokenType.lineTo, _lastCommand.coordinateType); 95 | } else { 96 | token = _lastCommand; 97 | } 98 | } else { 99 | token = _scanner.scan()!; 100 | } 101 | 102 | switch (token.type) { 103 | case TokenType.moveTo: 104 | _parseMoveTo(token as CommandToken); 105 | return; 106 | case TokenType.closePath: 107 | _parseClosePath(token as CommandToken); 108 | return; 109 | case TokenType.lineTo: 110 | _parseLineTo(token as CommandToken); 111 | return; 112 | case TokenType.horizontalLineTo: 113 | _parseHorizontalLineTo(token as CommandToken); 114 | return; 115 | case TokenType.verticalLineTo: 116 | _parseVerticalLineTo(token as CommandToken); 117 | return; 118 | case TokenType.curveTo: 119 | _parseCurveTo(token as CommandToken); 120 | return; 121 | case TokenType.smoothCurveTo: 122 | _parseSmoothCurveTo(token as CommandToken); 123 | return; 124 | case TokenType.quadraticBezierCurveTo: 125 | _parseQuadraticBezierCurveTo(token as CommandToken); 126 | return; 127 | case TokenType.smoothQuadraticBezierCurveTo: 128 | _parseSmoothQuadraticBezierCurveTo(token as CommandToken); 129 | return; 130 | case TokenType.ellipticalArcTo: 131 | _parseEllipticalArcTo(token as CommandToken); 132 | return; 133 | default: 134 | return; 135 | } 136 | } 137 | 138 | /// Parses a [CommandToken] of type [TokenType.moveTo] and it's Argument [ValueToken]s. 139 | /// 140 | /// move-to-args: x, y (absolute) 141 | /// move-to-args: dx, dy (relative) 142 | _parseMoveTo(CommandToken commandToken) { 143 | var x = (_scanner.scan()! as ValueToken).value; 144 | var y = (_scanner.scan()! as ValueToken).value; 145 | 146 | if (commandToken.coordinateType == CoordinateType.absolute) { 147 | this.path.moveTo(x as double, y as double); 148 | _currentPoint = Offset(x, y); 149 | } else { 150 | this.path.relativeMoveTo(x as double, y as double); 151 | _currentPoint = _currentPoint.translate(x, y); 152 | } 153 | // moveTo command reset the initial and current point 154 | _initialPoint = _currentPoint; 155 | 156 | _lastCommand = commandToken; 157 | _lastCommandArgs = [x, y]; 158 | } 159 | 160 | /// Parses a [CommandToken] of type [TokenType.closePath]. 161 | _parseClosePath(CommandToken commandToken) { 162 | this.path.close(); 163 | // closePath resets the current point to initial point. 164 | _currentPoint = _initialPoint; 165 | 166 | _lastCommand = commandToken; 167 | _lastCommandArgs.clear(); 168 | } 169 | 170 | /// Parses a [CommandToken] of type [TokenType.lineTo] and it's Argument [ValueToken]s. 171 | /// 172 | /// line-to-args: x, y (absolute) 173 | /// line-to-args: dx, dy (relative) 174 | _parseLineTo(CommandToken commandToken) { 175 | var x = (_scanner.scan()! as ValueToken).value; 176 | var y = (_scanner.scan()! as ValueToken).value; 177 | 178 | if (commandToken.coordinateType == CoordinateType.absolute) { 179 | this.path.lineTo(x as double, y as double); 180 | _currentPoint = Offset(x, y); 181 | } else { 182 | this.path.relativeLineTo(x as double, y as double); 183 | _currentPoint = _currentPoint.translate(x, y); 184 | } 185 | 186 | _lastCommand = commandToken; 187 | _lastCommandArgs = [x, y]; 188 | } 189 | 190 | /// Parses a [CommandToken] of type [TokenType.horizontalLineTo] and it's Argument [ValueToken]s. 191 | /// 192 | /// horizontal-line-to-args: x (absolute) 193 | /// horizontal-line-to-args: dx (relative) 194 | _parseHorizontalLineTo(CommandToken commandToken) { 195 | var h = (_scanner.scan()! as ValueToken).value; 196 | var y = _currentPoint.dy; 197 | 198 | if (commandToken.coordinateType == CoordinateType.absolute) { 199 | this.path.lineTo(h as double, y); 200 | _currentPoint = Offset(h, y); 201 | } else { 202 | this.path.relativeLineTo(h as double, 0); 203 | _currentPoint = _currentPoint.translate(h, 0); 204 | } 205 | 206 | _lastCommand = commandToken; 207 | _lastCommandArgs = [h]; 208 | } 209 | 210 | /// Parses a [CommandToken] of type [TokenType.verticalLineTo] and it's Argument [ValueToken]s. 211 | /// 212 | /// vertical-line-to-args: y (absolute) 213 | /// vertical-line-to-args: dy (relative) 214 | _parseVerticalLineTo(CommandToken commandToken) { 215 | var v = (_scanner.scan()! as ValueToken).value; 216 | var x = _currentPoint.dx; 217 | 218 | if (commandToken.coordinateType == CoordinateType.absolute) { 219 | this.path.lineTo(x, v as double); 220 | _currentPoint = Offset(x, v); 221 | } else { 222 | this.path.relativeLineTo(0, v as double); 223 | _currentPoint = _currentPoint.translate(0, v); 224 | } 225 | 226 | _lastCommand = commandToken; 227 | _lastCommandArgs = [v]; 228 | } 229 | 230 | /// Parses a [CommandToken] of type [TokenType.curveTo] and it's Argument [ValueToken]s. 231 | /// 232 | /// curve-to-args: x1,y1 x2,y2 x,y (absolute) 233 | /// curve-to-args: dx1,dy1 dx2,dy2 dx,dy (relative) 234 | _parseCurveTo(CommandToken commandToken) { 235 | var x1 = (_scanner.scan()! as ValueToken).value as double; 236 | var y1 = (_scanner.scan()! as ValueToken).value as double; 237 | var x2 = (_scanner.scan()! as ValueToken).value as double; 238 | var y2 = (_scanner.scan()! as ValueToken).value as double; 239 | var x = (_scanner.scan()! as ValueToken).value as double; 240 | var y = (_scanner.scan()! as ValueToken).value as double; 241 | 242 | if (commandToken.coordinateType == CoordinateType.absolute) { 243 | this.path.cubicTo(x1, y1, x2, y2, x, y); 244 | _currentPoint = Offset(x, y); 245 | } else { 246 | this.path.relativeCubicTo(x1, y1, x2, y2, x, y); 247 | _currentPoint = _currentPoint.translate(x, y); 248 | } 249 | 250 | _lastCommand = commandToken; 251 | _lastCommandArgs = [x1, y1, x2, y2, x, y]; 252 | _lastCommandControlPoints = [ 253 | Offset(x1, y1) 254 | .relativeTranslate(_currentPoint, commandToken.coordinateType), 255 | Offset(x2, y2) 256 | .relativeTranslate(_currentPoint, commandToken.coordinateType) 257 | ]; 258 | } 259 | 260 | /// Parses a [CommandToken] of type [TokenType.smoothCurveTo] and it's Argument [ValueToken]s. 261 | /// 262 | /// smooth-curve-to-args: x1,y1 x,y (absolute) 263 | /// smooth-curve-to-args: dx1,dy1 dx,dy (relative) 264 | _parseSmoothCurveTo(CommandToken commandToken) { 265 | var x2 = (_scanner.scan()! as ValueToken).value as double; 266 | var y2 = (_scanner.scan()! as ValueToken).value as double; 267 | var x = (_scanner.scan()! as ValueToken).value as double; 268 | var y = (_scanner.scan()! as ValueToken).value as double; 269 | // Calculate the first control point 270 | var cp = _calculateCubicControlPoint(); 271 | 272 | if (commandToken.coordinateType == CoordinateType.absolute) { 273 | this.path.cubicTo(cp.dx, cp.dy, x2, y2, x, y); 274 | _currentPoint = Offset(x, y); 275 | } else { 276 | this.path.relativeCubicTo( 277 | cp.dx - _currentPoint.dx, cp.dy - _currentPoint.dy, x2, y2, x, y); 278 | _currentPoint = _currentPoint.translate(x, y); 279 | } 280 | 281 | _lastCommand = commandToken; 282 | _lastCommandArgs = [x2, y2, x, y]; 283 | _lastCommandControlPoints = [ 284 | cp, 285 | Offset(x2, y2) 286 | .relativeTranslate(_currentPoint, commandToken.coordinateType) 287 | ]; 288 | } 289 | 290 | /// Parses a [CommandToken] of type [TokenType.quadraticBezierCurveTo] and it's Argument [ValueToken]s. 291 | /// Parses a [CommandToken] of type [TokenType.smoothCurveTo] and it's Argument [ValueToken]s. 292 | /// 293 | /// quadratic-curve-to-args: x1,y1 x,y (absolute) 294 | /// quadratic-curve-to-args: dx1,dy1 dx,dy (relative) 295 | _parseQuadraticBezierCurveTo(CommandToken commandToken) { 296 | var x1 = (_scanner.scan()! as ValueToken).value as double; 297 | var y1 = (_scanner.scan()! as ValueToken).value as double; 298 | var x = (_scanner.scan()! as ValueToken).value as double; 299 | var y = (_scanner.scan()! as ValueToken).value as double; 300 | 301 | if (commandToken.coordinateType == CoordinateType.absolute) { 302 | this.path.quadraticBezierTo(x1, y1, x, y); 303 | _currentPoint = Offset(x, y); 304 | } else { 305 | this.path.relativeQuadraticBezierTo(x1, y1, x, y); 306 | _currentPoint = _currentPoint.translate(x, y); 307 | } 308 | 309 | _lastCommand = commandToken; 310 | _lastCommandArgs = [x1, y1, x, y]; 311 | _lastCommandControlPoints = [ 312 | Offset(x1, y1) 313 | .relativeTranslate(_currentPoint, commandToken.coordinateType) 314 | ]; 315 | } 316 | 317 | /// Parses a [CommandToken] of type [TokenType.smoothQuadraticBezierCurveTo] and it's Argument [ValueToken]s. 318 | /// 319 | /// smooth-quadratic-curve-to-args: x,y (absolute) 320 | /// smooth-quadratic-curve-to-args: dx,dy (relative) 321 | _parseSmoothQuadraticBezierCurveTo(CommandToken commandToken) { 322 | var x = (_scanner.scan()! as ValueToken).value as double; 323 | var y = (_scanner.scan()! as ValueToken).value as double; 324 | 325 | // Calculate the control point 326 | var cp = _calculateQuadraticControlPoint(); 327 | 328 | if (commandToken.coordinateType == CoordinateType.absolute) { 329 | this.path.quadraticBezierTo(cp.dx, cp.dy, x, y); 330 | _currentPoint = Offset(x, y); 331 | } else { 332 | this.path.relativeQuadraticBezierTo( 333 | cp.dx - _currentPoint.dx, cp.dy - _currentPoint.dy, x, y); 334 | _currentPoint = _currentPoint.translate(x, y); 335 | } 336 | 337 | _lastCommand = commandToken; 338 | _lastCommandArgs = [x, y]; 339 | _lastCommandControlPoints = [cp]; 340 | } 341 | 342 | /// Parses a [CommandToken] of type [TokenType.ellipticalArcTo] and it's Argument [ValueToken]s. 343 | /// 344 | /// smooth-elliptical-curve-to-args: rx ry x-axis-rotation large-arc-flag sweep-flag x y (absolute) 345 | /// smooth-elliptical-curve-to-args: rx ry x-axis-rotation large-arc-flag sweep-flag dx dy (relative) 346 | _parseEllipticalArcTo(CommandToken commandToken) { 347 | var rx = (_scanner.scan()! as ValueToken).value as double; 348 | var ry = (_scanner.scan()! as ValueToken).value as double; 349 | var theta = (_scanner.scan()! as ValueToken).value as double; 350 | var fa = (_scanner.scan()! as ValueToken).value == 1; 351 | var fb = (_scanner.scan()! as ValueToken).value == 1; 352 | var x = (_scanner.scan()! as ValueToken).value as double; 353 | var y = (_scanner.scan()! as ValueToken).value as double; 354 | 355 | if (commandToken.coordinateType == CoordinateType.absolute) { 356 | this.path.arcToPoint(Offset(x, y), 357 | radius: Radius.elliptical(rx, ry), 358 | rotation: theta, 359 | largeArc: fa, 360 | clockwise: fb); 361 | _currentPoint = Offset(x, y); 362 | } else { 363 | this.path.relativeArcToPoint(Offset(x, y), 364 | radius: Radius.elliptical(rx, ry), 365 | rotation: theta, 366 | largeArc: fa, 367 | clockwise: fb); 368 | _currentPoint = _currentPoint.translate(x, y); 369 | } 370 | 371 | _lastCommand = commandToken; 372 | _lastCommandArgs = [rx, ry, theta, fa, fb, x, y]; 373 | _lastCommandControlPoints = []; 374 | } 375 | 376 | /// Predicts the Control Point [Offset] for a smooth cubic curve command. 377 | Offset _calculateCubicControlPoint() { 378 | if (_lastCommand.type == TokenType.curveTo) { 379 | return _currentPoint.translate( 380 | _lastCommandArgs[4] - _lastCommandArgs[2], 381 | _lastCommandArgs[5] - _lastCommandArgs[3], 382 | ); 383 | } else if (_lastCommand.type == TokenType.smoothCurveTo) { 384 | return _currentPoint.translate( 385 | _lastCommandArgs[2] - _lastCommandArgs[0], 386 | _lastCommandArgs[3] - _lastCommandArgs[1], 387 | ); 388 | } else { 389 | return _currentPoint; 390 | } 391 | } 392 | 393 | /// Predicts the Control Point [Offset] for a smooth quadratic bezier curve command. 394 | Offset _calculateQuadraticControlPoint() { 395 | if (_lastCommand.type == TokenType.quadraticBezierCurveTo) { 396 | return _currentPoint * 2 - _lastCommandControlPoints[0]; 397 | } else if (_lastCommand.type == TokenType.smoothQuadraticBezierCurveTo) { 398 | return _currentPoint - (_lastCommandControlPoints[0] - _currentPoint); 399 | } else { 400 | return _currentPoint; 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /lib/src/scanner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:string_scanner/string_scanner.dart'; 4 | import 'package:svg_path_parser/src/tokens.dart'; 5 | 6 | /// A scanner that reads a string of Unicode characters and emits [Token]s. 7 | /// 8 | /// This scanner is based on the guidelines provided by W3C on svg path, 9 | /// available at https://www.w3.org/TR/SVG11/paths.html. 10 | class Scanner { 11 | static const LETTER_A = 0x41; 12 | static const LETTER_a = 0x61; 13 | static const LETTER_C = 0x43; 14 | static const LETTER_c = 0x63; 15 | static const LETTER_E = 0x45; 16 | static const LETTER_e = 0x65; 17 | static const LETTER_h = 0x48; 18 | static const LETTER_H = 0x68; 19 | static const LETTER_L = 0x4c; 20 | static const LETTER_l = 0x6c; 21 | static const LETTER_M = 0x4d; 22 | static const LETTER_m = 0x6d; 23 | static const LETTER_Q = 0x51; 24 | static const LETTER_q = 0x71; 25 | static const LETTER_S = 0x53; 26 | static const LETTER_s = 0x73; 27 | static const LETTER_T = 0x54; 28 | static const LETTER_t = 0x74; 29 | static const LETTER_V = 0x56; 30 | static const LETTER_v = 0x76; 31 | static const LETTER_Z = 0x5a; 32 | static const LETTER_z = 0x7a; 33 | 34 | static const NUMBER_0 = 0x30; 35 | static const NUMBER_9 = 0x39; 36 | 37 | static const MINUS_SIGN = 0x2d; 38 | static const PLUS_SIGN = 0x2b; 39 | static const PERIOD = 0x2e; 40 | static const COMMA = 0x2c; 41 | static const SP = 0x20; 42 | 43 | /// The [RegExp] pattern to match a valid float value. Allowed float values include 44 | /// starting with decimal (.3) and exponent notation (1.3e+4). 45 | static final floatPattern = RegExp(r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?'); 46 | 47 | /// TODO: update floatPattern to handle this specialCase 48 | static final doubleZeros = RegExp(r'00'); 49 | 50 | /// The [RegExp] pattern to match a valid non-negative float value. Allowed float 51 | /// values include starting with decimal (.3) and exponent notation (1.3e+4). 52 | static final nonNegativeFloatPattern = 53 | RegExp(r'[+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?'); 54 | 55 | /// The [RegExp] pattern to match a boolean flag (`1` or `0`). 56 | static final flagPattern = RegExp(r'(0|1)'); 57 | 58 | /// Queue of tokens generated to be returned. 59 | final _tokens = Queue(); 60 | 61 | /// The underlying StringScanner which scans the [source]. 62 | final StringScanner _scanner; 63 | 64 | /// Is the stream end token is produced. 65 | bool _streamEndProduced = false; 66 | 67 | /// Is the stream start token is produced. 68 | bool _streamStartProduced = false; 69 | 70 | /// checks if the next character is a whitespace character. 71 | bool get _isWhitespace => _isWhitespaceAt(0); 72 | 73 | /// checks if the next character is a command character. 74 | bool get _isCommand => _isCommandAt(0); 75 | 76 | /// checks if the next character is a comma. 77 | bool get _isSeparator => _scanner.peekChar() == COMMA; 78 | 79 | /// checks if the end of string is reached. 80 | bool get isDone => _scanner.isDone; 81 | 82 | /// returns the [CoordinateType] based on the case of a character 83 | CoordinateType _coordinateType(char) { 84 | return _isLowerChar(char) 85 | ? CoordinateType.relative 86 | : CoordinateType.absolute; 87 | } 88 | 89 | bool _isWhitespaceAt(int offset) { 90 | var char = _scanner.peekChar(offset); 91 | return char == 0x20 || char == 0x9 || char == 0xd || char == 0xa; 92 | } 93 | 94 | bool _isCommandAt(int offset) { 95 | var char = _scanner.peekChar(); 96 | return [ 97 | LETTER_A, 98 | LETTER_a, 99 | LETTER_C, 100 | LETTER_c, 101 | LETTER_H, 102 | LETTER_h, 103 | LETTER_L, 104 | LETTER_l, 105 | LETTER_M, 106 | LETTER_m, 107 | LETTER_Q, 108 | LETTER_q, 109 | LETTER_S, 110 | LETTER_s, 111 | LETTER_T, 112 | LETTER_t, 113 | LETTER_V, 114 | LETTER_v, 115 | LETTER_Z, 116 | LETTER_z, 117 | ].contains(char); 118 | } 119 | 120 | bool _isLowerChar(char) { 121 | return (LETTER_a <= char && char <= LETTER_z); 122 | } 123 | 124 | /// Creates a [Scanner] that scans [source]. 125 | /// 126 | /// [source] cannot be `null`. 127 | Scanner(String source) : _scanner = StringScanner(source); 128 | 129 | /// Consumes and returns the next token. 130 | Token? scan() { 131 | if (_streamEndProduced) return null; 132 | if (_tokens.isEmpty) _fetchNextToken(); 133 | return _tokens.removeFirst(); 134 | } 135 | 136 | /// Returns the next token without consuming it. 137 | Token? peek() { 138 | if (_streamEndProduced) return null; 139 | if (_tokens.isEmpty) _fetchNextToken(); 140 | return _tokens.first; 141 | } 142 | 143 | /// Populates [_tokens] by fetching more tokens. 144 | _fetchNextToken() { 145 | if (!_streamStartProduced) { 146 | _fetchStreamStart(); 147 | return; 148 | } 149 | 150 | _scanToNextToken(); 151 | 152 | if (_scanner.isDone) { 153 | _fetchStreamEnd(); 154 | return; 155 | } 156 | 157 | if (_isCommand) { 158 | _fetchCommand(); 159 | return; 160 | } 161 | 162 | _invalidCharacter(1); 163 | } 164 | 165 | /// Fetches a [CommandToken] and the required arguments' [ValueToken]s. 166 | _fetchCommand() { 167 | var coordinateType = _coordinateType(_scanner.peekChar()); 168 | var tokenType = _scanCommand(); 169 | 170 | _tokens.add(CommandToken(tokenType, coordinateType)); 171 | 172 | switch (tokenType) { 173 | case TokenType.ellipticalArcTo: 174 | _fetchArcCommandParams(); 175 | return; 176 | case TokenType.curveTo: 177 | _fetchMultipleCoordinatePair(3); 178 | return; 179 | case TokenType.smoothCurveTo: 180 | case TokenType.quadraticBezierCurveTo: 181 | _fetchMultipleCoordinatePair(2); 182 | return; 183 | case TokenType.lineTo: 184 | case TokenType.moveTo: 185 | case TokenType.smoothQuadraticBezierCurveTo: 186 | _fetchCoordinatePair(); 187 | return; 188 | case TokenType.horizontalLineTo: 189 | case TokenType.verticalLineTo: 190 | _fetchCoordinate(); 191 | return; 192 | case TokenType.closePath: 193 | return; 194 | } 195 | } 196 | 197 | /// Consumes whitespaces and commas until the next token or 198 | /// the end of source is reached. 199 | _scanToNextToken() { 200 | while (!isDone && (_isWhitespace || _isSeparator)) { 201 | _scanner.readChar(); 202 | } 203 | } 204 | 205 | /// Consumes all the whitespace till a non-whitespace character occurs 206 | /// or till the end of the source. 207 | _skipWhitespace() { 208 | while (!isDone && _isWhitespace) { 209 | _scanner.readChar(); 210 | } 211 | } 212 | 213 | /// Fetches a stream start token. 214 | _fetchStreamStart() { 215 | _tokens.add(Token(TokenType.streamStart)); 216 | _streamStartProduced = true; 217 | } 218 | 219 | /// Fetches a stream end token. 220 | _fetchStreamEnd() { 221 | _tokens.add(Token(TokenType.streamEnd)); 222 | _streamEndProduced = true; 223 | } 224 | 225 | /// Fetches a comma but raises an error when a second comma is found. 226 | _fetchSeparator() { 227 | _skipWhitespace(); 228 | if (_scanner.scanChar(COMMA)) { 229 | _skipWhitespace(); 230 | // Extra comma would raise an error. 231 | if (_scanner.peekChar() == COMMA) { 232 | _invalidCharacter(1); 233 | } 234 | } 235 | _skipWhitespace(); 236 | } 237 | 238 | /// Fetch the next comma. 239 | _fetchSingleSeparator() { 240 | _skipWhitespace(); 241 | _scanner.scanChar(COMMA); 242 | } 243 | 244 | /// Fetch a float value. 245 | _fetchFloatValue() => 246 | _tokens.add(ValueToken(TokenType.value, _scanFloatValue())); 247 | 248 | /// Fetch a non-negative float value. 249 | _fetchNonNegativeFloatValue() { 250 | _tokens.add(ValueToken(TokenType.value, _scanNonNegativeFloatValue())); 251 | } 252 | 253 | /// Fetch a boolean (1 | 0) flag. 254 | _fetchFlag() => _tokens.add(ValueToken(TokenType.flag, _scanFlag())); 255 | 256 | /// Fetch Parameters for ellipticalArcTo command. 257 | /// 258 | /// Production for ellipticalArcTo Arguments: 259 | /// elliptical-arc-argument-sequence: elliptical-arc-argument+ 260 | /// elliptical-arc-argument: 261 | /// nonnegative-number comma-wsp? nonnegative-number comma-wsp? 262 | /// number comma-wsp flag comma-wsp? flag comma-wsp? coordinate-pair 263 | _fetchArcCommandParams() { 264 | do { 265 | _skipWhitespace(); 266 | _fetchNonNegativeFloatValue(); 267 | _fetchSeparator(); 268 | _fetchNonNegativeFloatValue(); 269 | _fetchSeparator(); 270 | _fetchFloatValue(); 271 | _fetchSeparator(); 272 | _fetchFlag(); 273 | _fetchSeparator(); 274 | _fetchFlag(); 275 | _fetchSeparator(); 276 | _fetchSingleCoordinatePair(); 277 | _fetchSingleSeparator(); 278 | } while (!isDone && !_isCommand); 279 | } 280 | 281 | /// Fetch coordinate Pairs for moveTo, LineTo, smoothQuadraticBezierCurveTo commands. 282 | /// 283 | /// Production for ellipticalArcTo Arguments: 284 | /// lineto-argument-sequence: 285 | /// coordinate-pair 286 | /// | coordinate-pair comma-wsp? lineto-argument-sequence 287 | _fetchCoordinatePair() { 288 | do { 289 | _skipWhitespace(); 290 | _fetchSingleCoordinate(); 291 | _fetchSeparator(); 292 | _fetchSingleCoordinate(); 293 | _fetchSingleSeparator(); 294 | } while (!isDone && !_isCommand && !_isSeparator); 295 | } 296 | 297 | /// Fetch Single coordinates for horizontalMoveTo, verticalMoveTo commands. 298 | /// 299 | /// Production for ellipticalArcTo Arguments: 300 | /// horizontal-lineto-argument-sequence: 301 | /// coordinate 302 | /// | coordinate comma-wsp? horizontal-lineto-argument-sequence 303 | _fetchCoordinate() { 304 | do { 305 | _fetchSingleCoordinate(); 306 | _fetchSingleSeparator(); 307 | } while (!isDone && !_isCommand && !_isSeparator); 308 | } 309 | 310 | /// Fetch a single float value 311 | _fetchSingleCoordinate() { 312 | _skipWhitespace(); 313 | _fetchFloatValue(); 314 | _skipWhitespace(); 315 | } 316 | 317 | /// Fetch a single coordinate pair 318 | _fetchSingleCoordinatePair() { 319 | _skipWhitespace(); 320 | _fetchSingleCoordinate(); 321 | _fetchSeparator(); 322 | _fetchSingleCoordinate(); 323 | } 324 | 325 | /// fetches Multiple coordinate Pairs. 326 | /// 327 | /// Used to fetch Arguments for curveTo, smoothCurveTo, quadraticBezierCurveTo commands. 328 | _fetchMultipleCoordinatePair(int count) { 329 | do { 330 | for (var i = 1; i <= count; i++) { 331 | _skipWhitespace(); 332 | _fetchSingleCoordinate(); 333 | _fetchSeparator(); 334 | _fetchSingleCoordinate(); 335 | _fetchSingleSeparator(); 336 | } 337 | } while (!isDone && !_isCommand && !_isSeparator); 338 | } 339 | 340 | /// scans the source and generates a [CommandToken]. 341 | _scanCommand() { 342 | var char = _scanner.readChar(); 343 | if (char == LETTER_A || char == LETTER_a) return TokenType.ellipticalArcTo; 344 | if (char == LETTER_C || char == LETTER_c) return TokenType.curveTo; 345 | if (char == LETTER_H || char == LETTER_h) return TokenType.horizontalLineTo; 346 | if (char == LETTER_L || char == LETTER_l) return TokenType.lineTo; 347 | if (char == LETTER_M || char == LETTER_m) return TokenType.moveTo; 348 | if (char == LETTER_Q || char == LETTER_q) 349 | return TokenType.quadraticBezierCurveTo; 350 | if (char == LETTER_S || char == LETTER_s) return TokenType.smoothCurveTo; 351 | if (char == LETTER_T || char == LETTER_t) 352 | return TokenType.smoothQuadraticBezierCurveTo; 353 | if (char == LETTER_V || char == LETTER_v) return TokenType.verticalLineTo; 354 | if (char == LETTER_Z || char == LETTER_z) return TokenType.closePath; 355 | } 356 | 357 | /// scans the source and generates a [ValueToken]. 358 | double? _scanFloatValue() { 359 | if (_scanner.matches(doubleZeros)) { 360 | _scanner.scanChar(NUMBER_0); 361 | return 0; 362 | } else if (_scanner.matches(floatPattern)) { 363 | _scanner.scan(floatPattern); 364 | return double.parse(_scanner.lastMatch!.group(0)!); 365 | } else { 366 | _expectedFloatValue(); 367 | } 368 | return null; 369 | } 370 | 371 | /// scans the source and generates a [ValueToken]. 372 | 373 | double? _scanNonNegativeFloatValue() { 374 | if (_scanner.matches(nonNegativeFloatPattern)) { 375 | _scanner.scan(nonNegativeFloatPattern); 376 | return double.parse(_scanner.lastMatch!.group(0)!); 377 | } else { 378 | _expectedNonNegativeFloatValue(); 379 | } 380 | return null; 381 | } 382 | 383 | /// scans the source and generates a [ValueToken] having [TokenType.flag]. 384 | _scanFlag() { 385 | if (_scanner.scan(flagPattern)) { 386 | return int.parse(_scanner.lastMatch!.group(0)!); 387 | } else { 388 | _expectedZeroOneValue(); 389 | } 390 | } 391 | 392 | /// Raise an error for a unexpected character. 393 | _invalidCharacter([int length = 0]) { 394 | _scanner.error('Unexpected character.', length: length); 395 | } 396 | 397 | /// Raise an error when a float value is not found 398 | _expectedFloatValue() { 399 | _scanner.error("Expected a float Value."); 400 | } 401 | 402 | /// Raise an error when a non-negative float value is not found 403 | _expectedNonNegativeFloatValue() { 404 | _scanner.error("Expected a non-negative float Value."); 405 | } 406 | 407 | /// Raise an error when a boolean(1 | 0) is not found. 408 | _expectedZeroOneValue() { 409 | _scanner.error("Expected a 0 or 1."); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /lib/src/tokens.dart: -------------------------------------------------------------------------------- 1 | /// A token emitted by a [Scanner]. 2 | class Token { 3 | final TokenType type; 4 | 5 | Token(this.type); 6 | 7 | @override 8 | String toString() { 9 | return 'Token $type'; 10 | } 11 | 12 | @override 13 | bool operator ==(other) { 14 | return (other is Token) && this.type == other.type; 15 | } 16 | 17 | @override 18 | int get hashCode => type.hashCode; 19 | } 20 | 21 | /// A Token representing a command. 22 | class CommandToken implements Token { 23 | @override 24 | final TokenType type; 25 | 26 | /// Type of coordinates to use for the command. 27 | CoordinateType coordinateType; 28 | 29 | CommandToken(this.type, [this.coordinateType = CoordinateType.absolute]); 30 | 31 | @override 32 | String toString() { 33 | return 'COMMAND $type ($coordinateType)'; 34 | } 35 | 36 | @override 37 | bool operator ==(other) { 38 | if (other is CommandToken) { 39 | return this.type == other.type && 40 | this.coordinateType == other.coordinateType; 41 | } 42 | return false; 43 | } 44 | 45 | @override 46 | int get hashCode => type.hashCode * coordinateType.hashCode; 47 | } 48 | 49 | /// A token representing an argument value. 50 | class ValueToken implements Token { 51 | @override 52 | final TokenType type; 53 | 54 | /// The value of the argument 55 | final Object? value; 56 | 57 | ValueToken(this.type, this.value); 58 | 59 | @override 60 | String toString() { 61 | return 'VALUE $type $value'; 62 | } 63 | 64 | @override 65 | bool operator ==(other) { 66 | if (other is ValueToken) { 67 | return this.type == other.type && this.value == other.value; 68 | } 69 | return false; 70 | } 71 | 72 | @override 73 | int get hashCode => type.hashCode * value.hashCode; 74 | } 75 | 76 | /// The types of [Token] objects. 77 | enum TokenType { 78 | // Move To / Draw To Commands 79 | moveTo, 80 | closePath, 81 | lineTo, 82 | horizontalLineTo, 83 | verticalLineTo, 84 | curveTo, 85 | smoothCurveTo, 86 | quadraticBezierCurveTo, 87 | smoothQuadraticBezierCurveTo, 88 | ellipticalArcTo, 89 | 90 | // Command Parameters 91 | value, 92 | flag, 93 | 94 | // Stream Start/End 95 | streamStart, 96 | streamEnd 97 | } 98 | 99 | /// The types of coordinates to use for commands. 100 | enum CoordinateType { absolute, relative } 101 | -------------------------------------------------------------------------------- /lib/svg_path_parser.dart: -------------------------------------------------------------------------------- 1 | library svg_path_parser; 2 | 3 | import 'src/parser.dart'; 4 | import 'dart:ui'; 5 | 6 | export 'src/tokens.dart'; 7 | export 'src/scanner.dart'; 8 | export 'src/parser.dart'; 9 | 10 | /// A wrapper to quickly parse a Svg path. 11 | Path parseSvgPath(String source, {bool failSilently = false}) { 12 | try { 13 | return Parser(source).parse(); 14 | } catch (e) { 15 | if (!failSilently) { 16 | rethrow; 17 | } else { 18 | return Path(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: svg_path_parser 2 | description: > 3 | A Flutter/Dart utility to parse an SVG path into a 4 | equivalent Path object from dart:ui library. 5 | version: 1.1.2 6 | homepage: https://github.com/masterashu/svg_path_parser 7 | repository: https://github.com/masterashu/svg_path_parser 8 | issue_tracker: https://github.com/masterashu/svg_path_parser/issues 9 | 10 | environment: 11 | sdk: '>=2.12.0 <3.0.0' 12 | 13 | dependencies: 14 | string_scanner: ^1.1.0 15 | flutter: 16 | sdk: flutter 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | test: ^1.16.5 22 | 23 | flutter: 24 | -------------------------------------------------------------------------------- /test/parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:svg_path_parser/svg_path_parser.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | // Tests the scanner for correctly parses the input. 8 | main() { 9 | final testingArea = 10 | Rect.fromCenter(center: Offset.zero, width: 15, height: 15); 11 | group('Testing paths from commands', () { 12 | group('test moveTo commands', () { 13 | test('test consecutive arguments act as lineTo commands.', () { 14 | // test relative path 15 | var expectedPath = Path() 16 | ..moveTo(2, 2) 17 | ..lineTo(5, 6) 18 | ..lineTo(6, 8); 19 | expect( 20 | parseSvgPath('M2,2 5,6, 6,8'), 21 | coversSameAreaAs(expectedPath, 22 | areaToCompare: testingArea, sampleSize: 400)); 23 | // test relative path 24 | expect( 25 | parseSvgPath('m2,2 3,4, 1,2'), 26 | coversSameAreaAs(expectedPath, 27 | areaToCompare: testingArea, sampleSize: 400)); 28 | }); 29 | 30 | test('test moves', () { 31 | var expectedPath = Path() 32 | ..moveTo(1, 1) 33 | ..moveTo(2, 2) 34 | ..lineTo(3, 3) 35 | ..lineTo(4, 2); 36 | expect( 37 | parseSvgPath('M1,1M2,2L3,3L4,2'), 38 | coversSameAreaAs(expectedPath, 39 | areaToCompare: testingArea, sampleSize: 400)); 40 | expect( 41 | parseSvgPath('M1,1m1,1L3,3L4,2'), 42 | coversSameAreaAs(expectedPath, 43 | areaToCompare: testingArea, sampleSize: 400)); 44 | }); 45 | }); 46 | 47 | group('test lineTo commands', () { 48 | test('test lineTo absolute', () { 49 | var expectedPath = Path() 50 | ..moveTo(2, 2) 51 | ..lineTo(0, 4) 52 | ..lineTo(2, 6) 53 | ..lineTo(4, 4); 54 | expect( 55 | parseSvgPath('M2,2L0 4, 2,6, 4 4'), 56 | coversSameAreaAs(expectedPath, 57 | areaToCompare: testingArea, sampleSize: 400)); 58 | }); 59 | 60 | test('test lineTo relative', () { 61 | var expectedPath = Path() 62 | ..moveTo(2, 2) 63 | ..lineTo(2, 6) 64 | ..lineTo(0, 4) 65 | ..lineTo(4, 4); 66 | expect( 67 | parseSvgPath('M2,2l0 4-2-2,4,0'), 68 | coversSameAreaAs(expectedPath, 69 | areaToCompare: testingArea, sampleSize: 400)); 70 | }); 71 | 72 | group('test horizontal/vertical lineTo commands', () { 73 | test('test horizontal/vertocalLineTo absolute', () { 74 | var expectedPath = Path() 75 | ..moveTo(2, 2) 76 | ..lineTo(2, 4) 77 | ..lineTo(4, 4) 78 | ..lineTo(4, 2); 79 | expect( 80 | parseSvgPath('M2,2V4H4V2'), 81 | coversSameAreaAs(expectedPath, 82 | areaToCompare: testingArea, sampleSize: 400)); 83 | }); 84 | 85 | test('test horizontal/vertocalLineTo relative', () { 86 | var expectedPath = Path() 87 | ..moveTo(2, 2) 88 | ..lineTo(2, 4) 89 | ..lineTo(4, 4) 90 | ..lineTo(4, 2); 91 | expect( 92 | parseSvgPath('M2,2v2h2v-2'), 93 | coversSameAreaAs(expectedPath, 94 | areaToCompare: testingArea, sampleSize: 400)); 95 | }); 96 | }); 97 | }); 98 | 99 | group('test curveTo commands', () { 100 | test('test normal curveTo', () { 101 | var expectedPath = Path() 102 | ..moveTo(2, 2) 103 | ..cubicTo(3, 3, 6, 6, 4, 4); 104 | // test absolute 105 | expect( 106 | parseSvgPath('M2,2C3,3,6,6,4,4'), 107 | coversSameAreaAs(expectedPath, 108 | areaToCompare: testingArea, sampleSize: 400)); 109 | // test relative 110 | expect( 111 | parseSvgPath('M2 2c1 1 4 4 2 2'), 112 | coversSameAreaAs(expectedPath, 113 | areaToCompare: testingArea, sampleSize: 400)); 114 | }); 115 | 116 | group('test chained curveTo', () { 117 | test('test chained curveTo', () { 118 | var expectedPath = Path() 119 | ..cubicTo(1, 1, 4, 3, 5, 5) 120 | ..cubicTo(3, 4, 4, 7, 8, 5); 121 | expect( 122 | parseSvgPath('C1,1,4,3,5,5, 3 4 4 7 8,5'), 123 | coversSameAreaAs(expectedPath, 124 | areaToCompare: testingArea, sampleSize: 400)); 125 | }); 126 | 127 | test('test chained curveTo and smooth curveTo', () { 128 | var expectedPath = Path() 129 | ..cubicTo(1, 1, 4, 3, 5, 5) 130 | ..cubicTo(6, 7, 4, 7, 8, 5); 131 | expect( 132 | parseSvgPath('C1,1,4,3,5,5S 4 7 8,5'), 133 | coversSameAreaAs(expectedPath, 134 | areaToCompare: testingArea, sampleSize: 400)); 135 | }); 136 | 137 | test('test smoothCurveTo takes current point as control point', () { 138 | var expectedPath = Path() 139 | ..moveTo(2, 2) 140 | ..cubicTo(2, 2, 3, 5, 2, 6); 141 | expect( 142 | parseSvgPath('M2,2 S3,5 2,6'), 143 | coversSameAreaAs(expectedPath, 144 | areaToCompare: testingArea, sampleSize: 400)); 145 | }); 146 | }); 147 | }); 148 | 149 | group('test quadraticBezierTo commands', () { 150 | test('test normal quadraticBezier', () { 151 | var expectedPath = Path() 152 | ..moveTo(2, 2) 153 | ..quadraticBezierTo(5, 6, 4, 4); 154 | // test absolute 155 | expect( 156 | parseSvgPath('M2,2Q5,6,4,4'), 157 | coversSameAreaAs(expectedPath, 158 | areaToCompare: testingArea, sampleSize: 400)); 159 | // test relative 160 | expect( 161 | parseSvgPath('M2 2q 3 4 2 2'), 162 | coversSameAreaAs(expectedPath, 163 | areaToCompare: testingArea, sampleSize: 400)); 164 | }); 165 | 166 | group('test chained quadraticBezier', () { 167 | test('test chained quadraticBezier', () { 168 | var expectedPath = Path() 169 | ..quadraticBezierTo(1, 1, 4, 3) 170 | ..quadraticBezierTo(3, 7, 8, 5); 171 | expect( 172 | parseSvgPath('Q1,1,4,3Q3,7 8,5'), 173 | coversSameAreaAs(expectedPath, 174 | areaToCompare: testingArea, sampleSize: 400)); 175 | }); 176 | 177 | test('test chained quadraticBezier and smooth quadraticBezier', () { 178 | var expectedPath = Path() 179 | ..quadraticBezierTo(1, 3, 5, 5) 180 | ..quadraticBezierTo(9, 7, 8, 5); 181 | expect( 182 | parseSvgPath('Q1,3,5,5T8,5'), 183 | coversSameAreaAs(expectedPath, 184 | areaToCompare: testingArea, sampleSize: 400)); 185 | }); 186 | }); 187 | }); 188 | 189 | group('test ellipticalArcTo commands', () { 190 | test('test ellipticalArtTo absolute', () { 191 | var expectedPath = Path() 192 | ..moveTo(0, 4) 193 | ..arcToPoint(Offset(7, 1), 194 | radius: Radius.elliptical(5, 8), 195 | rotation: 0.12, 196 | largeArc: true, 197 | clockwise: true); 198 | expect( 199 | parseSvgPath('M0,4A5,8,.12 1 1, 7,1'), 200 | coversSameAreaAs(expectedPath, 201 | areaToCompare: testingArea, sampleSize: 400)); 202 | }); 203 | 204 | test('test ellipticalArtTo relative', () { 205 | var expectedPath = Path() 206 | ..moveTo(0, 0) 207 | ..arcToPoint(Offset(5, 5), 208 | radius: Radius.elliptical(11, 7), largeArc: true, clockwise: true) 209 | ..close(); 210 | // Note: Make a check for out of range parameters: 211 | // https://www.w3.org/TR/SVG11/implnote.html#ArcOutOfRangeParameters 212 | expect( 213 | parseSvgPath('a11,7, 0 1 1, 5,5z'), 214 | coversSameAreaAs(expectedPath, 215 | areaToCompare: testingArea, sampleSize: 400)); 216 | }); 217 | }); 218 | }); 219 | 220 | group('Testing combination of relative and absolute commands', () { 221 | final testingArea = 222 | Rect.fromCenter(center: Offset.zero, width: 20, height: 20); 223 | 224 | group('Test Flag pattern on cubic curves', () { 225 | final testingArea = 226 | Rect.fromCenter(center: Offset.zero, width: 20, height: 20); 227 | group('test ellipticalArcTo commands', () { 228 | test('test ellipticalArtTo absolute', () { 229 | var expectedPath = Path() 230 | ..moveTo(0, 4) 231 | ..arcToPoint(Offset(7, 1), 232 | radius: Radius.elliptical(5, 8), 233 | rotation: 0.12, 234 | largeArc: true, 235 | clockwise: true); 236 | expect( 237 | parseSvgPath('M0,4A5,8,.12 11 7 1'), 238 | coversSameAreaAs(expectedPath, 239 | areaToCompare: testingArea, sampleSize: 400)); 240 | }); 241 | 242 | test('test ellipticalArtTo relative', () { 243 | var expectedPath = Path() 244 | ..moveTo(0, 0) 245 | ..arcToPoint(Offset(5, 5), 246 | radius: Radius.elliptical(11, 7), 247 | largeArc: true, 248 | clockwise: true) 249 | ..close(); 250 | // Note: Make a check for out of range parameters: 251 | // https://www.w3.org/TR/SVG11/implnote.html#ArcOutOfRangeParameters 252 | expect( 253 | parseSvgPath('a11,7, 0 115,5z'), 254 | coversSameAreaAs(expectedPath, 255 | areaToCompare: testingArea, sampleSize: 400)); 256 | }); 257 | }); 258 | }); 259 | }); 260 | 261 | group('Test square path on different combinations', () { 262 | test('Testing flags without any spaces', () { 263 | var expectedPath = Path() 264 | ..moveTo(0, 0) 265 | ..lineTo(0, 5) 266 | ..lineTo(5, 5) 267 | ..lineTo(5, 0) 268 | ..close(); 269 | 270 | expect( 271 | parseSvgPath('M 0 0 V5 H5 V0z'), 272 | coversSameAreaAs(expectedPath, 273 | areaToCompare: testingArea, sampleSize: 400)); 274 | expect( 275 | parseSvgPath('M0 0 v5 h5 V0z'), 276 | coversSameAreaAs(expectedPath, 277 | areaToCompare: testingArea, sampleSize: 400)); 278 | expect( 279 | parseSvgPath('M0 0 V5 h5 v-5z'), 280 | coversSameAreaAs(expectedPath, 281 | areaToCompare: testingArea, sampleSize: 400)); 282 | expect( 283 | parseSvgPath('M0 0 h5 V5 H0 z'), 284 | coversSameAreaAs(expectedPath, 285 | areaToCompare: testingArea, sampleSize: 400)); 286 | expect( 287 | parseSvgPath('M0 0 v5 H5 v-5z'), 288 | coversSameAreaAs(expectedPath, 289 | areaToCompare: testingArea, sampleSize: 400)); 290 | }); 291 | }); 292 | } 293 | -------------------------------------------------------------------------------- /test/scanner_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:string_scanner/string_scanner.dart'; 2 | import 'package:svg_path_parser/svg_path_parser.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | // Tests the scanner for correctly generating the tokens and throwing errors 6 | main() { 7 | test("scan empty string", () { 8 | var scanner = Scanner(''); 9 | expect(scanner.scan()!.type, TokenType.streamStart); 10 | expect(scanner.scan()!.type, TokenType.streamEnd); 11 | }); 12 | 13 | group("All Commands are scanned correctly", () { 14 | group("closePath command", () { 15 | test("closePath command scanned correctly", () { 16 | var scanner = Scanner('Z,z')..scan(); 17 | expect(scanner.scan(), 18 | CommandToken(TokenType.closePath, CoordinateType.absolute)); 19 | expect(scanner.scan(), 20 | CommandToken(TokenType.closePath, CoordinateType.relative)); 21 | }); 22 | }); 23 | 24 | group("moveTo command", () { 25 | test("moveTo command scanned correctly", () { 26 | var scanner = Scanner('M10,20')..scan(); 27 | expect(scanner.scan(), 28 | CommandToken(TokenType.moveTo, CoordinateType.absolute)); 29 | }); 30 | 31 | test("relativeMoveTo command scanned correctly", () { 32 | var scanner = Scanner('m10,20')..scan(); 33 | expect(scanner.scan(), 34 | CommandToken(TokenType.moveTo, CoordinateType.relative)); 35 | }); 36 | 37 | test("moveTo missing/incomplete args", () { 38 | var scanner = Scanner('m')..scan(); 39 | expect(() => scanner.scan(), 40 | throwsA(isInstanceOf())); 41 | scanner = Scanner('m10')..scan(); 42 | expect(() => scanner.scan(), 43 | throwsA(isInstanceOf())); 44 | scanner = Scanner('m10,20,30')..scan(); 45 | expect(() => scanner.scan(), 46 | throwsA(isInstanceOf())); 47 | }); 48 | 49 | test("moveTo scan extra args", () { 50 | var scanner = Scanner('m1,1 2,4')..scan(); 51 | expect(scanner.scan(), 52 | CommandToken(TokenType.moveTo, CoordinateType.relative)); 53 | expect(scanner.scan(), ValueToken(TokenType.value, 1.0)); 54 | expect(scanner.scan(), ValueToken(TokenType.value, 1.0)); 55 | expect(scanner.scan(), ValueToken(TokenType.value, 2.0)); 56 | expect(scanner.scan(), ValueToken(TokenType.value, 4.0)); 57 | }); 58 | 59 | test("moveTo when first argument is zero", () { 60 | var scanner = Scanner('m00')..scan(); 61 | expect(scanner.scan(), 62 | CommandToken(TokenType.moveTo, CoordinateType.relative)); 63 | expect(scanner.scan(), ValueToken(TokenType.value, 0.0)); 64 | expect(scanner.scan(), ValueToken(TokenType.value, 0.0)); 65 | }); 66 | }); 67 | 68 | group("lineTo command", () { 69 | test("lineTo command scanned correctly", () { 70 | var scanner = Scanner('L10,20')..scan(); 71 | expect(scanner.scan(), 72 | CommandToken(TokenType.lineTo, CoordinateType.absolute)); 73 | }); 74 | 75 | test("relativeLineTo command scanned correctly", () { 76 | var scanner = Scanner('l10,20')..scan(); 77 | expect(scanner.scan(), 78 | CommandToken(TokenType.lineTo, CoordinateType.relative)); 79 | }); 80 | 81 | test("lineTo missing/incomplete args", () { 82 | var scanner = Scanner('l')..scan(); 83 | expect(() => scanner.scan(), 84 | throwsA(isInstanceOf())); 85 | scanner = Scanner('l10')..scan(); 86 | expect(() => scanner.scan(), 87 | throwsA(isInstanceOf())); 88 | scanner = Scanner('l10,20,30')..scan(); 89 | expect(() => scanner.scan(), 90 | throwsA(isInstanceOf())); 91 | }); 92 | 93 | test("lineTo scan extra args", () { 94 | var scanner = Scanner('l1,1 2,4')..scan(); 95 | expect(scanner.scan(), 96 | CommandToken(TokenType.lineTo, CoordinateType.relative)); 97 | expect(scanner.scan(), ValueToken(TokenType.value, 1.0)); 98 | expect(scanner.scan(), ValueToken(TokenType.value, 1.0)); 99 | expect(scanner.scan(), ValueToken(TokenType.value, 2.0)); 100 | expect(scanner.scan(), ValueToken(TokenType.value, 4.0)); 101 | }); 102 | }); 103 | 104 | group("horizontalLineTo command", () { 105 | test("horizontalLineTo command scanned correctly", () { 106 | var scanner = Scanner('H10')..scan(); 107 | expect(scanner.scan(), 108 | CommandToken(TokenType.horizontalLineTo, CoordinateType.absolute)); 109 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 110 | }); 111 | 112 | test("horizontalLineTo command scanned correctly spaced", () { 113 | var scanner = Scanner('H 10')..scan(); 114 | expect(scanner.scan(), 115 | CommandToken(TokenType.horizontalLineTo, CoordinateType.absolute)); 116 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 117 | }); 118 | 119 | test("relativeHorizontalLineTo command scanned correctly", () { 120 | var scanner = Scanner('h10')..scan(); 121 | expect(scanner.scan(), 122 | CommandToken(TokenType.horizontalLineTo, CoordinateType.relative)); 123 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 124 | }); 125 | 126 | test("horizontalTo missing/incomplete args", () { 127 | var scanner = Scanner('H')..scan(); 128 | expect(() => scanner.scan(), 129 | throwsA(isInstanceOf())); 130 | }); 131 | 132 | test("horizontalTo scan extra args", () { 133 | var scanner = Scanner('h1,4')..scan(); 134 | expect(scanner.scan(), 135 | CommandToken(TokenType.horizontalLineTo, CoordinateType.relative)); 136 | expect(scanner.scan(), ValueToken(TokenType.value, 1.0)); 137 | expect(scanner.scan(), ValueToken(TokenType.value, 4.0)); 138 | }); 139 | }); 140 | 141 | group("verticalLineTo command", () { 142 | test("verticalLineTo command scanned correctly", () { 143 | var scanner = Scanner('V10')..scan(); 144 | expect(scanner.scan(), 145 | CommandToken(TokenType.verticalLineTo, CoordinateType.absolute)); 146 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 147 | }); 148 | 149 | test("relativeVerticalLineTo command scanned correctly", () { 150 | var scanner = Scanner('v10')..scan(); 151 | expect(scanner.scan(), 152 | CommandToken(TokenType.verticalLineTo, CoordinateType.relative)); 153 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 154 | }); 155 | 156 | test("verticalTo missing/incomplete args", () { 157 | var scanner = Scanner('V')..scan(); 158 | expect(() => scanner.scan(), 159 | throwsA(isInstanceOf())); 160 | scanner = Scanner('m10')..scan(); 161 | }); 162 | 163 | test("verticalTo scan extra args", () { 164 | var scanner = Scanner('v1,4')..scan(); 165 | expect(scanner.scan(), 166 | CommandToken(TokenType.verticalLineTo, CoordinateType.relative)); 167 | expect(scanner.scan(), ValueToken(TokenType.value, 1.0)); 168 | expect(scanner.scan(), ValueToken(TokenType.value, 4.0)); 169 | }); 170 | }); 171 | 172 | group("curveTo command", () { 173 | test("curveTo command scanned correctly", () { 174 | var scanner = Scanner('C10,10 20,20, 15,0')..scan(); 175 | expect(scanner.scan(), 176 | CommandToken(TokenType.curveTo, CoordinateType.absolute)); 177 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 178 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 179 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 180 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 181 | expect(scanner.scan(), ValueToken(TokenType.value, 15.0)); 182 | expect(scanner.scan(), ValueToken(TokenType.value, 0.0)); 183 | }); 184 | 185 | test("relativeCurveTo command scanned correctly", () { 186 | var scanner = Scanner('c10,10 5,-5, -10,5')..scan(); 187 | expect(scanner.scan(), 188 | CommandToken(TokenType.curveTo, CoordinateType.relative)); 189 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 190 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 191 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 192 | expect(scanner.scan(), ValueToken(TokenType.value, -5.0)); 193 | expect(scanner.scan(), ValueToken(TokenType.value, -10.0)); 194 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 195 | }); 196 | 197 | test("curveTo missing/incomplete args", () { 198 | var scanner = Scanner('C 10,10')..scan(); 199 | expect(() => scanner.scan(), 200 | throwsA(isInstanceOf())); 201 | scanner = Scanner('C 10,10 5,5')..scan(); 202 | expect(() => scanner.scan(), 203 | throwsA(isInstanceOf())); 204 | scanner = Scanner('C 10,10, 20,20, 10,50, 10')..scan(); 205 | expect(() => scanner.scan(), 206 | throwsA(isInstanceOf())); 207 | }); 208 | 209 | test("curveTo scan extra args", () { 210 | var scanner = Scanner('C 10,10, 20,20, 10,50, 20,20, 10,50, 34,55') 211 | ..scan(); 212 | expect(scanner.scan(), 213 | CommandToken(TokenType.curveTo, CoordinateType.absolute)); 214 | }); 215 | }); 216 | 217 | group("smoothCurveTo command", () { 218 | test("smoothCurveTo command scanned correctly", () { 219 | var scanner = Scanner('S10,10 20,20')..scan(); 220 | expect(scanner.scan(), 221 | CommandToken(TokenType.smoothCurveTo, CoordinateType.absolute)); 222 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 223 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 224 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 225 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 226 | }); 227 | 228 | test("relativeSmoothCurveTo command scanned correctly", () { 229 | var scanner = Scanner('s10,10 5,5')..scan(); 230 | expect(scanner.scan(), 231 | CommandToken(TokenType.smoothCurveTo, CoordinateType.relative)); 232 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 233 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 234 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 235 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 236 | }); 237 | test("smoothCurveTo missing/incomplete args", () { 238 | var scanner = Scanner('s 10')..scan(); 239 | expect(() => scanner.scan(), 240 | throwsA(isInstanceOf())); 241 | scanner = Scanner('s 10,10 5')..scan(); 242 | expect(() => scanner.scan(), 243 | throwsA(isInstanceOf())); 244 | scanner = Scanner('S 10,10, 20,20, 10,50, 10')..scan(); 245 | expect(() => scanner.scan(), 246 | throwsA(isInstanceOf())); 247 | }); 248 | 249 | test("smoothCurveTo scan extra args", () { 250 | var scanner = Scanner('S 10,10, 20,20, 10,50, 20,20')..scan(); 251 | expect(scanner.scan(), 252 | CommandToken(TokenType.smoothCurveTo, CoordinateType.absolute)); 253 | }); 254 | }); 255 | 256 | group("quadraticBezierCurveTo command", () { 257 | test("quadraticBezierCurveTo command scanned correctly", () { 258 | var scanner = Scanner('Q10,10 20,20')..scan(); 259 | expect( 260 | scanner.scan(), 261 | CommandToken( 262 | TokenType.quadraticBezierCurveTo, CoordinateType.absolute)); 263 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 264 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 265 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 266 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 267 | }); 268 | 269 | test("relativeQuadraticBezierCurveTo command scanned correctly", () { 270 | var scanner = Scanner('q10,-5, -10,5')..scan(); 271 | expect( 272 | scanner.scan(), 273 | CommandToken( 274 | TokenType.quadraticBezierCurveTo, CoordinateType.relative)); 275 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 276 | expect(scanner.scan(), ValueToken(TokenType.value, -5.0)); 277 | expect(scanner.scan(), ValueToken(TokenType.value, -10.0)); 278 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 279 | }); 280 | 281 | test("quadraticBezierCurveTo missing/incomplete args", () { 282 | var scanner = Scanner('q 10')..scan(); 283 | expect(() => scanner.scan(), 284 | throwsA(isInstanceOf())); 285 | scanner = Scanner('q 10,10 5')..scan(); 286 | expect(() => scanner.scan(), 287 | throwsA(isInstanceOf())); 288 | scanner = Scanner('Q 10,10, 20,20, 10,50, 10')..scan(); 289 | expect(() => scanner.scan(), 290 | throwsA(isInstanceOf())); 291 | }); 292 | 293 | test("quadraticBezierCurveTo scan extra args", () { 294 | var scanner = Scanner('Q 10,10, 20,20, 10,50, 20,20')..scan(); 295 | expect( 296 | scanner.scan(), 297 | CommandToken( 298 | TokenType.quadraticBezierCurveTo, CoordinateType.absolute)); 299 | }); 300 | }); 301 | 302 | group("smoothQuadraticBezierCurveTo command", () { 303 | test("smoothQuadraticBezierCurveTo command scanned correctly", () { 304 | var scanner = Scanner('T10,10')..scan(); 305 | expect( 306 | scanner.scan(), 307 | CommandToken(TokenType.smoothQuadraticBezierCurveTo, 308 | CoordinateType.absolute)); 309 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 310 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 311 | }); 312 | 313 | test("relativeSmoothQuadraticBezierCurveTo command scanned correctly", 314 | () { 315 | var scanner = Scanner('t10,5')..scan(); 316 | expect( 317 | scanner.scan(), 318 | CommandToken(TokenType.smoothQuadraticBezierCurveTo, 319 | CoordinateType.relative)); 320 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 321 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 322 | }); 323 | 324 | test("smoothQuadraticBezierCurveTo missing/incomplete args", () { 325 | var scanner = Scanner('t 10')..scan(); 326 | expect(() => scanner.scan(), 327 | throwsA(isInstanceOf())); 328 | scanner = Scanner('T 10,10 5')..scan(); 329 | expect(() => scanner.scan(), 330 | throwsA(isInstanceOf())); 331 | }); 332 | 333 | test("smoothQuadraticBezierCurveTo scan extra args", () { 334 | var scanner = Scanner('T 10,10, 20,20')..scan(); 335 | expect( 336 | scanner.scan(), 337 | CommandToken(TokenType.smoothQuadraticBezierCurveTo, 338 | CoordinateType.absolute)); 339 | }); 340 | }); 341 | 342 | group("ellipticalArcTo command", () { 343 | test("ellipticalArcTo command scanned correctly", () { 344 | var scanner = Scanner('A10,10 20, 1,0 20,10')..scan(); 345 | expect(scanner.scan(), 346 | CommandToken(TokenType.ellipticalArcTo, CoordinateType.absolute)); 347 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 348 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 349 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 350 | expect(scanner.scan(), ValueToken(TokenType.flag, 1)); 351 | expect(scanner.scan(), ValueToken(TokenType.flag, 0)); 352 | expect(scanner.scan(), ValueToken(TokenType.value, 20.0)); 353 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 354 | }); 355 | 356 | test("relativeEllipticalArcTo command scanned correctly", () { 357 | var scanner = Scanner('a10,10 5 0,0 -5,5')..scan(); 358 | expect(scanner.scan(), 359 | CommandToken(TokenType.ellipticalArcTo, CoordinateType.relative)); 360 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 361 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 362 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 363 | expect(scanner.scan(), ValueToken(TokenType.flag, 0)); 364 | expect(scanner.scan(), ValueToken(TokenType.flag, 0)); 365 | expect(scanner.scan(), ValueToken(TokenType.value, -5.0)); 366 | expect(scanner.scan(), ValueToken(TokenType.value, 5.0)); 367 | }); 368 | 369 | test("ellipticalArcTo missing/incomplete args", () { 370 | var scanner = Scanner('a 10')..scan(); 371 | expect(() => scanner.scan(), 372 | throwsA(isInstanceOf())); 373 | scanner = Scanner('a 10,10 5')..scan(); 374 | expect(() => scanner.scan(), 375 | throwsA(isInstanceOf())); 376 | scanner = Scanner('a10,10, 20 0,1 10 50,10 10')..scan(); 377 | expect(() => scanner.scan(), 378 | throwsA(isInstanceOf())); 379 | }); 380 | 381 | test("wrong flag argument", () { 382 | var scanner = Scanner('a10,10 5 0.1,1 -5')..scan(); 383 | expect(() => scanner.scan(), 384 | throwsA(isInstanceOf())); 385 | }); 386 | 387 | test("ellipticalArcTo scan extra args", () { 388 | var scanner = Scanner('A 10,10, 20,0,1 10,50 5,60, 10,1,1 16,10') 389 | ..scan(); 390 | expect(scanner.scan(), 391 | CommandToken(TokenType.ellipticalArcTo, CoordinateType.absolute)); 392 | }); 393 | }); 394 | }); 395 | 396 | group("test correct scan of commas", () { 397 | test("error on consecutive comma bewteen values", () { 398 | var scanner = Scanner('M 10,,10')..scan(); 399 | expect(() => scanner.scan(), 400 | throwsA(isInstanceOf())); 401 | }); 402 | test("allow consecutive comma bewteen/before commands", () { 403 | var scanner = Scanner('M10,10,,,z')..scan(); 404 | expect(scanner.scan(), 405 | CommandToken(TokenType.moveTo, CoordinateType.absolute)); 406 | }); 407 | test("allow consecutive comma at the end", () { 408 | var scanner = Scanner('M10,10,,,,')..scan(); 409 | expect(scanner.scan(), 410 | CommandToken(TokenType.moveTo, CoordinateType.absolute)); 411 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 412 | expect(scanner.scan(), ValueToken(TokenType.value, 10.0)); 413 | expect(scanner.scan(), Token(TokenType.streamEnd)); 414 | }); 415 | }); 416 | } 417 | -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:svg_path_parser/svg_path_parser.dart'; 2 | 3 | bool equalToken(Token a, Token b) { 4 | if (a is CommandToken && b is CommandToken) { 5 | return a.type == b.type && a.coordinateType == b.coordinateType; 6 | } else if (a is ValueToken && b is ValueToken) { 7 | return a.value == b.value; 8 | } else { 9 | return false; 10 | } 11 | } 12 | --------------------------------------------------------------------------------