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ライフを。