diff --git a/build.js b/build.js index fb6f0374..27f83faa 100644 --- a/build.js +++ b/build.js @@ -244,6 +244,11 @@ var filesToInclude = [ ifSpecifiedInclude('itext', 'src/mixins/itext_key_behavior.mixin.js'), ifSpecifiedInclude('itext', 'src/mixins/itext.svg_export.js'), + ifSpecifiedInclude('textbox', 'src/shapes/textbox.class.js'), + ifSpecifiedInclude('textbox', 'src/mixins/textbox_behavior.mixin.js'), + ifSpecifiedInclude('textbox', 'src/mixins/textbox_click_behavior.mixin.js'), + ifSpecifiedInclude('textbox', 'src/mixins/textbox_key_behavior.mixin.js'), + ifSpecifiedInclude('node', 'src/node.js'), ifSpecifiedAMDInclude(amdLib) diff --git a/build.sh b/build.sh old mode 100755 new mode 100644 diff --git a/build_all b/build_all old mode 100755 new mode 100644 diff --git a/src/mixins/texbox_key_behavior.mixin.js b/src/mixins/texbox_key_behavior.mixin.js new file mode 100644 index 00000000..7f9a095f --- /dev/null +++ b/src/mixins/texbox_key_behavior.mixin.js @@ -0,0 +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 diff --git a/src/mixins/textbox_behavior.mixin.js b/src/mixins/textbox_behavior.mixin.js new file mode 100644 index 00000000..dd427949 --- /dev/null +++ b/src/mixins/textbox_behavior.mixin.js @@ -0,0 +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) { + + 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; + } + } + }; + +})(); \ No newline at end of file diff --git a/src/mixins/textbox_click_behavior.mixin.js b/src/mixins/textbox_click_behavior.mixin.js new file mode 100644 index 00000000..36f06fdd --- /dev/null +++ b/src/mixins/textbox_click_behavior.mixin.js @@ -0,0 +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 diff --git a/src/shapes/itext.class.js b/src/shapes/itext.class.js index 277b37df..b3f2912b 100644 --- a/src/shapes/itext.class.js +++ b/src/shapes/itext.class.js @@ -59,14 +59,14 @@ /** * Index where text selection starts (or where cursor is when there is no selection) - * @type Nubmer + * @type Number * @default */ selectionStart: 0, /** * Index where text selection ends - * @type Nubmer + * @type Number * @default */ selectionEnd: 0, @@ -932,7 +932,7 @@ * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - if (this.textAlign === 'justify' && /\s/.test(_char)) { + if (this.textAlign === 'justify' && this._reSpacesAndTabs.test(_char)) { return this._getWidthOfSpace(ctx, lineIndex); } @@ -1008,7 +1008,7 @@ var line = this._textLines[lineIndex], wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), widthDiff = this.width - wordsWidth, - numSpaces = line.length - line.replace(/\s+/g, '').length, + numSpaces = line.length - line.replace(this._reSpacesAndTabs, '').length, width = widthDiff / numSpaces; this.__widthOfSpace[lineIndex] = width; return width; diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 66a9164f..81cc214f 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -59,6 +59,13 @@ */ _reNewline: /\r?\n/, + /** + * Use this regular expression to filter for whitespace that is not a new line. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reSpacesAndTabs: /[ \t\r]+/g, + /** * Retrieves object's fontSize * @method getFontSize @@ -329,7 +336,7 @@ ctx = fabric.util.createCanvasElement().getContext('2d'); this._setTextStyles(ctx); } - this._textLines = this.text.split(this._reNewline); + this._textLines = this._splitTextIntoLines(); this._clearCache(); var currentTextAlign = this.textAlign; this.textAlign = 'left'; @@ -798,6 +805,14 @@ ctx.restore(); }, + /** + * Returns the text as an array of lines. + * @returns {Array} Lines in the text + */ + _splitTextIntoLines: function() { + return this.text.split(this._reNewline); + }, + /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output