Skip to content

Commit

Permalink
fix(itext): improved style handling for new lines (#6268)
Browse files Browse the repository at this point in the history
* fix(itext): carry over style of selected test when replacing by typing (#6172)

* more changes to style handling

Co-authored-by: MVS <[email protected]>
  • Loading branch information
asturur and MVS authored Apr 12, 2020
1 parent cb29618 commit ce22409
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 29 deletions.
36 changes: 24 additions & 12 deletions src/mixins/itext_behavior.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,10 @@
},

/**
* Inserts new style object
* Handle insertion of more consecutive style lines for when one or more
* newlines gets added to the text. Since current style needs to be shifted
* first we shift the current style of the number lines needed, then we add
* new lines from the last to the first.
* @param {Number} lineIndex Index of a line
* @param {Number} charIndex Index of a char
* @param {Number} qty number of lines to add
Expand All @@ -734,14 +737,14 @@
insertNewlineStyleObject: function(lineIndex, charIndex, qty, copiedStyle) {
var currentCharStyle,
newLineStyles = {},
somethingAdded = false;
somethingAdded = false,
isEndOfLine = this._unwrappedTextLines[lineIndex].length === charIndex;

qty || (qty = 1);
this.shiftLineStyles(lineIndex, qty);
if (this.styles[lineIndex]) {
currentCharStyle = this.styles[lineIndex][charIndex === 0 ? charIndex : charIndex - 1];
}

// we clone styles of all chars
// after cursor onto the current line
for (var index in this.styles[lineIndex]) {
Expand All @@ -750,28 +753,35 @@
somethingAdded = true;
newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index];
// remove lines from the previous line since they're on a new line now
delete this.styles[lineIndex][index];
if (!(isEndOfLine && charIndex === 0)) {
delete this.styles[lineIndex][index];
}
}
}
if (somethingAdded) {
var styleCarriedOver = false;
if (somethingAdded && !isEndOfLine) {
// if is end of line, the extra style we copied
// is probably not something we want
this.styles[lineIndex + qty] = newLineStyles;
styleCarriedOver = true;
}
else {
delete this.styles[lineIndex + qty];
if (styleCarriedOver) {
// skip the last line of since we already prepared it.
qty--;
}
// for the other lines
// for the all the lines or all the other lines
// we clone current char style onto the next (otherwise empty) line
while (qty > 1) {
qty--;
if (copiedStyle && copiedStyle[qty]) {
this.styles[lineIndex + qty] = { 0: clone(copiedStyle[qty]) };
while (qty > 0) {
if (copiedStyle && copiedStyle[qty - 1]) {
this.styles[lineIndex + qty] = { 0: clone(copiedStyle[qty - 1]) };
}
else if (currentCharStyle) {
this.styles[lineIndex + qty] = { 0: clone(currentCharStyle) };
}
else {
delete this.styles[lineIndex + qty];
}
qty--;
}
this._forceClearCache = true;
},
Expand Down Expand Up @@ -834,6 +844,7 @@
insertNewStyleBlock: function(insertedText, start, copiedStyle) {
var cursorLoc = this.get2DCursorLocation(start, true),
addedLines = [0], linesLength = 0;
// get an array of how many char per lines are being added.
for (var i = 0; i < insertedText.length; i++) {
if (insertedText[i] === '\n') {
linesLength++;
Expand All @@ -843,6 +854,7 @@
addedLines[linesLength]++;
}
}
// for the first line copy the style from the current char position.
if (addedLines[0] > 0) {
this.insertCharStyleObject(cursorLoc.lineIndex, cursorLoc.charIndex, addedLines[0], copiedStyle);
copiedStyle = copiedStyle && copiedStyle.slice(addedLines[0] + 1);
Expand Down
47 changes: 32 additions & 15 deletions src/mixins/itext_key_behavior.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
charCount = this._text.length,
nextCharCount = nextText.length,
removedText, insertedText,
charDiff = nextCharCount - charCount;
charDiff = nextCharCount - charCount,
selectionStart = this.selectionStart, selectionEnd = this.selectionEnd,
selection = selectionStart !== selectionEnd,
copiedStyle, removeFrom, removeTo;
if (this.hiddenTextarea.value === '') {
this.styles = { };
this.updateFromTextArea();
Expand All @@ -165,40 +168,54 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
this.hiddenTextarea.selectionEnd,
this.hiddenTextarea.value
);
var backDelete = this.selectionStart > textareaSelection.selectionStart;
var backDelete = selectionStart > textareaSelection.selectionStart;

if (this.selectionStart !== this.selectionEnd) {
removedText = this._text.slice(this.selectionStart, this.selectionEnd);
charDiff += this.selectionEnd - this.selectionStart;
if (selection) {
removedText = this._text.slice(selectionStart, selectionEnd);
charDiff += selectionEnd - selectionStart;
}
else if (nextCharCount < charCount) {
if (backDelete) {
removedText = this._text.slice(this.selectionEnd + charDiff, this.selectionEnd);
removedText = this._text.slice(selectionEnd + charDiff, selectionEnd);
}
else {
removedText = this._text.slice(this.selectionStart, this.selectionStart - charDiff);
removedText = this._text.slice(selectionStart, selectionStart - charDiff);
}
}
insertedText = nextText.slice(textareaSelection.selectionEnd - charDiff, textareaSelection.selectionEnd);
if (removedText && removedText.length) {
if (this.selectionStart !== this.selectionEnd) {
this.removeStyleFromTo(this.selectionStart, this.selectionEnd);
if (insertedText.length) {
// let's copy some style before deleting.
// we want to copy the style before the cursor OR the style at the cursor if selection
// is bigger than 0.
copiedStyle = this.getSelectionStyles(selectionStart, selectionStart + 1, true);
// now duplicate the style one for each inserted text.
copiedStyle = insertedText.map(function() {
// this return an array of references, but that is fine since we are
// copying the style later.
return copiedStyle[0];
});
}
if (selection) {
removeFrom = selectionStart;
removeTo = selectionEnd;
}
else if (backDelete) {
// detect differencies between forwardDelete and backDelete
this.removeStyleFromTo(this.selectionEnd - removedText.length, this.selectionEnd);
removeFrom = selectionEnd - removedText.length;
removeTo = selectionEnd;
}
else {
this.removeStyleFromTo(this.selectionEnd, this.selectionEnd + removedText.length);
removeFrom = selectionEnd;
removeTo = selectionEnd + removedText.length;
}
this.removeStyleFromTo(removeFrom, removeTo);
}
if (insertedText.length) {
if (fromPaste && insertedText.join('') === fabric.copiedText && !fabric.disableStyleCopyPaste) {
this.insertNewStyleBlock(insertedText, this.selectionStart, fabric.copiedTextStyle);
}
else {
this.insertNewStyleBlock(insertedText, this.selectionStart);
copiedStyle = fabric.copiedTextStyle;
}
this.insertNewStyleBlock(insertedText, selectionStart, copiedStyle);
}
this.updateFromTextArea();
this.fire('changed');
Expand Down
2 changes: 2 additions & 0 deletions src/shapes/itext.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@
* High level function to know the height of the cursor.
* the currentChar is the one that precedes the cursor
* Returns fontSize of char at the current cursor
* Unused from the library, is for the end user
* @return {Number} Character font size
*/
getCurrentCharFontSize: function() {
Expand All @@ -468,6 +469,7 @@
* High level function to know the color of the cursor.
* the currentChar is the one that precedes the cursor
* Returns color (fill) of char at the current cursor
* Unused from the library, is for the end user
* @return {String} Character color (fill)
*/
getCurrentCharColor: function() {
Expand Down
15 changes: 13 additions & 2 deletions test/unit/itext.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,24 @@

assert.equal(typeof iText.insertNewlineStyleObject, 'function');

iText.insertNewlineStyleObject(0, 4, true);
iText.insertNewlineStyleObject(0, 4, 1);
assert.deepEqual(iText.styles, { }, 'does not insert empty styles');
iText.styles = { 1: { 0: { fill: 'blue' } } };
iText.insertNewlineStyleObject(0, 4, true);
iText.insertNewlineStyleObject(0, 4, 1);
assert.deepEqual(iText.styles, { 2: { 0: { fill: 'blue' } } }, 'correctly shift styles');
});

QUnit.test('insertNewlineStyleObject with existing style', function(assert) {
var iText = new fabric.IText('test\n2');

iText.styles = { 0: { 3: { fill: 'red' } }, 1: { 0: { fill: 'blue' } } };
iText.insertNewlineStyleObject(0, 4, 3);
assert.deepEqual(iText.styles[4], { 0: { fill: 'blue' } }, 'correctly shift styles 3 lines');
assert.deepEqual(iText.styles[3], { 0: { fill: 'red' } }, 'correctly copied previous style line 3');
assert.deepEqual(iText.styles[2], { 0: { fill: 'red' } }, 'correctly copied previous style line 2');
assert.deepEqual(iText.styles[1], { 0: { fill: 'red' } }, 'correctly copied previous style line 1');
});

QUnit.test('shiftLineStyles', function(assert) {
var iText = new fabric.IText('test\ntest\ntest', {
styles: {
Expand Down

0 comments on commit ce22409

Please sign in to comment.