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

feat(Text): condensed styles structure v6 #8006

Merged
merged 13 commits into from
Jul 5, 2022
2 changes: 1 addition & 1 deletion src/mixins/itext.svg_export.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
// if we have charSpacing, we render char by char
actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);
nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);
timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle);
timeToRender = fabric.util.hasStyleChanged(actualStyle, nextStyle, true);
}
if (timeToRender) {
style = this._getStyleDeclaration(lineIndex, i) || { };
Expand Down
5 changes: 4 additions & 1 deletion src/shapes/itext.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,9 @@
* @returns {Promise<fabric.IText>}
*/
fabric.IText.fromObject = function(object) {
return fabric.Object._fromObject(fabric.IText, object, { extraParam: 'text' });
var styles = fabric.util.stylesFromArray(object.styles, object.text);
//copy object to prevent mutation
var objCopy = Object.assign({}, object, { styles: styles });
melchiar marked this conversation as resolved.
Show resolved Hide resolved
return fabric.Object._fromObject(fabric.IText, objCopy, { extraParam: 'text' });
};
})();
41 changes: 7 additions & 34 deletions src/shapes/text.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

'use strict';

var fabric = global.fabric || (global.fabric = { }),
clone = fabric.util.object.clone;
var fabric = global.fabric || (global.fabric = { });

if (fabric.Text) {
fabric.warn('fabric.Text is already defined');
Expand Down Expand Up @@ -1090,7 +1089,7 @@
// if we have charSpacing, we render char by char
actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);
nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);
timeToRender = this._hasStyleChanged(actualStyle, nextStyle);
timeToRender = fabric.util.hasStyleChanged(actualStyle, nextStyle, false);
}
if (timeToRender) {
if (path) {
Expand Down Expand Up @@ -1260,34 +1259,6 @@
return this;
},

/**
* @private
* @param {Object} prevStyle
* @param {Object} thisStyle
*/
_hasStyleChanged: function(prevStyle, thisStyle) {
return prevStyle.fill !== thisStyle.fill ||
prevStyle.stroke !== thisStyle.stroke ||
prevStyle.strokeWidth !== thisStyle.strokeWidth ||
prevStyle.fontSize !== thisStyle.fontSize ||
prevStyle.fontFamily !== thisStyle.fontFamily ||
prevStyle.fontWeight !== thisStyle.fontWeight ||
prevStyle.fontStyle !== thisStyle.fontStyle ||
prevStyle.deltaY !== thisStyle.deltaY;
},

/**
* @private
* @param {Object} prevStyle
* @param {Object} thisStyle
*/
_hasStyleChangedForSvg: function(prevStyle, thisStyle) {
return this._hasStyleChanged(prevStyle, thisStyle) ||
prevStyle.overline !== thisStyle.overline ||
prevStyle.underline !== thisStyle.underline ||
prevStyle.linethrough !== thisStyle.linethrough;
},

/**
* @private
* @param {Number} lineIndex index text line
Expand Down Expand Up @@ -1566,8 +1537,7 @@
toObject: function(propertiesToInclude) {
var allProperties = additionalProps.concat(propertiesToInclude);
var obj = this.callSuper('toObject', allProperties);
// styles will be overridden with a properly cloned structure
obj.styles = clone(this.styles, true);
obj.styles = fabric.util.stylesToArray(this.styles, this.text);
if (obj.path) {
obj.path = this.path.toObject();
}
Expand Down Expand Up @@ -1730,7 +1700,10 @@
* @returns {Promise<fabric.Text>}
*/
fabric.Text.fromObject = function(object) {
return fabric.Object._fromObject(fabric.Text, object, { extraParam: 'text' });
var styles = fabric.util.stylesFromArray(object.styles, object.text);
//copy object to prevent mutation
var objCopy = Object.assign({}, object, { styles: styles });
return fabric.Object._fromObject(fabric.Text, objCopy, { extraParam: 'text' });
};

fabric.Text.genericFonts = ['sans-serif', 'serif', 'cursive', 'fantasy', 'monospace'];
Expand Down
5 changes: 4 additions & 1 deletion src/shapes/textbox.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@
* @returns {Promise<fabric.Textbox>}
*/
fabric.Textbox.fromObject = function(object) {
return fabric.Object._fromObject(fabric.Textbox, object, { extraParam: 'text' });
var styles = fabric.util.stylesFromArray(object.styles, object.text);
//copy object to prevent mutation
var objCopy = Object.assign({}, object, { styles: styles });
return fabric.Object._fromObject(fabric.Textbox, objCopy, { extraParam: 'text' });
};
})(typeof exports !== 'undefined' ? exports : this);
107 changes: 107 additions & 0 deletions src/util/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1233,5 +1233,112 @@
}
return new fabric.Group([a], { clipPath: b, inverted: inverted });
},

/**
* @memberOf fabric.util
* @param {Object} prevStyle first style to compare
* @param {Object} thisStyle second style to compare
* @param {boolean} forTextSpans whether to check overline, underline, and line-through properties
* @return {boolean} true if the style changed
*/
hasStyleChanged: function(prevStyle, thisStyle, forTextSpans) {
forTextSpans = forTextSpans || false;
return (prevStyle.fill !== thisStyle.fill ||
prevStyle.stroke !== thisStyle.stroke ||
prevStyle.strokeWidth !== thisStyle.strokeWidth ||
prevStyle.fontSize !== thisStyle.fontSize ||
prevStyle.fontFamily !== thisStyle.fontFamily ||
prevStyle.fontWeight !== thisStyle.fontWeight ||
prevStyle.fontStyle !== thisStyle.fontStyle ||
prevStyle.deltaY !== thisStyle.deltaY) ||
(forTextSpans &&
(prevStyle.overline !== thisStyle.overline ||
prevStyle.underline !== thisStyle.underline ||
prevStyle.linethrough !== thisStyle.linethrough));
},

