I'm working on a project with Fabric.js.
Now I need to create a custom class and to export it to SVG.
I'm using Fabric.js tutorial to begin:
http://www.sitepoint.com/fabric-js-advanced/
Here is the javascript code:
var LabeledRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = { });
this.callSuper('initialize', options);
this.set('label', options.label || '');
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
}
});
var canvas = new fabric.Canvas('container');
var labeledRect = new LabeledRect({
width: 100,
height: 50,
left: 100,
top: 100,
label: 'test',
fill: '#faa'
});
canvas.add(labeledRect);
document.getElementById('export-btn').onclick = function() {
canvas.deactivateAll().renderAll();
window.open('data:image/svg+xml;utf8,' + encodeURIComponent(canvas.toSVG()));
};
Here the HTML:
<canvas id="container" width="780" height="500"></canvas>
<a href="#" id="export-btn">Export SVG</a>
Here is my jsfiddle..
http://jsfiddle.net/QPDy5/
What I'm doing wrong?
Thanks in advance.
Every "class" in Fabric (rectangle, circle, image, path, etc.) knows how to output its SVG markup. The method responsible for it is toSVG
. You can see toSVG
of fabric.Rect
, for example, or fabric.Text
one.
Since you created subclass of fabric.Rect
here and didn't specify toSVG
, toSVG
of the next object in prototype chain is used. The next "class" in the inheritance chain happens to be fabric.Rect
, so you're seeing a result of fabric.Rect.prototype.toSVG
.
The solution is simple: specify toSVG
in your subclass. Theoretically, you would need to combine the code from fabric.Rect#toSVG
and fabric.Text#toSVG
, but to avoid repetition and keep things maintainable, we can utilize a bit of a hack:
toSVG: function() {
var label = new fabric.Text(this.label, {
originX: 'left',
originY: 'top',
left: this.left - this.width / 2,
top: this.top - this.height / 2,
fontSize: 20,
fill: '#333',
fontFamily: 'Helvetica'
});
return this.callSuper('toSVG') + label.toSVG();
}
The "fontSize", "fill", and "fontFamily" should ideally be moved to an instance-level property of course, to avoid repeating them there.
Here's a modified test — http://jsfiddle.net/fabricjs/QPDy5/1/
What @Sergiu Paraschiv suggested with a group is another just-as-viable solution.
Problem is there's no SVG equivalent to fillText
so FabricJS seems to ignore it.
My workaround is to use a fabric.Group
and build an inner Rect
and Text
var LabeledRect = function(options) {
var rect = new fabric.Rect({
width: options.width,
height: options.height,
fill: options.fill
});
var label = new fabric.Text(options.label);
var group = new fabric.Group([rect, label]);
group.left = options.left;
group.top = options.top;
group.toSVG = function() {
var e=[];
for(var t = 0; t < this._objects.length; t++)
e.push(this._objects[t].toSVG());
return'<g transform="'+this.getSvgTransform()+'">'+e.join("")+"</g>"
};
return group;
};
The reason I overwrote toSVG
is because the default implementation cycles through the children in reverse order (for(var t=this._objects.length;t--;)
). I have no clue why it does that, but the text renders underneath the rectangle.