PHP Classes

File: toastui/src/js/graphics.js

Recommend this page to a friend!
  Classes of Mark de Leon  >  PHP Document Scanner using SANE or eSCL AirPrint  >  toastui/src/js/graphics.js  >  Download  
File: toastui/src/js/graphics.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: PHP Document Scanner using SANE or eSCL AirPrint
Web interface to scan printed documents
Author: By
Last change:
Date: 1 year ago
Size: 31,898 bytes
 

Contents

Class file image Download
/**
 * @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
 * @fileoverview Graphics module
 */
import snippet from 'tui-code-snippet';
import Promise from 'core-js/library/es6/promise';
import fabric from 'fabric/dist/fabric.require';
import ImageLoader from './component/imageLoader';
import Cropper from './component/cropper';
import Flip from './component/flip';
import Rotation from './component/rotation';
import FreeDrawing from './component/freeDrawing';
import Line from './component/line';
import Text from './component/text';
import Icon from './component/icon';
import Filter from './component/filter';
import Shape from './component/shape';
import CropperDrawingMode from './drawingMode/cropper';
import FreeDrawingMode from './drawingMode/freeDrawing';
import LineDrawingMode from './drawingMode/lineDrawing';
import ShapeDrawingMode from './drawingMode/shape';
import TextDrawingMode from './drawingMode/text';
import consts from './consts';
import util from './util';

const components = consts.componentNames;
const events = consts.eventNames;

const {drawingModes, fObjectOptions} = consts;
const {extend, stamp, isArray, isString, forEachArray, forEachOwnProperties, CustomEvents} = snippet;

const DEFAULT_CSS_MAX_WIDTH = 1000;
const DEFAULT_CSS_MAX_HEIGHT = 800;

const cssOnly = {
    cssOnly: true
};
const backstoreOnly = {
    backstoreOnly: true
};

/**
 * Graphics class
 * @class
 * @param {string|HTMLElement} wrapper - Wrapper's element or selector
 * @param {Object} [option] - Canvas max width & height of css
 *  @param {number} option.cssMaxWidth - Canvas css-max-width
 *  @param {number} option.cssMaxHeight - Canvas css-max-height
 *  @param {boolean} option.useItext - Use IText in text mode
 *  @param {boolean} option.useDragAddIcon - Use dragable add in icon mode
 * @ignore
 */
class Graphics {
    constructor(element, {
        cssMaxWidth,
        cssMaxHeight,
        useItext = false,
        useDragAddIcon = false
    } = {}) {
        /**
         * Fabric image instance
         * @type {fabric.Image}
         */
        this.canvasImage = null;

        /**
         * Max width of canvas elements
         * @type {number}
         */
        this.cssMaxWidth = cssMaxWidth || DEFAULT_CSS_MAX_WIDTH;

        /**
         * Max height of canvas elements
         * @type {number}
         */
        this.cssMaxHeight = cssMaxHeight || DEFAULT_CSS_MAX_HEIGHT;

        /**
         * Use Itext mode for text component
         * @type {boolean}
         */
        this.useItext = useItext;

        /**
         * Use add drag icon mode for icon component
         * @type {boolean}
         */
        this.useDragAddIcon = useDragAddIcon;

        /**
         * cropper Selection Style
         * @type {Object}
         */
        this.cropSelectionStyle = {};

        /**
         * Image name
         * @type {string}
         */
        this.imageName = '';

        /**
         * Object Map
         * @type {Object}
         * @private
         */
        this._objects = {};

        /**
         * Fabric-Canvas instance
         * @type {fabric.Canvas}
         * @private
         */
        this._canvas = null;

        /**
         * Drawing mode
         * @type {string}
         * @private
         */
        this._drawingMode = drawingModes.NORMAL;

        /**
         * DrawingMode map
         * @type {Object.<string, DrawingMode>}
         * @private
         */
        this._drawingModeMap = {};

        /**
         * Component map
         * @type {Object.<string, Component>}
         * @private
         */
        this._componentMap = {};

        /**
         * fabric event handlers
         * @type {Object.<string, function>}
         * @private
         */
        this._handler = {
            onMouseDown: this._onMouseDown.bind(this),
            onObjectAdded: this._onObjectAdded.bind(this),
            onObjectRemoved: this._onObjectRemoved.bind(this),
            onObjectMoved: this._onObjectMoved.bind(this),
            onObjectScaled: this._onObjectScaled.bind(this),
            onObjectSelected: this._onObjectSelected.bind(this),
            onPathCreated: this._onPathCreated.bind(this),
            onSelectionCleared: this._onSelectionCleared.bind(this),
            onSelectionCreated: this._onSelectionCreated.bind(this)
        };

        this._setCanvasElement(element);
        this._createDrawingModeInstances();
        this._createComponents();
        this._attachCanvasEvents();
    }

