You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
7.8 KiB
252 lines
7.8 KiB
/**
|
|
* Copyright (c) 2015-present, Horcrux.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the MIT-style license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import "RNSVGPainter.h"
|
|
#import "RNSVGPattern.h"
|
|
#import "RNSVGViewBox.h"
|
|
|
|
@implementation RNSVGPainter {
|
|
NSArray<RNSVGLength *> *_points;
|
|
NSArray<NSNumber *> *_colors;
|
|
RNSVGBrushType _type;
|
|
BOOL _useObjectBoundingBox;
|
|
BOOL _useContentObjectBoundingBox;
|
|
CGAffineTransform _transform;
|
|
CGRect _userSpaceBoundingBox;
|
|
}
|
|
|
|
- (instancetype)initWithPointsArray:(NSArray<RNSVGLength *> *)pointsArray
|
|
{
|
|
if ((self = [super init])) {
|
|
_points = pointsArray;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|
|
|
- (void)setUnits:(RNSVGUnits)unit
|
|
{
|
|
_useObjectBoundingBox = unit == kRNSVGUnitsObjectBoundingBox;
|
|
}
|
|
|
|
- (void)setContentUnits:(RNSVGUnits)unit
|
|
{
|
|
_useContentObjectBoundingBox = unit == kRNSVGUnitsObjectBoundingBox;
|
|
}
|
|
|
|
- (void)setUserSpaceBoundingBox:(CGRect)userSpaceBoundingBox
|
|
{
|
|
_userSpaceBoundingBox = userSpaceBoundingBox;
|
|
}
|
|
|
|
- (void)setTransform:(CGAffineTransform)transform
|
|
{
|
|
_transform = transform;
|
|
}
|
|
|
|
- (void)setPattern:(RNSVGPattern *)pattern
|
|
{
|
|
if (_type != kRNSVGUndefinedType) {
|
|
// todo: throw error
|
|
return;
|
|
}
|
|
|
|
_type = kRNSVGPattern;
|
|
_pattern = pattern;
|
|
}
|
|
|
|
- (void)setLinearGradientColors:(NSArray<NSNumber *> *)colors
|
|
{
|
|
if (_type != kRNSVGUndefinedType) {
|
|
// todo: throw error
|
|
return;
|
|
}
|
|
|
|
_type = kRNSVGLinearGradient;
|
|
_colors = colors;
|
|
}
|
|
|
|
- (void)setRadialGradientColors:(NSArray<NSNumber *> *)colors
|
|
{
|
|
if (_type != kRNSVGUndefinedType) {
|
|
// todo: throw error
|
|
return;
|
|
}
|
|
|
|
_type = kRNSVGRadialGradient;
|
|
_colors = colors;
|
|
}
|
|
|
|
- (void)paint:(CGContextRef)context bounds:(CGRect)bounds
|
|
{
|
|
if (_type == kRNSVGLinearGradient) {
|
|
[self paintLinearGradient:context bounds:bounds];
|
|
} else if (_type == kRNSVGRadialGradient) {
|
|
[self paintRadialGradient:context bounds:bounds];
|
|
} else if (_type == kRNSVGPattern) {
|
|
[self paintPattern:context bounds:bounds];
|
|
}
|
|
}
|
|
|
|
- (CGRect)getPaintRect:(CGContextRef)context bounds:(CGRect)bounds
|
|
{
|
|
CGRect rect = _useObjectBoundingBox ? bounds : _userSpaceBoundingBox;
|
|
CGFloat height = CGRectGetHeight(rect);
|
|
CGFloat width = CGRectGetWidth(rect);
|
|
CGFloat x = 0.0;
|
|
CGFloat y = 0.0;
|
|
|
|
if (_useObjectBoundingBox) {
|
|
x = CGRectGetMinX(rect);
|
|
y = CGRectGetMinY(rect);
|
|
}
|
|
|
|
return CGRectMake(x, y, width, height);
|
|
}
|
|
|
|
void PatternFunction(void *info, CGContextRef context)
|
|
{
|
|
RNSVGPainter *_painter = (__bridge RNSVGPainter *)info;
|
|
RNSVGPattern *_pattern = [_painter pattern];
|
|
CGRect rect = _painter.paintBounds;
|
|
CGFloat minX = _pattern.minX;
|
|
CGFloat minY = _pattern.minY;
|
|
CGFloat vbWidth = _pattern.vbWidth;
|
|
CGFloat vbHeight = _pattern.vbHeight;
|
|
if (vbWidth > 0 && vbHeight > 0) {
|
|
CGRect vbRect = CGRectMake(minX, minY, vbWidth, vbHeight);
|
|
CGAffineTransform _viewBoxTransform = [RNSVGViewBox getTransform:vbRect
|
|
eRect:rect
|
|
align:_pattern.align
|
|
meetOrSlice:_pattern.meetOrSlice];
|
|
CGContextConcatCTM(context, _viewBoxTransform);
|
|
}
|
|
|
|
if (_painter.useObjectBoundingBoxForContentUnits) {
|
|
CGRect bounds = _painter.bounds;
|
|
CGContextConcatCTM(context, CGAffineTransformMakeScale(bounds.size.width, bounds.size.height));
|
|
}
|
|
|
|
[_pattern renderTo:context rect:rect];
|
|
}
|
|
|
|
- (CGFloat)getVal:(RNSVGLength *)length relative:(CGFloat)relative
|
|
{
|
|
RNSVGLengthUnitType unit = [length unit];
|
|
CGFloat val = [RNSVGPropHelper fromRelative:length relative:relative];
|
|
return _useObjectBoundingBox && unit == SVG_LENGTHTYPE_NUMBER ? val * relative : val;
|
|
}
|
|
|
|
- (void)paintPattern:(CGContextRef)context bounds:(CGRect)bounds
|
|
{
|
|
CGRect rect = [self getPaintRect:context bounds:bounds];
|
|
CGFloat height = CGRectGetHeight(rect);
|
|
CGFloat width = CGRectGetWidth(rect);
|
|
|
|
CGFloat x = [self getVal:[_points objectAtIndex:0] relative:width];
|
|
CGFloat y = [self getVal:[_points objectAtIndex:1] relative:height];
|
|
CGFloat w = [self getVal:[_points objectAtIndex:2] relative:width];
|
|
CGFloat h = [self getVal:[_points objectAtIndex:3] relative:height];
|
|
|
|
CGAffineTransform viewbox = [self.pattern.svgView getViewBoxTransform];
|
|
#if TARGET_OS_OSX
|
|
// This is needed because macOS and iOS have different conventions for where the origin is.
|
|
// For macOS, it's in the bottom-left corner. For iOS, it's in the top-left corner.
|
|
viewbox = CGAffineTransformScale(viewbox, 1, -1);
|
|
#endif // TARGET_OS_OSX
|
|
CGRect newBounds = CGRectMake(x, y, w, h);
|
|
CGSize size = newBounds.size;
|
|
self.useObjectBoundingBoxForContentUnits = _useContentObjectBoundingBox;
|
|
self.paintBounds = newBounds;
|
|
self.bounds = rect;
|
|
|
|
const CGPatternCallbacks callbacks = {0, &PatternFunction, NULL};
|
|
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
|
|
CGContextSetFillColorSpace(context, patternSpace);
|
|
CGColorSpaceRelease(patternSpace);
|
|
|
|
CGPatternRef pattern = CGPatternCreate(
|
|
(__bridge void *_Nullable)(self),
|
|
newBounds,
|
|
viewbox,
|
|
size.width,
|
|
size.height,
|
|
kCGPatternTilingConstantSpacing,
|
|
true,
|
|
&callbacks);
|
|
CGFloat alpha = 1.0;
|
|
CGContextSetFillPattern(context, pattern, &alpha);
|
|
CGPatternRelease(pattern);
|
|
|
|
CGContextFillRect(context, bounds);
|
|
}
|
|
|
|
- (void)paintLinearGradient:(CGContextRef)context bounds:(CGRect)bounds
|
|
{
|
|
if ([_colors count] == 0) {
|
|
RCTLogWarn(@"No stops in gradient");
|
|
return;
|
|
}
|
|
CGGradientRef gradient = CGGradientRetain([RCTConvert RNSVGCGGradient:_colors]);
|
|
CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
|
|
|
CGRect rect = [self getPaintRect:context bounds:bounds];
|
|
CGFloat height = CGRectGetHeight(rect);
|
|
CGFloat width = CGRectGetWidth(rect);
|
|
CGFloat offsetX = CGRectGetMinX(rect);
|
|
CGFloat offsetY = CGRectGetMinY(rect);
|
|
|
|
CGFloat x1 = [self getVal:[_points objectAtIndex:0] relative:width] + offsetX;
|
|
CGFloat y1 = [self getVal:[_points objectAtIndex:1] relative:height] + offsetY;
|
|
CGFloat x2 = [self getVal:[_points objectAtIndex:2] relative:width] + offsetX;
|
|
CGFloat y2 = [self getVal:[_points objectAtIndex:3] relative:height] + offsetY;
|
|
|
|
CGContextConcatCTM(context, _transform);
|
|
CGContextDrawLinearGradient(context, gradient, CGPointMake(x1, y1), CGPointMake(x2, y2), extendOptions);
|
|
CGGradientRelease(gradient);
|
|
}
|
|
|
|
- (void)paintRadialGradient:(CGContextRef)context bounds:(CGRect)bounds
|
|
{
|
|
if ([_colors count] == 0) {
|
|
RCTLogWarn(@"No stops in gradient");
|
|
return;
|
|
}
|
|
CGGradientRef gradient = CGGradientRetain([RCTConvert RNSVGCGGradient:_colors]);
|
|
CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
|
|
|
CGRect rect = [self getPaintRect:context bounds:bounds];
|
|
|
|
CGFloat width = CGRectGetWidth(rect);
|
|
CGFloat height = CGRectGetHeight(rect);
|
|
|
|
CGFloat offsetX = CGRectGetMinX(rect);
|
|
CGFloat offsetY = CGRectGetMinY(rect);
|
|
|
|
CGFloat rx = [self getVal:[_points objectAtIndex:2] relative:width];
|
|
CGFloat ry = [self getVal:[_points objectAtIndex:3] relative:height];
|
|
|
|
double ratio = ry / rx;
|
|
|
|
CGFloat fx = [self getVal:[_points objectAtIndex:0] relative:width] + offsetX;
|
|
CGFloat fy = ([self getVal:[_points objectAtIndex:1] relative:height] + offsetY) / ratio;
|
|
|
|
CGFloat cx = [self getVal:[_points objectAtIndex:4] relative:width] + offsetX;
|
|
CGFloat cy = ([self getVal:[_points objectAtIndex:5] relative:height] + offsetY) / ratio;
|
|
|
|
CGAffineTransform transform = CGAffineTransformMakeScale(1, ratio);
|
|
CGContextConcatCTM(context, transform);
|
|
|
|
CGContextConcatCTM(context, _transform);
|
|
CGContextDrawRadialGradient(context, gradient, CGPointMake(fx, fy), 0, CGPointMake(cx, cy), rx, extendOptions);
|
|
CGGradientRelease(gradient);
|
|
}
|
|
|
|
@end
|