Skip to content

Commit

Permalink
fix svg export of gradients (fabricjs#5456)
Browse files Browse the repository at this point in the history
* fix svg export of gradients

* fixed UTs

* visual tests too

* and the pictures

* fixed test lint
  • Loading branch information
asturur authored Jan 6, 2019
1 parent fdb85cf commit 910be53
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 17 deletions.
20 changes: 8 additions & 12 deletions src/gradient.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,29 +174,25 @@
var coords = clone(this.coords, true), i, len,
markup, commonAttributes, colorStops = clone(this.colorStops, true),
needsSwap = coords.r1 > coords.r2,
offsetX = object.width / 2, offsetY = object.height / 2;
transform = this.gradientTransform ? this.gradientTransform.concat() : fabric.iMatrix.concat(),
offsetX = object.width / 2 - this.offsetX, offsetY = object.height / 2 - this.offsetY;
// colorStops must be sorted ascending
colorStops.sort(function(a, b) {
return a.offset - b.offset;
});

if (object.type === 'path') {
offsetX -= object.pathOffset.x;
offsetY -= object.pathOffset.y;
}
for (var prop in coords) {
if (prop === 'x1' || prop === 'x2') {
coords[prop] += this.offsetX - offsetX;
}
else if (prop === 'y1' || prop === 'y2') {
coords[prop] += this.offsetY - offsetY;
}
}

transform[4] -= offsetX;
transform[5] -= offsetY;

commonAttributes = 'id="SVGID_' + this.id +
'" gradientUnits="userSpaceOnUse"';
if (this.gradientTransform) {
commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
}
commonAttributes += ' gradientTransform="matrix(' + transform.join(' ') + ')" ';

if (this.type === 'linear') {
markup = [
'<linearGradient ',
Expand Down
2 changes: 2 additions & 0 deletions src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,8 @@
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Object} filler fabric.Pattern or fabric.Gradient
* @return {Object} offset.offsetX offset for text rendering
* @return {Object} offset.offsetY offset for text rendering
*/
_applyPatternGradientTransform: function(ctx, filler) {
if (!filler || !filler.toLive) {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/canvas_static.js
Original file line number Diff line number Diff line change
Expand Up @@ -1827,7 +1827,7 @@
},
});
var svg = canvas2.toSVG();
var expectedSVG = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="300" height="150" viewBox="0 0 300 150" xml:space="preserve">\n<desc>Created with Fabric.js ' + fabric.version + '</desc>\n<defs>\n<linearGradient id="SVGID_0" gradientUnits="userSpaceOnUse" x1="-150" y1="-75" x2="150" y2="-75">\n<stop offset="0%" style="stop-color:black;"/>\n<stop offset="100%" style="stop-color:white;"/>\n</linearGradient>\n</defs>\n<rect transform="translate(150,75)" x="-150" y="-75" width="300" height="150" fill="url(#SVGID_0)"></rect>\n</svg>';
var expectedSVG = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="300" height="150" viewBox="0 0 300 150" xml:space="preserve">\n<desc>Created with Fabric.js ' + fabric.version + '</desc>\n<defs>\n<linearGradient id=\"SVGID_0\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(1 0 0 1 -150 -75)\" x1=\"0\" y1=\"0\" x2=\"300\" y2=\"0\">\n<stop offset="0%" style="stop-color:black;"/>\n<stop offset="100%" style="stop-color:white;"/>\n</linearGradient>\n</defs>\n<rect transform="translate(150,75)" x="-150" y="-75" width="300" height="150" fill="url(#SVGID_0)"></rect>\n</svg>';
assert.equal(svg, expectedSVG, 'svg is as expected');
});

Expand Down
8 changes: 4 additions & 4 deletions test/unit/gradient.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@
});
}

