diff --git a/src/index.html b/src/index.html
index 6e208eb..02c2cec 100644
--- a/src/index.html
+++ b/src/index.html
@@ -383,9 +383,11 @@
Offset
-
Remove path
+ Change side
-
+
+
+ Remove path
@@ -477,7 +479,7 @@
diff --git a/src/js/Text.js b/src/js/Text.js
index 5aeb7bf..7188a63 100644
--- a/src/js/Text.js
+++ b/src/js/Text.js
@@ -22,6 +22,19 @@ MD.Text = function(){
editor.selectedChanged(window, [text]);
}
+ function changeTextOnPath(){
+ const elem = svgCanvas.getSelectedElems()[0];
+ const textPath = elem.querySelector("textPath");
+ if (!textPath) return;
+ const path = svgCanvas.getTextPath(elem);
+ const d = path.getAttribute("d");
+ const reversed = utils.SVGPathEditor.reverse(d);
+ path.setAttribute("d", reversed);
+ const selector = svgCanvas.selectorManager.requestSelector(elem);
+ selector.resize();
+ editor.selectedChanged(window, [elem]);
+ }
+
function setBold(){
if ($(this).hasClass("disabled")) return;
svgCanvas.setBold( !svgCanvas.getBold() );
@@ -40,6 +53,7 @@ MD.Text = function(){
$('#tool_text_on_path').click(placeTextOnPath);
$('#tool_release_text_on_path').click(releaseTextOnPath);
+ $('#tool_change_text_on_path').click(changeTextOnPath);
$("#tool_bold").on("click", setBold);
$("#tool_italic").on("click", setItalic);
diff --git a/src/js/editor.js b/src/js/editor.js
index a80176b..f49eb24 100644
--- a/src/js/editor.js
+++ b/src/js/editor.js
@@ -104,7 +104,7 @@ MD.Editor = function(){
svgCanvas.convertToPath();
var elems = svgCanvas.getSelectedElems()
svgCanvas.selectorManager.requestSelector(elems[0]).reset(elems[0])
- svgCanvas.selectorManager.requestSelector(elems[0]).selectorRect.setAttribute("display", "none");
+ //svgCanvas.selectorManager.requestSelector(elems[0]).selectorRect.setAttribute("display", "none");
svgCanvas.setMode("pathedit");
svgCanvas.pathActions.toEditMode(elems[0]);
svgCanvas.clearSelection();
diff --git a/src/js/svgcanvas.js b/src/js/svgcanvas.js
index 12da7cd..827a922 100644
--- a/src/js/svgcanvas.js
+++ b/src/js/svgcanvas.js
@@ -3359,6 +3359,11 @@ var getMouseTarget = this.getMouseTarget = function(evt) {
canvas.setMode("pathedit");
canvas.pathActions.toEditMode(evt_target);
}
+
+ // Reset context
+ if(tagName === "ellipse" || tagName === "circle" || tagName === "line" || tagName === "rect") {
+ editor.convertToPath();
+ }
if((parent.tagName !== 'g' && parent.tagName !== 'a') ||
parent === getCurrentDrawing().getCurrentLayer() ||
@@ -4038,13 +4043,15 @@ var pathActions = canvas.pathActions = function() {
var endseg = drawn_path.createSVGPathSegClosePath();
seglist.appendItem(newseg);
seglist.appendItem(endseg);
- selectorManager.requestSelector(newpath).showGrips(true)
+ selectorManager.requestSelector(newpath).showGrips(true);
} else if(len < 3) {
keep = false;
+
return keep;
}
stretchy.parentNode.removeChild(stretchy);
+ state.set("canvasMode", "select");
// this will signal to commit the path
element = newpath;
@@ -4526,6 +4533,15 @@ var pathActions = canvas.pathActions = function() {
addToSelection([elem], true);
call("changed", selectedElements);
},
+
+ reverse: function() {
+
+ var elem = selectedElements[0];
+ if(!elem || !elem.getAttribute("d")) return;
+
+ const d = elem.getAttribute("d");
+ var reversed = reverse(path);
+ },
clear: function(remove) {
current_path = null;
@@ -4623,113 +4639,114 @@ var pathActions = canvas.pathActions = function() {
return svgedit.path.path;
},
opencloseSubPath: function() {
- var sel_pts = svgedit.path.path.selected_pts;
+ const path = svgedit.path.path;
+ const selPts = path.selected_pts;
// Only allow one selected node for now
- if(sel_pts.length !== 1) return;
-
- var elem = svgedit.path.path.elem;
- var list = elem.pathSegList;
+ if (selPts.length !== 1) { return; }
- var len = list.numberOfItems;
+ const { elem } = path;
+ const list = elem.pathSegList;
- var index = sel_pts[0];
-
- var open_pt = null;
- var start_item = null;
+ // const len = list.numberOfItems;
+
+ const index = selPts[0];
+
+ let openPt = null;
+ let startItem = null;
// Check if subpath is already open
- svgedit.path.path.eachSeg(function(i) {
- if(this.type === 2 && i <= index) {
- start_item = this.item;
+ path.eachSeg(function (i) {
+ if (this.type === 2 && i <= index) {
+ startItem = this.item;
}
- if(i <= index) return true;
- if(this.type === 2) {
+ if (i <= index) { return true; }
+ if (this.type === 2) {
// Found M first, so open
- open_pt = i;
- return false;
- } else if(this.type === 1) {
- // Found Z first, so closed
- open_pt = false;
+ openPt = i;
return false;
}
+ if (this.type === 1) {
+ // Found Z first, so closed
+ openPt = false;
+ return false;
+ }
+ return true;
});
-
- if(open_pt == null) {
+
+ if (openPt == null) {
// Single path, so close last seg
- open_pt = svgedit.path.path.segs.length - 1;
+ openPt = path.segs.length - 1;
}
- if(open_pt !== false) {
+ if (openPt !== false) {
// Close this path
-
+
// Create a line going to the previous "M"
- var newseg = elem.createSVGPathSegLinetoAbs(start_item.x, start_item.y);
-
- var closer = elem.createSVGPathSegClosePath();
- if(open_pt == svgedit.path.path.segs.length) {
+ const newseg = elem.createSVGPathSegLinetoAbs(startItem.x, startItem.y);
+
+ const closer = elem.createSVGPathSegClosePath();
+ if (openPt === path.segs.length - 1) {
list.appendItem(newseg);
list.appendItem(closer);
} else {
- svgedit.path.insertItemBefore(elem, closer, open_pt);
- svgedit.path.insertItemBefore(elem, newseg, open_pt);
+ list.insertItemBefore(closer, openPt);
+ list.insertItemBefore(newseg, openPt);
}
-
- svgedit.path.path.init().selectPt(open_pt+1);
+
+ path.init().selectPt(openPt + 1);
return;
}
-
-
// M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
// M 2,2 L 3,3 L 1,1
-
- // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
- // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
-
- var seg = svgedit.path.path.segs[index];
-
- if(seg.mate) {
+
+ // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
+ // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
+
+ const seg = path.segs[index];
+
+ if (seg.mate) {
list.removeItem(index); // Removes last "L"
list.removeItem(index); // Removes the "Z"
- svgedit.path.path.init().selectPt(index - 1);
+ path.init().selectPt(index - 1);
return;
}
-
- var last_m, z_seg;
-
- // Find this sub-path's closing point and remove
- for(var i=0; i 2) {
+ for (a = 0; a < alen; a += 2) {
+ if (op === "m") {
+ x += args[a];
+ y += args[a+1];
+ } else {
+ x = args[a];
+ y = args[a+1];
+ }
+ normalized += "L " + x + " " + y + " ";
+ }
+ }
+ }
+
+ // lineto commands
+ else if(lop === "l") {
+ for (a = 0; a < alen; a += 2) {
+ if (op === "l") {
+ x += args[a];
+ y += args[a+1];
+ } else {
+ x = args[a];
+ y = args[a+1];
+ }
+ normalized += "L " + x + " " + y + " ";
+ }
+ }
+ else if(lop === "h") {
+ for (a = 0; a < alen; a++) {
+ if (op === "h") {
+ x += args[a];
+ } else {
+ x = args[a];
+ }
+ normalized += "L " + x + " " + y + " ";
+ }
+ }
+ else if(lop === "v") {
+ for (a = 0; a < alen; a++) {
+ if (op === "v") {
+ y += args[a];
+ } else {
+ y = args[a];
+ }
+ normalized += "L " + x + " " + y + " ";
+ }
+ }
+
+ // quadratic curveto commands
+ else if(lop === "q") {
+ for (a = 0; a < alen; a += 4) {
+ if (op === "q") {
+ cx = x + args[a];
+ cy = y + args[a+1];
+ x += args[a+2];
+ y += args[a+3];
+ } else {
+ cx = args[a];
+ cy = args[a+1];
+ x = args[a+2];
+ y = args[a+3];
+ }
+ normalized += "Q " + cx + " " + cy + " " + x + " " + y + " ";
+ }
+ }
+ else if(lop === "t") {
+ for (a = 0; a < alen; a += 2) {
+ // reflect previous cx/cy over x/y
+ cx = x + (x-cx);
+ cy = y + (y-cy);
+ // then get real end point
+ if (op === "t") {
+ x += args[a];
+ y += args[a+1];
+ } else {
+ x = args[a];
+ y = args[a+1];
+ }
+ normalized += "Q " + cx + " " + cy + " " + x + " " + y + " ";
+ }
+ }
+
+ // cubic curveto commands
+ else if(lop === "c") {
+ for (a = 0; a < alen; a += 6) {
+ if (op === "c") {
+ cx = x + args[a];
+ cy = y + args[a+1];
+ cx2 = x + args[a+2];
+ cy2 = y + args[a+3];
+ x += args[a+4];
+ y += args[a+5];
+ } else {
+ cx = args[a];
+ cy = args[a+1];
+ cx2 = args[a+2];
+ cy2 = args[a+3];
+ x = args[a+4]
+ y = args[a+5];
+ }
+ normalized += "C " + cx + " " + cy + " " + cx2 + " " + cy2 + " " + x + " " + y + " ";
+ }
+ }
+ else if(lop === "s") {
+ for (a = 0; a < alen; a += 4) {
+ // reflect previous cx2/cy2 over x/y
+ cx = x + (x-cx2);
+ cy = y + (y-cy2);
+ // then get real control and end point
+ if (op === "s") {
+ cx2 = x + args[a];
+ cy2 = y + args[a+1];
+ x += args[a+2];
+ y += args[a+3];
+ } else {
+ cx2 = args[a];
+ cy2 = args[a+1];
+ x = args[a+2]
+ y = args[a+3];
+ }
+ normalized += "C " + cx + " " + cy + " " + cx2 + " " + cy2 + " " + x + " " + y + " ";
+ }
+ }
+
+ // rx ry x-axis-rotation large-arc-flag sweep-flag x y
+ // a 25,25 -30 0, 1 50,-25
+
+ // arc command
+ else if(lop === "a") {
+ for (a = 0; a < alen; a += 7) {
+ rx = args[a];
+ ry = args[a+1];
+ xrot = args[a+2];
+
+ lflag = oargs[a+3]; // we need the original string to deal with leading zeroes
+ let fixed = false;
+
+ if(lflag.length > 1) {
+ let b1 = parseInt(lflag[0]),
+ b2 = parseInt(lflag[1]),
+ rest = undefined;
+ if(lflag.length > 2) rest = parseFloat(lflag.substring(2));
+ args[a+3] = b1;
+ args.splice(a+4, 0, b2);
+ if (rest!==undefined) args.splice(a+5, 0, rest);
+ fixed = true;
+ }
+ lflag = args[a+3];
+
+ sweep = fixed ? args[a+4] : oargs[a+4]; // we need the original string to deal with leading zeroes
+ if(!fixed && sweep.length > 1) {
+ args[a+4] = parseInt(sweep[0]);
+ args.splice(a+5, 0, parseFloat(sweep.substring(1)));
+ }
+ sweep = args[a+4];
+
+ if (op === "a") {
+ x += args[a+5];
+ y += args[a+6];
+ } else {
+ x = args[a+5];
+ y = args[a+6];
+ }
+
+ normalized += "A " + rx + " " + ry + " " + xrot + " " + lflag + " " + sweep + " " + x + " " + y + " ";
+ }
+ }
+
+ else if(lop === "z") {
+ normalized += "Z ";
+ // not unimportant: path closing changes the current x/y coordinate
+ x = sx;
+ y = sy;
}
}
- }
\ No newline at end of file
+ return normalized.trim();
+ };
+
+ /**
+ * Reverse an SVG path.
+ * As long as the input path is normalised, this is actually really
+ * simple to do. As all pathing commands are symmetrical, meaning
+ * that they render the same when you reverse the coordinate order,
+ * the grand trick here is to reverse the path (making sure to keep
+ * coordinates ordered pairwise) and shift the operators left by
+ * one or two coordinate pairs depending on the operator:
+ *
+ * - Z is removed (after noting it existed),
+ * - L moves to 2 spots earlier (skipping one coordinate),
+ * - Q moves to 2 spots earlier (skipping one coordinate),
+ * - C moves to 4 spots earlier (skipping two coordinates)
+ * and its arguments get reversed,
+ * - the path start becomes M.
+ * - the path end becomes Z iff it was there to begin with.
+ */
+ function reverseNormalizedPath(normalized) {
+ var terms = normalized.trim().split(' '),
+ term,
+ tlen = terms.length,
+ tlen1 = tlen-1,
+ t,
+ reversed = [],
+ x, y,
+ pair, pairs,
+ shift,
+ matcher = new RegExp('[QAZLCM]',''),
+ closed = terms.slice(-1)[0].toUpperCase() === 'Z';
+
+ for (t = 0; t < tlen; t++) {
+ term = terms[t];
+
+ // Is this an operator? If it is, run through its
+ // argument list, which we know is fixed length.
+ if (matcher.test(term)) {
+
+ // Arc processing relies on not-just-coordinates
+ if (term === "A") {
+ reversed.push(terms[t+5] === '0' ? '1' : '0');
+ reversed.push(terms[t+4]);
+ reversed.push(terms[t+3]);
+ reversed.push(terms[t+2]);
+ reversed.push(terms[t+1]);
+ reversed.push(term);
+ reversed.push(terms[t+7]);
+ reversed.push(terms[t+6]);
+ t += 7;
+ continue;
+ }
+
+ // how many coordinate pairs do we need to read,
+ // and by how many pairs should this operator be
+ // shifted left?
+ else if (term === "C") { pairs = 3; shift = 2; }
+ else if (term === "Q") { pairs = 2; shift = 1; }
+ else if (term === "L") { pairs = 1; shift = 1; }
+ else if (term === "M") { pairs = 1; shift = 0; }
+ else { continue; }
+
+ // do the argument reading and operator shifting
+ if (pairs === shift) {
+ reversed.push(term);
+ }
+ for (pair = 0; pair < pairs; pair++) {
+ if (pair === shift) {
+ reversed.push(term);
+ }
+ x = terms[++t];
+ y = terms[++t];
+ reversed.push(y);
+ reversed.push(x);
+ }
+ }
+ // the code has been set up so that every time we
+ // iterate because of the for() operation, the term
+ // we see is a pathing operator, not a number. As
+ // such, if we get to this "else" the path is malformed.
+ else {
+ var pre = terms.slice(Math.max(t-3,0),3).join(" ");
+ post = terms.slice(t+1,Math.min(t+4,tlen1)).join(" ");
+ range = pre + " [" + term + "] " + post;
+ throw("Error while trying to reverse normalized SVG path, at position "+t+" ("+range+").\n" +
+ "Either the path is not normalised, or it's malformed."); }
+ }
+
+ reversed.push('M');
+
+ // generating the reversed path string involves
+ // running through our transformed terms in reverse.
+ var revstring = "", rlen1 = reversed.length-1, r;
+ for (r = rlen1; r > 0; r--) {
+ revstring += reversed[r] + " ";
+ }
+ if (closed) revstring += "Z";
+ revstring = revstring.replace(/M M/g,'Z M');
+
+ return revstring;
+ };
+
+ /**
+ * This is the function that you'll actually want to
+ * make use of, because it lets you reverse individual
+ * subpaths in some "d" attribute.
+ */
+ function reverseSubPath(path, subpath) {
+ subpath = parseInt(subpath)==subpath ? subpath : false;
+ var path = normalizePath(path),
+ paths = path.replace(/M/g,'|M').split("|"),
+ revpath;
+ paths.splice(0,1);
+ if (subpath !== false && subpath >= paths.length) {
+ return path;
+ }
+
+ if (subpath === false) {
+ paths = paths.map(function(spath) {
+ return reverseNormalizedPath(spath.trim());
+ });
+ } else {
+ var spath = paths[subpath];
+ if (spath) {
+ revpath = reverseNormalizedPath(spath.trim());
+ paths[subpath] = revpath;
+ }
+ }
+
+ return paths.join(" ").replace(/ +/g,' ').trim();
+ };
+
+ /**
+ * Our return object
+ */
+ var SVGPathEditor = {
+ normalize: normalizePath,
+ reverseNormalized: reverseNormalizedPath,
+ reverse: reverseSubPath
+ };
+
+ return SVGPathEditor;
+}));