Skip to content

feat!: Svg intrinsic size, auto and custom shadow nodes for RNSVGSvg #2577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/src/main/java/com/horcrux/svg/SVGLength.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ private SVGLength() {
} else if (length.codePointAt(percentIndex) == '%') {
unit = UnitType.PERCENTAGE;
value = Double.valueOf(length.substring(0, percentIndex));
} else if (length.equals("auto")) {
unit = UnitType.PERCENTAGE;
value = 100;
} else {
int twoLetterUnitIndex = stringLength - 2;
if (twoLetterUnitIndex > 0) {
Expand Down
10 changes: 9 additions & 1 deletion apple/Elements/RNSVGSvgView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTConversions.h>
#import <React/RCTFabricComponentsPlugins.h>
#import <react/renderer/components/rnsvg/ComponentDescriptors.h>
#import <react/renderer/components/view/conversions.h>
#import <rnsvg/RNSVGComponentDescriptors.h>
#import "RNSVGFabricConversions.h"
#endif // RCT_NEW_ARCH_ENABLED

Expand Down Expand Up @@ -73,6 +73,14 @@ + (ComponentDescriptorProvider)componentDescriptorProvider
return concreteComponentDescriptorProvider<RNSVGSvgViewComponentDescriptor>();
}

- (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics
{
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
[self clearChildCache];
[self invalidate];
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = static_cast<const RNSVGSvgViewProps &>(*props);
Expand Down
3 changes: 3 additions & 0 deletions apple/RNSVGNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ - (void)clearPath
- (void)clearChildCache
{
[self clearPath];
canvasWidth = -1;
canvasHeight = -1;
canvasDiagonal = -1;
for (__kindof RNSVGNode *node in self.subviews) {
if ([node isKindOfClass:[RNSVGNode class]]) {
[node clearChildCache];
Expand Down
3 changes: 3 additions & 0 deletions apple/Utils/RNSVGLength.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ + (instancetype)lengthWithString:(NSString *)lengthString
} else if ([length characterAtIndex:percentIndex] == '%') {
output.unit = SVG_LENGTHTYPE_PERCENTAGE;
output.value = (CGFloat)[[length substringWithRange:NSMakeRange(0, percentIndex)] doubleValue];
} else if ([length isEqualToString:@"auto"]) {
output.unit = SVG_LENGTHTYPE_PERCENTAGE;
output.value = 100;
} else {
NSInteger twoLetterUnitIndex = stringLength - 2;
RNSVGLengthUnitType unit = SVG_LENGTHTYPE_NUMBER;
Expand Down
108 changes: 91 additions & 17 deletions apps/common/example/examples/Svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@ import React, {useRef, useState} from 'react';
import {Image, StyleSheet, View} from 'react-native';
import {Circle, G, Line, Path, Rect, Svg} from 'react-native-svg';

const styles = StyleSheet.create({
container: {
flex: 1,
height: 100,
width: 200,
},
svg: {
flex: 1,
alignSelf: 'stretch',
},
});

function SvgExample() {
return (
<Svg height="100" width="100">
Expand Down Expand Up @@ -82,10 +70,10 @@ function SvgViewbox() {
SvgViewbox.title =
'SVG with `viewBox="40 20 100 40" and preserveAspectRatio="none"';

function SvgLayout() {
function SvgWithoutSizing() {
return (
<View style={styles.container}>
<Svg style={styles.svg}>
<View style={{width: '90%'}}>
<Svg viewBox="0 0 300 100">
<Rect
width="80%"
height="80%"
Expand Down Expand Up @@ -115,7 +103,90 @@ function SvgLayout() {
</View>
);
}
SvgLayout.title = 'SVG with flex layout';
SvgWithoutSizing.title = 'SVG with viewBox and without width/height';

function SvgWithoutSizingAndVb() {
return (
<Svg>
<Rect
width="80%"
height="80%"
x="10%"
y="10%"
fill="purple"
stroke="yellow"
strokeWidth="4"
/>
<Line
x1="10%"
y1="10%"
x2="90%"
y2="90%"
stroke="yellow"
strokeWidth="4"
/>
<Line
x1="10%"
y1="90%"
x2="90%"
y2="10%"
stroke="yellow"
strokeWidth="4"
/>
</Svg>
);
}
SvgWithoutSizingAndVb.title = 'SVG without viewBox and width/height';

function SvgIntrinsicWidth() {
return (
<Svg height="75" viewBox="0 0 100 100" style={{backgroundColor: 'gray'}}>
<Circle
cx="50"
cy="50"
r="45"
stroke="blue"
strokeWidth="2.5"
fill="green"
/>
<Rect
x="15"
y="15"
width="70"
height="70"
stroke="red"
strokeWidth="2"
fill="yellow"
/>
</Svg>
);
}
SvgIntrinsicWidth.title = 'SVG with intrinsic width';

function SvgIntrinsicHeight() {
return (
<Svg width="50" viewBox="0 0 100 100" style={{backgroundColor: 'gray'}}>
<Circle
cx="50"
cy="50"
r="45"
stroke="blue"
strokeWidth="2.5"
fill="green"
/>
<Rect
x="15"
y="15"
width="70"
height="70"
stroke="red"
strokeWidth="2"
fill="yellow"
/>
</Svg>
);
}
SvgIntrinsicHeight.title = 'SVG with intrinsic height';

function SvgNativeMethods() {
const [base64, setBase64] = useState('');
Expand Down Expand Up @@ -179,7 +250,10 @@ const samples = [
SvgExample,
SvgOpacity,
SvgViewbox,
SvgLayout,
SvgWithoutSizing,
SvgWithoutSizingAndVb,
SvgIntrinsicWidth,
SvgIntrinsicHeight,
SvgNativeMethods,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include "RNSVGShadowNodes.h"
#include "RNSVGSvgShadowNode.h"

namespace facebook::react {

using RNSVGSvgViewComponentDescriptor =
ConcreteComponentDescriptor<RNSVGSvgViewShadowNode>;
using RNSVGSvgViewAndroidComponentDescriptor =
ConcreteComponentDescriptor<RNSVGSvgViewAndroidShadowNode>;
using RNSVGCircleComponentDescriptor =
ConcreteComponentDescriptor<RNSVGCircleShadowNode>;
using RNSVGClipPathComponentDescriptor =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace facebook::react {

extern const char RNSVGSvgViewAndroidComponentName[] = "RNSVGSvgViewAndroid";
extern const char RNSVGSvgViewComponentName[] = "RNSVGSvgView";
extern const char RNSVGCircleComponentName[] = "RNSVGCircle";
extern const char RNSVGClipPathComponentName[] = "RNSVGClipPath";
extern const char RNSVGDefsComponentName[] = "RNSVGDefs";
Expand Down
18 changes: 18 additions & 0 deletions common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@
#include <react/renderer/components/rnsvg/Props.h>
#include "RNSVGConcreteShadowNode.h"
#include "RNSVGImageState.h"
#include "RNSVGSvgShadowNode.h"

namespace facebook::react {

JSI_EXPORT extern const char RNSVGSvgViewComponentName[];

/*
* `ShadowNode` for <RNSVGSvgView> component.
*/
using RNSVGSvgViewShadowNode =
RNSVGSvgShadowNode<RNSVGSvgViewComponentName, RNSVGSvgViewProps>;

JSI_EXPORT extern const char RNSVGSvgViewAndroidComponentName[];

/*
* `ShadowNode` for <RNSVGSvgViewAndroid> component.
*/
using RNSVGSvgViewAndroidShadowNode = RNSVGSvgShadowNode<
RNSVGSvgViewAndroidComponentName,
RNSVGSvgViewAndroidProps>;

JSI_EXPORT extern const char RNSVGCircleComponentName[];

/*
Expand Down
88 changes: 88 additions & 0 deletions common/cpp/react/renderer/components/rnsvg/RNSVGSvgShadowNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#pragma once

#include <jsi/jsi.h>
#include <react/renderer/components/rnsvg/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/core/LayoutContext.h>

namespace facebook::react {

template <const char *componentName, typename ViewPropsT = ViewProps>
class RNSVGSvgShadowNode final
: public ConcreteViewShadowNode<componentName, ViewPropsT> {
public:
RNSVGSvgShadowNode(
const ShadowNodeFragment &fragment,
const ShadowNodeFamily::Shared &family,
ShadowNodeTraits traits)
: ConcreteViewShadowNode<componentName, ViewPropsT>(
fragment,
family,
traits) {
initialize();
}

RNSVGSvgShadowNode(
const ShadowNode &sourceShadowNode,
const ShadowNodeFragment &fragment)
: ConcreteViewShadowNode<componentName, ViewPropsT>(
sourceShadowNode,
fragment) {
initialize();
}

void layout(LayoutContext layoutContext) {
auto affectedNodes = layoutContext.affectedNodes;
layoutContext.affectedNodes = nullptr;
YogaLayoutableShadowNode::layout(layoutContext);
layoutContext.affectedNodes = affectedNodes;
}

private:
void initialize() {
auto style = this->yogaNode_.style();
const auto &props = this->getConcreteProps();

bool hasViewBox = props.vbWidth != 0 && props.vbHeight != 0;
bool isWidthEmpty = props.bbWidth.empty();
bool isHeightEmpty = props.bbHeight.empty();

if (!hasViewBox) {
if (isWidthEmpty) {
style.setDimension(yoga::Dimension::Width, yoga::value::points(300));
}
if (isHeightEmpty) {
style.setDimension(yoga::Dimension::Height, yoga::value::points(150));
}
} else if (isWidthEmpty || isHeightEmpty) {
auto vbAspectRatio = props.vbWidth / props.vbHeight;
style.setAspectRatio(yoga::FloatOptional(vbAspectRatio));
if (isWidthEmpty && isHeightEmpty) {
style.setDimension(yoga::Dimension::Width, yoga::value::percent(100));
} else if (isWidthEmpty) {
style.setDimension(yoga::Dimension::Width, yoga::value::undefined());
} else if (isHeightEmpty) {
style.setDimension(yoga::Dimension::Height, yoga::value::undefined());
}
}

bool isWidthAuto =
props.bbWidth.isString() && props.bbWidth.asString() == "auto";
bool isHeightAuto =
props.bbHeight.isString() && props.bbHeight.asString() == "auto";

if (isWidthAuto) {
style.setDimension(yoga::Dimension::Width, yoga::value::percent(100));
}
if (isHeightAuto) {
style.setDimension(yoga::Dimension::Height, yoga::value::percent(100));
}

this->yogaNode_.setStyle(style);
this->yogaNode_.setDirty(true);
}
};

} // namespace facebook::react
13 changes: 5 additions & 8 deletions src/elements/Svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ const styles = StyleSheet.create({
const defaultStyle = styles.svg;

export interface SvgProps extends GProps, ViewProps, HitSlop {
width?: NumberProp;
height?: NumberProp;
width?: NumberProp | 'auto';
height?: NumberProp | 'auto';
viewBox?: string;
preserveAspectRatio?: string;
color?: ColorValue;
Expand Down Expand Up @@ -73,8 +73,8 @@ export default class Svg extends Shape<SvgProps> {

setNativeProps = (
props: SvgProps & {
bbWidth?: NumberProp;
bbHeight?: NumberProp;
width?: NumberProp;
height?: NumberProp;
}
) => {
const { root } = this;
Expand Down Expand Up @@ -106,7 +106,7 @@ export default class Svg extends Shape<SvgProps> {
...(Array.isArray(style) ? Object.assign({}, ...style) : style),
...extracted,
};
let {
const {
width,
height,
focusable,
Expand All @@ -126,9 +126,6 @@ export default class Svg extends Shape<SvgProps> {
strokeLinejoin,
strokeMiterlimit,
} = stylesAndProps;
if (width === undefined && height === undefined) {
width = height = '100%';
}

const props: extractedProps = extracted as extractedProps;
props.focusable = Boolean(focusable) && focusable !== 'false';
Expand Down
1 change: 1 addition & 0 deletions src/fabric/AndroidSvgViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ interface NativeProps extends ViewProps {

export default codegenNativeComponent<NativeProps>('RNSVGSvgViewAndroid', {
excludedPlatforms: ['iOS'],
interfaceOnly: true,
});
1 change: 1 addition & 0 deletions src/fabric/IOSSvgViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ interface NativeProps extends ViewProps {

export default codegenNativeComponent<NativeProps>('RNSVGSvgView', {
excludedPlatforms: ['android'],
interfaceOnly: true,
});
Loading