├── .babelrc ├── preview.gif ├── docs ├── preview.gif ├── image │ ├── search.png │ ├── esdoc-logo-mini.png │ ├── esdoc-logo-mini-black.png │ ├── badge.svg │ └── manual-badge.svg ├── script │ ├── patch-for-local.js │ ├── manual.js │ ├── pretty-print.js │ ├── inherited-summary.js │ ├── inner-link.js │ └── test-summary.js ├── css │ ├── identifiers.css │ ├── source.css │ ├── test.css │ ├── github.css │ ├── search.css │ ├── prettify-tomorrow.css │ └── manual.css ├── badge.svg └── lint.json ├── config ├── rollup.config.build.js ├── rollup.config.dev.js └── rollup.config.js ├── test ├── nightwatch │ ├── commands │ │ ├── listenEditor.js │ │ ├── waitForIdle.js │ │ ├── getProperty.js │ │ ├── getNumberOfSpans.js │ │ ├── getJiixExports.js │ │ ├── setProperty.js │ │ ├── waitUntilElementPropertyEqual.js │ │ ├── playStrokes.js │ │ └── waitUntil.js │ ├── full │ │ ├── 01-rest │ │ │ ├── 03-shape-rest-cdkv3.js │ │ │ ├── 04-music-rest-cdkv3.js │ │ │ ├── 05-analyzer-rest-cdkv3.js │ │ │ ├── 06-rawContent-rest-full-cdkv4.js │ │ │ ├── 02-text-rest-full-cdkv3.js │ │ │ └── 01-math-rest-full-cdkv3.js │ │ ├── 02-websocket │ │ │ ├── 03-math-ws-full-cdkv4.js │ │ │ ├── 02-text-ws-full-cdkv3.js │ │ │ └── 01-math-ws-full-cdkv3.js │ │ └── 00-env │ │ │ └── 00-websocket-test.js │ ├── partial │ │ ├── 01-rest-v3 │ │ │ ├── 03-shape-rest-cdkv3.js │ │ │ ├── 04-music-rest-cdkv3.js │ │ │ ├── 05-analyzer-rest-cdkv3.js │ │ │ ├── 02-text-rest-cdkv3.js │ │ │ └── 01-math-rest-cdkv3.js │ │ ├── 03-iink │ │ │ ├── 05-textRAB-iink-ws-cdkv4.js │ │ │ ├── 04-mathRAB-iink-ws-cdkv4.js │ │ │ ├── 03-text-iink-rest-cdkv4.js │ │ │ ├── 02-text-iink-ws-cdkv4.js │ │ │ └── 01-math-iink-ws-cdkv4.js │ │ └── 02-websocket-v3 │ │ │ ├── 02-text-ws-cdkv3.js │ │ │ └── 01-math-ws-cdkv3.js │ └── local-configuration.json ├── mocha │ └── partial │ │ ├── CryptoHelper.spec.babel.js │ │ ├── 00-configuration │ │ ├── LoggerConfig.spec.babel.js │ │ ├── Constants.spec.babel.js │ │ ├── DefaultConfiguration.spec.babel.js │ │ └── DefaultBehaviors.spec.babel.js │ │ ├── 02-behaviors │ │ └── PointerEventGrabber.spec.babel.js │ │ ├── ModelStats.spec.babel.js │ │ └── 01-model │ │ ├── UndoRedoManager.spec.babel.js │ │ ├── StrokeComponent.spec.babel.js │ │ └── InkModel.spec.babel.js └── lib │ └── inks │ ├── one.json │ ├── hello.json │ ├── rabText.json │ ├── equation.json │ └── equation3.json ├── .gitignore ├── index.html ├── docker └── examples │ ├── createIndexFile.sh │ ├── Dockerfile │ └── nginx.conf ├── .esdoc.json ├── assets ├── vue.svg ├── angular.svg └── react.svg ├── src ├── renderer │ ├── svg │ │ └── symbols │ │ │ └── StrokeSymbolSVGRenderer.js │ ├── canvas │ │ ├── symbols │ │ │ ├── StrokeSymbolCanvasRenderer.js │ │ │ ├── MathSymbolCanvasRenderer.js │ │ │ └── TextSymbolCanvasRenderer.js │ │ └── ImageRenderer.js │ └── QuadraticUtils.js ├── util │ ├── PromiseHelper.js │ └── ModelStats.js ├── callback │ └── EventCallback.js ├── recognizer │ ├── CryptoHelper.js │ ├── common │ │ ├── v3 │ │ │ ├── Cdkv3CommonTextRecognizer.js │ │ │ ├── Cdkv3CommonMathRecognizer.js │ │ │ └── Cdkv3CommonShapeRecognizer.js │ │ └── CdkCommonUtil.js │ └── rest │ │ └── v3 │ │ ├── Cdkv3RestRecognizerUtil.js │ │ ├── Cdkv3RestTextRecognizer.js │ │ └── Cdkv3RestMathRecognizer.js ├── myscript.js ├── model │ └── UndoRedoContext.js ├── EditorFacade.js ├── configuration │ ├── DefaultPenStyle.js │ ├── LoggerConfig.js │ ├── languagesV3.json │ ├── languages.json │ ├── DefaultTheme.js │ └── Constants.js └── eastereggs │ └── InkImporter.js ├── CONTRIBUTING.md ├── LICENSE ├── .editorconfig ├── .eslintrc.json ├── examples ├── assets │ └── img │ │ ├── redo.svg │ │ ├── undo.svg │ │ ├── plus.svg │ │ ├── edit.svg │ │ ├── clear.svg │ │ └── document.svg ├── non-version-specific │ ├── handle_errors.html │ └── change_language.html ├── v4 │ ├── multiple_inputs.html │ ├── rest │ │ └── diagram.css │ ├── custom_resources_text.html │ ├── import_content.html │ ├── custom_lexicon_text.html │ ├── websocket_text_iink.html │ ├── websocket_text_iink_no_guides.html │ ├── websocket_text_iink_decoration.html │ └── pointer_events.html ├── experimental │ ├── rest_analyzer_cdk32.html │ ├── rest_shape.html │ └── rest_music_cdk32.html └── v3 │ ├── rest_math.html │ └── websocket_math.html ├── dev └── AIRBNB.xml ├── SETUP.md ├── Jenkinsfile ├── package.json └── Makefile.inc /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env" 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/MyScriptJS/HEAD/preview.gif -------------------------------------------------------------------------------- /docs/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/MyScriptJS/HEAD/docs/preview.gif -------------------------------------------------------------------------------- /docs/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/MyScriptJS/HEAD/docs/image/search.png -------------------------------------------------------------------------------- /config/rollup.config.build.js: -------------------------------------------------------------------------------- 1 | import config from './rollup.config'; 2 | 3 | export default config; 4 | -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/MyScriptJS/HEAD/docs/image/esdoc-logo-mini.png -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyScript/MyScriptJS/HEAD/docs/image/esdoc-logo-mini-black.png -------------------------------------------------------------------------------- /test/nightwatch/commands/listenEditor.js: -------------------------------------------------------------------------------- 1 | exports.command = function listenEditor(done) { 2 | this.injectScript('/examples/dev/tests/editorSupervisor.js', 'customScript', done); 3 | }; 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /docs/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /docker/examples/createIndexFile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo " 6 | 7 | MyScript JS examples 8 | 9 | 10 |

Examples[view]

