Files
cappuccino/AppKit/CoreGraphics/CGPath.j
T
Aparajita Fishman a011596231 CGContext fixes/enhancements
- NEW: Retrieve the underlying Image element from a CPImage with -image.
- NEW: You can now render any arbitrary drawing to a pattern context and use that as a fill or stroke pattern. See CGContextCreatePatternContext, CGContextSetFillPattern and CGContextSetStrokePattern. Works in all canvas-enabled browsers, including IE 9+.
- NEW: An example of using a custom rendered pattern is in Tests/Manual/PatternFillTest.
- NEW: Test if a CPImage is a single image (vs. three/nine part) with -isSingleImage.
- FIXED: With canvas, we have to track ourselves whether the context has a path or not.
- FIXED: All shapes except rects may not be added to a path with no context. If you attempt to do so, an error is logged.
- FIXED: CGPath was not setting the start and current point correctly in some cases.
- FIXED: CGContextAddPath was not moving to the path's start point at the beginning.
- FIXED: Removed superfluous CGContextClosePath commands, fixed some drawing sequences.
- FIXED: Misc. formatting.

Sorry, these changes are canvas only (including IE 9+)! I am not going to spend the time to port these fixes to VML (IE 8).
2013-02-23 13:53:20 -05:00

562 lines
17 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);
if (aPath.count)
{
if (aPath.current.x !== arcEndX || aPath.current.y !== arcEndY)
CGPathAddLineToPoint(aPath, aTransform, arcEndX, arcEndY);
}
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, clockwise: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.clockwise);
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[count - 1];
if (previous.type === kCGPathElementMoveToPoint)
{
previous.x = point.x;
previous.y = point.y;
}
}
else
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, points:[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;
if ((element.type === kCGPathElementAddArc || element.type === kCGPathElementAddArcToPoint) &&
element.radius != anotherElement.radius)
return NO;
var j = element.points.length;
while (j--)
if (!_CGPointEqualToPoint(element.points[j], anotherElement.points[j]))
return NO;
}
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);
}
/*!
@}
*/