├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .mocharc.e2e.env.js ├── .mocharc.e2e.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── Makefile ├── Makefile.inc ├── README.md ├── SETUP.md ├── assets ├── angular.svg ├── react.svg └── vue.svg ├── config ├── rollup.config.build.js ├── rollup.config.dev.js └── rollup.config.js ├── dist ├── iink.esm.js ├── iink.esm.js.map ├── iink.min.css ├── iink.min.js └── iink.min.js.map ├── docker ├── builder │ └── Dockerfile ├── examples │ ├── Dockerfile │ ├── createIndexFile.sh │ ├── entrypoint.sh │ └── nginx.conf └── wait-tcp │ ├── Dockerfile │ └── entrypoint.sh ├── docs ├── Editor.html ├── Editor.js.html ├── EditorFacade.js.html ├── configuration_Constants.js.html ├── configuration_DefaultBehaviors.js.html ├── configuration_DefaultConfiguration.js.html ├── configuration_DefaultPenStyle.js.html ├── configuration_DefaultTheme.js.html ├── configuration_LoggerConfig.js.html ├── eastereggs_InkImporter.js.html ├── event_Event.js.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.svg │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.svg │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff ├── global.html ├── grabber_PointerEventGrabber.js.html ├── index.html ├── model_InkModel.js.html ├── model_RecognizerContext.js.html ├── model_StrokeComponent.js.html ├── model_Symbol.js.html ├── model_UndoRedoContext.js.html ├── model_UndoRedoManager.js.html ├── recognizer_CryptoHelper.js.html ├── recognizer_DefaultRecognizer.js.html ├── recognizer_RecognizerService.js.html ├── recognizer_rest_iinkRestRecognizer.js.html ├── recognizer_rest_networkInterface.js.html ├── recognizer_websocket_WsBuilder.js.html ├── recognizer_websocket_WsRecognizerUtil.js.html ├── recognizer_websocket_iinkWsRecognizer.js.html ├── recognizer_websocket_networkWSInterface.js.html ├── renderer_QuadraticUtils.js.html ├── renderer_canvas_CanvasRenderer.js.html ├── renderer_canvas_ImageRenderer.js.html ├── renderer_canvas_stroker_QuadraticCanvasStroker.js.html ├── renderer_canvas_symbols_MathSymbolCanvasRenderer.js.html ├── renderer_canvas_symbols_ShapeSymbolCanvasRenderer.js.html ├── renderer_canvas_symbols_StrokeSymbolCanvasRenderer.js.html ├── renderer_canvas_symbols_TextSymbolCanvasRenderer.js.html ├── renderer_svg_SVGRenderer.js.html ├── renderer_svg_stroker_QuadraticSVGStroker.js.html ├── renderer_svg_symbols_StrokeSymbolSVGRenderer.js.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js ├── smartguide_SmartGuide.js.html ├── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css └── util_PromiseHelper.js.html ├── examples ├── assets │ └── img │ │ ├── clear.svg │ │ ├── document.svg │ │ ├── edit.svg │ │ ├── eraser.svg │ │ ├── pen.svg │ │ ├── plus.svg │ │ ├── redo.svg │ │ └── undo.svg ├── dev │ ├── debug.html │ ├── index.html │ └── style │ │ ├── guide-text.html │ │ ├── style-math.html │ │ └── style-text.html ├── examples.css ├── experimental │ ├── websocket_diagram_iink.html │ └── websocket_text_document_iink.html ├── index.html ├── non-version-specific │ ├── change_configuration.html │ ├── handle_errors.html │ └── on_demand_exports.html └── v4 │ ├── diagram.css │ ├── my-custom-class.css │ ├── rest_diagram_iink.html │ ├── rest_math_iink.html │ ├── rest_no_ui.html │ ├── rest_raw_content_iink.html │ ├── rest_text_iink.html │ ├── rest_text_iink_eraser.html │ ├── websocket_math_custom_resources.html │ ├── websocket_math_custom_resources_compiled.html │ ├── websocket_math_iink.html │ ├── websocket_math_iink_eraser.html │ ├── websocket_math_import_jiix.html │ ├── websocket_math_inside_page.html │ ├── websocket_text_custom_lexicon.html │ ├── websocket_text_custom_resources.html │ ├── websocket_text_customize_editor_css.html │ ├── websocket_text_customize_stroke_style.html │ ├── websocket_text_file_export.html │ ├── websocket_text_highlight_words.html │ ├── websocket_text_iink.html │ ├── websocket_text_iink_eraser.html │ ├── websocket_text_iink_import_jiix.html │ ├── websocket_text_iink_no_guides.html │ ├── websocket_text_iink_search.html │ ├── websocket_text_import_content.html │ ├── websocket_text_interact.html │ ├── websocket_text_local_storage_text.html │ ├── websocket_text_multiple_inputs.html │ ├── websocket_text_multiple_inputs.js │ └── websocket_text_pointer_events.html ├── index.html ├── package-lock.json ├── package.json ├── preview.gif ├── src ├── Editor.js ├── EditorFacade.js ├── configuration │ ├── Constants.js │ ├── DefaultBehaviors.js │ ├── DefaultConfiguration.js │ ├── DefaultPenStyle.js │ ├── DefaultTheme.js │ └── LoggerConfig.js ├── eastereggs │ └── InkImporter.js ├── event │ └── Event.js ├── grabber │ └── PointerEventGrabber.js ├── iink.css ├── iink.js ├── model │ ├── InkModel.js │ ├── RecognizerContext.js │ ├── StrokeComponent.js │ ├── Symbol.js │ ├── UndoRedoContext.js │ └── UndoRedoManager.js ├── recognizer │ ├── CryptoHelper.js │ ├── DefaultRecognizer.js │ ├── RecognizerService.js │ ├── rest │ │ ├── iinkRestRecognizer.js │ │ └── networkInterface.js │ └── websocket │ │ ├── WsBuilder.js │ │ ├── WsRecognizerUtil.js │ │ ├── iinkWsRecognizer.js │ │ └── networkWSInterface.js ├── renderer │ ├── QuadraticUtils.js │ ├── canvas │ │ ├── CanvasRenderer.js │ │ ├── ImageRenderer.js │ │ ├── stroker │ │ │ └── QuadraticCanvasStroker.js │ │ └── symbols │ │ │ ├── MathSymbolCanvasRenderer.js │ │ │ ├── ShapeSymbolCanvasRenderer.js │ │ │ ├── StrokeSymbolCanvasRenderer.js │ │ │ └── TextSymbolCanvasRenderer.js │ └── svg │ │ ├── SVGRenderer.js │ │ ├── stroker │ │ └── QuadraticSVGStroker.js │ │ └── symbols │ │ └── StrokeSymbolSVGRenderer.js ├── smartguide │ └── SmartGuide.js └── util │ └── PromiseHelper.js ├── test ├── .eslintrc.js ├── lib │ ├── configuration.js │ ├── inks │ │ ├── 3times2.json │ │ ├── emphasized.json │ │ ├── equation.json │ │ ├── equation2.json │ │ ├── equation3.json │ │ ├── fence.json │ │ ├── fourSquare.json │ │ ├── hello.json │ │ ├── helloHowAreYou.json │ │ ├── helloStrike.json │ │ ├── highlighted.json │ │ ├── music.json │ │ ├── one.json │ │ ├── rabText.json │ │ ├── rc_de_291.json │ │ ├── rc_es_233.json │ │ ├── rc_fr_187.json │ │ ├── rc_fr_simple.json │ │ ├── rc_it_216.json │ │ ├── rc_ja_87.json │ │ ├── rc_ja_90.json │ │ ├── rc_ko_262.json │ │ ├── rc_ko_282.json │ │ ├── shape.json │ │ ├── system.json │ │ └── welcome_en_US.json │ └── inksDatas.js ├── mocha │ └── partial │ │ ├── 00-configuration │ │ ├── Constants.spec.babel.js │ │ ├── DefaultBehaviors.spec.babel.js │ │ ├── DefaultConfiguration.spec.babel.js │ │ └── LoggerConfig.spec.babel.js │ │ ├── 01-model │ │ ├── InkModel.spec.babel.js │ │ ├── StrokeComponent.spec.babel.js │ │ └── UndoRedoManager.spec.babel.js │ │ ├── 02-behaviors │ │ └── PointerEventGrabber.spec.babel.js │ │ └── CryptoHelper.spec.babel.js └── playwright │ ├── 01-ws-math.spec.js │ ├── 02-ws-math-import.spec.js │ ├── 03-ws-text.spec.js │ ├── 04-ws-text-hightlight-word.spec.js │ ├── 05-rest-text.spec.js │ ├── 06-ws-math-custom-resources.spec.js │ ├── 07-ws-text-custom-lexicon.spec.js │ ├── 08-rest-raw-content.spec.js │ ├── 09-change-configuration.spec.js │ ├── 10-ws-text-big-text.spec.js │ └── helper │ ├── index.js │ └── mochaHooks.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env" 5 | ] 6 | ], 7 | "plugins": [ 8 | "@babel/transform-runtime" 9 | ] 10 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=4 7 | 8 | [{.babelrc,.stylelintrc,.eslintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{*.applejs,*.js,*.html}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | [*.js.flow] 17 | indent_style=space 18 | indent_size=2 19 | 20 | [{jshint.json,*.jshintrc}] 21 | indent_style=space 22 | indent_size=2 23 | 24 | [{*.jscs.json,*.jscsrc}] 25 | indent_style=space 26 | indent_size=2 27 | 28 | [*.scss] 29 | indent_style=space 30 | indent_size=2 31 | 32 | [*.coffee] 33 | indent_style=space 34 | indent_size=2 35 | 36 | [{.analysis_options,*.yml,*.yaml}] 37 | indent_style=space 38 | indent_size=2 39 | 40 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: [ 7 | 'standard' 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 2018, 11 | sourceType: 'module' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | **/*.iml 3 | **/*.idea/ 4 | /bower_components/ 5 | **/*/delivery/ 6 | /test/**/*.xml 7 | /test/mocha/results/ 8 | /test/nightwatch/results/ 9 | /test/webdriverio/results/ 10 | /test/e2e/* 11 | .vscode -------------------------------------------------------------------------------- /.mocharc.e2e.env.js: -------------------------------------------------------------------------------- 1 | process.env.LAUNCH_URL = process.env.LAUNCH_URL || 'http://localhost:8080' 2 | process.env.HEADLESS = true 3 | process.env.BROWSER = process.env.BROWSER || 'chromium' -------------------------------------------------------------------------------- /.mocharc.e2e.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exit: false, 3 | recursive: true, 4 | spec: './test/playwright/*.spec.js', 5 | require: '.mocharc.e2e.env.js,./test/playwright/helper/mochaHooks.js', 6 | timeout: 30000, 7 | parallel: false, 8 | reporter: 'spec' 9 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [v2.0.2](https://github.com/MyScript/iinkJS/tree/v2.0.2) 2 | 3 | ## Bug fix 4 | - responseCallback() always raises an error [link](https://github.com/MyScript/iinkJS/issues/18) 5 | - add query param to applicationKey 6 | # [v2.0.1](https://github.com/MyScript/iinkJS/tree/v2.0.1) 7 | 8 | ## Bug fix 9 | - bad link on the Get source code for Change configuration 10 | - enhance iink eraser websocket sample 11 | - math example: empty mathML is displayed in result instead of nothing 12 | - customize example, colour picker disappears after undo-redo 13 | - gesture.enable = false broken 14 | - can change configuration if first host is wrong 15 | 16 | # [v1.5.4](https://github.com/MyScript/iinkJS/tree/v1.5.4) 17 | 18 | ## Features 19 | - minor modification for specific integration 20 | # [v1.5.3](https://github.com/MyScript/iinkJS/tree/v1.5.3) 21 | 22 | ## Bug fix 23 | @Editor 24 | - fix change configuration IInks doesn't set the right font 25 | 26 | # [v1.5.2](https://github.com/MyScript/iinkJS/tree/v1.5.2) 27 | 28 | ## Bug fix 29 | @Editor 30 | - fix change configuration ink disappears in example configured in REST 31 | 32 | # [v1.5.1](https://github.com/MyScript/iinkJS/tree/v1.5.1) 33 | 34 | ## Bug fix 35 | @Editor 36 | - fix change configuration restart websocket connection 37 | - fix lost connection due to inactivity is now displayed 38 | - fix style is wrapped by global class and can be customized 39 | 40 | @examples 41 | - fix bad position of the searching highlight in searching text example 42 | - new examples with eraser 43 | 44 | ## Features 45 | - erase mode is now an option in websocket text 46 | 47 | ## Chore 48 | - refactor of examples 49 | 50 | # [v1.4.5](https://github.com/MyScript/iinkJS/tree/v1.4.5) 51 | 52 | ## Bug fix 53 | 54 | - fix missing ink on iOS with Scribble feature on 55 | 56 | # [v1.4.4](https://github.com/MyScript/iinkJS/tree/v1.4.4) 57 | 58 | ## Features 59 | 60 | - REST requests use `fetch` instead of `XMLHttpRequest` 61 | 62 | ## Bugs fix 63 | 64 | @Editor 65 | - fix setTheme not sent on reconnect or language change 66 | - fix resize on REST mode 67 | 68 | @examples 69 | - remove mixed-content image 70 | 71 | @docs 72 | - generated directly in sub folder /docs and accessible at https://myscript.github.io/iinkJS/docs/ 73 | 74 | # [v1.4.3](https://github.com/MyScript/iinkJS/tree/v1.4.3) 75 | 76 | ## Bugs fix 77 | 78 | @Editor 79 | - fix `response is not a function` on reconnect 80 | - fix setTheme error on init 81 | - fix setTheme sent twice on init 82 | 83 | @examples 84 | - update katex to 0.12.0 85 | - simplify clean latex methods 86 | 87 | ## Chore 88 | 89 | - chore(deps): update rollup-plugin-terser to v7 90 | 91 | # [v1.4.2](https://github.com/MyScript/iinkJS/tree/v1.4.2) 92 | 93 | ## Bugs fix 94 | 95 | - fix(reco): last export not taken 96 | 97 | ## Chore 98 | 99 | - chore(deps): update minimist to 1.2.5 100 | - chore(deps): bump lodash from 4.17.15 to 4.17.19 101 | 102 | # [v1.4.1](https://github.com/MyScript/iinkJS/tree/v1.4.1) 103 | 104 | ## Breaking changes 105 | 106 | - ⚠ Package was renamed from `myscript` to `iink-js` to synchronize our SDK release version 107 | - V3 API is not available anymore, as such, configuration : `recognitionParams.(v3|v4)` was renamed to `recognitionParams.iink` 108 | - Remove stats from editor 109 | - Callbacks have been replaced by Promises 110 | 111 | ## Features 112 | 113 | - Editor exposes a new method to properly close websocket connection 114 | - Editor makes an API call to get list of available languages. 115 | 116 | ## Bugs fix 117 | 118 | @Editor 119 | - Fix `canClear` flag 120 | - Fix `clear` method 121 | - Fix error display 122 | - Fix lost strokes on reconnection 123 | 124 | @examples 125 | - Fix scrolling on iOS 126 | - Fix click on iOS after simple recognition 127 | - Fix export to file with FileSaver 128 | 129 | ### Chore 130 | 131 | - Remove `esdoc` to use `jsdoc` with npx 132 | - Upgrade tooling 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We gladly welcome pull requests to iinkJS. If you have any questions, or need help to solve a problem, feel free to stop by the [MyScript forum](https://developer-support.myscript.com/support/discussions/forums/16000096021). 4 | 5 | ## CLA 6 | 7 | In order to contribute, you must first agree to the **Contributor License Agreement** available [here](http://goo.gl/forms/YyzZ9VSvYG). 8 | 9 | Make sure you read the article "[Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/)" to understand the contributing process. -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | 2 | def debianBuildDockerImage 3 | String dockerArgs = '-v /var/run/docker.sock:/var/run/docker.sock --userns host --privileged -v $HOME:$HOME -e "HOME=${HOME}' 4 | 5 | pipeline { 6 | agent { label "docker" } 7 | options { disableConcurrentBuilds() } 8 | environment { 9 | PROJECTNAME = "iink-js ${env.BRANCH_NAME}" 10 | PROJECTHOME = '/tmp/iink-js' 11 | PROJECT_DIR = "${WORKSPACE.replace('/var/jenkins_home/workspace','/dockervolumes/cloud/master/jenkins/workspace')}" 12 | APPLICATION_KEY = credentials('APPLICATION_KEY') 13 | HMAC_KEY = credentials('HMAC_KEY') 14 | MAKE_ARGS =" PROJECT_DIR=${env.PROJECT_DIR} HOME=${env.PROJECTHOME} BUILDID=${env.BUILD_NUMBER} DEV_APPLICATIONKEY=${env.APPLICATION_KEY} DEV_HMACKEY=${env.HMAC_KEY} " 15 | } 16 | 17 | stages { 18 | 19 | stage('Build docker builder') { 20 | steps { 21 | script { 22 | debianBuildDockerImage = docker.build("iink-js.debian-builder:${env.BUILD_ID}", '-f ./docker/builder/Dockerfile ./') 23 | } 24 | } 25 | } 26 | 27 | stage ('purge'){ 28 | steps { 29 | script { 30 | debianBuildDockerImage.inside(dockerArgs) { 31 | sh "make ${env.MAKE_ARGS} purge" 32 | } 33 | } 34 | } 35 | } 36 | 37 | stage ('prepare'){ 38 | steps { 39 | script { 40 | debianBuildDockerImage.inside(dockerArgs) { 41 | sh "make ${env.MAKE_ARGS} prepare" 42 | sh "make ${env.MAKE_ARGS} docker-wait-tcp" 43 | } 44 | } 45 | } 46 | } 47 | 48 | stage ('init_examples'){ 49 | steps { 50 | script { 51 | debianBuildDockerImage.inside(dockerArgs) { 52 | sh "make ${env.MAKE_ARGS} docker-examples init_examples" 53 | } 54 | } 55 | } 56 | } 57 | 58 | stage('test browser with legacy server') { 59 | 60 | failFast false 61 | 62 | parallel { 63 | 64 | stage ('test-chromium'){ 65 | steps { 66 | script { 67 | debianBuildDockerImage.inside(dockerArgs) { 68 | sh "BROWSER=chromium make ${env.MAKE_ARGS} test-e2e" 69 | } 70 | } 71 | } 72 | } 73 | 74 | stage ('test-webkit'){ 75 | steps { 76 | script { 77 | debianBuildDockerImage.inside(dockerArgs) { 78 | sh "BROWSER=webkit make ${env.MAKE_ARGS} test-e2e" 79 | } 80 | } 81 | } 82 | } 83 | 84 | stage ('test-firefox'){ 85 | steps { 86 | script { 87 | debianBuildDockerImage.inside(dockerArgs) { 88 | sh "BROWSER=firefox make ${env.MAKE_ARGS} test-e2e" 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | } 96 | 97 | stage ('audit'){ 98 | steps { 99 | script { 100 | debianBuildDockerImage.inside(dockerArgs) { 101 | sh "npm audit --production" 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | post { 109 | always { 110 | sh "make ${env.MAKE_ARGS} killdocker" 111 | } 112 | 113 | success { 114 | slackSend color: "good", message: "${env.PROJECTNAME}: Build success ${env.JOB_NAME} ${env.BUILD_NUMBER}." 115 | } 116 | unstable { 117 | slackSend color: "warning", message: "${env.PROJECTNAME}: Unstable build, ${currentBuild.fullDisplayName} is unstable" 118 | } 119 | failure { 120 | slackSend color: "danger", message: "@group ${env.PROJECTNAME}: FAILURE, ${currentBuild.fullDisplayName} failed see there ${env.BUILD_URL}" 121 | } 122 | /* changed { 123 | slackSend color: "good", message: "${env.PROJECTNAME}: Build changed" 124 | }*/ 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright MyScript. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | Unless required by applicable law or agreed to in writing, software 7 | distributed under the License is distributed on an "AS IS" BASIS, 8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | See the License for the specific language governing permissions and 10 | limitations under the License. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.inc 2 | 3 | ALL: clean prepare docker test ## (default) Build all and launch test. 4 | 5 | .PHONY: ALL purge clean prepare build docker test docs 6 | 7 | purge: ## Reset the local directory as if a fresh git checkout was just make. 8 | @rm -rf node_modules 9 | 10 | clean: ## Remove all produced binaries. 11 | @rm -rf dist 12 | @rm -rf docs 13 | 14 | prepare: ## Install all dependencies. 15 | @PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --force 16 | 17 | build: clean ## Building the dist files from sources. 18 | @npm run build 19 | 20 | docs: ## Building the doc files from sources. 21 | @npm run docs 22 | 23 | docker-wait-tcp: 24 | @cd docker/wait-tcp/ && docker build -t $(WAITTCP_DOCKERREPOSITORY) . 25 | 26 | docker-examples: build ## Build the docker image containing last version of myscript-js and examples. 27 | @rm -rf docker/examples/delivery/ 28 | @mkdir -p docker/examples/delivery 29 | @cp -R dist docker/examples/delivery/ 30 | @cp -R examples docker/examples/delivery/ 31 | @cp -R node_modules docker/examples/delivery/ 32 | @cd docker/examples/ && \ 33 | docker build \ 34 | --build-arg applicationkey=${DEV_APPLICATIONKEY} \ 35 | --build-arg hmackey=${DEV_HMACKEY} \ 36 | -t $(EXAMPLES_DOCKERREPOSITORY) . 37 | 38 | killdocker: 39 | @docker ps -a | grep "iinkjs-$(DOCKERTAG)-$(BUILDENV)-" | awk '{print $$1}' | xargs -r docker rm -f 2>/dev/null 1>/dev/null || true 40 | 41 | 42 | local-test-e2e: docker-examples init_examples 43 | @$(MAKE) BROWSER=$(BROWSER) test-e2e 44 | 45 | test-e2e: 46 | @if [[ $(DEVLOCAL) == true ]]; then \ 47 | EXAMPLES_IP=localhost; \ 48 | else \ 49 | EXAMPLES_IP=$$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $(TEST_DOCKER_EXAMPLES_INSTANCE_NAME)); \ 50 | fi && \ 51 | docker run -i --rm \ 52 | -v $(CURRENT_PWD):/home/pwuser/tests \ 53 | $(DOCKER_EXAMPLES_PARAMETERS) \ 54 | -e LAUNCH_URL="http://$${EXAMPLES_IP}:$(EXAMPLES_LISTEN_PORT)" \ 55 | -e BROWSER=$(BROWSER) \ 56 | -w "/home/pwuser/tests" \ 57 | --name "playwright-$(BROWSER)-$(BUILDID)" mcr.microsoft.com/playwright:v1.16.0 \ 58 | yarn test:e2e 59 | 60 | dev-test: docker-examples init_examples ## Launch all the requirements for launching tests 61 | 62 | _launch_examples: 63 | @echo "Starting examples container!" 64 | @docker run -d \ 65 | -e "LISTEN_PORT=$(EXAMPLES_LISTEN_PORT)" \ 66 | -e "APISCHEME=$(APISCHEME)" \ 67 | -e "APIHOST=$(APIHOST)" \ 68 | -e "APPLICATIONKEY=$(DEV_APPLICATIONKEY)" \ 69 | -e "HMACKEY=$(DEV_HMACKEY)" \ 70 | $(DOCKER_EXAMPLES_PARAMETERS) \ 71 | --name $(TEST_DOCKER_EXAMPLES_INSTANCE_NAME) $(EXAMPLES_DOCKERREPOSITORY) 72 | 73 | _check_examples: 74 | @docker run --rm \ 75 | --link $(TEST_DOCKER_EXAMPLES_INSTANCE_NAME):WAITHOST \ 76 | -e "WAIT_PORT=$(EXAMPLES_LISTEN_PORT)" \ 77 | -e "WAIT_SERVICE=Test examples" \ 78 | $(WAITTCP_DOCKERREPOSITORY) 79 | @echo "Examples started!" 80 | 81 | init_examples: _launch_examples _check_examples 82 | 83 | help: ## This help. 84 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 85 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | GIT_VERSION := $(shell git describe --tags --long --always) 3 | CURRENT_USER_UID := $(shell id -u) 4 | CURRENT_USER_GID := $(shell id -g) 5 | CURRENT_PWD := $(shell pwd) 6 | 7 | ifeq ($(findstring dev-,$(MAKECMDGOALS)),) 8 | BUILDID := $(shell date +"%Y-%m-%d_%H_%M_%S_%N") 9 | else 10 | BUILDID := DEV 11 | endif 12 | 13 | FULL = false 14 | OFFLINE = false 15 | IINKAPILOCAL = false 16 | CDKAPILOCAL = false 17 | DEVLOCAL = false 18 | CURRENT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 19 | PROJECT_DIR ?= $(CURRENT_DIR) 20 | GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 21 | 22 | MAKE := $(MAKE) PROJECT_DIR=$(PROJECT_DIR) BUILDID=$(BUILDID) --no-print-directory 23 | NPM_CACHE = $(HOME)/.npm 24 | 25 | DOCKERTAG := master 26 | GIT_TAG = 2.0.0 27 | 28 | REGISTRY = registry.corp.myscript.com:5000 29 | DOC_DOCKERREPOSITORY = $(REGISTRY)/iinkjs-docs:$(DOCKERTAG) 30 | EXAMPLES_DOCKERREPOSITORY = $(REGISTRY)/iinkjs-examples:$(DOCKERTAG) 31 | 32 | CONFIGURATION_DOCKERTAG := 1.0.0 33 | MOCHA_DOCKERREPOSITORY = $(REGISTRY)/iink-js-mocha:$(CONFIGURATION_DOCKERTAG) 34 | WAITTCP_DOCKERREPOSITORY = $(REGISTRY)/iink-js-wait-tcp:$(CONFIGURATION_DOCKERTAG) 35 | 36 | BUILDENV := test 37 | TEST_DOCKER_NAME_PREFIX := iinkjs-$(DOCKERTAG)-$(BUILDENV)-$(BUILDID) 38 | TEST_DOCKER_EXAMPLES_INSTANCE_NAME := $(TEST_DOCKER_NAME_PREFIX)-examples 39 | 40 | APPLICATIONKEY := 7d223f9e-a3cb-4213-ba4b-85e930605f8b 41 | HMACKEY := 5ab1935e-529a-4d48-a695-158450e52b13 42 | 43 | DEV_APPLICATIONKEY := 515131ab-35fa-411c-bb4d-3917e00faf60 44 | DEV_HMACKEY := 54b2ca8a-6752-469d-87dd-553bb450e9ad 45 | 46 | APIHOST := cloud-internal-master.corp.myscript.com 47 | APISCHEME := https 48 | ifeq ($(CDKAPILOCAL),true) 49 | APIHOST := localhost:8894 50 | APISCHEME := http 51 | endif 52 | ifeq ($(IINKAPILOCAL),true) 53 | APIHOST := localhost:8897 54 | APISCHEME := http 55 | endif 56 | 57 | ifeq ($(DEVLOCAL),true) 58 | DOCKER_EXAMPLES_PARAMETERS := --net=host --userns=host 59 | EXAMPLES_LISTEN_PORT := 8080 60 | else 61 | EXAMPLES_LISTEN_PORT := 80 62 | endif 63 | -------------------------------------------------------------------------------- /SETUP.md: -------------------------------------------------------------------------------- 1 | # Environment setup 2 | 3 | ## Configure project 4 | 5 | 1. Download sources 6 | 2. Install dependencies. 7 | * `npm install` 8 | 3. Build the project using our npm script. 9 | * `npm run build` 10 | 4. Run the server and the live reload using our npm script. 11 | * `npm run dev`. Examples will be available on `http://localhost:8080/examples/index.html` 12 | 13 | **Start coding** 14 | 15 | 5. Debug using your favorite browser dev tools. The sources will be available under the webpack source folder (for chrome dev tools). Every change in sources will trigger a rebuild with linter and basic tests. 16 | 17 | ## Set up your IDE 18 | 19 | At MyScript, we use WebStorm to develop our library. You can find below some help to configure WebStorm like we do. Obviously, feel free to use any code editor as the configuration can be adapted for any editor. 20 | 21 | ### WebStorm 22 | 23 | Configure all the librairies to have a good code completion [https://blog.jetbrains.com/webstorm/2014/07/how-webstorm-works-completion-for-javascript-libraries/](https://blog.jetbrains.com/webstorm/2014/07/how-webstorm-works-completion-for-javascript-libraries/) 24 | 25 | Configure the code format by going in Files -> Settings the Editor -> Code Style -> Javascript and press the button manage. Load the configuration file locate in ./dev/AIRBNB. This will allow you to reformat the javascript code with IDEA formatter as expected by most of configured ES6 rules. 26 | 27 | Debug mocha test 28 | - Add a mocha test configuration with test directory 29 | - Configure the launcher with the extra mocha option `--compilers js:babel-core/register` 30 | 31 | Activate ESLint checks [https://www.jetbrains.com/help/webstorm/2016.2/eslint.html](https://www.jetbrains.com/help/webstorm/2016.2/eslint.html) and use the automatic search option. 32 | -------------------------------------------------------------------------------- /assets/angular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/rollup.config.build.js: -------------------------------------------------------------------------------- 1 | import config from './rollup.config' 2 | 3 | export default config 4 | -------------------------------------------------------------------------------- /config/rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | import serve from 'rollup-plugin-serve' 2 | import livereload from 'rollup-plugin-livereload' 3 | import config from './rollup.config' 4 | 5 | config[0].plugins.push( 6 | serve({ 7 | open: true, 8 | verbose: true, 9 | contentBase: '', 10 | host: 'localhost', 11 | port: 8080, 12 | headers: { 13 | 'Access-Control-Allow-Origin': '*' 14 | } 15 | }), 16 | livereload({ 17 | watch: [ 18 | 'dist', 19 | 'examples' 20 | ] 21 | }) 22 | ) 23 | 24 | config.watch = { 25 | include: 'src/**' 26 | } 27 | 28 | export default config 29 | -------------------------------------------------------------------------------- /config/rollup.config.js: -------------------------------------------------------------------------------- 1 | import json from '@rollup/plugin-json' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | import babel from '@rollup/plugin-babel' 5 | import { terser } from 'rollup-plugin-terser' 6 | import postcss from 'rollup-plugin-postcss' 7 | import toImport from 'postcss-import' 8 | 9 | const plugins = [ 10 | json(), 11 | resolve(), 12 | commonjs({ 13 | namedExports: { 14 | 'node_modules/loglevel/lib/loglevel.js': ['noConflict'] 15 | } 16 | }), 17 | babel({ 18 | exclude: 'node_modules/**', 19 | babelrc: false, 20 | babelHelpers: 'runtime', 21 | presets: [ 22 | ['@babel/env'] 23 | ], 24 | plugins: [ 25 | '@babel/transform-runtime' 26 | ] 27 | }), 28 | terser({ 29 | keep_fnames: true 30 | }), 31 | postcss({ 32 | plugins: [toImport], 33 | inject: false 34 | }) 35 | ] 36 | 37 | export default [{ 38 | input: 'src/iink.js', 39 | output: [ 40 | { 41 | name: 'iink', 42 | file: 'dist/iink.min.js', 43 | format: 'umd', 44 | exports: 'named' 45 | } 46 | ], 47 | plugins 48 | }, { 49 | input: 'src/iink.js', 50 | output: [ 51 | { 52 | file: 'dist/iink.esm.js', 53 | format: 'es' 54 | } 55 | ], 56 | plugins 57 | }] 58 | -------------------------------------------------------------------------------- /docker/builder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | ENV DEBIAN_FRONTEND noninteractive 3 | RUN apt update -yyq; 4 | RUN apt install make npm yarn git curl -yyq 5 | RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh ./get-docker.sh 6 | -------------------------------------------------------------------------------- /docker/examples/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:stable-alpine 2 | 3 | COPY nginx.conf /etc/nginx/nginx.conf 4 | COPY delivery /usr/share/nginx/html 5 | COPY *.sh / 6 | RUN chmod a+x /entrypoint.sh 7 | 8 | RUN /createIndexFile.sh 9 | 10 | ENV LISTEN_PORT 80 11 | ENV APISCHEME "https" 12 | ENV APIHOST "webdemoapi.myscript.com" 13 | ARG applicationkey 14 | ARG hmackey 15 | ENV DEV_APPLICATIONKEY=$applicationkey 16 | ENV DEV_HMACKEY=$hmackey 17 | 18 | ENTRYPOINT ["/entrypoint.sh"] 19 | -------------------------------------------------------------------------------- /docker/examples/createIndexFile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo " 6 | 7 | iinkJS examples 8 | 9 | 10 |

Examples[view]

11 | 12 | 13 | " > /usr/share/nginx/html/index.html 14 | -------------------------------------------------------------------------------- /docker/examples/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "Running nginx" 6 | echo "========================" 7 | echo "LISTEN_PORT=>>${LISTEN_PORT}<<" 8 | echo "APIHOST=${APIHOST}" 9 | echo "APISCHEME=${APISCHEME}" 10 | echo "APPLICATIONKEY=${DEV_APPLICATIONKEY}" 11 | echo "HMACKEY=${DEV_HMACKEY}" 12 | 13 | 14 | sed -i -e "s/\(listen\s\+\)\(80;\)/\1${LISTEN_PORT};/g" /etc/nginx/conf.d/default.conf 15 | cat /etc/nginx/conf.d/default.conf 16 | 17 | for filename in /usr/share/nginx/html/examples/**/*.*; do 18 | sed -i "s/scheme: 'https'/scheme: '${APISCHEME}'/g" "${filename}" 19 | 20 | sed -i "s/webdemoapi.myscript.com/${APIHOST}/g" "${filename}" 21 | sed -i "s/newcloud.myscript.com/${APIHOST}/g" "${filename}" 22 | 23 | sed -i "s/515131ab-35fa-411c-bb4d-3917e00faf60/${DEV_APPLICATIONKEY}/g" "${filename}" 24 | sed -i "s/54b2ca8a-6752-469d-87dd-553bb450e9ad/${DEV_HMACKEY}/g" "${filename}" 25 | 26 | sed -i "s/7d223f9e-a3cb-4213-ba4b-85e930605f8b/${DEV_APPLICATIONKEY}/g" "${filename}" 27 | sed -i "s/5ab1935e-529a-4d48-a695-158450e52b13/${DEV_HMACKEY}/g" "${filename}" 28 | done 29 | 30 | nginx 31 | -------------------------------------------------------------------------------- /docker/examples/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | daemon off; 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | '$status $body_bytes_sent "$http_referer" ' 19 | '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | access_log /var/log/nginx/access.log main; 22 | 23 | sendfile off; 24 | expires 0; 25 | autoindex on; 26 | #tcp_nopush on; 27 | 28 | keepalive_timeout 65; 29 | 30 | #gzip on; 31 | 32 | include /etc/nginx/conf.d/*.conf; 33 | } 34 | -------------------------------------------------------------------------------- /docker/wait-tcp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | ENV DEBIAN_FRONTEND noninteractive 3 | 4 | RUN apt update -yy;apt install -yy curl jq netcat-openbsd;rm -rf /var/lib/apt/* /var/cache/apt/* 5 | 6 | ENV WAIT_SERVICE "Undef" 7 | ENV WAIT_PORT 80 8 | ENV WAIT_HOST WAITHOST 9 | ENV TIMEOUT 90 10 | ENV DEBUG false 11 | ADD /entrypoint.sh /entrypoint.sh 12 | 13 | ENTRYPOINT ["/entrypoint.sh"] 14 | -------------------------------------------------------------------------------- /docker/wait-tcp/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ${DEBUG} != "false" ]; then 4 | set -xve 5 | fi 6 | 7 | echo -n " => Waiting for ${WAIT_SERVICE} on tcp Port ${WAIT_PORT}" 8 | CPT=0 9 | while ! nc -z -w 1 ${WAIT_HOST} ${WAIT_PORT}; do 10 | ((CPT++)) 11 | if [ ${CPT} -gt ${TIMEOUT} ]; then 12 | echo " Timeout!" 13 | exit 1 14 | fi; 15 | sleep 1 16 | echo -n . 17 | done 18 | echo " Found!" 19 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /examples/assets/img/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 11 Copy 3 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/assets/img/document.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/assets/img/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/assets/img/eraser.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /examples/assets/img/pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /examples/assets/img/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/assets/img/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/assets/img/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/dev/debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Debug 12 | 13 | 14 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 49 |
50 |
51 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /examples/experimental/websocket_diagram_iink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | WEBSOCKET Diagram iink 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 37 |
38 |
39 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /examples/experimental/websocket_text_document_iink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | WEBSOCKET Nebo iink 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 29 |
30 |
31 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /examples/non-version-specific/handle_errors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Handle error 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/non-version-specific/on_demand_exports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | On-demand exports iink 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 45 |
46 |
47 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/v4/diagram.css: -------------------------------------------------------------------------------- 1 | nav { 2 | background-color: white; 3 | border-top: none; 4 | flex-wrap: wrap-reverse; 5 | } 6 | 7 | .nav-group { 8 | overflow: auto; 9 | display: flex; 10 | align-items: center; 11 | } 12 | 13 | #progress { 14 | transition: width 10s; 15 | width: 0; 16 | height: 2px; 17 | background-color: #1A9FFF; 18 | opacity: 0; 19 | } 20 | 21 | .classic-btn { 22 | margin-left: 12px; 23 | display: flex; 24 | align-items: center; 25 | height: 38px; 26 | } 27 | 28 | .classic-btn > img { 29 | margin-right: 12px; 30 | height: 24px; 31 | } 32 | 33 | .delete { 34 | -webkit-touch-callout: none; 35 | -webkit-user-select: none; 36 | -moz-user-select: none; 37 | -ms-user-select: none; 38 | user-select: none; 39 | -moz-appearance: none; 40 | -webkit-appearance: none; 41 | background-color: rgba(10, 10, 10, 0.2); 42 | border: none; 43 | border-radius: 290486px; 44 | cursor: pointer; 45 | display: inline-block; 46 | -webkit-box-flex: 0; 47 | -ms-flex-positive: 0; 48 | flex-grow: 0; 49 | -ms-flex-negative: 0; 50 | flex-shrink: 0; 51 | font-size: 0; 52 | height: 20px; 53 | max-height: 20px; 54 | max-width: 20px; 55 | min-height: 20px; 56 | min-width: 20px; 57 | outline: none; 58 | vertical-align: top; 59 | width: 20px; 60 | position: absolute; 61 | right: 0.5rem; 62 | top: 0.5rem; 63 | } 64 | 65 | .delete:before, .delete:after { 66 | background-color: white; 67 | content: ""; 68 | display: block; 69 | left: 50%; 70 | position: absolute; 71 | top: 50%; 72 | -webkit-transform: translateX(-50%) translateY(-50%) rotate(45deg); 73 | transform: translateX(-50%) translateY(-50%) rotate(45deg); 74 | -webkit-transform-origin: center center; 75 | transform-origin: center center; 76 | } 77 | 78 | .delete:before { 79 | height: 2px; 80 | width: 50%; 81 | } 82 | 83 | .delete:after { 84 | height: 50%; 85 | width: 2px; 86 | } 87 | 88 | .delete:hover, .delete:focus { 89 | background-color: rgba(10, 10, 10, 0.3); 90 | } 91 | 92 | .delete:active { 93 | background-color: rgba(10, 10, 10, 0.4); 94 | } 95 | 96 | -------------------------------------------------------------------------------- /examples/v4/websocket_math_custom_resources_compiled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Custom resources math (pre-compiled) 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 |

