diff --git a/src/mixins/itext_behavior.mixin.js b/src/mixins/itext_behavior.mixin.js index 5182fb01..6ff8e566 100644 --- a/src/mixins/itext_behavior.mixin.js +++ b/src/mixins/itext_behavior.mixin.js @@ -841,7 +841,12 @@ linesLenght && this.insertNewlineStyleObject( cursorLoc.lineIndex, cursorLoc.charIndex + addedLines[0], linesLenght); for (var i = 1; i < linesLenght; i++) { - this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); + if (addedLines[i] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); + } + else if (copiedStyle) { + this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; + } copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1); } // we use i outside the loop to get it like linesLength diff --git a/src/mixins/itext_key_behavior.mixin.js b/src/mixins/itext_key_behavior.mixin.js index fb1496e6..dea16295 100644 --- a/src/mixins/itext_key_behavior.mixin.js +++ b/src/mixins/itext_key_behavior.mixin.js @@ -605,11 +605,42 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this._text.splice(start, end - start); this.text = this._text.join(''); this.set('dirty', true); - this._removeExtraneousStyles(); if (this._shouldClearDimensionCache()) { this.initDimensions(); this.setCoords(); } + this._removeExtraneousStyles(); + }, + + /** + * insert characters at start position, before start position. + * start equal 1 it means the text get inserted between actual grapheme 0 and 1 + * if style array is provided, it must be as the same length of text in graphemes + * if end is provided and is bigger than start, old text is replaced. + * start/end ar per grapheme position in _text array. + * + * @param {String} text text to insert + * @param {Array} style array of style objects + * @param {Number} start + * @param {Number} end default to start + 1 + */ + insertChars: function(text, style, start, end) { + if (typeof end === 'undefined') { + end = start; + } + if (end > start) { + this.removeStyleFromTo(start, end); + } + var graphemes = fabric.util.string.graphemeSplit(text); + this.insertNewStyleBlock(graphemes, start, style); + this._text = [].concat(this._text.slice(0, start), graphemes, this._text.slice(end)); + this.text = this._text.join(''); + this.set('dirty', true); + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this._removeExtraneousStyles(); }, }); diff --git a/test.js b/test.js index 5f0792c4..141eccd0 100644 --- a/test.js +++ b/test.js @@ -47,6 +47,7 @@ testrunner.run({ './test/unit/intersection.js', './test/unit/stateful.js' ], + tests: ['./test/unit/itext_key_behaviour.js',] }, function(err, report) { if (err) { console.log(err); diff --git a/test/unit/itext_key_behaviour.js b/test/unit/itext_key_behaviour.js index 61c8975e..f60a4b35 100644 --- a/test/unit/itext_key_behaviour.js +++ b/test/unit/itext_key_behaviour.js @@ -280,4 +280,61 @@ assert.deepEqual(iText._text, ['t','t'], 'text has been remoed'); assert.equal(iText.styles[0][1], undefined, 'style has been removed'); }); + + QUnit.test('insertChars', function(assert) { + var iText = new fabric.IText('test'); + assert.ok(typeof iText.insertChars === 'function'); + iText.insertChars('ab', null, 1); + assert.equal(iText.text, 'tabest', 'text has been added'); + assert.deepEqual(iText._text.join(''), 'tabest', '_text has been updated'); + }); + + QUnit.test('insertChars can remove chars', function(assert) { + var iText = new fabric.IText('test'); + iText.insertChars('ab', null, 1, 2); + assert.equal(iText.text, 'tabst', 'text has added'); + assert.deepEqual(iText._text.join(''), 'tabst', '_text has been updated'); + var iText = new fabric.IText('test'); + iText.insertChars('ab', null, 1, 4); + assert.equal(iText.text, 'tab', 'text has added'); + assert.deepEqual(iText._text.join(''), 'tab', '_text has been updated'); + }); + + QUnit.test('insertChars pick up the style of the character behind and replicates it', function(assert) { + var iText = new fabric.IText('test', { fontSize: 25, styles: { 0: { 0: { fill: 'red' }, 1: { fill: 'blue' }}}}); + iText.insertChars('ab', null, 1); + assert.equal(iText.styles[0][0].fill, 'red', 'style 0 0 did not change'); + assert.equal(iText.styles[0][1].fill, 'red', 'style 0 1 has been inserted red'); + assert.equal(iText.styles[0][2].fill, 'red', 'style 0 2 has been inserted red'); + assert.equal(iText.styles[0][3].fill, 'blue', 'style 0 3 was the old blue moved 2 char later'); + }); + + QUnit.test('insertChars removes style from the removed text', function(assert) { + var iText = new fabric.IText('test', { fontSize: 25, styles: { 0: { 0: { fill: 'red' }, 1: { fill: 'blue' }}}}); + iText.insertChars('ab', null, 1, 2); + assert.equal(iText.styles[0][0].fill, 'red', 'style 0 0 did not change'); + assert.equal(iText.styles[0][1].fill, 'red', 'style 0 1 has been inserted red'); + assert.equal(iText.styles[0][2].fill, 'red', 'style 0 2 has been inserted red'); + assert.equal(iText.styles[0][3], undefined, 'style 0 3 has been removed'); + }); + + QUnit.test('insertChars handles new lines correctly', function(assert) { + var iText = new fabric.IText('test', { fontSize: 25, styles: { 0: { 0: { fill: 'red' }, 1: { fill: 'blue' }}}}); + iText.insertChars('ab\n\n', null, 1); + assert.equal(iText.styles[0][0].fill, 'red', 'style 0 0 did not change'); + assert.equal(iText.styles[0][1].fill, 'red', 'style 0 1 has been inserted red'); + assert.equal(iText.styles[0][2].fill, 'red', 'style 0 2 has been inserted red'); + assert.equal(iText.styles[2][0].fill, 'blue', 'blue has been moved down'); + }); + + QUnit.test('insertChars can accept some style for the new text', function(assert) { + var iText = new fabric.IText('test', { fontSize: 25, styles: { 0: { 0: { fill: 'red' }, 1: { fill: 'blue' }}}}); + iText.insertChars('ab\n\na', [{ fill: 'col1'},{ fill: 'col2'},{ fill: 'col3'},{ fill: 'col4'},{ fill: 'col5'}], 1); + assert.equal(iText.styles[0][0].fill, 'red', 'style 0 0 did not change'); + assert.equal(iText.styles[0][1].fill, 'col1', 'style 0 1 has been inserted col1'); + assert.equal(iText.styles[0][2].fill, 'col2', 'style 0 2 has been inserted col2'); + assert.equal(iText.styles[1][0].fill, 'col4', 'style 1 0 has been inserted col4'); + assert.equal(iText.styles[2][0].fill, 'col5', 'style 2 0 has been inserted col5'); + assert.equal(iText.styles[2][1].fill, 'blue', 'style 2 1 has been inserted blue'); + }); })();