mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
*/
|
||||
|
||||
typedef CF_ENUM(int32_t, RNSVGCGFCRule) {
|
||||
kRNSVGCGFCRuleEvenodd,
|
||||
kRNSVGCGFCRuleNonzero
|
||||
kRNSVGCGFCRuleEvenodd,
|
||||
kRNSVGCGFCRuleNonzero
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -7,6 +7,6 @@
|
||||
*/
|
||||
|
||||
typedef CF_ENUM(int32_t, RNSVGUnits) {
|
||||
kRNSVGUnitsObjectBoundingBox,
|
||||
kRNSVGUnitsUserSpaceOnUse
|
||||
kRNSVGUnitsObjectBoundingBox,
|
||||
kRNSVGUnitsUserSpaceOnUse
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
typedef CF_ENUM(int32_t, RNSVGVBMOS) {
|
||||
kRNSVGVBMOSMeet,
|
||||
kRNSVGVBMOSSlice,
|
||||
kRNSVGVBMOSNone
|
||||
kRNSVGVBMOSMeet,
|
||||
kRNSVGVBMOSSlice,
|
||||
kRNSVGVBMOSNone
|
||||
};
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
typedef CF_ENUM(int32_t, RNSVGVectorEffect) {
|
||||
kRNSVGVectorEffectDefault,
|
||||
kRNSVGVectorEffectNonScalingStroke,
|
||||
kRNSVGVectorEffectInherit,
|
||||
kRNSVGVectorEffectUri
|
||||
kRNSVGVectorEffectDefault,
|
||||
kRNSVGVectorEffectNonScalingStroke,
|
||||
kRNSVGVectorEffectInherit,
|
||||
kRNSVGVectorEffectUri
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user