/**
* Returns the array form of a text object's inline styles property with styles grouped in ranges
* rather than per character. This format is less verbose, and is better suited for storage
* so it is used in serialization (not during runtime).
* @memberOf fabric.util
* @param {object} styles per character styles for a text object
* @param {String} text the text string that the styles are applied to
* @return {{start: number, end: number, style: object}[]}
*/
stylesToArray: function(styles, text) {
// clone style structure to prevent mutation
var styles = fabric.util.object.clone(styles, true),
textLines = text.split('\n'),
charIndex = -1, prevStyle = {}, stylesArray = [];
//loop through each textLine
for (var i = 0; i < textLines.length; i++) {
if (!styles[i]) {
//no styles exist for this line, so add the line's length to the charIndex total
charIndex += textLines[i].length;
continue;
}
//loop through each character of the current line
for (var c = 0; c < textLines[i].length; c++) {
charIndex++;
var thisStyle = styles[i][c];
//check if style exists for this character
if (thisStyle) {
var styleChanged = fabric.util.hasStyleChanged(prevStyle, thisStyle, true);
if (styleChanged) {
stylesArray.push({
start: charIndex,
end: charIndex + 1,
style: thisStyle
});
}
else {
//if style is the same as previous character, increase end index
stylesArray[stylesArray.length - 1].end++;
}
}
prevStyle = thisStyle || {};
}
}
return stylesArray;
},

/**
* Returns the object form of the styles property with styles that are assigned per
* character rather than grouped by range. This format is more verbose, and is
* only used during runtime (not for serialization/storage)
* @memberOf fabric.util
* @param {Array} styles the serialized form of a text object's styles
* @param {String} text the text string that the styles are applied to
* @return {Object}
*/
stylesFromArray: function(styles, text) {
if (!Array.isArray(styles)) {
return styles;
}
var textLines = text.split('\n'),
charIndex = -1, styleIndex = 0, stylesObject = {};
//loop through each textLine
for (var i = 0; i < textLines.length; i++) {
//loop through each character of the current line
for (var c = 0; c < textLines[i].length; c++) {
charIndex++;
//check if there's a style collection that includes the current character
if (styles[styleIndex]
&& styles[styleIndex].start <= charIndex
&& charIndex < styles[styleIndex].end) {
//create object for line index if it doesn't exist
stylesObject[i] = stylesObject[i] || {};
//assign a style at this character's index
stylesObject[i][c] = Object.assign({}, styles[styleIndex].style);
//if character is at the end of the current style collection, move to the next
if (charIndex === styles[styleIndex].end - 1) {
styleIndex++;
}
}
}
}
return stylesObject;
}
};
})(typeof exports !== 'undefined' ? exports : this);
32 changes: 24 additions & 8 deletions test/unit/itext.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
skewX: 0,
skewY: 0,
charSpacing: 0,
styles: { },
styles: [],
strokeUniform: false,
path: null,
direction: 'ltr',
Expand Down Expand Up @@ -131,22 +131,38 @@
});

QUnit.test('toObject', function(assert) {
var styles = {
var stylesObject = {
0: {
0: { fill: 'red' },
1: { textDecoration: 'underline' }
}
};
var stylesArray = [
{
start: 0,
end: 1,
style: { fill: 'red' }
},
{
start: 1,
end: 2,
style: { textDecoration: 'underline' }
}
];
var iText = new fabric.IText('test', {
styles: styles
styles: stylesObject
});
assert.equal(typeof iText.toObject, 'function');
var obj = iText.toObject();
assert.deepEqual(obj.styles, styles);
assert.notEqual(obj.styles[0], styles[0]);
assert.notEqual(obj.styles[0][1], styles[0][1]);
assert.deepEqual(obj.styles[0], styles[0]);
assert.deepEqual(obj.styles[0][1], styles[0][1]);
assert.deepEqual(obj.styles, stylesArray);
assert.notEqual(obj.styles[0], stylesArray[0]);
assert.notEqual(obj.styles[1], stylesArray[1]);
assert.notEqual(obj.styles[0].style, stylesArray[0].style);
assert.notEqual(obj.styles[1].style, stylesArray[1].style);
assert.deepEqual(obj.styles[0], stylesArray[0]);
assert.deepEqual(obj.styles[1], stylesArray[1]);
assert.deepEqual(obj.styles[0].style, stylesArray[0].style);
assert.deepEqual(obj.styles[1].style, stylesArray[1].style);
});

QUnit.test('setSelectionStart', function(assert) {
Expand Down
3 changes: 1 addition & 2 deletions test/unit/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
skewX: 0,
skewY: 0,
charSpacing: 0,
styles: {},
styles: [],
path: null,
strokeUniform: false,
direction: 'ltr',
Expand Down Expand Up @@ -264,7 +264,6 @@
fontSize: 123,
underline: true,
});

assert.deepEqual(textWithAttrs.toObject(), expectedObject);
});
});
Expand Down
Loading