├── .gitattributes
├── .gitignore
├── CONTRIBUTING.md
├── Demo
├── build.gradle
├── sampledata
│ ├── colors
│ ├── pen_brushes
│ └── tools.json
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── myscript
│ │ ├── certificate
│ │ └── MyCertificate.java
│ │ └── iink
│ │ └── demo
│ │ ├── IInkApplication.kt
│ │ ├── MainActivity.kt
│ │ ├── data
│ │ ├── ContentRepository.kt
│ │ ├── IContentRepository.kt
│ │ └── ToolRepository.kt
│ │ ├── di
│ │ └── DemoModule.kt
│ │ ├── domain
│ │ ├── ConfigurationProfile.kt
│ │ └── PartEditor.kt
│ │ ├── ui
│ │ ├── ColorsAdapter.kt
│ │ ├── EditorViewModel.kt
│ │ ├── ThicknessesAdapter.kt
│ │ ├── ToolbarTransformer.kt
│ │ └── ToolsAdapter.kt
│ │ └── util
│ │ ├── Dialogs.kt
│ │ ├── autoCloseable.kt
│ │ └── viewExt.kt
│ └── res
│ ├── drawable
│ ├── bottom_sheet_handle.xml
│ ├── disc.xml
│ ├── ic_add.xml
│ ├── ic_brush_outlined.xml
│ ├── ic_chevron_left.xml
│ ├── ic_chevron_right.xml
│ ├── ic_colorize_outlined.xml
│ ├── ic_delete.xml
│ ├── ic_eraser.xml
│ ├── ic_hand_outlined.xml
│ ├── ic_lasso.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_pen_outlined.xml
│ ├── ic_redo.xml
│ ├── ic_reset_view.xml
│ ├── ic_undo.xml
│ ├── ic_zoom_in.xml
│ ├── ic_zoom_out.xml
│ ├── texture_background.png
│ ├── texture_stamp.png
│ ├── tool_background.xml
│ └── tool_background_selected.xml
│ ├── font
│ ├── myscriptinter.xml
│ ├── myscriptinter_bold.otf
│ ├── myscriptinter_regular.otf
│ ├── stix.xml
│ ├── stix_italic.otf
│ └── stix_regular.otf
│ ├── layout
│ ├── editor_prediction_layout.xml
│ ├── editor_text_input_layout.xml
│ ├── editor_toolbar.xml
│ ├── main_activity.xml
│ ├── toolbar_color_cell.xml
│ ├── toolbar_pen_brush_row.xml
│ ├── toolbar_settings_sheet_layout.xml
│ ├── toolbar_thickness_cell.xml
│ └── toolbar_tool_cell.xml
│ ├── menu
│ └── main_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── raw
│ └── theme.css
│ ├── values-large
│ └── dimens.xml
│ ├── values-night
│ ├── colors.xml
│ └── themes.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ └── file_paths.xml
├── GetStarted
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── myscript
│ │ ├── certificate
│ │ └── MyCertificate.java
│ │ └── iink
│ │ └── getstarted
│ │ ├── ErrorActivity.java
│ │ ├── IInkApplication.java
│ │ └── MainActivity.java
│ └── res
│ ├── color
│ ├── button_text_color.xml
│ └── editor_icon_color.xml
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── button_background.xml
│ ├── ic_delete.xml
│ ├── ic_redo.xml
│ └── ic_undo.xml
│ ├── layout
│ ├── error_activity.xml
│ └── main_activity.xml
│ ├── menu
│ └── main_activity_menu.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
├── LICENSE
├── LICENSES
├── androidSupportLib.txt
├── inter.txt
├── kotlin.txt
├── materialDesign.txt
└── stix.pdf
├── README.md
├── UIReferenceImplementation
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── myscript
│ │ └── iink
│ │ └── uireferenceimplementation
│ │ ├── Canvas.java
│ │ ├── ContextualActions.java
│ │ ├── ContextualActionsHelper.java
│ │ ├── CustomTextSpan.java
│ │ ├── EditorBinding.java
│ │ ├── EditorData.java
│ │ ├── EditorView.java
│ │ ├── FontMetricsProvider.java
│ │ ├── FontUtils.java
│ │ ├── FrameTimeEstimator.java
│ │ ├── IInputControllerListener.java
│ │ ├── ISizeListener.java
│ │ ├── ImageLoader.java
│ │ ├── ImagePainter.java
│ │ ├── InputController.java
│ │ ├── JiixDefinitions.java
│ │ ├── LayerView.java
│ │ ├── OfflineSurfaceManager.java
│ │ ├── Path.java
│ │ └── SmartGuideView.java
│ └── res
│ ├── drawable
│ ├── ic_smart_guide_more.xml
│ └── smart_guide_bottom_border.xml
│ ├── layout
│ ├── editor_view.xml
│ └── smart_guide_layout.xml
│ └── values
│ ├── colors.xml
│ └── dimens.xml
├── build.gradle
├── configurations
├── Raw Content
│ ├── drawing.json
│ └── text_math_shape.json
└── interactivity.json
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | # git
4 | .gitignore text
5 |
6 | # source & misc files
7 | *.c text diff=cpp whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,no-newline-at-eof
8 | *.cpp text diff=cpp whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,no-newline-at-eof
9 | *.cs text diff=csharp whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
10 | *.h text diff=cpp whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,no-newline-at-eof
11 | *.hpp text diff=cpp whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,no-newline-at-eof
12 | *.html text diff=html whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
13 | *.java text diff=java whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
14 | *.js text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
15 | *.l text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
16 | *.m text diff=objc whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,no-newline-at-eof
17 | *.mm text diff=objc whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,no-newline-at-eof
18 | *.pl text diff=perl whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
19 | *.py text diff=python whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
20 | *.rb text diff=ruby whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
21 | *.tex text diff=text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
22 | *.txt text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
23 | *.xml text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
24 | *.y text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
25 | *.tsv text whitespace=-blank-at-eol,blank-at-eof,-space-before-tab,-tab-in-indent
26 | *.groovy text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
27 |
28 | # Shell scripts
29 | *.sh diff=bash text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=lf executable=maybe
30 | *.bat text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=crlf executable=maybe
31 | *.ps1 text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=crlf executable=maybe
32 |
33 | # Awk scripts
34 | *.awk text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=lf
35 |
36 | # GNU Makefile
37 | Makefile text whitespace=blank-at-eol,blank-at-eof,space-before-tab eol=lf
38 |
39 | # Android
40 | *.mk text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=lf
41 | *.gradle text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent
42 | gradlew text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=lf executable
43 | gradlew.bat text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent eol=crlf executable=maybe
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | build/
3 |
4 | # gradle & android specific files
5 | .gradle/
6 | *.iml
7 | .idea
8 | local.properties
9 |
10 | /Demo/src/main/assets
11 | /Demo/tmp-assets
12 | /GetStarted/src/main/assets
13 | /GetStarted/tmp-assets
14 |
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We gladly welcome pull requests. If you have any questions, or want help solving a problem, feel free to stop by the [#MyScript forum](https://developer.myscript.com/support/).
4 |
5 | ## License
6 |
7 | Those examples are licensed under the [Apache 2.0](https://opensource.org/license/apache-2-0).
8 |
--------------------------------------------------------------------------------
/Demo/build.gradle:
--------------------------------------------------------------------------------
1 | import org.apache.commons.io.FileUtils
2 | import org.apache.commons.io.filefilter.FileFilterUtils
3 |
4 | plugins {
5 | id 'com.android.application'
6 | id 'kotlin-android'
7 | }
8 |
9 | android {
10 | namespace 'com.myscript.iink.demo'
11 |
12 | compileSdk project.ext.compileSdk
13 |
14 | buildFeatures {
15 | viewBinding true
16 | buildConfig true
17 | }
18 |
19 | defaultConfig {
20 | minSdk project.ext.minSdk
21 | targetSdk project.ext.targetSdk
22 |
23 | applicationId 'com.myscript.iink.demo'
24 | versionCode 4100
25 | versionName '4.1.0'
26 |
27 | vectorDrawables.useSupportLibrary true
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}"
33 | implementation 'androidx.core:core-ktx:1.13.1'
34 | implementation 'androidx.activity:activity-ktx:1.9.2'
35 | implementation 'androidx.documentfile:documentfile:1.0.1'
36 | implementation 'androidx.cardview:cardview:1.0.0'
37 | implementation 'androidx.preference:preference-ktx:1.2.1'
38 | implementation 'com.google.android.material:material:1.12.0'
39 |
40 | def lifecycle_version = '2.8.6'
41 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
42 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
43 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
44 |
45 | implementation project(':UIReferenceImplementation')
46 | }
47 |
48 | task DownloadAndExtractAssets(type: Copy) {
49 | def sourceUrls = ['https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-diagram.zip',
50 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-raw-content.zip',
51 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-raw-content2.zip',
52 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-math.zip',
53 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-math2.zip',
54 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-text-en_US.zip']
55 | def targetDir = new File(projectDir, "src/main/assets/")
56 | def diagramConf = new File(targetDir, "conf/diagram.conf")
57 | def rawContentConf = new File(targetDir, "conf/raw-content.conf")
58 | def mathConf = new File(targetDir, "conf/math.conf")
59 | def textConf = new File(targetDir, "conf/en_US.conf")
60 | def rawContent2Conf = new File(targetDir, "conf/raw-content2.conf")
61 | def math2Conf = new File(targetDir, "conf/math2.conf")
62 |
63 | if (!diagramConf.exists() || !rawContentConf.exists() || !mathConf.exists() || !textConf.exists() || !rawContent2Conf.exists() || !math2Conf.exists()) {
64 | def tmpAssetsDir = new File(projectDir, "tmp-assets/")
65 | def zipDir = new File(tmpAssetsDir, "zips")
66 |
67 | if (!tmpAssetsDir.isDirectory())
68 | tmpAssetsDir.mkdirs()
69 |
70 | if (!zipDir.isDirectory())
71 | zipDir.mkdirs()
72 |
73 | sourceUrls.each { sourceUrl ->
74 | ant.get(src: sourceUrl, dest: zipDir.getPath())
75 | }
76 |
77 | File[] zipFiles = FileUtils.listFiles(zipDir, FileFilterUtils.suffixFileFilter("zip"), FileFilterUtils.trueFileFilter())
78 | zipFiles.each { File zipFile ->
79 | from zipTree(zipFile)
80 | into tmpAssetsDir
81 | }
82 | }
83 | }
84 |
85 | task CopyAssets(type: Copy, dependsOn: DownloadAndExtractAssets) {
86 | def targetDir = new File(projectDir, "src/main/assets/")
87 | def diagramConf = new File(targetDir, "conf/diagram.conf")
88 | def rawContentConf = new File(targetDir, "conf/raw-content.conf")
89 | def mathConf = new File(targetDir, "conf/math.conf")
90 | def textConf = new File(targetDir, "conf/en_US.conf")
91 | def rawContent2Conf = new File(targetDir, "conf/raw-content2.conf")
92 | def math2Conf = new File(targetDir, "conf/math2.conf")
93 |
94 | if (!diagramConf.exists() || !rawContentConf.exists() || !mathConf.exists() || !textConf.exists() || !rawContent2Conf.exists() || !math2Conf.exists()) {
95 | def tmpAssetsDir = new File(projectDir, "tmp-assets/")
96 |
97 | if (!tmpAssetsDir.isDirectory())
98 | tmpAssetsDir.mkdirs()
99 |
100 | def recognitionAssetDir = new File(tmpAssetsDir, "recognition-assets/")
101 |
102 | println "Copying downloaded assets from $recognitionAssetDir to $targetDir"
103 | from recognitionAssetDir
104 | into targetDir
105 |
106 | doLast {
107 | tmpAssetsDir.deleteDir()
108 | }
109 | }
110 | }
111 |
112 | task CopyConfigurations(type: Copy) {
113 | from "${projectDir}/../configurations"
114 | into "${projectDir}/src/main/assets/parts"
115 | include(
116 | "**/*.json"
117 | )
118 | }
119 |
120 | preBuild.dependsOn(CopyAssets, CopyConfigurations)
121 |
--------------------------------------------------------------------------------
/Demo/sampledata/colors:
--------------------------------------------------------------------------------
1 | #000000
2 | #FBBC05
3 | #EA4335
4 | #34A853
5 | #4285F4
--------------------------------------------------------------------------------
/Demo/sampledata/pen_brushes:
--------------------------------------------------------------------------------
1 | Felt Pen
2 | Fountain Pen
3 | Calligraphic Brush
--------------------------------------------------------------------------------
/Demo/sampledata/tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "tools": [
3 | { "image" : "@drawable/ic_hand_outlined" },
4 | { "image" : "@drawable/ic_pen_outlined" },
5 | { "image" : "@drawable/ic_brush_outlined" },
6 | { "image" : "@drawable/ic_eraser" },
7 | { "image" : "@drawable/ic_lasso" },
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/Demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/certificate/MyCertificate.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.certificate;
4 |
5 | public final class MyCertificate
6 | {
7 | public static final byte[] getBytes()
8 | {
9 | throw new RuntimeException("Please replace the content of MyCertificate.java with the certificate you received from the developer portal");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/IInkApplication.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo
4 |
5 | import android.app.Application
6 | import com.myscript.iink.demo.di.DemoModule
7 |
8 | class IInkApplication : Application() {
9 |
10 | companion object {
11 | lateinit var DemoModule: DemoModule
12 | }
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | DemoModule = DemoModule(this)
17 | }
18 |
19 | override fun onTerminate() {
20 | DemoModule.close()
21 | super.onTerminate()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/data/ContentRepository.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.data
4 |
5 | import android.content.SharedPreferences
6 | import android.content.res.AssetManager
7 | import androidx.core.content.edit
8 | import com.myscript.iink.ContentPart
9 | import com.myscript.iink.Engine
10 | import com.myscript.iink.demo.domain.PartType
11 | import com.myscript.iink.demo.domain.getConfigurationProfile
12 | import com.myscript.iink.demo.domain.setConfigurationProfile
13 | import java.io.File
14 | import java.io.FileNotFoundException
15 | import java.io.Reader
16 |
17 | class ContentRepository(
18 | private val rootDir: File,
19 | private val engine: Engine?,
20 | private val preferences: SharedPreferences,
21 | private val assetManager: AssetManager? = null
22 | ) : IContentRepository {
23 |
24 | init {
25 | rootDir.mkdirs()
26 | }
27 |
28 | override val allParts: List
29 | get() = preferences.getStringSet(ALL_PARTS_TYPE_KEY, null)
30 | ?.toList()
31 | ?.sorted()
32 | ?: emptyList()
33 |
34 | private fun contentFile(contentId: String): File = File(rootDir, "$contentId.iink")
35 |
36 | private fun savePartId(contentId: String) {
37 | val ids = preferences.getStringSet(ALL_PARTS_TYPE_KEY, null)?.toMutableSet() ?: mutableSetOf()
38 | ids.add(contentId)
39 | preferences.edit {
40 | putStringSet(ALL_PARTS_TYPE_KEY, ids)
41 | }
42 | }
43 |
44 | private fun removePartId(contentId: String) {
45 | val ids = preferences.getStringSet(ALL_PARTS_TYPE_KEY, null)?.toMutableSet() ?: mutableSetOf()
46 | ids.remove(contentId)
47 | preferences.edit {
48 | putStringSet(ALL_PARTS_TYPE_KEY, ids)
49 | }
50 | }
51 |
52 | override fun getPart(contentId: String): ContentPart {
53 | val engine = checkNotNull(engine) { "Cannot get part without valid engine" }
54 |
55 | engine.openPackage(contentFile(contentId)).use { contentPackage ->
56 | return contentPackage.getPart(0)
57 | }
58 | }
59 |
60 | override fun copyPart(contentId: String, outputDir: File): File {
61 | val engine = checkNotNull(engine) { "Cannot copy part without valid engine" }
62 |
63 | val outputFile = File(outputDir, "${contentId}_${System.currentTimeMillis()}.iink")
64 | engine.createPackage(outputFile).use { targetPackage ->
65 | getPart(contentId).use { sourcePart ->
66 | // enforce a save in case such part is being edited at the moment
67 | sourcePart.getPackage().save()
68 | targetPackage.clonePart(sourcePart).also(ContentPart::close)
69 | }
70 | targetPackage.save()
71 | }
72 | return outputFile
73 | }
74 |
75 | override fun createPart(partType: PartType): String {
76 | val engine = checkNotNull(engine) { "Cannot create part without valid engine" }
77 |
78 | // Here we use a human readable name to ease readability in Demo UI.
79 | // Ideally, application should use *stable* naming such as a UUID and link such file path to
80 | // an application data layer (such a Room database) to deal with part metadata (title, type,
81 | // language, last modification date or anything linked to application logic).
82 | var index = allParts.size
83 | var contentId: String
84 | do {
85 | ++index
86 | contentId = "Part$index"
87 | } while (allParts.contains(contentId))
88 | engine.createPackage(contentFile(contentId)).use { contentPackage ->
89 | contentPackage.createPart(partType.iinkPartType).use { part ->
90 | part.setConfigurationProfile(partType.configurationProfile)
91 | }
92 | // TODO async
93 | contentPackage.save()
94 | }
95 | savePartId(contentId)
96 | return contentId
97 | }
98 |
99 | override fun deletePart(contentId: String) {
100 | removePartId(contentId)
101 | contentFile(contentId).delete()
102 | }
103 |
104 | override fun hasPart(contentId: String): Boolean {
105 | return contentFile(contentId).exists()
106 | }
107 |
108 | override fun savePart(contentId: String) {
109 | engine?.openPackage(contentFile(contentId))?.use { contentPackage ->
110 | contentPackage.save()
111 | }
112 | }
113 |
114 | override fun requestPartTypes(): List {
115 | val configurationProfiles = mutableListOf()
116 |
117 | engine?.supportedPartTypes?.forEach { partName ->
118 | if (partName != null) {
119 | configurationProfiles.add(PartType(partName))
120 |
121 | val availableProfiles = assetManager?.list(File(CONFIGURATION_PROFILE_DIRECTORY, partName).path)
122 | if (!availableProfiles.isNullOrEmpty()) {
123 | availableProfiles.forEach { profile ->
124 | configurationProfiles.add(PartType(partName, File(profile).nameWithoutExtension))
125 | }
126 | }
127 | }
128 | }
129 | return configurationProfiles
130 | }
131 |
132 | override fun getConfiguration(contentPart: ContentPart): String? {
133 | val assetManager = assetManager ?: return null
134 |
135 | val defaultFile = File(CONFIGURATION_PROFILE_DIRECTORY, DEFAULT_RAW_CONTENT_CONFIGURATION_FILE_NAME)
136 |
137 | val configurationProfile = contentPart.getConfigurationProfile()
138 | val configurationFile = if (configurationProfile != null) {
139 | val parent = File(CONFIGURATION_PROFILE_DIRECTORY, contentPart.type.toString())
140 | val confFile = assetManager.list(parent.path)?.firstOrNull {
141 | File(it).nameWithoutExtension == configurationProfile
142 | }
143 | if (confFile != null) {
144 | File(parent, confFile)
145 | } else {
146 | defaultFile
147 | }
148 | } else {
149 | defaultFile
150 | }
151 |
152 | return try {
153 | assetManager.open(configurationFile.path).bufferedReader().use(Reader::readText)
154 | } catch (e: FileNotFoundException) {
155 | null
156 | }
157 | }
158 |
159 | override fun importContent(file: File): List {
160 | val engine = checkNotNull(engine) { "Cannot import content without valid engine" }
161 | require(file.exists()) { "File '$file' does not exist" }
162 | val ids = mutableListOf()
163 | engine.openPackage(file).use { sourcePackage ->
164 | for (partIndex in 0 until sourcePackage.partCount) {
165 | sourcePackage.getPart(partIndex).use { sourcePart ->
166 | // See `createPart` for part id definition, here made simple for Demo purpose
167 | val contentId = "Part${System.currentTimeMillis()}"
168 | engine.createPackage(contentFile(contentId)).use { targetPackage ->
169 | targetPackage.clonePart(sourcePart).also(ContentPart::close)
170 | targetPackage.save()
171 | }
172 | savePartId(contentId)
173 | ids += contentId
174 | }
175 | }
176 | }
177 | return ids
178 | }
179 |
180 | override var lastOpenedPartId: String?
181 | get() = preferences.getString(LAST_PART_ID_KEY, null)
182 | set(value) {
183 | preferences.edit {
184 | putString(LAST_PART_ID_KEY, value)
185 | }
186 | }
187 |
188 | override var lastChosenPartTypeIndex: Int
189 | get() = preferences.getInt(LAST_PART_TYPE_INDEX_KEY, -1)
190 | set(value) {
191 | preferences.edit {
192 | putInt(LAST_PART_TYPE_INDEX_KEY, value)
193 | }
194 | }
195 |
196 | companion object {
197 | private const val LAST_PART_ID_KEY = "iink.demo.lastPartId"
198 | private const val LAST_PART_TYPE_INDEX_KEY = "iink.demo.lastPartTypeIndex"
199 | private const val ALL_PARTS_TYPE_KEY = "iink.demo.allparts"
200 |
201 | private const val CONFIGURATION_PROFILE_DIRECTORY = "parts"
202 | private const val DEFAULT_RAW_CONTENT_CONFIGURATION_FILE_NAME = "interactivity.json"
203 | }
204 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/data/IContentRepository.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.data
4 |
5 | import com.myscript.iink.ContentPart
6 | import com.myscript.iink.demo.domain.PartType
7 | import java.io.File
8 |
9 | interface IContentRepository {
10 |
11 | /**
12 | * Returns the identifiers of all the available parts.
13 | *
14 | * @return A list of all the parts' identifiers.
15 | */
16 | val allParts: List
17 |
18 | /**
19 | * Returns the identifier of the newly created part.
20 | * Such identifier can be further reused in other [IContentRepository] APIs.
21 | *
22 | * @param partType the type of the part to create.
23 | * @return the identifier of the newly created part.
24 | */
25 | fun createPart(partType: PartType): String
26 |
27 | /**
28 | * Import all [ContentPart] contained in the given [ContentPackage] file.
29 | * Each individual [ContentPart] from source [ContentPackage] is split in its own [ContentPackage]
30 | * locally.
31 | *
32 | * @param file the content package where to find content part to import.
33 | * @return the list of content id imported (that can be used with [getPart]).
34 | *
35 | * @see ContentPackage
36 | * @see ContentPart
37 | * @see allParts
38 | */
39 | fun importContent(file: File): List
40 |
41 | /**
42 | * Deletes the part file defined by the given identifier.
43 | *
44 | * @param contentId the part identifier of the part to consider.
45 | */
46 | fun deletePart(contentId: String)
47 |
48 | /**
49 | * Tells whether the given part identifier departs an existing part or not.
50 | *
51 | * @param contentId the part identifier of the part to consider.
52 | * @return `true` if a part matching the given identifier exists, `false` otherwise.
53 | */
54 | fun hasPart(contentId: String): Boolean // TODO remove
55 |
56 | /**
57 | * Saves the part on disk.
58 | *
59 | * @param contentId the part identifier of the part to consider.
60 | */
61 | fun savePart(contentId: String)
62 |
63 | /**
64 | * Loads the content part identified by the given content id.
65 | *
66 | * @param contentId the part identifier of the part to consider.
67 | * @return the content part identified by the given content id.
68 | */
69 | fun getPart(contentId: String): ContentPart
70 |
71 | /**
72 | * Copies the content part within a content package file.
73 | *
74 | * @param contentId the part identifier of the part to consider.
75 | * @param outputDir the directory where to copy the part.
76 | * @return the file where the part was copied.
77 | */
78 | fun copyPart(contentId: String, outputDir: File): File
79 |
80 | var lastOpenedPartId: String?
81 |
82 | var lastChosenPartTypeIndex: Int
83 |
84 | fun requestPartTypes(): List
85 |
86 | fun getConfiguration(contentPart: ContentPart): String?
87 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/data/ToolRepository.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.data
4 |
5 | import android.content.SharedPreferences
6 | import android.graphics.Color
7 | import androidx.annotation.ColorInt
8 | import androidx.core.content.edit
9 |
10 | class ToolRepository(private val preferences: SharedPreferences) {
11 |
12 | @ColorInt
13 | fun getToolColor(toolKey: String): Int {
14 | return preferences.getInt("$toolKey-color", Color.TRANSPARENT)
15 | }
16 |
17 | fun saveToolColor(toolKey: String, @ColorInt color: Int) {
18 | preferences.edit {
19 | putInt("$toolKey-color", color)
20 | }
21 | }
22 |
23 | fun getToolThickness(toolKey: String): Float {
24 | return preferences.getFloat("$toolKey-thickness", 0f)
25 | }
26 |
27 | fun saveToolThickness(toolKey: String, thickness: Float) {
28 | preferences.edit {
29 | putFloat("$toolKey-thickness", thickness)
30 | }
31 | }
32 |
33 | fun getPenBrush(toolKey: String): String? {
34 | return preferences.getString("$toolKey-pen-brush", null)
35 | }
36 |
37 | fun savePenBrush(toolKey: String, brush: String) {
38 | preferences.edit {
39 | putString("$toolKey-pen-brush", brush)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/domain/ConfigurationProfile.kt:
--------------------------------------------------------------------------------
1 | package com.myscript.iink.demo.domain
2 |
3 | import com.myscript.iink.ContentPart
4 | import java.io.File
5 |
6 | fun ContentPart.getConfigurationProfile(): String? {
7 | return metadata.getString("configuration-profile", "").takeIf(String::isNotEmpty)
8 | }
9 |
10 | fun ContentPart.setConfigurationProfile(configurationProfile: String?) {
11 | metadata = metadata.apply {
12 | setString("configuration-profile", configurationProfile?.takeUnless(String::isNullOrBlank) ?: "")
13 | }
14 | }
15 |
16 | open class PartType(val iinkPartType: String, val configurationProfile: String? = null) {
17 |
18 | data object Diagram : PartType("Diagram")
19 | data object Math : PartType("Math")
20 | data object RawContent : PartType("Raw Content")
21 | data object Text : PartType("Text")
22 | data object TextDocument : PartType("Text Document")
23 | data object Drawing : PartType("Drawing")
24 |
25 | override fun toString(): String {
26 | return if (configurationProfile.isNullOrEmpty()) {
27 | iinkPartType
28 | } else {
29 | "$iinkPartType ($configurationProfile)"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/ui/ColorsAdapter.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.ui
4 |
5 | import android.content.res.ColorStateList
6 | import android.view.LayoutInflater
7 | import android.view.ViewGroup
8 | import android.widget.ImageView
9 | import androidx.recyclerview.widget.DiffUtil
10 | import androidx.recyclerview.widget.ListAdapter
11 | import androidx.recyclerview.widget.RecyclerView
12 | import com.myscript.iink.demo.R
13 |
14 |
15 | class ColorViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
16 | LayoutInflater.from(parent.context).inflate(R.layout.toolbar_color_cell, parent, false)
17 | ) {
18 | private val colorView: ImageView = itemView.findViewById(R.id.toolbar_color_icon)
19 |
20 | fun bind(state: ColorState, onColorSelected: (ColorState) -> Unit) {
21 | colorView.imageTintList = ColorStateList.valueOf(state.color.opaque)
22 |
23 | itemView.isSelected = state.isSelected
24 | itemView.setOnClickListener { onColorSelected(state) }
25 | }
26 |
27 | fun unbind() {
28 | itemView.setOnClickListener(null)
29 | }
30 | }
31 |
32 | class ColorsAdapter(private val onColorSelected: (ColorState) -> Unit)
33 | : ListAdapter(COLOR_DIFF_UTIL_CALLBACK) {
34 |
35 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {
36 | return ColorViewHolder(parent)
37 | }
38 |
39 | override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
40 | holder.bind(getItem(position), onColorSelected)
41 | }
42 |
43 | override fun onViewRecycled(holder: ColorViewHolder) {
44 | holder.unbind()
45 | }
46 |
47 | override fun submitList(list: List?) {
48 | if (list == null) {
49 | super.submitList(null)
50 | } else {
51 | super.submitList(list.toList()) // force DiffUtil by creating a new list
52 | }
53 | }
54 |
55 | companion object {
56 | private val COLOR_DIFF_UTIL_CALLBACK = object : DiffUtil.ItemCallback() {
57 | override fun areItemsTheSame(oldItem: ColorState, newItem: ColorState): Boolean {
58 | return oldItem.color == newItem.color
59 | }
60 |
61 | override fun areContentsTheSame(oldItem: ColorState, newItem: ColorState): Boolean {
62 | return oldItem == newItem
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/ui/ThicknessesAdapter.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.ui
4 |
5 | import android.view.LayoutInflater
6 | import android.view.ViewGroup
7 | import android.widget.ImageView
8 | import androidx.recyclerview.widget.DiffUtil
9 | import androidx.recyclerview.widget.ListAdapter
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.myscript.iink.demo.R
12 |
13 | private fun ThicknessState.toScale(): Float = when (this.thickness) {
14 | Thickness.THIN -> .25f
15 | Thickness.MEDIUM -> .5f
16 | Thickness.LARGE -> 1f
17 | }
18 |
19 | class ThicknessViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
20 | LayoutInflater.from(parent.context).inflate(R.layout.toolbar_thickness_cell, parent, false)
21 | ) {
22 | private val thicknessView: ImageView = itemView.findViewById(R.id.toolbar_thickness_icon)
23 |
24 | fun bind(state: ThicknessState, onThicknessSelected: (ThicknessState) -> Unit) {
25 | val scale = state.toScale()
26 | thicknessView.scaleX = scale
27 | thicknessView.scaleY = scale
28 |
29 | itemView.isSelected = state.isSelected
30 | itemView.setOnClickListener { onThicknessSelected(state) }
31 | }
32 |
33 | fun unbind() {
34 | itemView.setOnClickListener(null)
35 | }
36 | }
37 |
38 | class ThicknessesAdapter(private val onThicknessSelected: (ThicknessState) -> Unit)
39 | : ListAdapter(THICKNESS_DIFF_UTIL_CALLBACK) {
40 |
41 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThicknessViewHolder {
42 | return ThicknessViewHolder(parent)
43 | }
44 |
45 | override fun onBindViewHolder(holder: ThicknessViewHolder, position: Int) {
46 | holder.bind(getItem(position), onThicknessSelected)
47 | }
48 |
49 | override fun onViewRecycled(holder: ThicknessViewHolder) {
50 | holder.unbind()
51 | }
52 |
53 | override fun submitList(list: List?) {
54 | if (list == null) {
55 | super.submitList(null)
56 | } else {
57 | super.submitList(list.toList()) // force DiffUtil by creating a new list
58 | }
59 | }
60 |
61 | companion object {
62 | private val THICKNESS_DIFF_UTIL_CALLBACK = object : DiffUtil.ItemCallback() {
63 | override fun areItemsTheSame(oldItem: ThicknessState, newItem: ThicknessState): Boolean {
64 | return oldItem.thickness == newItem.thickness
65 | }
66 |
67 | override fun areContentsTheSame(oldItem: ThicknessState, newItem: ThicknessState): Boolean {
68 | return oldItem == newItem
69 | }
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/ui/ToolbarTransformer.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.ui
4 |
5 | import android.graphics.Color
6 | import androidx.annotation.ColorInt
7 | import androidx.core.graphics.ColorUtils
8 | import com.myscript.iink.demo.domain.ToolType
9 | import com.myscript.iink.graphics.Color as IInkColor
10 |
11 | @get:ColorInt
12 | val IInkColor.androidColor: Int
13 | get() = Color.argb(a(), r(), g(), b())
14 |
15 | val Int.iinkColor: IInkColor
16 | get() {
17 | val r = this shr 16 and 0xff
18 | val g = this shr 8 and 0xff
19 | val b = this and 0xff
20 | val a = this shr 24 and 0xff
21 | return IInkColor(r, g, b, a)
22 | }
23 |
24 | @get:ColorInt
25 | val Int.opaque: Int
26 | get() = ColorUtils.setAlphaComponent(this, 0xFF)
27 |
28 | fun ToolType.toToolState(isSelected: Boolean, isEnable: Boolean) = ToolState(this, isSelected, isEnable)
29 |
30 | fun Thickness.toFloat(toolType: ToolType) = when (toolType) {
31 | ToolType.PEN -> when (this) {
32 | Thickness.THIN -> .25f
33 | Thickness.MEDIUM -> .65f
34 | Thickness.LARGE -> 1.65f
35 | }
36 | ToolType.HIGHLIGHTER -> when (this) {
37 | Thickness.THIN -> 1.67f
38 | Thickness.MEDIUM -> 5f
39 | Thickness.LARGE -> 15f
40 | }
41 | else -> 0f
42 | }
43 |
44 | fun Float.toThickness(toolType: ToolType?) = when (toolType) {
45 | ToolType.PEN -> when {
46 | this <= .25f -> Thickness.THIN
47 | this == .65f -> Thickness.MEDIUM
48 | this >= 1.65f -> Thickness.LARGE
49 | else -> Thickness.THIN
50 | }
51 | ToolType.HIGHLIGHTER -> when {
52 | this <= 1.67f -> Thickness.THIN
53 | this == 5f -> Thickness.MEDIUM
54 | this >= 15f -> Thickness.LARGE
55 | else -> null
56 | }
57 | else -> Thickness.MEDIUM
58 | }
59 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/ui/ToolsAdapter.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.ui
4 |
5 | import android.view.LayoutInflater
6 | import android.view.ViewGroup
7 | import android.widget.ImageView
8 | import androidx.annotation.DrawableRes
9 | import androidx.annotation.StringRes
10 | import androidx.recyclerview.widget.DiffUtil
11 | import androidx.recyclerview.widget.ListAdapter
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.myscript.iink.demo.R
14 | import com.myscript.iink.demo.domain.ToolType
15 | import com.myscript.iink.demo.util.setContentDescription
16 | import com.myscript.iink.demo.util.setTooltipText
17 |
18 | @get:DrawableRes
19 | private val ToolType.asDrawable: Int
20 | get() = when (this) {
21 | ToolType.HAND -> R.drawable.ic_hand_outlined
22 | ToolType.PEN -> R.drawable.ic_pen_outlined
23 | ToolType.ERASER -> R.drawable.ic_eraser
24 | ToolType.HIGHLIGHTER -> R.drawable.ic_brush_outlined
25 | ToolType.LASSO -> R.drawable.ic_lasso
26 | }
27 |
28 | @get:StringRes
29 | private val ToolType.label: Int
30 | get() = when (this) {
31 | ToolType.HAND -> R.string.tool_hand
32 | ToolType.PEN -> R.string.tool_pen
33 | ToolType.ERASER -> R.string.tool_eraser
34 | ToolType.HIGHLIGHTER -> R.string.tool_highlighter
35 | ToolType.LASSO -> R.string.tool_lasso
36 | }
37 |
38 | class ToolStateViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
39 | LayoutInflater.from(parent.context).inflate(R.layout.toolbar_tool_cell, parent, false)
40 | ) {
41 | private val toolView: ImageView = itemView.findViewById(R.id.toolbar_tool_icon)
42 |
43 | fun bind(state: ToolState, onToolSelected: (ToolState) -> Unit) {
44 | toolView.setImageResource(state.type.asDrawable)
45 | toolView.setContentDescription(state.type.label)
46 |
47 | itemView.setTooltipText(state.type.label)
48 | itemView.isEnabled = state.isEnabled
49 | itemView.isSelected = state.isSelected
50 | itemView.setOnClickListener { onToolSelected(state) }
51 | }
52 |
53 | fun unbind() {
54 | itemView.setOnClickListener(null)
55 | }
56 | }
57 |
58 | class ToolsAdapter(private val onToolSelected: (ToolState) -> Unit)
59 | : ListAdapter(TOOL_DIFF_UTIL_CALLBACK) {
60 |
61 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ToolStateViewHolder {
62 | return ToolStateViewHolder(parent)
63 | }
64 |
65 | override fun onBindViewHolder(holder: ToolStateViewHolder, position: Int) {
66 | holder.bind(getItem(position), onToolSelected)
67 | }
68 |
69 | override fun onViewRecycled(holder: ToolStateViewHolder) {
70 | holder.unbind()
71 | }
72 |
73 | override fun submitList(list: List?) {
74 | if (list == null) {
75 | super.submitList(null)
76 | } else {
77 | super.submitList(list.toList()) // force DiffUtil by creating a new list
78 | }
79 | }
80 |
81 | companion object {
82 | private val TOOL_DIFF_UTIL_CALLBACK = object : DiffUtil.ItemCallback() {
83 | override fun areItemsTheSame(oldItem: ToolState, newItem: ToolState): Boolean {
84 | return oldItem.type == newItem.type
85 | }
86 |
87 | override fun areContentsTheSame(oldItem: ToolState, newItem: ToolState): Boolean {
88 | return oldItem == newItem
89 | }
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/util/Dialogs.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.util
4 |
5 | import android.app.AlertDialog
6 | import android.content.Context
7 | import android.view.LayoutInflater
8 | import android.widget.EditText
9 | import android.widget.SeekBar
10 | import android.widget.SeekBar.OnSeekBarChangeListener
11 | import android.widget.TextView
12 | import androidx.annotation.StringRes
13 | import androidx.appcompat.widget.SwitchCompat
14 | import com.myscript.iink.demo.R
15 |
16 | fun Context.launchSingleChoiceDialog(
17 | @StringRes titleRes: Int,
18 | items: List,
19 | selectedIndex: Int = -1,
20 | onItemSelected: (selected: Int) -> Unit
21 | ) {
22 | var newIndex = selectedIndex
23 | AlertDialog.Builder(this)
24 | .setTitle(titleRes)
25 | .setSingleChoiceItems(
26 | items.toTypedArray(),
27 | selectedIndex
28 | ) { _, which ->
29 | newIndex = which
30 | }
31 | .setPositiveButton(R.string.dialog_ok) { _, _ ->
32 | if (newIndex in items.indices) {
33 | onItemSelected(newIndex)
34 | }
35 | }
36 | .setNegativeButton(R.string.dialog_cancel, null)
37 | .show()
38 | }
39 |
40 | fun Context.launchActionChoiceDialog(
41 | items: List,
42 | onItemSelected: (selected: Int) -> Unit
43 | ) {
44 | AlertDialog.Builder(this)
45 | .setItems(items.toTypedArray()) { _, which ->
46 | if (which in items.indices) {
47 | onItemSelected(which)
48 | }
49 | }
50 | .show()
51 | }
52 |
53 | fun Context.launchTextBlockInputDialog(onInputDone: (text: String) -> Unit) {
54 | val editTextLayout = LayoutInflater.from(this).inflate(R.layout.editor_text_input_layout, null)
55 | val editText = editTextLayout.findViewById(R.id.editor_text_input)
56 | val builder = AlertDialog.Builder(this)
57 | .setView(editTextLayout)
58 | .setTitle(R.string.editor_dialog_insert_text_title)
59 | .setPositiveButton(R.string.editor_dialog_insert_text_action) { _, _ ->
60 | val text = editText.text.toString()
61 | if (text.isNotBlank()) {
62 | onInputDone(text)
63 | }
64 | }
65 | .setNegativeButton(R.string.dialog_cancel, null)
66 | .create()
67 | editText.requestFocus()
68 | builder.show()
69 | }
70 |
71 | fun Context.launchPredictionDialog(
72 | enabled: Boolean,
73 | durationMs: Int,
74 | onInputDone: (Boolean, Int) -> Unit
75 | ) {
76 | val editPredictionLayout = LayoutInflater.from(this).inflate(R.layout.editor_prediction_layout, null)
77 | val enablePredictionSwitch = editPredictionLayout.findViewById(R.id.editor_dialog_prediction_toggle)
78 | val durationLabel = editPredictionLayout.findViewById(R.id.editor_dialog_prediction_duration_label)
79 | durationLabel.text = getString(R.string.editor_dialog_prediction_duration, durationMs)
80 | val durationSeekBar = editPredictionLayout.findViewById(R.id.editor_dialog_prediction_duration).apply {
81 | setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
82 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
83 | if (fromUser) {
84 | durationLabel.text = getString(R.string.editor_dialog_prediction_duration, seekBar?.progress ?: 0)
85 | }
86 | }
87 |
88 | override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
89 | override fun onStopTrackingTouch(seekBar: SeekBar?) = Unit
90 | })
91 | }
92 |
93 | enablePredictionSwitch.isChecked = enabled
94 | durationSeekBar.progress = durationMs
95 |
96 | AlertDialog.Builder(this)
97 | .setView(editPredictionLayout)
98 | .setTitle(R.string.editor_dialog_prediction_title)
99 | .setPositiveButton(R.string.dialog_ok) { _, _ ->
100 | durationSeekBar.setOnSeekBarChangeListener(null)
101 | onInputDone(enablePredictionSwitch.isChecked, durationSeekBar.progress)
102 | }
103 | .setNegativeButton(R.string.dialog_cancel, null)
104 | .show()
105 | }
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/util/autoCloseable.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.util
4 |
5 | import kotlin.properties.ReadWriteProperty
6 | import kotlin.reflect.KProperty
7 |
8 |
9 | /**
10 | * Property delegate to enforce [AutoCloseable.close] being called when overwriting a previous value.
11 | * This ensures native resources cleanup in any circumstances when storing [AutoCloseable] objects
12 | * as member variables.
13 | *
14 | * ```kotlin
15 | * private var myAutoCloseable by autoCloseable(null) { oldValue ->
16 | * // oldValue (if not null) can be used if needed
17 | * }
18 | *
19 | * private var myOtherAutoCloseable by autoCloseable(MyAutoCloseable())
20 | *
21 | * fun doSomething() {
22 | * myAutoCloseable = MyAutoCloseable()
23 | * myOtherAutoCloseable = null // ensures previous value is closed
24 | * }
25 | * ```
26 | *
27 | * It is similar to [use] (or Java try-with-resources) when handling [AutoCloseable].
28 | *
29 | * ```kotlin
30 | * object.getAutoCloseableObject().use { obj ->
31 | * // obj.close() automatically closed at the end of `use` block scope.
32 | * }
33 | * ```
34 | *
35 | * @param initialValue initial value to set to underlying backing field
36 | * @param onUpdate callback called when the value is updated. The `oldValue` will be closed *after*
37 | * this callback is called and *after* the new value is set to underlying backing field.
38 | * This allows working safely with `oldValue` before it's closed.
39 | *
40 | * @see https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
41 | * @see https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/use.html
42 | * @see https://kotlinlang.org/docs/delegated-properties.html
43 | */
44 | class AutoCloseableDelegate(initialValue: T?, private val onUpdate: ((oldValue: T?) -> Unit)? = null)
45 | : ReadWriteProperty
46 | where T : AutoCloseable? {
47 | private var value: T? = initialValue
48 |
49 | override fun getValue(thisRef: Any, property: KProperty<*>): T? {
50 | return value
51 | }
52 |
53 | @Suppress("ConvertTryFinallyToUseCall")
54 | override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
55 | val oldValue = this.value
56 | // overwriting with same value is no-op
57 | if (oldValue === value)
58 | return
59 | try {
60 | this.value = value
61 | onUpdate?.invoke(oldValue)
62 | } finally {
63 | oldValue?.close()
64 | }
65 | }
66 | }
67 |
68 | /**
69 | * Convenience helper for [AutoCloseableDelegate].
70 | *
71 | * @see [AutoCloseableDelegate]
72 | */
73 | fun autoCloseable(initialValue: T? = null, onUpdate: ((oldValue: T?) -> Unit)? = null): AutoCloseableDelegate
74 | where T : AutoCloseable? {
75 | return AutoCloseableDelegate(initialValue, onUpdate)
76 | }
77 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/myscript/iink/demo/util/viewExt.kt:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.demo.util
4 |
5 | import android.view.View
6 | import android.widget.ImageView
7 | import androidx.annotation.StringRes
8 | import androidx.appcompat.widget.TooltipCompat
9 |
10 | fun View.setTooltipText(@StringRes textRes: Int) {
11 | TooltipCompat.setTooltipText(this, context.getString(textRes))
12 | }
13 |
14 | fun ImageView.setContentDescription(@StringRes textRes: Int) {
15 | contentDescription = context.getString(textRes)
16 | }
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/bottom_sheet_handle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/disc.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_brush_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_chevron_left.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_chevron_right.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_colorize_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_eraser.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_hand_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_lasso.xml:
--------------------------------------------------------------------------------
1 |
7 |
25 |
26 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_pen_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_redo.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_reset_view.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_undo.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_zoom_in.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_zoom_out.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/texture_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/drawable/texture_background.png
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/texture_stamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/drawable/texture_stamp.png
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/tool_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/tool_background_selected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/font/myscriptinter.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/Demo/src/main/res/font/myscriptinter_bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/font/myscriptinter_bold.otf
--------------------------------------------------------------------------------
/Demo/src/main/res/font/myscriptinter_regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/font/myscriptinter_regular.otf
--------------------------------------------------------------------------------
/Demo/src/main/res/font/stix.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/Demo/src/main/res/font/stix_italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/font/stix_italic.otf
--------------------------------------------------------------------------------
/Demo/src/main/res/font/stix_regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/font/stix_regular.otf
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/editor_prediction_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/editor_text_input_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/editor_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
21 |
22 |
31 |
32 |
41 |
42 |
51 |
52 |
61 |
62 |
71 |
72 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
19 |
20 |
24 |
25 |
28 |
29 |
30 |
31 |
34 |
35 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/toolbar_color_cell.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/toolbar_pen_brush_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/toolbar_settings_sheet_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
28 |
29 |
38 |
39 |
50 |
51 |
62 |
63 |
70 |
71 |
76 |
77 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/toolbar_thickness_cell.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/toolbar_tool_cell.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Demo/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
50 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/raw/theme.css:
--------------------------------------------------------------------------------
1 | /*
2 | * see https://developer.myscript.com/docs/interactive-ink/latest/reference/styling for styling reference
3 | */
4 |
5 | glyph {
6 | /* Font family must be aligned with the key used to register the corresponding Typeface in typeface map (see DemoModule) */
7 | font-family: MyScriptInter;
8 | }
9 |
10 | .math {
11 | /* Font family must be aligned with the key used to register the corresponding Typeface in typeface map (see DemoModule) */
12 | font-family: STIX;
13 | }
14 |
15 | .math-variable {
16 | font-style: italic;
17 | }
18 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values-large/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 72dp
5 | 56dp
6 |
7 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #1C1C1C
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #009fe3
5 | #F5F6F7
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 64dp
5 | 24dp
6 |
7 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | iink Demo
5 |
6 | OK
7 | Cancel
8 |
9 | Invalid certificate
10 | Please check certificate data provided at Engine creation.
11 | Error while inserting image
12 | Unsupported file type
13 | %s not supported (only image content).
14 | %s not supported (only iink content).
15 | [%1$s] %2$s
16 |
17 | New part…
18 | New part type
19 | Previous part
20 | Next part
21 |
22 | Convert
23 | Prediction…
24 | Export…
25 | %1$s (%2$s)
26 | Export error
27 | Save
28 | Import file…
29 | Share file…
30 |
31 | Active pen
32 | Undo
33 | Redo
34 | Clear content
35 | Zoom in
36 | Zoom out
37 | Reset view
38 |
39 | Copy
40 | Paste
41 | Delete
42 | Convert
43 | Export…
44 | Add…
45 | Format text…
46 | As heading level 1
47 | As heading level 2
48 | As paragraph
49 | As bullet list
50 | As check list
51 | As numbered list
52 | Selection mode…
53 | None
54 | Lasso
55 | Item
56 | Resize
57 | Reflow
58 | Selection type…
59 | Text
60 | Text - Single block
61 | Math
62 | Math - Single block
63 |
64 | Tool thickness
65 | Choose a color
66 | Add text block
67 | Insert
68 | Prediction settings
69 | Enable
70 | Duration (%d ms)
71 |
72 | Hand
73 | Pen
74 | Highlighter
75 | Lasso
76 | Eraser
77 |
78 | Brush
79 | Felt Pen
80 | Fountain Pen
81 | Calligraphic Brush
82 | Pencil Brush
83 |
84 |
85 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Demo/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/GetStarted/build.gradle:
--------------------------------------------------------------------------------
1 | import org.apache.commons.io.FileUtils
2 | import org.apache.commons.io.filefilter.FileFilterUtils
3 |
4 | plugins {
5 | id 'com.android.application'
6 | id 'kotlin-android'
7 | }
8 |
9 | android {
10 | namespace 'com.myscript.iink.getstarted'
11 |
12 | compileSdk project.ext.compileSdk
13 |
14 | buildFeatures {
15 | viewBinding true
16 | buildConfig true
17 | }
18 |
19 | defaultConfig {
20 | minSdk project.ext.minSdk
21 | targetSdk project.ext.targetSdk
22 |
23 | applicationId 'com.myscript.iink.getstarted'
24 | versionCode 4100
25 | versionName '4.1.0'
26 |
27 | vectorDrawables.useSupportLibrary true
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}"
33 | implementation project(':UIReferenceImplementation')
34 | }
35 |
36 | task DownloadAndExtractAssets(type: Copy) {
37 | def sourceUrls = ['https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-diagram.zip',
38 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-raw-content.zip',
39 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-raw-content2.zip',
40 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-math.zip',
41 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-math2.zip',
42 | 'https://download.myscript.com/iink/recognitionAssets_iink_4.1/myscript-iink-recognition-text-en_US.zip']
43 | def targetDir = new File(projectDir, "src/main/assets/")
44 | def diagramConf = new File(targetDir, "conf/diagram.conf")
45 | def rawContentConf = new File(targetDir, "conf/raw-content.conf")
46 | def mathConf = new File(targetDir, "conf/math.conf")
47 | def textConf = new File(targetDir, "conf/en_US.conf")
48 | def rawContent2Conf = new File(targetDir, "conf/raw-content2.conf")
49 | def math2Conf = new File(targetDir, "conf/math2.conf")
50 |
51 | if (!diagramConf.exists() || !rawContentConf.exists() || !mathConf.exists() || !textConf.exists() || !rawContent2Conf.exists() || !math2Conf.exists()) {
52 | def tmpAssetsDir = new File(projectDir, "tmp-assets/")
53 | def zipDir = new File(tmpAssetsDir, "zips")
54 |
55 | if (!tmpAssetsDir.isDirectory())
56 | tmpAssetsDir.mkdirs()
57 |
58 | if (!zipDir.isDirectory())
59 | zipDir.mkdirs()
60 |
61 | sourceUrls.each { sourceUrl ->
62 | ant.get(src: sourceUrl, dest: zipDir.getPath())
63 | }
64 |
65 | File[] zipFiles = FileUtils.listFiles(zipDir, FileFilterUtils.suffixFileFilter("zip"), FileFilterUtils.trueFileFilter())
66 | zipFiles.each { File zipFile ->
67 | from zipTree(zipFile)
68 | into tmpAssetsDir
69 | }
70 | }
71 | }
72 |
73 | task CopyAssets(type: Copy, dependsOn: DownloadAndExtractAssets) {
74 | def targetDir = new File(projectDir, "src/main/assets/")
75 | def diagramConf = new File(targetDir, "conf/diagram.conf")
76 | def rawContentConf = new File(targetDir, "conf/raw-content.conf")
77 | def mathConf = new File(targetDir, "conf/math.conf")
78 | def textConf = new File(targetDir, "conf/en_US.conf")
79 | def rawContent2Conf = new File(targetDir, "conf/raw-content2.conf")
80 | def math2Conf = new File(targetDir, "conf/math2.conf")
81 |
82 | if (!diagramConf.exists() || !rawContentConf.exists() || !mathConf.exists() || !textConf.exists() || !rawContent2Conf.exists() || !math2Conf.exists()) {
83 | def tmpAssetsDir = new File(projectDir, "tmp-assets/")
84 |
85 | if (!tmpAssetsDir.isDirectory())
86 | tmpAssetsDir.mkdirs()
87 |
88 | def recognitionAssetDir = new File(tmpAssetsDir, "recognition-assets/")
89 |
90 | println "Copying downloaded assets from $recognitionAssetDir to $targetDir"
91 | from recognitionAssetDir
92 | into targetDir
93 |
94 | doLast {
95 | tmpAssetsDir.deleteDir()
96 | }
97 | }
98 | }
99 |
100 | preBuild.dependsOn(CopyAssets)
101 |
--------------------------------------------------------------------------------
/GetStarted/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/GetStarted/src/main/java/com/myscript/certificate/MyCertificate.java:
--------------------------------------------------------------------------------
1 | // Copyright MyScript. All rights reserved.
2 |
3 | package com.myscript.certificate;
4 |
5 | public final class MyCertificate
6 | {
7 | public static final byte[] getBytes()
8 | {
9 | throw new RuntimeException("Please replace the content of MyCertificate.java with the certificate you received from the developer portal");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/GetStarted/src/main/java/com/myscript/iink/getstarted/ErrorActivity.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.getstarted;
4 |
5 | import android.app.Activity;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import androidx.annotation.Nullable;
9 | import android.text.method.ScrollingMovementMethod;
10 | import android.widget.TextView;
11 |
12 | import java.io.PrintWriter;
13 | import java.io.StringWriter;
14 |
15 |
16 | /**
17 | * This activity displays an error message when an uncaught exception is thrown within an activity
18 | * that installed the associated exception handler. Since this application targets developers it's
19 | * better to clearly explain what happened.
20 | * The code is inspired by:
21 | * https://trivedihardik.wordpress.com/2011/08/20/how-to-avoid-force-close-error-in-android/
22 | */
23 | public class ErrorActivity extends Activity
24 | {
25 | public static final String INTENT_EXTRA_MESSAGE = "message";
26 | public static final String INTENT_EXTRA_DETAILS = "details";
27 |
28 | @Override
29 | protected void onCreate(@Nullable Bundle savedInstanceState)
30 | {
31 | super.onCreate(savedInstanceState);
32 |
33 | setContentView(R.layout.error_activity);
34 |
35 | TextView messageView = findViewById(R.id.error_message);
36 | messageView.setText(getIntent().getStringExtra(INTENT_EXTRA_MESSAGE));
37 |
38 | TextView detailsView = findViewById(R.id.error_details);
39 | detailsView.setText(getIntent().getStringExtra(INTENT_EXTRA_DETAILS));
40 | detailsView.setMovementMethod(new ScrollingMovementMethod());
41 | }
42 |
43 | public static void start(Activity context, String message, String details)
44 | {
45 | Intent intent = new Intent(context, ErrorActivity.class);
46 | intent.putExtra(INTENT_EXTRA_MESSAGE, message);
47 | intent.putExtra(INTENT_EXTRA_DETAILS, details);
48 | context.startActivity(intent);
49 | }
50 |
51 | public static void installHandler(Activity context)
52 | {
53 | Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(context));
54 | }
55 |
56 | private static class ExceptionHandler implements Thread.UncaughtExceptionHandler
57 | {
58 | private final Activity context;
59 |
60 | public ExceptionHandler(Activity context)
61 | {
62 | this.context = context;
63 | }
64 |
65 | @Override
66 | public void uncaughtException(Thread thread, Throwable throwable)
67 | {
68 | // Get the message of the root cause
69 | Throwable rootCause = throwable;
70 | while (rootCause.getCause() != null)
71 | rootCause = rootCause.getCause();
72 | String message = rootCause.getMessage();
73 |
74 | // Print the stack trace to a string
75 | StringWriter stackTraceWriter = new StringWriter();
76 | throwable.printStackTrace(new PrintWriter(stackTraceWriter));
77 | String stackTrace = stackTraceWriter.toString();
78 |
79 | // Launch the error activity with the message and stack trace
80 | start(context, message, stackTrace);
81 |
82 | // Kill the current activity
83 | android.os.Process.killProcess(android.os.Process.myPid());
84 | System.exit(10);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/GetStarted/src/main/java/com/myscript/iink/getstarted/IInkApplication.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.getstarted;
4 |
5 | import android.app.Application;
6 |
7 | import com.myscript.certificate.MyCertificate;
8 | import com.myscript.iink.Engine;
9 |
10 | public class IInkApplication extends Application
11 | {
12 | private static Engine engine;
13 |
14 | public static synchronized Engine getEngine()
15 | {
16 | if (engine == null)
17 | {
18 | engine = Engine.create(MyCertificate.getBytes());
19 | }
20 |
21 | return engine;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/color/button_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/color/editor_icon_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/drawable/button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/drawable/ic_redo.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/drawable/ic_undo.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/layout/error_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
24 |
25 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
29 |
30 |
37 |
38 |
45 |
46 |
51 |
52 |
60 |
61 |
69 |
70 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/menu/main_activity_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/GetStarted/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GetStarted/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/GetStarted/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GetStarted/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/GetStarted/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GetStarted/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/GetStarted/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GetStarted/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/GetStarted/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/GetStarted/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #e91e63
5 | #448aff
6 | #3f51b5
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 48dp
5 | 12dp
6 | 56dp
7 | 32dp
8 |
9 | 72dp
10 | 24dp
11 |
12 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ffffff
4 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | iink GetStarted
6 |
7 | Type: %s
8 |
9 | Input mode
10 | Undo
11 | Redo
12 | Clear
13 | Convert
14 | Export
15 | Export - select type
16 | Export - select file
17 | Add image - select file
18 | Input mode - select behavior
19 | Pen
20 | Touch
21 | Auto
22 |
23 |
24 |
--------------------------------------------------------------------------------
/GetStarted/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright MyScript.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 | http://www.apache.org/licenses/LICENSE-2.0
6 | Unless required by applicable law or agreed to in writing, software
7 | distributed under the License is distributed on an "AS IS" BASIS,
8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | See the License for the specific language governing permissions and
10 | limitations under the License.
--------------------------------------------------------------------------------
/LICENSES/androidSupportLib.txt:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License");
2 | you may not use this file except in compliance with the License.
3 | You may obtain a copy of the License at
4 |
5 | http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
--------------------------------------------------------------------------------
/LICENSES/inter.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-2020 The Inter Project Authors.
2 | "Inter" is trademark of Rasmus Andersson.
3 | https://github.com/rsms/inter
4 |
5 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
6 | This license is copied below, and is also available with a FAQ at:
7 | http://scripts.sil.org/OFL
8 |
9 | -----------------------------------------------------------
10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11 | -----------------------------------------------------------
12 |
13 | PREAMBLE
14 | The goals of the Open Font License (OFL) are to stimulate worldwide
15 | development of collaborative font projects, to support the font creation
16 | efforts of academic and linguistic communities, and to provide a free and
17 | open framework in which fonts may be shared and improved in partnership
18 | with others.
19 |
20 | The OFL allows the licensed fonts to be used, studied, modified and
21 | redistributed freely as long as they are not sold by themselves. The
22 | fonts, including any derivative works, can be bundled, embedded,
23 | redistributed and/or sold with any software provided that any reserved
24 | names are not used by derivative works. The fonts and derivatives,
25 | however, cannot be released under any other type of license. The
26 | requirement for fonts to remain under this license does not apply
27 | to any document created using the fonts or their derivatives.
28 |
29 | DEFINITIONS
30 | "Font Software" refers to the set of files released by the Copyright
31 | Holder(s) under this license and clearly marked as such. This may
32 | include source files, build scripts and documentation.
33 |
34 | "Reserved Font Name" refers to any names specified as such after the
35 | copyright statement(s).
36 |
37 | "Original Version" refers to the collection of Font Software components as
38 | distributed by the Copyright Holder(s).
39 |
40 | "Modified Version" refers to any derivative made by adding to, deleting,
41 | or substituting -- in part or in whole -- any of the components of the
42 | Original Version, by changing formats or by porting the Font Software to a
43 | new environment.
44 |
45 | "Author" refers to any designer, engineer, programmer, technical
46 | writer or other person who contributed to the Font Software.
47 |
48 | PERMISSION AND CONDITIONS
49 | Permission is hereby granted, free of charge, to any person obtaining
50 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
51 | redistribute, and sell modified and unmodified copies of the Font
52 | Software, subject to the following conditions:
53 |
54 | 1) Neither the Font Software nor any of its individual components,
55 | in Original or Modified Versions, may be sold by itself.
56 |
57 | 2) Original or Modified Versions of the Font Software may be bundled,
58 | redistributed and/or sold with any software, provided that each copy
59 | contains the above copyright notice and this license. These can be
60 | included either as stand-alone text files, human-readable headers or
61 | in the appropriate machine-readable metadata fields within text or
62 | binary files as long as those fields can be easily viewed by the user.
63 |
64 | 3) No Modified Version of the Font Software may use the Reserved Font
65 | Name(s) unless explicit written permission is granted by the corresponding
66 | Copyright Holder. This restriction only applies to the primary font name as
67 | presented to the users.
68 |
69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70 | Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except to acknowledge the contribution(s) of the
72 | Copyright Holder(s) and the Author(s) or with their explicit written
73 | permission.
74 |
75 | 5) The Font Software, modified or unmodified, in part or in whole,
76 | must be distributed entirely under this license, and must not be
77 | distributed under any other license. The requirement for fonts to
78 | remain under this license does not apply to any document created
79 | using the Font Software.
80 |
81 | TERMINATION
82 | This license becomes null and void if any of the above conditions are
83 | not met.
84 |
85 | DISCLAIMER
86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94 | OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
/LICENSES/kotlin.txt:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License");
2 | you may not use this file except in compliance with the License.
3 | You may obtain a copy of the License at
4 |
5 | http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 |
--------------------------------------------------------------------------------
/LICENSES/materialDesign.txt:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License");
2 | you may not use this file except in compliance with the License.
3 | You may obtain a copy of the License at
4 |
5 | http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
--------------------------------------------------------------------------------
/LICENSES/stix.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/LICENSES/stix.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## What is it about?
2 |
3 | Interactive Ink SDK is the best way to integrate handwriting recognition capabilities into your Android application. Interactive Ink extends digital ink to allow users to more intuitively create, interact with, and share content in digital form. Handwritten text, mathematical equations or even diagrams are interpreted in real-time to be editable via simple gestures, responsive and easy to convert to a neat output.
4 |
5 | This repository contains a "get started" example, a complete example and a reference implementation of the Android integration part that developers using Interactive Ink SDK can reuse inside their projects.
6 |
7 | ## Getting started
8 |
9 | ### Installation
10 |
11 | 1. Clone the examples repository `git clone https://github.com/MyScript/interactive-ink-examples-android.git`
12 |
13 | 2. Claim a certificate to receive the free license to start develop your application by following the first steps of [Getting Started](https://developer.myscript.com/getting-started)
14 |
15 | 3. Copy this certificate to `GetStarted/src/main/java/com/myscript/certificate/MyCertificate.java` and `Demo/src/main/java/com/myscript/certificate/MyCertificate.java`
16 |
17 | ## Building your own integration
18 |
19 | This repository provides you with a ready-to-use reference implementation of the Android integration part, covering aspects like ink capture and rendering. It is located in `UIReferenceImplementation` directory and can be simply added to your project by referencing it in your `settings.gradle`.
20 |
21 | ## Documentation
22 |
23 | A complete guide is available on [MyScript Developer website](https://developer.myscript.com/docs/interactive-ink/latest/android/).
24 |
25 | The API Reference is available directly in Android Studio once the dependencies are downloaded.
26 |
27 | ## Getting support
28 |
29 | You can get some support from the dedicated section on [MyScript Developer website](https://developer.myscript.com/support/).
30 |
31 | ## Troubleshoot
32 |
33 | If you encounter build errors of the form `No version of NDK matched the requested version`, please install the requested NDK version or update the one referenced in `build.gradle` to match your installed NDK version. You can follow [these instructions](https://developer.android.com/studio/projects/install-ndk#specific-version).
34 |
35 | ## Sharing your feedback ?
36 |
37 | Made a cool app with Interactive Ink? Ready to cross join our marketing efforts? We would love to hear about you!
38 | We’re planning to showcase apps using it so let us know by sending a quick mail to [myapp@myscript.com](mailto://myapp@myscript.com).
39 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | namespace 'com.myscript.iink.uireferenceimplementation'
8 |
9 | compileSdk project.ext.compileSdk
10 |
11 | defaultConfig {
12 | minSdk project.ext.minSdk
13 | targetSdk project.ext.targetSdk
14 | versionCode 4100
15 | versionName '4.1.0'
16 |
17 | vectorDrawables.useSupportLibrary true
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}"
23 | implementation "com.google.code.gson:gson:2.11.0"
24 | api "com.myscript:iink:4.1.0"
25 | }
26 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActions.java:
--------------------------------------------------------------------------------
1 | package com.myscript.iink.uireferenceimplementation;
2 |
3 | import com.myscript.iink.ContentSelection;
4 | import com.myscript.iink.Editor;
5 |
6 | /**
7 | * Describes the actions available for a given selection or block.
8 | *
9 | * @since 2.0
10 | */
11 | public enum ContextualActions {
12 | /**
13 | * Add block
14 | */
15 | ADD_BLOCK,
16 | /**
17 | * Remove block or selection
18 | */
19 | REMOVE,
20 | /**
21 | * Convert.
22 | * @see Editor#getSupportedTargetConversionStates(ContentSelection)
23 | */
24 | CONVERT,
25 | /**
26 | * Copy block or selection
27 | */
28 | COPY,
29 | /**
30 | * Paste current copy
31 | */
32 | PASTE,
33 | /**
34 | * Export.
35 | * @see Editor#getSupportedExportMimeTypes(ContentSelection)
36 | */
37 | EXPORT,
38 | /**
39 | * Change Text blocks format.
40 | * @see Editor#getSupportedTextFormats(ContentSelection)
41 | */
42 | FORMAT_TEXT,
43 | /**
44 | * Change selection mode.
45 | * @see Editor#getAvailableSelectionModes()
46 | */
47 | SELECTION_MODE,
48 | /**
49 | * Change selection type.
50 | * @see Editor#getAvailableSelectionTypes(ContentSelection)
51 | */
52 | SELECTION_TYPE
53 | }
54 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import com.myscript.iink.ContentBlock;
6 | import com.myscript.iink.ContentPart;
7 | import com.myscript.iink.ContentSelection;
8 | import com.myscript.iink.ContentSelectionMode;
9 | import com.myscript.iink.Editor;
10 |
11 | import java.util.EnumSet;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 |
16 | public final class ContextualActionsHelper
17 | {
18 | private ContextualActionsHelper()
19 | {
20 | // utility class
21 | }
22 |
23 | @NonNull
24 | public static EnumSet getAvailableActionsForBlock(@NonNull Editor editor, @NonNull ContentBlock block)
25 | {
26 | EnumSet actions = EnumSet.noneOf(ContextualActions.class);
27 |
28 | try (ContentBlock rootBlock = editor.getRootBlock())
29 | {
30 | ContentPart part = editor.getPart();
31 | boolean isRootBlock = rootBlock != null && block.getId().equals(rootBlock.getId());
32 | boolean onTextDocument = "Text Document".equals(part != null ? part.getType() : null);
33 | boolean blockIsEmpty = editor.isEmpty(block);
34 |
35 | boolean displayAddBlock = editor.getSupportedAddBlockTypes().length > 0 && (!onTextDocument || isRootBlock);
36 | boolean displayRemove = !isRootBlock;
37 | boolean displayCopy = !isRootBlock || !onTextDocument;
38 | boolean displayConvert = !blockIsEmpty && editor.getSupportedTargetConversionStates(block).length > 0;
39 | boolean displayExport = editor.getSupportedExportMimeTypes(block).length > 0;
40 | boolean displayFormatText = editor.getSupportedTextFormats(block).size() > 0;
41 | boolean displaySelectionMode = editor.getAvailableSelectionModes().size() > 0;
42 | boolean displaySelectionType = editor.getAvailableSelectionTypes(block).length > 0;
43 |
44 | if (displayAddBlock) actions.add(ContextualActions.ADD_BLOCK);
45 | if (displayRemove) actions.add(ContextualActions.REMOVE);
46 | if (displayConvert) actions.add(ContextualActions.CONVERT);
47 | if (displayCopy) actions.add(ContextualActions.COPY);
48 | if (isRootBlock) actions.add(ContextualActions.PASTE);
49 | if (displayExport) actions.add(ContextualActions.EXPORT);
50 | if (displayFormatText) actions.add(ContextualActions.FORMAT_TEXT);
51 | if (displaySelectionMode) actions.add(ContextualActions.SELECTION_MODE);
52 | if (displaySelectionType) actions.add(ContextualActions.SELECTION_TYPE);
53 | }
54 | return actions;
55 | }
56 |
57 | @NonNull
58 | public static EnumSet getAvailableActionsForSelection(@NonNull Editor editor, @Nullable ContentSelection selection)
59 | {
60 | EnumSet actions = EnumSet.noneOf(ContextualActions.class);
61 |
62 | boolean displayConvert = editor.getSupportedTargetConversionStates(selection).length > 0;
63 | boolean displayExport = editor.getSupportedExportMimeTypes(selection).length > 0;
64 | boolean displayFormatText = selection != null && !editor.getSupportedTextFormats(selection).isEmpty();
65 | boolean displaySelectionMode = editor.getAvailableSelectionModes().size() > 0;
66 | boolean displaySelectionType = editor.getAvailableSelectionTypes(selection).length > 0;
67 |
68 | actions.add(ContextualActions.REMOVE);
69 | if (displayConvert) actions.add(ContextualActions.CONVERT);
70 | actions.add(ContextualActions.COPY);
71 | if (displayExport) actions.add(ContextualActions.EXPORT);
72 | if (displayFormatText) actions.add(ContextualActions.FORMAT_TEXT);
73 | if (displaySelectionMode) actions.add(ContextualActions.SELECTION_MODE);
74 | if (displaySelectionType) actions.add(ContextualActions.SELECTION_TYPE);
75 |
76 | return actions;
77 | }
78 | }
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/CustomTextSpan.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.content.res.ColorStateList;
6 | import android.graphics.Typeface;
7 | import android.os.Parcel;
8 | import android.text.TextPaint;
9 | import android.text.style.TextAppearanceSpan;
10 |
11 | public class CustomTextSpan extends TextAppearanceSpan
12 | {
13 |
14 | public static final Creator CREATOR = new Creator()
15 | {
16 | @Override
17 | public CustomTextSpan createFromParcel(Parcel in)
18 | {
19 | return new CustomTextSpan(in);
20 | }
21 |
22 | @Override
23 | public CustomTextSpan[] newArray(int size)
24 | {
25 | return new CustomTextSpan[size];
26 | }
27 | };
28 |
29 | private Typeface mTypeface;
30 |
31 | public CustomTextSpan(Typeface tf, int style, int size, ColorStateList color, ColorStateList linkColor)
32 | {
33 | super("", style, size, color, linkColor);
34 | mTypeface = tf;
35 | }
36 |
37 | protected CustomTextSpan(Parcel in)
38 | {
39 | super(in);
40 | }
41 |
42 | @Override
43 | public int describeContents()
44 | {
45 | return 0;
46 | }
47 |
48 | @Override
49 | public void updateDrawState(TextPaint ds)
50 | {
51 | applyCustomTypeFace(ds);
52 |
53 | ColorStateList textColor = getTextColor();
54 | if (textColor != null)
55 | {
56 | ds.setColor(textColor.getColorForState(ds.drawableState, 0));
57 | }
58 |
59 | ColorStateList linkColor = getLinkTextColor();
60 | if (linkColor != null)
61 | {
62 | ds.linkColor = linkColor.getColorForState(ds.drawableState, 0);
63 | }
64 | }
65 |
66 | @Override
67 | public void updateMeasureState(TextPaint ds)
68 | {
69 | applyCustomTypeFace(ds);
70 | }
71 |
72 | private void applyCustomTypeFace(TextPaint ds)
73 | {
74 |
75 | int mStyle = getTextStyle();
76 | if (mTypeface != null || mStyle != 0)
77 | {
78 | Typeface tf = ds.getTypeface();
79 | int style = Typeface.NORMAL;
80 |
81 | if (tf != null)
82 | {
83 | style = tf.getStyle();
84 | }
85 |
86 | style |= mStyle;
87 |
88 | if (mTypeface != null)
89 | {
90 | tf = Typeface.create(mTypeface, style);
91 | }
92 | else if (tf == null)
93 | {
94 | tf = Typeface.defaultFromStyle(style);
95 | }
96 | else
97 | {
98 | tf = Typeface.create(tf, style);
99 | }
100 |
101 | int fake = style & ~tf.getStyle();
102 |
103 | if ((fake & Typeface.BOLD) != 0)
104 | {
105 | ds.setFakeBoldText(true);
106 | }
107 |
108 | if ((fake & Typeface.ITALIC) != 0)
109 | {
110 | ds.setTextSkewX(-0.25f);
111 | }
112 |
113 | ds.setTypeface(tf);
114 | }
115 |
116 | int mTextSize = getTextSize();
117 | if (mTextSize > 0)
118 | {
119 | ds.setTextSize(mTextSize);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.content.res.Resources;
6 | import android.graphics.Typeface;
7 | import android.util.DisplayMetrics;
8 |
9 | import com.myscript.iink.Editor;
10 | import com.myscript.iink.Engine;
11 | import com.myscript.iink.Renderer;
12 |
13 | import java.util.Map;
14 |
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.Nullable;
17 |
18 | public final class EditorBinding
19 | {
20 | @Nullable
21 | private final Engine engine;
22 | @NonNull
23 | private final Map typefaces;
24 | @Nullable
25 | private InputController inputController;
26 |
27 | public EditorBinding(@Nullable Engine engine, @NonNull Map typefaces)
28 | {
29 | this.engine = engine;
30 | this.typefaces = typefaces;
31 | }
32 |
33 | private void bindEditor(@NonNull EditorView editorView, @Nullable Editor editor)
34 | {
35 | editorView.setTypefaces(typefaces);
36 | editorView.setEditor(editor);
37 | if (editor != null)
38 | {
39 | editorView.setImageLoader(new ImageLoader(editor));
40 | inputController = new InputController(editorView.getContext(), editorView, editor);
41 | }
42 | else
43 | {
44 | editorView.setImageLoader(null);
45 | inputController = null;
46 | }
47 | editorView.setOnTouchListener(inputController);
48 | }
49 |
50 | @NonNull
51 | public EditorData openEditor(@Nullable EditorView editorView)
52 | {
53 | Editor editor = null;
54 | Renderer renderer = null;
55 | if (engine != null && editorView != null)
56 | {
57 | Resources resources = editorView.getResources();
58 | DisplayMetrics displayMetrics = resources.getDisplayMetrics();
59 | renderer = engine.createRenderer(displayMetrics.xdpi, displayMetrics.ydpi, editorView);
60 | renderer.setViewOffset(0.0f, 0.0f);
61 | renderer.setViewScale(1.0f);
62 | editor = engine.createEditor(renderer, engine.createToolController());
63 | editor.setFontMetricsProvider(new FontMetricsProvider(displayMetrics, typefaces));
64 | bindEditor(editorView, editor);
65 | }
66 | return new EditorData(editor, renderer, inputController);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import com.myscript.iink.Editor;
6 | import com.myscript.iink.Renderer;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | public final class EditorData
11 | {
12 | @Nullable
13 | private final Editor editor;
14 | @Nullable
15 | private final Renderer renderer;
16 | @Nullable
17 | private final InputController inputController;
18 |
19 | @Nullable
20 | public Editor getEditor()
21 | {
22 | return this.editor;
23 | }
24 |
25 | @Nullable
26 | public Renderer getRenderer()
27 | {
28 | return this.renderer;
29 | }
30 |
31 | @Nullable
32 | public InputController getInputController()
33 | {
34 | return this.inputController;
35 | }
36 |
37 | public EditorData(@Nullable Editor editor, @Nullable Renderer renderer, @Nullable InputController inputController)
38 | {
39 | this.editor = editor;
40 | this.renderer = renderer;
41 | this.inputController = inputController;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Rect;
8 | import android.graphics.Typeface;
9 | import android.util.AttributeSet;
10 | import android.view.View;
11 | import android.widget.FrameLayout;
12 |
13 | import com.myscript.iink.Editor;
14 | import com.myscript.iink.IRenderTarget;
15 | import com.myscript.iink.Renderer;
16 | import com.myscript.iink.graphics.ICanvas;
17 | import com.myscript.iink.graphics.Point;
18 |
19 | import java.util.Collections;
20 | import java.util.EnumSet;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 |
25 | import androidx.annotation.NonNull;
26 | import androidx.annotation.Nullable;
27 |
28 | public class EditorView extends FrameLayout implements IRenderTarget, InputController.ViewListener
29 | {
30 | private int viewWidth;
31 | private int viewHeight;
32 |
33 | @Nullable
34 | private Renderer renderer;
35 | @Nullable
36 | private Editor editor;
37 | @Nullable
38 | private ImageLoader imageLoader;
39 | @NonNull
40 | private final OfflineSurfaceManager offlineSurfaceManager;
41 | @Nullable
42 | private LayerView layerView;
43 |
44 | private Map typefaceMap = new HashMap<>();
45 | @NonNull
46 | private List extraBrushConfigs = Collections.emptyList();
47 |
48 | public EditorView(Context context)
49 | {
50 | this(context, null, 0);
51 | }
52 |
53 | public EditorView(Context context, @Nullable AttributeSet attrs)
54 | {
55 | this(context, attrs, 0);
56 | }
57 |
58 | public EditorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
59 | {
60 | super(context, attrs, defStyleAttr);
61 | offlineSurfaceManager = new OfflineSurfaceManager();
62 | }
63 |
64 | @Override
65 | protected void onFinishInflate()
66 | {
67 | super.onFinishInflate();
68 |
69 | // find child render views and initialize them
70 | for (int i = 0, count = getChildCount(); i < count; ++i)
71 | {
72 | View view = getChildAt(i);
73 | if (view instanceof LayerView)
74 | {
75 | layerView = (LayerView) view;
76 |
77 | layerView.setRenderTarget(this);
78 | if (editor != null)
79 | {
80 | layerView.setEditor(editor);
81 | }
82 | if (imageLoader != null)
83 | {
84 | layerView.setImageLoader(imageLoader);
85 | }
86 |
87 | layerView.setCustomTypefaces(typefaceMap);
88 | layerView.setOfflineSurfaceManager(offlineSurfaceManager);
89 | }
90 | }
91 | }
92 |
93 | /**
94 | * editor creation logic to this view.
95 | *
96 | * @param editor the editor to bind to this view to edit content.
97 | */
98 | public void setEditor(@Nullable Editor editor)
99 | {
100 | this.editor = editor;
101 | if (editor != null)
102 | {
103 | renderer = editor.getRenderer();
104 | if (layerView != null)
105 | {
106 | layerView.setEditor(editor);
107 | }
108 | if (viewWidth > 0 && viewHeight > 0)
109 | {
110 | editor.setViewSize(viewWidth, viewHeight);
111 | }
112 | invalidate(renderer, EnumSet.allOf(IRenderTarget.LayerType.class));
113 | }
114 | else
115 | {
116 | renderer = null;
117 | }
118 | }
119 |
120 | @Nullable
121 | public Editor getEditor()
122 | {
123 | return editor;
124 | }
125 |
126 | @Nullable
127 | public Renderer getRenderer()
128 | {
129 | return renderer;
130 | }
131 |
132 | public void setExtraBrushConfigs(@NonNull List extraBrushConfigs)
133 | {
134 | if (editor != null)
135 | {
136 | throw new IllegalStateException("Please set the extra brush configs of the EditorView before binding the editor (through EditorView.setEngine() or EditorView.setEditor())");
137 | }
138 |
139 | this.extraBrushConfigs = extraBrushConfigs;
140 | for (int i = 0, count = getChildCount(); i < count; ++i)
141 | {
142 | View view = getChildAt(i);
143 | if (view instanceof LayerView)
144 | {
145 | LayerView layerView = (LayerView) view;
146 | layerView.setExtraBrushConfigs(extraBrushConfigs);
147 | }
148 | }
149 | }
150 |
151 | @NonNull
152 | public List getExtraBrushConfigs()
153 | {
154 | return extraBrushConfigs;
155 | }
156 |
157 | public void setImageLoader(ImageLoader imageLoader)
158 | {
159 | this.imageLoader = imageLoader;
160 |
161 | // transfer image loader to render views
162 | if (layerView != null)
163 | {
164 | layerView.setImageLoader(imageLoader);
165 | }
166 | }
167 |
168 | @Nullable
169 | public ImageLoader getImageLoader()
170 | {
171 | return imageLoader;
172 | }
173 |
174 | public void setTypefaces(@NonNull Map typefaceMap)
175 | {
176 | if (editor != null)
177 | {
178 | throw new IllegalStateException("Please set the typeface map of the EditorView before binding the editor (through EditorView.setEngine() or EditorView.setEditor())");
179 | }
180 |
181 | this.typefaceMap = typefaceMap;
182 | for (int i = 0, count = getChildCount(); i < count; ++i)
183 | {
184 | View view = getChildAt(i);
185 | if (view instanceof LayerView)
186 | {
187 | LayerView layerView = (LayerView) view;
188 | layerView.setCustomTypefaces(typefaceMap);
189 | }
190 | }
191 | }
192 |
193 | public Map getTypefaces()
194 | {
195 | return typefaceMap;
196 | }
197 |
198 | @Override
199 | protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight)
200 | {
201 | viewWidth = newWidth;
202 | viewHeight = newHeight;
203 |
204 | if (editor != null)
205 | {
206 | editor.setViewSize(newWidth, newHeight);
207 | invalidate(renderer, EnumSet.allOf(IRenderTarget.LayerType.class));
208 | }
209 |
210 | super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
211 | }
212 |
213 | @Override
214 | public final void invalidate(@NonNull Renderer renderer, @NonNull EnumSet layers)
215 | {
216 | invalidate(renderer, 0, 0, viewWidth, viewHeight, layers);
217 | }
218 |
219 | @Override
220 | public final void invalidate(@NonNull Renderer renderer, int x, int y, int width, int height, @NonNull EnumSet layers)
221 | {
222 | if (width <= 0 || height <= 0)
223 | return;
224 |
225 | if (layerView != null)
226 | {
227 | layerView.update(renderer, x, y, width, height);
228 | }
229 | }
230 |
231 | @Override
232 | public void invalidate()
233 | {
234 | super.invalidate();
235 | invalidate(renderer, EnumSet.allOf(LayerType.class));
236 | }
237 |
238 | @SuppressWarnings("deprecation")
239 | @Override
240 | public void invalidate(int l, int t, int r, int b)
241 | {
242 | super.invalidate(l, t, r, b);
243 | invalidate(renderer, l, t, r - l, b - t, EnumSet.allOf(LayerType.class));
244 | }
245 |
246 | @SuppressWarnings("deprecation")
247 | @Override
248 | public void invalidate(Rect dirty)
249 | {
250 | super.invalidate(dirty);
251 | int l = dirty.left;
252 | int t = dirty.top;
253 | int w = dirty.width();
254 | int h = dirty.height();
255 | invalidate(renderer, l, t, w, h, EnumSet.allOf(LayerType.class));
256 | }
257 |
258 | @Override
259 | public boolean supportsOffscreenRendering()
260 | {
261 | return true;
262 | }
263 |
264 | @Override
265 | public float getPixelDensity()
266 | {
267 | return 1f;
268 | }
269 |
270 | @Override
271 | public int createOffscreenRenderSurface(int width, int height, boolean alphaOnly)
272 | {
273 | return offlineSurfaceManager.create(width, height, alphaOnly);
274 | }
275 |
276 | @Override
277 | public void releaseOffscreenRenderSurface(int offscreenID)
278 | {
279 | offlineSurfaceManager.release(offscreenID);
280 | }
281 |
282 | @Override
283 | public ICanvas createOffscreenRenderCanvas(int offscreenID)
284 | {
285 | if (renderer == null)
286 | throw new IllegalStateException("Cannot create offscreen render canvas if renderer is null");
287 | if (offscreenID < 0)
288 | return null;
289 | Bitmap offlineBitmap = offlineSurfaceManager.getBitmap(offscreenID);
290 | if (offlineBitmap == null)
291 | return null;
292 | android.graphics.Canvas canvas = new android.graphics.Canvas(offlineBitmap);
293 | return new Canvas(canvas, extraBrushConfigs, typefaceMap, imageLoader, offlineSurfaceManager, renderer.getDpiX(), renderer.getDpiY());
294 | }
295 |
296 | @Override
297 | public void showScrollbars()
298 | {
299 | int viewHeightPx = editor.getViewHeight();
300 | int viewWidthPx = editor.getViewWidth();
301 | Point topLeftPx = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
302 | Point bottomRightPx = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
303 | editor.clampViewOffset(topLeftPx);
304 | editor.clampViewOffset(bottomRightPx);
305 | float pageHeightPx = bottomRightPx.y - topLeftPx.y + viewHeightPx;
306 | float pageWidthPx = bottomRightPx.x - topLeftPx.x + viewWidthPx;
307 | layerView.setScrollbar(renderer, viewWidthPx, (int) pageWidthPx, (int) topLeftPx.x, viewHeightPx, (int) pageHeightPx, (int) topLeftPx.y);
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.annotation.SuppressLint;
6 | import android.content.res.ColorStateList;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Path;
10 | import android.graphics.Rect;
11 | import android.graphics.RectF;
12 | import android.graphics.Typeface;
13 | import android.os.Build;
14 | import android.text.Layout;
15 | import android.text.SpannableString;
16 | import android.text.Spanned;
17 | import android.text.StaticLayout;
18 | import android.text.TextPaint;
19 | import android.text.style.MetricAffectingSpan;
20 | import android.text.style.TextAppearanceSpan;
21 | import android.util.DisplayMetrics;
22 | import android.util.TypedValue;
23 |
24 | import androidx.annotation.NonNull;
25 | import androidx.collection.LruCache;
26 | import androidx.core.util.Pair;
27 |
28 | import com.myscript.iink.graphics.Rectangle;
29 | import com.myscript.iink.graphics.Style;
30 | import com.myscript.iink.text.GlyphMetrics;
31 | import com.myscript.iink.text.IFontMetricsProvider;
32 | import com.myscript.iink.text.Text;
33 | import com.myscript.iink.text.TextSpan;
34 |
35 | import java.util.Objects;
36 | import java.util.Map;
37 |
38 | public class FontMetricsProvider implements IFontMetricsProvider
39 | {
40 | private static class FontKey
41 | {
42 | @NonNull
43 | final String family;
44 | final int style;
45 | final int size;
46 |
47 | public FontKey(@NonNull String family, int style, int size)
48 | {
49 | this.family = family;
50 | this.style = style;
51 | this.size = size;
52 | }
53 |
54 | @Override
55 | public int hashCode()
56 | {
57 | return Objects.hash(family, style, size);
58 | }
59 |
60 | @Override
61 | public boolean equals(Object obj)
62 | {
63 | if (obj == this)
64 | return true;
65 | if (!(obj instanceof FontKey))
66 | return false;
67 | FontKey other = (FontKey)obj;
68 | return style == other.style && size == other.size && family.equals(other.family);
69 | }
70 | }
71 |
72 | private interface IValueProvider
73 | {
74 | T get();
75 | }
76 |
77 | private final DisplayMetrics displayMetrics;
78 | private final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
79 | private final TextPaint paint_ = new TextPaint(Paint.ANTI_ALIAS_FLAG);
80 | private final Path charPath = new Path();
81 | private final RectF charBox = new RectF();
82 | private final Map typefaceMap;
83 |
84 | private final LruCache, GlyphMetrics> glyphMetricsCache = new LruCache<>(1024);
85 |
86 | public FontMetricsProvider(DisplayMetrics displayMetrics, Map typefaceMap)
87 | {
88 | this.displayMetrics = displayMetrics;
89 | this.typefaceMap = typefaceMap;
90 | }
91 |
92 | private float y_mm2px(float mm)
93 | {
94 | return (mm / 25.4f) * displayMetrics.ydpi;
95 | }
96 |
97 | private float x_px2mm(float px)
98 | {
99 | return 25.4f * (px / displayMetrics.xdpi);
100 | }
101 |
102 | private float y_px2mm(float px)
103 | {
104 | return 25.4f * (px / displayMetrics.ydpi);
105 | }
106 |
107 | private void updatePaint(int[] fontSizes, Typeface[] typefaces, int spanIndex)
108 | {
109 | paint.setTypeface(typefaces[spanIndex]);
110 | paint.setTextSize(fontSizes[spanIndex]);
111 | }
112 |
113 | @Override
114 | public Rectangle[] getCharacterBoundingBoxes(@NonNull Text text, TextSpan[] spans)
115 | {
116 | GlyphMetrics[] metrics = getGlyphMetrics(text, spans);
117 | Rectangle[] charBoxes = new Rectangle[metrics.length];
118 | for (int i = 0; i < charBoxes.length; ++i)
119 | charBoxes[i] = new Rectangle(metrics[i].boundingBox);
120 | return charBoxes;
121 | }
122 |
123 | @Override
124 | public float getFontSizePx(Style style)
125 | {
126 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, style.getFontSize(), displayMetrics);
127 | }
128 |
129 | @Override
130 | public boolean supportsGlyphMetrics()
131 | {
132 | return true;
133 | }
134 |
135 | private GlyphMetrics getGlyphMetrics(FontKey fontKey, String glyph, IValueProvider valueProvider)
136 | {
137 | Pair key = new Pair<>(fontKey, glyph);
138 | synchronized (glyphMetricsCache)
139 | {
140 | GlyphMetrics metrics = glyphMetricsCache.get(key);
141 | if (metrics == null)
142 | {
143 | metrics = valueProvider.get();
144 | glyphMetricsCache.put(key, metrics);
145 | }
146 | return metrics;
147 | }
148 | }
149 |
150 | @SuppressLint("NewApi")
151 | @SuppressWarnings("deprecation")
152 | @NonNull
153 | private StaticLayout getLayout(@NonNull SpannableString string)
154 | {
155 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M)
156 | {
157 | return StaticLayout.Builder.obtain(string, 0, string.length(), paint_, Integer.MAX_VALUE).setIncludePad(false).build();
158 | }
159 | else
160 | {
161 | return new StaticLayout(string, paint_, Integer.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
162 | }
163 | }
164 |
165 | @Override
166 | public synchronized GlyphMetrics[] getGlyphMetrics(Text text, TextSpan[] spans)
167 | {
168 | final String label = text.getLabel();
169 |
170 | // Create spannable string that represent text with spans (ignoring color)
171 | final SpannableString string = new SpannableString(label);
172 |
173 | ColorStateList fontColor = ColorStateList.valueOf(Color.BLACK);
174 | ColorStateList fontLinkColor = null;
175 |
176 | int[] fontSizes = new int[spans.length];
177 | Typeface[] typefaces = new Typeface[spans.length];
178 |
179 | for (int i = 0; i < spans.length; i++)
180 | {
181 | Style style = spans[i].style;
182 |
183 | String fontFamily = style.getFontFamily();
184 |
185 | int typefaceStyle = FontUtils.getTypefaceStyle(style);
186 | int fontSize = Math.round(y_mm2px(style.getFontSize()));
187 | fontSize = Math.max(fontSize, 1);
188 | int start = text.getGlyphBeginAt(spans[i].beginPosition);
189 | int end = text.getGlyphEndAt(spans[i].endPosition - 1);
190 |
191 | MetricAffectingSpan span;
192 | Typeface typeface = FontUtils.getTypeface(typefaceMap, fontFamily, style.getFontStyle(), style.getFontVariant(), style.getFontWeight());
193 | if (typeface == null)
194 | span = new TextAppearanceSpan(fontFamily, typefaceStyle, fontSize, fontColor, fontLinkColor);
195 | else
196 | span = new CustomTextSpan(typeface, typefaceStyle, fontSize, fontColor, fontLinkColor);
197 |
198 | string.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
199 |
200 | fontSizes[i] = fontSize;
201 | typefaces[i] = typeface;
202 | }
203 |
204 | int glyphCount = text.getGlyphCount();
205 | GlyphMetrics[] charBoxes = new GlyphMetrics[glyphCount];
206 |
207 | // Layout text
208 | int spanEnd = -1;
209 | int spanIndex = -1;
210 | FontKey fontKey = null;
211 |
212 | final StaticLayout layout = getLayout(string);
213 | if (layout.getLineCount() != 1)
214 | {
215 | throw new RuntimeException();
216 | }
217 |
218 | for (int i = 0; i < glyphCount; ++i)
219 | {
220 | if (i >= spanEnd)
221 | {
222 | ++spanIndex;
223 | fontKey = new FontKey(spans[spanIndex].style.getFontFamily(), typefaces[spanIndex].getStyle(), fontSizes[spanIndex]);
224 | spanEnd = spans[spanIndex].endPosition;
225 | updatePaint(fontSizes, typefaces, spanIndex);
226 | }
227 |
228 | final int start = text.getGlyphBeginAt(i);
229 | final int end = text.getGlyphEndAt(i);
230 | String glyph = label.substring(start, end);
231 | GlyphMetrics m = getGlyphMetrics(fontKey, glyph, () -> {
232 | paint.getTextPath(label, start, end, 0, 0, charPath);
233 | charPath.computeBounds(charBox, true);
234 |
235 | // some glyphs paths may not be available (like for emojis)
236 | // in that case we use simple text bounds, which are less precise but correct
237 | if (charBox.isEmpty() && !label.equals(" "))
238 | {
239 | Rect box = new Rect();
240 | paint.getTextBounds(label, start, end, box);
241 | charBox.left = box.left;
242 | charBox.top = box.top;
243 | charBox.right = box.right;
244 | charBox.bottom = box.bottom;
245 | }
246 |
247 | float x = x_px2mm(charBox.left);
248 | float y = y_px2mm(charBox.top);
249 | float width = x_px2mm(charBox.width());
250 | float height = y_px2mm(charBox.height());
251 |
252 | float leftSideBearing = -x;
253 | float rightSideBearing;
254 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
255 | {
256 | float advance = paint.getRunAdvance(label, start, end, start, end, false, end);
257 | rightSideBearing = x_px2mm(advance - charBox.right);
258 | }
259 | else
260 | {
261 | rightSideBearing = 0; // expect degraded reflow of typeset text
262 | }
263 |
264 | return new GlyphMetrics(x, y, width, height, leftSideBearing, rightSideBearing);
265 | });
266 |
267 | charBoxes[i] = new GlyphMetrics(x_px2mm(layout.getPrimaryHorizontal(start)) + m.boundingBox.x, m.boundingBox.y, m.boundingBox.width, m.boundingBox.height, m.leftSideBearing, m.rightSideBearing);
268 | }
269 |
270 | return charBoxes;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontUtils.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.content.res.AssetManager;
6 | import android.graphics.Typeface;
7 | import android.util.Log;
8 |
9 | import com.myscript.iink.graphics.Style;
10 | import com.myscript.util.TTFAnalyzer;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.util.HashMap;
16 | import java.util.Map;
17 |
18 | public final class FontUtils
19 | {
20 | private FontUtils()
21 | {
22 | // utility class
23 | }
24 |
25 | public static Map loadFontsFromAssets(AssetManager assetManager)
26 | {
27 | return loadFontsFromAssets(assetManager, "fonts");
28 | }
29 |
30 | public static Map loadFontsFromAssets(AssetManager assetManager, String assetsDir)
31 | {
32 | Map typefaceMap = new HashMap<>();
33 | try
34 | {
35 | String[] files = assetManager.list(assetsDir);
36 | for (String filename : files)
37 | {
38 | if (!filename.endsWith(".ttf") && !filename.endsWith(".otf"))
39 | continue;
40 |
41 | String fontPath = assetsDir + File.separatorChar + filename;
42 | String fontFamily = FontUtils.getFontFamily(assetManager, fontPath);
43 | final Typeface typeface = Typeface.createFromAsset(assetManager, fontPath);
44 | if (fontFamily != null && typeface != null)
45 | {
46 | typefaceMap.put(fontFamily, typeface);
47 | }
48 | }
49 | }
50 | catch (IOException e)
51 | {
52 | Log.e("FontUtils", "Failed to list fonts from assets", e);
53 | return null;
54 | }
55 | return typefaceMap;
56 | }
57 |
58 | public static int getTypefaceStyle(String fontStyle, String fontVariant, int fontWeight)
59 | {
60 | // Looking at Typeface documentation we see that NORMAL = 0, BOLD = 1, ITALIC = 2, and
61 | // BOLD_ITALIC = 3, so Android font style is a simple BOLD and ITALIC bit flag combination:
62 | int typefaceStyle = Typeface.NORMAL;
63 | if (fontWeight >= 700)
64 | typefaceStyle |= Typeface.BOLD;
65 | if ("italic".equals(fontStyle))
66 | typefaceStyle |= Typeface.ITALIC;
67 | return typefaceStyle;
68 | }
69 |
70 | public static int getTypefaceStyle(Style style)
71 | {
72 | return getTypefaceStyle(style.getFontStyle(), style.getFontVariant(), style.getFontWeight());
73 | }
74 |
75 | public static Typeface getTypeface(String fontFamily, int typefaceStyle)
76 | {
77 | return Typeface.create(fontFamily, typefaceStyle);
78 | }
79 |
80 | public static Typeface getTypeface(String fontFamily, String fontStyle, String fontVariant, int fontWeight)
81 | {
82 | return getTypeface(fontFamily, getTypefaceStyle(fontStyle, fontVariant, fontWeight));
83 | }
84 |
85 | public static Typeface getTypeface(Style style)
86 | {
87 | return getTypeface(style.getFontFamily(), getTypefaceStyle(style));
88 | }
89 |
90 | public static Typeface getTypeface(Map typefaceMap, String fontFamily, String fontStyle, String fontVariant, int fontWeight)
91 | {
92 | Typeface ref = typefaceMap.get(fontFamily);
93 |
94 | if (ref == null)
95 | return getTypeface(fontFamily, getTypefaceStyle(fontStyle, fontVariant, fontWeight));
96 |
97 | return Typeface.create(ref, FontUtils.getTypefaceStyle(fontStyle, fontVariant, fontWeight));
98 | }
99 |
100 | public static String getFontFamily(AssetManager assets, String fontPath)
101 | {
102 | try (InputStream in = assets.open(fontPath))
103 | {
104 | return TTFAnalyzer.getTtfFontName(in);
105 | }
106 | catch (IOException e)
107 | {
108 | // Most likely a corrupted font file
109 | return null;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FrameTimeEstimator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Adapted from:
18 | // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:input/input-motionprediction/
19 |
20 | package com.myscript.iink.uireferenceimplementation;
21 |
22 | import android.content.Context;
23 | import android.os.Build;
24 | import android.view.Display;
25 | import android.view.WindowManager;
26 |
27 | import androidx.annotation.DoNotInline;
28 | import androidx.annotation.NonNull;
29 | import androidx.annotation.RequiresApi;
30 |
31 | /**
32 | * Get screen fastest refresh rate (in ms)
33 | */
34 | @SuppressWarnings("deprecation")
35 | public class FrameTimeEstimator {
36 | private static final float LEGACY_FRAME_TIME_MS = 16f;
37 | private static final float MS_IN_A_SECOND = 1000f;
38 |
39 | static public float getFrameTime(@NonNull Context context)
40 | {
41 | return getFastestFrameTimeMs(context);
42 | }
43 |
44 | private static Display getDisplayForContext(Context context)
45 | {
46 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
47 | {
48 | return Api30Impl.getDisplayForContext(context);
49 | }
50 | return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
51 | }
52 |
53 | private static float getFastestFrameTimeMs(Context context)
54 | {
55 | Display defaultDisplay = getDisplayForContext(context);
56 |
57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
58 | {
59 | return Api23Impl.getFastestFrameTimeMs(defaultDisplay);
60 | }
61 | else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
62 | {
63 | return Api21Impl.getFastestFrameTimeMs(defaultDisplay);
64 | }
65 | else
66 | {
67 | return LEGACY_FRAME_TIME_MS;
68 | }
69 | }
70 |
71 | @SuppressWarnings("deprecation")
72 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
73 | static class Api21Impl
74 | {
75 | private Api21Impl()
76 | {
77 | // Not instantiable
78 | }
79 |
80 | @DoNotInline
81 | static float getFastestFrameTimeMs(Display display)
82 | {
83 | float[] refreshRates = display.getSupportedRefreshRates();
84 | float largestRefreshRate = refreshRates[0];
85 |
86 | for (int c = 1; c < refreshRates.length; c++)
87 | {
88 | if (refreshRates[c] > largestRefreshRate)
89 | largestRefreshRate = refreshRates[c];
90 | }
91 |
92 | return MS_IN_A_SECOND / largestRefreshRate;
93 | }
94 | }
95 |
96 | @RequiresApi(Build.VERSION_CODES.M)
97 | static class Api23Impl
98 | {
99 | private Api23Impl()
100 | {
101 | // Not instantiable
102 | }
103 |
104 | @DoNotInline
105 | static float getFastestFrameTimeMs(Display display)
106 | {
107 | Display.Mode[] displayModes = display.getSupportedModes();
108 | float largestRefreshRate = displayModes[0].getRefreshRate();
109 |
110 | for (int c = 1; c < displayModes.length; c++)
111 | {
112 | float currentRefreshRate = displayModes[c].getRefreshRate();
113 | if (currentRefreshRate > largestRefreshRate)
114 | largestRefreshRate = currentRefreshRate;
115 | }
116 |
117 | return MS_IN_A_SECOND / largestRefreshRate;
118 | }
119 | }
120 |
121 | @RequiresApi(Build.VERSION_CODES.R)
122 | static class Api30Impl
123 | {
124 | private Api30Impl()
125 | {
126 | // Not instantiable
127 | }
128 |
129 | @DoNotInline
130 | static Display getDisplayForContext(Context context)
131 | {
132 | return context.getDisplay();
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IInputControllerListener.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import androidx.annotation.Nullable;
6 |
7 | public interface IInputControllerListener
8 | {
9 | boolean onLongPress(final float x, final float y, final @Nullable String contentBlockId);
10 | }
11 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ISizeListener.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | public interface ISizeListener
6 | {
7 | void sizeChanged(int newWidth, int newHeight);
8 | }
9 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Color;
8 | import android.util.Log;
9 | import android.util.Pair;
10 | import android.util.LruCache;
11 |
12 | import java.io.File;
13 |
14 | import com.myscript.iink.Editor;
15 |
16 | import androidx.annotation.NonNull;
17 |
18 |
19 | public class ImageLoader
20 | {
21 | @NonNull
22 | private final Editor editor;
23 | LruCache cache;
24 | static final float CACHE_MAX_MEMORY_RATIO = 1.f / 8; // in ]0, 1[
25 |
26 | public ImageLoader(@NonNull Editor editor)
27 | {
28 | this.editor = editor;
29 |
30 | // Use a part of the maximum available memory to define the cache's size (in Bytes)
31 | int cacheSize = (int) (Runtime.getRuntime().maxMemory() * CACHE_MAX_MEMORY_RATIO);
32 |
33 | this.cache = new LruCache(cacheSize)
34 | {
35 | @Override
36 | protected int sizeOf(String key, Bitmap value)
37 | {
38 | // The cache size will be measured in bytes rather than number of items
39 | return value.getByteCount();
40 | }
41 | @Override
42 | protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue)
43 | {
44 | if (evicted && oldValue != null && !oldValue.isRecycled())
45 | {
46 | oldValue.recycle();
47 | }
48 | super.entryRemoved(evicted, key, oldValue, newValue);
49 | }
50 | };
51 | }
52 |
53 | @NonNull
54 | public Editor getEditor()
55 | {
56 | return editor;
57 | }
58 |
59 | public synchronized Bitmap getImage(final String url, final String mimeType, final int dstWidth, final int dstHeight)
60 | {
61 | Bitmap image = cache.get(url);
62 | if (image != null)
63 | return image; // found
64 |
65 | Pair newImage = renderObject(url, mimeType, dstWidth, dstHeight);
66 |
67 | if (newImage.second) // Not dummy
68 | {
69 | int imageSize = newImage.first.getByteCount();
70 | if (imageSize > cache.maxSize())
71 | {
72 | Log.w("ImageLoader", "Image too big for cache: resizing cache ("
73 | + imageSize / (1024.f * 1024.f) + "MB > " + cache.maxSize() / (1024.f * 1024.f) + "MB)");
74 | cache.resize(imageSize);
75 | }
76 |
77 | cache.put(url, newImage.first);
78 | }
79 |
80 | return newImage.first;
81 | }
82 |
83 | private Pair renderObject(String url, String mimeType, int dstWidth, int dstHeight)
84 | {
85 | if (mimeType.startsWith("image/"))
86 | {
87 | try
88 | {
89 | File file = new File(url);
90 | Bitmap image = BitmapFactory.decodeFile(file.getAbsolutePath());
91 |
92 | if (image != null)
93 | {
94 | // Reduce size if larger than destination
95 | if (image.getWidth() > dstWidth || image.getHeight() > dstHeight)
96 | {
97 | Bitmap scaledImage = Bitmap.createScaledBitmap(image, dstWidth, dstHeight, false);
98 |
99 | if (scaledImage != null)
100 | return Pair.create(scaledImage, true);
101 | else
102 | Log.e("ImageLoader", "Unable to scale image: using placeholder image");
103 | }
104 | else
105 | {
106 | return Pair.create(image, true);
107 | }
108 | }
109 | else
110 | {
111 | Log.e("ImageLoader", "Unable to decode file: using placeholder image");
112 | }
113 | }
114 | catch (Exception e)
115 | {
116 | // Error: use fallback bitmap
117 | Log.e("ImageLoader", "Unexpected exception: using placeholder image", e);
118 | }
119 | catch (OutOfMemoryError e)
120 | {
121 | // Error: use fallback bitmap
122 | Log.w("ImageLoader", "Out of memory: unable to load image: using placeholder instead", e);
123 | }
124 | }
125 |
126 | // Fallback 1x1 bitmap
127 | Bitmap image = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
128 | if (image != null)
129 | image.eraseColor(Color.WHITE);
130 | else
131 | Log.e("ImageLoader", "Unable to render image nor placeholder");
132 |
133 | return Pair.create(image, false);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.graphics.Bitmap;
6 | import android.graphics.Color;
7 | import android.graphics.Typeface;
8 |
9 | import com.myscript.iink.IImagePainter;
10 | import com.myscript.iink.graphics.ICanvas;
11 |
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.io.OutputStream;
15 | import java.util.Collections;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | import androidx.annotation.ColorInt;
20 | import androidx.annotation.NonNull;
21 |
22 | public class ImagePainter implements IImagePainter
23 | {
24 | private ImageLoader imageLoader = null;
25 | private Map typefaceMap = null;
26 | protected android.graphics.Canvas canvas = null;
27 | private Bitmap bitmap = null;
28 | @NonNull
29 | private final List extraBrushConfigs;
30 | private float dpi = 96.f;
31 | @ColorInt
32 | private int backgroundColor = Color.WHITE;
33 |
34 | public ImagePainter()
35 | {
36 | this(Collections.emptyList());
37 | }
38 |
39 | public ImagePainter(@NonNull List extraBrushConfigs)
40 | {
41 | this.extraBrushConfigs = extraBrushConfigs;
42 | }
43 |
44 | public void setImageLoader(ImageLoader imageLoader)
45 | {
46 | this.imageLoader = imageLoader;
47 | }
48 |
49 | public void setTypefaceMap(Map typefaceMap)
50 | {
51 | this.typefaceMap = typefaceMap;
52 | }
53 |
54 | public void setBackgroundColor(@ColorInt int backgroundColor)
55 | {
56 | this.backgroundColor = backgroundColor;
57 | }
58 |
59 | @Override
60 | public ICanvas createCanvas()
61 | {
62 | return new Canvas(canvas, extraBrushConfigs, typefaceMap, imageLoader, dpi, dpi);
63 | }
64 |
65 | @Override
66 | public void prepareImage(int width, int height, float dpi)
67 | {
68 | if (bitmap != null)
69 | {
70 | bitmap.recycle();
71 | bitmap = null;
72 | }
73 | canvas = null;
74 |
75 | this.dpi = dpi;
76 | bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
77 | canvas = new android.graphics.Canvas(bitmap);
78 | canvas.drawARGB(Color.alpha(backgroundColor), Color.red(backgroundColor), Color.green(backgroundColor), Color.blue(backgroundColor));
79 | }
80 |
81 | @Override
82 | public void saveImage(@NonNull String path) throws IOException
83 | {
84 | if (bitmap == null)
85 | return;
86 |
87 | try
88 | {
89 | int quality = 100; // max quality
90 | Bitmap.CompressFormat format;
91 |
92 | if (path.endsWith(".png"))
93 | format = Bitmap.CompressFormat.PNG;
94 | else if (path.endsWith(".jpg") || path.endsWith(".jpeg") || path.endsWith(".jpe"))
95 | format = Bitmap.CompressFormat.JPEG;
96 | else
97 | throw new IOException("No appropriate image format found");
98 |
99 | try (OutputStream stream = new FileOutputStream(path))
100 | {
101 | bitmap.compress(format, quality, stream);
102 | }
103 | }
104 | catch (Exception e)
105 | {
106 | throw new IOException("Cannot save image");
107 | }
108 | finally
109 | {
110 | bitmap.recycle();
111 | }
112 |
113 | bitmap = null;
114 | canvas = null;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/JiixDefinitions.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import com.google.gson.annotations.SerializedName;
6 |
7 | /**
8 | * Class definition used for Gson parsing
9 | */
10 | public class JiixDefinitions
11 | {
12 | public static class Padding
13 | {
14 | public float left;
15 | public float right;
16 | }
17 |
18 | public static class Word
19 | {
20 | public static String LABEL_FIELDNAME = "label";
21 | public String label;
22 | public String[] candidates;
23 | @SerializedName(value = "reflow-label")
24 | public String reflowlabel;
25 | }
26 |
27 | public static class Result
28 | {
29 | public static String WORDS_FIELDNAME = "words";
30 | public Word[] words;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Color;
8 | import android.graphics.PorterDuff;
9 | import android.graphics.Rect;
10 | import android.graphics.Typeface;
11 | import android.os.Build;
12 | import android.util.AttributeSet;
13 | import android.util.DisplayMetrics;
14 | import android.view.View;
15 |
16 | import com.myscript.iink.Editor;
17 | import com.myscript.iink.IRenderTarget;
18 | import com.myscript.iink.IRenderTarget.LayerType;
19 | import com.myscript.iink.Renderer;
20 |
21 | import java.util.ArrayList;
22 | import java.util.Collections;
23 | import java.util.EnumSet;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | import androidx.annotation.NonNull;
28 | import androidx.annotation.Nullable;
29 |
30 | public class LayerView extends View
31 | {
32 | private final static int MODEL = 0;
33 | private final static int CAPTURE = 1;
34 | private ImageLoader imageLoader;
35 |
36 | @Nullable
37 | private Map typefaceMap;
38 |
39 | @Nullable
40 | private Renderer lastRenderer = null;
41 |
42 | @Nullable
43 | private OfflineSurfaceManager offlineSurfaceManager = null;
44 | @Nullable
45 | private Renderer renderer = null;
46 | @NonNull
47 | private Rect updateArea = new Rect(0, 0, 0, 0);
48 | @NonNull
49 | private Rect localUpdateArea = new Rect(0, 0, 0, 0);
50 | @Nullable
51 | private Bitmap bitmap = null; // for API < 28
52 | @Nullable
53 | private android.graphics.Canvas sysCanvas = null; // for API < 28
54 | @Nullable
55 | private Canvas iinkCanvas = null;
56 | @NonNull
57 | private List extraBrushConfigs = Collections.emptyList();
58 | private int pageWidth = 0;
59 | private int pageHeight = 0;
60 | private int viewWidth = 0;
61 | private int viewHeight = 0;
62 | private int canvasWidth = 0;
63 | private int canvasHeight = 0;
64 | private int xMin = 0;
65 | private int yMin = 0;
66 |
67 | public LayerView(Context context)
68 | {
69 | this(context, null, 0);
70 | }
71 |
72 | public LayerView(Context context, @Nullable AttributeSet attrs)
73 | {
74 | this(context, attrs, 0);
75 | }
76 |
77 | public LayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
78 | {
79 | super(context, attrs, defStyleAttr);
80 | }
81 |
82 | public void setRenderTarget(IRenderTarget renderTarget)
83 | {
84 | // do not need the renderTarget
85 | }
86 |
87 | public void setExtraBrushConfigs(@NonNull List extraBrushConfigs)
88 | {
89 | this.extraBrushConfigs = extraBrushConfigs;
90 | }
91 |
92 | public void setOfflineSurfaceManager(@Nullable OfflineSurfaceManager offlineSurfaceManager)
93 | {
94 | this.offlineSurfaceManager = offlineSurfaceManager;
95 | }
96 |
97 | public void setEditor(Editor editor)
98 | {
99 | // do not need the editor
100 | }
101 |
102 | public void setImageLoader(ImageLoader imageLoader)
103 | {
104 | this.imageLoader = imageLoader;
105 | }
106 |
107 | public void setCustomTypefaces(Map typefaceMap)
108 | {
109 | this.typefaceMap = typefaceMap;
110 | }
111 |
112 | @Override
113 | protected final void onDraw(android.graphics.Canvas canvas)
114 | {
115 | super.onDraw(canvas);
116 |
117 | // Draw directly in hardware-accelerated Canvas if scaling is supported (since API 28)
118 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
119 | {
120 | Renderer renderer;
121 | synchronized (this)
122 | {
123 | localUpdateArea.set(0, 0, canvasWidth, canvasHeight);
124 | renderer = lastRenderer;
125 | }
126 |
127 | iinkCanvas.setCanvas(canvas);
128 | prepare(canvas, localUpdateArea);
129 |
130 | try
131 | {
132 | renderer.drawModel(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas);
133 | renderer.drawCaptureStrokes(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas);
134 | }
135 | finally
136 | {
137 | restore(canvas);
138 | }
139 | }
140 | else // Draw in intermediate bitmap
141 | {
142 | Renderer renderer;
143 | synchronized (this)
144 | {
145 | localUpdateArea.set(this.updateArea);
146 | this.updateArea.setEmpty();
147 |
148 | renderer = lastRenderer;
149 | lastRenderer = null;
150 | }
151 |
152 | if (!localUpdateArea.isEmpty())
153 | {
154 | prepare(sysCanvas, localUpdateArea);
155 | try
156 | {
157 | renderer.drawModel(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas);
158 | renderer.drawCaptureStrokes(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas);
159 | }
160 | finally
161 | {
162 | restore(sysCanvas);
163 | }
164 | }
165 |
166 | canvas.drawBitmap(bitmap, 0, 0, null);
167 | }
168 | }
169 |
170 | @Override
171 | protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight)
172 | {
173 | DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
174 |
175 | synchronized (this)
176 | {
177 | // Direct draw
178 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
179 | {
180 | if (iinkCanvas != null)
181 | iinkCanvas.destroy();
182 |
183 | iinkCanvas = new Canvas(null, extraBrushConfigs, typefaceMap, imageLoader, offlineSurfaceManager, metrics.xdpi, metrics.ydpi);
184 | }
185 | else // Bitmap draw
186 | {
187 | if (bitmap != null)
188 | bitmap.recycle();
189 | if (iinkCanvas != null)
190 | iinkCanvas.destroy();
191 |
192 | bitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
193 | sysCanvas = new android.graphics.Canvas(bitmap);
194 | iinkCanvas = new Canvas(sysCanvas, extraBrushConfigs, typefaceMap, imageLoader, offlineSurfaceManager, metrics.xdpi, metrics.ydpi);
195 | }
196 |
197 | iinkCanvas.setClearOnStartDraw(false);
198 | iinkCanvas.setKeepGLRenderer(true);
199 | canvasWidth = newWidth;
200 | canvasHeight = newHeight;
201 | }
202 |
203 | super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
204 | }
205 |
206 | private void prepare(android.graphics.Canvas canvas, Rect clipRect)
207 | {
208 | canvas.save();
209 | canvas.clipRect(clipRect);
210 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
211 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
212 | }
213 |
214 | private void restore(android.graphics.Canvas canvas)
215 | {
216 | canvas.restore();
217 | }
218 |
219 | public final void update(Renderer renderer, int x, int y, int width, int height)
220 | {
221 | boolean emptyArea;
222 |
223 | // Direct draw
224 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
225 | {
226 | Rect updatedArea = new Rect(x, y, x + width, y + height);
227 | synchronized (this)
228 | {
229 | if (canvasWidth > 0 && canvasHeight > 0)
230 | updatedArea.intersect(new Rect(0, 0, canvasWidth, canvasHeight));
231 |
232 | emptyArea = updatedArea.isEmpty();
233 | lastRenderer = renderer;
234 | }
235 | }
236 | else // Bitmap draw
237 | {
238 | synchronized (this)
239 | {
240 | updateArea.union(x, y, x + width, y + height);
241 | if (canvasWidth > 0 && canvasHeight > 0)
242 | updateArea.intersect(new Rect(0, 0, canvasWidth, canvasHeight));
243 |
244 | emptyArea = updateArea.isEmpty();
245 | lastRenderer = renderer;
246 | }
247 | }
248 |
249 | if (!emptyArea)
250 | {
251 | postInvalidate(x, y, x + width, y + height);
252 | }
253 | }
254 |
255 | public void setScrollbar(Renderer renderer, int viewWidthPx, int pageWidthPx, int xMin, int viewHeightPx, int pageHeightPx, int yMin)
256 | {
257 | this.viewWidth = viewWidthPx;
258 | this.pageWidth = pageWidthPx;
259 | this.renderer = renderer;
260 | this.pageHeight = pageHeightPx;
261 | this.viewHeight = viewHeightPx;
262 | this.xMin = xMin;
263 | this.yMin = yMin;
264 | setVerticalScrollBarEnabled(true);
265 | awakenScrollBars();
266 | }
267 |
268 | @Override
269 | protected int computeVerticalScrollRange()
270 | {
271 | return pageHeight;
272 | }
273 |
274 | @Override
275 | protected int computeVerticalScrollExtent()
276 | {
277 | return viewHeight;
278 | }
279 |
280 | @Override
281 | protected int computeVerticalScrollOffset()
282 | {
283 | return renderer != null ? (int) renderer.getViewOffset().y - yMin : 0;
284 | }
285 |
286 | @Override
287 | protected int computeHorizontalScrollRange()
288 | {
289 | return pageWidth;
290 | }
291 |
292 | @Override
293 | protected int computeHorizontalScrollExtent()
294 | {
295 | return viewWidth;
296 | }
297 |
298 | @Override
299 | protected int computeHorizontalScrollOffset()
300 | {
301 | return renderer != null ? (int) renderer.getViewOffset().x - xMin : 0;
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/OfflineSurfaceManager.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import android.graphics.Bitmap;
6 | import android.util.SparseArray;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | public class OfflineSurfaceManager
11 | {
12 | private int nextID = 0;
13 |
14 | private final SparseArray offlineSurfaces = new SparseArray<>();
15 |
16 | public synchronized int create(int width, int height, boolean alphaOnly)
17 | {
18 | int offscreenID = nextID++;
19 |
20 | Bitmap surface;
21 | try
22 | {
23 | surface = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
24 | }
25 | catch (Exception | OutOfMemoryError e)
26 | {
27 | return -1;
28 | }
29 | offlineSurfaces.put(offscreenID, surface);
30 |
31 | return offscreenID;
32 | }
33 |
34 | public synchronized void release(int offscreenID)
35 | {
36 | if (offscreenID < 0)
37 | return;// not a correct id...
38 | Bitmap bitmap = offlineSurfaces.get(offscreenID);
39 | // can be null after a rotation (a new OfflineSurfaceManager can be created during animations)
40 | if (bitmap == null)
41 | return;
42 | bitmap.recycle();
43 | offlineSurfaces.remove(offscreenID);
44 | }
45 |
46 | @Nullable
47 | public synchronized Bitmap getBitmap(int id)
48 | {
49 | // can return null after a rotation (a new OfflineSurfaceManager can be created during animations)
50 | return offlineSurfaces.get(id);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Path.java:
--------------------------------------------------------------------------------
1 | // Copyright @ MyScript. All rights reserved.
2 |
3 | package com.myscript.iink.uireferenceimplementation;
4 |
5 | import com.myscript.iink.graphics.IPath;
6 |
7 | import java.util.EnumSet;
8 |
9 | import androidx.annotation.NonNull;
10 |
11 | public class Path extends android.graphics.Path implements IPath
12 | {
13 |
14 | @NonNull
15 | @Override
16 | public EnumSet unsupportedOperations()
17 | {
18 | return EnumSet.of(OperationType.ARC_OPS);
19 | }
20 |
21 | @Override
22 | public void curveTo(float x1, float y1, float x2, float y2, float x, float y)
23 | {
24 | cubicTo(x1, y1, x2, y2, x, y);
25 | }
26 |
27 | @Override
28 | public void arcTo(float rx, float ry, float phi, boolean fA, boolean fS, float x, float y)
29 | {
30 | throw new UnsupportedOperationException("arcTo");
31 | }
32 |
33 | @Override
34 | public void closePath()
35 | {
36 | close();
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/res/drawable/ic_smart_guide_more.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/res/drawable/smart_guide_bottom_border.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/res/layout/editor_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/res/layout/smart_guide_layout.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
22 |
23 |
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #959da6
4 | #bfbfbf
5 | #f2ffffff
6 |
7 |
--------------------------------------------------------------------------------
/UIReferenceImplementation/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 20sp
5 | 8dp
6 | 8dp
7 |
8 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:8.2.2'
9 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24'
10 | }
11 | }
12 |
13 | subprojects {
14 | afterEvaluate { proj ->
15 | if (proj.hasProperty('android')) {
16 | android {
17 | compileOptions {
18 | sourceCompatibility JavaVersion.VERSION_11
19 | targetCompatibility JavaVersion.VERSION_11
20 | }
21 |
22 | if (proj.hasProperty('kotlin')) {
23 | kotlinOptions {
24 | jvmTarget = JavaVersion.VERSION_11.toString()
25 | }
26 | }
27 |
28 | ndkVersion '21.4.7075529'
29 | }
30 | }
31 | }
32 | }
33 |
34 | allprojects {
35 | repositories {
36 | google()
37 | mavenCentral()
38 | }
39 |
40 | ext {
41 | // configure versions used by dependencies to harmonize and update easily across all components
42 |
43 | // Android SDK
44 | compileSdk = 34
45 | minSdk = 21
46 | targetSdk = 34
47 |
48 | // Android libraries
49 | appcompatVersion = '1.7.0'
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/configurations/Raw Content/drawing.json:
--------------------------------------------------------------------------------
1 | {
2 | "raw-content": {
3 | "line-pattern": "none",
4 | "recognition": {
5 | "types": [ ]
6 | },
7 | "classification": {
8 | "types": [ ]
9 | },
10 | "convert": {
11 | "shape-on-hold": false
12 | },
13 | "shape": {
14 | "snap-axis": [ ]
15 | },
16 | "interactive-blocks": {
17 | "auto-classified": [ ]
18 | },
19 | "eraser": {
20 | "erase-precisely": true,
21 | "dynamic-radius": true
22 | },
23 | "auto-connection": false,
24 | "guides": {
25 | "show": [ ],
26 | "snap": [ ]
27 | },
28 | "pen": {
29 | "gestures": [ ]
30 | },
31 | "rotation": [ "image" ]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/configurations/Raw Content/text_math_shape.json:
--------------------------------------------------------------------------------
1 | {
2 | "raw-content": {
3 | "configuration": {
4 | "analyzer": {
5 | "bundle": "raw-content2",
6 | "name": "standard"
7 | },
8 | "math": {
9 | "bundle": "math2",
10 | "name": "standard"
11 | }
12 | },
13 | "classification": {
14 | "types": [ "text", "shape", "math", "drawing" ]
15 | },
16 | "recognition": {
17 | "types": [ "text", "shape", "math" ]
18 | },
19 | "convert": {
20 | "shape-on-hold": true
21 | },
22 | "shape": {
23 | "snap-axis": [ ]
24 | },
25 | "eraser": {
26 | "erase-precisely": false,
27 | "dynamic-radius": true
28 | },
29 | "auto-connection": false,
30 | "edge": {
31 | "policy": [ ]
32 | },
33 | "interactive-blocks": {
34 | "auto-classified": [ "text", "math", "shape" ],
35 | "feedback": [ "text", "math" ],
36 | "feedback-hints": [ "text", "math" ]
37 | },
38 | "pen": {
39 | "gestures": [ "scratch-out", "strike-through" ]
40 | },
41 | "line-pattern": "grid",
42 | "guides": {
43 | "show": [ "alignment", "text", "square", "square-inside", "image-aspect-ratio", "rotation" ],
44 | "snap": [ "alignment", "text", "square", "square-inside", "image-aspect-ratio", "rotation" ]
45 | }
46 | },
47 | "convert": {
48 | "convert-on-double-tap": false
49 | },
50 | "math": {
51 | "solver": {
52 | "options": "algebraic",
53 | "numerical-computation": [ "at-right-of-equal-sign", "at-question-mark" ],
54 | "enable-syntactic-correction": false,
55 | "display-implicit-multiply": false,
56 | "auto-variable-management": {
57 | "enable": true,
58 | "scoping-policy": "closest"
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/configurations/interactivity.json:
--------------------------------------------------------------------------------
1 | {
2 | "raw-content": {
3 | "classification": {
4 | "types": [ "text", "drawing" ]
5 | },
6 | "recognition": {
7 | "types": [ "text", "math" ]
8 | },
9 | "convert": {
10 | "shape-on-hold": true
11 | },
12 | "shape": {
13 | "snap-axis": [ "triangle", "rectangle", "rhombus", "parallelogram", "ellipse" ]
14 | },
15 | "eraser": {
16 | "erase-precisely": false,
17 | "dynamic-radius": true
18 | },
19 | "auto-connection": true,
20 | "interactive-blocks": {
21 | "feedback": [ "math" ]
22 | },
23 | "pen": {
24 | "gestures": [ "underline", "scratch-out", "strike-through" ]
25 | },
26 | "line-pattern": "grid",
27 | "guides": {
28 | "show": [ "alignment", "text", "square", "square-inside", "image-aspect-ratio", "rotation" ],
29 | "snap": [ "alignment", "text", "square", "square-inside", "image-aspect-ratio", "rotation" ]
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | org.gradle.jvmargs=-Xms256m -Xmx8g -XX:+HeapDumpOnOutOfMemoryError -XX:ReservedCodeCacheSize=2g -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.enableJetifier=false
21 | android.useAndroidX=true
22 | android.nonTransitiveRClass=true
23 | android.defaults.buildfeatures.aidl=false
24 | android.defaults.buildfeatures.buildConfig=false
25 | android.defaults.buildfeatures.compose=false
26 | android.defaults.buildfeatures.prefab=false
27 | android.defaults.buildfeatures.renderScript=false
28 | android.defaults.buildfeatures.resValues=true
29 | android.defaults.buildfeatures.shaders=false
30 | android.defaults.buildfeatures.dataBinding=false
31 | android.defaults.buildfeatures.viewBinding=false
32 | android.buildOnlyTargetAbi=true
33 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyScript/interactive-ink-examples-android/c9f3eac9d7dabba9449d1225ebdc219672baf651/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'MyScript iink'
2 |
3 | include 'Demo'
4 | project(':Demo').dir = file("$settingsDir/Demo")
5 | include ':GetStarted'
6 | project(':GetStarted').dir = file("$settingsDir/GetStarted")
7 | include ':UIReferenceImplementation'
8 | project(':UIReferenceImplementation').dir = file("$settingsDir/UIReferenceImplementation")
9 |
--------------------------------------------------------------------------------