    /**
     * Destroy canvas element
     */
    destroy() {
        const {wrapperEl} = this._canvas;

        this._canvas.clear();

        wrapperEl.parentNode.removeChild(wrapperEl);
    }

    /**
     * Deactivates all objects on canvas
     * @returns {Graphics} this
     */
    deactivateAll() {
        this._canvas.deactivateAll();

        return this;
    }

    /**
     * Renders all objects on canvas
     * @returns {Graphics} this
     */
    renderAll() {
        this._canvas.renderAll();

        return this;
    }

    /**
     * Adds objects on canvas
     * @param {Object|Array} objects - objects
     */
    add(objects) {
        let theArgs = [];
        if (isArray(objects)) {
            theArgs = objects;
        } else {
            theArgs.push(objects);
        }

        this._canvas.add(...theArgs);
    }

    /**
     * Removes the object or group
     * @param {Object} target - graphics object or group
     * @returns {boolean} true if contains or false
     */
    contains(target) {
        return this._canvas.contains(target);
    }

    /**
     * Gets all objects or group
     * @returns {Array} all objects, shallow copy
     */
    getObjects() {
        return this._canvas.getObjects().slice();
    }

    /**
     * Get an object by id
     * @param {number} id - object id
     * @returns {fabric.Object} object corresponding id
     */
    getObject(id) {
        return this._objects[id];
    }

    /**
     * Removes the object or group
     * @param {Object} target - graphics object or group
     */
    remove(target) {
        this._canvas.remove(target);
    }

    /**
     * Removes all object or group
     * @param {boolean} includesBackground - remove the background image or not
     * @returns {Array} all objects array which is removed
     */
    removeAll(includesBackground) {
        const canvas = this._canvas;
        const objects = canvas.getObjects().slice();
        canvas.remove(...this._canvas.getObjects());

        if (includesBackground) {
            canvas.clear();
        }

        return objects;
    }

    /**
     * Removes an object or group by id
     * @param {number} id - object id
     * @returns {Array} removed objects
     */
    removeObjectById(id) {
        const objects = [];
        const canvas = this._canvas;
        const target = this.getObject(id);
        const isValidGroup = target && target.isType('group') && !target.isEmpty();

        if (isValidGroup) {
            canvas.discardActiveGroup(); // restore states for each objects
            target.forEachObject(obj => {
                objects.push(obj);
                obj.remove();
            });
        } else if (canvas.contains(target)) {
            objects.push(target);
            target.remove();
        }

        return objects;
    }

    /**
     * Get an id by object instance
     * @param {fabric.Object} object object
     * @returns {number} object id if it exists or null
     */
    getObjectId(object) {
        let key = null;
        for (key in this._objects) {
            if (this._objects.hasOwnProperty(key)) {
                if (object === this._objects[key]) {
                    return key;
                }
            }
        }

        return null;
    }

    /**
     * Gets an active object or group
     * @returns {Object} active object or group instance
     */
    getActiveObject() {
        return this._canvas.getActiveObject();
    }

    /**
     * Gets an active group object
     * @returns {Object} active group object instance
     */
    getActiveGroupObject() {
        return this._canvas.getActiveGroup();
    }