This example use a math custom resource. The grammar restrict the recognition to only basic elementary school math (addition and substraction). The id of a pre-compiled grammar is sent.

27 |
28 |
29 | 44 |
45 |
46 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_custom_lexicon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Custom lexicon 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 |
27 | 40 |
41 |
42 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_custom_resources.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Custom pre-loaded resources 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 28 | 29 | 30 |
31 | 42 |
43 |
44 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_customize_editor_css.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Styling editor style 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 32 |
33 |
34 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_iink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | WEBSOCKET Text iink 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 32 |
33 |
34 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_iink_import_jiix.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Import jiix 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 34 |
35 |
36 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_iink_no_guides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | No guides 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 31 |
32 |
33 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_import_content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Import content 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 34 |
35 |
36 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_local_storage_text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | WEBSOCKET Text iink 12 | 13 | 14 | 15 | 16 | 17 | 30 | 31 | 32 | 33 |
34 | 45 |
46 |
47 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_multiple_inputs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Multiple inputs 12 | 13 | 14 | 15 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_pointer_events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Pointer events 12 | 13 | 14 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | 58 | 59 |
60 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iink-js", 3 | "version": "2.0.2", 4 | "main": "dist/iink.min.js", 5 | "module": "dist/iink.esm.js", 6 | "description": "iinkJS is the fastest way to integrate handwriting panel and recognition in your webapp", 7 | "keywords": [ 8 | "myscript", 9 | "javascript", 10 | "developer", 11 | "handwriting", 12 | "recognition", 13 | "cloud", 14 | "iink" 15 | ], 16 | "files": [ 17 | "dist" 18 | ], 19 | "license": "Apache-2.0", 20 | "homepage": "https://myscript.github.io/iinkJS/", 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/MyScript/iinkJS.git" 24 | }, 25 | "dependencies": { 26 | "@babel/runtime": "^7.9.2", 27 | "clipboard": "^1.7.1", 28 | "crypto-js": "^3.3.0", 29 | "d3-selection": "^1.4.1", 30 | "json-css": "^1.5.6", 31 | "lodash.merge": "^4.6.2", 32 | "loglevel": "^1.6.8", 33 | "perfect-scrollbar": "^1.5.0", 34 | "uuid-js": "^0.7.5" 35 | }, 36 | "devDependencies": { 37 | "@babel/cli": "^7.8.4", 38 | "@babel/core": "^7.9.0", 39 | "@babel/plugin-transform-runtime": "^7.9.0", 40 | "@babel/preset-env": "^7.9.5", 41 | "@babel/register": "^7.9.0", 42 | "@rollup/plugin-babel": "^5.0.0", 43 | "@rollup/plugin-json": "^4.0.2", 44 | "@rollup/plugin-node-resolve": "^6.1.0", 45 | "chai": "^4.3.4", 46 | "clean-css-cli": "^4.3.0", 47 | "eslint": "^6.8.0", 48 | "eslint-config-standard": "^14.1.1", 49 | "eslint-plugin-import": "^2.20.2", 50 | "eslint-plugin-node": "^11.1.0", 51 | "eslint-plugin-promise": "^4.2.1", 52 | "eslint-plugin-standard": "^4.0.1", 53 | "minami": "^1.2.3", 54 | "mocha": "^9.0.3", 55 | "mock-css-modules": "^2.0.0", 56 | "npm-run-all": "^4.1.5", 57 | "playwright": "1.16.0", 58 | "postcss-import": "^12.0.1", 59 | "rollup": "^2.18.0", 60 | "rollup-plugin-commonjs": "^10.1.0", 61 | "rollup-plugin-livereload": "^1.2.0", 62 | "rollup-plugin-postcss": "^2.6.2", 63 | "rollup-plugin-progress": "^1.1.1", 64 | "rollup-plugin-serve": "^1.0.1", 65 | "rollup-plugin-terser": "^7.0.1", 66 | "sinon": "^2.4.1", 67 | "taffydb": "^2.7.3" 68 | }, 69 | "scripts": { 70 | "lint": "eslint --ext js src test examples", 71 | "lint:fix": "eslint --ext js src test examples --fix", 72 | "docs": "npx jsdoc -R README.md -P '' -d docs -t node_modules/minami -r src", 73 | "minify-css": "cleancss -o dist/iink.min.css src/*.css", 74 | "test:mocha": "mocha --require @babel/register,mock-css-modules --recursive test/mocha/ --reporter progress", 75 | "test:mocha-xunit": "mocha --require @babel/register --recursive test/mocha/ --reporter xunit --reporter-options output=./test/mocha/results/xunit.xml", 76 | "test:e2e": "mocha --config .mocharc.e2e.js", 77 | "test:chromium": "BROWSER=chromium mocha --config .mocharc.e2e.js", 78 | "test:webkit": "BROWSER=webkit mocha --config .mocharc.e2e.js", 79 | "test:firefox": "BROWSER=firefox mocha --config .mocharc.e2e.js", 80 | "build:js": "rollup -c config/rollup.config.build.js --sourcemap", 81 | "build": "npm-run-all test:mocha lint build:js minify-css docs", 82 | "dev:js": "rollup -c config/rollup.config.dev.js -w --sourcemap", 83 | "dev": "npm-run-all minify-css dev:js", 84 | "start": "npm run dev" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/iinkJS/5e55c677617f56edde2877cf4c31231cf970c790/preview.gif -------------------------------------------------------------------------------- /src/EditorFacade.js: -------------------------------------------------------------------------------- 1 | import { editorLogger as logger } from './configuration/LoggerConfig' 2 | import { Editor } from './Editor' 3 | 4 | /** 5 | * Attach an Editor to a DOMElement 6 | * @param {Element} element DOM element to attach an editor 7 | * @param {Configuration} [configuration] Configuration to apply 8 | * @param {PenStyle} [penStyle] Pen style to apply 9 | * @param {Theme} [theme] Theme to apply 10 | * @param {Behaviors} [behaviors] Custom behaviors to apply 11 | * @param {String} [globalClassCSS] Replace global class css 'ms-editor' to customize style 12 | * @return {Editor} New editor 13 | */ 14 | export function register (element, configuration, penStyle, theme, behaviors, globalClassCSS) { 15 | logger.debug('Registering a new editor') 16 | return new Editor(element, configuration, penStyle, theme, behaviors, globalClassCSS) 17 | } 18 | 19 | /** 20 | * Return the list of available recognition languages 21 | * @param {Configuration} [configuration] Configuration to get the languages 22 | * @return {JSON} A list of available languages 23 | */ 24 | export async function getAvailableLanguageList (configuration) { 25 | try { 26 | if (configuration && configuration.recognitionParams && 27 | configuration.recognitionParams.server && configuration.recognitionParams.server.host) { 28 | const serverConfig = configuration.recognitionParams.server 29 | const response = await fetch(`${serverConfig.scheme}://${serverConfig.host}/api/v4.0/iink/availableLanguageList`) 30 | if (response && response.ok) { 31 | return response.json() 32 | } 33 | } else { 34 | console.error('Cannot get languages ! Please check your configuration!') 35 | } 36 | } catch (error) { 37 | console.error(error) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/configuration/Constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Constants 3 | */ 4 | 5 | const Constants = { 6 | EventType: { 7 | IDLE: 'idle', 8 | CHANGED: 'changed', 9 | IMPORTED: 'imported', 10 | EXPORTED: 'exported', 11 | CONVERTED: 'converted', 12 | RENDERED: 'rendered', // Internal use only 13 | LOADED: 'loaded', 14 | UNDO: 'undo', 15 | REDO: 'redo', 16 | CLEAR: 'clear', 17 | IMPORT: 'import', 18 | SUPPORTED_IMPORT_MIMETYPES: 'supportedImportMimeTypes', 19 | EXPORT: 'export', 20 | CONVERT: 'convert', 21 | ERROR: 'error' 22 | }, 23 | RecognitionType: { 24 | TEXT: 'TEXT', 25 | MATH: 'MATH', 26 | DIAGRAM: 'DIAGRAM', 27 | RAWCONTENT: 'Raw Content' 28 | }, 29 | Protocol: { 30 | WEBSOCKET: 'WEBSOCKET', 31 | REST: 'REST' 32 | }, 33 | ModelState: { 34 | INITIALIZING: 'INITIALIZING', 35 | INITIALIZED: 'INITIALIZED', 36 | EXPORTING: 'EXPORTING', 37 | EXPORTED: 'EXPORTED', 38 | PENDING: 'PENDING', 39 | MODIFIED: 'MODIFIED', 40 | ERROR: 'ERROR' 41 | }, 42 | Trigger: { 43 | QUIET_PERIOD: 'QUIET_PERIOD', 44 | POINTER_UP: 'POINTER_UP', 45 | DEMAND: 'DEMAND' 46 | }, 47 | Logger: { 48 | EDITOR: 'editor', 49 | MODEL: 'model', 50 | GRABBER: 'grabber', 51 | RENDERER: 'renderer', 52 | RECOGNIZER: 'recognizer', 53 | EVENT: 'event', 54 | UTIL: 'util', 55 | SMARTGUIDE: 'smartguide' 56 | }, 57 | LogLevel: { 58 | TRACE: 'TRACE', 59 | DEBUG: 'DEBUG', 60 | INFO: 'INFO', 61 | WARN: 'WARN', 62 | ERROR: 'ERROR' 63 | }, 64 | Languages: { 65 | zh_CN: 'Noto Sans CJK tc', 66 | zh_HK: 'Noto Sans CJK tc', 67 | zh_TW: 'Noto Sans CJK tc', 68 | ko_KR: 'Noto Sans CJK kr', 69 | ja_JP: 'Noto Sans CJK jp', 70 | default: 'MyScriptInter' 71 | }, 72 | Error: { 73 | NOT_REACHABLE: 'MyScript recognition server is not reachable. Please reload once you are connected.', 74 | WRONG_CREDENTIALS: 'Application credentials are invalid. Please check or regenerate your application key and hmackey.', 75 | TOO_OLD: 'Session is too old. Max Session Duration Reached.', 76 | NO_ACTIVITY: 'Session closed due to no activity.', 77 | CANT_ESTABLISH: 'Unable to establish a connection to the server. Check the host and your connectivity', 78 | CLOSE: 'Connection closed' 79 | }, 80 | Exports: { 81 | JIIX: 'application/vnd.myscript.jiix' 82 | } 83 | } 84 | export default Constants 85 | -------------------------------------------------------------------------------- /src/configuration/DefaultBehaviors.js: -------------------------------------------------------------------------------- 1 | import { editorLogger as logger } from './LoggerConfig' 2 | import * as PointerEventGrabber from '../grabber/PointerEventGrabber' 3 | import * as CanvasRenderer from '../renderer/canvas/CanvasRenderer' 4 | import * as QuadraticCanvasStroker from '../renderer/canvas/stroker/QuadraticCanvasStroker' 5 | import * as SVGRenderer from '../renderer/svg/SVGRenderer' 6 | import * as QuadraticSVGStroker from '../renderer/svg/stroker/QuadraticSVGStroker' 7 | import * as iinkRestRecognizer from '../recognizer/rest/iinkRestRecognizer' 8 | import * as iinkWsRecognizer from '../recognizer/websocket/iinkWsRecognizer' 9 | import emit from '../event/Event' 10 | 11 | /** 12 | * Current behavior 13 | * @typedef {Object} Behavior 14 | * @property {Grabber} grabber Grabber to capture strokes 15 | * @property {Stroker} stroker Stroker to draw stroke 16 | * @property {Renderer} renderer Renderer to draw on the editor 17 | * @property {Recognizer} recognizer Recognizer to call the recognition service 18 | * @property {Array} events Functions to handle model changes 19 | */ 20 | 21 | /** 22 | * Set of behaviors to be used by the {@link Editor} 23 | * @typedef {Object} Behaviors 24 | * @property {Grabber} grabber Grabber to capture strokes 25 | * @property {Array} strokerList List of stroker to draw stroke 26 | * @property {Array} rendererList List of renderer to draw on the editor 27 | * @property {Array} recognizerList Recognizers to call the recognition service 28 | * @property {function} getBehaviorFromConfiguration Get the current behavior to use regarding the current configuration 29 | * @property {Array} events Functions to handle model changes 30 | */ 31 | 32 | /** 33 | * Default behaviors 34 | * @type {Behaviors} 35 | */ 36 | export const defaultBehaviors = { 37 | grabber: PointerEventGrabber, 38 | strokerList: [QuadraticCanvasStroker, QuadraticSVGStroker], 39 | rendererList: [CanvasRenderer, SVGRenderer], 40 | recognizerList: [iinkRestRecognizer, iinkWsRecognizer], 41 | events: emit, 42 | getBehaviorFromConfiguration: (behaviors, configuration) => { 43 | const behavior = {} 44 | behavior.grabber = behaviors.grabber 45 | if (configuration) { 46 | if (configuration.recognitionParams.protocol === 'REST') { 47 | behavior.stroker = QuadraticCanvasStroker 48 | behavior.renderer = CanvasRenderer 49 | behavior.recognizer = iinkRestRecognizer 50 | } else { 51 | behavior.stroker = QuadraticSVGStroker 52 | behavior.renderer = SVGRenderer 53 | behavior.recognizer = iinkWsRecognizer 54 | } 55 | } 56 | behavior.events = behaviors.events 57 | return behavior 58 | } 59 | } 60 | 61 | /** 62 | * Generate behaviors 63 | * @param {Behaviors} behaviors Behaviors to be used 64 | * @return {Behaviors} Overridden behaviors 65 | */ 66 | export function overrideDefaultBehaviors (behaviors) { 67 | if (behaviors) { 68 | const currentBehaviors = { 69 | grabber: behaviors.grabber || defaultBehaviors.grabber, 70 | rendererList: behaviors.rendererList || defaultBehaviors.rendererList, 71 | strokerList: behaviors.strokerList || defaultBehaviors.strokerList, 72 | recognizerList: behaviors.recognizerList || defaultBehaviors.recognizerList, 73 | events: behaviors.events || defaultBehaviors.events, 74 | getBehaviorFromConfiguration: behaviors.getBehaviorFromConfiguration || defaultBehaviors.getBehaviorFromConfiguration 75 | } 76 | logger.debug('Override default behaviors', currentBehaviors) 77 | return currentBehaviors 78 | } 79 | return defaultBehaviors 80 | } 81 | 82 | export default defaultBehaviors 83 | -------------------------------------------------------------------------------- /src/configuration/DefaultPenStyle.js: -------------------------------------------------------------------------------- 1 | import JsonCSS from 'json-css' 2 | import merge from 'lodash.merge' 3 | import { editorLogger as logger } from './LoggerConfig' 4 | 5 | /** 6 | * @typedef {Object} PenStyle 7 | * @property {String} color=#000000 Color (supported formats rgb() rgba() hsl() hsla() #rgb #rgba #rrggbb #rrggbbaa) 8 | * @property {String} -myscript-pen-width=1 Width of strokes and primitives in mm (no other unit is supported yet) 9 | * @property {String} -myscript-pen-fill-style=none 10 | * @property {String} -myscript-pen-fill-color=#FFFFFF00 Color filled inside the area delimited by strokes and primitives 11 | */ 12 | 13 | /** 14 | * Default style 15 | * @type {PenStyle} 16 | */ 17 | const defaultPenStyle = undefined 18 | const parser = new JsonCSS() 19 | 20 | /** 21 | * Generate style 22 | * @param {PenStyle} style Custom style to be applied 23 | * @return {PenStyle} Overridden style 24 | */ 25 | export function overrideDefaultPenStyle (style) { 26 | const currentStyle = merge({}, defaultPenStyle, style === undefined ? {} : style) 27 | logger.debug('Override default pen style', currentStyle) 28 | return currentStyle 29 | } 30 | 31 | export function toCSS (penStyle) { // FIXME Ugly hack to parse JSON to CSS inline 32 | const css = parser.toCSS({ css: penStyle }) 33 | return css.substring(6, css.length - 3) 34 | } 35 | 36 | export function toJSON (penStyle) { // FIXME Ugly hack to parse CSS inline to JSON 37 | return parser.toJSON(`css {${penStyle}}`).css 38 | } 39 | 40 | export default defaultPenStyle 41 | -------------------------------------------------------------------------------- /src/configuration/DefaultTheme.js: -------------------------------------------------------------------------------- 1 | import JsonCSS from 'json-css' 2 | import merge from 'lodash.merge' 3 | import { editorLogger as logger } from './LoggerConfig' 4 | 5 | /** 6 | * @typedef {PenStyle} InkTheme 7 | */ 8 | /** 9 | * @typedef {Object} MathTheme 10 | * @property {String} font-family=STIXGeneral Font-family to be used 11 | */ 12 | /** 13 | * @typedef {Object} GeneratedTheme 14 | * @property {String} font-family=STIXGeneral Font-family to be used 15 | * @property {String} color=#A8A8A8FF Color to be used 16 | */ 17 | /** 18 | * @typedef {Object} TextTheme 19 | * @property {String} font-family=OpenSans Font-family to be used 20 | * @property {Number} font-size=10 Font-size to be used 21 | */ 22 | /** 23 | * @typedef {Object} Theme 24 | * @property {InkTheme} ink General settings 25 | * @property {MathTheme} .math Math theme 26 | * @property {GeneratedTheme} .math-solver Theme to be used for generated items 27 | * @property {TextTheme} .text Text theme 28 | */ 29 | 30 | /** 31 | * Default theme 32 | * @type {Theme} 33 | */ 34 | const defaultTheme = { 35 | ink: { 36 | color: '#000000', 37 | '-myscript-pen-width': 1, 38 | '-myscript-pen-fill-style': 'none', 39 | '-myscript-pen-fill-color': '#FFFFFF00' 40 | }, 41 | '.math': { 42 | 'font-family': 'STIXGeneral' 43 | }, 44 | '.math-solved': { 45 | 'font-family': 'STIXGeneral', 46 | color: '#A8A8A8FF' 47 | }, 48 | '.text': { 49 | 'font-family': 'MyScriptInter', 50 | 'font-size': 10 51 | } 52 | } 53 | const parser = new JsonCSS() 54 | 55 | /** 56 | * Generate theme 57 | * @param {Theme} theme Custom theme to be applied 58 | * @return {Theme} Overridden theme 59 | */ 60 | export function overrideDefaultTheme (theme) { 61 | const currentTheme = merge({}, defaultTheme, theme === undefined ? {} : theme) 62 | logger.debug('Override default theme', currentTheme) 63 | return currentTheme 64 | } 65 | 66 | export function toCSS (theme) { 67 | return parser.toCSS(theme) 68 | } 69 | 70 | export function toJSON (theme) { 71 | return parser.toJSON(theme) 72 | } 73 | 74 | export default defaultTheme 75 | -------------------------------------------------------------------------------- /src/configuration/LoggerConfig.js: -------------------------------------------------------------------------------- 1 | import * as loglevel from 'loglevel' 2 | import Constants from './Constants' 3 | 4 | /** 5 | * Main log instance 6 | * @type {Object} 7 | */ 8 | const log = loglevel.noConflict() 9 | export default log 10 | 11 | /** 12 | * Log editor events 13 | * @type {Object} 14 | */ 15 | export const editorLogger = log.getLogger(Constants.Logger.EDITOR) 16 | editorLogger.setDefaultLevel(Constants.LogLevel.ERROR) 17 | 18 | /** 19 | * Log editor events 20 | * @type {Object} 21 | */ 22 | export const smartGuideLogger = log.getLogger(Constants.Logger.SMARTGUIDE) 23 | editorLogger.setDefaultLevel(Constants.LogLevel.ERROR) 24 | 25 | /** 26 | * Log model events 27 | * @type {Object} 28 | */ 29 | export const modelLogger = log.getLogger(Constants.Logger.MODEL) 30 | modelLogger.setDefaultLevel(Constants.LogLevel.ERROR) 31 | 32 | /** 33 | * Log grabber events 34 | * @type {Object} 35 | */ 36 | export const grabberLogger = log.getLogger(Constants.Logger.GRABBER) 37 | grabberLogger.setDefaultLevel(Constants.LogLevel.ERROR) 38 | 39 | /** 40 | * Log grabber events 41 | * @type {Object} 42 | */ 43 | export const rendererLogger = log.getLogger(Constants.Logger.RENDERER) 44 | rendererLogger.setDefaultLevel(Constants.LogLevel.ERROR) 45 | 46 | /** 47 | * Log recognizer events 48 | * @type {Object} 49 | */ 50 | export const recognizerLogger = log.getLogger(Constants.Logger.RECOGNIZER) 51 | recognizerLogger.setDefaultLevel(Constants.LogLevel.ERROR) 52 | 53 | /** 54 | * Log callback events 55 | * @type {Object} 56 | */ 57 | export const eventLogger = log.getLogger(Constants.Logger.EVENT) 58 | eventLogger.setDefaultLevel(Constants.LogLevel.ERROR) 59 | 60 | /** 61 | * Log util events 62 | * @type {Object} 63 | */ 64 | export const utilLogger = log.getLogger(Constants.Logger.UTIL) 65 | utilLogger.setDefaultLevel(Constants.LogLevel.ERROR) 66 | 67 | /** 68 | * Log tests events 69 | * @type {Object} 70 | */ 71 | export const testLogger = log.getLogger('test') 72 | testLogger.setDefaultLevel(Constants.LogLevel.ERROR) 73 | -------------------------------------------------------------------------------- /src/eastereggs/InkImporter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { editorLogger as logger } from '../configuration/LoggerConfig' 3 | import * as InkModel from '../model/InkModel' 4 | 5 | /** 6 | * Function to copy past to inject ink during tutorial. 7 | * @param editorParam 8 | * @param strokes 9 | * @param delayBetweenStrokes 10 | * @param lastOneDelay 11 | */ 12 | export function inkImporter (editorParam, strokes, delayBetweenStrokes, lastOneDelay) { 13 | const editor = editorParam 14 | logger.debug('inkImporter start importing =>', strokes) 15 | const origGrabber = Object.assign({}, editor.behavior.grabber) 16 | origGrabber.detach = editor.behavior.grabber.detach 17 | editor.behavior.grabber = {} 18 | const actions = [] 19 | strokes.forEach((stroke) => { 20 | if (stroke.convert) { 21 | actions.push({ action: 'convert', value: true }) 22 | } else if (stroke.setDelay) { 23 | actions.push({ action: 'setDelay', value: stroke.setDelay }) 24 | } else { 25 | if (stroke.color) { 26 | actions.push({ action: 'setColor', value: stroke.color }) 27 | } 28 | stroke.X.forEach((x, idx) => { 29 | let action = 'move' 30 | if (idx === 0) { 31 | action = 'down' 32 | } else if (idx === (stroke.X.length - 1)) { 33 | action = 'up' 34 | } 35 | actions.push({ action, point: { x: stroke.X[idx], y: stroke.Y[idx] } }) 36 | }) 37 | } 38 | }) 39 | logger.debug('Array of actions =>', actions) 40 | const play = (actionsArray, position, delay) => { 41 | if (position < actionsArray.length) { 42 | const currentAction = actionsArray[position] 43 | let nextDelay = delay 44 | if (currentAction.action === 'convert') { 45 | editor.convert() 46 | } else if (currentAction.action === 'setDelay') { 47 | nextDelay = currentAction.value 48 | } else if (currentAction.action === 'setColor') { 49 | editor.penStyle = { 50 | color: currentAction.value 51 | } 52 | } else { 53 | currentAction.point.t = new Date().getTime() 54 | if (currentAction.action === 'down') { 55 | editor.pointerDown(currentAction.point) 56 | } else if (currentAction.action === 'up') { 57 | editor.pointerUp(currentAction.point) 58 | } else if (currentAction.action === 'move') { 59 | editor.pointerMove(currentAction.point) 60 | } 61 | } if (lastOneDelay && position === actionsArray.map(x => x.action).lastIndexOf('down') - 1) { 62 | setTimeout(() => { 63 | play(actionsArray, position + 1, nextDelay) 64 | }, lastOneDelay) 65 | } else if (position === actionsArray.length - 1) { 66 | const event = new Event('drawEnded') 67 | document.dispatchEvent(event) 68 | editor.behavior.grabber = origGrabber 69 | } else { 70 | setTimeout(() => { 71 | play(actionsArray, position + 1, nextDelay) 72 | }, nextDelay) 73 | } 74 | } 75 | } 76 | play(actions, 0, delayBetweenStrokes) 77 | } 78 | 79 | export function importStrokeGroups (editorParam, strokeGroups) { 80 | strokeGroups.forEach((group) => { 81 | group.strokes.forEach((strokeFromGroup) => { 82 | InkModel.addStroke(editorParam.model, strokeFromGroup) 83 | InkModel.addStrokeToGroup(editorParam.model, strokeFromGroup, group.penStyle) 84 | }) 85 | }) 86 | editorParam.renderer.drawModel(editorParam.rendererContext, editorParam.model, editorParam.stroker) 87 | } 88 | -------------------------------------------------------------------------------- /src/event/Event.js: -------------------------------------------------------------------------------- 1 | import { eventLogger as logger } from '../configuration/LoggerConfig' 2 | 3 | /** 4 | * Emits an event when the editor state change 5 | * @param {String} type 6 | * @param {Object} data 7 | * @emits {Event} 8 | */ 9 | export default function emit (type, data) { 10 | logger.info(`emitting ${type} event`, data) 11 | // We are making usage of a browser provided class 12 | // eslint-disable-next-line no-undef 13 | this.dispatchEvent(new CustomEvent(type, Object.assign({ bubbles: true, composed: true }, data ? { detail: data } : undefined))) 14 | } 15 | -------------------------------------------------------------------------------- /src/iink.js: -------------------------------------------------------------------------------- 1 | import Constants from './configuration/Constants' 2 | import LoggerConfig from './configuration/LoggerConfig' 3 | import DefaultConfiguration from './configuration/DefaultConfiguration' 4 | import DefaultPenStyle from './configuration/DefaultPenStyle' 5 | import DefaultTheme from './configuration/DefaultTheme' 6 | import DefaultBehaviors from './configuration/DefaultBehaviors' 7 | import * as InkModel from './model/InkModel' 8 | import { Editor } from './Editor' 9 | import { register, getAvailableLanguageList } from './EditorFacade' 10 | import * as RecognizerContext from './model/RecognizerContext' 11 | 12 | const iink = { 13 | Constants, 14 | // Default instantiations 15 | DefaultConfiguration, 16 | DefaultBehaviors, 17 | DefaultPenStyle, 18 | DefaultTheme, 19 | // Helper functions 20 | register, 21 | getAvailableLanguageList, 22 | // Internals 23 | LoggerConfig, 24 | Editor, 25 | InkModel, 26 | RecognizerContext 27 | } 28 | 29 | export { 30 | iink as default, 31 | Constants, 32 | // Default instantiations 33 | DefaultConfiguration, 34 | DefaultBehaviors, 35 | DefaultPenStyle, 36 | DefaultTheme, 37 | // Helper functions 38 | register, 39 | getAvailableLanguageList, 40 | // Internals 41 | LoggerConfig, 42 | Editor, 43 | InkModel, 44 | RecognizerContext 45 | } 46 | -------------------------------------------------------------------------------- /src/model/Symbol.js: -------------------------------------------------------------------------------- 1 | function mergeBounds (boundsA, boundsB) { 2 | return { 3 | minX: Math.min(boundsA.minX, boundsB.minX), 4 | maxX: Math.max(boundsA.maxX, boundsB.maxX), 5 | minY: Math.min(boundsA.minY, boundsB.minY), 6 | maxY: Math.max(boundsA.maxY, boundsB.maxY) 7 | } 8 | } 9 | 10 | function getLineBounds (line) { 11 | return { 12 | minX: Math.min(line.firstPoint.x, line.lastPoint.x), 13 | maxX: Math.max(line.firstPoint.x, line.lastPoint.x), 14 | minY: Math.min(line.firstPoint.y, line.lastPoint.y), 15 | maxY: Math.max(line.firstPoint.y, line.lastPoint.y) 16 | } 17 | } 18 | 19 | function getEllipseBounds (ellipse) { 20 | const angleStep = 0.02 // angle delta between interpolated points on the arc, in radian 21 | 22 | let z1 = Math.cos(ellipse.orientation) 23 | let z3 = Math.sin(ellipse.orientation) 24 | let z2 = z1 25 | let z4 = z3 26 | z1 *= ellipse.maxRadius 27 | z2 *= ellipse.minRadius 28 | z3 *= ellipse.maxRadius 29 | z4 *= ellipse.minRadius 30 | 31 | const n = Math.abs(ellipse.sweepAngle) / angleStep 32 | 33 | const x = [] 34 | const y = [] 35 | 36 | for (let i = 0; i <= n; i++) { 37 | const angle = ellipse.startAngle + ((i / n) * ellipse.sweepAngle) 38 | const alpha = Math.atan2(Math.sin(angle) / ellipse.minRadius, Math.cos(angle) / ellipse.maxRadius) 39 | 40 | const cosAlpha = Math.cos(alpha) 41 | const sinAlpha = Math.sin(alpha) 42 | 43 | x.push(ellipse.center.x + ((z1 * cosAlpha) - (z4 * sinAlpha))) 44 | y.push(ellipse.center.y + ((z2 * sinAlpha) + (z3 * cosAlpha))) 45 | } 46 | 47 | return { 48 | minX: Math.min(...x), 49 | maxX: Math.max(...x), 50 | minY: Math.min(...y), 51 | maxY: Math.max(...y) 52 | } 53 | } 54 | 55 | function getTextLineBounds (textLine) { 56 | return { 57 | minX: textLine.data.topLeftPoint.x, 58 | maxX: textLine.data.topLeftPoint.x + textLine.data.width, 59 | minY: textLine.data.topLeftPoint.y, 60 | maxY: textLine.data.topLeftPoint.y + textLine.data.height 61 | } 62 | } 63 | 64 | function getClefBounds (clef) { 65 | return { 66 | minX: clef.boundingBox.x, 67 | maxX: clef.boundingBox.x + clef.boundingBox.width, 68 | minY: clef.boundingBox.y, 69 | maxY: clef.boundingBox.y + clef.boundingBox.height 70 | } 71 | } 72 | 73 | function getStrokeBounds (stroke) { 74 | return { 75 | minX: Math.min(...stroke.x), 76 | maxX: Math.max(...stroke.x), 77 | minY: Math.min(...stroke.y), 78 | maxY: Math.max(...stroke.y) 79 | } 80 | } 81 | 82 | /** 83 | * Get the box enclosing the given symbols 84 | * @param {Array} symbols Symbols to extract bounds from 85 | * @param {Bounds} [bounds] Starting bounds for recursion 86 | * @return {Bounds} Bounding box enclosing symbols 87 | */ 88 | export function getSymbolsBounds (symbols, bounds = { minX: Number.MAX_VALUE, maxX: Number.MIN_VALUE, minY: Number.MAX_VALUE, maxY: Number.MIN_VALUE }) { 89 | let boundsRef = bounds 90 | boundsRef = symbols 91 | .filter(symbol => symbol.type === 'stroke') 92 | .map(getStrokeBounds) 93 | .reduce(mergeBounds, boundsRef) 94 | boundsRef = symbols 95 | .filter(symbol => symbol.type === 'clef') 96 | .map(getClefBounds) 97 | .reduce(mergeBounds, boundsRef) 98 | boundsRef = symbols 99 | .filter(symbol => symbol.type === 'line') 100 | .map(getLineBounds) 101 | .reduce(mergeBounds, boundsRef) 102 | boundsRef = symbols 103 | .filter(symbol => symbol.type === 'ellipse') 104 | .map(getEllipseBounds) 105 | .reduce(mergeBounds, boundsRef) 106 | boundsRef = symbols 107 | .filter(symbol => symbol.type === 'textLine') 108 | .map(getTextLineBounds) 109 | .reduce(mergeBounds, boundsRef) 110 | return boundsRef 111 | } 112 | -------------------------------------------------------------------------------- /src/model/UndoRedoContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Undo/redo context 3 | * @typedef {Object} UndoRedoContext 4 | * @property {Array} stack=[] List of processed models. 5 | * @property {Number} currentPosition=-1 Current model index into the stack. 6 | * @property {Number} maxSize Max size of the stack. 7 | * @property {Boolean} canUndo=false 8 | * @property {Boolean} canRedo=false 9 | */ 10 | 11 | /** 12 | * Create a new undo/redo context 13 | * @param {Configuration} configuration Current configuration 14 | * @return {UndoRedoContext} New undo/redo context 15 | */ 16 | export function createUndoRedoContext (configuration) { 17 | return { 18 | stack: [], 19 | currentPosition: -1, 20 | maxSize: configuration.undoRedoMaxStackSize, 21 | canUndo: false, 22 | canRedo: false 23 | } 24 | } 25 | 26 | /** 27 | * Update the undo/redo state 28 | * @param {UndoRedoContext} undoRedoContext Current undo/redo context 29 | * @return {UndoRedoContext} Updated undo/redo context 30 | */ 31 | export function updateUndoRedoState (undoRedoContext) { 32 | const undoRedoContextRef = undoRedoContext 33 | undoRedoContextRef.canUndo = undoRedoContext.currentPosition > 0 34 | undoRedoContextRef.canRedo = undoRedoContext.currentPosition < (undoRedoContext.stack.length - 1) 35 | return undoRedoContextRef 36 | } 37 | -------------------------------------------------------------------------------- /src/model/UndoRedoManager.js: -------------------------------------------------------------------------------- 1 | import * as InkModel from '../model/InkModel' 2 | import * as UndoRedoContext from '../model/UndoRedoContext' 3 | import { modelLogger as logger } from '../configuration/LoggerConfig' 4 | import Constants from '../configuration/Constants' 5 | 6 | /** 7 | * Undo/redo manager 8 | * @typedef {Object} UndoRedoManager 9 | * @property {function} updateModel Push the current model into the undo/redo context. 10 | * @property {function} undo Undo. 11 | * @property {function} redo Redo. 12 | * @property {function} clear Clear. 13 | */ 14 | 15 | /** 16 | * Get current model in stack 17 | * @param {UndoRedoContext} undoRedoContext Current undo/redo context 18 | * @param {Boolean} [clone=true] Whether or not to clone the model 19 | * @param {...String} types 20 | */ 21 | export function getModel (undoRedoContext, clone = true, ...types) { 22 | const model = undoRedoContext.stack[undoRedoContext.currentPosition] 23 | const val = { 24 | res: clone ? InkModel.cloneModel(model) : model, 25 | types 26 | } 27 | return Promise.resolve(val) 28 | } 29 | 30 | /** 31 | * Mutate the undoRedo stack by adding a new model to it. 32 | * @param {UndoRedoContext} undoRedoContext Current undo/redo context. 33 | * @param {Model} model Current model. 34 | */ 35 | export function updateModel (undoRedoContext, model) { 36 | // Used to update the model with the recognition result if relevant 37 | const modelIndex = undoRedoContext.stack.findIndex(item => (item.modificationTime === model.modificationTime) && (item.rawStrokes.length === model.rawStrokes.length)) 38 | 39 | const modelReference = model 40 | modelReference.modificationTime = new Date().getTime() 41 | 42 | const types = [] 43 | if (modelIndex > -1) { 44 | undoRedoContext.stack.splice(modelIndex, 1, InkModel.cloneModel(modelReference)) 45 | logger.debug('model updated', modelReference) 46 | } else { 47 | const undoRedoContextReference = undoRedoContext 48 | undoRedoContextReference.currentPosition += 1 49 | undoRedoContextReference.stack = undoRedoContextReference.stack.slice(0, undoRedoContextReference.currentPosition) 50 | undoRedoContextReference.stack.push(InkModel.cloneModel(modelReference)) 51 | if (undoRedoContextReference.stack.length > undoRedoContextReference.maxSize) { 52 | undoRedoContextReference.stack.shift() 53 | undoRedoContextReference.currentPosition-- 54 | } 55 | logger.debug('model pushed', modelReference) 56 | types.push(Constants.EventType.CHANGED) 57 | } 58 | UndoRedoContext.updateUndoRedoState(undoRedoContext) 59 | logger.debug('undo/redo stack updated', undoRedoContext) 60 | return getModel(undoRedoContext, false, ...types) 61 | } 62 | 63 | /** 64 | * Undo 65 | * @param {UndoRedoContext} undoRedoContext Current undo/redo context. 66 | * @param {Model} model Current model. 67 | */ 68 | export function undo (undoRedoContext, model) { 69 | const undoRedoContextReference = undoRedoContext 70 | if (undoRedoContextReference.currentPosition > 0) { 71 | undoRedoContextReference.currentPosition -= 1 72 | UndoRedoContext.updateUndoRedoState(undoRedoContext) 73 | logger.debug('undo index', undoRedoContextReference.currentPosition) 74 | } 75 | return getModel(undoRedoContext, true, Constants.EventType.CHANGED, Constants.EventType.EXPORTED) 76 | } 77 | 78 | /** 79 | * Redo 80 | * @param {UndoRedoContext} undoRedoContext Current undo/redo context. 81 | * @param {Model} model Current model. 82 | */ 83 | export function redo (undoRedoContext, model) { 84 | const undoRedoContextReference = undoRedoContext 85 | if (undoRedoContextReference.currentPosition < undoRedoContextReference.stack.length - 1) { 86 | undoRedoContextReference.currentPosition += 1 87 | UndoRedoContext.updateUndoRedoState(undoRedoContext) 88 | logger.debug('redo index', undoRedoContextReference.currentPosition) 89 | } 90 | return getModel(undoRedoContext, true, Constants.EventType.CHANGED, Constants.EventType.EXPORTED) 91 | } 92 | -------------------------------------------------------------------------------- /src/recognizer/CryptoHelper.js: -------------------------------------------------------------------------------- 1 | import Hex from 'crypto-js/enc-hex' 2 | import HmacSHA512 from 'crypto-js/hmac-sha512' 3 | import { recognizerLogger as logger } from '../configuration/LoggerConfig' 4 | 5 | /** 6 | * Compute HMAC signature for server authentication 7 | * 8 | * @param {Object} input Input data to compute HMAC 9 | * @param {String} applicationKey Current applicationKey 10 | * @param {String} hmacKey Current hmacKey 11 | * @return {String} Signature 12 | */ 13 | export function computeHmac (input, applicationKey, hmacKey) { 14 | const jsonInput = (typeof input === 'object') ? JSON.stringify(input) : input 15 | logger.debug('The HmacSHA512 function is loaded', HmacSHA512) 16 | return new HmacSHA512(jsonInput, applicationKey + hmacKey).toString(Hex) 17 | } 18 | -------------------------------------------------------------------------------- /src/recognizer/rest/networkInterface.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import * as CryptoHelper from '../CryptoHelper' 3 | 4 | /** 5 | * Post request 6 | * @param {RecognizerContext} recognizerContext Recognizer context 7 | * @param {String} url URL 8 | * @param {Object} data Data to be sent 9 | * @param {String} apiVersion api version 10 | * @param {String} mimeType MimeType to be used 11 | * @return {Promise} 12 | */ 13 | export async function post (recognizerContext, url, data, mimeType) { 14 | const configuration = recognizerContext.editor.configuration 15 | const recognizerContextRef = recognizerContext 16 | if (recognizerContextRef) { 17 | recognizerContextRef.idle = true 18 | } 19 | try { 20 | const headers = new Headers() 21 | headers.append('Accept', 'application/json,' + mimeType) 22 | headers.append('applicationKey', configuration.recognitionParams.server.applicationKey) 23 | headers.append('hmac', CryptoHelper.computeHmac(JSON.stringify(data), configuration.recognitionParams.server.applicationKey, configuration.recognitionParams.server.hmacKey)) 24 | headers.append('Content-Type', 'application/json') 25 | const reqInit = { 26 | method: 'POST', 27 | headers, 28 | body: JSON.stringify(data), 29 | credentials: 'omit' 30 | } 31 | const request = new Request(url, reqInit) 32 | const response = await fetch(request) 33 | const contentType = response.headers.get('content-type') 34 | let result = '' 35 | switch (contentType) { 36 | case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 37 | case 'image/png': 38 | case 'image/jpeg': 39 | result = await response.blob() 40 | break 41 | case 'application/json': 42 | result = await response.json() 43 | break 44 | case 'application/vnd.myscript.jiix': 45 | result = await response.clone().json().catch(async () => await response.text()) 46 | break 47 | default: 48 | result = await response.text() 49 | break 50 | } 51 | return result 52 | } catch (error) { 53 | throw new Error({ msg: `Could not connect to ${url} connection error`, recoverable: false }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/recognizer/websocket/networkWSInterface.js: -------------------------------------------------------------------------------- 1 | import { recognizerLogger as logger } from '../../configuration/LoggerConfig' 2 | import * as RecognizerContext from '../../model/RecognizerContext' 3 | 4 | function infinitePing (websocket) { 5 | const websocketRef = websocket 6 | websocketRef.pingLostCount++ 7 | if (websocketRef.pingLostCount > websocketRef.maxPingLost) { 8 | websocket.close(1000, 'PING_LOST') 9 | } else if (websocketRef.readyState <= 1) { 10 | setTimeout(() => { 11 | if (websocketRef.readyState <= 1) { 12 | websocketRef.send(JSON.stringify({ type: 'ping' })) 13 | infinitePing(websocketRef) 14 | } 15 | }, websocketRef.pingDelay) 16 | } 17 | } 18 | 19 | /** 20 | * Attach all socket attributes helping managing server connexion 21 | * @param {WebSocket} websocket Current WebSocket 22 | * @param {RecognizerContext} recognizerContext 23 | */ 24 | function addWebsocketAttributes (websocket, recognizerContext) { 25 | const websocketConfiguration = recognizerContext.editor.configuration.recognitionParams.server.websocket 26 | const socket = websocket 27 | socket.start = new Date() 28 | socket.autoReconnect = websocketConfiguration.autoReconnect 29 | socket.maxRetryCount = websocketConfiguration.maxRetryCount 30 | socket.pingEnabled = websocketConfiguration.pingEnabled 31 | socket.pingDelay = websocketConfiguration.pingDelay 32 | socket.maxPingLost = websocketConfiguration.maxPingLostCount 33 | socket.pingLostCount = 0 34 | socket.recognizerContext = recognizerContext 35 | } 36 | 37 | /** 38 | * @param {RecognizerContext} recognizerContext Recognizer context 39 | * @return {WebSocket} Opened WebSocket 40 | */ 41 | export function openWebSocket (recognizerContext) { 42 | let socket 43 | try { 44 | // eslint-disable-next-line no-undef 45 | socket = new WebSocket(recognizerContext.url) 46 | 47 | addWebsocketAttributes(socket, recognizerContext) 48 | 49 | if (socket.pingEnabled) { 50 | infinitePing(socket) 51 | } 52 | 53 | socket.onopen = (e) => { 54 | logger.trace('onOpen') 55 | recognizerContext.websocketCallback(e) 56 | } 57 | 58 | socket.onclose = (e) => { 59 | logger.trace('onClose', new Date() - socket.start) 60 | recognizerContext.websocketCallback(e) 61 | } 62 | 63 | socket.onerror = (e) => { 64 | logger.trace('onError') 65 | recognizerContext.websocketCallback(e) 66 | } 67 | 68 | socket.onmessage = (e) => { 69 | logger.trace('onMessage') 70 | socket.pingLostCount = 0 71 | const parsedMessage = JSON.parse(e.data) 72 | if (parsedMessage.type !== 'pong') { 73 | const callBackParam = { 74 | type: e.type, 75 | data: JSON.parse(e.data) 76 | } 77 | recognizerContext.websocketCallback(callBackParam) 78 | } 79 | } 80 | } catch (error) { 81 | recognizerContext.recognitionContexts[0].initPromise.reject(error) 82 | logger.error('Unable to open websocket, Check the host and your connectivity') 83 | } 84 | 85 | return socket 86 | } 87 | 88 | /** 89 | * Send data message 90 | * @param {RecognizerContext} recognizerContext Current recognizer context 91 | * @param {Object} message Data message 92 | */ 93 | export function send (recognizerContext, message) { 94 | const recognizerContextRef = recognizerContext 95 | recognizerContextRef.idle = false 96 | 97 | const websocket = recognizerContextRef.websocket 98 | if (websocket.readyState === 1) { 99 | websocket.send(JSON.stringify(message)) 100 | logger.debug(`${message.type} message sent`, message) 101 | } else { 102 | throw RecognizerContext.LOST_CONNEXION_MESSAGE 103 | } 104 | } 105 | 106 | /** 107 | * Close the websocket 108 | * @param {RecognizerContext} recognizerContext Current recognizer context 109 | * @param {Number} code Exit code 110 | * @param {String} reason Exit reason 111 | */ 112 | export function close (recognizerContext, code, reason) { 113 | const websocket = recognizerContext.websocket 114 | if (websocket && websocket.readyState < 2) { 115 | websocket.close(code, reason) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/renderer/QuadraticUtils.js: -------------------------------------------------------------------------------- 1 | /** =============================================================================================== 2 | * Compute quadratics control points 3 | * ============================================================================================= */ 4 | 5 | /** 6 | * 7 | * @param {{x: Number, y: Number, p: Number}} point 8 | * @param angle 9 | * @param width 10 | * @return {({x: number, y: *}|{x: *, y: number})} 11 | */ 12 | export function computeLinksPoints (point, angle, width) { 13 | const radius = point.p * width 14 | return [{ 15 | x: (point.x - (Math.sin(angle) * radius)), 16 | y: (point.y + (Math.cos(angle) * radius)) 17 | }, { 18 | x: (point.x + (Math.sin(angle) * radius)), 19 | y: (point.y - (Math.cos(angle) * radius)) 20 | }] 21 | } 22 | 23 | /** 24 | * 25 | * @param {{x: Number, y: Number, p: Number}} point1 26 | * @param {{x: Number, y: Number, p: Number}} point2 27 | * @return {{x: Number, y: Number, p: Number}} 28 | */ 29 | export function computeMiddlePoint (point1, point2) { 30 | return { 31 | x: ((point2.x + point1.x) / 2), 32 | y: ((point2.y + point1.y) / 2), 33 | p: ((point2.p + point1.p) / 2) 34 | } 35 | } 36 | 37 | /** 38 | * 39 | * @param {{x: Number, y: Number}} begin 40 | * @param {{x: Number, y: Number}} end 41 | * @return {Number} 42 | */ 43 | export function computeAxeAngle (begin, end) { 44 | return Math.atan2(end.y - begin.y, end.x - begin.x) 45 | } 46 | -------------------------------------------------------------------------------- /src/renderer/canvas/ImageRenderer.js: -------------------------------------------------------------------------------- 1 | import { drawModel } from './CanvasRenderer' 2 | import * as InkModel from '../../model/InkModel' 3 | 4 | function createCanvas (borderCoordinates, margin = 10) { 5 | // eslint-disable-next-line no-undef 6 | const browserDocument = document 7 | const canvas = browserDocument.createElement('canvas') 8 | canvas.width = Math.abs(borderCoordinates.maxX - borderCoordinates.minX) + (2 * margin) 9 | canvas.style.width = `${canvas.width}px` 10 | canvas.height = Math.abs(borderCoordinates.maxY - borderCoordinates.minY) + (2 * margin) 11 | canvas.style.height = `${canvas.height}px` 12 | return canvas 13 | } 14 | 15 | /** 16 | * Generate a PNG image data url from the model 17 | * @param {Model} model Current model 18 | * @param {Stroker} stroker Current stroker 19 | * @param {Number} [margin=10] Margins to apply around the image 20 | * @return {String} Image data string result 21 | */ 22 | export function getImage (model, stroker, margin = 10) { 23 | if (model.rawStrokes.length > 0) { 24 | const borderCoordinates = InkModel.getBorderCoordinates(model) 25 | 26 | const capturingCanvas = createCanvas(borderCoordinates, margin) 27 | const renderingCanvas = createCanvas(borderCoordinates, margin) 28 | const renderStructure = { 29 | renderingCanvas, 30 | renderingCanvasContext: renderingCanvas.getContext('2d'), 31 | capturingCanvas, 32 | capturingCanvasContext: capturingCanvas.getContext('2d') 33 | } 34 | // Change canvas origin 35 | renderStructure.renderingCanvasContext.translate(-borderCoordinates.minX + margin, -borderCoordinates.minY + margin) 36 | drawModel(renderStructure, model, stroker) 37 | return renderStructure.renderingCanvas.toDataURL('image/png') 38 | } 39 | return null 40 | } 41 | -------------------------------------------------------------------------------- /src/renderer/canvas/symbols/MathSymbolCanvasRenderer.js: -------------------------------------------------------------------------------- 1 | import { rendererLogger as logger } from '../../../configuration/LoggerConfig' 2 | import { drawStroke } from './StrokeSymbolCanvasRenderer' 3 | import * as InkModel from '../../../model/InkModel' 4 | 5 | /** 6 | * @type {{inputCharacter: String, char: String, string: String, textLine: String}} 7 | */ 8 | export const MathSymbols = { 9 | nonTerminalNode: 'nonTerminalNode', 10 | terminalNode: 'terminalNode', 11 | rule: 'rule' 12 | } 13 | 14 | function drawTerminalNode (context, terminalNode, model, stroker) { 15 | terminalNode.inkRanges.forEach((inkRange) => { 16 | InkModel.extractStrokesFromInkRange(model, inkRange.component, inkRange.component, inkRange.firstItem, inkRange.lastItem) 17 | .forEach(stroke => drawStroke(context, stroke, stroker)) 18 | }) 19 | } 20 | 21 | /** 22 | * Draw a math symbol 23 | * @param {Object} context Current rendering context 24 | * @param {Object} symbol Symbol to draw 25 | * @param {Model} model Current model 26 | * @param {Stroker} stroker Stroker to use to render a stroke 27 | */ 28 | export function drawMathSymbol (context, symbol, model, stroker) { 29 | logger.debug(`draw ${symbol.type} text input`) 30 | const contextReference = context 31 | contextReference.save() 32 | try { 33 | contextReference.lineWidth = symbol.width 34 | contextReference.strokeStyle = symbol.color 35 | 36 | switch (symbol.type) { 37 | case MathSymbols.nonTerminalNode: 38 | drawMathSymbol(contextReference, symbol.candidates[symbol.selectedCandidate], model, stroker) 39 | break 40 | case MathSymbols.terminalNode: 41 | drawTerminalNode(contextReference, symbol, model, stroker) 42 | break 43 | case MathSymbols.rule: 44 | symbol.children.forEach(child => drawMathSymbol(contextReference, child, model, stroker)) 45 | break 46 | default: 47 | logger.error(`${symbol.type} not implemented`) 48 | } 49 | } finally { 50 | contextReference.restore() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/renderer/canvas/symbols/StrokeSymbolCanvasRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Draw a stroke symbol 3 | * @param {Object} context Current rendering context 4 | * @param {Stroke} stroke Stroke to be drawn 5 | * @param {Stroker} stroker Stroker to use to render a stroke 6 | */ 7 | export function drawStroke (context, stroke, stroker) { 8 | if (stroker && (!stroke || stroke.pointerType !== 'ERASER')) { 9 | stroker.drawStroke(context, stroke) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/renderer/canvas/symbols/TextSymbolCanvasRenderer.js: -------------------------------------------------------------------------------- 1 | import { rendererLogger as logger } from '../../../configuration/LoggerConfig' 2 | import { drawLine } from './ShapeSymbolCanvasRenderer' 3 | 4 | /** 5 | * @type {{inputCharacter: String, char: String, string: String, textLine: String}} 6 | */ 7 | export const TextSymbols = { 8 | inputCharacter: 'inputCharacter', 9 | char: 'char', 10 | string: 'string', 11 | textLine: 'textLine' 12 | } 13 | 14 | function drawUnderline (context, underline, label, data) { 15 | const delta = data.width / label.length 16 | const p1 = { 17 | x: data.topLeftPoint.x + (underline.data.firstCharacter * delta), 18 | y: data.topLeftPoint.y + data.height 19 | } 20 | const p2 = { 21 | x: data.topLeftPoint.x + (underline.data.lastCharacter * delta), 22 | y: data.topLeftPoint.y + data.height 23 | } 24 | drawLine(context, p1, p2) 25 | } 26 | 27 | function drawText (context, label, data) { 28 | const contextReference = context 29 | contextReference.save() 30 | try { 31 | contextReference.font = `${data.textHeight}px serif` 32 | contextReference.textAlign = (data.justificationType === 'CENTER') ? 'center' : 'left' 33 | contextReference.textBaseline = 'bottom' 34 | contextReference.fillStyle = contextReference.strokeStyle 35 | contextReference.fillText(label, data.topLeftPoint.x, (data.topLeftPoint.y + data.height)) 36 | } finally { 37 | contextReference.restore() 38 | } 39 | } 40 | 41 | function drawTextLine (context, textLine) { 42 | drawText(context, textLine.label, textLine.data) 43 | textLine.underlineList.forEach((underline) => { 44 | drawUnderline(context, underline, textLine.label, textLine.data) 45 | }) 46 | } 47 | 48 | /** 49 | * Draw a text symbol 50 | * @param {Object} context Current rendering context 51 | * @param {Object} symbol Symbol to draw 52 | */ 53 | export function drawTextSymbol (context, symbol) { 54 | logger.debug(`draw ${symbol.type} symbol`) 55 | const contextReference = context 56 | contextReference.save() 57 | try { 58 | contextReference.lineWidth = symbol.width 59 | contextReference.strokeStyle = symbol.color 60 | 61 | if (symbol.elementType) { 62 | switch (symbol.elementType) { 63 | case TextSymbols.textLine: 64 | drawTextLine(contextReference, symbol) 65 | break 66 | default: 67 | logger.error(`${symbol.elementType} not implemented`) 68 | break 69 | } 70 | } else { 71 | switch (symbol.type) { 72 | case TextSymbols.textLine: 73 | drawTextLine(contextReference, symbol) 74 | break 75 | default: 76 | logger.error(`${symbol.type} not implemented`) 77 | } 78 | } 79 | } finally { 80 | contextReference.restore() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/renderer/svg/symbols/StrokeSymbolSVGRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Draw a stroke symbol 3 | * @param {Object} context Current rendering context 4 | * @param {Stroke} stroke Stroke to be drawn 5 | * @param {Stroker} stroker Stroker to use to render a stroke 6 | */ 7 | export function drawStroke (context, stroke, stroker) { 8 | if (stroker) { 9 | if (stroke.pointerType === 'ERASER') { 10 | stroker.drawErasingStroke(context, stroke) 11 | } else { 12 | stroker.drawStroke(context, stroke) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/util/PromiseHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} DestructuredPromise 3 | * @property {Promise} promise 4 | * @property {function} resolve 5 | * @property {function} reject 6 | */ 7 | 8 | /** 9 | * destructurePromise 10 | * @returns {{resolve: *, reject: *, promise: Promise}} 11 | */ 12 | export function destructurePromise () { 13 | let resolveParam 14 | let rejectParam 15 | const initPromise = new Promise( 16 | (resolve, reject) => { 17 | resolveParam = async (v) => { 18 | initPromise.isFullfilled = true 19 | initPromise.isPending = false 20 | return resolve(v) 21 | } 22 | rejectParam = async (e) => { 23 | initPromise.isRejected = true 24 | initPromise.isPending = false 25 | reject(e) 26 | } 27 | }) 28 | 29 | initPromise.isPending = true 30 | 31 | return { promise: initPromise, resolve: resolveParam, reject: rejectParam } 32 | } 33 | 34 | /** 35 | * @param time 36 | * @return {{timer: *, promise: Promise}} 37 | */ 38 | export function delay (time) { 39 | let timer = null 40 | const promise = new Promise((resolve) => { 41 | timer = setTimeout(resolve, time) 42 | }) 43 | return { 44 | promise, 45 | timer 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | }, 5 | globals: { 6 | browser: true, 7 | page: true, 8 | expect: true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/lib/configuration.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = [ 3 | { 4 | type: 'MATH', 5 | protocol: 'WEBSOCKET', 6 | apiVersion: 'V4' 7 | }, { 8 | type: 'TEXT', 9 | protocol: 'WEBSOCKET', 10 | apiVersion: 'V4' 11 | }, { 12 | type: 'TEXT', 13 | protocol: 'REST', 14 | apiVersion: 'V4' 15 | }, { 16 | type: 'Raw Content', 17 | protocol: 'REST', 18 | apiVersion: 'V4' 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /test/lib/inks/3times2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 100, 104, 107, 110, 113, 116, 120, 124, 127, 133, 139, 144, 149, 152, 156, 5 | 160, 163, 166, 168, 165, 162, 159, 155, 150, 146, 143, 140, 137, 133, 130, 6 | 127, 124, 119, 123, 126, 130, 135, 139, 142, 146, 149, 153, 156, 162, 165, 7 | 168, 170, 173, 175, 175, 175, 175, 174, 169, 166, 162, 156, 152, 148, 141, 8 | 136, 133, 130, 127, 124, 120, 116, 113, 109, 110 9 | ], 10 | "y": [ 11 | 143, 146, 146, 146, 146, 144, 143, 143, 143, 145, 146, 148, 150, 153, 156, 12 | 160, 162, 167, 174, 176, 177, 178, 179, 181, 182, 183, 183, 183, 183, 183, 13 | 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 185, 188, 14 | 190, 194, 197, 202, 206, 209, 212, 216, 222, 222, 222, 222, 222, 222, 222, 15 | 222, 222, 221, 220, 219, 217, 217, 216, 216, 220 16 | ] 17 | }, 18 | { 19 | "x": [217, 222, 226, 229, 231, 236, 240, 243, 254, 257, 263], 20 | "y": [166, 166, 168, 169, 172, 175, 180, 182, 200, 205, 211] 21 | }, 22 | { 23 | "x": [271, 268, 263, 261, 256, 252, 247, 241, 237, 232, 228, 227, 226], 24 | "y": [166, 170, 174, 178, 183, 187, 192, 198, 202, 207, 211, 215, 218] 25 | }, 26 | { 27 | "x": [ 28 | 327, 327, 325, 322, 322, 322, 326, 334, 337, 343, 348, 352, 355, 361, 364, 29 | 366, 366, 363, 362, 359, 356, 352, 348, 345, 325, 323, 320, 323, 326, 329, 30 | 332, 335, 338, 343, 352, 358, 361, 364, 367, 371, 374, 377, 380, 380 31 | ], 32 | "y": [ 33 | 155, 152, 148, 145, 141, 137, 134, 130, 128, 128, 130, 134, 137, 149, 151, 34 | 158, 169, 181, 187, 189, 194, 198, 203, 205, 221, 224, 226, 227, 227, 227, 35 | 227, 226, 226, 225, 222, 220, 219, 219, 219, 219, 219, 219, 219, 222 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /test/lib/inks/emphasized.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 226, 230, 235, 240, 243, 248, 253, 257, 258, 258, 258, 258, 255, 250, 247, 5 | 247, 247, 247, 247, 247, 247, 248, 249, 250, 251, 251, 252, 252, 253, 253, 6 | 253, 253, 254, 258, 261, 261, 261, 262, 265, 269, 273, 276, 277, 278, 277, 7 | 272, 271, 271, 274, 278, 283, 286, 289, 292, 295, 296, 298, 299, 300, 301, 8 | 301, 301, 301, 301, 297, 295, 295, 295, 295, 295, 295, 295, 296, 297, 298, 9 | 300, 302, 305, 309, 314, 318, 319, 320, 321, 322, 322, 322, 322, 320, 316, 10 | 315, 315, 315, 315, 315, 315, 315, 315, 318, 322, 325, 328, 332, 337, 340, 11 | 341, 341, 341, 339, 339, 339, 339, 340, 343, 346, 348, 348, 348, 345, 345, 12 | 349, 353, 357, 360, 363 13 | ], 14 | "y": [ 15 | 246, 246, 246, 243, 241, 238, 234, 228, 225, 221, 216, 212, 210, 211, 215, 16 | 218, 221, 224, 227, 230, 234, 239, 242, 246, 249, 252, 255, 258, 261, 257, 17 | 254, 249, 245, 241, 243, 247, 250, 254, 256, 256, 255, 253, 250, 246, 242, 18 | 243, 247, 252, 256, 258, 256, 254, 252, 249, 247, 243, 239, 232, 229, 224, 19 | 221, 217, 213, 210, 209, 212, 216, 219, 222, 228, 232, 235, 238, 242, 246, 20 | 249, 254, 256, 256, 252, 245, 242, 238, 232, 229, 225, 221, 216, 211, 209, 21 | 213, 217, 223, 229, 234, 240, 244, 247, 250, 254, 256, 256, 253, 247, 243, 22 | 240, 236, 233, 237, 240, 243, 246, 250, 250, 247, 243, 237, 234, 233, 237, 23 | 237, 239, 240, 240, 239 24 | ] 25 | }, 26 | { 27 | "x": [ 28 | 397, 402, 408, 415, 419, 423, 425, 426, 426, 424, 420, 416, 416, 416, 416, 29 | 416, 416, 416, 416, 416, 416, 416, 416, 416, 416, 416, 416, 416, 417, 420, 30 | 423, 425, 428, 429, 429, 434, 439, 444, 447, 449, 450, 450, 449, 449, 449, 31 | 452, 456, 459, 460, 460, 460, 458, 454, 454, 458, 462, 466, 471, 477, 477, 32 | 476, 476, 476, 476, 479, 483, 487, 491, 493, 495, 495, 494, 494, 494, 494, 33 | 498, 501, 504, 507, 508, 508, 507, 503, 505, 509, 512 34 | ], 35 | "y": [ 36 | 241, 241, 241, 241, 238, 233, 228, 220, 214, 210, 208, 210, 218, 221, 227, 37 | 230, 234, 237, 241, 245, 249, 254, 260, 265, 261, 258, 252, 247, 242, 242, 38 | 245, 248, 253, 256, 261, 262, 261, 259, 255, 247, 244, 240, 244, 247, 251, 39 | 254, 254, 251, 247, 243, 239, 236, 237, 241, 244, 245, 245, 245, 239, 242, 40 | 246, 249, 252, 255, 255, 255, 251, 248, 245, 240, 237, 240, 244, 247, 250, 41 | 252, 252, 248, 245, 241, 238, 235, 233, 236, 237, 234 42 | ] 43 | }, 44 | { 45 | "x": [ 46 | 398, 404, 408, 411, 415, 419, 423, 429, 434, 440, 443, 448, 454, 461, 465, 47 | 471, 475, 479, 487, 490, 498, 502, 510, 515, 521, 525, 529, 532 48 | ], 49 | "y": [ 50 | 266, 266, 266, 266, 266, 266, 266, 266, 266, 266, 266, 266, 266, 266, 266, 51 | 266, 266, 266, 266, 266, 266, 266, 264, 264, 264, 264, 264, 264 52 | ] 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /test/lib/inks/equation.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 530, 533, 537, 538, 540, 541, 542, 544, 545, 546, 548, 548, 552, 555, 558, 5 | 561, 565, 569, 573, 577, 582, 583, 585, 588, 589, 591, 593, 593, 593, 592, 6 | 591, 591, 590, 589, 588, 593, 598, 604, 613, 627, 646, 665, 682, 697, 713, 7 | 727, 738, 745, 751, 756, 762, 768, 772 8 | ], 9 | "y": [ 10 | 93, 95, 99, 102, 107, 112, 117, 123, 127, 130, 133, 136, 138, 141, 143, 11 | 144, 144, 141, 136, 130, 124, 118, 112, 107, 101, 95, 90, 84, 79, 76, 72, 12 | 68, 64, 59, 55, 54, 54, 54, 54, 54, 52, 52, 49, 49, 49, 49, 49, 49, 49, 13 | 49, 49, 48, 48 14 | ] 15 | }, 16 | { 17 | "x": 18 | [ 19 | 669, 666, 665, 665, 668, 672, 676, 680, 684, 687, 689, 689, 690, 690, 688, 20 | 686, 684, 681, 678, 675, 670, 667, 664, 661, 658, 655, 654, 657, 661, 664, 21 | 668, 672, 677, 682, 689, 700, 712, 720, 723, 720 22 | ], 23 | "y": [ 24 | 81, 80, 77, 74, 75, 76, 77, 79, 81, 84, 87, 90, 93, 97, 102, 105, 109, 25 | 112, 114, 118, 121, 123, 125, 127, 130, 133, 136, 137, 138, 138, 138, 138, 26 | 138, 137, 136, 136, 135, 133, 133, 132 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /test/lib/inks/equation2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 134, 137, 141, 145, 148, 153, 154, 157, 162, 165, 171, 173, 176, 178, 178, 5 | 178, 180, 182, 185, 187, 190, 191, 193, 195, 196, 196, 196, 196, 195, 194, 6 | 194, 193, 193, 192, 191, 191, 199, 213, 249, 271, 289, 321, 340, 360, 371, 7 | 428, 440, 465, 489, 528, 540, 560, 576, 598, 604, 620, 635, 651, 656, 661, 8 | 664, 667, 671 9 | ], 10 | "y": [ 11 | 347, 347, 348, 352, 356, 366, 369, 374, 386, 393, 405, 409, 413, 416, 411, 12 | 400, 377, 365, 353, 341, 319, 312, 301, 290, 282, 265, 259, 248, 227, 223, 13 | 215, 194, 186, 180, 172, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 14 | 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 15 | 169, 169, 169 16 | ] 17 | }, 18 | { 19 | "x": [ 20 | 410, 406, 402, 399, 395, 394, 394, 394, 399, 408, 418, 429, 439, 443, 445, 21 | 445, 439, 432, 377, 365, 355, 350, 350, 354, 364, 387, 410, 426, 440, 452, 22 | 456, 459, 462 23 | ], 24 | "y": [ 25 | 291, 290, 288, 286, 283, 280, 273, 269, 265, 264, 264, 264, 273, 289, 319, 26 | 332, 344, 352, 361, 362, 364, 365, 368, 370, 372, 374, 374, 374, 374, 374, 27 | 374, 374, 373 28 | ] 29 | }, 30 | { 31 | "x": [ 32 | 371, 434, 438, 441, 424, 413, 392, 322, 341, 353, 389, 402, 426, 498, 492, 33 | 448, 432, 418, 384, 359, 319, 324, 339, 411, 420, 439, 447, 457, 471, 474, 34 | 449, 433, 422, 402, 385, 371, 343, 356, 367, 391, 417, 445, 488, 382, 375, 35 | 370, 367, 395, 415, 465, 504, 530, 520, 503, 480, 467, 402, 393, 379, 368, 36 | 352, 349, 462, 465, 448, 428, 413, 409, 397, 380, 375, 372, 428, 438, 442, 37 | 448, 454, 465 38 | ], 39 | "y": [ 40 | 238, 238, 239, 240, 249, 252, 254, 271, 272, 272, 272, 272, 272, 274, 277, 41 | 280, 280, 280, 280, 282, 289, 291, 291, 291, 291, 291, 291, 291, 292, 292, 42 | 301, 303, 303, 303, 304, 304, 310, 312, 313, 314, 314, 314, 314, 333, 335, 43 | 335, 337, 343, 345, 349, 349, 352, 353, 353, 353, 353, 353, 353, 355, 356, 44 | 359, 361, 364, 364, 373, 376, 378, 379, 381, 383, 383, 383, 387, 387, 387, 45 | 387, 387, 387 46 | ] 47 | }, 48 | { 49 | "x": [ 50 | 186, 202, 210, 228, 252, 419, 435, 468, 498, 524, 559, 567, 580, 586, 596, 51 | 606, 609, 612, 607, 392, 307, 280, 236, 198, 32, 28, 33, 60, 86, 130, 176, 52 | 397, 433, 442, 471, 490, 516, 520, 524, 303, 266, 209, 177, 159, 148, 145, 53 | 137, 126, 102, 143, 195, 263, 328, 583, 611, 616, 374, 290, 208, 143, 109, 54 | 90, 75, 72, 68, 62, 252, 336, 409, 442, 494, 520, 358, 289, 256, 193, 158, 55 | 148, 146, 278, 367, 416, 500, 557, 601, 504, 450, 417, 352, 281, 185, 180, 56 | 463, 512, 535, 543, 546, 497, 448, 382, 312, 245, 196, 158, 154, 151, 152, 57 | 192, 208, 254, 306, 364, 377, 396, 412, 423, 378, 347, 315, 289, 262, 211, 58 | 188, 168, 156, 151, 185, 220, 274, 439, 450, 453, 456, 449, 284, 261, 246, 59 | 234, 225, 211, 208, 204, 196, 189, 164, 184, 194, 219, 252, 296, 336, 350, 60 | 377, 399, 439, 458, 468 61 | ], 62 | "y": [ 63 | 69, 67, 67, 67, 66, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 65, 64 | 66, 93, 93, 93, 93, 93, 115, 117, 120, 123, 123, 123, 123, 124, 124, 124, 65 | 124, 124, 128, 129, 129, 141, 147, 155, 161, 164, 168, 168, 170, 171, 177, 66 | 180, 180, 180, 180, 174, 174, 174, 191, 191, 194, 205, 210, 213, 217, 217, 67 | 220, 220, 228, 228, 228, 228, 228, 228, 242, 245, 247, 257, 264, 268, 271, 68 | 280, 280, 280, 280, 280, 281, 295, 295, 295, 295, 297, 304, 306, 309, 309, 69 | 309, 309, 308, 316, 321, 322, 322, 328, 334, 340, 341, 342, 352, 355, 355, 70 | 355, 355, 355, 355, 355, 355, 355, 357, 359, 364, 369, 372, 372, 372, 372, 71 | 374, 375, 381, 386, 391, 396, 396, 396, 396, 396, 396, 396, 396, 396, 397, 72 | 397, 397, 397, 397, 397, 398, 401, 402, 405, 410, 414, 418, 419, 422, 422, 73 | 422, 422, 422 74 | ] 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /test/lib/inks/equation3.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 73, 74, 74, 75, 75, 76, 77, 82, 85, 89, 93, 95, 95, 95, 96, 96, 96, 96, 5 | 95, 94, 93, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 87, 83, 78, 73, 69, 6 | 69, 69, 70, 72, 77, 81, 84, 90, 94, 97, 101, 107, 110, 113, 119, 122 7 | ], 8 | "y": [ 9 | 168, 173, 176, 179, 182, 185, 188, 189, 188, 185, 182, 177, 173, 170, 165, 10 | 161, 164, 168, 172, 176, 181, 188, 191, 198, 205, 214, 224, 233, 240, 244, 11 | 249, 252, 252, 250, 246, 242, 238, 234, 231, 228, 223, 220, 219, 217, 214, 12 | 213, 211, 209, 208, 206, 202, 201 13 | ] 14 | }, 15 | { 16 | "x": [176, 184, 189, 195, 199], 17 | "y": [171, 170, 168, 167, 166] 18 | }, 19 | { 20 | "x": [170, 176, 185, 194, 199, 202, 205, 211, 214], 21 | "y": [189, 189, 189, 188, 187, 187, 187, 187, 187] 22 | }, 23 | { 24 | "x": [ 25 | 270, 276, 279, 282, 285, 286, 286, 286, 286, 283, 278, 274, 271, 267, 271, 26 | 274, 276, 277, 278, 278, 272, 269, 265, 262, 259, 255 27 | ], 28 | "y": [ 29 | 145, 145, 145, 146, 149, 152, 156, 161, 164, 167, 168, 169, 170, 170, 170, 30 | 172, 175, 178, 181, 184, 187, 189, 191, 191, 192, 192 31 | ] 32 | }, 33 | { 34 | "x": [ 35 | 311, 314, 317, 321, 324, 327, 331, 335, 337, 337, 337, 334, 331, 326, 323, 36 | 320, 315 37 | ], 38 | "y": [ 39 | 180, 175, 171, 168, 167, 167, 168, 171, 174, 177, 181, 183, 185, 186, 186, 40 | 187, 187 41 | ] 42 | }, 43 | { 44 | "x": [349, 344, 341, 338, 336, 335, 335, 337, 340, 343, 347, 351, 354, 357], 45 | "y": [170, 170, 170, 172, 175, 178, 181, 184, 185, 186, 186, 186, 186, 186] 46 | }, 47 | { 48 | "x": [381, 386, 393, 400, 403, 407, 410, 415, 419], 49 | "y": [181, 181, 181, 181, 180, 180, 180, 180, 180] 50 | }, 51 | { 52 | "x": [402, 401, 401, 401, 401, 401, 401, 401], 53 | "y": [169, 175, 178, 181, 186, 190, 193, 196] 54 | }, 55 | { 56 | "x": [ 57 | 461, 457, 456, 455, 455, 461, 465, 469, 473, 476, 478, 479, 480, 481, 481, 58 | 481, 480, 478, 476, 473, 470, 465, 460, 457, 455, 462, 470, 476, 480, 485, 59 | 491, 495, 498 60 | ], 61 | "y": [ 62 | 165, 160, 157, 152, 149, 147, 147, 147, 147, 147, 150, 154, 158, 162, 165, 63 | 168, 171, 174, 177, 181, 184, 184, 186, 187, 191, 193, 193, 193, 193, 193, 64 | 193, 193, 193 65 | ] 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /test/lib/inks/fence.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 234, 237, 238, 239, 242, 244, 246, 247, 249, 250, 252, 253, 253, 253, 253, 5 | 253, 253, 253, 253, 253, 253, 253, 254, 255, 257, 258, 258, 262, 266, 273, 6 | 283, 297, 313, 325, 338, 345, 349, 353 7 | ], 8 | "y": [ 9 | 90, 94, 98, 103, 110, 114, 118, 122, 125, 128, 131, 127, 123, 119, 114, 10 | 109, 105, 101, 98, 95, 91, 87, 82, 77, 71, 68, 65, 64, 64, 64, 64, 64, 64, 11 | 64, 64, 64, 64, 64 12 | ] 13 | }, 14 | { 15 | "x": [ 16 | 297, 301, 305, 310, 313, 317, 322, 324, 324, 324, 322, 319, 315, 311, 314, 17 | 318, 322, 326, 326, 326, 322, 317, 312, 307, 302, 298, 294, 290 18 | ], 19 | "y": [ 20 | 82, 82, 82, 81, 81, 82, 83, 86, 89, 93, 96, 98, 98, 98, 100, 102, 105, 21 | 107, 110, 113, 114, 117, 118, 119, 121, 121, 121, 120 22 | ] 23 | }, 24 | { 25 | "x": [ 26 | 250, 250, 253, 255, 258, 260, 263, 266, 268, 270, 273, 274, 277, 277, 277, 27 | 277, 277, 278, 279, 280, 280, 280, 280, 286, 294, 302, 312, 326, 344, 365, 28 | 385, 401, 413, 416 29 | ], 30 | "y": [ 31 | 198, 202, 209, 215, 222, 229, 237, 243, 250, 256, 262, 265, 260, 254, 246, 32 | 236, 223, 208, 198, 194, 189, 185, 182, 181, 181, 181, 181, 181, 181, 181, 33 | 181, 181, 181, 181 34 | ] 35 | }, 36 | { 37 | "x": [ 38 | 361, 358, 353, 349, 346, 342, 336, 334, 330, 328, 326, 324, 323, 323, 323, 39 | 323, 323, 326, 327, 330, 334, 337, 341, 345, 348, 350, 350, 347, 343, 340, 40 | 332, 327, 324, 319 41 | ], 42 | "y": [ 43 | 212, 210, 209, 208, 208, 208, 211, 214, 218, 221, 225, 230, 234, 242, 247, 44 | 254, 258, 264, 267, 269, 270, 270, 268, 266, 262, 259, 254, 253, 251, 250, 45 | 248, 248, 248, 248 46 | ] 47 | }, 48 | { 49 | "x": [ 50 | 229, 226, 223, 220, 216, 213, 210, 206, 205, 202, 201, 200, 199, 199, 199, 51 | 199, 199, 199, 199, 199, 199, 199, 198, 196, 194, 190, 186, 182, 176, 169, 52 | 163, 156, 151, 146, 143, 139, 136, 134, 133, 133, 135, 138, 142, 146, 150, 53 | 151, 154, 156, 157, 157, 157, 157, 157, 157, 157, 154, 151, 148, 145, 140, 54 | 135, 134, 134, 134, 134, 135, 136, 136, 138, 144, 151, 162, 174, 183, 193, 55 | 202, 214, 222, 228, 233, 236, 241, 244 56 | ], 57 | "y": [ 58 | 35, 34, 33, 33, 33, 33, 33, 35, 38, 41, 45, 48, 54, 64, 76, 87, 98, 107, 59 | 116, 130, 142, 155, 171, 186, 202, 219, 236, 252, 268, 278, 288, 295, 297, 60 | 295, 293, 291, 289, 286, 283, 278, 275, 275, 277, 283, 288, 294, 302, 312, 61 | 317, 321, 326, 331, 337, 342, 350, 358, 370, 385, 402, 432, 453, 470, 487, 62 | 503, 515, 528, 532, 535, 538, 539, 540, 540, 540, 540, 540, 539, 537, 535, 63 | 532, 530, 527, 525, 522 64 | ] 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /test/lib/inks/hello.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 397, 400, 404, 408, 411, 414, 417, 418, 418, 419, 419, 418, 414, 412, 412, 5 | 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 413, 417, 418, 6 | 419, 419, 421, 425, 426 7 | ], 8 | "y": [ 9 | 254, 249, 247, 242, 240, 235, 231, 227, 224, 220, 216, 211, 210, 213, 217, 10 | 221, 226, 230, 234, 238, 243, 247, 251, 255, 252, 248, 244, 241, 240, 243, 11 | 247, 251, 254, 253, 250 12 | ] 13 | }, 14 | { 15 | "x": [427, 433, 437, 440, 439, 435, 430, 428, 428, 429, 432, 436, 440, 443], 16 | "y": [244, 248, 245, 242, 238, 238, 240, 245, 249, 253, 253, 253, 251, 251] 17 | }, 18 | { 19 | "x": [ 20 | 443, 450, 453, 456, 459, 460, 461, 461, 461, 460, 456, 452, 450, 450, 450, 21 | 450, 450, 450, 450, 450, 449, 449, 451, 455, 460, 463, 464 22 | ], 23 | "y": [ 24 | 242, 242, 242, 238, 236, 233, 230, 226, 222, 217, 214, 214, 219, 223, 227, 25 | 231, 234, 238, 242, 246, 249, 253, 257, 257, 255, 252, 249 26 | ] 27 | }, 28 | { 29 | "x": [ 30 | 464, 466, 469, 471, 472, 474, 475, 473, 469, 465, 465, 465, 465, 465, 465, 31 | 465, 465, 465, 466, 467, 470, 474, 480, 482 32 | ], 33 | "y": [ 34 | 243, 236, 234, 230, 227, 223, 219, 216, 215, 216, 220, 225, 229, 233, 237, 35 | 240, 244, 248, 252, 255, 256, 254, 250, 247 36 | ] 37 | }, 38 | { 39 | "x": [ 40 | 489, 483, 482, 482, 482, 485, 489, 494, 495, 495, 492, 490, 494, 497, 501, 41 | 504 42 | ], 43 | "y": [ 44 | 238, 242, 245, 249, 253, 254, 252, 248, 244, 239, 238, 242, 242, 239, 238, 45 | 236 46 | ] 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /test/lib/inks/one.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 368, 370, 374, 381, 388, 395, 401, 405, 409, 414, 418, 422, 425, 428, 433, 5 | 436, 437, 439, 440, 441, 442, 443, 443, 443, 443, 443, 443, 443, 443, 443, 6 | 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 7 | 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, 443 8 | ], 9 | "y": [ 10 | 134, 130, 126, 121, 115, 110, 106, 103, 98, 93, 90, 86, 82, 80, 73, 69, 11 | 66, 63, 60, 63, 68, 72, 76, 80, 83, 86, 91, 94, 98, 103, 107, 111, 117, 12 | 120, 124, 127, 131, 134, 138, 142, 147, 151, 154, 158, 163, 168, 172, 175, 13 | 179, 182, 185, 188, 191, 195, 199, 202, 205 14 | ] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /test/lib/inks/rabText.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 319, 316, 313, 310, 307, 304, 303, 302, 307, 315, 320, 327, 331, 334, 336, 5 | 333, 328, 323, 320, 317, 318, 323, 328, 331, 332, 331, 329, 336, 344, 348, 6 | 350, 349, 351, 354, 357, 360, 360, 364, 370, 380, 386, 394, 403, 411, 419, 7 | 421, 417, 409, 404, 400, 396, 391, 388, 387, 386, 386, 386, 386, 386, 386, 8 | 386, 386, 383, 377, 373, 370, 368, 371, 376, 383, 392, 409, 417, 421, 423, 9 | 420, 417, 414, 413, 415, 419, 423, 429, 439, 445, 450, 455, 459, 462, 462, 10 | 461, 458, 452, 449, 445, 442, 438, 435, 433, 433, 433, 435, 438, 440, 442, 11 | 442, 442, 442, 441, 440, 438, 436, 431, 430, 436, 443, 450, 461, 467, 471, 12 | 473, 474, 471, 468, 463, 461, 467, 473, 479, 484 13 | ], 14 | "y": [ 15 | 329, 328, 328, 328, 329, 330, 333, 337, 341, 343, 343, 342, 341, 340, 336, 16 | 334, 335, 337, 339, 343, 347, 348, 346, 344, 341, 337, 334, 334, 332, 331, 17 | 334, 339, 342, 342, 341, 339, 335, 335, 335, 334, 333, 330, 327, 324, 318, 18 | 315, 308, 304, 303, 303, 304, 308, 312, 317, 322, 328, 338, 344, 350, 357, 19 | 362, 366, 366, 359, 354, 350, 346, 345, 345, 345, 344, 342, 340, 338, 335, 20 | 331, 330, 332, 337, 340, 343, 343, 343, 340, 337, 333, 328, 324, 318, 314, 21 | 310, 306, 304, 303, 304, 305, 308, 314, 320, 326, 334, 345, 351, 358, 363, 22 | 367, 371, 374, 370, 365, 359, 352, 345, 342, 341, 342, 342, 340, 338, 336, 23 | 333, 330, 328, 329, 331, 337, 342, 344, 345, 345 24 | ] 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /test/lib/inks/rc_fr_simple.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [77, 78, 80, 82, 82, 83, 84, 86, 86, 86, 86, 82], 4 | "y": [117, 121, 126, 131, 136, 144, 155, 166, 170, 175, 182, 175] 5 | }, 6 | { 7 | "x": [ 8 | 75, 79, 84, 107, 140, 173, 208, 240, 262, 275, 284, 292, 298, 304, 310, 9 | 316, 324, 331, 338, 348, 361, 371, 376, 380, 383, 386, 389, 392, 393, 393, 10 | 394, 394, 394, 394, 393, 393, 393, 392, 392, 390, 385, 367, 334, 312, 292, 11 | 280, 268, 261, 253, 245, 238, 229, 217, 211, 190, 180, 172, 166, 163, 153, 12 | 147, 140, 133, 128, 120, 113, 108, 104, 100, 97, 93, 90, 87, 88 13 | ], 14 | "y": [ 15 | 112, 115, 116, 117, 118, 120, 119, 116, 114, 113, 111, 111, 110, 108, 107, 16 | 107, 106, 106, 106, 107, 109, 110, 112, 113, 115, 116, 117, 118, 121, 128, 17 | 136, 144, 151, 155, 160, 163, 166, 169, 172, 176, 178, 176, 173, 170, 170, 18 | 169, 168, 168, 168, 168, 168, 169, 171, 172, 173, 173, 173, 173, 173, 173, 19 | 173, 174, 174, 174, 174, 173, 171, 171, 170, 169, 169, 168, 168, 174 20 | ] 21 | }, 22 | { 23 | "x": [ 24 | 78, 81, 85, 88, 91, 95, 101, 109, 123, 133, 141, 148, 149, 149, 149, 149, 25 | 148, 149, 149, 149, 149, 149, 149, 149, 149, 148, 148, 146, 144, 143, 143, 26 | 147, 148, 151, 154, 157, 160, 164, 167, 171, 175, 178, 184, 188, 192, 194, 27 | 195, 194, 191, 188, 187, 186, 184, 183, 183, 184, 188, 191, 194, 200, 208, 28 | 214, 220, 225, 226, 225, 222, 221, 220, 216, 212, 205, 203, 201, 203, 206, 29 | 212, 220, 223, 228, 235, 239, 244, 246, 249, 250, 251, 251, 250, 248, 245, 30 | 242, 239, 237, 237, 238, 241, 244, 246, 248, 251, 255, 260, 263, 265, 267, 31 | 267, 266, 263, 260, 260, 258, 257, 259, 262, 265, 268, 269, 270, 270, 269, 32 | 268, 263, 262, 268, 273, 276, 280, 283, 287 33 | ], 34 | "y": [ 35 | 252, 253, 253, 254, 254, 254, 252, 249, 243, 234, 224, 217, 212, 208, 204, 36 | 201, 206, 216, 224, 236, 248, 251, 256, 259, 263, 269, 272, 278, 275, 272, 37 | 267, 259, 256, 254, 253, 256, 259, 266, 270, 272, 272, 271, 264, 260, 255, 38 | 250, 246, 241, 239, 238, 246, 251, 257, 262, 265, 270, 275, 277, 278, 277, 39 | 271, 265, 256, 245, 232, 220, 212, 206, 201, 202, 209, 219, 227, 241, 252, 40 | 264, 272, 279, 280, 282, 279, 274, 266, 256, 243, 231, 218, 210, 204, 200, 41 | 199, 210, 224, 239, 250, 261, 270, 277, 283, 288, 291, 292, 286, 279, 272, 42 | 265, 261, 257, 255, 260, 264, 271, 277, 285, 288, 290, 288, 280, 272, 269, 43 | 261, 256, 254, 260, 262, 260, 258, 256, 255, 256 44 | ] 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /test/lib/inks/shape.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": [ 4 | 132, 129, 123, 119, 114, 111, 110, 110, 110, 110, 110, 110, 111, 113, 114, 5 | 117, 119, 123, 124, 131, 140, 149, 156, 161, 168, 192, 203, 209, 214, 219, 6 | 222, 223, 227, 228, 231, 231, 231, 231, 231, 231, 230, 229, 221, 219, 214, 7 | 211, 194, 191, 179, 173, 170, 164, 159, 154, 149, 140, 136, 133, 127, 123, 8 | 119, 117, 114, 113, 112, 111, 109 9 | ], 10 | "y": [ 11 | 222, 222, 226, 227, 230, 233, 240, 244, 248, 252, 256, 262, 267, 272, 278, 12 | 283, 288, 293, 296, 301, 307, 310, 311, 311, 312, 310, 307, 302, 294, 287, 13 | 284, 281, 273, 269, 262, 258, 246, 241, 237, 233, 229, 226, 212, 209, 204, 14 | 201, 197, 196, 196, 196, 196, 197, 198, 198, 200, 204, 207, 207, 211, 214, 15 | 217, 220, 224, 228, 231, 236, 239 16 | ] 17 | }, 18 | { 19 | "x": [ 20 | 233, 239, 251, 254, 262, 276, 296, 323, 333, 342, 351, 387, 410, 418, 431, 21 | 444, 452, 459, 470, 494, 508, 511, 511, 511, 511, 511, 511, 510, 509, 509, 22 | 508, 507, 507, 507, 507, 507, 507, 506, 506, 506, 504, 503, 502, 502, 502, 23 | 502, 502, 502, 502, 502, 502, 502, 502, 502, 502, 502, 502, 503 24 | ], 25 | "y": [ 26 | 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 27 | 251, 251, 251, 251, 251, 251, 252, 257, 261, 267, 272, 276, 280, 287, 291, 28 | 298, 304, 308, 313, 320, 324, 329, 333, 339, 346, 350, 360, 370, 377, 382, 29 | 388, 393, 398, 404, 408, 411, 422, 428, 431, 441, 447, 450, 454 30 | ] 31 | }, 32 | { 33 | "x": [ 34 | 439, 443, 446, 456, 460, 466, 473, 476, 491, 498, 503, 507, 518, 523, 526, 35 | 532, 554, 561, 567, 571, 574, 578, 586, 586, 586, 586, 586, 586, 586, 586, 36 | 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 586, 584, 582, 582, 579, 37 | 576, 570, 567, 564, 561, 552, 547, 543, 537, 531, 522, 510, 507, 503, 497, 38 | 492, 486, 483, 480, 477, 473, 470, 467, 464, 461, 457, 453, 449, 444, 441, 39 | 439, 439, 438, 437, 437, 436, 436, 434, 434, 434, 434, 437, 439, 441, 442, 40 | 442, 443, 443, 443, 443, 442, 442, 440 41 | ], 42 | "y": [ 43 | 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 457, 44 | 457, 457, 457, 456, 456, 456, 456, 456, 461, 466, 469, 474, 478, 481, 488, 45 | 493, 496, 499, 503, 510, 513, 516, 521, 524, 527, 532, 538, 542, 547, 548, 46 | 548, 548, 548, 547, 546, 544, 544, 544, 544, 544, 544, 544, 544, 544, 546, 47 | 546, 546, 546, 546, 546, 546, 546, 547, 547, 547, 547, 546, 546, 544, 544, 48 | 540, 534, 531, 527, 524, 521, 518, 510, 506, 503, 499, 497, 492, 489, 486, 49 | 483, 480, 477, 474, 471, 468, 464, 461 50 | ] 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /test/mocha/partial/00-configuration/Constants.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import Constants from '../../../../src/configuration/Constants' 4 | import { testLogger } from '../../../../src/configuration/LoggerConfig' 5 | 6 | describe('Check constants', () => { 7 | testLogger.debug(Constants) 8 | 9 | const recognitionTypes = ['TEXT', 'MATH', 'DIAGRAM', 'RAWCONTENT'] 10 | recognitionTypes.forEach((recognitionType) => { 11 | it(`Should have ${recognitionType} recognition type declared`, () => { 12 | let result = recognitionType 13 | if (recognitionType === 'RAWCONTENT') { 14 | result = 'Raw Content' 15 | } 16 | expect(Constants.RecognitionType[recognitionType]).to.equal(result) 17 | }) 18 | }) 19 | 20 | const protocols = ['REST', 'WEBSOCKET'] 21 | protocols.forEach((protocol) => { 22 | it(`Should have ${protocol} protocol declared`, () => { 23 | expect(Constants.Protocol[protocol]).to.equal(protocol) 24 | }) 25 | }) 26 | 27 | const triggers = ['QUIET_PERIOD', 'POINTER_UP', 'DEMAND'] 28 | triggers.forEach((trigger) => { 29 | it(`Should have ${trigger} trigger declared`, () => { 30 | expect(Constants.Trigger[trigger]).to.equal(trigger) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/mocha/partial/00-configuration/DefaultBehaviors.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import configurations from '../../../lib/configuration' 4 | import * as DefaultConfiguration from '../../../../src/configuration/DefaultConfiguration' 5 | import * as DefaultBehaviors from '../../../../src/configuration/DefaultBehaviors' 6 | 7 | const defaultBehaviors = DefaultBehaviors.overrideDefaultBehaviors() 8 | 9 | configurations.forEach((configuration) => { 10 | const currentConfiguration = DefaultConfiguration.overrideDefaultConfiguration({ recognitionParams: configuration }) 11 | 12 | describe(`Check behaviors for API ${currentConfiguration.recognitionParams.apiVersion} ${currentConfiguration.recognitionParams.type} ${currentConfiguration.recognitionParams.protocol}`, () => { 13 | const behavior = defaultBehaviors.getBehaviorFromConfiguration(defaultBehaviors, currentConfiguration) 14 | 15 | it('grabber', () => { 16 | assert.isDefined(behavior.grabber, 'grabber should be defined') 17 | }) 18 | 19 | it('stroker', () => { 20 | assert.isDefined(behavior.stroker, 'stroker should be defined') 21 | const strokerType = currentConfiguration.recognitionParams.protocol === 'WEBSOCKET' ? 'svg' : 'canvas' 22 | assert.strictEqual(behavior.stroker.getInfo().type, strokerType) 23 | }) 24 | 25 | it('renderer', () => { 26 | assert.isDefined(behavior.renderer, 'renderer should be defined') 27 | const rendererType = currentConfiguration.recognitionParams.protocol === 'WEBSOCKET' ? 'svg' : 'canvas' 28 | assert.strictEqual(behavior.renderer.getInfo().type, rendererType) 29 | }) 30 | 31 | it('recognizer', () => { 32 | assert.isDefined(behavior.recognizer, 'recognizer should be defined') 33 | assert.include(behavior.recognizer.getInfo().types, currentConfiguration.recognitionParams.type) 34 | assert.strictEqual(behavior.recognizer.getInfo().protocol, currentConfiguration.recognitionParams.protocol) 35 | // assert.strictEqual(defaultBehaviors.optimizedParameters.exportContentTriggerOn, trigger, `${trigger} should be the default value for ${behavior} exportContentTriggerOn`); 36 | }) 37 | 38 | it('events', () => { 39 | assert.isDefined(behavior.events, 'events should be defined') 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/mocha/partial/00-configuration/DefaultConfiguration.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import configurations from '../../../lib/configuration' 4 | import * as DefaultConfiguration from '../../../../src/configuration/DefaultConfiguration' 5 | 6 | configurations.forEach((configuration) => { 7 | describe(`Check configuration for API ${configuration.apiVersion} ${configuration.type} ${configuration.protocol}`, () => { 8 | const watcher = { 9 | update: (value) => { 10 | assert.equal('ja_JP', value) 11 | }, 12 | prop: 'lang' 13 | } 14 | const currentConfiguration = DefaultConfiguration.overrideDefaultConfiguration({ recognitionParams: configuration }, watcher) 15 | 16 | it('type', () => { 17 | assert.isDefined(currentConfiguration.recognitionParams.type, 'type should be defined') 18 | assert.strictEqual(currentConfiguration.recognitionParams.type, configuration.type, `${currentConfiguration.recognitionParams.type} should be the default value for type`) 19 | }) 20 | 21 | it('protocol', () => { 22 | assert.isDefined(currentConfiguration.recognitionParams.protocol, 'protocol should be defined') 23 | assert.strictEqual(currentConfiguration.recognitionParams.protocol, configuration.protocol, `${currentConfiguration.recognitionParams.protocol} should be the default value for protocol`) 24 | }) 25 | 26 | it('apiVersion', () => { 27 | assert.isDefined(currentConfiguration.recognitionParams.apiVersion, 'apiVersion should be defined') 28 | assert.strictEqual(currentConfiguration.recognitionParams.apiVersion, configuration.apiVersion, `${currentConfiguration.recognitionParams.apiVersion} should be the default value for apiVersion`) 29 | }) 30 | 31 | it('server', () => { 32 | assert.isDefined(currentConfiguration.recognitionParams.server, 'recognitionParams.server should keep its default value') 33 | }) 34 | 35 | it('should notify language change', () => { 36 | currentConfiguration.recognitionParams.iink.lang = 'ja_JP' 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/mocha/partial/00-configuration/LoggerConfig.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import * as loggers from '../../../../src/configuration/LoggerConfig' 4 | 5 | describe('Check loggers definition and initialization', () => { 6 | it('module is define', () => { 7 | assert.notEqual(loggers, undefined) 8 | }) 9 | 10 | const loggerList = ['grabber', 'editor', 'renderer', 'model', 'recognizer', 'test', 'util'] 11 | loggerList.forEach((loggerName) => { 12 | const logger = loggers[`${loggerName}Logger`] 13 | 14 | it(`${loggerName}Logger is define`, () => { 15 | assert.notEqual(logger, undefined) 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/mocha/partial/01-model/InkModel.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import { testLogger as logger } from '../../../../src/configuration/LoggerConfig' 4 | import * as InkModel from '../../../../src/model/InkModel' 5 | 6 | describe('Testing InkModel', () => { 7 | describe('constructor', () => { 8 | let param 9 | let model 10 | 11 | beforeEach(() => { 12 | model = InkModel.createModel(param) 13 | }) 14 | 15 | it('Check mandatory properties', () => { 16 | assert.property(model, 'currentStroke') 17 | assert.property(model, 'rawStrokes') 18 | assert.property(model, 'lastPositions') 19 | assert.nestedProperty(model, 'lastPositions.lastSentPosition') 20 | assert.nestedProperty(model, 'lastPositions.lastReceivedPosition') 21 | assert.property(model, 'defaultSymbols') 22 | assert.property(model, 'recognizedSymbols') 23 | assert.nestedProperty(model, 'rawResults.convert') 24 | assert.nestedProperty(model, 'rawResults.exports') 25 | assert.property(model, 'creationTime') 26 | assert.property(model, 'modificationTime') 27 | }) 28 | }) 29 | describe('workflow', () => { 30 | const model = InkModel.createModel() 31 | 32 | it('Creating a model and update pending strokes', () => { 33 | const updatedModel1 = InkModel.initPendingStroke(model, { x: 1, y: 1 }) 34 | const updatedModel2 = InkModel.appendToPendingStroke(updatedModel1, { x: 2, y: 2 }) 35 | const updatedModel3 = InkModel.appendToPendingStroke(updatedModel2, { x: 3, y: 3 }) 36 | const updatedModel4 = InkModel.endPendingStroke(updatedModel3, { x: 4, y: 4 }) 37 | logger.debug('Last model is ', updatedModel4) 38 | assert.deepEqual(model, updatedModel4) 39 | }) 40 | 41 | it('Should clone model', () => { 42 | const copy = InkModel.cloneModel(model) 43 | assert.equal(model.currentStroke, copy.currentStroke) 44 | assert.sameDeepMembers(model.rawStrokes, copy.rawStrokes) 45 | assert.equal(model.lastPositions.lastReceivedPosition, copy.lastPositions.lastReceivedPosition) 46 | assert.equal(model.lastPositions.lastSentPosition, copy.lastPositions.lastSentPosition) 47 | assert.sameDeepMembers(model.defaultSymbols, copy.defaultSymbols) 48 | assert.equal(model.recognizedSymbols, copy.recognizedSymbols) 49 | assert.equal(model.rawResults.exports, copy.rawResults.exports) 50 | assert.equal(model.rawResults.convert, copy.rawResults.convert) 51 | assert.equal(model.creationTime, copy.creationTime) 52 | }) 53 | 54 | it('Should merge models', () => { 55 | const modelToMerge = InkModel.cloneModel(model) 56 | modelToMerge.currentStroke = { x: 1, y: 1 } 57 | 58 | const mergedModel = InkModel.mergeModels(modelToMerge, model) 59 | assert.equal(mergedModel.recognizedSymbols, modelToMerge.recognizedSymbols) 60 | }) 61 | 62 | // TODO Test all other function 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /test/mocha/partial/01-model/StrokeComponent.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import * as StrokeComponent from '../../../../src/model/StrokeComponent' 4 | 5 | describe('Check StrokeComponent', () => { 6 | describe('constructor', () => { 7 | const obj = { color: '#000F55', width: 3, x: [10, 20], y: [30, 40], t: [50, 60] } 8 | Object.keys(obj).forEach((key) => { 9 | it(`Check custom constructor param ${key}`, () => { 10 | assert.property(stroke, key) 11 | assert.propertyVal(stroke, key, obj[key]) 12 | }) 13 | }) 14 | let stroke 15 | 16 | beforeEach(() => { 17 | stroke = StrokeComponent.createStrokeComponent(obj) 18 | }) 19 | 20 | it('Check mandatory properties', () => { 21 | assert.property(stroke, 'type') 22 | assert.propertyVal(stroke, 'type', 'stroke') 23 | assert.property(stroke, 'x') 24 | assert.property(stroke, 'y') 25 | assert.property(stroke, 't') 26 | assert.property(stroke, 'p') 27 | assert.property(stroke, 'l') 28 | assert.property(stroke, 'width') 29 | }) 30 | it('Check toJSON function', () => { 31 | assert.deepEqual({ x: obj.x, y: obj.y, t: obj.t, pointerType: undefined }, StrokeComponent.toJSON(stroke)) 32 | }) 33 | }) 34 | 35 | describe('workflow', () => { 36 | const stroke = StrokeComponent.createStrokeComponent() 37 | 38 | const pointsNb = 10 39 | const filledStroke = { type: 'stroke', x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], y: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18], t: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27], p: [0.5, 0.8504651218778779, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082], l: [0, 2.23606797749979, 4.47213595499958, 6.708203932499369, 8.94427190999916, 11.180339887498949, 13.416407864998739, 15.652475842498529, 17.88854381999832, 20.12461179749811], width: 0 } 40 | it(`Check addPoint (adding ${pointsNb} points)`, () => { 41 | for (let i = 0; i < pointsNb; i++) { 42 | StrokeComponent.addPoint(stroke, { x: i, y: i * 2, t: i * 3 }) 43 | } 44 | assert.deepEqual(filledStroke, stroke) 45 | }) 46 | 47 | const point = { x: 5, y: 10, t: 15, p: 0.6372367375521082, l: 11.180339887498949 } 48 | it('Check getPointByIndex', () => { 49 | assert.deepEqual(point, StrokeComponent.getPointByIndex(stroke, 5)) 50 | }) 51 | 52 | const slicedStroke = { type: 'stroke', x: [5, 6, 7, 8, 9], y: [10, 12, 14, 16, 18], t: [15, 18, 21, 24, 27], p: [0.5, 0.8504651218778779, 0.6372367375521082, 0.6372367375521082, 0.6372367375521082], l: [0, 2.23606797749979, 4.47213595499958, 6.708203932499369, 8.94427190999916], width: 0, color: undefined } 53 | it('Check slice', () => { 54 | assert.deepEqual(slicedStroke, StrokeComponent.slice(stroke, 5)) 55 | }) 56 | }) 57 | 58 | // TODO Test all other function 59 | }) 60 | -------------------------------------------------------------------------------- /test/mocha/partial/01-model/UndoRedoManager.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import * as InkModel from '../../../../src/model/InkModel' 4 | import * as UndoRedoContext from '../../../../src/model/UndoRedoContext' 5 | import * as UndoRedoManager from '../../../../src/model/UndoRedoManager' 6 | import * as DefaultConfiguration from '../../../../src/configuration/DefaultConfiguration' 7 | 8 | describe('Check undo/redo manager', () => { 9 | const configuration = DefaultConfiguration.overrideDefaultConfiguration() 10 | const undoRedoContext = UndoRedoContext.createUndoRedoContext(configuration) 11 | const maxSize = undoRedoContext.maxSize 12 | 13 | it('Should be empty', () => { 14 | assert.lengthOf(undoRedoContext.stack, 0) 15 | assert.equal(undoRedoContext.currentPosition, -1) 16 | assert.equal(undoRedoContext.maxSize, configuration.undoRedoMaxStackSize) 17 | }) 18 | 19 | const count = maxSize 20 | it(`Should add ${count} models in stack`, (done) => { 21 | for (let i = 0; i < count; i++) { 22 | UndoRedoManager.updateModel(undoRedoContext, InkModel.createModel(configuration)) 23 | } 24 | assert.lengthOf(undoRedoContext.stack, maxSize) 25 | assert.equal(undoRedoContext.currentPosition, maxSize - 1) 26 | UndoRedoManager.getModel(undoRedoContext) 27 | .then(({ res, types }) => { 28 | assert.isTrue(undoRedoContext.canUndo, 'Wrong canUndo state') 29 | assert.isFalse(undoRedoContext.canRedo, 'Wrong canRedo state') 30 | done() 31 | }) 32 | }) 33 | 34 | it(`Should undo and update current index to ${maxSize - 2}`, (done) => { 35 | UndoRedoManager.undo(undoRedoContext, undefined) 36 | .then(({ res, types }) => { 37 | assert.lengthOf(undoRedoContext.stack, maxSize) 38 | assert.equal(undoRedoContext.currentPosition, maxSize - 2) 39 | assert.isTrue(undoRedoContext.canUndo, 'Wrong canUndo state') 40 | assert.isTrue(undoRedoContext.canRedo, 'Wrong canRedo state') 41 | done() 42 | }) 43 | }) 44 | 45 | it(`Should redo and update current index to ${maxSize - 1}`, (done) => { 46 | UndoRedoManager.redo(undoRedoContext, undefined) 47 | .then(({ res, types }) => { 48 | assert.lengthOf(undoRedoContext.stack, maxSize) 49 | assert.equal(undoRedoContext.currentPosition, maxSize - 1) 50 | assert.isTrue(undoRedoContext.canUndo, 'Wrong canUndo state') 51 | assert.isFalse(undoRedoContext.canRedo, 'Wrong canRedo state') 52 | done() 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/mocha/partial/02-behaviors/PointerEventGrabber.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import * as sinon from 'sinon' 4 | import { testLogger as logger } from '../../../../src/configuration/LoggerConfig' 5 | import * as grabber from '../../../../src/grabber/PointerEventGrabber' 6 | 7 | describe('Testing the Grabber', () => { 8 | beforeEach(() => { 9 | global.document = { documentElement: { addEventListener: () => {} } } 10 | }) 11 | 12 | after(() => { 13 | global.document = undefined 14 | }) 15 | 16 | it('Test event registration', () => { 17 | const spiedEditor = { pointerUp: sinon.spy(), configuration: { capture: true } } 18 | const spiedDomDocument = { addEventListener: sinon.spy() } 19 | logger.debug('Attaching document to spied element') 20 | grabber.attach(spiedDomDocument, spiedEditor) 21 | 22 | assert.strictEqual(spiedDomDocument.addEventListener.callCount, 7, 'Not all events have been registered') 23 | }) 24 | 25 | // TODO Add some tests sending events and checking that grabber behave as expected 26 | }) 27 | -------------------------------------------------------------------------------- /test/mocha/partial/CryptoHelper.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { assert } from 'chai' 3 | import * as CryptoHelper from '../../../src/recognizer/CryptoHelper' 4 | 5 | describe('Hmac computation test', () => { 6 | it('nominal case', () => { 7 | const computedHmac = CryptoHelper.computeHmac('Message', 'Key') 8 | assert.equal(computedHmac, '7a2d9a9ad584ccbb3c110bf4e94d8dfef284eb258da89b2aeb01c43fa7e9d719a2b765af3f208f62ed36723d9562b9fe68a9f7e38b49e2ae6558deadcb274d8f') 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/playwright/02-ws-math-import.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | const { one } = require('../lib/inksDatas') 4 | 5 | describe(`${process.env.BROWSER}:v4/websocket_math_import_jiix.html`, () => { 6 | it('should test import', async () => { 7 | const editorEl = await page.waitForSelector('#editor') 8 | const isInit = await isEditorInitialized(editorEl) 9 | expect(isInit).to.equal(true) 10 | 11 | await playStrokes(page, one.strokes, 100, 100) 12 | await page.evaluate(exported) 13 | 14 | const latex = await editorEl.evaluate(node => node.editor.model.exports['application/x-latex']) 15 | expect(latex).to.equal(one.exports.LATEX[one.exports.LATEX.length - 1]) 16 | 17 | const editorEl2 = await page.waitForSelector('#editor2') 18 | const isInit2 = await isEditorInitialized(editorEl2) 19 | expect(isInit2).to.equal(true) 20 | 21 | await page.click('#import') 22 | await page.evaluate(`(async () => { 23 | return new Promise((resolve, reject) => { 24 | document.getElementById('editor2').addEventListener('exported', (e) => { 25 | resolve('exported'); 26 | }); 27 | }); 28 | })()`) 29 | 30 | const jiix = await editorEl2.evaluate(node => node.editor.model.exports['application/vnd.myscript.jiix']) 31 | const jiixParsed = JSON.parse(jiix) 32 | expect(one.exports.LATEX[one.exports.LATEX.length - 1]).to.equal(jiixParsed.expressions[0].label) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/playwright/03-ws-text.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | 4 | const { helloHow, helloStrike } = require('../lib/inksDatas') 5 | 6 | describe(`${process.env.BROWSER}:v4/websocket_text_iink.html`, () => { 7 | it('should check smartguide', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | expect(await isEditorInitialized(editorEl)).to.equal(true) 10 | 11 | await playStrokes(page, helloHow.strokes, 0, 0) 12 | await page.evaluate(exported) 13 | 14 | const smartguide = await page.waitForSelector('.smartguide') 15 | const randomString = await smartguide.evaluate(node => node.id.replace('smartguide', '')) 16 | 17 | const prompterText = await page.waitForSelector('.prompter-text') 18 | let textContent = await prompterText.evaluate(node => node.textContent) 19 | const labelsWithNbsp = helloHow.exports.TEXT[helloHow.exports.TEXT.length - 1] 20 | .replace(/\s/g, '\u00A0') 21 | 22 | expect(labelsWithNbsp).to.equal(textContent) 23 | 24 | const ellipsis = await page.locator('.ellipsis') 25 | await ellipsis.click() 26 | const moreMenu = await page.locator('.more-menu') 27 | const convert = await moreMenu.locator('button:text("Convert")') 28 | await convert.click() 29 | 30 | await page.evaluate(exported) 31 | 32 | textContent = await prompterText.evaluate(node => node.textContent) 33 | expect(labelsWithNbsp).to.equal(textContent) 34 | 35 | const words = labelsWithNbsp.toString().split('\u00A0') 36 | // a random word in the smartGuide 37 | const wordIdx = Math.floor(Math.random() * words.length) 38 | await page.click('#word-' + (wordIdx * 2) + randomString) 39 | 40 | await page.waitForSelector(`#candidates${randomString}`) 41 | const candidates = await page.waitForSelector(`#candidates${randomString}`) 42 | const nbCand = await candidates.evaluate(node => node.getElementsByTagName('span').length) 43 | 44 | // a random candidate in the smartGuide 45 | const candIdx = Math.floor(Math.random() * nbCand) 46 | const candidateEl = await page.waitForSelector(`#cdt-${candIdx}${randomString}`) 47 | const candidateTextContent = await candidateEl.evaluate(node => node.textContent) 48 | 49 | const exportedPromise = page.evaluate(exported) 50 | await page.click(`#cdt-${candIdx}${randomString}`) 51 | await exportedPromise 52 | 53 | textContent = await prompterText.evaluate(node => node.textContent) 54 | expect(textContent.indexOf(candidateTextContent)).to.greaterThan(-1) 55 | }) 56 | 57 | it('should check gesture works', async () => { 58 | const editorEl = await page.waitForSelector('#editor') 59 | expect(await isEditorInitialized(editorEl)).to.equal(true) 60 | await editorEl.evaluate(node => { 61 | const conf = JSON.parse(JSON.stringify(node.editor.configuration)) 62 | conf.recognitionParams.iink.gesture = { enable: true } 63 | node.editor.configuration = conf 64 | }) 65 | expect(await isEditorInitialized(editorEl)).to.equal(true) 66 | 67 | let exportPromise = page.evaluate(exported) 68 | await playStrokes(page, helloStrike.strokes, 0, 0) 69 | await exportPromise 70 | await page.waitForTimeout(1000) 71 | let result = await editorEl.evaluate(node => node.editor.model.exports['text/plain']) 72 | expect(result).to.equal('') 73 | 74 | await editorEl.evaluate(node => { 75 | const conf = JSON.parse(JSON.stringify(node.editor.configuration)) 76 | conf.recognitionParams.iink.gesture = { enable: false } 77 | node.editor.configuration = conf 78 | }) 79 | expect(await isEditorInitialized(editorEl)).to.equal(true) 80 | 81 | exportPromise = page.evaluate(exported) 82 | await playStrokes(page, helloStrike.strokes, 0, 0) 83 | await exportPromise 84 | 85 | result = await editorEl.evaluate(node => node.editor.model.exports['text/plain']) 86 | expect(result).not.equal('') 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /test/playwright/04-ws-text-hightlight-word.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes, findValuesByKey } = require('./helper') 3 | 4 | const { helloHowDecoHighlighted } = require('../lib/inksDatas') 5 | 6 | describe(`${process.env.BROWSER}:v4/websocket_text_highlight_words.html`, () => { 7 | it('should check text decorations', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | const isInit = await isEditorInitialized(editorEl) 10 | expect(isInit).to.equal(true) 11 | 12 | await playStrokes(page, helloHowDecoHighlighted.strokes, 100, 100) 13 | await page.evaluate(exported) 14 | 15 | const plainText = await editorEl.evaluate(node => node.editor.model.exports['text/plain']) 16 | expect(plainText).to.equal(helloHowDecoHighlighted.exports.TEXT[helloHowDecoHighlighted.exports.TEXT.length - 1]) 17 | 18 | const smartguide = await page.waitForSelector('.smartguide') 19 | const randomString = await smartguide.evaluate(node => node.id.replace('smartguide', '')) 20 | 21 | await page.click(`#ellipsis${randomString}`) 22 | await page.click(`#convert${randomString}`) 23 | 24 | const jiix = await editorEl.evaluate(node => node.editor.model.exports['application/vnd.myscript.jiix']) 25 | const spanList = findValuesByKey(jiix, 'spans') 26 | expect(spanList.length).to.equal(2) 27 | 28 | const span0 = spanList[0] 29 | expect(span0['first-char']).to.equal(0) 30 | expect(span0['last-char']).to.equal(4) 31 | expect(span0.class).to.equal('text') 32 | 33 | const span1 = spanList[1] 34 | expect(span1['first-char']).to.equal(6) 35 | expect(span1['last-char']).to.equal(8) 36 | if (helloHowDecoHighlighted.name.includes('Highlighted')) { 37 | expect(span1.class).to.equal('text text-highlight') 38 | } else { 39 | expect(span1.class).to.equal('text text-emphasis1') 40 | } 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/playwright/05-rest-text.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | 4 | const { hellov4rest } = require('../lib/inksDatas') 5 | 6 | describe(`${process.env.BROWSER}:v4/rest_text_iink.html`, () => { 7 | it('should test labels', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | 10 | expect(await isEditorInitialized(editorEl)).to.equal(true) 11 | 12 | await playStrokes(page, hellov4rest.strokes, 100, 100) 13 | await page.evaluate(exported) 14 | await page.waitForTimeout(1000) 15 | const plainText = await editorEl.evaluate(node => node.editor.model.exports['text/plain']) 16 | expect(plainText).to.equal(hellov4rest.exports.TEXT[hellov4rest.exports.TEXT.length - 1]) 17 | }) 18 | 19 | it('should test undo/redo with REST', async () => { 20 | const editorEl = await page.waitForSelector('#editor') 21 | const isInit = await isEditorInitialized(editorEl) 22 | expect(isInit).to.equal(true) 23 | 24 | await playStrokes(page, hellov4rest.strokes, 100, 100) 25 | await page.evaluate(exported) 26 | await page.waitForTimeout(1000) 27 | let raw = await editorEl.evaluate(node => node.editor.model.rawStrokes) 28 | expect(raw.length).to.equal(hellov4rest.strokes.length) 29 | const plain = await editorEl.evaluate(node => node.editor.model.exports['text/plain']) 30 | expect(plain).to.equal(hellov4rest.exports.TEXT[hellov4rest.exports.TEXT.length - 1]) 31 | 32 | const clearClick = page.click('#clear') 33 | let exportedEvent = page.evaluate(exported) 34 | await Promise.all([clearClick, exportedEvent]) 35 | const exports = await editorEl.evaluate(node => node.editor.model.exports) 36 | expect(exports).to.equal(undefined) 37 | 38 | let undoClick = page.click('#undo') 39 | exportedEvent = page.evaluate(exported) 40 | await Promise.all([undoClick, exportedEvent]) 41 | raw = await editorEl.evaluate(node => node.editor.model.rawStrokes) 42 | expect(raw.length).to.equal(hellov4rest.strokes.length) 43 | 44 | undoClick = page.click('#undo') 45 | exportedEvent = page.evaluate(exported) 46 | await Promise.all([undoClick, exportedEvent]) 47 | raw = await editorEl.evaluate(node => node.editor.model.rawStrokes) 48 | expect(raw.length).to.equal(hellov4rest.strokes.length - 1) 49 | 50 | undoClick = page.click('#undo') 51 | exportedEvent = page.evaluate(exported) 52 | await Promise.all([undoClick, exportedEvent]) 53 | raw = await editorEl.evaluate(node => node.editor.model.rawStrokes) 54 | expect(raw.length).to.equal(hellov4rest.strokes.length - 2) 55 | 56 | undoClick = page.click('#redo') 57 | exportedEvent = page.evaluate(exported) 58 | await Promise.all([undoClick, exportedEvent]) 59 | raw = await editorEl.evaluate(node => node.editor.model.rawStrokes) 60 | expect(raw.length).to.equal(hellov4rest.strokes.length - 1) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/playwright/06-ws-math-custom-resources.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | 4 | const equation = require('../lib/inksDatas')['3times2'] 5 | 6 | describe(`${process.env.BROWSER}:v4/websocket_math_custom_resources.html`, () => { 7 | it('should test recognition asset builder', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | const isInit = await isEditorInitialized(editorEl) 10 | expect(isInit).to.equal(true) 11 | 12 | await playStrokes(page, equation.strokes, 200, 200) 13 | await page.evaluate(exported) 14 | 15 | const latex = await editorEl.evaluate(node => node.editor.model.exports['application/x-latex']) 16 | expect(latex).to.equal(equation.exports.LATEX[equation.exports.LATEX.length - 1]) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/playwright/07-ws-text-custom-lexicon.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | 4 | const { rabText } = require('../lib/inksDatas') 5 | 6 | describe(`${process.env.BROWSER}:v4/websocket_text_custom_lexicon.html`, () => { 7 | it('should test recognition asset builder lexicon', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | const isInit = await isEditorInitialized(editorEl) 10 | expect(isInit).to.equal(true) 11 | 12 | await page.waitForSelector('#lexicon') 13 | await page.type('#lexicon', 'covfefe') 14 | await page.click('#reinit') 15 | await playStrokes(page, rabText.strokes, 100, 100) 16 | await page.evaluate(exported) 17 | 18 | const prompterText = await page.waitForSelector('.prompter-text') 19 | const textContent = await prompterText.evaluate(node => node.textContent) 20 | expect(textContent).to.equal(rabText.exports.TEXT[0]) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/playwright/08-rest-raw-content.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | 4 | const { rawContentFr } = require('../lib/inksDatas') 5 | 6 | describe(`${process.env.BROWSER}:v4/rest_raw_content_iink.html`, () => { 7 | it('should test raw content on rest text', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | const isInit = await isEditorInitialized(editorEl) 10 | expect(isInit).to.equal(true) 11 | 12 | await playStrokes(page, rawContentFr.strokes, 200, 200) 13 | await page.evaluate(exported) 14 | 15 | const jiix = await editorEl.evaluate(node => node.editor.model.exports['application/vnd.myscript.jiix']) 16 | const parsed = JSON.parse(JSON.stringify(jiix)) 17 | 18 | expect(parsed.type).to.equal('Raw Content') 19 | expect(parsed.elements.length > 0).to.equal(true) 20 | 21 | let nonTextFound = false 22 | let textFound = '' 23 | parsed.elements.forEach((element) => { 24 | if (element.type === 'Raw Content' && element.kind === 'non-text') { 25 | nonTextFound = true 26 | } 27 | if (element.type === 'Text') { 28 | textFound = element.label 29 | } 30 | }) 31 | 32 | expect(nonTextFound).to.equal(true) 33 | expect(textFound.length > 0).to.equal(true) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/playwright/09-change-configuration.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes, getStrokesFromJIIX } = require('./helper') 3 | 4 | const { hello, equation3 } = require('../lib/inksDatas') 5 | 6 | async function openCard (title) { 7 | return await page.click(`text=${title}`) 8 | } 9 | 10 | describe(`${process.env.BROWSER}:non-version-specific/change_configuration.html`, () => { 11 | it('should test default configuration', async () => { 12 | const editorEl = await page.waitForSelector('#editor') 13 | const isInit = await isEditorInitialized(editorEl) 14 | expect(isInit).to.equal(true) 15 | 16 | // expect(await page.inputValue('#server-scheme')).to.equal('https') 17 | // expect(await page.inputValue('#server-host')).to.equal('webdemoapi.myscript.com') 18 | expect(await page.inputValue('#recognition-type')).to.equal('TEXT') 19 | expect(await page.inputValue('#recognition-type')).to.equal('TEXT') 20 | expect(await page.inputValue('#recognition-protocol')).to.equal('WEBSOCKET') 21 | expect(await page.inputValue('#iink-language')).to.equal('en_US') 22 | expect(await page.isChecked('#iink-smartGuide')).to.equal(true) 23 | expect(await page.isChecked('#iink-guides')).to.equal(true) 24 | expect(await page.inputValue('#triggers-delay')).to.equal('2000') 25 | expect(await page.inputValue('#triggers-exportContent')).to.equal('POINTER_UP') 26 | 27 | await playStrokes(page, hello.strokes, 0, 0) 28 | await page.evaluate(exported) 29 | // ugly but useful for webkit & firefox 30 | await page.waitForTimeout(500) 31 | 32 | const plainText = await editorEl.evaluate(node => node.editor.model.exports['text/plain']) 33 | expect(plainText).to.equal(hello.exports.TEXT[hello.exports.TEXT.length - 1]) 34 | }) 35 | 36 | it('should test WEBSOCKET MATH config', async () => { 37 | let editorEl = await page.waitForSelector('#editor') 38 | let isInit = await isEditorInitialized(editorEl) 39 | expect(isInit).to.equal(true) 40 | 41 | await openCard('Recognition params') 42 | await page.selectOption('#recognition-type', 'MATH') 43 | 44 | expect(await page.inputValue('#recognition-type')).to.equal('MATH') 45 | expect(await page.inputValue('#recognition-protocol')).to.equal('WEBSOCKET') 46 | expect(await page.inputValue('#iink-language')).to.equal('en_US') 47 | expect(await page.isChecked('#iink-smartGuide')).to.equal(true) 48 | expect(await page.isChecked('#iink-guides')).to.equal(true) 49 | expect(await page.inputValue('#triggers-delay')).to.equal('2000') 50 | expect(await page.inputValue('#triggers-exportContent')).to.equal('POINTER_UP') 51 | 52 | await page.click('#valid-btn') 53 | 54 | editorEl = await page.waitForSelector('#editor') 55 | isInit = await isEditorInitialized(editorEl) 56 | expect(isInit).to.equal(true) 57 | 58 | await playStrokes(page, equation3.strokes, 0, 0) 59 | await page.evaluate(exported) 60 | 61 | const jiix = await editorEl.evaluate(node => node.editor.model.exports['application/vnd.myscript.jiix']) 62 | expect(getStrokesFromJIIX(jiix).length).to.equal(equation3.strokes.length) 63 | 64 | const latex = await editorEl.evaluate(node => node.editor.model.exports['application/x-latex']) 65 | expect(latex).to.equal(equation3.exports.LATEX[equation3.exports.LATEX.length - 1]) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/playwright/10-ws-text-big-text.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { exported, isEditorInitialized, playStrokes } = require('./helper') 3 | 4 | const { bigText } = require('../lib/inksDatas') 5 | 6 | describe(`${process.env.BROWSER}:v4/websocket_text_iink_no_guides.html`, () => { 7 | it('should get the correct number of strokes', async () => { 8 | const editorEl = await page.waitForSelector('#editor') 9 | const isInit = await isEditorInitialized(editorEl) 10 | expect(isInit).to.equal(true) 11 | 12 | await playStrokes(page, bigText.strokes, 100, 100) 13 | await page.evaluate(exported) 14 | 15 | const nbStrokes = bigText.strokes.length 16 | const modelLocator = await page.locator('(//*[@data-layer="MODEL"])') 17 | const pathModelLocator = await modelLocator.locator('path') 18 | expect(await pathModelLocator.count()).to.equal(nbStrokes) 19 | }).timeout(90000) 20 | }) 21 | -------------------------------------------------------------------------------- /test/playwright/helper/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param obj 4 | * @param key 5 | * @param list 6 | * @returns {*} 7 | */ 8 | function findValuesHelper (obj, key, list) { 9 | let listRef = list 10 | if (!obj) return listRef 11 | if (obj instanceof Array) { 12 | Object.keys(obj).forEach((k) => { 13 | listRef = listRef.concat(findValuesHelper(obj[k], key, [])) 14 | }) 15 | return listRef 16 | } 17 | if (obj[key]) { 18 | if (obj[key] instanceof Array) { 19 | Object.keys(obj[key]).forEach((l) => { 20 | listRef.push(obj[key][l]) 21 | }) 22 | } else { 23 | listRef.push(obj[key]) 24 | } 25 | } 26 | 27 | if (typeof obj === 'object') { 28 | const children = Object.keys(obj) 29 | if (children.length > 0) { 30 | children.forEach((child) => { 31 | listRef = listRef.concat(findValuesHelper(obj[child], key, [])) 32 | }) 33 | } 34 | } 35 | return listRef 36 | } 37 | 38 | /** 39 | * 40 | * @param obj 41 | * @param key 42 | * @returns {*} 43 | */ 44 | function findValuesByKey (obj, key) { 45 | return findValuesHelper(JSON.parse(obj), key, []) 46 | } 47 | 48 | /** 49 | * 50 | * @param jiix 51 | * @returns {*} 52 | */ 53 | function getStrokesFromJIIX (jiix) { 54 | const itemsList = findValuesByKey(jiix, 'items') 55 | return itemsList.filter(item => item.type === 'stroke') 56 | } 57 | 58 | /** 59 | * 60 | * @param page 61 | * @param strokes 62 | * @param offsetX 63 | * @param offsetY 64 | * @returns {Promise} 65 | */ 66 | async function playStrokes (page, strokes, offsetX, offsetY) { 67 | const offsetXRef = offsetX || 0 68 | const offsetYRef = offsetY || 0 69 | 70 | for (const { x, y, t } of strokes) { 71 | const hasTimeStamp = t && t.length === x.length 72 | await page.mouse.move(offsetXRef + x[0], offsetYRef + y[0]) 73 | await page.mouse.down() 74 | 75 | let oldTimestamp = hasTimeStamp ? t[0] : null 76 | for (let p = 0; p < x.length; p++) { 77 | let waitTime = 0 78 | if (hasTimeStamp) { 79 | waitTime = t[p] - oldTimestamp 80 | oldTimestamp = t[p] 81 | } 82 | await page.waitForTimeout(waitTime) 83 | await page.mouse.move(offsetXRef + x[p], offsetYRef + y[p]) 84 | } 85 | await page.mouse.up() 86 | await page.waitForTimeout(100) 87 | } 88 | } 89 | 90 | /** 91 | * @param page 92 | * @returns {Promise} 93 | */ 94 | async function isEditorInitialized (editorEl) { 95 | await editorEl.evaluate(node => node.editor.recognizerContext.initPromise) 96 | return await editorEl.evaluate(node => node.editor.initialized) 97 | } 98 | 99 | const exported = `(async () => { 100 | return new Promise((resolve, reject) => { 101 | document.getElementById('editor').addEventListener('exported', (e) => { 102 | resolve('exported'); 103 | }); 104 | }); 105 | })()` 106 | 107 | module.exports = { 108 | getStrokesFromJIIX, 109 | playStrokes, 110 | findValuesByKey, 111 | isEditorInitialized, 112 | exported 113 | } 114 | -------------------------------------------------------------------------------- /test/playwright/helper/mochaHooks.js: -------------------------------------------------------------------------------- 1 | const { chromium, webkit, firefox } = require('playwright') 2 | 3 | exports.mochaHooks = { 4 | async beforeAll () { 5 | const browserName = process.env.BROWSER || 'firefox' 6 | let args = [] 7 | if (browserName === 'chromium') { 8 | args = ['--shm-size=5gb', '--disable-dev-shm-usage', '--no-sandbox', '--disable-setuid-sandbox'] 9 | } 10 | global.browser = await { chromium, webkit, firefox }[browserName].launch({ headless: JSON.parse(process.env.HEADLESS), args }) 11 | const context = await browser.newContext() 12 | global.page = await context.newPage() 13 | return Promise.resolve() 14 | }, 15 | async beforeEach () { 16 | const exampleFilePath = this.currentTest.parent.title.split(':')[1] 17 | return await global.page.goto(`${process.env.LAUNCH_URL}/examples/${exampleFilePath}`) 18 | }, 19 | async afterEach () { 20 | if (process.env.SCREEN_SHOT) { 21 | const [browserName, exampleFilePath] = this.currentTest.parent.title.split(':') 22 | return await global.page.screenshot({ fullPage: false, path: 'test/playwright/screenshots/' + browserName + '/' + exampleFilePath + '.png' }) 23 | } 24 | }, 25 | async afterAll () { 26 | return await global.browser.close() 27 | } 28 | } 29 | --------------------------------------------------------------------------------