(function(){ var global = this; if (!global.Canvas) { global.Canvas = { }; } if (global.Canvas.Path) { console.warn('Canvas.Path is already defined'); return; } if (!global.Canvas.Object) { console.warn('Canvas.Path requires Canvas.Object'); return; } // Instance methods Canvas.Path = Class.create(Canvas.Object, Canvas.IStub, { 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.isArray(path); 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) { 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 = Object.extend(this.callSuper('toObject'), { path: this.path, 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) { if (this.stub) { this.stub.set(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).strip().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.each(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); aX.push(x); aY.push(y); }, this); var minX = aX.min(), minY = aY.min(), deltaX = deltaY = 0; var o = { top: minY - deltaY, left: minX - deltaX, bottom: aY.max() - deltaY, right: aX.max() - deltaX } o.width = o.right - o.left; o.height = o.bottom - o.top; return o; } }); /** * Creates an instance of Canvas.Path from an object * @static * @method Canvas.Path.fromObject * @return {Canvas.Path} Instance of Canvas.Path */ Canvas.Path.fromObject = function(object) { return new Canvas.Path(object.path, object); }; var ATTRIBUTE_NAMES = Canvas.Path.ATTRIBUTE_NAMES = $w('d fill fill-opacity fill-rule stroke stroke-width transform'); /** * Creates an instance of Canvas.Path from an SVG element * @static * @method Canvas.Path.fromElement * @param {SVGElement} element to parse * @param {Object} options object * @return {Canvas.Path} Instance of Canvas.Path */ Canvas.Path.fromElement = function(element, options) { var parsedAttributes = Canvas.parseAttributes(element, ATTRIBUTE_NAMES), path = parsedAttributes.d; delete parsedAttributes.d; return new Canvas.Path(path, Object.extend(parsedAttributes, options)); } })();