    /**
     * Activates an object or group
     * @param {Object} target - target object or group
     */
    setActiveObject(target) {
        this._canvas.setActiveObject(target);
    }

    /**
     * Set Crop selection style
     * @param {Object} style - Selection styles
     */
    setCropSelectionStyle(style) {
        this.cropSelectionStyle = style;
    }

    /**
     * Get component
     * @param {string} name - Component name
     * @returns {Component}
     */
    getComponent(name) {
        return this._componentMap[name];
    }

    /**
     * Get current drawing mode
     * @returns {string}
     */
    getDrawingMode() {
        return this._drawingMode;
    }

    /**
     * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
     * @param {String} mode Can be one of <I>'CROPPER', 'FREE_DRAWING', 'LINE', 'TEXT', 'SHAPE'</I>
     * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING'
     *  @param {Number} [option.width] brush width
     *  @param {String} [option.color] brush color
     * @returns {boolean} true if success or false
     */
    startDrawingMode(mode, option) {
        if (this._isSameDrawingMode(mode)) {
            return true;
        }

        // If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
        this.stopDrawingMode();

        const drawingModeInstance = this._getDrawingModeInstance(mode);
        if (drawingModeInstance && drawingModeInstance.start) {
            drawingModeInstance.start(this, option);

            this._drawingMode = mode;
        }

        return !!drawingModeInstance;
    }

    /**
     * Stop the current drawing mode and back to the 'NORMAL' mode
     */
    stopDrawingMode() {
        if (this._isSameDrawingMode(drawingModes.NORMAL)) {
            return;
        }

        const drawingModeInstance = this._getDrawingModeInstance(this.getDrawingMode());
        if (drawingModeInstance && drawingModeInstance.end) {
            drawingModeInstance.end(this);
        }
        this._drawingMode = drawingModes.NORMAL;
    }

    /**
     * To data url from canvas
     * @param {Object} options - options for toDataURL
     *   @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
     *   @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
     *   @param {Number} [options.multiplier=1] Multiplier to scale by
     *   @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14
     *   @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14
     *   @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14
     *   @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14
     * @returns {string} A DOMString containing the requested data URI.
     */
    toDataURL(options) {
        return this._canvas && this._canvas.toDataURL(options);
    }

    /**
     * Save image(background) of canvas
     * @param {string} name - Name of image
     * @param {?fabric.Image} canvasImage - Fabric image instance
     */
    setCanvasImage(name, canvasImage) {
        if (canvasImage) {
            stamp(canvasImage);
        }
        this.imageName = name;
        this.canvasImage = canvasImage;
    }

    /**
     * Set css max dimension
     * @param {{width: number, height: number}} maxDimension - Max width & Max height
     */
    setCssMaxDimension(maxDimension) {
        this.cssMaxWidth = maxDimension.width || this.cssMaxWidth;
        this.cssMaxHeight = maxDimension.height || this.cssMaxHeight;
    }

    /**
     * Adjust canvas dimension with scaling image
     */
    adjustCanvasDimension() {
        const canvasImage = this.canvasImage.scale(1);
        const {width, height} = canvasImage.getBoundingRect();
        const maxDimension = this._calcMaxDimension(width, height);

        this.setCanvasCssDimension({
            width: '100%',
            height: '100%', // Set height '' for IE9
            'max-width': `${maxDimension.width}px`,
            'max-height': `${maxDimension.height}px`
        });

        this.setCanvasBackstoreDimension({
            width,
            height
        });
        this._canvas.centerObject(canvasImage);
    }

    /**
     * Set canvas dimension - css only
     *  {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions}
     * @param {Object} dimension - Canvas css dimension
     */
    setCanvasCssDimension(dimension) {
        this._canvas.setDimensions(dimension, cssOnly);
    }

    /**
     * Set canvas dimension - backstore only
     *  {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions}
     * @param {Object} dimension - Canvas backstore dimension
     */
    setCanvasBackstoreDimension(dimension) {
        this._canvas.setDimensions(dimension, backstoreOnly);
    }

