chore: add CI for JS, iOS and Android formatting (#1782)

Added CI workflow and local pre-commit hook for formatting and linting the newly added JS, iOS and Android code.
This commit is contained in:
Wojciech Lewicki
2022-08-16 12:00:32 +02:00
committed by GitHub
parent 77267be5fc
commit 98c14b4f45
177 changed files with 16855 additions and 16018 deletions

View File

@@ -6,22 +6,22 @@
* LICENSE file in the root directory of this source tree.
*/
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
#import "RCTConvert+RNSVG.h"
#import <QuartzCore/QuartzCore.h>
#import <React/RCTConvert.h>
#import "RCTConvert+RNSVG.h"
#import "RNSVGCGFCRule.h"
#import "RNSVGVBMOS.h"
#import "RNSVGUnits.h"
#import "RNSVGLength.h"
#import "RNSVGPathParser.h"
#import "RNSVGUnits.h"
#import "RNSVGVBMOS.h"
@class RNSVGBrush;
@interface RCTConvert (RNSVG)
+ (RNSVGLength*)RNSVGLength:(id)json;
+ (NSArray<RNSVGLength *>*)RNSVGLengthArray:(id)json;
+ (RNSVGLength *)RNSVGLength:(id)json;
+ (NSArray<RNSVGLength *> *)RNSVGLengthArray:(id)json;
+ (RNSVGCGFCRule)RNSVGCGFCRule:(id)json;
+ (RNSVGVBMOS)RNSVGVBMOS:(id)json;
+ (RNSVGUnits)RNSVGUnits:(id)json;

View File

@@ -8,184 +8,190 @@
#import "RCTConvert+RNSVG.h"
#import <React/RCTFont.h>
#import <React/RCTLog.h>
#import "RNSVGContextBrush.h"
#import "RNSVGPainterBrush.h"
#import "RNSVGSolidColorBrush.h"
#import "RNSVGContextBrush.h"
#import <React/RCTLog.h>
#import <React/RCTFont.h>
NSRegularExpression *RNSVGDigitRegEx;
@implementation RCTConvert (RNSVG)
RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{
@"evenodd": @(kRNSVGCGFCRuleEvenodd),
@"nonzero": @(kRNSVGCGFCRuleNonzero),
}), kRNSVGCGFCRuleNonzero, intValue)
RCT_ENUM_CONVERTER(
RNSVGCGFCRule,
(@{
@"evenodd" : @(kRNSVGCGFCRuleEvenodd),
@"nonzero" : @(kRNSVGCGFCRuleNonzero),
}),
kRNSVGCGFCRuleNonzero,
intValue)
RCT_ENUM_CONVERTER(RNSVGVBMOS, (@{
@"meet": @(kRNSVGVBMOSMeet),
@"slice": @(kRNSVGVBMOSSlice),
@"none": @(kRNSVGVBMOSNone)
}), kRNSVGVBMOSMeet, intValue)
RCT_ENUM_CONVERTER(
RNSVGVBMOS,
(@{@"meet" : @(kRNSVGVBMOSMeet), @"slice" : @(kRNSVGVBMOSSlice), @"none" : @(kRNSVGVBMOSNone)}),
kRNSVGVBMOSMeet,
intValue)
RCT_ENUM_CONVERTER(RNSVGUnits, (@{
@"objectBoundingBox": @(kRNSVGUnitsObjectBoundingBox),
@"userSpaceOnUse": @(kRNSVGUnitsUserSpaceOnUse),
}), kRNSVGUnitsObjectBoundingBox, intValue)
RCT_ENUM_CONVERTER(
RNSVGUnits,
(@{
@"objectBoundingBox" : @(kRNSVGUnitsObjectBoundingBox),
@"userSpaceOnUse" : @(kRNSVGUnitsUserSpaceOnUse),
}),
kRNSVGUnitsObjectBoundingBox,
intValue)
+ (RNSVGBrush *)RNSVGBrush:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return [[RNSVGSolidColorBrush alloc] initWithNumber:json];
if ([json isKindOfClass:[NSNumber class]]) {
return [[RNSVGSolidColorBrush alloc] initWithNumber:json];
}
if ([json isKindOfClass:[NSString class]]) {
NSString *value = [self NSString:json];
if (!RNSVGDigitRegEx) {
RNSVGDigitRegEx = [NSRegularExpression regularExpressionWithPattern:@"[0-9.-]+"
options:NSRegularExpressionCaseInsensitive
error:nil];
}
if ([json isKindOfClass:[NSString class]]) {
NSString *value = [self NSString:json];
if (!RNSVGDigitRegEx) {
RNSVGDigitRegEx = [NSRegularExpression regularExpressionWithPattern:@"[0-9.-]+" options:NSRegularExpressionCaseInsensitive error:nil];
}
NSArray<NSTextCheckingResult*> *_matches = [RNSVGDigitRegEx matchesInString:value options:0 range:NSMakeRange(0, [value length])];
NSMutableArray<NSNumber*> *output = [NSMutableArray array];
NSUInteger i = 0;
[output addObject:[NSNumber numberWithInteger:0]];
for (NSTextCheckingResult *match in _matches) {
NSString* strNumber = [value substringWithRange:match.range];
[output addObject:[NSNumber numberWithDouble:(i++ < 3 ? strNumber.doubleValue / 255 : strNumber.doubleValue)]];
}
if ([output count] < 5) {
[output addObject:[NSNumber numberWithDouble:1]];
}
return [[RNSVGSolidColorBrush alloc] initWithArray:output];
NSArray<NSTextCheckingResult *> *_matches = [RNSVGDigitRegEx matchesInString:value
options:0
range:NSMakeRange(0, [value length])];
NSMutableArray<NSNumber *> *output = [NSMutableArray array];
NSUInteger i = 0;
[output addObject:[NSNumber numberWithInteger:0]];
for (NSTextCheckingResult *match in _matches) {
NSString *strNumber = [value substringWithRange:match.range];
[output addObject:[NSNumber numberWithDouble:(i++ < 3 ? strNumber.doubleValue / 255 : strNumber.doubleValue)]];
}
NSDictionary *dict = [self NSDictionary:json];
int type = [dict[@"type"] intValue];
if ([output count] < 5) {
[output addObject:[NSNumber numberWithDouble:1]];
}
return [[RNSVGSolidColorBrush alloc] initWithArray:output];
}
NSDictionary *dict = [self NSDictionary:json];
int type = [dict[@"type"] intValue];
switch (type) {
case 0: // solid color
switch (type) {
case 0: // solid color
// These are probably expensive allocations since it's often the same value.
// We should memoize colors but look ups may be just as expensive.
{
NSArray *arr = @[@(0), dict[@"value"]];
return [[RNSVGSolidColorBrush alloc] initWithArray:arr];
}
case 1: // brush
{
NSArray *arr = @[@(1), dict[@"brushRef"]];
return [[RNSVGPainterBrush alloc] initWithArray:arr];
}
case 2: // currentColor
return [[RNSVGBrush alloc] initWithArray:nil];
case 3: // context-fill
return [[RNSVGContextBrush alloc] initFill];
case 4: // context-stroke
return [[RNSVGContextBrush alloc] initStroke];
default:
RCTLogError(@"Unknown brush type: %zd", (unsigned long)type);
return nil;
{
NSArray *arr = @[ @(0), dict[@"value"] ];
return [[RNSVGSolidColorBrush alloc] initWithArray:arr];
}
case 1: // brush
{
NSArray *arr = @[ @(1), dict[@"brushRef"] ];
return [[RNSVGPainterBrush alloc] initWithArray:arr];
}
case 2: // currentColor
return [[RNSVGBrush alloc] initWithArray:nil];
case 3: // context-fill
return [[RNSVGContextBrush alloc] initFill];
case 4: // context-stroke
return [[RNSVGContextBrush alloc] initStroke];
default:
RCTLogError(@"Unknown brush type: %zd", (unsigned long)type);
return nil;
}
}
+ (RNSVGPathParser *)RNSVGCGPath:(NSString *)d
{
return [[RNSVGPathParser alloc] initWithPathString: d];
return [[RNSVGPathParser alloc] initWithPathString:d];
}
+ (RNSVGLength *)RNSVGLength:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return [RNSVGLength lengthWithNumber:(CGFloat)[json doubleValue]];
} else if ([json isKindOfClass:[NSString class]]) {
NSString *stringValue = (NSString *)json;
return [RNSVGLength lengthWithString:stringValue];
} else {
return [[RNSVGLength alloc] init];
}
if ([json isKindOfClass:[NSNumber class]]) {
return [RNSVGLength lengthWithNumber:(CGFloat)[json doubleValue]];
} else if ([json isKindOfClass:[NSString class]]) {
NSString *stringValue = (NSString *)json;
return [RNSVGLength lengthWithString:stringValue];
} else {
return [[RNSVGLength alloc] init];
}
}
+ (NSArray<RNSVGLength *>*)RNSVGLengthArray:(id)json
+ (NSArray<RNSVGLength *> *)RNSVGLengthArray:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
RNSVGLength* length = [RNSVGLength lengthWithNumber:(CGFloat)[json doubleValue]];
return [NSArray arrayWithObject:length];
} else if ([json isKindOfClass:[NSArray class]]) {
NSArray *arrayValue = (NSArray*)json;
NSMutableArray<RNSVGLength*>* lengths = [NSMutableArray arrayWithCapacity:[arrayValue count]];
for (id obj in arrayValue) {
[lengths addObject:[self RNSVGLength:obj]];
}
return lengths;
} else if ([json isKindOfClass:[NSString class]]) {
NSString *stringValue = (NSString *)json;
RNSVGLength* length = [RNSVGLength lengthWithString:stringValue];
return [NSArray arrayWithObject:length];
} else {
return nil;
if ([json isKindOfClass:[NSNumber class]]) {
RNSVGLength *length = [RNSVGLength lengthWithNumber:(CGFloat)[json doubleValue]];
return [NSArray arrayWithObject:length];
} else if ([json isKindOfClass:[NSArray class]]) {
NSArray *arrayValue = (NSArray *)json;
NSMutableArray<RNSVGLength *> *lengths = [NSMutableArray arrayWithCapacity:[arrayValue count]];
for (id obj in arrayValue) {
[lengths addObject:[self RNSVGLength:obj]];
}
return lengths;
} else if ([json isKindOfClass:[NSString class]]) {
NSString *stringValue = (NSString *)json;
RNSVGLength *length = [RNSVGLength lengthWithString:stringValue];
return [NSArray arrayWithObject:length];
} else {
return nil;
}
}
+ (CGRect)RNSVGCGRect:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
if (arr.count < offset + 4) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", (ssize_t)(4 + offset), arr);
return CGRectZero;
}
return (CGRect){
{[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
{[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
};
NSArray *arr = [self NSArray:json];
if (arr.count < offset + 4) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", (ssize_t)(4 + offset), arr);
return CGRectZero;
}
return (CGRect){
{[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
{[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
};
}
+ (RNSVGColor *)RNSVGColor:(id)json offset:(NSUInteger)offset
{
NSArray *arr = [self NSArray:json];
if (arr.count == offset + 1) {
return [self RNSVGColor:[arr objectAtIndex:offset]];
}
if (arr.count < offset + 4) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", (ssize_t)(4 + offset), arr);
return nil;
}
return [self RNSVGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
NSArray *arr = [self NSArray:json];
if (arr.count == offset + 1) {
return [self RNSVGColor:[arr objectAtIndex:offset]];
}
if (arr.count < offset + 4) {
RCTLogError(@"Too few elements in array (expected at least %zd): %@", (ssize_t)(4 + offset), arr);
return nil;
}
return [self RNSVGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
}
+ (CGGradientRef)RNSVGCGGradient:(id)json
{
NSArray *arr = [self NSArray:json];
NSUInteger count = arr.count / 2;
NSUInteger values = count * 5;
NSUInteger offsetIndex = values - count;
CGFloat colorsAndOffsets[values];
for (NSUInteger i = 0; i < count; i++) {
NSUInteger stopIndex = i * 2;
CGFloat offset = (CGFloat)[arr[stopIndex] doubleValue];
NSUInteger argb = [self NSUInteger:arr[stopIndex + 1]];
NSArray *arr = [self NSArray:json];
NSUInteger count = arr.count / 2;
NSUInteger values = count * 5;
NSUInteger offsetIndex = values - count;
CGFloat colorsAndOffsets[values];
for (NSUInteger i = 0; i < count; i++) {
NSUInteger stopIndex = i * 2;
CGFloat offset = (CGFloat)[arr[stopIndex] doubleValue];
NSUInteger argb = [self NSUInteger:arr[stopIndex + 1]];
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
CGFloat b = (argb & 0xFF) / 255.0;
CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
CGFloat b = (argb & 0xFF) / 255.0;
NSUInteger colorIndex = i * 4;
colorsAndOffsets[colorIndex] = r;
colorsAndOffsets[colorIndex + 1] = g;
colorsAndOffsets[colorIndex + 2] = b;
colorsAndOffsets[colorIndex + 3] = a;
NSUInteger colorIndex = i * 4;
colorsAndOffsets[colorIndex] = r;
colorsAndOffsets[colorIndex + 1] = g;
colorsAndOffsets[colorIndex + 2] = b;
colorsAndOffsets[colorIndex + 3] = a;
colorsAndOffsets[offsetIndex + i] = fmax(0, fmin(offset, 1));
}
colorsAndOffsets[offsetIndex + i] = fmax(0, fmin(offset, 1));
}
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(
rgb,
colorsAndOffsets,
colorsAndOffsets + offsetIndex,
count
);
CGColorSpaceRelease(rgb);
return (CGGradientRef)CFAutorelease(gradient);
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient =
CGGradientCreateWithColorComponents(rgb, colorsAndOffsets, colorsAndOffsets + offsetIndex, count);
CGColorSpaceRelease(rgb);
return (CGGradientRef)CFAutorelease(gradient);
}
@end

View File

@@ -19,8 +19,7 @@
@property (nonatomic, assign) CGPoint controlPoint2;
// Instance creation
+ (instancetype) elementWithPathElement: (CGPathElement) element;
+ (NSArray *) elementsFromCGPath:(CGPathRef)path;
+ (instancetype)elementWithPathElement:(CGPathElement)element;
+ (NSArray *)elementsFromCGPath:(CGPathRef)path;
@end

View File

@@ -9,69 +9,63 @@
#pragma mark - Bezier Element -
@implementation RNSVGBezierElement
- (instancetype) init
- (instancetype)init
{
self = [super init];
if (self)
{
_elementType = kCGPathElementMoveToPoint;
_point = RNSVGNULLPOINT;
_controlPoint1 = RNSVGNULLPOINT;
_controlPoint2 = RNSVGNULLPOINT;
}
return self;
self = [super init];
if (self) {
_elementType = kCGPathElementMoveToPoint;
_point = RNSVGNULLPOINT;
_controlPoint1 = RNSVGNULLPOINT;
_controlPoint2 = RNSVGNULLPOINT;
}
return self;
}
+ (instancetype) elementWithPathElement: (CGPathElement) element
+ (instancetype)elementWithPathElement:(CGPathElement)element
{
RNSVGBezierElement *newElement = [[self alloc] init];
newElement.elementType = element.type;
RNSVGBezierElement *newElement = [[self alloc] init];
newElement.elementType = element.type;
switch (newElement.elementType)
{
case kCGPathElementCloseSubpath:
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
{
newElement.point = element.points[0];
break;
}
case kCGPathElementAddQuadCurveToPoint:
{
newElement.point = element.points[1];
newElement.controlPoint1 = element.points[0];
break;
}
case kCGPathElementAddCurveToPoint:
{
newElement.point = element.points[2];
newElement.controlPoint1 = element.points[0];
newElement.controlPoint2 = element.points[1];
break;
}
default:
break;
switch (newElement.elementType) {
case kCGPathElementCloseSubpath:
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint: {
newElement.point = element.points[0];
break;
}
case kCGPathElementAddQuadCurveToPoint: {
newElement.point = element.points[1];
newElement.controlPoint1 = element.points[0];
break;
}
case kCGPathElementAddCurveToPoint: {
newElement.point = element.points[2];
newElement.controlPoint1 = element.points[0];
newElement.controlPoint2 = element.points[1];
break;
}
default:
break;
}
return newElement;
return newElement;
}
// Convert one element to RNSVGBezierElement and save to array
void RNSVGBezierElement_getBezierElements(void *info, const CGPathElement *element)
{
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
if (element)
[bezierElements addObject:[RNSVGBezierElement elementWithPathElement:*element]];
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
if (element)
[bezierElements addObject:[RNSVGBezierElement elementWithPathElement:*element]];
}
// Retrieve array of component elements
+ (NSArray *) elementsFromCGPath:(CGPathRef)path
+ (NSArray *)elementsFromCGPath:(CGPathRef)path
{
NSMutableArray *elements = [NSMutableArray array];
CGPathApply(path, (__bridge void *)elements, RNSVGBezierElement_getBezierElements);
return elements;
NSMutableArray *elements = [NSMutableArray array];
CGPathApply(path, (__bridge void *)elements, RNSVGBezierElement_getBezierElements);
return elements;
}
@end

View File

@@ -7,6 +7,6 @@
*/
typedef CF_ENUM(int32_t, RNSVGCGFCRule) {
kRNSVGCGFCRuleEvenodd,
kRNSVGCGFCRuleNonzero
kRNSVGCGFCRuleEvenodd,
kRNSVGCGFCRuleNonzero
};

View File

@@ -1,178 +1,192 @@
#import "RNSVGPainterBrush.h"
#import "RNSVGSolidColorBrush.h"
#import "RNSVGContextBrush.h"
#import "RNSVGRenderable.h"
#import "RNSVGLength.h"
#import "RNSVGVBMOS.h"
#import "RNSVGGroup.h"
#import "RNSVGLength.h"
#import "RNSVGPainterBrush.h"
#import "RNSVGRenderable.h"
#import "RNSVGSolidColorBrush.h"
#import "RNSVGVBMOS.h"
#import "RCTFabricComponentsPlugins.h"
#import "RCTConversions.h"
#import "RCTFabricComponentsPlugins.h"
template<typename T>
template <typename T>
RNSVGBrush *brushFromColorStruct(T fillObject)
{
int type = fillObject.type;
int type = fillObject.type;
switch (type) {
case -1: // empty struct
return nil;
case 0: // solid color
{
// These are probably expensive allocations since it's often the same value.
// We should memoize colors but look ups may be just as expensive.
RNSVGColor *color = RCTUIColorFromSharedColor(fillObject.value) ?: [RNSVGColor clearColor];
return [[RNSVGSolidColorBrush alloc] initWithColor:color];
}
case 1: // brush
{
NSArray *arr = @[@(type), RCTNSStringFromString(fillObject.brushRef)];
return [[RNSVGPainterBrush alloc] initWithArray:arr];
}
case 2: // currentColor
return [[RNSVGBrush alloc] initWithArray:nil];
case 3: // context-fill
return [[RNSVGContextBrush alloc] initFill];
case 4: // context-stroke
return [[RNSVGContextBrush alloc] initStroke];
default:
RCTLogError(@"Unknown brush type: %d", type);
return nil;
switch (type) {
case -1: // empty struct
return nil;
case 0: // solid color
{
// These are probably expensive allocations since it's often the same value.
// We should memoize colors but look ups may be just as expensive.
RNSVGColor *color = RCTUIColorFromSharedColor(fillObject.value) ?: [RNSVGColor clearColor];
return [[RNSVGSolidColorBrush alloc] initWithColor:color];
}
case 1: // brush
{
NSArray *arr = @[ @(type), RCTNSStringFromString(fillObject.brushRef) ];
return [[RNSVGPainterBrush alloc] initWithArray:arr];
}
case 2: // currentColor
return [[RNSVGBrush alloc] initWithArray:nil];
case 3: // context-fill
return [[RNSVGContextBrush alloc] initFill];
case 4: // context-stroke
return [[RNSVGContextBrush alloc] initStroke];
default:
RCTLogError(@"Unknown brush type: %d", type);
return nil;
}
}
template<typename T>
template <typename T>
void setCommonNodeProps(T nodeProps, RNSVGNode *node)
{
node.name = RCTNSStringFromStringNilIfEmpty(nodeProps.name);
node.opacity = nodeProps.opacity;
if (nodeProps.matrix.size() == 6) {
node.matrix = CGAffineTransformMake(nodeProps.matrix.at(0), nodeProps.matrix.at(1), nodeProps.matrix.at(2), nodeProps.matrix.at(3), nodeProps.matrix.at(4), nodeProps.matrix.at(5));
}
CATransform3D transform3d = RCTCATransform3DFromTransformMatrix(nodeProps.transform);
CGAffineTransform transform = CATransform3DGetAffineTransform(transform3d);
node.invTransform = CGAffineTransformInvert(transform);
node.transforms = transform;
node.mask = RCTNSStringFromStringNilIfEmpty(nodeProps.mask);
node.markerStart = RCTNSStringFromStringNilIfEmpty(nodeProps.markerStart);
node.markerMid = RCTNSStringFromStringNilIfEmpty(nodeProps.markerMid);
node.markerEnd = RCTNSStringFromStringNilIfEmpty(nodeProps.markerEnd);
node.clipPath = RCTNSStringFromStringNilIfEmpty(nodeProps.clipPath);
node.clipRule = nodeProps.clipRule == 0 ? kRNSVGCGFCRuleEvenodd : kRNSVGCGFCRuleNonzero;
node.responsible = nodeProps.responsible;
// onLayout
node.display = RCTNSStringFromStringNilIfEmpty(nodeProps.display);
switch (nodeProps.pointerEvents) {
case facebook::react::PointerEventsMode::Auto:
node.pointerEvents = RCTPointerEventsUnspecified;
case facebook::react::PointerEventsMode::None:
node.pointerEvents = RCTPointerEventsNone;
case facebook::react::PointerEventsMode::BoxNone:
node.pointerEvents = RCTPointerEventsBoxNone;
case facebook::react::PointerEventsMode::BoxOnly:
node.pointerEvents = RCTPointerEventsBoxOnly;
default:
node.pointerEvents = RCTPointerEventsUnspecified;
}
node.name = RCTNSStringFromStringNilIfEmpty(nodeProps.name);
node.opacity = nodeProps.opacity;
if (nodeProps.matrix.size() == 6) {
node.matrix = CGAffineTransformMake(
nodeProps.matrix.at(0),
nodeProps.matrix.at(1),
nodeProps.matrix.at(2),
nodeProps.matrix.at(3),
nodeProps.matrix.at(4),
nodeProps.matrix.at(5));
}
CATransform3D transform3d = RCTCATransform3DFromTransformMatrix(nodeProps.transform);
CGAffineTransform transform = CATransform3DGetAffineTransform(transform3d);
node.invTransform = CGAffineTransformInvert(transform);
node.transforms = transform;
node.mask = RCTNSStringFromStringNilIfEmpty(nodeProps.mask);
node.markerStart = RCTNSStringFromStringNilIfEmpty(nodeProps.markerStart);
node.markerMid = RCTNSStringFromStringNilIfEmpty(nodeProps.markerMid);
node.markerEnd = RCTNSStringFromStringNilIfEmpty(nodeProps.markerEnd);
node.clipPath = RCTNSStringFromStringNilIfEmpty(nodeProps.clipPath);
node.clipRule = nodeProps.clipRule == 0 ? kRNSVGCGFCRuleEvenodd : kRNSVGCGFCRuleNonzero;
node.responsible = nodeProps.responsible;
// onLayout
node.display = RCTNSStringFromStringNilIfEmpty(nodeProps.display);
switch (nodeProps.pointerEvents) {
case facebook::react::PointerEventsMode::Auto:
node.pointerEvents = RCTPointerEventsUnspecified;
case facebook::react::PointerEventsMode::None:
node.pointerEvents = RCTPointerEventsNone;
case facebook::react::PointerEventsMode::BoxNone:
node.pointerEvents = RCTPointerEventsBoxNone;
case facebook::react::PointerEventsMode::BoxOnly:
node.pointerEvents = RCTPointerEventsBoxOnly;
default:
node.pointerEvents = RCTPointerEventsUnspecified;
}
}
static NSMutableArray<RNSVGLength *> *createLengthArrayFromStrings(std::vector<std::string> stringArray) {
if (stringArray.empty()) {
return nil;
}
NSMutableArray<RNSVGLength *> *lengthArray = [NSMutableArray new];
for (auto str : stringArray) {
RNSVGLength *lengthFromString = [RNSVGLength lengthWithString:RCTNSStringFromString(str)];
[lengthArray addObject:lengthFromString];
}
return lengthArray;
static NSMutableArray<RNSVGLength *> *createLengthArrayFromStrings(std::vector<std::string> stringArray)
{
if (stringArray.empty()) {
return nil;
}
NSMutableArray<RNSVGLength *> *lengthArray = [NSMutableArray new];
for (auto str : stringArray) {
RNSVGLength *lengthFromString = [RNSVGLength lengthWithString:RCTNSStringFromString(str)];
[lengthArray addObject:lengthFromString];
}
return lengthArray;
}
template<typename T>
template <typename T>
void setCommonRenderableProps(T renderableProps, RNSVGRenderable *renderableNode)
{
setCommonNodeProps(renderableProps, renderableNode);
renderableNode.fill = brushFromColorStruct(renderableProps.fill);
renderableNode.fillOpacity = renderableProps.fillOpacity;
renderableNode.fillRule = renderableProps.fillRule == 0 ? kRNSVGCGFCRuleEvenodd : kRNSVGCGFCRuleNonzero;
renderableNode.stroke = brushFromColorStruct(renderableProps.stroke);
renderableNode.strokeOpacity = renderableProps.strokeOpacity;
renderableNode.strokeWidth = [RNSVGLength lengthWithString:RCTNSStringFromString(renderableProps.strokeWidth)];
renderableNode.strokeLinecap = renderableProps.strokeLinecap == 0 ? kCGLineCapButt : renderableProps.strokeLinecap == 1 ? kCGLineCapRound : kCGLineCapSquare;
renderableNode.strokeLinejoin = renderableProps.strokeLinejoin == 0 ? kCGLineJoinMiter : renderableProps.strokeLinejoin == 1 ? kCGLineJoinRound : kCGLineJoinBevel;
renderableNode.strokeDasharray = createLengthArrayFromStrings(renderableProps.strokeDasharray);
renderableNode.strokeDashoffset = renderableProps.strokeDashoffset;
renderableNode.strokeMiterlimit = renderableProps.strokeMiterlimit;
renderableNode.vectorEffect = renderableProps.vectorEffect == 0 ? kRNSVGVectorEffectDefault : renderableProps.vectorEffect == 1 ? kRNSVGVectorEffectNonScalingStroke : renderableProps.vectorEffect == 2 ? kRNSVGVectorEffectInherit : kRNSVGVectorEffectUri;
if (renderableProps.propList.size() > 0) {
NSMutableArray<NSString *> *propArray = [NSMutableArray new];
for (auto str : renderableProps.propList) {
[propArray addObject:RCTNSStringFromString(str)];
}
renderableNode.propList = propArray;
setCommonNodeProps(renderableProps, renderableNode);
renderableNode.fill = brushFromColorStruct(renderableProps.fill);
renderableNode.fillOpacity = renderableProps.fillOpacity;
renderableNode.fillRule = renderableProps.fillRule == 0 ? kRNSVGCGFCRuleEvenodd : kRNSVGCGFCRuleNonzero;
renderableNode.stroke = brushFromColorStruct(renderableProps.stroke);
renderableNode.strokeOpacity = renderableProps.strokeOpacity;
renderableNode.strokeWidth = [RNSVGLength lengthWithString:RCTNSStringFromString(renderableProps.strokeWidth)];
renderableNode.strokeLinecap = renderableProps.strokeLinecap == 0 ? kCGLineCapButt
: renderableProps.strokeLinecap == 1 ? kCGLineCapRound
: kCGLineCapSquare;
renderableNode.strokeLinejoin = renderableProps.strokeLinejoin == 0 ? kCGLineJoinMiter
: renderableProps.strokeLinejoin == 1 ? kCGLineJoinRound
: kCGLineJoinBevel;
renderableNode.strokeDasharray = createLengthArrayFromStrings(renderableProps.strokeDasharray);
renderableNode.strokeDashoffset = renderableProps.strokeDashoffset;
renderableNode.strokeMiterlimit = renderableProps.strokeMiterlimit;
renderableNode.vectorEffect = renderableProps.vectorEffect == 0 ? kRNSVGVectorEffectDefault
: renderableProps.vectorEffect == 1 ? kRNSVGVectorEffectNonScalingStroke
: renderableProps.vectorEffect == 2 ? kRNSVGVectorEffectInherit
: kRNSVGVectorEffectUri;
if (renderableProps.propList.size() > 0) {
NSMutableArray<NSString *> *propArray = [NSMutableArray new];
for (auto str : renderableProps.propList) {
[propArray addObject:RCTNSStringFromString(str)];
}
renderableNode.propList = propArray;
}
}
static void addValueToDict(NSMutableDictionary *dict, std::string value, NSString *key)
{
NSString *valueOrNil = RCTNSStringFromStringNilIfEmpty(value);
if (valueOrNil) {
dict[key] = valueOrNil;
}
NSString *valueOrNil = RCTNSStringFromStringNilIfEmpty(value);
if (valueOrNil) {
dict[key] = valueOrNil;
}
}
template<typename T>
template <typename T>
NSDictionary *parseFontStruct(T fontStruct)
{
NSMutableDictionary *fontDict = [NSMutableDictionary new];
// TODO: do it better maybe
addValueToDict(fontDict, fontStruct.fontStyle, @"fontStyle");
addValueToDict(fontDict, fontStruct.fontVariant, @"fontVariant");
addValueToDict(fontDict, fontStruct.fontWeight, @"fontWeight");
addValueToDict(fontDict, fontStruct.fontStretch, @"fontStretch");
addValueToDict(fontDict, fontStruct.fontSize, @"fontSize");
addValueToDict(fontDict, fontStruct.fontFamily, @"fontFamily");
addValueToDict(fontDict, fontStruct.textAnchor, @"textAnchor");
addValueToDict(fontDict, fontStruct.textDecoration, @"textDecoration");
addValueToDict(fontDict, fontStruct.letterSpacing, @"letterSpacing");
addValueToDict(fontDict, fontStruct.wordSpacing, @"wordSpacing");
addValueToDict(fontDict, fontStruct.kerning, @"kerning");
addValueToDict(fontDict, fontStruct.fontFeatureSettings, @"fontFeatureSettings");
addValueToDict(fontDict, fontStruct.fontVariantLigatures, @"fontVariantLigatures");
addValueToDict(fontDict, fontStruct.fontVariationSettings, @"fontVariationSettings");
return [NSDictionary dictionaryWithDictionary:fontDict];
NSMutableDictionary *fontDict = [NSMutableDictionary new];
// TODO: do it better maybe
addValueToDict(fontDict, fontStruct.fontStyle, @"fontStyle");
addValueToDict(fontDict, fontStruct.fontVariant, @"fontVariant");
addValueToDict(fontDict, fontStruct.fontWeight, @"fontWeight");
addValueToDict(fontDict, fontStruct.fontStretch, @"fontStretch");
addValueToDict(fontDict, fontStruct.fontSize, @"fontSize");
addValueToDict(fontDict, fontStruct.fontFamily, @"fontFamily");
addValueToDict(fontDict, fontStruct.textAnchor, @"textAnchor");
addValueToDict(fontDict, fontStruct.textDecoration, @"textDecoration");
addValueToDict(fontDict, fontStruct.letterSpacing, @"letterSpacing");
addValueToDict(fontDict, fontStruct.wordSpacing, @"wordSpacing");
addValueToDict(fontDict, fontStruct.kerning, @"kerning");
addValueToDict(fontDict, fontStruct.fontFeatureSettings, @"fontFeatureSettings");
addValueToDict(fontDict, fontStruct.fontVariantLigatures, @"fontVariantLigatures");
addValueToDict(fontDict, fontStruct.fontVariationSettings, @"fontVariationSettings");
return [NSDictionary dictionaryWithDictionary:fontDict];
}
template<typename T>
template <typename T>
void setCommonGroupProps(T groupProps, RNSVGGroup *groupNode)
{
setCommonRenderableProps(groupProps, groupNode);
if (RCTNSStringFromStringNilIfEmpty(groupProps.fontSize)) {
groupNode.font = @{ @"fontSize": RCTNSStringFromString(groupProps.fontSize) };
}
if (RCTNSStringFromStringNilIfEmpty(groupProps.fontWeight)) {
groupNode.font = @{ @"fontWeight": RCTNSStringFromString(groupProps.fontWeight) };
}
NSDictionary *fontDict = parseFontStruct(groupProps.font);
if (groupNode.font == nil || fontDict.count > 0) {
// some of text's rendering logic requires that `font` is not nil so we always set it
// even if to an empty dict
groupNode.font = fontDict;
}
setCommonRenderableProps(groupProps, groupNode);
if (RCTNSStringFromStringNilIfEmpty(groupProps.fontSize)) {
groupNode.font = @{@"fontSize" : RCTNSStringFromString(groupProps.fontSize)};
}
if (RCTNSStringFromStringNilIfEmpty(groupProps.fontWeight)) {
groupNode.font = @{@"fontWeight" : RCTNSStringFromString(groupProps.fontWeight)};
}
NSDictionary *fontDict = parseFontStruct(groupProps.font);
if (groupNode.font == nil || fontDict.count > 0) {
// some of text's rendering logic requires that `font` is not nil so we always set it
// even if to an empty dict
groupNode.font = fontDict;
}
}
static RNSVGVBMOS intToRNSVGVBMOS(int value)
{
switch (value) {
case 0:
return kRNSVGVBMOSMeet;
case 1:
return kRNSVGVBMOSSlice;
case 2:
return kRNSVGVBMOSNone;
default:
return kRNSVGVBMOSMeet;
}
switch (value) {
case 0:
return kRNSVGVBMOSMeet;
case 1:
return kRNSVGVBMOSSlice;
case 2:
return kRNSVGVBMOSNone;
default:
return kRNSVGVBMOSMeet;
}
}

View File

@@ -5,17 +5,17 @@
// https://www.w3.org/TR/SVG/types.html#InterfaceSVGLength
typedef CF_ENUM(unsigned short, RNSVGLengthUnitType) {
SVG_LENGTHTYPE_UNKNOWN,
SVG_LENGTHTYPE_NUMBER,
SVG_LENGTHTYPE_PERCENTAGE,
SVG_LENGTHTYPE_EMS,
SVG_LENGTHTYPE_EXS,
SVG_LENGTHTYPE_PX,
SVG_LENGTHTYPE_CM,
SVG_LENGTHTYPE_MM,
SVG_LENGTHTYPE_IN,
SVG_LENGTHTYPE_PT,
SVG_LENGTHTYPE_PC,
SVG_LENGTHTYPE_UNKNOWN,
SVG_LENGTHTYPE_NUMBER,
SVG_LENGTHTYPE_PERCENTAGE,
SVG_LENGTHTYPE_EMS,
SVG_LENGTHTYPE_EXS,
SVG_LENGTHTYPE_PX,
SVG_LENGTHTYPE_CM,
SVG_LENGTHTYPE_MM,
SVG_LENGTHTYPE_IN,
SVG_LENGTHTYPE_PT,
SVG_LENGTHTYPE_PC,
};
@interface RNSVGLength : NSObject
@@ -23,9 +23,9 @@ typedef CF_ENUM(unsigned short, RNSVGLengthUnitType) {
@property (nonatomic, assign) CGFloat value;
@property (nonatomic, assign) RNSVGLengthUnitType unit;
+ (instancetype) lengthWithNumber: (CGFloat) number;
+ (instancetype) lengthWithString: (NSString *) lengthString;
- (BOOL) isEqualTo: (RNSVGLength *)other;
+ (instancetype)lengthWithNumber:(CGFloat)number;
+ (instancetype)lengthWithString:(NSString *)lengthString;
- (BOOL)isEqualTo:(RNSVGLength *)other;
@end

View File

@@ -1,75 +1,76 @@
#import <Foundation/Foundation.h>
#import "RNSVGLength.h"
#import <Foundation/Foundation.h>
@implementation RNSVGLength
- (instancetype) init
- (instancetype)init
{
self = [super init];
if (self)
{
_value = 0;
_unit = SVG_LENGTHTYPE_UNKNOWN;
}
return self;
self = [super init];
if (self) {
_value = 0;
_unit = SVG_LENGTHTYPE_UNKNOWN;
}
return self;
}
+ (instancetype) lengthWithNumber:(CGFloat)number
+ (instancetype)lengthWithNumber:(CGFloat)number
{
RNSVGLength *length = [[self alloc] init];
length.unit = SVG_LENGTHTYPE_NUMBER;
length.value = number;
return length;
RNSVGLength *length = [[self alloc] init];
length.unit = SVG_LENGTHTYPE_NUMBER;
length.value = number;
return length;
}
+ (instancetype) lengthWithString: (NSString *) lengthString {
NSString *length = [lengthString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSUInteger stringLength = [length length];
NSInteger percentIndex = stringLength - 1;
RNSVGLength *output = [RNSVGLength alloc];
if (stringLength == 0) {
output.unit = SVG_LENGTHTYPE_UNKNOWN;
output.value = 0;
return output;
} else if ([length characterAtIndex:percentIndex] == '%') {
output.unit = SVG_LENGTHTYPE_PERCENTAGE;
output.value = (CGFloat)[[length substringWithRange:NSMakeRange(0, percentIndex)] doubleValue];
} else {
NSInteger twoLetterUnitIndex = stringLength - 2;
RNSVGLengthUnitType unit = SVG_LENGTHTYPE_NUMBER;
if (twoLetterUnitIndex > 0) {
NSString *lastTwo = [length substringFromIndex:twoLetterUnitIndex];
NSUInteger end = twoLetterUnitIndex;
if ([lastTwo isEqualToString:@"px"]) {
unit = SVG_LENGTHTYPE_PX;
} else if ([lastTwo isEqualToString:@"em"]) {
unit = SVG_LENGTHTYPE_EMS;
} else if ([lastTwo isEqualToString:@"ex"]) {
unit = SVG_LENGTHTYPE_EXS;
} else if ([lastTwo isEqualToString:@"pt"]) {
unit = SVG_LENGTHTYPE_PT;
} else if ([lastTwo isEqualToString:@"pc"]) {
unit = SVG_LENGTHTYPE_PC;
} else if ([lastTwo isEqualToString:@"mm"]) {
unit = SVG_LENGTHTYPE_MM;
} else if ([lastTwo isEqualToString:@"cm"]) {
unit = SVG_LENGTHTYPE_CM;
} else if ([lastTwo isEqualToString:@"in"]) {
unit = SVG_LENGTHTYPE_IN;
} else {
end = stringLength;
}
output.value = (CGFloat)[[length substringWithRange:NSMakeRange(0, end)] doubleValue];
} else {
output.value = (CGFloat)[length doubleValue];
}
output.unit = unit;
}
+ (instancetype)lengthWithString:(NSString *)lengthString
{
NSString *length = [lengthString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSUInteger stringLength = [length length];
NSInteger percentIndex = stringLength - 1;
RNSVGLength *output = [RNSVGLength alloc];
if (stringLength == 0) {
output.unit = SVG_LENGTHTYPE_UNKNOWN;
output.value = 0;
return output;
} else if ([length characterAtIndex:percentIndex] == '%') {
output.unit = SVG_LENGTHTYPE_PERCENTAGE;
output.value = (CGFloat)[[length substringWithRange:NSMakeRange(0, percentIndex)] doubleValue];
} else {
NSInteger twoLetterUnitIndex = stringLength - 2;
RNSVGLengthUnitType unit = SVG_LENGTHTYPE_NUMBER;
if (twoLetterUnitIndex > 0) {
NSString *lastTwo = [length substringFromIndex:twoLetterUnitIndex];
NSUInteger end = twoLetterUnitIndex;
if ([lastTwo isEqualToString:@"px"]) {
unit = SVG_LENGTHTYPE_PX;
} else if ([lastTwo isEqualToString:@"em"]) {
unit = SVG_LENGTHTYPE_EMS;
} else if ([lastTwo isEqualToString:@"ex"]) {
unit = SVG_LENGTHTYPE_EXS;
} else if ([lastTwo isEqualToString:@"pt"]) {
unit = SVG_LENGTHTYPE_PT;
} else if ([lastTwo isEqualToString:@"pc"]) {
unit = SVG_LENGTHTYPE_PC;
} else if ([lastTwo isEqualToString:@"mm"]) {
unit = SVG_LENGTHTYPE_MM;
} else if ([lastTwo isEqualToString:@"cm"]) {
unit = SVG_LENGTHTYPE_CM;
} else if ([lastTwo isEqualToString:@"in"]) {
unit = SVG_LENGTHTYPE_IN;
} else {
end = stringLength;
}
output.value = (CGFloat)[[length substringWithRange:NSMakeRange(0, end)] doubleValue];
} else {
output.value = (CGFloat)[length doubleValue];
}
output.unit = unit;
}
return output;
}
- (BOOL) isEqualTo: (RNSVGLength *)other {
return self.unit == other.unit && self.value == other.value;
- (BOOL)isEqualTo:(RNSVGLength *)other
{
return self.unit == other.unit && self.value == other.value;
}
@end

View File

@@ -2,11 +2,7 @@
#import "RNSVGUIKit.h"
typedef enum RNSVGMarkerType {
kStartMarker,
kMidMarker,
kEndMarker
} RNSVGMarkerType;
typedef enum RNSVGMarkerType { kStartMarker, kMidMarker, kEndMarker } RNSVGMarkerType;
#define RNSVGZEROPOINT CGRectZero.origin
@@ -18,8 +14,8 @@ typedef enum RNSVGMarkerType {
@property (nonatomic, assign) float angle;
// Instance creation
+ (instancetype) markerPosition:(RNSVGMarkerType)type origin:(CGPoint)origin angle:(float)angle;
+ (instancetype)markerPosition:(RNSVGMarkerType)type origin:(CGPoint)origin angle:(float)angle;
+ (NSArray<RNSVGMarkerPosition*>*) fromCGPath:(CGPathRef)path;
+ (NSArray<RNSVGMarkerPosition *> *)fromCGPath:(CGPathRef)path;
@end

View File

@@ -2,155 +2,161 @@
#import "RNSVGMarkerPosition.h"
@implementation RNSVGMarkerPosition
- (instancetype) init
- (instancetype)init
{
self = [super init];
if (self)
{
_type = kStartMarker;
_origin = RNSVGZEROPOINT;
_angle = 0;
}
return self;
self = [super init];
if (self) {
_type = kStartMarker;
_origin = RNSVGZEROPOINT;
_angle = 0;
}
return self;
}
+ (instancetype) markerPosition:(RNSVGMarkerType)type origin:(CGPoint)origin angle:(float)angle {
RNSVGMarkerPosition *newElement = [[self alloc] init];
newElement.type = type;
newElement.origin = origin;
newElement.angle = angle;
return newElement;
+ (instancetype)markerPosition:(RNSVGMarkerType)type origin:(CGPoint)origin angle:(float)angle
{
RNSVGMarkerPosition *newElement = [[self alloc] init];
newElement.type = type;
newElement.origin = origin;
newElement.angle = angle;
return newElement;
}
+ (NSArray<RNSVGMarkerPosition*>*) fromCGPath:(CGPathRef)path {
positions_ = [[NSMutableArray alloc] init];
element_index_ = 0;
origin_ = RNSVGZEROPOINT;
subpath_start_ = RNSVGZEROPOINT;
CGPathApply(path, (__bridge void *)positions_, UpdateFromPathElement);
PathIsDone();
return positions_;
+ (NSArray<RNSVGMarkerPosition *> *)fromCGPath:(CGPathRef)path
{
positions_ = [[NSMutableArray alloc] init];
element_index_ = 0;
origin_ = RNSVGZEROPOINT;
subpath_start_ = RNSVGZEROPOINT;
CGPathApply(path, (__bridge void *)positions_, UpdateFromPathElement);
PathIsDone();
return positions_;
}
void PathIsDone() {
float angle = CurrentAngle(kEndMarker);
[positions_ addObject:[RNSVGMarkerPosition markerPosition:kEndMarker origin:origin_ angle:angle]];
void PathIsDone()
{
float angle = CurrentAngle(kEndMarker);
[positions_ addObject:[RNSVGMarkerPosition markerPosition:kEndMarker origin:origin_ angle:angle]];
}
static double BisectingAngle(double in_angle, double out_angle) {
// WK193015: Prevent bugs due to angles being non-continuous.
if (fabs(in_angle - out_angle) > 180)
in_angle += 360;
return (in_angle + out_angle) / 2;
static double BisectingAngle(double in_angle, double out_angle)
{
// WK193015: Prevent bugs due to angles being non-continuous.
if (fabs(in_angle - out_angle) > 180)
in_angle += 360;
return (in_angle + out_angle) / 2;
}
static CGFloat RNSVG_radToDeg = 180 / (CGFloat)M_PI;
double rad2deg(CGFloat rad) {
return rad * RNSVG_radToDeg;
double rad2deg(CGFloat rad)
{
return rad * RNSVG_radToDeg;
}
CGFloat SlopeAngleRadians(CGSize p) {
CGFloat angle = atan2(p.height, p.width);
return angle;
CGFloat SlopeAngleRadians(CGSize p)
{
CGFloat angle = atan2(p.height, p.width);
return angle;
}
double CurrentAngle(RNSVGMarkerType type) {
// For details of this calculation, see:
// http://www.w3.org/TR/SVG/single-page.html#painting-MarkerElement
double in_angle = rad2deg(SlopeAngleRadians(in_slope_));
double out_angle = rad2deg(SlopeAngleRadians(out_slope_));
switch (type) {
case kStartMarker:
if (auto_start_reverse_)
out_angle += 180;
return out_angle;
case kMidMarker:
return BisectingAngle(in_angle, out_angle);
case kEndMarker:
return in_angle;
}
return 0;
double CurrentAngle(RNSVGMarkerType type)
{
// For details of this calculation, see:
// http://www.w3.org/TR/SVG/single-page.html#painting-MarkerElement
double in_angle = rad2deg(SlopeAngleRadians(in_slope_));
double out_angle = rad2deg(SlopeAngleRadians(out_slope_));
switch (type) {
case kStartMarker:
if (auto_start_reverse_)
out_angle += 180;
return out_angle;
case kMidMarker:
return BisectingAngle(in_angle, out_angle);
case kEndMarker:
return in_angle;
}
return 0;
}
typedef struct SegmentData {
CGSize start_tangent; // Tangent in the start point of the segment.
CGSize end_tangent; // Tangent in the end point of the segment.
CGPoint position; // The end point of the segment.
CGSize start_tangent; // Tangent in the start point of the segment.
CGSize end_tangent; // Tangent in the end point of the segment.
CGPoint position; // The end point of the segment.
} SegmentData;
CGSize subtract(CGPoint* p1, CGPoint* p2) {
return CGSizeMake(p2->x - p1->x, p2->y - p1->y);
CGSize subtract(CGPoint *p1, CGPoint *p2)
{
return CGSizeMake(p2->x - p1->x, p2->y - p1->y);
}
static void ComputeQuadTangents(SegmentData* data,
CGPoint* start,
CGPoint* control,
CGPoint* end) {
data->start_tangent = subtract(control, start);
data->end_tangent = subtract(end, control);
if (CGSizeEqualToSize(CGSizeZero, data->start_tangent))
data->start_tangent = data->end_tangent;
else if (CGSizeEqualToSize(CGSizeZero, data->end_tangent))
data->end_tangent = data->start_tangent;
static void ComputeQuadTangents(SegmentData *data, CGPoint *start, CGPoint *control, CGPoint *end)
{
data->start_tangent = subtract(control, start);
data->end_tangent = subtract(end, control);
if (CGSizeEqualToSize(CGSizeZero, data->start_tangent))
data->start_tangent = data->end_tangent;
else if (CGSizeEqualToSize(CGSizeZero, data->end_tangent))
data->end_tangent = data->start_tangent;
}
SegmentData ExtractPathElementFeatures(const CGPathElement* element) {
SegmentData data;
CGPoint* points = element->points;
switch (element->type) {
case kCGPathElementAddCurveToPoint:
data.position = points[2];
data.start_tangent = subtract(&points[0], &origin_);
data.end_tangent = subtract(&points[2], &points[1]);
if (CGSizeEqualToSize(CGSizeZero, data.start_tangent))
ComputeQuadTangents(&data, &points[0], &points[1], &points[2]);
else if (CGSizeEqualToSize(CGSizeZero, data.end_tangent))
ComputeQuadTangents(&data, &origin_, &points[0], &points[1]);
break;
case kCGPathElementAddQuadCurveToPoint:
data.position = points[1];
ComputeQuadTangents(&data, &origin_, &points[0], &points[1]);
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
data.position = points[0];
data.start_tangent = subtract(&data.position, &origin_);
data.end_tangent = subtract(&data.position, &origin_);
break;
case kCGPathElementCloseSubpath:
data.position = subpath_start_;
data.start_tangent = subtract(&data.position, &origin_);
data.end_tangent = subtract(&data.position, &origin_);
break;
}
return data;
SegmentData ExtractPathElementFeatures(const CGPathElement *element)
{
SegmentData data;
CGPoint *points = element->points;
switch (element->type) {
case kCGPathElementAddCurveToPoint:
data.position = points[2];
data.start_tangent = subtract(&points[0], &origin_);
data.end_tangent = subtract(&points[2], &points[1]);
if (CGSizeEqualToSize(CGSizeZero, data.start_tangent))
ComputeQuadTangents(&data, &points[0], &points[1], &points[2]);
else if (CGSizeEqualToSize(CGSizeZero, data.end_tangent))
ComputeQuadTangents(&data, &origin_, &points[0], &points[1]);
break;
case kCGPathElementAddQuadCurveToPoint:
data.position = points[1];
ComputeQuadTangents(&data, &origin_, &points[0], &points[1]);
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
data.position = points[0];
data.start_tangent = subtract(&data.position, &origin_);
data.end_tangent = subtract(&data.position, &origin_);
break;
case kCGPathElementCloseSubpath:
data.position = subpath_start_;
data.start_tangent = subtract(&data.position, &origin_);
data.end_tangent = subtract(&data.position, &origin_);
break;
}
return data;
}
void UpdateFromPathElement(void *info, const CGPathElement *element) {
SegmentData segment_data = ExtractPathElementFeatures(element);
// First update the outgoing slope for the previous element.
out_slope_ = segment_data.start_tangent;
// Record the marker for the previous element.
if (element_index_ > 0) {
RNSVGMarkerType marker_type =
element_index_ == 1 ? kStartMarker : kMidMarker;
float angle = CurrentAngle(marker_type);
[positions_ addObject:[RNSVGMarkerPosition markerPosition:marker_type origin:origin_ angle:angle]];
}
// Update the incoming slope for this marker position.
in_slope_ = segment_data.end_tangent;
// Update marker position.
origin_ = segment_data.position;
// If this is a 'move to' segment, save the point for use with 'close'.
if (element->type == kCGPathElementMoveToPoint)
subpath_start_ = element->points[0];
else if (element->type == kCGPathElementCloseSubpath)
subpath_start_ = CGPointZero;
++element_index_;
void UpdateFromPathElement(void *info, const CGPathElement *element)
{
SegmentData segment_data = ExtractPathElementFeatures(element);
// First update the outgoing slope for the previous element.
out_slope_ = segment_data.start_tangent;
// Record the marker for the previous element.
if (element_index_ > 0) {
RNSVGMarkerType marker_type = element_index_ == 1 ? kStartMarker : kMidMarker;
float angle = CurrentAngle(marker_type);
[positions_ addObject:[RNSVGMarkerPosition markerPosition:marker_type origin:origin_ angle:angle]];
}
// Update the incoming slope for this marker position.
in_slope_ = segment_data.end_tangent;
// Update marker position.
origin_ = segment_data.position;
// If this is a 'move to' segment, save the point for use with 'close'.
if (element->type == kCGPathElementMoveToPoint)
subpath_start_ = element->points[0];
else if (element->type == kCGPathElementCloseSubpath)
subpath_start_ = CGPointZero;
++element_index_;
}
NSMutableArray<RNSVGMarkerPosition*>* positions_;
NSMutableArray<RNSVGMarkerPosition *> *positions_;
unsigned element_index_;
CGPoint origin_;
CGPoint subpath_start_;

View File

@@ -6,7 +6,10 @@
## License
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/us/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/">Creative Commons Attribution 3.0 United States License</a>.
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/"><img alt="Creative Commons License"
style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/us/88x31.png" /></a><br />This work is licensed
under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/">Creative Commons Attribution 3.0 United
States License</a>.
For attribution, please include:
@@ -22,9 +25,9 @@ static CGFloat idealFlatness = (CGFloat).01;
*/
static CGFloat distance(CGPoint p1, CGPoint p2)
{
CGFloat dx = p2.x - p1.x;
CGFloat dy = p2.y - p1.y;
return hypot(dx, dy);
CGFloat dx = p2.x - p1.x;
CGFloat dy = p2.y - p1.y;
return hypot(dx, dy);
}
// Subdivide a Bézier (specific division)
@@ -60,168 +63,164 @@ static CGFloat distance(CGPoint p1, CGPoint p2)
*/
static void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t)
{
CGPoint q;
CGFloat mt = 1 - t;
CGPoint q;
CGFloat mt = 1 - t;
bez1[0].x = bez[0].x;
bez1[0].y = bez[0].y;
bez2[3].x = bez[3].x;
bez2[3].y = bez[3].y;
bez1[0].x = bez[0].x;
bez1[0].y = bez[0].y;
bez2[3].x = bez[3].x;
bez2[3].y = bez[3].y;
q.x = mt * bez[1].x + t * bez[2].x;
q.y = mt * bez[1].y + t * bez[2].y;
bez1[1].x = mt * bez[0].x + t * bez[1].x;
bez1[1].y = mt * bez[0].y + t * bez[1].y;
bez2[2].x = mt * bez[2].x + t * bez[3].x;
bez2[2].y = mt * bez[2].y + t * bez[3].y;
q.x = mt * bez[1].x + t * bez[2].x;
q.y = mt * bez[1].y + t * bez[2].y;
bez1[1].x = mt * bez[0].x + t * bez[1].x;
bez1[1].y = mt * bez[0].y + t * bez[1].y;
bez2[2].x = mt * bez[2].x + t * bez[3].x;
bez2[2].y = mt * bez[2].y + t * bez[3].y;
bez1[2].x = mt * bez1[1].x + t * q.x;
bez1[2].y = mt * bez1[1].y + t * q.y;
bez2[1].x = mt * q.x + t * bez2[2].x;
bez2[1].y = mt * q.y + t * bez2[2].y;
bez1[2].x = mt * bez1[1].x + t * q.x;
bez1[2].y = mt * bez1[1].y + t * q.y;
bez2[1].x = mt * q.x + t * bez2[2].x;
bez2[1].y = mt * q.y + t * bez2[2].y;
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
}
@implementation RNSVGPathMeasure
- (void)addLine:(CGPoint *)last next:(const CGPoint *)next {
NSArray *line = @[[NSValue valueWithCGPoint:*last], [NSValue valueWithCGPoint:*next]];
_pathLength += distance(*last, *next);
[_lengths addObject:[NSNumber numberWithDouble:_pathLength]];
[_lines addObject:line];
*last = *next;
- (void)addLine:(CGPoint *)last next:(const CGPoint *)next
{
NSArray *line = @[ [NSValue valueWithCGPoint:*last], [NSValue valueWithCGPoint:*next] ];
_pathLength += distance(*last, *next);
[_lengths addObject:[NSNumber numberWithDouble:_pathLength]];
[_lines addObject:line];
*last = *next;
}
- (void)reset {
_lengths = nil;
_lines = nil;
_isClosed = NO;
_lineCount = 0;
_pathLength = 0;
_path = nil;
- (void)reset
{
_lengths = nil;
_lines = nil;
_isClosed = NO;
_lineCount = 0;
_pathLength = 0;
_path = nil;
}
- (void)extractPathData:(CGPathRef)path {
if (path == _path) {
return;
}
_path = path;
CGPoint origin = CGPointMake (0.0, 0.0);
CGPoint last = CGPointMake (0.0, 0.0);
_lengths = [NSMutableArray array];
_lines = [NSMutableArray array];
_isClosed = NO;
_lineCount = 0;
_pathLength = 0;
NSArray *elements = [RNSVGBezierElement elementsFromCGPath:path];
for (RNSVGBezierElement *element in elements) {
switch (element.elementType)
{
case kCGPathElementMoveToPoint:
origin = last = element.point;
break;
- (void)extractPathData:(CGPathRef)path
{
if (path == _path) {
return;
}
_path = path;
CGPoint origin = CGPointMake(0.0, 0.0);
CGPoint last = CGPointMake(0.0, 0.0);
_lengths = [NSMutableArray array];
_lines = [NSMutableArray array];
_isClosed = NO;
_lineCount = 0;
_pathLength = 0;
NSArray *elements = [RNSVGBezierElement elementsFromCGPath:path];
for (RNSVGBezierElement *element in elements) {
switch (element.elementType) {
case kCGPathElementMoveToPoint:
origin = last = element.point;
break;
case kCGPathElementAddLineToPoint: {
CGPoint next = element.point;
[self addLine:&last next:&next];
_lineCount++;
break;
}
case kCGPathElementAddQuadCurveToPoint:
case kCGPathElementAddCurveToPoint:
{
// handle both curve types gracefully
CGPoint curveTo = element.point;
CGPoint ctrl1 = element.controlPoint1;
CGPoint ctrl2 = element.elementType == kCGPathElementAddQuadCurveToPoint ? ctrl1 : element.controlPoint2;
case kCGPathElementAddLineToPoint: {
CGPoint next = element.point;
[self addLine:&last next:&next];
_lineCount++;
break;
}
case kCGPathElementAddQuadCurveToPoint:
case kCGPathElementAddCurveToPoint: {
// handle both curve types gracefully
CGPoint curveTo = element.point;
CGPoint ctrl1 = element.controlPoint1;
CGPoint ctrl2 = element.elementType == kCGPathElementAddQuadCurveToPoint ? ctrl1 : element.controlPoint2;
// this is the bezier for our current element
CGPoint bezier[4] = { last, ctrl1, ctrl2, curveTo };
NSValue *arr = [NSValue valueWithBytes:&bezier objCType:@encode(CGPoint[4])];
NSMutableArray *curves = [NSMutableArray arrayWithObjects:arr, nil];
// this is the bezier for our current element
CGPoint bezier[4] = {last, ctrl1, ctrl2, curveTo};
NSValue *arr = [NSValue valueWithBytes:&bezier objCType:@encode(CGPoint[4])];
NSMutableArray *curves = [NSMutableArray arrayWithObjects:arr, nil];
for (NSInteger curveIndex = 0; curveIndex >= 0; curveIndex--) {
CGPoint bez[4];
[curves[curveIndex] getValue:&bez];
[curves removeLastObject];
for (NSInteger curveIndex = 0; curveIndex >= 0; curveIndex--) {
CGPoint bez[4];
[curves[curveIndex] getValue:&bez];
[curves removeLastObject];
// calculate the error rate of the curve vs
// a line segment between the start and end points
CGPoint ctrl1 = bez[1];
CGPoint ctrl2 = bez[2];
CGPoint next = bez[3];
CGFloat polyLen =
distance(last, ctrl1) +
distance(ctrl1, ctrl2) +
distance(ctrl2, next);
CGFloat chordLen = distance(last, next);
CGFloat error = polyLen - chordLen;
// calculate the error rate of the curve vs
// a line segment between the start and end points
CGPoint ctrl1 = bez[1];
CGPoint ctrl2 = bez[2];
CGPoint next = bez[3];
CGFloat polyLen = distance(last, ctrl1) + distance(ctrl1, ctrl2) + distance(ctrl2, next);
CGFloat chordLen = distance(last, next);
CGFloat error = polyLen - chordLen;
// if the error is less than our accepted level of error
// then add a line, else, split the curve in half
if (error <= idealFlatness) {
[self addLine:&last next:&next];
_lineCount++;
} else {
CGPoint bez1[4], bez2[4];
subdivideBezierAtT(bez, bez1, bez2, .5);
[curves addObject:[NSValue valueWithBytes:&bez2 objCType:@encode(CGPoint[4])]];
[curves addObject:[NSValue valueWithBytes:&bez1 objCType:@encode(CGPoint[4])]];
curveIndex += 2;
}
}
break;
}
case kCGPathElementCloseSubpath: {
CGPoint next = origin;
[self addLine:&last next:&next];
_lineCount++;
_isClosed = YES;
break;
}
default:
break;
// if the error is less than our accepted level of error
// then add a line, else, split the curve in half
if (error <= idealFlatness) {
[self addLine:&last next:&next];
_lineCount++;
} else {
CGPoint bez1[4], bez2[4];
subdivideBezierAtT(bez, bez1, bez2, .5);
[curves addObject:[NSValue valueWithBytes:&bez2 objCType:@encode(CGPoint[4])]];
[curves addObject:[NSValue valueWithBytes:&bez1 objCType:@encode(CGPoint[4])]];
curveIndex += 2;
}
}
break;
}
case kCGPathElementCloseSubpath: {
CGPoint next = origin;
[self addLine:&last next:&next];
_lineCount++;
_isClosed = YES;
break;
}
default:
break;
}
}
}
- (void)getPosAndTan:(CGFloat *)angle midPoint:(CGFloat)midPoint x:(CGFloat *)x y:(CGFloat *)y {
// Investigation suggests binary search is faster at lineCount >= 16
// https://gist.github.com/msand/4c7993319425f9d7933be58ad9ada1a4
NSUInteger i = _lineCount < 16 ?
[_lengths
indexOfObjectPassingTest:^(NSNumber* length, NSUInteger index, BOOL * _Nonnull stop) {
BOOL contains = midPoint <= [length doubleValue];
return contains;
}]
:
[_lengths
indexOfObject:[NSNumber numberWithDouble:midPoint]
inSortedRange:NSMakeRange(0, _lineCount)
options:NSBinarySearchingInsertionIndex
usingComparator:^(NSNumber* obj1, NSNumber* obj2) {
return [obj1 compare:obj2];
}];
- (void)getPosAndTan:(CGFloat *)angle midPoint:(CGFloat)midPoint x:(CGFloat *)x y:(CGFloat *)y
{
// Investigation suggests binary search is faster at lineCount >= 16
// https://gist.github.com/msand/4c7993319425f9d7933be58ad9ada1a4
NSUInteger i = _lineCount < 16
? [_lengths indexOfObjectPassingTest:^(NSNumber *length, NSUInteger index, BOOL *_Nonnull stop) {
BOOL contains = midPoint <= [length doubleValue];
return contains;
}]
: [_lengths indexOfObject:[NSNumber numberWithDouble:midPoint]
inSortedRange:NSMakeRange(0, _lineCount)
options:NSBinarySearchingInsertionIndex
usingComparator:^(NSNumber *obj1, NSNumber *obj2) {
return [obj1 compare:obj2];
}];
CGFloat totalLength = (CGFloat)[_lengths[i] doubleValue];
CGFloat prevLength = i == 0 ? 0 : (CGFloat)[_lengths[i - 1] doubleValue];
CGFloat totalLength = (CGFloat)[_lengths[i] doubleValue];
CGFloat prevLength = i == 0 ? 0 : (CGFloat)[_lengths[i - 1] doubleValue];
CGFloat length = totalLength - prevLength;
CGFloat percent = (midPoint - prevLength) / length;
CGFloat length = totalLength - prevLength;
CGFloat percent = (midPoint - prevLength) / length;
NSArray * points = [_lines objectAtIndex: i];
CGPoint p1 = [[points objectAtIndex: 0] CGPointValue];
CGPoint p2 = [[points objectAtIndex: 1] CGPointValue];
NSArray *points = [_lines objectAtIndex:i];
CGPoint p1 = [[points objectAtIndex:0] CGPointValue];
CGPoint p2 = [[points objectAtIndex:1] CGPointValue];
CGFloat ldx = p2.x - p1.x;
CGFloat ldy = p2.y - p1.y;
*angle = atan2(ldy, ldx);
*x = p1.x + ldx * percent;
*y = p1.y + ldy * percent;
CGFloat ldx = p2.x - p1.x;
CGFloat ldy = p2.y - p1.y;
*angle = atan2(ldy, ldx);
*x = p1.x + ldx * percent;
*y = p1.y + ldy * percent;
}
@end

View File

@@ -10,7 +10,7 @@
@interface RNSVGPathParser : NSObject
- (instancetype) initWithPathString:(NSString *)d;
- (instancetype)initWithPathString:(NSString *)d;
- (CGPathRef)getPath;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,6 @@
*/
typedef CF_ENUM(int32_t, RNSVGUnits) {
kRNSVGUnitsObjectBoundingBox,
kRNSVGUnitsUserSpaceOnUse
kRNSVGUnitsObjectBoundingBox,
kRNSVGUnitsUserSpaceOnUse
};

View File

@@ -7,7 +7,7 @@
*/
typedef CF_ENUM(int32_t, RNSVGVBMOS) {
kRNSVGVBMOSMeet,
kRNSVGVBMOSSlice,
kRNSVGVBMOSNone
kRNSVGVBMOSMeet,
kRNSVGVBMOSSlice,
kRNSVGVBMOSNone
};

View File

@@ -7,8 +7,8 @@
*/
typedef CF_ENUM(int32_t, RNSVGVectorEffect) {
kRNSVGVectorEffectDefault,
kRNSVGVectorEffectNonScalingStroke,
kRNSVGVectorEffectInherit,
kRNSVGVectorEffectUri
kRNSVGVectorEffectDefault,
kRNSVGVectorEffectNonScalingStroke,
kRNSVGVectorEffectInherit,
kRNSVGVectorEffectUri
};

View File

@@ -12,6 +12,9 @@
@interface RNSVGViewBox : NSObject
+ (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice;
+ (CGAffineTransform)getTransform:(CGRect)vbRect
eRect:(CGRect)eRect
align:(NSString *)align
meetOrSlice:(RNSVGVBMOS)meetOrSlice;
@end

View File

@@ -6,93 +6,99 @@
* LICENSE file in the root directory of this source tree.
*/
#import <math.h>
#import "RNSVGViewBox.h"
#import <math.h>
#import "RNSVGUse.h"
@implementation RNSVGViewBox
+ (CGAffineTransform)getTransform:(CGRect)vbRect eRect:(CGRect)eRect align:(NSString *)align meetOrSlice:(RNSVGVBMOS)meetOrSlice
+ (CGAffineTransform)getTransform:(CGRect)vbRect
eRect:(CGRect)eRect
align:(NSString *)align
meetOrSlice:(RNSVGVBMOS)meetOrSlice
{
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
// based on https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
CGFloat vbX = CGRectGetMinX(vbRect);
CGFloat vbY = CGRectGetMinY(vbRect);
CGFloat vbWidth = CGRectGetWidth(vbRect);
CGFloat vbHeight = CGRectGetHeight(vbRect);
// Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute
// respectively.
CGFloat vbX = CGRectGetMinX(vbRect);
CGFloat vbY = CGRectGetMinY(vbRect);
CGFloat vbWidth = CGRectGetWidth(vbRect);
CGFloat vbHeight = CGRectGetHeight(vbRect);
// Let e-x, e-y, e-width, e-height be the position and size of the element respectively.
CGFloat eX = CGRectGetMinX(eRect);
CGFloat eY = CGRectGetMinY(eRect);
CGFloat eWidth = CGRectGetWidth(eRect);
CGFloat eHeight = CGRectGetHeight(eRect);
// Let e-x, e-y, e-width, e-height be the position and size of the element respectively.
CGFloat eX = CGRectGetMinX(eRect);
CGFloat eY = CGRectGetMinY(eRect);
CGFloat eWidth = CGRectGetWidth(eRect);
CGFloat eHeight = CGRectGetHeight(eRect);
// Let align be the align value of preserveAspectRatio, or 'xMidyMid' if preserveAspectRatio is not defined.
// Let align be the align value of preserveAspectRatio, or 'xMidyMid' if preserveAspectRatio is not defined.
// Let meetOrSlice be the meetOrSlice value of preserveAspectRatio, or 'meet' if preserveAspectRatio is not defined or if meetOrSlice is missing from this value.
// Let meetOrSlice be the meetOrSlice value of preserveAspectRatio, or 'meet' if preserveAspectRatio is not defined or
// if meetOrSlice is missing from this value.
// Initialize scale-x to e-width/vb-width.
CGFloat scaleX = eWidth / vbWidth;
// Initialize scale-x to e-width/vb-width.
CGFloat scaleX = eWidth / vbWidth;
// Initialize scale-y to e-height/vb-height.
CGFloat scaleY = eHeight / vbHeight;
// Initialize scale-y to e-height/vb-height.
CGFloat scaleY = eHeight / vbHeight;
// Initialize translate-x to e-x - (vb-x * scale-x).
// Initialize translate-y to e-y - (vb-y * scale-y).
CGFloat translateX = eX - (vbX * scaleX);
CGFloat translateY = eY - (vbY * scaleY);
// Initialize translate-x to e-x - (vb-x * scale-x).
// Initialize translate-y to e-y - (vb-y * scale-y).
CGFloat translateX = eX - (vbX * scaleX);
CGFloat translateY = eY - (vbY * scaleY);
// If align is 'none'
if (meetOrSlice == kRNSVGVBMOSNone) {
// Let scale be set the smaller value of scale-x and scale-y.
// Assign scale-x and scale-y to scale.
CGFloat scale = scaleX = scaleY = fmin(scaleX, scaleY);
// If align is 'none'
if (meetOrSlice == kRNSVGVBMOSNone) {
// Let scale be set the smaller value of scale-x and scale-y.
// Assign scale-x and scale-y to scale.
CGFloat scale = scaleX = scaleY = fmin(scaleX, scaleY);
// If scale is greater than 1
if (scale > 1) {
// Minus translateX by (eWidth / scale - vbWidth) / 2
// Minus translateY by (eHeight / scale - vbHeight) / 2
translateX -= (eWidth / scale - vbWidth) / 2;
translateY -= (eHeight / scale - vbHeight) / 2;
} else {
translateX -= (eWidth - vbWidth * scale) / 2;
translateY -= (eHeight - vbHeight * scale) / 2;
}
// If scale is greater than 1
if (scale > 1) {
// Minus translateX by (eWidth / scale - vbWidth) / 2
// Minus translateY by (eHeight / scale - vbHeight) / 2
translateX -= (eWidth / scale - vbWidth) / 2;
translateY -= (eHeight / scale - vbHeight) / 2;
} else {
// If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller.
// Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the larger.
if (![align isEqualToString: @"none"] && meetOrSlice == kRNSVGVBMOSMeet) {
scaleX = scaleY = fmin(scaleX, scaleY);
} else if (![align isEqualToString: @"none"] && meetOrSlice == kRNSVGVBMOSSlice) {
scaleX = scaleY = fmax(scaleX, scaleY);
}
// If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x.
if ([align containsString:@"xMid"]) {
translateX += (eWidth - vbWidth * scaleX) / 2.0;
}
// If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x.
if ([align containsString:@"xMax"]) {
translateX += (eWidth - vbWidth * scaleX);
}
// If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y.
if ([align containsString:@"YMid"]) {
translateY += (eHeight - vbHeight * scaleY) / 2.0;
}
// If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y.
if ([align containsString:@"YMax"]) {
translateY += (eHeight - vbHeight * scaleY);
}
translateX -= (eWidth - vbWidth * scale) / 2;
translateY -= (eHeight - vbHeight * scale) / 2;
}
} else {
// If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller.
// Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the
// larger.
if (![align isEqualToString:@"none"] && meetOrSlice == kRNSVGVBMOSMeet) {
scaleX = scaleY = fmin(scaleX, scaleY);
} else if (![align isEqualToString:@"none"] && meetOrSlice == kRNSVGVBMOSSlice) {
scaleX = scaleY = fmax(scaleX, scaleY);
}
// The transform applied to content contained by the element is given by
// translate(translate-x, translate-y) scale(scale-x, scale-y).
CGAffineTransform transform = CGAffineTransformMakeTranslation(translateX, translateY);
return CGAffineTransformScale(transform, scaleX, scaleY);
// If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x.
if ([align containsString:@"xMid"]) {
translateX += (eWidth - vbWidth * scaleX) / 2.0;
}
// If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x.
if ([align containsString:@"xMax"]) {
translateX += (eWidth - vbWidth * scaleX);
}
// If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y.
if ([align containsString:@"YMid"]) {
translateY += (eHeight - vbHeight * scaleY) / 2.0;
}
// If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y.
if ([align containsString:@"YMax"]) {
translateY += (eHeight - vbHeight * scaleY);
}
}
// The transform applied to content contained by the element is given by
// translate(translate-x, translate-y) scale(scale-x, scale-y).
CGAffineTransform transform = CGAffineTransformMakeTranslation(translateX, translateY);
return CGAffineTransformScale(transform, scaleX, scaleY);
}
@end