From c989d3ee3f84ca8e191d4a52255e007ccf9e8b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Thu, 24 Nov 2011 15:18:57 +0100 Subject: [PATCH] Restructure code a bit for easier reading. --- src/path/Path.js | 483 ++++++++++++++++++++++++----------------------- 1 file changed, 244 insertions(+), 239 deletions(-) diff --git a/src/path/Path.js b/src/path/Path.js index fe54d831..50cb03c5 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -1761,6 +1761,107 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ } }; }, new function() { // A dedicated scope for the tricky bounds calculations + /** + * Returns the bounding rectangle of the item excluding stroke width. + */ + function getBounds(that, matrix, strokePadding) { + // Code ported and further optimised from: + // http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + var segments = that._segments, + first = segments[0]; + if (!first) + return null; + var coords = new Array(6), + prevCoords = new Array(6); + // Make coordinates for first segment available in prevCoords. + first._transformCoordinates(matrix, prevCoords, false); + var min = prevCoords.slice(0, 2), + max = min.slice(0), // clone + // Add some tolerance for good roots, as t = 0 / 1 are added + // seperately anyhow, and we don't want joins to be added with + // radiuses in getStrokeBounds() + tMin = Numerical.TOLERANCE, + tMax = 1 - tMin; + function processSegment(segment) { + segment._transformCoordinates(matrix, coords, false); + + for (var i = 0; i < 2; i++) { + var v0 = prevCoords[i], // prev.point + v1 = prevCoords[i + 4], // prev.handleOut + v2 = coords[i + 2], // segment.handleIn + v3 = coords[i]; // segment.point + + function add(value, t) { + var padding = 0; + if (value == null) { + // Calculate bezier polynomial at t + var u = 1 - t; + value = u * u * u * v0 + + 3 * u * u * t * v1 + + 3 * u * t * t * v2 + + t * t * t * v3; + // Only add strokeWidth to bounds for points which lie + // within 0 < t < 1. The corner cases for cap and join + // are handled in getStrokeBounds() + padding = strokePadding ? strokePadding[i] : 0; + } + var left = value - padding, + right = value + padding; + if (left < min[i]) + min[i] = left; + if (right > max[i]) + max[i] = right; + + } + add(v3, null); + + // Calculate derivative of our bezier polynomial, divided by 3. + // Dividing by 3 allows for simpler calculations of a, b, c and + // leads to the same quadratic roots below. + var a = 3 * (v1 - v2) - v0 + v3, + b = 2 * (v0 + v2) - 4 * v1, + c = v1 - v0; + + // Solve for derivative for quadratic roots. Each good root + // (meaning a solution 0 < t < 1) is an extrema in the cubic + // polynomial and thus a potential point defining the bounds + // TODO: Use tolerance here, just like Numerical.solveQuadratic + if (a == 0) { + if (b == 0) + continue; + var t = -c / b; + // Test for good root and add to bounds if good (same below) + if (tMin < t && t < tMax) + add(null, t); + continue; + } + + var q = b * b - 4 * a * c; + if (q < 0) + continue; + // TODO: Match this with Numerical.solveQuadratic + var sqrt = Math.sqrt(q), + f = -0.5 / a, + t1 = (b - sqrt) * f, + t2 = (b + sqrt) * f; + if (tMin < t1 && t1 < tMax) + add(null, t1); + if (tMin < t2 && t2 < tMax) + add(null, t2); + } + // Swap coordinate buffers + var tmp = prevCoords; + prevCoords = coords; + coords = tmp; + } + for (var i = 1, l = segments.length; i < l; i++) + processSegment(segments[i]); + if (that._closed) + processSegment(first); + return Rectangle.create(min[0], min[1], + max[0] - min[0], max[1] - min[1]); + } + /** * Returns the horizontal and vertical padding that a transformed round * stroke adds to the bounding box, by calculating the dimensions of a @@ -1803,256 +1904,160 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ return [Math.abs(x), Math.abs(y)]; } - var get = { - /** - * Returns the bounding rectangle of the item excluding stroke width. - */ - bounds: function(that, matrix, strokePadding) { - // Code ported and further optimised from: - // http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - var segments = that._segments, - first = segments[0]; - if (!first) - return null; - var coords = new Array(6), - prevCoords = new Array(6); - // Make coordinates for first segment available in prevCoords. - first._transformCoordinates(matrix, prevCoords, false); - var min = prevCoords.slice(0, 2), - max = min.slice(0), // clone - // Add some tolerance for good roots, as t = 0 / 1 are added - // seperately anyhow, and we don't want joins to be added with - // radiuses in getStrokeBounds() - tMin = Numerical.TOLERANCE, - tMax = 1 - tMin; - function processSegment(segment) { - segment._transformCoordinates(matrix, coords, false); + /** + * Returns the bounding rectangle of the item including stroke width. + */ + function getStrokeBounds(that, matrix) { + // TODO: Should we access this.getStrokeColor, as we do in _transform? + // TODO: Find a way to reuse 'bounds' cache instead? + if (!that._style._strokeColor || !that._style._strokeWidth) + return getBounds(that, matrix); + var width = that.getStrokeWidth(), + radius = width / 2, + padding = getPenPadding(radius, matrix), + join = that.getStrokeJoin(), + cap = that.getStrokeCap(), + // miter is relative to width. Divide it by 2 since we're + // measuring half the distance below + miter = that.getMiterLimit() * width / 2, + segments = that._segments, + length = segments.length, + // It seems to be compatible with Ai we need to pass pen padding + // untransformed to getBounds + bounds = getBounds(that, matrix, getPenPadding(radius)); + // Create a rectangle of padding size, used for union with bounds + // further down + var joinBounds = new Rectangle(new Size(padding).multiply(2)); - for (var i = 0; i < 2; i++) { - var v0 = prevCoords[i], // prev.point - v1 = prevCoords[i + 4], // prev.handleOut - v2 = coords[i + 2], // segment.handleIn - v3 = coords[i]; // segment.point + function add(point) { + bounds = bounds.include(matrix + ? matrix.transform(point) : point); + } - function add(value, t) { - var padding = 0; - if (value == null) { - // Calculate bezier polynomial at t - var u = 1 - t; - value = u * u * u * v0 - + 3 * u * u * t * v1 - + 3 * u * t * t * v2 - + t * t * t * v3; - // Only add strokeWidth to bounds for points which lie - // within 0 < t < 1. The corner cases for cap and join - // are handled in getStrokeBounds() - padding = strokePadding ? strokePadding[i] : 0; - } - var left = value - padding, - right = value + padding; - if (left < min[i]) - min[i] = left; - if (right > max[i]) - max[i] = right; + function addBevelJoin(curve, t) { + var point = curve.getPoint(t), + normal = curve.getNormal(t).normalize(radius); + add(point.add(normal)); + add(point.subtract(normal)); + } - } - add(v3, null); - - // Calculate derivative of our bezier polynomial, divided by 3. - // Dividing by 3 allows for simpler calculations of a, b, c and - // leads to the same quadratic roots below. - var a = 3 * (v1 - v2) - v0 + v3, - b = 2 * (v0 + v2) - 4 * v1, - c = v1 - v0; - - // Solve for derivative for quadratic roots. Each good root - // (meaning a solution 0 < t < 1) is an extrema in the cubic - // polynomial and thus a potential point defining the bounds - // TODO: Use tolerance here, just like Numerical.solveQuadratic - if (a == 0) { - if (b == 0) - continue; - var t = -c / b; - // Test for good root and add to bounds if good (same below) - if (tMin < t && t < tMax) - add(null, t); - continue; - } - - var q = b * b - 4 * a * c; - if (q < 0) - continue; - // TODO: Match this with Numerical.solveQuadratic - var sqrt = Math.sqrt(q), - f = -0.5 / a, - t1 = (b - sqrt) * f, - t2 = (b + sqrt) * f; - if (tMin < t1 && t1 < tMax) - add(null, t1); - if (tMin < t2 && t2 < tMax) - add(null, t2); + function addJoin(segment, join) { + // When both handles are set in a segment, the join setting is + // ignored and round is always used. + if (join === 'round' || !segment._handleIn.isZero() + && !segment._handleOut.isZero()) { + bounds = bounds.unite(joinBounds.setCenter(matrix + ? matrix.transform(segment._point) : segment._point)); + } else if (join == 'bevel') { + var curve = segment.getCurve(); + addBevelJoin(curve, 0); + addBevelJoin(curve.getPrevious(), 1); + } else if (join == 'miter') { + var curve2 = segment.getCurve(), + curve1 = curve2.getPrevious(), + point = curve2.getPoint(0), + normal1 = curve1.getNormal(1).normalize(radius), + normal2 = curve2.getNormal(0).normalize(radius), + // Intersect the two lines + line1 = new Line(point.subtract(normal1), + new Point(-normal1.y, normal1.x)), + line2 = new Line(point.subtract(normal2), + new Point(-normal2.y, normal2.x)), + corner = line1.intersect(line2); + // Now measure the distance from the segment to the + // intersection, which his half of the miter distance + if (!corner || point.getDistance(corner) > miter) { + addJoin(segment, 'bevel'); + } else { + add(corner); } - // Swap coordinate buffers - var tmp = prevCoords; - prevCoords = coords; - coords = tmp; } - for (var i = 1, l = segments.length; i < l; i++) - processSegment(segments[i]); - if (that._closed) - processSegment(first); - return Rectangle.create(min[0], min[1], - max[0] - min[0], max[1] - min[1]); - }, + } - /** - * Returns the bounding rectangle of the item including stroke width. - */ - strokeBounds: function(that, matrix) { - // TODO: Should we access this.getStrokeColor, as we do in _transform? - // TODO: Find a way to reuse 'bounds' cache instead? - if (!that._style._strokeColor || !that._style._strokeWidth) - return get.bounds(that, matrix); - var width = that.getStrokeWidth(), - radius = width / 2, - padding = getPenPadding(radius, matrix), - join = that.getStrokeJoin(), - cap = that.getStrokeCap(), - // miter is relative to width. Divide it by 2 since we're - // measuring half the distance below - miter = that.getMiterLimit() * width / 2, - segments = that._segments, - length = segments.length, - // It seems to be compatible with Ai we need to pass pen padding - // untransformed to get.bounds - bounds = get.bounds(that, matrix, getPenPadding(radius)); - // Create a rectangle of padding size, used for union with bounds - // further down - var joinBounds = new Rectangle(new Size(padding).multiply(2)); - - function add(point) { - bounds = bounds.include(matrix - ? matrix.transform(point) : point); - } - - function addBevelJoin(curve, t) { - var point = curve.getPoint(t), + function addCap(segment, cap, t) { + switch (cap) { + case 'round': + return addJoin(segment, cap); + case 'butt': + case 'square': + // Calculate the corner points of butt and square caps + var curve = segment.getCurve(), + point = curve.getPoint(t), normal = curve.getNormal(t).normalize(radius); + // For square caps, we need to step away from point in the + // direction of the tangent, which is the rotated normal + if (cap === 'square') + point = point.add(normal.y, -normal.x); add(point.add(normal)); add(point.subtract(normal)); + break; } - - function addJoin(segment, join) { - // When both handles are set in a segment, the join setting is - // ignored and round is always used. - if (join === 'round' || !segment._handleIn.isZero() - && !segment._handleOut.isZero()) { - bounds = bounds.unite(joinBounds.setCenter(matrix - ? matrix.transform(segment._point) : segment._point)); - } else if (join == 'bevel') { - var curve = segment.getCurve(); - addBevelJoin(curve, 0); - addBevelJoin(curve.getPrevious(), 1); - } else if (join == 'miter') { - var curve2 = segment.getCurve(), - curve1 = curve2.getPrevious(), - point = curve2.getPoint(0), - normal1 = curve1.getNormal(1).normalize(radius), - normal2 = curve2.getNormal(0).normalize(radius), - // Intersect the two lines - line1 = new Line(point.subtract(normal1), - new Point(-normal1.y, normal1.x)), - line2 = new Line(point.subtract(normal2), - new Point(-normal2.y, normal2.x)), - corner = line1.intersect(line2); - // Now measure the distance from the segment to the - // intersection, which his half of the miter distance - if (!corner || point.getDistance(corner) > miter) { - addJoin(segment, 'bevel'); - } else { - add(corner); - } - } - } - - function addCap(segment, cap, t) { - switch (cap) { - case 'round': - return addJoin(segment, cap); - case 'butt': - case 'square': - // Calculate the corner points of butt and square caps - var curve = segment.getCurve(), - point = curve.getPoint(t), - normal = curve.getNormal(t).normalize(radius); - // For square caps, we need to step away from point in the - // direction of the tangent, which is the rotated normal - if (cap === 'square') - point = point.add(normal.y, -normal.x); - add(point.add(normal)); - add(point.subtract(normal)); - break; - } - } - - for (var i = 1, l = length - (that._closed ? 0 : 1); i < l; i++) { - addJoin(segments[i], join); - } - if (that._closed) { - addJoin(segments[0], join); - } else { - addCap(segments[0], cap, 0); - addCap(segments[length - 1], cap, 1); - } - return bounds; - }, - - /** - * Returns the bounding rectangle of the item including handles. - */ - handleBounds: function(that, matrix, stroke, join) { - var coords = new Array(6), - x1 = Infinity, - x2 = -x1, - y1 = x1, - y2 = x2; - stroke = stroke / 2 || 0; // Stroke padding - join = join / 2 || 0; // Join padding, for miterLimit - for (var i = 0, l = that._segments.length; i < l; i++) { - var segment = that._segments[i]; - segment._transformCoordinates(matrix, coords, false); - for (var j = 0; j < 6; j += 2) { - // Use different padding for points or handles - var padding = j == 0 ? join : stroke, - x = coords[j], - y = coords[j + 1], - xn = x - padding, - xx = x + padding, - yn = y - padding, - yx = y + padding; - if (xn < x1) x1 = xn; - if (xx > x2) x2 = xx; - if (yn < y1) y1 = yn; - if (yx > y2) y2 = yx; - } - } - return Rectangle.create(x1, y1, x2 - x1, y2 - y1); - }, - - /** - * Returns the rough bounding rectangle of the item that is shure to - * include all of the drawing, including stroke width. - */ - roughBounds: function(that, matrix) { - // Delegate to handleBounds, but pass on radius values for stroke - // and joins. Hanlde miter joins specially, by passing the largets - // radius possible. - var width = that.getStrokeWidth(); - return get.handleBounds(that, matrix, width, - that.getStrokeJoin() == 'miter' - ? width * that.getMiterLimit() - : width); } + + for (var i = 1, l = length - (that._closed ? 0 : 1); i < l; i++) { + addJoin(segments[i], join); + } + if (that._closed) { + addJoin(segments[0], join); + } else { + addCap(segments[0], cap, 0); + addCap(segments[length - 1], cap, 1); + } + return bounds; + } + + /** + * Returns the bounding rectangle of the item including handles. + */ + function getHandleBounds(that, matrix, stroke, join) { + var coords = new Array(6), + x1 = Infinity, + x2 = -x1, + y1 = x1, + y2 = x2; + stroke = stroke / 2 || 0; // Stroke padding + join = join / 2 || 0; // Join padding, for miterLimit + for (var i = 0, l = that._segments.length; i < l; i++) { + var segment = that._segments[i]; + segment._transformCoordinates(matrix, coords, false); + for (var j = 0; j < 6; j += 2) { + // Use different padding for points or handles + var padding = j == 0 ? join : stroke, + x = coords[j], + y = coords[j + 1], + xn = x - padding, + xx = x + padding, + yn = y - padding, + yx = y + padding; + if (xn < x1) x1 = xn; + if (xx > x2) x2 = xx; + if (yn < y1) y1 = yn; + if (yx > y2) y2 = yx; + } + } + return Rectangle.create(x1, y1, x2 - x1, y2 - y1); + } + + /** + * Returns the rough bounding rectangle of the item that is shure to include + * all of the drawing, including stroke width. + */ + function getRoughBounds(that, matrix) { + // Delegate to handleBounds, but pass on radius values for stroke and + // joins. Hanlde miter joins specially, by passing the largets radius + // possible. + var width = that.getStrokeWidth(); + return getHandleBounds(that, matrix, width, + that.getStrokeJoin() == 'miter' + ? width * that.getMiterLimit() + : width); + } + + var get = { + bounds: getBounds, + strokeBounds: getStrokeBounds, + handleBounds: getHandleBounds, + roughBounds: getRoughBounds }; return {