diff --git a/src/mixins/textbox_behavior.mixin.js b/src/mixins/textbox_behavior.mixin.js index d9edc850..966b870e 100644 --- a/src/mixins/textbox_behavior.mixin.js +++ b/src/mixins/textbox_behavior.mixin.js @@ -1,41 +1,41 @@ (function () { - /** - * Override _setObjectScale and add Textbox specific resizing behavior. Resizing - * a Textbox doesn't scale text, it only changes width and makes text wrap automatically. - */ - var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale; - fabric.Canvas.prototype._setObjectScale = function (localMouse, transform, - lockScalingX, lockScalingY, by, lockScalingFlip) { + /** + * Override _setObjectScale and add Textbox specific resizing behavior. Resizing + * a Textbox doesn't scale text, it only changes width and makes text wrap automatically. + */ + var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale; + fabric.Canvas.prototype._setObjectScale = function (localMouse, transform, + lockScalingX, lockScalingY, by, lockScalingFlip) { - var t = transform.target; - if (t instanceof fabric.Textbox) { - var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth)); - if (w >= t.minWidth) { - t.set('width', w); - } - } - else { - setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform, - lockScalingX, lockScalingY, by, lockScalingFlip); - } - }; + var t = transform.target; + if (t instanceof fabric.Textbox) { + var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth)); + if (w >= t.minWidth) { + t.set('width', w); + } + } + else { + setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform, + lockScalingX, lockScalingY, by, lockScalingFlip); + } + }; - /** - * Sets controls of this group to the Textbox's special configuration if - * one is present in the group. Deletes _controlsVisibility otherwise, so that - * it gets initialized to default value at runtime. - */ - fabric.Group.prototype._refreshControlsVisibility = function () { - if (typeof fabric.Textbox === 'undefined') { - return; - } - for (var i = this._objects.length; i--;) { - if (this._objects[i] instanceof fabric.Textbox) { - this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); - return; - } - } - }; + /** + * Sets controls of this group to the Textbox's special configuration if + * one is present in the group. Deletes _controlsVisibility otherwise, so that + * it gets initialized to default value at runtime. + */ + fabric.Group.prototype._refreshControlsVisibility = function () { + if (typeof fabric.Textbox === 'undefined') { + return; + } + for (var i = this._objects.length; i--;) { + if (this._objects[i] instanceof fabric.Textbox) { + this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); + return; + } + } + }; -})(); \ No newline at end of file +})(); diff --git a/src/mixins/textbox_click_behavior.mixin.js b/src/mixins/textbox_click_behavior.mixin.js index 5aa7ba7d..c0ef1c0e 100644 --- a/src/mixins/textbox_click_behavior.mixin.js +++ b/src/mixins/textbox_click_behavior.mixin.js @@ -1,22 +1,22 @@ (function () { - var getNewSelectionStartFromOffsetOverriden = fabric.IText.prototype._getNewSelectionStartFromOffset; - /** - * Overrides the IText implementation and always sends lineIndex as 0 for Textboxes. - * @param {Number} mouseOffset - * @param {Number} prevWidth - * @param {Number} width - * @param {Number} index - * @param {Number} lineIndex - * @param {Number} jlen - * @returns {Number} - */ - fabric.IText.prototype._getNewSelectionStartFromOffset = function (mouseOffset, - prevWidth, width, index, lineIndex, jlen) { - if (this instanceof fabric.Textbox) { - lineIndex = 0; - } - return getNewSelectionStartFromOffsetOverriden - .call(this, mouseOffset, - prevWidth, width, index, lineIndex, jlen); - }; -})(); \ No newline at end of file + var getNewSelectionStartFromOffsetOverriden = fabric.IText.prototype._getNewSelectionStartFromOffset; + /** + * Overrides the IText implementation and always sends lineIndex as 0 for Textboxes. + * @param {Number} mouseOffset + * @param {Number} prevWidth + * @param {Number} width + * @param {Number} index + * @param {Number} lineIndex + * @param {Number} jlen + * @returns {Number} + */ + fabric.IText.prototype._getNewSelectionStartFromOffset = function (mouseOffset, + prevWidth, width, index, lineIndex, jlen) { + if (this instanceof fabric.Textbox) { + lineIndex = 0; + } + return getNewSelectionStartFromOffsetOverriden + .call(this, mouseOffset, + prevWidth, width, index, lineIndex, jlen); + }; +})(); diff --git a/src/mixins/textbox_key_behavior.mixin.js b/src/mixins/textbox_key_behavior.mixin.js index 64089c20..c1170d41 100644 --- a/src/mixins/textbox_key_behavior.mixin.js +++ b/src/mixins/textbox_key_behavior.mixin.js @@ -1,22 +1,22 @@ fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.Textbox.prototype */ { - /** - * Overrides superclass function and adjusts cursor offset value because - * lines do not necessarily end with a newline in Textbox. - * @param {Event} e - * @param {Boolean} isRight - * @returns {Number} - */ - getDownCursorOffset: function (e, isRight) { - return fabric.IText.prototype.getDownCursorOffset.apply(this, [e, isRight]) - 1; - }, - /** - * Overrides superclass function and adjusts cursor offset value because - * lines do not necessarily end with a newline in Textbox. - * @param {Event} e - * @param {Boolean} isRight - * @returns {Number} - */ - getUpCursorOffset: function (e, isRight) { - return fabric.IText.prototype.getUpCursorOffset.apply(this, [e, isRight]) - 1; - } -}); \ No newline at end of file + /** + * Overrides superclass function and adjusts cursor offset value because + * lines do not necessarily end with a newline in Textbox. + * @param {Event} e + * @param {Boolean} isRight + * @returns {Number} + */ + getDownCursorOffset: function (e, isRight) { + return fabric.IText.prototype.getDownCursorOffset.apply(this, [e, isRight]) - 1; + }, + /** + * Overrides superclass function and adjusts cursor offset value because + * lines do not necessarily end with a newline in Textbox. + * @param {Event} e + * @param {Boolean} isRight + * @returns {Number} + */ + getUpCursorOffset: function (e, isRight) { + return fabric.IText.prototype.getUpCursorOffset.apply(this, [e, isRight]) - 1; + } +}); diff --git a/src/shapes/textbox.class.js b/src/shapes/textbox.class.js index 01ac7d88..cb7fd463 100644 --- a/src/shapes/textbox.class.js +++ b/src/shapes/textbox.class.js @@ -1,317 +1,317 @@ (function () { - var clone = fabric.util.object.clone; + var clone = fabric.util.object.clone; + /** + * Textbox class, based on IText, allows the user to resize the text rectangle + * and wraps lines automatically. Textboxes have their Y scaling locked, the + * user can only change width. Height is adjusted automatically based on the + * wrapping of lines. + * @class fabric.Textbox + * @extends fabric.IText + * @mixes fabric.Observable + * @return {fabric.Textbox} thisArg + * @see {@link fabric.Textbox#initialize} for constructor definition + */ + fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { /** - * Textbox class, based on IText, allows the user to resize the text rectangle - * and wraps lines automatically. Textboxes have their Y scaling locked, the - * user can only change width. Height is adjusted automatically based on the - * wrapping of lines. - * @class fabric.Textbox - * @extends fabric.IText - * @mixes fabric.Observable - * @return {fabric.Textbox} thisArg - * @see {@link fabric.Textbox#initialize} for constructor definition + * Type of an object + * @type String + * @default */ - fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { - /** - * Type of an object - * @type String - * @default - */ - type: 'textbox', - /** - * Minimum width of textbox, in pixels. - * @type Number - * @default - */ - minWidth: 20, - /** - * Cached array of text wrapping. - * @type Array - */ - __cachedLines: null, - /** - * Constructor. Some scaling related property values are forced. Visibility - * of controls is also fixed; only the rotation and width controls are - * made available. - * @param {String} text Text string - * @param {Object} [options] Options object - * @return {fabric.Textbox} thisArg - */ - initialize: function (text, options) { - this.ctx = fabric.util.createCanvasElement().getContext('2d'); - - this.callSuper('initialize', text, options); - this.set({ - lockUniScaling: false, - lockScalingY: true, - lockScalingFlip: true, - hasBorders: true - }); - this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); - - // add width to this list of props that effect line wrapping. - this._dimensionAffectingProps.width = true; - }, - /** - * Unlike superclass's version of this function, Textbox does not update - * its width. - * @param {CanvasRenderingContext2D} ctx Context to use for measurements - * @private - * @override - */ - _initDimensions: function (ctx) { - if (this.__skipDimension) { - return; - } - - if (!ctx) { - ctx = fabric.util.createCanvasElement().getContext('2d'); - this._setTextStyles(ctx); - } - this._textLines = this._splitTextIntoLines(); - this._clearCache(); - this.height = this._getTextHeight(ctx); - }, - /** - * Wraps text using the 'width' property of Textbox. First this function - * splits text on newlines, so we preserve newlines entered by the user. - * Then it wraps each line using the width of the Textbox by calling - * _wrapLine(). - * @param {CanvasRenderingContext2D} ctx Context to use for measurements - * @param {String} text The string of text that is split into lines - * @returns {Array} Array of lines - */ - _wrapText: function (ctx, text) { - var lines = text.split(this._reNewline), wrapped = [], i; - - for (i = 0; i < lines.length; i++) { - wrapped = wrapped.concat(this._wrapLine(ctx, lines[i])); - } - - return wrapped; - }, - /** - * Wraps a line of text using the width of the Textbox and a context. - * @param {CanvasRenderingContext2D} ctx Context to use for measurements - * @param {String} text The string of text to split into lines - * @returns {Array} Array of line(s) into which the given text is wrapped - * to. - */ - _wrapLine: function (ctx, text) { - var maxWidth = this.width, words = text.split(' '), - lines = [], - line = ''; - - if (ctx.measureText(text).width < maxWidth) { - lines.push(text); - } - else { - while (words.length > 0) { - - /* - * If the textbox's width is less than the widest letter. - * TODO: Performance improvement - cache the width of W whenever - * fontSize changes. - */ - if (maxWidth <= ctx.measureText('W').width) { - return text.split(''); - } - - /* - * This handles a word that is longer than the width of the - * text area. - */ - while (Math.ceil(ctx.measureText(words[0]).width) >= maxWidth) { - var tmp = words[0]; - words[0] = tmp.slice(0, -1); - if (words.length > 1) { - words[1] = tmp.slice(-1) + words[1]; - } - else { - words.push(tmp.slice(-1)); - } - } - - if (Math.ceil(ctx.measureText(line + words[0]).width) < maxWidth) { - line += words.shift() + ' '; - } - else { - lines.push(line); - line = ''; - } - if (words.length === 0) { - lines.push(line.substring(0, line.length - 1)); - } - } - } - - return lines; - }, - /** - * Gets lines of text to render in the Textbox. This function calculates - * text wrapping on the fly everytime it is called. - * @returns {Array} Array of lines in the Textbox. - * @override - */ - _splitTextIntoLines: function () { - this.ctx.save(); - this._setTextStyles(this.ctx); - - lines = this._wrapText(this.ctx, this.text); - - this.ctx.restore(); - return lines; - }, - - /** - * When part of a group, we don't want the Textbox's scale to increase if - * the group's increases. That's why we reduce the scale of the Textbox by - * the amount that the group's increases. This is to maintain the effective - * scale of the Textbox at 1, so that font-size values make sense. Otherwise - * the same font-size value would result in different actual size depending - * on the value of the scale. - * @param {String} key - * @param {Any} value - */ - setOnGroup: function (key, value) { - if (key === 'scaleX') { - this.set(key, Math.abs(1 / value)); - this.set('width', (this.get('width') * value) / - (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX)); - this.__oldScaleX = value; - } - }, - - /** - * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start). - * Overrides the superclass function to take into account text wrapping. - * @param {Number} selectionStart Optional index. When not given, current selectionStart is used. - * @returns {Object} This object has 'lineIndex' and 'charIndex' properties set to Numbers. - */ - get2DCursorLocation: function (selectionStart) { - if (typeof selectionStart === 'undefined') { - selectionStart = this.selectionStart; - } - - /* - * We use `temp` to populate linesBeforeCursor instead of simply splitting - * textBeforeCursor with newlines to handle the case of the - * selectionStart value being on a word that, because of its length, - * needs to be wrapped to the next line. - */ - var lineIndex = 0, - linesBeforeCursor = [], - allLines = this._textLines, temp = selectionStart; - - while (temp >= 0) { - if (lineIndex > allLines.length - 1) { - break; - } - temp -= allLines[lineIndex].length; - if (temp < 0) { - linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex].slice(0, - temp + allLines[lineIndex].length); - } - else { - linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex]; - } - lineIndex++; - } - lineIndex--; - - var lastLine = linesBeforeCursor[linesBeforeCursor.length - 1], - charIndex = lastLine.length; - - if (linesBeforeCursor[lineIndex] === allLines[lineIndex]) { - if (lineIndex + 1 < allLines.length - 1) { - lineIndex++; - charIndex = 0; - } - } - - return { - lineIndex: lineIndex, - charIndex: charIndex - }; - }, - /** - * Overrides superclass function and uses text wrapping data to get cursor - * boundary offsets instead of the array of chars. - * @param {Array} chars Unused - * @param {String} typeOfBoundaries Can be 'cursor' or 'selection' - * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set. - */ - _getCursorBoundariesOffsets: function (chars, typeOfBoundaries) { - var topOffset = 0, - leftOffset = 0, - cursorLocation = this.get2DCursorLocation(), - lineChars = this._textLines[cursorLocation.lineIndex].split(''), - lineLeftOffset = this._getCachedLineOffset(cursorLocation.lineIndex); - - for (var i = 0; i < cursorLocation.charIndex; i++) { - leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i); - } - - for (i = 0; i < cursorLocation.lineIndex; i++) { - topOffset += this._getHeightOfLine(this.ctx, i); - } - - if (typeOfBoundaries === 'cursor') { - topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex) / this.lineHeight - - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) * (1 - this._fontSizeFraction); - } - - return { - top: topOffset, - left: leftOffset, - lineLeft: lineLeftOffset - }; - }, - /** - * Returns object representation of an instance - * @method toObject - * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output - * @return {Object} object representation of an instance - */ - toObject: function (propertiesToInclude) { - return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { - minWidth: this.minWidth - }); - } - }); + type: 'textbox', /** - * Returns fabric.Textbox instance from an object representation - * @static - * @memberOf fabric.Textbox - * @param {Object} object Object to create an instance from - * @return {fabric.Textbox} instance of fabric.Textbox + * Minimum width of textbox, in pixels. + * @type Number + * @default */ - fabric.Textbox.fromObject = function (object) { - return new fabric.Textbox(object.text, clone(object)); - }; + minWidth: 20, /** - * Returns the default controls visibility required for Textboxes. - * @returns {Object} - */ - fabric.Textbox.getTextboxControlVisibility = function () { - return { - tl: false, - tr: false, - br: false, - bl: false, - ml: true, - mt: false, - mr: true, - mb: false, - mtr: true - }; - }; - /** - * Contains all fabric.Textbox objects that have been created - * @static - * @memberof fabric.Textbox + * Cached array of text wrapping. * @type Array */ - fabric.Textbox.instances = []; -})(); \ No newline at end of file + __cachedLines: null, + /** + * Constructor. Some scaling related property values are forced. Visibility + * of controls is also fixed; only the rotation and width controls are + * made available. + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Textbox} thisArg + */ + initialize: function (text, options) { + this.ctx = fabric.util.createCanvasElement().getContext('2d'); + + this.callSuper('initialize', text, options); + this.set({ + lockUniScaling: false, + lockScalingY: true, + lockScalingFlip: true, + hasBorders: true + }); + this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility()); + + // add width to this list of props that effect line wrapping. + this._dimensionAffectingProps.width = true; + }, + /** + * Unlike superclass's version of this function, Textbox does not update + * its width. + * @param {CanvasRenderingContext2D} ctx Context to use for measurements + * @private + * @override + */ + _initDimensions: function (ctx) { + if (this.__skipDimension) { + return; + } + + if (!ctx) { + ctx = fabric.util.createCanvasElement().getContext('2d'); + this._setTextStyles(ctx); + } + this._textLines = this._splitTextIntoLines(); + this._clearCache(); + this.height = this._getTextHeight(ctx); + }, + /** + * Wraps text using the 'width' property of Textbox. First this function + * splits text on newlines, so we preserve newlines entered by the user. + * Then it wraps each line using the width of the Textbox by calling + * _wrapLine(). + * @param {CanvasRenderingContext2D} ctx Context to use for measurements + * @param {String} text The string of text that is split into lines + * @returns {Array} Array of lines + */ + _wrapText: function (ctx, text) { + var lines = text.split(this._reNewline), wrapped = [], i; + + for (i = 0; i < lines.length; i++) { + wrapped = wrapped.concat(this._wrapLine(ctx, lines[i])); + } + + return wrapped; + }, + /** + * Wraps a line of text using the width of the Textbox and a context. + * @param {CanvasRenderingContext2D} ctx Context to use for measurements + * @param {String} text The string of text to split into lines + * @returns {Array} Array of line(s) into which the given text is wrapped + * to. + */ + _wrapLine: function (ctx, text) { + var maxWidth = this.width, words = text.split(' '), + lines = [], + line = ''; + + if (ctx.measureText(text).width < maxWidth) { + lines.push(text); + } + else { + while (words.length > 0) { + + /* + * If the textbox's width is less than the widest letter. + * TODO: Performance improvement - cache the width of W whenever + * fontSize changes. + */ + if (maxWidth <= ctx.measureText('W').width) { + return text.split(''); + } + + /* + * This handles a word that is longer than the width of the + * text area. + */ + while (Math.ceil(ctx.measureText(words[0]).width) >= maxWidth) { + var tmp = words[0]; + words[0] = tmp.slice(0, -1); + if (words.length > 1) { + words[1] = tmp.slice(-1) + words[1]; + } + else { + words.push(tmp.slice(-1)); + } + } + + if (Math.ceil(ctx.measureText(line + words[0]).width) < maxWidth) { + line += words.shift() + ' '; + } + else { + lines.push(line); + line = ''; + } + if (words.length === 0) { + lines.push(line.substring(0, line.length - 1)); + } + } + } + + return lines; + }, + /** + * Gets lines of text to render in the Textbox. This function calculates + * text wrapping on the fly everytime it is called. + * @returns {Array} Array of lines in the Textbox. + * @override + */ + _splitTextIntoLines: function () { + this.ctx.save(); + this._setTextStyles(this.ctx); + + lines = this._wrapText(this.ctx, this.text); + + this.ctx.restore(); + return lines; + }, + + /** + * When part of a group, we don't want the Textbox's scale to increase if + * the group's increases. That's why we reduce the scale of the Textbox by + * the amount that the group's increases. This is to maintain the effective + * scale of the Textbox at 1, so that font-size values make sense. Otherwise + * the same font-size value would result in different actual size depending + * on the value of the scale. + * @param {String} key + * @param {Any} value + */ + setOnGroup: function (key, value) { + if (key === 'scaleX') { + this.set(key, Math.abs(1 / value)); + this.set('width', (this.get('width') * value) / + (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX)); + this.__oldScaleX = value; + } + }, + + /** + * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start). + * Overrides the superclass function to take into account text wrapping. + * @param {Number} selectionStart Optional index. When not given, current selectionStart is used. + * @returns {Object} This object has 'lineIndex' and 'charIndex' properties set to Numbers. + */ + get2DCursorLocation: function (selectionStart) { + if (typeof selectionStart === 'undefined') { + selectionStart = this.selectionStart; + } + + /* + * We use `temp` to populate linesBeforeCursor instead of simply splitting + * textBeforeCursor with newlines to handle the case of the + * selectionStart value being on a word that, because of its length, + * needs to be wrapped to the next line. + */ + var lineIndex = 0, + linesBeforeCursor = [], + allLines = this._textLines, temp = selectionStart; + + while (temp >= 0) { + if (lineIndex > allLines.length - 1) { + break; + } + temp -= allLines[lineIndex].length; + if (temp < 0) { + linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex].slice(0, + temp + allLines[lineIndex].length); + } + else { + linesBeforeCursor[linesBeforeCursor.length] = allLines[lineIndex]; + } + lineIndex++; + } + lineIndex--; + + var lastLine = linesBeforeCursor[linesBeforeCursor.length - 1], + charIndex = lastLine.length; + + if (linesBeforeCursor[lineIndex] === allLines[lineIndex]) { + if (lineIndex + 1 < allLines.length - 1) { + lineIndex++; + charIndex = 0; + } + } + + return { + lineIndex: lineIndex, + charIndex: charIndex + }; + }, + /** + * Overrides superclass function and uses text wrapping data to get cursor + * boundary offsets instead of the array of chars. + * @param {Array} chars Unused + * @param {String} typeOfBoundaries Can be 'cursor' or 'selection' + * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set. + */ + _getCursorBoundariesOffsets: function (chars, typeOfBoundaries) { + var topOffset = 0, + leftOffset = 0, + cursorLocation = this.get2DCursorLocation(), + lineChars = this._textLines[cursorLocation.lineIndex].split(''), + lineLeftOffset = this._getCachedLineOffset(cursorLocation.lineIndex); + + for (var i = 0; i < cursorLocation.charIndex; i++) { + leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i); + } + + for (i = 0; i < cursorLocation.lineIndex; i++) { + topOffset += this._getHeightOfLine(this.ctx, i); + } + + if (typeOfBoundaries === 'cursor') { + topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex) / this.lineHeight + - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex) * (1 - this._fontSizeFraction); + } + + return { + top: topOffset, + left: leftOffset, + lineLeft: lineLeftOffset + }; + }, + /** + * Returns object representation of an instance + * @method toObject + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function (propertiesToInclude) { + return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { + minWidth: this.minWidth + }); + } + }); + /** + * Returns fabric.Textbox instance from an object representation + * @static + * @memberOf fabric.Textbox + * @param {Object} object Object to create an instance from + * @return {fabric.Textbox} instance of fabric.Textbox + */ + fabric.Textbox.fromObject = function (object) { + return new fabric.Textbox(object.text, clone(object)); + }; + /** + * Returns the default controls visibility required for Textboxes. + * @returns {Object} + */ + fabric.Textbox.getTextboxControlVisibility = function () { + return { + tl: false, + tr: false, + br: false, + bl: false, + ml: true, + mt: false, + mr: true, + mb: false, + mtr: true + }; + }; + /** + * Contains all fabric.Textbox objects that have been created + * @static + * @memberof fabric.Textbox + * @type Array + */ + fabric.Textbox.instances = []; +})();