Fabric.jsが出力するSVGを拡張して独自の属性を追加する

Fabric.js を使用してCanvas上にフリーハンドで描いた内容を fabric.Canvas
toSVG で出力してサーバに送信し、サーバ側で受信したSVG文字列を解析してあれこれしていたのですが、 toSVG で出力されるSVG文字列だけでは情報が足りない…なんとかSVGのなかに必要な情報をセットできないかな?ということがありました。

例えば、Canvas上に絵を描くブラシの種類が複数あるとき、描かれた線がどのブラシで描画されたのかSVGをみて判断できるように、ブラシの種類をSVGに含めるにはどうすればよいでしょうか?

SVG 1.1の仕様をみてみる

Fabric.jsのバージョン 3.4.0 で toSVG を実行すると、SVG 1.1 のSVG文字列が出力されます。SVG 1.1 の仕様を見てみると、独自の要素や属性を追加できそうです。今回は fabric.PencilBrush をつかってCanvasに絵を描いていたので、手っ取り早くPath要素にブラシの種類を示す独自の属性を追加することにしました。( fabric.PencilBrush をつかってCanvasに絵を描くと、描いた内容が fabric.Path のオブジェクトとしてCanvasに追加されます。)
view要素の拡張例を参考に、独自の属性 bit:brushType をPath要素に追加します。

サブクラスをつくって toSVG の出力内容を拡張する

Fabric.jsのソースをみてみると、 fabric.Canvas (具体的な処理は親クラスの fabric.StaticCanvas を参照)と fabric.Path でDTDやPath要素の生成が行われていることがわかりました。
これらのクラスのサブクラスをつくって、目的のSVG文字列が出力されるようにしてやればよさそうです。

まず、 fabric.Canvas のサブクラス fabric.ExCanvas をつくって、_setSVGPreamble でDTDに独自の属性 bit:brushType を追加します。

fabric.ExCanvas = fabric.util.createClass(fabric.Canvas, {

  _setSVGPreamble (markup, options) {
    if (options.suppressPreamble) {
      return;
    }

    markup.push(
      '<?xml version="1.0" encoding="', (options.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',
      '<!ATTLIST path\n',
      ' xmlns:bit CDATA #FIXED "http://www.bitmeister.jp/example/bit"\n',
      ' bit:brushType CDATA #IMPLIED>\n',
      ']>\n'
    );
  }

});

次に、fabric.Path のサブクラス fabric.ExPath を作って、 fabric.ExCanvastoSVG を実行したときに、独自の属性 bit:brushType にブラシの種類がセットされるようにします。fabric.Path でPath要素を生成しているのは _toSVG の処理なので、この処理をオーバーライドします。

fabric.ExPath = fabric.util.createClass(fabric.Path, {
  type: 'exPath',

  brushType: undefined,

  initialize (path, options) {
    options || (options = {});

    this.callSuper('initialize', path, options);

    this.brushType = options.brushType || 'pencil';

  },

  _toSVG () {
    var path = this.path.map((path) => {
      return path.join(' ');
    }).join(' ');
    return [
      '<path ', 'COMMON_PARTS',
      'd="', path,
      '" stroke-linecap="round" ',
      // fabric.ExCanvasで追加した独自属性をセット
      `bit:brushType="${this.brushType}" `,
      '/>\n'
    ];
  }

});

最後に、fabric.PencilBrush のサブクラスをつくって、 fabric.ExPath のオブジェクトがCanvasに追加されるように createPathfabric.ExPath をnewします。

fabric.ExPencilBrush = fabric.util.createClass(fabric.PencilBrush, {
  brushType: 'pencil',

  createPath (pathData) {
    const path = new fabric.ExPath(pathData, {
      fill: null,
      stroke: this.color,
      strokeWidth: this.width,
      strokeLineCap: this.strokeLineCap,
      strokeMiterLimit: this.strokeMiterLimit,
      strokeLineJoin: this.strokeLineJoin,
      strokeDashArray: this.strokeDashArray,
      brushType: this.brushType // ブラシの種類をセット
    });
    if (this.shadow) {
      this.shadow.affectStroke = true;
      path.setShadow(this.shadow);
    }

    return path;
  }
});

Path要素に独自属性を持つSVG文字列を出力する

fabric.ExCanvasfabric.ExPencilBrush をつかってCanvasをつくります。

const canvas = new fabric.ExCanvas('c', {
  isDrawingMode: true
});

canvas.freeDrawingBrush = new fabric.ExPencilBrush(canvas);
canvas.freeDrawingBrush.brushType = 'highlighter';

Canvasに線を描いて fabric.ExCanvastoSVG を実行すると、以下のようなSVG文字列が出力されました。上記の canvas.freeDrawingBrush.brushType に設定したブラシの種類がPath要素の属性 bit:brushType に出力されていますね。

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ATTLIST path
  xmlns:bit CDATA #FIXED "http://www.bitmeister.jp/example/bit"
  bit:brushType CDATA #IMPLIED>
]>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="893" height="1263" viewBox="0 0 893 1263" xml:space="preserve">
<desc>Created with Fabric.js 3.4.0</desc>
<defs>
</defs>
<g transform="matrix(1 0 0 1 347.89 158.91)"  >
<path style="stroke: rgb(0,0,255); stroke-opacity: 0.2; stroke-width: 10; stroke-dasharray: none; stroke-linecap: round; stroke-dashoffset: 0; stroke-linejoin: round; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;"  transform=" translate(-347.89, -158.91)" d="(省略)" stroke-linecap="round" bit:brushType="highlighter" />
</g>
</svg>

Path要素に独自属性を持つSVGをCanvasにロードする

fabric.loadSVGFromString で、Path要素に独自の属性 bit:brushType をもつSVG文字列をロードするとき、SVGのPath要素を先ほどつくった fabric.ExPath のオブジェクトとして読み込ませるには、 fabric.PathfromElement で独自属性を解釈して fabric.ExPath のオブジェクトをコールバックに渡してやる必要があります。
こんなかんじで、fabric.Path.fromElement をオーバーライドしました。

fabric.Path.fromElement = (element, callback, options) => {
  const parsedAttributes = fabric.parseAttributes(
    element,
    fabric.Path.ATTRIBUTE_NAMES.concat([ 'bit:brushType' ]);
  );

  parsedAttributes.fromSVG = true;
  parsedAttributes.brushType = parsedAttributes['bit:brushType'];
  delete parsedAttributes['bit:brushType'];

  callback(new fabric.ExPath(
    parsedAttributes.d,
    fabric.util.object.extend(parsedAttributes, options)
  ));
};

それでは、よいFabric.jsライフを。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*