Move most of Path into svgedit.path

git-svn-id: http://svg-edit.googlecode.com/svn/trunk@1978 eee81c28-f429-11dd-99c0-75d572ba1ddd
master
Jeff Schiller 2011-02-04 16:06:25 +00:00
parent 8032d08acb
commit 52c74f266b
5 changed files with 377 additions and 336 deletions

View File

@ -16,6 +16,7 @@ JS_FILES=\
history.js \
select.js \
draw.js \
path.js \
svgcanvas.js \
svg-editor.js \
locale/locale.js

View File

@ -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 <path> 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);
}
}
};
})();

View File

@ -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

View File

@ -13,6 +13,7 @@
<iframe src='select_test.html' width='100%' height='300'></iframe>
<iframe src='draw_test.html' width='100%' height='300'></iframe>
<iframe src='units_test.html' width='100%' height='300'></iframe>
<iframe src='path_test.html' width='100%' height='300'></iframe>
</body>
<script>
window.setTimeout(function() {

29
test/path_test.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='qunit/qunit.css' type='text/css'/>
<script type='text/javascript' src='../editor/jquery.js'></script>
<script type='text/javascript' src='../editor/path.js'></script>
<script type='text/javascript' src='qunit/qunit.js'></script>
<script type='text/javascript'>
$(function() {
// log function
QUnit.log = function(result, message) {
if (window.console && window.console.log) {
window.console.log(result +' :: '+ message);
}
};
});
</script>
</head>
<body>
<h1 id='qunit-header'>Unit Tests for path.js</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'>
</ol>
<div id='anchor' style='visibility:hidden'>
</div>
</body>
</html>