You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

657 lines
15 KiB

/**
* 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 <React/RCTLog.h>
#import <math.h>
#import "RNSVGBezierElement.h"
@implementation RNSVGPathParser {
char prev_cmd;
NSUInteger i;
NSUInteger l;
NSString *s;
float _penX;
float _penY;
float _penDownX;
float _penDownY;
float _pivotX;
float _pivotY;
BOOL _valid;
BOOL _penDownSet;
}
- (instancetype)initWithPathString:(NSString *)d
{
if (self = [super init]) {
prev_cmd = ' ';
l = [d length];
i = 0;
s = d;
}
return self;
}
#define NEXT_FLOAT [self parse_list_number]
#define NEXT_BOOL [self parse_flag]
- (CGPathRef)getPath
{
CGMutablePathRef path = CGPathCreateMutable();
while (i < l) {
[self skip_spaces];
if (i >= l) {
break;
}
bool has_prev_cmd = prev_cmd != ' ';
char first_char = [s characterAtIndex:i];
if (!has_prev_cmd && first_char != 'M' && first_char != 'm') {
// The first segment must be a MoveTo.
RCTLogError(@"UnexpectedData: %@", s);
CGPathRelease(path);
return nil;
}
// TODO: simplify
bool is_implicit_move_to = false;
char cmd = ' ';
if ([self is_cmd:first_char]) {
is_implicit_move_to = false;
cmd = first_char;
i += 1;
} else if ([self is_number_start:first_char] && has_prev_cmd) {
if (prev_cmd == 'Z' || prev_cmd == 'z') {
// ClosePath cannot be followed by a number.
RCTLogError(@"UnexpectedData: %@", s);
CGPathRelease(path);
return nil;
}
if (prev_cmd == 'M' || prev_cmd == 'm') {
// 'If a moveto is followed by multiple pairs of coordinates,
// the subsequent pairs are treated as implicit lineto commands.'
// So we parse them as LineTo.
is_implicit_move_to = true;
if ([self is_absolute:prev_cmd]) {
cmd = 'L';
} else {
cmd = 'l';
}
} else {
is_implicit_move_to = false;
cmd = prev_cmd;
}
} else {
RCTLogError(@"UnexpectedData: %@", s);
CGPathRelease(path);
return nil;
}
bool absolute = [self is_absolute:cmd];
switch (cmd) {
case 'm': {
[self move:path x:NEXT_FLOAT y:NEXT_FLOAT];
break;
}
case 'M': {
[self moveTo:path x:NEXT_FLOAT y:NEXT_FLOAT];
break;
}
case 'l': {
[self line:path x:NEXT_FLOAT y:NEXT_FLOAT];
break;
}
case 'L': {
[self lineTo:path x:NEXT_FLOAT y:NEXT_FLOAT];
break;
}
case 'h': {
[self line:path x:NEXT_FLOAT y:0];
break;
}
case 'H': {
[self lineTo:path x:NEXT_FLOAT y:_penY];
break;
}
case 'v': {
[self line:path x:0 y:NEXT_FLOAT];
break;
}
case 'V': {
[self lineTo:path x:_penX y:NEXT_FLOAT];
break;
}
case 'c': {
[self curve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT];
break;
}
case 'C': {
[self curveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT];
break;
}
case 's': {
[self smoothCurve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT];
break;
}
case 'S': {
[self smoothCurveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT ex:NEXT_FLOAT ey:NEXT_FLOAT];
break;
}
case 'q': {
[self quadraticBezierCurve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT];
break;
}
case 'Q': {
[self quadraticBezierCurveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT c2x:NEXT_FLOAT c2y:NEXT_FLOAT];
break;
}
case 't': {
[self smoothQuadraticBezierCurve:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT];
break;
}
case 'T': {
[self smoothQuadraticBezierCurveTo:path c1x:NEXT_FLOAT c1y:NEXT_FLOAT];
break;
}
case 'a': {
[self arc:path
rx:NEXT_FLOAT
ry:NEXT_FLOAT
rotation:NEXT_FLOAT
outer:NEXT_BOOL
clockwise:NEXT_BOOL
x:NEXT_FLOAT
y:NEXT_FLOAT];
break;
}
case '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];
break;
}
case 'z':
case 'Z': {
[self close:path];
break;
}
default: {
RCTLogError(@"UnexpectedData: %@", s);
CGPathRelease(path);
return nil;
}
}
if (is_implicit_move_to) {
if (absolute) {
prev_cmd = 'M';
} else {
prev_cmd = 'm';
}
} else {
prev_cmd = cmd;
}
}
return (CGPathRef)CFAutorelease(path);
}
- (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
{
// RCTLogInfo(@"move x: %f y: %f", x, y);
_penDownX = _pivotX = _penX = x;
_penDownY = _pivotY = _penY = y;
CGPathMoveToPoint(path, nil, x, y);
}
- (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
{
// RCTLogInfo(@"line x: %f y: %f", x, y);
[self setPenDown];
_pivotX = _penX = x;
_pivotY = _penY = y;
CGPathAddLineToPoint(path, nil, x, y);
}
- (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
{
// RCTLogInfo(@"curve c1x: %f c1y: %f c2x: %f c2y: %f ex: %f ey: %f", c1x, c1y, c2x, c2y, ex, 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);
}
- (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
{
// RCTLogInfo(@"smoothcurve c1x: %f c1y: %f ex: %f ey: %f", c1x, c1y, ex, 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
{
// RCTLogInfo(@"quad c1x: %f c1y: %f c2x: %f c2y: %f", c1x, c1y, c2x, 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
{
// RCTLogInfo(@"smoothquad c1x: %f c1y: %f", c1x, 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
{
// RCTLogInfo(@"arc rx: %f ry: %f rotation: %f outer: %i clockwise: %i x: %f y: %f", rx, ry, rotation, outer,
// clockwise, x, 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 * (float)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 = (int)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;
float c1x = cx + xx * cp1x + yx * cp1y;
float c1y = cy + xy * cp1x + yy * cp1y;
float c2x = cx + xx * cp2x + yx * cp2y;
float c2y = cy + xy * cp2x + yy * cp2y;
float ex = cx + xx * x + yx * y;
float ey = cy + xy * x + yy * y;
CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey);
}
}
- (void)close:(CGMutablePathRef)path
{
if (_penDownSet) {
_penX = _penDownX;
_penY = _penDownY;
_penDownSet = NO;
CGPathCloseSubpath(path);
}
}
- (void)setPenDown
{
if (!_penDownSet) {
_penDownX = _penX;
_penDownY = _penY;
_penDownSet = YES;
}
}
- (void)skip_spaces
{
while (i < l && [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[s characterAtIndex:i]])
i++;
}
- (bool)is_cmd:(char)c
{
switch (c) {
case 'M':
case 'm':
case 'Z':
case 'z':
case 'L':
case 'l':
case 'H':
case 'h':
case 'V':
case 'v':
case 'C':
case 'c':
case 'S':
case 's':
case 'Q':
case 'q':
case 'T':
case 't':
case 'A':
case 'a':
return true;
}
return false;
}
- (bool)is_number_start:(char)c
{
return (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+';
}
- (bool)is_absolute:(char)c
{
return [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:c];
}
// By the SVG spec 'large-arc' and 'sweep' must contain only one char
// and can be written without any separators, e.g.: 10 20 30 01 10 20.
- (bool)parse_flag
{
[self skip_spaces];
char c = [s characterAtIndex:i];
switch (c) {
case '0':
case '1': {
i += 1;
if (i < l && [s characterAtIndex:i] == ',') {
i += 1;
}
[self skip_spaces];
break;
}
default:
RCTLogError(@"UnexpectedData: %@", s);
}
return c == '1';
}
- (float)parse_list_number
{
if (i == l) {
RCTLogError(@"UnexpectedEnd: %@", s);
}
float n = [self parse_number];
[self skip_spaces];
[self parse_list_separator];
return n;
}
- (float)parse_number
{
// Strip off leading whitespaces.
[self skip_spaces];
if (i == l) {
RCTLogError(@"InvalidNumber: %@", s);
}
NSUInteger start = i;
char c = [s characterAtIndex:i];
// Consume sign.
if (c == '-' || c == '+') {
i += 1;
c = [s characterAtIndex:i];
}
// Consume integer.
if (c >= '0' && c <= '9') {
[self skip_digits];
if (i < l) {
c = [s characterAtIndex:i];
}
} else if (c != '.') {
RCTLogError(@"InvalidNumber: %@", s);
}
// Consume fraction.
if (c == '.') {
i += 1;
[self skip_digits];
if (i < l) {
c = [s characterAtIndex:i];
}
}
if ((c == 'e' || c == 'E') && i + 1 < l) {
char c2 = [s characterAtIndex:i + 1];
// Check for `em`/`ex`.
if (c2 != 'm' && c2 != 'x') {
i += 1;
c = [s characterAtIndex:i];
if (c == '+' || c == '-') {
i += 1;
[self skip_digits];
} else if (c >= '0' && c <= '9') {
[self skip_digits];
} else {
RCTLogError(@"InvalidNumber: %@", s);
}
}
}
NSString *num = [s substringWithRange:NSMakeRange(start, i - start)];
float n = [num floatValue];
// inf, nan, etc. are an error.
if (!isfinite(n)) {
RCTLogError(@"InvalidNumber: %@", s);
}
return n;
}
- (void)parse_list_separator
{
if (i < l && [s characterAtIndex:i] == ',') {
i += 1;
}
}
- (void)skip_digits
{
while (i < l && [[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[s characterAtIndex:i]])
i++;
}
@end