File

src/lib/myScript/inkPaper.ts

Description

Paper to make writing

Constructor

constructor(element: HTMLElement, options: InkPaperOptions, callback: (data?: any, error?: any) => any)

Creates an instance of InkPaper., error?) => any | void)} [callback]

Parameters :

Methods

setWidth
setWidth(width: number)

Set the width

Parameters :
  • width : Number
Returns: void
setHeight
setHeight(height: number)

Set the height

Parameters :
  • height : Number
Returns: void
setProtocol
setProtocol(protocol: string)

Set the network protocol (REST or WebSocket)

Parameters :
  • protocol
Returns: void
getProtocol
getProtocol()

Get the network protocol (REST or WebSocket)

Returns: string
setType
setType(type: string)

Set recognition type

Parameters :
  • type
Returns: void
getType
getType()

Get recognition type

Returns: string

type

getTimeout
getTimeout()

Get the recognition timeout

Returns: number
setTimeout
setTimeout(timeout: number)

Set the recognition timeout

Parameters :
  • timeout : Number
Returns: void
setPrecision
setPrecision(precision: number)

Set the recognition precision

Parameters :
  • precision : Number
Returns: void
getComponents
getComponents()

Get the default components

setComponents
setComponents(components: AbstractComponent[])

Set the default components

Parameters :
  • components : Array
Returns: void
getApplicationKey
getApplicationKey()

Get the application key

Returns: string
setApplicationKey
setApplicationKey(applicationKey: string)

Set the application key

Parameters :
  • applicationKey : String
Returns: void
getHmacKey
getHmacKey()

Get the HMAC key

Returns: string
setHmacKey
setHmacKey(hmacKey: string)

Set the HMAC key

Parameters :
  • hmacKey : String
Returns: void
setTextParameters
setTextParameters(textParameters: TextParameter)

Set text recognition parameters

Parameters :
Returns: void
getTextParameters
getTextParameters()

Get text recognition parameters

Returns: TextParameter

textParameters

setPenParameters
setPenParameters(penParameters: PenParameters)

Set pen parameters

Parameters :
Returns: void
getPenParameters
getPenParameters()

Get pen parameters

Returns: PenParameters

penParameters

setTypeset
setTypeset(typeset: boolean)

Enable / disable typeset

Parameters :
  • typeset : Boolean
Returns: void
getAvailableLanguages
getAvailableLanguages(inputMode: string)

Get available languages

Parameters :
  • inputMode : String

    input mode

Returns: void
getRenderer
getRenderer()

Get the renderer

Returns: AbstractRenderer
getInkGrabber
getInkGrabber()

Get the ink capturer

Returns: InkGrabber
getRecognizer
getRecognizer()

Get the recognizer

setChangeCallback
setChangeCallback(changeCallback: (data?: InkChangeData) => any)

Set the change callback

Parameters :
  • callback : Function

    callback function

  • callback : Object

    .data The inkPaper state

Returns: void
setResultCallback
setResultCallback(callback: (data?: any, error?: any) => any)

Set the recognition result callback

Parameters :
  • callback : Function

    callback function

  • callback : Object

    .data The recognition result

Returns: void
recognize
recognize()

Recognize

Returns: void | Promise<any>
Private _startRESTRecognition
_startRESTRecognition(components: AbstractComponent[])
Returns: Promise<any>
Private _continueRESTRecognition
_continueRESTRecognition(components: AbstractComponent[], instanceId: string)
Returns: Promise<any>
Private _clearRESTRecognition
_clearRESTRecognition(instanceId: string)
Returns: void
canUndo
canUndo()

Return true if you can undo

Returns: boolean
undo
undo()

Undo

Returns: void
canRedo
canRedo()

Return true if you can redo

Returns: boolean
redo
redo()

Redo

Returns: void
clear
clear()

Clear the ink paper

Returns: void
Private _down
_down(x: number, y: number, t: number | Date)
Parameters :
  • x : Number

    X coordinate

  • y : Number

    Y coordinate

  • t : Date

    timeStamp

Returns: void
Private _move
_move(x: number, y: number, t: number | Date)
Parameters :
  • x : Number

    X coordinate

  • y : Number

    Y coordinate

  • t : Date

    timeStamp

Returns: void
Private _up
_up(x: number, y: number, t: number | Date)
Parameters :
  • x : Number

    X coordinate

  • y : Number

    Y coordinate

  • t : Date

    timeStamp

Returns: void
Private _onResult
_onResult(data: any, err: any)
Returns: void
Private _onChange
_onChange()
Returns: void
Private _renderResult
_renderResult(data: any)
Returns: void
setHost
setHost(host: string)

Set recognition service url

Parameters :
  • host : String
Returns: void
Private setSSL
setSSL(ssl: any)
Returns: void
Private _attachListeners
_attachListeners(element: HTMLElement)

Tool to attach touch events

Parameters :
  • element : Element
Returns: void
Private _initRenderingCanvas
_initRenderingCanvas()
Returns: void
Private _handleMessage
_handleMessage(message: any, error: any)
Parameters :
  • message
  • error
Returns: boolean

false no immediate replay needed, true when the call need to be replay ASAP

