/*globals $, svgedit, svgroot*/ /*jslint vars: true, eqeq: true, continue: true*/ /** * Package: svgedit.path * * Licensed under the MIT License * * Copyright(c) 2011 Alexis Deveria * Copyright(c) 2011 Jeff Schiller */ // Dependencies: // 1) jQuery // 2) browser.js // 3) math.js // 4) svgutils.js (function() {'use strict'; if (!svgedit.path) { svgedit.path = {}; } var NS = svgedit.NS; var uiStrings = { 'pathNodeTooltip': 'Drag node to move it. Double-click node to change segment type', 'pathCtrlPtTooltip': 'Drag control point to adjust curve properties' }; var segData = { 2: ['x', 'y'], 4: ['x', 'y'], 6: ['x', 'y', 'x1', 'y1', 'x2', 'y2'], 8: ['x', 'y', 'x1', 'y1'], 10: ['x', 'y', 'r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag'], 12: ['x'], 14: ['y'], 16: ['x', 'y', 'x2', 'y2'], 18: ['x', 'y'] }; var pathFuncs = []; var link_control_pts = true; // Stores references to paths via IDs. // TODO: Make this cross-document happy. var pathData = {}; svgedit.path.setLinkControlPoints = function(lcp) { link_control_pts = lcp; }; svgedit.path.path = null; var editorContext_ = null; svgedit.path.init = function(editorContext) { editorContext_ = editorContext; pathFuncs = [0, 'ClosePath']; var pathFuncsStrs = ['Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc', 'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth']; $.each(pathFuncsStrs, function(i, s) { pathFuncs.push(s+'Abs'); pathFuncs.push(s+'Rel'); }); }; svgedit.path.insertItemBefore = function(elem, newseg, index) { // Support insertItemBefore on paths for FF2 var list = elem.pathSegList; if (svgedit.browser.supportsPathInsertItemBefore()) { list.insertItemBefore(newseg, index); return; } var len = list.numberOfItems; var arr = []; var i; for (i=0; i < len; i++) { var cur_seg = list.getItem(i); arr.push(cur_seg); } list.clear(); for (i=0; i < len; i++) { if (i == index) { //index+1 list.appendItem(newseg); } list.appendItem(arr[i]); } }; // TODO: See if this should just live in replacePathSeg svgedit.path.ptObjToArr = function(type, seg_item) { var arr = segData[type], len = arr.length; var out = Array(len); var i; for (i = 0; i < len; i++) { out[i] = seg_item[arr[i]]; } return out; }; svgedit.path.getGripPt = function(seg, alt_pt) { var out = { x: alt_pt? alt_pt.x : seg.item.x, y: alt_pt? alt_pt.y : seg.item.y }, path = seg.path; if (path.matrix) { var pt = svgedit.math.transformPoint(out.x, out.y, path.matrix); out = pt; } out.x *= editorContext_.getCurrentZoom(); out.y *= editorContext_.getCurrentZoom(); return out; }; svgedit.path.getPointFromGrip = function(pt, path) { var out = { x: pt.x, y: pt.y }; if (path.matrix) { pt = svgedit.math.transformPoint(out.x, out.y, path.imatrix); out.x = pt.x; out.y = pt.y; } out.x /= editorContext_.getCurrentZoom(); out.y /= editorContext_.getCurrentZoom(); return out; }; svgedit.path.addPointGrip = function(index, x, y) { // create the container of all the point grips var pointGripContainer = svgedit.path.getGripContainer(); var pointGrip = svgedit.utilities.getElem('pathpointgrip_'+index); // create it if (!pointGrip) { pointGrip = document.createElementNS(NS.SVG, 'circle'); svgedit.utilities.assignAttributes(pointGrip, { 'id': 'pathpointgrip_' + index, 'display': 'none', 'r': 4, 'fill': '#0FF', 'stroke': '#00F', 'stroke-width': 2, 'cursor': 'move', 'style': 'pointer-events:all', 'xlink:title': uiStrings.pathNodeTooltip }); pointGrip = pointGripContainer.appendChild(pointGrip); var grip = $('#pathpointgrip_'+index); grip.dblclick(function() { if (svgedit.path.path) { svgedit.path.path.setSegType(); } }); } if (x && y) { // set up the point grip element and display it svgedit.utilities.assignAttributes(pointGrip, { 'cx': x, 'cy': y, 'display': 'inline' }); } return pointGrip; }; svgedit.path.getGripContainer = function() { var c = svgedit.utilities.getElem('pathpointgrip_container'); if (!c) { var parent = svgedit.utilities.getElem('selectorParentGroup'); c = parent.appendChild(document.createElementNS(NS.SVG, 'g')); c.id = 'pathpointgrip_container'; } return c; }; svgedit.path.addCtrlGrip = function(id) { var pointGrip = svgedit.utilities.getElem('ctrlpointgrip_'+id); if (pointGrip) {return pointGrip;} pointGrip = document.createElementNS(NS.SVG, 'circle'); svgedit.utilities.assignAttributes(pointGrip, { 'id': 'ctrlpointgrip_' + id, 'display': 'none', 'r': 4, 'fill': '#0FF', 'stroke': '#55F', 'stroke-width': 1, 'cursor': 'move', 'style': 'pointer-events:all', 'xlink:title': uiStrings.pathCtrlPtTooltip }); svgedit.path.getGripContainer().appendChild(pointGrip); return pointGrip; }; svgedit.path.getCtrlLine = function(id) { var ctrlLine = svgedit.utilities.getElem('ctrlLine_'+id); if (ctrlLine) {return ctrlLine;} ctrlLine = document.createElementNS(NS.SVG, 'line'); svgedit.utilities.assignAttributes(ctrlLine, { 'id': 'ctrlLine_'+id, 'stroke': '#555', 'stroke-width': 1, 'style': 'pointer-events:none' }); svgedit.path.getGripContainer().appendChild(ctrlLine); return ctrlLine; }; svgedit.path.getPointGrip = function(seg, update) { var index = seg.index; var pointGrip = svgedit.path.addPointGrip(index); if (update) { var pt = svgedit.path.getGripPt(seg); svgedit.utilities.assignAttributes(pointGrip, { 'cx': pt.x, 'cy': pt.y, 'display': 'inline' }); } return pointGrip; }; svgedit.path.getControlPoints = function(seg) { var item = seg.item; var index = seg.index; if (!('x1' in item) || !('x2' in item)) {return null;} var cpt = {}; var pointGripContainer = svgedit.path.getGripContainer(); // Note that this is intentionally not seg.prev.item var prev = svgedit.path.path.segs[index-1].item; var seg_items = [prev, item]; var i; for (i = 1; i < 3; i++) { var id = index + 'c' + i; var ctrlLine = cpt['c' + i + '_line'] = svgedit.path.getCtrlLine(id); var pt = svgedit.path.getGripPt(seg, {x:item['x' + i], y:item['y' + i]}); var gpt = svgedit.path.getGripPt(seg, {x:seg_items[i-1].x, y:seg_items[i-1].y}); svgedit.utilities.assignAttributes(ctrlLine, { 'x1': pt.x, 'y1': pt.y, 'x2': gpt.x, 'y2': gpt.y, 'display': 'inline' }); cpt['c' + i + '_line'] = ctrlLine; // create it pointGrip = cpt['c' + i] = svgedit.path.addCtrlGrip(id); svgedit.utilities.assignAttributes(pointGrip, { 'cx': pt.x, 'cy': pt.y, 'display': 'inline' }); cpt['c' + i] = pointGrip; } return cpt; }; // This replaces the segment at the given index. Type is given as number. svgedit.path.replacePathSeg = function(type, index, pts, elem) { var path = elem || svgedit.path.path.elem; var func = 'createSVGPathSeg' + pathFuncs[type]; var seg = path[func].apply(path, pts); if (svgedit.browser.supportsPathReplaceItem()) { path.pathSegList.replaceItem(seg, index); } else { var segList = path.pathSegList; var len = segList.numberOfItems; var arr = []; var i; for (i = 0; i < len; i++) { var cur_seg = segList.getItem(i); arr.push(cur_seg); } segList.clear(); for (i = 0; i < len; i++) { if (i == index) { segList.appendItem(seg); } else { segList.appendItem(arr[i]); } } } }; svgedit.path.getSegSelector = function(seg, update) { var index = seg.index; var segLine = svgedit.utilities.getElem('segline_' + index); if (!segLine) { var pointGripContainer = svgedit.path.getGripContainer(); // create segline segLine = document.createElementNS(NS.SVG, 'path'); svgedit.utilities.assignAttributes(segLine, { 'id': 'segline_' + index, 'display': 'none', 'fill': 'none', 'stroke': '#0FF', 'stroke-width': 2, 'style':'pointer-events:none', 'd': 'M0,0 0,0' }); pointGripContainer.appendChild(segLine); } if (update) { var prev = seg.prev; if (!prev) { segLine.setAttribute('display', 'none'); return segLine; } var pt = svgedit.path.getGripPt(prev); // Set start point svgedit.path.replacePathSeg(2, 0, [pt.x, pt.y], segLine); var pts = svgedit.path.ptObjToArr(seg.type, seg.item, true); var i; for (i = 0; i < pts.length; i += 2) { pt = svgedit.path.getGripPt(seg, {x:pts[i], y:pts[i+1]}); pts[i] = pt.x; pts[i+1] = pt.y; } svgedit.path.replacePathSeg(seg.type, 1, pts, segLine); } return segLine; }; // Function: smoothControlPoints // Takes three points and creates a smoother line based on them // // Parameters: // ct1 - Object with x and y values (first control point) // ct2 - Object with x and y values (second control point) // pt - Object with x and y values (third point) // // Returns: // Array of two "smoothed" point objects svgedit.path.smoothControlPoints = function(ct1, ct2, pt) { // each point must not be the origin var x1 = ct1.x - pt.x, y1 = ct1.y - pt.y, x2 = ct2.x - pt.x, y2 = ct2.y - pt.y; if ( (x1 != 0 || y1 != 0) && (x2 != 0 || y2 != 0) ) { var anglea = Math.atan2(y1, x1), angleb = Math.atan2(y2, x2), r1 = Math.sqrt(x1*x1+y1*y1), r2 = Math.sqrt(x2*x2+y2*y2), nct1 = editorContext_.getSVGRoot().createSVGPoint(), nct2 = editorContext_.getSVGRoot().createSVGPoint(); if (anglea < 0) { anglea += 2*Math.PI; } if (angleb < 0) { angleb += 2*Math.PI; } var angleBetween = Math.abs(anglea - angleb), angleDiff = Math.abs(Math.PI - angleBetween)/2; var new_anglea, new_angleb; if (anglea - angleb > 0) { new_anglea = angleBetween < Math.PI ? (anglea + angleDiff) : (anglea - angleDiff); new_angleb = angleBetween < Math.PI ? (angleb - angleDiff) : (angleb + angleDiff); } else { new_anglea = angleBetween < Math.PI ? (anglea - angleDiff) : (anglea + angleDiff); new_angleb = angleBetween < Math.PI ? (angleb + angleDiff) : (angleb - angleDiff); } // rotate the points nct1.x = r1 * Math.cos(new_anglea) + pt.x; nct1.y = r1 * Math.sin(new_anglea) + pt.y; nct2.x = r2 * Math.cos(new_angleb) + pt.x; nct2.y = r2 * Math.sin(new_angleb) + pt.y; return [nct1, nct2]; } return undefined; }; svgedit.path.Segment = function(index, item) { this.selected = false; this.index = index; this.item = item; this.type = item.pathSegType; this.ctrlpts = []; this.ptgrip = null; this.segsel = null; }; svgedit.path.Segment.prototype.showCtrlPts = function(y) { var i; for (i in this.ctrlpts) { this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none'); } }; svgedit.path.Segment.prototype.selectCtrls = function(y) { $('#ctrlpointgrip_' + this.index + 'c1, #ctrlpointgrip_' + this.index + 'c2'). attr('fill', y ? '#0FF' : '#EEE'); }; svgedit.path.Segment.prototype.show = function(y) { if (this.ptgrip) { this.ptgrip.setAttribute('display', y ? 'inline' : 'none'); this.segsel.setAttribute('display', y ? 'inline' : 'none'); // Show/hide all control points if available this.showCtrlPts(y); } }; svgedit.path.Segment.prototype.select = function(y) { if (this.ptgrip) { this.ptgrip.setAttribute('stroke', y ? '#0FF' : '#00F'); this.segsel.setAttribute('display', y ? 'inline' : 'none'); if (this.ctrlpts) { this.selectCtrls(y); } this.selected = y; } }; svgedit.path.Segment.prototype.addGrip = function() { this.ptgrip = svgedit.path.getPointGrip(this, true); this.ctrlpts = svgedit.path.getControlPoints(this, true); this.segsel = svgedit.path.getSegSelector(this, true); }; svgedit.path.Segment.prototype.update = function(full) { if (this.ptgrip) { var pt = svgedit.path.getGripPt(this); svgedit.utilities.assignAttributes(this.ptgrip, { 'cx': pt.x, 'cy': pt.y }); svgedit.path.getSegSelector(this, true); if (this.ctrlpts) { if (full) { this.item = svgedit.path.path.elem.pathSegList.getItem(this.index); this.type = this.item.pathSegType; } svgedit.path.getControlPoints(this); } // this.segsel.setAttribute('display', y?'inline':'none'); } }; svgedit.path.Segment.prototype.move = function(dx, dy) { var item = this.item; if (this.ctrlpts) { var cur_pts = [item.x += dx, item.y += dy, item.x1, item.y1, item.x2 += dx, item.y2 += dy]; } else { var cur_pts = [item.x += dx, item.y += dy]; } svgedit.path.replacePathSeg(this.type, this.index, cur_pts); if (this.next && this.next.ctrlpts) { var next = this.next.item; var next_pts = [next.x, next.y, next.x1 += dx, next.y1 += dy, next.x2, next.y2]; svgedit.path.replacePathSeg(this.next.type, this.next.index, next_pts); } if (this.mate) { // The last point of a closed subpath has a 'mate', // which is the 'M' segment of the subpath item = this.mate.item; var pts = [item.x += dx, item.y += dy]; svgedit.path.replacePathSeg(this.mate.type, this.mate.index, pts); // Has no grip, so does not need 'updating'? } this.update(true); if (this.next) {this.next.update(true);} }; svgedit.path.Segment.prototype.setLinked = function(num) { var seg, anum, pt; if (num == 2) { anum = 1; seg = this.next; if (!seg) {return;} pt = this.item; } else { anum = 2; seg = this.prev; if (!seg) {return;} pt = seg.item; } var item = seg.item; item['x' + anum] = pt.x + (pt.x - this.item['x' + num]); item['y' + anum] = pt.y + (pt.y - this.item['y' + num]); var pts = [item.x, item.y, item.x1, item.y1, item.x2, item.y2]; svgedit.path.replacePathSeg(seg.type, seg.index, pts); seg.update(true); }; svgedit.path.Segment.prototype.moveCtrl = function(num, dx, dy) { var item = this.item; item['x' + num] += dx; item['y' + num] += dy; var pts = [item.x, item.y, item.x1, item.y1, item.x2, item.y2]; svgedit.path.replacePathSeg(this.type, this.index, pts); this.update(true); }; svgedit.path.Segment.prototype.setType = function(new_type, pts) { svgedit.path.replacePathSeg(new_type, this.index, pts); this.type = new_type; this.item = svgedit.path.path.elem.pathSegList.getItem(this.index); this.showCtrlPts(new_type === 6); this.ctrlpts = svgedit.path.getControlPoints(this); this.update(true); }; svgedit.path.Path = function(elem) { if (!elem || elem.tagName !== 'path') { throw 'svgedit.path.Path constructed without a element'; } this.elem = elem; this.segs = []; this.selected_pts = []; svgedit.path.path = this; this.init(); }; // Reset path data svgedit.path.Path.prototype.init = function() { // Hide all grips, etc $(svgedit.path.getGripContainer()).find('*').attr('display', 'none'); var segList = this.elem.pathSegList; var len = segList.numberOfItems; this.segs = []; this.selected_pts = []; this.first_seg = null; // Set up segs array var i; for (i = 0; i < len; i++) { var item = segList.getItem(i); var segment = new svgedit.path.Segment(i, item); segment.path = this; this.segs.push(segment); } var segs = this.segs; var start_i = null; for (i = 0; i < len; i++) { var seg = segs[i]; var next_seg = (i+1) >= len ? null : segs[i+1]; var prev_seg = (i-1) < 0 ? null : segs[i-1]; var start_seg; if (seg.type === 2) { if (prev_seg && prev_seg.type !== 1) { // New sub-path, last one is open, // so add a grip to last sub-path's first point start_seg = segs[start_i]; start_seg.next = segs[start_i+1]; start_seg.next.prev = start_seg; start_seg.addGrip(); } // Remember that this is a starter seg start_i = i; } else if (next_seg && next_seg.type === 1) { // This is the last real segment of a closed sub-path // Next is first seg after "M" seg.next = segs[start_i+1]; // First seg after "M"'s prev is this seg.next.prev = seg; seg.mate = segs[start_i]; seg.addGrip(); if (this.first_seg == null) { this.first_seg = seg; } } else if (!next_seg) { if (seg.type !== 1) { // Last seg, doesn't close so add a grip // to last sub-path's first point start_seg = segs[start_i]; start_seg.next = segs[start_i+1]; start_seg.next.prev = start_seg; start_seg.addGrip(); seg.addGrip(); if (!this.first_seg) { // Open path, so set first as real first and add grip this.first_seg = segs[start_i]; } } } else if (seg.type !== 1){ // Regular segment, so add grip and its "next" seg.addGrip(); // Don't set its "next" if it's an "M" if (next_seg && next_seg.type !== 2) { seg.next = next_seg; seg.next.prev = seg; } } } return this; }; svgedit.path.Path.prototype.eachSeg = function(fn) { var i; var len = this.segs.length; for (i = 0; i < len; i++) { var ret = fn.call(this.segs[i], i); if (ret === false) {break;} } }; svgedit.path.Path.prototype.addSeg = function(index) { // Adds a new segment var seg = this.segs[index]; if (!seg.prev) {return;} var prev = seg.prev; var newseg; switch(seg.item.pathSegType) { case 4: var new_x = (seg.item.x + prev.item.x) / 2; var new_y = (seg.item.y + prev.item.y) / 2; newseg = this.elem.createSVGPathSegLinetoAbs(new_x, new_y); break; case 6: //make it a curved segment to preserve the shape (WRS) // http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm#Geometric_interpretation var p0_x = (prev.item.x + seg.item.x1)/2; var p1_x = (seg.item.x1 + seg.item.x2)/2; var p2_x = (seg.item.x2 + seg.item.x)/2; var p01_x = (p0_x + p1_x)/2; var p12_x = (p1_x + p2_x)/2; var new_x = (p01_x + p12_x)/2; var p0_y = (prev.item.y + seg.item.y1)/2; var p1_y = (seg.item.y1 + seg.item.y2)/2; var p2_y = (seg.item.y2 + seg.item.y)/2; var p01_y = (p0_y + p1_y)/2; var p12_y = (p1_y + p2_y)/2; var new_y = (p01_y + p12_y)/2; newseg = this.elem.createSVGPathSegCurvetoCubicAbs(new_x, new_y, p0_x, p0_y, p01_x, p01_y); var pts = [seg.item.x, seg.item.y, p12_x, p12_y, p2_x, p2_y]; svgedit.path.replacePathSeg(seg.type, index, pts); break; } svgedit.path.insertItemBefore(this.elem, newseg, index); }; svgedit.path.Path.prototype.deleteSeg = function(index) { var seg = this.segs[index]; var list = this.elem.pathSegList; seg.show(false); var next = seg.next; var pt; if (seg.mate) { // Make the next point be the "M" point pt = [next.item.x, next.item.y]; svgedit.path.replacePathSeg(2, next.index, pt); // Reposition last node svgedit.path.replacePathSeg(4, seg.index, pt); list.removeItem(seg.mate.index); } else if (!seg.prev) { // First node of open path, make next point the M var item = seg.item; pt = [next.item.x, next.item.y]; svgedit.path.replacePathSeg(2, seg.next.index, pt); list.removeItem(index); } else { list.removeItem(index); } }; svgedit.path.Path.prototype.subpathIsClosed = function(index) { var closed = false; // Check if subpath is already open svgedit.path.path.eachSeg(function(i) { if (i <= index) {return true;} if (this.type === 2) { // Found M first, so open return false; } if (this.type === 1) { // Found Z first, so closed closed = true; return false; } }); return closed; }; svgedit.path.Path.prototype.removePtFromSelection = function(index) { var pos = this.selected_pts.indexOf(index); if (pos == -1) { return; } this.segs[index].select(false); this.selected_pts.splice(pos, 1); }; svgedit.path.Path.prototype.clearSelection = function() { this.eachSeg(function() { // 'this' is the segment here this.select(false); }); this.selected_pts = []; }; svgedit.path.Path.prototype.storeD = function() { this.last_d = this.elem.getAttribute('d'); }; svgedit.path.Path.prototype.show = function(y) { // Shows this path's segment grips this.eachSeg(function() { // 'this' is the segment here this.show(y); }); if (y) { this.selectPt(this.first_seg.index); } return this; }; // Move selected points svgedit.path.Path.prototype.movePts = function(d_x, d_y) { var i = this.selected_pts.length; while(i--) { var seg = this.segs[this.selected_pts[i]]; seg.move(d_x, d_y); } }; svgedit.path.Path.prototype.moveCtrl = function(d_x, d_y) { var seg = this.segs[this.selected_pts[0]]; seg.moveCtrl(this.dragctrl, d_x, d_y); if (link_control_pts) { seg.setLinked(this.dragctrl); } }; svgedit.path.Path.prototype.setSegType = function(new_type) { this.storeD(); var i = this.selected_pts.length; var text; while(i--) { var sel_pt = this.selected_pts[i]; // Selected seg var cur = this.segs[sel_pt]; var prev = cur.prev; if (!prev) {continue;} if (!new_type) { // double-click, so just toggle text = 'Toggle Path Segment Type'; // Toggle segment to curve/straight line var old_type = cur.type; new_type = (old_type == 6) ? 4 : 6; } new_type = new_type-0; var cur_x = cur.item.x; var cur_y = cur.item.y; var prev_x = prev.item.x; var prev_y = prev.item.y; var points; switch ( new_type ) { case 6: if (cur.olditem) { var old = cur.olditem; points = [cur_x, cur_y, old.x1, old.y1, old.x2, old.y2]; } else { var diff_x = cur_x - prev_x; var diff_y = cur_y - prev_y; // get control points from straight line segment /* var ct1_x = (prev_x + (diff_y/2)); var ct1_y = (prev_y - (diff_x/2)); var ct2_x = (cur_x + (diff_y/2)); var ct2_y = (cur_y - (diff_x/2)); */ //create control points on the line to preserve the shape (WRS) var ct1_x = (prev_x + (diff_x/3)); var ct1_y = (prev_y + (diff_y/3)); var ct2_x = (cur_x - (diff_x/3)); var ct2_y = (cur_y - (diff_y/3)); points = [cur_x, cur_y, ct1_x, ct1_y, ct2_x, ct2_y]; } break; case 4: points = [cur_x, cur_y]; // Store original prevve segment nums cur.olditem = cur.item; break; } cur.setType(new_type, points); } svgedit.path.path.endChanges(text); }; svgedit.path.Path.prototype.selectPt = function(pt, ctrl_num) { this.clearSelection(); if (pt == null) { this.eachSeg(function(i) { // 'this' is the segment here. if (this.prev) { pt = i; } }); } this.addPtsToSelection(pt); if (ctrl_num) { this.dragctrl = ctrl_num; if (link_control_pts) { this.segs[pt].setLinked(ctrl_num); } } }; // Update position of all points svgedit.path.Path.prototype.update = function() { var elem = this.elem; if (svgedit.utilities.getRotationAngle(elem)) { this.matrix = svgedit.math.getMatrix(elem); this.imatrix = this.matrix.inverse(); } else { this.matrix = null; this.imatrix = null; } this.eachSeg(function(i) { this.item = elem.pathSegList.getItem(i); this.update(); }); return this; }; svgedit.path.getPath_ = function(elem) { var p = pathData[elem.id]; if (!p) { p = pathData[elem.id] = new svgedit.path.Path(elem); } return p; }; svgedit.path.removePath_ = function(id) { if (id in pathData) {delete pathData[id];} }; var getRotVals = function(x, y) { dx = x - oldcx; dy = y - oldcy; // rotate the point around the old center r = Math.sqrt(dx*dx + dy*dy); theta = Math.atan2(dy, dx) + angle; dx = r * Math.cos(theta) + oldcx; dy = r * Math.sin(theta) + oldcy; // dx,dy should now hold the actual coordinates of each // point after being rotated // now we want to rotate them around the new center in the reverse direction dx -= newcx; dy -= newcy; r = Math.sqrt(dx*dx + dy*dy); theta = Math.atan2(dy, dx) - angle; return {'x':(r * Math.cos(theta) + newcx)/1, 'y':(r * Math.sin(theta) + newcy)/1}; }; // If the path was rotated, we must now pay the piper: // Every path point must be rotated into the rotated coordinate system of // its old center, then determine the new center, then rotate it back // This is because we want the path to remember its rotation // TODO: This is still using ye olde transform methods, can probably // be optimized or even taken care of by recalculateDimensions svgedit.path.recalcRotatedPath = function() { var current_path = svgedit.path.path.elem; var angle = svgedit.utilities.getRotationAngle(current_path, true); if (!angle) {return;} // selectedBBoxes[0] = svgedit.path.path.oldbbox; var box = svgedit.utilities.getBBox(current_path), oldbox = svgedit.path.path.oldbbox,//selectedBBoxes[0], oldcx = oldbox.x + oldbox.width/2, oldcy = oldbox.y + oldbox.height/2, newcx = box.x + box.width/2, newcy = box.y + box.height/2, // un-rotate the new center to the proper position dx = newcx - oldcx, dy = newcy - oldcy, r = Math.sqrt(dx*dx + dy*dy), theta = Math.atan2(dy, dx) + angle; newcx = r * Math.cos(theta) + oldcx; newcy = r * Math.sin(theta) + oldcy; var list = current_path.pathSegList, i = list.numberOfItems; while (i) { i -= 1; var seg = list.getItem(i), type = seg.pathSegType; if (type == 1) {continue;} var rvals = getRotVals(seg.x, seg.y), points = [rvals.x, rvals.y]; if (seg.x1 != null && seg.x2 != null) { c_vals1 = getRotVals(seg.x1, seg.y1); c_vals2 = getRotVals(seg.x2, seg.y2); points.splice(points.length, 0, c_vals1.x , c_vals1.y, c_vals2.x, c_vals2.y); } svgedit.path.replacePathSeg(type, i, points); } // loop for each point box = svgedit.utilities.getBBox(current_path); // selectedBBoxes[0].x = box.x; selectedBBoxes[0].y = box.y; // selectedBBoxes[0].width = box.width; selectedBBoxes[0].height = box.height; // now we must set the new transform to be rotated around the new center var R_nc = svgroot.createSVGTransform(), tlist = svgedit.transformlist.getTransformList(current_path); R_nc.setRotate((angle * 180.0 / Math.PI), newcx, newcy); tlist.replaceItem(R_nc,0); }; // ==================================== // Public API starts here svgedit.path.clearData = function() { pathData = {}; }; }());