diff --git a/Makefile b/Makefile
index dd351c79..beab2bb5 100644
--- a/Makefile
+++ b/Makefile
@@ -16,6 +16,7 @@ JS_FILES=\
history.js \
select.js \
draw.js \
+ path.js \
svgcanvas.js \
svg-editor.js \
locale/locale.js
diff --git a/editor/path.js b/editor/path.js
index fd8c5f28..84364294 100644
--- a/editor/path.js
+++ b/editor/path.js
@@ -42,6 +42,12 @@ var segData = {
var pathFuncs = [];
+var link_control_pts = true;
+
+svgedit.path.setLinkControlPoints = function(lcp) {
+ link_control_pts = lcp;
+};
+
svgedit.path.path = null;
var editorContext_ = null;
@@ -485,4 +491,318 @@ svgedit.path.Segment.prototype.setType = function(new_type, pts) {
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
+ for(var 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(var 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];
+
+ 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
+ var 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
+ var 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 len = this.segs.length
+ for(var 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;
+ if(seg.mate) {
+ // Make the next point be the "M" point
+ var 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;
+ var 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;
+ } else 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(i) {
+ // '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);
+ }
+ }
+};
+
})();
diff --git a/editor/svgcanvas.js b/editor/svgcanvas.js
index f8b99853..db183e9a 100644
--- a/editor/svgcanvas.js
+++ b/editor/svgcanvas.js
@@ -3993,382 +3993,71 @@ var pathActions = canvas.pathActions = function() {
p.setAttribute("d", pathActions.convertPath(p));
}
- function Path(elem) {
- if(!elem || elem.tagName !== "path") return false;
-
- var p = svgedit.path.path = this;
- this.elem = elem;
- this.segs = [];
- this.selected_pts = [];
-
- // Reset path data
- this.init = function() {
- // Hide all grips, etc
- $(svgedit.path.getGripContainer()).find("*").attr("display", "none");
- var segList = elem.pathSegList;
- var len = segList.numberOfItems;
- p.segs = [];
- p.selected_pts = [];
- p.first_seg = null;
-
- // Set up segs array
- for(var i=0; i < len; i++) {
- var item = segList.getItem(i);
- var segment = new svgedit.path.Segment(i, item);
- segment.path = p;
- p.segs.push(segment);
- }
-
- var segs = p.segs;
- var start_i = null;
-
- for(var 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];
-
- 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
- var 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(p.first_seg == null) {
- p.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
- var start_seg = segs[start_i];
- start_seg.next = segs[start_i+1];
- start_seg.next.prev = start_seg;
- start_seg.addGrip();
- seg.addGrip();
-
- if(!p.first_seg) {
- // Open path, so set first as real first and add grip
- p.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 p;
- }
-
- this.init();
-
+ // TODO: Move into path.js
// Update position of all points
- this.update = function() {
- if(getRotationAngle(p.elem)) {
- p.matrix = getMatrix(svgedit.path.path.elem);
- p.imatrix = p.matrix.inverse();
+ svgedit.path.Path.prototype.update = function() {
+ var elem = this.elem;
+ if(getRotationAngle(elem)) {
+ this.matrix = getMatrix(elem);
+ this.imatrix = this.matrix.inverse();
} else {
- p.matrix = null;
- p.imatrix = null;
+ this.matrix = null;
+ this.imatrix = null;
}
- p.eachSeg(function(i) {
+ this.eachSeg(function(i) {
this.item = elem.pathSegList.getItem(i);
this.update();
});
- return p;
+ return this;
}
- this.eachSeg = function(fn) {
- var len = p.segs.length
- for(var i=0; i < len; i++) {
- var ret = fn.call(p.segs[i], i);
- if(ret === false) break;
- }
- }
-
- this.addSeg = function(index) {
- // Adds a new segment
- var seg = p.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 = 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 = 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(elem, newseg, index);
- }
-
- this.deleteSeg = function(index) {
- var seg = p.segs[index];
- var list = elem.pathSegList;
-
- seg.show(false);
- var next = seg.next;
- if(seg.mate) {
- // Make the next point be the "M" point
- var 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;
- var pt = [next.item.x, next.item.y];
- svgedit.path.replacePathSeg(2, seg.next.index, pt);
- list.removeItem(index);
-
- } else {
- list.removeItem(index);
- }
- }
-
- this.endChanges = function(text) {
- if(svgedit.browser.isWebkit()) resetD(p.elem);
- var cmd = new ChangeElementCommand(elem, {d: p.last_d}, text);
+ svgedit.path.Path.prototype.endChanges = function(text) {
+ if(svgedit.browser.isWebkit()) resetD(this.elem);
+ var cmd = new ChangeElementCommand(this.elem, {d: this.last_d}, text);
addCommandToHistory(cmd);
- call("changed", [elem]);
+ call("changed", [this.elem]);
}
- this.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;
- } else if(this.type === 1) {
- // Found Z first, so closed
- closed = true;
- return false;
- }
- });
-
- return closed;
- }
-
- this.addPtsToSelection = function(indexes) {
+ svgedit.path.Path.prototype.addPtsToSelection = function(indexes) {
if(!$.isArray(indexes)) indexes = [indexes];
for(var i=0; i< indexes.length; i++) {
var index = indexes[i];
- var seg = p.segs[index];
+ var seg = this.segs[index];
if(seg.ptgrip) {
- if(p.selected_pts.indexOf(index) == -1 && index >= 0) {
- p.selected_pts.push(index);
+ if(this.selected_pts.indexOf(index) == -1 && index >= 0) {
+ this.selected_pts.push(index);
}
}
};
- p.selected_pts.sort();
- var i = p.selected_pts.length,
+ this.selected_pts.sort();
+ var i = this.selected_pts.length,
grips = new Array(i);
// Loop through points to be selected and highlight each
while(i--) {
- var pt = p.selected_pts[i];
- var seg = p.segs[pt];
+ var pt = this.selected_pts[i];
+ var seg = this.segs[pt];
seg.select(true);
grips[i] = seg.ptgrip;
}
// TODO: Correct this:
pathActions.canDeleteNodes = true;
- pathActions.closed_subpath = p.subpathIsClosed(p.selected_pts[0]);
+ pathActions.closed_subpath = this.subpathIsClosed(this.selected_pts[0]);
call("selected", grips);
}
- this.removePtFromSelection = function(index) {
- var pos = p.selected_pts.indexOf(index);
- if(pos == -1) {
- return;
- }
- p.segs[index].select(false);
- p.selected_pts.splice(pos, 1);
- }
-
-
- this.clearSelection = function() {
- p.eachSeg(function(i) {
- this.select(false);
- });
- p.selected_pts = [];
- }
-
- this.selectPt = function(pt, ctrl_num) {
- p.clearSelection();
- if(pt == null) {
- p.eachSeg(function(i) {
- if(this.prev) {
- pt = i;
- }
- });
- }
- p.addPtsToSelection(pt);
- if(ctrl_num) {
- p.dragctrl = ctrl_num;
-
- if(link_control_pts) {
- p.segs[pt].setLinked(ctrl_num);
- }
- }
- }
-
- this.storeD = function() {
- this.last_d = elem.getAttribute('d');
- }
-
- this.show = function(y) {
- // Shows this path's segment grips
- p.eachSeg(function() {
- this.show(y);
- });
- if(y) {
- p.selectPt(p.first_seg.index);
- }
- return p;
- }
-
- // Move selected points
- this.movePts = function(d_x, d_y) {
- var i = p.selected_pts.length;
- while(i--) {
- var seg = p.segs[p.selected_pts[i]];
- seg.move(d_x, d_y);
- }
- }
-
- this.moveCtrl = function(d_x, d_y) {
- var seg = p.segs[p.selected_pts[0]];
- seg.moveCtrl(p.dragctrl, d_x, d_y);
- if(link_control_pts) {
- seg.setLinked(p.dragctrl);
- }
- }
-
- this.setSegType = function(new_type) {
- p.storeD();
- var i = p.selected_pts.length;
- var text;
- while(i--) {
- var sel_pt = p.selected_pts[i];
-
- // Selected seg
- var cur = p.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);
- return;
- }
-
- }
-
function getPath(elem) {
var p = pathData[elem.id];
- if(!p) p = pathData[elem.id] = new Path(elem);
+ if(!p) p = pathData[elem.id] = new svgedit.path.Path(elem);
return p;
}
var current_path = null,
drawn_path = null,
- link_control_pts = true,
hasMoved = false;
// This function converts a polyline (created by the fh_path tool) into
@@ -5070,7 +4759,7 @@ var pathActions = canvas.pathActions = function() {
};
},
linkControlPoints: function(linkPoints) {
- link_control_pts = linkPoints;
+ svgedit.path.setLinkControlPoints(linkPoints);
},
clonePathNode: function() {
svgedit.path.path.storeD();
@@ -5488,6 +5177,7 @@ var pathActions = canvas.pathActions = function() {
}
}
}();
+// end pathActions
// Group: Serialization
diff --git a/test/all_tests.html b/test/all_tests.html
index 3ae234e5..0fb28bad 100644
--- a/test/all_tests.html
+++ b/test/all_tests.html
@@ -13,6 +13,7 @@
+
+
+
+
+
+
+
+
+