//= require "object.class" (function(){ var fabric = this.fabric || (this.fabric = { }); if (fabric.Path) { console.warn('fabric.Path is already defined'); return; } if (!fabric.Object) { console.warn('fabric.Path requires fabric.Object'); return; } // Instance methods fabric.Path = fabric.util.createClass(fabric.Object, { type: 'path', /** * @constructor * @param path {Array | String} path data * (i.e. sequence of coordinates and corresponding "command" tokens) * @param options {Object} options object */ initialize: function(path, options) { options = options || { }; this.setOptions(options); this._importProperties(); this.originalState = { }; if (!path) { throw Error('`path` argument is required'); } var fromArray = Object.prototype.toString.call(path) === '[object Array]'; this.path = fromArray ? path : path.match && path.match(/[a-zA-Z][^a-zA-Z]*/g); if (!this.path) return; // TODO (kangax): rewrite this idiocracy if (!fromArray) { this._initializeFromArray(options); }; this.setCoords(); if (options.sourcePath) { this.setSourcePath(options.sourcePath); } }, _initializeFromArray: function(options) { var isWidthSet = 'width' in options, isHeightSet = 'height' in options; this.path = this._parsePath(); if (!isWidthSet || !isHeightSet) { fabric.util.object.extend(this, this._parseDimensions()); if (isWidthSet) { this.width = this.options.width; } if (isHeightSet) { this.height = this.options.height; } } }, _render: function(ctx) { var current, // current instruction x = 0, // current x y = 0, // current y controlX = 0, // current control point x controlY = 0, // current control point y tempX, tempY, l = -(this.width / 2), t = -(this.height / 2); for (var i=0, len=this.path.length; i'; }, /** * @method toObject * @return {Object} */ toObject: function() { var o = fabric.util.object.extend(this.callSuper('toObject'), { path: this.path }); if (this.sourcePath) { o.sourcePath = this.sourcePath; } if (this.transformMatrix) { o.transformMatrix = this.transformMatrix; } return o; }, /** * @method toDatalessObject * @return {Object} */ toDatalessObject: function() { var o = this.toObject(); if (this.sourcePath) { o.path = this.sourcePath; } delete o.sourcePath; return o; }, complexity: function() { return this.path.length; }, set: function(prop, value) { return this.callSuper('set', prop, value); }, _parsePath: function() { var result = [], currentPath, chunks; // use plain loop for perf. for (var i = 0, len = this.path.length; i < len; i++) { currentPath = this.path[i]; chunks = currentPath.slice(1).trim().replace(/(\d)-/g, '$1###-').split(/\s|,|###/); result.push([currentPath.charAt(0)].concat(chunks.map(parseFloat))); } return result; }, _parseDimensions: function() { var aX = [], aY = [], previousX, previousY, isLowerCase = false, x, y; function getX(item) { if (item[0] === 'H') { return item[1]; } return item[item.length - 2]; } function getY(item) { if (item[0] === 'V') { return item[1]; } return item[item.length - 1]; } this.path.forEach(function(item, i) { if (item[0] !== 'H') { previousX = (i === 0) ? getX(item) : getX(this.path[i-1]); } if (item[0] !== 'V') { previousY = (i === 0) ? getY(item) : getY(this.path[i-1]); } // lowercased letter denotes relative position; // transform to absolute if (item[0] === item[0].toLowerCase()) { isLowerCase = true; } // last 2 items in an array of coordinates are the actualy x/y (except H/V); // collect them // TODO (kangax): support relative h/v commands x = isLowerCase ? previousX + getX(item) : item[0] === 'V' ? previousX : getX(item); y = isLowerCase ? previousY + getY(item) : item[0] === 'H' ? previousY : getY(item); var val = parseInt(x, 10); if (!isNaN(val)) aX.push(val); val = parseInt(y, 10); if (!isNaN(val)) aY.push(val); }, this); var minX = fabric.util.array.min(aX), minY = fabric.util.array.min(aY), deltaX = deltaY = 0; var o = { top: minY - deltaY, left: minX - deltaX, bottom: fabric.util.array.max(aY) - deltaY, right: fabric.util.array.max(aX) - deltaX }; o.width = o.right - o.left; o.height = o.bottom - o.top; return o; } }); /** * Creates an instance of fabric.Path from an object * @static * @method fabric.Path.fromObject * @return {fabric.Path} Instance of fabric.Path */ fabric.Path.fromObject = function(object) { return new fabric.Path(object.path, object); }; var ATTRIBUTE_NAMES = fabric.Path.ATTRIBUTE_NAMES = 'd fill fill-opacity fill-rule stroke stroke-width transform'.split(' '); /** * Creates an instance of fabric.Path from an SVG element * @static * @method fabric.Path.fromElement * @param {SVGElement} element to parse * @param {Object} options object * @return {fabric.Path} Instance of fabric.Path */ fabric.Path.fromElement = function(element, options) { var parsedAttributes = fabric.parseAttributes(element, ATTRIBUTE_NAMES), path = parsedAttributes.d; delete parsedAttributes.d; return new fabric.Path(path, fabric.util.object.extend(parsedAttributes, options)); } })();