From 40c988b313c6972afc27db69f0d115f969f4b6e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Sun, 15 May 2011 17:59:06 +0100
Subject: [PATCH 1/5] Add Path#is/setClockwise(), as a way to check and define
 a path's orientation.

---
 src/path/Path.js | 83 +++++++++++++++++++++++++++++++-----------------
 1 file changed, 53 insertions(+), 30 deletions(-)

diff --git a/src/path/Path.js b/src/path/Path.js
index 330d0058..4c84f60c 100644
--- a/src/path/Path.js
+++ b/src/path/Path.js
@@ -34,6 +34,8 @@ var Path = this.Path = PathItem.extend({
 			delete this._bounds;
 			delete this._position;
 			delete this._strokeBounds;
+			// Clockwise state becomes undefined as soon as geometry changes.
+			delete this._clockwise;
 		} else if (flags & ChangeFlags.STROKE) {
 			delete this._strokeBounds;
 		}
@@ -302,20 +304,66 @@ var Path = this.Path = PathItem.extend({
 	// TODO: curvesToPoints([maxPointDistance[, flatness]])
 	// TODO: reduceSegments([flatness])
 	// TODO: split(offset) / split(location) / split(index[, parameter])
-	
+
+	/**
+	 * Returns true if the path is oriented clock-wise, false otherwise.
+	 */
+	isClockwise: function() {
+		if (this._clockwise !== undefined)
+			return this._clockwise;
+		var sum = 0,
+			xPre, yPre;
+		function edge(x, y) {
+			if (xPre !== undefined)
+				sum += (xPre - x) * (y + yPre);
+			xPre = x;
+			yPre = y;
+		}
+		// Method derived from:
+		// http://stackoverflow.com/questions/1165647
+		// We treat the curve points and handles as the outline of a polygon of
+		// which we determine the orientation using the method of calculating
+		// the sum over the edges. This will work even with non-convex polygons,
+		// telling you whether it's mostly clockwise
+		for (var i = 0, l = this._segments.length; i < l; i++) {
+			var seg1 = this._segments[i],
+				seg2 = this._segments[i + 1 < l ? i + 1 : 0],
+				point1 = seg1._point,
+				handle1 = seg1._handleOut,
+				handle2 = seg2._handleIn,
+				point2 = seg2._point;
+			edge(point1._x, point1._y);
+			edge(point1._x + handle1._x, point1._y + handle1._y);
+			edge(point2._x + handle2._x, point2._y + handle2._y);
+			edge(point2._x, point2._y);
+		}
+		return this._clockwise = sum > 0;
+	},
+
+	setClockwise: function(clockwise) {
+		// On-the-fly conversion to boolean:
+		if (this.isClockwise() != (clockwise = !!clockwise)) {
+			// Only revers the path if its clockwise orientation is not the same
+			// as what it is now demanded to be.
+			this.reverse();
+		}
+	},
+
 	/**
 	 * Reverses the segments of the path.
 	 */
 	reverse: function() {
-		var segments = this._segments;
-		segments.reverse();
+		this._segments.reverse();
 		// Reverse the handles:
-		for (var i = 0, l = segments.length; i < l; i++) {
-			var segment = segments[i];
+		for (var i = 0, l = this._segments.length; i < l; i++) {
+			var segment = this._segments[i];
 			var handleIn = segment._handleIn;
 			segment._handleIn = segment._handleOut;
 			segment._handleOut = handleIn;
 		}
+		// Flip clockwise state if it's defined
+		if (this._clockwise !== undefined)
+			this._clockwise = !this._clockwise;
 	},
 
 	join: function(path) {
@@ -356,31 +404,6 @@ var Path = this.Path = PathItem.extend({
 		return false;
 	},
 
-	getOrientation: function() {
-		var sum = 0;
-		var xPre, yPre;
-		function edge(x, y) {
-			if (xPre !== undefined) {
-				sum += (xPre - x) * (y + yPre);
-			}
-			xPre = x;
-			yPre = y;
-		}
-		for (var i = 0, l = this._segments.length; i < l; i++) {
-			var seg1 = this._segments[i];
-			var seg2 = this._segments[i + 1 < l ? i + 1 : 0];
-			var point1 = seg1._point;
-			var handle1 = seg1._handleOut;
-			var handle2 = seg2._handleIn;
-			var point2 = seg2._point;
-			edge(point1._x, point1._y);
-			edge(point1._x + handle1._x, point1._y + handle1._y);
-			edge(point2._x + handle2._x, point2._y + handle2._y);
-			edge(point2._x, point2._y);
-		}
-		return sum;
-	},
-
 	getLength: function() {
 		if (this._length == null) {
 			var curves = this.getCurves();

From 6e0e31480ac331bdacb4efa96f7af2e9744ccf9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Sun, 15 May 2011 17:59:37 +0100
Subject: [PATCH 2/5] Remove recently introduced code blocks for small
 conditional statements.

---
 src/tool/ToolHandler.js | 30 ++++++++++--------------------
 1 file changed, 10 insertions(+), 20 deletions(-)

diff --git a/src/tool/ToolHandler.js b/src/tool/ToolHandler.js
index 51e1ca41..db45ac74 100644
--- a/src/tool/ToolHandler.js
+++ b/src/tool/ToolHandler.js
@@ -25,9 +25,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 		this._firstMove = true;
 		this._count = 0;
 		this._downCount = 0;
-		for (var i in handlers) {
+		for (var i in handlers)
 			this[i] = handlers[i];
-		}
 	},
 
 	/**
@@ -59,10 +58,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 	},
 
 	getFixedDistance: function() {
-		if (this._minDistance == this._maxDistance) {
-			return this._minDistance;
-		}
-		return null;
+		return this._minDistance == this._maxDistance
+			? this._minDistance : null;
 	},
 
 	setFixedDistance: function(distance) {
@@ -77,9 +74,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 				var minDist = minDistance != null ? minDistance : 0;
 				var vector = pt.subtract(this._point);
 				var distance = vector.getLength();
-				if (distance < minDist) {
+				if (distance < minDist)
 					return false;
-				}
 				// Produce a new point on the way to pt if pt is further away
 				// than maxDistance
 				var maxDist = maxDistance != null ? maxDistance : 0;
@@ -91,9 +87,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 					}
 				}
 			}
-			if (needsChange && pt.equals(this._point)) {
+			if (needsChange && pt.equals(this._point))
 				return false;
-			}
 		}
 		this._lastPoint = this._point;
 		this._point = pt;
@@ -118,9 +113,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 		switch (type) {
 		case 'mousedown':
 			this.updateEvent(type, pt, null, null, true, false, false);
-			if (this.onMouseDown) {
+			if (this.onMouseDown)
 				this.onMouseDown(new ToolEvent(this, type, event));
-			}
 			break;
 		case 'mousedrag':
 			// In order for idleInterval drag events to work, we need to not
@@ -135,9 +129,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 				matchMaxDistance = false;
 			while (this.updateEvent(type, pt, this.minDistance,
 					this.maxDistance, false, needsChange, matchMaxDistance)) {
-				if (this.onMouseDrag) {
+				if (this.onMouseDrag)
 					this.onMouseDrag(new ToolEvent(this, type, event));
-				}
 				needsChange = true;
 				matchMaxDistance = true;
 			}
@@ -148,15 +141,13 @@ var ToolHandler = this.ToolHandler = Base.extend({
 			if ((this._point.x != pt.x || this._point.y != pt.y)
 					&& this.updateEvent('mousedrag', pt, this.minDistance,
 							this.maxDistance, false, false, false)) {
-				if (this.onMouseDrag) {
+				if (this.onMouseDrag)
 					this.onMouseDrag(new ToolEvent(this, type, event));
-				}
 			}
 			this.updateEvent(type, pt, null, this.maxDistance, false,
 					false, false);
-			if (this.onMouseUp) {
+			if (this.onMouseUp)
 				this.onMouseUp(new ToolEvent(this, type, event));
-			}
 			// Start with new values for 'mousemove'
 			this.updateEvent(type, pt, null, null, true, false, false);
 			this._firstMove = true;
@@ -164,9 +155,8 @@ var ToolHandler = this.ToolHandler = Base.extend({
 		case 'mousemove':
 			while (this.updateEvent(type, pt, this.minDistance,
 					this.maxDistance, this._firstMove, true, false)) {
-				if (this.onMouseMove) {
+				if (this.onMouseMove)
 					this.onMouseMove(new ToolEvent(this, type, event));
-				}
 				this._firstMove = false;
 			}
 			break;

From 813b70c70bdab3455a13e84780fb2d935112d09f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Sun, 15 May 2011 17:59:57 +0100
Subject: [PATCH 3/5] Update comment a bit to make code more clear.

---
 src/path/CompoundPath.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js
index e856b874..78305a7e 100644
--- a/src/path/CompoundPath.js
+++ b/src/path/CompoundPath.js
@@ -22,9 +22,9 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
 		if (items) {
 			for (var i = 0, l = items.length; i < l; i++) {
 				var item = items[i];
-				// All paths except for the first one are reversed when
-				// creating a compound path, so that they draw holes.
-				// When keepDirection is set to true, child paths aren't reversed.
+				// All paths except for the top one (last one in list) are
+				// reversed when creating a compound path, so that they draw
+				// holes. When keepDirection is set, children aren't reversed.
 				if (!keepDirection && i != l - 1)
 					item.reverse();
 				this.appendTop(items[i]);

From 3d760346006339b7b57ee962df9995274c8314cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Sun, 15 May 2011 18:05:00 +0100
Subject: [PATCH 4/5] Use Path#setClockwise() in CompoundPath constructor to
 reverse top path so that the others appear as holes cut out from it.

---
 src/path/CompoundPath.js | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js
index 78305a7e..4608efe1 100644
--- a/src/path/CompoundPath.js
+++ b/src/path/CompoundPath.js
@@ -15,18 +15,18 @@
  */
 
 var CompoundPath = this.CompoundPath = PathItem.extend({
-	// PORT: port the reversing of segments and keepDirection flag
-	initialize: function(items, keepDirection) {
+	initialize: function(items) {
 		this.base();
 		this._children = [];
 		if (items) {
 			for (var i = 0, l = items.length; i < l; i++) {
 				var item = items[i];
 				// All paths except for the top one (last one in list) are
-				// reversed when creating a compound path, so that they draw
-				// holes. When keepDirection is set, children aren't reversed.
-				if (!keepDirection && i != l - 1)
-					item.reverse();
+				// set to clockwise orientation when creating a compound path,
+				// so that they appear as holes, but only if their orientation
+				// was not already specified before (= _clockwise is defined).
+				if (item._clockwise === undefined)
+					item.setClockwise(i < l - 1);
 				this.appendTop(items[i]);
 			}
 		}

From 4cee442a05b1dc2f906712593085cfe9d5f916d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Sun, 15 May 2011 18:05:47 +0100
Subject: [PATCH 5/5] Rename items parameter to paths.

---
 src/path/CompoundPath.js | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js
index 4608efe1..0bc95726 100644
--- a/src/path/CompoundPath.js
+++ b/src/path/CompoundPath.js
@@ -15,19 +15,19 @@
  */
 
 var CompoundPath = this.CompoundPath = PathItem.extend({
-	initialize: function(items) {
+	initialize: function(paths) {
 		this.base();
 		this._children = [];
-		if (items) {
-			for (var i = 0, l = items.length; i < l; i++) {
-				var item = items[i];
+		if (paths) {
+			for (var i = 0, l = paths.length; i < l; i++) {
+				var path = paths[i];
 				// All paths except for the top one (last one in list) are
 				// set to clockwise orientation when creating a compound path,
 				// so that they appear as holes, but only if their orientation
 				// was not already specified before (= _clockwise is defined).
-				if (item._clockwise === undefined)
-					item.setClockwise(i < l - 1);
-				this.appendTop(items[i]);
+				if (path._clockwise === undefined)
+					path.setClockwise(i < l - 1);
+				this.appendTop(path);
 			}
 		}
 	},