libs-gui/Source/NSBezierPath.m

1885 lines
43 KiB
Mathematica
Raw Normal View History

/** <title>NSBezierPath.m</title>
<abstract>The NSBezierPath class</abstract>
Copyright (C) 1999 Free Software Foundation, Inc.
Author: Enrico Sersale <enrico@imago.ro>
Date: Dec 1999
Modified: Fred Kiefer <FredKiefer@gmx.de>
Date: January 2001
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111 - 1307, USA.
*/
#include <AppKit/NSBezierPath.h>
#include <AppKit/NSAffineTransform.h>
#include <AppKit/NSFont.h>
#include <AppKit/NSImage.h>
#include <AppKit/PSOperators.h>
#include <AppKit/GSFontInfo.h>
#include <math.h>
#ifndef PI
#define PI 3.1415926535897932384626433
#endif
// This magic number is 4 *(sqrt(2) -1)/3
#define KAPPA 0.5522847498
#define INVALIDATE_CACHE() [self _invalidateCache]
static void flatten(NSPoint coeff[], float flatness, NSBezierPath *path);
static Class NSBezierPath_concrete_class = nil;
static NSWindingRule default_winding_rule = NSNonZeroWindingRule;
static float default_line_width = 1.0;
static float default_flatness = 1.0;
static NSLineJoinStyle default_line_join_style = NSMiterLineJoinStyle;
static NSLineCapStyle default_line_cap_style = NSButtLineCapStyle;
static float default_miter_limit = 10.0;
@interface NSBezierPath (PrivateMethods)
- (void)_invalidateCache;
- (void)_recalculateBounds;
@end
@class GSBezierPath;
@implementation NSBezierPath
+ (void)initialize
{
if(self == [NSBezierPath class])
NSBezierPath_concrete_class = [GSBezierPath class];
}
+ (void)_setConcreteClass:(Class)c
{
NSBezierPath_concrete_class = c;
}
+ (Class)_concreteClass
{
return NSBezierPath_concrete_class;
}
//
// Creating common paths
//
+ (id) allocWithZone: (NSZone*)z
{
if (self != NSBezierPath_concrete_class)
{
return [NSBezierPath_concrete_class allocWithZone: z];
}
else
{
return NSAllocateObject (self, 0, z);
}
}
+ (id)bezierPath
{
return AUTORELEASE ([[NSBezierPath_concrete_class alloc] init]);
}
+ (NSBezierPath *)bezierPathWithRect: (NSRect)aRect
{
NSBezierPath *path;
path = [self bezierPath];
[path appendBezierPathWithRect: aRect];
return path;
}
+ (NSBezierPath *)bezierPathWithOvalInRect: (NSRect)aRect
{
NSBezierPath *path;
path = [self bezierPath];
[path appendBezierPathWithOvalInRect: aRect];
return path;
}
//
// Immediate mode drawing of common paths
//
+ (void)fillRect: (NSRect)aRect
{
PSrectfill(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect));
}
+ (void)strokeRect: (NSRect)aRect
{
PSrectstroke(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect));
}
+ (void)clipRect: (NSRect)aRect
{
PSrectclip(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect));
}
+ (void)strokeLineFromPoint: (NSPoint)point1 toPoint: (NSPoint)point2
{
NSBezierPath *path = [[self alloc] init];
[path moveToPoint: point1];
[path lineToPoint: point2];
[path stroke];
RELEASE(path);
}
+ (void)drawPackedGlyphs: (const char *)packedGlyphs atPoint: (NSPoint)aPoint
{
NSBezierPath *path = [[self alloc] init];
[path moveToPoint: aPoint];
[path appendBezierPathWithPackedGlyphs: packedGlyphs];
[path stroke];
RELEASE(path);
}
//
// Default path rendering parameters
//
+ (void)setDefaultMiterLimit:(float)limit
{
default_miter_limit = limit;
// Do we need this?
PSsetmiterlimit(limit);
}
+ (float)defaultMiterLimit
{
return default_miter_limit;
}
+ (void)setDefaultFlatness:(float)flatness
{
default_flatness = flatness;
PSsetflat(flatness);
}
+ (float)defaultFlatness
{
return default_flatness;
}
+ (void)setDefaultWindingRule:(NSWindingRule)windingRule
{
default_winding_rule = windingRule;
}
+ (NSWindingRule)defaultWindingRule
{
return default_winding_rule;
}
+ (void)setDefaultLineCapStyle:(NSLineCapStyle)lineCapStyle
{
default_line_cap_style = lineCapStyle;
PSsetlinecap(lineCapStyle);
}
+ (NSLineCapStyle)defaultLineCapStyle
{
return default_line_cap_style;
}
+ (void)setDefaultLineJoinStyle:(NSLineJoinStyle)lineJoinStyle
{
default_line_join_style = lineJoinStyle;
PSsetlinejoin(lineJoinStyle);
}
+ (NSLineJoinStyle)defaultLineJoinStyle
{
return default_line_join_style;
}
+ (void)setDefaultLineWidth:(float)lineWidth
{
default_line_width = lineWidth;
PSsetlinewidth(lineWidth);
}
+ (float)defaultLineWidth
{
return default_line_width;
}
- (id) init
{
[super init];
// Those values come from the default.
[self setLineWidth: default_line_width];
[self setFlatness: default_flatness];
[self setLineCapStyle: default_line_cap_style];
[self setLineJoinStyle: default_line_join_style];
[self setMiterLimit: default_miter_limit];
[self setWindingRule: default_winding_rule];
// Set by allocation
//_bounds = NSZeroRect;
//_controlPointBounds = NSZeroRect;
//_cachesBezierPath = NO;
//_cacheImage = nil;
//_dash_count = 0;
//_dash_phase = 0;
//_dash_pattern = NULL;
return self;
}
- (void) dealloc
{
if(_cacheImage != nil)
RELEASE(_cacheImage);
if (_dash_pattern != NULL)
NSZoneFree([self zone], _dash_pattern);
[super dealloc];
}
//
// Path construction
//
- (void)moveToPoint:(NSPoint)aPoint
{
[self subclassResponsibility:_cmd];
}
- (void)lineToPoint:(NSPoint)aPoint
{
[self subclassResponsibility:_cmd];
}
- (void)curveToPoint:(NSPoint)aPoint
controlPoint1:(NSPoint)controlPoint1
controlPoint2:(NSPoint)controlPoint2
{
[self subclassResponsibility:_cmd];
}
- (void)closePath
{
[self subclassResponsibility:_cmd];
}
- (void)removeAllPoints
{
[self subclassResponsibility:_cmd];
}
//
// Relative path construction
//
- (void)relativeMoveToPoint:(NSPoint)aPoint
{
NSPoint p = [self currentPoint];
p.x = p.x + aPoint.x;
p.y = p.y + aPoint.y;
[self moveToPoint: p];
}
- (void)relativeLineToPoint:(NSPoint)aPoint
{
NSPoint p = [self currentPoint];
p.x = p.x + aPoint.x;
p.y = p.y + aPoint.y;
[self lineToPoint: p];
}
- (void)relativeCurveToPoint:(NSPoint)aPoint
controlPoint1:(NSPoint)controlPoint1
controlPoint2:(NSPoint)controlPoint2
{
NSPoint p = [self currentPoint];
aPoint.x = p.x + aPoint.x;
aPoint.y = p.y + aPoint.y;
controlPoint1.x = p.x + controlPoint1.x;
controlPoint1.y = p.y + controlPoint1.y;
controlPoint2.x = p.x + controlPoint2.x;
controlPoint2.y = p.y + controlPoint2.y;
[self curveToPoint: aPoint
controlPoint1: controlPoint1
controlPoint2: controlPoint2];
}
//
// Path rendering parameters
//
- (float)lineWidth
{
return _lineWidth;
}
- (void)setLineWidth:(float)lineWidth
{
_lineWidth = lineWidth;
}
- (NSLineCapStyle)lineCapStyle
{
return _lineCapStyle;
}
- (void)setLineCapStyle:(NSLineCapStyle)lineCapStyle
{
_lineCapStyle = lineCapStyle;
}
- (NSLineJoinStyle)lineJoinStyle
{
return _lineJoinStyle;
}
- (void)setLineJoinStyle:(NSLineJoinStyle)lineJoinStyle
{
_lineJoinStyle = lineJoinStyle;
}
- (NSWindingRule)windingRule
{
return _windingRule;
}
- (void)setWindingRule:(NSWindingRule)windingRule
{
_windingRule = windingRule;
}
- (void)setFlatness:(float)flatness
{
_flatness = flatness;
}
- (float)flatness
{
return _flatness;
}
- (void)setMiterLimit:(float)limit
{
_miterLimit = limit;
}
- (float)miterLimit
{
return _miterLimit;
}
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase
{
// FIXME: How big is the pattern array?
// We assume that this value is in count!
if (count != NULL)
{
if (*count < _dash_count)
{
*count = _dash_count;
return;
}
*count = _dash_count;
}
if (phase != NULL)
*phase = _dash_phase;
memcpy(pattern, _dash_pattern, _dash_count * sizeof(float));
}
- (void)setLineDash:(const float *)pattern count:(int)count phase:(float)phase
{
NSZone *myZone = [self zone];
if ((pattern == NULL) || (count == 0))
{
if (_dash_pattern != NULL)
{
NSZoneFree(myZone, _dash_pattern);
_dash_pattern = NULL;
}
_dash_count = 0;
_dash_phase = 0.0;
return;
}
if (_dash_pattern == NULL)
_dash_pattern = NSZoneMalloc(myZone, count * sizeof(float));
else
NSZoneRealloc(myZone, _dash_pattern, count * sizeof(float));
_dash_count = count;
_dash_phase = phase;
memcpy(_dash_pattern, pattern, _dash_count * sizeof(float));
}
//
// Path operations
//
- (void)stroke
{
NSGraphicsContext *ctxt = GSCurrentContext();
if(_cachesBezierPath)
{
NSRect bounds = [self bounds];
NSPoint origin = bounds.origin;
// FIXME: I don't see how this should work with color changes
if(_cacheImage == nil)
{
_cacheImage = [[NSImage alloc] initWithSize: bounds.size];
[_cacheImage lockFocus];
DPStranslate(ctxt, -origin.x, -origin.y);
[ctxt GSSendBezierPath: self];
DPSstroke(ctxt);
[_cacheImage unlockFocus];
}
[_cacheImage compositeToPoint: origin operation: NSCompositeCopy];
}
else
{
[ctxt GSSendBezierPath: self];
DPSstroke(ctxt);
}
}
- (void)fill
{
NSGraphicsContext *ctxt = GSCurrentContext();
if(_cachesBezierPath)
{
NSRect bounds = [self bounds];
NSPoint origin = bounds.origin;
// FIXME: I don't see how this should work with color changes
if(_cacheImage == nil)
{
_cacheImage = [[NSImage alloc] initWithSize: bounds.size];
[_cacheImage lockFocus];
DPStranslate(ctxt, -origin.x, -origin.y);
[ctxt GSSendBezierPath: self];
if([self windingRule] == NSNonZeroWindingRule)
DPSfill(ctxt);
else
DPSeofill(ctxt);
[_cacheImage unlockFocus];
}
[_cacheImage compositeToPoint: origin operation: NSCompositeCopy];
}
else
{
[ctxt GSSendBezierPath: self];
if([self windingRule] == NSNonZeroWindingRule)
DPSfill(ctxt);
else
DPSeofill(ctxt);
}
}
- (void)addClip
{
NSGraphicsContext *ctxt = GSCurrentContext();
[ctxt GSSendBezierPath: self];
if([self windingRule] == NSNonZeroWindingRule)
DPSclip(ctxt);
else
DPSeoclip(ctxt);
}
- (void)setClip
{
NSGraphicsContext *ctxt = GSCurrentContext();
DPSinitclip(ctxt);
[ctxt GSSendBezierPath: self];
if([self windingRule] == NSNonZeroWindingRule)
DPSclip(ctxt);
else
DPSeoclip(ctxt);
}
//
// Path modifications.
//
- (NSBezierPath *)bezierPathByFlatteningPath
{
NSBezierPath *path = [isa bezierPath];
NSBezierPathElement type;
NSPoint pts[3];
NSPoint coeff[4];
NSPoint p, last_p;
int i, count;
BOOL first = YES;
count = [self elementCount];
for(i = 0; i < count; i++)
{
type = [self elementAtIndex: i associatedPoints: pts];
switch(type)
{
case NSMoveToBezierPathElement:
[path moveToPoint: pts[0]];
last_p = p = pts[0];
first = NO;
break;
case NSLineToBezierPathElement:
[path lineToPoint: pts[0]];
p = pts[0];
if (first)
{
last_p = pts[0];
first = NO;
}
break;
case NSCurveToBezierPathElement:
coeff[0] = p;
coeff[1] = pts[0];
coeff[2] = pts[1];
coeff[3] = pts[2];
flatten(coeff, [self flatness], path);
p = pts[2];
if (first)
{
last_p = pts[2];
first = NO;
}
break;
case NSClosePathBezierPathElement:
[path closePath];
p = last_p;
break;
default:
break;
}
}
return path;
}
- (NSBezierPath *) bezierPathByReversingPath
{
NSBezierPath *path = [isa bezierPath];
NSBezierPathElement type, last_type;
NSPoint pts[3];
NSPoint p, cp1, cp2;
int i, j, count;
BOOL closed = NO;
last_type = NSMoveToBezierPathElement;
count = [self elementCount];
for(i = count - 1; i >= 0; i--)
{
type = [self elementAtIndex: i associatedPoints: pts];
switch(type)
{
case NSMoveToBezierPathElement:
p = pts[0];
break;
case NSLineToBezierPathElement:
p = pts[0];
break;
case NSCurveToBezierPathElement:
cp1 = pts[0];
cp2 = pts[1];
p = pts[2];
break;
case NSClosePathBezierPathElement:
// find the first point of segment
for (j = i - 1; j >= 0; j--)
{
type = [self elementAtIndex: i associatedPoints: pts];
if (type == NSMoveToBezierPathElement)
{
p = pts[0];
break;
}
}
// FIXME: What to do if we don't find a move element?
break;
default:
break;
}
switch(last_type)
{
case NSMoveToBezierPathElement:
if (closed)
{
[path closePath];
closed = NO;
}
[path moveToPoint: p];
break;
case NSLineToBezierPathElement:
[path lineToPoint: p];
break;
case NSCurveToBezierPathElement:
[path curveToPoint: p
controlPoint1: cp2
controlPoint2: cp1];
break;
case NSClosePathBezierPathElement:
closed = YES;
break;
default:
break;
}
last_type = type;
}
if (closed)
[path closePath];
return self;
}
//
// Applying transformations.
//
- (void) transformUsingAffineTransform: (NSAffineTransform *)transform
{
NSBezierPathElement type;
NSPoint pts[3];
int i, count;
count = [self elementCount];
for(i = 0; i < count; i++)
{
type = [self elementAtIndex: i associatedPoints: pts];
switch(type)
{
case NSMoveToBezierPathElement:
case NSLineToBezierPathElement:
pts[0] = [transform transformPoint: pts[0]];
[self setAssociatedPoints: pts atIndex: i];
break;
case NSCurveToBezierPathElement:
pts[0] = [transform transformPoint: pts[0]];
pts[1] = [transform transformPoint: pts[1]];
pts[2] = [transform transformPoint: pts[2]];
[self setAssociatedPoints: pts atIndex: i];
break;
case NSClosePathBezierPathElement:
break;
default:
break;
}
}
INVALIDATE_CACHE();
}
//
// Path info
//
- (BOOL) isEmpty
{
return ([self elementCount] == 0);
}
- (NSPoint) currentPoint
{
NSBezierPathElement type;
NSPoint points[3];
int i, count;
count = [self elementCount];
if (!count)
[NSException raise: NSGenericException
format: @"No current Point in NSBezierPath"];
type = [self elementAtIndex: count - 1 associatedPoints: points];
switch(type)
{
case NSMoveToBezierPathElement:
case NSLineToBezierPathElement:
return points[0];
break;
case NSCurveToBezierPathElement:
return points[2];
break;
case NSClosePathBezierPathElement:
// We have to find the last move element and take its point
for (i = count - 2; i >= 0; i--)
{
type = [self elementAtIndex: i associatedPoints: points];
if (type == NSMoveToBezierPathElement)
return points[0];
}
break;
default:
break;
}
return NSZeroPoint;
}
- (NSRect) controlPointBounds
{
if (_shouldRecalculateBounds)
[self _recalculateBounds];
return _controlPointBounds;
}
- (NSRect) bounds
{
if (_shouldRecalculateBounds)
[self _recalculateBounds];
return _bounds;
}
//
// Elements
//
- (int) elementCount
{
[self subclassResponsibility:_cmd];
return 0;
}
- (NSBezierPathElement) elementAtIndex: (int)index
associatedPoints: (NSPoint *)points
{
[self subclassResponsibility:_cmd];
return 0;
}
- (NSBezierPathElement) elementAtIndex: (int)index
{
return [self elementAtIndex: index associatedPoints: NULL];
}
- (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index
{
[self subclassResponsibility:_cmd];
}
//
// Appending common paths
//
- (void) appendBezierPath: (NSBezierPath *)aPath
{
NSBezierPathElement type;
NSPoint points[3];
int i, count;
count = [aPath elementCount];
for (i = 0; i < count; i++)
{
type = [aPath elementAtIndex: i associatedPoints: points];
switch(type)
{
case NSMoveToBezierPathElement:
[self moveToPoint: points[0]];
break;
case NSLineToBezierPathElement:
[self lineToPoint: points[0]];
break;
case NSCurveToBezierPathElement:
[self curveToPoint: points[2]
controlPoint1: points[0]
controlPoint2: points[1]];
break;
case NSClosePathBezierPathElement:
[self closePath];
break;
default:
break;
}
}
}
- (void)appendBezierPathWithRect:(NSRect)aRect
{
NSPoint p;
[self moveToPoint: aRect.origin];
p.x = aRect.origin.x + aRect.size.width;
p.y = aRect.origin.y;
[self lineToPoint: p];
p.x = aRect.origin.x + aRect.size.width;
p.y = aRect.origin.y + aRect.size.height;
[self lineToPoint: p];
p.x = aRect.origin.x;
p.y = aRect.origin.y + aRect.size.height;
[self lineToPoint: p];
[self closePath];
}
- (void)appendBezierPathWithPoints:(NSPoint *)points count:(int)count
{
int i;
if (!count)
return;
if ([self isEmpty])
{
[self moveToPoint: points[0]];
}
else
{
[self lineToPoint: points[0]];
}
for (i = 1; i < count; i++)
{
[self lineToPoint: points[i]];
}
}
- (void) appendBezierPathWithOvalInRect: (NSRect)rect
{
NSPoint p, p1, p2;
double originx = rect.origin.x;
double originy = rect.origin.y;
double width = rect.size.width;
double height = rect.size.height;
double hdiff = width / 2 * KAPPA;
double vdiff = height / 2 * KAPPA;
p = NSMakePoint(originx + width / 2, originy + height);
[self moveToPoint: p];
p = NSMakePoint(originx, originy + height / 2);
p1 = NSMakePoint(originx + width / 2 - hdiff, originy + height);
p2 = NSMakePoint(originx, originy + height / 2 + vdiff);
[self curveToPoint: p controlPoint1: p1 controlPoint2: p2];
p = NSMakePoint(originx + width / 2, originy);
p1 = NSMakePoint(originx, originy + height / 2 - vdiff);
p2 = NSMakePoint(originx + width / 2 - hdiff, originy);
[self curveToPoint: p controlPoint1: p1 controlPoint2: p2];
p = NSMakePoint(originx + width, originy + height / 2);
p1 = NSMakePoint(originx + width / 2 + hdiff, originy);
p2 = NSMakePoint(originx + width, originy + height / 2 - vdiff);
[self curveToPoint: p controlPoint1: p1 controlPoint2: p2];
p = NSMakePoint(originx + width / 2, originy + height);
p1 = NSMakePoint(originx + width, originy + height / 2 + vdiff);
p2 = NSMakePoint(originx + width / 2 + hdiff, originy + height);
[self curveToPoint: p controlPoint1: p1 controlPoint2: p2];
}
/* startAngle and endAngle are in degrees, counterclockwise, from the
x axis */
- (void) appendBezierPathWithArcWithCenter: (NSPoint)center
radius: (float)radius
startAngle: (float)startAngle
endAngle: (float)endAngle
clockwise: (BOOL)clockwise
{
float startAngle_rad, endAngle_rad, diff;
NSPoint p0, p1, p2, p3;
/* We use the Postscript prescription for managing the angles and
drawing the arc. See the documentation for `arc' and `arcn' in
the Postscript Reference. */
if (clockwise)
{
/* This modification of the angles is the postscript
prescription. */
while (startAngle < endAngle)
endAngle -= 360;
/* This is used when we draw a clockwise quarter of
circumference. By adding diff at the starting angle of the
quarter, we get the ending angle. diff is negative because
we draw clockwise. */
diff = - PI / 2;
}
else
{
/* This modification of the angles is the postscript
prescription. */
while (endAngle < startAngle)
endAngle += 360;
/* This is used when we draw a counterclockwise quarter of
circumference. By adding diff at the starting angle of the
quarter, we get the ending angle. diff is positive because
we draw counterclockwise. */
diff = PI / 2;
}
/* Convert the angles to radians */
startAngle_rad = PI * startAngle / 180;
endAngle_rad = PI * endAngle / 180;
/* Start point */
p0 = NSMakePoint (center.x + radius * cos (startAngle_rad),
center.y + radius * sin (startAngle_rad));
if ([self elementCount] == 0)
{
[self moveToPoint: p0];
}
else
{
NSPoint ps = [self currentPoint];
if (p0.x != ps.x || p0.y != ps.y)
{
[self lineToPoint: p0];
}
}
while ((clockwise) ? (startAngle_rad > endAngle_rad)
: (startAngle_rad < endAngle_rad))
{
/* Add a quarter circle */
if ((clockwise) ? (startAngle_rad + diff >= endAngle_rad)
: (startAngle_rad + diff <= endAngle_rad))
{
float sin_start = sin (startAngle_rad);
float cos_start = cos (startAngle_rad);
float sign = (clockwise) ? -1.0 : 1.0;
p1 = NSMakePoint (center.x
+ radius * (cos_start - KAPPA * sin_start * sign),
center.y
+ radius * (sin_start + KAPPA * cos_start * sign));
p2 = NSMakePoint (center.x
+ radius * (-sin_start * sign + KAPPA * cos_start),
center.y
+ radius * (cos_start * sign + KAPPA * sin_start));
p3 = NSMakePoint (center.x + radius * (-sin_start * sign),
center.y + radius * cos_start * sign);
[self curveToPoint: p3 controlPoint1: p1 controlPoint2: p2];
startAngle_rad += diff;
}
else
{
/* Add the missing bit
* We require that the arc be less than a semicircle.
* The arc may go either clockwise or counterclockwise.
* The approximation is a very simple one: a single curve
* whose middle two control points are a fraction F of the way
* to the intersection of the tangents, where
* F = (4/3) / (1 + sqrt (1 + (d / r)^2))
* where r is the radius and d is the distance from either tangent
* point to the intersection of the tangents. This produces
* a curve whose center point, as well as its ends, lies on
* the desired arc.
*/
NSPoint ps = [self currentPoint];
/* tangent is the tangent of half the angle */
float tangent = tan ((endAngle_rad - startAngle_rad) / 2);
/* trad is the distance from either tangent point to the
intersection of the tangents */
float trad = radius * tangent;
/* pt is the intersection of the tangents */
NSPoint pt = NSMakePoint (ps.x - trad * sin (startAngle_rad),
ps.y + trad * cos (startAngle_rad));
/* This is F - in this expression we need to compute
(trad/radius)^2, which is simply tangent^2 */
float f = (4.0 / 3.0) / (1.0 + sqrt (1.0 + (tangent * tangent)));
p1 = NSMakePoint (ps.x + (pt.x - ps.x) * f, ps.y + (pt.y - ps.y) * f);
p3 = NSMakePoint(center.x + radius * cos (endAngle_rad),
center.y + radius * sin (endAngle_rad));
p2 = NSMakePoint (p3.x + (pt.x - p3.x) * f, p3.y + (pt.y - p3.y) * f);
[self curveToPoint: p3 controlPoint1: p1 controlPoint2: p2];
break;
}
}
}
- (void) appendBezierPathWithArcWithCenter: (NSPoint)center
radius: (float)radius
startAngle: (float)startAngle
endAngle: (float)endAngle
{
[self appendBezierPathWithArcWithCenter: center radius: radius
startAngle: startAngle endAngle: endAngle clockwise: NO];
}
- (void) appendBezierPathWithArcFromPoint: (NSPoint)point1
toPoint: (NSPoint)point2
radius: (float)radius
{
float x1, y1;
float dx1, dy1, dx2, dy2;
float l, a1, a2;
NSPoint p;
p = [self currentPoint];
x1 = point1.x;
y1 = point1.y;
dx1 = p.x - x1;
dy1 = p.y - y1;
l= dx1*dx1 + dy1*dy1;
if (l <= 0)
{
[self lineToPoint: point1];
return;
}
l = 1/sqrt(l);
dx1 *= l;
dy1 *= l;
dx2 = point2.x - x1;
dy2 = point2.y - y1;
l = dx2*dx2 + dy2*dy2;
if (l <= 0)
{
[self lineToPoint: point1];
return;
}
l = 1/sqrt(l);
dx2 *= l;
dy2 *= l;
l = dx1*dx2 + dy1*dy2;
if (l < -0.999)
{
[self lineToPoint: point1];
return;
}
l = radius/sin(acos(l));
p.x = x1 + (dx1 + dx2)*l;
p.y = y1 + (dy1 + dy2)*l;
if (dx1 < -1)
a1 = 180;
else if (dx1 > 1)
a1 = 0;
else
a1 = acos(dx1)/PI*180;
if (dy1 < 0)
{
a1 = -a1;
}
if (dx2 < -1)
a2 = 180;
else if (dx2 > 1)
a2 = 0;
else
a2 = acos(dx2)/PI*180;
if (dy2 < 0)
{
a2 = -a2;
}
l = dx1*dy2 - dx2*dy1;
if (l < 0)
{
a2 = a2 - 90;
a1 = a1 + 90;
[self appendBezierPathWithArcWithCenter: p
radius: radius
startAngle: a1
endAngle: a2
clockwise: NO];
}
else
{
a2 = a2 + 90;
a1 = a1 - 90;
[self appendBezierPathWithArcWithCenter: p
radius: radius
startAngle: a1
endAngle: a2
clockwise: YES];
}
}
- (void)appendBezierPathWithGlyph:(NSGlyph)glyph inFont:(NSFont *)font
{
[[font fontInfo] appendBezierPathWithGlyphs: &glyph
count: 1
toBezierPath: self];
}
- (void)appendBezierPathWithGlyphs:(NSGlyph *)glyphs
count:(int)count
inFont:(NSFont *)font
{
[[font fontInfo] appendBezierPathWithGlyphs: glyphs
count: count
toBezierPath: self];
}
- (void)appendBezierPathWithPackedGlyphs:(const char *)packedGlyphs
{
// TODO
}
//
// Hit detection
//
- (BOOL)containsPoint:(NSPoint)point
{
[self subclassResponsibility:_cmd];
return NO;
}
//
// Caching paths
//
//
// Caching
//
- (BOOL)cachesBezierPath
{
return _cachesBezierPath;
}
- (void)setCachesBezierPath:(BOOL)flag
{
_cachesBezierPath = flag;
if(!flag)
INVALIDATE_CACHE();
}
//
// NSCoding protocol
//
- (void)encodeWithCoder:(NSCoder *)aCoder
{
NSBezierPathElement type;
NSPoint pts[3];
int i, count;
float f;
f = [self lineWidth];
[aCoder encodeValueOfObjCType: @encode(float) at: &f];
i = [self lineCapStyle];
[aCoder encodeValueOfObjCType: @encode(int) at: &i];
i = [self lineJoinStyle];
[aCoder encodeValueOfObjCType: @encode(int) at: &i];
i = [self windingRule];
[aCoder encodeValueOfObjCType: @encode(int) at: &i];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_cachesBezierPath];
count = [self elementCount];
[aCoder encodeValueOfObjCType: @encode(int) at: &count];
for(i = 0; i < count; i++)
{
type = [self elementAtIndex: i associatedPoints: pts];
[aCoder encodeValueOfObjCType: @encode(NSBezierPathElement) at: &type];
switch(type)
{
case NSMoveToBezierPathElement:
case NSLineToBezierPathElement:
[aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
break;
case NSCurveToBezierPathElement:
[aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
[aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[1]];
[aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[2]];
break;
case NSClosePathBezierPathElement:
break;
default:
break;
}
}
}
- (id)initWithCoder:(NSCoder *)aCoder
{
NSBezierPathElement type;
NSPoint pts[3];
int i, count;
float f;
// We have to init the place to store the elements
[self init];
[aCoder decodeValueOfObjCType: @encode(float) at: &f];
[self setLineWidth: f];
[aCoder decodeValueOfObjCType: @encode(int) at: &i];
[self setLineCapStyle: i];
[aCoder decodeValueOfObjCType: @encode(int) at: &i];
[self setLineJoinStyle: i];
[aCoder decodeValueOfObjCType: @encode(int) at: &i];
[self setWindingRule: i];
[aCoder decodeValueOfObjCType: @encode(BOOL) at: &_cachesBezierPath];
_cacheImage = nil;
_shouldRecalculateBounds = YES;
[aCoder decodeValueOfObjCType: @encode(int) at: &count];
for(i = 0; i < count; i++)
{
[aCoder decodeValueOfObjCType: @encode(NSBezierPathElement) at: &type];
switch(type)
{
case NSMoveToBezierPathElement:
[aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
[self moveToPoint: pts[0]];
case NSLineToBezierPathElement:
[aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
[self lineToPoint: pts[0]];
break;
case NSCurveToBezierPathElement:
[aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
[aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[1]];
[aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[2]];
[self curveToPoint: pts[0] controlPoint1: pts[1] controlPoint2: pts[2]];
break;
case NSClosePathBezierPathElement:
[self closePath];
break;
default:
break;
}
}
return self;
}
//
// NSCopying Protocol
//
- (id)copyWithZone:(NSZone *)zone
{
NSBezierPath *path = (NSBezierPath*)NSCopyObject (self, 0, zone);
if(_cachesBezierPath && _cacheImage)
path->_cacheImage = [_cacheImage copy];
if (_dash_pattern != NULL)
{
float *pattern = NSZoneMalloc(zone, _dash_count * sizeof(float));
memcpy(pattern, _dash_pattern, _dash_count * sizeof(float));
_dash_pattern = pattern;
}
return path;
}
@end
@implementation NSBezierPath (PrivateMethods)
- (void) _invalidateCache
{
_shouldRecalculateBounds = YES;
DESTROY(_cacheImage);
}
- (void)_recalculateBounds
{
NSBezierPathElement type;
NSPoint p, last_p;
NSPoint pts[3];
// This will compute three intermediate points per curve
double x, y, t, k = 0.25;
float maxx, minx, maxy, miny;
float cpmaxx, cpminx, cpmaxy, cpminy;
int i, count;
BOOL first = YES;
count = [self elementCount];
if(!count)
{
_bounds = NSZeroRect;
_controlPointBounds = NSZeroRect;
return;
}
// Some big starting values
maxx = maxy = cpmaxx = cpmaxy = -1E9;
minx = miny = cpminx = cpminy = 1E9;
for(i = 0; i < count; i++)
{
type = [self elementAtIndex: i associatedPoints: pts];
switch(type)
{
case NSMoveToBezierPathElement:
last_p = pts[0];
// NO BREAK
case NSLineToBezierPathElement:
if (first)
{
maxx = minx = cpmaxx = cpminx = pts[0].x;
maxy = miny = cpmaxy = cpminy = pts[0].y;
last_p = pts[0];
first = NO;
}
else
{
if(pts[0].x > maxx) maxx = pts[0].x;
if(pts[0].x < minx) minx = pts[0].x;
if(pts[0].y > maxy) maxy = pts[0].y;
if(pts[0].y < miny) miny = pts[0].y;
if(pts[0].x > cpmaxx) cpmaxx = pts[0].x;
if(pts[0].x < cpminx) cpminx = pts[0].x;
if(pts[0].y > cpmaxy) cpmaxy = pts[0].y;
if(pts[0].y < cpminy) cpminy = pts[0].y;
}
p = pts[0];
break;
case NSCurveToBezierPathElement:
if (first)
{
maxx = minx = cpmaxx = cpminx = pts[0].x;
maxy = miny = cpmaxy = cpminy = pts[0].y;
p = last_p = pts[0];
first = NO;
}
if(pts[2].x > maxx) maxx = pts[2].x;
if(pts[2].x < minx) minx = pts[2].x;
if(pts[2].y > maxy) maxy = pts[2].y;
if(pts[2].y < miny) miny = pts[2].y;
if(pts[0].x > cpmaxx) cpmaxx = pts[0].x;
if(pts[0].x < cpminx) cpminx = pts[0].x;
if(pts[0].y > cpmaxy) cpmaxy = pts[0].y;
if(pts[0].y < cpminy) cpminy = pts[0].y;
if(pts[1].x > cpmaxx) cpmaxx = pts[1].x;
if(pts[1].x < cpminx) cpminx = pts[1].x;
if(pts[1].y > cpmaxy) cpmaxy = pts[1].y;
if(pts[1].y < cpminy) cpminy = pts[1].y;
if(pts[2].x > cpmaxx) cpmaxx = pts[2].x;
if(pts[2].x < cpminx) cpminx = pts[2].x;
if(pts[2].y > cpmaxy) cpmaxy = pts[2].y;
if(pts[2].y < cpminy) cpminy = pts[2].y;
for(t = k; t <= 1+k; t += k)
{
x = (p.x+t*(-p.x*3+t*(3*p.x-p.x*t)))+
t*(3*pts[0].x+t*(-6*pts[0].x+pts[0].x*3*t))+
t*t*(pts[1].x*3-pts[1].x*3*t)+pts[2].x*t*t*t;
y = (p.y+t*(-p.y*3+t*(3*p.y-p.y*t)))+
t*(3*pts[0].y+t*(-6*pts[0].y+pts[0].y*3*t))+
t*t*(pts[1].y*3-pts[1].y*3*t)+pts[2].y*t*t*t;
if(x > cpmaxx) cpmaxx = x;
if(x < cpminx) cpminx = x;
if(y > cpmaxy) cpmaxy = y;
if(y < cpminy) cpminy = y;
}
p = pts[2];
break;
case NSClosePathBezierPathElement:
// This does not add to the bounds, but changes the current point
p = last_p;
break;
default:
break;
}
}
_bounds = NSMakeRect(minx, miny, maxx - minx, maxy - miny);
_controlPointBounds = NSMakeRect(cpminx, cpminy, cpmaxx - cpminx, cpmaxy - cpminy);
_shouldRecalculateBounds = NO;
}
@end
typedef struct _PathElement
{
NSBezierPathElement type;
NSPoint points[3];
} PathElement;
//#define GSUNION_TYPES GSUNION_OBJ
#define GSI_ARRAY_TYPES 0
#define GSI_ARRAY_TYPE PathElement
#define GSI_ARRAY_NO_RETAIN
#define GSI_ARRAY_NO_RELEASE
#ifdef GSIArray
#undef GSIArray
#endif
#include <base/GSIArray.h>
@interface GSBezierPath : NSBezierPath
{
GSIArray pathElements;
BOOL flat;
}
@end
@implementation GSBezierPath
- (id)init
{
NSZone *zone;
self = [super init];
zone = GSObjCZone(self);
pathElements = NSZoneMalloc(zone, sizeof(GSIArray_t));
GSIArrayInitWithZoneAndCapacity(pathElements, zone, 8);
flat = YES;
return self;
}
- (void)dealloc
{
GSIArrayEmpty(pathElements);
NSZoneFree(GSObjCZone(self), pathElements);
[super dealloc];
}
//
// Path construction
//
- (void)moveToPoint:(NSPoint)aPoint
{
PathElement elem;
elem.type = NSMoveToBezierPathElement;
elem.points[0] = aPoint;
GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
INVALIDATE_CACHE();
}
- (void)lineToPoint:(NSPoint)aPoint
{
PathElement elem;
elem.type = NSLineToBezierPathElement;
elem.points[0] = aPoint;
GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
INVALIDATE_CACHE();
}
- (void) curveToPoint: (NSPoint)aPoint
controlPoint1: (NSPoint)controlPoint1
controlPoint2: (NSPoint)controlPoint2
{
PathElement elem;
elem.type = NSCurveToBezierPathElement;
elem.points[0] = controlPoint1;
elem.points[1] = controlPoint2;
elem.points[2] = aPoint;
GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
flat = NO;
INVALIDATE_CACHE();
}
- (void)closePath
{
PathElement elem;
elem.type = NSClosePathBezierPathElement;
GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
INVALIDATE_CACHE();
}
- (void)removeAllPoints
{
GSIArrayRemoveAllItems(pathElements);
INVALIDATE_CACHE();
}
//
// Elements
//
- (int)elementCount
{
return GSIArrayCount(pathElements);
}
- (NSBezierPathElement)elementAtIndex:(int)index
associatedPoints:(NSPoint *)points
{
PathElement elm = GSIArrayItemAtIndex(pathElements, index).ext;
NSBezierPathElement type = elm.type;
if (points != NULL)
{
if(type == NSMoveToBezierPathElement || type == NSLineToBezierPathElement)
{
points[0] = elm.points[0];
}
else if(type == NSCurveToBezierPathElement)
{
points[0] = elm.points[0];
points[1] = elm.points[1];
points[2] = elm.points[2];
}
}
return type;
}
- (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index
{
PathElement elm = GSIArrayItemAtIndex(pathElements, index).ext;
NSBezierPathElement type = elm.type;
switch(type)
{
case NSMoveToBezierPathElement:
case NSLineToBezierPathElement:
elm.points[0] = points[0];
break;
case NSCurveToBezierPathElement:
elm.points[0] = points[0];
elm.points[1] = points[1];
elm.points[2] = points[2];
break;
case NSClosePathBezierPathElement:
break;
default:
break;
}
GSIArraySetItemAtIndex(pathElements, (GSIArrayItem)elm, index);
INVALIDATE_CACHE();
}
//
// Path modifications.
//
- (NSBezierPath *)bezierPathByFlatteningPath
{
if (flat)
return self;
return [super bezierPathByFlatteningPath];
}
- (void) transformUsingAffineTransform: (NSAffineTransform *)transform
{
NSBezierPathElement type;
int i, count;
PathElement *elments = (PathElement *)GSIArrayItems(pathElements);
SEL transformPointSel = @selector(transformPoint:);
NSPoint (*transformPointImp)(NSAffineTransform*, SEL, NSPoint);
transformPointImp = (NSPoint (*)(NSAffineTransform*, SEL, NSPoint))
[transform methodForSelector: transformPointSel];
count = GSIArrayCount(pathElements);
for(i = 0; i < count; i++)
{
type = elments[i].type;
switch(type)
{
case NSMoveToBezierPathElement:
case NSLineToBezierPathElement:
elments[i].points[0] = (*transformPointImp)(transform,
transformPointSel,
elments[i].points[0]);
break;
case NSCurveToBezierPathElement:
elments[i].points[0] = (*transformPointImp)(transform,
transformPointSel,
elments[i].points[0]);
elments[i].points[1] = (*transformPointImp)(transform,
transformPointSel,
elments[i].points[1]);
elments[i].points[2] = (*transformPointImp)(transform,
transformPointSel,
elments[i].points[2]);
break;
case NSClosePathBezierPathElement:
break;
default:
break;
}
}
INVALIDATE_CACHE();
}
- (void) appendBezierPath: (NSBezierPath *)aPath
{
PathElement elem;
int i, count;
if (![aPath isKindOfClass: isa])
{
[super appendBezierPath: aPath];
return;
}
flat = flat && ((GSBezierPath*)aPath)->flat;
count = [aPath elementCount];
for (i = 0; i < count; i++)
{
elem = GSIArrayItemAtIndex(((GSBezierPath*)aPath)->pathElements, i).ext;
GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
}
INVALIDATE_CACHE();
}
//
// NSCopying Protocol
//
- (id)copyWithZone:(NSZone *)zone
{
GSBezierPath *path = [super copyWithZone: zone];
path->pathElements = GSIArrayCopyWithZone(pathElements, zone);
return path;
}
//
// Hit detection
//
#define PMAX 10000
- (BOOL)containsPoint:(NSPoint)point
{
NSPoint draftPolygon[PMAX];
int pcount = 0;
// Coordinates of the current point
double cx, cy;
// Coordinates of the last point
double lx, ly;
int i;
int Rcross = 0;
int Lcross = 0;
NSBezierPathElement bpt;
NSPoint p, pts[3];
double x, y, t, k = 0.25;
int count = [self elementCount];
if(!count)
return NO;
if (!NSPointInRect(point, [self bounds]))
return NO;
// FIXME: This does not handle multiple segments!
for(i = 0; i < count; i++)
{
bpt = [self elementAtIndex: i associatedPoints: pts];
if(bpt == NSMoveToBezierPathElement || bpt == NSLineToBezierPathElement)
{
draftPolygon[pcount].x = pts[0].x;
draftPolygon[pcount].y = pts[0].y;
pcount++;
}
else if(bpt == NSCurveToBezierPathElement)
{
if(pcount)
{
p.x = draftPolygon[pcount -1].x;
p.y = draftPolygon[pcount -1].y;
}
else
{
p.x = pts[0].x;
p.y = pts[0].y;
}
for(t = k; t <= 1+k; t += k)
{
x = (p.x+t*(-p.x*3+t*(3*p.x-p.x*t)))+
t*(3*pts[0].x+t*(-6*pts[0].x+pts[0].x*3*t))+
t*t*(pts[1].x*3-pts[1].x*3*t)+pts[2].x*t*t*t;
y = (p.y+t*(-p.y*3+t*(3*p.y-p.y*t)))+
t*(3*pts[0].y+t*(-6*pts[0].y+pts[0].y*3*t))+
t*t*(pts[1].y*3-pts[1].y*3*t)+pts[2].y*t*t*t;
draftPolygon[pcount].x = x;
draftPolygon[pcount].y = y;
pcount++;
}
}
// Simple overflow check
if (pcount == PMAX)
return NO;
}
lx = draftPolygon[pcount - 1].x - point.x;
ly = draftPolygon[pcount - 1].y - point.y;
for(i = 0; i < pcount; i++)
{
cx = draftPolygon[i].x - point.x;
cy = draftPolygon[i].y - point.y;
if(cx == 0 && cy == 0)
// on a vertex
return NO;
if((cy > 0) && !(ly > 0))
{
if (((cx * ly - lx * cy) / (ly - cy)) > 0)
Rcross++;
}
if((cy < 0 ) && !(ly < 0))
{
if (((cx * ly - lx * cy) / (ly - cy)) < 0);
Lcross++;
}
lx = cx;
ly = cy;
}
if((Rcross % 2) != (Lcross % 2))
// On the border
return NO;
if((Rcross % 2) == 1)
return YES;
else
return NO;
}
@end // GSBezierPath
static void flatten(NSPoint coeff[], float flatness, NSBezierPath *path)
{
// Check if the Bezier path defined by the four points has the given flatness.
// If not split it up in the middle and recurse.
// Otherwise add the end point to the path.
BOOL flat = YES;
// This criteria for flatness is based on code from Libart which has the
// following copyright:
/* Libart_LGPL - library of basic graphic primitives
* Copyright (C) 1998 Raph Levien
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
double x1_0, y1_0;
double x3_2, y3_2;
double x3_0, y3_0;
double z3_0_dot;
double z1_dot, z2_dot;
double z1_perp, z2_perp;
double max_perp_sq;
x3_0 = coeff[3].x - coeff[0].x;
y3_0 = coeff[3].y - coeff[0].y;
x3_2 = coeff[3].x - coeff[2].x;
y3_2 = coeff[3].y - coeff[2].y;
x1_0 = coeff[1].x - coeff[0].x;
y1_0 = coeff[1].y - coeff[0].y;
z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0;
if (z3_0_dot < 0.001)
flat = YES;
else
{
max_perp_sq = flatness * flatness * z3_0_dot;
z1_perp = y1_0 * x3_0 - x1_0 * y3_0;
if (z1_perp * z1_perp > max_perp_sq)
flat = NO;
else
{
z2_perp = y3_2 * x3_0 - x3_2 * y3_0;
if (z2_perp * z2_perp > max_perp_sq)
flat = NO;
else
{
z1_dot = x1_0 * x3_0 + y1_0 * y3_0;
if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq)
flat = NO;
else
{
z2_dot = x3_2 * x3_0 + y3_2 * y3_0;
if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq)
flat = NO;
else
{
if ((z1_dot + z1_dot > z3_0_dot) ||
(z2_dot + z2_dot > z3_0_dot))
flat = NO;
}
}
}
}
}
if (!flat)
{
NSPoint bleft[4], bright[4];
bleft[0] = coeff[0];
bleft[1].x = (coeff[0].x + coeff[1].x) / 2;
bleft[1].y = (coeff[0].y + coeff[1].y) / 2;
bleft[2].x = (coeff[0].x + 2*coeff[1].x + coeff[2].x) / 4;
bleft[2].y = (coeff[0].y + 2*coeff[1].y + coeff[2].y) / 4;
bleft[3].x = (coeff[0].x + 3*(coeff[1].x + coeff[2].x) + coeff[3].x) / 8;
bleft[3].y = (coeff[0].y + 3*(coeff[1].y + coeff[2].y) + coeff[3].y) / 8;
bright[0].x = bleft[3].x;
bright[0].y = bleft[3].y;
bright[1].x = (coeff[3].x + 2*coeff[2].x + coeff[1].x) / 4;
bright[1].y = (coeff[3].y + 2*coeff[2].y + coeff[1].y) / 4;
bright[2].x = (coeff[3].x + coeff[2].x) / 2;
bright[2].y = (coeff[3].y + coeff[2].y) / 2;
bright[3] = coeff[3];
flatten(bleft, flatness, path);
flatten(bright, flatness, path);
}
else
{
//[path lineToPoint: coeff[1]];
//[path lineToPoint: coeff[2]];
[path lineToPoint: coeff[3]];
}
}