From f2944a359d80e56fd03b4588573da4c4f5663493 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 7 Oct 2018 15:43:37 -0400 Subject: [PATCH] Rework SVG code to have clipPath friendly transformations (#5284) * 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 --- src/mixins/itext.svg_export.js | 25 +++--- src/mixins/object.svg_export.js | 130 ++++++++++++++++++++------------ src/shapes/circle.class.js | 35 ++++----- src/shapes/ellipse.class.js | 21 ++---- src/shapes/group.class.js | 32 ++++---- src/shapes/image.class.js | 36 +++++---- src/shapes/line.class.js | 24 +++--- src/shapes/path.class.js | 49 +++++++----- src/shapes/polyline.class.js | 21 ++---- src/shapes/rect.class.js | 23 +++--- src/shapes/triangle.class.js | 28 +++---- src/static_canvas.class.js | 22 +++++- test/unit/canvas_static.js | 12 ++- test/unit/circle.js | 10 ++- test/unit/ellipse.js | 25 +++++- test/unit/group.js | 28 ++++++- test/unit/header.js | 17 +++++ test/unit/image.js | 6 +- test/unit/line.js | 6 ++ test/unit/path.js | 32 +++++++- test/unit/polygon.js | 7 ++ test/unit/polyline.js | 7 ++ test/unit/rect.js | 10 +-- test/unit/text_to_svg.js | 12 +-- test/visual/svg_export.js | 10 +-- 25 files changed, 386 insertions(+), 242 deletions(-) create mode 100644 test/unit/header.js diff --git a/src/mixins/itext.svg_export.js b/src/mixins/itext.svg_export.js index 307e77be5c1..5be8ec34586 100644 --- a/src/mixins/itext.svg_export.js +++ b/src/mixins/itext.svg_export.js @@ -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 }); }, /** @@ -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\n', + return [ textAndBg.textBgRects.join(''), '\t\t', textAndBg.textSpans.join(''), - '\n', - '\t\n' - ); + '\n' + ]; }, /** diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index 931f5c64eba..47f9058fec1 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -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', @@ -128,45 +128,16 @@ /** * 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() + '" '; }, /** @@ -174,7 +145,7 @@ * @return {String} */ getSvgTransformMatrix: function() { - return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : ''; + return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; }, _setSVGBg: function(textBgRects) { @@ -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 = '\n' + + this.clipPath.toClipPathSVG(reviver) + + '\n'; + } + if (absoluteClipPath) { + markup.push( + '\n' + ); + } + markup.push( + '\n' + ); + commonPieces = [ + styleInfo, + noStyle ? '' : this.addPaintOrder(), ' ' + ].join(''); + objectMarkup[index] = commonPieces; if (this.fill && this.fill.toLive) { markup.push(this.fill.toSVG(this, false)); } @@ -211,14 +247,12 @@ markup.push(this.shadow.toSVG(this)); } if (clipPath) { - clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; - markup.push( - '\n\t', - this.clipPath.toSVG(), - '\n' - ); + markup.push(clipPathMarkup); } - return markup; + markup.push(objectMarkup.join('')); + markup.push('\n'); + absoluteClipPath && markup.push('\n'); + return reviver ? reviver(markup.join('')) : markup.join(''); }, addPaintOrder: function() { diff --git a/src/shapes/circle.class.js b/src/shapes/circle.class.js index 25f7a4fc56c..2cc6a72bd6d 100644 --- a/src/shapes/circle.class.js +++ b/src/shapes/circle.class.js @@ -78,26 +78,23 @@ }, /* _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( - '\n' - ); + '" />\n' + ]; } else { var startX = fabric.util.cos(this.startAngle) * this.radius, @@ -105,20 +102,14 @@ endX = fabric.util.cos(this.endAngle) * this.radius, endY = fabric.util.sin(this.endAngle) * this.radius, largeFlag = angle > pi ? '1' : '0'; - - markup.push( + svgString = [ '\n' - ); + '"', 'COMMON_PARTS', ' />\n' + ]; } - - return reviver ? reviver(markup.join('')) : markup.join(''); + return svgString; }, /* _TO_SVG_END_ */ diff --git a/src/shapes/ellipse.class.js b/src/shapes/ellipse.class.js index 0492a3002d6..bad5c55dbba 100644 --- a/src/shapes/ellipse.class.js +++ b/src/shapes/ellipse.class.js @@ -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( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index 95b53f54834..ebdda3e54c8 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -521,24 +521,30 @@ * @return {String} svg representation of an instance */ toSVG: function(reviver) { - var markup = this._createBaseSVGMarkup(); - markup.push( - '\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('\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_ */ }); diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index 0ac483b0ed2..1a6c9af3def 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -286,53 +286,51 @@ /* _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 + * Returns 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 = -this.width / 2, y = -this.height / 2, clipPath = ''; + _toSVG: function() { + var svgString = [], imageMarkup = [], strokeSvg, + x = -this.width / 2, y = -this.height / 2, clipPath = ''; if (this.hasCrop()) { var clipPathId = fabric.Object.__uid++; - markup.push( + svgString.push( '\n', '\t\n', '\n' ); clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" '; } - markup.push('\n'); - var imageMarkup = ['\t\n']; - if (this.paintFirst === 'fill') { - Array.prototype.push.apply(markup, imageMarkup); - } + '>\n'); + if (this.stroke || this.strokeDashArray) { var origFill = this.fill; this.fill = null; - markup.push( + strokeSvg = [ '\t\n' - ); + ]; this.fill = origFill; } if (this.paintFirst !== 'fill') { - Array.prototype.push.apply(markup, imageMarkup); + svgString = svgString.concat(strokeSvg, imageMarkup); } - markup.push('\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); + else { + svgString = svgString.concat(imageMarkup, strokeSvg); + } + return svgString; }, /* _TO_SVG_END_ */ diff --git a/src/shapes/line.class.js b/src/shapes/line.class.js index 9176c61edaa..26343258ed9 100644 --- a/src/shapes/line.class.js +++ b/src/shapes/line.class.js @@ -247,26 +247,20 @@ /* _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 + * Returns 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(), - p = this.calcLinePoints(); - markup.push( - '\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ }); diff --git a/src/shapes/path.class.js b/src/shapes/path.class.js index a81b8964a7a..d4b1b690c77 100644 --- a/src/shapes/path.class.js +++ b/src/shapes/path.class.js @@ -8,6 +8,7 @@ extend = fabric.util.object.extend, _toString = Object.prototype.toString, drawArc = fabric.util.drawArc, + toFixed = fabric.util.toFixed, commandLengths = { m: 2, l: 2, @@ -466,29 +467,39 @@ /* _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 chunks = [], - markup = this._createBaseSVGMarkup(), addTransform = ''; - - for (var i = 0, len = this.path.length; i < len; i++) { - chunks.push(this.path[i].join(' ')); - } - var path = chunks.join(' '); - addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; - markup.push( - '\n' - ); + ]; + }, - return reviver ? reviver(markup.join('')) : markup.join(''); + _getOffsetTransform: function() { + var digits = fabric.Object.NUM_FRACTION_DIGITS; + return ' translate(' + toFixed(-this.pathOffset.x, digits) + ', ' + + toFixed(-this.pathOffset.y, digits) + ')'; + }, + + /** + * 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 additionalTransform = this._getOffsetTransform(); + return '\t' + this._createBaseClipPathSVGMarkup( + this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform } + ); }, /* _TO_SVG_END_ */ diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index 42d7efc29bd..b01601aa33e 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -117,12 +117,11 @@ /* _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) { + _toSVG: function() { var points = [], diffX = this.pathOffset.x, diffY = this.pathOffset.y, - markup = this._createBaseSVGMarkup(), NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; for (var i = 0, len = this.points.length; i < len; i++) { @@ -131,17 +130,11 @@ toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ' ); } - markup.push( - '<', this.type, ' ', this.getSvgCommons(), + return [ + '<' + this.type + ' ', 'COMMON_PARTS', 'points="', points.join(''), - '" style="', this.getSvgStyles(), - '" transform="', this.getSvgTransform(), - ' ', this.getSvgTransformMatrix(), '"', - this.addPaintOrder(), - '/>\n' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ diff --git a/src/shapes/rect.class.js b/src/shapes/rect.class.js index 2a3dd1e539c..24d7ca6113f 100644 --- a/src/shapes/rect.class.js +++ b/src/shapes/rect.class.js @@ -144,23 +144,18 @@ /* _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 = -this.width / 2, y = -this.height / 2; - markup.push( - '\n'); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />\n' + ]; }, /* _TO_SVG_END_ */ }); diff --git a/src/shapes/triangle.class.js b/src/shapes/triangle.class.js index f171b6f5895..e7226d5ad7e 100644 --- a/src/shapes/triangle.class.js +++ b/src/shapes/triangle.class.js @@ -73,31 +73,23 @@ /* _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 + * Returns 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(), - widthBy2 = this.width / 2, + _toSVG: function() { + var widthBy2 = this.width / 2, heightBy2 = this.height / 2, points = [ -widthBy2 + ' ' + heightBy2, '0 ' + -heightBy2, widthBy2 + ' ' + heightBy2 - ] - .join(','); - - markup.push( - '' - ); - - return reviver ? reviver(markup.join('')) : markup.join(''); + '" />' + ]; }, /* _TO_SVG_END_ */ }); diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index a3a82af732d..9e05f8d50fc 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -1288,7 +1288,7 @@ */ toSVG: function(options, reviver) { options || (options = { }); - + options.reviver = reviver; var markup = []; this._setSVGPreamble(markup, options); @@ -1296,9 +1296,13 @@ this._setSVGBgOverlayColor(markup, 'backgroundColor'); this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver); - + if (this.clipPath) { + markup.push('\n'); + } this._setSVGObjects(markup, reviver); - + if (this.clipPath) { + markup.push('\n'); + } this._setSVGBgOverlayColor(markup, 'overlayColor'); this._setSVGBgOverlayImage(markup, 'overlayImage', reviver); @@ -1361,10 +1365,22 @@ '\n', this.createSVGFontFacesMarkup(), this.createSVGRefElementsMarkup(), + this.createSVGClipPathMarkup(options), '\n' ); }, + createSVGClipPathMarkup: function(options) { + var clipPath = this.clipPath; + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + return '\n' + + this.clipPath.toClipPathSVG(options.reviver) + + '\n'; + } + return ''; + }, + /** * Creates markup containing SVG referenced elements like patterns, gradients etc. * @return {String} diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index 97b6e9d55dc..e730bad81d1 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -155,6 +155,7 @@ QUnit.module('fabric.StaticCanvas', { beforeEach: function() { + fabric.Object.__uid = 0; canvas.clear(); canvas.backgroundColor = fabric.StaticCanvas.prototype.backgroundColor; canvas.backgroundImage = fabric.StaticCanvas.prototype.backgroundImage; @@ -866,6 +867,15 @@ canvas.renderOnAddRemove = true; }); + QUnit.test('toSVG with a clipPath', function(assert) { + var canvasClip = new fabric.StaticCanvas(null, { width: 400, height: 400 }); + canvasClip.clipPath = new fabric.Rect({ width: 200, height: 200 }); + canvasClip.add(new fabric.Circle({ radius: 200 })); + var svg = canvasClip.toSVG(); + var expectedSVG = '\n\n\nCreated with Fabric.js 2.4.1\n\n\n\t\n\n\n\n\n\n\n\n'; + assert.equal(svg, expectedSVG, 'SVG with clipPath should match'); + }); + QUnit.test('toSVG with exclude from export background', function(assert) { var image = fabric.document.createElement('img'), imageBG = new fabric.Image(image, {width: 0, height: 0}), @@ -874,7 +884,7 @@ canvas.renderOnAddRemove = false; canvas.backgroundImage = imageBG; canvas.overlayImage = imageOL; - var expectedSVG = '\n\n\nCreated with Fabric.js ' + fabric.version + '\n\n\n\n\t\n\n\n\t\n\n'; + var expectedSVG = '\n\n\nCreated with Fabric.js 2.4.1\n\n\n\n\t\n\n\n\t\n\n'; var svg1 = canvas.toSVG(); assert.equal(svg1, expectedSVG, 'svg with bg and overlay do not match'); imageBG.excludeFromExport = true; diff --git a/test/unit/circle.js b/test/unit/circle.js index 73f5f0e5c9e..a11d51e796f 100644 --- a/test/unit/circle.js +++ b/test/unit/circle.js @@ -140,15 +140,17 @@ QUnit.test('toSVG with full circle', function(assert) { var circle = new fabric.Circle({ width: 100, height: 100, radius: 10 }); var svg = circle.toSVG(); - - assert.equal(svg, '\n'); + var svgClipPath = circle.toClipPathSVG(); + assert.equal(svg, '\n\n\n'); + assert.equal(svgClipPath, '\t\n', 'circle as clipPath'); }); QUnit.test('toSVG with half circle', function(assert) { var circle = new fabric.Circle({ width: 100, height: 100, radius: 10, endAngle: Math.PI }); var svg = circle.toSVG(); - - assert.equal(svg, '\n'); + var svgClipPath = circle.toClipPathSVG(); + assert.equal(svg, '\n\n\n'); + assert.equal(svgClipPath, '\t\n', 'half circle as clipPath'); }); QUnit.test('fromElement', function(assert) { diff --git a/test/unit/ellipse.js b/test/unit/ellipse.js index b57203f499a..03c8d6ce36f 100644 --- a/test/unit/ellipse.js +++ b/test/unit/ellipse.js @@ -1,6 +1,10 @@ (function(){ - QUnit.module('fabric.Ellipse'); + QUnit.module('fabric.Ellipse', { + beforeEach: function() { + fabric.Object.__uid = 0; + } + }); QUnit.test('constructor', function(assert) { assert.ok(fabric.Ellipse); @@ -93,6 +97,25 @@ assert.equal(wasRenderCalled, false, 'should not render when rx/ry are 0'); }); + QUnit.test('toSVG', function(assert) { + var ellipse = new fabric.Ellipse({ rx: 100, ry: 12, fill: 'red', stroke: 'blue' }); + assert.equal(ellipse.toSVG(), '\n\n\n', 'SVG should match'); + assert.equal(ellipse.toClipPathSVG(), '\t\n', 'SVG clippath should match'); + }); + + QUnit.test('toSVG with a clipPath', function(assert) { + var ellipse = new fabric.Ellipse({ rx: 100, ry: 12, fill: 'red', stroke: 'blue' }); + ellipse.clipPath = new fabric.Ellipse({ rx: 12, ry: 100, left: 60, top: -50 }); + assert.equal(ellipse.toSVG(), '\n\n\t\n\n\n\n', 'SVG with clipPath should match'); + }); + + QUnit.test('toSVG with a clipPath absolute positioned', function(assert) { + var ellipse = new fabric.Ellipse({ rx: 100, ry: 12, fill: 'red', stroke: 'blue' }); + ellipse.clipPath = new fabric.Ellipse({ rx: 12, ry: 100, left: 60, top: -50 }); + ellipse.clipPath.absolutePositioned = true; + assert.equal(ellipse.toSVG(), '\n\n\n\t\n\n\n\n\n', 'SVG with clipPath should match'); + }); + QUnit.test('fromElement', function(assert) { assert.ok(typeof fabric.Ellipse.fromElement === 'function'); diff --git a/test/unit/group.js b/test/unit/group.js index ffadb914456..88f95b7cd9f 100644 --- a/test/unit/group.js +++ b/test/unit/group.js @@ -26,6 +26,7 @@ QUnit.module('fabric.Group', { afterEach: function() { + fabric.Object.__uid = 0; canvas.clear(); canvas.backgroundColor = fabric.Canvas.prototype.backgroundColor; canvas.calcOffset(); @@ -414,7 +415,32 @@ var group = makeGroupWith2Objects(); assert.ok(typeof group.toSVG === 'function'); - var expectedSVG = '\n\t\n\t\n\n'; + var expectedSVG = '\n\t\n\n\n\t\n\n\n\n'; + assert.equal(group.toSVG(), expectedSVG); + }); + + QUnit.test('toSVG with a clipPath', function(assert) { + var group = makeGroupWith2Objects(); + assert.ok(typeof group.toSVG === 'function'); + group.clipPath = new fabric.Rect({ width: 100, height: 100 }); + var expectedSVG = '\n\n\t\n\n\t\n\n\n\t\n\n\n\n'; + assert.equal(group.toSVG(), expectedSVG); + }); + + QUnit.test('toSVG with a clipPath absolutePositioned', function(assert) { + var group = makeGroupWith2Objects(); + assert.ok(typeof group.toSVG === 'function'); + group.clipPath = new fabric.Rect({ width: 100, height: 100 }); + group.clipPath.absolutePositioned = true; + var expectedSVG = '\n\n\n\t\n\n\t\n\n\n\t\n\n\n\n\n'; + assert.equal(group.toSVG(), expectedSVG); + }); + + QUnit.test('toSVG with a group as a clipPath', function(assert) { + var group = makeGroupWith2Objects(); + assert.ok(typeof group.toSVG === 'function'); + group.clipPath = makeGroupWith2Objects(); + var expectedSVG = '\n\n\t\t\n\t\t\n\n\t\n\n\n\t\n\n\n\n'; assert.equal(group.toSVG(), expectedSVG); }); diff --git a/test/unit/header.js b/test/unit/header.js new file mode 100644 index 00000000000..d7afcbc0600 --- /dev/null +++ b/test/unit/header.js @@ -0,0 +1,17 @@ +(function() { + QUnit.module('fabric header.js'); + + QUnit.test('default values', function(assert) { + assert.ok(typeof fabric.document !== 'undefined', 'document is set'); + assert.ok(typeof fabric.window !== 'undefined', 'window is set'); + assert.ok(typeof fabric.isTouchSupported !== 'undefined', 'isTouchSupported is set'); + assert.ok(typeof fabric.isLikelyNode !== 'undefined', 'isLikelyNode is set'); + assert.equal(fabric.SHARED_ATTRIBUTES.length, 17, 'SHARED_ATTRIBUTES is set'); + }); + + QUnit.test('initFilterBackend', function(assert) { + assert.ok(typeof fabric.initFilterBackend === 'function', 'initFilterBackend is a function'); + assert.ok(typeof fabric.maxTextureSize === 'undefined', 'maxTextureSize is not set yet'); + fabric.initFilterBackend(); + }); +})(); diff --git a/test/unit/image.js b/test/unit/image.js index 7b12f1864a8..90f86cdefdb 100644 --- a/test/unit/image.js +++ b/test/unit/image.js @@ -220,7 +220,7 @@ }); }); - QUnit.test('toSVG wit crop', function(assert) { + QUnit.test('toSVG with crop', function(assert) { var done = assert.async(); createImageObject(function(image) { image.cropX = 1; @@ -228,7 +228,7 @@ image.width -= 2; image.height -= 2; fabric.Object.__uid = 1; - var expectedSVG = '\n\t\n\n\n\t\n\n'; + var expectedSVG = '\n\n\t\n\n\t\n\n'; assert.equal(image.toSVG(), expectedSVG); done(); }); @@ -257,7 +257,7 @@ var done = assert.async(); createImageObject(function(image) { assert.ok(typeof image.toSVG === 'function'); - var expectedSVG = '\n\t\n\n'; + var expectedSVG = '\n\t\n\n'; assert.equal(image.toSVG(), expectedSVG); done(); }); diff --git a/test/unit/line.js b/test/unit/line.js index dda60283d34..667df52d155 100644 --- a/test/unit/line.js +++ b/test/unit/line.js @@ -67,6 +67,12 @@ assert.ok(typeof line.complexity === 'function'); }); + QUnit.test('toSVG', function(assert) { + var line = new fabric.Line([11, 12, 13, 14]); + var EXPECTED_SVG = '\n\n\n'; + assert.equal(line.toSVG(), EXPECTED_SVG); + }); + QUnit.test('toObject', function(assert) { var line = new fabric.Line([11, 12, 13, 14]); assert.ok(typeof line.toObject === 'function'); diff --git a/test/unit/path.js b/test/unit/path.js index c21ec004bd0..a7395c2721a 100644 --- a/test/unit/path.js +++ b/test/unit/path.js @@ -55,7 +55,11 @@ getPathObject('M 100 100 L 300 100 L 200 300 z', callback); } - QUnit.module('fabric.Path'); + QUnit.module('fabric.Path', { + beforeEach: function() { + fabric.Object.__uid = 0; + } + }); QUnit.test('constructor', function(assert) { var done = assert.async(); @@ -124,11 +128,35 @@ var done = assert.async(); makePathObject(function(path) { assert.ok(typeof path.toSVG === 'function'); - assert.deepEqual(path.toSVG(), '\n'); + assert.deepEqual(path.toSVG(), '\n\n\n'); done(); }); }); + QUnit.test('toSVG with a clipPath path', function(assert) { + var done = assert.async(); + makePathObject(function(path) { + makePathObject(function(path2) { + path.clipPath = path2; + assert.deepEqual(path.toSVG(), '\n\n\t\n\n\n\n', 'path clipPath toSVG should match'); + done(); + }); + }); + }); + + + QUnit.test('toSVG with a clipPath path absolutePositioned', function(assert) { + var done = assert.async(); + makePathObject(function(path) { + makePathObject(function(path2) { + path.clipPath = path2; + path.clipPath.absolutePositioned = true; + assert.deepEqual(path.toSVG(), '\n\n\n\t\n\n\n\n\n', 'path clipPath toSVG absolute should match'); + done(); + }); + }); + }); + QUnit.test('path array not shared when cloned', function(assert) { var done = assert.async(); makePathObject(function(originalPath) { diff --git a/test/unit/polygon.js b/test/unit/polygon.js index bc3c4bb61fe..4dbc115bd20 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -80,6 +80,13 @@ assert.deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT); }); + QUnit.test('toSVG', function(assert) { + var polygon = new fabric.Polygon(getPoints(), { fill: 'red', stroke: 'blue' }); + assert.ok(typeof polygon.toSVG === 'function'); + var EXPECTED_SVG = '\n\n\n'; + assert.deepEqual(polygon.toSVG(), EXPECTED_SVG); + }); + QUnit.test('fromObject', function(assert) { var done = assert.async(); assert.ok(typeof fabric.Polygon.fromObject === 'function'); diff --git a/test/unit/polyline.js b/test/unit/polyline.js index cd8e6d01425..0d93bcb6875 100644 --- a/test/unit/polyline.js +++ b/test/unit/polyline.js @@ -79,6 +79,13 @@ assert.deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT); }); + QUnit.test('toSVG', function(assert) { + var polyline = new fabric.Polygon(getPoints(), { fill: 'red', stroke: 'blue' }); + assert.ok(typeof polyline.toSVG === 'function'); + var EXPECTED_SVG = '\n\n\n'; + assert.deepEqual(polyline.toSVG(), EXPECTED_SVG); + }); + QUnit.test('fromObject', function(assert) { var done = assert.async(); assert.ok(typeof fabric.Polyline.fromObject === 'function'); diff --git a/test/unit/rect.js b/test/unit/rect.js index 1a23ce5a727..02d8ea9e5c9 100644 --- a/test/unit/rect.js +++ b/test/unit/rect.js @@ -172,31 +172,31 @@ var rect = new fabric.Rect({ width: 100, height: 100, rx: 20, ry: 30, strokeWidth: 0 }); var svg = rect.toSVG(); - assert.equal(svg, '\n'); + assert.equal(svg, '\n\n\n'); }); QUnit.test('toSVG with alpha colors fill', function(assert) { var rect = new fabric.Rect({ width: 100, height: 100, strokeWidth: 0, fill: 'rgba(255, 0, 0, 0.5)' }); var svg = rect.toSVG(); - assert.equal(svg, '\n'); + assert.equal(svg, '\n\n\n'); }); QUnit.test('toSVG with id', function(assert) { var rect = new fabric.Rect({id: 'myRect', width: 100, height: 100, strokeWidth: 0, fill: 'rgba(255, 0, 0, 0.5)' }); var svg = rect.toSVG(); - assert.equal(svg, '\n'); + assert.equal(svg, '\n\n\n'); }); QUnit.test('toSVG with alpha colors stroke', function(assert) { var rect = new fabric.Rect({ width: 100, height: 100, strokeWidth: 0, fill: '', stroke: 'rgba(255, 0, 0, 0.5)' }); var svg = rect.toSVG(); - assert.equal(svg, '\n'); + assert.equal(svg, '\n\n\n'); }); QUnit.test('toSVG with paintFirst set to stroke', function(assert) { var rect = new fabric.Rect({ width: 100, height: 100, paintFirst: 'stroke' }); var svg = rect.toSVG(); - assert.equal(svg, '\n'); + assert.equal(svg, '\n\n\n'); }); QUnit.test('toObject without default values', function(assert) { diff --git a/test/unit/text_to_svg.js b/test/unit/text_to_svg.js index 590aa6932af..d7d46cada97 100644 --- a/test/unit/text_to_svg.js +++ b/test/unit/text_to_svg.js @@ -1,17 +1,17 @@ (function() { function removeTranslate(str) { - return str.replace(/translate\(.*?\)/, ''); + return str.replace(/matrix\(.*?\)/, ''); } QUnit.module('fabric.Text'); QUnit.test('toSVG', function(assert) { - var TEXT_SVG = '\t\n\t\tx\n\t\n'; + var TEXT_SVG = '\n\t\tx\n\n'; var text = new fabric.Text('x'); assert.equal(removeTranslate(text.toSVG()), removeTranslate(TEXT_SVG)); text.set('fontFamily', 'Arial'); assert.equal(removeTranslate(text.toSVG()), removeTranslate(TEXT_SVG.replace('font-family="Times New Roman"', 'font-family="Arial"'))); }); QUnit.test('toSVG justified', function(assert) { - var TEXT_SVG_JUSTIFIED = '\t\n\t\txxxxxxx y\n\t\n'; + var TEXT_SVG_JUSTIFIED = '\n\t\txxxxxxx y\n\n'; var text = new fabric.Text('xxxxxx\nx y', { textAlign: 'justify', }); @@ -19,13 +19,13 @@ assert.equal(removeTranslate(text.toSVG()), removeTranslate(TEXT_SVG_JUSTIFIED)); }); QUnit.test('toSVG with multiple spaces', function(assert) { - var TEXT_SVG_MULTIPLESPACES = '\t\n\t\tx y\n\t\n'; + var TEXT_SVG_MULTIPLESPACES = '\n\t\tx y\n\n'; var text = new fabric.Text('x y'); assert.equal(removeTranslate(text.toSVG()), removeTranslate(TEXT_SVG_MULTIPLESPACES)); }); QUnit.test('toSVG with deltaY', function(assert) { fabric.Object.NUM_FRACTION_DIGITS = 0; - var TEXT_SVG = '\t\n\t\txx\n\t\n'; + var TEXT_SVG = '\n\t\txx\n\n'; var text = new fabric.Text('xx', { styles: { 0: { @@ -41,7 +41,7 @@ }); QUnit.test('toSVG with font', function(assert) { - var TEXT_SVG_WITH_FONT = '\t\n\t\txxxxxxx y\n\t\n'; + var TEXT_SVG_WITH_FONT = '\n\t\txxxxxxx y\n\n'; var text = new fabric.Text('xxxxxx\nx y', { textAlign: 'justify', styles: {0: { diff --git a/test/visual/svg_export.js b/test/visual/svg_export.js index fdebbbbc48f..71481268276 100644 --- a/test/visual/svg_export.js +++ b/test/visual/svg_export.js @@ -134,7 +134,7 @@ code: clipping3, golden: 'clipping3.png', percentage: 0.06, - disabled: true, + disabled: false, }); function clipping4(canvas, callback) { @@ -206,7 +206,7 @@ code: clipping5, golden: 'clipping5.png', percentage: 0.06, - disabled: true, + disabled: false, }); function clipping6(canvas, callback) { @@ -270,7 +270,7 @@ code: clipping7, golden: 'clipping7.png', percentage: 0.06, - disabled: true, + disabled: false, }); function clipping8(canvas, callback) { @@ -294,7 +294,7 @@ code: clipping8, golden: 'clipping8.png', percentage: 0.06, - disabled: true, + disabled: false, }); function clipping9(canvas, callback) { @@ -316,7 +316,7 @@ code: clipping9, golden: 'clipping9.png', percentage: 0.06, - disabled: true, + disabled: false, }); tests.forEach(visualTestLoop(fabricCanvas, QUnit));