getStats
getStats()

Return the stats allowing to monitor what ink size is send to the server.

Returns: Object

Stats objects format {strokesCount : 0, pointsCount : 0, byteSize : 0, humanSize : 0, humanUnit : 'BYTE'} humanUnit could have the values BYTE, BYTES, KiB, MiB

Private getInkAsImageData
getInkAsImageData(marginX: number, marginY: number)
Parameters :
  • marginX

    the horizontal margin to apply (by default 10)

  • marginY

    the vertical margin to apply (by default 10)

Returns: ImageData

Build an ImageData object with content shrink to border of strokes.

Private getInkAsPng
getInkAsPng(marginX: number, marginY: number)
Parameters :
  • marginX

    the horizontal margin to apply (by default 10)

  • marginY

    the vertical margin to apply (by default 10)

Returns: string

Build an String containg dataUrl with content shrink to border of strokes.

Static createCanvas
createCanvas(parent: HTMLElement, id: string, styles: Object)

Tool to create canvas

Parameters :
  • parent : Element
  • id : String
Returns: HTMLCanvasElement
Static getCanvasRatio
getCanvasRatio(canvas: HTMLCanvasElement)

Tool to get canvas ratio (retina display)

Parameters :
  • canvas : Element
Returns: number
Static getCoordinates
getCoordinates(e: any, container: HTMLElement)

Tool to get proper coordinates

Parameters :
  • e : Event
  • element : Element
Returns: Point

Properties

Private _captureCanvas
_captureCanvas: HTMLCanvasElement
Private _components
_components: AbstractComponent[]
Private _element
_element: HTMLElement
Private _initialized
_initialized: boolean
Default value: false
Private _inkGrabber
_inkGrabber: InkGrabber
Private _instanceId
_instanceId: string
Private _lastSentComponentIndex
_lastSentComponentIndex: number
Default value: 0
Private _lastSentHTMLElementIndex
_lastSentHTMLElementIndex: number
Default value: 0
Private _redoComponents
_redoComponents: AbstractComponent[]
Private _renderingCanvas
_renderingCanvas: HTMLCanvasElement
Private _selectedRecognizer
_selectedRecognizer: AbstractRecognizer | AbstractWSRecognizer
Private _selectedRenderer
_selectedRenderer: AbstractRenderer
Private _selectedRESTRecognizer
_selectedRESTRecognizer: AbstractRecognizer
Private _selectedWSRecognizer
_selectedWSRecognizer: AbstractWSRecognizer
Private _textRecognizer
_textRecognizer: TextRecognizer
Private _textRenderer
_textRenderer: TextRenderer
Private _textWSRecognizer
_textWSRecognizer: TextWSRecognizer
Private _timerId
_timerId: number
Private applicationKey
applicationKey: string
canvasRatio
canvasRatio: number
Private changeCallback
changeCallback: (data?: InkChangeData) => any
event
event: { addDomListener: (element: any, useCapture: any, myfunction: any) => void; }
Private hmacKey
hmacKey: string
Private isStarted
isStarted: boolean
Default value: false
Private options
options: InkPaperOptions
Private resultCallback
resultCallback: (data?: any, error?: any) => any
Private timeout
timeout: number
Private updatedModel
updatedModel: any
import {
	RecognitionType,
	Protocol,
	Point,
} from './index';

import { PenParameters } from './common/penParameters';

import { TextParameter } from './input/text/textParameter';
import { AbstractComponent } from './input/generic/components/abstractComponent';
import { StrokeComponent } from './input/generic/components/strokeComponent';

import { AbstractRecognizer } from './recognition/abstractRecognizer';
import { AbstractWSRecognizer } from './recognition/abstractWSRecognizer';
import { TextRecognizer } from './recognition/textRecognizer';
import { TextWSRecognizer } from './recognition/textWSRecognizer';

import { InkGrabber } from './rendering/inkGrabber';
import { AbstractRenderer } from './rendering/abstractRenderer';
import { TextRenderer } from './rendering/textRenderer';
import { ImageRenderer } from './rendering/imageRenderer';

import * as Q from 'q';

const captureCanvasStyles = {
	position: 'absolute',
	height: '100%',
	width: '100%',
	'z-index': 1,
	top: 0,
	right: 0,
	'touch-action': 'none'
};

const renderingCanvasStyles = {
	position: 'absolute',
	height: '100%',
	width: '100%',
	'z-index': 1,
	top: 0,
	right: 0,
	'touch-action': 'none'
};

export interface InkPaperOptions {
	applicationKey: string;
	hmacKey: string;
	host?: string;
	type?: string;
	protocol?: string;
	ssl?: boolean;
	width?: number;
	height?: number;
	timeout?: number;
	typeset?: boolean;
	components?: AbstractComponent[];
	textParameters?: TextParameter;
	penParameters?: PenParameters;
	precision?: number
}

export interface InkChangeData {
	canUndo: boolean;
	undoLength: number;
	canRedo: boolean;
	redoLength: number;
}

/**
 * Paper to make writing
 * 
 * @export
 * @class InkPaper
 */
export class InkPaper {

	private options: InkPaperOptions;

