/** * Copyright (c) 2015-present, Horcrux. * All rights reserved. * * This source code is licensed under the MIT-style license found in the * LICENSE file in the root directory of this source tree. */ #import "RNSVGPathParser.h" #import #import "math.h" @implementation RNSVGPathParser { NSString* _d; NSString* _originD; NSRegularExpression* _pathRegularExpression; NSMutableArray* _bezierCurves; NSValue *_lastStartPoint; float _penX; float _penY; float _penDownX; float _penDownY; float _pivotX; float _pivotY; BOOL _valid; BOOL _penDownSet; } - (instancetype) initWithPathString:(NSString *)d { if (self = [super init]) { NSRegularExpression* decimalRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"(\\.\\d+)(?=\\-?\\.)" options:0 error:nil]; _originD = d; _d = [decimalRegularExpression stringByReplacingMatchesInString:d options:0 range:NSMakeRange(0, [d length]) withTemplate:@"$1,"]; _pathRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"[a-df-z]|[\\-+]?(?:[\\d.]e[\\-+]?|[^\\s\\-+,a-z])+" options:NSRegularExpressionCaseInsensitive error:nil]; } return self; } - (CGPathRef)getPath { CGMutablePathRef path = CGPathCreateMutable(); NSArray* results = [_pathRegularExpression matchesInString:_d options:0 range:NSMakeRange(0, [_d length])]; _bezierCurves = [[NSMutableArray alloc] init]; int count = [results count]; if (count) { NSUInteger i = 0; #define NEXT_VALUE [self getNextValue:results[i++]] #define NEXT_FLOAT [self float:NEXT_VALUE] #define NEXT_BOOL [self bool:NEXT_VALUE] NSString* lastCommand; NSString* command = NEXT_VALUE; @try { while (command) { if ([command isEqualToString:@"m"]) { // moveTo command [self move:path x:NEXT_FLOAT y:NEXT_FLOAT]; } else if ([command isEqualToString:@"M"]) { [self moveTo:path x:NEXT_FLOAT y:NEXT_FLOAT]; } else if ([command isEqualToString:@"l"]) { // lineTo command [self line:path x:NEXT_FLOAT y:NEXT_FLOAT]; } else if ([command isEqualToString:@"L"]) { [self lineTo:path x:NEXT_FLOAT y:NEXT_FLOAT]; } else if ([command isEqualToString:@"h"]) { // horizontalTo command [self line:path x:NEXT_FLOAT y:0]; } else if ([command isEqualToString:@"H"]) { [self lineTo:path x:NEXT_FLOAT y:_penY]; } else if ([command isEqualToString:@"v"]) { // verticalTo command [self line:path x:0 y:NEXT_FLOAT]; } else if ([command isEqualToString:@"V"]) { [self lineTo:path x:_penX y:NEXT_FLOAT]; } else if ([command isEqualToString:@"c"]) { // curveTo command [self curve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT]; } else if ([command isEqualToString:@"C"]) { [self curveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT]; } else if ([command isEqualToString:@"s"]) { // smoothCurveTo command [self smoothCurve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT]; } else if ([command isEqualToString:@"S"]) { [self smoothCurveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT]; } else if ([command isEqualToString:@"q"]) { // quadraticBezierCurveTo command [self quadraticBezierCurve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT]; } else if ([command isEqualToString:@"Q"]) { [self quadraticBezierCurveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT]; } else if ([command isEqualToString:@"t"]) {// smoothQuadraticBezierCurveTo command [self smoothQuadraticBezierCurve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT]; } else if ([command isEqualToString:@"T"]) { [self smoothQuadraticBezierCurveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT]; } else if ([command isEqualToString:@"a"]) { // arcTo command [self arc:path rx:NEXT_FLOAT ry:NEXT_FLOAT rotation:NEXT_FLOAT outer:NEXT_BOOL clockwise:NEXT_BOOL x:NEXT_FLOAT y:NEXT_FLOAT]; } else if ([command isEqualToString:@"A"]) { [self arcTo:path rx:NEXT_FLOAT ry:NEXT_FLOAT rotation:NEXT_FLOAT outer:NEXT_BOOL clockwise:NEXT_BOOL x:NEXT_FLOAT y:NEXT_FLOAT]; } else if ([command isEqualToString:@"z"]) { // close command [self close:path]; } else if ([command isEqualToString:@"Z"]) { [self close:path]; } else { command = lastCommand; i--; continue; } lastCommand = command; if ([lastCommand isEqualToString:@"m"]) { lastCommand = @"l"; } else if ([lastCommand isEqualToString:@"M"]) { lastCommand = @"L"; } command = i < count ? NEXT_VALUE : nil; } } @catch (NSException *exception) { RCTLogWarn(@"Invalid CGPath format: %@", _originD); CGPathRelease(path); return nil; } } return (CGPathRef)CFAutorelease(path); } - (NSArray *)getBezierCurves { if (!_bezierCurves) { CGPathRelease([self getPath]); } return [_bezierCurves copy]; } - (NSString *)getNextValue:(NSTextCheckingResult *)result { if (!result) { return nil; } return [_d substringWithRange:NSMakeRange(result.range.location, result.range.length)]; } - (float)float:(NSString *)value { return [value floatValue]; } - (BOOL)bool:(NSString *)value { return ![value isEqualToString:@"0"]; } - (void)move:(CGMutablePathRef)path x:(float)x y:(float)y { [self moveTo:path x:x + _penX y:y + _penY]; } - (void)moveTo:(CGMutablePathRef)path x:(float)x y:(float)y { _pivotX = _penX = x; _pivotY = _penY = y; CGPathMoveToPoint(path, nil, x, y); _lastStartPoint = [NSValue valueWithCGPoint: CGPointMake(x, y)]; [_bezierCurves addObject: @[_lastStartPoint]]; } - (void)line:(CGMutablePathRef)path x:(float)x y:(float)y { [self lineTo:path x:x + _penX y:y + _penY]; } - (void)lineTo:(CGMutablePathRef)path x:(float)x y:(float)y{ [self setPenDown]; _pivotX = _penX = x; _pivotY = _penY = y; CGPathAddLineToPoint(path, nil, x, y); NSValue * destination = [NSValue valueWithCGPoint:CGPointMake(x, y)]; [_bezierCurves addObject: @[destination, destination, destination]]; } - (void)curve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey { [self curveTo:path c1x:c1x + _penX c1y:c1y + _penY c2x:c2x + _penX c2y:c2y + _penY ex:ex + _penX ey:ey + _penY]; } - (void)curveTo:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey { _pivotX = c2x; _pivotY = c2y; [self curveToPoint:path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey]; } - (void)curveToPoint:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey { [self setPenDown]; _penX = ex; _penY = ey; CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey); [_bezierCurves addObject: @[ [NSValue valueWithCGPoint:CGPointMake(c1x, c1y)], [NSValue valueWithCGPoint:CGPointMake(c2x, c2y)], [NSValue valueWithCGPoint:CGPointMake(ex, ey)] ]]; } - (void)smoothCurve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y ex:(float)ex ey:(float)ey { [self smoothCurveTo:path c1x:c1x + _penX c1y:c1y + _penY ex:ex + _penX ey:ey + _penY]; } - (void)smoothCurveTo:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y ex:(float)ex ey:(float)ey { float c2x = c1x; float c2y = c1y; c1x = (_penX * 2) - _pivotX; c1y = (_penY * 2) - _pivotY; _pivotX = c2x; _pivotY = c2y; [self curveToPoint:path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey]; } - (void)quadraticBezierCurve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y { [self quadraticBezierCurveTo:path c1x:(float)c1x + _penX c1y:(float)c1y + _penY c2x:(float)c2x + _penX c2y:(float)c2y + _penY]; } - (void)quadraticBezierCurveTo:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y { _pivotX = c1x; _pivotY = c1y; float ex = c2x; float ey = c2y; c2x = (ex + c1x * 2) / 3; c2y = (ey + c1y * 2) / 3; c1x = (_penX + c1x * 2) / 3; c1y = (_penY + c1y * 2) / 3; [self curveToPoint:path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey]; } - (void)smoothQuadraticBezierCurve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y { [self smoothQuadraticBezierCurveTo:path c1x:c1x + _penX c1y:c1y + _penY]; } - (void)smoothQuadraticBezierCurveTo:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y { float c2x = c1x; float c2y = c1y; c1x = (_penX * 2) - _pivotX; c1y = (_penY * 2) - _pivotY; [self quadraticBezierCurveTo:path c1x:c1x c1y:c1y c2x:c2x c2y:c2y]; } - (void)arc:(CGMutablePathRef)path rx:(float)rx ry:(float)ry rotation:(float)rotation outer:(BOOL)outer clockwise:(BOOL)clockwise x:(float)x y:(float)y { [self arcTo:path rx:rx ry:ry rotation:rotation outer:outer clockwise:clockwise x:x + _penX y:y + _penY]; } - (void)arcTo:(CGMutablePathRef)path rx:(float)rx ry:(float)ry rotation:(float)rotation outer:(BOOL)outer clockwise:(BOOL)clockwise x:(float)x y:(float)y { float tX = _penX; float tY = _penY; ry = fabsf(ry == 0 ? (rx == 0 ? (y - tY) : rx) : ry); rx = fabsf(rx == 0 ? (x - tX) : rx); if (rx == 0 || ry == 0 || (x == tX && y == tY)) { [self lineTo:path x:x y:y]; return; } float rad = rotation * M_PI / 180; float cosed = cosf(rad); float sined = sinf(rad); x -= tX; y -= tY; // Ellipse Center float cx = cosed * x / 2 + sined * y / 2; float cy = -sined * x / 2 + cosed * y / 2; float rxry = rx * rx * ry * ry; float rycx = ry * ry * cx * cx; float rxcy = rx * rx * cy * cy; float a = rxry - rxcy - rycx; if (a < 0){ a = sqrtf(1 - a / rxry); rx *= a; ry *= a; cx = x / 2; cy = y / 2; } else { a = sqrtf(a / (rxcy + rycx)); if (outer == clockwise) { a = -a; } float cxd = -a * cy * rx / ry; float cyd = a * cx * ry / rx; cx = cosed * cxd - sined * cyd + x / 2; cy = sined * cxd + cosed * cyd + y / 2; } // Rotation + Scale Transform float xx = cosed / rx; float yx = sined / rx; float xy = -sined / ry; float yy = cosed / ry; // Start and End Angle float sa = atan2f(xy * -cx + yy * -cy, xx * -cx + yx * -cy); float ea = atan2f(xy * (x - cx) + yy * (y - cy), xx * (x - cx) + yx * (y - cy)); cx += tX; cy += tY; x += tX; y += tY; [self setPenDown]; _penX = _pivotX = x; _penY = _pivotY = y; [self arcToBezier:path cx:cx cy:cy rx:rx ry:ry sa:sa ea:ea clockwise:clockwise rad:rad]; } - (void)arcToBezier:(CGMutablePathRef)path cx:(float)cx cy:(float)cy rx:(float)rx ry:(float)ry sa:(float)sa ea:(float)ea clockwise:(BOOL)clockwise rad:(float)rad { // Inverse Rotation + Scale Transform float cosed = cosf(rad); float sined = sinf(rad); float xx = cosed * rx; float yx = -sined * ry; float xy = sined * rx; float yy = cosed * ry; // Bezier Curve Approximation float arc = ea - sa; if (arc < 0 && clockwise) { arc += M_PI * 2; } else if (arc > 0 && !clockwise) { arc -= M_PI * 2; } int n = ceilf(fabsf(arc / ((float)M_PI / 2))); float step = arc / n; float k = (4.0f / 3.0f) * tanf(step / 4); float x = cosf(sa); float y = sinf(sa); for (int i = 0; i < n; i++){ float cp1x = x - k * y; float cp1y = y + k * x; sa += step; x = cosf(sa); y = sinf(sa); float cp2x = x + k * y; float cp2y = y - k * x; CGPathAddCurveToPoint(path, nil, cx + xx * cp1x + yx * cp1y, cy + xy * cp1x + yy * cp1y, cx + xx * cp2x + yx * cp2y, cy + xy * cp2x + yy * cp2y, cx + xx * x + yx * y, cy + xy * x + yy * y); } } - (void)close:(CGMutablePathRef)path { if (_penDownSet) { _penX = _penDownX; _penY = _penDownY; _penDownSet = NO; CGPathCloseSubpath(path); [_bezierCurves addObject: @[_lastStartPoint, _lastStartPoint, _lastStartPoint]]; } } - (void)setPenDown { if (!_penDownSet) { _penDownX = _penX; _penDownY = _penY; _penDownSet = YES; } } @end