diff --git a/.gitignore b/.gitignore index b724f29..d7d42e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,71 +2,16 @@ # Android ################################################################################ -# Built application files -*.apk -*.ap_ - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files bin/ -gen/ -out/ - -# Gradle files -.gradle/ build/ -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ +# gradle & android specific files +.gradle/ *.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -.idea/caches - -# Keystore files -# Uncomment the following line if you do not want to check your keystore files in. -#*.jks - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json +.idea +local.properties -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md +.DS_Store ################################################################################ # MyScript diff --git a/LICENSES/androidSupportLib.txt b/LICENSES/androidSupportLib.txt new file mode 100644 index 0000000..d955a86 --- /dev/null +++ b/LICENSES/androidSupportLib.txt @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/LICENSES/inter.txt b/LICENSES/inter.txt new file mode 100644 index 0000000..65ec0f9 --- /dev/null +++ b/LICENSES/inter.txt @@ -0,0 +1,94 @@ +Copyright (c) 2016-2020 The Inter Project Authors. +"Inter" is trademark of Rasmus Andersson. +https://github.com/rsms/inter + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/LICENSES/kotlin.txt b/LICENSES/kotlin.txt new file mode 100644 index 0000000..51fca54 --- /dev/null +++ b/LICENSES/kotlin.txt @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/materialDesign.txt b/LICENSES/materialDesign.txt new file mode 100644 index 0000000..d955a86 --- /dev/null +++ b/LICENSES/materialDesign.txt @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/LICENSES/stix.pdf b/LICENSES/stix.pdf new file mode 100644 index 0000000..45f6d31 Binary files /dev/null and b/LICENSES/stix.pdf differ diff --git a/LICENSES/surface-duo-sdk.txt b/LICENSES/surface-duo-sdk.txt new file mode 100644 index 0000000..7965606 --- /dev/null +++ b/LICENSES/surface-duo-sdk.txt @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE \ No newline at end of file diff --git a/README.md b/README.md index 5648ba6..95140cb 100644 --- a/README.md +++ b/README.md @@ -8,37 +8,26 @@ This repository comes in addition with further advanced Android examples that de ## Installation -1. Clone the examples repository `git clone https://github.com/MyScript/iink_sdk-additional-examples-android.git`. +1. Clone the examples repository `git clone https://github.com/MyScript/iink_sdk-additional-examples-android.git`. 2. If you already have a certificate go to next step, else claim to receive the free license to start develop your application by following the first steps of [Getting Started](https://developer.myscript.com/getting-started). -3. Copy this certificate to `certificate/src/main/java/com/myscript/certificate/MyCertificate.java` +3. Copy this certificate to `samples/certificate/src/main/java/com/myscript/certificate/MyCertificate.java` -4. Open `java` folder in Android Studio. +4. Open `samples` folder in Android Studio. ## Various examples This repository provides you with an additional set of ready-to-use examples based on Android: -1. The batch mode sample is an example of how to integrate iink SDK off-screen, without any user interface. It consists in batch processing content, i.e. processing a series of pointer events corresponding to already collected ink strokes and exporting the recognition result. It comes with four pointer events samples that correspond to four different content types "Text", "Math", "Diagram", "Raw Content". When starting the app a dialog will be displayed to choose which type of part you want to proceed. By default those content types are exported in respectively .txt, LaTeX, svg and JIIX formats, but you can choose to export in png by modifying the following line in the MainActivity class: - -~~~#!java - // this is the function where we process exteranl output and export it - // add true if you want to export in png - offScreenProcess(typeOfPart[it]) -~~~ -
- batch sample -
- -NB: you will retrieve data converted in your device internal storage : Android\data\com.myscript.iink.samples.batchmode\files +1. The batch mode sample is an example of how to integrate iink SDK without any user interface. It consists in batch processing content, i.e. processing a series of pointer events corresponding to already collected ink strokes and exporting the recognition result. It comes with four pointer events samples that correspond to four different content types "Text", "Math", "Diagram", "Raw Content". When starting the app a dialog will be displayed to choose which type of part you want to proceed. By default those content types are exported in respectively .txt, LaTeX, svg and JIIX formats, but you can choose to export in png depending on your choice in the configuration window. 2. The exercise assessment illustrates the case when you want to use several writing areas each one for a specific purpose in your application. It is thus using multiple editors, one per writing area, as each one has a different purpose: - First one is dedicated to "Math" content types -- Second one is dedicated to "Math" content types but with user defined gramar which is dynamically loaded at start. +- Second one is dedicated to "Math" content types but with a user defined grammar which is dynamically loaded at start. - Third one is dedicated to "Text" content types -- Fourth one is dedicated to "Diagram" content types -- Fifth one is dedicated to "Draw" content types +- Fourth one is dedicated to "Diagram" content types +- Fifth one is dedicated to "Draw" content types
assessment sample @@ -52,9 +41,43 @@ NB: you will retrieve data converted in your device internal storage : Android\d val partType = "Raw Content" // change to "Text Document" if you want to test ~~~
- search sample + search sample +
+ +4. The write to type example gives you a hint how to implement Scribble like feature relying on the Recognizer API of iink SDK. It is based on a contextless gesture recognition combined with a text recognition. +To run both recognitions simultaneously, two instances of Recognizer are created - one for Gesture recognition and the other one for Text recognition: +~~~#!java + static final float INCH_IN_MILLIMETER = 25.4f; + + float scaleX = INCH_IN_MILLIMETER / displayMetrics.xdpi; + float scaleY = INCH_IN_MILLIMETER / displayMetrics.ydpi; + + Recognizer textRecognizer = engine.createRecognizer(scaleX, scaleY, "Text"); + Recognizer gestureRecognizer = engine.createRecognizer(scaleX, scaleY, "Gesture"); +~~~ + +
+ write to type sample
+NB: the Recognizer API is available from iink SDK 2.1. + +For more details on the sample, read more [here](samples/write-to-type/ReadMe.pdf). + +5. The offscreen-interactivity samples shows how to integrate MyScript iink SDK interactivity with your own rendering. +It drives the content model by sending the captured strokes to iink SDK and keeps the incremental recognition principle and gesture notifications. +This sample uses a third-party rendering library to manage the captured strokes and display its model, and get real-time recognition results and gesture notifications. + +
+ offscreen interacticity sample +
+ +6. The keyboard input sample shows a specific usage for the `Placeholder` feature. In this sample, you'll be able to input text by keyboard within an iink document, with minumum code. +It shows how to manage switching from an Android view to an image managed by iink. + +7. The handwriting generation sample demonstrates the use of MyScript's handwriting generation APIs. It shows how to generate ink from text input, in different styles, and how to create a style from your own handwriting. +To use the handwriting generation, you must [contact our sales team](https://developer.myscript.com/contact-us/on-device-recognition). You will then have access to the "myscript-iink-handwriting-generation" resource package, from which `handwriting_generation` folder must extracted and copied to `samples/hwgeneration/src/main/assets/resources/` + ## Documentation A complete guide is available on [MyScript Developer Portal](https://developer.myscript.com/docs/interactive-ink/latest/android/). diff --git a/UIReferenceImplementation/src/main/AndroidManifest.xml b/UIReferenceImplementation/src/main/AndroidManifest.xml deleted file mode 100644 index 40dfebb..0000000 --- a/UIReferenceImplementation/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IRenderView.java b/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IRenderView.java deleted file mode 100644 index cc1b182..0000000 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IRenderView.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright @ MyScript. All rights reserved. - -package com.myscript.iink.uireferenceimplementation; - -import android.graphics.Typeface; - -import com.myscript.iink.Editor; -import com.myscript.iink.IRenderTarget; -import com.myscript.iink.Renderer; - -import java.util.EnumSet; -import java.util.Map; - -/** - * Implemented by views that render Interactive Ink content. - */ -public interface IRenderView -{ - /** - * Tells whether this view renders a single layer or all the layers - * @return true if this view renders a single layer. - */ - boolean isSingleLayerView(); - - /** - * If the view is a single layer view return the type of the layer it renders. - * @return the type of the rendered layer. - */ - IRenderTarget.LayerType getType(); - - /** - * Sets the render target that owns this render view. - * @param renderTarget the render target. - */ - void setRenderTarget(IRenderTarget renderTarget); - - /** - * Sets the editor that holds the content to render. - * @param editor the editor. - */ - void setEditor(Editor editor); - - /** - * Sets the image loader used to render images. - * @param imageLoader the image loader. - */ - void setImageLoader(ImageLoader imageLoader); - - /** - * Sets the map of custom typefaces to use for text rendering. - * @param typefaceMap the map of custom typefaces. - */ - void setCustomTypefaces(Map typefaceMap); - - /** - * Requests an update of the specified area of the view. - * @param renderer the renderer to be used to render the area. - * @param x the area top x position. - * @param y the area top y position. - * @param width the area width. - * @param height the area height. - * @param layers the layers to update. To be ignored if this is a single layer view. - */ - void update(Renderer renderer, int x, int y, int width, int height, EnumSet layers); -} diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java b/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java deleted file mode 100644 index 00406bc..0000000 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright @ MyScript. All rights reserved. - -package com.myscript.iink.uireferenceimplementation; - -import android.content.Context; -import android.os.SystemClock; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; - -import com.myscript.iink.ContentBlock; -import com.myscript.iink.Editor; -import com.myscript.iink.IRenderTarget; -import com.myscript.iink.PointerEvent; -import com.myscript.iink.PointerEventType; -import com.myscript.iink.PointerType; -import com.myscript.iink.graphics.Point; - -import java.util.EnumSet; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.GestureDetectorCompat; - -public class InputController implements View.OnTouchListener, GestureDetector.OnGestureListener -{ - - public interface ViewListener - { - void showScrollbars(); - } - - public static final int INPUT_MODE_NONE = -1; - public static final int INPUT_MODE_FORCE_PEN = 0; - public static final int INPUT_MODE_FORCE_TOUCH = 1; - public static final int INPUT_MODE_AUTO = 2; - public static final int INPUT_MODE_ERASER = 3; - - private final IRenderTarget renderTarget; - private final Editor editor; - private int _inputMode; - private final GestureDetectorCompat gestureDetector; - private IInputControllerListener _listener; - private final long eventTimeOffset; - @VisibleForTesting - public PointerType iinkPointerType; - private ViewListener _viewListener; - - public InputController(Context context, IRenderTarget renderTarget, Editor editor) - { - this.renderTarget = renderTarget; - this.editor = editor; - _listener = null; - _inputMode = INPUT_MODE_AUTO; - gestureDetector = new GestureDetectorCompat(context, this); - - long rel_t = SystemClock.uptimeMillis(); - long abs_t = System.currentTimeMillis(); - eventTimeOffset = abs_t - rel_t; - } - - public final synchronized void setInputMode(int inputMode) - { - this._inputMode = inputMode; - } - - public final synchronized int getInputMode() - { - return _inputMode; - } - - public final synchronized void setViewListener(ViewListener listener) - { - this._viewListener = listener; - } - - public final synchronized void setListener(IInputControllerListener listener) - { - this._listener = listener; - } - - public final synchronized IInputControllerListener getListener() - { - return _listener; - } - - private boolean handleOnTouchForPointer(MotionEvent event, int actionMask, int pointerIndex) - { - final int pointerId = event.getPointerId(pointerIndex); - final int pointerType = event.getToolType(pointerIndex); - - int inputMode = getInputMode(); - - if (inputMode == INPUT_MODE_FORCE_PEN) - { - iinkPointerType = PointerType.PEN; - } - else if (inputMode == INPUT_MODE_FORCE_TOUCH) - { - iinkPointerType = PointerType.TOUCH; - } - else - { - switch (pointerType) - { - case MotionEvent.TOOL_TYPE_STYLUS: - if (inputMode == INPUT_MODE_ERASER) - { - iinkPointerType = PointerType.ERASER; - } - else - { - iinkPointerType = PointerType.PEN; - } - break; - case MotionEvent.TOOL_TYPE_FINGER: - case MotionEvent.TOOL_TYPE_MOUSE: - iinkPointerType = PointerType.TOUCH; - break; - default: - // unsupported event type - return false; - } - } - - if (iinkPointerType == PointerType.TOUCH) - { - gestureDetector.onTouchEvent(event); - } - - int historySize = event.getHistorySize(); - - switch (actionMask) - { - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_DOWN: - editor.pointerDown(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); - return true; - - case MotionEvent.ACTION_MOVE: - if (historySize > 0) - { - PointerEvent[] pointerEvents = new PointerEvent[historySize + 1]; - for (int i = 0; i < historySize; ++i) - pointerEvents[i] = new PointerEvent(PointerEventType.MOVE, event.getHistoricalX(pointerIndex, i), event.getHistoricalY(pointerIndex, i), eventTimeOffset + event.getHistoricalEventTime(i), event.getHistoricalPressure(pointerIndex, i), iinkPointerType, pointerId); - pointerEvents[historySize] = new PointerEvent(PointerEventType.MOVE, event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); - editor.pointerEvents(pointerEvents, true); - } - else - { - editor.pointerMove(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); - } - return true; - - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_UP: - if (historySize > 0) - { - PointerEvent[] pointerEvents = new PointerEvent[historySize]; - for (int i = 0; i < historySize; ++i) - pointerEvents[i] = new PointerEvent(PointerEventType.MOVE, event.getHistoricalX(pointerIndex, i), event.getHistoricalY(pointerIndex, i), eventTimeOffset + event.getHistoricalEventTime(i), event.getHistoricalPressure(pointerIndex, i), iinkPointerType, pointerId); - editor.pointerEvents(pointerEvents, true); - } - editor.pointerUp(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); - return true; - - case MotionEvent.ACTION_CANCEL: - editor.pointerCancel(pointerId); - return true; - - default: - return false; - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) - { - if (editor == null) - { - return false; - } - - final int action = event.getAction(); - final int actionMask = action & MotionEvent.ACTION_MASK; - - if (actionMask == MotionEvent.ACTION_POINTER_DOWN || actionMask == MotionEvent.ACTION_POINTER_UP) - { - final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - return handleOnTouchForPointer(event, actionMask, pointerIndex); - } - else - { - boolean consumed = false; - final int pointerCount = event.getPointerCount(); - for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) - { - try - { - consumed = consumed || handleOnTouchForPointer(event, actionMask, pointerIndex); - } - catch(Exception e) { - // ignore spurious invalid touch events that may occurs when spamming undo/redo button - } - } - return consumed; - } - } - - @Override - public boolean onDown(MotionEvent event) - { - return false; - } - - @Override - public void onShowPress(MotionEvent event) - { - // no-op - } - - @Override - public boolean onSingleTapUp(MotionEvent event) - { - return false; - } - - @Override - public void onLongPress(MotionEvent event) - { - IInputControllerListener listener = getListener(); - if (listener != null) - { - final float x = event.getX(); - final float y = event.getY(); - // Only handle block ID and not `ContentBlock` to simplify native `AutoCloseable` object lifecycle. - // Otherwise, depending on which object owns such `ContentBlock`, the reasoning about closing it - // would be more complicated. - // Providing block ID delegates to listeners the ownership of the retrieved block (if any), - // typically calling `Editor.getBlockById()`. - try (@Nullable ContentBlock block = editor.hitBlock(x, y)) - { - String blockId = block != null ? block.getId() : null; - listener.onLongPress(x, y, blockId); - } - } - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) - { - if (editor.isScrollAllowed()) - { - Point oldOffset = editor.getRenderer().getViewOffset(); - Point newOffset = new Point(oldOffset.x + distanceX, oldOffset.y + distanceY); - editor.clampViewOffset(newOffset); - editor.getRenderer().setViewOffset(Math.round(newOffset.x), Math.round(newOffset.y)); - renderTarget.invalidate(editor.getRenderer(), EnumSet.allOf(IRenderTarget.LayerType.class)); - if(_viewListener != null) - { - _viewListener.showScrollbars(); - } - return true; - } - return false; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) - { - return false; - } -} diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java b/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java deleted file mode 100644 index 81063cc..0000000 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright @ MyScript. All rights reserved. - -package com.myscript.iink.uireferenceimplementation; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Typeface; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.View; - -import com.myscript.iink.Editor; -import com.myscript.iink.IRenderTarget; -import com.myscript.iink.IRenderTarget.LayerType; -import com.myscript.iink.Renderer; - -import java.util.EnumSet; -import java.util.Map; - -public class LayerView extends View implements IRenderView -{ - private final LayerType type; - private IRenderTarget renderTarget; - - private ImageLoader imageLoader; - - @Nullable - private Map typefaceMap; - - @Nullable - private Renderer lastRenderer = null; - - @Nullable - private Rect updateArea; - @Nullable - private Bitmap bitmap; - @Nullable - private android.graphics.Canvas sysCanvas; - @Nullable - private Canvas iinkCanvas; - @Nullable - private OfflineSurfaceManager offlineSurfaceManager = null; - @Nullable - private Renderer renderer = null; - private int pageHeight = 0; - private int viewHeight = 0; - private int viewWidth = 0; - private int pageWidth = 0; - private int yMin = 0; - private int xMin = 0; - - public LayerView(Context context) - { - this(context, null, 0); - } - - public LayerView(Context context, @Nullable AttributeSet attrs) - { - this(context, attrs, 0); - } - - public LayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) - { - super(context, attrs, defStyleAttr); - - updateArea = null; - - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LayerView, defStyleAttr, 0); - try - { - int typeOrdinal = typedArray.getInteger(R.styleable.LayerView_layerType, 0); - type = LayerType.values()[typeOrdinal]; - } - finally - { - typedArray.recycle(); - } - } - - @Override - public boolean isSingleLayerView() - { - return true; - } - - @Override - public LayerType getType() - { - return type; - } - - @Override - public void setRenderTarget(IRenderTarget renderTarget) - { - this.renderTarget = renderTarget; - } - - public void setOfflineSurfaceManager(@Nullable OfflineSurfaceManager offlineSurfaceManager) - { - this.offlineSurfaceManager = offlineSurfaceManager; - } - - @Override - public void setEditor(Editor editor) - { - // do not need the editor - } - - @Override - public void setImageLoader(ImageLoader imageLoader) - { - this.imageLoader = imageLoader; - } - - public void setCustomTypefaces(Map typefaceMap) - { - this.typefaceMap = typefaceMap; - } - - @Override - protected final void onDraw(android.graphics.Canvas canvas) - { - Rect localUpdateArea; - Renderer renderer; - - synchronized (this) - { - localUpdateArea = this.updateArea; - - this.updateArea = null; - renderer = lastRenderer; - lastRenderer = null; - } - - if (localUpdateArea != null) - { - - prepare(sysCanvas, localUpdateArea); - try - { - switch (type) - { - case MODEL: - renderer.drawModel(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas); - break; - case CAPTURE: - renderer.drawCaptureStrokes(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas); - break; - default: - break; - } - } - finally - { - restore(sysCanvas); - } - } - - canvas.drawBitmap(bitmap, 0, 0, null); - } - - @Override - protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) - { - if (bitmap != null) - { - bitmap.recycle(); - } - bitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); - sysCanvas = new android.graphics.Canvas(bitmap); - DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - iinkCanvas = new Canvas(sysCanvas, typefaceMap, imageLoader, offlineSurfaceManager, metrics.xdpi, metrics.ydpi); - - super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); - } - - private void prepare(android.graphics.Canvas canvas, Rect clipRect) - { - canvas.save(); - canvas.clipRect(clipRect); - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - } - - private void restore(android.graphics.Canvas canvas) - { - canvas.restore(); - } - - @Override - public final void update(Renderer renderer, int x, int y, int width, int height, EnumSet layers) - { - boolean emptyArea; - synchronized (this) - { - if (updateArea != null) - updateArea.union(x, y, x + width, y + height); - else - updateArea = new Rect(x, y, x + width, y + height); - - if (bitmap != null) - updateArea.intersect(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())); - emptyArea = updateArea.isEmpty(); - lastRenderer = renderer; - } - if (!emptyArea) - postInvalidate(x, y, x + width, y + height); - } - - public void setScrollbar(Renderer renderer, int viewWidthPx, int pageWidthtPx, int xMin, int viewHeightPx, int pageHeightPx, int yMin) - { - this.viewWidth = viewWidthPx; - this.pageWidth = pageWidthtPx; - this.renderer = renderer; - this.pageHeight = pageHeightPx; - this.viewHeight = viewHeightPx; - this.xMin = xMin; - this.yMin = yMin; - setVerticalScrollBarEnabled(true); - awakenScrollBars(); - } - - @Override - protected int computeVerticalScrollRange() - { - return pageHeight; - } - - @Override - protected int computeVerticalScrollExtent() - { - return viewHeight; - } - - @Override - protected int computeVerticalScrollOffset() - { - return renderer != null ? (int) renderer.getViewOffset().y - yMin : 0; - } - - @Override - protected int computeHorizontalScrollRange() - { - return pageWidth; - } - - @Override - protected int computeHorizontalScrollExtent() - { - return viewWidth; - } - - @Override - protected int computeHorizontalScrollOffset() - { - return renderer != null ? (int) renderer.getViewOffset().x - xMin : 0; - } -} diff --git a/UIReferenceImplementation/src/main/res/values/attrs.xml b/UIReferenceImplementation/src/main/res/values/attrs.xml deleted file mode 100644 index 9fecfd5..0000000 --- a/UIReferenceImplementation/src/main/res/values/attrs.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/batch.gif b/batch.gif deleted file mode 100644 index 6db7339..0000000 Binary files a/batch.gif and /dev/null differ diff --git a/certificate/.gitignore b/certificate/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/certificate/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/certificate/build_android.gradle b/certificate/build_android.gradle deleted file mode 100644 index a9b8c17..0000000 --- a/certificate/build_android.gradle +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id 'com.android.library' -} - -android { - compileSdkVersion = project.ext.compileSdkVersion - defaultConfig { - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode 1 - versionName '1.0' - } -} diff --git a/certificate/src/main/AndroidManifest.xml b/certificate/src/main/AndroidManifest.xml deleted file mode 100644 index d8a8531..0000000 --- a/certificate/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/java/.gitignore b/java/.gitignore deleted file mode 100644 index 1add293..0000000 --- a/java/.gitignore +++ /dev/null @@ -1,78 +0,0 @@ -################################################################################ -# Android -################################################################################ - -# Built application files -*.apk -*.ap_ - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ -out/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -.idea/caches - -# Keystore files -# Uncomment the following line if you do not want to check your keystore files in. -#*.jks - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json - -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md - -################################################################################ -# MyScript -################################################################################ - -# auto generated -.idea/.name -.idea/misc.xml -.idea/modules.xml diff --git a/java/.idea/codeStyles/Project.xml b/java/.idea/codeStyles/Project.xml deleted file mode 100644 index 06f1f7c..0000000 --- a/java/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - -
- - - - xmlns:android - ^$ - - - -
-
- - - - xmlns:.* - ^$ - - - BY_NAME - -
-
- - - - .*:id - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - ^$ - - - -
-
- - - - style - ^$ - - - -
-
- - - - .* - ^$ - - - BY_NAME - -
-
- - - - .* - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - .* - - - BY_NAME - -
-
-
-
- - -
-
\ No newline at end of file diff --git a/java/.idea/codeStyles/codeStyleConfig.xml b/java/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123..0000000 --- a/java/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/java/.idea/compiler.xml b/java/.idea/compiler.xml deleted file mode 100644 index fb7f4a8..0000000 --- a/java/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java/.idea/copyright/MyScript.xml b/java/.idea/copyright/MyScript.xml deleted file mode 100644 index 8d99023..0000000 --- a/java/.idea/copyright/MyScript.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/java/.idea/copyright/profiles_settings.xml b/java/.idea/copyright/profiles_settings.xml deleted file mode 100644 index 5a9c9a9..0000000 --- a/java/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/java/.idea/jarRepositories.xml b/java/.idea/jarRepositories.xml deleted file mode 100644 index d2ce72d..0000000 --- a/java/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/.idea/vcs.xml b/java/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/java/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java/build.gradle b/java/build.gradle deleted file mode 100644 index d9e4344..0000000 --- a/java/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ - -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21' - } -} - -subprojects { - afterEvaluate { proj -> - if (proj.hasProperty('android')) { - android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - if (proj.hasProperty('kotlin')) { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - } - - ndkVersion '21.4.7075529' - } - } - } -} -allprojects { - repositories { - google() - mavenCentral() - } - - ext { - // configure versions used by dependencies to harmonize and update easily across all components - - // Android SDK - compileSdkVersion = 31 - minSdkVersion = 21 - targetSdkVersion = 31 - - // Android libraries - kotlinCoreVersion='1.7.0' - appcompatVersion = '1.4.1' - materialLibraryVersion = '1.6.0' - gsonVersion = '2.8.9' - - // iink version - iinkVersionCode = 2010 - iinkVersionName = '2.0.1' - } - -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/java/buildSrc/settings.gradle b/java/buildSrc/settings.gradle deleted file mode 100644 index 5ddf9ad..0000000 --- a/java/buildSrc/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -/* - * Copyright (c) MyScript. All rights reserved. - */ diff --git a/java/buildSrc/src/main/groovy/com/myscript/gradle/tasks/CopyResourceAssets.groovy b/java/buildSrc/src/main/groovy/com/myscript/gradle/tasks/CopyResourceAssets.groovy deleted file mode 100644 index a6fbe1e..0000000 --- a/java/buildSrc/src/main/groovy/com/myscript/gradle/tasks/CopyResourceAssets.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) MyScript. All rights reserved. - */ - -package com.myscript.gradle.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.TaskAction - -/** - * Download resource assets from remote server. - */ -@SuppressWarnings("GroovyUnusedDeclaration") -class CopyResourceAssetsTask extends DefaultTask { - - @TaskAction - void copyResourceAssets() { - def baseUrl = "https://s3-us-west-2.amazonaws.com/iink/assets/$project.ext.iinkVersionName" - def urls = [ - "$baseUrl/myscript-iink-recognition-diagram.zip", - "$baseUrl/myscript-iink-recognition-raw-content.zip", - "$baseUrl/myscript-iink-recognition-math.zip", - "$baseUrl/myscript-iink-recognition-text-en_US.zip" - ] - - def intoDir = project.file("$project.projectDir/src/main/assets") - if (!intoDir.isDirectory()) - intoDir.mkdirs() - - def diagramConf = project.file("$intoDir/conf/diagram.conf") - def rawContentConf = project.file("$intoDir/conf/raw-content.conf") - def mathConf = project.file("$intoDir/conf/math.conf") - def textConf = project.file("$intoDir/conf/en_US.conf") - - if (!diagramConf.exists() || !rawContentConf.exists() || !mathConf.exists() || !textConf - .exists()) { - def fromDir = project.file("$intoDir/temp") - if (!fromDir.isDirectory()) - fromDir.mkdirs() - - // download resource zips - urls.each { url -> - ant.get(src: url, dest: fromDir.getPath()) - } - - // unzip - fromDir.listFiles({ it.name.endsWith(".zip") } as FileFilter).each { - def filePath = it.getPath() - project.copy { - from project.zipTree(filePath) - into fromDir - } - } - - // copy into assets folder - project.copy { - from "$fromDir/recognition-assets" - into intoDir - } - - // delete useless files - project.delete(fromDir) - } - } -} diff --git a/java/common-kotlin/.gitignore b/java/common-kotlin/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/java/common-kotlin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/java/common-kotlin/build.gradle b/java/common-kotlin/build.gradle deleted file mode 100644 index 66a98e2..0000000 --- a/java/common-kotlin/build.gradle +++ /dev/null @@ -1,53 +0,0 @@ -import com.myscript.gradle.tasks.CopyResourceAssetsTask - -/* - * Copyright (c) MyScript. All rights reserved. - */ - -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - compileSdk project.ext.compileSdkVersion - - defaultConfig { - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode project.ext.iinkVersionCode - versionName project.ext.iinkVersionName - } - - buildTypes { - release { - minifyEnabled false - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - - implementation "androidx.core:core-ktx:${project.ext.kotlinCoreVersion}" - implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}" - implementation "com.google.android.material:material:${project.ext.materialLibraryVersion}" - - // iink SDK. - // once iink wil be published we will use this - api "com.myscript:iink:${project.ext.iinkVersionName}" -} - -clean.doFirst { - delete "${projectDir}/src/main/assets/conf" - delete "${projectDir}/src/main/assets/resources" -} - -task copyResourceAssets(type: CopyResourceAssetsTask) -preBuild.dependsOn copyResourceAssets \ No newline at end of file diff --git a/java/common-kotlin/src/main/AndroidManifest.xml b/java/common-kotlin/src/main/AndroidManifest.xml deleted file mode 100644 index e07f1c9..0000000 --- a/java/common-kotlin/src/main/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/IInteractiveInkApplication.kt b/java/common-kotlin/src/main/java/com/myscript/iink/app/common/IInteractiveInkApplication.kt deleted file mode 100644 index 35512e7..0000000 --- a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/IInteractiveInkApplication.kt +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) MyScript. All rights reserved. - */ - -package com.myscript.iink.app.common - -annotation class IInteractiveInkApplication() diff --git a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/CustomProgressDialog.kt b/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/CustomProgressDialog.kt deleted file mode 100644 index 0fa03b8..0000000 --- a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/CustomProgressDialog.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.myscript.iink.app.common.utils - -import android.app.Dialog -import android.content.Context -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.view.WindowManager -import com.myscript.iink.app.common.R - -class CustomProgressDialog(ctx:Context) : Dialog(ctx){ - init{ - val wlmp: WindowManager.LayoutParams = window!!.attributes - - wlmp.gravity = Gravity.CENTER_HORIZONTAL - window!!.attributes = wlmp - setTitle(null) - setCancelable(false) - setOnCancelListener(null) - val view: View = LayoutInflater.from(context).inflate( - R.layout.custom_progress_dlg, null - ) - setContentView(view) - } -} \ No newline at end of file diff --git a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/Dialogs.kt b/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/Dialogs.kt deleted file mode 100644 index 123fc02..0000000 --- a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/Dialogs.kt +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright @ MyScript. All rights reserved. - -package com.myscript.iink.app.common.utils - -import android.app.AlertDialog -import android.content.Context -import android.content.DialogInterface -import android.view.LayoutInflater -import android.widget.EditText -import androidx.annotation.StringRes -import com.myscript.iink.app.common.R - -fun Context.launchSingleChoiceDialog( - @StringRes titleRes: Int, - items: List, - selectedIndex: Int = -1, - onItemSelected: (selected: Int) -> Unit, - onCancelSelected: DialogInterface.OnClickListener? =null -){ - var newIndex = selectedIndex - AlertDialog.Builder(this) - .setTitle(titleRes) - .setSingleChoiceItems( - items.toTypedArray(), - selectedIndex - ) { _, which -> - newIndex = which - } - .setPositiveButton(R.string.dialog_ok) { _, _ -> - if (newIndex in items.indices) { - onItemSelected(newIndex) - } - } - .setNegativeButton(R.string.dialog_cancel, onCancelSelected) - .show() -} - -fun Context.launchActionChoiceDialog( - @StringRes titleRes: Int, - items: List, - onItemSelected: (selected: Int) -> Unit -) -{ - AlertDialog.Builder(this) - .setTitle(titleRes) - .setItems(items.toTypedArray()) { _, which -> - if (which in items.indices) { - onItemSelected(which) - } - } - .show() -} -fun Context.launchActionChoiceDialog( - items: List, - onItemSelected: (selected: Int) -> Unit -) { - AlertDialog.Builder(this) - .setItems(items.toTypedArray()) { _, which -> - if (which in items.indices) { - onItemSelected(which) - } - } - .show() -} - -fun Context.launchTextBlockInputDialog(onInputDone: (text: String) -> Unit) { - val editTextLayout = LayoutInflater.from(this).inflate(R.layout.editor_text_input_layout, null) - val editText = editTextLayout.findViewById(R.id.editor_text_input) - val builder = AlertDialog.Builder(this) - .setView(editTextLayout) - .setTitle(R.string.editor_dialog_insert_text_title) - .setPositiveButton(R.string.editor_dialog_insert_text_action) { _, _ -> - val text = editText.text.toString() - if (text.isNotBlank()) { - onInputDone(text) - } - } - .setNegativeButton(R.string.dialog_cancel, null) - .create() - editText.requestFocus() - builder.show() -} \ No newline at end of file diff --git a/java/common-kotlin/src/main/res/layout/custom_progress_dlg.xml b/java/common-kotlin/src/main/res/layout/custom_progress_dlg.xml deleted file mode 100644 index 45c2d4e..0000000 --- a/java/common-kotlin/src/main/res/layout/custom_progress_dlg.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/java/common-kotlin/src/main/res/layout/editor_text_input_layout.xml b/java/common-kotlin/src/main/res/layout/editor_text_input_layout.xml deleted file mode 100644 index 7db19d1..0000000 --- a/java/common-kotlin/src/main/res/layout/editor_text_input_layout.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/java/common-kotlin/src/main/res/values/strings.xml b/java/common-kotlin/src/main/res/values/strings.xml deleted file mode 100644 index 7321b97..0000000 --- a/java/common-kotlin/src/main/res/values/strings.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - OK - Cancel - - Invalid certificate - Please check certificate data provided at Engine creation. - [%1$s] %2$s - - Add text block - Insert - \ No newline at end of file diff --git a/java/gradle/wrapper/gradle-wrapper.jar b/java/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 5c2d1cf..0000000 Binary files a/java/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/java/gradlew b/java/gradlew deleted file mode 100755 index dd7dcde..0000000 --- a/java/gradlew +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/java/samples/batch-mode-kt/build.gradle b/java/samples/batch-mode-kt/build.gradle deleted file mode 100644 index 7dd82fc..0000000 --- a/java/samples/batch-mode-kt/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' -} - -android { - compileSdkVersion project.ext.compileSdkVersion - - buildFeatures { - viewBinding true - } - - defaultConfig { - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - - applicationId 'com.myscript.iink.samples.batchmode' - versionCode project.ext.iinkVersionCode - versionName project.ext.iinkVersionName - - vectorDrawables.useSupportLibrary true - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - implementation "androidx.core:core-ktx:${project.ext.kotlinCoreVersion}" - implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}" - implementation "com.google.android.material:material:${project.ext.materialLibraryVersion}" - implementation "com.google.code.gson:gson:${project.ext.gsonVersion}" - - implementation project(':UIReferenceImplementation') - implementation project(':myscript-certificate') - implementation project(':common-kotlin') -} \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/MainActivity.kt b/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/MainActivity.kt deleted file mode 100644 index cf5328a..0000000 --- a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/MainActivity.kt +++ /dev/null @@ -1,285 +0,0 @@ -package com.myscript.iink.samples.batchmode - -import android.content.DialogInterface -import android.graphics.Typeface -import android.os.Bundle -import android.util.DisplayMetrics -import android.util.Log -import android.widget.Toast -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import com.google.gson.Gson -import com.myscript.iink.* -import com.myscript.iink.app.common.utils.autoCloseable -import com.myscript.iink.app.common.utils.launchSingleChoiceDialog -import com.myscript.iink.uireferenceimplementation.FontMetricsProvider -import com.myscript.iink.uireferenceimplementation.FontUtils -import com.myscript.iink.uireferenceimplementation.ImageLoader -import com.myscript.iink.uireferenceimplementation.ImagePainter -import java.io.* - - -class MainActivity : AppCompatActivity() { - private val TAG = "MainActivity" - - /**/ - private var renderer by autoCloseable(null) - private var editor by autoCloseable(null) - private var contentPackage : ContentPackage? =null - private var contentPart : ContentPart? = null - - /**/ - //warning use the real myscript name of part as this string will be use for part creation - private val typeOfPart = listOf("Text", "Math", "Diagram", "Raw Content") - private val iinkPackageName = "package.iink" - private val exportFileName = "export" - private val language = "en_US" - private val incremental = false - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Note: could be managed by domain layer and handled through observable error channel - // but kept simple as is to avoid adding too much complexity for this special (unrecoverable) error case - if (IInkApplication.getEngine() == null) { - // the certificate provided in `BatchModule.provideEngine` is most likely incorrect - AlertDialog.Builder(this) - .setTitle( getString(R.string.app_error_invalid_certificate_title)) - .setMessage( getString(R.string.app_error_invalid_certificate_message)) - .setPositiveButton(R.string.dialog_ok, null) - .show() - finishAndRemoveTask() // be sure to end the application - return - } - - // at creation we have to pre initialise the editor with dpi and screen size - intialConfiguration() - - //Small dialog to ask user which type of Part he/she wants to proceed - launchSingleChoiceDialog(R.string.dialog_Part_choice_Title, - typeOfPart, - 0, - { - // this is the function where we process exteranl output and export it - // add true if you want to export in png - offScreenProcess(typeOfPart[it]) - - //close the application - Thread(Runnable { - Thread.sleep(2000)// just ot let time to display the toast (2sec) - finishAndRemoveTask() // be sure to end the application - }).start() - }, - DialogInterface.OnClickListener { _, _ -> finishAndRemoveTask() }) - } - - private fun intialConfiguration(){ - val displayMetrics = resources.displayMetrics - - // configure recognition - IInkApplication.getEngine()?.apply { - configuration.let { conf -> - val confDir = "zip://${application.packageCodePath}!/assets/conf" - conf.setStringArray("configuration-manager.search-path", arrayOf(confDir)) - val tempDir = File(application.cacheDir, "tmp") - conf.setString("content-package.temp-folder", tempDir.absolutePath) - - // To enable text recognition for a specific language, - conf.setString("lang", language); - // Configure the engine to disable guides (recommended in batch mode) - conf.setBoolean("text.guides.enable", false); - } - } - - // Create a renderer with a null render target - renderer = IInkApplication.getEngine()?.createRenderer(displayMetrics.xdpi, displayMetrics.ydpi, null) - renderer?.setViewOffset(0.0f, 0.0f) - renderer?.viewScale = 1.0f - - // Create the editor - editor = IInkApplication.getEngine()?.createEditor(renderer!!, IInkApplication.getEngine()!!.createToolController()) - - // The editor requires a font metrics provider and a view size *before* calling setPart() - val typefaceMap: Map = HashMap() - editor!!.setFontMetricsProvider(FontMetricsProvider(displayMetrics, typefaceMap)) - editor!!.setViewSize(displayMetrics.widthPixels, displayMetrics.heightPixels); - } - - private fun offScreenProcess(partType: String, renderToPNG : Boolean = false){ - try { - // Create a new package - contentPackage = IInkApplication.getEngine()?.createPackage(iinkPackageName)!! - - // Create a new part - contentPart = contentPackage!!.createPart(partType) - } catch (e: IOException) { - Log.e(TAG, "Failed to open package \"$iinkPackageName\"", e) - AlertDialog.Builder(this) - .setTitle( "Package Creation IllegalArgumentException") - .setMessage( "Failed to open package \"$iinkPackageName\"") - .setPositiveButton(R.string.dialog_ok, null) - .show() - return; - - } catch (e: IllegalArgumentException) { - Log.e(TAG, "Failed to open type of part \"$partType\"", e) - AlertDialog.Builder(this) - .setTitle( "Part Creation IllegalArgumentException") - .setMessage( "Failed to open type of part \"$partType\"") - .setPositiveButton(R.string.dialog_ok, null) - .show() - return; - } - - // Associate editor with the new part - editor!!.part = contentPart - - // now we can process pointer events - // and we feed the editor with an array of Pointer Events loaded for the right json file - // incremntal way or not - loadAndFeedPointerEvents(incremental, editor!!, partType, resources.displayMetrics) - - - // choose the right mimeType to export according to the partType we choose - var mimeType = MimeType.PNG - if(!renderToPNG) { - when (partType) { - typeOfPart[0] -> mimeType = MimeType.TEXT // Text - typeOfPart[1] -> mimeType = MimeType.LATEX // Math - typeOfPart[2] -> mimeType = MimeType.SVG // Diagram - typeOfPart[3] -> mimeType = MimeType.JIIX //Raw Content - else -> {} - } - } - - // Exported file is stored in the Virtual SD Card : "Android/data/com.myscript.iink.samples.batchmode/files" - val file = File( - getExternalFilesDir(null), - File.separator + exportFileName.toString() + mimeType.fileExtensions - ) - editor!!.waitForIdle() - try { - var imagePainter : ImagePainter? = null - if(mimeType.isImage) { - //we have to create a image painter to render in png - imagePainter = ImagePainter().apply { - setImageLoader(ImageLoader(editor!!)) - // load fonts - - // load fonts - val assetManager = applicationContext.assets - val typefaceMap = FontUtils.loadFontsFromAssets(assetManager) - setTypefaceMap(typefaceMap) - } - } - editor!!.export_(null, file.getAbsolutePath(), mimeType, imagePainter) - } catch (e: Exception) { - e.printStackTrace() - } - - //quick reminder display of where the data has been exported - Toast.makeText(applicationContext, "File exported in : ${file.path}", Toast.LENGTH_SHORT).show() - - //clean the elements - editor!!.part = null - contentPart?.close() - contentPackage?.close() - try { - IInkApplication.getEngine()?.deletePackage(packageName) - } catch (e: IOException) { - e.printStackTrace() - } - editor!!.close() - renderer!!.close() - - } - - private fun loadAndFeedPointerEvents(incremental : Boolean, editor: Editor, partType: String, displayMetrics: DisplayMetrics){ - var pointerEventsPath = - "conf/pointerEvents/${partType.lowercase()}/pointerEvents.json" - if (partType.lowercase().equals("text")) { - pointerEventsPath = "conf/pointerEvents/${partType.lowercase()}/$language/pointerEvents.json" - } - - try { - // Loading the content of the pointerEvents JSON file - val inputStream: InputStream = resources.assets.open(pointerEventsPath) - - // Mapping the content into a JsonResult class - val jsonResult: JsonResult = - Gson().fromJson(InputStreamReader(inputStream), JsonResult::class.java) - - // add each element to a list - var pointerEventsList = mutableListOf() - - for (stroke in jsonResult.getStrokes()!!) { - val strokeX: FloatArray = stroke.x - val strokeY: FloatArray = stroke.y - val strokeT: LongArray = stroke.t - val strokeP: FloatArray = stroke.p - val length: Int = stroke.x.size - for (j in 0 until length) { - if(incremental){ - //in incremental mode we send data direct to the editor - if (j == 0) { - editor.pointerDown(strokeX[j] / 25.4f * displayMetrics.xdpi, - strokeY[j] / 25.4f * displayMetrics.ydpi, - strokeT[j], - strokeP[j], - PointerType.PEN, - 1) - } else if (j == length - 1) { - editor.pointerUp(strokeX[j] / 25.4f * displayMetrics.xdpi, - strokeY[j] / 25.4f * displayMetrics.ydpi, - strokeT[j], - strokeP[j], - PointerType.PEN, - 1) - } else { - editor.pointerMove(strokeX[j] / 25.4f * displayMetrics.xdpi, - strokeY[j] / 25.4f * displayMetrics.ydpi, - strokeT[j], - strokeP[j], - PointerType.PEN, - stroke.pointerId) - } - }else { - //in batch mode we keep data in a array - val pointerEvent = PointerEvent() - pointerEvent.pointerType = stroke.pointerType!! - pointerEvent.pointerId = stroke.pointerId - if (j == 0) { - pointerEvent.eventType = PointerEventType.DOWN - } else if (j == length - 1) { - pointerEvent.eventType = PointerEventType.UP - } else { - pointerEvent.eventType = PointerEventType.MOVE - } - - // Transform the x and y coordinates of the stroke from mm to px - // This is needed to be adaptive for each device - pointerEvent.x = strokeX[j] / 25.4f * displayMetrics.xdpi - pointerEvent.y = strokeY[j] / 25.4f * displayMetrics.ydpi - pointerEvent.t = strokeT[j] - pointerEvent.f = strokeP[j] - //add it to the list - pointerEventsList += pointerEvent - } - } - } - if(!incremental){ - editor.pointerEvents(pointerEventsList.toTypedArray(), false) - } - } catch (e: FileNotFoundException) { - AlertDialog.Builder(this) - .setTitle( "file not found") - .setMessage( "no file to parse found : $pointerEventsPath") - .setPositiveButton(R.string.dialog_ok, null) - .show() - e.printStackTrace() - } catch (e: IOException) { - e.printStackTrace() - } - } -} \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/res/values-night/themes.xml b/java/samples/batch-mode-kt/src/main/res/values-night/themes.xml deleted file mode 100644 index 47fc32a..0000000 --- a/java/samples/batch-mode-kt/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/res/values/colors.xml b/java/samples/batch-mode-kt/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127..0000000 --- a/java/samples/batch-mode-kt/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/res/values/strings.xml b/java/samples/batch-mode-kt/src/main/res/values/strings.xml deleted file mode 100644 index ae8c503..0000000 --- a/java/samples/batch-mode-kt/src/main/res/values/strings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - Batch Mode - - Invalid certificate - Please check certificate data provided at Engine creation. - - Choose the part type sample you want to proceed. \n This operation may take time! - Start the conversion - - \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/res/values/themes.xml b/java/samples/batch-mode-kt/src/main/res/values/themes.xml deleted file mode 100644 index fbb15da..0000000 --- a/java/samples/batch-mode-kt/src/main/res/values/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/java/samples/exercise-assessment-kt/.gitignore b/java/samples/exercise-assessment-kt/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/java/samples/exercise-assessment-kt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/java/samples/exercise-assessment-kt/build.gradle b/java/samples/exercise-assessment-kt/build.gradle deleted file mode 100644 index d133ef5..0000000 --- a/java/samples/exercise-assessment-kt/build.gradle +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) MyScript. All rights reserved. - */ - -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' -} - -android { - compileSdk project.ext.compileSdkVersion - - defaultConfig { - applicationId "com.myscript.iink.samples.assessment" - - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode project.ext.iinkVersionCode - versionName project.ext.iinkVersionName - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - - implementation "androidx.core:core-ktx:${project.ext.kotlinCoreVersion}" - implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}" - implementation "com.google.android.material:material:${project.ext.materialLibraryVersion}" - - implementation project(':UIReferenceImplementation') - implementation project(':myscript-certificate') - implementation project(':common-kotlin') -} - -task copyCustomMathConfigRecognitionAssets(type: org.gradle.api.tasks.Copy) { - from "${projectDir}/custom_res" - into "${projectDir}/src/main/assets" -} - -preBuild.dependsOn(copyCustomMathConfigRecognitionAssets) \ No newline at end of file diff --git a/java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MathGrammarK8DynamicRes.kt b/java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MathGrammarK8DynamicRes.kt deleted file mode 100644 index d81f9b8..0000000 --- a/java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MathGrammarK8DynamicRes.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) MyScript. All rights reserved. - */ - -package com.myscript.iink.samples.assessment - -import com.myscript.iink.Engine -import java.io.File -import java.io.IOException - - -class MathGrammarK8DynamicRes -{ - private val fileName = "math-grm-standardK8.res" - private val k8Grammar = """symbol = 0 1 2 3 4 5 6 7 8 9 + - / ÷ = . , % | ( ) : * x -leftpar = ( -rightpar = ) -currency_symbol = $ R € ₹ £ -character ::= identity(symbol) - | identity(currency_symbol) -fractionless ::= identity(character) - | fence (fractionless, leftpar, rightpar) - | hpair(fractionless, fractionless) -fractionable ::= identity(character) - | fence (fractionable, leftpar, rightpar) - | hpair(fractionable, fractionable) - | fraction(fractionless, fractionless) -expression ::= identity(character) - | fence (expression, leftpar, rightpar) - | hpair(expression, expression) - | fraction(fractionable, fractionable) -start(expression)""" - - @Throws(IllegalArgumentException::class, RuntimeException::class, IOException::class) - fun build(eng : Engine, filePath : String) { - val rab = eng.createRecognitionAssetsBuilder() - rab.compile("Math Grammar",k8Grammar) - val file: File = File(filePath, File.separator + fileName) - rab.store(file.path) - } -} diff --git a/java/samples/search-kt/.gitignore b/java/samples/search-kt/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/java/samples/search-kt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/java/samples/search-kt/build.gradle b/java/samples/search-kt/build.gradle deleted file mode 100644 index 6689b62..0000000 --- a/java/samples/search-kt/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) MyScript. All rights reserved. - */ - -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' -} - -android { - compileSdkVersion project.ext.compileSdkVersion - defaultConfig { - applicationId "com.myscript.iink.samples.search" - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode project.ext.iinkVersionCode - versionName project.ext.iinkVersionName - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - implementation project(':UIReferenceImplementation') - implementation project(':myscript-certificate') - implementation project(':common-kotlin') - - implementation "androidx.core:core-ktx:${project.ext.kotlinCoreVersion}" - implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}" - implementation "com.google.android.material:material:${project.ext.materialLibraryVersion}" - - // Google Gson - implementation "com.google.code.gson:gson:${project.ext.gsonVersion}" - implementation 'org.jetbrains:annotations:15.0' -} \ No newline at end of file diff --git a/java/settings.gradle b/java/settings.gradle deleted file mode 100644 index 450d65c..0000000 --- a/java/settings.gradle +++ /dev/null @@ -1,30 +0,0 @@ - -/* - * Copyright (c) MyScript. All rights reserved. - */ - -rootProject.name = 'myscript.iink.samples.android.java' - -def myProjects = [ - // samples - ':batch-mode-kotlin' : file("$settingsDir/samples/batch-mode-kt"), - ':exercise-assessment-kotlin' : file("$settingsDir/samples/exercise-assessment-kt"), - ':search-sample-kotlin' : file("$settingsDir/samples/search-kt"), - // MyScript certificate -> this should be empty - ':myscript-certificate' : file("${settingsDir.parent}/certificate"), - // common libraries - ':common-kotlin' : file("$settingsDir/common-kotlin"), - // MyScript iink UI reference implementation. - // Copy from: https://github.com/MyScript/interactive-ink-examples-android/tree/master/UIReferenceImplementation - ':UIReferenceImplementation' : file("${settingsDir.parent}/UIReferenceImplementation"), - -] - -myProjects.each { myProject, dir -> - logger.info("Including $myProject") - include myProject - if (dir != null) - project(myProject).projectDir = dir - if(myProject==':myscript-certificate') - project(myProject).buildFileName = 'build_android.gradle' -} diff --git a/offscreen-sample.gif b/offscreen-sample.gif new file mode 100644 index 0000000..717d516 Binary files /dev/null and b/offscreen-sample.gif differ diff --git a/UIReferenceImplementation/build.gradle b/samples/UIReferenceImplementation/build.gradle similarity index 51% rename from UIReferenceImplementation/build.gradle rename to samples/UIReferenceImplementation/build.gradle index 6aec1a7..d7234d6 100644 --- a/UIReferenceImplementation/build.gradle +++ b/samples/UIReferenceImplementation/build.gradle @@ -3,13 +3,15 @@ plugins { } android { - compileSdkVersion project.ext.compileSdkVersion + namespace 'com.myscript.iink.uireferenceimplementation' + + compileSdk project.ext.compileSdk defaultConfig { - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode 2010 - versionName '2.0.1' + minSdk project.ext.minSdk + targetSdk project.ext.targetSdk + versionCode project.ext.iinkVersionCode + versionName project.ext.iinkVersionName vectorDrawables.useSupportLibrary true } @@ -18,5 +20,7 @@ android { dependencies { implementation "androidx.appcompat:appcompat:${project.ext.appcompatVersion}" implementation "com.google.code.gson:gson:${project.ext.gsonVersion}" + // iink SDK. + // once iink wil be published we will use this api "com.myscript:iink:${project.ext.iinkVersionName}" } diff --git a/samples/UIReferenceImplementation/src/main/AndroidManifest.xml b/samples/UIReferenceImplementation/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9b65eb0 --- /dev/null +++ b/samples/UIReferenceImplementation/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/samples/UIReferenceImplementation/src/main/assets/fonts/MyScriptInter-Bold.otf b/samples/UIReferenceImplementation/src/main/assets/fonts/MyScriptInter-Bold.otf new file mode 100644 index 0000000..93179f8 Binary files /dev/null and b/samples/UIReferenceImplementation/src/main/assets/fonts/MyScriptInter-Bold.otf differ diff --git a/samples/UIReferenceImplementation/src/main/assets/fonts/MyScriptInter-Regular.otf b/samples/UIReferenceImplementation/src/main/assets/fonts/MyScriptInter-Regular.otf new file mode 100644 index 0000000..935f484 Binary files /dev/null and b/samples/UIReferenceImplementation/src/main/assets/fonts/MyScriptInter-Regular.otf differ diff --git a/samples/UIReferenceImplementation/src/main/assets/fonts/STIX-Italic.otf b/samples/UIReferenceImplementation/src/main/assets/fonts/STIX-Italic.otf new file mode 100644 index 0000000..b79db47 Binary files /dev/null and b/samples/UIReferenceImplementation/src/main/assets/fonts/STIX-Italic.otf differ diff --git a/samples/UIReferenceImplementation/src/main/assets/fonts/STIXGeneral.ttf b/samples/UIReferenceImplementation/src/main/assets/fonts/STIXGeneral.ttf new file mode 100644 index 0000000..ac15532 Binary files /dev/null and b/samples/UIReferenceImplementation/src/main/assets/fonts/STIXGeneral.ttf differ diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Canvas.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Canvas.java similarity index 70% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Canvas.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Canvas.java index d84d3d2..a787f79 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Canvas.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Canvas.java @@ -6,41 +6,48 @@ import android.graphics.DashPathEffect; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.graphics.ColorUtils; - +import android.graphics.Xfermode; import android.text.TextPaint; import android.util.Log; +import com.myscript.iink.GLRenderer; +import com.myscript.iink.ParameterSet; import com.myscript.iink.graphics.Color; +import com.myscript.iink.graphics.ExtraBrushStyle; import com.myscript.iink.graphics.FillRule; import com.myscript.iink.graphics.ICanvas; import com.myscript.iink.graphics.IPath; +import com.myscript.iink.graphics.InkPoints; import com.myscript.iink.graphics.LineCap; import com.myscript.iink.graphics.LineJoin; -import com.myscript.iink.graphics.Point; import com.myscript.iink.graphics.Style; import com.myscript.iink.graphics.Transform; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.Objects; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; public class Canvas implements ICanvas { private static final Style DEFAULT_SVG_STYLE = new Style(); + private static final PorterDuffXfermode xferModeSrcOver = new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); - @NonNull - private final android.graphics.Canvas canvas; + @Nullable + private android.graphics.Canvas canvas; @NonNull private final Paint strokePaint; @@ -76,13 +83,18 @@ public class Canvas implements ICanvas @Nullable private final ImageLoader imageLoader; private final OfflineSurfaceManager offlineSurfaceManager; + @Nullable + private GLRenderer glRenderer; + private boolean keepGLRenderer = false; + + private boolean clearOnStartDraw = true; - private final Set clips; + private final List clips; private final Map typefaceMap; private float[] dashArray; - private int dashOffset = 0; + private float dashOffset = 0; private final float xdpi; private final float ydpi; @@ -92,7 +104,37 @@ public class Canvas implements ICanvas @NonNull private final Matrix pointScaleMatrix; - public Canvas(@NonNull android.graphics.Canvas canvas, Map typefaceMap, ImageLoader imageLoader, @Nullable OfflineSurfaceManager offlineSurfaceManager, float xdpi, float ydpi) + public static class ExtraBrushConfig + { + @NonNull + public final String baseName; + @NonNull + public final Bitmap stampBitmap; + @Nullable + public final Bitmap backgroundBitmap; + @NonNull + public final ParameterSet config; + + public ExtraBrushConfig(@NonNull String baseName, @NonNull Bitmap stampBitmap, @Nullable Bitmap backgroundBitmap, @NonNull ParameterSet config) + { + this.baseName = baseName; + this.stampBitmap = stampBitmap; + this.backgroundBitmap = backgroundBitmap; + this.config = config; + } + } + + public Canvas(@Nullable android.graphics.Canvas canvas, Map typefaceMap, ImageLoader imageLoader, float xdpi, float ydpi) + { + this(canvas, Collections.emptyList(), typefaceMap, imageLoader, null, xdpi, ydpi); + } + + public Canvas(@Nullable android.graphics.Canvas canvas, @NonNull List extraBrushConfigs, Map typefaceMap, ImageLoader imageLoader, float xdpi, float ydpi) + { + this(canvas, extraBrushConfigs, typefaceMap, imageLoader, null, xdpi, ydpi); + } + + public Canvas(@Nullable android.graphics.Canvas canvas, @NonNull List extraBrushConfigs, Map typefaceMap, ImageLoader imageLoader, @Nullable OfflineSurfaceManager offlineSurfaceManager, float xdpi, float ydpi) { this.canvas = canvas; this.typefaceMap = typefaceMap; @@ -101,7 +143,14 @@ public Canvas(@NonNull android.graphics.Canvas canvas, Map typ this.xdpi = xdpi; this.ydpi = ydpi; - clips = new HashSet<>(); + if (!extraBrushConfigs.isEmpty() && GLRenderer.isDeviceSupported()) + { + glRenderer = new GLRenderer(); + for (ExtraBrushConfig config : extraBrushConfigs) + glRenderer.configureBrush(config.baseName, config.stampBitmap, config.backgroundBitmap, config.config); + } + + clips = new ArrayList<>(); strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); strokePaint.setStyle(Paint.Style.STROKE); @@ -135,9 +184,28 @@ public Canvas(@NonNull android.graphics.Canvas canvas, Map typ applyStyle(DEFAULT_SVG_STYLE); } - public Canvas(@NonNull android.graphics.Canvas canvas, Map typefaceMap, ImageLoader imageLoader, float xdpi, float ydpi) + public void destroy() + { + if (glRenderer != null) + { + glRenderer.destroy(); + glRenderer = null; + } + } + + public void setCanvas(@NonNull android.graphics.Canvas canvas) + { + this.canvas = canvas; + } + + public void setClearOnStartDraw(boolean clearOnStartDraw) { - this(canvas, typefaceMap, imageLoader, null, xdpi, ydpi); + this.clearOnStartDraw = clearOnStartDraw; + } + + public void setKeepGLRenderer(boolean keepGLRenderer) + { + this.keepGLRenderer = keepGLRenderer; } private void applyStyle(@NonNull Style style) @@ -174,6 +242,7 @@ public void setTransform(@NonNull Transform transform) transformValues[Matrix.MSCALE_Y] = (float) transform.yy; transformValues[Matrix.MTRANS_Y] = (float) transform.ty; + Objects.requireNonNull(canvas); transformMatrix.setValues(transformValues); canvas.setMatrix(transformMatrix); @@ -265,6 +334,7 @@ public void setStrokeDashArray(float[] strokeDashArray) @Override public void setStrokeDashOffset(float strokeDashOffset) { + dashOffset = strokeDashOffset; if (dashArray != null) strokePaint.setPathEffect(new DashPathEffect(dashArray, dashOffset)); else @@ -313,6 +383,7 @@ public final void setFontProperties(@NonNull String fontFamily, float fontLineHe @Override public void startDraw(int x, int y, int width, int height) { + Objects.requireNonNull(canvas); canvas.save(); pointsCache[0] = x; @@ -322,7 +393,7 @@ public void startDraw(int x, int y, int width, int height) // When offscreen rendering is supported, clear the destination // Otherwise, do not clear the destination (e.g. when exporting image, we want a white background) - if (offlineSurfaceManager != null) + if (offlineSurfaceManager != null && clearOnStartDraw) canvas.drawRect(pointsCache[0], pointsCache[1], pointsCache[2], pointsCache[3], clearPaint); // Hardware canvas does not support PorterDuffXfermode @@ -332,6 +403,13 @@ public void startDraw(int x, int y, int width, int height) @Override public void endDraw() { + if (!keepGLRenderer && glRenderer != null) + { + glRenderer.destroy(); + glRenderer = null; + } + + Objects.requireNonNull(canvas); canvas.restore(); } @@ -340,6 +418,7 @@ public void startGroup(@NonNull String id, float x, float y, float width, float { if (clipContent) { + Objects.requireNonNull(canvas); clips.add(id); canvas.save(); @@ -350,9 +429,12 @@ public void startGroup(@NonNull String id, float x, float y, float width, float @Override public void endGroup(@NonNull String id) { - if (clips.remove(id)) + int index = clips.lastIndexOf(id); + if (index != -1) { + Objects.requireNonNull(canvas); canvas.restore(); + clips.remove(index); } } @@ -378,6 +460,7 @@ public final IPath createPath() @Override public void drawPath(@NonNull IPath ipath) { + Objects.requireNonNull(canvas); Path path = (Path) ipath; if (android.graphics.Color.alpha(fillPaint.getColor()) != 0) @@ -391,9 +474,65 @@ public void drawPath(@NonNull IPath ipath) } } + @Override + public boolean isExtraBrushSupported(@NonNull String brushName) + { + return glRenderer != null && glRenderer.isBrushSupported(brushName); + } + + @Override + public void drawStrokeWithExtraBrush(@NonNull InkPoints[] vInkPoints, int temporaryPoints, + @NonNull ExtraBrushStyle style, boolean fullStroke, long id) + { + Objects.requireNonNull(canvas); + + if (!isExtraBrushSupported(style.brushName)) + return; + + if (vInkPoints.length == 0 || vInkPoints[0].x.length == 0 || style.strokeWidth <= 0.f || android.graphics.Color.alpha(fillPaint.getColor()) == 0) + return; + + if (!glRenderer.isInitialized()) + { + glRenderer.initialize(keepGLRenderer, canvas.getWidth(), canvas.getHeight(), xdpi, ydpi); + } + + Xfermode xfm = fillPaint.getXfermode(); + + try + { + canvas.setMatrix(null); // GLRenderer works with pixels + fillPaint.setXfermode(xferModeSrcOver); + + PointF strokeOrigin = glRenderer.drawStroke(vInkPoints, temporaryPoints, transformValues, style, fillPaint, fullStroke, id); + Bitmap strokeBitmap = glRenderer.saveStroke(); + if (strokeBitmap != null) + canvas.drawBitmap(strokeBitmap, strokeOrigin.x, strokeOrigin.y, fillPaint); + + if (temporaryPoints > 0 && vInkPoints.length == 1) + { + PointF temporaryOrigin = glRenderer.drawTemporary(vInkPoints, temporaryPoints, transformValues, style, fillPaint); + Bitmap temporaryBitmap = glRenderer.saveTemporary(); + if (temporaryBitmap != null) + canvas.drawBitmap(temporaryBitmap, temporaryOrigin.x, temporaryOrigin.y, fillPaint); + } + } + catch (Exception e) + { + Log.e("Canvas", "Error trying to draw stroke with extra brush: " + e.getMessage(), e); + } + finally + { + // restore + fillPaint.setXfermode(xfm); + canvas.setMatrix(transformMatrix); + } + } + @Override public void drawRectangle(float x, float y, float width, float height) { + Objects.requireNonNull(canvas); if (android.graphics.Color.alpha(fillPaint.getColor()) != 0) { canvas.drawRect(x, y, x + width, y + height, fillPaint); @@ -407,6 +546,7 @@ public void drawRectangle(float x, float y, float width, float height) @Override public void drawLine(float x1, float y1, float x2, float y2) { + Objects.requireNonNull(canvas); canvas.drawLine(x1, y1, x2, y2, strokePaint); } @@ -416,16 +556,16 @@ public void drawObject(@NonNull String url, @NonNull String mimeType, float x, f if (imageLoader == null) return; - Point screenMin = new Point(x, y); - transform.apply(screenMin); - Point screenMax = new Point(x + width, y + height); - transform.apply(screenMax); + Objects.requireNonNull(canvas); + + RectF pixelSize = new RectF(x,y,x + width, y + height); + transformMatrix.mapRect(pixelSize); final Rect targetRect = new Rect( - (int) Math.floor(screenMin.x), - (int) Math.floor(screenMin.y), - (int) (Math.ceil(screenMax.x) - x), - (int) (Math.ceil(screenMax.y) - y)); + (int) Math.floor(pixelSize.left), + (int) Math.floor(pixelSize.top), + (int) (Math.ceil(pixelSize.right)), + (int) (Math.ceil(pixelSize.bottom))); synchronized (imageLoader) { @@ -441,22 +581,6 @@ public void drawObject(@NonNull String url, @NonNull String mimeType, float x, f } else { - // adjust rectangle so that the image gets fit into original rectangle - float fx = width / image.getWidth(); - float fy = height / image.getHeight(); - if (fx > fy) - { - float w = image.getWidth() * fy; - x += (width - w) / 2; - width = w; - } - else - { - float h = image.getHeight() * fx; - y += (height - h) / 2; - height = h; - } - // draw the image Rect srcRect = new Rect(0, 0, image.getWidth(), image.getHeight()); RectF dstRect = new RectF(x, y, x + width, y + height); @@ -471,6 +595,7 @@ public void drawObject(@NonNull String url, @NonNull String mimeType, float x, f @Override public void drawText(@NonNull String label, float x, float y, float xmin, float ymin, float xmax, float ymax) { + Objects.requireNonNull(canvas); // transform the insertion point so that it is not impacted by text scale pointsCache[0] = x; pointsCache[1] = y; @@ -496,6 +621,7 @@ public void blendOffscreen(int id, float srcX, float srcY, float srcWidth, float if (bitmap != null) { + Objects.requireNonNull(canvas); floatRectCache.set(destX, destY, destX + destWidth, destY + destHeight); simpleRectCache.set(Math.round(srcX), Math.round(srcY), Math.round(srcX + srcWidth), Math.round(srcY + srcHeight)); diff --git a/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActions.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActions.java new file mode 100644 index 0000000..ce2f7d4 --- /dev/null +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActions.java @@ -0,0 +1,53 @@ +package com.myscript.iink.uireferenceimplementation; + +import com.myscript.iink.ContentSelection; +import com.myscript.iink.Editor; + +/** + * Describes the actions available for a given selection or block. + * + * @since 2.0 + */ +public enum ContextualActions { + /** + * Add block + */ + ADD_BLOCK, + /** + * Remove block or selection + */ + REMOVE, + /** + * Convert. + * @see Editor#getSupportedTargetConversionStates(ContentSelection) + */ + CONVERT, + /** + * Copy block or selection + */ + COPY, + /** + * Paste current copy + */ + PASTE, + /** + * Export. + * @see Editor#getSupportedExportMimeTypes(ContentSelection) + */ + EXPORT, + /** + * Change Text blocks format. + * @see Editor#getSupportedTextFormats(ContentSelection) + */ + FORMAT_TEXT, + /** + * Change selection mode. + * @see Editor#getAvailableSelectionModes() + */ + SELECTION_MODE, + /** + * Change selection type. + * @see Editor#getAvailableSelectionTypes(ContentSelection) + */ + SELECTION_TYPE +} diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java similarity index 78% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java index 4ba7399..972d845 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ContextualActionsHelper.java @@ -5,7 +5,7 @@ import com.myscript.iink.ContentBlock; import com.myscript.iink.ContentPart; import com.myscript.iink.ContentSelection; -import com.myscript.iink.ContextualActions; +import com.myscript.iink.ContentSelectionMode; import com.myscript.iink.Editor; import java.util.EnumSet; @@ -32,12 +32,14 @@ public static EnumSet getAvailableActionsForBlock(@NonNull Ed boolean onTextDocument = "Text Document".equals(part != null ? part.getType() : null); boolean blockIsEmpty = editor.isEmpty(block); - boolean displayAddBlock = editor.getSupportedAddBlockTypes().length > 0 && isRootBlock; + boolean displayAddBlock = editor.getSupportedAddBlockTypes().length > 0 && (!onTextDocument || isRootBlock); boolean displayRemove = !isRootBlock; boolean displayCopy = !isRootBlock || !onTextDocument; boolean displayConvert = !blockIsEmpty && editor.getSupportedTargetConversionStates(block).length > 0; boolean displayExport = editor.getSupportedExportMimeTypes(block).length > 0; boolean displayFormatText = editor.getSupportedTextFormats(block).size() > 0; + boolean displaySelectionMode = editor.getAvailableSelectionModes().size() > 0; + boolean displaySelectionType = editor.getAvailableSelectionTypes(block).length > 0; if (displayAddBlock) actions.add(ContextualActions.ADD_BLOCK); if (displayRemove) actions.add(ContextualActions.REMOVE); @@ -46,6 +48,8 @@ public static EnumSet getAvailableActionsForBlock(@NonNull Ed if (isRootBlock) actions.add(ContextualActions.PASTE); if (displayExport) actions.add(ContextualActions.EXPORT); if (displayFormatText) actions.add(ContextualActions.FORMAT_TEXT); + if (displaySelectionMode) actions.add(ContextualActions.SELECTION_MODE); + if (displaySelectionType) actions.add(ContextualActions.SELECTION_TYPE); } return actions; } @@ -58,12 +62,16 @@ public static EnumSet getAvailableActionsForSelection(@NonNul boolean displayConvert = editor.getSupportedTargetConversionStates(selection).length > 0; boolean displayExport = editor.getSupportedExportMimeTypes(selection).length > 0; boolean displayFormatText = selection != null && !editor.getSupportedTextFormats(selection).isEmpty(); + boolean displaySelectionMode = editor.getAvailableSelectionModes().size() > 0; + boolean displaySelectionType = editor.getAvailableSelectionTypes(selection).length > 0; actions.add(ContextualActions.REMOVE); if (displayConvert) actions.add(ContextualActions.CONVERT); actions.add(ContextualActions.COPY); if (displayExport) actions.add(ContextualActions.EXPORT); if (displayFormatText) actions.add(ContextualActions.FORMAT_TEXT); + if (displaySelectionMode) actions.add(ContextualActions.SELECTION_MODE); + if (displaySelectionType) actions.add(ContextualActions.SELECTION_TYPE); return actions; } diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/CustomTextSpan.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/CustomTextSpan.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/CustomTextSpan.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/CustomTextSpan.java diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java similarity index 96% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java index 9be2092..90c6e8b 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorBinding.java @@ -48,7 +48,7 @@ private void bindEditor(@NonNull EditorView editorView, @Nullable Editor editor) } @NonNull - public final EditorData openEditor(@Nullable EditorView editorView) + public EditorData openEditor(@Nullable EditorView editorView) { Editor editor = null; Renderer renderer = null; diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java similarity index 86% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java index 283ce0f..ec781bf 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorData.java @@ -2,11 +2,11 @@ package com.myscript.iink.uireferenceimplementation; -import androidx.annotation.Nullable; - import com.myscript.iink.Editor; import com.myscript.iink.Renderer; +import androidx.annotation.Nullable; + public final class EditorData { @Nullable @@ -17,19 +17,19 @@ public final class EditorData private final InputController inputController; @Nullable - public final Editor getEditor() + public Editor getEditor() { return this.editor; } @Nullable - public final Renderer getRenderer() + public Renderer getRenderer() { return this.renderer; } @Nullable - public final InputController getInputController() + public InputController getInputController() { return this.inputController; } diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java similarity index 69% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java index c8987bb..b557b04 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/EditorView.java @@ -10,19 +10,21 @@ import android.view.View; import android.widget.FrameLayout; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.myscript.iink.Editor; import com.myscript.iink.IRenderTarget; import com.myscript.iink.Renderer; import com.myscript.iink.graphics.ICanvas; import com.myscript.iink.graphics.Point; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; +import java.util.List; import java.util.Map; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + public class EditorView extends FrameLayout implements IRenderTarget, InputController.ViewListener { private int viewWidth; @@ -37,22 +39,20 @@ public class EditorView extends FrameLayout implements IRenderTarget, InputContr @NonNull private final OfflineSurfaceManager offlineSurfaceManager; @Nullable - private IRenderView renderView; - @Nullable - private IRenderView[] layerViews; + private LayerView layerView; private Map typefaceMap = new HashMap<>(); + @NonNull + private List extraBrushConfigs = Collections.emptyList(); public EditorView(Context context) { - super(context); - offlineSurfaceManager = new OfflineSurfaceManager(); + this(context, null, 0); } public EditorView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - offlineSurfaceManager = new OfflineSurfaceManager(); + this(context, attrs, 0); } public EditorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) @@ -70,46 +70,22 @@ protected void onFinishInflate() for (int i = 0, count = getChildCount(); i < count; ++i) { View view = getChildAt(i); - if (view instanceof IRenderView) + if (view instanceof LayerView) { - IRenderView renderView = (IRenderView) view; - if (renderView.isSingleLayerView()) - { - if (layerViews == null) - layerViews = new IRenderView[2]; - if (renderView.getType() == LayerType.MODEL) - layerViews[0] = renderView; - else if (renderView.getType() == LayerType.CAPTURE) - { - layerViews[1] = renderView; - } - else - { - throw new RuntimeException("Unknown layer view type"); - } - } - else - { - this.renderView = renderView; - } + layerView = (LayerView) view; - renderView.setRenderTarget(this); + layerView.setRenderTarget(this); if (editor != null) { - renderView.setEditor(editor); + layerView.setEditor(editor); } if (imageLoader != null) { - renderView.setImageLoader(imageLoader); + layerView.setImageLoader(imageLoader); } - renderView.setCustomTypefaces(typefaceMap); - - if (view instanceof LayerView) - { - LayerView layerView = (LayerView) view; - layerView.setOfflineSurfaceManager(offlineSurfaceManager); - } + layerView.setCustomTypefaces(typefaceMap); + layerView.setOfflineSurfaceManager(offlineSurfaceManager); } } } @@ -125,19 +101,9 @@ public void setEditor(@Nullable Editor editor) if (editor != null) { renderer = editor.getRenderer(); - if (renderView != null) - { - renderView.setEditor(editor); - } - else if (layerViews != null) + if (layerView != null) { - for (IRenderView layerView : layerViews) - { - if (layerView != null) - { - layerView.setEditor(editor); - } - } + layerView.setEditor(editor); } if (viewWidth > 0 && viewHeight > 0) { @@ -163,25 +129,48 @@ public Renderer getRenderer() return renderer; } - public void setImageLoader(ImageLoader imageLoader) + public void setExtraBrushConfigs(@NonNull List extraBrushConfigs) { - this.imageLoader = imageLoader; - - // transfer image loader to render views - if (renderView != null) + if (editor != null) { - renderView.setImageLoader(imageLoader); + throw new IllegalStateException("Please set the extra brush configs of the EditorView before binding the editor (through EditorView.setEngine() or EditorView.setEditor())"); } - else if (layerViews != null) + + this.extraBrushConfigs = extraBrushConfigs; + for (int i = 0, count = getChildCount(); i < count; ++i) { - for (IRenderView layerView : layerViews) + View view = getChildAt(i); + if (view instanceof LayerView) { - if (layerView != null) - layerView.setImageLoader(imageLoader); + LayerView layerView = (LayerView) view; + layerView.setExtraBrushConfigs(extraBrushConfigs); } } } + @NonNull + public List getExtraBrushConfigs() + { + return extraBrushConfigs; + } + + public void setImageLoader(ImageLoader imageLoader) + { + this.imageLoader = imageLoader; + + // transfer image loader to render views + if (layerView != null) + { + layerView.setImageLoader(imageLoader); + } + } + + @Nullable + public ImageLoader getImageLoader() + { + return imageLoader; + } + public void setTypefaces(@NonNull Map typefaceMap) { if (editor != null) @@ -193,10 +182,10 @@ public void setTypefaces(@NonNull Map typefaceMap) for (int i = 0, count = getChildCount(); i < count; ++i) { View view = getChildAt(i); - if (view instanceof IRenderView) + if (view instanceof LayerView) { - IRenderView renderView = (IRenderView) view; - renderView.setCustomTypefaces(typefaceMap); + LayerView layerView = (LayerView) view; + layerView.setCustomTypefaces(typefaceMap); } } } @@ -206,12 +195,6 @@ public Map getTypefaces() return typefaceMap; } - @Nullable - public ImageLoader getImageLoader() - { - return imageLoader; - } - @Override protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) { @@ -239,19 +222,9 @@ public final void invalidate(@NonNull Renderer renderer, int x, int y, int width if (width <= 0 || height <= 0) return; - if (renderView != null) + if (layerView != null) { - renderView.update(renderer, x, y, width, height, layers); - } - else if (layerViews != null) - { - for (LayerType type : layers) - { - int layerID = (type == LayerType.MODEL) ? 0 : 1; - IRenderView layerView = layerViews[layerID]; - if (layerView != null) - layerView.update(renderer, x, y, width, height, layers); - } + layerView.update(renderer, x, y, width, height); } } @@ -317,7 +290,7 @@ public ICanvas createOffscreenRenderCanvas(int offscreenID) if (offlineBitmap == null) return null; android.graphics.Canvas canvas = new android.graphics.Canvas(offlineBitmap); - return new Canvas(canvas, typefaceMap, imageLoader, offlineSurfaceManager, renderer.getDpiX(), renderer.getDpiY()); + return new Canvas(canvas, extraBrushConfigs, typefaceMap, imageLoader, offlineSurfaceManager, renderer.getDpiX(), renderer.getDpiY()); } @Override @@ -331,10 +304,6 @@ public void showScrollbars() editor.clampViewOffset(bottomRightPx); float pageHeightPx = bottomRightPx.y - topLeftPx.y + viewHeightPx; float pageWidthPx = bottomRightPx.x - topLeftPx.x + viewWidthPx; - for (IRenderView layerView : layerViews) - { - if (layerView instanceof LayerView && layerView.getType() == LayerType.MODEL) - ((LayerView) layerView).setScrollbar(renderer, viewWidthPx, (int) pageWidthPx, (int) topLeftPx.x, viewHeightPx, (int) pageHeightPx, (int) topLeftPx.y); - } + layerView.setScrollbar(renderer, viewWidthPx, (int) pageWidthPx, (int) topLeftPx.x, viewHeightPx, (int) pageHeightPx, (int) topLeftPx.y); } } diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java similarity index 96% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java index 1ce918e..1b13888 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontMetricsProvider.java @@ -19,6 +19,7 @@ import android.text.style.MetricAffectingSpan; import android.text.style.TextAppearanceSpan; import android.util.DisplayMetrics; +import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.collection.LruCache; @@ -88,11 +89,6 @@ public FontMetricsProvider(DisplayMetrics displayMetrics, Map this.typefaceMap = typefaceMap; } - private float x_mm2px(float mm) - { - return (mm / 25.4f) * displayMetrics.xdpi; - } - private float y_mm2px(float mm) { return (mm / 25.4f) * displayMetrics.ydpi; @@ -127,7 +123,7 @@ public Rectangle[] getCharacterBoundingBoxes(@NonNull Text text, TextSpan[] span @Override public float getFontSizePx(Style style) { - return style.getFontSize() * displayMetrics.scaledDensity; + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, style.getFontSize(), displayMetrics); } @Override @@ -167,7 +163,7 @@ private StaticLayout getLayout(@NonNull SpannableString string) } @Override - public GlyphMetrics[] getGlyphMetrics(Text text, TextSpan[] spans) + public synchronized GlyphMetrics[] getGlyphMetrics(Text text, TextSpan[] spans) { final String label = text.getLabel(); @@ -188,6 +184,7 @@ public GlyphMetrics[] getGlyphMetrics(Text text, TextSpan[] spans) int typefaceStyle = FontUtils.getTypefaceStyle(style); int fontSize = Math.round(y_mm2px(style.getFontSize())); + fontSize = Math.max(fontSize, 1); int start = text.getGlyphBeginAt(spans[i].beginPosition); int end = text.getGlyphEndAt(spans[i].endPosition - 1); diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontUtils.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontUtils.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontUtils.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FontUtils.java diff --git a/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FrameTimeEstimator.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FrameTimeEstimator.java new file mode 100644 index 0000000..8c61275 --- /dev/null +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/FrameTimeEstimator.java @@ -0,0 +1,135 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Adapted from: +// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:input/input-motionprediction/ + +package com.myscript.iink.uireferenceimplementation; + +import android.content.Context; +import android.os.Build; +import android.view.Display; +import android.view.WindowManager; + +import androidx.annotation.DoNotInline; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +/** + * Get screen fastest refresh rate (in ms) + */ +@SuppressWarnings("deprecation") +public class FrameTimeEstimator { + private static final float LEGACY_FRAME_TIME_MS = 16f; + private static final float MS_IN_A_SECOND = 1000f; + + static public float getFrameTime(@NonNull Context context) + { + return getFastestFrameTimeMs(context); + } + + private static Display getDisplayForContext(Context context) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + { + return Api30Impl.getDisplayForContext(context); + } + return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + } + + private static float getFastestFrameTimeMs(Context context) + { + Display defaultDisplay = getDisplayForContext(context); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + return Api23Impl.getFastestFrameTimeMs(defaultDisplay); + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + return Api21Impl.getFastestFrameTimeMs(defaultDisplay); + } + else + { + return LEGACY_FRAME_TIME_MS; + } + } + + @SuppressWarnings("deprecation") + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + static class Api21Impl + { + private Api21Impl() + { + // Not instantiable + } + + @DoNotInline + static float getFastestFrameTimeMs(Display display) + { + float[] refreshRates = display.getSupportedRefreshRates(); + float largestRefreshRate = refreshRates[0]; + + for (int c = 1; c < refreshRates.length; c++) + { + if (refreshRates[c] > largestRefreshRate) + largestRefreshRate = refreshRates[c]; + } + + return MS_IN_A_SECOND / largestRefreshRate; + } + } + + @RequiresApi(Build.VERSION_CODES.M) + static class Api23Impl + { + private Api23Impl() + { + // Not instantiable + } + + @DoNotInline + static float getFastestFrameTimeMs(Display display) + { + Display.Mode[] displayModes = display.getSupportedModes(); + float largestRefreshRate = displayModes[0].getRefreshRate(); + + for (int c = 1; c < displayModes.length; c++) + { + float currentRefreshRate = displayModes[c].getRefreshRate(); + if (currentRefreshRate > largestRefreshRate) + largestRefreshRate = currentRefreshRate; + } + + return MS_IN_A_SECOND / largestRefreshRate; + } + } + + @RequiresApi(Build.VERSION_CODES.R) + static class Api30Impl + { + private Api30Impl() + { + // Not instantiable + } + + @DoNotInline + static Display getDisplayForContext(Context context) + { + return context.getDisplay(); + } + } +} diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IInputControllerListener.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IInputControllerListener.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IInputControllerListener.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/IInputControllerListener.java diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ISizeListener.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ISizeListener.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ISizeListener.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ISizeListener.java diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java similarity index 77% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java index d82e044..0c15530 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImageLoader.java @@ -21,13 +21,14 @@ public class ImageLoader @NonNull private final Editor editor; LruCache cache; + static final float CACHE_MAX_MEMORY_RATIO = 1.f / 8; // in ]0, 1[ public ImageLoader(@NonNull Editor editor) { this.editor = editor; - // Use a part of the maximum available memory to define the cache's size - int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 8); + // Use a part of the maximum available memory to define the cache's size (in Bytes) + int cacheSize = (int) (Runtime.getRuntime().maxMemory() * CACHE_MAX_MEMORY_RATIO); this.cache = new LruCache(cacheSize) { @@ -59,12 +60,22 @@ public synchronized Bitmap getImage(final String url, final String mimeType, fin { Bitmap image = cache.get(url); if (image != null) - return image; + return image; // found Pair newImage = renderObject(url, mimeType, dstWidth, dstHeight); if (newImage.second) // Not dummy + { + int imageSize = newImage.first.getByteCount(); + if (imageSize > cache.maxSize()) + { + Log.w("ImageLoader", "Image too big for cache: resizing cache (" + + imageSize / (1024.f * 1024.f) + "MB > " + cache.maxSize() / (1024.f * 1024.f) + "MB)"); + cache.resize(imageSize); + } + cache.put(url, newImage.first); + } return newImage.first; } @@ -87,12 +98,18 @@ private Pair renderObject(String url, String mimeType, int dstW if (scaledImage != null) return Pair.create(scaledImage, true); + else + Log.e("ImageLoader", "Unable to scale image: using placeholder image"); } else { return Pair.create(image, true); } } + else + { + Log.e("ImageLoader", "Unable to decode file: using placeholder image"); + } } catch (Exception e) { @@ -102,7 +119,7 @@ private Pair renderObject(String url, String mimeType, int dstW catch (OutOfMemoryError e) { // Error: use fallback bitmap - Log.w("ImageLoader", "Out of memory: unable to load image, using placeholder instead", e); + Log.w("ImageLoader", "Out of memory: unable to load image: using placeholder instead", e); } } @@ -110,6 +127,9 @@ private Pair renderObject(String url, String mimeType, int dstW Bitmap image = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); if (image != null) image.eraseColor(Color.WHITE); + else + Log.e("ImageLoader", "Unable to render image nor placeholder"); + return Pair.create(image, false); } } diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java similarity index 82% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java index 70b8b08..54be6df 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/ImagePainter.java @@ -5,8 +5,6 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Typeface; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; import com.myscript.iink.IImagePainter; import com.myscript.iink.graphics.ICanvas; @@ -14,19 +12,35 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.Collections; +import java.util.List; import java.util.Map; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; + public class ImagePainter implements IImagePainter { private ImageLoader imageLoader = null; private Map typefaceMap = null; + protected android.graphics.Canvas canvas = null; private Bitmap bitmap = null; - private android.graphics.Canvas canvas = null; - private float dpi = 96; - + @NonNull + private final List extraBrushConfigs; + private float dpi = 96.f; @ColorInt private int backgroundColor = Color.WHITE; + public ImagePainter() + { + this(Collections.emptyList()); + } + + public ImagePainter(@NonNull List extraBrushConfigs) + { + this.extraBrushConfigs = extraBrushConfigs; + } + public void setImageLoader(ImageLoader imageLoader) { this.imageLoader = imageLoader; @@ -45,7 +59,7 @@ public void setBackgroundColor(@ColorInt int backgroundColor) @Override public ICanvas createCanvas() { - return new Canvas(canvas, typefaceMap, imageLoader, null, dpi, dpi); + return new Canvas(canvas, extraBrushConfigs, typefaceMap, imageLoader, dpi, dpi); } @Override diff --git a/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java new file mode 100644 index 0000000..d17673c --- /dev/null +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/InputController.java @@ -0,0 +1,456 @@ +// Copyright @ MyScript. All rights reserved. + +package com.myscript.iink.uireferenceimplementation; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; + +import com.myscript.iink.ContentBlock; +import com.myscript.iink.Editor; +import com.myscript.iink.IRenderTarget; +import com.myscript.iink.PointerEvent; +import com.myscript.iink.PointerEventType; +import com.myscript.iink.PointerTool; +import com.myscript.iink.PointerType; +import com.myscript.iink.Renderer; +import com.myscript.iink.ToolController; +import com.myscript.iink.graphics.Point; + +import java.util.EnumSet; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +public class InputController implements View.OnTouchListener, GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener +{ + + public interface ViewListener + { + void showScrollbars(); + } + + public static final int INPUT_MODE_NONE = -1; + public static final int INPUT_MODE_FORCE_PEN = 0; + public static final int INPUT_MODE_FORCE_TOUCH = 1; + public static final int INPUT_MODE_AUTO = 2; + public static final int INPUT_MODE_ERASER = 3; + + private static final float SCALING_SENSIBILITY = 1.5f; + private static final float SCALING_THRESHOLD = 0.02f; + + private final EditorView editorView; + private final Editor editor; + private int _inputMode; + private final GestureDetector gestureDetector; + private final ScaleGestureDetector scaleGestureDetector; + private IInputControllerListener _listener; + private final long eventTimeOffset; + @VisibleForTesting + public PointerType iinkPointerType; + private ViewListener _viewListener; + + private boolean isScalingEnabled = false; + private float getPreviousScalingSpan; + private float previousScalingFocusX; + private float previousScalingFocusY; + private boolean isMultiFingerTouch = false; + private int previousPointerId; + + private boolean isScrollingEnabled = true; + + public InputController(Context context, EditorView editorView, Editor editor) + { + this.editorView = editorView; + this.editor = editor; + _listener = null; + _inputMode = INPUT_MODE_AUTO; + scaleGestureDetector = new ScaleGestureDetector(context, this); + gestureDetector = new GestureDetector(context, this); + + long rel_t = SystemClock.uptimeMillis(); + long abs_t = System.currentTimeMillis(); + eventTimeOffset = abs_t - rel_t; + } + + public final synchronized void setInputMode(int inputMode) + { + this._inputMode = inputMode; + } + + public final synchronized int getInputMode() + { + return _inputMode; + } + + public final synchronized void setViewListener(ViewListener listener) + { + this._viewListener = listener; + } + + public final synchronized void setListener(IInputControllerListener listener) + { + this._listener = listener; + } + + public final synchronized void setScalingEnabled(boolean enabled) + { + isScalingEnabled = enabled; + } + + public final synchronized void setScrollingEnabled(boolean enabled) { + isScrollingEnabled = enabled; + } + + public final synchronized IInputControllerListener getListener() + { + return _listener; + } + + public final synchronized int getPreviousPointerId() + { + return previousPointerId; + } + + private boolean handleOnTouchForPointer(MotionEvent event, int actionMask, int pointerIndex) + { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerType = event.getToolType(pointerIndex); + final int historySize = event.getHistorySize(); + final boolean useTiltInfo = pointerType == MotionEvent.TOOL_TYPE_STYLUS; + + int inputMode = getInputMode(); + if (inputMode == INPUT_MODE_FORCE_PEN) + { + iinkPointerType = PointerType.PEN; + } + else if (inputMode == INPUT_MODE_FORCE_TOUCH) + { + iinkPointerType = PointerType.TOUCH; + } + else + { + switch (pointerType) + { + case MotionEvent.TOOL_TYPE_STYLUS: + if (inputMode == INPUT_MODE_ERASER) + iinkPointerType = PointerType.ERASER; + else + iinkPointerType = PointerType.PEN; + break; + case MotionEvent.TOOL_TYPE_FINGER: + case MotionEvent.TOOL_TYPE_MOUSE: + iinkPointerType = PointerType.TOUCH; + break; + default: + // unsupported event type + return false; + } + } + + if (isScalingEnabled) + scaleGestureDetector.onTouchEvent(event); + + if (iinkPointerType == PointerType.TOUCH) + gestureDetector.onTouchEvent(event); + + switch (actionMask) + { + // ACTION_POINTER_DOWN is "A non-primary pointer has gone down", only called when a pointer is already on the touchscreen. + case MotionEvent.ACTION_POINTER_DOWN: + { + isMultiFingerTouch = true; + if (previousPointerId != -1) + { + editor.pointerCancel(previousPointerId); + previousPointerId = -1; + } + return true; + } + case MotionEvent.ACTION_DOWN: + { + previousPointerId = pointerId; + isMultiFingerTouch = false; + // Request unbuffered events for tools that require low capture latency + ToolController toolController = editor.getToolController(); + PointerTool tool = toolController.getToolForType(iinkPointerType); + if (tool == PointerTool.PEN || tool == PointerTool.HIGHLIGHTER) + editorView.requestUnbufferedDispatch(event); + + try + { + if (useTiltInfo) + editor.pointerDown(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), + event.getPressure(), event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex), event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), iinkPointerType, pointerId); + else + editor.pointerDown(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); + } + catch (UnsupportedOperationException e) { + // Special case: pointerDown already called, discard previous and retry + editor.pointerCancel(pointerId); + if (useTiltInfo) + editor.pointerDown(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), + event.getPressure(), event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex), event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), iinkPointerType, pointerId); + else + editor.pointerDown(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); + } + return true; + } + case MotionEvent.ACTION_MOVE: + { + if (isMultiFingerTouch) + return true; + + if (historySize > 0) + { + PointerEvent[] pointerEvents = new PointerEvent[historySize + 1]; + if (useTiltInfo) + { + for (int i = 0; i < historySize; ++i) + { + pointerEvents[i] = new PointerEvent(PointerEventType.MOVE, event.getHistoricalX(pointerIndex, i), event.getHistoricalY(pointerIndex, i), eventTimeOffset + event.getHistoricalEventTime(i), + event.getHistoricalPressure(pointerIndex, i), event.getHistoricalAxisValue(MotionEvent.AXIS_TILT, pointerIndex, i), event.getHistoricalAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex, i), iinkPointerType, pointerId); + } + pointerEvents[historySize] = new PointerEvent(PointerEventType.MOVE, event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), + event.getPressure(), event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex), event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), iinkPointerType, pointerId); + } + else + { + for (int i = 0; i < historySize; ++i) + { + pointerEvents[i] = new PointerEvent(PointerEventType.MOVE, event.getHistoricalX(pointerIndex, i), event.getHistoricalY(pointerIndex, i), eventTimeOffset + event.getHistoricalEventTime(i), + event.getHistoricalPressure(pointerIndex, i), iinkPointerType, pointerId); + } + pointerEvents[historySize] = new PointerEvent(PointerEventType.MOVE, event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); + } + editor.pointerEvents(pointerEvents, true); + } + else // no history + { + if (useTiltInfo) + editor.pointerMove(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), + event.getPressure(), event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex), event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), iinkPointerType, pointerId); + else + editor.pointerMove(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); + } + return true; + } + // ACTION_POINTER_UP is "A non-primary pointer has gone up", at least one finger is still on the touchscreen. + case MotionEvent.ACTION_POINTER_UP: + { + return true; + } + case MotionEvent.ACTION_UP: + { + if (isMultiFingerTouch) + { + isMultiFingerTouch = false; + return true; + } + if (historySize > 0) + { + PointerEvent[] pointerEvents = new PointerEvent[historySize]; + if (useTiltInfo) + { + for (int i = 0; i < historySize; ++i) + { + pointerEvents[i] = new PointerEvent(PointerEventType.MOVE, event.getHistoricalX(pointerIndex, i), event.getHistoricalY(pointerIndex, i), eventTimeOffset + event.getHistoricalEventTime(i), + event.getHistoricalPressure(pointerIndex, i), event.getHistoricalAxisValue(MotionEvent.AXIS_TILT, pointerIndex, i), event.getHistoricalAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex, i), iinkPointerType, pointerId); + } + } + else + { + for (int i = 0; i < historySize; ++i) + { + pointerEvents[i] = new PointerEvent(PointerEventType.MOVE, event.getHistoricalX(pointerIndex, i), event.getHistoricalY(pointerIndex, i), eventTimeOffset + event.getHistoricalEventTime(i), + event.getHistoricalPressure(pointerIndex, i), iinkPointerType, pointerId); + } + } + editor.pointerEvents(pointerEvents, true); + } + if (useTiltInfo) + editor.pointerUp(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), + event.getPressure(), event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex), event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), iinkPointerType, pointerId); + else + editor.pointerUp(event.getX(pointerIndex), event.getY(pointerIndex), eventTimeOffset + event.getEventTime(), event.getPressure(), iinkPointerType, pointerId); + + return true; + } + case MotionEvent.ACTION_CANCEL: + { + editor.pointerCancel(pointerId); + return true; + } + default: + return false; + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) + { + if (editor == null) + { + return false; + } + + final int action = event.getAction(); + final int actionMask = action & MotionEvent.ACTION_MASK; + + try + { + if (actionMask == MotionEvent.ACTION_POINTER_DOWN || actionMask == MotionEvent.ACTION_POINTER_UP) + { + final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + return handleOnTouchForPointer(event, actionMask, pointerIndex); + } + else + { + boolean consumed = false; + final int pointerCount = event.getPointerCount(); + for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) + { + try + { + consumed = consumed || handleOnTouchForPointer(event, actionMask, pointerIndex); + } + catch(Exception e) { + // ignore spurious invalid touch events that may occurs when spamming undo/redo button + } + } + return consumed; + } + } + catch(UnsupportedOperationException e) { + // such an error may be generated by monkey tests + Log.e("InputController", "bad touch sequence", e); + return false; + } + } + + @Override + public boolean onDown(MotionEvent event) + { + return false; + } + + @Override + public void onShowPress(MotionEvent event) + { + // no-op + } + + @Override + public boolean onSingleTapUp(MotionEvent event) + { + return false; + } + + @Override + public void onLongPress(MotionEvent event) + { + IInputControllerListener listener = getListener(); + if (listener != null) + { + final float x = event.getX(); + final float y = event.getY(); + // Only handle block ID and not `ContentBlock` to simplify native `AutoCloseable` object lifecycle. + // Otherwise, depending on which object owns such `ContentBlock`, the reasoning about closing it + // would be more complicated. + // Providing block ID delegates to listeners the ownership of the retrieved block (if any), + // typically calling `Editor.getBlockById()`. + try (@Nullable ContentBlock block = editor.hitBlock(x, y)) + { + String blockId = block != null ? block.getId() : null; + listener.onLongPress(x, y, blockId); + } + } + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) + { + if (editor.isScrollAllowed() && isScrollingEnabled) + { + Point oldOffset = editor.getRenderer().getViewOffset(); + Point newOffset = new Point(oldOffset.x + distanceX, oldOffset.y + distanceY); + editor.getRenderer().setViewOffset(Math.round(newOffset.x), Math.round(newOffset.y)); + editorView.invalidate(editor.getRenderer(), EnumSet.allOf(IRenderTarget.LayerType.class)); + if(_viewListener != null) + { + _viewListener.showScrollbars(); + } + return true; + } + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) + { + return false; + } + + @Override + public boolean onScale(ScaleGestureDetector scaleGestureDetector) + { + Renderer renderer = editorView.getRenderer(); + + // Store the current focus of the scaleGestureDetector + float currentScalingFocusX = scaleGestureDetector.getFocusX(); + float currentScalingFocusY = scaleGestureDetector.getFocusY(); + float currentSpan = scaleGestureDetector.getCurrentSpan(); + + // Measure the delta of the currentFocus to the previous + float distanceX = previousScalingFocusX - currentScalingFocusX; + float distanceY = previousScalingFocusY - currentScalingFocusY; + + previousScalingFocusX = currentScalingFocusX; + previousScalingFocusY = currentScalingFocusY; + + Point oldOffset = renderer.getViewOffset(); + Point newOffset = new Point(oldOffset.x + distanceX, oldOffset.y + distanceY); + + // Apply the translation of the scaling focus to the render + renderer.setViewOffset(Math.round(newOffset.x), Math.round(newOffset.y)); + + float deltaSpan = getPreviousScalingSpan / currentSpan; + // Apply a ratio in order to avoid the scaling to move too fast + deltaSpan = 1.0f + ((1.0f - deltaSpan) / SCALING_SENSIBILITY); + + // Do not move if the scaling is too small + if (deltaSpan > (1 + SCALING_THRESHOLD) || deltaSpan < (1 - SCALING_THRESHOLD)) + { + renderer.zoomAt(new Point(currentScalingFocusX, currentScalingFocusY), deltaSpan); + } + + // Store the span for next time + getPreviousScalingSpan = currentSpan; + editorView.invalidate(renderer, EnumSet.allOf(IRenderTarget.LayerType.class)); + + if(_viewListener != null) + { + _viewListener.showScrollbars(); + } + return true; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) + { + getPreviousScalingSpan = scaleGestureDetector.getCurrentSpan(); + previousScalingFocusX = scaleGestureDetector.getFocusX(); + previousScalingFocusY = scaleGestureDetector.getFocusY(); + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) + { + // no-op + } +} diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/JiixDefinitions.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/JiixDefinitions.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/JiixDefinitions.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/JiixDefinitions.java diff --git a/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java new file mode 100644 index 0000000..de30b2e --- /dev/null +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/LayerView.java @@ -0,0 +1,303 @@ +// Copyright @ MyScript. All rights reserved. + +package com.myscript.iink.uireferenceimplementation; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Build; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.View; + +import com.myscript.iink.Editor; +import com.myscript.iink.IRenderTarget; +import com.myscript.iink.IRenderTarget.LayerType; +import com.myscript.iink.Renderer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class LayerView extends View +{ + private final static int MODEL = 0; + private final static int CAPTURE = 1; + private ImageLoader imageLoader; + + @Nullable + private Map typefaceMap; + + @Nullable + private Renderer lastRenderer = null; + + @Nullable + private OfflineSurfaceManager offlineSurfaceManager = null; + @Nullable + private Renderer renderer = null; + @NonNull + private Rect updateArea = new Rect(0, 0, 0, 0); + @NonNull + private Rect localUpdateArea = new Rect(0, 0, 0, 0); + @Nullable + private Bitmap bitmap = null; // for API < 28 + @Nullable + private android.graphics.Canvas sysCanvas = null; // for API < 28 + @Nullable + private Canvas iinkCanvas = null; + @NonNull + private List extraBrushConfigs = Collections.emptyList(); + private int pageWidth = 0; + private int pageHeight = 0; + private int viewWidth = 0; + private int viewHeight = 0; + private int canvasWidth = 0; + private int canvasHeight = 0; + private int xMin = 0; + private int yMin = 0; + + public LayerView(Context context) + { + this(context, null, 0); + } + + public LayerView(Context context, @Nullable AttributeSet attrs) + { + this(context, attrs, 0); + } + + public LayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + } + + public void setRenderTarget(IRenderTarget renderTarget) + { + // do not need the renderTarget + } + + public void setExtraBrushConfigs(@NonNull List extraBrushConfigs) + { + this.extraBrushConfigs = extraBrushConfigs; + } + + public void setOfflineSurfaceManager(@Nullable OfflineSurfaceManager offlineSurfaceManager) + { + this.offlineSurfaceManager = offlineSurfaceManager; + } + + public void setEditor(Editor editor) + { + // do not need the editor + } + + public void setImageLoader(ImageLoader imageLoader) + { + this.imageLoader = imageLoader; + } + + public void setCustomTypefaces(Map typefaceMap) + { + this.typefaceMap = typefaceMap; + } + + @Override + protected final void onDraw(android.graphics.Canvas canvas) + { + super.onDraw(canvas); + + // Draw directly in hardware-accelerated Canvas if scaling is supported (since API 28) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + { + Renderer renderer; + synchronized (this) + { + localUpdateArea.set(0, 0, canvasWidth, canvasHeight); + renderer = lastRenderer; + } + + iinkCanvas.setCanvas(canvas); + prepare(canvas, localUpdateArea); + + try + { + renderer.drawModel(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas); + renderer.drawCaptureStrokes(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas); + } + finally + { + restore(canvas); + } + } + else // Draw in intermediate bitmap + { + Renderer renderer; + synchronized (this) + { + localUpdateArea.set(this.updateArea); + this.updateArea.setEmpty(); + + renderer = lastRenderer; + lastRenderer = null; + } + + if (!localUpdateArea.isEmpty()) + { + prepare(sysCanvas, localUpdateArea); + try + { + renderer.drawModel(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas); + renderer.drawCaptureStrokes(localUpdateArea.left, localUpdateArea.top, localUpdateArea.width(), localUpdateArea.height(), iinkCanvas); + } + finally + { + restore(sysCanvas); + } + } + + canvas.drawBitmap(bitmap, 0, 0, null); + } + } + + @Override + protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) + { + DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + + synchronized (this) + { + // Direct draw + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + { + if (iinkCanvas != null) + iinkCanvas.destroy(); + + iinkCanvas = new Canvas(null, extraBrushConfigs, typefaceMap, imageLoader, offlineSurfaceManager, metrics.xdpi, metrics.ydpi); + } + else // Bitmap draw + { + if (bitmap != null) + bitmap.recycle(); + if (iinkCanvas != null) + iinkCanvas.destroy(); + + bitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); + sysCanvas = new android.graphics.Canvas(bitmap); + iinkCanvas = new Canvas(sysCanvas, extraBrushConfigs, typefaceMap, imageLoader, offlineSurfaceManager, metrics.xdpi, metrics.ydpi); + } + + iinkCanvas.setClearOnStartDraw(false); + iinkCanvas.setKeepGLRenderer(true); + canvasWidth = newWidth; + canvasHeight = newHeight; + } + + super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); + } + + private void prepare(android.graphics.Canvas canvas, Rect clipRect) + { + canvas.save(); + canvas.clipRect(clipRect); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + } + + private void restore(android.graphics.Canvas canvas) + { + canvas.restore(); + } + + public final void update(Renderer renderer, int x, int y, int width, int height) + { + boolean emptyArea; + + // Direct draw + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + { + Rect updatedArea = new Rect(x, y, x + width, y + height); + synchronized (this) + { + if (canvasWidth > 0 && canvasHeight > 0) + updatedArea.intersect(new Rect(0, 0, canvasWidth, canvasHeight)); + + emptyArea = updatedArea.isEmpty(); + lastRenderer = renderer; + } + } + else // Bitmap draw + { + synchronized (this) + { + updateArea.union(x, y, x + width, y + height); + if (canvasWidth > 0 && canvasHeight > 0) + updateArea.intersect(new Rect(0, 0, canvasWidth, canvasHeight)); + + emptyArea = updateArea.isEmpty(); + lastRenderer = renderer; + } + } + + if (!emptyArea) + { + postInvalidate(x, y, x + width, y + height); + } + } + + public void setScrollbar(Renderer renderer, int viewWidthPx, int pageWidthPx, int xMin, int viewHeightPx, int pageHeightPx, int yMin) + { + this.viewWidth = viewWidthPx; + this.pageWidth = pageWidthPx; + this.renderer = renderer; + this.pageHeight = pageHeightPx; + this.viewHeight = viewHeightPx; + this.xMin = xMin; + this.yMin = yMin; + setVerticalScrollBarEnabled(true); + awakenScrollBars(); + } + + @Override + protected int computeVerticalScrollRange() + { + return pageHeight; + } + + @Override + protected int computeVerticalScrollExtent() + { + return viewHeight; + } + + @Override + protected int computeVerticalScrollOffset() + { + return renderer != null ? (int) renderer.getViewOffset().y - yMin : 0; + } + + @Override + protected int computeHorizontalScrollRange() + { + return pageWidth; + } + + @Override + protected int computeHorizontalScrollExtent() + { + return viewWidth; + } + + @Override + protected int computeHorizontalScrollOffset() + { + return renderer != null ? (int) renderer.getViewOffset().x - xMin : 0; + } +} diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/OfflineSurfaceManager.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/OfflineSurfaceManager.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/OfflineSurfaceManager.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/OfflineSurfaceManager.java diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Path.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Path.java similarity index 100% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Path.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/Path.java diff --git a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/SmartGuideView.java b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/SmartGuideView.java similarity index 96% rename from UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/SmartGuideView.java rename to samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/SmartGuideView.java index 868b011..9389293 100644 --- a/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/SmartGuideView.java +++ b/samples/UIReferenceImplementation/src/main/java/com/myscript/iink/uireferenceimplementation/SmartGuideView.java @@ -311,12 +311,15 @@ public void setEditor(@Nullable Editor editor) Engine engine = editor.getEngine(); exportParams = engine.createParameterSet(); - exportParams.setBoolean("export.jiix.text.words", true); - exportParams.setBoolean("export.jiix.strokes", false); exportParams.setBoolean("export.jiix.bounding-box", false); exportParams.setBoolean("export.jiix.glyphs", false); exportParams.setBoolean("export.jiix.primitives", false); - exportParams.setBoolean("export.jiix.chars", false); + exportParams.setBoolean("export.jiix.strokes", false); + exportParams.setBoolean("export.jiix.text.chars", false); + exportParams.setBoolean("export.jiix.text.lines", false); + exportParams.setBoolean("export.jiix.text.spans", false); + exportParams.setBoolean("export.jiix.text.structure", false); + exportParams.setBoolean("export.jiix.text.words", true); importParams = engine.createParameterSet(); importParams.setString("diagram.import.jiix.action", "update"); @@ -330,7 +333,6 @@ public void setEditor(@Nullable Editor editor) fadeOutOtherDelay = configuration.getNumber("smart-guide.fade-out-delay.other", SMART_GUIDE_FADE_OUT_DELAY_OTHER_DEFAULT).intValue(); removeHighlightDelay = configuration.getNumber("smart-guide.highlight-removal-delay", SMART_GUIDE_HIGHLIGHT_REMOVAL_DELAY_DEFAULT).intValue(); } - } public void setMenuListener(@Nullable MenuListener moreMenuListener) @@ -399,12 +401,16 @@ public void contentChanged(@NonNull Editor editor, String[] blockIds) // the old instance is invalid but can be restored by remapping the identifier if (activeBlock != null && !activeBlock.isValid()) { - activeBlock.close(); - activeBlock = editor.getBlockById(activeBlock.getId()); - if (activeBlock == null) + String activeBlockId = activeBlock.getId(); + ContentBlock newActiveBlock = editor.getBlockById(activeBlockId); + if (newActiveBlock != null) + { + activeBlock.close(); + activeBlock = newActiveBlock; + } + else { update(null, UpdateCause.EDIT); - return; } } @@ -423,12 +429,7 @@ public void onError(@NonNull Editor editor, @NonNull String blockId, @NonNull Ed @Override public void selectionChanged(@NonNull Editor editor) { - if (selectedBlock != null) - { - selectedBlock.close(); - } - selectedBlock = null; - + ContentBlock newSelectionBlock = null; ContentSelectionMode mode = editor.getSelectionMode(); if (mode != ContentSelectionMode.NONE && mode != ContentSelectionMode.LASSO) { @@ -446,7 +447,7 @@ public void selectionChanged(@NonNull Editor editor) ContentBlock block = editor.getBlockById(blockId); if (block != null && block.getType().equals("Text")) { - selectedBlock = block; + newSelectionBlock = block; break; } else if (block != null) @@ -456,7 +457,13 @@ else if (block != null) } } - update(selectedBlock, UpdateCause.SELECTION); + update(newSelectionBlock, UpdateCause.SELECTION); + + if (selectedBlock != null) + { + selectedBlock.close(); + } + selectedBlock = newSelectionBlock; } @Override @@ -468,13 +475,15 @@ public void activeBlockChanged(@NonNull Editor editor, @NonNull String blockId) // selectionChanged already changed the active block return; } + + ContentBlock newActiveBlock = editor.getBlockById(blockId); + update(newActiveBlock, UpdateCause.EDIT); + if (activeBlock != null) { activeBlock.close(); } - activeBlock = editor.getBlockById(blockId); - - update(activeBlock, UpdateCause.EDIT); + activeBlock = newActiveBlock; } @Override diff --git a/UIReferenceImplementation/src/main/res/drawable/ic_smart_guide_more.xml b/samples/UIReferenceImplementation/src/main/res/drawable/ic_smart_guide_more.xml similarity index 100% rename from UIReferenceImplementation/src/main/res/drawable/ic_smart_guide_more.xml rename to samples/UIReferenceImplementation/src/main/res/drawable/ic_smart_guide_more.xml diff --git a/UIReferenceImplementation/src/main/res/drawable/smart_guide_bottom_border.xml b/samples/UIReferenceImplementation/src/main/res/drawable/smart_guide_bottom_border.xml similarity index 100% rename from UIReferenceImplementation/src/main/res/drawable/smart_guide_bottom_border.xml rename to samples/UIReferenceImplementation/src/main/res/drawable/smart_guide_bottom_border.xml diff --git a/UIReferenceImplementation/src/main/res/layout/editor_view.xml b/samples/UIReferenceImplementation/src/main/res/layout/editor_view.xml similarity index 63% rename from UIReferenceImplementation/src/main/res/layout/editor_view.xml rename to samples/UIReferenceImplementation/src/main/res/layout/editor_view.xml index 0a0991d..443f223 100644 --- a/UIReferenceImplementation/src/main/res/layout/editor_view.xml +++ b/samples/UIReferenceImplementation/src/main/res/layout/editor_view.xml @@ -6,29 +6,19 @@ android:id="@+id/editor_view" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@android:color/white" android:visibility="invisible"> - - \ No newline at end of file diff --git a/UIReferenceImplementation/src/main/res/layout/smart_guide_layout.xml b/samples/UIReferenceImplementation/src/main/res/layout/smart_guide_layout.xml similarity index 100% rename from UIReferenceImplementation/src/main/res/layout/smart_guide_layout.xml rename to samples/UIReferenceImplementation/src/main/res/layout/smart_guide_layout.xml diff --git a/UIReferenceImplementation/src/main/res/values/colors.xml b/samples/UIReferenceImplementation/src/main/res/values/colors.xml similarity index 100% rename from UIReferenceImplementation/src/main/res/values/colors.xml rename to samples/UIReferenceImplementation/src/main/res/values/colors.xml diff --git a/UIReferenceImplementation/src/main/res/values/dimens.xml b/samples/UIReferenceImplementation/src/main/res/values/dimens.xml similarity index 100% rename from UIReferenceImplementation/src/main/res/values/dimens.xml rename to samples/UIReferenceImplementation/src/main/res/values/dimens.xml diff --git a/samples/batch-mode/build.gradle b/samples/batch-mode/build.gradle new file mode 100644 index 0000000..9c54d51 --- /dev/null +++ b/samples/batch-mode/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + namespace 'com.myscript.iink.samples.batchmode' + + compileSdk Versions.compileSdk + + buildFeatures { + viewBinding true + } + + defaultConfig { + minSdk Versions.minSdk + targetSdk Versions.targetSdk + + applicationId 'com.myscript.iink.samples.batchmode' + versionCode project.ext.iinkVersionCode + versionName project.ext.iinkVersionName + + vectorDrawables.useSupportLibrary true + } +} + +dependencies { + implementation "androidx.core:core-ktx:${Versions.androidx_core}" + implementation "androidx.activity:activity-ktx:${Versions.androidx_activity}" + implementation "androidx.appcompat:appcompat:${Versions.appcompat}" + implementation "com.google.android.material:material:${Versions.material}" + implementation "com.google.code.gson:gson:${Versions.gson}" + + implementation project(':UIReferenceImplementation') + implementation project(':myscript-certificate') +} \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/AndroidManifest.xml b/samples/batch-mode/src/main/AndroidManifest.xml similarity index 64% rename from java/samples/batch-mode-kt/src/main/AndroidManifest.xml rename to samples/batch-mode/src/main/AndroidManifest.xml index c1d6c93..2e787fa 100644 --- a/java/samples/batch-mode-kt/src/main/AndroidManifest.xml +++ b/samples/batch-mode/src/main/AndroidManifest.xml @@ -1,19 +1,16 @@ - + + + + android:theme="@style/Theme.AppCompat.Light.DarkActionBar"> diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/diagram/export.svg b/samples/batch-mode/src/main/assets/conf/pointerEvents/diagram/export.svg similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/diagram/export.svg rename to samples/batch-mode/src/main/assets/conf/pointerEvents/diagram/export.svg diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/diagram/pointerEvents.json b/samples/batch-mode/src/main/assets/conf/pointerEvents/diagram/pointerEvents.json similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/diagram/pointerEvents.json rename to samples/batch-mode/src/main/assets/conf/pointerEvents/diagram/pointerEvents.json diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/math/export.tex b/samples/batch-mode/src/main/assets/conf/pointerEvents/math/export.tex similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/math/export.tex rename to samples/batch-mode/src/main/assets/conf/pointerEvents/math/export.tex diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/math/pointerEvents.json b/samples/batch-mode/src/main/assets/conf/pointerEvents/math/pointerEvents.json similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/math/pointerEvents.json rename to samples/batch-mode/src/main/assets/conf/pointerEvents/math/pointerEvents.json diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/raw content/export.jiix b/samples/batch-mode/src/main/assets/conf/pointerEvents/raw content/export.jiix similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/raw content/export.jiix rename to samples/batch-mode/src/main/assets/conf/pointerEvents/raw content/export.jiix diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/raw content/pointerEvents.json b/samples/batch-mode/src/main/assets/conf/pointerEvents/raw content/pointerEvents.json similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/raw content/pointerEvents.json rename to samples/batch-mode/src/main/assets/conf/pointerEvents/raw content/pointerEvents.json diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/text/en_US/export.txt b/samples/batch-mode/src/main/assets/conf/pointerEvents/text/en_US/export.txt similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/text/en_US/export.txt rename to samples/batch-mode/src/main/assets/conf/pointerEvents/text/en_US/export.txt diff --git a/java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/text/en_US/pointerEvents.json b/samples/batch-mode/src/main/assets/conf/pointerEvents/text/en_US/pointerEvents.json similarity index 100% rename from java/samples/batch-mode-kt/src/main/assets/conf/pointerEvents/text/en_US/pointerEvents.json rename to samples/batch-mode/src/main/assets/conf/pointerEvents/text/en_US/pointerEvents.json diff --git a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/IInkApplication.kt b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/IInkApplication.kt similarity index 91% rename from java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/IInkApplication.kt rename to samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/IInkApplication.kt index c042ccd..9663635 100644 --- a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/IInkApplication.kt +++ b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/IInkApplication.kt @@ -6,13 +6,11 @@ import com.myscript.iink.Engine class IInkApplication : Application() { - companion object { - private var engine: Engine? = null fun getEngine(): Engine? { - if (IInkApplication.engine == null) { + if (engine == null) { engine = Engine.create(MyCertificate.getBytes()) } return engine diff --git a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/JsonResult.kt b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/JsonResult.kt similarity index 87% rename from java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/JsonResult.kt rename to samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/JsonResult.kt index ad9ae4f..041ee20 100644 --- a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/JsonResult.kt +++ b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/JsonResult.kt @@ -8,10 +8,6 @@ class JsonResult { @SerializedName("events") private var strokes: ArrayList? = null - fun JsonResult(strokes: ArrayList?) { - this.strokes = strokes - } - fun getStrokes(): ArrayList? { return strokes } diff --git a/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/MainActivity.kt b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/MainActivity.kt new file mode 100644 index 0000000..9e0d88b --- /dev/null +++ b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/MainActivity.kt @@ -0,0 +1,379 @@ +package com.myscript.iink.samples.batchmode + +import android.content.Intent +import android.graphics.Typeface +import android.os.Bundle +import android.view.View +import android.widget.ProgressBar +import android.widget.RadioGroup +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatButton +import androidx.core.content.res.ResourcesCompat +import com.google.gson.Gson +import com.myscript.iink.ConversionState +import com.myscript.iink.Editor +import com.myscript.iink.MimeType +import com.myscript.iink.PointerEvent +import com.myscript.iink.PointerEventType +import com.myscript.iink.PointerType +import com.myscript.iink.Renderer +import com.myscript.iink.uireferenceimplementation.FontMetricsProvider +import com.myscript.iink.uireferenceimplementation.FontUtils +import com.myscript.iink.uireferenceimplementation.ImageLoader +import com.myscript.iink.uireferenceimplementation.ImagePainter +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader +import java.lang.reflect.Array.setBoolean +import java.nio.file.Files + +class MainActivity : AppCompatActivity() { + + private lateinit var renderer : Renderer + private lateinit var editor: Editor + private lateinit var imagePainter: ImagePainter + + private var useCustomInputFile = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_main) + + val engine = IInkApplication.getEngine() + + if (engine == null) { + // The certificate provided is incorrect, you need to use the one provided by MyScript + AlertDialog.Builder(this) + .setTitle( getString(R.string.app_error_invalid_certificate_title)) + .setMessage( getString(R.string.app_error_invalid_certificate_message)) + .setPositiveButton(android.R.string.ok, null) + .show() + finishAndRemoveTask() // be sure to end the application + return + } + + // Configure recognition + engine.apply { + configuration.apply { + val confDir = "zip://${application.packageCodePath}!/assets/conf" + setStringArray("configuration-manager.search-path", arrayOf(confDir)) + setString("content-package.temp-folder", application.cacheDir.absolutePath) + // To enable text recognition for a specific language, + setString("lang", language); + // Configure the engine to disable guides (recommended in batch mode) + setBoolean("text.guides.enable", false); + + // Activate handwriting recognition for text and shapes + setStringArray("raw-content.recognition.types", arrayOf("text", "shape")) + + // Allow conversion of text and shapes + setStringArray("raw-content.convert.types", arrayOf("text", "shape")) + } + } + + // At creation we have to pre initialise the editor with dpi and screen size + + // Create a renderer with a null render target + val displayMetrics = resources.displayMetrics + renderer = engine.createRenderer(displayMetrics.xdpi, displayMetrics.ydpi, null).apply { + setViewOffset(0.0f, 0.0f) + viewScale = 1.0f + } + + // Create the editor + editor = engine.createEditor(renderer, engine.createToolController()).apply { + // The editor requires a font metrics provider and a view size *before* calling setPart() + val typefaceMap = mutableMapOf() + setFontMetricsProvider(FontMetricsProvider(displayMetrics, typefaceMap)) + setViewSize(displayMetrics.widthPixels, displayMetrics.heightPixels) + } + + // Create a image painter to render in png + imagePainter = ImagePainter().apply { + setImageLoader(ImageLoader(editor)) + // load fonts + val typefaceMap = provideTypefaces() + setTypefaceMap(typefaceMap) + editor.setFontMetricsProvider(FontMetricsProvider(applicationContext.resources.displayMetrics, typefaceMap)) + editor.theme = (".math {font-family: STIX;}") + } + + useCustomInputFile = false + + findViewById(R.id.batch_sample_pick_file).setOnClickListener { _ -> pickFile() } + + findViewById(R.id.batch_sample_remove_file).setOnClickListener(this::removeFile) + + findViewById(R.id.batch_sample_execute).setOnClickListener(this::execute) + } + + private fun provideTypefaces(): Map { + val typefaces = FontUtils.loadFontsFromAssets(application.assets) ?: mutableMapOf() + // Map key must be aligned with the font-family used in theme.css + val myscriptInterFont = ResourcesCompat.getFont(application, R.font.myscriptinter) + if (myscriptInterFont != null) { + typefaces["MyScriptInter"] = myscriptInterFont + } + val stixFont = ResourcesCompat.getFont(application, R.font.stix) + if (stixFont != null) { + typefaces["STIX"] = stixFont + } + return typefaces + } + + private fun pickFile() { + pickFileLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + type = "application/*" + addCategory(Intent.CATEGORY_OPENABLE) + }) + } + + private fun removeFile(removeButton : View) { + useCustomInputFile = false + removeButton.visibility = View.GONE + findViewById(R.id.batch_sample_pick_file).visibility = View.VISIBLE + findViewById(R.id.batch_sample_file_description).visibility = View.VISIBLE + } + + private val pickFileLauncher: ActivityResultLauncher = + registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + it.data?.data?.also { uri -> + val contentResolver = applicationContext.contentResolver + contentResolver.openInputStream(uri)?.use { inputStream -> + val workingFile = File(applicationContext.cacheDir, customInputFileName) + workingFile.delete() + Files.copy(inputStream, workingFile.toPath()) + } + + useCustomInputFile = true + findViewById(R.id.batch_sample_remove_file).visibility = View.VISIBLE + findViewById(R.id.batch_sample_pick_file).visibility = View.GONE + findViewById(R.id.batch_sample_file_description).visibility = View.GONE + } + } + + @OptIn(DelicateCoroutinesApi::class) + private fun execute(executeButton: View) { + val partTypeGroup = findViewById(R.id.batch_sample_part_type_group) + val partType = when(partTypeGroup.checkedRadioButtonId) { + R.id.batch_sample_part_type_math -> "Math" + R.id.batch_sample_part_type_diagram -> "Diagram" + R.id.batch_sample_part_type_raw -> "Raw Content" + else -> "Text" + } + + val modeGroup = findViewById(R.id.batch_sample_mode_group) + val incremental = when(modeGroup.checkedRadioButtonId) { + R.id.batch_sample_mode_incremental -> true + else -> false + } + + val outputGroup = findViewById(R.id.batch_sample_output_group) + val pngOutput = when(outputGroup.checkedRadioButtonId) { + R.id.batch_sample_output_png -> true + else -> false + } + + val outputStyleGroup = findViewById(R.id.batch_sample_output_style_group) + val convert = when(outputStyleGroup.checkedRadioButtonId) { + R.id.batch_sample_output_style_converted -> true + else -> false + } + + val progress = findViewById(R.id.batch_sample_progress) + + progress.visibility = View.VISIBLE + executeButton.isEnabled = false + partTypeGroup.isEnabled = false + modeGroup.isEnabled = false + outputGroup.isEnabled = false + outputStyleGroup.isEnabled = false + + GlobalScope.launch(Dispatchers.Main) { + val outputFile = withContext(Dispatchers.IO) { + process(partType, incremental, pngOutput, convert) + } + + progress.visibility = View.GONE + executeButton.isEnabled = true + partTypeGroup.isEnabled = true + modeGroup.isEnabled = true + outputGroup.isEnabled = true + outputStyleGroup.isEnabled = true + + if (!outputFile.exists()) { + Toast.makeText(applicationContext, "An error occurred when exporting", Toast.LENGTH_LONG).show() + return@launch + } + + exportedFilePath = outputFile.absolutePath + + saveFileLauncher.launch(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + type = "application/*" + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_TITLE, outputFile.name) + }) + } + } + + private var exportedFilePath: String? = null + private val saveFileLauncher: ActivityResultLauncher = + registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + exportedFilePath?.let { exportedFilePath -> + val exportedFile = File(exportedFilePath) + if (exportedFile.exists()) { + it.data?.data?.also { uri -> + val contentResolver = applicationContext.contentResolver + contentResolver.openOutputStream(uri)?.use { outputStream -> + Files.copy(exportedFile.toPath(), outputStream) + this.exportedFilePath = null + } + } + } + } + } + + override fun onDestroy() { + super.onDestroy() + + editor.close() + renderer.close() + } + + private fun process(partType: String, incremental: Boolean, renderToPNG: Boolean, convert: Boolean): File { + val engine = requireNotNull(IInkApplication.getEngine()) + + // Create a new package + val contentPackage = engine.createPackage(iinkPackageName) + // Create a new part + val contentPart = contentPackage.createPart(partType) + // Associate editor with the new part + editor.part = contentPart + + // Now we can process pointer events and feed the editor with an array of Pointer Events loaded from the json file + loadAndFeedPointerEvents(partType, incremental) + + // Choose the right mimeType to export according to the partType chosen + var mimeType = MimeType.PNG + if(!renderToPNG) { + when (partType) { + typeOfPart[0] -> mimeType = MimeType.TEXT // Text + typeOfPart[1] -> mimeType = MimeType.LATEX // Math + typeOfPart[2] -> mimeType = MimeType.SVG // Diagram + typeOfPart[3] -> mimeType = MimeType.JIIX // Raw Content + else -> {} + } + } + + editor.waitForIdle() + + if (convert) { + editor.convert(editor.rootBlock, ConversionState.DIGITAL_EDIT) + } + val outputFile = File(applicationContext.filesDir, "$exportFileName${mimeType.fileExtensions}") + editor.export_(editor.rootBlock, outputFile.absolutePath, mimeType, if (mimeType.isImage) imagePainter else null) + + // Closing after using + editor.part = null + contentPart.close() + contentPackage.close() + engine.deletePackage(packageName) + + return outputFile + } + + private fun loadAndFeedPointerEvents(partType: String, incremental: Boolean = false) { + // Loading the content of the pointerEvents JSON file + val customInputFile = File(applicationContext.cacheDir, customInputFileName) + val inputStream = if (useCustomInputFile && customInputFile.exists()) { + FileInputStream(customInputFile) + } else { + val partTypeLowercase = partType.lowercase() + val pointerEventsPath = if (partTypeLowercase == "text") { + "conf/pointerEvents/$partTypeLowercase/$language/pointerEvents.json" + } else { + "conf/pointerEvents/$partTypeLowercase/pointerEvents.json" + } + + resources.assets.open(pointerEventsPath) + } + + // Mapping the content into a JsonResult class + val jsonResult = Gson().fromJson(InputStreamReader(inputStream), JsonResult::class.java) + + // Add each element to a list + val pointerEventsList = mutableListOf() + + val xdpi = resources.displayMetrics.xdpi + val ydpi = resources.displayMetrics.ydpi + + jsonResult.getStrokes()?.forEach { stroke -> + val strokeX = stroke.x + val strokeY = stroke.y + val strokeT = stroke.t + val strokeP = stroke.p + val length = stroke.x.size + + for (index in 0 until length) { + // Transform the x and y coordinates of the stroke from mm to px + // This is needed to be adaptive for each device + val x = strokeX[index] / 25.4f * xdpi + val y = strokeY[index] / 25.4f * ydpi + + if (incremental) { + // In incremental mode we send data direct to the editor + when (index) { + 0 -> editor.pointerDown(x, y, strokeT[index], strokeP[index], PointerType.PEN, 1) + length - 1 -> editor.pointerUp(x, y, strokeT[index], strokeP[index], PointerType.PEN, 1) + else -> editor.pointerMove(x, y, strokeT[index], strokeP[index], PointerType.PEN, stroke.pointerId) + } + } else { + // In batch mode we keep data in a array + pointerEventsList += PointerEvent().apply { + pointerType = stroke.pointerType + pointerId = stroke.pointerId + eventType = when (index) { + 0 -> PointerEventType.DOWN + length - 1 -> PointerEventType.UP + else -> PointerEventType.MOVE + } + this.x = x + this.y = y + t = strokeT[index] + f = strokeP[index] + } + } + } + } + if (!incremental) { + editor.pointerEvents(pointerEventsList.toTypedArray(), false) + } + } + + companion object { + private const val TAG = "MainActivity" + + private const val customInputFileName = "customInputFile" + + // /!\ Warning use the real MyScript name of part as this string will be used for part creation + private val typeOfPart = listOf("Text", "Math", "Diagram", "Raw Content") + private const val iinkPackageName = "package.iink" + private const val exportFileName = "export" + private const val language = "en_US" + } +} \ No newline at end of file diff --git a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/Stroke.kt b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/Stroke.kt similarity index 76% rename from java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/Stroke.kt rename to samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/Stroke.kt index 7b0c608..788d0fd 100644 --- a/java/samples/batch-mode-kt/src/main/java/com/myscript/iink/samples/batchmode/Stroke.kt +++ b/samples/batch-mode/src/main/java/com/myscript/iink/samples/batchmode/Stroke.kt @@ -7,12 +7,12 @@ import com.myscript.iink.PointerType import java.util.* data class Stroke( - var pointerType: PointerType? = null, - var pointerId: Int = 0, - var x: FloatArray, - var y: FloatArray, - var t: LongArray, - var p: FloatArray + val pointerType: PointerType, + val pointerId: Int = 0, + val x: FloatArray, + val y: FloatArray, + val t: LongArray, + val p: FloatArray ) { override fun toString(): String = "{" + "\"pointerType\":\"$pointerType\"," + diff --git a/java/common-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samples/batch-mode/src/main/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from java/common-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to samples/batch-mode/src/main/res/drawable-v24/ic_launcher_foreground.xml diff --git a/java/common-kotlin/src/main/res/drawable/ic_launcher_background.xml b/samples/batch-mode/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from java/common-kotlin/src/main/res/drawable/ic_launcher_background.xml rename to samples/batch-mode/src/main/res/drawable/ic_launcher_background.xml diff --git a/samples/batch-mode/src/main/res/font/myscriptinter.xml b/samples/batch-mode/src/main/res/font/myscriptinter.xml new file mode 100644 index 0000000..5c40ea3 --- /dev/null +++ b/samples/batch-mode/src/main/res/font/myscriptinter.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/samples/batch-mode/src/main/res/font/myscriptinter_bold.otf b/samples/batch-mode/src/main/res/font/myscriptinter_bold.otf new file mode 100644 index 0000000..93179f8 Binary files /dev/null and b/samples/batch-mode/src/main/res/font/myscriptinter_bold.otf differ diff --git a/samples/batch-mode/src/main/res/font/myscriptinter_regular.otf b/samples/batch-mode/src/main/res/font/myscriptinter_regular.otf new file mode 100644 index 0000000..935f484 Binary files /dev/null and b/samples/batch-mode/src/main/res/font/myscriptinter_regular.otf differ diff --git a/samples/batch-mode/src/main/res/font/stix.xml b/samples/batch-mode/src/main/res/font/stix.xml new file mode 100644 index 0000000..b83505f --- /dev/null +++ b/samples/batch-mode/src/main/res/font/stix.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/samples/batch-mode/src/main/res/font/stix_general.ttf b/samples/batch-mode/src/main/res/font/stix_general.ttf new file mode 100644 index 0000000..ac15532 Binary files /dev/null and b/samples/batch-mode/src/main/res/font/stix_general.ttf differ diff --git a/samples/batch-mode/src/main/res/font/stix_italic.otf b/samples/batch-mode/src/main/res/font/stix_italic.otf new file mode 100644 index 0000000..b79db47 Binary files /dev/null and b/samples/batch-mode/src/main/res/font/stix_italic.otf differ diff --git a/samples/batch-mode/src/main/res/layout/activity_main.xml b/samples/batch-mode/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..28c9abc --- /dev/null +++ b/samples/batch-mode/src/main/res/layout/activity_main.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/common-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/samples/batch-mode/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to samples/batch-mode/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/java/common-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/samples/batch-mode/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to samples/batch-mode/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/java/common-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png b/samples/batch-mode/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png rename to samples/batch-mode/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/java/common-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png b/samples/batch-mode/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png rename to samples/batch-mode/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/java/common-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples/batch-mode/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png rename to samples/batch-mode/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/java/common-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples/batch-mode/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to samples/batch-mode/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/java/common-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples/batch-mode/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from java/common-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to samples/batch-mode/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/samples/batch-mode/src/main/res/values/strings.xml b/samples/batch-mode/src/main/res/values/strings.xml new file mode 100644 index 0000000..e0017c3 --- /dev/null +++ b/samples/batch-mode/src/main/res/values/strings.xml @@ -0,0 +1,33 @@ + + Batch Mode + + Invalid certificate + Please check certificate data provided at Engine creation. + + Pick a file + Choose + No file, embedded file will be used + Remove Custom File + + Select a part type + Text + Math + Diagram + Raw Content + + Mode + Batch + Incremental + + Output + Auto + PNG + If Auto is selected, Text will be exported to plain text, Math to LaTeX, Diagram to SVG and Raw Content to JIIX. + + Output Style + Ink + Converted + + Execute! + + \ No newline at end of file diff --git a/samples/build.gradle b/samples/build.gradle new file mode 100644 index 0000000..b1ef3f9 --- /dev/null +++ b/samples/build.gradle @@ -0,0 +1,124 @@ +import org.apache.commons.io.FileUtils +import org.apache.commons.io.filefilter.FileFilterUtils + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:${Versions.android_gradle_plugin}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" + } +} + +subprojects { + afterEvaluate { proj -> + if (proj.hasProperty('android')) { + configure(android.lintOptions) { + abortOnError false + } + android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } + + ndkVersion Versions.ndk + } + } + + plugins.withId('com.android.application') { + tasks.register('DownloadAndExtractAssets', Copy) { + def resourcesURL = gradle.ext.iinkResourcesURL + def sourceUrls = ["${resourcesURL}/myscript-iink-recognition-diagram.zip", + "${resourcesURL}/myscript-iink-recognition-raw-content.zip", + "${resourcesURL}/myscript-iink-recognition-raw-content2.zip", + "${resourcesURL}/myscript-iink-recognition-math.zip", + "${resourcesURL}/myscript-iink-recognition-math2.zip", + "${resourcesURL}/myscript-iink-recognition-text-en_US.zip"] + def targetDir = new File(proj.projectDir, "src/main/assets/") + def diagramConf = new File(targetDir, "conf/diagram.conf") + def rawContentConf = new File(targetDir, "conf/raw-content.conf") + def rawContent2Conf = new File(targetDir, "conf/raw-content2.conf") + def mathConf = new File(targetDir, "conf/math.conf") + def math2Conf = new File(targetDir, "conf/math2.conf") + def textConf = new File(targetDir, "conf/en_US.conf") + + if (!diagramConf.exists() || !rawContentConf.exists() || !rawContent2Conf.exists() || !mathConf.exists() || !math2Conf.exists() || !textConf.exists()) { + def tmpAssetsDir = new File(proj.projectDir, "tmp-assets/") + def zipDir = new File(tmpAssetsDir, "zips") + + if (!tmpAssetsDir.isDirectory()) + tmpAssetsDir.mkdirs() + + if (!zipDir.isDirectory()) + zipDir.mkdirs() + + sourceUrls.each { sourceUrl -> + ant.get(src: sourceUrl, dest: zipDir.getPath()) + } + + File[] zipFiles = FileUtils.listFiles(zipDir, FileFilterUtils.suffixFileFilter("zip"), FileFilterUtils.trueFileFilter()) + zipFiles.each { File zipFile -> + from zipTree(zipFile) + into tmpAssetsDir + } + } + } + + tasks.register('CopyAssets', Copy) { + dependsOn DownloadAndExtractAssets + def targetDir = new File(proj.projectDir, "src/main/assets/") + def diagramConf = new File(targetDir, "conf/diagram.conf") + def rawContentConf = new File(targetDir, "conf/raw-content.conf") + def rawContent2Conf = new File(targetDir, "conf/raw-content2.conf") + def mathConf = new File(targetDir, "conf/math.conf") + def math2Conf = new File(targetDir, "conf/math2.conf") + def textConf = new File(targetDir, "conf/en_US.conf") + + if (!diagramConf.exists() || !rawContentConf.exists() || !rawContent2Conf.exists() || !mathConf.exists() || !math2Conf.exists() || !textConf.exists()) { + def tmpAssetsDir = new File(proj.projectDir, "tmp-assets/") + + if (!tmpAssetsDir.isDirectory()) + tmpAssetsDir.mkdirs() + + def recognitionAssetDir = new File(tmpAssetsDir, "recognition-assets/") + + println "Copying downloaded assets from $recognitionAssetDir to $targetDir" + from recognitionAssetDir + into targetDir + + doLast { + tmpAssetsDir.deleteDir() + } + } + } + + preBuild.dependsOn(CopyAssets) + } + } +} + +allprojects { + repositories { + google() + mavenCentral() + } + + ext { + compileSdk = Versions.compileSdk + minSdk = Versions.minSdk + targetSdk = Versions.targetSdk + + appcompatVersion = Versions.appcompat + gsonVersion = Versions.gson + + iinkVersionName = gradle.ext.iinkVersionName + iinkVersionCode = gradle.ext.iinkVersionCode + } +} + +tasks.register('clean', Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/samples/buildSrc/build.gradle.kts b/samples/buildSrc/build.gradle.kts new file mode 100644 index 0000000..876c922 --- /dev/null +++ b/samples/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} diff --git a/samples/buildSrc/src/main/java/Dependencies.kt b/samples/buildSrc/src/main/java/Dependencies.kt new file mode 100644 index 0000000..797fa55 --- /dev/null +++ b/samples/buildSrc/src/main/java/Dependencies.kt @@ -0,0 +1,33 @@ +// Copyright MyScript. All rights reserved. + +object Versions { + const val android_gradle_plugin = "8.5.0" + + const val kotlin = "1.9.23" + + // configure versions used by dependencies to harmonize and update easily across all components + const val compileSdk = 34 + const val minSdk = 23 + const val targetSdk = 34 + + // native build tools + const val ndk = "21.4.7075529" + const val cmake = "3.22.1" + + // android + const val androidx_core = "1.10.1" + const val androidx_activity = "1.7.2" + const val androidx_cardview = "1.0.0" + const val androidx_preference = "1.2.0" + const val androidx_lifecycle = "2.6.1" + const val androidx_recyclerview = "1.3.2" + const val material = "1.9.0" + const val appcompat = "1.6.1" + const val annotation = "1.6.0" + const val documentfile = "1.0.1" + const val recyclerview = "1.3.2" + + // 3rd party + const val gson = "2.10.1" + const val okhttp = "4.11.0" +} \ No newline at end of file diff --git a/samples/certificate/build_android.gradle b/samples/certificate/build_android.gradle new file mode 100644 index 0000000..8676f38 --- /dev/null +++ b/samples/certificate/build_android.gradle @@ -0,0 +1,15 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.myscript.certificate' + + compileSdk = Versions.compileSdk + defaultConfig { + minSdk Versions.minSdk + targetSdk Versions.targetSdk + versionCode 1 + versionName '1.0' + } +} diff --git a/samples/certificate/src/main/AndroidManifest.xml b/samples/certificate/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9b65eb0 --- /dev/null +++ b/samples/certificate/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/certificate/src/main/java/com/myscript/certificate/MyCertificate.java b/samples/certificate/src/main/java/com/myscript/certificate/MyCertificate.java similarity index 54% rename from certificate/src/main/java/com/myscript/certificate/MyCertificate.java rename to samples/certificate/src/main/java/com/myscript/certificate/MyCertificate.java index b393269..7217267 100644 --- a/certificate/src/main/java/com/myscript/certificate/MyCertificate.java +++ b/samples/certificate/src/main/java/com/myscript/certificate/MyCertificate.java @@ -12,9 +12,9 @@ public final class MyCertificate * * @return The bytes of the certificate. */ - public static byte[] getBytes() { - throw new RuntimeException( - "Please replace the content of MyCertificate.java with the certificate you received from " - + "the developer portal"); - } + public static byte[] getBytes() { + throw new RuntimeException( + "Please replace the content of MyCertificate.java with the certificate you received from " + + "the developer portal"); + } } \ No newline at end of file diff --git a/samples/exercise-assessment/build.gradle b/samples/exercise-assessment/build.gradle new file mode 100644 index 0000000..a72b7ea --- /dev/null +++ b/samples/exercise-assessment/build.gradle @@ -0,0 +1,39 @@ +/* + * Copyright (c) MyScript. All rights reserved. + */ + +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.myscript.iink.samples.assessment' + + compileSdk Versions.compileSdk + + defaultConfig { + applicationId "com.myscript.iink.samples.assessment" + + minSdk Versions.minSdk + targetSdk Versions.targetSdk + versionCode project.ext.iinkVersionCode + versionName project.ext.iinkVersionName + } +} + +dependencies { + implementation "androidx.core:core-ktx:${Versions.androidx_core}" + implementation "androidx.appcompat:appcompat:${Versions.appcompat}" + implementation "com.google.android.material:material:${Versions.material}" + + implementation project(':UIReferenceImplementation') + implementation project(':myscript-certificate') +} + +tasks.register('copyCustomMathConfigRecognitionAssets', Copy) { + from "${projectDir}/custom_res" + into "${projectDir}/src/main/assets" +} + +preBuild.dependsOn(copyCustomMathConfigRecognitionAssets) \ No newline at end of file diff --git a/samples/exercise-assessment/custom_res/conf/math.conf b/samples/exercise-assessment/custom_res/conf/math.conf new file mode 100644 index 0000000..e6b245d --- /dev/null +++ b/samples/exercise-assessment/custom_res/conf/math.conf @@ -0,0 +1,17 @@ +Bundle-Version: 1.0 +Bundle-Name: math +Configuration-Script: + AddResDir ../resources + AddResDir /data/user/0/com.myscript.iink.samples.assessment/files + +Name: standard +Type: Math +Configuration-Script: + AddResource math/math-ak.res + AddResource math/math-grm-standard.res + +Name: standardK8 +Type: Math +Configuration-Script: + AddResource math/math-ak.res + AddResource math-grm-standardK8.res \ No newline at end of file diff --git a/java/samples/exercise-assessment-kt/src/main/AndroidManifest.xml b/samples/exercise-assessment/src/main/AndroidManifest.xml similarity index 84% rename from java/samples/exercise-assessment-kt/src/main/AndroidManifest.xml rename to samples/exercise-assessment/src/main/AndroidManifest.xml index 496f955..f0cb577 100644 --- a/java/samples/exercise-assessment-kt/src/main/AndroidManifest.xml +++ b/samples/exercise-assessment/src/main/AndroidManifest.xml @@ -3,8 +3,9 @@ ~ Copyright (c) MyScript. All rights reserved. --> - + + + - + \ No newline at end of file diff --git a/java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MainActivity.kt b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MainActivity.kt similarity index 81% rename from java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MainActivity.kt rename to samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MainActivity.kt index def50ee..ed0e62e 100644 --- a/java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MainActivity.kt +++ b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MainActivity.kt @@ -1,6 +1,5 @@ package com.myscript.iink.samples.assessment -import android.content.DialogInterface import android.graphics.Color import android.graphics.Rect import android.graphics.Typeface @@ -9,12 +8,9 @@ import android.util.Log import android.view.* import android.widget.Toast import androidx.annotation.DimenRes -import androidx.annotation.NonNull -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.constraintlayout.widget.ConstraintLayout import com.myscript.iink.* -import com.myscript.iink.app.common.activities.ErrorActivity +import com.myscript.iink.samples.assessment.utils.ErrorActivity import com.myscript.iink.uireferenceimplementation.* class MainActivity : AppCompatActivity(), IEditorListener { @@ -32,54 +28,46 @@ class MainActivity : AppCompatActivity(), IEditorListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + ErrorActivity.setExceptionHandler(applicationContext) - // Note: could be managed by domain layer and handled through observable error channel - // but kept simple as is to avoid adding too much complexity for this special (unrecoverable) error case - if (MyIInkApplication.getEngine() == null) { - // the certificate provided in `BatchModule.provideEngine` is most likely incorrect - AlertDialog.Builder(this) - .setTitle( getString(R.string.app_error_invalid_certificate_title)) - .setMessage( getString(R.string.app_error_invalid_certificate_message)) - .setPositiveButton(R.string.dialog_ok, - DialogInterface.OnClickListener { - _, - _ -> - finishAffinity() - finishAndRemoveTask() // be sure to end the application - }) - .show() - return - } // Create a new package - contentPackage = MyIInkApplication.getEngine()?.createPackage(IINK_PACKAGE_NAME) - if(contentPackage==null){ - return; - } - + MyIInkApplication.getEngine()?.createPackage(IINK_PACKAGE_NAME)?.let { contentPackage -> + this.contentPackage = contentPackage - // TODO: try different part types: Diagram, Drawing, Math, Text, Text Document. - answerEditorView1 =findViewById(R.id.problemSolver1).findViewById(R.id.editor_view) - initWith(answerEditorView1, contentPackage!!,"Math","standard") + // TODO: try different part types: Diagram, Drawing, Math, Text, Text Document. + findViewById(R.id.problemSolver1).findViewById(com.myscript.iink.uireferenceimplementation.R.id.editor_view)?.let { answerEditorView -> + answerEditorView1 = answerEditorView + initWith(answerEditorView, contentPackage,"Math","standard") + } - answerEditorView2 =findViewById(R.id.problemSolver2).findViewById(R.id.editor_view) - initWith(answerEditorView2, contentPackage!!,"Math","standardK8") + findViewById(R.id.problemSolver2).findViewById(com.myscript.iink.uireferenceimplementation.R.id.editor_view)?.let { answerEditorView -> + answerEditorView2 = answerEditorView + initWith(answerEditorView, contentPackage,"Math","standardK8") + } - answerEditorView3 =findViewById(R.id.problemSolver3).findViewById(R.id.editor_view) - initWith(answerEditorView3, contentPackage!!,"Text") + findViewById(R.id.problemSolver3).findViewById(com.myscript.iink.uireferenceimplementation.R.id.editor_view)?.let { answerEditorView -> + answerEditorView3 = answerEditorView + initWith(answerEditorView, contentPackage,"Text") + } - answerEditorView4 =findViewById(R.id.problemSolver4).findViewById(R.id.editor_view) - initWith(answerEditorView4, contentPackage!!,"Diagram") + findViewById(R.id.problemSolver4).findViewById(com.myscript.iink.uireferenceimplementation.R.id.editor_view)?.let { answerEditorView -> + answerEditorView4 = answerEditorView + initWith(answerEditorView, contentPackage,"Diagram") + } - answerEditorView5 =findViewById(R.id.problemSolver5).findViewById(R.id.editor_view) - initWith(answerEditorView5, contentPackage!!,"Drawing") + findViewById(R.id.problemSolver5).findViewById(com.myscript.iink.uireferenceimplementation.R.id.editor_view)?.let { answerEditorView -> + answerEditorView5 = answerEditorView + initWith(answerEditorView, contentPackage,"Drawing") + } + } } - fun initWith(@NonNull editorView: EditorView?,@NonNull contentPackage: ContentPackage,@NonNull partType:String, mathConfig: String="" ){ - if(editorView==null||contentPackage==null||partType.isEmpty()) + private fun initWith(editorView: EditorView, contentPackage: ContentPackage, partType:String, mathConfig: String="" ){ + if(partType.isEmpty()) return - val editorBinding: EditorBinding = EditorBinding(MyIInkApplication.getEngine(), + val editorBinding = EditorBinding(MyIInkApplication.getEngine(), FontUtils.loadFontsFromAssets(application.assets) ?: emptyMap()) val editorData = editorBinding.openEditor(editorView) @@ -151,6 +139,7 @@ class MainActivity : AppCompatActivity(), IEditorListener { val horizontalMargin = resources.getDimension(horizontalMarginRes) val verticalMarginMM = 25.4f * verticalMargin / displayMetrics.ydpi val horizontalMarginMM = 25.4f * horizontalMargin / displayMetrics.xdpi + setNumber("text.margin.top", verticalMarginMM) setNumber("text.margin.left", horizontalMarginMM) setNumber("text.margin.right", horizontalMarginMM) setNumber("math.margin.top", verticalMarginMM) @@ -225,7 +214,7 @@ class MainActivity : AppCompatActivity(), IEditorListener { R.id.menu_clear -> editor.clear() R.id.menu_convert -> { val conversionState = editor.getSupportedTargetConversionStates(editor.rootBlock) - if (conversionState != null && conversionState.isNotEmpty()) { + if (conversionState.isNotEmpty()) { editor.convert(editor.rootBlock, conversionState.first()) } } diff --git a/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MathGrammarK8DynamicRes.kt b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MathGrammarK8DynamicRes.kt new file mode 100644 index 0000000..c1b1022 --- /dev/null +++ b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MathGrammarK8DynamicRes.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) MyScript. All rights reserved. + */ + +package com.myscript.iink.samples.assessment + +import com.myscript.iink.Engine +import java.io.File +import java.io.IOException + + +class MathGrammarK8DynamicRes +{ + private val fileName = "math-grm-standardK8.res" + private val k8Grammar = "symbol = 0 1 2 3 4 5 6 7 8 9 + - / ÷ = . , % | ( ) : * x\n" + + "leftpar = (\n" + + "rightpar = )\n" + + "currency_symbol = \$ R € ₹ £\n" + + "character ::= identity(symbol)\n" + + " | identity(currency_symbol)\n" + + "fractionless ::= identity(character)\n" + + " | fence (fractionless, leftpar, rightpar)\n" + + " | hpair(fractionless, fractionless)\n" + + "fractionable ::= identity(character)\n" + + " | fence (fractionable, leftpar, rightpar)\n" + + " | hpair(fractionable, fractionable)\n" + + " | fraction(fractionless, fractionless)\n" + + "expression ::= identity(character)\n" + + " | fence (expression, leftpar, rightpar)\n" + + " | hpair(expression, expression)\n" + + " | fraction(fractionable, fractionable)\n" + + "start(expression)" + + @Throws(IllegalArgumentException::class, RuntimeException::class, IOException::class) + fun build(eng : Engine, filePath : String) { + val rab = eng.createRecognitionAssetsBuilder() + rab.compile("Math Grammar",k8Grammar) + val file: File = File(filePath, File.separator + fileName) + rab.store(file.path) + } +} diff --git a/java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MyIInkApplication.kt b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MyIInkApplication.kt similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/java/com/myscript/iink/samples/assessment/MyIInkApplication.kt rename to samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/MyIInkApplication.kt diff --git a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/activities/ErrorActivity.kt b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/utils/ErrorActivity.kt similarity index 88% rename from java/common-kotlin/src/main/java/com/myscript/iink/app/common/activities/ErrorActivity.kt rename to samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/utils/ErrorActivity.kt index 509724e..c77959d 100644 --- a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/activities/ErrorActivity.kt +++ b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/utils/ErrorActivity.kt @@ -2,20 +2,17 @@ * Copyright (c) MyScript. All rights reserved. */ -package com.myscript.iink.app.common.activities +package com.myscript.iink.samples.assessment.utils -import android.app.Application import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.os.Process import android.text.method.ScrollingMovementMethod import android.widget.Button import android.widget.TextView -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import com.myscript.iink.app.common.R +import com.myscript.iink.samples.assessment.R import java.io.PrintWriter import java.io.StringWriter import kotlin.system.exitProcess @@ -61,7 +58,7 @@ class ErrorActivity : AppCompatActivity() { } } - private class ExceptionHandler internal constructor(private val context: Context) : Thread.UncaughtExceptionHandler { + private class ExceptionHandler(private val context: Context) : Thread.UncaughtExceptionHandler { override fun uncaughtException(t: Thread, e: Throwable) { // get message from the root cause. var root: Throwable? = e @@ -79,7 +76,7 @@ class ErrorActivity : AppCompatActivity() { val intent = Intent(context, ErrorActivity::class.java) intent.putExtra(ERR_TITLE, message) intent.putExtra(ERR_MESSAGE, trace) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK; context.startActivity(intent) // kill the current activity. Process.killProcess(Process.myPid()) diff --git a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/LockableScrollView.kt b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/utils/LockableScrollView.kt similarity index 97% rename from java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/LockableScrollView.kt rename to samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/utils/LockableScrollView.kt index a5e9598..014f103 100644 --- a/java/common-kotlin/src/main/java/com/myscript/iink/app/common/utils/LockableScrollView.kt +++ b/samples/exercise-assessment/src/main/java/com/myscript/iink/samples/assessment/utils/LockableScrollView.kt @@ -2,7 +2,7 @@ * Copyright (c) MyScript. All rights reserved. */ -package com.myscript.iink.app.common.utils +package com.myscript.iink.samples.assessment.utils import android.content.Context import android.util.AttributeSet diff --git a/samples/exercise-assessment/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samples/exercise-assessment/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..aa654c2 --- /dev/null +++ b/samples/exercise-assessment/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_answer.xml b/samples/exercise-assessment/src/main/res/drawable/ic_answer.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_answer.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_answer.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_clear.xml b/samples/exercise-assessment/src/main/res/drawable/ic_clear.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_clear.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_clear.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_convert.xml b/samples/exercise-assessment/src/main/res/drawable/ic_convert.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_convert.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_convert.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_equation.xml b/samples/exercise-assessment/src/main/res/drawable/ic_equation.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_equation.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_equation.xml diff --git a/samples/exercise-assessment/src/main/res/drawable/ic_launcher_background.xml b/samples/exercise-assessment/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..85efc6f --- /dev/null +++ b/samples/exercise-assessment/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_1.xml b/samples/exercise-assessment/src/main/res/drawable/ic_num_1.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_1.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_num_1.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_2.xml b/samples/exercise-assessment/src/main/res/drawable/ic_num_2.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_2.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_num_2.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_3.xml b/samples/exercise-assessment/src/main/res/drawable/ic_num_3.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_3.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_num_3.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_4.xml b/samples/exercise-assessment/src/main/res/drawable/ic_num_4.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_4.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_num_4.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_5.xml b/samples/exercise-assessment/src/main/res/drawable/ic_num_5.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_num_5.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_num_5.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_redo.xml b/samples/exercise-assessment/src/main/res/drawable/ic_redo.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_redo.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_redo.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_twolinearequation.xml b/samples/exercise-assessment/src/main/res/drawable/ic_twolinearequation.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_twolinearequation.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_twolinearequation.xml diff --git a/java/samples/exercise-assessment-kt/src/main/res/drawable/ic_undo.xml b/samples/exercise-assessment/src/main/res/drawable/ic_undo.xml similarity index 100% rename from java/samples/exercise-assessment-kt/src/main/res/drawable/ic_undo.xml rename to samples/exercise-assessment/src/main/res/drawable/ic_undo.xml diff --git a/samples/exercise-assessment/src/main/res/layout/activity_error.xml b/samples/exercise-assessment/src/main/res/layout/activity_error.xml new file mode 100644 index 0000000..37bdb21 --- /dev/null +++ b/samples/exercise-assessment/src/main/res/layout/activity_error.xml @@ -0,0 +1,36 @@ + + + + + + + + +