Skip to content

Commit

Permalink
feat(Text): condensed styles structure for export (#7842)
Browse files Browse the repository at this point in the history
  • Loading branch information
melchiar committed Jun 12, 2022
1 parent 6ca7ebd commit 3f4fe92
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 48 deletions.
2 changes: 1 addition & 1 deletion src/mixins/itext.svg_export.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,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
1 change: 1 addition & 0 deletions src/shapes/itext.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@
* @param {function} [callback] invoked with new instance as argument
*/
fabric.IText.fromObject = function(object, callback) {
object.styles = fabric.util.stylesFromArray(object.styles, object.text);
parseDecoration(object);
if (object.styles) {
for (var i in object.styles) {
Expand Down
34 changes: 3 additions & 31 deletions src/shapes/text.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,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 @@ -1249,34 +1249,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 @@ -1538,8 +1510,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 @@ -1705,6 +1676,7 @@
var objectCopy = clone(object), path = object.path;
delete objectCopy.path;
return fabric.Object._fromObject('Text', objectCopy, function(textInstance) {
textInstance.styles = fabric.util.stylesFromArray(object.styles, object.text);
if (path) {
fabric.Object._fromObject('Path', path, function(pathInstance) {
textInstance.set('path', pathInstance);
Expand Down
1 change: 1 addition & 0 deletions src/shapes/textbox.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@
* @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created
*/
fabric.Textbox.fromObject = function(object, callback) {
object.styles = fabric.util.stylesFromArray(object.styles, object.text);
return fabric.Object._fromObject('Textbox', object, callback, '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 @@ -1215,5 +1215,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);
37 changes: 28 additions & 9 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 @@ -105,7 +105,10 @@
assert.ok(typeof fabric.IText.fromObject === 'function');
fabric.IText.fromObject(ITEXT_OBJECT, function(iText) {
assert.ok(iText instanceof fabric.IText);
assert.deepEqual(ITEXT_OBJECT, iText.toObject());
// change styles from array to object for comparison
var object = iText.toObject();
object.styles = {};
assert.deepEqual(ITEXT_OBJECT, object);
done();
});
});
Expand All @@ -131,22 +134,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
2 changes: 1 addition & 1 deletion 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
Loading

0 comments on commit 3f4fe92

Please sign in to comment.