11 | 12 | 13 | " > /usr/share/nginx/html/index.html 14 | -------------------------------------------------------------------------------- /.esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "plugins": [ 5 | { 6 | "name": "esdoc-standard-plugin", 7 | "option": { 8 | "manual": { 9 | "index": "./CHANGELOG.md", 10 | "asset": "", 11 | "files": [] 12 | } 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | stroker.drawStroke(context, stroke); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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) { 9 | stroker.drawStroke(context, stroke); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/nightwatch/commands/waitForIdle.js: -------------------------------------------------------------------------------- 1 | exports.command = function waitForIdle(element, timeout, callback) { 2 | function waitForEditorIdle(client, done) { 3 | client 4 | .click('#waitForIdle') 5 | .waitUntilElementPropertyEqual(element, 'idle', true, timeout, done); 6 | } 7 | 8 | this.perform(waitForEditorIdle); 9 | 10 | if (typeof callback === 'function') { 11 | callback.call(this); 12 | } 13 | return this; 14 | }; 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/angular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/script/manual.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/); 3 | if (!matched) return; 4 | 5 | var currentName = matched[1]; 6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]'; 7 | var styleText = cssClass + '{ display: block; }\n'; 8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }'; 9 | var style = document.createElement('style'); 10 | style.textContent = styleText; 11 | document.querySelector('head').appendChild(style); 12 | })(); 13 | -------------------------------------------------------------------------------- /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/nightwatch/full/01-rest/03-shape-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('SHAPE', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 6 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.SEGMENTS, '#editor', '[data-key=SEGMENTS]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .forEach(ink => runInkTests(ink)); 12 | -------------------------------------------------------------------------------- /test/nightwatch/full/01-rest/04-music-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MUSIC', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 6 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.MUSICXML, '#editor', '[data-key=MUSICXML]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .forEach(ink => runInkTests(ink)); 12 | -------------------------------------------------------------------------------- /test/nightwatch/full/01-rest/05-analyzer-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('ANALYZER', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 6 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.ANALYSIS, '#editor', '[data-key=ANALYSIS]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .forEach(ink => runInkTests(ink)); 12 | -------------------------------------------------------------------------------- /test/nightwatch/commands/getProperty.js: -------------------------------------------------------------------------------- 1 | exports.command = function getProperty(element, property, callback) { 2 | const self = this; 3 | 4 | function getElementProperty(el, prop) { 5 | // eslint-disable-next-line no-undef 6 | return document.querySelector(el)[prop]; 7 | } 8 | 9 | function getElementPropertyCallback(res) { 10 | if (typeof callback === 'function') { 11 | callback.call(self, res); 12 | } 13 | } 14 | 15 | this.execute(getElementProperty, [element, property], getElementPropertyCallback); 16 | return this; 17 | }; 18 | -------------------------------------------------------------------------------- /test/nightwatch/commands/getNumberOfSpans.js: -------------------------------------------------------------------------------- 1 | exports.command = function getNumberOfSpans(element, callback) { 2 | const self = this; 3 | 4 | function getNumberOfSpansFunc(el) { 5 | // eslint-disable-next-line no-undef 6 | return document.querySelector(el).getElementsByTagName('span').length; 7 | } 8 | 9 | function getNumberOfSpansCallback(res) { 10 | if (typeof callback === 'function') { 11 | callback.call(self, res); 12 | } 13 | } 14 | 15 | this.execute(getNumberOfSpansFunc, [element], getNumberOfSpansCallback); 16 | return this; 17 | }; 18 | -------------------------------------------------------------------------------- /src/util/PromiseHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} DestructuredPromise 3 | * @property {Promise} promise 4 | * @property {function(value: Object)} resolve 5 | * @property {function(reason: Object)} reject 6 | */ 7 | 8 | /** 9 | * @return {DestructuredPromise} 10 | */ 11 | export function destructurePromise() { 12 | let resolve; 13 | let reject; 14 | const initPromise = new Promise( 15 | (resolveParam, rejectParam) => { 16 | resolve = resolveParam; 17 | reject = rejectParam; 18 | }); 19 | return { promise: initPromise, resolve, reject }; 20 | } 21 | -------------------------------------------------------------------------------- /test/nightwatch/partial/01-rest-v3/03-shape-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('SHAPE', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 6 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.SEGMENTS, '#editor', '[data-key=SEGMENTS]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .filter(ink => ['shape'].includes(ink.name)) 12 | .forEach(ink => runInkTests(ink)); 13 | -------------------------------------------------------------------------------- /test/nightwatch/partial/01-rest-v3/04-music-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MUSIC', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 6 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.MUSICXML, '#editor', '[data-key=MUSICXML]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .filter(ink => ['music'].includes(ink.name)) 12 | .forEach(ink => runInkTests(ink)); 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We gladly welcome pull requests to MyScriptJS. 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. -------------------------------------------------------------------------------- /src/callback/EventCallback.js: -------------------------------------------------------------------------------- 1 | import { callbackLogger 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 eventCallback(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 | -------------------------------------------------------------------------------- /test/nightwatch/commands/getJiixExports.js: -------------------------------------------------------------------------------- 1 | exports.command = function getJiixExports(component, callback) { 2 | const self = this; 3 | 4 | function getElementProperty(comp) { 5 | // eslint-disable-next-line no-undef 6 | return document.querySelector(comp).editor.model.exports['application/vnd.myscript.jiix']; 7 | } 8 | 9 | function getElementPropertyCallback(res) { 10 | if (typeof callback === 'function') { 11 | callback.call(self, res); 12 | } 13 | } 14 | 15 | this.execute(getElementProperty, [component], getElementPropertyCallback); 16 | return this; 17 | }; 18 | -------------------------------------------------------------------------------- /test/nightwatch/partial/01-rest-v3/05-analyzer-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('ANALYZER', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 6 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.ANALYSIS, '#editor', '[data-key=ANALYSIS]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .filter(ink => ['fourSquare'].includes(ink.name)) 12 | .forEach(ink => runInkTests(ink)); 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/nightwatch/commands/setProperty.js: -------------------------------------------------------------------------------- 1 | exports.command = function setProperty(element, property, value, callback) { 2 | const self = this; 3 | 4 | function setElementProperty(el, prop, val) { 5 | // eslint-disable-next-line no-undef 6 | document.querySelector(el)[prop] = val; 7 | return val; 8 | } 9 | 10 | function setElementPropertyCallback(result) { 11 | if (typeof callback === 'function') { 12 | callback.call(self, result); 13 | } 14 | } 15 | 16 | this.execute(setElementProperty, [element, property, value], setElementPropertyCallback); 17 | return this; 18 | }; 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/nightwatch/full/01-rest/06-rawContent-rest-full-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('Raw Content', 'REST', 'V4'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkRawContent ' + ink.name] = function checkRawContent(browser) { 6 | inkPlayer.checkRawContent(browser, config, ink.strokes, '#editor', '[data-key="application/vnd.myscript.jiix"]'); 7 | }; 8 | }; 9 | 10 | config.inks 11 | //.filter(ink => ['fourSquare'].includes(ink.name)) 12 | .forEach(ink => runInkTests(ink)); 13 | -------------------------------------------------------------------------------- /test/nightwatch/commands/waitUntilElementPropertyEqual.js: -------------------------------------------------------------------------------- 1 | exports.command = function waitUntilPropertyEqual(element, property, expected, timeout, callback) { 2 | const browser = this; 3 | 4 | function getActualValue(done) { 5 | browser.getProperty(element, property, (value) => { 6 | done(value); 7 | }); 8 | } 9 | 10 | function predicate(actual) { 11 | return (expected === actual); 12 | } 13 | 14 | return browser.waitUntil(getActualValue, predicate, timeout, (result) => { 15 | if (typeof callback === 'function') { 16 | callback.call(browser, result); 17 | } 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /test/nightwatch/full/02-websocket/03-math-ws-full-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'WEBSOCKET', 'V4'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkAlwaysConnected ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkAlwaysConnected(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 7 | } 8 | } 9 | 10 | config.inks 11 | .filter(ink => ['equation3'].includes(ink.name)) 12 | .forEach(ink => runInkTests(ink)); 13 | -------------------------------------------------------------------------------- /test/nightwatch/partial/03-iink/05-textRAB-iink-ws-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'WEBSOCKET', 'V4', '', 'RAB'); 3 | 4 | function runRecognitionAssetBuilderTest(ink) { 5 | module.exports[config.header + ' CheckRecognitionAssetBuilder ' + ink.name] = function CheckRecognitionAssetBuilder(browser) { 6 | inkPlayer.checkRecognitionAssetBuilder(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .filter(ink => ['rabText'].includes(ink.name)) 12 | .forEach(ink => runRecognitionAssetBuilderTest(ink)) 13 | -------------------------------------------------------------------------------- /test/nightwatch/partial/03-iink/04-mathRAB-iink-ws-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'WEBSOCKET', 'V4', '', 'RAB'); 3 | 4 | function runRecognitionAssetBuilderTest(ink) { 5 | module.exports[config.header + ' CheckRecognitionAssetBuilder ' + ink.name] = function CheckRecognitionAssetBuilder(browser) { 6 | inkPlayer.checkRecognitionAssetBuilder(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 7 | }; 8 | } 9 | 10 | config.inks 11 | .filter(ink => ['3times2'].includes(ink.name)) 12 | .forEach(ink => runRecognitionAssetBuilderTest(ink)) 13 | -------------------------------------------------------------------------------- /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/nightwatch/full/01-rest/02-text-rest-full-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 7 | }; 8 | 9 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 10 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 11 | }; 12 | } 13 | 14 | config.inks 15 | .forEach(ink => runInkTests(ink)); 16 | -------------------------------------------------------------------------------- /test/nightwatch/full/02-websocket/02-text-ws-full-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'WEBSOCKET', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 7 | }; 8 | 9 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 10 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 11 | }; 12 | } 13 | 14 | config.inks 15 | .forEach(ink => runInkTests(ink)); 16 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/nightwatch/partial/01-rest-v3/02-text-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'REST', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 7 | }; 8 | 9 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 10 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 11 | }; 12 | } 13 | 14 | config.inks 15 | .filter(ink => ['hello'].includes(ink.name)) 16 | .forEach(ink => runInkTests(ink)); 17 | -------------------------------------------------------------------------------- /test/nightwatch/partial/02-websocket-v3/02-text-ws-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'WEBSOCKET', 'V3'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 7 | }; 8 | 9 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 10 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key=TEXT]'); 11 | }; 12 | } 13 | 14 | config.inks 15 | .filter(ink => ['hello'].includes(ink.name)) 16 | .forEach(ink => runInkTests(ink)); 17 | -------------------------------------------------------------------------------- /docs/css/identifiers.css: -------------------------------------------------------------------------------- 1 | .identifiers-wrap { 2 | display: flex; 3 | align-items: flex-start; 4 | } 5 | 6 | .identifier-dir-tree { 7 | background: #fff; 8 | border: solid 1px #ddd; 9 | border-radius: 0.25em; 10 | top: 52px; 11 | position: -webkit-sticky; 12 | position: sticky; 13 | max-height: calc(100vh - 155px); 14 | overflow-y: scroll; 15 | min-width: 200px; 16 | margin-left: 1em; 17 | } 18 | 19 | .identifier-dir-tree-header { 20 | padding: 0.5em; 21 | background-color: #fafafa; 22 | border-bottom: solid 1px #ddd; 23 | } 24 | 25 | .identifier-dir-tree-content { 26 | padding: 0 0.5em 0; 27 | } 28 | 29 | .identifier-dir-tree-content > div { 30 | padding-top: 0.25em; 31 | padding-bottom: 0.25em; 32 | } 33 | 34 | .identifier-dir-tree-content a { 35 | color: inherit; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /test/nightwatch/partial/03-iink/03-text-iink-rest-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'REST', 'V4'); 3 | 4 | function runInkTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 7 | }; 8 | 9 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 10 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 11 | }; 12 | } 13 | 14 | config.inks 15 | .filter(ink => ['hellov4rest'].includes(ink.name)) 16 | .forEach(ink => runInkTests(ink)); 17 | -------------------------------------------------------------------------------- /src/recognizer/common/v3/Cdkv3CommonTextRecognizer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extract the exports 3 | * @param {Model} model Current model 4 | * @return {Object} exports 5 | */ 6 | export function extractExports(model) { 7 | // We recopy the recognized strokes to flag them as toBeRemove if they are scratched out or map with a symbol 8 | if (model.rawResults && 9 | model.rawResults.exports && 10 | model.rawResults.exports.result && 11 | model.rawResults.exports.result.textSegmentResult && 12 | model.rawResults.exports.result.textSegmentResult.candidates) { 13 | return { 14 | CANDIDATES: model.rawResults.exports.result, 15 | TEXT: model.rawResults.exports.result.textSegmentResult.candidates[model.rawResults.exports.result.textSegmentResult.selectedCandidateIdx].label 16 | }; 17 | } 18 | return {}; 19 | } 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "ecmaVersion": 2017, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true 8 | } 9 | }, 10 | "rules": { 11 | "semi": 2, 12 | "no-unused-vars" : "off", 13 | "comma-dangle" : "off", 14 | "no-plusplus" : "off", 15 | "no-restricted-properties" : "off", 16 | "prefer-template" : "off", 17 | "import/no-extraneous-dependencies" : "off", 18 | "import/prefer-default-export" : "off", 19 | "max-len": ["off", 100, 2, { 20 | "ignoreUrls": true, 21 | "ignoreComments": false, 22 | "ignoreStrings": true, 23 | "ignoreTemplateLiterals": true 24 | }] 25 | }, 26 | "globals": { 27 | "document": true, 28 | "window": true, 29 | "Blob": true, 30 | "FileReader": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | 71% 15 | 71% 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/assets/img/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /dev/AIRBNB.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /docs/image/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | @ratio@ 15 | @ratio@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/manual-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | manual 13 | manual 14 | @value@ 15 | @value@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/assets/img/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/script/pretty-print.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | prettyPrint(); 3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]'); 4 | for (var i = 0; i < lines.length; i++) { 5 | lines[i].id = 'lineNumber' + (i + 1); 6 | } 7 | 8 | var matched = location.hash.match(/errorLines=([\d,]+)/); 9 | if (matched) { 10 | var lines = matched[1].split(','); 11 | for (var i = 0; i < lines.length; i++) { 12 | var id = '#lineNumber' + lines[i]; 13 | var el = document.querySelector(id); 14 | el.classList.add('error-line'); 15 | } 16 | return; 17 | } 18 | 19 | if (location.hash) { 20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 22 | var line = document.querySelector(id); 23 | if (line) line.classList.add('active'); 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /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 | it('Test event registration', () => { 9 | const spiedEditor = { pointerUp: sinon.spy(), configuration: { capture: true } }; 10 | const spiedDomDocument = { addEventListener: sinon.spy() }; 11 | logger.debug('Attaching document to spied element'); 12 | grabber.attach(spiedDomDocument, spiedEditor); 13 | 14 | assert.strictEqual(spiedDomDocument.addEventListener.callCount, 6, 'Not all events have been registered'); 15 | }); 16 | 17 | // TODO Add some tests sending events and checking that grabber behave as expected 18 | }); 19 | -------------------------------------------------------------------------------- /test/nightwatch/full/01-rest/01-math-rest-full-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'REST', 'V3'); 3 | 4 | function runLabelsTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 7 | }; 8 | } 9 | 10 | function runUndoTests(ink) { 11 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 12 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 13 | }; 14 | } 15 | 16 | // config.inks 17 | // .forEach(ink => runLabelsTests(ink)); 18 | 19 | config.inks 20 | .filter(ink => ['one', 'equation3, 3times2'].includes(ink.name)) 21 | .forEach(ink => runUndoTests(ink)); 22 | -------------------------------------------------------------------------------- /src/recognizer/common/CdkCommonUtil.js: -------------------------------------------------------------------------------- 1 | import { recognizerLogger as logger } from '../../configuration/LoggerConfig'; 2 | 3 | /** 4 | * Extract the exports 5 | * @param {Model} model Current model 6 | * @return {Object} Recognition result 7 | */ 8 | export function extractExports(model) { 9 | if (model.rawResults && 10 | model.rawResults.exports && 11 | model.rawResults.exports.result && 12 | model.rawResults.exports.result.results && 13 | model.rawResults.exports.result.results.length > 0) { 14 | return model.rawResults.exports.result.results 15 | .map((item) => { 16 | const res = {}; 17 | if (Object.keys(item).includes('root')) { 18 | res[`${item.type}`] = item.root; 19 | } else { 20 | res[`${item.type}`] = item.value; 21 | } 22 | return res; 23 | }) 24 | .reduce((a, b) => Object.assign(a, b), {}); 25 | } 26 | return {}; 27 | } 28 | -------------------------------------------------------------------------------- /test/nightwatch/full/02-websocket/01-math-ws-full-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'WEBSOCKET', 'V3'); 3 | 4 | function runLabelsTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 7 | }; 8 | } 9 | 10 | function runUndoTests(ink) { 11 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 12 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 13 | }; 14 | } 15 | 16 | config.inks 17 | .forEach(ink => runLabelsTests(ink)); 18 | 19 | config.inks 20 | .filter(ink => ['one', 'equation3, 3times2'].includes(ink.name)) 21 | .forEach(ink => runUndoTests(ink)); 22 | -------------------------------------------------------------------------------- /docs/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /test/nightwatch/partial/01-rest-v3/01-math-rest-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'REST', 'V3'); 3 | 4 | function runLabelTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 7 | }; 8 | } 9 | 10 | function runUndoTests(ink) { 11 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 12 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 13 | }; 14 | } 15 | 16 | config.inks 17 | .filter(ink => ['equation'].includes(ink.name)) 18 | .forEach(ink => runLabelTests(ink)); 19 | 20 | config.inks 21 | .filter(ink => ['one', 'equation3'].includes(ink.name)) 22 | .forEach(ink => runUndoTests(ink)); 23 | -------------------------------------------------------------------------------- /test/nightwatch/full/00-env/00-websocket-test.js: -------------------------------------------------------------------------------- 1 | // const config = require('../../../lib/configuration').getConfiguration('TEXT', 'WEBSOCKET', 'V3'); 2 | // 3 | // module.exports[config.header + ' long time duration test'] = function simple(browser) { 4 | // // let cont = true; 5 | // const start = new Date().getTime(); 6 | // const strokes = config.inks[0].strokes; 7 | // const labels = config.inks[0].labels; 8 | // 9 | // console.log('start date = ' + start); 10 | // browser 11 | // .init(browser.launchUrl + config.componentPath) 12 | // .waitForElementVisible('#editor', 1000 * config.timeoutAmplificator) 13 | // .listenEditor() 14 | // .waitForElementPresent('#editorSupervisor', 1000 * config.timeoutAmplificator) 15 | // .waitUntilPlayInkThenClear(config, start, strokes, labels, 20000, null); 16 | // // .perform(checkLabelsThenClear) 17 | // // .getElapsedTime(start, stopCondition) 18 | // // .log(start); 19 | // browser.end(); 20 | // }; 21 | -------------------------------------------------------------------------------- /test/nightwatch/partial/02-websocket-v3/01-math-ws-cdkv3.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'WEBSOCKET', 'V3'); 3 | 4 | function runLabelTests(ink) { 5 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 6 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 7 | }; 8 | } 9 | 10 | function runUndoTests(ink) { 11 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 12 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key=LATEX]'); 13 | }; 14 | } 15 | 16 | config.inks 17 | .filter(ink => ['equation'].includes(ink.name)) 18 | .forEach(ink => runLabelTests(ink)); 19 | 20 | config.inks 21 | .filter(ink => ['one', 'equation3'].includes(ink.name)) 22 | .forEach(ink => runUndoTests(ink)); 23 | -------------------------------------------------------------------------------- /test/mocha/partial/ModelStats.spec.babel.js: -------------------------------------------------------------------------------- 1 | import { 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 | import * as ModelStats from '../../../src/util/ModelStats'; 6 | 7 | describe('Testing InkModel Stats', () => { 8 | it('Testing generated stats', () => { 9 | const initialModel = InkModel.createModel(); 10 | const updatedModel1 = InkModel.initPendingStroke(initialModel, { x: 1, y: 1 }); 11 | const updatedModel2 = InkModel.appendToPendingStroke(updatedModel1, { x: 2, y: 2 }); 12 | const updatedModel3 = InkModel.appendToPendingStroke(updatedModel2, { x: 3, y: 3 }); 13 | const updatedModel4 = InkModel.endPendingStroke(updatedModel3, { x: 4, y: 4 }); 14 | logger.debug('Last model stats are ', ModelStats.computeStats(updatedModel4)); 15 | assert.deepEqual(initialModel, updatedModel4); 16 | }); 17 | 18 | 19 | // TODO Test all other function 20 | }); 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/recognizer/common/v3/Cdkv3CommonMathRecognizer.js: -------------------------------------------------------------------------------- 1 | import { recognizerLogger as logger } from '../../../configuration/LoggerConfig'; 2 | 3 | /** 4 | * Extract the recognized symbols 5 | * @param {Model} model Current model 6 | * @return {Array} Recognized symbols 7 | */ 8 | export function extractRecognizedSymbols(model) { 9 | // We recopy the recognized strokes to flag them as toBeRemove if they are scratched out or map with a symbol 10 | const strokeList = [...model.rawStrokes]; 11 | 12 | if (model.rawResults && 13 | model.rawResults.exports && 14 | model.rawResults.exports.result && 15 | model.rawResults.exports.result.scratchOutResults && 16 | (model.rawResults.exports.result.scratchOutResults.length > 0)) { 17 | const inkRanges = model.rawResults.exports.result.scratchOutResults 18 | .map(scratchOutResult => scratchOutResult.erasedInkRanges.concat(scratchOutResult.inkRanges)) 19 | .reduce((a, b) => a.concat(b)); 20 | return strokeList.filter((stroke, index) => !inkRanges.find(inkRange => inkRange.component === index)); 21 | } 22 | return strokeList; 23 | } 24 | -------------------------------------------------------------------------------- /docs/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | var matched = location.hash.match(/errorLines=([\d,]+)/); 5 | if (matched) return; 6 | 7 | function adjust() { 8 | window.scrollBy(0, -55); 9 | var el = document.querySelector('.inner-link-active'); 10 | if (el) el.classList.remove('inner-link-active'); 11 | 12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 14 | var el = document.querySelector(id); 15 | if (el) el.classList.add('inner-link-active'); 16 | } 17 | 18 | window.addEventListener('hashchange', adjust); 19 | 20 | if (location.hash) { 21 | setTimeout(adjust, 0); 22 | } 23 | })(); 24 | 25 | (function(){ 26 | var els = document.querySelectorAll('[href^="#"]'); 27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash 28 | for (var i = 0; i < els.length; i++) { 29 | var el = els[i]; 30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /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', 'SHAPE', 'MUSIC', 'ANALYZER']; 10 | recognitionTypes.forEach((recognitionType) => { 11 | it(`Should have ${recognitionType} recognition type declared`, () => { 12 | expect(Constants.RecognitionType[recognitionType]).to.equal(recognitionType); 13 | }); 14 | }); 15 | 16 | const protocols = ['REST', 'WEBSOCKET']; 17 | protocols.forEach((protocol) => { 18 | it(`Should have ${protocol} protocol declared`, () => { 19 | expect(Constants.Protocol[protocol]).to.equal(protocol); 20 | }); 21 | }); 22 | 23 | const triggers = ['QUIET_PERIOD', 'POINTER_UP', 'DEMAND']; 24 | triggers.forEach((trigger) => { 25 | it(`Should have ${trigger} trigger declared`, () => { 26 | expect(Constants.Trigger[trigger]).to.equal(trigger); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /docs/css/source.css: -------------------------------------------------------------------------------- 1 | table.files-summary { 2 | width: 100%; 3 | margin: 10px 0; 4 | border-spacing: 0; 5 | border: 0; 6 | border-collapse: collapse; 7 | text-align: right; 8 | } 9 | 10 | table.files-summary tbody tr:hover { 11 | background: #eee; 12 | } 13 | 14 | table.files-summary td:first-child, 15 | table.files-summary td:nth-of-type(2) { 16 | text-align: left; 17 | } 18 | 19 | table.files-summary[data-use-coverage="false"] td.coverage { 20 | display: none; 21 | } 22 | 23 | table.files-summary thead { 24 | background: #fafafa; 25 | } 26 | 27 | table.files-summary td { 28 | border: solid 1px #ddd; 29 | padding: 4px 10px; 30 | vertical-align: top; 31 | } 32 | 33 | table.files-summary td.identifiers > span { 34 | display: block; 35 | margin-top: 4px; 36 | } 37 | table.files-summary td.identifiers > span:first-child { 38 | margin-top: 0; 39 | } 40 | 41 | table.files-summary .coverage-count { 42 | font-size: 12px; 43 | color: #aaa; 44 | display: inline-block; 45 | min-width: 40px; 46 | } 47 | 48 | .total-coverage-count { 49 | position: relative; 50 | bottom: 2px; 51 | font-size: 12px; 52 | color: #666; 53 | font-weight: 500; 54 | padding-left: 5px; 55 | } 56 | -------------------------------------------------------------------------------- /docs/css/test.css: -------------------------------------------------------------------------------- 1 | table.test-summary thead { 2 | background: #fafafa; 3 | } 4 | 5 | table.test-summary thead .test-description { 6 | width: 50%; 7 | } 8 | 9 | table.test-summary { 10 | width: 100%; 11 | margin: 10px 0; 12 | border-spacing: 0; 13 | border: 0; 14 | border-collapse: collapse; 15 | } 16 | 17 | table.test-summary thead .test-count { 18 | width: 3em; 19 | } 20 | 21 | table.test-summary tbody tr:hover { 22 | background-color: #eee; 23 | } 24 | 25 | table.test-summary td { 26 | border: solid 1px #ddd; 27 | padding: 4px 10px; 28 | vertical-align: top; 29 | } 30 | 31 | table.test-summary td p { 32 | margin: 0; 33 | } 34 | 35 | table.test-summary tr.test-interface .toggle { 36 | display: inline-block; 37 | float: left; 38 | margin-right: 4px; 39 | cursor: pointer; 40 | font-size: 0.8em; 41 | padding-top: 0.25em; 42 | } 43 | 44 | table.test-summary tr.test-interface .toggle.opened:before { 45 | content: '▼'; 46 | } 47 | 48 | table.test-summary tr.test-interface .toggle.closed:before { 49 | content: '▶'; 50 | } 51 | 52 | table.test-summary .test-target > span { 53 | display: block; 54 | margin-top: 4px; 55 | } 56 | table.test-summary .test-target > span:first-child { 57 | margin-top: 0; 58 | } 59 | -------------------------------------------------------------------------------- /config/rollup.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable object-shorthand */ 2 | import json from 'rollup-plugin-json'; 3 | import resolve from 'rollup-plugin-node-resolve'; 4 | import commonjs from 'rollup-plugin-commonjs'; 5 | import babel from 'rollup-plugin-babel'; 6 | import uglify from 'rollup-plugin-uglify'; 7 | 8 | const plugins = [ 9 | json(), 10 | resolve(), 11 | commonjs({ 12 | namedExports: { 13 | 'node_modules/loglevel/lib/loglevel.js': ['noConflict'], 14 | } 15 | }), 16 | babel({ 17 | exclude: 'node_modules/**', 18 | babelrc: false, 19 | presets: [ 20 | ['env', { modules: false }] 21 | ], 22 | plugins: [ 23 | 'external-helpers' 24 | ] 25 | }) 26 | ]; 27 | 28 | const pluginsWithUglify = [...plugins]; 29 | pluginsWithUglify.push(uglify()); 30 | 31 | export default [{ 32 | input: 'src/myscript.js', 33 | output: [ 34 | { 35 | name: 'MyScript', 36 | file: 'dist/myscript.min.js', 37 | format: 'umd', 38 | exports: 'named' 39 | } 40 | ], 41 | plugins: pluginsWithUglify 42 | }, { 43 | input: 'src/myscript.js', 44 | output: [ 45 | { 46 | file: 'dist/myscript.esm.js', 47 | format: 'es' 48 | } 49 | ], 50 | plugins: plugins 51 | }]; 52 | 53 | -------------------------------------------------------------------------------- /src/myscript.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 MyScript = { 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 | MyScript 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 | 47 | -------------------------------------------------------------------------------- /test/nightwatch/commands/playStrokes.js: -------------------------------------------------------------------------------- 1 | exports.command = function playStrokes(element, strokes, offsetX, offsetY, timeout, callback) { 2 | const offsetXRef = offsetX || 0; 3 | const offsetYRef = offsetY || 0; 4 | const timeoutRef = timeout || 3000; 5 | 6 | function playStroke(stroke, client) { 7 | if (stroke[0].length === 1 && stroke[0][0] === stroke[1][0]) { 8 | if (stroke[0][0] === -1) { 9 | client.click('#undo'); 10 | } else if (stroke[0][0] === 1) { 11 | client.click('#redo'); 12 | } else if (stroke[0][0] === 0) { 13 | client.click('#clear'); 14 | } 15 | } else { 16 | client.moveToElement(element, offsetXRef + stroke[0][0], offsetYRef + stroke[1][0]); 17 | client.mouseButtonDown('left'); 18 | for (let p = 0; p < stroke[0].length; p++) { 19 | client.moveToElement(element, offsetXRef + stroke[0][p], offsetYRef + stroke[1][p]); 20 | } 21 | client.mouseButtonUp('left') 22 | .pause(100); 23 | } 24 | } 25 | 26 | function playStrokesFunction(client, done) { 27 | strokes.forEach(stroke => playStroke(stroke, client)); 28 | client.waitForIdle('#editorSupervisor', timeoutRef, done); 29 | } 30 | 31 | this.perform(playStrokesFunction); 32 | 33 | if (typeof callback === 'function') { 34 | callback.call(this); 35 | } 36 | return this; 37 | }; 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/model/UndoRedoContext.js: -------------------------------------------------------------------------------- 1 | import { modelLogger as logger } from '../configuration/LoggerConfig'; 2 | 3 | /** 4 | * Undo/redo context 5 | * @typedef {Object} UndoRedoContext 6 | * @property {Array} stack=[] List of processed models. 7 | * @property {Number} currentPosition=-1 Current model index into the stack. 8 | * @property {Number} maxSize Max size of the stack. 9 | * @property {Boolean} canUndo=false 10 | * @property {Boolean} canRedo=false 11 | */ 12 | 13 | /** 14 | * Create a new undo/redo context 15 | * @param {Configuration} configuration Current configuration 16 | * @return {UndoRedoContext} New undo/redo context 17 | */ 18 | export function createUndoRedoContext(configuration) { 19 | return { 20 | stack: [], 21 | currentPosition: -1, 22 | maxSize: configuration.undoRedoMaxStackSize, 23 | canUndo: false, 24 | canRedo: false 25 | }; 26 | } 27 | 28 | /** 29 | * Update the undo/redo state 30 | * @param {UndoRedoContext} undoRedoContext Current undo/redo context 31 | * @return {UndoRedoContext} Updated undo/redo context 32 | */ 33 | export function updateUndoRedoState(undoRedoContext) { 34 | const undoRedoContextRef = undoRedoContext; 35 | undoRedoContextRef.canUndo = undoRedoContext.currentPosition > 0; 36 | undoRedoContextRef.canRedo = undoRedoContext.currentPosition < (undoRedoContext.stack.length - 1); 37 | return undoRedoContextRef; 38 | } 39 | -------------------------------------------------------------------------------- /src/EditorFacade.js: -------------------------------------------------------------------------------- 1 | import * as languagesJson from './configuration/languages.json'; 2 | import * as languagesJsonV3 from './configuration/languagesV3.json'; 3 | import { editorLogger as logger } from './configuration/LoggerConfig'; 4 | import { Editor } from './Editor'; 5 | import * as DefaultConfiguration from './configuration/DefaultConfiguration'; 6 | 7 | /** 8 | * Attach an Editor to a DOMElement 9 | * @param {Element} element DOM element to attach an editor 10 | * @param {Configuration} [configuration] Configuration to apply 11 | * @param {PenStyle} [penStyle] Pen style to apply 12 | * @param {Theme} [theme] Theme to apply 13 | * @param {Behaviors} [behaviors] Custom behaviors to apply 14 | * @return {Editor} New editor 15 | */ 16 | export function register(element, configuration, penStyle, theme, behaviors) { 17 | logger.debug('Registering a new editor'); 18 | return new Editor(element, configuration, penStyle, theme, behaviors); 19 | } 20 | 21 | /** 22 | * Return the list of available recognition languages 23 | * @param {Configuration} [configuration] Configuration to get the languages 24 | * @return {JSON} A list of available languages 25 | */ 26 | export function getAvailableLanguageList(configuration) { 27 | const innerConfiguration = DefaultConfiguration.overrideDefaultConfiguration(configuration); 28 | return innerConfiguration.recognitionParams.apiVersion === 'V4' ? languagesJson : languagesJsonV3; 29 | } 30 | -------------------------------------------------------------------------------- /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: Number},{x: Number, 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/recognizer/rest/v3/Cdkv3RestRecognizerUtil.js: -------------------------------------------------------------------------------- 1 | import { recognizerLogger as logger } from '../../../configuration/LoggerConfig'; 2 | import * as NetworkInterface from '../networkInterface'; 3 | import * as InkModel from '../../../model/InkModel'; 4 | import * as RecognizerContext from '../../../model/RecognizerContext'; 5 | 6 | /** 7 | * @param {String} suffixUrl 8 | * @param {RecognizerContext} recognizerContext 9 | * @param {Model} model 10 | * @param {function(recognizerContext: RecognizerContext, model: Model): Object} buildMessage 11 | * @return {Promise.} Promise that return an updated model as a result 12 | */ 13 | export function postMessage(suffixUrl, recognizerContext, model, buildMessage) { 14 | const configuration = recognizerContext.editor.configuration; 15 | return NetworkInterface.post(recognizerContext, `${configuration.recognitionParams.server.scheme}://${configuration.recognitionParams.server.host}${suffixUrl}`, buildMessage(recognizerContext, model), 'V3') 16 | .then( 17 | (response) => { 18 | logger.debug('Cdkv3RestRecognizer success', response); 19 | const positions = recognizerContext.lastPositions; 20 | positions.lastReceivedPosition = positions.lastSentPosition; 21 | const recognizerContextReference = RecognizerContext.updateRecognitionPositions(recognizerContext, positions); 22 | if (response.instanceId) { 23 | recognizerContextReference.instanceId = response.instanceId; 24 | } 25 | return response; 26 | } 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/configuration/DefaultPenStyle.js: -------------------------------------------------------------------------------- 1 | import JsonCSS from 'json-css'; 2 | import assign from 'assign-deep'; 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 = assign({}, 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 | -------------------------------------------------------------------------------- /docs/css/github.css: -------------------------------------------------------------------------------- 1 | /* github markdown */ 2 | .github-markdown { 3 | font-size: 16px; 4 | } 5 | 6 | .github-markdown h1, 7 | .github-markdown h2, 8 | .github-markdown h3, 9 | .github-markdown h4, 10 | .github-markdown h5 { 11 | margin-top: 1em; 12 | margin-bottom: 16px; 13 | font-weight: bold; 14 | padding: 0; 15 | } 16 | 17 | .github-markdown h1:nth-of-type(1) { 18 | margin-top: 0; 19 | } 20 | 21 | .github-markdown h1 { 22 | font-size: 2em; 23 | padding-bottom: 0.3em; 24 | } 25 | 26 | .github-markdown h2 { 27 | font-size: 1.75em; 28 | padding-bottom: 0.3em; 29 | } 30 | 31 | .github-markdown h3 { 32 | font-size: 1.5em; 33 | } 34 | 35 | .github-markdown h4 { 36 | font-size: 1.25em; 37 | } 38 | 39 | .github-markdown h5 { 40 | font-size: 1em; 41 | } 42 | 43 | .github-markdown ul, .github-markdown ol { 44 | padding-left: 2em; 45 | } 46 | 47 | .github-markdown pre > code { 48 | font-size: 0.85em; 49 | } 50 | 51 | .github-markdown table { 52 | margin-bottom: 1em; 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | 57 | .github-markdown table tr { 58 | background-color: #fff; 59 | border-top: 1px solid #ccc; 60 | } 61 | 62 | .github-markdown table th, 63 | .github-markdown table td { 64 | padding: 6px 13px; 65 | border: 1px solid #ddd; 66 | } 67 | 68 | .github-markdown table tr:nth-child(2n) { 69 | background-color: #f8f8f8; 70 | } 71 | 72 | .github-markdown hr { 73 | border-right: 0; 74 | border-bottom: 1px solid #e5e5e5; 75 | border-left: 0; 76 | border-top: 0; 77 | } 78 | 79 | /** badge(.svg) does not have border */ 80 | .github-markdown img:not([src*=".svg"]) { 81 | max-width: 100%; 82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5); 83 | } 84 | -------------------------------------------------------------------------------- /docs/css/search.css: -------------------------------------------------------------------------------- 1 | /* search box */ 2 | .search-box { 3 | position: absolute; 4 | top: 10px; 5 | right: 50px; 6 | padding-right: 8px; 7 | padding-bottom: 10px; 8 | line-height: normal; 9 | font-size: 12px; 10 | } 11 | 12 | .search-box img { 13 | width: 20px; 14 | vertical-align: top; 15 | } 16 | 17 | .search-input { 18 | display: inline; 19 | visibility: hidden; 20 | width: 0; 21 | padding: 2px; 22 | height: 1.5em; 23 | outline: none; 24 | background: transparent; 25 | border: 1px #0af; 26 | border-style: none none solid none; 27 | vertical-align: bottom; 28 | } 29 | 30 | .search-input-edge { 31 | display: none; 32 | width: 1px; 33 | height: 5px; 34 | background-color: #0af; 35 | vertical-align: bottom; 36 | } 37 | 38 | .search-result { 39 | position: absolute; 40 | display: none; 41 | height: 600px; 42 | width: 100%; 43 | padding: 0; 44 | margin-top: 5px; 45 | margin-left: 24px; 46 | background: white; 47 | box-shadow: 1px 1px 4px rgb(0,0,0); 48 | white-space: nowrap; 49 | overflow-y: scroll; 50 | } 51 | 52 | .search-result-import-path { 53 | color: #aaa; 54 | font-size: 12px; 55 | } 56 | 57 | .search-result li { 58 | list-style: none; 59 | padding: 2px 4px; 60 | } 61 | 62 | .search-result li a { 63 | display: block; 64 | } 65 | 66 | .search-result li.selected { 67 | background: #ddd; 68 | } 69 | 70 | .search-result li.search-separator { 71 | background: rgb(37, 138, 175); 72 | color: white; 73 | } 74 | 75 | .search-box.active .search-input { 76 | visibility: visible; 77 | transition: width 0.2s ease-out; 78 | width: 300px; 79 | } 80 | 81 | .search-box.active .search-input-edge { 82 | display: inline-block; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 |
25 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /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 { testLogger as logger } from '../../../../src/configuration/LoggerConfig'; 5 | import * as DefaultConfiguration from '../../../../src/configuration/DefaultConfiguration'; 6 | 7 | configurations.forEach((configuration) => { 8 | describe(`Check configuration for API ${configuration.apiVersion} ${configuration.type} ${configuration.protocol}`, () => { 9 | const currentConfiguration = DefaultConfiguration.overrideDefaultConfiguration({ recognitionParams: configuration }); 10 | 11 | it('type', () => { 12 | assert.isDefined(currentConfiguration.recognitionParams.type, 'type should be defined'); 13 | assert.strictEqual(currentConfiguration.recognitionParams.type, configuration.type, `${currentConfiguration.recognitionParams.type} should be the default value for type`); 14 | }); 15 | 16 | it('protocol', () => { 17 | assert.isDefined(currentConfiguration.recognitionParams.protocol, 'protocol should be defined'); 18 | assert.strictEqual(currentConfiguration.recognitionParams.protocol, configuration.protocol, `${currentConfiguration.recognitionParams.protocol} should be the default value for protocol`); 19 | }); 20 | 21 | it('apiVersion', () => { 22 | assert.isDefined(currentConfiguration.recognitionParams.apiVersion, 'apiVersion should be defined'); 23 | assert.strictEqual(currentConfiguration.recognitionParams.apiVersion, configuration.apiVersion, `${currentConfiguration.recognitionParams.apiVersion} should be the default value for apiVersion`); 24 | }); 25 | 26 | it('server', () => { 27 | assert.isDefined(currentConfiguration.recognitionParams.server, 'recognitionParams.server should keep its default value'); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/nightwatch/partial/03-iink/02-text-iink-ws-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('TEXT', 'WEBSOCKET', 'V4'); 3 | const configDecoration = require('../../../lib/configuration').getConfiguration('TEXT', 'WEBSOCKET', 'V4', '', 'Decoration'); 4 | 5 | function runInkTests(ink) { 6 | // module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 7 | // inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 8 | // }; 9 | // 10 | // module.exports[config.header + ' checkConvert ' + ink.name] = function checkConvert(browser) { 11 | // inkPlayer.checkConvert(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 12 | // }; 13 | 14 | module.exports[config.header + ' checkUndoRedoReconnect ' + ink.name] = function checkUndoRedoReconnect(browser) { 15 | inkPlayer.checkUndoRedoReconnect(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 16 | }; 17 | module.exports[config.header + ' checkSmartGuide ' + ink.name] = function checkSmartGuide(browser) { 18 | inkPlayer.checkSmartGuide(browser, config, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 19 | }; 20 | } 21 | 22 | function runDecorationTest(ink) { 23 | module.exports[configDecoration.header + ' checkDecoration ' + ink.name] = function checkDecoration(browser) { 24 | inkPlayer.checkDecoration(browser, configDecoration, ink.name, ink.strokes, ink.exports.TEXT, '#editor', '[data-key="text/plain"]'); 25 | }; 26 | } 27 | 28 | 29 | config.inks 30 | .filter(ink => ['hello', 'helloHow'].includes(ink.name)) 31 | .forEach(ink => runInkTests(ink)); 32 | 33 | configDecoration.inks 34 | .filter(ink => ink.name.includes('helloHowDeco')) 35 | .forEach(ink => runDecorationTest(ink)); 36 | -------------------------------------------------------------------------------- /test/nightwatch/local-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_folders": [ 3 | "partial" 4 | ], 5 | "output_folder": "results", 6 | "custom_commands_path": "commands/", 7 | "live_output": true, 8 | "test_settings": { 9 | "default": { 10 | "launch_url": "http://localhost:8080", 11 | "screenshots": { 12 | "enabled": false, 13 | "on_failure": false, 14 | "on_error": false, 15 | "path": "results" 16 | }, 17 | "desiredCapabilities": { 18 | "browserName": "chrome", 19 | "javascriptEnabled": true, 20 | "acceptSslCerts": true 21 | }, 22 | "selenium_host": "localhost", 23 | "selenium_port": 4444 24 | }, 25 | "firefox": { 26 | "desiredCapabilities": { 27 | "browserName": "firefox", 28 | "javascriptEnabled": true, 29 | "acceptSslCerts": true 30 | } 31 | }, 32 | "chrome": { 33 | "desiredCapabilities": { 34 | "browserName": "chrome", 35 | "javascriptEnabled": true, 36 | "acceptSslCerts": true 37 | } 38 | }, 39 | "safari": { 40 | "desiredCapabilities": { 41 | "browserName": "safari", 42 | "platform": "MAC", 43 | "javascriptEnabled": true, 44 | "acceptSslCerts": true 45 | } 46 | }, 47 | "ios": { 48 | "desiredCapabilities": { 49 | "browserName": "Safari", 50 | "deviceName": "iPad Simulator", 51 | "platformName": "iOS", 52 | "platform": "MAC", 53 | "javascriptEnabled": true, 54 | "acceptSslCerts": true 55 | } 56 | }, 57 | "android": { 58 | "desiredCapabilities": { 59 | "browserName": "Browser", 60 | "deviceName": "41073d5417087fc9", 61 | "platformName": "Android", 62 | "platform": "ANDROID", 63 | "javascriptEnabled": true, 64 | "acceptSslCerts": true 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/lib/inks/one.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | 368, 5 | 370, 6 | 374, 7 | 381, 8 | 388, 9 | 395, 10 | 401, 11 | 405, 12 | 409, 13 | 414, 14 | 418, 15 | 422, 16 | 425, 17 | 428, 18 | 433, 19 | 436, 20 | 437, 21 | 439, 22 | 440, 23 | 441, 24 | 442, 25 | 443, 26 | 443, 27 | 443, 28 | 443, 29 | 443, 30 | 443, 31 | 443, 32 | 443, 33 | 443, 34 | 443, 35 | 443, 36 | 443, 37 | 443, 38 | 443, 39 | 443, 40 | 443, 41 | 443, 42 | 443, 43 | 443, 44 | 443, 45 | 443, 46 | 443, 47 | 443, 48 | 443, 49 | 443, 50 | 443, 51 | 443, 52 | 443, 53 | 443, 54 | 443, 55 | 443, 56 | 443, 57 | 443, 58 | 443, 59 | 443, 60 | 443 61 | ], 62 | [ 63 | 134, 64 | 130, 65 | 126, 66 | 121, 67 | 115, 68 | 110, 69 | 106, 70 | 103, 71 | 98, 72 | 93, 73 | 90, 74 | 86, 75 | 82, 76 | 80, 77 | 73, 78 | 69, 79 | 66, 80 | 63, 81 | 60, 82 | 63, 83 | 68, 84 | 72, 85 | 76, 86 | 80, 87 | 83, 88 | 86, 89 | 91, 90 | 94, 91 | 98, 92 | 103, 93 | 107, 94 | 111, 95 | 117, 96 | 120, 97 | 124, 98 | 127, 99 | 131, 100 | 134, 101 | 138, 102 | 142, 103 | 147, 104 | 151, 105 | 154, 106 | 158, 107 | 163, 108 | 168, 109 | 172, 110 | 175, 111 | 179, 112 | 182, 113 | 185, 114 | 188, 115 | 191, 116 | 195, 117 | 199, 118 | 202, 119 | 205 120 | ] 121 | ] 122 | ] -------------------------------------------------------------------------------- /src/util/ModelStats.js: -------------------------------------------------------------------------------- 1 | import { utilLogger as logger } from '../configuration/LoggerConfig'; 2 | import * as Cdkv3RestTextRecognizer from '../recognizer/rest/v3/Cdkv3RestTextRecognizer'; 3 | import * as RecognizerContext from '../model/RecognizerContext'; 4 | import defaultConfiguration from '../configuration/DefaultConfiguration'; 5 | 6 | /** 7 | * @typedef {Object} Stats 8 | * @property {Number} strokesCount=0 9 | * @property {Number} pointsCount=0 10 | * @property {Number} byteSize=0 11 | * @property {Number} humanSize=0 12 | * @property {String} humanUnit=BYTE 13 | */ 14 | 15 | /** 16 | * @param {Model} model Current model 17 | * @return {Stats} Statistics about recognition 18 | */ 19 | export function computeStats(model) { 20 | const stats = { strokesCount: 0, pointsCount: 0, byteSize: 0, humanSize: 0, humanUnit: 'BYTE' }; 21 | if (model.rawStrokes) { 22 | stats.strokesCount = model.rawStrokes.length; 23 | 24 | const restMessage = Cdkv3RestTextRecognizer.buildInput(RecognizerContext.createEmptyRecognizerContext({ configuration: defaultConfiguration }), model); 25 | stats.pointsCount = model.rawStrokes.map(stroke => stroke.x.length).reduce((a, b) => a + b, 0); 26 | // We start with 270 as it is the size in bytes. Make a real computation implies to recode a doRecognition 27 | const byteSize = restMessage.textInput.length; 28 | stats.byteSize = byteSize; 29 | if (byteSize < 270) { 30 | stats.humanUnit = 'BYTE'; 31 | stats.byteSize = 0; 32 | stats.humanSize = 0; 33 | } else if (byteSize < 2048) { 34 | stats.humanUnit = 'BYTES'; 35 | stats.humanSize = byteSize; 36 | } else if (byteSize < 1024 * 1024) { 37 | stats.humanUnit = 'KiB'; 38 | stats.humanSize = (byteSize / 1024).toFixed(2); 39 | } else { 40 | stats.humanUnit = 'MiB'; 41 | stats.humanSize = (byteSize / 1024 / 1024).toFixed(2); 42 | } 43 | } 44 | logger.info('Stats', stats); 45 | return stats; 46 | } 47 | -------------------------------------------------------------------------------- /docs/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /test/nightwatch/partial/03-iink/01-math-iink-ws-cdkv4.js: -------------------------------------------------------------------------------- 1 | const inkPlayer = require('../../lib/inkPlayer'); 2 | const config = require('../../../lib/configuration').getConfiguration('MATH', 'WEBSOCKET', 'V4'); 3 | const configImport = require('../../../lib/configuration').getConfiguration('MATH', 'WEBSOCKET', 'V4', '', 'Import'); 4 | 5 | function runLabelTests(ink) { 6 | module.exports[config.header + ' checkConvert ' + ink.name] = function checkConvert(browser) { 7 | inkPlayer.checkConvert(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 8 | }; 9 | module.exports[config.header + ' checkLabels ' + ink.name] = function checkLabels(browser) { 10 | inkPlayer.checkLabels(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 11 | }; 12 | } 13 | 14 | function runUndoTests(ink) { 15 | module.exports[config.header + ' checkUndoRedoReconnect ' + ink.name] = function checkUndoRedoReconnect(browser) { 16 | inkPlayer.checkUndoRedoReconnect(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 17 | }; 18 | module.exports[config.header + ' checkUndoRedo ' + ink.name] = function checkUndoRedo(browser) { 19 | inkPlayer.checkUndoRedo(browser, config, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 20 | }; 21 | } 22 | 23 | function runImportTests(ink) { 24 | module.exports[configImport.header + ' runImportTests ' + ink.name] = function checkImport(browser) { 25 | inkPlayer.checkImport(browser, configImport, ink.strokes, ink.exports.LATEX, '#editor', '[data-key="application/x-latex"]'); 26 | }; 27 | } 28 | 29 | 30 | config.inks 31 | .filter(ink => ['equation3'].includes(ink.name)) 32 | .forEach(ink => runLabelTests(ink)); 33 | 34 | config.inks 35 | .filter(ink => ['equation3'].includes(ink.name)) 36 | .forEach(ink => runUndoTests(ink)); 37 | 38 | 39 | configImport.inks 40 | .filter(ink => ['one'].includes(ink.name)) 41 | .forEach(ink => runImportTests(ink)); 42 | -------------------------------------------------------------------------------- /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/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 callbackLogger = log.getLogger(Constants.Logger.CALLBACK); 58 | callbackLogger.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/configuration/languagesV3.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": { 3 | "af_ZA": "Afrikaans", 4 | "az_AZ": "Azərbaycanca", 5 | "id_ID": "Bahasa Indonesia", 6 | "ms_MY": "Bahasa Melayu (Malaysia)", 7 | "bs_BA": "Bosanski", 8 | "ca_ES": "Català", 9 | "da_DK": "Dansk", 10 | "de_DE": "Deutsch", 11 | "de_AT": "Deutsch (Österreich)", 12 | "et_EE": "Eesti", 13 | "en_CA": "English (Canada)", 14 | "en_GB": "English (United Kingdom)", 15 | "en_US": "English (United States)", 16 | "es_ES": "Español (España)", 17 | "es_MX": "Español (México)", 18 | "eu_ES": "Euskara", 19 | "fr_CA": "Français (Canada)", 20 | "fr_FR": "Français (France)", 21 | "ga_IE": "Gaeilge", 22 | "gl_ES": "Galego", 23 | "hr_HR": "Hrvatski", 24 | "it_IT": "Italiano", 25 | "lv_LV": "Latviešu", 26 | "lt_LT": "Lietuvių", 27 | "hu_HU": "Magyar", 28 | "nl_NL": "Nederlands", 29 | "nl_BE": "Nederlands (België)", 30 | "no_NO": "Norsk (Bokmål)", 31 | "pl_PL": "Polski", 32 | "pt_BR": "Português (Brasil)", 33 | "pt_PT": "Português (Portugal)", 34 | "ro_RO": "Română", 35 | "sq_AL": "Shqip", 36 | "sk_SK": "Slovenčina", 37 | "sl_SI": "Slovenščina", 38 | "sr_Latn_RS": "Srpski", 39 | "fi_FI": "Suomi", 40 | "sv_SE": "Svenska", 41 | "th_TH": "Thaiไทย", 42 | "vi_VN": "Tiếng Việt", 43 | "tr_TR": "Türkçe", 44 | "is_IS": "Íslenska", 45 | "cs_CZ": "Čeština", 46 | "el_GR": "Ελληνικά", 47 | "be_BY": "Беларуская", 48 | "bg_BG": "Български", 49 | "mk_MK": "Македонски", 50 | "mn_MN": "Монгол", 51 | "ru_RU": "Русский", 52 | "sr_Cyrl_RS": "Српски", 53 | "tt_RU": "Татарча", 54 | "uk_UA": "Українська", 55 | "kk_KZ": "Қазақша", 56 | "hy_AM": "Հայերեն", 57 | "he_IL": "עברית", 58 | "ur_PK": "اردو", 59 | "ar": "العربية", 60 | "fa_IR": "فارسی", 61 | "hi_IN": "हिन्दी", 62 | "ka_GE": "ქართული", 63 | "zh_CN": "中文 (中国)", 64 | "zh_TW": "中文 (台灣)", 65 | "zh_HK": "中文 (香港)", 66 | "ja_JP": "日本語", 67 | "ko_KR": "한국어" 68 | } 69 | } -------------------------------------------------------------------------------- /examples/v4/multiple_inputs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Multiple inputs 12 | 13 | 14 | 15 | 16 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 66 |
67 |
68 |
69 |
70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /src/configuration/languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": { 3 | "af_ZA": "Afrikaans", 4 | "az_AZ": "Azərbaycanca", 5 | "be_BY": "Беларуская", 6 | "bg_BG": "Български", 7 | "bs_BA": "Bosanski", 8 | "ca_ES": "Català", 9 | "ceb_PH": "Sinugboanon", 10 | "cs_CZ": "Čeština", 11 | "da_DK": "Dansk", 12 | "de_AT": "Deutsch (Österreich)", 13 | "de_DE": "Deutsch", 14 | "el_GR": "Ελληνικά", 15 | "en_CA": "English (Canada)", 16 | "en_GB": "English (United Kingdom)", 17 | "en_PH": "English (Philippines)", 18 | "en_US": "English (United States)", 19 | "es_CO": "Español (Colombia)", 20 | "es_ES": "Español (España)", 21 | "es_MX": "Español (México)", 22 | "et_EE": "Eesti", 23 | "eu_ES": "Euskara", 24 | "fi_FI": "Suomi", 25 | "fil_PH": "Filipino", 26 | "fr_CA": "Français (Canada)", 27 | "fr_FR": "Français (France)", 28 | "ga_IE": "Gaeilge", 29 | "gl_ES": "Galego", 30 | "hr_HR": "Hrvatski", 31 | "hu_HU": "Magyar", 32 | "hy_AM": "Հայերեն", 33 | "id_ID": "Bahasa Indonesia", 34 | "is_IS": "Íslenska", 35 | "it_IT": "Italiano", 36 | "ja_JP": "日本語", 37 | "ka_GE": "ქართული", 38 | "kk_KZ": "Қазақша", 39 | "ko_KR": "한국어", 40 | "lt_LT": "Lietuvių", 41 | "lv_LV": "Latviešu", 42 | "mg_MG": "Malagasy", 43 | "mk_MK": "Македонски", 44 | "mn_MN": "Монгол", 45 | "ms_MY": "Bahasa Melayu (Malaysia)", 46 | "nl_BE": "Nederlands (België)", 47 | "nl_NL": "Nederlands", 48 | "no_NO": "Norsk (Bokmål)", 49 | "pl_PL": "Polski", 50 | "pt_BR": "Português (Brasil)", 51 | "pt_PT": "Português (Portugal)", 52 | "ro_RO": "Română", 53 | "ru_RU": "Русский", 54 | "sk_SK": "Slovenčina", 55 | "sl_SI": "Slovenščina", 56 | "sq_AL": "Shqip", 57 | "sr_Cyrl_RS": "Српски", 58 | "sr_Latn_RS": "Srpski", 59 | "sv_SE": "Svenska", 60 | "sw_TZ": "Kiswahili", 61 | "tr_TR": "Türkçe", 62 | "tt_RU": "Татарча", 63 | "uk_UA": "Українська", 64 | "vi_VN": "Tiếng Việt", 65 | "zh_CN": "中文 (中国)", 66 | "zh_HK": "中文 (香港)", 67 | "zh_TW": "中文 (台灣)" 68 | } 69 | } -------------------------------------------------------------------------------- /src/configuration/DefaultTheme.js: -------------------------------------------------------------------------------- 1 | import JsonCSS from 'json-css'; 2 | import assign from 'assign-deep'; 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': 'Open Sans', 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 = assign({}, 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 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | options { disableConcurrentBuilds() } 4 | environment { 5 | PROJECTNAME = "myscriptjs ${env.BRANCH_NAME}" 6 | PROJECTHOME = '/tmp/myscriptjs' 7 | SELENIUM_ENV = 'chrome' 8 | PROJECT_DIR= "${WORKSPACE.replace('/var/jenkins_home/workspace','/dockervolumes/cloud/master/jenkins/workspace')}" 9 | APPLICATION_KEY = credentials('APPLICATION_KEY') 10 | HMAC_KEY = credentials('HMAC_KEY') 11 | MAKE_ARGS=" PROJECT_DIR=${env.PROJECT_DIR} HOME=${env.PROJECTHOME} SELENIUM_ENV=${env.SELENIUM_ENV} BUILDID=${env.BUILD_NUMBER} DEV_APPLICATIONKEY=${env.APPLICATION_KEY} DEV_HMACKEY=${env.HMAC_KEY} " 12 | } 13 | 14 | stages { 15 | 16 | stage ('purge'){ 17 | steps { 18 | sh "make ${env.MAKE_ARGS} purge" 19 | } 20 | } 21 | 22 | stage ('prepare'){ 23 | steps { 24 | sh "make ${env.MAKE_ARGS} prepare" 25 | } 26 | } 27 | 28 | stage ('docker'){ 29 | steps { 30 | sh "make ${env.MAKE_ARGS} docker" 31 | } 32 | } 33 | 34 | stage ('test'){ 35 | steps { 36 | sh "make ${env.MAKE_ARGS} test" 37 | } 38 | } 39 | 40 | stage ('audit'){ 41 | steps { 42 | sh "npm audit" 43 | } 44 | } 45 | } 46 | 47 | post { 48 | always { 49 | sh "make ${env.MAKE_ARGS} killdocker" 50 | } 51 | 52 | success { 53 | slackSend color: "good", message: "${env.PROJECTNAME}: Build success ${env.JOB_NAME} ${env.BUILD_NUMBER}." 54 | } 55 | unstable { 56 | slackSend color: "warning", message: "${env.PROJECTNAME}: Unstable build, ${currentBuild.fullDisplayName} is unstable" 57 | } 58 | failure { 59 | slackSend color: "danger", message: "@group ${env.PROJECTNAME}: FAILURE, ${currentBuild.fullDisplayName} failed see there ${env.BUILD_URL}" 60 | } 61 | /* changed { 62 | slackSend color: "good", message: "${env.PROJECTNAME}: Build changed" 63 | }*/ 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/v4/rest/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 | -------------------------------------------------------------------------------- /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 | SHAPE: 'SHAPE', 27 | MUSIC: 'MUSIC', 28 | ANALYZER: 'ANALYZER', 29 | DIAGRAM: 'DIAGRAM', 30 | NEBO: 'NEBO', 31 | RAWCONTENT: 'Raw Content' 32 | }, 33 | Protocol: { 34 | WEBSOCKET: 'WEBSOCKET', 35 | REST: 'REST' 36 | }, 37 | ModelState: { 38 | INITIALIZING: 'INITIALIZING', 39 | INITIALIZED: 'INITIALIZED', 40 | EXPORTING: 'EXPORTING', 41 | EXPORTED: 'EXPORTED', 42 | PENDING: 'PENDING', 43 | MODIFIED: 'MODIFIED', 44 | ERROR: 'ERROR' 45 | }, 46 | Trigger: { 47 | QUIET_PERIOD: 'QUIET_PERIOD', 48 | POINTER_UP: 'POINTER_UP', 49 | DEMAND: 'DEMAND' 50 | }, 51 | Logger: { 52 | EDITOR: 'editor', 53 | MODEL: 'model', 54 | GRABBER: 'grabber', 55 | RENDERER: 'renderer', 56 | RECOGNIZER: 'recognizer', 57 | CALLBACK: 'callback', 58 | UTIL: 'util', 59 | SMARTGUIDE: 'smartguide' 60 | }, 61 | LogLevel: { 62 | TRACE: 'TRACE', 63 | DEBUG: 'DEBUG', 64 | INFO: 'INFO', 65 | WARN: 'WARN', 66 | ERROR: 'ERROR' 67 | }, 68 | Languages: { 69 | zh_CN: 'Noto Sans CJK tc', 70 | zh_HK: 'Noto Sans CJK tc', 71 | zh_TW: 'Noto Sans CJK tc', 72 | ko_KR: 'Noto Sans CJK kr', 73 | ja_JP: 'Noto Sans CJK jp', 74 | hy_AM: 'Noto Sans Armenian', 75 | default: 'Open Sans' 76 | }, 77 | Error: { 78 | NOT_REACHABLE: 'MyScript recognition server is not reachable. Please reload once you are connected.', 79 | WRONG_CREDENTIALS: 'Application credentials are invalid. Please check or regenerate your application key and hmackey.', 80 | TOO_OLD: 'Session is too old. Max Session Duration Reached.' 81 | }, 82 | Exports: { 83 | JIIX: 'application/vnd.myscript.jiix' 84 | } 85 | }; 86 | export default Constants; 87 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/lib/inks/hello.json: -------------------------------------------------------------------------------- 1 | [[[397, 2 | 400, 3 | 404, 4 | 408, 5 | 411, 6 | 414, 7 | 417, 8 | 418, 9 | 418, 10 | 419, 11 | 419, 12 | 418, 13 | 414, 14 | 412, 15 | 412, 16 | 412, 17 | 412, 18 | 412, 19 | 412, 20 | 412, 21 | 412, 22 | 412, 23 | 412, 24 | 412, 25 | 412, 26 | 412, 27 | 412, 28 | 413, 29 | 417, 30 | 418, 31 | 419, 32 | 419, 33 | 421, 34 | 425, 35 | 426], 36 | [254, 37 | 249, 38 | 247, 39 | 242, 40 | 240, 41 | 235, 42 | 231, 43 | 227, 44 | 224, 45 | 220, 46 | 216, 47 | 211, 48 | 210, 49 | 213, 50 | 217, 51 | 221, 52 | 226, 53 | 230, 54 | 234, 55 | 238, 56 | 243, 57 | 247, 58 | 251, 59 | 255, 60 | 252, 61 | 248, 62 | 244, 63 | 241, 64 | 240, 65 | 243, 66 | 247, 67 | 251, 68 | 254, 69 | 253, 70 | 250]], 71 | [[427, 72 | 433, 73 | 437, 74 | 440, 75 | 439, 76 | 435, 77 | 430, 78 | 428, 79 | 428, 80 | 429, 81 | 432, 82 | 436, 83 | 440, 84 | 443], 85 | [244, 86 | 248, 87 | 245, 88 | 242, 89 | 238, 90 | 238, 91 | 240, 92 | 245, 93 | 249, 94 | 253, 95 | 253, 96 | 253, 97 | 251, 98 | 251]], 99 | [[443, 100 | 450, 101 | 453, 102 | 456, 103 | 459, 104 | 460, 105 | 461, 106 | 461, 107 | 461, 108 | 460, 109 | 456, 110 | 452, 111 | 450, 112 | 450, 113 | 450, 114 | 450, 115 | 450, 116 | 450, 117 | 450, 118 | 450, 119 | 449, 120 | 449, 121 | 451, 122 | 455, 123 | 460, 124 | 463, 125 | 464], 126 | [242, 127 | 242, 128 | 242, 129 | 238, 130 | 236, 131 | 233, 132 | 230, 133 | 226, 134 | 222, 135 | 217, 136 | 214, 137 | 214, 138 | 219, 139 | 223, 140 | 227, 141 | 231, 142 | 234, 143 | 238, 144 | 242, 145 | 246, 146 | 249, 147 | 253, 148 | 257, 149 | 257, 150 | 255, 151 | 252, 152 | 249]], 153 | [[464, 154 | 466, 155 | 469, 156 | 471, 157 | 472, 158 | 474, 159 | 475, 160 | 473, 161 | 469, 162 | 465, 163 | 465, 164 | 465, 165 | 465, 166 | 465, 167 | 465, 168 | 465, 169 | 465, 170 | 465, 171 | 466, 172 | 467, 173 | 470, 174 | 474, 175 | 480, 176 | 482], 177 | [243, 178 | 236, 179 | 234, 180 | 230, 181 | 227, 182 | 223, 183 | 219, 184 | 216, 185 | 215, 186 | 216, 187 | 220, 188 | 225, 189 | 229, 190 | 233, 191 | 237, 192 | 240, 193 | 244, 194 | 248, 195 | 252, 196 | 255, 197 | 256, 198 | 254, 199 | 250, 200 | 247]], 201 | [[489, 202 | 483, 203 | 482, 204 | 482, 205 | 482, 206 | 485, 207 | 489, 208 | 494, 209 | 495, 210 | 495, 211 | 492, 212 | 490, 213 | 494, 214 | 497, 215 | 501, 216 | 504], 217 | [238, 218 | 242, 219 | 245, 220 | 249, 221 | 253, 222 | 254, 223 | 252, 224 | 248, 225 | 244, 226 | 239, 227 | 238, 228 | 242, 229 | 242, 230 | 239, 231 | 238, 232 | 236]]] -------------------------------------------------------------------------------- /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), (err, model) => {}); 23 | } 24 | assert.lengthOf(undoRedoContext.stack, maxSize); 25 | assert.equal(undoRedoContext.currentPosition, maxSize - 1); 26 | UndoRedoManager.getModel(undoRedoContext, (err, model) => { 27 | assert.isTrue(undoRedoContext.canUndo, 'Wrong canUndo state'); 28 | assert.isFalse(undoRedoContext.canRedo, 'Wrong canRedo state'); 29 | done(err); 30 | }); 31 | }); 32 | 33 | it(`Should undo and update current index to ${maxSize - 2}`, (done) => { 34 | UndoRedoManager.undo(undoRedoContext, undefined, (err, model) => { 35 | assert.lengthOf(undoRedoContext.stack, maxSize); 36 | assert.equal(undoRedoContext.currentPosition, maxSize - 2); 37 | assert.isTrue(undoRedoContext.canUndo, 'Wrong canUndo state'); 38 | assert.isTrue(undoRedoContext.canRedo, 'Wrong canRedo state'); 39 | done(err); 40 | }); 41 | }); 42 | 43 | it(`Should redo and update current index to ${maxSize - 1}`, (done) => { 44 | UndoRedoManager.redo(undoRedoContext, undefined, (err, model) => { 45 | assert.lengthOf(undoRedoContext.stack, maxSize); 46 | assert.equal(undoRedoContext.currentPosition, maxSize - 1); 47 | assert.isTrue(undoRedoContext.canUndo, 'Wrong canUndo state'); 48 | assert.isFalse(undoRedoContext.canRedo, 'Wrong canRedo state'); 49 | done(err); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /examples/non-version-specific/change_language.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Changing the language dynamically 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 |
25 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/css/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: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 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 | -------------------------------------------------------------------------------- /test/lib/inks/rabText.json: -------------------------------------------------------------------------------- 1 | [[[319, 2 | 316, 3 | 313, 4 | 310, 5 | 307, 6 | 304, 7 | 303, 8 | 302, 9 | 307, 10 | 315, 11 | 320, 12 | 327, 13 | 331, 14 | 334, 15 | 336, 16 | 333, 17 | 328, 18 | 323, 19 | 320, 20 | 317, 21 | 318, 22 | 323, 23 | 328, 24 | 331, 25 | 332, 26 | 331, 27 | 329, 28 | 336, 29 | 344, 30 | 348, 31 | 350, 32 | 349, 33 | 351, 34 | 354, 35 | 357, 36 | 360, 37 | 360, 38 | 364, 39 | 370, 40 | 380, 41 | 386, 42 | 394, 43 | 403, 44 | 411, 45 | 419, 46 | 421, 47 | 417, 48 | 409, 49 | 404, 50 | 400, 51 | 396, 52 | 391, 53 | 388, 54 | 387, 55 | 386, 56 | 386, 57 | 386, 58 | 386, 59 | 386, 60 | 386, 61 | 386, 62 | 386, 63 | 383, 64 | 377, 65 | 373, 66 | 370, 67 | 368, 68 | 371, 69 | 376, 70 | 383, 71 | 392, 72 | 409, 73 | 417, 74 | 421, 75 | 423, 76 | 420, 77 | 417, 78 | 414, 79 | 413, 80 | 415, 81 | 419, 82 | 423, 83 | 429, 84 | 439, 85 | 445, 86 | 450, 87 | 455, 88 | 459, 89 | 462, 90 | 462, 91 | 461, 92 | 458, 93 | 452, 94 | 449, 95 | 445, 96 | 442, 97 | 438, 98 | 435, 99 | 433, 100 | 433, 101 | 433, 102 | 435, 103 | 438, 104 | 440, 105 | 442, 106 | 442, 107 | 442, 108 | 442, 109 | 441, 110 | 440, 111 | 438, 112 | 436, 113 | 431, 114 | 430, 115 | 436, 116 | 443, 117 | 450, 118 | 461, 119 | 467, 120 | 471, 121 | 473, 122 | 474, 123 | 471, 124 | 468, 125 | 463, 126 | 461, 127 | 467, 128 | 473, 129 | 479, 130 | 484], 131 | [329, 132 | 328, 133 | 328, 134 | 328, 135 | 329, 136 | 330, 137 | 333, 138 | 337, 139 | 341, 140 | 343, 141 | 343, 142 | 342, 143 | 341, 144 | 340, 145 | 336, 146 | 334, 147 | 335, 148 | 337, 149 | 339, 150 | 343, 151 | 347, 152 | 348, 153 | 346, 154 | 344, 155 | 341, 156 | 337, 157 | 334, 158 | 334, 159 | 332, 160 | 331, 161 | 334, 162 | 339, 163 | 342, 164 | 342, 165 | 341, 166 | 339, 167 | 335, 168 | 335, 169 | 335, 170 | 334, 171 | 333, 172 | 330, 173 | 327, 174 | 324, 175 | 318, 176 | 315, 177 | 308, 178 | 304, 179 | 303, 180 | 303, 181 | 304, 182 | 308, 183 | 312, 184 | 317, 185 | 322, 186 | 328, 187 | 338, 188 | 344, 189 | 350, 190 | 357, 191 | 362, 192 | 366, 193 | 366, 194 | 359, 195 | 354, 196 | 350, 197 | 346, 198 | 345, 199 | 345, 200 | 345, 201 | 344, 202 | 342, 203 | 340, 204 | 338, 205 | 335, 206 | 331, 207 | 330, 208 | 332, 209 | 337, 210 | 340, 211 | 343, 212 | 343, 213 | 343, 214 | 340, 215 | 337, 216 | 333, 217 | 328, 218 | 324, 219 | 318, 220 | 314, 221 | 310, 222 | 306, 223 | 304, 224 | 303, 225 | 304, 226 | 305, 227 | 308, 228 | 314, 229 | 320, 230 | 326, 231 | 334, 232 | 345, 233 | 351, 234 | 358, 235 | 363, 236 | 367, 237 | 371, 238 | 374, 239 | 370, 240 | 365, 241 | 359, 242 | 352, 243 | 345, 244 | 342, 245 | 341, 246 | 342, 247 | 342, 248 | 340, 249 | 338, 250 | 336, 251 | 333, 252 | 330, 253 | 328, 254 | 329, 255 | 331, 256 | 337, 257 | 342, 258 | 344, 259 | 345, 260 | 345]]] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myscript", 3 | "version": "4.3.0", 4 | "main": "dist/myscript.min.js", 5 | "module": "dist/myscript.esm.js", 6 | "description": "MyScriptJS 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 | ], 15 | "files": [ 16 | "dist" 17 | ], 18 | "license": "Apache-2.0", 19 | "homepage": "https://myscript.github.io/MyScriptJS/", 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/MyScript/MyScriptJS.git" 23 | }, 24 | "dependencies": { 25 | "assign-deep": "^0.4.7", 26 | "clipboard": "^1.7.1", 27 | "crypto-js": "^3.1.9-1", 28 | "d3-selection": "^1.3.0", 29 | "json-css": "^1.5.6", 30 | "loglevel": "^1.6.1", 31 | "perfect-scrollbar": "^1.3.0", 32 | "uuid-js": "^0.7.5" 33 | }, 34 | "devDependencies": { 35 | "babel-cli": "^6.26.0", 36 | "babel-core": "^6.26.3", 37 | "babel-loader": "^7.1.4", 38 | "babel-plugin-external-helpers": "^6.22.0", 39 | "babel-plugin-transform-runtime": "^6.23.0", 40 | "babel-polyfill": "^6.26.0", 41 | "babel-preset-env": "^1.7.0", 42 | "babel-register": "^6.26.0", 43 | "chai": "^4.1.2", 44 | "clean-css-cli": "^4.1.11", 45 | "esdoc": "^1.1.0", 46 | "esdoc-standard-plugin": "^1.0.0", 47 | "eslint": "^4.19.1", 48 | "eslint-config-airbnb": "^15.1.0", 49 | "eslint-loader": "^1.9.0", 50 | "eslint-plugin-import": "^2.11.0", 51 | "eslint-plugin-jsx-a11y": "^5.1.1", 52 | "eslint-plugin-react": "^7.8.2", 53 | "mocha": "^5.1.1", 54 | "npm-run-all": "^4.1.3", 55 | "rollup": "0.63.x", 56 | "rollup-plugin-babel": "^3.0.4", 57 | "rollup-plugin-commonjs": "^9.1.3", 58 | "rollup-plugin-json": "^2.3.1", 59 | "rollup-plugin-livereload": "^0.6.0", 60 | "rollup-plugin-node-resolve": "^3.3.0", 61 | "rollup-plugin-serve": "^0.4.2", 62 | "rollup-plugin-uglify": "^3.0.0", 63 | "sinon": "^2.4.1" 64 | }, 65 | "scripts": { 66 | "lint": "eslint --ext js src", 67 | "docs": "esdoc", 68 | "minify-css": "cleancss -o dist/myscript.min.css src/*.css", 69 | "test:mocha": "mocha --require babel-core/register --recursive test/mocha/ --reporter progress", 70 | "test:mocha-xunit": "mocha --require babel-core/register --recursive test/mocha/ --reporter xunit --reporter-options output=./test/mocha/results/xunit.xml", 71 | "build:js": "rollup -c config/rollup.config.build.js --sourcemap", 72 | "build": "npm-run-all test:mocha lint build:js minify-css docs", 73 | "dev:js": "rollup -c config/rollup.config.dev.js -w --sourcemap", 74 | "dev": "npm-run-all minify-css dev:js", 75 | "start": "npm run dev" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 6 | ifeq ($(findstring dev-,$(MAKECMDGOALS)),) 7 | BUILDID := $(shell date +"%Y-%m-%d_%H_%M_%S_%N") 8 | else 9 | BUILDID := DEV 10 | endif 11 | 12 | FULL = false 13 | OFFLINE = false 14 | IINKAPILOCAL = false 15 | CDKAPILOCAL = false 16 | DEVLOCAL = false 17 | SELENIUM_HOST = localhost 18 | SELENIUM_ENV = chrome 19 | CURRENT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 20 | PROJECT_DIR ?= $(CURRENT_DIR) 21 | GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 22 | 23 | MAKE := $(MAKE) PROJECT_DIR=$(PROJECT_DIR) BUILDID=$(BUILDID) --no-print-directory 24 | NPM_CACHE = $(HOME)/.npm 25 | 26 | DOCKERTAG := master 27 | GIT_TAG = 2.0.0 28 | 29 | REGISTRY = registry.corp.myscript.com:5000 30 | DOC_DOCKERREPOSITORY = $(REGISTRY)/myscriptjs-docs:$(DOCKERTAG) 31 | EXAMPLES_DOCKERREPOSITORY = $(REGISTRY)/myscriptjs-examples:$(DOCKERTAG) 32 | 33 | CONFIGURATION_DOCKERTAG := master 34 | SELENIUM_STANDALONE_DOCKERREPOSITORY = $(REGISTRY)/selenium-standalone-chrome-debug:$(CONFIGURATION_DOCKERTAG) 35 | NIGHTWATCH_DOCKERREPOSITORY = $(REGISTRY)/myscript-webcomponents-nightwatch:$(CONFIGURATION_DOCKERTAG) 36 | MOCHA_DOCKERREPOSITORY = $(REGISTRY)/myscript-webcomponents-mocha:$(CONFIGURATION_DOCKERTAG) 37 | WAITTCP_DOCKERREPOSITORY = $(REGISTRY)/myscript-webcomponents-wait-tcp:$(CONFIGURATION_DOCKERTAG) 38 | 39 | BUILDENV := test 40 | TEST_DOCKER_NAME_PREFIX := myscriptjs-$(DOCKERTAG)-$(BUILDENV)-$(BUILDID) 41 | TEST_DOCKER_EXAMPLES_INSTANCE_NAME := $(TEST_DOCKER_NAME_PREFIX)-examples 42 | TEST_DOCKER_SELENIUM_INSTANCE_NAME := selenium_hub_1 43 | 44 | APPLICATIONKEY := 7d223f9e-a3cb-4213-ba4b-85e930605f8b 45 | HMACKEY := 5ab1935e-529a-4d48-a695-158450e52b13 46 | 47 | APIHOST := cloud-internal-master.corp.myscript.com 48 | APISCHEME := https 49 | ifeq ($(CDKAPILOCAL),true) 50 | APIHOST := localhost:8894 51 | APISCHEME := http 52 | endif 53 | ifeq ($(IINKAPILOCAL),true) 54 | APIHOST := localhost:8897 55 | APISCHEME := http 56 | endif 57 | 58 | 59 | ifeq ($(OFFLINE),true) 60 | BOWER_PARAMETERS := --offline 61 | NPM_PARAMETERS := --cache-min 9999999 62 | NIGHTWATCH_TESTS := false 63 | else 64 | DOCKER_PARAMETERS := --pull 65 | NIGHTWATCH_TESTS := true 66 | endif 67 | 68 | ifeq ($(DEVLOCAL),true) 69 | DOCKER_NIGHTWATCH_PARAMETERS := --net=host 70 | DOCKER_EXAMPLES_PARAMETERS := --net=host 71 | DOCKER_SELENIUM_PARAMETERS := --net=host 72 | EXAMPLES_LISTEN_PORT := 8080 73 | SELENIUM_HOST := localhost 74 | else 75 | DOCKER_NIGHTWATCH_PARAMETERS := --link $(TEST_DOCKER_SELENIUM_INSTANCE_NAME):selenium 76 | DOCKER_SELENIUM_PARAMETERS := -p 4444:4444 -p 5900:5900 77 | EXAMPLES_LISTEN_PORT := 80 78 | SELENIUM_HOST := selenium 79 | endif 80 | -------------------------------------------------------------------------------- /docs/lint.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "manageResetState", 4 | "filePath": "src/Editor.js", 5 | "lines": [ 6 | { 7 | "lineNumber": 79, 8 | "line": "/**" 9 | }, 10 | { 11 | "lineNumber": 80, 12 | "line": " * Check if a clear is required, and does it if it is" 13 | }, 14 | { 15 | "lineNumber": 81, 16 | "line": " * @param {function(recognizerContext: RecognizerContext, model: Model, callback: RecognizerCallback)} resetFunc" 17 | }, 18 | { 19 | "lineNumber": 82, 20 | "line": " * @param {function(recognizerContext: RecognizerContext, model: Model, callback: RecognizerCallback)} func" 21 | }, 22 | { 23 | "lineNumber": 83, 24 | "line": " * @param {RecognizerContext} recognizerContext Current recognizer context" 25 | }, 26 | { 27 | "lineNumber": 84, 28 | "line": " * @param {Model} model Current model" 29 | }, 30 | { 31 | "lineNumber": 85, 32 | "line": " * @param {RecognizerCallback} callback" 33 | }, 34 | { 35 | "lineNumber": 86, 36 | "line": " */" 37 | }, 38 | { 39 | "lineNumber": 87, 40 | "line": "function manageResetState(resetFunc, func, recognizerContext, model, callback, ...params) {" 41 | } 42 | ], 43 | "codeParams": [ 44 | "resetFunc", 45 | "func", 46 | "recognizerContext", 47 | "model", 48 | "callback", 49 | "params" 50 | ], 51 | "docParams": [ 52 | "resetFunc", 53 | "func", 54 | "recognizerContext", 55 | "model", 56 | "callback" 57 | ] 58 | }, 59 | { 60 | "name": "showActions", 61 | "filePath": "src/smartguide/SmartGuide.js", 62 | "lines": [ 63 | { 64 | "lineNumber": 138, 65 | "line": "/**" 66 | }, 67 | { 68 | "lineNumber": 139, 69 | "line": " * Show the actions of the action menu." 70 | }, 71 | { 72 | "lineNumber": 140, 73 | "line": " * @param {Event} evt - Event used to insert the option div using the event's target." 74 | }, 75 | { 76 | "lineNumber": 141, 77 | "line": " * @param {Object} elements - All the elements of the smart guide." 78 | }, 79 | { 80 | "lineNumber": 142, 81 | "line": " * @param {SmartGuide} smartGuide" 82 | }, 83 | { 84 | "lineNumber": 143, 85 | "line": " */" 86 | }, 87 | { 88 | "lineNumber": 144, 89 | "line": "function showActions(evt, elements) {" 90 | } 91 | ], 92 | "codeParams": [ 93 | "evt", 94 | "elements" 95 | ], 96 | "docParams": [ 97 | "evt", 98 | "elements", 99 | "smartGuide" 100 | ] 101 | } 102 | ] -------------------------------------------------------------------------------- /docs/css/manual.css: -------------------------------------------------------------------------------- 1 | .github-markdown .manual-toc { 2 | padding-left: 0; 3 | } 4 | 5 | .manual-index .manual-cards { 6 | display: flex; 7 | flex-wrap: wrap; 8 | } 9 | 10 | .manual-index .manual-card-wrap { 11 | width: 280px; 12 | padding: 10px 20px 10px 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | .manual-index .manual-card-wrap > h1 { 17 | margin: 0; 18 | font-size: 1em; 19 | font-weight: 600; 20 | padding: 0.2em 0 0.2em 0.5em; 21 | border-radius: 0.1em 0.1em 0 0; 22 | border: none; 23 | } 24 | 25 | .manual-index .manual-card-wrap > h1 span { 26 | color: #555; 27 | } 28 | 29 | .manual-index .manual-card { 30 | height: 200px; 31 | overflow: hidden; 32 | border: solid 1px rgba(230, 230, 230, 0.84); 33 | border-radius: 0 0 0.1em 0.1em; 34 | padding: 8px; 35 | position: relative; 36 | } 37 | 38 | .manual-index .manual-card > div { 39 | transform: scale(0.4); 40 | transform-origin: 0 0; 41 | width: 250%; 42 | } 43 | 44 | .manual-index .manual-card > a { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | width: 100%; 49 | height: 100%; 50 | background: rgba(210, 210, 210, 0.1); 51 | } 52 | 53 | .manual-index .manual-card > a:hover { 54 | background: none; 55 | } 56 | 57 | .manual-index .manual-badge { 58 | margin: 0; 59 | } 60 | 61 | .manual-index .manual-user-index { 62 | margin-bottom: 1em; 63 | border-bottom: solid 1px #ddd; 64 | } 65 | 66 | .manual-root .navigation { 67 | padding-left: 4px; 68 | margin-top: 4px; 69 | } 70 | 71 | .navigation .manual-toc-root > div { 72 | padding-left: 0.25em; 73 | padding-right: 0.75em; 74 | } 75 | 76 | .github-markdown .manual-toc-title a { 77 | color: inherit; 78 | } 79 | 80 | .manual-breadcrumb-list { 81 | font-size: 0.8em; 82 | margin-bottom: 1em; 83 | } 84 | 85 | .manual-toc-title a:hover { 86 | color: #039BE5; 87 | } 88 | 89 | .manual-toc li { 90 | margin: 0.75em 0; 91 | list-style-type: none; 92 | } 93 | 94 | .navigation .manual-toc [class^="indent-h"] a { 95 | color: #666; 96 | } 97 | 98 | .navigation .manual-toc .indent-h1 a { 99 | color: #555; 100 | font-weight: 600; 101 | display: block; 102 | } 103 | 104 | .manual-toc .indent-h1 { 105 | display: block; 106 | margin: 0.4em 0 0 0.25em; 107 | padding: 0.2em 0 0.2em 0.5em; 108 | border-radius: 0.1em; 109 | } 110 | 111 | .manual-root .navigation .manual-toc li:not(.indent-h1) { 112 | margin-top: 0.5em; 113 | } 114 | 115 | .manual-toc .indent-h2 { 116 | display: none; 117 | margin-left: 1.5em; 118 | } 119 | .manual-toc .indent-h3 { 120 | display: none; 121 | margin-left: 2.5em; 122 | } 123 | .manual-toc .indent-h4 { 124 | display: none; 125 | margin-left: 3.5em; 126 | } 127 | .manual-toc .indent-h5 { 128 | display: none; 129 | margin-left: 4.5em; 130 | } 131 | 132 | .manual-nav li { 133 | margin: 0.75em 0; 134 | } 135 | -------------------------------------------------------------------------------- /examples/v4/custom_resources_text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Custom pre-loaded resources text 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | 31 |
32 | 43 |
44 |
45 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /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 | let obj; 8 | let stroke; 9 | 10 | beforeEach(() => { 11 | stroke = StrokeComponent.createStrokeComponent(obj); 12 | }); 13 | 14 | it('Check mandatory properties', () => { 15 | assert.property(stroke, 'type'); 16 | assert.propertyVal(stroke, 'type', 'stroke'); 17 | assert.property(stroke, 'x'); 18 | assert.property(stroke, 'y'); 19 | assert.property(stroke, 't'); 20 | assert.property(stroke, 'p'); 21 | assert.property(stroke, 'l'); 22 | assert.property(stroke, 'width'); 23 | }); 24 | 25 | obj = { color: '#000F55', width: 3, x: [10, 20], y: [30, 40], t: [50, 60] }; 26 | Object.keys(obj).forEach((key) => { 27 | it(`Check custom constructor param ${key}`, () => { 28 | assert.property(stroke, key); 29 | assert.propertyVal(stroke, key, obj[key]); 30 | }); 31 | }); 32 | 33 | it('Check toJSON function', () => { 34 | assert.deepEqual({ type: 'stroke', x: obj.x, y: obj.y, t: obj.t }, StrokeComponent.toJSON(stroke)); 35 | }); 36 | }); 37 | 38 | describe('workflow', () => { 39 | const stroke = StrokeComponent.createStrokeComponent(); 40 | 41 | const pointsNb = 10; 42 | 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 }; 43 | it(`Check addPoint (adding ${pointsNb} points)`, () => { 44 | for (let i = 0; i < pointsNb; i++) { 45 | StrokeComponent.addPoint(stroke, { x: i, y: i * 2, t: i * 3 }); 46 | } 47 | assert.deepEqual(filledStroke, stroke); 48 | }); 49 | 50 | const point = { x: 5, y: 10, t: 15, p: 0.6372367375521082, l: 11.180339887498949 }; 51 | it('Check getPointByIndex', () => { 52 | assert.deepEqual(point, StrokeComponent.getPointByIndex(stroke, 5)); 53 | }); 54 | 55 | 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 }; 56 | it('Check slice', () => { 57 | assert.deepEqual(slicedStroke, StrokeComponent.slice(stroke, 5)); 58 | }); 59 | }); 60 | 61 | // TODO Test all other function 62 | }); 63 | -------------------------------------------------------------------------------- /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 { testLogger as logger } from '../../../../src/configuration/LoggerConfig'; 5 | import * as DefaultConfiguration from '../../../../src/configuration/DefaultConfiguration'; 6 | import * as DefaultBehaviors from '../../../../src/configuration/DefaultBehaviors'; 7 | 8 | const defaultBehaviors = DefaultBehaviors.overrideDefaultBehaviors(); 9 | 10 | configurations.forEach((configuration) => { 11 | const currentConfiguration = DefaultConfiguration.overrideDefaultConfiguration({ recognitionParams: configuration }); 12 | 13 | describe(`Check behaviors for API ${currentConfiguration.recognitionParams.apiVersion} ${currentConfiguration.recognitionParams.type} ${currentConfiguration.recognitionParams.protocol}`, () => { 14 | const behavior = defaultBehaviors.getBehaviorFromConfiguration(defaultBehaviors, currentConfiguration); 15 | 16 | it('grabber', () => { 17 | assert.isDefined(behavior.grabber, 'grabber should be defined'); 18 | }); 19 | 20 | it('stroker', () => { 21 | assert.isDefined(behavior.stroker, 'stroker should be defined'); 22 | let strokerType; 23 | if (currentConfiguration.recognitionParams.apiVersion === 'V3') { 24 | strokerType = 'canvas'; 25 | } else if (currentConfiguration.recognitionParams.apiVersion === 'V4') { 26 | strokerType = currentConfiguration.recognitionParams.protocol === 'WEBSOCKET' ? 'svg' : 'canvas'; 27 | } 28 | assert.strictEqual(behavior.stroker.getInfo().type, strokerType); 29 | }); 30 | 31 | it('renderer', () => { 32 | assert.isDefined(behavior.renderer, 'renderer should be defined'); 33 | let rendererType; 34 | if (currentConfiguration.recognitionParams.apiVersion === 'V3') { 35 | rendererType = 'canvas'; 36 | } else if (currentConfiguration.recognitionParams.apiVersion === 'V4') { 37 | rendererType = currentConfiguration.recognitionParams.protocol === 'WEBSOCKET' ? 'svg' : 'canvas'; 38 | } 39 | assert.strictEqual(behavior.renderer.getInfo().type, rendererType); 40 | }); 41 | 42 | it('recognizer', () => { 43 | assert.isDefined(behavior.recognizer, 'recognizer should be defined'); 44 | assert.include(behavior.recognizer.getInfo().types, currentConfiguration.recognitionParams.type); 45 | assert.strictEqual(behavior.recognizer.getInfo().protocol, currentConfiguration.recognitionParams.protocol); 46 | assert.strictEqual(behavior.recognizer.getInfo().apiVersion, currentConfiguration.recognitionParams.apiVersion); 47 | // assert.strictEqual(defaultBehaviors.optimizedParameters.exportContentTriggerOn, trigger, `${trigger} should be the default value for ${behavior} exportContentTriggerOn`); 48 | }); 49 | 50 | it('callbacks', () => { 51 | assert.isDefined(behavior.callbacks, 'callbacks should be defined'); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /examples/experimental/rest_analyzer_cdk32.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | REST Analyzer v3 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 40 |
41 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /examples/v4/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 |
22 | 35 |
36 |
37 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/experimental/rest_shape.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | REST Shape v3 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 40 |
41 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /test/lib/inks/equation.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | 530, 5 | 533, 6 | 537, 7 | 538, 8 | 540, 9 | 541, 10 | 542, 11 | 544, 12 | 545, 13 | 546, 14 | 548, 15 | 548, 16 | 552, 17 | 555, 18 | 558, 19 | 561, 20 | 565, 21 | 569, 22 | 573, 23 | 577, 24 | 582, 25 | 583, 26 | 585, 27 | 588, 28 | 589, 29 | 591, 30 | 593, 31 | 593, 32 | 593, 33 | 592, 34 | 591, 35 | 591, 36 | 590, 37 | 589, 38 | 588, 39 | 593, 40 | 598, 41 | 604, 42 | 613, 43 | 627, 44 | 646, 45 | 665, 46 | 682, 47 | 697, 48 | 713, 49 | 727, 50 | 738, 51 | 745, 52 | 751, 53 | 756, 54 | 762, 55 | 768, 56 | 772 57 | ], 58 | [ 59 | 93, 60 | 95, 61 | 99, 62 | 102, 63 | 107, 64 | 112, 65 | 117, 66 | 123, 67 | 127, 68 | 130, 69 | 133, 70 | 136, 71 | 138, 72 | 141, 73 | 143, 74 | 144, 75 | 144, 76 | 141, 77 | 136, 78 | 130, 79 | 124, 80 | 118, 81 | 112, 82 | 107, 83 | 101, 84 | 95, 85 | 90, 86 | 84, 87 | 79, 88 | 76, 89 | 72, 90 | 68, 91 | 64, 92 | 59, 93 | 55, 94 | 54, 95 | 54, 96 | 54, 97 | 54, 98 | 54, 99 | 52, 100 | 52, 101 | 49, 102 | 49, 103 | 49, 104 | 49, 105 | 49, 106 | 49, 107 | 49, 108 | 49, 109 | 49, 110 | 48, 111 | 48 112 | ] 113 | ], 114 | [ 115 | [ 116 | 669, 117 | 666, 118 | 665, 119 | 665, 120 | 668, 121 | 672, 122 | 676, 123 | 680, 124 | 684, 125 | 687, 126 | 689, 127 | 689, 128 | 690, 129 | 690, 130 | 688, 131 | 686, 132 | 684, 133 | 681, 134 | 678, 135 | 675, 136 | 670, 137 | 667, 138 | 664, 139 | 661, 140 | 658, 141 | 655, 142 | 654, 143 | 657, 144 | 661, 145 | 664, 146 | 668, 147 | 672, 148 | 677, 149 | 682, 150 | 689, 151 | 700, 152 | 712, 153 | 720, 154 | 723, 155 | 720 156 | ], 157 | [ 158 | 81, 159 | 80, 160 | 77, 161 | 74, 162 | 75, 163 | 76, 164 | 77, 165 | 79, 166 | 81, 167 | 84, 168 | 87, 169 | 90, 170 | 93, 171 | 97, 172 | 102, 173 | 105, 174 | 109, 175 | 112, 176 | 114, 177 | 118, 178 | 121, 179 | 123, 180 | 125, 181 | 127, 182 | 130, 183 | 133, 184 | 136, 185 | 137, 186 | 138, 187 | 138, 188 | 138, 189 | 138, 190 | 138, 191 | 137, 192 | 136, 193 | 136, 194 | 135, 195 | 133, 196 | 133, 197 | 132 198 | ] 199 | ] 200 | ] -------------------------------------------------------------------------------- /src/recognizer/common/v3/Cdkv3CommonShapeRecognizer.js: -------------------------------------------------------------------------------- 1 | import { recognizerLogger as logger } from '../../../configuration/LoggerConfig'; 2 | import * as InkModel from '../../../model/InkModel'; 3 | 4 | /** 5 | * Get style for the strokes matching the ink ranges 6 | * @param {Model} model 7 | * @param {Array} inkRanges 8 | * @return {{color: String, width: Number}} Style to apply 9 | */ 10 | export function getStyleFromInkRanges(model, inkRanges) { 11 | let strokes = model.rawStrokes; 12 | if (inkRanges && (inkRanges.length > 0)) { 13 | strokes = inkRanges 14 | .map(inkRange => InkModel.extractStrokesFromInkRange(model, inkRange.stroke ? inkRange.stroke : inkRange.firstStroke, inkRange.stroke ? inkRange.stroke : inkRange.lastStroke, inkRange.firstPoint, inkRange.lastPoint)) 15 | .reduce((a, b) => a.concat(b)); 16 | } 17 | // FIXME hack to apply the rendering param of the first element' stroke 18 | return { 19 | color: strokes[0].color, 20 | width: strokes[0].width 21 | }; 22 | } 23 | 24 | /** 25 | * Extract recognized symbols from recognition output 26 | * @param {Model} model Current model 27 | * @param {Object} segment Shape recognition output 28 | * @return {Array} Recognized symbols 29 | */ 30 | export function extractShapeSymbols(model, segment) { 31 | if (segment.candidates && segment.candidates.length > 0) { 32 | const selectedCandidate = segment.candidates[segment.selectedCandidateIndex]; 33 | switch (selectedCandidate.type) { 34 | case 'notRecognized': 35 | if (segment.inkRanges && segment.inkRanges.length > 0) { 36 | return segment.inkRanges 37 | .map(inkRange => InkModel.extractStrokesFromInkRange(model, inkRange.firstStroke, inkRange.lastStroke, inkRange.firstPoint, inkRange.lastPoint)) 38 | .reduce((a, b) => a.concat(b)); 39 | } 40 | return []; 41 | case 'recognizedShape': 42 | return selectedCandidate.primitives; 43 | default: 44 | return []; 45 | } 46 | } 47 | return []; 48 | } 49 | 50 | /** 51 | * Extract the recognized symbols 52 | * @param {Model} model Current model 53 | * @return {Array} Recognized symbols 54 | */ 55 | export function extractRecognizedSymbols(model) { 56 | if (model.rawResults && 57 | model.rawResults.exports && 58 | model.rawResults.exports.result && 59 | model.rawResults.exports.result.segments) { 60 | return model.rawResults.exports.result.segments 61 | .map((segment) => { 62 | const style = getStyleFromInkRanges(model, segment.inkRanges); 63 | return extractShapeSymbols(model, segment).map(primitive => Object.assign(primitive, style)); 64 | }) 65 | .reduce((a, b) => a.concat(b)); 66 | } 67 | return []; 68 | } 69 | 70 | /** 71 | * Extract the exports 72 | * @param {Model} model Current model 73 | * @return {Object} exports 74 | */ 75 | export function extractExports(model) { 76 | // We recopy the recognized strokes to flag them as toBeRemove if they are scratched out or map with a symbol 77 | if (model.rawResults && 78 | model.rawResults.exports && 79 | model.rawResults.exports.result && 80 | model.rawResults.exports.result.segments) { 81 | return { 82 | SEGMENTS: model.rawResults.exports.result.segments 83 | }; 84 | } 85 | return {}; 86 | } 87 | -------------------------------------------------------------------------------- /test/nightwatch/commands/waitUntil.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const events = require('events'); 3 | 4 | const TIMEOUT_RETRY_INTERVAL = 500; 5 | 6 | function waitUntil() { 7 | events.EventEmitter.call(this); 8 | this.startTimeInMilliseconds = null; 9 | } 10 | 11 | util.inherits(waitUntil, events.EventEmitter); 12 | 13 | /** 14 | * The purpose of this command is to serve as a base for waitUntil_ commands. It will run the getActual function until 15 | * the predicate function return true or the timeout is reached. At that point, the assertion function will be called. 16 | * @param getActual {Function} - should passe the found value to its callback. The callback will be passed as the only 17 | * argument. 18 | * @param predicate {Function} - the wait will end when this return true. The actual value is passed as the only 19 | * argument. 20 | * @param assertion {Function} - the assertion to make. The assertion should pass when the predicate return true. This 21 | * function will be passed the actual value and the message. 22 | * @param timeoutInMilliseconds {Number} - the Number of milliseconds to wait before timing out and failing. 23 | * @param message {String} - the message to attach to the assertion. The elapsed time will be appended to this. 24 | * @return custom command waitUntil, which can be accessed as browser.waitUntil(args); 25 | */ 26 | waitUntil.prototype.command = function waitUntilCommand(getActual, predicate, timeoutInMilliseconds, callback) { 27 | let message = 'WaitUntil'; 28 | this.startTimeInMilliseconds = new Date().getTime(); 29 | const self = this; 30 | 31 | function checkResult(actual, loadedTimeInMilliseconds) { 32 | if (predicate(actual)) { 33 | message += ' was available after ' 34 | + (loadedTimeInMilliseconds - self.startTimeInMilliseconds) + ' milliseconds.'; 35 | } else { 36 | message += ' timed out after ' + timeoutInMilliseconds + ' milliseconds.'; 37 | } 38 | callback(actual, message); 39 | self.emit('complete'); 40 | } 41 | 42 | this.check(getActual, predicate, checkResult, timeoutInMilliseconds); 43 | 44 | return this; 45 | }; 46 | 47 | waitUntil.prototype.check = function waitUntilCheck(getActual, predicate, callback, maxTimeInMilliseconds) { 48 | const self = this; 49 | 50 | function checkResult(result) { 51 | // If the argument passed to the callback is an object, it is assumed that the format is of the argument passed 52 | // to callbacks by the Nightwatch API, in which the object has a "value" attribute with the actual information. 53 | let resultValue; 54 | if (typeof result !== 'object') { 55 | resultValue = result; 56 | // eslint-disable-next-line no-prototype-builtins 57 | } else if (result.hasOwnProperty('value')) { 58 | resultValue = result.value; 59 | } else { 60 | self.error('Result object does not have a value.'); 61 | return; 62 | } 63 | 64 | function deferredCheck() { 65 | self.check(getActual, predicate, callback, maxTimeInMilliseconds); 66 | } 67 | 68 | const now = new Date().getTime(); 69 | if (predicate(resultValue)) { 70 | callback(resultValue, now); 71 | } else if (now - self.startTimeInMilliseconds < maxTimeInMilliseconds) { 72 | setTimeout(deferredCheck, TIMEOUT_RETRY_INTERVAL); 73 | } else { 74 | callback(resultValue, null); 75 | } 76 | } 77 | 78 | getActual(checkResult); 79 | }; 80 | 81 | module.exports = waitUntil; 82 | -------------------------------------------------------------------------------- /assets/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /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 | 80 | export function importStrokeGroups(editorParam, strokeGroups) { 81 | strokeGroups.forEach((group) => { 82 | group.strokes.forEach((strokeFromGroup) => { 83 | InkModel.addStroke(editorParam.model, strokeFromGroup); 84 | InkModel.addStrokeToGroup(editorParam.model, strokeFromGroup, group.penStyle); 85 | }); 86 | }); 87 | editorParam.renderer.drawModel(editorParam.rendererContext, editorParam.model, editorParam.stroker); 88 | } 89 | -------------------------------------------------------------------------------- /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 | param = { recognitionParams: { type: 'MUSIC', v3: { musicParameter: { divisions: 480, staff: { top: 100, count: 5, gap: 20 }, clef: { symbol: 'G', octave: 0, line: 2 } } } } }; 30 | const defaultMusicSymbols = [{ type: 'staff', top: 100, count: 5, gap: 20 }, { type: 'clef', value: { symbol: 'G', octave: 0, yAnchor: 160 }, boundingBox: { height: 150, width: 56.25, x: 0, y: 70 } }]; 31 | it(`Check defaultSymbols for ${JSON.stringify(param)}`, () => { 32 | assert.deepEqual(model.defaultSymbols, defaultMusicSymbols); 33 | }); 34 | }); 35 | describe('workflow', () => { 36 | const model = InkModel.createModel(); 37 | 38 | it('Creating a model and update pending strokes', () => { 39 | const updatedModel1 = InkModel.initPendingStroke(model, { x: 1, y: 1 }); 40 | const updatedModel2 = InkModel.appendToPendingStroke(updatedModel1, { x: 2, y: 2 }); 41 | const updatedModel3 = InkModel.appendToPendingStroke(updatedModel2, { x: 3, y: 3 }); 42 | const updatedModel4 = InkModel.endPendingStroke(updatedModel3, { x: 4, y: 4 }); 43 | logger.debug('Last model is ', updatedModel4); 44 | assert.deepEqual(model, updatedModel4); 45 | }); 46 | 47 | it('Should clone model', () => { 48 | const copy = InkModel.cloneModel(model); 49 | assert.equal(model.currentStroke, copy.currentStroke); 50 | assert.sameDeepMembers(model.rawStrokes, copy.rawStrokes); 51 | assert.equal(model.lastPositions.lastReceivedPosition, copy.lastPositions.lastReceivedPosition); 52 | assert.equal(model.lastPositions.lastSentPosition, copy.lastPositions.lastSentPosition); 53 | assert.sameDeepMembers(model.defaultSymbols, copy.defaultSymbols); 54 | assert.equal(model.recognizedSymbols, copy.recognizedSymbols); 55 | assert.equal(model.rawResults.exports, copy.rawResults.exports); 56 | assert.equal(model.rawResults.convert, copy.rawResults.convert); 57 | assert.equal(model.creationTime, copy.creationTime); 58 | }); 59 | 60 | it('Should merge models', () => { 61 | const modelToMerge = InkModel.cloneModel(model); 62 | modelToMerge.currentStroke = { x: 1, y: 1 }; 63 | 64 | const mergedModel = InkModel.mergeModels(modelToMerge, model); 65 | assert.equal(mergedModel.recognizedSymbols, modelToMerge.recognizedSymbols); 66 | }); 67 | 68 | // TODO Test all other function 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /examples/v3/rest_math.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | REST Math v3 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 37 |
38 |
39 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/recognizer/rest/v3/Cdkv3RestTextRecognizer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { recognizerLogger as logger } from '../../../configuration/LoggerConfig'; 3 | import Constants from '../../../configuration/Constants'; 4 | import * as InkModel from '../../../model/InkModel'; 5 | import * as StrokeComponent from '../../../model/StrokeComponent'; 6 | import * as CryptoHelper from '../../CryptoHelper'; 7 | import * as Cdkv3RestRecognizerUtil from './Cdkv3RestRecognizerUtil'; 8 | import * as Cdkv3CommonTextRecognizer from '../../common/v3/Cdkv3CommonTextRecognizer'; 9 | 10 | export { init, close, clear, reset } from '../../DefaultRecognizer'; 11 | 12 | /** 13 | * Recognizer configuration 14 | * @type {RecognizerInfo} 15 | */ 16 | export const textRestV3Configuration = { 17 | types: [Constants.RecognitionType.TEXT], 18 | protocol: Constants.Protocol.REST, 19 | apiVersion: 'V3', 20 | availableTriggers: { 21 | exportContent: [ 22 | Constants.Trigger.QUIET_PERIOD, 23 | Constants.Trigger.DEMAND 24 | ] 25 | } 26 | }; 27 | 28 | /** 29 | * Get the configuration supported by this recognizer 30 | * @return {RecognizerInfo} 31 | */ 32 | export function getInfo() { 33 | return textRestV3Configuration; 34 | } 35 | 36 | /** 37 | * Internal function to build the payload to ask for a recognition. 38 | * @param {RecognizerContext} recognizerContext 39 | * @param {Model} model 40 | * @return {Object} 41 | */ 42 | export function buildInput(recognizerContext, model) { 43 | const configuration = recognizerContext.editor.configuration; 44 | const input = { 45 | inputUnits: [{ 46 | textInputType: 'MULTI_LINE_TEXT', 47 | // As Rest TEXT recognition is non incremental wa add the already recognized strokes 48 | components: model.rawStrokes.map(stroke => StrokeComponent.toJSON(stroke)) 49 | }] 50 | }; 51 | Object.assign(input, { textParameter: configuration.recognitionParams.v3.textParameter }); // Building the input with the suitable parameters 52 | 53 | logger.debug(`input.inputUnits[0].components size is ${input.inputUnits[0].components.length}`); 54 | 55 | const data = { 56 | instanceId: recognizerContext ? recognizerContext.instanceId : undefined, 57 | applicationKey: configuration.recognitionParams.server.applicationKey, 58 | textInput: JSON.stringify(input) 59 | }; 60 | 61 | if (configuration.recognitionParams.server.hmacKey) { 62 | data.hmac = CryptoHelper.computeHmac(data.textInput, configuration.recognitionParams.server.applicationKey, configuration.recognitionParams.server.hmacKey); 63 | } 64 | InkModel.updateModelSentPosition(model); 65 | return data; 66 | } 67 | 68 | function resultCallback(model, res, callback) { 69 | logger.debug('Cdkv3RestTextRecognizer result callback', model); 70 | const modelReference = InkModel.updateModelReceivedPosition(model); 71 | modelReference.rawResults.exports = res; 72 | modelReference.exports = Cdkv3CommonTextRecognizer.extractExports(model); 73 | logger.debug('Cdkv3RestTextRecognizer model updated', modelReference); 74 | callback(undefined, modelReference, Constants.EventType.EXPORTED, Constants.EventType.IDLE); 75 | } 76 | 77 | /** 78 | * Export content 79 | * @param {RecognizerContext} recognizerContext Current recognizer context 80 | * @param {Model} model Current model 81 | * @param {RecognizerCallback} callback 82 | */ 83 | export function export_(recognizerContext, model, callback) { 84 | Cdkv3RestRecognizerUtil.postMessage('/api/v3.0/recognition/rest/text/doSimpleRecognition.json', recognizerContext, model, buildInput) 85 | .then(res => resultCallback(model, res, callback)) 86 | .catch(err => callback(err, model)); 87 | } 88 | -------------------------------------------------------------------------------- /src/recognizer/rest/v3/Cdkv3RestMathRecognizer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { recognizerLogger as logger } from '../../../configuration/LoggerConfig'; 3 | import Constants from '../../../configuration/Constants'; 4 | import * as InkModel from '../../../model/InkModel'; 5 | import * as StrokeComponent from '../../../model/StrokeComponent'; 6 | import * as CryptoHelper from '../../CryptoHelper'; 7 | import * as CdkCommonUtil from '../../common/CdkCommonUtil'; 8 | import * as Cdkv3RestRecognizerUtil from './Cdkv3RestRecognizerUtil'; 9 | import * as Cdkv3CommonMathRecognizer from '../../common/v3/Cdkv3CommonMathRecognizer'; 10 | 11 | export { init, close, clear, reset } from '../../DefaultRecognizer'; 12 | 13 | /** 14 | * Recognizer configuration 15 | * @type {RecognizerInfo} 16 | */ 17 | export const mathRestV3Configuration = { 18 | types: [Constants.RecognitionType.MATH], 19 | protocol: Constants.Protocol.REST, 20 | apiVersion: 'V3', 21 | availableTriggers: { 22 | exportContent: [ 23 | Constants.Trigger.QUIET_PERIOD, 24 | Constants.Trigger.DEMAND 25 | ] 26 | } 27 | }; 28 | 29 | /** 30 | * Get the configuration supported by this recognizer 31 | * @return {RecognizerInfo} 32 | */ 33 | export function getInfo() { 34 | return mathRestV3Configuration; 35 | } 36 | 37 | /** 38 | * Internal function to build the payload to ask for a recognition. 39 | * @param {RecognizerContext} recognizerContext 40 | * @param {Model} model 41 | * @return {Object} 42 | */ 43 | function buildInput(recognizerContext, model) { 44 | const configuration = recognizerContext.editor.configuration; 45 | const input = { 46 | // As Rest MATH recognition is non incremental we add the already recognized strokes 47 | components: model.rawStrokes.map(stroke => StrokeComponent.toJSON(stroke)) 48 | }; 49 | Object.assign(input, configuration.recognitionParams.v3.mathParameter); // Building the input with the suitable parameters 50 | 51 | logger.debug(`input.components size is ${input.components.length}`); 52 | 53 | const data = { 54 | instanceId: recognizerContext ? recognizerContext.instanceId : undefined, 55 | applicationKey: configuration.recognitionParams.server.applicationKey, 56 | mathInput: JSON.stringify(input) 57 | }; 58 | 59 | if (configuration.recognitionParams.server.hmacKey) { 60 | data.hmac = CryptoHelper.computeHmac(data.mathInput, configuration.recognitionParams.server.applicationKey, configuration.recognitionParams.server.hmacKey); 61 | } 62 | InkModel.updateModelSentPosition(model); 63 | return data; 64 | } 65 | 66 | function resultCallback(model, res, callback) { 67 | logger.debug('Cdkv3RestMathRecognizer result callback', model); 68 | const modelReference = InkModel.updateModelReceivedPosition(model); 69 | modelReference.rawResults.exports = res; 70 | modelReference.recognizedSymbols = Cdkv3CommonMathRecognizer.extractRecognizedSymbols(model); 71 | modelReference.exports = CdkCommonUtil.extractExports(model); 72 | logger.debug('Cdkv3RestMathRecognizer model updated', modelReference); 73 | callback(undefined, modelReference, Constants.EventType.EXPORTED, Constants.EventType.IDLE); 74 | } 75 | 76 | /** 77 | * Export content 78 | * @param {RecognizerContext} recognizerContext Current recognizer context 79 | * @param {Model} model Current model 80 | * @param {RecognizerCallback} callback 81 | */ 82 | export function export_(recognizerContext, model, callback) { 83 | return Cdkv3RestRecognizerUtil.postMessage('/api/v3.0/recognition/rest/math/doSimpleRecognition.json', recognizerContext, model, buildInput) 84 | .then(res => resultCallback(model, res, callback)) 85 | .catch(err => callback(err, model)); 86 | } 87 | -------------------------------------------------------------------------------- /test/lib/inks/equation3.json: -------------------------------------------------------------------------------- 1 | [[[73, 2 | 74, 3 | 74, 4 | 75, 5 | 75, 6 | 76, 7 | 77, 8 | 82, 9 | 85, 10 | 89, 11 | 93, 12 | 95, 13 | 95, 14 | 95, 15 | 96, 16 | 96, 17 | 96, 18 | 96, 19 | 95, 20 | 94, 21 | 93, 22 | 92, 23 | 92, 24 | 92, 25 | 92, 26 | 92, 27 | 92, 28 | 92, 29 | 92, 30 | 92, 31 | 91, 32 | 87, 33 | 83, 34 | 78, 35 | 73, 36 | 69, 37 | 69, 38 | 69, 39 | 70, 40 | 72, 41 | 77, 42 | 81, 43 | 84, 44 | 90, 45 | 94, 46 | 97, 47 | 101, 48 | 107, 49 | 110, 50 | 113, 51 | 119, 52 | 122], 53 | [168, 54 | 173, 55 | 176, 56 | 179, 57 | 182, 58 | 185, 59 | 188, 60 | 189, 61 | 188, 62 | 185, 63 | 182, 64 | 177, 65 | 173, 66 | 170, 67 | 165, 68 | 161, 69 | 164, 70 | 168, 71 | 172, 72 | 176, 73 | 181, 74 | 188, 75 | 191, 76 | 198, 77 | 205, 78 | 214, 79 | 224, 80 | 233, 81 | 240, 82 | 244, 83 | 249, 84 | 252, 85 | 252, 86 | 250, 87 | 246, 88 | 242, 89 | 238, 90 | 234, 91 | 231, 92 | 228, 93 | 223, 94 | 220, 95 | 219, 96 | 217, 97 | 214, 98 | 213, 99 | 211, 100 | 209, 101 | 208, 102 | 206, 103 | 202, 104 | 201]], 105 | [[176, 106 | 184, 107 | 189, 108 | 195, 109 | 199], 110 | [171, 111 | 170, 112 | 168, 113 | 167, 114 | 166]], 115 | [[170, 116 | 176, 117 | 185, 118 | 194, 119 | 199, 120 | 202, 121 | 205, 122 | 211, 123 | 214], 124 | [189, 125 | 189, 126 | 189, 127 | 188, 128 | 187, 129 | 187, 130 | 187, 131 | 187, 132 | 187]], 133 | [[270, 134 | 276, 135 | 279, 136 | 282, 137 | 285, 138 | 286, 139 | 286, 140 | 286, 141 | 286, 142 | 283, 143 | 278, 144 | 274, 145 | 271, 146 | 267, 147 | 271, 148 | 274, 149 | 276, 150 | 277, 151 | 278, 152 | 278, 153 | 272, 154 | 269, 155 | 265, 156 | 262, 157 | 259, 158 | 255], 159 | [145, 160 | 145, 161 | 145, 162 | 146, 163 | 149, 164 | 152, 165 | 156, 166 | 161, 167 | 164, 168 | 167, 169 | 168, 170 | 169, 171 | 170, 172 | 170, 173 | 170, 174 | 172, 175 | 175, 176 | 178, 177 | 181, 178 | 184, 179 | 187, 180 | 189, 181 | 191, 182 | 191, 183 | 192, 184 | 192]], 185 | [[311, 186 | 314, 187 | 317, 188 | 321, 189 | 324, 190 | 327, 191 | 331, 192 | 335, 193 | 337, 194 | 337, 195 | 337, 196 | 334, 197 | 331, 198 | 326, 199 | 323, 200 | 320, 201 | 315], 202 | [180, 203 | 175, 204 | 171, 205 | 168, 206 | 167, 207 | 167, 208 | 168, 209 | 171, 210 | 174, 211 | 177, 212 | 181, 213 | 183, 214 | 185, 215 | 186, 216 | 186, 217 | 187, 218 | 187]], 219 | [[349, 220 | 344, 221 | 341, 222 | 338, 223 | 336, 224 | 335, 225 | 335, 226 | 337, 227 | 340, 228 | 343, 229 | 347, 230 | 351, 231 | 354, 232 | 357], 233 | [170, 234 | 170, 235 | 170, 236 | 172, 237 | 175, 238 | 178, 239 | 181, 240 | 184, 241 | 185, 242 | 186, 243 | 186, 244 | 186, 245 | 186, 246 | 186]], 247 | [[381, 248 | 386, 249 | 393, 250 | 400, 251 | 403, 252 | 407, 253 | 410, 254 | 415, 255 | 419], 256 | [181, 257 | 181, 258 | 181, 259 | 181, 260 | 180, 261 | 180, 262 | 180, 263 | 180, 264 | 180]], 265 | [[402, 266 | 401, 267 | 401, 268 | 401, 269 | 401, 270 | 401, 271 | 401, 272 | 401], 273 | [169, 274 | 175, 275 | 178, 276 | 181, 277 | 186, 278 | 190, 279 | 193, 280 | 196]], 281 | [[461, 282 | 457, 283 | 456, 284 | 455, 285 | 455, 286 | 461, 287 | 465, 288 | 469, 289 | 473, 290 | 476, 291 | 478, 292 | 479, 293 | 480, 294 | 481, 295 | 481, 296 | 481, 297 | 480, 298 | 478, 299 | 476, 300 | 473, 301 | 470, 302 | 465, 303 | 460, 304 | 457, 305 | 455, 306 | 462, 307 | 470, 308 | 476, 309 | 480, 310 | 485, 311 | 491, 312 | 495, 313 | 498], 314 | [165, 315 | 160, 316 | 157, 317 | 152, 318 | 149, 319 | 147, 320 | 147, 321 | 147, 322 | 147, 323 | 147, 324 | 150, 325 | 154, 326 | 158, 327 | 162, 328 | 165, 329 | 168, 330 | 171, 331 | 174, 332 | 177, 333 | 181, 334 | 184, 335 | 184, 336 | 186, 337 | 187, 338 | 191, 339 | 193, 340 | 193, 341 | 193, 342 | 193, 343 | 193, 344 | 193, 345 | 193, 346 | 193]]] 347 | -------------------------------------------------------------------------------- /examples/v4/custom_lexicon_text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Custom lexicon text 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | 31 |
32 | 45 |
46 |
47 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /examples/experimental/rest_music_cdk32.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | REST Music v3 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 46 |
47 |
48 |
49 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/v3/websocket_math.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | WEBSOCKET Math v3 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 37 |
38 |
39 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /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 |
22 | 33 |
34 |
35 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_iink_no_guides.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 |
22 | 33 |
34 |
35 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /examples/v4/websocket_text_iink_decoration.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 |
22 | 33 |
34 |
35 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/v4/pointer_events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Pointer events 12 | 13 | 14 | 15 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 | 59 | 60 |
61 | 92 | 93 | 94 | 95 | --------------------------------------------------------------------------------