diff --git a/src/elements_parser.js b/src/elements_parser.js index 30aa7eedb26..6e79e996d3f 100644 --- a/src/elements_parser.js +++ b/src/elements_parser.js @@ -1,9 +1,10 @@ -fabric.ElementsParser = function(elements, callback, options, reviver) { +fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions) { this.elements = elements; this.callback = callback; this.options = options; this.reviver = reviver; this.svgUid = (options && options.svgUid) || 0; + this.parsingOptions = parsingOptions; }; fabric.ElementsParser.prototype.parse = function() { diff --git a/src/gradient.class.js b/src/gradient.class.js index 0488d748d84..6d57d73ef6a 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -135,7 +135,7 @@ for (var position in colorStops) { var color = new fabric.Color(colorStops[position]); this.colorStops.push({ - offset: position, + offset: parseFloat(position), color: color.toRgb(), opacity: color.getAlpha() }); @@ -170,8 +170,8 @@ */ toSVG: function(object) { var coords = fabric.util.object.clone(this.coords), - markup, commonAttributes; - + markup, commonAttributes, colorStops = this.colorStops, + needsSwap = coords.r1 > coords.r2; // colorStops must be sorted ascending this.colorStops.sort(function(a, b) { return a.offset - b.offset; @@ -179,7 +179,7 @@ if (!(object.group && object.group.type === 'path-group')) { for (var prop in coords) { - if (prop === 'x1' || prop === 'x2' || prop === 'r2') { + if (prop === 'x1' || prop === 'x2') { coords[prop] += this.offsetX - object.width / 2; } else if (prop === 'y1' || prop === 'y2') { @@ -205,24 +205,45 @@ ]; } else if (this.type === 'radial') { + // svg radial gradient has just 1 radius. the biggest. markup = [ '\n' ]; } - for (var i = 0; i < this.colorStops.length; i++) { + if (this.type === 'radial') { + if (needsSwap) { + // svg goes from internal to external radius. if radius are inverted, swap color stops. + colorStops = colorStops.concat().reverse(); + for (var i = 0; i < colorStops.length; i++) { + colorStops[i].offset = 1 - colorStops[i].offset; + } + } + var minRadius = Math.min(coords.r1, coords.r2); + if (minRadius > 0) { + // i have to shift all colorStops and add new one in 0. + var maxRadius = Math.max(coords.r1, coords.r2), + percentageShift = minRadius / maxRadius; + for (var i = 0; i < colorStops.length; i++) { + colorStops[i].offset += percentageShift * (1 - colorStops[i].offset); + } + } + } + + for (var i = 0; i < colorStops.length; i++) { + var colorStop = colorStops[i]; markup.push( '\n' ); } @@ -274,7 +295,7 @@ if (typeof opacity !== 'undefined') { color = new fabric.Color(color).setAlpha(opacity).toRgba(); } - gradient.addColorStop(parseFloat(offset), color); + gradient.addColorStop(offset, color); } return gradient; diff --git a/src/parser.js b/src/parser.js index 3c71c44b762..9ad08962ccf 100644 --- a/src/parser.js +++ b/src/parser.js @@ -602,8 +602,10 @@ * @param {Function} callback Callback to call when parsing is finished; * It's being passed an array of elements (parsed from a document). * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [parsingOptions] options for parsing document + * @param {String} [parsingOptions.crossOrigin] crossOrigin settings */ - fabric.parseSVGDocument = function(doc, callback, reviver) { + fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) { if (!doc) { return; } @@ -613,7 +615,7 @@ var svgUid = fabric.Object.__uid++, options = applyViewboxTransform(doc), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - + options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; options.svgUid = svgUid; if (descendants.length === 0 && fabric.isLikelyNode) { @@ -645,7 +647,7 @@ if (callback) { callback(instances, options); } - }, clone(options), reviver); + }, clone(options), reviver, parsingOptions); }; var reFontDeclaration = new RegExp( @@ -797,8 +799,8 @@ * @param {Object} [options] Options object * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. */ - parseElements: function(elements, callback, options, reviver) { - new fabric.ElementsParser(elements, callback, options, reviver).parse(); + parseElements: function(elements, callback, options, reviver, parsingOptions) { + new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse(); }, /** @@ -924,8 +926,10 @@ * @param {String} url * @param {Function} callback * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [options] Object containing options for parsing + * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources */ - loadSVGFromURL: function(url, callback, reviver) { + loadSVGFromURL: function(url, callback, reviver, options) { url = url.replace(/^\n\s*/, '').trim(); new fabric.util.request(url, { @@ -946,9 +950,9 @@ callback && callback(null); } - fabric.parseSVGDocument(xml.documentElement, function (results, options) { - callback && callback(results, options); - }, reviver); + fabric.parseSVGDocument(xml.documentElement, function (results, _options) { + callback && callback(results, _options); + }, reviver, options); } }, @@ -958,8 +962,10 @@ * @param {String} string * @param {Function} callback * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [options] Object containing options for parsing + * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources */ - loadSVGFromString: function(string, callback, reviver) { + loadSVGFromString: function(string, callback, reviver, options) { string = string.trim(); var doc; if (typeof DOMParser !== 'undefined') { @@ -975,9 +981,9 @@ doc.loadXML(string.replace(//i, '')); } - fabric.parseSVGDocument(doc.documentElement, function (results, options) { - callback(results, options); - }, reviver); + fabric.parseSVGDocument(doc.documentElement, function (results, _options) { + callback(results, _options); + }, reviver, options); } }); diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index 28e731f1ac5..d3cfaee8201 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -646,7 +646,7 @@ * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} */ fabric.Image.ATTRIBUTE_NAMES = - fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' ')); + fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href crossOrigin'.split(' ')); /** * Returns {@link fabric.Image} instance from an SVG element diff --git a/test/unit/gradient.js b/test/unit/gradient.js index fb5a92f852f..12bc6d161bf 100644 --- a/test/unit/gradient.js +++ b/test/unit/gradient.js @@ -36,6 +36,47 @@ }); } + function createRadialGradientWithInternalRadius() { + return new fabric.Gradient({ + type: 'radial', + coords: { + x1: 0, + y1: 10, + x2: 100, + y2: 200, + r1: 10, + r2: 50 + }, + colorStops: [ + { offset: 0, color: 'red' }, + { offset: 1, color: 'green', opacity: 0 } + ] + }); + } + + function createRadialGradientSwapped() { + return new fabric.Gradient({ + type: 'radial', + coords: { + x1: 0, + y1: 10, + x2: 100, + y2: 200, + r1: 50, + r2: 10 + }, + colorStops: [ + { offset: 0, color: 'red' }, + { offset: 1, color: 'green', opacity: 0 } + ] + }); + } + + var SVG_LINEAR = '\n\n\n\n'; + var SVG_RADIAL = '\n\n\n\n'; + var SVG_INTERNALRADIUS = '\n\n\n\n'; + var SVG_SWAPPED = '\n\n\n\n'; + test('constructor linearGradient', function() { ok(fabric.Gradient); @@ -634,10 +675,35 @@ test('toSVG', function() { var gradient = createLinearGradient(); - ok(typeof gradient.toSVG == 'function'); + }); + + test('toSVG linear', function() { + fabric.Object.__uid = 0; + var gradient = createLinearGradient(); + var obj = new fabric.Object({ width: 100, height: 100 }); + equal(gradient.toSVG(obj), SVG_LINEAR); + }); + + test('toSVG radial', function() { + fabric.Object.__uid = 0; + var gradient = createRadialGradient(); + var obj = new fabric.Object({ width: 100, height: 100 }); + equal(gradient.toSVG(obj), SVG_RADIAL); + }); + + test('toSVG radial with r1 > 0', function() { + fabric.Object.__uid = 0; + var gradient = createRadialGradientWithInternalRadius(); + var obj = new fabric.Object({ width: 100, height: 100 }); + equal(gradient.toSVG(obj), SVG_INTERNALRADIUS); + }); - // TODO: test toSVG + test('toSVG radial with r1 > 0', function() { + fabric.Object.__uid = 0; + var gradient = createRadialGradientSwapped(); + var obj = new fabric.Object({ width: 100, height: 100 }); + equal(gradient.toSVG(obj), SVG_SWAPPED); }); })();