Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(itext): improved style handling for new lines #6268

Merged
merged 2 commits into from
Apr 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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