// Path points array SVG.PathArray = function(array, fallback) { SVG.Array.call(this, array, fallback || [['M', 0, 0]]) } // Inherit from SVG.Array SVG.PathArray.prototype = new SVG.Array SVG.PathArray.prototype.constructor = SVG.PathArray SVG.extend(SVG.PathArray, { // Convert array to string toString: function() { return arrayToString(this.value) } // Move path string , move: function(x, y) { // get bounding box of current situation var box = this.bbox() // get relative offset x -= box.x y -= box.y if (!isNaN(x) && !isNaN(y)) { // move every point for (var l, i = this.value.length - 1; i >= 0; i--) { l = this.value[i][0] if (l == 'M' || l == 'L' || l == 'T') { this.value[i][1] += x this.value[i][2] += y } else if (l == 'H') { this.value[i][1] += x } else if (l == 'V') { this.value[i][1] += y } else if (l == 'C' || l == 'S' || l == 'Q') { this.value[i][1] += x this.value[i][2] += y this.value[i][3] += x this.value[i][4] += y if (l == 'C') { this.value[i][5] += x this.value[i][6] += y } } else if (l == 'A') { this.value[i][6] += x this.value[i][7] += y } } } return this } // Resize path string , size: function(width, height) { // get bounding box of current situation var i, l, box = this.bbox() // recalculate position of all points according to new size for (i = this.value.length - 1; i >= 0; i--) { l = this.value[i][0] if (l == 'M' || l == 'L' || l == 'T') { this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y } else if (l == 'H') { this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x } else if (l == 'V') { this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y } else if (l == 'C' || l == 'S' || l == 'Q') { this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y if (l == 'C') { this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y } } else if (l == 'A') { // resize radii this.value[i][1] = (this.value[i][1] * width) / box.width this.value[i][2] = (this.value[i][2] * height) / box.height // move position values this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y } } return this } // Test if the passed path array use the same path data commands as this path array , equalCommands: function(pathArray) { var i, il, equalCommands pathArray = new SVG.PathArray(pathArray) equalCommands = this.value.length === pathArray.value.length for(i = 0, il = this.value.length; equalCommands && i < il; i++) { equalCommands = this.value[i][0] === pathArray.value[i][0] } return equalCommands } // Make path array morphable , morph: function(pathArray) { pathArray = new SVG.PathArray(pathArray) if(this.equalCommands(pathArray)) { this.destination = pathArray } else { this.destination = null } return this } // Get morphed path array at given position , at: function(pos) { // make sure a destination is defined if (!this.destination) return this var sourceArray = this.value , destinationArray = this.destination.value , array = [], pathArray = new SVG.PathArray() , i, il, j, jl // Animate has specified in the SVG spec // See: https://www.w3.org/TR/SVG11/paths.html#PathElement for (i = 0, il = sourceArray.length; i < il; i++) { array[i] = [sourceArray[i][0]] for(j = 1, jl = sourceArray[i].length; j < jl; j++) { array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos } // For the two flags of the elliptical arc command, the SVG spec say: // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true // Elliptical arc command as an array followed by corresponding indexes: // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] // 0 1 2 3 4 5 6 7 if(array[i][0] === 'A') { array[i][4] = +(array[i][4] != 0) array[i][5] = +(array[i][5] != 0) } } // Directly modify the value of a path array, this is done this way for performance pathArray.value = array return pathArray } // Absolutize and parse path to array , parse: function(array) { // if it's already a patharray, no need to parse it if (array instanceof SVG.PathArray) return array.valueOf() // prepare for parsing var i, x0, y0, s, seg, arr , x = 0 , y = 0 , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7 } if(typeof array == 'string'){ array = array .replace(SVG.regex.negExp, 'X') // replace all negative exponents with certain char .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers .replace(SVG.regex.hyphen, ' -') // add space before hyphen .replace(SVG.regex.comma, ' ') // unify all spaces .replace(SVG.regex.X, 'e-') // add back the expoent .trim() // trim .split(SVG.regex.whitespaces) // split into array // at this place there could be parts like ['3.124.854.32'] because we could not determine the point as seperator till now // we fix this elements in the next loop for(i = array.length; --i;){ if(array[i].indexOf('.') != array[i].lastIndexOf('.')){ var split = array[i].split('.') // split at the point var first = [split.shift(), split.shift()].join('.') // join the first number together array.splice.apply(array, [i, 1].concat(first, split.map(function(el){ return '.'+el }))) // add first and all other entries back to array } } }else{ array = array.reduce(function(prev, curr){ return [].concat.apply(prev, curr) }, []) } // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] var arr = [] do{ // Test if we have a path letter if(SVG.regex.isPathLetter.test(array[0])){ s = array[0] array.shift() // If last letter was a move command and we got no new, it defaults to [L]ine }else if(s == 'M'){ s = 'L' }else if(s == 'm'){ s = 'l' } // add path letter as first element seg = [s.toUpperCase()] // push all necessary parameters to segment for(i = 0; i < paramCnt[seg[0]]; ++i){ seg.push(parseFloat(array.shift())) } // upper case if(s == seg[0]){ if(s == 'M' || s == 'L' || s == 'C' || s == 'Q' || s == 'S' || s == 'T'){ x = seg[paramCnt[seg[0]]-1] y = seg[paramCnt[seg[0]]] }else if(s == 'V'){ y = seg[1] }else if(s == 'H'){ x = seg[1] }else if(s == 'A'){ x = seg[6] y = seg[7] } // lower case }else{ // convert relative to absolute values if(s == 'm' || s == 'l' || s == 'c' || s == 's' || s == 'q' || s == 't'){ seg[1] += x seg[2] += y if(seg[3] != null){ seg[3] += x seg[4] += y } if(seg[5] != null){ seg[5] += x seg[6] += y } // move pointer x = seg[paramCnt[seg[0]]-1] y = seg[paramCnt[seg[0]]] }else if(s == 'v'){ seg[1] += y y = seg[1] }else if(s == 'h'){ seg[1] += x x = seg[1] }else if(s == 'a'){ seg[6] += x seg[7] += y x = seg[6] y = seg[7] } } if(seg[0] == 'M'){ x0 = x y0 = y } if(seg[0] == 'Z'){ x = x0 y = y0 } arr.push(seg) }while(array.length) return arr } // Get bounding box of path , bbox: function() { SVG.parser.path.setAttribute('d', this.toString()) return SVG.parser.path.getBBox() } })