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.ExCanvas で toSVG を実行したときに、独自の属性 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に追加されるように createPath で fabric.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.ExCanvas と fabric.ExPencilBrush をつかってCanvasをつくります。
const canvas = new fabric.ExCanvas('c', {
isDrawingMode: true
});
canvas.freeDrawingBrush = new fabric.ExPencilBrush(canvas);
canvas.freeDrawingBrush.brushType = 'highlighter';
Canvasに線を描いて fabric.ExCanvas の toSVG を実行すると、以下のような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.Path の fromElement で独自属性を解釈して 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ライフを。
