diff --git a/android/src/main/java/com/horcrux/svg/PropHelper.java b/android/src/main/java/com/horcrux/svg/PropHelper.java index 691a12dd..2f02a675 100644 --- a/android/src/main/java/com/horcrux/svg/PropHelper.java +++ b/android/src/main/java/com/horcrux/svg/PropHelper.java @@ -196,7 +196,7 @@ import java.util.regex.Pattern; static class PathParser { static private Pattern PATH_REG_EXP = Pattern.compile("[a-df-z]|[\\-+]?(?:[\\d.]e[\\-+]?|[^\\s\\-+,a-z])+", Pattern.CASE_INSENSITIVE); - static private Pattern DECICAL_REG_EXP = Pattern.compile("(\\.\\d+)(?=\\-?\\.)"); + static private Pattern DECIMAL_REG_EXP = Pattern.compile("(\\.\\d+)(?=\\-?\\.)"); private Matcher mMatcher; private Path mPath; @@ -218,7 +218,7 @@ import java.util.regex.Pattern; mScale = scale; mString = d; mPath = new Path(); - mMatcher = PATH_REG_EXP.matcher(DECICAL_REG_EXP.matcher(mString).replaceAll("$1,")); + mMatcher = PATH_REG_EXP.matcher(DECIMAL_REG_EXP.matcher(mString).replaceAll("$1,")); while (mMatcher.find() && mValid) { executeCommand(mMatcher.group()); @@ -361,7 +361,7 @@ import java.util.regex.Pattern; } private void lineTo(float x, float y) { - setPendDown(); + setPenDown(); mPivotX = mPenX = x; mPivotY = mPenY = y; mPath.lineTo(x * mScale, y * mScale); @@ -378,7 +378,7 @@ import java.util.regex.Pattern; } private void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey) { - setPendDown(); + setPenDown(); mPenX = ex; mPenY = ey; mPath.cubicTo(c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale, ey * mScale); @@ -489,7 +489,7 @@ import java.util.regex.Pattern; x += tX; y += tY; - setPendDown(); + setPenDown(); mPenX = mPivotX = x; mPenY = mPivotY = y; @@ -570,7 +570,7 @@ import java.util.regex.Pattern; } } - private void setPendDown() { + private void setPenDown() { if (!mPendDownSet) { mPenDownX = mPenX; mPenDownY = mPenY; diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index 38003680..3033b8ab 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4A9D1CF0656A0078BC02 /* RNSVGClipPathManager.m */; }; 10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ED4AA11CF078830078BC02 /* RNSVGNode.m */; }; 10FDEEB21D3FB60500A5C46C /* RNSVGBaseBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 10FDEEB11D3FB60500A5C46C /* RNSVGBaseBrush.m */; }; + 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -155,6 +156,8 @@ 10FDEEB01D3FB60500A5C46C /* RNSVGBaseBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBaseBrush.h; sourceTree = ""; }; 10FDEEB11D3FB60500A5C46C /* RNSVGBaseBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGBaseBrush.m; sourceTree = ""; }; 10FDEEB31D3FBED400A5C46C /* RNSVGBrushType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBrushType.h; sourceTree = ""; }; + 7F9CDAF81E1F809C00E0C805 /* RNSVGPathParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGPathParser.h; path = Utils/RNSVGPathParser.h; sourceTree = ""; }; + 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGPathParser.m; path = Utils/RNSVGPathParser.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -310,13 +313,15 @@ 1039D29A1CE7212C001E90A8 /* Utils */ = { isa = PBXGroup; children = ( + 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */, 10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */, 10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */, 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */, 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */, + 7F9CDAF81E1F809C00E0C805 /* RNSVGPathParser.h */, + 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, - 1039D29E1CE72177001E90A8 /* RNSVGCGFloatArray.h */, ); name = Utils; sourceTree = ""; @@ -412,6 +417,7 @@ 10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */, 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */, 0CF68B0B1AF0549300FF9E5C /* RNSVGBrush.m in Sources */, + 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */, 10BA0D361CE74E3100887C2B /* RNSVGGroupManager.m in Sources */, 10BA0D4A1CE74E3D00887C2B /* RNSVGLine.m in Sources */, 10FDEEB21D3FB60500A5C46C /* RNSVGBaseBrush.m in Sources */, @@ -518,6 +524,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RNSVG; + PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; SKIP_INSTALL = YES; }; name = Debug; @@ -532,6 +539,7 @@ ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = RNSVG; + PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; SKIP_INSTALL = YES; }; name = Release; diff --git a/ios/Utils/RCTConvert+RNSVG.h b/ios/Utils/RCTConvert+RNSVG.h index a5dc77dc..2c29fd1c 100644 --- a/ios/Utils/RCTConvert+RNSVG.h +++ b/ios/Utils/RCTConvert+RNSVG.h @@ -17,13 +17,14 @@ @interface RCTConvert (RNSVG) -+ (CGPathRef)CGPath:(id)json; ++ (CGPathRef)CGPath:(NSString *)d; + (CTTextAlignment)CTTextAlignment:(id)json; + (RNSVGCGFCRule)RNSVGCGFCRule:(id)json; + (RNSVGTextFrame)RNSVGTextFrame:(id)json; + (RNSVGCGFloatArray)RNSVGCGFloatArray:(id)json; + (RNSVGBrush *)RNSVGBrush:(id)json; + + (NSArray *)RNSVGBezier:(id)json; + (CGRect)CGRect:(id)json offset:(NSUInteger)offset; + (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset; diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index b44923a1..1be458ae 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -15,54 +15,13 @@ #import "RNSVGCGFCRule.h" #import "RNSVGVBMOS.h" #import +#import "RNSVGPathParser.h" @implementation RCTConvert (RNSVG) -+ (CGPathRef)CGPath:(id)json ++ (CGPathRef)CGPath:(NSString *)d { - NSArray *arr = [self NSNumberArray:json]; - - NSUInteger count = [arr count]; - -#define NEXT_VALUE [self double:arr[i++]] - - CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, nil, 0, 0); - - @try { - NSUInteger i = 0; - while (i < count) { - NSUInteger type = [arr[i++] unsignedIntegerValue]; - switch (type) { - case 0: - CGPathMoveToPoint(path, nil, NEXT_VALUE, NEXT_VALUE); - break; - case 1: - CGPathCloseSubpath(path); - break; - case 2: - CGPathAddLineToPoint(path, nil, NEXT_VALUE, NEXT_VALUE); - break; - case 3: - CGPathAddCurveToPoint(path, nil, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE); - break; - case 4: - CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0); - break; - default: - RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr); - CGPathRelease(path); - return nil; - } - } - } - @catch (NSException *exception) { - RCTLogError(@"Invalid CGPath format: %@", arr); - CGPathRelease(path); - return nil; - } - - return (CGPathRef)CFAutorelease(path); + return [[[RNSVGPathParser alloc] initWithPathString: d] getPath]; } RCT_ENUM_CONVERTER(CTTextAlignment, (@{ diff --git a/ios/Utils/RNSVGPathParser.h b/ios/Utils/RNSVGPathParser.h new file mode 100644 index 00000000..1815390c --- /dev/null +++ b/ios/Utils/RNSVGPathParser.h @@ -0,0 +1,17 @@ +/** + * 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 +#import + +@interface RNSVGPathParser : NSObject + +- (instancetype) initWithPathString:(NSString *)d; +- (CGPathRef)getPath; + +@end diff --git a/ios/Utils/RNSVGPathParser.m b/ios/Utils/RNSVGPathParser.m new file mode 100644 index 00000000..3c07a9ca --- /dev/null +++ b/ios/Utils/RNSVGPathParser.m @@ -0,0 +1,379 @@ +/** + * 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 + +@implementation RNSVGPathParser : NSObject +{ + NSString* _d; + NSString* _originD; + NSRegularExpression* _pathRegularExpression; + double _penX; + double _penY; + double _penDownX; + double _penDownY; + double _pivotX; + double _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])]; + + int count = [results count]; + if (count) { + NSUInteger i = 0; + #define NEXT_VALUE [self getNextValue:results[i++]] + #define NEXT_DOUBLE [self double: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_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"M"]) { + [self moveTo:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"l"]) { // lineTo command + [self line:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"L"]) { + [self lineTo:path x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"h"]) { // horizontalTo command + [self line:path x:NEXT_DOUBLE y:0]; + } else if ([command isEqualToString:@"H"]) { + [self lineTo:path x:NEXT_DOUBLE y:_penY]; + } else if ([command isEqualToString:@"v"]) { // verticalTo command + [self line:path x:0 y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"V"]) { + [self lineTo:path x:_penX y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"c"]) { // curveTo command + [self curve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"C"]) { + [self curveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"s"]) { // smoothCurveTo command + [self smoothCurve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"S"]) { + [self smoothCurveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE ex:NEXT_DOUBLE ey:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"q"]) { // quadraticBezierCurveTo command + [self quadraticBezierCurve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"Q"]) { + [self quadraticBezierCurveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE c2x:NEXT_DOUBLE c2y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"t"]) {// smoothQuadraticBezierCurveTo command + [self smoothQuadraticBezierCurve:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"T"]) { + [self smoothQuadraticBezierCurveTo:path c1x:NEXT_DOUBLE c1y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"a"]) { // arcTo command + [self arc:path rx:NEXT_DOUBLE ry:NEXT_DOUBLE rotation:NEXT_DOUBLE outer:NEXT_BOOL clockwise:NEXT_BOOL x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } else if ([command isEqualToString:@"A"]) { + [self arcTo:path rx:NEXT_DOUBLE ry:NEXT_DOUBLE rotation:NEXT_DOUBLE outer:NEXT_BOOL clockwise:NEXT_BOOL x:NEXT_DOUBLE y:NEXT_DOUBLE]; + } 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); +} + +- (NSString *)getNextValue:(NSTextCheckingResult *)result +{ + if (!result) { + return nil; + } + return [_d substringWithRange:NSMakeRange(result.range.location, result.range.length)]; +} + +- (double)double:(NSString *)value +{ + return [value doubleValue]; +} + +- (BOOL)bool:(NSString *)value +{ + return ![value isEqualToString:@"0"]; +} + +- (void)move:(CGPathRef)path x:(double)x y:(double)y +{ + [self moveTo:path x:x + _penX y:y + _penY]; +} + +- (void)moveTo:(CGPathRef)path x:(double)x y:(double)y +{ + _pivotX = _penX = x; + _pivotY = _penY = y; + CGPathMoveToPoint(path, nil, x, y); +} + +- (void)line:(CGPathRef)path x:(double)x y:(double)y +{ + [self lineTo:path x:x + _penX y:y + _penY]; +} + +- (void)lineTo:(CGPathRef)path x:(double)x y:(double)y{ + [self setPenDown]; + _pivotX = _penX = x; + _pivotY = _penY = y; + CGPathAddLineToPoint(path, nil, x, y); +} + +- (void)curve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey +{ + [self curveTo:path c1x:c1x + _penX + c1y:c1y + _penY + c2x:c2x + _penX + c2y:c2y + _penY + ex:ex + _penX + ey:ey + _penY]; +} + +- (void)curveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey +{ + _pivotX = ex; + _pivotY = ey; + [self curveToPoint:path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey]; +} + +- (void)curveToPoint:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey +{ + [self setPenDown]; + _penX = ex; + _penY = ey; + CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey); +} + +- (void)smoothCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y ex:(double)ex ey:(double)ey +{ + [self smoothCurveTo:path c1x:c1x + _penX c1y:c1y + _penY ex:ex + _penX ey:ey + _penY]; +} + +- (void)smoothCurveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y ex:(double)ex ey:(double)ey +{ + double c2x = c1x; + double c2y = c1y; + c1x = (_penX * 2) - _pivotX; + c1y = (_penY * 2) - _pivotY; + _pivotX = c2x; + _pivotY = c2y; + [self curveToPoint:path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey]; +} + +- (void)quadraticBezierCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y +{ + [self quadraticBezierCurveTo:path c1x:(double)c1x + _penX c1y:(double)c1y + _penY c2x:(double)c2x + _penX c2y:(double)c2y + _penY]; +} + +- (void)quadraticBezierCurveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y +{ + _pivotX = c1x; + _pivotY = c1y; + double ex = c2x; + double 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:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey]; +} + +- (void)smoothQuadraticBezierCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y +{ + [self smoothQuadraticBezierCurveTo:path c1x:c1x + _penX c1y:c1y + _penY]; +} + +- (void)smoothQuadraticBezierCurveTo:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y +{ + double c2x = c1x; + double c2y = c1y; + c1x = (_penX * 2) - _pivotX; + c1y = (_penY * 2) - _pivotY; + [self quadraticBezierCurveTo:path c1x:c1x c1y:c1y c2x:c2x c2y:c2y]; +} + +- (void)arc:(CGPathRef)path rx:(double)rx ry:(double)ry rotation:(double)rotation outer:(BOOL)outer clockwise:(BOOL)clockwise x:(double)x y:(double)y +{ + [self arcTo:path rx:rx ry:ry rotation:rotation outer:outer clockwise:clockwise x:x + _penX y:y + _penY]; +} + +- (void)arcTo:(CGPathRef)path rx:(double)rx ry:(double)ry rotation:(double)rotation outer:(BOOL)outer clockwise:(BOOL)clockwise x:(double)x y:(double)y +{ + double tX = _penX; + double tY = _penY; + + ry = abs(ry == 0 ? (rx == 0 ? (y - tY) : rx) : ry); + rx = abs(rx == 0 ? (x - tX) : rx); + + if (rx == 0 || ry == 0 || (x == tX && y == tY)) { + [self lineTo:path x:x y:y]; + return; + } + + + double rad = rotation * M_PI / 180; + double cosed = cos(rad); + double sined = sin(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 = sqrt(1 - a / rxry); + rx *= a; + ry *= a; + cx = x / 2; + cy = y / 2; + } else { + a = sqrt(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 = atan2(xy * -cx + yy * -cy, xx * -cx + yx * -cy); + float ea = atan2(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; + + if (rx != ry || rad != 0) { + [self arcToBezier:path cx:cx cy:cy rx:rx ry:ry sa:sa ea:ea clockwise:clockwise rad:rad]; + } else { + CGPathAddArc(path, nil, cx, cy, rx, sa, ea, !clockwise); + } +} + +- (void)arcToBezier:(CGPathRef)path cx:(double)cx cy:(double)cy rx:(double)rx ry:(double)ry sa:(double)sa ea:(double)ea clockwise:(BOOL)clockwise rad:(double)rad +{ + // Inverse Rotation + Scale Transform + double cosed = cos(rad); + double sined = sin(rad); + double xx = cosed * rx; + double yx = -sined * ry; + double xy = sined * rx; + double yy = cosed * ry; + + // Bezier Curve Approximation + double arc = ea - sa; + if (arc < 0 && clockwise) { + arc += M_PI * 2; + } else if (arc > 0 && !clockwise) { + arc -= M_PI * 2; + } + + int n = ceil(abs(arc / (M_PI / 2))); + + double step = arc / n; + double k = (4 / 3) * tan(step / 4); + + double x = cos(sa); + double y = sin(sa); + + for (int i = 0; i < n; i++){ + double cp1x = x - k * y; + double cp1y = y + k * x; + + sa += step; + x = cos(sa); + y = sin(sa); + + double cp2x = x + k * y; + double 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:(CGPathRef)path +{ + if (_penDownSet) { + _penX = _penDownX; + _penY = _penDownY; + _penDownSet = NO; + CGPathCloseSubpath(path); + } +} + +- (void)setPenDown +{ + if (!_penDownSet) { + _penDownX = _penX; + _penDownY = _penY; + _penDownSet = YES; + } +} + +@end diff --git a/ios/Utils/RNSVGPercentageConverter.m b/ios/Utils/RNSVGPercentageConverter.m index 6b2b0a60..3ba5986f 100644 --- a/ios/Utils/RNSVGPercentageConverter.m +++ b/ios/Utils/RNSVGPercentageConverter.m @@ -12,7 +12,7 @@ { CGFloat _relative; CGFloat _offset; - NSRegularExpression *percentageRegularExpression; + NSRegularExpression *_percentageRegularExpression; } - (instancetype) initWithRelativeAndOffset:(CGFloat)relative offset:(CGFloat)offset @@ -20,7 +20,7 @@ if (self = [super init]) { _relative = relative; _offset = offset; - percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; + _percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; } return self; } @@ -28,14 +28,14 @@ - (id)init { if (self = [super init]) { - percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; + _percentageRegularExpression = [[NSRegularExpression alloc] initWithPattern:@"^(\\-?\\d+(?:\\.\\d+)?)%$" options:0 error:nil]; } return self; } - (NSRegularExpression *) getPercentageRegularExpression { - return percentageRegularExpression; + return _percentageRegularExpression; } - (CGFloat) stringToFloat:(NSString *)string @@ -61,7 +61,7 @@ { __block CGFloat matched; - [percentageRegularExpression enumerateMatchesInString:percentage + [_percentageRegularExpression enumerateMatchesInString:percentage options:0 range:NSMakeRange(0, percentage.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) @@ -76,7 +76,7 @@ - (BOOL) isPercentage:(NSString *) string { - return [percentageRegularExpression firstMatchInString:string options:0 range:NSMakeRange(0, [string length])] != nil; + return [_percentageRegularExpression firstMatchInString:string options:0 range:NSMakeRange(0, [string length])] != nil; } @end diff --git a/lib/extract/extractClipping.js b/lib/extract/extractClipping.js index 6b03a60d..fea0bff1 100644 --- a/lib/extract/extractClipping.js +++ b/lib/extract/extractClipping.js @@ -1,4 +1,3 @@ -import SerializablePath from '../SerializablePath'; import clipReg from './patternReg'; const clipRules = { @@ -18,7 +17,7 @@ export default function (props) { if (matched) { clippingProps.clipPathRef = matched[1]; } else { - clippingProps.clipPath = new SerializablePath(clipPath).toJSON(); + clippingProps.clipPath = clipPath; } }