	private _element: HTMLElement;
	private _instanceId: string;
	private _timerId: number;
	private _initialized: boolean = false;
	private _lastSentHTMLElementIndex: number = 0;
	private _components: AbstractComponent[] = [];
	private _redoComponents: AbstractComponent[] = [];

	// Capture
	private _captureCanvas: HTMLCanvasElement;
	private _inkGrabber: InkGrabber;

	// Rendering
	private _renderingCanvas: HTMLCanvasElement;
	private _selectedRenderer: AbstractRenderer;
	private _textRenderer: TextRenderer;

	// Recognition
	private _selectedRecognizer: AbstractRecognizer | AbstractWSRecognizer;
	private _selectedRESTRecognizer: AbstractRecognizer;
	private _selectedWSRecognizer: AbstractWSRecognizer;
	private _textRecognizer: TextRecognizer;
	private _textWSRecognizer: TextWSRecognizer;

	private isStarted: boolean = false;
	private timeout: number;
	private applicationKey: string;
	private hmacKey: string;

	private changeCallback: (data?: InkChangeData) => any | void;
	private resultCallback: (data?, error?) => any | void;
	private updatedModel;

	private _lastSentComponentIndex: number = 0;

	canvasRatio: number;

	/**
	 * Creates an instance of InkPaper.
	 * @param {HTMLElement} element 
	 * @param {InkPaperOptions} [options] 
	 * @param {((data?, error?) => any | void)} [callback] 
	 * 
	 * @memberof InkPaper
	 */
	constructor(
		element: HTMLElement,
		options?: InkPaperOptions,
		callback?: (data?, error?) => any | void
	) {

		// default options
		this.options = <InkPaperOptions>Object.assign({
			type: RecognitionType.TEXT,
			protocol: Protocol.REST,
			ssl: true,
			width: 400,
			height: 300,
			timeout: 2000,
			typeset: false,
			components: [],
			textParameters: new TextParameter(),
		}, <Object>options);

		this._element = element;
		this.resultCallback = callback;

		// Capture
		let tempCanvas = InkPaper.createCanvas(element, 'ms-temp-canvas');
		this.canvasRatio = InkPaper.getCanvasRatio(tempCanvas);
		element.removeChild(tempCanvas); //this.canvasRatio = 1;

		this._captureCanvas = InkPaper.createCanvas(element, 'ms-capture-canvas', 
			captureCanvasStyles);

		this._inkGrabber = new InkGrabber(this._captureCanvas.getContext('2d'));

		// Rendering
		this._renderingCanvas = InkPaper.createCanvas(element, 'ms-rendering-canvas', 
			renderingCanvasStyles);
		this._textRenderer = new TextRenderer(this._renderingCanvas.getContext('2d'));

		// Recognition
		this._textRecognizer = new TextRecognizer();
		this._textWSRecognizer = new TextWSRecognizer(this._handleMessage.bind(this));

		this._attachListeners(element);

		// Recognition type
		this.setType(this.options.type);

		this.setHost(this.options.host);
		this.setSSL(this.options.ssl);

		this.setTextParameters(this.options.textParameters);
		this.setProtocol(this.options.protocol);
		this.setTimeout(this.options.timeout);
		this.setApplicationKey(this.options.applicationKey);
		this.setHmacKey(this.options.hmacKey);

		this.setPenParameters(this.options.penParameters);

		this.setPrecision(this.options.precision);
		this.setTypeset(this.options.typeset);
		this.setComponents(this.options.components);

		this.setWidth(this.options.width);
		this.setHeight(this.options.height);
	}

	/**
	 * Set the width
	 *
	 * @method setWidth
	 * @param {Number} width
	 */
	setWidth(width: number) {
		if (width > 0) {
			this._captureCanvas.width = width * this.canvasRatio;
			this._captureCanvas.style.width = width + 'px';
			this._captureCanvas.getContext('2d').scale(this.canvasRatio, this.canvasRatio);

			this._renderingCanvas.width = width * this.canvasRatio;
			this._renderingCanvas.style.width = width + 'px';
			this._renderingCanvas.getContext('2d').scale(this.canvasRatio, this.canvasRatio);
		}
		this._initRenderingCanvas();
	};

	/**
	 * Set the height
	 *
	 * @method setHeight
	 * @param {Number} height
	 */
	setHeight(height: number) {
		if (height > 0) {
			this._captureCanvas.height = height * this.canvasRatio;
			this._captureCanvas.style.height = height + 'px';
			this._captureCanvas.getContext('2d').scale(this.canvasRatio, this.canvasRatio);

			this._renderingCanvas.height = height * this.canvasRatio;
			this._renderingCanvas.style.height = height + 'px';

			this._renderingCanvas.getContext('2d').scale(this.canvasRatio, this.canvasRatio);
		}
		this._initRenderingCanvas();
	};

	/**
	 * Set the network protocol (REST or WebSocket)
	 *
	 * @param {'REST'|'WebSocket'} protocol
	 */
	setProtocol(protocol: string) {
		switch (protocol) {
			case Protocol.REST:
				this._selectedRecognizer = this._selectedRESTRecognizer;
				break;
			case Protocol.WS:
				this.setTimeout(-1); // FIXME hack to avoid border issues
				this._selectedRecognizer = this._selectedWSRecognizer;
				break;
			default:
				throw new Error('Unknown protocol: ' + protocol);
		}
		this._instanceId = undefined;
		this._initialized = false;
		this._lastSentComponentIndex = 0;
	};

