mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
# Summary Closes #2535 When `fill` or `stroke` is set to `'none'` we should remove the value instead of keeping the last valid value. ## Test Plan ```tsx import React, {useState} from 'react'; import {Text, TouchableOpacity} from 'react-native'; import {Path, Rect, Svg} from 'react-native-svg'; function Example() { const [color, setColor] = useState('#0000FF'); return ( <> <Svg width={120} height={150} viewBox="0 0 12 15" fill="none"> <Path d="M1 3.353C1 2.053 2.053 1 3.353 1H8.647C9.947 1 11 2.053 11 3.353V13.207C11 13.3008 10.9736 13.3927 10.9238 13.4722C10.874 13.5518 10.8029 13.6157 10.7185 13.6567C10.6341 13.6976 10.5399 13.7141 10.4466 13.7041C10.3533 13.694 10.2648 13.658 10.191 13.6L7.202 11.25C6.99452 11.0876 6.7385 10.9995 6.475 11H3.353C2.72895 11 2.13045 10.7521 1.68918 10.3108C1.2479 9.86955 1 9.27105 1 8.647V3.353Z" stroke={color} fill={color} strokeOpacity={0.5} strokeWidth={1.5} /> </Svg> <TouchableOpacity onPress={() => setColor('none')}> <Text>Click to remove color</Text> </TouchableOpacity> </> ); } ``` ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | macOS | ✅ |
255 lines
10 KiB
Objective-C
255 lines
10 KiB
Objective-C
#import "RNSVGContextBrush.h"
|
|
#import "RNSVGFilterPrimitive.h"
|
|
#import "RNSVGGroup.h"
|
|
#import "RNSVGLength.h"
|
|
#import "RNSVGPainterBrush.h"
|
|
#import "RNSVGRenderable.h"
|
|
#import "RNSVGSolidColorBrush.h"
|
|
#import "RNSVGText.h"
|
|
#import "RNSVGVBMOS.h"
|
|
|
|
#import <React/RCTConversions.h>
|
|
#import <React/RCTFabricComponentsPlugins.h>
|
|
|
|
#import <folly/dynamic.h>
|
|
|
|
// copied from RCTFollyConvert
|
|
static id RNSVGConvertFollyDynamicToId(const folly::dynamic &dyn)
|
|
{
|
|
// I could imagine an implementation which avoids copies by wrapping the
|
|
// dynamic in a derived class of NSDictionary. We can do that if profiling
|
|
// implies it will help.
|
|
|
|
switch (dyn.type()) {
|
|
case folly::dynamic::NULLT:
|
|
return nil;
|
|
case folly::dynamic::BOOL:
|
|
return dyn.getBool() ? @YES : @NO;
|
|
case folly::dynamic::INT64:
|
|
return @(dyn.getInt());
|
|
case folly::dynamic::DOUBLE:
|
|
return @(dyn.getDouble());
|
|
case folly::dynamic::STRING:
|
|
return [[NSString alloc] initWithBytes:dyn.c_str() length:dyn.size() encoding:NSUTF8StringEncoding];
|
|
case folly::dynamic::ARRAY: {
|
|
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:dyn.size()];
|
|
for (const auto &elem : dyn) {
|
|
id value = RNSVGConvertFollyDynamicToId(elem);
|
|
if (value) {
|
|
[array addObject:value];
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
case folly::dynamic::OBJECT: {
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:dyn.size()];
|
|
for (const auto &elem : dyn.items()) {
|
|
id key = RNSVGConvertFollyDynamicToId(elem.first);
|
|
id value = RNSVGConvertFollyDynamicToId(elem.second);
|
|
if (key && value) {
|
|
dict[key] = value;
|
|
}
|
|
}
|
|
return dict;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void setCommonNodeProps(const 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);
|
|
std::string pointerEvents = nodeProps.pointerEvents;
|
|
NSString *pointerEventsString = RCTNSStringFromStringNilIfEmpty(pointerEvents);
|
|
if ([pointerEventsString isEqualToString:@"auto"]) {
|
|
node.pointerEvents = RCTPointerEventsUnspecified;
|
|
} else if ([pointerEventsString isEqualToString:@"none"]) {
|
|
node.pointerEvents = RCTPointerEventsNone;
|
|
} else if ([pointerEventsString isEqualToString:@"box-none"]) {
|
|
node.pointerEvents = RCTPointerEventsNone;
|
|
} else if ([pointerEventsString isEqualToString:@"box-only"]) {
|
|
node.pointerEvents = RCTPointerEventsNone;
|
|
} else {
|
|
node.pointerEvents = RCTPointerEventsUnspecified;
|
|
}
|
|
node.accessibilityIdentifier = RCTNSStringFromStringNilIfEmpty(nodeProps.testId);
|
|
#if !TARGET_OS_OSX
|
|
node.isAccessibilityElement = nodeProps.accessible;
|
|
#else
|
|
node.accessibilityElement = nodeProps.accessible;
|
|
#endif // !TARGET_OS_OSX
|
|
node.accessibilityLabel = RCTNSStringFromStringNilIfEmpty(nodeProps.accessibilityLabel);
|
|
}
|
|
|
|
template <typename T>
|
|
void setCommonRenderableProps(const T &renderableProps, RNSVGRenderable *renderableNode)
|
|
{
|
|
setCommonNodeProps(renderableProps, renderableNode);
|
|
if (RCTUIColorFromSharedColor(renderableProps.color)) {
|
|
[renderableNode setColor:RCTUIColorFromSharedColor(renderableProps.color)];
|
|
}
|
|
id fill = RNSVGConvertFollyDynamicToId(renderableProps.fill);
|
|
renderableNode.fill = [RCTConvert RNSVGBrush:fill];
|
|
renderableNode.fillOpacity = renderableProps.fillOpacity;
|
|
renderableNode.fillRule = renderableProps.fillRule == 0 ? kRNSVGCGFCRuleEvenodd : kRNSVGCGFCRuleNonzero;
|
|
id stroke = RNSVGConvertFollyDynamicToId(renderableProps.stroke);
|
|
renderableNode.stroke = [RCTConvert RNSVGBrush:stroke];
|
|
renderableNode.strokeOpacity = renderableProps.strokeOpacity;
|
|
id strokeWidth = RNSVGConvertFollyDynamicToId(renderableProps.strokeWidth);
|
|
if (strokeWidth != nil) {
|
|
renderableNode.strokeWidth = [RCTConvert RNSVGLength:strokeWidth];
|
|
}
|
|
renderableNode.strokeLinecap = renderableProps.strokeLinecap == 0 ? kCGLineCapButt
|
|
: renderableProps.strokeLinecap == 1 ? kCGLineCapRound
|
|
: kCGLineCapSquare;
|
|
renderableNode.strokeLinejoin = renderableProps.strokeLinejoin == 0 ? kCGLineJoinMiter
|
|
: renderableProps.strokeLinejoin == 1 ? kCGLineJoinRound
|
|
: kCGLineJoinBevel;
|
|
id strokeDasharray = RNSVGConvertFollyDynamicToId(renderableProps.strokeDasharray);
|
|
if (strokeDasharray != nil) {
|
|
renderableNode.strokeDasharray = [RCTConvert RNSVGLengthArray: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;
|
|
}
|
|
renderableNode.filter = RCTNSStringFromStringNilIfEmpty(renderableProps.filter);
|
|
}
|
|
|
|
template <typename T>
|
|
void setCommonGroupProps(const T &groupProps, RNSVGGroup *groupNode)
|
|
{
|
|
setCommonRenderableProps(groupProps, groupNode);
|
|
|
|
id fontSize = RNSVGConvertFollyDynamicToId(groupProps.fontSize);
|
|
if (fontSize != nil) {
|
|
groupNode.font = @{@"fontSize" : fontSize};
|
|
}
|
|
id fontWeight = RNSVGConvertFollyDynamicToId(groupProps.fontWeight);
|
|
if (fontWeight != nil) {
|
|
groupNode.font = @{@"fontWeight" : fontWeight};
|
|
}
|
|
id font = RNSVGConvertFollyDynamicToId(groupProps.font);
|
|
if (font != nil) {
|
|
NSDictionary *fontDict = (NSDictionary *)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;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void setCommonFilterProps(const T &filterProps, RNSVGFilterPrimitive *filterPrimitiveNode)
|
|
{
|
|
id x = RNSVGConvertFollyDynamicToId(filterProps.x);
|
|
if (x != nil) {
|
|
filterPrimitiveNode.x = [RCTConvert RNSVGLength:x];
|
|
}
|
|
id y = RNSVGConvertFollyDynamicToId(filterProps.y);
|
|
if (y != nil) {
|
|
filterPrimitiveNode.y = [RCTConvert RNSVGLength:y];
|
|
}
|
|
id height = RNSVGConvertFollyDynamicToId(filterProps.height);
|
|
if (height != nil) {
|
|
filterPrimitiveNode.height = [RCTConvert RNSVGLength:height];
|
|
}
|
|
id width = RNSVGConvertFollyDynamicToId(filterProps.width);
|
|
if (width != nil) {
|
|
filterPrimitiveNode.width = [RCTConvert RNSVGLength:width];
|
|
}
|
|
filterPrimitiveNode.result = RCTNSStringFromStringNilIfEmpty(filterProps.result);
|
|
}
|
|
|
|
template <typename T>
|
|
void setCommonTextProps(const T &textProps, RNSVGText *textNode)
|
|
{
|
|
setCommonGroupProps(textProps, textNode);
|
|
id deltaX = RNSVGConvertFollyDynamicToId(textProps.dx);
|
|
if (deltaX != nil) {
|
|
textNode.deltaX = [RCTConvert RNSVGLengthArray:deltaX];
|
|
}
|
|
id deltaY = RNSVGConvertFollyDynamicToId(textProps.dy);
|
|
if (deltaY != nil) {
|
|
textNode.deltaY = [RCTConvert RNSVGLengthArray:deltaY];
|
|
}
|
|
id positionX = RNSVGConvertFollyDynamicToId(textProps.x);
|
|
if (positionX != nil) {
|
|
textNode.positionX = [RCTConvert RNSVGLengthArray:positionX];
|
|
}
|
|
id positionY = RNSVGConvertFollyDynamicToId(textProps.y);
|
|
if (positionY != nil) {
|
|
textNode.positionY = [RCTConvert RNSVGLengthArray:positionY];
|
|
}
|
|
id rotate = RNSVGConvertFollyDynamicToId(textProps.rotate);
|
|
if (rotate != nil) {
|
|
textNode.rotate = [RCTConvert RNSVGLengthArray:rotate];
|
|
}
|
|
id textLength = RNSVGConvertFollyDynamicToId(textProps.textLength);
|
|
if (textLength != nil) {
|
|
textNode.textLength = [RCTConvert RNSVGLength:textLength];
|
|
}
|
|
id inlineSize = RNSVGConvertFollyDynamicToId(textProps.inlineSize);
|
|
if (inlineSize != nil) {
|
|
textNode.inlineSize = [RCTConvert RNSVGLength:inlineSize];
|
|
}
|
|
id baselineShift = RNSVGConvertFollyDynamicToId(textProps.baselineShift);
|
|
if (baselineShift != nil) {
|
|
if ([baselineShift isKindOfClass:[NSString class]]) {
|
|
NSString *stringValue = (NSString *)baselineShift;
|
|
textNode.baselineShift = stringValue;
|
|
} else {
|
|
textNode.baselineShift = [NSString stringWithFormat:@"%f", [baselineShift doubleValue]];
|
|
}
|
|
}
|
|
textNode.lengthAdjust = RCTNSStringFromStringNilIfEmpty(textProps.lengthAdjust);
|
|
textNode.alignmentBaseline = RCTNSStringFromStringNilIfEmpty(textProps.alignmentBaseline);
|
|
}
|
|
|
|
static RNSVGVBMOS intToRNSVGVBMOS(int value)
|
|
{
|
|
switch (value) {
|
|
case 0:
|
|
return kRNSVGVBMOSMeet;
|
|
case 1:
|
|
return kRNSVGVBMOSSlice;
|
|
case 2:
|
|
return kRNSVGVBMOSNone;
|
|
default:
|
|
return kRNSVGVBMOSMeet;
|
|
}
|
|
}
|