    /**
     * Set image properties
     * {@link http://fabricjs.com/docs/fabric.Image.html#set}
     * @param {Object} setting - Image properties
     * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas
     */
    setImageProperties(setting, withRendering) {
        const {canvasImage} = this;

        if (!canvasImage) {
            return;
        }

        canvasImage.set(setting).setCoords();
        if (withRendering) {
            this._canvas.renderAll();
        }
    }

    /**
     * Returns canvas element of fabric.Canvas[[lower-canvas]]
     * @returns {HTMLCanvasElement}
     */
    getCanvasElement() {
        return this._canvas.getElement();
    }

    /**
     * Get fabric.Canvas instance
     * @returns {fabric.Canvas}
     * @private
     */
    getCanvas() {
        return this._canvas;
    }

    /**
     * Get canvasImage (fabric.Image instance)
     * @returns {fabric.Image}
     */
    getCanvasImage() {
        return this.canvasImage;
    }

    /**
     * Get image name
     * @returns {string}
     */
    getImageName() {
        return this.imageName;
    }

    /**
     * Add image object on canvas
     * @param {string} imgUrl - Image url to make object
     * @returns {Promise}
     */
    addImageObject(imgUrl) {
        const callback = this._callbackAfterLoadingImageObject.bind(this);

        return new Promise(resolve => {
            fabric.Image.fromURL(imgUrl, image => {
                callback(image);
                resolve(this.createObjectProperties(image));
            }, {
                crossOrigin: 'Anonymous'
            }
            );
        });
    }

    /**
     * Get center position of canvas
     * @returns {Object} {left, top}
     */
    getCenter() {
        return this._canvas.getCenter();
    }

    /**
     * Get cropped rect
     * @returns {Object} rect
     */
    getCropzoneRect() {
        return this.getComponent(components.CROPPER).getCropzoneRect();
    }

    /**
     * Get cropped rect
     * @param {number} [mode] cropzone rect mode
     */
    setCropzoneRect(mode) {
        this.getComponent(components.CROPPER).setCropzoneRect(mode);
    }

    /**
     * Get cropped image data
     * @param {Object} cropRect cropzone rect
     *  @param {Number} cropRect.left left position
     *  @param {Number} cropRect.top top position
     *  @param {Number} cropRect.width width
     *  @param {Number} cropRect.height height
     * @returns {?{imageName: string, url: string}} cropped Image data
     */
    getCroppedImageData(cropRect) {
        return this.getComponent(components.CROPPER).getCroppedImageData(cropRect);
    }

    /**
     * Set brush option
     * @param {Object} option brush option
     *  @param {Number} option.width width
     *  @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)'
     */
    setBrush(option) {
        const drawingMode = this._drawingMode;
        let compName = components.FREE_DRAWING;

        if (drawingMode === drawingModes.LINE) {
            compName = drawingModes.LINE;
        }

        this.getComponent(compName).setBrush(option);
    }

    /**
     * Set states of current drawing shape
     * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
     * @param {Object} [options] - Shape options
     *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
     *      @param {string} [options.stoke] - Shape outline color
     *      @param {number} [options.strokeWidth] - Shape outline width
     *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
     *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
     *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
     *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
     *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
     */
    setDrawingShape(type, options) {
        this.getComponent(components.SHAPE).setStates(type, options);
    }

    /**
     * Register icon paths
     * @param {Object} pathInfos - Path infos
     *  @param {string} pathInfos.key - key
     *  @param {string} pathInfos.value - value
     */
    registerPaths(pathInfos) {
        this.getComponent(components.ICON).registerPaths(pathInfos);
    }

    /**
     * Change cursor style
     * @param {string} cursorType - cursor type
     */
    changeCursor(cursorType) {
        const canvas = this.getCanvas();
        canvas.defaultCursor = cursorType;
        canvas.renderAll();
    }

    /**
     * Whether it has the filter or not
     * @param {string} type - Filter type
     * @returns {boolean} true if it has the filter
     */
    hasFilter(type) {
        return this.getComponent(components.FILTER).hasFilter(type);
    }