	/**
	 * Get the network protocol (REST or WebSocket)
	 *
	 * @returns {'REST'|'WebSocket'}
	 */
	getProtocol(): string {
		if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
			return Protocol.WS;
		} else {
			return Protocol.REST;
		}
	};

	/**
	 * Set recognition type
	 *
	 * @method setType
	 * @param {'TEXT'|'MATH'|'SHAPE'|'MUSIC'|'ANALYZER'} type
	 */
	setType(type: string) {
		switch (type) {
			case RecognitionType.TEXT:
				this._selectedRenderer = this._textRenderer;
				this._selectedRESTRecognizer = this._textRecognizer;
				this._selectedWSRecognizer = this._textWSRecognizer;
				break;
			default:
				throw new Error('Unknown type: ' + type);
		}
		this._instanceId = undefined;
		this._initialized = false;
		this._lastSentComponentIndex = 0;
	};

	/**
	 * Get recognition type
	 *
	 * @method getType
	 * @returns {'TEXT'|'MATH'|'SHAPE'|'MUSIC'|'ANALYZER'} type
	 */
	getType(): string {
		if (this._selectedRenderer instanceof TextRenderer) {
			return RecognitionType.TEXT;
		}
		throw new Error('Unknown type');
	};

	/**
	 * Get the recognition timeout
	 *
	 * @method getTimeout
	 * @returns {Number}
	 */
	getTimeout(): number {
		return this.timeout;
	};

	/**
	 * Set the recognition timeout
	 *
	 * @method setTimeout
	 * @param {Number} timeout
	 */
	setTimeout(timeout: number) {
		this.timeout = timeout;
	};

	/**
	 * Set the recognition precision
	 *
	 * @method setPrecision
	 * @param {Number} precision
	 */
	setPrecision(precision: number) {
		this._textRecognizer.setPrecision(precision);
		this._textWSRecognizer.setPrecision(precision);
	};

	/**
	 * Get the default components
	 *
	 * @method getComponents
	 * @return {Array} components
	 */
	getComponents(): AbstractComponent[] {
		return this.options.components;
	};

	/**
	 * Set the default components
	 *
	 * @method setComponents
	 * @param {Array} components
	 */
	setComponents(components: AbstractComponent[]) {
		this.options.components = components;
		this._initRenderingCanvas();
	};


	/**
	 * Get the application key
	 *
	 * @method getApplicationKey
	 * @returns {String}
	 */
	getApplicationKey(): string {
		return this.applicationKey;
	};

	/**
	 * Set the application key
	 *
	 * @method setApplicationKey
	 * @param {String} applicationKey
	 */
	setApplicationKey(applicationKey: string) {
		this.applicationKey = applicationKey;
	};

	/**
	 * Get the HMAC key
	 *
	 * @method getHmacKey
	 * @returns {String}
	 */
	getHmacKey(): string {
		return this.hmacKey;
	};

	/**
	 * Set the HMAC key
	 *
	 * @method setHmacKey
	 * @param {String} hmacKey
	 */
	setHmacKey(hmacKey: string) {
		this.hmacKey = hmacKey;
	};

	/**
	 * Set text recognition parameters
	 *
	 * @method setTextParameters
	 * @param {TextParameter} textParameters
	 */
	setTextParameters(textParameters: TextParameter) {
		if (textParameters) {
			if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
				this.isStarted = false;
				this._selectedRecognizer.resetWSRecognition();
			}
			for (let i in textParameters) {
				if (textParameters[i] !== undefined) {
					this._textRecognizer.getParameters()[i] = textParameters[i]; // Override options
					this._textWSRecognizer.getParameters()[i] = textParameters[i]; // Override options
				}
			}
		}
	};

	/**
	 * Get text recognition parameters
	 *
	 * @method getTextParameters
	 * @returns {TextParameter} textParameters
	 */
	getTextParameters(): TextParameter {
		return <TextParameter>this._textRecognizer.getParameters();
	};

	/**
	 * Set pen parameters
	 *
	 * @method setPenParameters
	 * @param {PenParameters} penParameters
	 */
	setPenParameters(penParameters: PenParameters) {
		if (penParameters) {
			for (let i in penParameters) {
				if (penParameters[i] !== undefined) {
					this._selectedRenderer.getParameters()[i] = penParameters[i]; // Override options
				}
			}
			let params = this._selectedRenderer.getParameters();
			this._inkGrabber.setParameters(params); // Override options
			this._textRenderer.setParameters(params); // Override options
		}
	};

	/**
	 * Get pen parameters
	 *
	 * @method getPenParameters
	 * @returns {PenParameters} penParameters
	 */
	getPenParameters(): PenParameters {
		return this._selectedRenderer.getParameters();
	};

	/**
	 * Enable / disable typeset
	 *
	 * @method setTypeset
	 * @param {Boolean} typeset
	 */
	setTypeset(typeset: boolean) {
		this._textRenderer.setTypeset(typeset);
	};

	/**
	 * Get available languages
	 *
	 * @method getAvailableLanguages
	 * @param {String} [inputMode] input mode
	 */
	getAvailableLanguages(inputMode?: string) {
		return this._selectedRESTRecognizer.getAvailableLanguageList(
			this.getApplicationKey(),
			inputMode ? inputMode : (<TextParameter>this._textRecognizer.getParameters()).getInputMode()
		);
	};

	/**
	 * Get the renderer
	 *
	 * @method getRenderer
	 * @returns {AbstractRenderer}
	 */
	getRenderer(): AbstractRenderer {
		return this._selectedRenderer;
	};

	/**
	 * Get the ink capturer
	 *
	 * @method getInkGrabber
	 * @returns {InkGrabber}
	 */
	getInkGrabber(): InkGrabber {
		return this._inkGrabber;
	};

	/**
	 * Get the recognizer
	 *
	 * @method getRecognizer
	 * @returns {AbstractRecognizer}
	 */
	getRecognizer(): AbstractRecognizer {
		return <AbstractRecognizer>this._selectedRecognizer;
	};

	/**
	 * Set the change callback
	 *
	 * @method setChangeCallback
	 * @param {Function} callback callback function
	 * @param {Object} callback.data The inkPaper state
	 */
	setChangeCallback(changeCallback: (data?: InkChangeData) => any | void) {
		this.changeCallback = changeCallback;
	};

	/**
	 * Set the recognition result callback
	 *
	 * @method setResultCallback
	 * @param {Function} callback callback function
	 * @param {Object} callback.data The recognition result
	 */
	setResultCallback(callback: (data?, error?) => any | void) {
		this.resultCallback = callback;
	};

	/**
	 * Recognize
	 *
	 * @method recognize
	 * @returns {Promise}
	 */
	recognize(): Q.Promise<any> | void {
		let input = this.getComponents().concat(this._components);
		if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
			if (this._initialized) {
				let lastInput = input.slice(this._lastSentComponentIndex);

				if (lastInput.length > 0) {
					this._lastSentComponentIndex = input.length;
					if (!this.isStarted) {
						this.isStarted = true;
						return (<AbstractWSRecognizer>this._selectedRecognizer)
							.startWSRecognition(lastInput);
					}
					return (<AbstractWSRecognizer>this._selectedRecognizer)
						.continueWSRecognition(lastInput, this._instanceId);
				}
				return this._renderResult();
			}
		} else {

			if (input.length > 0) {
				if (!this.isStarted) {
					return this._startRESTRecognition(input);
				}
				return this._continueRESTRecognition(input, this._instanceId);
			}
			return this._renderResult();
		}
	};

	private _startRESTRecognition(components: AbstractComponent[]): Q.Promise<any> {
		this._instanceId = undefined;
		return (<AbstractRecognizer>this._selectedRecognizer).doSimpleRecognition(
			this.getApplicationKey(),
			this._instanceId,
			components,
			this.getHmacKey()
		).then(data => {
			if (!this.isStarted) {
				this.isStarted = true;
				this._lastSentComponentIndex = components.length;
				this._instanceId = data.getInstanceId();
				return this._renderResult(data);
			}
		}).catch(error => this._onResult(undefined, error));
	};

	private _continueRESTRecognition(components: AbstractComponent[], instanceId: string): Q.Promise<any> {
		return (<AbstractRecognizer>this._selectedRecognizer).doSimpleRecognition(
			this.getApplicationKey(),
			instanceId,
			components,
			this.getHmacKey()
		).then(data => {
			this._lastSentComponentIndex = this._lastSentComponentIndex + components.length;
			return this._renderResult(data);
		}).catch(error => this._onResult(undefined, error));
	};

	private _clearRESTRecognition(instanceId: string) {
		this._onResult();
	};

	/**
	 * Return true if you can undo
	 *
	 * @method canUndo
	 * @returns {Boolean}
	 */
	canUndo(): boolean {
		return this._components.length > 0;
	};

	/**
	 * Undo
	 *
	 * @method undo
	 */
	undo() {
		if (this.canUndo()) {
			//Remove the scratched state for Math strokes
			this._components.forEach(stroke => {
				stroke.scratchedStroke = false;
			});
			//Remove the latsModel used for Shape
			this.updatedModel = undefined;

			this._redoComponents.push(this._components.pop());

			this._clearRESTRecognition(this._instanceId);

			this._initRenderingCanvas();
			this._onChange();

			this.isStarted = false;
			if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
				this._selectedRecognizer.resetWSRecognition();
			} else {
				clearTimeout(this._timerId);
				if (this.getTimeout() > -1) {
					this._timerId = setTimeout(this.recognize.bind(this), this.getTimeout());
				} else {
					this._onResult();
				}
			}
		}
	};

	/**
	 * Return true if you can redo
	 *
	 * @method canRedo
	 * @returns {Boolean}
	 */
	canRedo(): boolean {
		return this._redoComponents.length > 0;
	};

	/**
	 * Redo
	 *
	 * @method redo
	 */
	redo() {
		if (this.canRedo()) {
			this._components.push(this._redoComponents.pop());

			this._clearRESTRecognition(this._instanceId);

			this._initRenderingCanvas();
			this._onChange();

			if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
				this.recognize();
			} else {
				clearTimeout(this._timerId);
				this.isStarted = false;
				if (this.getTimeout() > -1) {
					this._timerId = setTimeout(this.recognize.bind(this), this.getTimeout());
				} else {
					this._onResult();
				}
			}
		}
	};

	/**
	 * Clear the ink paper
	 *
	 * @method clear
	 */
	clear() {
		this._components = [];
		this._redoComponents = [];
		this._initRenderingCanvas();
		this._clearRESTRecognition(this._instanceId);

		this._onChange();

		if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
			this.isStarted = false;
			this._selectedRecognizer.resetWSRecognition();
		} else {
			clearTimeout(this._timerId);
			if (this.getTimeout() > -1) {
				this._timerId = setTimeout(this.recognize.bind(this), this.getTimeout());
			} else {
				this._onResult();
			}
		}
	};

	event = {
		addDomListener: (element, useCapture, myfunction) => {
			element.addEventListener(useCapture, myfunction);
		}
	};

	/**
	 *
	 * @private
	 * @method _down
	 * @param {Number} x X coordinate
	 * @param {Number} y Y coordinate
	 * @param {Date} [t] timeStamp
	 */
	private _down(x: number, y: number, t: Date | number) {
		clearTimeout(this._timerId);
		let sizeChanged = false;
		if (this._captureCanvas.clientHeight * this.canvasRatio !== this._captureCanvas.height) {
			this._captureCanvas.height = this._captureCanvas.clientHeight * this.canvasRatio;
			this._renderingCanvas.height = this._renderingCanvas.clientHeight * this.canvasRatio;
			sizeChanged = true;
		}

		if (this._captureCanvas.clientWidth * this.canvasRatio !== this._captureCanvas.width) {
			this._captureCanvas.width = this._captureCanvas.clientWidth * this.canvasRatio;
			this._renderingCanvas.width = this._renderingCanvas.clientWidth * this.canvasRatio;
			sizeChanged = true;
		}

		//Safari trash the canvas content when heigth or width are modified.
		if (sizeChanged) {

			this._captureCanvas.getContext('2d').scale(this.canvasRatio, this.canvasRatio);
			this._renderingCanvas.getContext('2d').scale(this.canvasRatio, this.canvasRatio);
			this._initRenderingCanvas();
		}

		if (this.canRedo()) {
			this._redoComponents = [];
			this._onChange();
		}

		this._inkGrabber.startCapture(x, y, t);
	};

	/**
	 *
	 * @private
	 * @method _move
	 * @param {Number} x X coordinate
	 * @param {Number} y Y coordinate
	 * @param {Date} [t] timeStamp
	 */
	private _move(x: number, y: number, t: Date | number) {
		this._inkGrabber.continueCapture(x, y, t);
	};

	/**
	 *
	 * @private
	 * @method _move
	 * @param {Number} x X coordinate
	 * @param {Number} y Y coordinate
	 * @param {Date} [t] timeStamp
	 */
	private _up(x: number, y: number, t: Date | number) {
		this._inkGrabber.endCapture(x, y, t);

		let stroke = this._inkGrabber.getStroke();

		this._inkGrabber.clear();
		this._selectedRenderer.drawComponent(stroke);

		this._components.push(stroke);
		this._onChange();

		if (this._selectedRecognizer instanceof AbstractWSRecognizer) {
			if (!this._selectedRecognizer.isOpen() && !this._selectedRecognizer.isConnecting()) {
				this._selectedRecognizer.open();
			} else {
				this.recognize();
			}
		} else {
			clearTimeout(this._timerId);
			if (this.getTimeout() > -1) {
				this._timerId = setTimeout(this.recognize.bind(this), this.getTimeout());
			}
		}
	};

	private _onResult(data?, err?) {
		if (this.resultCallback) {
			this.resultCallback(data, err);
		}
		if (err) {
			this._element.dispatchEvent(new CustomEvent('error', { detail: err }));
		} else {
			this._element.dispatchEvent(new CustomEvent('success', { detail: data }));
		}
	};

	private _onChange() {
		let data = <InkChangeData>{
			canUndo: this.canUndo(),
			undoLength: this._components.length,
			canRedo: this.canRedo(),
			redoLength: this._redoComponents.length
		};

		if (this.changeCallback) {
			this.changeCallback(data)
		}
		this._element.dispatchEvent(new CustomEvent('changed', { detail: data }));
	};

	private _renderResult(data?) {
		this.updatedModel = this._selectedRenderer.drawRecognitionResult(this.getComponents().concat(this._components), data ? data.getDocument() : undefined);
		this._onResult(data);
		return data;
	};

	/**
	 * Set recognition service url
	 *
	 * @param {String} host
	 */
	setHost(host: string) {
		this._textRecognizer.setHost(host);
		this._textWSRecognizer.setHost(host);
	};

	/**
	 * @private
	 */
	private setSSL(ssl) {
		this._textRecognizer.setSSL(ssl);
		this._textWSRecognizer.setSSL(ssl);
	};

	/**
	 * Tool to attach touch events
	 *
	 * @private
	 * @param {Element} element
	 */
	private _attachListeners(element: HTMLElement) {
		let self = this;
		let pointerId;

		//Desactivation of contextmenu to prevent safari to fire pointerdown only once
		element.addEventListener("contextmenu", function (e) {
			e.preventDefault();
			e.stopPropagation();
			return false;
		});

		element.addEventListener('pointerdown', function (e) {
			if (!pointerId) {
				pointerId = e.pointerId;
				e.preventDefault(); pointerId
				let coord = InkPaper.getCoordinates(e, element);
				self._down(coord.x, coord.y, coord.t);
			}
		}, false);

		element.addEventListener('pointermove', function (e) {
			if (pointerId === e.pointerId) {
				e.preventDefault();

				let coord = InkPaper.getCoordinates(e, element);
				self._move(coord.x, coord.y, coord.t);
			}
		}, false);

		element.addEventListener('pointerup', function (e) {
			if (pointerId === e.pointerId) {
				e.preventDefault();

				let coord = InkPaper.getCoordinates(e, element);
				self._up(coord.x, coord.y, coord.t);

				pointerId = undefined;
			}
		}, false);

		element.addEventListener('pointerleave', function (e) {
			if (pointerId === e.pointerId) {
				e.preventDefault();

				let point = self._inkGrabber.getStroke().getPointByIndex(self._inkGrabber.getStroke().getLastIndexPoint());
				self._up(point.x, point.y, point.t);
				pointerId = undefined;
			}
		}, false);

		element.addEventListener('pointerout', function (e) {
			if (pointerId === e.pointerId) {
				e.preventDefault();

				let point = self._inkGrabber.getStroke().getPointByIndex(self._inkGrabber.getStroke().getLastIndexPoint());
				self._up(point.x, point.y, point.t);
				pointerId = undefined;
			}
		}, false);
	};

	private _initRenderingCanvas() {
		this._selectedRenderer.clear();
		this._selectedRenderer.drawComponents(this.getComponents().concat(this._components));
	};

	/**
	 *
	 * @param message
	 * @param error
	 * @returns {boolean} false no immediate replay needed, true when the call need to be replay ASAP
	 * @private
	 */
	private _handleMessage(message, error?): boolean {
		let replayNeeded = false;
		if (error) {
			replayNeeded = true;
			this._instanceId = undefined;
			this.isStarted = false;
			this._lastSentComponentIndex = 0;
			this._onResult(undefined, error);
		}

		if (message) {
			switch (message.type) {
				case 'open':
					this._selectedWSRecognizer.initWSRecognition(this.getApplicationKey());
					break;
				case 'hmacChallenge':
					this._selectedWSRecognizer.takeUpHmacChallenge(this.getApplicationKey(), message.getChallenge(), this.getHmacKey());
					break;
				case 'init':
					this.isStarted = false;
					this._initialized = true;
					this._instanceId = undefined;
					this._lastSentComponentIndex = 0;
					this.recognize();
					break;
				case 'reset':
					this.isStarted = false;
					this._instanceId = undefined;
					this._lastSentComponentIndex = 0;
					this.recognize();
					break;
				case 'close':
					this._initialized = false;
					this._instanceId = undefined;
					this._lastSentComponentIndex = 0;
					break;
				default:
					this.isStarted = true;
					if (!this._instanceId) {
						this._instanceId = message.getInstanceId();
					}
					this._renderResult(message);
					break;
			}
		}
		return replayNeeded;
	};

	/**
	 * Return the stats allowing to monitor what ink size is send to the server.
	 * @returns Stats objects format {strokesCount : 0, pointsCount : 0, byteSize : 0, humanSize : 0, humanUnit : 'BYTE'} humanUnit could have the values BYTE, BYTES, KiB, MiB
	 */
	getStats(): Object {
		let stats = {
			strokesCount: 0,
			pointsCount: 0,
			byteSize: 0,
			humanSize: 0,
			humanUnit: 'BYTE'
		};

		if (this._components) {
			stats.strokesCount = this._components.length;
			let pointsCount = 0;
			for (let strokeNb = 0; strokeNb < this._components.length; strokeNb++) {
				pointsCount = pointsCount + (<StrokeComponent[]>this._components)[strokeNb].x.length;
			}
			stats.strokesCount = this._components.length;
			stats.pointsCount = pointsCount;
			//We start with 270 as it is the size in bytes. Make a real computation implies to recode a doRecogntion
			let byteSize = 270;
			byteSize = JSON.stringify(this._components).length;
			stats.byteSize = byteSize;
			if (byteSize < 270) {
				stats.humanUnit = 'BYTE';
				stats.byteSize = 0;
				stats.humanSize = 0;
			} else if (byteSize < 2048) {
				stats.humanUnit = 'BYTES';
				stats.humanSize = byteSize;
			} else if (byteSize < 1024 * 1024) {
				stats.humanUnit = 'KiB';
				stats.humanSize = parseFloat((byteSize / 1024).toFixed(2));
			} else {
				stats.humanUnit = 'MiB';
				stats.humanSize = parseFloat((byteSize / 1024 / 1024).toFixed(2));
			}
		}
		return stats;
	};

	/**
	 *
	 * @param marginX the horizontal margin to apply (by default 10)
	 * @param marginY the vertical margin to apply (by default 10)
	 * @returns {ImageData} Build an ImageData object with content shrink to border of strokes.
	 * @private
	 */
	private getInkAsImageData(marginX: number = 10, marginY: number = 10): ImageData {
		//Remove the scratched strokes
		let componentCopy = [];
		this._components.forEach(stroke => {
			if (stroke.scratchedStroke !== true) {
				componentCopy.push(stroke);
			}
		}
		);

		if (!marginX) {
			marginX = 10;
		}
		if (!marginY) {
			marginY = 10;
		}

		if (componentCopy && componentCopy.length > 0) {
			//Initializing min and max
			let minX = componentCopy[0].x[0];
			let maxX = componentCopy[0].x[0];
			let minY = componentCopy[0].y[0];
			let maxY = componentCopy[0].y[0];

			// Computing the min and max for x and y
			for (let strokeNb = 0; strokeNb < componentCopy.length; strokeNb++) {
				let pointCount = componentCopy[strokeNb].x.length;
				for (let pointNb = 0; pointNb < pointCount; pointNb++) {
					let currentX = componentCopy[strokeNb].x[pointNb];
					let currentY = componentCopy[strokeNb].y[pointNb];
					if (currentX < minX) {
						minX = currentX;
					}
					if (currentX > maxX) {
						maxX = currentX;
					}
					if (currentY < minY) {
						minY = currentY;
					}
					if (currentY > maxY) {
						maxY = currentY;
					}
				}
			}
			let nonDisplayCanvas = document.createElement('canvas');
			nonDisplayCanvas.width = (maxX) + (2 * marginX);
			nonDisplayCanvas.height = (maxY) + (2 * marginY)

			let ctx = nonDisplayCanvas.getContext("2d");

			let imageRendered = new ImageRenderer(ctx);
			imageRendered.drawComponents(componentCopy);

			// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData
			return ctx.getImageData(minX - marginX, minY - marginY, (maxX - minX) + (2 * marginX), (maxY - minY) + (2 * marginY));
		}
	};

	/**
	 *
	 * @param marginX the horizontal margin to apply (by default 10)
	 * @param marginY the vertical margin to apply (by default 10)
	 * @returns {String} Build an String containg dataUrl with content shrink to border of strokes.
	 * @private
	 */
	private getInkAsPng(marginX: number = 10, marginY: number = 10): string {
		let imageRenderingCanvas = document.createElement('canvas');
		imageRenderingCanvas.style.display = 'none';

		let imageDataToRender = this.getInkAsImageData();
		imageRenderingCanvas.width = imageDataToRender.width;
		imageRenderingCanvas.style.width = imageDataToRender.width + 'px';
		imageRenderingCanvas.height = imageDataToRender.height;
		imageRenderingCanvas.style.height = imageDataToRender.height + 'px';
		let ctx = imageRenderingCanvas.getContext('2d');
		ctx.putImageData(imageDataToRender, 0, 0);
		return imageRenderingCanvas.toDataURL("image/png");
	};

	/**
	 * Tool to create canvas
	 *
	 * @private
	 * @param {Element} parent
	 * @param {String} id
	 * @returns {Element}
	 */
	static createCanvas(parent: HTMLElement, id: string, styles?: Object): HTMLCanvasElement {
		let count = document.querySelectorAll('canvas[id^=' + id + ']').length;
		let canvas = document.createElement('canvas');
		canvas.id = id + '-' + count;
		if(styles){
			for(let key in styles){
				if(styles.hasOwnProperty(key)){
					canvas.style[key] = styles[key];
				}
			}
		}
		parent.appendChild(canvas);
		return canvas;
	}

	/**
	 * Tool to get canvas ratio (retina display)
	 *
	 * @private
	 * @param {Element} canvas
	 * @returns {Number}
	 */
	static getCanvasRatio(canvas: HTMLCanvasElement): number {
		if (canvas) {
			let context = canvas.getContext('2d'),
				devicePixelRatio = window.devicePixelRatio || 1,
				backingStoreRatio = (<any>context).webkitBackingStorePixelRatio ||
					(<any>context).mozBackingStorePixelRatio ||
					(<any>context).msBackingStorePixelRatio ||
					(<any>context).oBackingStorePixelRatio ||
					(<any>context).backingStorePixelRatio || 1;
			return devicePixelRatio / backingStoreRatio;
		}
		return 1;
	}

	/**
	 * Tool to get proper coordinates
	 *
	 * @private
	 * @param {Event} e
	 * @param {Element} element
	 * @returns {Object}
	 */
	static getCoordinates(e, container: HTMLElement): Point {
		if (e instanceof TouchEvent) e = e.changedTouches[0];
		let rect = container.getBoundingClientRect();
		return <Point>{
			x: e.clientX - rect.left - container.clientLeft,
			y: e.clientY - rect.top - container.clientTop,
			t: e.timeStamp
		};
	}
}

results matching ""

    No results matching ""