933 lines
32 KiB
Objective-C
933 lines
32 KiB
Objective-C
#import "SVGKPointsAndPathsParser.h"
|
|
|
|
#import "NSCharacterSet+SVGKExtensions.h"
|
|
|
|
|
|
static inline SVGCurve SVGCurveMakePoint(CGPoint p)
|
|
{
|
|
SVGCurve curve;
|
|
curve.type = SVGCurveTypePoint;
|
|
|
|
curve.c1 = p;
|
|
curve.c2 = p;
|
|
curve.p = p;
|
|
|
|
return curve;
|
|
}
|
|
|
|
static inline CGPoint SVGCurveReflectedControlPoint(SVGCurve prevCurve)
|
|
{
|
|
return CGPointMake(prevCurve.p.x+(prevCurve.p.x-prevCurve.c2.x), prevCurve.p.y+(prevCurve.p.y-prevCurve.c2.y));
|
|
}
|
|
|
|
|
|
@implementation SVGKPointsAndPathsParser
|
|
|
|
+ (SVGCurve) startingCurve
|
|
{
|
|
return SVGCurveMakePoint(CGPointZero);
|
|
}
|
|
|
|
|
|
/* references
|
|
http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataBNF
|
|
http://www.w3.org/TR/2011/REC-SVG11-20110816/shapes.html#PointsBNF
|
|
|
|
*/
|
|
|
|
/*
|
|
http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataBNF
|
|
svg-path:
|
|
wsp* moveto-drawto-command-groups? wsp*
|
|
moveto-drawto-command-groups:
|
|
moveto-drawto-command-group
|
|
| moveto-drawto-command-group wsp* moveto-drawto-command-groups
|
|
moveto-drawto-command-group:
|
|
moveto wsp* drawto-commands?
|
|
drawto-commands:
|
|
drawto-command
|
|
| drawto-command wsp* drawto-commands
|
|
drawto-command:
|
|
closepath
|
|
| lineto
|
|
| horizontal-lineto
|
|
| vertical-lineto
|
|
| curveto
|
|
| smooth-curveto
|
|
| quadratic-bezier-curveto
|
|
| smooth-quadratic-bezier-curveto
|
|
| elliptical-arc
|
|
moveto:
|
|
( "M" | "m" ) wsp* moveto-argument-sequence
|
|
moveto-argument-sequence:
|
|
coordinate-pair
|
|
| coordinate-pair comma-wsp? lineto-argument-sequence
|
|
closepath:
|
|
("Z" | "z")
|
|
lineto:
|
|
( "L" | "l" ) wsp* lineto-argument-sequence
|
|
lineto-argument-sequence:
|
|
coordinate-pair
|
|
| coordinate-pair comma-wsp? lineto-argument-sequence
|
|
horizontal-lineto:
|
|
( "H" | "h" ) wsp* horizontal-lineto-argument-sequence
|
|
horizontal-lineto-argument-sequence:
|
|
coordinate
|
|
| coordinate comma-wsp? horizontal-lineto-argument-sequence
|
|
vertical-lineto:
|
|
( "V" | "v" ) wsp* vertical-lineto-argument-sequence
|
|
vertical-lineto-argument-sequence:
|
|
coordinate
|
|
| coordinate comma-wsp? vertical-lineto-argument-sequence
|
|
curveto:
|
|
( "C" | "c" ) wsp* curveto-argument-sequence
|
|
curveto-argument-sequence:
|
|
curveto-argument
|
|
| curveto-argument comma-wsp? curveto-argument-sequence
|
|
curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair comma-wsp? coordinate-pair
|
|
smooth-curveto:
|
|
( "S" | "s" ) wsp* smooth-curveto-argument-sequence
|
|
smooth-curveto-argument-sequence:
|
|
smooth-curveto-argument
|
|
| smooth-curveto-argument comma-wsp? smooth-curveto-argument-sequence
|
|
smooth-curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair
|
|
quadratic-bezier-curveto:
|
|
( "Q" | "q" ) wsp* quadratic-bezier-curveto-argument-sequence
|
|
quadratic-bezier-curveto-argument-sequence:
|
|
quadratic-bezier-curveto-argument
|
|
| quadratic-bezier-curveto-argument comma-wsp?
|
|
quadratic-bezier-curveto-argument-sequence
|
|
quadratic-bezier-curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair
|
|
smooth-quadratic-bezier-curveto:
|
|
( "T" | "t" ) wsp* smooth-quadratic-bezier-curveto-argument-sequence
|
|
smooth-quadratic-bezier-curveto-argument-sequence:
|
|
coordinate-pair
|
|
| coordinate-pair comma-wsp? smooth-quadratic-bezier-curveto-argument-sequence
|
|
elliptical-arc:
|
|
( "A" | "a" ) wsp* elliptical-arc-argument-sequence
|
|
elliptical-arc-argument-sequence:
|
|
elliptical-arc-argument
|
|
| elliptical-arc-argument comma-wsp? elliptical-arc-argument-sequence
|
|
elliptical-arc-argument:
|
|
nonnegative-number comma-wsp? nonnegative-number comma-wsp?
|
|
number comma-wsp flag comma-wsp? flag comma-wsp? coordinate-pair
|
|
coordinate-pair:
|
|
coordinate comma-wsp? coordinate
|
|
coordinate:
|
|
number
|
|
nonnegative-number:
|
|
integer-constant
|
|
| floating-point-constant
|
|
number:
|
|
sign? integer-constant
|
|
| sign? floating-point-constant
|
|
flag:
|
|
"0" | "1"
|
|
comma-wsp:
|
|
(wsp+ comma? wsp*) | (comma wsp*)
|
|
comma:
|
|
","
|
|
integer-constant:
|
|
digit-sequence
|
|
floating-point-constant:
|
|
fractional-constant exponent?
|
|
| digit-sequence exponent
|
|
fractional-constant:
|
|
digit-sequence? "." digit-sequence
|
|
| digit-sequence "."
|
|
exponent:
|
|
( "e" | "E" ) sign? digit-sequence
|
|
sign:
|
|
"+" | "-"
|
|
digit-sequence:
|
|
digit
|
|
| digit digit-sequence
|
|
digit:
|
|
"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
|
*/
|
|
|
|
/*
|
|
http://www.w3.org/TR/2011/REC-SVG11-20110816/shapes.html#PointsBNF
|
|
|
|
list-of-points:
|
|
wsp* coordinate-pairs? wsp*
|
|
coordinate-pairs:
|
|
coordinate-pair
|
|
| coordinate-pair comma-wsp coordinate-pairs
|
|
coordinate-pair:
|
|
coordinate comma-wsp coordinate
|
|
| coordinate negative-coordinate
|
|
coordinate:
|
|
number
|
|
number:
|
|
sign? integer-constant
|
|
| sign? floating-point-constant
|
|
negative-coordinate:
|
|
"-" integer-constant
|
|
| "-" floating-point-constant
|
|
comma-wsp:
|
|
(wsp+ comma? wsp*) | (comma wsp*)
|
|
comma:
|
|
","
|
|
integer-constant:
|
|
digit-sequence
|
|
floating-point-constant:
|
|
fractional-constant exponent?
|
|
| digit-sequence exponent
|
|
fractional-constant:
|
|
digit-sequence? "." digit-sequence
|
|
| digit-sequence "."
|
|
exponent:
|
|
( "e" | "E" ) sign? digit-sequence
|
|
sign:
|
|
"+" | "-"
|
|
digit-sequence:
|
|
digit
|
|
| digit digit-sequence
|
|
digit:
|
|
"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
|
*/
|
|
|
|
/**
|
|
wsp:
|
|
(#x20 | #x9 | #xD | #xA)
|
|
*/
|
|
+ (void) readWhitespace:(NSScanner*)scanner
|
|
{
|
|
/** This log message can be called literally hundreds of thousands of times in a single parse, which defeats
|
|
even Cocoa Lumberjack.
|
|
|
|
Even in "verbose" debugging, that's too much!
|
|
|
|
Hence: commented-out
|
|
SVGKitLogVerbose(@"Apple's implementation of scanCharactersFromSet seems to generate large amounts of temporary objects and can cause a crash here by taking literally megabytes of RAM in temporary internal variables. This is surprising, but I can't see anythign we're doing wrong. Adding this autoreleasepool drops memory usage (inside Apple's methods!) massively, so it seems to be the right thing to do");
|
|
*/
|
|
@autoreleasepool
|
|
{
|
|
[scanner scanCharactersFromSet:[NSCharacterSet SVGWhitespaceCharacterSet]
|
|
intoString:NULL];
|
|
}
|
|
}
|
|
|
|
+ (void) readCommaAndWhitespace:(NSScanner*)scanner
|
|
{
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
static NSString* comma = @",";
|
|
[scanner scanString:comma intoString:NULL];
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
}
|
|
|
|
/**
|
|
moveto-drawto-command-groups:
|
|
moveto-drawto-command-group
|
|
| moveto-drawto-command-group wsp* moveto-drawto-command-groups
|
|
*/
|
|
+ (SVGCurve) readMovetoDrawtoCommandGroups:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: move-to, draw-to command");
|
|
#endif
|
|
SVGCurve lastCurve = [SVGKPointsAndPathsParser readMovetoDrawto:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
while (![scanner isAtEnd])
|
|
{
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
/** FIXME: wasn't originally, but maybe should be:
|
|
|
|
origin = isRelative ? lastCoord : origin;
|
|
*/
|
|
lastCurve = [SVGKPointsAndPathsParser readMovetoDrawto:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
|
|
return lastCurve;
|
|
}
|
|
|
|
/** moveto-drawto-command-group:
|
|
moveto wsp* drawto-commands?
|
|
*/
|
|
+ (SVGCurve) readMovetoDrawto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
SVGCurve lastCurve = [SVGKPointsAndPathsParser readMoveto:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
return lastCurve;
|
|
}
|
|
|
|
/**
|
|
moveto:
|
|
( "M" | "m" ) wsp* moveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readMoveto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Mm"];
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
|
|
{
|
|
NSAssert(FALSE, @"failed to scan move to command");
|
|
return SVGCurveMakePoint(origin);
|
|
}
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readMovetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
|
|
/** moveto-argument-sequence:
|
|
coordinate-pair
|
|
| coordinate-pair comma-wsp? lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readMovetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
CGPoint coord = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
coord.x += origin.x;
|
|
coord.y += origin.y;
|
|
|
|
CGPathMoveToPoint(path, NULL, coord.x, coord.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: MOVED to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
if ([scanner isAtEnd]) {
|
|
return SVGCurveMakePoint(coord);
|
|
} else {
|
|
return [SVGKPointsAndPathsParser readLinetoArgumentSequence:scanner path:path relativeTo:(isRelative)?coord:origin isRelative:isRelative];
|
|
}
|
|
}
|
|
|
|
/**
|
|
coordinate-pair:
|
|
coordinate comma-wsp? coordinate
|
|
*/
|
|
|
|
+ (CGPoint) readCoordinatePair:(NSScanner*)scanner
|
|
{
|
|
CGPoint p;
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&p.x];
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&p.y];
|
|
|
|
return p;
|
|
}
|
|
|
|
+ (void) readCoordinate:(NSScanner*)scanner intoFloat:(CGFloat*) floatPointer
|
|
{
|
|
#if CGFLOAT_IS_DOUBLE
|
|
if( ![scanner scanDouble:floatPointer])
|
|
NSAssert(FALSE, @"invalid coord");
|
|
#else
|
|
if( ![scanner scanFloat:floatPointer])
|
|
NSAssert(FALSE, @"invalid coord");
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
lineto:
|
|
( "L" | "l" ) wsp* lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: line-to command");
|
|
#endif
|
|
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Ll"];
|
|
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
|
|
{
|
|
NSAssert( FALSE, @"failed to scan line to command");
|
|
return SVGCurveMakePoint(origin);
|
|
}
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readLinetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
|
|
/**
|
|
lineto-argument-sequence:
|
|
coordinate-pair
|
|
| coordinate-pair comma-wsp? lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
CGPoint p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
CGPoint coord = CGPointMake(p.x+origin.x, p.y+origin.y);
|
|
CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: LINE to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
while( ![scanner isAtEnd])
|
|
{
|
|
origin = (isRelative)?coord:origin;
|
|
p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
coord = CGPointMake(p.x+origin.x, p.y+origin.y);
|
|
CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: LINE to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
}
|
|
|
|
return SVGCurveMakePoint(coord);
|
|
}
|
|
|
|
/**
|
|
quadratic-bezier-curveto:
|
|
( "Q" | "q" ) wsp* quadratic-bezier-curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readQuadraticCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: quadratic-bezier-curve-to command");
|
|
#endif
|
|
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Qq"];
|
|
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
|
|
{
|
|
NSAssert( FALSE, @"failed to scan quadratic curve to command");
|
|
return SVGCurveMakePoint(origin);
|
|
}
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
/**
|
|
quadratic-bezier-curveto-argument-sequence:
|
|
quadratic-bezier-curveto-argument
|
|
| quadratic-bezier-curveto-argument comma-wsp? quadratic-bezier-curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
SVGCurve curve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgument:scanner path:path relativeTo:origin];
|
|
|
|
while(![scanner isAtEnd])
|
|
{
|
|
curve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgument:scanner path:path relativeTo:(isRelative ? curve.p : origin)];
|
|
}
|
|
|
|
return curve;
|
|
}
|
|
|
|
/**
|
|
quadratic-bezier-curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair
|
|
*/
|
|
+ (SVGCurve) readQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
|
|
{
|
|
SVGCurve curveResult;
|
|
curveResult.type = SVGCurveTypeQuadratic;
|
|
|
|
curveResult.c1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
curveResult.c1.x += origin.x;
|
|
curveResult.c1.y += origin.y;
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
curveResult.c2 = curveResult.c1;
|
|
|
|
curveResult.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
curveResult.p.x += origin.x;
|
|
curveResult.p.y += origin.y;
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
CGPathAddQuadCurveToPoint(path, NULL, curveResult.c1.x, curveResult.c1.y, curveResult.p.x, curveResult.p.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: QUADRATIC CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], curveResult.c1.x, curveResult.c1.y, curveResult.p.x, curveResult.p.y);
|
|
#endif
|
|
|
|
return curveResult;
|
|
}
|
|
|
|
/**
|
|
smooth-quadratic-bezier-curveto:
|
|
( "T" | "t" ) wsp* smooth-quadratic-bezier-curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readSmoothQuadraticCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: smooth-quadratic-bezier-curve-to command");
|
|
#endif
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Tt"];
|
|
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
|
|
{
|
|
NSAssert( FALSE, @"failed to scan smooth quadratic curve to command");
|
|
return prevCurve;
|
|
}
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
|
|
}
|
|
|
|
|
|
/**
|
|
smooth-quadratic-bezier-curveto-argument-sequence:
|
|
smooth-quadratic-bezier-curveto-argument
|
|
| smooth-quadratic-bezier-curveto-argument comma-wsp? smooth-quadratic-bezier-curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readSmoothQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
|
|
{
|
|
SVGCurve curve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgument:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
|
|
|
|
if (![scanner isAtEnd]) {
|
|
curve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:curve.p withPrevCurve:curve];
|
|
}
|
|
|
|
return curve;
|
|
}
|
|
|
|
/**
|
|
smooth-quadratic-bezier-curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair
|
|
*/
|
|
+ (SVGCurve) readSmoothQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
|
|
{
|
|
SVGCurve thisCurve;
|
|
thisCurve.type = SVGCurveTypeQuadratic;
|
|
|
|
thisCurve.c2 = (prevCurve.type == thisCurve.type) ? SVGCurveReflectedControlPoint(prevCurve) : prevCurve.p;
|
|
|
|
thisCurve.c1 = thisCurve.c2; // this coordinate is never used, but c2 is better/safer than CGPointZero
|
|
|
|
thisCurve.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
thisCurve.p.x += origin.x;
|
|
thisCurve.p.y += origin.y;
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
CGPathAddQuadCurveToPoint(path, NULL, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y );
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: SMOOTH QUADRATIC CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], thisCurve.c1.x, thisCurve.c1.y, thisCurve.p.x, thisCurve.p.y );
|
|
#endif
|
|
|
|
return thisCurve;
|
|
}
|
|
|
|
/**
|
|
curveto:
|
|
( "C" | "c" ) wsp* curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: curve-to command");
|
|
#endif
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Cc"];
|
|
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd])
|
|
{
|
|
NSAssert( FALSE, @"failed to scan curve to command");
|
|
return SVGCurveMakePoint(origin);
|
|
}
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readCurvetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
|
|
/**
|
|
curveto-argument-sequence:
|
|
curveto-argument
|
|
| curveto-argument comma-wsp? curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
SVGCurve curve = [SVGKPointsAndPathsParser readCurvetoArgument:scanner path:path relativeTo:origin];
|
|
|
|
while( ![scanner isAtEnd])
|
|
{
|
|
CGPoint newOrigin = isRelative ? curve.p : origin;
|
|
|
|
curve = [SVGKPointsAndPathsParser readCurvetoArgument:scanner path:path relativeTo:newOrigin];
|
|
}
|
|
|
|
return curve;
|
|
}
|
|
|
|
/**
|
|
curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair comma-wsp? coordinate-pair
|
|
*/
|
|
+ (SVGCurve) readCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
|
|
{
|
|
SVGCurve curveResult;
|
|
curveResult.type = SVGCurveTypeCubic;
|
|
|
|
curveResult.c1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
curveResult.c1.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
|
|
curveResult.c1.y += origin.y;
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
curveResult.c2 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
curveResult.c2.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
|
|
curveResult.c2.y += origin.y;
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
curveResult.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
curveResult.p.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
|
|
curveResult.p.y += origin.y;
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
CGPathAddCurveToPoint(path, NULL, curveResult.c1.x, curveResult.c1.y, curveResult.c2.x, curveResult.c2.y, curveResult.p.x, curveResult.p.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], curveResult.c1.x, curveResult.c1.y, curveResult.c2.x, curveResult.c2.y, curveResult.p.x, curveResult.p.y);
|
|
#endif
|
|
|
|
return curveResult;
|
|
}
|
|
|
|
/**
|
|
smooth-curveto:
|
|
( "S" | "s" ) wsp* smooth-curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readSmoothCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve isRelative:(BOOL) isRelative
|
|
{
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Ss"];
|
|
BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:&cmd];
|
|
|
|
NSAssert(ok, @"failed to scan smooth curve to command");
|
|
if (!ok) return prevCurve;
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readSmoothCurvetoArgumentSequence:scanner path:path relativeTo:origin withPrevCurve:prevCurve isRelative:isRelative];
|
|
}
|
|
|
|
/**
|
|
smooth-curveto-argument-sequence:
|
|
smooth-curveto-argument
|
|
| smooth-curveto-argument comma-wsp? smooth-curveto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readSmoothCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve isRelative:(BOOL) isRelative
|
|
{
|
|
SVGCurve curve = [SVGKPointsAndPathsParser readSmoothCurvetoArgument:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
|
|
|
|
if (![scanner isAtEnd]) {
|
|
CGPoint newOrigin = isRelative ? curve.p : origin;
|
|
curve = [SVGKPointsAndPathsParser readSmoothCurvetoArgumentSequence:scanner path:path relativeTo:newOrigin withPrevCurve:curve isRelative: isRelative];
|
|
}
|
|
|
|
return curve;
|
|
}
|
|
|
|
/**
|
|
smooth-curveto-argument:
|
|
coordinate-pair comma-wsp? coordinate-pair
|
|
*/
|
|
+ (SVGCurve) readSmoothCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
|
|
{
|
|
SVGCurve thisCurve;
|
|
thisCurve.type = SVGCurveTypeCubic;
|
|
|
|
thisCurve.c1 = (prevCurve.type == thisCurve.type) ? SVGCurveReflectedControlPoint(prevCurve) : prevCurve.p;
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
thisCurve.c2 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
thisCurve.c2.x += origin.x;
|
|
thisCurve.c2.y += origin.y;
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
thisCurve.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
thisCurve.p.x += origin.x;
|
|
thisCurve.p.y += origin.y;
|
|
|
|
CGPathAddCurveToPoint(path, NULL, thisCurve.c1.x, thisCurve.c1.y, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: SMOOTH CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], thisCurve.c1.x, thisCurve.c1.y, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y );
|
|
#endif
|
|
|
|
return thisCurve;
|
|
}
|
|
|
|
/**
|
|
vertical-lineto-argument-sequence:
|
|
coordinate
|
|
| coordinate comma-wsp? vertical-lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readVerticalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
// FIXME: reduce the allocations here; make one CGPoint and update it, not multiple
|
|
CGFloat yValue;
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&yValue];
|
|
CGPoint vertCoord = CGPointMake(origin.x, origin.y+yValue);
|
|
CGPoint currentPoint = CGPathGetCurrentPoint(path);
|
|
CGPoint coord = CGPointMake(currentPoint.x, currentPoint.y+(vertCoord.y-currentPoint.y));
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: VERTICAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
|
|
|
|
while (![scanner isAtEnd]) {
|
|
origin = isRelative ? coord : origin;
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&yValue];
|
|
vertCoord = CGPointMake(origin.x, origin.y+yValue);
|
|
currentPoint = CGPathGetCurrentPoint(path);
|
|
coord = CGPointMake(currentPoint.x, currentPoint.y+(vertCoord.y-currentPoint.y));
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: VERTICAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
|
|
}
|
|
|
|
return SVGCurveMakePoint(coord);
|
|
}
|
|
|
|
/**
|
|
vertical-lineto:
|
|
( "V" | "v" ) wsp* vertical-lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readVerticalLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: vertical-line-to command");
|
|
#endif
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Vv"];
|
|
BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:&cmd];
|
|
|
|
NSAssert(ok, @"failed to scan vertical line to command");
|
|
if (!ok) return SVGCurveMakePoint(origin);
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readVerticalLinetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
|
|
/**
|
|
horizontal-lineto-argument-sequence:
|
|
coordinate
|
|
| coordinate comma-wsp? horizontal-lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readHorizontalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
// FIXME: reduce the allocations here; make one CGPoint and update it, not multiple
|
|
CGFloat xValue;
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&xValue];
|
|
CGPoint horizCoord = CGPointMake(origin.x+xValue, origin.y);
|
|
CGPoint currentPoint = CGPathGetCurrentPoint(path);
|
|
CGPoint coord = CGPointMake(currentPoint.x+(horizCoord.x-currentPoint.x), currentPoint.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: HORIZONTAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
|
|
|
|
while (![scanner isAtEnd]) {
|
|
origin = isRelative ? coord : origin;
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&xValue];
|
|
horizCoord = CGPointMake(origin.x+xValue, origin.y);
|
|
currentPoint = CGPathGetCurrentPoint(path);
|
|
coord = CGPointMake(currentPoint.x+(horizCoord.x-currentPoint.x), currentPoint.y);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: HORIZONTAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
|
|
#endif
|
|
CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
|
|
}
|
|
|
|
return SVGCurveMakePoint(coord);
|
|
}
|
|
|
|
/**
|
|
horizontal-lineto:
|
|
( "H" | "h" ) wsp* horizontal-lineto-argument-sequence
|
|
*/
|
|
+ (SVGCurve) readHorizontalLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: horizontal-line-to command");
|
|
#endif
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Hh"];
|
|
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
|
|
{
|
|
NSAssert( FALSE, @"failed to scan horizontal line to command");
|
|
return SVGCurveMakePoint(origin);
|
|
}
|
|
|
|
[SVGKPointsAndPathsParser readWhitespace:scanner];
|
|
|
|
return [SVGKPointsAndPathsParser readHorizontalLinetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
|
|
}
|
|
|
|
+ (SVGCurve) readCloseCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
|
|
{
|
|
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
|
|
SVGKitLogVerbose(@"Parsing command string: close command");
|
|
#endif
|
|
NSString* cmd = nil;
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Zz"];
|
|
|
|
if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
|
|
{
|
|
NSAssert( FALSE, @"failed to scan close command");
|
|
return SVGCurveMakePoint(origin);
|
|
}
|
|
|
|
CGPathCloseSubpath(path);
|
|
#if DEBUG_PATH_CREATION
|
|
SVGKitLogWarn(@"[%@] PATH: finished path", [SVGKPointsAndPathsParser class] );
|
|
#endif
|
|
|
|
return SVGCurveMakePoint(CGPathGetCurrentPoint(path));
|
|
}
|
|
|
|
+ (SVGCurve) readEllipticalArcArguments:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
|
|
{
|
|
NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Aa"];
|
|
BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:nil];
|
|
|
|
NSAssert(ok, @"failed to scan arc to command");
|
|
if (!ok) return SVGCurveMakePoint(origin);
|
|
|
|
CGPoint endPoint = [SVGKPointsAndPathsParser readEllipticalArcArgumentsSequence:scanner path:path relativeTo:origin];
|
|
|
|
while (![scanner isAtEnd]) {
|
|
CGPoint newOrigin = isRelative ? endPoint : origin;
|
|
endPoint = [SVGKPointsAndPathsParser readEllipticalArcArgumentsSequence:scanner path:path relativeTo:newOrigin];
|
|
}
|
|
|
|
return SVGCurveMakePoint(endPoint);
|
|
}
|
|
|
|
+ (CGPoint)readEllipticalArcArgumentsSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
|
|
{
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
// need to find the center point of the ellipse from the two points and an angle
|
|
// see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes for these calculations
|
|
|
|
CGPoint currentPt = CGPathGetCurrentPoint(path);
|
|
|
|
CGFloat x1 = currentPt.x;
|
|
CGFloat y1 = currentPt.y;
|
|
|
|
CGPoint radii = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
CGFloat rx = fabs(radii.x);
|
|
CGFloat ry = fabs(radii.y);
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
CGFloat phi;
|
|
|
|
[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&phi];
|
|
|
|
phi *= M_PI/180.;
|
|
|
|
phi = fmod(phi, 2 * M_PI);
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
CGPoint flags = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
|
|
BOOL largeArcFlag = flags.x != 0.;
|
|
BOOL sweepFlag = flags.y != 0.;
|
|
|
|
[SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
|
|
|
|
CGPoint endPoint = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
|
|
|
|
// end parsing
|
|
|
|
CGFloat x2 = origin.x + endPoint.x;
|
|
CGFloat y2 = origin.y + endPoint.y;
|
|
|
|
if (rx == 0 || ry == 0)
|
|
{
|
|
CGPathAddLineToPoint(path, NULL, x2, y2);
|
|
return CGPointMake(x2, y2);
|
|
}
|
|
CGFloat cosPhi = cos(phi);
|
|
CGFloat sinPhi = sin(phi);
|
|
|
|
CGFloat x1p = cosPhi * (x1-x2)/2. + sinPhi * (y1-y2)/2.;
|
|
CGFloat y1p = -sinPhi * (x1-x2)/2. + cosPhi * (y1-y2)/2.;
|
|
|
|
CGFloat lhs;
|
|
{
|
|
CGFloat rx_2 = rx * rx;
|
|
CGFloat ry_2 = ry * ry;
|
|
CGFloat xp_2 = x1p * x1p;
|
|
CGFloat yp_2 = y1p * y1p;
|
|
|
|
CGFloat delta = xp_2/rx_2 + yp_2/ry_2;
|
|
|
|
if (delta > 1.0)
|
|
{
|
|
rx *= sqrt(delta);
|
|
ry *= sqrt(delta);
|
|
rx_2 = rx * rx;
|
|
ry_2 = ry * ry;
|
|
}
|
|
CGFloat sign = (largeArcFlag == sweepFlag) ? -1 : 1;
|
|
CGFloat numerator = rx_2 * ry_2 - rx_2 * yp_2 - ry_2 * xp_2;
|
|
CGFloat denom = rx_2 * yp_2 + ry_2 * xp_2;
|
|
|
|
numerator = MAX(0, numerator);
|
|
|
|
if (denom == 0) {
|
|
lhs = 0;
|
|
}else {
|
|
lhs = sign * sqrt(numerator/denom);
|
|
}
|
|
}
|
|
|
|
CGFloat cxp = lhs * (rx*y1p)/ry;
|
|
CGFloat cyp = lhs * -((ry * x1p)/rx);
|
|
|
|
CGFloat cx = cosPhi * cxp + -sinPhi * cyp + (x1+x2)/2.;
|
|
CGFloat cy = cxp * sinPhi + cyp * cosPhi + (y1+y2)/2.;
|
|
|
|
// transform our ellipse into the unit circle
|
|
|
|
CGAffineTransform tr = CGAffineTransformMakeScale(1./rx, 1./ry);
|
|
|
|
tr = CGAffineTransformRotate(tr, -phi);
|
|
tr = CGAffineTransformTranslate(tr, -cx, -cy);
|
|
|
|
CGPoint arcPt1 = CGPointApplyAffineTransform(CGPointMake(x1, y1), tr);
|
|
CGPoint arcPt2 = CGPointApplyAffineTransform(CGPointMake(x2, y2), tr);
|
|
|
|
CGFloat startAngle = atan2(arcPt1.y, arcPt1.x);
|
|
CGFloat endAngle = atan2(arcPt2.y, arcPt2.x);
|
|
|
|
CGFloat angleDelta = endAngle - startAngle;;
|
|
|
|
if (sweepFlag)
|
|
{
|
|
if (angleDelta < 0)
|
|
angleDelta += 2. * M_PI;
|
|
}
|
|
else
|
|
{
|
|
if (angleDelta > 0)
|
|
angleDelta = angleDelta - 2 * M_PI;
|
|
}
|
|
// construct the inverse transform
|
|
CGAffineTransform trInv = CGAffineTransformMakeTranslation( cx, cy);
|
|
|
|
trInv = CGAffineTransformRotate(trInv, phi);
|
|
trInv = CGAffineTransformScale(trInv, rx, ry);
|
|
|
|
// add a inversely transformed circular arc to the current path
|
|
CGPathAddRelativeArc( path, &trInv, 0, 0, 1., startAngle, angleDelta);
|
|
|
|
return CGPointMake(x2, y2);
|
|
}
|
|
|
|
@end
|