toSVG method not working on extended class with Fa

2020-07-27 04:53发布

问题:

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.

回答1:

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.



回答2:

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.