diff --git a/android/src/main/java/com/horcrux/svg/MarkerView.java b/android/src/main/java/com/horcrux/svg/MarkerView.java index e813f6bc2..308c318bc 100644 --- a/android/src/main/java/com/horcrux/svg/MarkerView.java +++ b/android/src/main/java/com/horcrux/svg/MarkerView.java @@ -37,6 +37,8 @@ class MarkerView extends GroupView { String mAlign; int mMeetOrSlice; + Matrix markerTransform = new Matrix(); + public MarkerView(ReactContext reactContext) { super(reactContext); } @@ -130,21 +132,19 @@ void saveDefinition() { void renderMarker(Canvas canvas, Paint paint, float opacity, RNSVGMarkerPosition position, float strokeWidth) { int count = saveAndSetupCanvas(canvas, mCTM); + markerTransform.reset(); Point origin = position.origin; - Matrix transform = new Matrix(); - transform.setTranslate((float)origin.x * mScale, (float)origin.y * mScale); + markerTransform.setTranslate((float)origin.x * mScale, (float)origin.y * mScale); double markerAngle = "auto".equals(mOrient) ? -1 : Double.parseDouble(mOrient); float degrees = 180 + (float) (markerAngle == -1 ? position.angle : markerAngle); - transform.preRotate(degrees); + markerTransform.preRotate(degrees); boolean useStrokeWidth = "strokeWidth".equals(mMarkerUnits); if (useStrokeWidth) { - transform.preScale(strokeWidth, strokeWidth); + markerTransform.preScale(strokeWidth, strokeWidth); } - canvas.concat(transform); - double width = relativeOnWidth(mMarkerWidth) / mScale; double height = relativeOnHeight(mMarkerHeight) / mScale; RectF eRect = new RectF(0, 0, (float)width, (float)height); @@ -153,12 +153,14 @@ void renderMarker(Canvas canvas, Paint paint, float opacity, RNSVGMarkerPosition Matrix viewBoxMatrix = ViewBox.getTransform(vbRect, eRect, mAlign, mMeetOrSlice); float[] values = new float[9]; viewBoxMatrix.getValues(values); - canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]); + markerTransform.preScale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]); } double x = relativeOnWidth(mRefX); double y = relativeOnHeight(mRefY); - canvas.translate((float)-x, (float)-y); + markerTransform.preTranslate((float)-x, (float)-y); + + canvas.concat(markerTransform); drawGroup(canvas, paint, opacity); diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java index 27baf5687..b405086ba 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableView.java +++ b/android/src/main/java/com/horcrux/svg/RenderableView.java @@ -12,6 +12,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.DashPathEffect; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; @@ -382,24 +383,29 @@ void renderMarkers(Canvas canvas, Paint paint, float opacity) { contextElement = this; ArrayList positions = RNSVGMarkerPosition.fromPath(elements); float width = (float)(this.strokeWidth != null ? relativeOnOther(this.strokeWidth) : 1); + mMarkerPath = new Path(); for (RNSVGMarkerPosition position : positions) { RNSVGMarkerType type = position.type; + MarkerView marker = null; switch (type) { case kStartMarker: - if (markerStart != null) markerStart.renderMarker(canvas, paint, opacity, position, width); + marker = markerStart; break; case kMidMarker: - if (markerMid != null) markerMid.renderMarker(canvas, paint, opacity, position, width); + marker = markerMid; break; case kEndMarker: - if (markerEnd != null) markerEnd.renderMarker(canvas, paint, opacity, position, width); - break; - - default: + marker = markerEnd; break; } + if (marker == null) { + continue; + } + marker.renderMarker(canvas, paint, opacity, position, width); + Matrix transform = marker.markerTransform; + mMarkerPath.addPath(marker.getPath(canvas, paint), transform); } contextElement = null; } @@ -520,9 +526,13 @@ int hitTest(final float[] src) { if (mStrokeRegion == null && mStrokePath != null) { mStrokeRegion = getRegion(mStrokePath); } + if (mMarkerRegion == null && mMarkerPath != null) { + mMarkerRegion = getRegion(mMarkerPath); + } if ( (mRegion == null || !mRegion.contains(x, y)) && - (mStrokeRegion == null || !mStrokeRegion.contains(x, y)) + (mStrokeRegion == null || !mStrokeRegion.contains(x, y) && + (mMarkerRegion == null || !mMarkerRegion.contains(x, y))) ) { return -1; } diff --git a/android/src/main/java/com/horcrux/svg/VirtualView.java b/android/src/main/java/com/horcrux/svg/VirtualView.java index d3e9359b0..cb3ba9343 100644 --- a/android/src/main/java/com/horcrux/svg/VirtualView.java +++ b/android/src/main/java/com/horcrux/svg/VirtualView.java @@ -91,8 +91,10 @@ abstract public class VirtualView extends ReactViewGroup { Path mPath; Path mFillPath; Path mStrokePath; + Path mMarkerPath; RectF mBox; Region mRegion; + Region mMarkerRegion; Region mStrokeRegion; Region mClipRegion; Path mClipRegionPath; @@ -114,6 +116,7 @@ void clearCache() { canvasWidth = -1; fontSize = -1; mStrokeRegion = null; + mMarkerRegion = null; mRegion = null; mPath = null; } diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 1a3dd096e..f21fcad2e 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -140,6 +140,7 @@ - (CGPathRef)getPath:(CGContextRef)context if ([node isKindOfClass:[RNSVGNode class]] && ![node isKindOfClass:[RNSVGMask class]]) { CGAffineTransform transform = CGAffineTransformConcat(node.matrix, node.transforms); CGPathAddPath(path, &transform, [node getPath:context]); + CGPathAddPath(path, &transform, [node markerPath]); node.dirty = false; } return YES; diff --git a/ios/Elements/RNSVGMarker.m b/ios/Elements/RNSVGMarker.m index b1827580f..c7b38cabf 100644 --- a/ios/Elements/RNSVGMarker.m +++ b/ios/Elements/RNSVGMarker.m @@ -173,8 +173,6 @@ - (void)renderMarker:(CGContextRef)context rect:(CGRect)rect position:(RNSVGMark transform = CGAffineTransformScale(transform, strokeWidth, strokeWidth); } - CGContextConcatCTM(context, transform); - CGFloat width = [self relativeOnWidth:self.markerWidth]; CGFloat height = [self relativeOnHeight:self.markerHeight]; CGRect eRect = CGRectMake(0, 0, width, height); @@ -183,12 +181,15 @@ - (void)renderMarker:(CGContextRef)context rect:(CGRect)rect position:(RNSVGMark eRect:eRect align:self.align meetOrSlice:self.meetOrSlice]; - CGContextScaleCTM(context, viewBoxTransform.a, viewBoxTransform.d); + transform = CGAffineTransformScale(transform, viewBoxTransform.a, viewBoxTransform.d); } CGFloat x = [self relativeOnWidth:self.refX]; CGFloat y = [self relativeOnHeight:self.refY]; - CGContextTranslateCTM(context, -x, -y); + transform = CGAffineTransformTranslate(transform, -x, -y); + + self.transform = transform; + CGContextConcatCTM(context, transform); [self renderGroupTo:context rect:eRect]; diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index eb8f64ee3..6623a3725 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -45,8 +45,10 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; @property (nonatomic, assign) BOOL skip; @property (nonatomic, assign) CGPathRef path; @property (nonatomic, assign) CGPathRef strokePath; +@property (nonatomic, assign) CGPathRef markerPath; @property (nonatomic, assign) CGRect clientRect; @property (nonatomic, assign) CGRect pathBounds; +@property (nonatomic, assign) CGRect markerBounds; @property (nonatomic, copy) RCTDirectEventBlock onLayout; diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 0ef7f8cea..7ae048989 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -30,6 +30,7 @@ @property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, assign) RNSVGVectorEffect vectorEffect; @property (nonatomic, copy) NSArray *propList; +@property (nonatomic, assign) CGPathRef hitArea; - (void)setHitArea:(CGPathRef)path; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 914170820..29f33e75e 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -23,7 +23,6 @@ @implementation RNSVGRenderable NSArray *_sourceStrokeDashArray; CGFloat *_strokeDashArrayData; CGPathRef _srcHitPath; - CGPathRef _hitArea; } static RNSVGRenderable * _contextElement; @@ -311,25 +310,39 @@ - (void)renderMarkers:(CGContextRef)context path:(CGPathRef)path rect:(const CGR _contextElement = self; NSArray* positions = [RNSVGMarkerPosition fromCGPath:path]; CGFloat width = self.strokeWidth ? [self relativeOnOther:self.strokeWidth] : 1; + __block CGRect bounds = CGRectNull; + CGMutablePathRef markerPath = CGPathCreateMutable(); for (RNSVGMarkerPosition* position in positions) { RNSVGMarkerType type = [position type]; + RNSVGMarker *marker; switch (type) { case kStartMarker: - [markerStart renderMarker:context rect:*rect position:position strokeWidth:width]; + marker = markerStart; break; case kMidMarker: - [markerMid renderMarker:context rect:*rect position:position strokeWidth:width]; + marker = markerMid; break; case kEndMarker: - [markerEnd renderMarker:context rect:*rect position:position strokeWidth:width]; + marker = markerEnd; break; + } + if (!marker) { + continue; + } - default: - break; + [marker renderMarker:context rect:*rect position:position strokeWidth:width]; + CGAffineTransform transform = marker.transform; + CGPathRef hitArea = marker.hitArea; + CGPathAddPath(markerPath, &transform, hitArea); + CGRect nodeRect = marker.pathBounds; + if (!CGRectIsEmpty(nodeRect)) { + bounds = CGRectUnion(bounds, CGRectApplyAffineTransform(nodeRect, transform)); } } + self.markerBounds = bounds; + self.markerPath = markerPath; _contextElement = nil; } } @@ -513,13 +526,15 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); transformed = CGPointApplyAffineTransform(transformed, self.invTransform); - if (!CGRectContainsPoint(self.pathBounds, transformed)) { + if (!CGRectContainsPoint(self.pathBounds, transformed) && + !CGRectContainsPoint(self.markerBounds, transformed)) { return nil; } BOOL evenodd = self.fillRule == kRNSVGCGFCRuleEvenodd; if (!CGPathContainsPoint(_hitArea, nil, transformed, evenodd) && - !CGPathContainsPoint(self.strokePath, nil, transformed, NO)) { + !CGPathContainsPoint(self.strokePath, nil, transformed, NO) && + !CGPathContainsPoint(self.markerPath, nil, transformed, NO)) { return nil; }