var SVG_LINEAR = '<linearGradient id="SVGID_0" gradientUnits="userSpaceOnUse" x1="-50" y1="-40" x2="50" y2="150">\n<stop offset="0%" style="stop-color:red;stop-opacity: 0"/>\n<stop offset="100%" style="stop-color:green;"/>\n</linearGradient>\n';
var SVG_RADIAL = '<radialGradient id="SVGID_0" gradientUnits="userSpaceOnUse" cx="50" cy="150" r="50" fx="-50" fy="-40">\n<stop offset="0%" style="stop-color:red;"/>\n<stop offset="100%" style="stop-color:green;stop-opacity: 0"/>\n</radialGradient>\n';
var SVG_INTERNALRADIUS = '<radialGradient id="SVGID_0" gradientUnits="userSpaceOnUse" cx="50" cy="150" r="50" fx="-50" fy="-40">\n<stop offset="20%" style="stop-color:red;"/>\n<stop offset="100%" style="stop-color:green;stop-opacity: 0"/>\n</radialGradient>\n';
var SVG_SWAPPED = '<radialGradient id="SVGID_0" gradientUnits="userSpaceOnUse" cx="-50" cy="-40" r="50" fx="50" fy="150">\n<stop offset="20%" style="stop-color:green;stop-opacity: 0"/>\n<stop offset="100%" style="stop-color:red;"/>\n</radialGradient>\n';
var SVG_LINEAR = '<linearGradient id=\"SVGID_0\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(1 0 0 1 -50 -50)\" x1=\"0\" y1=\"10\" x2=\"100\" y2=\"200\">\n<stop offset=\"0%\" style=\"stop-color:red;stop-opacity: 0\"/>\n<stop offset=\"100%\" style=\"stop-color:green;\"/>\n</linearGradient>\n';
var SVG_RADIAL = '<radialGradient id=\"SVGID_0\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(1 0 0 1 -50 -50)\" cx=\"100\" cy=\"200\" r=\"50\" fx=\"0\" fy=\"10\">\n<stop offset=\"0%\" style=\"stop-color:red;\"/>\n<stop offset=\"100%\" style=\"stop-color:green;stop-opacity: 0\"/>\n</radialGradient>\n';
var SVG_INTERNALRADIUS = '<radialGradient id=\"SVGID_0\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(1 0 0 1 -50 -50)\" cx=\"100\" cy=\"200\" r=\"50\" fx=\"0\" fy=\"10\">\n<stop offset=\"20%\" style=\"stop-color:red;\"/>\n<stop offset=\"100%\" style=\"stop-color:green;stop-opacity: 0\"/>\n</radialGradient>\n';
var SVG_SWAPPED = '<radialGradient id=\"SVGID_0\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(1 0 0 1 -50 -50)\" cx=\"0\" cy=\"10\" r=\"50\" fx=\"100\" fy=\"200\">\n<stop offset=\"20%\" style=\"stop-color:green;stop-opacity: 0\"/>\n<stop offset=\"100%\" style=\"stop-color:red;\"/>\n</radialGradient>\n';


QUnit.test('constructor linearGradient', function(assert) {
Expand Down
Binary file added test/visual/golden/clipping12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/visual/golden/clipping13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions test/visual/z_svg_export.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,5 +349,35 @@
percentage: 0.06,
});

function clipping12(canvas, callback) {
var jsonData = '{"version":"2.4.6","objects":[{"type":"ellipse","version":"2.4.6","left":2.5,"top":-56.5,"width":220,"height":300,"fill":{"type":"radial","coords":{"x1":110.00000000000001,"y1":110.00000000000001,"x2":110.00000000000001,"y2":110.00000000000001,"r1":0,"r2":110.00000000000001},"colorStops":[{"offset":1,"color":"rgb(0,0,255)","opacity":1},{"offset":0.6,"color":"rgb(0,153,153)","opacity":0.5},{"offset":0.3,"color":"rgb(0,0,255)","opacity":1},{"offset":0,"color":"rgb(255,0,0)","opacity":0.8}],"offsetX":0,"offsetY":0,"gradientTransform":[1,0,0,1.3636363636363635,0,0]},"scaleX":0.69,"scaleY":1.07,"skewY":-32.03,"rx":110,"ry":150}]}';
canvas.loadFromJSON(jsonData, function() {
var obj1 = canvas.getObjects()[0];
canvas.setDimensions({ width: obj1.width, height: obj1.height });
toSVGCanvas(canvas, callback);
});
}

tests.push({
test: 'Export a radial svg with scaling',
code: clipping12,
golden: 'clipping12.png',
percentage: 0.06,
});

