diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 9dc8f3a..fc2f5d7 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -5,7 +5,7 @@ on:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: [self-hosted, macOS, ARM64]
steps:
- uses: actions/checkout@v2
@@ -24,5 +24,15 @@ jobs:
flutter channel stable
flutter doctor
+ - name: Build WASM
+ run: |
+ cd wasm
+ ./build_wasm.sh release
+
+ - name: Build Shared Lib
+ run: |
+ cd shared_lib
+ ./build_shared.sh
+
- name: Run tests
- run: flutter test
\ No newline at end of file
+ run: flutter test
diff --git a/.gitignore b/.gitignore
index 5c27990..2491f4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,3 +74,19 @@ pubspec.lock
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+
+ios/rive-cpp
+ios/harfbuzz
+macos/rive-cpp
+macos/harfbuzz
+macos/SheenBidi
+wasm/bin/
+wasm/Makefile
+wasm/render_font.make
+node_modules
+wasm/*.make
+wasm/dependencies
+shared_lib/dependencies
+shared_lib/bin
+*.make
+Makefile
diff --git a/.lua-format b/.lua-format
new file mode 100644
index 0000000..9a55042
--- /dev/null
+++ b/.lua-format
@@ -0,0 +1,32 @@
+column_limit: 80
+indent_width: 4
+use_tab: false
+tab_width: 4
+continuation_indent_width: 4
+spaces_before_call: 1
+keep_simple_control_block_one_line: true
+keep_simple_function_one_line: true
+align_args: true
+break_after_functioncall_lp: false
+break_before_functioncall_rp: false
+spaces_inside_functioncall_parens: false
+spaces_inside_functiondef_parens: false
+align_parameter: true
+chop_down_parameter: false
+break_after_functiondef_lp: false
+break_before_functiondef_rp: false
+align_table_field: true
+break_after_table_lb: true
+break_before_table_rb: true
+chop_down_table: true
+chop_down_kv_table: true
+table_sep: ","
+column_table_limit: column_limit
+extra_sep_at_table_end: false
+spaces_inside_table_braces: false
+break_after_operator: true
+double_quote_to_single_quote: false
+single_quote_to_double_quote: false
+spaces_around_equals_in_field: true
+line_breaks_after_function_body: 1
+line_separator: input
diff --git a/.metadata b/.metadata
index 0692685..909dc9a 100644
--- a/.metadata
+++ b/.metadata
@@ -1,10 +1,42 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
-# This file should be version controlled and should not be manually edited.
+# This file should be version controlled.
version:
- revision: b9a56b9f48462d897101284f5f57b8568ef683c6
- channel: master
+ revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ channel: unknown
-project_type: package
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ - platform: android
+ create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ - platform: ios
+ create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ - platform: macos
+ create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ - platform: web
+ create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ - platform: windows
+ create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/.rive_head b/.rive_head
index 99b7c3a..0470aa7 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-f15304279962053360702d3ef8443d110d444893
+3be5ff0d873280d2bcec300fb764f016ac96a08d
diff --git a/README.md b/README.md
index 6ed9d85..21def3d 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,9 @@ dependencies:
rive: ^0.9.0
```
+## Platform Considerations
+Read some [platform specific considerations](platform_considerations.md) with regards to the Rive Flutter package.
+
## Quick Start
Play an animation from a Rive file over HTTP:
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt
new file mode 100644
index 0000000..f97800e
--- /dev/null
+++ b/android/CMakeLists.txt
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 3.4.1) # for example
+
+set(CMAKE_CXX_FLAGS
+ "${CMAKE_CXX_FLAGS} -DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -std=c++17"
+)
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSB_CONFIG_UNITY")
+
+add_library(
+ rive_text
+ # Sets the library as a shared library.
+ SHARED
+ # Provides a relative path to your source file(s).
+ ../ios/rive_text/rive_text.cpp
+ ../ios/rive-cpp/src/math/raw_path.cpp
+ ../ios/rive-cpp/src/math/mat2d.cpp
+ ../ios/rive-cpp/src/rive_counter.cpp
+ ../ios/rive-cpp/src/renderer.cpp
+ ../ios/rive-cpp/src/text/font_hb.cpp
+ ../ios/rive-cpp/src/text/line_breaker.cpp
+ ../ios/harfbuzz/src/hb-aat-layout.cc
+ ../ios/harfbuzz/src/hb-aat-map.cc
+ ../ios/harfbuzz/src/hb-blob.cc
+ ../ios/harfbuzz/src/hb-buffer-serialize.cc
+ ../ios/harfbuzz/src/hb-buffer-verify.cc
+ ../ios/harfbuzz/src/hb-buffer.cc
+ ../ios/harfbuzz/src/hb-common.cc
+ ../ios/harfbuzz/src/hb-draw.cc
+ ../ios/harfbuzz/src/hb-face.cc
+ ../ios/harfbuzz/src/hb-font.cc
+ ../ios/harfbuzz/src/hb-map.cc
+ ../ios/harfbuzz/src/hb-number.cc
+ ../ios/harfbuzz/src/hb-ot-cff1-table.cc
+ ../ios/harfbuzz/src/hb-ot-cff2-table.cc
+ ../ios/harfbuzz/src/hb-ot-color.cc
+ ../ios/harfbuzz/src/hb-ot-face.cc
+ ../ios/harfbuzz/src/hb-ot-font.cc
+ ../ios/harfbuzz/src/hb-ot-layout.cc
+ ../ios/harfbuzz/src/hb-ot-map.cc
+ ../ios/harfbuzz/src/hb-ot-math.cc
+ ../ios/harfbuzz/src/hb-ot-meta.cc
+ ../ios/harfbuzz/src/hb-ot-metrics.cc
+ ../ios/harfbuzz/src/hb-ot-name.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-arabic.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-default.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-hangul.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-hebrew.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-indic-table.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-indic.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-khmer.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-myanmar.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-syllabic.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-thai.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-use.cc
+ ../ios/harfbuzz/src/hb-ot-shape-complex-vowel-constraints.cc
+ ../ios/harfbuzz/src/hb-ot-shape-fallback.cc
+ ../ios/harfbuzz/src/hb-ot-shape-normalize.cc
+ ../ios/harfbuzz/src/hb-ot-shape.cc
+ ../ios/harfbuzz/src/hb-ot-tag.cc
+ ../ios/harfbuzz/src/hb-ot-var.cc
+ ../ios/harfbuzz/src/hb-set.cc
+ ../ios/harfbuzz/src/hb-shape-plan.cc
+ ../ios/harfbuzz/src/hb-shape.cc
+ ../ios/harfbuzz/src/hb-shaper.cc
+ ../ios/harfbuzz/src/hb-static.cc
+ ../ios/harfbuzz/src/hb-subset-cff-common.cc
+ ../ios/harfbuzz/src/hb-subset-cff1.cc
+ ../ios/harfbuzz/src/hb-subset-cff2.cc
+ ../ios/harfbuzz/src/hb-subset-input.cc
+ ../ios/harfbuzz/src/hb-subset-plan.cc
+ ../ios/harfbuzz/src/hb-subset-repacker.cc
+ ../ios/harfbuzz/src/hb-subset.cc
+ ../ios/harfbuzz/src/hb-ucd.cc
+ ../ios/harfbuzz/src/hb-unicode.cc
+ ../ios/SheenBidi/Source/SheenBidi.c)
+
+target_include_directories(
+ rive_text PRIVATE ../ios/harfbuzz/src ../ios/rive-cpp/skia/renderer/include
+ ../ios/rive-cpp/include ../ios/SheenBidi/Headers)
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..5dcd2e4
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,56 @@
+group 'app.rive.rive'
+version '1.0-SNAPSHOT'
+
+buildscript {
+ ext.kotlin_version = '1.6.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 31
+ ndkVersion "25.1.8937393"
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ minSdkVersion 16
+ }
+ externalNativeBuild {
+ cmake {
+ path "CMakeLists.txt"
+ }
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..cf5b375
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'rive'
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b0b7a68
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/android/src/main/kotlin/app/rive/rive/RivePlugin.kt b/android/src/main/kotlin/app/rive/rive/RivePlugin.kt
new file mode 100644
index 0000000..607532f
--- /dev/null
+++ b/android/src/main/kotlin/app/rive/rive/RivePlugin.kt
@@ -0,0 +1,35 @@
+package app.rive.rive
+
+import androidx.annotation.NonNull
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+
+/** RivePlugin */
+class RivePlugin: FlutterPlugin, MethodCallHandler {
+ /// The MethodChannel that will the communication between Flutter and native Android
+ ///
+ /// This local reference serves to register the plugin with the Flutter Engine and unregister it
+ /// when the Flutter Engine is detached from the Activity
+ private lateinit var channel : MethodChannel
+
+ override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "rive")
+ channel.setMethodCallHandler(this)
+ }
+
+ override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
+ if (call.method == "getPlatformVersion") {
+ result.success("Android ${android.os.Build.VERSION.RELEASE}")
+ } else {
+ result.notImplemented()
+ }
+ }
+
+ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ }
+}
diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/example/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at
+ # https://dart-lang.github.io/linter/lints/index.html.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index f6039fd..6838e83 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -27,6 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 31
+ ndkVersion "25.1.8937393"
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
diff --git a/example/android/app/src/main/kotlin/app/rive/rive_example/MainActivity.kt b/example/android/app/src/main/kotlin/app/rive/rive_example/MainActivity.kt
new file mode 100644
index 0000000..0768ca8
--- /dev/null
+++ b/example/android/app/src/main/kotlin/app/rive/rive_example/MainActivity.kt
@@ -0,0 +1,6 @@
+package app.rive.rive_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 6b4c0f7..4f8d4d2 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 11.0
diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
index 592ceee..ec97fc6 100644
--- a/example/ios/Flutter/Debug.xcconfig
+++ b/example/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
index 592ceee..c4855bf 100644
--- a/example/ios/Flutter/Release.xcconfig
+++ b/example/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/example/ios/Podfile b/example/ios/Podfile
new file mode 100644
index 0000000..88359b2
--- /dev/null
+++ b/example/ios/Podfile
@@ -0,0 +1,41 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '11.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
new file mode 100644
index 0000000..3ed09cf
--- /dev/null
+++ b/example/ios/Podfile.lock
@@ -0,0 +1,22 @@
+PODS:
+ - Flutter (1.0.0)
+ - rive (0.0.1):
+ - Flutter
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - rive (from `.symlinks/plugins/rive/ios`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ rive:
+ :path: ".symlinks/plugins/rive/ios"
+
+SPEC CHECKSUMS:
+ Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ rive: 75e8ef88dfbec24b5dac39121b8df26efafe5097
+
+PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
+
+COCOAPODS: 1.11.3
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 4f54eeb..20974bd 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,12 +3,13 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 57BFAF227DA797F2DC6C76E7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 994CA1ABA4A9C2999F2DEB4C /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@@ -31,7 +32,10 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 3621BEEC2FEB1765CC1955B6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 3EE3885D8C1E4061691A4E9C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 6F08542C2AA5F268C6BCDAA6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
@@ -42,6 +46,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 994CA1ABA4A9C2999F2DEB4C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -49,12 +54,21 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 57BFAF227DA797F2DC6C76E7 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 82EBFB8F4C6F19875EADADDE /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 994CA1ABA4A9C2999F2DEB4C /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -72,6 +86,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
+ D3FB9C997ED1EAC1563A8534 /* Pods */,
+ 82EBFB8F4C6F19875EADADDE /* Frameworks */,
);
sourceTree = "";
};
@@ -106,6 +122,16 @@
name = "Supporting Files";
sourceTree = "";
};
+ D3FB9C997ED1EAC1563A8534 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 6F08542C2AA5F268C6BCDAA6 /* Pods-Runner.debug.xcconfig */,
+ 3621BEEC2FEB1765CC1955B6 /* Pods-Runner.release.xcconfig */,
+ 3EE3885D8C1E4061691A4E9C /* Pods-Runner.profile.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -113,12 +139,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 9C42EB10E73B1A6E7577C992 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 54AC55EE59C86AF00C4FB9C1 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -135,7 +163,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1020;
+ LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -191,6 +219,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
+ 54AC55EE59C86AF00C4FB9C1 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -205,6 +250,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ 9C42EB10E73B1A6E7577C992 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -280,7 +347,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -296,13 +363,17 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = NJ3JMFUNS9;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@@ -362,7 +433,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -411,7 +482,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -428,13 +499,17 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = NJ3JMFUNS9;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@@ -455,13 +530,17 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = NJ3JMFUNS9;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 1d526a1..919434a 100644
--- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,6 @@
+ location = "self:">
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..3db53b6 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
+
+
diff --git a/example/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h
new file mode 100644
index 0000000..36e21bb
--- /dev/null
+++ b/example/ios/Runner/AppDelegate.h
@@ -0,0 +1,6 @@
+#import
+#import
+
+@interface AppDelegate : FlutterAppDelegate
+
+@end
diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m
new file mode 100644
index 0000000..70e8393
--- /dev/null
+++ b/example/ios/Runner/AppDelegate.m
@@ -0,0 +1,13 @@
+#import "AppDelegate.h"
+#import "GeneratedPluginRegistrant.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ [GeneratedPluginRegistrant registerWithRegistry:self];
+ // Override point for customization after application launch.
+ return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+
+@end
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index ec3815f..960a342 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -41,5 +41,7 @@
UIViewControllerBasedStatusBarAppearance
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/example/ios/Runner/main.m b/example/ios/Runner/main.m
new file mode 100644
index 0000000..dff6597
--- /dev/null
+++ b/example/ios/Runner/main.m
@@ -0,0 +1,9 @@
+#import
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig
index c2efd0b..4b81f9b 100644
--- a/example/macos/Flutter/Flutter-Debug.xcconfig
+++ b/example/macos/Flutter/Flutter-Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig
index c2efd0b..5caa9d1 100644
--- a/example/macos/Flutter/Flutter-Release.xcconfig
+++ b/example/macos/Flutter/Flutter-Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index cccf817..69e88fc 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation
+import rive
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin"))
}
diff --git a/example/macos/Podfile b/example/macos/Podfile
new file mode 100644
index 0000000..dade8df
--- /dev/null
+++ b/example/macos/Podfile
@@ -0,0 +1,40 @@
+platform :osx, '10.11'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_macos_build_settings(target)
+ end
+end
diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock
new file mode 100644
index 0000000..b3187a9
--- /dev/null
+++ b/example/macos/Podfile.lock
@@ -0,0 +1,22 @@
+PODS:
+ - FlutterMacOS (1.0.0)
+ - rive (0.0.1):
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - FlutterMacOS (from `Flutter/ephemeral`)
+ - rive (from `Flutter/ephemeral/.symlinks/plugins/rive/macos`)
+
+EXTERNAL SOURCES:
+ FlutterMacOS:
+ :path: Flutter/ephemeral
+ rive:
+ :path: Flutter/ephemeral/.symlinks/plugins/rive/macos
+
+SPEC CHECKSUMS:
+ FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
+ rive: 8bf1bb49b46a4c3045424aa7c1243fe8942bb576
+
+PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
+
+COCOAPODS: 1.11.3
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj
index cc89c87..ec5cd8e 100644
--- a/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/example/macos/Runner.xcodeproj/project.pbxproj
@@ -26,6 +26,7 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+ 3C0FB406882FB59B3F797C42 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF6C7F17DBDC2D7E326330D2 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -52,9 +53,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 2D87453FF79CB62E45B9D168 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
- 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
@@ -68,6 +70,9 @@
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
+ D803C4DFFE53D1FFAB2CF942 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ DF6C7F17DBDC2D7E326330D2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ E32AFBBD7A837D065523019F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -75,12 +80,24 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 3C0FB406882FB59B3F797C42 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 22BEA6C90E8F99148A5C1825 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 2D87453FF79CB62E45B9D168 /* Pods-Runner.debug.xcconfig */,
+ E32AFBBD7A837D065523019F /* Pods-Runner.release.xcconfig */,
+ D803C4DFFE53D1FFAB2CF942 /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
@@ -99,6 +116,7 @@
33CEB47122A05771004F2AC0 /* Flutter */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
+ 22BEA6C90E8F99148A5C1825 /* Pods */,
);
sourceTree = "";
};
@@ -148,6 +166,7 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ DF6C7F17DBDC2D7E326330D2 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -159,11 +178,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 6CAF1097CBE73A2DBD900610 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
+ 1BB3133E7507708F9822CB2F /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -233,6 +254,23 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 1BB3133E7507708F9822CB2F /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -270,6 +308,28 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
+ 6CAF1097CBE73A2DBD900610 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index ae8ff59..393646a 100644
--- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -36,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
-
-
+
+
-
-
+
+
diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ
diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ
diff --git a/example/windows/.gitignore b/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..c027074
--- /dev/null
+++ b/example/windows/CMakeLists.txt
@@ -0,0 +1,101 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.14)
+project(example LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "example")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Define build configuration option.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+ set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+ CACHE STRING "" FORCE)
+else()
+ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+ endif()
+endif()
+# Define settings for the Profile build mode.
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_17)
+ target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+ target_compile_options(${TARGET} PRIVATE /EHsc)
+ target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+ target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+ install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ CONFIGURATIONS Profile;Release
+ COMPONENT Runtime)
diff --git a/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..930d207
--- /dev/null
+++ b/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,104 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.14)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "flutter_export.h"
+ "flutter_windows.h"
+ "flutter_messenger.h"
+ "flutter_plugin_registrar.h"
+ "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+ "core_implementations.cc"
+ "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+ "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+ "flutter_engine.cc"
+ "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+ POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+ CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+ "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+ "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+ ${CPP_WRAPPER_SOURCES_APP}
+ ${PHONY_OUTPUT}
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+ windows-x64 $
+ VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_PLUGIN}
+ ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..1c682a0
--- /dev/null
+++ b/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,14 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+#include
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+ RivePluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("RivePlugin"));
+}
diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..dc139d8
--- /dev/null
+++ b/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..69d6ad1
--- /dev/null
+++ b/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,24 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+ rive
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..17411a8
--- /dev/null
+++ b/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,39 @@
+cmake_minimum_required(VERSION 3.14)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME} WIN32
+ "flutter_window.cpp"
+ "main.cpp"
+ "utils.cpp"
+ "win32_window.cpp"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+ "Runner.rc"
+ "runner.exe.manifest"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..0f5c085
--- /dev/null
+++ b/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON ICON "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
+#else
+#define VERSION_AS_NUMBER 1,0,0,0
+#endif
+
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "com.example" "\0"
+ VALUE "FileDescription", "example" "\0"
+ VALUE "FileVersion", VERSION_AS_STRING "\0"
+ VALUE "InternalName", "example" "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
+ VALUE "OriginalFilename", "example.exe" "\0"
+ VALUE "ProductName", "example" "\0"
+ VALUE "ProductVersion", VERSION_AS_STRING "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..594156d
--- /dev/null
+++ b/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,70 @@
+#include "flutter_window.h"
+
+#include
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate()
+{
+ if (!Win32Window::OnCreate())
+ {
+ return false;
+ }
+
+ RECT frame = GetClientArea();
+
+ // The size here must match the window dimensions to avoid unnecessary surface
+ // creation / destruction in the startup path.
+ flutter_controller_ = std::make_unique(frame.right - frame.left,
+ frame.bottom - frame.top,
+ project_);
+ // Ensure that basic setup of the controller was successful.
+ if (!flutter_controller_->engine() || !flutter_controller_->view())
+ {
+ return false;
+ }
+ RegisterPlugins(flutter_controller_->engine());
+ SetChildContent(flutter_controller_->view()->GetNativeWindow());
+ return true;
+}
+
+void FlutterWindow::OnDestroy()
+{
+ if (flutter_controller_)
+ {
+ flutter_controller_ = nullptr;
+ }
+
+ Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept
+{
+ // Give Flutter, including plugins, an opportunity to handle window messages.
+ if (flutter_controller_)
+ {
+ std::optional result =
+ flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam);
+ if (result)
+ {
+ return *result;
+ }
+ }
+
+ switch (message)
+ {
+ case WM_FONTCHANGE:
+ flutter_controller_->engine()->ReloadSystemFonts();
+ break;
+ }
+
+ return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..5d03489
--- /dev/null
+++ b/example/windows/runner/flutter_window.h
@@ -0,0 +1,36 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include
+#include
+
+#include
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window
+{
+public:
+ // Creates a new FlutterWindow hosting a Flutter view running |project|.
+ explicit FlutterWindow(const flutter::DartProject& project);
+ virtual ~FlutterWindow();
+
+protected:
+ // Win32Window:
+ bool OnCreate() override;
+ void OnDestroy() override;
+ LRESULT MessageHandler(HWND window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept override;
+
+private:
+ // The project to run.
+ flutter::DartProject project_;
+
+ // The Flutter instance hosted by this window.
+ std::unique_ptr flutter_controller_;
+};
+
+#endif // RUNNER_FLUTTER_WINDOW_H_
diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp
new file mode 100644
index 0000000..f1844f6
--- /dev/null
+++ b/example/windows/runner/main.cpp
@@ -0,0 +1,48 @@
+#include
+#include
+#include
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance,
+ _In_opt_ HINSTANCE prev,
+ _In_ wchar_t* command_line,
+ _In_ int show_command)
+{
+ // Attach to console when present (e.g., 'flutter run') or create a
+ // new console when running with a debugger.
+ if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())
+ {
+ CreateAndAttachConsole();
+ }
+
+ // Initialize COM, so that it is available for use in the library and/or
+ // plugins.
+ ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ flutter::DartProject project(L"data");
+
+ std::vector command_line_arguments = GetCommandLineArguments();
+
+ project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+ FlutterWindow window(project);
+ Win32Window::Point origin(10, 10);
+ Win32Window::Size size(1280, 720);
+ if (!window.CreateAndShow(L"example", origin, size))
+ {
+ return EXIT_FAILURE;
+ }
+ window.SetQuitOnClose(true);
+
+ ::MSG msg;
+ while (::GetMessage(&msg, nullptr, 0, 0))
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+
+ ::CoUninitialize();
+ return EXIT_SUCCESS;
+}
diff --git a/example/windows/runner/resource.h b/example/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/example/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
Binary files /dev/null and b/example/windows/runner/resources/app_icon.ico differ
diff --git a/example/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..a42ea76
--- /dev/null
+++ b/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+ PerMonitorV2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/windows/runner/utils.cpp b/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..f0d6096
--- /dev/null
+++ b/example/windows/runner/utils.cpp
@@ -0,0 +1,84 @@
+#include "utils.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+void CreateAndAttachConsole()
+{
+ if (::AllocConsole())
+ {
+ FILE* unused;
+ if (freopen_s(&unused, "CONOUT$", "w", stdout))
+ {
+ _dup2(_fileno(stdout), 1);
+ }
+ if (freopen_s(&unused, "CONOUT$", "w", stderr))
+ {
+ _dup2(_fileno(stdout), 2);
+ }
+ std::ios::sync_with_stdio();
+ FlutterDesktopResyncOutputStreams();
+ }
+}
+
+std::vector GetCommandLineArguments()
+{
+ // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+ int argc;
+ wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+ if (argv == nullptr)
+ {
+ return std::vector();
+ }
+
+ std::vector command_line_arguments;
+
+ // Skip the first argument as it's the binary name.
+ for (int i = 1; i < argc; i++)
+ {
+ command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+ }
+
+ ::LocalFree(argv);
+
+ return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string)
+{
+ if (utf16_string == nullptr)
+ {
+ return std::string();
+ }
+ int target_length = ::WideCharToMultiByte(CP_UTF8,
+ WC_ERR_INVALID_CHARS,
+ utf16_string,
+ -1,
+ nullptr,
+ 0,
+ nullptr,
+ nullptr);
+ std::string utf8_string;
+ if (target_length == 0 || target_length > utf8_string.max_size())
+ {
+ return utf8_string;
+ }
+ utf8_string.resize(target_length);
+ int converted_length = ::WideCharToMultiByte(CP_UTF8,
+ WC_ERR_INVALID_CHARS,
+ utf16_string,
+ -1,
+ utf8_string.data(),
+ target_length,
+ nullptr,
+ nullptr);
+ if (converted_length == 0)
+ {
+ return std::string();
+ }
+ return utf8_string;
+}
diff --git a/example/windows/runner/utils.h b/example/windows/runner/utils.h
new file mode 100644
index 0000000..ebc8ccc
--- /dev/null
+++ b/example/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include
+#include
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector,
+// encoded in UTF-8. Returns an empty std::vector on failure.
+std::vector GetCommandLineArguments();
+
+#endif // RUNNER_UTILS_H_
diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..370c53f
--- /dev/null
+++ b/example/windows/runner/win32_window.cpp
@@ -0,0 +1,282 @@
+#include "win32_window.h"
+
+#include
+
+#include "resource.h"
+
+namespace
+{
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); }
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd)
+{
+ HMODULE user32_module = LoadLibraryA("User32.dll");
+ if (!user32_module)
+ {
+ return;
+ }
+ auto enable_non_client_dpi_scaling = reinterpret_cast(
+ GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+ if (enable_non_client_dpi_scaling != nullptr)
+ {
+ enable_non_client_dpi_scaling(hwnd);
+ FreeLibrary(user32_module);
+ }
+}
+
+} // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar
+{
+public:
+ ~WindowClassRegistrar() = default;
+
+ // Returns the singleton registar instance.
+ static WindowClassRegistrar* GetInstance()
+ {
+ if (!instance_)
+ {
+ instance_ = new WindowClassRegistrar();
+ }
+ return instance_;
+ }
+
+ // Returns the name of the window class, registering the class if it hasn't
+ // previously been registered.
+ const wchar_t* GetWindowClass();
+
+ // Unregisters the window class. Should only be called if there are no
+ // instances of the window.
+ void UnregisterWindowClass();
+
+private:
+ WindowClassRegistrar() = default;
+
+ static WindowClassRegistrar* instance_;
+
+ bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass()
+{
+ if (!class_registered_)
+ {
+ WNDCLASS window_class{};
+ window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ window_class.lpszClassName = kWindowClassName;
+ window_class.style = CS_HREDRAW | CS_VREDRAW;
+ window_class.cbClsExtra = 0;
+ window_class.cbWndExtra = 0;
+ window_class.hInstance = GetModuleHandle(nullptr);
+ window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+ window_class.hbrBackground = 0;
+ window_class.lpszMenuName = nullptr;
+ window_class.lpfnWndProc = Win32Window::WndProc;
+ RegisterClass(&window_class);
+ class_registered_ = true;
+ }
+ return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass()
+{
+ UnregisterClass(kWindowClassName, nullptr);
+ class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window()
+{
+ --g_active_window_count;
+ Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size)
+{
+ Destroy();
+
+ const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+ const POINT target_point = {static_cast(origin.x), static_cast(origin.y)};
+ HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+ UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+ double scale_factor = dpi / 96.0;
+
+ HWND window = CreateWindow(window_class,
+ title.c_str(),
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+ Scale(origin.x, scale_factor),
+ Scale(origin.y, scale_factor),
+ Scale(size.width, scale_factor),
+ Scale(size.height, scale_factor),
+ nullptr,
+ nullptr,
+ GetModuleHandle(nullptr),
+ this);
+
+ if (!window)
+ {
+ return false;
+ }
+
+ return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept
+{
+ if (message == WM_NCCREATE)
+ {
+ auto window_struct = reinterpret_cast(lparam);
+ SetWindowLongPtr(window,
+ GWLP_USERDATA,
+ reinterpret_cast(window_struct->lpCreateParams));
+
+ auto that = static_cast(window_struct->lpCreateParams);
+ EnableFullDpiSupportIfAvailable(window);
+ that->window_handle_ = window;
+ }
+ else if (Win32Window* that = GetThisFromHandle(window))
+ {
+ return that->MessageHandler(window, message, wparam, lparam);
+ }
+
+ return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept
+{
+ switch (message)
+ {
+ case WM_DESTROY:
+ window_handle_ = nullptr;
+ Destroy();
+ if (quit_on_close_)
+ {
+ PostQuitMessage(0);
+ }
+ return 0;
+
+ case WM_DPICHANGED:
+ {
+ auto newRectSize = reinterpret_cast(lparam);
+ LONG newWidth = newRectSize->right - newRectSize->left;
+ LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+ SetWindowPos(hwnd,
+ nullptr,
+ newRectSize->left,
+ newRectSize->top,
+ newWidth,
+ newHeight,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+
+ return 0;
+ }
+ case WM_SIZE:
+ {
+ RECT rect = GetClientArea();
+ if (child_content_ != nullptr)
+ {
+ // Size and position the child window.
+ MoveWindow(child_content_,
+ rect.left,
+ rect.top,
+ rect.right - rect.left,
+ rect.bottom - rect.top,
+ TRUE);
+ }
+ return 0;
+ }
+
+ case WM_ACTIVATE:
+ if (child_content_ != nullptr)
+ {
+ SetFocus(child_content_);
+ }
+ return 0;
+ }
+
+ return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy()
+{
+ OnDestroy();
+
+ if (window_handle_)
+ {
+ DestroyWindow(window_handle_);
+ window_handle_ = nullptr;
+ }
+ if (g_active_window_count == 0)
+ {
+ WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+ }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept
+{
+ return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content)
+{
+ child_content_ = content;
+ SetParent(content, window_handle_);
+ RECT frame = GetClientArea();
+
+ MoveWindow(content,
+ frame.left,
+ frame.top,
+ frame.right - frame.left,
+ frame.bottom - frame.top,
+ true);
+
+ SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea()
+{
+ RECT frame;
+ GetClientRect(window_handle_, &frame);
+ return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; }
+
+bool Win32Window::OnCreate()
+{
+ // No-op; provided for subclasses.
+ return true;
+}
+
+void Win32Window::OnDestroy()
+{
+ // No-op; provided for subclasses.
+}
diff --git a/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..7768aab
--- /dev/null
+++ b/example/windows/runner/win32_window.h
@@ -0,0 +1,98 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include
+
+#include
+#include
+#include
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window
+{
+public:
+ struct Point
+ {
+ unsigned int x;
+ unsigned int y;
+ Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+ };
+
+ struct Size
+ {
+ unsigned int width;
+ unsigned int height;
+ Size(unsigned int width, unsigned int height) : width(width), height(height) {}
+ };
+
+ Win32Window();
+ virtual ~Win32Window();
+
+ // Creates and shows a win32 window with |title| and position and size using
+ // |origin| and |size|. New windows are created on the default monitor. Window
+ // sizes are specified to the OS in physical pixels, hence to ensure a
+ // consistent size to will treat the width height passed in to this function
+ // as logical pixels and scale to appropriate for the default monitor. Returns
+ // true if the window was created successfully.
+ bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size);
+
+ // Release OS resources associated with window.
+ void Destroy();
+
+ // Inserts |content| into the window tree.
+ void SetChildContent(HWND content);
+
+ // Returns the backing Window handle to enable clients to set icon and other
+ // window properties. Returns nullptr if the window has been destroyed.
+ HWND GetHandle();
+
+ // If true, closing this window will quit the application.
+ void SetQuitOnClose(bool quit_on_close);
+
+ // Return a RECT representing the bounds of the current client area.
+ RECT GetClientArea();
+
+protected:
+ // Processes and route salient window messages for mouse handling,
+ // size change and DPI. Delegates handling of these to member overloads that
+ // inheriting classes can handle.
+ virtual LRESULT MessageHandler(HWND window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Called when CreateAndShow is called, allowing subclass window-related
+ // setup. Subclasses should return false if setup fails.
+ virtual bool OnCreate();
+
+ // Called when Destroy is called.
+ virtual void OnDestroy();
+
+private:
+ friend class WindowClassRegistrar;
+
+ // OS callback called by message pump. Handles the WM_NCCREATE message which
+ // is passed when the non-client area is being created and enables automatic
+ // non-client DPI scaling so that the non-client area automatically
+ // responsponds to changes in DPI. All other messages are handled by
+ // MessageHandler.
+ static LRESULT CALLBACK WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Retrieves a class instance pointer for |window|
+ static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+ bool quit_on_close_ = false;
+
+ // window handle for top level window.
+ HWND window_handle_ = nullptr;
+
+ // window handle for hosted content.
+ HWND child_content_ = nullptr;
+};
+
+#endif // RUNNER_WIN32_WINDOW_H_
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..0c88507
--- /dev/null
+++ b/ios/.gitignore
@@ -0,0 +1,38 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ios/Classes/RivePlugin.h b/ios/Classes/RivePlugin.h
new file mode 100644
index 0000000..79ea804
--- /dev/null
+++ b/ios/Classes/RivePlugin.h
@@ -0,0 +1,4 @@
+#import
+
+@interface RivePlugin : NSObject
+@end
diff --git a/ios/Classes/RivePlugin.m b/ios/Classes/RivePlugin.m
new file mode 100644
index 0000000..2b96b32
--- /dev/null
+++ b/ios/Classes/RivePlugin.m
@@ -0,0 +1,15 @@
+#import "RivePlugin.h"
+#if __has_include()
+#import
+#else
+// Support project import fallback if the generated compatibility header
+// is not copied when this plugin is created as a library.
+// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
+#import "rive-Swift.h"
+#endif
+
+@implementation RivePlugin
++ (void)registerWithRegistrar:(NSObject*)registrar {
+ [SwiftRivePlugin registerWithRegistrar:registrar];
+}
+@end
diff --git a/ios/Classes/SwiftRivePlugin.swift b/ios/Classes/SwiftRivePlugin.swift
new file mode 100644
index 0000000..36911b7
--- /dev/null
+++ b/ios/Classes/SwiftRivePlugin.swift
@@ -0,0 +1,14 @@
+import Flutter
+import UIKit
+
+public class SwiftRivePlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let channel = FlutterMethodChannel(name: "rive", binaryMessenger: registrar.messenger())
+ let instance = SwiftRivePlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ result("iOS " + UIDevice.current.systemVersion)
+ }
+}
diff --git a/ios/rive.podspec b/ios/rive.podspec
new file mode 100644
index 0000000..1f34464
--- /dev/null
+++ b/ios/rive.podspec
@@ -0,0 +1,96 @@
+Pod::Spec.new do |s|
+ s.name = "rive"
+ s.version = "0.0.1"
+ s.summary = "Rive font abstraction."
+ s.description = <<-DESC
+Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
+ DESC
+ s.homepage = "https://rive.app"
+ s.license = { :file => "../LICENSE" }
+ s.author = { "Rive" => "hello@rive.app" }
+
+ s.source = { :path => "." }
+ s.source_files = [
+ "Classes/**/*",
+ "rive_text/**/*.{cpp,hpp,c,h}",
+ "rive-cpp/src/math/raw_path.cpp",
+ "rive-cpp/src/math/mat2d.cpp",
+ "rive-cpp/src/rive_counter.cpp",
+ "rive-cpp/src/renderer.cpp",
+ "rive-cpp/src/text/font_hb.cpp",
+ "rive-cpp/src/text/line_breaker.cpp",
+
+ "harfbuzz/src/hb-aat-layout.cc",
+ "harfbuzz/src/hb-aat-map.cc",
+ "harfbuzz/src/hb-blob.cc",
+ "harfbuzz/src/hb-buffer-serialize.cc",
+ "harfbuzz/src/hb-buffer-verify.cc",
+ "harfbuzz/src/hb-buffer.cc",
+ "harfbuzz/src/hb-common.cc",
+ "harfbuzz/src/hb-draw.cc",
+ "harfbuzz/src/hb-face.cc",
+ "harfbuzz/src/hb-font.cc",
+ "harfbuzz/src/hb-map.cc",
+ "harfbuzz/src/hb-number.cc",
+ "harfbuzz/src/hb-ot-cff1-table.cc",
+ "harfbuzz/src/hb-ot-cff2-table.cc",
+ "harfbuzz/src/hb-ot-color.cc",
+ "harfbuzz/src/hb-ot-face.cc",
+ "harfbuzz/src/hb-ot-font.cc",
+ "harfbuzz/src/hb-ot-layout.cc",
+ "harfbuzz/src/hb-ot-map.cc",
+ "harfbuzz/src/hb-ot-math.cc",
+ "harfbuzz/src/hb-ot-meta.cc",
+ "harfbuzz/src/hb-ot-metrics.cc",
+ "harfbuzz/src/hb-ot-name.cc",
+ "harfbuzz/src/hb-ot-shape-complex-arabic.cc",
+ "harfbuzz/src/hb-ot-shape-complex-default.cc",
+ "harfbuzz/src/hb-ot-shape-complex-hangul.cc",
+ "harfbuzz/src/hb-ot-shape-complex-hebrew.cc",
+ "harfbuzz/src/hb-ot-shape-complex-indic-table.cc",
+ "harfbuzz/src/hb-ot-shape-complex-indic.cc",
+ "harfbuzz/src/hb-ot-shape-complex-khmer.cc",
+ "harfbuzz/src/hb-ot-shape-complex-myanmar.cc",
+ "harfbuzz/src/hb-ot-shape-complex-syllabic.cc",
+ "harfbuzz/src/hb-ot-shape-complex-thai.cc",
+ "harfbuzz/src/hb-ot-shape-complex-use.cc",
+ "harfbuzz/src/hb-ot-shape-complex-vowel-constraints.cc",
+ "harfbuzz/src/hb-ot-shape-fallback.cc",
+ "harfbuzz/src/hb-ot-shape-normalize.cc",
+ "harfbuzz/src/hb-ot-shape.cc",
+ "harfbuzz/src/hb-ot-tag.cc",
+ "harfbuzz/src/hb-ot-var.cc",
+ "harfbuzz/src/hb-set.cc",
+ "harfbuzz/src/hb-shape-plan.cc",
+ "harfbuzz/src/hb-shape.cc",
+ "harfbuzz/src/hb-shaper.cc",
+ "harfbuzz/src/hb-static.cc",
+ "harfbuzz/src/hb-subset-cff-common.cc",
+ "harfbuzz/src/hb-subset-cff1.cc",
+ "harfbuzz/src/hb-subset-cff2.cc",
+ "harfbuzz/src/hb-subset-input.cc",
+ "harfbuzz/src/hb-subset-plan.cc",
+ "harfbuzz/src/hb-subset-repacker.cc",
+ "harfbuzz/src/hb-subset.cc",
+ "harfbuzz/src/hb-ucd.cc",
+ "harfbuzz/src/hb-unicode.cc",
+
+ "SheenBidi/Headers/*.h",
+ "SheenBidi/Source/SheenBidi.c",
+ ]
+ s.dependency "Flutter"
+ s.platform = :ios, "9.0"
+
+ # Flutter.framework does not contain a i386 slice.
+ s.pod_target_xcconfig = {
+ "DEFINES_MODULE" => "YES",
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" => "i386",
+ "OTHER_CFLAGS" => "-DSB_CONFIG_UNITY -DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -Wno-documentation -Wno-comma -Wno-unreachable-code -Wno-shorten-64-to-32",
+ "OTHER_CPLUSPLUSFLAGS" => "-DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -Wno-conditional-uninitialized -Wno-documentation -Wno-comma -Wno-unreachable-code -Wno-shorten-64-to-32 -std=c++17",
+ "USER_HEADER_SEARCH_PATHS" => '"$(PODS_TARGET_SRCROOT)/SheenBidi/Headers" "$(PODS_TARGET_SRCROOT)/harfbuzz/src" "$(PODS_TARGET_SRCROOT)/rive-cpp/include" "$(PODS_TARGET_SRCROOT)/rive-cpp/skia/renderer/include"',
+ "OTHER_CPLUSPLUSFLAGS[config=Release]" => "-DNDEBUG -DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -Wno-conditional-uninitialized -Wno-documentation -Wno-comma -Wno-unreachable-code -Wno-shorten-64-to-32 -std=c++17",
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
+ "CLANG_CXX_LIBRARY" => "libc++",
+ }
+ s.swift_version = "5.0"
+end
diff --git a/ios/rive_text/rive_text.cpp b/ios/rive_text/rive_text.cpp
new file mode 100644
index 0000000..5c22c3d
--- /dev/null
+++ b/ios/rive_text/rive_text.cpp
@@ -0,0 +1,139 @@
+#include
+#include
+
+#include "rive/text/font_hb.hpp"
+
+#if defined(_MSC_VER)
+#define EXPORT extern "C" __declspec(dllexport)
+#else
+#define EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used))
+#endif
+
+EXPORT
+rive::Font* makeFont(const uint8_t* bytes, uint64_t length)
+{
+ auto result = HBFont::Decode(rive::Span(bytes, length));
+ if (result)
+ {
+ auto ptr = result.release();
+ return ptr;
+ }
+ return nullptr;
+}
+
+EXPORT void deleteFont(rive::Font* font) { delete font; }
+
+struct GlyphPath
+{
+ rive::RawPath* rawPath;
+ rive::Vec2D* points;
+ rive::PathVerb* verbs;
+ uint16_t verbCount;
+};
+
+EXPORT
+GlyphPath makeGlyphPath(rive::Font* font, rive::GlyphID id)
+{
+ rive::RawPath* path = new rive::RawPath(font->getPath(id));
+
+ return {
+ .rawPath = path,
+ .points = path->points().data(),
+ .verbs = path->verbs().data(),
+ .verbCount = (uint16_t)path->verbs().size(),
+ };
+}
+
+EXPORT void deleteGlyphPath(rive::RawPath* rawPath) { delete rawPath; }
+
+EXPORT
+rive::SimpleArray*
+shapeText(const uint32_t* text, uint64_t length, rive::TextRun* runs, uint64_t runsLength)
+{
+ if (runsLength == 0 || length == 0)
+ {
+ return nullptr;
+ }
+ return new rive::SimpleArray(
+ runs[0].font->shapeText(rive::Span(text, length), rive::Span(runs, runsLength)));
+}
+
+EXPORT void deleteShapeResult(rive::SimpleArray* shapeResult)
+{
+ delete shapeResult;
+}
+
+EXPORT rive::SimpleArray>*
+breakLines(rive::SimpleArray* paragraphs, float width, uint8_t align)
+{
+ bool autoWidth = width == -1.0f;
+ float paragraphWidth = width;
+
+ rive::SimpleArray>* lines =
+ new rive::SimpleArray>(paragraphs->size());
+ rive::SimpleArray>& linesRef = *lines;
+ size_t paragraphIndex = 0;
+ for (auto& para : *paragraphs)
+ {
+ linesRef[paragraphIndex] =
+ rive::GlyphLine::BreakLines(para.runs, autoWidth ? -1.0f : width);
+ if (autoWidth)
+ {
+ paragraphWidth =
+ std::max(paragraphWidth,
+ rive::GlyphLine::ComputeMaxWidth(linesRef[paragraphIndex], para.runs));
+ }
+ paragraphIndex++;
+ }
+ paragraphIndex = 0;
+ for (auto& para : *paragraphs)
+ {
+ rive::GlyphLine::ComputeLineSpacing(linesRef[paragraphIndex++],
+ para.runs,
+ paragraphWidth,
+ (rive::TextAlign)align);
+ }
+ return lines;
+}
+
+EXPORT void deleteLines(rive::SimpleArray>* result)
+{
+ delete result;
+}
+
+std::vector fallbackFonts;
+
+EXPORT
+void setFallbackFonts(rive::Font** fonts, uint64_t fontsLength)
+{
+ if (fontsLength == 0)
+ {
+ fallbackFonts = std::vector();
+ return;
+ }
+ fallbackFonts = std::vector(fonts, fonts + fontsLength);
+}
+
+static rive::rcp pickFallbackFont(rive::Span missing)
+{
+ size_t length = fallbackFonts.size();
+ for (size_t i = 0; i < length; i++)
+ {
+ HBFont* font = static_cast(fallbackFonts[i]);
+ if (i == length - 1 || font->hasGlyph(missing))
+ {
+ rive::rcp rcFont = rive::rcp(font);
+ // because the font was released at load time, we need to give it an
+ // extra ref whenever we bump it to a reference counted pointer.
+ rcFont->ref();
+ return rcFont;
+ }
+ }
+ return nullptr;
+}
+EXPORT
+void init()
+{
+ fallbackFonts.clear();
+ HBFont::gFallbackProc = pickFallbackFont;
+}
\ No newline at end of file
diff --git a/lib/rive_web.dart b/lib/rive_web.dart
new file mode 100644
index 0000000..055dd7a
--- /dev/null
+++ b/lib/rive_web.dart
@@ -0,0 +1,15 @@
+// In order to *not* need this ignore, consider extracting the "web" version
+// of your plugin as a separate package, instead of inlining it in the same
+// package as the core of your plugin.
+// ignore: avoid_web_libraries_in_flutter
+import 'dart:html' as html show window;
+
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+
+/// A web implementation of the RivePlatform of the Rive plugin.
+class RivePlugin {
+ /// Constructs a RiveWeb
+ RivePlugin();
+
+ static void registerWith(Registrar registrar) {}
+}
diff --git a/lib/src/platform.dart b/lib/src/platform.dart
new file mode 100644
index 0000000..baf6d6a
--- /dev/null
+++ b/lib/src/platform.dart
@@ -0,0 +1,6 @@
+import 'platform_native.dart' if (dart.library.html) 'platform_web.dart';
+
+abstract class Platform {
+ bool get isTesting;
+ static final Platform instance = makePlatform();
+}
diff --git a/lib/src/platform_native.dart b/lib/src/platform_native.dart
new file mode 100644
index 0000000..2adeb6d
--- /dev/null
+++ b/lib/src/platform_native.dart
@@ -0,0 +1,10 @@
+import 'dart:io' as io show Platform;
+
+import 'platform.dart';
+
+Platform makePlatform() => PlatformNative();
+
+class PlatformNative extends Platform {
+ @override
+ bool get isTesting => io.Platform.environment.containsKey('FLUTTER_TEST');
+}
diff --git a/lib/src/platform_web.dart b/lib/src/platform_web.dart
new file mode 100644
index 0000000..63c7cd3
--- /dev/null
+++ b/lib/src/platform_web.dart
@@ -0,0 +1,8 @@
+import 'platform.dart';
+
+Platform makePlatform() => PlatformWeb();
+
+class PlatformWeb extends Platform {
+ @override
+ bool get isTesting => false;
+}
diff --git a/lib/src/rive_core/shapes/shape.dart b/lib/src/rive_core/shapes/shape.dart
index 1f148ed..6e0f1c4 100644
--- a/lib/src/rive_core/shapes/shape.dart
+++ b/lib/src/rive_core/shapes/shape.dart
@@ -234,6 +234,12 @@ class Shape extends ShapeBase with ShapePaintContainer {
@override
void draw(ui.Canvas canvas) {
+ // canvas.draw(path, ui.Paint()..color = ui.Color(0xFFFF0000));
+ // canvas.drawCircle(
+ // ui.Offset.zero, 15, ui.Paint()..color = ui.Color(0xFFFF0000));
+ // shader = ui.Gradient.radial(ui.Offset.zero, 15.0,
+ // [ui.Color(0xFFFF0000), ui.Color(0x00FF0000)], [0.0, 1.0]));
+ // return;
bool clipped = clip(canvas);
var path = pathComposer.fillPath;
if (!_fillInWorld) {
@@ -243,6 +249,7 @@ class Shape extends ShapeBase with ShapePaintContainer {
for (final fill in fills) {
fill.draw(canvas, path);
}
+
if (!_fillInWorld) {
canvas.restore();
}
diff --git a/lib/src/rive_text.dart b/lib/src/rive_text.dart
new file mode 100644
index 0000000..9c928ea
--- /dev/null
+++ b/lib/src/rive_text.dart
@@ -0,0 +1,319 @@
+import 'dart:collection';
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:rive/math.dart';
+
+import 'rive_text_ffi.dart' if (dart.library.html) 'rive_text_wasm.dart';
+
+enum RawPathVerb { move, line, quad, cubic, close }
+
+abstract class RawPathCommand {
+ final RawPathVerb verb;
+ RawPathCommand(this.verb);
+ Vec2D point(int index);
+}
+
+abstract class RawPath with IterableMixin {
+ void dispose();
+ void issueCommands(ui.Path path) {
+ for (final command in this) {
+ switch (command.verb) {
+ case RawPathVerb.move:
+ var p = command.point(0);
+ path.moveTo(p.x, p.y);
+ break;
+ case RawPathVerb.line:
+ var p = command.point(1);
+ path.lineTo(p.x, p.y);
+ break;
+ case RawPathVerb.quad:
+ var p1 = command.point(1);
+ var p2 = command.point(2);
+ path.quadraticBezierTo(p1.x, p1.y, p2.x, p2.y);
+
+ break;
+ case RawPathVerb.cubic:
+ var p1 = command.point(1);
+ var p2 = command.point(2);
+ var p3 = command.point(3);
+ path.cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
+ break;
+ case RawPathVerb.close:
+ path.close();
+ break;
+ }
+ }
+ }
+}
+
+enum TextDirection { ltr, rtl }
+
+enum TextAlign { left, right, center }
+
+abstract class Paragraph {
+ TextDirection get direction;
+ List get runs;
+ List get logicalRuns => runs;
+
+ List orderVisually(List glyphRuns) {
+ if (direction == TextDirection.ltr) {
+ return glyphRuns;
+ }
+ var visualOrder = [];
+ if (glyphRuns.isNotEmpty) {
+ var reversed = glyphRuns.reversed;
+ GlyphRun previous = reversed.first;
+ visualOrder.add(previous);
+ int ltrIndex = 0;
+ for (final run in reversed.skip(1)) {
+ if (run.direction == TextDirection.ltr &&
+ previous.direction == run.direction) {
+ visualOrder.insert(ltrIndex, run);
+ } else {
+ if (run.direction == TextDirection.ltr) {
+ ltrIndex = visualOrder.length;
+ }
+ visualOrder.add(run);
+ }
+ previous = run;
+ }
+ }
+ return visualOrder;
+ }
+
+ List get visualRuns => orderVisually(runs);
+}
+
+abstract class GlyphRun {
+ Font get font;
+ double get fontSize;
+ int get styleId;
+ int get glyphCount;
+ TextDirection get direction;
+ int glyphIdAt(int index);
+ int textIndexAt(int index);
+ double advanceAt(int index);
+}
+
+class LineRunGlyph {
+ final GlyphRun run;
+ final int index;
+
+ LineRunGlyph(this.run, this.index);
+
+ Float64List renderTransform(double x, double y) {
+ var scale = run.fontSize;
+ return Float64List.fromList([
+ scale,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ scale,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ x,
+ y,
+ 0.0,
+ 1.0
+ ]);
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is LineRunGlyph && other.run == run && other.index == index;
+}
+
+abstract class GlyphLine {
+ int get startRun;
+ int get startIndex;
+ int get endRun;
+ int get endIndex;
+ double get startX;
+ double get top;
+ double get baseline;
+ double get bottom;
+
+ /// Returns an iterator that traverses the glyphs in a line in visual order
+ /// taking into account both the paragraph's runs bidi order and the
+ /// individual glyphs bidi order within a run.
+ Iterable glyphs(Paragraph paragraph) sync* {
+ var displayRuns = [];
+ var glyphRuns = paragraph.runs;
+ int runIndex, bidiEndRun, runInc;
+ // if (paragraph.direction == TextDirection.rtl) {
+ // runIndex = endRun;
+ // bidiEndRun = startRun - 1;
+ // runInc = -1;
+ // } else {
+ runIndex = startRun;
+ bidiEndRun = endRun + 1;
+ runInc = 1;
+ // }
+ while (runIndex != bidiEndRun) {
+ var run = glyphRuns[runIndex];
+ displayRuns.add(run);
+ // int startGIndex = runIndex == startRun ? startIndex : 0;
+ // int endGIndex = runIndex == endRun ? endIndex : run.glyphCount;
+
+ // int j, end, inc;
+ // if (run.direction == TextDirection.rtl) {
+ // j = endGIndex - 1;
+ // end = startGIndex - 1;
+ // inc = -1;
+ // } else {
+ // j = startGIndex;
+ // end = endGIndex;
+ // inc = 1;
+ // }
+
+ // while (j != end) {
+ // yield LineRunGlyph(run, j);
+ // startGIndex = 0;
+ // j += inc;
+ // }
+ runIndex += runInc;
+ }
+
+ var startRunRef = displayRuns.first;
+ var endRunRef = displayRuns.last;
+
+ for (final run in paragraph.orderVisually(displayRuns)) {
+ int startGIndex = startRunRef == run ? startIndex : 0;
+ int endGIndex = endRunRef == run ? endIndex : run.glyphCount;
+
+ int j, end, inc;
+ if (run.direction == TextDirection.rtl) {
+ j = endGIndex - 1;
+ end = startGIndex - 1;
+ inc = -1;
+ } else {
+ j = startGIndex;
+ end = endGIndex;
+ inc = 1;
+ }
+
+ while (j != end) {
+ yield LineRunGlyph(run, j);
+ startGIndex = 0;
+ j += inc;
+ }
+ }
+
+ // var glyphRuns = paragraph.visualRuns;
+ // int runIndex, bidiEndRun, runInc;
+ // if (paragraph.direction == TextDirection.rtl) {
+ // runIndex = endRun;
+ // bidiEndRun = startRun - 1;
+ // runInc = -1;
+ // } else {
+ // runIndex = startRun;
+ // bidiEndRun = endRun + 1;
+ // runInc = 1;
+ // }
+ // while (runIndex != bidiEndRun) {
+ // var run = glyphRuns[runIndex];
+ // int startGIndex = runIndex == startRun ? startIndex : 0;
+ // int endGIndex = runIndex == endRun ? endIndex : run.glyphCount;
+
+ // int j, end, inc;
+ // if (run.direction == TextDirection.rtl) {
+ // j = endGIndex - 1;
+ // end = startGIndex - 1;
+ // inc = -1;
+ // } else {
+ // j = startGIndex;
+ // end = endGIndex;
+ // inc = 1;
+ // }
+
+ // while (j != end) {
+ // yield LineRunGlyph(run, j);
+ // startGIndex = 0;
+ // j += inc;
+ // }
+ // runIndex += runInc;
+ // }
+ }
+}
+
+abstract class BreakLinesResult extends ListBase> {
+ void dispose();
+}
+
+abstract class TextShapeResult {
+ List get paragraphs;
+ void dispose();
+ BreakLinesResult breakLines(double width, TextAlign alignment);
+}
+
+/// A representation of a styled section of text in Rive.
+class TextRun {
+ final Font font;
+ final double fontSize;
+ final int unicharCount;
+ final int styleId;
+
+ TextRun({
+ required this.font,
+ required this.fontSize,
+ required this.unicharCount,
+ this.styleId = 0,
+ });
+
+ TextRun copy({required int copyUnicharCount}) => TextRun(
+ font: font,
+ fontSize: fontSize,
+ unicharCount: copyUnicharCount,
+ styleId: styleId,
+ );
+
+ @override
+ String toString() => 'TextRun($fontSize:$unicharCount:$styleId)';
+
+ @override
+ bool operator ==(Object other) =>
+ other is TextRun &&
+ other.font == font &&
+ other.fontSize == fontSize &&
+ other.unicharCount == unicharCount &&
+ other.styleId == styleId;
+
+ @override
+ int get hashCode => Object.hash(font, fontSize, unicharCount, styleId);
+}
+
+abstract class Font {
+ static Future initialize() => initFont();
+ static Font? decode(Uint8List bytes) {
+ return decodeFont(bytes);
+ }
+
+ static void setFallbacks(List fonts) {
+ setFallbackFonts(fonts);
+ }
+
+ RawPath getPath(int glyphId);
+ void dispose();
+
+ TextShapeResult shape(String text, List runs);
+
+ final HashMap _glyphPaths = HashMap();
+ ui.Path getUiPath(int glyphId) {
+ var glyphPath = _glyphPaths[glyphId];
+ if (glyphPath != null) {
+ return glyphPath;
+ }
+ var path = ui.Path();
+ var rawPath = getPath(glyphId);
+ rawPath.issueCommands(path);
+ rawPath.dispose();
+ _glyphPaths[glyphId] = path;
+ return path;
+ }
+}
diff --git a/lib/src/rive_text_ffi.dart b/lib/src/rive_text_ffi.dart
new file mode 100644
index 0000000..ade27ee
--- /dev/null
+++ b/lib/src/rive_text_ffi.dart
@@ -0,0 +1,605 @@
+import 'dart:collection';
+import 'dart:ffi';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:ffi/ffi.dart';
+import 'package:rive/math.dart';
+import 'package:rive/src/platform.dart' as rive;
+import 'package:rive/src/rive_text.dart';
+
+final DynamicLibrary nativeLib = _loadLibrary();
+
+DynamicLibrary _loadLibrary() {
+ if (rive.Platform.instance.isTesting) {
+ var paths = [
+ '',
+ '../../packages/rive_flutter/',
+ ];
+ if (Platform.isMacOS) {
+ for (final path in paths) {
+ try {
+ return DynamicLibrary.open(
+ '${path}shared_lib/build/bin/debug/librive_text.dylib',
+ );
+ // ignore: avoid_catching_errors
+ } on ArgumentError catch (_) {}
+ }
+ } else if (Platform.isLinux) {
+ for (final path in paths) {
+ try {
+ return DynamicLibrary.open(
+ '${path}shared_lib/build/bin/debug/librive_text.so',
+ );
+ // ignore: avoid_catching_errors
+ } on ArgumentError catch (_) {}
+ }
+ }
+ }
+
+ if (Platform.isAndroid) {
+ return DynamicLibrary.open('librive_text.so');
+ } else if (Platform.isWindows) {
+ return DynamicLibrary.open('rive_plugin.dll');
+ }
+ return DynamicLibrary.process();
+}
+
+class PathPoint extends Struct {
+ @Float()
+ external double x;
+ @Float()
+ external double y;
+
+ @override
+ String toString() => '[$x, $y]';
+}
+
+class GlyphPathStruct extends Struct {
+ external Pointer rawPath;
+ external Pointer points;
+ external Pointer verbs;
+
+ @Uint16()
+ external int verbCount;
+}
+
+class GlyphLineNative extends Struct {
+ @Uint32()
+ external int startRun;
+
+ @Uint32()
+ external int startIndex;
+
+ @Uint32()
+ external int endRun;
+
+ @Uint32()
+ external int endIndex;
+
+ @Float()
+ external double startX;
+
+ @Float()
+ external double top;
+
+ @Float()
+ external double baseline;
+
+ @Float()
+ external double bottom;
+}
+
+class GlyphLineFFI extends GlyphLine {
+ final GlyphLineNative nativeLine;
+
+ GlyphLineFFI(this.nativeLine);
+
+ @override
+ double get baseline => nativeLine.baseline;
+
+ @override
+ double get bottom => nativeLine.bottom;
+
+ @override
+ int get endIndex => nativeLine.endIndex;
+
+ @override
+ int get endRun => nativeLine.endRun;
+
+ @override
+ int get startIndex => nativeLine.startIndex;
+
+ @override
+ int get startRun => nativeLine.startRun;
+
+ @override
+ double get startX => nativeLine.startX;
+
+ @override
+ double get top => nativeLine.top;
+}
+
+class SimpleLineList extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+}
+
+class SimpleLineDoubleList extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+}
+
+class SimpleUint16Array extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+}
+
+class SimpleUint32Array extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+}
+
+class SimpleFloatArray extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+}
+
+class TextRunNative extends Struct {
+ external Pointer font;
+ @Float()
+ external double size;
+ @Uint32()
+ external int unicharCount;
+ @Uint32()
+ external int script;
+ @Uint16()
+ external int styleId;
+ @Uint8()
+ external int dir;
+}
+
+class SimpleGlyphRunArray extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+
+ List toList() {
+ var list = [];
+ for (int i = 0; i < size; i++) {
+ list.add(data.elementAt(i).ref);
+ }
+ return list;
+ }
+}
+
+class GlyphRunNative extends Struct implements GlyphRun {
+ external Pointer fontPtr;
+ @Float()
+ external double size;
+ external SimpleUint16Array glyphs;
+ external SimpleUint32Array textIndices;
+ external SimpleFloatArray advances;
+ external SimpleFloatArray xpos;
+ external SimpleUint32Array breaks;
+ @override
+ @Uint16()
+ external int styleId;
+ @Uint8()
+ external int dir;
+
+ @override
+ double get fontSize => size;
+
+ @override
+ int get glyphCount => glyphs.size;
+
+ @override
+ int glyphIdAt(int index) => glyphs.data.elementAt(index).value;
+
+ @override
+ Font get font => FontFFI(fontPtr);
+
+ @override
+ int textIndexAt(int index) => textIndices.data.elementAt(index).value;
+
+ @override
+ double advanceAt(int index) => advances.data.elementAt(index).value;
+
+ @override
+ TextDirection get direction => TextDirection.values[dir];
+}
+
+class DynamicTextRunArray extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+}
+
+class ParagraphNative extends Struct {
+ external SimpleGlyphRunArray runs;
+ @Uint8()
+ external int direction;
+}
+
+class SimpleParagraphArray extends Struct {
+ external Pointer data;
+ @Uint64()
+ external int size;
+
+ List toList() {
+ var list = [];
+ for (int i = 0; i < size; i++) {
+ list.add(ParagraphFFI(data.elementAt(i).ref));
+ }
+ return list;
+ }
+}
+
+class ParagraphFFI extends Paragraph {
+ final ParagraphNative nativeParagraph;
+ @override
+ TextDirection get direction =>
+ TextDirection.values[nativeParagraph.direction];
+
+ @override
+ final List runs;
+
+ ParagraphFFI(this.nativeParagraph) : runs = nativeParagraph.runs.toList();
+}
+
+class ParagraphsListFFI extends ListBase {
+ final SimpleParagraphArray nativeList;
+ @override
+ int get length => nativeList.size;
+
+ ParagraphsListFFI._(this.nativeList);
+
+ @override
+ ParagraphFFI operator [](int index) =>
+ ParagraphFFI(nativeList.data.elementAt(index).ref);
+
+ @override
+ void operator []=(int index, ParagraphFFI value) {
+ throw UnsupportedError('Cannot set Paragraph on ParagraphList');
+ }
+
+ @override
+ set length(int newLength) {
+ throw UnsupportedError('Cannot set length on ParagraphList');
+ }
+}
+
+class LineList extends ListBase {
+ final SimpleLineList nativeList;
+
+ LineList(this.nativeList);
+ @override
+ int get length => nativeList.size;
+
+ @override
+ GlyphLine operator [](int index) =>
+ GlyphLineFFI(nativeList.data.elementAt(index).ref);
+
+ @override
+ void operator []=(int index, GlyphLine value) {
+ throw UnsupportedError('Cannot set glyphline on LineList');
+ }
+
+ @override
+ set length(int newLength) {
+ throw UnsupportedError('Cannot set length on LineList');
+ }
+}
+
+class LineDoubleList extends BreakLinesResult {
+ final Pointer nativeDoubleListPtr;
+ final SimpleLineDoubleList nativeDoubleList;
+
+ @override
+ int get length => nativeDoubleList.size;
+
+ LineDoubleList(this.nativeDoubleListPtr)
+ : nativeDoubleList = nativeDoubleListPtr.ref;
+
+ @override
+ List operator [](int index) =>
+ LineList(nativeDoubleList.data.elementAt(index).ref);
+
+ @override
+ void operator []=(int index, List value) {
+ throw UnsupportedError('Cannot set list on LineDoubleList');
+ }
+
+ @override
+ set length(int newLength) {
+ throw UnsupportedError('Cannot set length on LineDoubleList');
+ }
+
+ @override
+ void dispose() => deleteLines(nativeDoubleListPtr);
+}
+
+class TextShapeResultFFI extends TextShapeResult {
+ final Pointer nativeResult;
+ TextShapeResultFFI(this.nativeResult)
+ : paragraphs = nativeResult.ref.toList();
+
+ @override
+ void dispose() {
+ deleteShapeResult(nativeResult);
+ }
+
+ @override
+ BreakLinesResult breakLines(double width, TextAlign alignment) {
+ return LineDoubleList(
+ breakLinesNative(nativeResult, width, alignment.index));
+ }
+
+ @override
+ final List paragraphs;
+}
+
+final Pointer Function(Pointer text,
+ int textLength, Pointer runs, int runsLength) shapeText =
+ nativeLib
+ .lookup<
+ NativeFunction<
+ Pointer Function(Pointer, Uint64,
+ Pointer, Uint64)>>('shapeText')
+ .asFunction();
+
+final void Function(Pointer font) deleteShapeResult =
+ nativeLib
+ .lookup)>>(
+ 'deleteShapeResult')
+ .asFunction();
+
+final Pointer Function(
+ Pointer, double width, int align)
+ breakLinesNative = nativeLib
+ .lookup<
+ NativeFunction<
+ Pointer Function(
+ Pointer, Float, Uint8)>>('breakLines')
+ .asFunction();
+
+final void Function(Pointer) deleteLines = nativeLib
+ .lookup)>>(
+ 'deleteLines')
+ .asFunction();
+
+final Pointer Function(Pointer bytes, int count) makeFont =
+ nativeLib
+ .lookup Function(Pointer, Uint64)>>(
+ 'makeFont')
+ .asFunction();
+
+final void Function(Pointer font) deleteFont = nativeLib
+ .lookup)>>('deleteFont')
+ .asFunction();
+
+final GlyphPathStruct Function(
+ Pointer font,
+ int
+ glyphId) makeGlyphPath = nativeLib
+ .lookup, Uint16)>>(
+ 'makeGlyphPath')
+ .asFunction();
+
+final void Function(Pointer font) deleteGlyphPath = nativeLib
+ .lookup)>>('deleteGlyphPath')
+ .asFunction();
+
+final void Function() init =
+ nativeLib.lookup>('init').asFunction();
+
+final Pointer Function(
+ Pointer> fonts, int fontsLength) setFallbackFontsNative =
+ nativeLib
+ .lookup<
+ NativeFunction<
+ Pointer Function(
+ Pointer>, Uint64)>>('setFallbackFonts')
+ .asFunction();
+
+class RawPathCommandWasm extends RawPathCommand {
+ final Pointer _points;
+
+ RawPathCommandWasm._(
+ RawPathVerb verb,
+ this._points,
+ ) : super(verb);
+
+ @override
+ Vec2D point(int index) {
+ var ref = _points.elementAt(index).ref;
+ return Vec2D.fromValues(ref.x, ref.y);
+ }
+}
+
+RawPathVerb _verbFromNative(int nativeVerb) {
+ switch (nativeVerb) {
+ case 0:
+ return RawPathVerb.move;
+ case 1:
+ return RawPathVerb.line;
+ case 2:
+ return RawPathVerb.quad;
+ case 4:
+ return RawPathVerb.cubic;
+ case 5:
+ return RawPathVerb.close;
+ default:
+ throw Exception('Unexpected nativeVerb: $nativeVerb');
+ }
+}
+
+int _ptsAdvanceAfterVerb(RawPathVerb verb) {
+ switch (verb) {
+ case RawPathVerb.move:
+ return 1;
+ case RawPathVerb.line:
+ return 1;
+ case RawPathVerb.quad:
+ return 2;
+ case RawPathVerb.cubic:
+ return 3;
+ case RawPathVerb.close:
+ return 0;
+ default:
+ throw Exception('Unexpected nativeVerb: $verb');
+ }
+}
+
+int _ptsBacksetForVerb(RawPathVerb verb) {
+ switch (verb) {
+ case RawPathVerb.move:
+ return 0;
+ case RawPathVerb.line:
+ return -1;
+ case RawPathVerb.quad:
+ return -1;
+ case RawPathVerb.cubic:
+ return -1;
+ case RawPathVerb.close:
+ return -1;
+ default:
+ throw Exception('Unexpected nativeVerb: $verb');
+ }
+}
+
+class RawPathIterator extends Iterator {
+ final GlyphPathStruct _native;
+ int _verbIndex = -1;
+ int _ptIndex = -1;
+
+ RawPathVerb _verb = RawPathVerb.move;
+
+ RawPathIterator._(this._native);
+
+ @override
+ RawPathCommand get current => RawPathCommandWasm._(
+ _verb,
+ _native.points.elementAt(_ptIndex + _ptsBacksetForVerb(_verb)),
+ );
+
+ @override
+ bool moveNext() {
+ if (++_verbIndex < _native.verbCount) {
+ _ptIndex += _ptsAdvanceAfterVerb(_verb);
+ _verb = _verbFromNative(_native.verbs.elementAt(_verbIndex).value);
+ return true;
+ }
+ return false;
+ }
+}
+
+class RawPathFFI extends RawPath {
+ final GlyphPathStruct _native;
+ RawPathFFI._(this._native);
+
+ @override
+ Iterator get iterator => RawPathIterator._(_native);
+
+ @override
+ void dispose() => deleteGlyphPath(_native.rawPath);
+}
+
+/// A Font created and owned by Dart code. User is expected to call
+/// dispose to release the font when they are done with it.
+class StrongFontFFI extends FontFFI {
+ StrongFontFFI(Pointer ptr) : super(ptr);
+
+ @override
+ void dispose() => deleteFont(fontPtr);
+}
+
+/// A Font reference that should not be explicitly disposed by the user.
+/// Returned while shaping.
+class FontFFI extends Font {
+ Pointer fontPtr;
+
+ FontFFI(this.fontPtr);
+
+ @override
+ RawPath getPath(int glyphId) {
+ var glyphPath = makeGlyphPath(fontPtr, glyphId);
+ return RawPathFFI._(glyphPath);
+ }
+
+ @override
+ void dispose() {}
+
+ @override
+ TextShapeResult shape(String text, List runs) {
+ var textUni = text.codeUnits;
+
+ // Allocate and copy to runs memory.
+ var runsMemory =
+ calloc.allocate(runs.length * sizeOf());
+ int runIndex = 0;
+ for (final run in runs) {
+ runsMemory[runIndex++]
+ ..font = (run.font as FontFFI).fontPtr
+ ..size = run.fontSize
+ ..script = 0
+ ..unicharCount = run.unicharCount
+ ..styleId = run.styleId
+ ..dir = 0;
+ }
+
+ // Allocate and copy to text buffer.
+ var textBuffer = calloc.allocate(textUni.length * sizeOf());
+ for (int i = 0; i < textUni.length; i++) {
+ textBuffer[i] = textUni[i];
+ }
+
+ var shapeResult =
+ shapeText(textBuffer, textUni.length, runsMemory, runs.length);
+
+ // Free memory for structs passed into native that we no longer need.
+ calloc.free(textBuffer);
+ calloc.free(runsMemory);
+
+ return TextShapeResultFFI(shapeResult);
+ }
+}
+
+Font? decodeFont(Uint8List bytes) {
+ // Copy them to the native heap.
+ var pointer = calloc.allocate(bytes.length);
+ for (int i = 0; i < bytes.length; i++) {
+ pointer[i] = bytes[i];
+ }
+
+ // Pass the pointer in to a native method.
+ var result = makeFont(pointer, bytes.length);
+ calloc.free(pointer);
+
+ return FontFFI(result);
+}
+
+Future initFont() async {
+ init();
+}
+
+void setFallbackFonts(List fonts) {
+ // Allocate and copy to fonts list memory.
+ var fontListMemory =
+ calloc.allocate>(fonts.length * sizeOf>());
+ int fontIndex = 0;
+ for (final font in fonts) {
+ fontListMemory[fontIndex++] = (font as FontFFI).fontPtr;
+ }
+
+ setFallbackFontsNative(fontListMemory, fonts.length);
+
+ // Free memory for structs passed into native that we no longer need.
+ calloc.free(fontListMemory);
+}
diff --git a/lib/src/rive_text_wasm.dart b/lib/src/rive_text_wasm.dart
new file mode 100644
index 0000000..318206e
--- /dev/null
+++ b/lib/src/rive_text_wasm.dart
@@ -0,0 +1,551 @@
+// ignore: avoid_web_libraries_in_flutter
+import 'dart:async';
+import 'dart:collection';
+import 'dart:html' as html;
+import 'dart:js' as js;
+import 'dart:typed_data';
+
+import 'package:rive/math.dart';
+import 'package:rive/src/rive_text.dart';
+import 'package:rive/src/rive_text_wasm_version.dart';
+import 'package:rive/src/utilities/binary_buffer/binary_reader.dart';
+import 'package:rive/src/utilities/binary_buffer/binary_writer.dart';
+
+late js.JsFunction _makeFont;
+late js.JsFunction _deleteFont;
+late js.JsFunction _makeGlyphPath;
+late js.JsFunction _deleteGlyphPath;
+late js.JsFunction _shapeText;
+late js.JsFunction _setFallbackFonts;
+late js.JsFunction _deleteShapeResult;
+late js.JsFunction _breakLines;
+late js.JsFunction _deleteLines;
+
+class RawPathWasm extends RawPath {
+ final Uint8List verbs;
+ final Float32List points;
+
+ RawPathWasm({
+ required this.verbs,
+ required this.points,
+ });
+
+ @override
+ void dispose() {}
+
+ @override
+ Iterator get iterator => RawPathIterator._(verbs, points);
+}
+
+class RawPathCommandWasm extends RawPathCommand {
+ final Float32List _points;
+ final int _pointsOffset;
+
+ RawPathCommandWasm._(
+ RawPathVerb verb,
+ this._points,
+ this._pointsOffset,
+ ) : super(verb);
+
+ @override
+ Vec2D point(int index) {
+ var base = _pointsOffset + index * 2;
+ return Vec2D.fromValues(_points[base], _points[base + 1]);
+ }
+}
+
+RawPathVerb _verbFromNative(int nativeVerb) {
+ switch (nativeVerb) {
+ case 0:
+ return RawPathVerb.move;
+ case 1:
+ return RawPathVerb.line;
+ case 2:
+ return RawPathVerb.quad;
+ case 4:
+ return RawPathVerb.cubic;
+ case 5:
+ return RawPathVerb.close;
+ default:
+ throw Exception('Unexpected nativeVerb: $nativeVerb');
+ }
+}
+
+int _ptsAdvanceAfterVerb(RawPathVerb verb) {
+ switch (verb) {
+ case RawPathVerb.move:
+ return 1;
+ case RawPathVerb.line:
+ return 1;
+ case RawPathVerb.quad:
+ return 2;
+ case RawPathVerb.cubic:
+ return 3;
+ case RawPathVerb.close:
+ return 0;
+ default:
+ throw Exception('Unexpected nativeVerb: $verb');
+ }
+}
+
+int _ptsBacksetForVerb(RawPathVerb verb) {
+ switch (verb) {
+ case RawPathVerb.move:
+ return 0;
+ case RawPathVerb.line:
+ return -1;
+ case RawPathVerb.quad:
+ return -1;
+ case RawPathVerb.cubic:
+ return -1;
+ case RawPathVerb.close:
+ return -1;
+ default:
+ throw Exception('Unexpected nativeVerb: $verb');
+ }
+}
+
+class RawPathIterator extends Iterator {
+ final Uint8List verbs;
+ final Float32List points;
+ int _verbIndex = -1;
+ int _ptIndex = -1;
+
+ RawPathVerb _verb = RawPathVerb.move;
+
+ RawPathIterator._(this.verbs, this.points);
+
+ @override
+ RawPathCommand get current => RawPathCommandWasm._(
+ _verb,
+ points,
+ (_ptIndex + _ptsBacksetForVerb(_verb)) * 2,
+ );
+
+ @override
+ bool moveNext() {
+ if (++_verbIndex < verbs.length) {
+ _ptIndex += _ptsAdvanceAfterVerb(_verb);
+ _verb = _verbFromNative(verbs[_verbIndex]);
+ return true;
+ }
+ return false;
+ }
+}
+
+class GlyphLineWasm extends GlyphLine {
+ @override
+ final int startRun;
+
+ @override
+ final int startIndex;
+
+ @override
+ final int endRun;
+
+ @override
+ final int endIndex;
+
+ @override
+ final double startX;
+
+ @override
+ final double top;
+
+ @override
+ final double baseline;
+
+ @override
+ final double bottom;
+
+ GlyphLineWasm(ByteData data)
+ : startRun = data.getUint32(0, Endian.little),
+ startIndex = data.getUint32(4, Endian.little),
+ endRun = data.getUint32(8, Endian.little),
+ endIndex = data.getUint32(12, Endian.little),
+ startX = data.getFloat32(16, Endian.little),
+ top = data.getFloat32(20, Endian.little),
+ baseline = data.getFloat32(24, Endian.little),
+ bottom = data.getFloat32(28, Endian.little);
+
+ @override
+ String toString() {
+ return "GlyphLineWasm $startRun $startIndex $endRun $endIndex $startX $top $baseline $bottom";
+ }
+}
+
+class BreakLinesResultFFI extends BreakLinesResult {
+ final List> list;
+ BreakLinesResultFFI(this.list);
+ @override
+ int get length => list.length;
+
+ @override
+ set length(int value) => list.length = value;
+
+ @override
+ List operator [](int index) => list[index];
+
+ @override
+ void operator []=(int index, List value) => list[index] = value;
+
+ @override
+ void dispose() {}
+}
+
+class TextShapeResultWasm extends TextShapeResult {
+ final int shapeResultPtr;
+ @override
+ final List paragraphs;
+
+ TextShapeResultWasm(this.shapeResultPtr, this.paragraphs);
+ @override
+ void dispose() => _deleteShapeResult.apply([shapeResultPtr]);
+
+ @override
+ BreakLinesResult breakLines(double width, TextAlign alignment) {
+ var result = _breakLines.apply(
+ [
+ shapeResultPtr,
+ width,
+ alignment.index,
+ ],
+ ) as js.JsObject;
+
+ var rawResult = result['rawResult'] as int;
+ var results = result['results'] as Uint8List;
+
+ const lineSize = 32;
+ var paragraphsList = ByteData.view(results.buffer, results.offsetInBytes)
+ .readDynamicArray(0);
+ var paragraphsLines = >[];
+ var pointerEnd = paragraphsList.size * 8;
+ for (var pointer = 0; pointer < pointerEnd; pointer += 8) {
+ var sublist = paragraphsList.data.readDynamicArray(pointer);
+ var lines = [];
+
+ var end = sublist.data.offsetInBytes + sublist.size * lineSize;
+ for (var lineOffset = sublist.data.offsetInBytes;
+ lineOffset < end;
+ lineOffset += lineSize) {
+ lines.add(
+ GlyphLineWasm(
+ ByteData.view(
+ sublist.data.buffer,
+ lineOffset,
+ ),
+ ),
+ );
+ }
+ paragraphsLines.add(lines);
+ }
+ _deleteLines.apply(
+ [
+ rawResult,
+ ],
+ );
+
+ return BreakLinesResultFFI(paragraphsLines);
+ }
+}
+
+extension ByteDataWasm on ByteData {
+ WasmDynamicArray readDynamicArray(int offset) {
+ return WasmDynamicArray(
+ ByteData.view(buffer, getUint32(offset, Endian.little)),
+ getUint32(
+ offset + 4,
+ Endian.little,
+ ),
+ );
+ }
+
+ Uint16List readUint16List(int offset, {bool clone = true}) {
+ var array = readDynamicArray(offset);
+ var list =
+ array.data.buffer.asUint16List(array.data.offsetInBytes, array.size);
+ if (clone) {
+ return Uint16List.fromList(list);
+ }
+ return list;
+ }
+
+ Uint32List readUint32List(int offset, {bool clone = true}) {
+ var array = readDynamicArray(offset);
+ var list =
+ array.data.buffer.asUint32List(array.data.offsetInBytes, array.size);
+ if (clone) {
+ return Uint32List.fromList(list);
+ }
+ return list;
+ }
+
+ Float32List readFloat32List(int offset, {bool clone = true}) {
+ var array = readDynamicArray(offset);
+ var list =
+ array.data.buffer.asFloat32List(array.data.offsetInBytes, array.size);
+
+ if (clone) {
+ return Float32List.fromList(list);
+ }
+ return list;
+ }
+}
+
+class WasmDynamicArray {
+ final ByteData data;
+ final int size;
+ WasmDynamicArray(this.data, this.size);
+}
+
+class LinesWasm extends ListBase {
+ final WasmDynamicArray wasmDynamicArray;
+
+ LinesWasm(this.wasmDynamicArray);
+
+ @override
+ int get length => wasmDynamicArray.size;
+
+ @override
+ GlyphLineWasm operator [](int index) {
+ const lineSize = 4 + //startRun
+ 4 + // startIndex
+ 4 + // endRun
+ 4 + // endIndex
+ 4 + // startX
+ 4 + // top
+ 4 + // baseline
+ 4; // bottom
+ var data = wasmDynamicArray.data;
+ return GlyphLineWasm(
+ ByteData.view(
+ data.buffer,
+ data.offsetInBytes + index * lineSize,
+ ),
+ );
+ }
+
+ @override
+ void operator []=(int index, GlyphLineWasm value) {
+ throw UnsupportedError('Cannot set Line on LinesWasm array');
+ }
+
+ @override
+ set length(int newLength) {
+ throw UnsupportedError('Cannot set Line count on LinesWasm array');
+ }
+}
+
+class ParagraphWasm extends Paragraph {
+ final ByteData data;
+ @override
+ final TextDirection direction;
+
+ @override
+ final List runs = [];
+
+ ParagraphWasm(this.data)
+ : direction = TextDirection.values[data.getUint8(8)] {
+ const runSize =
+ 52; // see rive_text_bindings.cpp assertSomeAssumptions for explanation
+ var runsPointer = data.getUint32(0, Endian.little);
+ var runsCount = data.getUint32(4, Endian.little);
+
+ for (int i = 0, runPointer = runsPointer;
+ i < runsCount;
+ i++, runPointer += runSize) {
+ runs.add(GlyphRunWasm(ByteData.view(data.buffer, runPointer)));
+ }
+ }
+}
+
+class GlyphRunWasm extends GlyphRun {
+ final ByteData byteData;
+ final Uint16List glyphs;
+ final Uint32List textIndices;
+ final Float32List xPositions;
+
+ @override
+ final TextDirection direction;
+
+ @override
+ final int styleId;
+
+ @override
+ final Font font;
+
+ @override
+ final double fontSize;
+
+ GlyphRunWasm(this.byteData)
+ : font = FontWasm(byteData.getUint32(0, Endian.little)),
+ fontSize = byteData.getFloat32(4, Endian.little),
+ glyphs = byteData.readUint16List(8),
+ textIndices = byteData.readUint32List(16),
+ xPositions = byteData.readFloat32List(24),
+ styleId = byteData.getUint16(48, Endian.little),
+ direction = TextDirection.values[byteData.getUint8(50)];
+
+ @override
+ int get glyphCount => glyphs.length;
+
+ @override
+ int glyphIdAt(int index) => glyphs[index];
+
+ @override
+ int textIndexAt(int index) => textIndices[index];
+
+ @override
+ double advanceAt(int index) => xPositions[index];
+}
+
+/// A Font reference that should not be explicitly disposed by the user.
+/// Returned while shaping.
+class FontWasm extends Font {
+ final int fontPtr;
+ FontWasm(this.fontPtr);
+
+ @override
+ String toString() {
+ return 'FontWasm $fontPtr';
+ }
+
+ @override
+ void dispose() {}
+
+ @override
+ RawPath getPath(int glyphId) {
+ var object =
+ _makeGlyphPath.apply([fontPtr, glyphId]) as js.JsObject;
+ var rawPathPtr = object['rawPath'] as int;
+
+ // The buffer for these share the WASM heap buffer, which is efficient but
+ // could also be lost in-between calls to WASM.
+ var verbs = object['verbs'] as Uint8List;
+ var points = object['points'] as Float32List;
+
+ // We copy the verb and points structures so we don't have to worry about
+ // the references being lost if the WASM heap is re-allocated.
+ var rawPath = RawPathWasm(
+ verbs: Uint8List.fromList(verbs),
+ points: Float32List.fromList(points),
+ );
+ // Immediately delete the native glyph's raw path.
+ _deleteGlyphPath.apply([rawPathPtr]);
+ return rawPath;
+ }
+
+ static const int sizeOfNativeTextRun = 20;
+
+ @override
+ TextShapeResult shape(String text, List runs) {
+ var writer = BinaryWriter(
+ alignment: runs.length * sizeOfNativeTextRun,
+ );
+ for (final run in runs) {
+ writer.writeUint32((run.font as FontWasm).fontPtr);
+ writer.writeFloat32(run.fontSize);
+ writer.writeUint32(run.unicharCount);
+ writer.writeUint32(0); // script (unknown at this point)
+ writer.writeUint16(run.styleId);
+ writer.writeUint8(0); // dir (unknown at this point)
+ writer.writeUint8(0); // padding to word align struct
+ }
+
+ var result = _shapeText.apply(
+ [
+ Uint32List.fromList(text.codeUnits),
+ writer.uint8Buffer,
+ ],
+ ) as js.JsObject;
+
+ var rawResult = result['rawResult'] as int;
+ var results = result['results'] as Uint8List;
+
+ var reader = BinaryReader.fromList(results);
+ var paragraphsPointer = reader.readUint32();
+ var paragraphsSize = reader.readUint32();
+
+ var paragraphList = [];
+ const paragraphSize = 12; // runs = 8, direction = 1, padding = 3
+
+ for (int i = 0;
+ i < paragraphsSize;
+ i++, paragraphsPointer += paragraphSize) {
+ paragraphList
+ .add(ParagraphWasm(ByteData.view(results.buffer, paragraphsPointer)));
+ }
+
+ return TextShapeResultWasm(rawResult, paragraphList);
+ }
+}
+
+/// A Font created and owned by Dart code. User is expected to call
+/// dispose to release the font when they are done with it.
+class StrongFontWasm extends FontWasm {
+ StrongFontWasm(int fontPtr) : super(fontPtr);
+
+ @override
+ void dispose() => _deleteFont.apply([fontPtr]);
+}
+
+Font? decodeFont(Uint8List bytes) {
+ int ptr = _makeFont.apply([bytes]) as int;
+ if (ptr == 0) {
+ return null;
+ }
+ return StrongFontWasm(ptr);
+}
+
+Future initFont() async {
+ var script = html.ScriptElement()
+ ..src = const bool.fromEnvironment(
+ 'LOCAL_RIVE_FLUTTER_WASM',
+ defaultValue: false,
+ )
+ ? 'http://localhost:8282/release/rive_text.js'
+ : 'https://unpkg.com/@rive-app/flutter-wasm@$wasmVersion/build/bin/release/rive_text.js'
+ ..type = 'application/javascript'
+ ..defer = true;
+
+ html.document.body!.append(script);
+ await script.onLoad.first;
+
+ var initWasm = js.context['RiveText'] as js.JsFunction;
+ var promise = initWasm.apply([]) as js.JsObject;
+ var thenFunction = promise['then'] as js.JsFunction;
+ var completer = Completer();
+ thenFunction.apply(
+ [
+ (js.JsObject module) {
+ var init = module['init'] as js.JsFunction;
+ init.apply([]);
+ _makeFont = module['makeFont'] as js.JsFunction;
+ _deleteFont = module['deleteFont'] as js.JsFunction;
+ _makeGlyphPath = module['makeGlyphPath'] as js.JsFunction;
+ _deleteGlyphPath = module['deleteGlyphPath'] as js.JsFunction;
+ _shapeText = module['shapeText'] as js.JsFunction;
+ _setFallbackFonts = module['setFallbackFonts'] as js.JsFunction;
+ _deleteShapeResult = module['deleteShapeResult'] as js.JsFunction;
+ _breakLines = module['breakLines'] as js.JsFunction;
+ _deleteLines = module['deleteLines'] as js.JsFunction;
+ completer.complete();
+ }
+ ],
+ thisArg: promise,
+ );
+ return completer.future;
+}
+
+void setFallbackFonts(List fonts) {
+ _setFallbackFonts.apply(
+ [
+ Uint32List.fromList(
+ fonts
+ .cast()
+ .map((font) => font.fontPtr)
+ .toList(growable: false),
+ ),
+ ],
+ );
+}
diff --git a/lib/src/rive_text_wasm_version.dart b/lib/src/rive_text_wasm_version.dart
new file mode 100644
index 0000000..0d9234b
--- /dev/null
+++ b/lib/src/rive_text_wasm_version.dart
@@ -0,0 +1 @@
+const wasmVersion = '6.0.0';
diff --git a/macos/Classes/RivePlugin.swift b/macos/Classes/RivePlugin.swift
new file mode 100644
index 0000000..30429c6
--- /dev/null
+++ b/macos/Classes/RivePlugin.swift
@@ -0,0 +1,19 @@
+import Cocoa
+import FlutterMacOS
+
+public class RivePlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let channel = FlutterMethodChannel(name: "rive", binaryMessenger: registrar.messenger)
+ let instance = RivePlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "getPlatformVersion":
+ result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
new file mode 100644
index 0000000..cccf817
--- /dev/null
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -0,0 +1,10 @@
+//
+// Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+}
diff --git a/macos/rive.podspec b/macos/rive.podspec
new file mode 100644
index 0000000..cd85aaa
--- /dev/null
+++ b/macos/rive.podspec
@@ -0,0 +1,94 @@
+Pod::Spec.new do |s|
+ s.name = "rive"
+ s.version = "0.0.1"
+ s.summary = "Rive text abstraction."
+ s.description = <<-DESC
+Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
+ DESC
+ s.homepage = "https://rive.app"
+ s.license = { :file => "../LICENSE" }
+ s.author = { "Rive" => "hello@rive.app" }
+
+ s.source = { :path => "." }
+ s.source_files = [
+ "Classes/**/*",
+ "rive_text/**/*.{cpp,hpp,c,h}",
+ "rive-cpp/src/math/raw_path.cpp",
+ "rive-cpp/src/math/mat2d.cpp",
+ "rive-cpp/src/rive_counter.cpp",
+ "rive-cpp/src/renderer.cpp",
+ "rive-cpp/src/text/font_hb.cpp",
+ "rive-cpp/src/text/line_breaker.cpp",
+
+ "harfbuzz/src/hb-aat-layout.cc",
+ "harfbuzz/src/hb-aat-map.cc",
+ "harfbuzz/src/hb-blob.cc",
+ "harfbuzz/src/hb-buffer-serialize.cc",
+ "harfbuzz/src/hb-buffer-verify.cc",
+ "harfbuzz/src/hb-buffer.cc",
+ "harfbuzz/src/hb-common.cc",
+ "harfbuzz/src/hb-draw.cc",
+ "harfbuzz/src/hb-face.cc",
+ "harfbuzz/src/hb-font.cc",
+ "harfbuzz/src/hb-map.cc",
+ "harfbuzz/src/hb-number.cc",
+ "harfbuzz/src/hb-ot-cff1-table.cc",
+ "harfbuzz/src/hb-ot-cff2-table.cc",
+ "harfbuzz/src/hb-ot-color.cc",
+ "harfbuzz/src/hb-ot-face.cc",
+ "harfbuzz/src/hb-ot-font.cc",
+ "harfbuzz/src/hb-ot-layout.cc",
+ "harfbuzz/src/hb-ot-map.cc",
+ "harfbuzz/src/hb-ot-math.cc",
+ "harfbuzz/src/hb-ot-meta.cc",
+ "harfbuzz/src/hb-ot-metrics.cc",
+ "harfbuzz/src/hb-ot-name.cc",
+ "harfbuzz/src/hb-ot-shape-complex-arabic.cc",
+ "harfbuzz/src/hb-ot-shape-complex-default.cc",
+ "harfbuzz/src/hb-ot-shape-complex-hangul.cc",
+ "harfbuzz/src/hb-ot-shape-complex-hebrew.cc",
+ "harfbuzz/src/hb-ot-shape-complex-indic-table.cc",
+ "harfbuzz/src/hb-ot-shape-complex-indic.cc",
+ "harfbuzz/src/hb-ot-shape-complex-khmer.cc",
+ "harfbuzz/src/hb-ot-shape-complex-myanmar.cc",
+ "harfbuzz/src/hb-ot-shape-complex-syllabic.cc",
+ "harfbuzz/src/hb-ot-shape-complex-thai.cc",
+ "harfbuzz/src/hb-ot-shape-complex-use.cc",
+ "harfbuzz/src/hb-ot-shape-complex-vowel-constraints.cc",
+ "harfbuzz/src/hb-ot-shape-fallback.cc",
+ "harfbuzz/src/hb-ot-shape-normalize.cc",
+ "harfbuzz/src/hb-ot-shape.cc",
+ "harfbuzz/src/hb-ot-tag.cc",
+ "harfbuzz/src/hb-ot-var.cc",
+ "harfbuzz/src/hb-set.cc",
+ "harfbuzz/src/hb-shape-plan.cc",
+ "harfbuzz/src/hb-shape.cc",
+ "harfbuzz/src/hb-shaper.cc",
+ "harfbuzz/src/hb-static.cc",
+ "harfbuzz/src/hb-subset-cff-common.cc",
+ "harfbuzz/src/hb-subset-cff1.cc",
+ "harfbuzz/src/hb-subset-cff2.cc",
+ "harfbuzz/src/hb-subset-input.cc",
+ "harfbuzz/src/hb-subset-plan.cc",
+ "harfbuzz/src/hb-subset-repacker.cc",
+ "harfbuzz/src/hb-subset.cc",
+ "harfbuzz/src/hb-ucd.cc",
+ "harfbuzz/src/hb-unicode.cc",
+
+ "SheenBidi/Headers/*.h",
+ "SheenBidi/Source/SheenBidi.c",
+ ]
+ s.dependency "FlutterMacOS"
+
+ s.platform = :osx, "10.11"
+ s.pod_target_xcconfig = {
+ "DEFINES_MODULE" => "YES",
+ "OTHER_CFLAGS" => "-DSB_CONFIG_UNITY -DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -Wno-documentation -Wno-comma -Wno-unreachable-code -Wno-shorten-64-to-32",
+ "OTHER_CPLUSPLUSFLAGS" => "-DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -Wno-conditional-uninitialized -Wno-documentation -Wno-comma -Wno-unreachable-code -Wno-shorten-64-to-32 -std=c++17",
+ "USER_HEADER_SEARCH_PATHS" => '"$(PODS_TARGET_SRCROOT)/SheenBidi/Headers" "$(PODS_TARGET_SRCROOT)/harfbuzz/src" "$(PODS_TARGET_SRCROOT)/rive-cpp/include" "$(PODS_TARGET_SRCROOT)/rive-cpp/skia/renderer/include"',
+ "OTHER_CPLUSPLUSFLAGS[config=Release]" => "-DNDEBUG -DWITH_RIVE_TEXT -DHAVE_OT -DHB_NO_FALLBACK_SHAPE -DHB_NO_WIN1256 -Wno-conditional-uninitialized -Wno-documentation -Wno-comma -Wno-unreachable-code -Wno-shorten-64-to-32 -std=c++17",
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
+ "CLANG_CXX_LIBRARY" => "libc++",
+ }
+ s.swift_version = "5.0"
+end
diff --git a/macos/rive_text/rive_text.cpp b/macos/rive_text/rive_text.cpp
new file mode 100644
index 0000000..5bae090
--- /dev/null
+++ b/macos/rive_text/rive_text.cpp
@@ -0,0 +1,135 @@
+#include
+#include
+
+#include "rive/text/font_hb.hpp"
+
+#define EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used))
+
+EXPORT
+rive::Font* makeFont(const uint8_t* bytes, uint64_t length)
+{
+ auto result = HBFont::Decode(rive::Span(bytes, length));
+ if (result)
+ {
+ auto ptr = result.release();
+ return ptr;
+ }
+ return nullptr;
+}
+
+EXPORT void deleteFont(rive::Font* font) { delete font; }
+
+struct GlyphPath
+{
+ rive::RawPath* rawPath;
+ rive::Vec2D* points;
+ rive::PathVerb* verbs;
+ uint16_t verbCount;
+};
+
+EXPORT
+GlyphPath makeGlyphPath(rive::Font* font, rive::GlyphID id)
+{
+ rive::RawPath* path = new rive::RawPath(font->getPath(id));
+
+ return {
+ .rawPath = path,
+ .points = path->points().data(),
+ .verbs = path->verbs().data(),
+ .verbCount = (uint16_t)path->verbs().size(),
+ };
+}
+
+EXPORT void deleteGlyphPath(rive::RawPath* rawPath) { delete rawPath; }
+
+EXPORT
+rive::SimpleArray*
+shapeText(const uint32_t* text, uint64_t length, rive::TextRun* runs, uint64_t runsLength)
+{
+ if (runsLength == 0 || length == 0)
+ {
+ return nullptr;
+ }
+ return new rive::SimpleArray(
+ runs[0].font->shapeText(rive::Span(text, length), rive::Span(runs, runsLength)));
+}
+
+EXPORT void deleteShapeResult(rive::SimpleArray* shapeResult)
+{
+ delete shapeResult;
+}
+
+EXPORT rive::SimpleArray>*
+breakLines(rive::SimpleArray* paragraphs, float width, uint8_t align)
+{
+ bool autoWidth = width == -1.0f;
+ float paragraphWidth = width;
+
+ rive::SimpleArray>* lines =
+ new rive::SimpleArray>(paragraphs->size());
+ rive::SimpleArray>& linesRef = *lines;
+ size_t paragraphIndex = 0;
+ for (auto& para : *paragraphs)
+ {
+ linesRef[paragraphIndex] =
+ rive::GlyphLine::BreakLines(para.runs, autoWidth ? -1.0f : width);
+ if (autoWidth)
+ {
+ paragraphWidth =
+ std::max(paragraphWidth,
+ rive::GlyphLine::ComputeMaxWidth(linesRef[paragraphIndex], para.runs));
+ }
+ paragraphIndex++;
+ }
+ paragraphIndex = 0;
+ for (auto& para : *paragraphs)
+ {
+ rive::GlyphLine::ComputeLineSpacing(linesRef[paragraphIndex++],
+ para.runs,
+ paragraphWidth,
+ (rive::TextAlign)align);
+ }
+ return lines;
+}
+
+EXPORT void deleteLines(rive::SimpleArray>* result)
+{
+ delete result;
+}
+
+std::vector fallbackFonts;
+
+EXPORT
+void setFallbackFonts(rive::Font** fonts, uint64_t fontsLength)
+{
+ if (fontsLength == 0)
+ {
+ fallbackFonts = std::vector();
+ return;
+ }
+ fallbackFonts = std::vector(fonts, fonts + fontsLength);
+}
+
+static rive::rcp pickFallbackFont(rive::Span missing)
+{
+ size_t length = fallbackFonts.size();
+ for (size_t i = 0; i < length; i++)
+ {
+ HBFont* font = static_cast(fallbackFonts[i]);
+ if (i == length - 1 || font->hasGlyph(missing))
+ {
+ rive::rcp rcFont = rive::rcp(font);
+ // because the font was released at load time, we need to give it an
+ // extra ref whenever we bump it to a reference counted pointer.
+ rcFont->ref();
+ return rcFont;
+ }
+ }
+ return nullptr;
+}
+EXPORT
+void init()
+{
+ fallbackFonts.clear();
+ HBFont::gFallbackProc = pickFallbackFont;
+}
\ No newline at end of file
diff --git a/platform_considerations.md b/platform_considerations.md
new file mode 100644
index 0000000..bc06ead
--- /dev/null
+++ b/platform_considerations.md
@@ -0,0 +1,35 @@
+# Platform Considerations
+
+In order to support some of our more low level features, Rive brings some of its C++ runtime to Flutter.
+| Platform | Technology | Dependencies |
+| ------------- | ------------- | ------------- |
+| iOS | FFI | statically linked |
+| Android | FFI | rive_text.so |
+| Windows | FFI | rive_plugin.dll |
+| Mac | FFI | statically linked |
+| Web | WASM | rive_text.js, rive_text.wasm |
+
+## iOS & Mac
+
+We use cocoapods to build and statically link to your project the portions of Rive's C++ runtime that are necessary for text features.
+
+## Android
+
+We use Gradle & CMake to build rive_text.so. Rive's runtime uses modern features that are only available on newer NDKs, for this reason we recommend updating your build.gradle to include ndkVersion 25.1.8937393
+
+```
+android {
+ compileSdkVersion 31
+ ndkVersion "25.1.8937393"
+ ...
+}
+```
+
+## Windows
+
+We use CMake to build rive_plugin.dll. Note that Clang compiler is required, see here for how to enable it in your Visual Studio:
+https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-170
+
+## Web
+
+We use emscripten to build a wasm and js file which are statically served via unpkg similarly to how Flutter delivers the CanvasKit wasm file.
diff --git a/premake5_rive_plugin.lua b/premake5_rive_plugin.lua
new file mode 100644
index 0000000..233977f
--- /dev/null
+++ b/premake5_rive_plugin.lua
@@ -0,0 +1,340 @@
+local dependency = require 'dependency'
+
+harfbuzz = dependency.github('harfbuzz/harfbuzz', '858570b1d9912a1b746ab39fbe62a646c4f7a5b1')
+sheenbidi = dependency.github('Tehreer/SheenBidi', 'v2.6')
+
+workspace 'rive_text'
+configurations {'debug', 'release'}
+
+source = os.isdir('../../packages/runtime') and '../../packages/runtime' or 'macos/rive-cpp'
+
+project 'rive_sheenbidi'
+do
+ kind 'StaticLib'
+ language 'C'
+ toolset 'clang'
+ targetdir 'shared_lib/build/bin/%{cfg.buildcfg}/'
+ objdir 'shared_lib/build/obj/%{cfg.buildcfg}/'
+
+ includedirs {
+ sheenbidi .. '/Headers'
+ }
+
+ filter {'options:arch=wasm'}
+ do
+ targetdir 'wasm/build/bin/%{cfg.buildcfg}/'
+ objdir 'wasm/build/obj/%{cfg.buildcfg}/'
+ end
+
+ filter 'configurations:debug'
+ do
+ files {
+ sheenbidi .. '/Source/BidiChain.c',
+ sheenbidi .. '/Source/BidiTypeLookup.c',
+ sheenbidi .. '/Source/BracketQueue.c',
+ sheenbidi .. '/Source/GeneralCategoryLookup.c',
+ sheenbidi .. '/Source/IsolatingRun.c',
+ sheenbidi .. '/Source/LevelRun.c',
+ sheenbidi .. '/Source/PairingLookup.c',
+ sheenbidi .. '/Source/RunQueue.c',
+ sheenbidi .. '/Source/SBAlgorithm.c',
+ sheenbidi .. '/Source/SBBase.c',
+ sheenbidi .. '/Source/SBCodepointSequence.c',
+ sheenbidi .. '/Source/SBLine.c',
+ sheenbidi .. '/Source/SBLog.c',
+ sheenbidi .. '/Source/SBMirrorLocator.c',
+ sheenbidi .. '/Source/SBParagraph.c',
+ sheenbidi .. '/Source/SBScriptLocator.c',
+ sheenbidi .. '/Source/ScriptLookup.c',
+ sheenbidi .. '/Source/ScriptStack.c',
+ sheenbidi .. '/Source/StatusStack.c'
+ }
+ end
+ filter 'configurations:release'
+ do
+ files {
+ sheenbidi .. '/Source/SheenBidi.c'
+ }
+ end
+
+ buildoptions {
+ '-Wall',
+ '-ansi',
+ '-pedantic',
+ '-DANSI_DECLARATORS'
+ }
+
+ buildoptions {
+ '-Wno-c++17-extensions',
+ '-fno-exceptions',
+ '-fno-rtti',
+ '-fno-unwind-tables',
+ '-Wno-deprecated-builtins'
+ }
+
+ filter {'options:arch=wasm'}
+ do
+ buildoptions {
+ '-s STRICT=1',
+ '-s DISABLE_EXCEPTION_CATCHING=1',
+ '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0',
+ '--no-entry'
+ }
+ end
+
+ filter 'configurations:debug'
+ do
+ defines {'DEBUG'}
+ symbols 'On'
+ end
+
+ filter 'configurations:release'
+ do
+ defines {'RELEASE', 'NDEBUG', 'SB_CONFIG_UNITY'}
+ optimize 'On'
+ buildoptions {
+ '-Oz',
+ '-g0',
+ '-flto'
+ }
+
+ linkoptions {
+ '-Oz',
+ '-g0',
+ '-flto'
+ }
+ end
+ filter {'system:macosx', 'options:arch=arm64'}
+ do
+ buildoptions {'-target arm64-apple-macos11'}
+ linkoptions {'-target arm64-apple-macos11'}
+ end
+ filter {'system:macosx', 'options:arch=x86_64'}
+ do
+ buildoptions {'-target x86_64-apple-macos10.12'}
+ linkoptions {'-target x86_64-apple-macos10.12'}
+ end
+end
+
+project 'rive_text'
+do
+ kind 'SharedLib'
+ language 'C++'
+ cppdialect 'C++17'
+ toolset 'clang'
+ targetdir('shared_lib/build/bin/%{cfg.buildcfg}')
+ objdir('shared_lib/build/obj/%{cfg.buildcfg}')
+
+ dependson {
+ 'rive_sheenbidi'
+ }
+
+ defines {
+ 'WITH_RIVE_TEXT',
+ 'HAVE_OT',
+ 'HB_NO_FALLBACK_SHAPE',
+ 'HB_NO_WIN1256'
+ }
+
+ includedirs {
+ source .. '/include',
+ source .. '/skia/renderer/include',
+ harfbuzz .. '/src/',
+ sheenbidi .. '/Headers'
+ }
+
+ files {
+ source .. '/src/renderer.cpp',
+ source .. '/src/rive_counter.cpp',
+ source .. '/src/math/mat2d.cpp',
+ source .. '/src/math/raw_path.cpp',
+ source .. '/src/text/font_hb.cpp',
+ source .. '/src/text/line_breaker.cpp',
+ harfbuzz .. '/src/hb-aat-layout.cc',
+ harfbuzz .. '/src/hb-aat-map.cc',
+ harfbuzz .. '/src/hb-blob.cc',
+ harfbuzz .. '/src/hb-buffer-serialize.cc',
+ harfbuzz .. '/src/hb-buffer-verify.cc',
+ harfbuzz .. '/src/hb-buffer.cc',
+ harfbuzz .. '/src/hb-common.cc',
+ harfbuzz .. '/src/hb-draw.cc',
+ harfbuzz .. '/src/hb-face.cc',
+ harfbuzz .. '/src/hb-font.cc',
+ harfbuzz .. '/src/hb-map.cc',
+ harfbuzz .. '/src/hb-number.cc',
+ harfbuzz .. '/src/hb-ot-cff1-table.cc',
+ harfbuzz .. '/src/hb-ot-cff2-table.cc',
+ harfbuzz .. '/src/hb-ot-color.cc',
+ harfbuzz .. '/src/hb-ot-face.cc',
+ harfbuzz .. '/src/hb-ot-font.cc',
+ harfbuzz .. '/src/hb-ot-layout.cc',
+ harfbuzz .. '/src/hb-ot-map.cc',
+ harfbuzz .. '/src/hb-ot-math.cc',
+ harfbuzz .. '/src/hb-ot-meta.cc',
+ harfbuzz .. '/src/hb-ot-metrics.cc',
+ harfbuzz .. '/src/hb-ot-name.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-arabic.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-default.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-hangul.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-hebrew.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-indic-table.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-indic.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-khmer.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-myanmar.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-syllabic.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-thai.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-use.cc',
+ harfbuzz .. '/src/hb-ot-shape-complex-vowel-constraints.cc',
+ harfbuzz .. '/src/hb-ot-shape-fallback.cc',
+ harfbuzz .. '/src/hb-ot-shape-normalize.cc',
+ harfbuzz .. '/src/hb-ot-shape.cc',
+ harfbuzz .. '/src/hb-ot-tag.cc',
+ harfbuzz .. '/src/hb-ot-var.cc',
+ harfbuzz .. '/src/hb-set.cc',
+ harfbuzz .. '/src/hb-shape-plan.cc',
+ harfbuzz .. '/src/hb-shape.cc',
+ harfbuzz .. '/src/hb-shaper.cc',
+ harfbuzz .. '/src/hb-static.cc',
+ harfbuzz .. '/src/hb-subset-cff-common.cc',
+ harfbuzz .. '/src/hb-subset-cff1.cc',
+ harfbuzz .. '/src/hb-subset-cff2.cc',
+ harfbuzz .. '/src/hb-subset-input.cc',
+ harfbuzz .. '/src/hb-subset-plan.cc',
+ harfbuzz .. '/src/hb-subset-repacker.cc',
+ harfbuzz .. '/src/hb-subset.cc',
+ harfbuzz .. '/src/hb-ucd.cc',
+ harfbuzz .. '/src/hb-unicode.cc'
+ }
+
+ links {
+ 'rive_sheenbidi'
+ }
+
+ buildoptions {
+ '-Wno-c++17-extensions',
+ '-fno-exceptions',
+ '-fno-rtti',
+ '-fno-unwind-tables',
+ '-Wno-deprecated-builtins',
+ '-DANSI_DECLARATORS'
+ }
+
+ filter {'not options:arch=wasm'}
+ do
+ files {
+ 'macos/rive_text/rive_text.cpp'
+ }
+ end
+
+ filter {'options:arch=wasm'}
+ do
+ targetdir 'wasm/build/bin/%{cfg.buildcfg}/'
+ objdir 'wasm/build/obj/%{cfg.buildcfg}/'
+ kind 'ConsoleApp'
+ files {
+ 'wasm/rive_text_bindings.cpp'
+ }
+ buildoptions {
+ '-s STRICT=1',
+ '-s DISABLE_EXCEPTION_CATCHING=1',
+ '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0',
+ '--no-entry'
+ }
+ linkoptions {
+ '--closure 1',
+ '--closure-args="--externs ./wasm/js/externs.js"',
+ '--bind',
+ '-s FORCE_FILESYSTEM=0',
+ '-s MODULARIZE=1',
+ '-s NO_EXIT_RUNTIME=1',
+ '-s STRICT=1',
+ '-s ALLOW_MEMORY_GROWTH=1',
+ '-s DISABLE_EXCEPTION_CATCHING=1',
+ '-s WASM=1',
+ '-s USE_ES6_IMPORT_META=0',
+ '-s EXPORT_NAME="RiveText"',
+ '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0',
+ '--no-entry',
+ '--pre-js ./wasm/js/rive_text.js'
+ }
+ end
+
+ linkoptions {
+ '-Wno-c++17-extensions',
+ '-fno-exceptions',
+ '-fno-rtti',
+ '-fno-unwind-tables'
+ }
+
+ filter {'options:arch=wasm', 'options:single_file'}
+ do
+ linkoptions {
+ '-o %{cfg.targetdir}/rive_text_single.js',
+ '-s SINGLE_FILE=1'
+ }
+ end
+
+ filter {'options:arch=wasm', 'options:not single_file'}
+ do
+ linkoptions {
+ '-o %{cfg.targetdir}/rive_text.js'
+ }
+ end
+
+ filter 'configurations:debug'
+ do
+ defines {'DEBUG'}
+ symbols 'On'
+ end
+
+ filter {'configurations:debug', 'options:arch=wasm'}
+ do
+ linkoptions {'-s ASSERTIONS=1'}
+ end
+
+ filter 'configurations:release'
+ do
+ defines {'RELEASE'}
+ defines {'NDEBUG'}
+ optimize 'On'
+
+ buildoptions {
+ '-Oz',
+ '-g0',
+ '-flto'
+ }
+
+ linkoptions {
+ '-Oz',
+ '-g0',
+ '-flto'
+ }
+ end
+
+ filter {'configurations:release', 'options:arch=wasm'}
+ do
+ linkoptions {'-s ASSERTIONS=0'}
+ end
+
+ filter {'system:macosx', 'options:arch=arm64'}
+ do
+ buildoptions {'-target arm64-apple-macos11'}
+ linkoptions {'-target arm64-apple-macos11'}
+ end
+ filter {'system:macosx', 'options:arch=x86_64'}
+ do
+ buildoptions {'-target x86_64-apple-macos10.12'}
+ linkoptions {'-target x86_64-apple-macos10.12'}
+ end
+end
+
+newoption {
+ trigger = 'single_file',
+ description = 'Set when the wasm should be packed in with the js code.'
+}
+
+newoption {
+ trigger = 'arch',
+ description = 'Architectures we can target',
+ allowed = {{'x86_64'}, {'arm64'}, {'wasm'}}
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1918a24..114b776 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,20 +1,37 @@
name: rive
-description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
version: 0.9.1
-repository: https://github.com/rive-app/rive-flutter
homepage: https://rive.app
-
+description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
+repository: https://github.com/rive-app/rive-flutter
environment:
sdk: ">=2.12.0 <3.0.0"
-
+ flutter: ">=2.5.0"
dependencies:
collection: ^1.15.0
- flutter:
- sdk: flutter
+ ffi: ^2.0.1
graphs: ^2.0.0
http: ^0.13.3
meta: ^1.3.0
-
+ plugin_platform_interface: ^2.0.2
+ flutter:
+ sdk: flutter
+ flutter_web_plugins:
+ sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
+flutter:
+ plugin:
+ platforms:
+ android:
+ package: app.rive.rive
+ pluginClass: RivePlugin
+ macos:
+ pluginClass: RivePlugin
+ windows:
+ pluginClass: RivePlugin
+ ios:
+ pluginClass: RivePlugin
+ web:
+ pluginClass: RivePlugin
+ fileName: rive_web.dart
diff --git a/shared_lib/build_shared.sh b/shared_lib/build_shared.sh
new file mode 100755
index 0000000..3a5d527
--- /dev/null
+++ b/shared_lib/build_shared.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+set -e
+
+CONFIG=debug
+SINGLE=
+for var in "$@"; do
+ if [[ $var = "release" ]]; then
+ CONFIG=release
+ fi
+done
+
+unameOut="$(uname -s)"
+case "${unameOut}" in
+Linux*) MACHINE=linux ;;
+Darwin*) MACHINE=mac ;;
+CYGWIN*) MACHINE=cygwin ;;
+MINGW*) MACHINE=mingw ;;
+*) MACHINE="UNKNOWN:${unameOut}" ;;
+esac
+
+pushd ../
+./update_dependencies.sh
+popd
+
+if [[ ! -f "bin/premake5" ]]; then
+ mkdir -p bin
+ pushd bin
+ echo Downloading Premake5
+ if [ "$MACHINE" = 'mac' ]; then
+ PREMAKE_URL=https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-macosx.tar.gz
+ else
+ PREMAKE_URL=https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz
+ fi
+ curl $PREMAKE_URL -L -o premake.tar.gz
+ # Export premake5 into bin
+ tar -xvf premake.tar.gz 2>/dev/null
+ # Delete downloaded archive
+ rm premake.tar.gz
+ popd
+fi
+
+export PREMAKE=bin/premake5
+
+$PREMAKE --scripts=../macos/rive-cpp/build --file=../premake5_rive_plugin.lua gmake2 --arch=x86_64
+
+cd ..
+for var in "$@"; do
+ if [[ $var = "clean" ]]; then
+ make clean
+ make config=release clean
+ fi
+done
+
+make config=$CONFIG -j$(($(sysctl -n hw.physicalcpu) + 1))
+if [ "$MACHINE" = 'mac' ]; then
+ du -hs shared_lib/build/bin/debug/librive_text.dylib
+else
+ du -hs shared_lib/build/bin/debug/librive_text.so
+fi
diff --git a/test/assets/RobotoFlex.ttf b/test/assets/RobotoFlex.ttf
new file mode 100644
index 0000000..4cf1ecb
Binary files /dev/null and b/test/assets/RobotoFlex.ttf differ
diff --git a/test/text_test.dart b/test/text_test.dart
new file mode 100644
index 0000000..4e551ec
--- /dev/null
+++ b/test/text_test.dart
@@ -0,0 +1,44 @@
+import 'package:flutter_test/flutter_test.dart';
+// ignore: implementation_imports
+import 'package:rive/src/rive_text.dart';
+import 'src/utils.dart';
+
+void main() {
+ test('text shaping works', () async {
+ await Font.initialize();
+ final bytes = loadFile('assets/RobotoFlex.ttf');
+ expect(bytes.lengthInBytes, 1654412);
+
+ var roboto = Font.decode(bytes.buffer.asUint8List());
+ expect(roboto, isNotNull);
+
+ var text = 'ffi test';
+
+ var runs = [
+ TextRun(
+ font: roboto!,
+ fontSize: 32.0,
+ unicharCount: text.length,
+ styleId: 0,
+ )
+ ];
+
+ var result = runs.first.font.shape(text, runs);
+ expect(result.paragraphs.length, 1);
+ expect(result.paragraphs.first.runs.length, 1);
+ var glyphRun = result.paragraphs.first.runs.first;
+
+ // ffi gets ligated as a single glyph
+ expect(glyphRun.glyphCount, 6);
+ expect(glyphRun.textIndexAt(0), 0); // ffi
+ expect(glyphRun.textIndexAt(1), 3); // space after ffi
+
+ var breakLinesResult = result.breakLines(60, TextAlign.left);
+ expect(breakLinesResult.length, 1); // 1 paragraph
+ expect(breakLinesResult.first.length, 2); // 2 lines in the paragraph
+ // first line shows first glyph on the left
+ expect(breakLinesResult.first.first.startIndex, 0);
+ // second line shows third glyph on the left, which is the start of 'test'
+ expect(breakLinesResult.first[1].startIndex, 2);
+ });
+}
diff --git a/update_dependencies.sh b/update_dependencies.sh
new file mode 100755
index 0000000..059e171
--- /dev/null
+++ b/update_dependencies.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+set -e
+
+# silence push/pop
+pushd() {
+ command pushd "$@" >/dev/null
+}
+
+popd() {
+ command popd "$@" >/dev/null
+}
+
+FORCE=false
+for var in "$@"; do
+ if [[ $var = "force" ]]; then
+ FORCE=true
+ fi
+done
+
+function installRiveCpp {
+ if [ $FORCE == "true" ] || [ ! -d rive-cpp ]; then
+ rm -fR rive-cpp
+ if [ -d ../../runtime ]; then
+ echo "Getting rive-cpp from current repo."
+ export INSTALL_TO=$PWD
+ mkdir -p rive-cpp
+ # cp -fR ../../runtime rive-cpp
+ # git clone machine1:/path/to/project machine2:/target/path
+ pushd ../../runtime
+
+ function copyRepoFile {
+ mkdir -p $(dirname $INSTALL_TO/rive-cpp/$1)
+ cp $1 $INSTALL_TO/rive-cpp/$1
+ echo -en "\r\033[K$1"
+ }
+ export -f copyRepoFile
+ git ls-files | xargs -n1 bash -c 'copyRepoFile "$@"' _
+ echo -en "\r\033[K"
+ popd
+ else
+ echo "Cloning rive-cpp."
+ git clone https://github.com/rive-app/rive-cpp
+ fi
+ # TODO: Fix this so we build the rive.podspec file based on paths determined
+ # here (for harfbuzz and sheenbidi)
+ #
+ # install dependencies from rive-cpp
+ # pushd rive-cpp/dependencies/macosx
+ # source config_directories.sh
+ # popd
+ # pushd rive-cpp
+ # ./build.sh
+ # popd
+ fi
+
+ # For now just manually install the deps.
+ if [ $FORCE == "true" ] || [ ! -d harfbuzz ]; then
+ rm -fR harfbuzz
+ echo "Cloning Harfbuzz."
+ git clone https://github.com/harfbuzz/harfbuzz
+ pushd harfbuzz
+ git checkout 858570b1d9912a1b746ab39fbe62a646c4f7a5b1 .
+ popd
+ fi
+ if [ $FORCE == "true" ] || [ ! -d SheenBidi ]; then
+ rm -fR SheenBidi
+ echo "Cloning Harfbuzz."
+ git clone https://github.com/Tehreer/SheenBidi.git
+ pushd SheenBidi
+ git checkout v2.6 .
+ popd
+ fi
+}
+
+pushd macos
+installRiveCpp
+popd
+
+pushd ios
+installRiveCpp
+popd
diff --git a/wasm/.npmrc b/wasm/.npmrc
new file mode 100644
index 0000000..9555e17
--- /dev/null
+++ b/wasm/.npmrc
@@ -0,0 +1 @@
+tag-version-prefix=""
diff --git a/wasm/README.md b/wasm/README.md
new file mode 100644
index 0000000..7b957df
--- /dev/null
+++ b/wasm/README.md
@@ -0,0 +1,30 @@
+# Rive Flutter WASM
+
+This folder contains the WASM portion of the Rive Flutter runtime.
+
+## Delivered via Unpkg
+
+This is published to NPM so that Flutter Web projects can all benefit from common caching of the WASM file via unpkg (similar to Flutter's same strategy for CanvasKit).
+
+## Local Development
+
+For local development the Flutter Runtime can be configured to use a local dev server. Steps are as follows:
+
+```
+cd wasm
+npm run serve
+```
+
+Run any project depending on rive-flutter with a `--dart-define` argument to instruct the runtime to look for the local dev server.
+
+```
+flutter run --dart-define=LOCAL_RIVE_FLUTTER_WASM=true -d chrome
+```
+
+As you make changes to the C++ codebase, recompile with:
+
+```
+./build_wasm.sh
+```
+
+Refresh or Hot Restart the Flutter Web project.
diff --git a/wasm/build/bin/release/rive_text.js b/wasm/build/bin/release/rive_text.js
new file mode 100644
index 0000000..599d18d
--- /dev/null
+++ b/wasm/build/bin/release/rive_text.js
@@ -0,0 +1,70 @@
+
+var RiveText = (() => {
+ var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
+ if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;
+ return (
+function(RiveText) {
+ RiveText = RiveText || {};
+
+
+var f;f||(f=typeof RiveText !== 'undefined' ? RiveText : {});var aa,r;f.ready=new Promise(function(a,b){aa=a;r=b});
+RiveText.onRuntimeInitialized=function(){var a=RiveText.makeGlyphPath;RiveText.makeGlyphPath=function(d,e){d=a(d,e);e=d[1];var h=d[2];h=RiveText.HEAPU8.subarray(h,h+d[3]);let l=0;for(var g of h)switch(g){case 0:case 1:l++;break;case 2:l+=2;break;case 4:l+=3}g=e/4;return{rawPath:d[0],verbs:h,points:RiveText.HEAPF32.subarray(g,g+2*l)}};var b=RiveText.shapeText;RiveText.shapeText=function(d,e){d=b(d,e);return{rawResult:d,results:RiveText.HEAPU8.subarray(d)}};var c=RiveText.breakLines;RiveText.breakLines=
+function(d,e,h){d=c(d,e,h);return{rawResult:d,results:RiveText.HEAPU8.subarray(d)}}};var ba=Object.assign({},f),da="./this.program",ea="object"==typeof window,t="function"==typeof importScripts,fa="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,u="",ha,v,w;
+if(fa){u=t?require("path").dirname(u)+"/":__dirname+"/";var fs,ia;"function"===typeof require&&(fs=require("fs"),ia=require("path"));ha=(a,b)=>{a=ia.normalize(a);return fs.readFileSync(a,b?void 0:"utf8")};w=a=>{a=ha(a,!0);a.buffer||(a=new Uint8Array(a));return a};v=(a,b,c)=>{a=ia.normalize(a);fs.readFile(a,function(d,e){d?c(d):b(e.buffer)})};1{var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},t&&(w=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),
+v=(a,b,c)=>{var d=new XMLHttpRequest;d.open("GET",a,!0);d.responseType="arraybuffer";d.onload=()=>{200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var ja=f.print||console.log.bind(console),x=f.printErr||console.warn.bind(console);Object.assign(f,ba);ba=null;f.thisProgram&&(da=f.thisProgram);var y;f.wasmBinary&&(y=f.wasmBinary);var noExitRuntime=f.noExitRuntime||!0;"object"!=typeof WebAssembly&&C("no native wasm support detected");
+var ka,la=!1,ma="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;
+function na(a,b,c){var d=b+c;for(c=b;a[c]&&!(c>=d);)++c;if(16e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}var oa,D,F,G,H,I,J,pa,qa;
+function ra(){var a=ka.buffer;oa=a;f.HEAP8=D=new Int8Array(a);f.HEAP16=G=new Int16Array(a);f.HEAP32=I=new Int32Array(a);f.HEAPU8=F=new Uint8Array(a);f.HEAPU16=H=new Uint16Array(a);f.HEAPU32=J=new Uint32Array(a);f.HEAPF32=pa=new Float32Array(a);f.HEAPF64=qa=new Float64Array(a)}var sa,ta=[],ua=[],va=[];function wa(){var a=f.preRun.shift();ta.unshift(a)}var K=0,xa=null,L=null;
+function C(a){if(f.onAbort)f.onAbort(a);a="Aborted("+a+")";x(a);la=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");r(a);throw a;}function ya(){return M.startsWith("data:application/octet-stream;base64,")}var M;M="rive_text.wasm";if(!ya()){var za=M;M=f.locateFile?f.locateFile(za,u):u+za}function Aa(){var a=M;try{if(a==M&&y)return new Uint8Array(y);if(w)return w(a);throw"both async and sync fetching of the wasm failed";}catch(b){C(b)}}
+function Ba(){if(!y&&(ea||t)){if("function"==typeof fetch&&!M.startsWith("file://"))return fetch(M,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+M+"'";return a.arrayBuffer()}).catch(function(){return Aa()});if(v)return new Promise(function(a,b){v(M,function(c){a(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return Aa()})}function Ca(a){for(;0>2])}var P={},Q={},Ea={};function Fa(a){if(void 0===a)return"_unknown";a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?"_"+a:a}function Ga(a,b){a=Fa(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}
+function Ha(a){var b=Error,c=Ga(a,function(d){this.name=a;this.message=d;d=Error(d).stack;void 0!==d&&(this.stack=this.toString()+"\n"+d.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c}var Ia=void 0;
+function Ja(a,b,c){function d(g){g=c(g);if(g.length!==a.length)throw new Ia("Mismatched type converter count");for(var k=0;k{Q.hasOwnProperty(g)?e[k]=Q[g]:(h.push(g),P.hasOwnProperty(g)||(P[g]=[]),P[g].push(()=>{e[k]=Q[g];++l;l===h.length&&d(e)}))});0===h.length&&d(e)}
+function Ka(a){switch(a){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+a);}}var La=void 0;function T(a){for(var b="";F[a];)b+=La[F[a++]];return b}var Ma=void 0;function U(a){throw new Ma(a);}
+function R(a,b,c={}){if(!("argPackAdvance"in b))throw new TypeError("registerType registeredInstance requires argPackAdvance");var d=b.name;a||U('type "'+d+'" must have a positive integer typeid pointer');if(Q.hasOwnProperty(a)){if(c.T)return;U("Cannot register type '"+d+"' twice")}Q[a]=b;delete Ea[a];P.hasOwnProperty(a)&&(b=P[a],delete P[a],b.forEach(e=>e()))}var Na=[],V=[{},{value:void 0},{value:null},{value:!0},{value:!1}];function Oa(a){4{a||U("Cannot use deleted val. handle = "+a);return V[a].value},X=a=>{switch(a){case void 0:return 1;case null:return 2;case !0:return 3;case !1:return 4;default:var b=Na.length?Na.pop():V.length;V[b]={N:1,value:a};return b}};function Pa(a,b){switch(b){case 2:return function(c){return this.fromWireType(pa[c>>2])};case 3:return function(c){return this.fromWireType(qa[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}
+function Qa(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=Ga(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
+function Ra(a,b){var c=f;if(void 0===c[a].L){var d=c[a];c[a]=function(){c[a].L.hasOwnProperty(arguments.length)||U("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+c[a].L+")!");return c[a].L[arguments.length].apply(this,arguments)};c[a].L=[];c[a].L[d.O]=d}}
+function Sa(a,b,c){f.hasOwnProperty(a)?((void 0===c||void 0!==f[a].L&&void 0!==f[a].L[c])&&U("Cannot register public name '"+a+"' twice"),Ra(a,a),f.hasOwnProperty(c)&&U("Cannot register multiple overloads of a function with the same number of arguments ("+c+")!"),f[a].L[c]=b):(f[a]=b,void 0!==c&&(f[a].$=c))}function Ta(a,b){for(var c=[],d=0;d>2]);return c}
+function Ua(a,b){var c=[];return function(){c.length=0;Object.assign(c,arguments);if(a.includes("j")){var d=f["dynCall_"+a];d=c&&c.length?d.apply(null,[b].concat(c)):d.call(null,b)}else d=sa.get(b).apply(null,c);return d}}function Y(a,b){a=T(a);var c=a.includes("j")?Ua(a,b):sa.get(b);"function"!=typeof c&&U("unknown function pointer with signature "+a+": "+b);return c}var Va=void 0;function Wa(a){a=Xa(a);var b=T(a);Z(a);return b}
+function Ya(a,b){function c(h){e[h]||Q[h]||(Ea[h]?Ea[h].forEach(c):(d.push(h),e[h]=!0))}var d=[],e={};b.forEach(c);throw new Va(a+": "+d.map(Wa).join([", "]));}function Za(a,b,c){switch(b){case 0:return c?function(d){return D[d]}:function(d){return F[d]};case 1:return c?function(d){return G[d>>1]}:function(d){return H[d>>1]};case 2:return c?function(d){return I[d>>2]}:function(d){return J[d>>2]};default:throw new TypeError("Unknown integer type: "+a);}}
+var $a="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0;function ab(a,b){var c=a>>1;for(var d=c+b/2;!(c>=d)&&H[c];)++c;c<<=1;if(32=b/2);++d){var e=G[a+2*d>>1];if(0==e)break;c+=String.fromCharCode(e)}return c}function bb(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var e=0;e>1]=a.charCodeAt(e),b+=2;G[b>>1]=0;return b-d}function cb(a){return 2*a.length}
+function db(a,b){for(var c=0,d="";!(c>=b/4);){var e=I[a+4*c>>2];if(0==e)break;++c;65536<=e?(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023)):d+=String.fromCharCode(e)}return d}function eb(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var d=b;c=d+c-4;for(var e=0;e=h){var l=a.charCodeAt(++e);h=65536+((h&1023)<<10)|l&1023}I[b>>2]=h;b+=4;if(b+4>c)break}I[b>>2]=0;return b-d}
+function fb(a){for(var b=0,c=0;c=d&&++c;b+=4}return b}function gb(a,b){var c=Q[a];void 0===c&&U(b+" has unknown type "+Wa(a));return c}var hb={};function ib(a){var b=hb[a];return void 0===b?T(a):b}var jb=[];function kb(a){var b=jb.length;jb.push(a);return b}function lb(a,b){for(var c=Array(a),d=0;d>2],"parameter "+d);return c}var mb=[],nb={};
+function ob(){if(!pb){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:da||"./this.program"},b;for(b in nb)void 0===nb[b]?delete a[b]:a[b]=nb[b];var c=[];for(b in a)c.push(b+"="+a[b]);pb=c}return pb}var pb,qb=[null,[],[]];Ia=f.InternalError=Ha("InternalError");for(var rb=Array(256),sb=0;256>sb;++sb)rb[sb]=String.fromCharCode(sb);La=rb;Ma=f.BindingError=Ha("BindingError");
+f.count_emval_handles=function(){for(var a=0,b=5;b{var n=g[m],q=k.P,z=k.R,A=g[m+d],p=k.X,ca=k.Z;k.read=B=>n.fromWireType(q(z,B));k.write=(B,E)=>{var S=[];p(ca,B,A.toWireType(S,E));Da(S)}});return[{name:b.name,fromWireType:function(k){for(var m=Array(d),n=0;n>
+h])},M:null})},x:function(a,b){b=T(b);R(a,{name:b,fromWireType:function(c){var d=W(c);Oa(c);return d},toWireType:function(c,d){return X(d)},argPackAdvance:8,readValueFromPointer:O,M:null})},k:function(a,b,c){c=Ka(c);b=T(b);R(a,{name:b,fromWireType:function(d){return d},toWireType:function(d,e){return e},argPackAdvance:8,readValueFromPointer:Pa(b,c),M:null})},e:function(a,b,c,d,e,h){var l=Ta(b,c);a=T(a);e=Y(d,e);Sa(a,function(){Ya("Cannot call "+a+" due to unbound types",l)},b-1);Ja([],l,function(g){var k=
+[g[0],null].concat(g.slice(1)),m=g=a,n=e,q=k.length;2>q&&U("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var z=null!==k[1]&&!1,A=!1,p=1;pg;if(0===d){var l=32-8*c;h=g=>g<>>l}c=b.includes("unsigned")?function(g,k){return k>>>0}:function(g,k){return k};R(a,{name:b,fromWireType:h,toWireType:c,argPackAdvance:8,readValueFromPointer:Za(b,e,0!==d),M:null})},a:function(a,b,c){function d(h){h>>=2;var l=J;return new e(oa,l[h+1],l[h])}var e=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=T(c);R(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{T:!0})},l:function(a,
+b){b=T(b);var c="std::string"===b;R(a,{name:b,fromWireType:function(d){var e=J[d>>2],h=d+4;if(c)for(var l=h,g=0;g<=e;++g){var k=h+g;if(g==e||0==F[k]){l=l?na(F,l,k-l):"";if(void 0===m)var m=l;else m+=String.fromCharCode(0),m+=l;l=k+1}}else{m=Array(e);for(g=0;g=k?g++:2047>=k?g+=2:55296<=k&&57343>=k?(g+=4,++h):g+=3}else g=e.length;h=g;g=tb(4+h+1);k=g+4;J[g>>2]=h;if(c&&l){if(l=k,k=h+1,h=F,0