    /**
     * Set selection style of fabric object by init option
     * @param {Object} styles - Selection styles
     */
    setSelectionStyle(styles) {
        extend(fObjectOptions.SELECTION_STYLE, styles);
    }

    /**
     * Set object properties
     * @param {number} id - object id
     * @param {Object} props - props
     *     @param {string} [props.fill] Color
     *     @param {string} [props.fontFamily] Font type for text
     *     @param {number} [props.fontSize] Size
     *     @param {string} [props.fontStyle] Type of inclination (normal / italic)
     *     @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
     *     @param {string} [props.textAlign] Type of text align (left / center / right)
     *     @param {string} [props.textDecoraiton] Type of line (underline / line-throgh / overline)
     * @returns {Object} applied properties
     */
    setObjectProperties(id, props) {
        const object = this.getObject(id);
        const clone = extend({}, props);

        object.set(clone);

        object.setCoords();

        this.getCanvas().renderAll();

        return clone;
    }

    /**
     * Get object properties corresponding key
     * @param {number} id - object id
     * @param {Array<string>|ObjectProps|string} keys - property's key
     * @returns {Object} properties
     */
    getObjectProperties(id, keys) {
        const object = this.getObject(id);
        const props = {};

        if (isString(keys)) {
            props[keys] = object[keys];
        } else if (isArray(keys)) {
            forEachArray(keys, value => {
                props[value] = object[value];
            });
        } else {
            forEachOwnProperties(keys, (value, key) => {
                props[key] = object[key];
            });
        }

        return props;
    }

    /**
     * Get object position by originX, originY
     * @param {number} id - object id
     * @param {string} originX - can be 'left', 'center', 'right'
     * @param {string} originY - can be 'top', 'center', 'bottom'
     * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null
     */
    getObjectPosition(id, originX, originY) {
        const targetObj = this.getObject(id);
        if (!targetObj) {
            return null;
        }

        return targetObj.getPointByOrigin(originX, originY);
    }

    /**
     * Set object position  by originX, originY
     * @param {number} id - object id
     * @param {Object} posInfo - position object
     *  @param {number} posInfo.x - x position
     *  @param {number} posInfo.y - y position
     *  @param {string} posInfo.originX - can be 'left', 'center', 'right'
     *  @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
     * @returns {boolean} true if target id is valid or false
     */
    setObjectPosition(id, posInfo) {
        const targetObj = this.getObject(id);
        const {x, y, originX, originY} = posInfo;
        if (!targetObj) {
            return false;
        }

        const targetOrigin = targetObj.getPointByOrigin(originX, originY);
        const centerOrigin = targetObj.getPointByOrigin('center', 'center');
        const diffX = centerOrigin.x - targetOrigin.x;
        const diffY = centerOrigin.y - targetOrigin.y;

        targetObj.set({
            left: x + diffX,
            top: y + diffY
        });

        targetObj.setCoords();

        return true;
    }

    /**
     * Get the canvas size
     * @returns {Object} {{width: number, height: number}} image size
     */
    getCanvasSize() {
        const image = this.getCanvasImage();

        return {
            width: image ? image.width : 0,
            height: image ? image.height : 0
        };
    }

    /**
     * Get a DrawingMode instance
     * @param {string} modeName - DrawingMode Class Name
     * @returns {DrawingMode} DrawingMode instance
     * @private
     */
    _getDrawingModeInstance(modeName) {
        return this._drawingModeMap[modeName];
    }

    /**
     * Set canvas element to fabric.Canvas
     * @param {Element|string} element - Wrapper or canvas element or selector
     * @private
     */
    _setCanvasElement(element) {
        let selectedElement;
        let canvasElement;

        if (element.nodeType) {
            selectedElement = element;
        } else {
            selectedElement = document.querySelector(element);
        }

        if (selectedElement.nodeName.toUpperCase() !== 'CANVAS') {
            canvasElement = document.createElement('canvas');
            selectedElement.appendChild(canvasElement);
        }

        this._canvas = new fabric.Canvas(canvasElement, {
            containerClass: 'tui-image-editor-canvas-container',
            enableRetinaScaling: false
        });
    }

