Skip to content

Commit

Permalink
Rework SVG code to have clipPath friendly transformations (fabricjs#5284
Browse files Browse the repository at this point in the history
)

* so far i think i broke everything

* halp super hard

* this is what i wanted

* fixed error

* some fixes

* svg-working

* less code

* fixed shadow for text and groups

* fixed tests

* one file test more

* fixed lint

* works

* ok more tests

* remove a lint issue

* removed unused method
  • Loading branch information
asturur authored Oct 7, 2018
1 parent 053f1cd commit f2944a3
Show file tree
Hide file tree
Showing 25 changed files with 386 additions and 242 deletions.
25 changes: 10 additions & 15 deletions src/mixins/itext.svg_export.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
var markup = this._createBaseSVGMarkup(),
offsets = this._getSVGLeftTopOffsets(),
textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
this._wrapSVGTextAndBg(markup, textAndBg);

return reviver ? reviver(markup.join('')) : markup.join('');
var offsets = this._getSVGLeftTopOffsets(),
textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft),
internalMarkup = this._wrapSVGTextAndBg(textAndBg);
return this._createBaseSVGMarkup(
internalMarkup, { reviver: reviver, noStyle: true, withShadow: true });
},

/**
Expand All @@ -33,13 +32,10 @@
/**
* @private
*/
_wrapSVGTextAndBg: function(markup, textAndBg) {
var noShadow = true, filter = this.getSvgFilter(),
style = filter === '' ? '' : ' style="' + filter + '"',
_wrapSVGTextAndBg: function(textAndBg) {
var noShadow = true,
textDecoration = this.getSvgTextDecoration(this);
markup.push(
'\t<g ', this.getSvgCommons(), 'transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '"',
style, '>\n',
return [
textAndBg.textBgRects.join(''),
'\t\t<text xml:space="preserve" ',
(this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ' : ''),
Expand All @@ -49,9 +45,8 @@
(textDecoration ? 'text-decoration="' + textDecoration + '" ' : ''),
'style="', this.getSvgStyles(noShadow), '"', this.addPaintOrder(), ' >',
textAndBg.textSpans.join(''),
'</text>\n',
'\t</g>\n'
);
'</text>\n'
];
},

/**
Expand Down
130 changes: 82 additions & 48 deletions src/mixins/object.svg_export.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/
getSvgStyles: function(skipShadow) {

var fillRule = this.fillRule,
var fillRule = this.fillRule ? this.fillRule : 'nonzero',
strokeWidth = this.strokeWidth ? this.strokeWidth : '0',
strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none',
strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt',
Expand Down Expand Up @@ -128,53 +128,24 @@

/**
* Returns transform-string for svg-export
* @param {Boolean} use the full transform or the single object one.
* @return {String}
*/
getSvgTransform: function() {
var angle = this.angle,
skewX = (this.skewX % 360),
skewY = (this.skewY % 360),
center = this.getCenterPoint(),

NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,

translatePart = 'translate(' +
toFixed(center.x, NUM_FRACTION_DIGITS) +
' ' +
toFixed(center.y, NUM_FRACTION_DIGITS) +
')',

anglePart = angle !== 0
? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')')
: '',

scalePart = (this.scaleX === 1 && this.scaleY === 1)
? '' :
(' scale(' +
toFixed(this.scaleX, NUM_FRACTION_DIGITS) +
' ' +
toFixed(this.scaleY, NUM_FRACTION_DIGITS) +
')'),

skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '',

skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '',

flipXPart = this.flipX ? ' matrix(-1 0 0 1 0 0) ' : '',

flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 0)' : '';

return [
translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart
].join('');
getSvgTransform: function(full, additionalTransform) {
var transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(),
svgTransform = transform.map(function(value) {
return toFixed(value, fabric.Object.NUM_FRACTION_DIGITS);
}).join(' ');
return 'transform="matrix(' + svgTransform + ')' +
(additionalTransform || '') + this.getSvgTransformMatrix() + '" ';
},

/**
* Returns transform-string for svg-export from the transform matrix of single elements
* @return {String}
*/
getSvgTransformMatrix: function() {
return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : '';
return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : '';
},

_setSVGBg: function(textBgRects) {
Expand All @@ -195,12 +166,77 @@
}
},

/**
* Returns svg representation of an instance
* @param {Function} [reviver] Method for further parsing of svg representation.
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver });
},

/**
* Returns svg clipPath representation of an instance
* @param {Function} [reviver] Method for further parsing of svg representation.
* @return {String} svg representation of an instance
*/
toClipPathSVG: function(reviver) {
return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(), { reviver: reviver });
},

