var π = Math.PI var _120 = radians(120) module.exports = normalize /** * describe `path` in terms of cubic bézier * curves and move commands * * @param {Array} path * @return {Array} */ function normalize(path){ // init state var prev var result = [] var bezierX = 0 var bezierY = 0 var startX = 0 var startY = 0 var quadX = null var quadY = null var x = 0 var y = 0 for (var i = 0, len = path.length; i < len; i++) { var seg = path[i] var command = seg[0] switch (command) { case 'M': startX = seg[1] startY = seg[2] break case 'A': seg = arc(x, y,seg[1],seg[2],radians(seg[3]),seg[4],seg[5],seg[6],seg[7]) // split multi part seg.unshift('C') if (seg.length > 7) { result.push(seg.splice(0, 7)) seg.unshift('C') } break case 'S': // default control point var cx = x var cy = y if (prev == 'C' || prev == 'S') { cx += cx - bezierX // reflect the previous command's control cy += cy - bezierY // point relative to the current point } seg = ['C', cx, cy, seg[1], seg[2], seg[3], seg[4]] break case 'T': if (prev == 'Q' || prev == 'T') { quadX = x * 2 - quadX // as with 'S' reflect previous control point quadY = y * 2 - quadY } else { quadX = x quadY = y } seg = quadratic(x, y, quadX, quadY, seg[1], seg[2]) break case 'Q': quadX = seg[1] quadY = seg[2] seg = quadratic(x, y, seg[1], seg[2], seg[3], seg[4]) break case 'L': seg = line(x, y, seg[1], seg[2]) break case 'H': seg = line(x, y, seg[1], y) break case 'V': seg = line(x, y, x, seg[1]) break case 'Z': seg = line(x, y, startX, startY) break } // update state prev = command x = seg[seg.length - 2] y = seg[seg.length - 1] if (seg.length > 4) { bezierX = seg[seg.length - 4] bezierY = seg[seg.length - 3] } else { bezierX = x bezierY = y } result.push(seg) } return result } function line(x1, y1, x2, y2){ return ['C', x1, y1, x2, y2, x2, y2] } function quadratic(x1, y1, cx, cy, x2, y2){ return [ 'C', x1/3 + (2/3) * cx, y1/3 + (2/3) * cy, x2/3 + (2/3) * cx, y2/3 + (2/3) * cy, x2, y2 ] } // This function is ripped from // github.com/DmitryBaranovskiy/raphael/blob/4d97d4/raphael.js#L2216-L2304 // which references w3.org/TR/SVG11/implnote.html#ArcImplementationNotes // TODO: make it human readable function arc(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { if (!recursive) { var xy = rotate(x1, y1, -angle) x1 = xy.x y1 = xy.y xy = rotate(x2, y2, -angle) x2 = xy.x y2 = xy.y var x = (x1 - x2) / 2 var y = (y1 - y2) / 2 var h = (x * x) / (rx * rx) + (y * y) / (ry * ry) if (h > 1) { h = Math.sqrt(h) rx = h * rx ry = h * ry } var rx2 = rx * rx var ry2 = ry * ry var k = (large_arc_flag == sweep_flag ? -1 : 1) * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))) if (k == Infinity) k = 1 // neutralize var cx = k * rx * y / ry + (x1 + x2) / 2 var cy = k * -ry * x / rx + (y1 + y2) / 2 var f1 = Math.asin(((y1 - cy) / ry).toFixed(9)) var f2 = Math.asin(((y2 - cy) / ry).toFixed(9)) f1 = x1 < cx ? π - f1 : f1 f2 = x2 < cx ? π - f2 : f2 if (f1 < 0) f1 = π * 2 + f1 if (f2 < 0) f2 = π * 2 + f2 if (sweep_flag && f1 > f2) f1 = f1 - π * 2 if (!sweep_flag && f2 > f1) f2 = f2 - π * 2 } else { f1 = recursive[0] f2 = recursive[1] cx = recursive[2] cy = recursive[3] } // greater than 120 degrees requires multiple segments if (Math.abs(f2 - f1) > _120) { var f2old = f2 var x2old = x2 var y2old = y2 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1) x2 = cx + rx * Math.cos(f2) y2 = cy + ry * Math.sin(f2) var res = arc(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]) } var t = Math.tan((f2 - f1) / 4) var hx = 4 / 3 * rx * t var hy = 4 / 3 * ry * t var curve = [ 2 * x1 - (x1 + hx * Math.sin(f1)), 2 * y1 - (y1 - hy * Math.cos(f1)), x2 + hx * Math.sin(f2), y2 - hy * Math.cos(f2), x2, y2 ] if (recursive) return curve if (res) curve = curve.concat(res) for (var i = 0; i < curve.length;) { var rot = rotate(curve[i], curve[i+1], angle) curve[i++] = rot.x curve[i++] = rot.y } return curve } function rotate(x, y, rad){ return { x: x * Math.cos(rad) - y * Math.sin(rad), y: x * Math.sin(rad) + y * Math.cos(rad) } } function radians(degress){ return degress * (π / 180) }