Files
cappuccino/AppKit/CoreGraphics/CGPath.j
T

632 lines
19 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* CGPath.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CGAffineTransform.j"
@import "CGGeometry.j"
kCGPathElementMoveToPoint = 0;
kCGPathElementAddLineToPoint = 1;
kCGPathElementAddQuadCurveToPoint = 2;
kCGPathElementAddCurveToPoint = 3;
kCGPathElementCloseSubpath = 4;
kCGPathElementAddArc = 5;
kCGPathElementAddArcToPoint = 6;
/*!
@addtogroup coregraphics
@{
*/
/*!
Returns a new CGPath object.
*/
function CGPathCreateMutable()
{
return { count:0, start:CGPointMake(0, 0), current:CGPointMake(0, 0), elements:[] };
}
/*!
Returns a copy of the given path object.
*/
function CGPathCreateMutableCopy(aPath)
{
var path = CGPathCreateMutable();
CGPathAddPath(path, aPath);
return path;
}
/*!
Returns a copy of the given path object.
*/
function CGPathCreateCopy(aPath)
{
return CGPathCreateMutableCopy(aPath);
}
function CGPathRelease(aPath)
{
}
function CGPathRetain(aPath)
{
return aPath;
}
function CGPathAddArc(aPath, aTransform, x, y, aRadius, aStartAngle, anEndAngle, isClockwise)
{
if (aTransform && !CGAffineTransformIsIdentity(aTransform))
{
var center = CGPointMake(x, y),
end = CGPointMake(COS(anEndAngle), SIN(anEndAngle)),
start = CGPointMake(COS(aStartAngle), SIN(aStartAngle));
end = CGPointApplyAffineTransform(end, aTransform);
start = CGPointApplyAffineTransform(start, aTransform);
center = CGPointApplyAffineTransform(center, aTransform);
x = center.x;
y = center.y;
var oldEndAngle = anEndAngle,
oldStartAngle = aStartAngle;
anEndAngle = ATAN2(end.y - aTransform.ty, end.x - aTransform.tx);
aStartAngle = ATAN2(start.y - aTransform.ty, start.x - aTransform.tx);
// Angles that equal "modulo" 2 pi return as equal after transforming them,
// so we have to make sure to make them different again if they were different
// to start out with. It's the difference between no circle and a full circle.
if (anEndAngle === aStartAngle && oldEndAngle !== oldStartAngle)
if (oldStartAngle > oldEndAngle)
anEndAngle = anEndAngle - PI2;
else
aStartAngle = aStartAngle - PI2;
aRadius = CGSizeMake(aRadius, 0);
aRadius = CGSizeApplyAffineTransform(aRadius, aTransform);
aRadius = SQRT(aRadius.width * aRadius.width + aRadius.height * aRadius.height);
}
/*
From the Cocoa docs:
If the specified path already contains a subpath, Quartz implicitly adds a line connecting the subpaths current point to the beginning of the arc. If the path is empty, Quartz creates a new subpath with a starting point set to the starting point of the arc.
The ending point of the arc becomes the new current point of the path.
*/
var arcEndX = x + aRadius * COS(anEndAngle),
arcEndY = y + aRadius * SIN(anEndAngle),
arcStartX = x + aRadius * COS(aStartAngle),
arcStartY = y + aRadius * SIN(aStartAngle);
if (aPath.count)
{
if (aPath.current.x !== x || aPath.current.y !== y)
CGPathAddLineToPoint(aPath, aTransform, arcStartX, arcStartY);
}
else
{
var arcStartX = x + aRadius * COS(aStartAngle),
arcStartY = y + aRadius * SIN(aStartAngle);
aPath.start = CGPointMake(arcStartX, arcStartY);
}
aPath.current = CGPointMake(arcEndX, arcEndY);
aPath.elements[aPath.count++] = { type:kCGPathElementAddArc, x:x, y:y, radius:aRadius, startAngle:aStartAngle, endAngle:anEndAngle, isClockwise:isClockwise };
}
function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
{
var p1 = CGPointMake(x1, y1),
p2 = CGPointMake(x2, y2);
if (aTransform)
{
p1 = CGPointApplyAffineTransform(p1, aTransform);
p2 = CGPointApplyAffineTransform(p2, aTransform);
}
/*
From the Cocoa docs:
If the current point and the first tangent point of the arc (the starting point) are not equal, Quartz appends a straight line segment from the current point to the first tangent point.
The ending point of the arc becomes the new current point of the path.
*/
if (aPath.count)
{
if (aPath.current.x !== p1.x || aPath.current.y !== p1.y)
CGPathAddLineToPoint(aPath, aTransform, p1.x, p1.y);
}
else
aPath.start = p1;
aPath.current = p2;
aPath.elements[aPath.count++] = { type:kCGPathElementAddArcToPoint, p1x:p1.x, p1y:p1.y, p2x:p2.x, p2y:p2.y, radius:aRadius };
}
function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
{
var cp1 = CGPointMake(cp1x, cp1y),
cp2 = CGPointMake(cp2x, cp2y),
end = CGPointMake(x, y);
if (aTransform)
{
cp1 = CGPointApplyAffineTransform(cp1, aTransform);
cp2 = CGPointApplyAffineTransform(cp2, aTransform);
end = CGPointApplyAffineTransform(end, aTransform);
}
aPath.current = end;
aPath.elements[aPath.count++] = { type:kCGPathElementAddCurveToPoint, cp1x:cp1.x, cp1y:cp1.y, cp2x:cp2.x, cp2y:cp2.y, x:end.x, y:end.y };
}
function CGPathAddLines(aPath, aTransform, points, count)
{
if (count === null || count === undefined)
count = points.length;
if (!aPath || count < 1)
return;
CGPathMoveToPoint(aPath, aTransform, points[0].x, points[0].y);
for (var i = 1; i < count; ++i)
CGPathAddLineToPoint(aPath, aTransform, points[i].x, points[i].y);
}
function CGPathAddLineToPoint(aPath, aTransform, x, y)
{
var point = CGPointMake(x, y);
if (aTransform !== NULL)
point = CGPointApplyAffineTransform(point, aTransform);
aPath.elements[aPath.count++] = { type: kCGPathElementAddLineToPoint, x:point.x, y:point.y };
aPath.current = point;
}
function CGPathAddPath(aPath, aTransform, anotherPath)
{
for (var i = 0, count = anotherPath.count; i < count; ++i)
{
var element = anotherPath.elements[i];
switch (element.type)
{
case kCGPathElementAddLineToPoint:
CGPathAddLineToPoint(aPath, aTransform, element.x, element.y);
break;
case kCGPathElementAddCurveToPoint:
CGPathAddCurveToPoint(aPath, aTransform,
element.cp1x, element.cp1y,
element.cp2x, element.cp2y,
element.x, element.y);
break;
case kCGPathElementAddArc:
CGPathAddArc(aPath, aTransform, element.x, element.y,
element.radius, element.startAngle,
element.endAngle, element.isClockwise);
break;
case kCGPathElementAddArcToPoint:
CGPathAddArcToPoint(aPath, aTransform,
element.p1x, element.p1y,
element.p2x, element.p2y,
element.radius);
break;
case kCGPathElementAddQuadCurveToPoint:
CGPathAddQuadCurveToPoint(aPath, aTransform,
element.cpx, element.cpy,
element.x, element.y);
break;
case kCGPathElementMoveToPoint:
CGPathMoveToPoint(aPath, aTransform, element.x, element.y);
break;
case kCGPathElementCloseSubpath:
CGPathCloseSubpath(aPath);
break;
}
}
}
function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
{
var cp = CGPointMake(cpx, cpy),
end = CGPointMake(x, y);
if (aTransform)
{
cp = CGPointApplyAffineTransform(cp, aTransform);
end = CGPointApplyAffineTransform(end, aTransform);
}
aPath.elements[aPath.count++] = { type:kCGPathElementAddQuadCurveToPoint, cpx:cp.x, cpy:cp.y, x:end.x, y:end.y }
aPath.current = end;
}
function CGPathAddRect(aPath, aTransform, aRect)
{
CGPathAddRects(aPath, aTransform, [aRect], 1);
}
function CGPathAddRects(aPath, aTransform, rects, count)
{
var i = 0;
if (count === NULL)
var count = rects.length;
for (; i < count; ++i)
{
var rect = rects[i];
CGPathMoveToPoint(aPath, aTransform, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPathAddLineToPoint(aPath, aTransform, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGPathAddLineToPoint(aPath, aTransform, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGPathAddLineToPoint(aPath, aTransform, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGPathCloseSubpath(aPath);
}
}
function CGPathMoveToPoint(aPath, aTransform, x, y)
{
var point = CGPointMake(x, y);
if (aTransform !== NULL)
point = CGPointApplyAffineTransform(point, aTransform);
aPath.start = aPath.current = point;
// If the previous op was a move, just update that point
if (aPath.count)
{
var previous = aPath.elements[aPath.count - 1];
if (previous.type === kCGPathElementMoveToPoint)
{
previous.x = point.x;
previous.y = point.y;
return;
}
}
aPath.elements[aPath.count++] = { type:kCGPathElementMoveToPoint, x:point.x, y:point.y };
}
var KAPPA = 4.0 * ((SQRT2 - 1.0) / 3.0);
function CGPathWithEllipseInRect(aRect)
{
var path = CGPathCreateMutable();
if (CGRectGetWidth(aRect) === CGRectGetHeight(aRect))
CGPathAddArc(path, nil, CGRectGetMidX(aRect), CGRectGetMidY(aRect), CGRectGetWidth(aRect) / 2.0, 0.0, 2 * PI, YES);
else
{
var axis = CGSizeMake(CGRectGetWidth(aRect) / 2.0, CGRectGetHeight(aRect) / 2.0),
center = CGPointMake(CGRectGetMinX(aRect) + axis.width, CGRectGetMinY(aRect) + axis.height);
CGPathMoveToPoint(path, nil, center.x, center.y - axis.height);
CGPathAddCurveToPoint(path, nil, center.x + (KAPPA * axis.width), center.y - axis.height, center.x + axis.width, center.y - (KAPPA * axis.height), center.x + axis.width, center.y);
CGPathAddCurveToPoint(path, nil, center.x + axis.width, center.y + (KAPPA * axis.height), center.x + (KAPPA * axis.width), center.y + axis.height, center.x, center.y + axis.height);
CGPathAddCurveToPoint(path, nil, center.x - (KAPPA * axis.width), center.y + axis.height, center.x - axis.width, center.y + (KAPPA * axis.height), center.x - axis.width, center.y);
CGPathAddCurveToPoint(path, nil, center.x - axis.width, center.y - (KAPPA * axis.height), center.x - (KAPPA * axis.width), center.y - axis.height, center.x, center.y - axis.height);
}
CGPathCloseSubpath(path);
return path;
}
function CGPathWithRoundedRectangleInRect(aRect, xRadius, yRadius/*not currently supported*/, ne, se, sw, nw)
{
var path = CGPathCreateMutable(),
xMin = CGRectGetMinX(aRect),
xMax = CGRectGetMaxX(aRect),
yMin = CGRectGetMinY(aRect),
yMax = CGRectGetMaxY(aRect);
CGPathMoveToPoint(path, nil, xMin + xRadius, yMin);
if (ne)
{
CGPathAddLineToPoint(path, nil, xMax - xRadius, yMin);
CGPathAddCurveToPoint(path, nil, xMax - xRadius, yMin, xMax, yMin, xMax, yMin + xRadius);
}
else
CGPathAddLineToPoint(path, nil, xMax, yMin);
if (se)
{
CGPathAddLineToPoint(path, nil, xMax, yMax - xRadius);
CGPathAddCurveToPoint(path, nil, xMax, yMax - xRadius, xMax, yMax, xMax - xRadius, yMax);
}
else
CGPathAddLineToPoint(path, nil, xMax, yMax);
if (sw)
{
CGPathAddLineToPoint(path, nil, xMin + xRadius, yMax);
CGPathAddCurveToPoint(path, nil, xMin + xRadius, yMax, xMin, yMax, xMin, yMax - xRadius);
}
else
CGPathAddLineToPoint(path, nil, xMin, yMax);
if (nw)
{
CGPathAddLineToPoint(path, nil, xMin, yMin + xRadius);
CGPathAddCurveToPoint(path, nil, xMin, yMin + xRadius, xMin, yMin, xMin + xRadius, yMin);
}
else
CGPathAddLineToPoint(path, nil, xMin, yMin);
CGPathCloseSubpath(path);
return path;
}
function CGPathCloseSubpath(aPath)
{
var count = aPath.count;
// Don't bother closing this subpath if there aren't any current elements, or the last element already closed the subpath.
if (count === 0 || aPath.elements[count - 1].type === kCGPathElementCloseSubpath)
return;
// After closing, the current point is the previous path's starting point
aPath.current = CGPointCreateCopy(aPath.start);
aPath.elements[aPath.count++] = { type:kCGPathElementCloseSubpath, start:aPath.start };
}
function CGPathEqualToPath(aPath, anotherPath)
{
if (aPath === anotherPath)
return YES;
if (aPath.count !== anotherPath.count || !CGPointEqualToPoint(aPath.start, anotherPath.start) || !CGPointEqualToPoint(aPath.current, anotherPath.current))
return NO;
var i = 0,
count = aPath.count;
for (; i < count; ++i)
{
var element = aPath[i],
anotherElement = anotherPath[i];
if (element.type !== anotherElement.type)
return NO;
switch (element.type)
{
case kCGPathElementAddArc:
if (element.x !== anotherElement.x ||
element.y !== anotherElement.y ||
element.radius !== anotherElement.radius ||
element.startAngle !== anotherElement.startAngle ||
element.endAngle !== anotherElement.endAngle ||
element.isClockwise !== anotherElement.isClockwise)
{
return NO;
}
break;
case kCGPathElementAddArcToPoint:
if (element.p1x !== anotherElement.p1x ||
element.p1y !== anotherElement.p1y ||
element.p2x !== anotherElement.p2x ||
element.p2y !== anotherElement.p2y ||
element.radius !== anotherElement.radius)
{
return NO;
}
break;
case kCGPathElementAddCurveToPoint:
if (element.cp1x !== anotherElement.cp1x ||
element.cp1y !== anotherElement.cp1y ||
element.cp2x !== anotherElement.cp2x ||
element.cp2y !== anotherElement.cp2y ||
element.x !== anotherElement.x ||
element.y !== anotherElement.y)
{
return NO;
}
break;
case kCGPathElementAddLineToPoint:
case kCGPathElementMoveToPoint:
if (element.x !== anotherElement.x ||
element.y !== anotherElement.y)
{
return NO;
}
break;
case kCGPathElementAddQuadCurveToPoint:
if (element.cpx !== anotherElement.cpx ||
element.cpy !== anotherElement.cpy ||
element.x !== anotherElement.x ||
element.y !== anotherElement.y)
{
return NO;
}
break;
case kCGPathElementCloseSubpath:
if (!CGPointEqualToPoint(element.start, anotherElement.start))
return NO;
break;
}
}
return YES;
}
function CGPathGetCurrentPoint(aPath)
{
return CGPointCreateCopy(aPath.current);
}
function CGPathIsEmpty(aPath)
{
return !aPath || aPath.count === 0;
}
/*!
Calculate the smallest rectangle to contain both the path of the receiver and all control points.
*/
function CGPathGetBoundingBox(aPath)
{
if (!aPath || !aPath.count)
return CGRectMakeZero();
var ox = 0,
oy = 0,
rx = 0,
ry = 0,
movePoint = nil;
function addPoint(x, y)
{
ox = MIN(ox, x);
oy = MIN(oy, y);
rx = MAX(rx, x);
ry = MAX(ry, y);
}
for (var i = 0, count = aPath.count; i < count; ++i)
{
var element = aPath.elements[i];
// Just enclose all the control points. The curves must be inside of the control points.
// This won't work for CGPathGetBoundingBox.
switch (element.type)
{
case kCGPathElementAddLineToPoint:
if (movePoint)
{
addPoint(movePoint.x, movePoint.y);
movePoint = nil;
}
addPoint(element.x, element.y);
break;
case kCGPathElementAddCurveToPoint:
if (movePoint)
{
addPoint(movePoint.x, movePoint.y);
movePoint = nil;
}
addPoint(element.cp1x, element.cp1y);
addPoint(element.cp2x, element.cp2y);
addPoint(element.x, element.y);
break;
case kCGPathElementAddArc:
if (movePoint)
{
addPoint(movePoint.x, movePoint.y);
movePoint = nil;
}
addPoint(element.x, element.y);
break;
case kCGPathElementAddArcToPoint:
if (movePoint)
{
addPoint(movePoint.x, movePoint.y);
movePoint = nil;
}
addPoint(element.p1x, element.p1y);
addPoint(element.p2x, element.p2y);
break;
case kCGPathElementAddQuadCurveToPoint:
if (movePoint)
{
addPoint(movePoint.x, movePoint.y);
movePoint = nil;
}
addPoint(element.cpx, element.cpy);
addPoint(element.x, element.y);
break;
case kCGPathElementMoveToPoint:
movePoint = CGPointMake(element.x, element.y);
break;
case kCGPathElementCloseSubpath:
if (movePoint)
{
addPoint(movePoint.x, movePoint.y);
movePoint = nil;
}
break;
}
}
return CGRectMake(ox, oy, rx - ox, ry - oy);
}
function CGPathContainsPoint(aPath, aTransform, point, eoFill)
{
if (!aPath.count)
return NO;
if (aTransform)
point = CGPointApplyAffineTransform(point, aTransform);
var context = CGBitmapGraphicsContextCreate();
CGContextBeginPath(context);
CGContextAddPath(context, aPath);
CGContextClosePath(context);
return context.isPointInPath(point.x, point.y);
}
/*!
@}
*/