Files
react-native-svg/apple/Brushes/RNSVGPainter.mm
SergeyYurkevich 832522d1c1 feat: implement mask-type property (#2152)
# Summary

"mask-type: alpha" is not supported. 
resolve issue: #1790  

## Explanation

svg example:
```
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" fill="none">
<g clip-path="url(#clip0_8_3)">
<rect width="100" height="100" fill="white"/>
<mask id="mask0_8_3" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
<circle cx="50" cy="50" r="50" fill="#000000"/>
</mask>
<g mask="url(#mask0_8_3)">
<rect x="-26" y="-78" width="209" height="263" fill="#252E74"/>
</g>
</g>
<defs>
<clipPath id="clip0_8_3">
<rect width="100" height="100" fill="white"/>
</clipPath>
</defs>
</svg>
```

Current behavior: 

![image](https://github.com/software-mansion/react-native-svg/assets/17138397/2dca6f46-fe8f-48f3-80f9-799563911e8b)

Expected behavior:

![image](https://github.com/software-mansion/react-native-svg/assets/17138397/fb49cf0b-d677-491f-8215-9c9b1d69080f)

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |        |
| Android |        |

## Checklist

<!-- Check completed item, when applicable, via: [X] -->

- [x] I have tested this on a device and a simulator
- [ ] I added documentation in `README.md`
- [x] I updated the typed files (typescript)
- [ ] I added a test for the API in the `__tests__` folder

---------

Co-authored-by: Sergey <s.yurkevich@logiclike.com>
Co-authored-by: Jakub Grzywacz <jakub.grzywacz@swmansion.com>
2024-07-05 15:11:48 +02:00

262 lines
8.2 KiB
Plaintext

/**
* 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];
if (rx <= 0 || ry <= 0) {
// Gradient with radius = 0 should be rendered as solid color of the last stop
rx = width;
ry = height;
CGGradientRelease(gradient);
NSArray<NSNumber *> *gradientArray =
@[ _colors.firstObject, _colors.lastObject, _colors[_colors.count - 2], _colors.lastObject ];
gradient = CGGradientRetain([RCTConvert RNSVGCGGradient:gradientArray]);
}
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