/**
* @private
*/
_createBaseSVGMarkup: function() {
var markup = [], clipPath = this.clipPath;
_createBaseClipPathSVGMarkup: function(objectMarkup, options) {
options = options || {};
var reviver = options.reviver,
additionalTransform = options.additionalTransform || '',
commonPieces = [
this.getSvgTransform(true, additionalTransform),
this.getSvgCommons(),
].join(''),
// insert commons in the markup, style and svgCommons
index = objectMarkup.indexOf('COMMON_PARTS');
objectMarkup[index] = commonPieces;
return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join('');
},

/**
* @private
*/
_createBaseSVGMarkup: function(objectMarkup, options) {
options = options || {};
var noStyle = options.noStyle, withShadow = options.withShadow,
reviver = options.reviver,
styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ',
shadowInfo = withShadow ? 'style="' + this.getSvgFilter() + '" ' : '',
clipPath = this.clipPath,
absoluteClipPath = this.clipPath && this.clipPath.absolutePositioned,
commonPieces, markup = [], clipPathMarkup,
// insert commons in the markup, style and svgCommons
index = objectMarkup.indexOf('COMMON_PARTS');
if (clipPath) {
clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
clipPathMarkup = '<clipPath id="' + clipPath.clipPathId + '" >\n' +
this.clipPath.toClipPathSVG(reviver) +
'</clipPath>\n';
}
if (absoluteClipPath) {
markup.push(
'<g ', shadowInfo, this.getSvgCommons(), ' >\n'
);
}
markup.push(
'<g ',
this.getSvgTransform(false),
!absoluteClipPath ? shadowInfo + this.getSvgCommons() : '',
' >\n'
);
commonPieces = [
styleInfo,
noStyle ? '' : this.addPaintOrder(), ' '
].join('');
objectMarkup[index] = commonPieces;
if (this.fill && this.fill.toLive) {
markup.push(this.fill.toSVG(this, false));
}
Expand All @@ -211,14 +247,12 @@
markup.push(this.shadow.toSVG(this));
}
if (clipPath) {
clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
markup.push(
'<clipPath id="' + clipPath.clipPathId + '" >\n\t',
this.clipPath.toSVG(),
'</clipPath>\n'
);
markup.push(clipPathMarkup);
}
return markup;
markup.push(objectMarkup.join(''));
markup.push('</g>\n');
absoluteClipPath && markup.push('</g>\n');
return reviver ? reviver(markup.join('')) : markup.join('');
},

addPaintOrder: function() {
Expand Down
35 changes: 13 additions & 22 deletions src/shapes/circle.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,47 +78,38 @@
},

/* _TO_SVG_START_ */

/**
* Returns svg representation of an instance
* @param {Function} [reviver] Method for further parsing of svg representation.
* @return {String} svg representation of an instance
* @return {Array} an array of strings with the specific svg representation
* of the instance
*/
toSVG: function(reviver) {
var markup = this._createBaseSVGMarkup(), x = 0, y = 0,
_toSVG: function() {
var svgString, x = 0, y = 0,
angle = (this.endAngle - this.startAngle) % ( 2 * pi);

if (angle === 0) {
markup.push(
'<circle ', this.getSvgCommons(),
svgString = [
'<circle ', 'COMMON_PARTS',
'cx="' + x + '" cy="' + y + '" ',
'r="', this.radius,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
' ', this.getSvgTransformMatrix(), '"',
this.addPaintOrder(),
'/>\n'
);
'" />\n'
];
}
else {
var startX = fabric.util.cos(this.startAngle) * this.radius,
startY = fabric.util.sin(this.startAngle) * this.radius,
endX = fabric.util.cos(this.endAngle) * this.radius,
endY = fabric.util.sin(this.endAngle) * this.radius,
largeFlag = angle > pi ? '1' : '0';

markup.push(
svgString = [
'<path d="M ' + startX + ' ' + startY,
' A ' + this.radius + ' ' + this.radius,
' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
' ', this.getSvgTransformMatrix(), '"',
this.addPaintOrder(),
'/>\n'
);
'"', 'COMMON_PARTS', ' />\n'
];
}

return reviver ? reviver(markup.join('')) : markup.join('');
return svgString;
},
/* _TO_SVG_END_ */

Expand Down
21 changes: 7 additions & 14 deletions src/shapes/ellipse.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,17 @@
/* _TO_SVG_START_ */
/**
* Returns svg representation of an instance
* @param {Function} [reviver] Method for further parsing of svg representation.
* @return {String} svg representation of an instance
* @return {Array} an array of strings with the specific svg representation
* of the instance
*/
toSVG: function(reviver) {
var markup = this._createBaseSVGMarkup();
markup.push(
'<ellipse ', this.getSvgCommons(),
_toSVG: function() {
return [
'<ellipse ', 'COMMON_PARTS',
'cx="0" cy="0" ',
'rx="', this.rx,
'" ry="', this.ry,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
this.getSvgTransformMatrix(), '"',
this.addPaintOrder(),
'/>\n'
);

return reviver ? reviver(markup.join('')) : markup.join('');
'" />\n'
];
},
/* _TO_SVG_END_ */

Expand Down
32 changes: 19 additions & 13 deletions src/shapes/group.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,24 +521,30 @@
* @return {String} svg representation of an instance
*/
toSVG: function(reviver) {
var markup = this._createBaseSVGMarkup();
markup.push(
'<g ', this.getSvgCommons(), 'transform="',
/* avoiding styles intentionally */
this.getSvgTransform(),
this.getSvgTransformMatrix(),
'" style="',
this.getSvgFilter(),
'">\n'
);
var svgString = [];

for (var i = 0, len = this._objects.length; i < len; i++) {
markup.push('\t', this._objects[i].toSVG(reviver));
svgString.push('\t', this._objects[i].toSVG(reviver));
}

markup.push('</g>\n');
return this._createBaseSVGMarkup(
svgString,
{ reviver: reviver, noStyle: true, withShadow: true });
},

/**
* Returns svg clipPath representation of an instance
* @param {Function} [reviver] Method for further parsing of svg representation.
* @return {String} svg representation of an instance
*/
toClipPathSVG: function(reviver) {
var svgString = [];

for (var i = 0, len = this._objects.length; i < len; i++) {
svgString.push('\t', this._objects[i].toClipPathSVG(reviver));
}

return reviver ? reviver(markup.join('')) : markup.join('');
return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver });
},
/* _TO_SVG_END_ */
});
Expand Down
Loading

0 comments on commit f2944a3

Please sign in to comment.