    /**
     * Creates DrawingMode instances
     * @private
     */
    _createDrawingModeInstances() {
        this._register(this._drawingModeMap, new CropperDrawingMode());
        this._register(this._drawingModeMap, new FreeDrawingMode());
        this._register(this._drawingModeMap, new LineDrawingMode());
        this._register(this._drawingModeMap, new ShapeDrawingMode());
        this._register(this._drawingModeMap, new TextDrawingMode());
    }

    /**
     * Create components
     * @private
     */
    _createComponents() {
        this._register(this._componentMap, new ImageLoader(this));
        this._register(this._componentMap, new Cropper(this));
        this._register(this._componentMap, new Flip(this));
        this._register(this._componentMap, new Rotation(this));
        this._register(this._componentMap, new FreeDrawing(this));
        this._register(this._componentMap, new Line(this));
        this._register(this._componentMap, new Text(this));
        this._register(this._componentMap, new Icon(this));
        this._register(this._componentMap, new Filter(this));
        this._register(this._componentMap, new Shape(this));
    }

    /**
     * Register component
     * @param {Object} map - map object
     * @param {Object} module - module which has getName method
     * @private
     */
    _register(map, module) {
        map[module.getName()] = module;
    }

    /**
     * Get the current drawing mode is same with given mode
     * @param {string} mode drawing mode
     * @returns {boolean} true if same or false
     */
    _isSameDrawingMode(mode) {
        return this.getDrawingMode() === mode;
    }

    /**
     * Calculate max dimension of canvas
     * The css-max dimension is dynamically decided with maintaining image ratio
     * The css-max dimension is lower than canvas dimension (attribute of canvas, not css)
     * @param {number} width - Canvas width
     * @param {number} height - Canvas height
     * @returns {{width: number, height: number}} - Max width & Max height
     * @private
     */
    _calcMaxDimension(width, height) {
        const wScaleFactor = this.cssMaxWidth / width;
        const hScaleFactor = this.cssMaxHeight / height;
        let cssMaxWidth = Math.min(width, this.cssMaxWidth);
        let cssMaxHeight = Math.min(height, this.cssMaxHeight);

        if (wScaleFactor < 1 && wScaleFactor < hScaleFactor) {
            cssMaxWidth = width * wScaleFactor;
            cssMaxHeight = height * wScaleFactor;
        } else if (hScaleFactor < 1 && hScaleFactor < wScaleFactor) {
            cssMaxWidth = width * hScaleFactor;
            cssMaxHeight = height * hScaleFactor;
        }

        return {
            width: Math.floor(cssMaxWidth),
            height: Math.floor(cssMaxHeight)
        };
    }

    /**
     * Callback function after loading image
     * @param {fabric.Image} obj - Fabric image object
     * @private
     */
    _callbackAfterLoadingImageObject(obj) {
        const centerPos = this.getCanvasImage().getCenterPoint();

        obj.set(consts.fObjectOptions.SELECTION_STYLE);
        obj.set({
            left: centerPos.x,
            top: centerPos.y,
            crossOrigin: 'Anonymous'
        });

        this.getCanvas().add(obj).setActiveObject(obj);
    }

    /**
     * Attach canvas's events
     */
    _attachCanvasEvents() {
        const canvas = this._canvas;
        const handler = this._handler;
        canvas.on({
            'mouse:down': handler.onMouseDown,
            'object:added': handler.onObjectAdded,
            'object:removed': handler.onObjectRemoved,
            'object:moving': handler.onObjectMoved,
            'object:scaling': handler.onObjectScaled,
            'object:selected': handler.onObjectSelected,
            'path:created': handler.onPathCreated,
            'selection:cleared': handler.onSelectionCleared,
            'selection:created': handler.onSelectionCreated
        });
    }

