diff --git a/android/src/main/java/com/horcrux/svg/MarkerView.java b/android/src/main/java/com/horcrux/svg/MarkerView.java new file mode 100644 index 000000000..e8f723957 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/MarkerView.java @@ -0,0 +1,124 @@ +/* + * 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. + */ + + +package com.horcrux.svg; + +import android.annotation.SuppressLint; +import android.graphics.RectF; + +import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.uimanager.annotations.ReactProp; + +@SuppressLint("ViewConstructor") +class MarkerView extends GroupView { + + private SVGLength mRefX; + private SVGLength mRefY; + private SVGLength mMarkerWidth; + private SVGLength mMarkerHeight; + private String mMarkerUnits; + private String mOrient; + + private float mMinX; + private float mMinY; + private float mVbWidth; + private float mVbHeight; + String mAlign; + int mMeetOrSlice; + + public MarkerView(ReactContext reactContext) { + super(reactContext); + } + + @ReactProp(name = "refX") + public void setRefX(Dynamic refX) { + mRefX = SVGLength.from(refX); + invalidate(); + } + + @ReactProp(name = "refY") + public void setRefY(Dynamic refY) { + mRefY = SVGLength.from(refY); + invalidate(); + } + + @ReactProp(name = "markerWidth") + public void setMarkerWidth(Dynamic markerWidth) { + mMarkerWidth = SVGLength.from(markerWidth); + invalidate(); + } + + @ReactProp(name = "markerHeight") + public void setMarkerHeight(Dynamic markerHeight) { + mMarkerHeight = SVGLength.from(markerHeight); + invalidate(); + } + + @ReactProp(name = "markerUnits") + public void setMarkerUnits(String markerUnits) { + mMarkerUnits = markerUnits; + invalidate(); + } + + @ReactProp(name = "orient") + public void setOrient(String orient) { + mOrient = orient; + invalidate(); + } + + @ReactProp(name = "minX") + public void setMinX(float minX) { + mMinX = minX; + invalidate(); + } + + @ReactProp(name = "minY") + public void setMinY(float minY) { + mMinY = minY; + invalidate(); + } + + @ReactProp(name = "vbWidth") + public void setVbWidth(float vbWidth) { + mVbWidth = vbWidth; + invalidate(); + } + + @ReactProp(name = "vbHeight") + public void setVbHeight(float vbHeight) { + mVbHeight = vbHeight; + invalidate(); + } + + @ReactProp(name = "align") + public void setAlign(String align) { + mAlign = align; + invalidate(); + } + + @ReactProp(name = "meetOrSlice") + public void setMeetOrSlice(int meetOrSlice) { + mMeetOrSlice = meetOrSlice; + invalidate(); + } + + + RectF getViewBox() { + return new RectF(mMinX * mScale, mMinY * mScale, (mMinX + mVbWidth) * mScale, (mMinY + mVbHeight) * mScale); + } + + @Override + void saveDefinition() { + if (mName != null) { + SvgView svg = getSvgView(); + svg.defineMarker(this, mName); + } + } +} diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 7bb703187..e93cee8ca 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -51,7 +51,7 @@ */ class RenderableViewManager extends ViewGroupManager { - enum SVGClass { + private enum SVGClass { RNSVGGroup, RNSVGPath, RNSVGText, @@ -70,6 +70,7 @@ enum SVGClass { RNSVGRadialGradient, RNSVGPattern, RNSVGMask, + RNSVGMarker, } class RenderableShadowNode extends LayoutShadowNode { @@ -855,6 +856,72 @@ public void setMaskTransform(MaskView node, @Nullable ReadableArray matrixArray) } } + static class MarkerManager extends GroupViewManager { + MarkerManager() { + super(SVGClass.RNSVGMarker); + } + + @ReactProp(name = "refX") + public void setRefX(MarkerView node, Dynamic refX) { + node.setRefX(refX); + } + + @ReactProp(name = "refY") + public void setRefY(MarkerView node, Dynamic refY) { + node.setRefY(refY); + } + + @ReactProp(name = "markerWidth") + public void setMarkerWidth(MarkerView node, Dynamic markerWidth) { + node.setMarkerWidth(markerWidth); + } + + @ReactProp(name = "markerHeight") + public void setMarkerHeight(MarkerView node, Dynamic markerHeight) { + node.setMarkerHeight(markerHeight); + } + + @ReactProp(name = "markerUnits") + public void setMarkerUnits(MarkerView node, String markerUnits) { + node.setMarkerUnits(markerUnits); + } + + @ReactProp(name = "orient") + public void setOrient(MarkerView node, String orient) { + node.setOrient(orient); + } + + @ReactProp(name = "minX") + public void setMinX(MarkerView node, float minX) { + node.setMinX(minX); + } + + @ReactProp(name = "minY") + public void setMinY(MarkerView node, float minY) { + node.setMinY(minY); + } + + @ReactProp(name = "vbWidth") + public void setVbWidth(MarkerView node, float vbWidth) { + node.setVbWidth(vbWidth); + } + + @ReactProp(name = "vbHeight") + public void setVbHeight(MarkerView node, float vbHeight) { + node.setVbHeight(vbHeight); + } + + @ReactProp(name = "align") + public void setAlign(MarkerView node, String align) { + node.setAlign(align); + } + + @ReactProp(name = "meetOrSlice") + public void setMeetOrSlice(MarkerView node, int meetOrSlice) { + node.setMeetOrSlice(meetOrSlice); + } + } + static class LinearGradientManager extends RenderableViewManager { LinearGradientManager() { super(SVGClass.RNSVGLinearGradient); @@ -1162,6 +1229,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex return new PatternView(reactContext); case RNSVGMask: return new MaskView(reactContext); + case RNSVGMarker: + return new MarkerView(reactContext); default: throw new IllegalStateException("Unexpected type " + svgClass.toString()); } diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 64a2ee177..833b549a2 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -47,6 +47,7 @@ public List createViewManagers(@Nonnull ReactApplicationContext rea new RadialGradientManager(), new PatternManager(), new MaskManager(), + new MarkerManager(), new SvgViewManager()); } diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index a90e4f4c8..36687c65a 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -133,6 +133,7 @@ public int reactTagForTouch(float touchX, float touchY) { private final Map mDefinedClipPaths = new HashMap<>(); private final Map mDefinedTemplates = new HashMap<>(); + private final Map mDefinedMarkers = new HashMap<>(); private final Map mDefinedMasks = new HashMap<>(); private final Map mDefinedBrushes = new HashMap<>(); private Canvas mCanvas; @@ -412,4 +413,12 @@ void defineMask(VirtualView mask, String maskRef) { VirtualView getDefinedMask(String maskRef) { return mDefinedMasks.get(maskRef); } + + void defineMarker(VirtualView marker, String markerRef) { + mDefinedMasks.put(markerRef, marker); + } + + VirtualView getDefinedMarker(String markerRef) { + return mDefinedMasks.get(markerRef); + } } diff --git a/ios/Elements/RNSVGMarker.h b/ios/Elements/RNSVGMarker.h new file mode 100644 index 000000000..4af82eb78 --- /dev/null +++ b/ios/Elements/RNSVGMarker.h @@ -0,0 +1,21 @@ + +#import "RNSVGGroup.h" +#import "RNSVGLength.h" + +@interface RNSVGMarker : RNSVGGroup + +@property (nonatomic, strong) RNSVGLength *refX; +@property (nonatomic, strong) RNSVGLength *refY; +@property (nonatomic, strong) RNSVGLength *markerWidth; +@property (nonatomic, strong) RNSVGLength *markerHeight; +@property (nonatomic, assign) NSString *markerUnits; +@property (nonatomic, assign) NSString *orient; + +@property (nonatomic, assign) CGFloat minX; +@property (nonatomic, assign) CGFloat minY; +@property (nonatomic, assign) CGFloat vbWidth; +@property (nonatomic, assign) CGFloat vbHeight; +@property (nonatomic, strong) NSString *align; +@property (nonatomic, assign) RNSVGVBMOS meetOrSlice; + +@end diff --git a/ios/Elements/RNSVGMarker.m b/ios/Elements/RNSVGMarker.m new file mode 100644 index 000000000..7e6f9bc6f --- /dev/null +++ b/ios/Elements/RNSVGMarker.m @@ -0,0 +1,147 @@ +/** + * 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 "RNSVGMarker.h" +#import "RNSVGPainter.h" +#import "RNSVGBrushType.h" +#import "RNSVGNode.h" + +@implementation RNSVGMarker + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + return nil; +} + +- (void)parseReference +{ + self.dirty = false; + [self.svgView defineMarker:self markerName:self.name]; +} + +- (void)setX:(RNSVGLength *)refX +{ + if ([refX isEqualTo:_refX]) { + return; + } + + _refX = refX; + [self invalidate]; +} + +- (void)setY:(RNSVGLength *)refY +{ + if ([refY isEqualTo:_refY]) { + return; + } + + _refY = refY; + [self invalidate]; +} + +- (void)setMarkerWidth:(RNSVGLength *)markerWidth +{ + if ([markerWidth isEqualTo:_markerWidth]) { + return; + } + + _markerWidth = markerWidth; + [self invalidate]; +} + +- (void)setMarkerHeight:(RNSVGLength *)markerHeight +{ + if ([markerHeight isEqualTo:_markerHeight]) { + return; + } + + _markerHeight = markerHeight; + [self invalidate]; +} + +- (void)setMarkerUnits:(NSString *)markerUnits +{ + if (markerUnits == _markerUnits) { + return; + } + + _markerUnits = markerUnits; + [self invalidate]; +} + +- (void)setOrient:(NSString *)orient +{ + if ([orient isEqualToString:_orient]) { + return; + } + + [self invalidate]; + _orient = orient; +} + +- (void)setMinX:(CGFloat)minX +{ + if (minX == _minX) { + return; + } + + [self invalidate]; + _minX = minX; +} + +- (void)setMinY:(CGFloat)minY +{ + if (minY == _minY) { + return; + } + + [self invalidate]; + _minY = minY; +} + +- (void)setVbWidth:(CGFloat)vbWidth +{ + if (vbWidth == _vbWidth) { + return; + } + + [self invalidate]; + _vbWidth = vbWidth; +} + +- (void)setVbHeight:(CGFloat)vbHeight +{ + if (_vbHeight == vbHeight) { + return; + } + + [self invalidate]; + _vbHeight = vbHeight; +} + +- (void)setAlign:(NSString *)align +{ + if ([align isEqualToString:_align]) { + return; + } + + [self invalidate]; + _align = align; +} + +- (void)setMeetOrSlice:(RNSVGVBMOS)meetOrSlice +{ + if (meetOrSlice == _meetOrSlice) { + return; + } + + [self invalidate]; + _meetOrSlice = meetOrSlice; +} + +@end + diff --git a/ios/RNSVGPattern.h b/ios/Elements/RNSVGPattern.h similarity index 100% rename from ios/RNSVGPattern.h rename to ios/Elements/RNSVGPattern.h diff --git a/ios/Elements/RNSVGSvgView.h b/ios/Elements/RNSVGSvgView.h index 808c41aba..a5150ffc1 100644 --- a/ios/Elements/RNSVGSvgView.h +++ b/ios/Elements/RNSVGSvgView.h @@ -46,6 +46,10 @@ - (RNSVGPainter *)getDefinedPainter:(NSString *)painterName; +- (void)defineMarker:(RNSVGNode *)marker markerName:(NSString *)markerName; + +- (RNSVGNode *)getDefinedMarker:(NSString *)markerName; + - (void)defineMask:(RNSVGNode *)mask maskName:(NSString *)maskName; - (RNSVGNode *)getDefinedMask:(NSString *)maskName; diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index ef486a3bd..e479d5682 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -16,6 +16,7 @@ @implementation RNSVGSvgView NSMutableDictionary *_clipPaths; NSMutableDictionary *_templates; NSMutableDictionary *_painters; + NSMutableDictionary *_markers; NSMutableDictionary *_masks; CGAffineTransform _viewBoxTransform; CGAffineTransform _invviewBoxTransform; @@ -324,6 +325,19 @@ - (RNSVGPainter *)getDefinedPainter:(NSString *)painterName; return _painters ? [_painters objectForKey:painterName] : nil; } +- (void)defineMarker:(RNSVGNode *)marker markerName:(NSString *)markerName +{ + if (!_markers) { + _markers = [[NSMutableDictionary alloc] init]; + } + [_markers setObject:marker forKey:markerName]; +} + +- (RNSVGNode *)getDefinedMarker:(NSString *)markerName; +{ + return _markers ? [_markers objectForKey:markerName] : nil; +} + - (void)defineMask:(RNSVGNode *)mask maskName:(NSString *)maskName { if (!_masks) { diff --git a/ios/ViewManagers/RNSVGMarkerManager.h b/ios/ViewManagers/RNSVGMarkerManager.h new file mode 100644 index 000000000..da104819b --- /dev/null +++ b/ios/ViewManagers/RNSVGMarkerManager.h @@ -0,0 +1,13 @@ +/** + * 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 "RNSVGGroupManager.h" + +@interface RNSVGMarkerManager : RNSVGGroupManager + +@end diff --git a/ios/ViewManagers/RNSVGMarkerManager.m b/ios/ViewManagers/RNSVGMarkerManager.m new file mode 100644 index 000000000..00b762278 --- /dev/null +++ b/ios/ViewManagers/RNSVGMarkerManager.m @@ -0,0 +1,36 @@ +/** + * 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 "RNSVGMarkerManager.h" +#import "RNSVGMarker.h" + +@implementation RNSVGMarkerManager + +RCT_EXPORT_MODULE() + +- (RNSVGMarker *)node +{ + return [RNSVGMarker new]; +} + +RCT_EXPORT_VIEW_PROPERTY(refX, RNSVGLength*) +RCT_EXPORT_VIEW_PROPERTY(refY, RNSVGLength*) +RCT_EXPORT_VIEW_PROPERTY(markerHeight, RNSVGLength*) +RCT_EXPORT_VIEW_PROPERTY(markerWidth, RNSVGLength*) +RCT_EXPORT_VIEW_PROPERTY(markerUnits, NSString*) +RCT_EXPORT_VIEW_PROPERTY(orient, NSString*) + +RCT_EXPORT_VIEW_PROPERTY(minX, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(minY, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(vbWidth, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(vbHeight, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(align, NSString) +RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS) + +@end + diff --git a/src/ReactNativeSVG.ts b/src/ReactNativeSVG.ts index 6480167f1..7ed17fd94 100644 --- a/src/ReactNativeSVG.ts +++ b/src/ReactNativeSVG.ts @@ -21,6 +21,7 @@ import Stop from './elements/Stop'; import ClipPath, { RNSVGClipPath } from './elements/ClipPath'; import Pattern, { RNSVGPattern } from './elements/Pattern'; import Mask, { RNSVGMask } from './elements/Mask'; +import Marker, { RNSVGMarker } from './elements/Marker'; import { parse, SvgAst, SvgFromUri, SvgFromXml, SvgUri, SvgXml } from './xml'; export { @@ -46,6 +47,7 @@ export { ClipPath, Pattern, Mask, + Marker, parse, SvgAst, SvgFromUri, @@ -53,6 +55,7 @@ export { SvgUri, SvgXml, Shape, + RNSVGMarker, RNSVGMask, RNSVGPattern, RNSVGClipPath, diff --git a/src/ReactNativeSVG.web.ts b/src/ReactNativeSVG.web.ts index bb363b308..faf681ff7 100644 --- a/src/ReactNativeSVG.web.ts +++ b/src/ReactNativeSVG.web.ts @@ -218,6 +218,12 @@ export class Mask extends Component { } } +export class Marker extends Component { + render() { + return createElement('marker', prepare(this.props)); + } +} + export class Pattern extends Component { render() { return createElement('pattern', prepare(this.props)); diff --git a/src/elements/Marker.tsx b/src/elements/Marker.tsx new file mode 100644 index 000000000..470b28070 --- /dev/null +++ b/src/elements/Marker.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { requireNativeComponent } from 'react-native'; +import extractViewBox from '../lib/extract/extractViewBox'; +import { NumberProp } from '../lib/extract/types'; +import Shape from './Shape'; + +export default class Marker extends Shape<{ + id?: string; + viewBox?: string; + preserveAspectRatio?: string; + refX?: NumberProp; + refY?: NumberProp; + markerWidth?: NumberProp; + markerHeight?: NumberProp; + markerUnits?: 'strokeWidth' | 'userSpaceOnUse'; + orient?: 'auto' | 'auto-start-reverse' | NumberProp; +}> { + static displayName = 'Marker'; + + static defaultProps = { + refX: 0, + refY: 0, + orient: '0', + markerWidth: 3, + markerHeight: 3, + markerUnits: 'strokeWidth', + }; + + render() { + const { props } = this; + const { + id, + viewBox, + preserveAspectRatio, + refX, + refY, + markerUnits, + orient, + markerWidth, + markerHeight, + children, + } = props; + return ( + + {children} + + ); + } +} + +export const RNSVGMarker = requireNativeComponent('RNSVGMarker'); diff --git a/src/xml.tsx b/src/xml.tsx index 3769de0bc..f5fd151d6 100644 --- a/src/xml.tsx +++ b/src/xml.tsx @@ -27,6 +27,7 @@ import Stop from './elements/Stop'; import ClipPath from './elements/ClipPath'; import Pattern from './elements/Pattern'; import Mask from './elements/Mask'; +import Marker from './elements/Marker'; export const tags: { [tag: string]: ComponentType } = { svg: Svg, @@ -51,6 +52,7 @@ export const tags: { [tag: string]: ComponentType } = { clipPath: ClipPath, pattern: Pattern, mask: Mask, + marker: Marker, }; function missingTag() {