Text added on the canvas once exported as SVG and imported to canvas, is not aligning the same way as it's original
http://jsbin.com/ruluko/edit?html,js,output
using fabric js v1.6.0-rc.1
This looks like a bug in fabric's svg parser.
You can see it if, instead of grouping the svg elements, you add them one by one :
var canvas1 = new fabric.Canvas('c1');
var circle = new fabric.Circle({
radius: 50,
fill: '#eef'
});
canvas1.add(circle);
var text = new fabric.Text('fabric', { fill: 'red', left: 100, top: 100 });
canvas1.add(text);
var topText = new fabric.Text('Top', { fill: 'red', left: 100, top: 0 });
canvas1.add(topText);
var botText = new fabric.Text('Bot LL', { fill: 'red', left: 80, top: 163 });
canvas1.add(botText);
var canvas2 = new fabric.Canvas('c2');
var doAction = function() {
var mysvg = canvas1.toSVG();
canvas2.clear();
fabric.loadSVGFromString(mysvg, function(objects, options) {
for (var i = 0; i < objects.length; i++) {
canvas2.add(objects[i]);
}
});
};
.canvas-container{border:1px solid; display: inline-block; position: relative;}
<script src="//code.jquery.com/jquery.min.js"></script>
<script src="//cdn.bootcss.com/fabric.js/1.6.0-rc.1/fabric.min.js"></script>
<canvas id="c1" width="200" height="200"></canvas>
<canvas id="c2" width="200" height="200"></canvas>
<button onclick='doAction()'>Export & Import as SVG</button>
As you can see in the snippet, each element's box is at the wrong coordinates.
Note that it also occurs with the loadSVGFromURL()
method.
One quick workaround if you were going to group those elements anyway, is to draw your svg as an image :
var canvas1 = new fabric.Canvas('c1');
var circle = new fabric.Circle({
radius: 50,
fill: '#eef'
});
canvas1.add(circle);
var text = new fabric.Text('fabric', { fill: 'red', left: 100, top: 100 });
canvas1.add(text);
var topText = new fabric.Text('Top', { fill: 'red', left: 100, top: 0 });
canvas1.add(topText);
var botText = new fabric.Text('Bot LL', { fill: 'red', left: 80, top: 163 });
canvas1.add(botText);
var canvas2 = new fabric.Canvas('c2');
var doAction = function() {
var mysvg = canvas1.toSVG();
canvas2.clear();
var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(mysvg.split('svg11.dtd">')[1]);
fabric.Image.fromURL(svgURL, function(oImg) {
canvas2.add(oImg)
});
};
.canvas-container{border:1px solid; display: inline-block; position: relative;}
<script src="//code.jquery.com/jquery.min.js"></script>
<script src="//cdn.bootcss.com/fabric.js/1.6.0-rc.1/fabric.min.js"></script>
<canvas id="c1" width="200" height="200"></canvas>
<canvas id="c2" width="200" height="200"></canvas>
<button onclick='doAction()'>Export & Import as SVG</button>
The main caveat of this workaround is that you will lose ability to modify each path of your group once loaded.
Since the problem occurs because of <tspan>
elements, a better fix would be to first clean your svg string, by replacing all those <tspan>
to <text>
elements :
var canvas1 = new fabric.Canvas('c1');
var circle = new fabric.Circle({
radius: 50,
fill: '#eef'
});
canvas1.add(circle);
var text = 'this is\na multiline\ntext';
var alignedRightText = new fabric.Text(text, {
textAlign: 'right'
});
canvas1.add(alignedRightText)
var topText = new fabric.Text('Top', { fill: 'red', left: 100, top: 0 });
canvas1.add(topText);
var botText = new fabric.Text('Bot LL', { fill: 'red', left: 80, top: 163 });
canvas1.add(botText);
var canvas2 = new fabric.Canvas('c2');
var doAction = function() {
var mysvg = canvas1.toSVG();
canvas2.clear();
fabric.loadSVGFromString(fixSVGText(mysvg), function(objects, options) {
options.selectable = false;
var obj = fabric.util.groupSVGElements(objects, options);
canvas2.add(obj).renderAll();
});
};
function fixSVGText(str) {
// parse our string as a DOM object and get the SVGElement
var svg = new DOMParser().parseFromString(str, "image/svg+xml").documentElement;
// get all <tspan> elements
var tspans = svg.querySelectorAll('tspan');
for (var i = 0; i < tspans.length; i++) {
var ts = tspans[i],
parent = ts.parentNode,
gParent = parent.parentNode;
var j = 0;
// create a new SVGTextElement to replace our tspan
var replace = document.createElementNS('http://www.w3.org/2000/svg', 'text');
var tsAttr = ts.attributes;
// set the 'x', 'y' and 'fill' attributes to our new element
for (j = 0; j < tsAttr.length; j++) {
replace.setAttributeNS(null, tsAttr[j].name, tsAttr[j].value);
}
// append the contentText
var childNodes = ts.childNodes;
for (j = 0; j < childNodes.length; j++) {
replace.appendChild(ts.childNodes[j]);
}
var tAttr = parent.attributes;
// set the original text attributes to our new one
for (j = 0; j < tAttr.length; j++) {
replace.setAttributeNS(null, tAttr[j].name, tAttr[j].value);
}
// append our new text to the grand-parent
gParent.appendChild(replace);
// if this is the last tspan
if (ts === parent.lastElementChild)
// remove the old, now empty, SVGTextElement
gParent.removeChild(parent)
}
// return a string version of our cleaned svg
return new XMLSerializer().serializeToString(svg);
}
.canvas-container{border:1px solid; display: inline-block; position: relative;}
<script src="//code.jquery.com/jquery.min.js"></script>
<script src="//cdn.bootcss.com/fabric.js/1.6.0-rc.1/fabric.min.js"></script>
<canvas id="c1" width="200" height="200"></canvas>
<canvas id="c2" width="200" height="200"></canvas>
<button onclick='doAction()'>Export & Import as SVG</button>
But the best solution is still to open a new issue to kangax, and hope that this bug will be fixed in future versions.
I really appreciate finding your solution Moussa. Thank you.
I noticed for multiline text boxes, that solution is only grabbing the last line. Below is a solution I worked out starting from your code. The alignment is still funky for that, but at least this looks like a step in the right direction:
function convertTspans(svg) {
var tspans = svg.match(/<\s*tspan[^>]*>(.*?)<\s*\/\s*tspan>/g);
var x = null;
var y = null;
var groupString = null;
var matrixString = null;
var xMatrix = null;
var yMatrix = null;
var transformedSvg = svg;
if (tspans === null) {
return svg;
}
for (var i = 0; i < tspans.length; i++) {
var tspanString = tspans[i];
var text = tspanString.replace(/<\s*tspan[^>]*>/g, '');
var text = text.replace(/<\s*\/tspan[^>]*>/g, '');
var coordMatch = tspanString.match(/x="(-?\d*\.?\d+)" y="(-?\d*\.?\d+)"/);
if (coordMatch && coordMatch.length > 2) {
x = parseFloat(coordMatch[1]);
y = parseFloat(coordMatch[2]);
}
var groupTagMatch = svg.match(/<g transform.*>/);
if (groupTagMatch) {
groupString = groupTagMatch[0];
// groupString is like <g transform="matrix(a b c d e f)" style="" > where e = xMatrix et f = yMatrix
var matrixMatch = groupString.match(/matrix\((-?\d*\.?\d+) (-?\d*\.?\d+) (-?\d*\.?\d+) (-?\d*\.?\d+) (-?\d*\.?\d+) (-?\d*\.?\d+)\)/);
if (matrixMatch && matrixMatch.length > 6) {
matrixString = matrixMatch[0];
xMatrix = parseFloat(matrixMatch[5]);
yMatrix = parseFloat(matrixMatch[6]);
if (x !== null
&& y !== null
&& xMatrix !== null
&& yMatrix !== null
&& text !== null
&& groupString !== null
&& matrixString !== null
) {
var newMatrixString = 'matrix(' + matrixMatch[1] + ' ' + matrixMatch[2] + ' ' + matrixMatch[3] + ' ' + matrixMatch[4] + ' ' +
(x + xMatrix) + ' ' + (y + yMatrix) + ')';
transformedSvg = svg
.replace(matrixString, newMatrixString)
.replace(tspanString, text);
console.log('--> svg', svg);
console.log('--> transformedSvg', transformedSvg);
}
}
}
}
return transformedSvg;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With