Skip to content

feat: rewrite Svg transform #2403

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

Merged
merged 6 commits into from
Aug 19, 2024
Merged
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
44 changes: 34 additions & 10 deletions android/src/main/java/com/horcrux/svg/SvgView.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public String toString() {
public SvgView(ReactContext reactContext) {
super(reactContext);
mScale = DisplayMetricsHolder.getScreenDisplayMetrics().density;
mScaleX = 1;
mScaleY = 1;
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
mPaint.setTypeface(Typeface.DEFAULT);

// for some reason on Fabric the `onDraw` won't be called without it
setWillNotDraw(false);
}
Expand Down Expand Up @@ -131,7 +136,15 @@ protected void onDraw(Canvas canvas) {
mBitmap = drawOutput();
}
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
if (mScaleX != 1 || mScaleY != 1) {
canvas.drawBitmap(
mBitmap,
-(float) (mBitmap.getWidth() - getWidth()) / 2,
-(float) (mBitmap.getHeight() - getHeight()) / 2,
mPaint);
} else {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
}
if (toDataUrlTask != null) {
toDataUrlTask.run();
toDataUrlTask = null;
Expand Down Expand Up @@ -166,6 +179,9 @@ public int reactTagForTouch(float touchX, float touchY) {
private final Map<String, Brush> mDefinedBrushes = new HashMap<>();
private Canvas mCanvas;
private final float mScale;
private float mScaleX;
private float mScaleY;
private final Paint mPaint = new Paint();

private float mMinX;
private float mMinY;
Expand Down Expand Up @@ -265,7 +281,9 @@ private Bitmap drawOutput() {
if (invalid) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Bitmap bitmap =
Bitmap.createBitmap(
(int) (width * mScaleX), (int) (height * mScaleY), Bitmap.Config.ARGB_8888);
mCurrentBitmap = bitmap;
drawChildren(new Canvas(bitmap));
return bitmap;
Expand Down Expand Up @@ -297,12 +315,6 @@ synchronized void drawChildren(final Canvas canvas) {
canvas.concat(mViewBoxMatrix);
}

final Paint paint = new Paint();

paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG | Paint.SUBPIXEL_TEXT_FLAG);

paint.setTypeface(Typeface.DEFAULT);

for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof VirtualView) {
Expand All @@ -315,7 +327,7 @@ synchronized void drawChildren(final Canvas canvas) {
if (lNode instanceof VirtualView) {
VirtualView node = (VirtualView) lNode;
int count = node.saveAndSetupCanvas(canvas, mViewBoxMatrix);
node.render(canvas, paint, 1f);
node.render(canvas, mPaint, 1f);
node.restoreCanvas(canvas, count);

if (node.isResponsible() && !mResponsible) {
Expand Down Expand Up @@ -374,7 +386,11 @@ private int hitTest(float touchX, float touchY) {
}

float[] transformed = {touchX, touchY};
mInvViewBoxMatrix.mapPoints(transformed);
int width = getWidth();
int height = getHeight();
Matrix invViewBoxMatrix = new Matrix(mInvViewBoxMatrix);
invViewBoxMatrix.preTranslate((width * mScaleX - width) / 2, (height * mScaleY - height) / 2);
invViewBoxMatrix.mapPoints(transformed);

int count = getChildCount();
int viewTag = -1;
Expand Down Expand Up @@ -444,4 +460,12 @@ VirtualView getDefinedMarker(String markerRef) {
public Bitmap getCurrentBitmap() {
return mCurrentBitmap;
}

public void setTransformProperty() {
mScaleX = super.getScaleX();
mScaleY = super.getScaleY();
super.setScaleX(1);
super.setScaleY(1);
invalidate();
}
}
11 changes: 11 additions & 0 deletions android/src/main/java/com/horcrux/svg/SvgViewManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

import android.graphics.Rect;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.PixelUtil;
Expand Down Expand Up @@ -390,4 +392,13 @@ public void setBorderStartEndRadius(SvgView view, double value) {
public void setBorderStartStartRadius(SvgView view, double value) {
super.setBorderRadius(view, 12, (float) value);
}

@Override
public void setTransformProperty(
@NonNull ReactViewGroup view,
@androidx.annotation.Nullable ReadableArray transforms,
@androidx.annotation.Nullable ReadableArray transformOrigin) {
super.setTransformProperty(view, transforms, transformOrigin);
((SvgView) view).setTransformProperty();
}
}
1 change: 1 addition & 0 deletions apps/test-examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Test2327 from './src/Test2327';
import Test2233 from './src/Test2233';
import Test2366 from './src/Test2366';
import Test2397 from './src/Test2397';
import Test2403 from './src/Test2403';
import Test2407 from './src/Test2407';

export default function App() {
Expand Down
95 changes: 95 additions & 0 deletions apps/test-examples/src/Test2403.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {useCallback} from 'react';
import {
Animated,
Button,
Easing,
Text,
View,
useAnimatedValue,
} from 'react-native';
import {Circle, Mask, Path, Rect, Svg} from 'react-native-svg';

export const EASING_IN: (t: number) => number = Easing.bezier(0.7, 0, 0.3, 1);
export const EASING_OUT: (t: number) => number = Easing.bezier(0.5, 0, 0.5, 1);

const AnimatedSvg = Animated.createAnimatedComponent(Svg);

export default function Playground() {
const animatedValue = useAnimatedValue(0);

const handleMouseEnter = useCallback(() => {
Animated.timing(animatedValue, {
duration: 350,
easing: EASING_IN,
toValue: 1,
useNativeDriver: false,
}).start();
}, [animatedValue]);

const handleMouseLeave = useCallback(() => {
Animated.timing(animatedValue, {
duration: 350,
easing: EASING_OUT,
toValue: 0,
useNativeDriver: false,
}).start();
}, [animatedValue]);

return (
<View
style={{
paddingTop: 100,
marginHorizontal: 16,
flex: 1,
}}>
<Text style={{zIndex: 100}}>Text1</Text>
<AnimatedSvg
height="200"
viewBox="0 0 200 200"
width="200"
// use transform or style.transform
// transform={[
// {
// scale: animatedValue.interpolate({
// inputRange: [0, 1],
// outputRange: [1, 1.2],
// }),
// },
// ]}
style={{
transform: [
{
scale: animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.2],
}),
},
],
}}
//
>
<Mask id="myMask">
<Rect fill="white" height="100" width="100" x="0" y="0" />
<Path
d="M10,35 A20,20,0,0,1,50,35 A20,20,0,0,1,90,35 Q90,65,50,95 Q10,65,10,35 Z"
fill="black"
/>
</Mask>
<Rect fill="pink" height="200" width="300" x="0" y="0" />
<Circle
cx="50"
cy="50"
fill="purple"
mask="url(#myMask)"
r="50"
onPress={() => console.log('sadas')}
/>
<Rect fill="green" x="50" y="100" width="100" height="100" />
</AnimatedSvg>

<Text>Text2</Text>
<Button onPress={handleMouseEnter} title="Mouse Enter" />
<Button onPress={handleMouseLeave} title="Mouse Leave" />
</View>
);
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"format-js": "prettier --write README.md CONTRIBUTING.md CODE_OF_CONDUCT.md USAGE.md ./src/**/*.{ts,tsx} ./apps/**/*.tsx ./example/**/*.tsx ./*-example/**/*.tsx",
"jest": "jest",
"lint": "eslint --ext .ts,.tsx src",
"peg": "pegjs -o src/lib/extract/transform.js ./src/lib/extract/transform.peg && peggy -o src/filter-image/extract/extractFiltersString.js src/filter-image/extract/extractFiltersString.pegjs",
"peg": "pegjs -o src/lib/extract/transform.js ./src/lib/extract/transform.peg && peggy -o src/filter-image/extract/extractFiltersString.js src/filter-image/extract/extractFiltersString.pegjs && peggy -o src/lib/extract/transformToRn.js src/lib/extract/transformToRn.pegjs",
"prepare": "npm run bob && husky install",
"release": "npm login && release-it",
"test": "npm run lint && npm run tsc",
Expand Down
17 changes: 7 additions & 10 deletions src/elements/Svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import RNSVGSvgIOS from '../fabric/IOSSvgViewNativeComponent';
import type { Spec } from '../fabric/NativeSvgViewModule';
import extractOpacity from '../lib/extract/extractOpacity';
import { extractTransformSvgView } from '../lib/extract/extractTransform';
import { ViewProps } from '../fabric/utils';

const styles = StyleSheet.create({
Expand Down Expand Up @@ -179,16 +180,12 @@
}

const gStyle = Object.assign({}, StyleSheet.flatten(style));
// if transform prop is of RN style's kind, we want `SvgView` to handle it
// since it can be done here. Otherwise, if transform is of `svg` kind, e.g. string,
// we want G element to parse it since `Svg` does not include parsing of those custom transforms.
// It is problematic due to fact that we either move the `Svg` or just its `G` child, and in the
// second case, when the `G` leaves the area of `Svg`, it will just disappear.
if (Array.isArray(transform) && typeof transform[0] === 'object') {
gStyle.transform = undefined;
} else {
props.transform = undefined;
gStyle.transform = transform;
if (transform) {
if (gStyle.transform) {
props.transform = gStyle.transform;
gStyle.transform = undefined;
}
props.transform = extractTransformSvgView(props as any);

Check warning on line 188 in src/elements/Svg.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
}

const RNSVGSvg = Platform.OS === 'android' ? RNSVGSvgAndroid : RNSVGSvgIOS;
Expand Down
10 changes: 10 additions & 0 deletions src/lib/extract/extractTransform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TransformsStyle } from 'react-native';
import { append, appendTransform, identity, reset, toArray } from '../Matrix2D';
import { parse } from './transform';
import { parse as parseTransformSvgToRnStyle } from './transformToRn';
import type {
ColumnMajorTransformMatrix,
NumberProp,
Expand Down Expand Up @@ -224,3 +225,12 @@ export default function extractTransform(
transformProps?.transform
);
}

export function extractTransformSvgView(
props: TransformsStyle
): TransformsStyle['transform'] {
if (typeof props.transform === 'string') {
return parseTransformSvgToRnStyle(props.transform);
}
return props.transform as TransformsStyle['transform'];
}
4 changes: 4 additions & 0 deletions src/lib/extract/transformToRn.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function parse(
transform: string,
options?: object
): TransformsStyle['transform'];
Loading
Loading