function clipping13(canvas, callback) {
var jsonData = '{"version":"2.4.6","objects":[{"type":"path","version":"2.4.6","left":84.63385601266276,"top":385.11623376730074,"width":3.14,"height":10.44,"fill":{"type":"linear","coords":{"x1":481.066,"y1":785.465,"x2":480.953,"y2":793.102},"colorStops":[{"offset":1,"color":"rgb(160,137,44)","opacity":1},{"offset":0.9,"color":"rgb(85,68,0)","opacity":1},{"offset":0.78,"color":"rgb(80,68,22)","opacity":1},{"offset":0.607,"color":"rgb(160,137,44)","opacity":1},{"offset":0.467,"color":"rgb(255,255,255)","opacity":1},{"offset":0.299,"color":"rgb(200,171,55)","opacity":1},{"offset":0.24,"color":"rgb(160,137,44)","opacity":1},{"offset":0.096,"color":"rgb(120,103,33)","opacity":1},{"offset":0,"color":"rgb(211,188,95)","opacity":1}],"offsetX":-439.1113425994523,"offsetY":-783.951,"gradientTransform":[-1,0,0,1,921.58,0]},"scaleX":9.32,"scaleY":10.58,"angle":-90,"path":[["M",442.142,794.394],["l",-2.583,-1.46],["c",-0.644,-2.39,-0.512,-5.004,-0.113,-7.693],["l",2.808,-1.29]]},{"type":"path","version":"2.4.6","left":78.08737427855635,"top":315.5127636903678,"width":2.42,"height":11.51,"fill":{"type":"linear","coords":{"x1":473.934,"y1":792.821,"x2":473.822,"y2":784.005},"colorStops":[{"offset":1,"color":"rgb(211,188,95)","opacity":1},{"offset":0.904,"color":"rgb(120,103,33)","opacity":1},{"offset":0.76,"color":"rgb(160,137,44)","opacity":1},{"offset":0.701,"color":"rgb(200,171,55)","opacity":1},{"offset":0.533,"color":"rgb(255,255,255)","opacity":1},{"offset":0.393,"color":"rgb(160,137,44)","opacity":1},{"offset":0.22,"color":"rgb(80,68,22)","opacity":1},{"offset":0.1,"color":"rgb(85,68,0)","opacity":1},{"offset":0,"color":"rgb(160,137,44)","opacity":1}],"offsetX":-446.578,"offsetY":-783.332,"gradientTransform":[-1,0,0,1,921.58,0]},"scaleX":9.32,"scaleY":10.58,"angle":-90,"path":[["M",448.32,783.332],["l",0.673,2.02],["c",-0.885,1.242,-0.83,2.952,-0.075,7.92],["l",-1.16,1.57],["l",-1.18,-4.49],["l",0.17,-5.673],["z"]]},{"type":"path","version":"2.4.6","left":92.34368668174653,"top":366.5275069557187,"width":6.57,"height":5.67,"fill":{"type":"linear","coords":{"x1":475.081,"y1":785.381,"x2":479.3,"y2":788.975},"colorStops":[{"offset":1,"color":"rgb(120,103,33)","opacity":1},{"offset":0,"color":"rgb(200,171,55)","opacity":1}],"offsetX":-441.1054336190674,"offsetY":-784.68,"gradientTransform":[-1,0,0,1,921.58,0]},"scaleX":9.32,"scaleY":10.58,"angle":-90,"path":[["M",447.365,784.68],["h",-5.616],["c",-0.592,1.87,-0.812,3.743,-0.506,5.615],["l",5.952,0.056],["c",0.658,-1.976,0.554,-3.843,0.17,-5.67],["z"]]},{"type":"path","version":"2.4.6","left":151.23029459047214,"top":365.75783100618594,"width":6.58,"height":4.48,"fill":{"type":"linear","coords":{"x1":476.181,"y1":791.235,"x2":477.099,"y2":794.257},"colorStops":[{"offset":1,"color":"rgb(120,103,33)","opacity":1},{"offset":0,"color":"rgb(200,171,55)","opacity":1}],"offsetX":-441.18800000000005,"offsetY":-790.248,"gradientTransform":[-1,0,0,1,921.58,0]},"scaleX":9.32,"scaleY":10.58,"angle":-90,"path":[["M",447.196,790.248],["c",0.397,1.492,0.613,3.14,0.562,4.483],["l",-5.503,-0.056],["c",-0.974,-1.853,-0.957,-3.128,-1.067,-4.406],["z"]]},{"type":"path","version":"2.4.6","left":78.67962464545468,"top":360.50959855742764,"width":6.4,"height":1.35,"fill":{"type":"linear","coords":{"x1":473.32,"y1":784.06,"x2":479.896,"y2":784.06},"colorStops":[{"offset":1,"color":"rgb(200,171,55)","opacity":1},{"offset":0,"color":"rgb(120,103,33)","opacity":1}],"offsetX":-441.751,"offsetY":-783.3879999999999,"gradientTransform":[-1,0,0,1,921.58,0]},"scaleX":9.32,"scaleY":10.58,"angle":-90,"path":[["M",447.196,784.68],["c",0.783,-0.43,0.715,-0.862,0.955,-1.292],["l",-5.67,0.224],["c",-0.308,0.343,-0.704,0.64,-0.73,1.123],["z"]]},{"type":"path","version":"2.4.6","left":195.70884317712697,"top":264.26532049868615,"width":2.99,"height":8.78,"fill":{"type":"linear","coords":{"x1":481.066,"y1":785.465,"x2":480.953,"y2":793.102},"colorStops":[{"offset":1,"color":"rgb(160,137,44)","opacity":1},{"offset":0.9,"color":"rgb(85,68,0)","opacity":1},{"offset":0.78,"color":"rgb(80,68,22)","opacity":1},{"offset":0.607,"color":"rgb(160,137,44)","opacity":1},{"offset":0.467,"color":"rgb(255,255,255)","opacity":1},{"offset":0.299,"color":"rgb(200,171,55)","opacity":1},{"offset":0.24,"color":"rgb(160,137,44)","opacity":1},{"offset":0.096,"color":"rgb(120,103,33)","opacity":1},{"offset":0,"color":"rgb(211,188,95)","opacity":1}],"offsetX":-228.298,"offsetY":-835.244,"gradientTransform":[0.93343,0,0,0.85628,-219.064,163.965]},"scaleX":9.32,"scaleY":10.58,"angle":90,"flipY":true,"path":[["M",228.298,844.02],["l",2.57,-1.083],["c",0.6,-2.048,0.477,-4.285,0.104,-6.587],["l",-2.62,-1.106]]},{"type":"circle","version":"2.4.6","left":-28.49,"top":-28.49,"width":4.64,"height":4.64,"fill":{"type":"radial","coords":{"x1":193.676,"y1":141.252,"x2":193.676,"y2":141.252,"r1":0,"r2":4.082},"colorStops":[{"offset":1,"color":"rgb(0,0,0)","opacity":1},{"offset":0.969,"color":"rgb(0,0,0)","opacity":1},{"offset":0.904,"color":"rgb(236,236,236)","opacity":1},{"offset":0.874,"color":"rgb(77,77,77)","opacity":1},{"offset":0.837,"color":"rgb(237,237,237)","opacity":1},{"offset":0.817,"color":"rgb(0,0,0)","opacity":1},{"offset":0,"color":"rgb(0,0,0)","opacity":1}],"offsetX":-116.293,"offsetY":-166.167,"gradientTransform":[0.3487,0.40483,-0.40345,0.34752,108.054,40.97]},"scaleX":59.8,"scaleY":59.8,"radius":2.321,"startAngle":0,"endAngle":6.283185307179586},{"type":"circle","version":"2.4.6","left":32.24,"top":30.43,"width":3.58,"height":3.58,"fill":{"type":"linear","coords":{"x1":195.171,"y1":143.461,"x2":191.574,"y2":138.568},"colorStops":[{"offset":1,"color":"rgb(204,204,204)","opacity":1},{"offset":0.687,"color":"rgb(255,255,255)","opacity":1},{"offset":0,"color":"rgb(255,255,255)","opacity":1}],"offsetX":-116.817,"offsetY":-166.661,"gradientTransform":[0.52872,0,0,0.52872,16.3,93.714]},"stroke":"#b3b3b3","strokeWidth":0.02,"strokeLineCap":"round","scaleX":59.8,"scaleY":59.8,"radius":1.789,"startAngle":0,"endAngle":6.283185307179586},{"type":"circle","version":"2.4.6","left":125.91,"top":124.11,"width":0.46,"height":0.46,"fill":{"type":"linear","coords":{"x1":656.429,"y1":320.934,"x2":506.429,"y2":131.648},"colorStops":[{"offset":1,"color":"rgb(242,242,242)","opacity":1},{"offset":0,"color":"rgb(102,102,102)","opacity":1}],"offsetX":-118.37599999999999,"offsetY":-168.22,"gradientTransform":[0.0017,0,0,0.0017,117.64,168.082]},"stroke":"#999","strokeWidth":0,"strokeLineCap":"round","scaleX":59.8,"scaleY":59.8,"radius":0.23,"startAngle":0,"endAngle":6.283185307179586}]}';
canvas.loadFromJSON(jsonData, function() {
canvas.setDimensions({ width: 290, height: 400 });
toSVGCanvas(canvas, callback);
});
}

tests.push({
test: 'Export complex gradients',
code: clipping13,
golden: 'clipping13.png',
percentage: 0.06,
});
tests.forEach(visualTestLoop(fabricCanvas, QUnit));
})();

0 comments on commit 910be53

Please sign in to comment.