    /**
     * "mouse:down" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onMouseDown(fEvent) {
        const originPointer = this._canvas.getPointer(fEvent.e);
        this.fire(events.MOUSE_DOWN, fEvent.e, originPointer);
    }

    /**
     * "object:added" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onObjectAdded(fEvent) {
        const obj = fEvent.target;
        if (obj.isType('cropzone')) {
            return;
        }

        this._addFabricObject(obj);
    }

    /**
     * "object:removed" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onObjectRemoved(fEvent) {
        const obj = fEvent.target;

        this._removeFabricObject(stamp(obj));
    }

    /**
     * "object:moving" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onObjectMoved(fEvent) {
        const {target} = fEvent;
        const params = this.createObjectProperties(target);

        this.fire(events.OBJECT_MOVED, params);
    }

    /**
     * "object:scaling" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onObjectScaled(fEvent) {
        const {target} = fEvent;
        const params = this.createObjectProperties(target);

        this.fire(events.OBJECT_SCALED, params);
    }

    /**
     * "object:selected" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onObjectSelected(fEvent) {
        const {target} = fEvent;
        const params = this.createObjectProperties(target);

        this.fire(events.OBJECT_ACTIVATED, params);
    }

    /**
     * "path:created" canvas event handler
     * @param {{path: fabric.Path}} obj - Path object
     * @private
     */
    _onPathCreated(obj) {
        obj.path.set(consts.fObjectOptions.SELECTION_STYLE);

        const params = this.createObjectProperties(obj.path);

        this.fire(events.ADD_OBJECT, params);
    }

    /**
     * "selction:cleared" canvas event handler
     * @private
     */
    _onSelectionCleared() {
        this.fire(events.SELECTION_CLEARED);
    }

    /**
     * "selction:created" canvas event handler
     * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
     * @private
     */
    _onSelectionCreated(fEvent) {
        this.fire(events.SELECTION_CREATED, fEvent.target);
    }

    /**
     * Canvas discard selection all
     */
    discardSelection() {
        this._canvas.discardActiveGroup();
        this._canvas.discardActiveObject();
        this._canvas.renderAll();
    }

    /**
     * Canvas Selectable status change
     * @param {boolean} selectable - expect status
     */
    changeSelectableAll(selectable) {
        this._canvas.forEachObject(obj => {
            obj.selectable = selectable;
            obj.hoverCursor = selectable ? 'move' : 'crosshair';
        });
    }

    /**
     * Return object's properties
     * @param {fabric.Object} obj - fabric object
     * @returns {Object} properties object
     */
    createObjectProperties(obj) {
        const predefinedKeys = [
            'left',
            'top',
            'width',
            'height',
            'fill',
            'stroke',
            'strokeWidth',
            'opacity'
        ];
        const props = {
            id: stamp(obj),
            type: obj.type
        };

        extend(props, util.getProperties(obj, predefinedKeys));

        if (['i-text', 'text'].indexOf(obj.type) > -1) {
            extend(props, this._createTextProperties(obj, props));
        }

        return props;
    }

    /**
     * Get text object's properties
     * @param {fabric.Object} obj - fabric text object
     * @param {Object} props - properties
     * @returns {Object} properties object
     */
    _createTextProperties(obj) {
        const predefinedKeys = [
            'text',
            'fontFamily',
            'fontSize',
            'fontStyle',
            'textAlign',
            'textDecoration'
        ];
        const props = {};
        extend(props, util.getProperties(obj, predefinedKeys));

        return props;
    }

    /**
     * Add object array by id
     * @param {fabric.Object} obj - fabric object
     * @returns {number} object id
     */
    _addFabricObject(obj) {
        const id = stamp(obj);
        this._objects[id] = obj;

        return id;
    }

    /**
     * Remove an object in array yb id
     * @param {number} id - object id
     */
    _removeFabricObject(id) {
        delete this._objects[id];
    }
}

CustomEvents.mixin(Graphics);
module.exports = Graphics;
For more information send a message to info at phpclasses dot org.