feat: implement getBBox, getCTM, getScreenCTM

make callback last argument, unless options and optional
This commit is contained in:
Mikael Sand
2019-10-04 12:45:49 +03:00
parent cd667d0a39
commit f13d54a850
14 changed files with 243 additions and 58 deletions
@@ -224,7 +224,9 @@ class GroupView extends RenderableView {
if (clipPath != null) {
if (mClipRegionPath != clipPath) {
mClipRegionPath = clipPath;
mClipRegion = getRegion(clipPath);
mClipBounds = new RectF();
clipPath.computeBounds(mClipBounds, true);
mClipRegion = getRegion(clipPath, mClipBounds);
}
if (!mClipRegion.contains(x, y)) {
return -1;
@@ -203,7 +203,6 @@ class ImageView extends RenderableView {
Paint alphaPaint = new Paint();
alphaPaint.setAlpha((int) (opacity * 255));
canvas.drawBitmap(bitmap, null, vbRect, alphaPaint);
//noinspection deprecation
mCTM.mapRect(vbRect);
this.setClientRect(vbRect);
}
@@ -9,7 +9,10 @@
package com.horcrux.svg;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
@@ -69,9 +72,9 @@ class RNSVGRenderableManager extends ReactContextBaseJavaModule {
@SuppressWarnings("unused")
@ReactMethod
public void isPointInFill(int tag, ReadableMap options, Callback successCallback) {
float x = (float)options.getDouble("x");
float y = (float)options.getDouble("y");
float[] src = new float[] { x, y };
float x = (float) options.getDouble("x");
float y = (float) options.getDouble("y");
float[] src = new float[]{x, y};
isPointInFill(tag, src, successCallback, 0);
}
@@ -86,7 +89,7 @@ class RNSVGRenderableManager extends ReactContextBaseJavaModule {
@SuppressWarnings("unused")
@ReactMethod
public void getPointAtLength(int tag, ReadableMap options, Callback successCallback) {
float length = (float)options.getDouble("length");
float length = (float) options.getDouble("length");
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
PathMeasure pm = new PathMeasure(svg.getPath(null, null), false);
float pathLength = pm.getLength();
@@ -95,4 +98,60 @@ class RNSVGRenderableManager extends ReactContextBaseJavaModule {
pm.getPosTan(Math.max(0, Math.min(length, pathLength)), pos, tan);
successCallback.invoke(pos[0], pos[1]);
}
@SuppressWarnings("unused")
@ReactMethod
public void getBBox(int tag, ReadableMap options, Callback successCallback) {
boolean fill = options.getBoolean("fill");
boolean stroke = options.getBoolean("stroke");
boolean markers = options.getBoolean("markers");
boolean clipped = options.getBoolean("clipped");
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
Path path = svg.getPath(null, null);
svg.initBounds();
RectF bounds = new RectF();
if (fill) {
bounds.union(svg.mFillBounds);
}
if (stroke) {
bounds.union(svg.mStrokeBounds);
}
if (markers) {
bounds.union(svg.mMarkerBounds);
}
if (clipped) {
bounds.intersect(svg.mClipBounds);
}
successCallback.invoke(bounds.left, bounds.top, bounds.width(), bounds.height());
}
@SuppressWarnings("unused")
@ReactMethod
public void getCTM(int tag, Callback successCallback) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
Matrix screenCTM = svg.mCTM;
Matrix invViewBox = svg.getSvgView().mInvViewBoxMatrix;
Matrix ctm = new Matrix(screenCTM);
ctm.preConcat(invViewBox);
float[] values = new float[9];
ctm.getValues(values);
successCallback.invoke(
values[Matrix.MSCALE_X], values[Matrix.MSKEW_X], values[Matrix.MTRANS_X],
values[Matrix.MSKEW_Y], values[Matrix.MSCALE_Y], values[Matrix.MTRANS_Y]
);
}
@SuppressWarnings("unused")
@ReactMethod
public void getScreenCTM(int tag, Callback successCallback) {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
Matrix screenCTM = svg.mCTM;
float[] values = new float[9];
screenCTM.getValues(values);
successCallback.invoke(
values[Matrix.MSCALE_X], values[Matrix.MSKEW_X], values[Matrix.MTRANS_X],
values[Matrix.MSKEW_Y], values[Matrix.MSCALE_Y], values[Matrix.MTRANS_Y]
);
}
}
@@ -523,18 +523,8 @@ abstract public class RenderableView extends VirtualView {
int x = Math.round(dst[0]);
int y = Math.round(dst[1]);
if (mRegion == null && mFillPath != null) {
mRegion = getRegion(mFillPath);
}
if (mRegion == null && mPath != null) {
mRegion = getRegion(mPath);
}
if (mStrokeRegion == null && mStrokePath != null) {
mStrokeRegion = getRegion(mStrokePath);
}
if (mMarkerRegion == null && mMarkerPath != null) {
mMarkerRegion = getRegion(mMarkerPath);
}
initBounds();
if (
(mRegion == null || !mRegion.contains(x, y)) &&
(mStrokeRegion == null || !mStrokeRegion.contains(x, y) &&
@@ -545,10 +535,6 @@ abstract public class RenderableView extends VirtualView {
Path clipPath = getClipPath();
if (clipPath != null) {
if (mClipRegionPath != clipPath) {
mClipRegionPath = clipPath;
mClipRegion = getRegion(clipPath);
}
if (!mClipRegion.contains(x, y)) {
return -1;
}
@@ -557,10 +543,39 @@ abstract public class RenderableView extends VirtualView {
return getId();
}
Region getRegion(Path path) {
RectF rectF = new RectF();
path.computeBounds(rectF, true);
void initBounds() {
if (mRegion == null && mFillPath != null) {
mFillBounds = new RectF();
mFillPath.computeBounds(mFillBounds, true);
mRegion = getRegion(mFillPath, mFillBounds);
}
if (mRegion == null && mPath != null) {
mFillBounds = new RectF();
mPath.computeBounds(mFillBounds, true);
mRegion = getRegion(mPath, mFillBounds);
}
if (mStrokeRegion == null && mStrokePath != null) {
mStrokeBounds = new RectF();
mStrokePath.computeBounds(mStrokeBounds, true);
mStrokeRegion = getRegion(mStrokePath, mStrokeBounds);
}
if (mMarkerRegion == null && mMarkerPath != null) {
mMarkerBounds = new RectF();
mMarkerPath.computeBounds(mMarkerBounds, true);
mMarkerRegion = getRegion(mMarkerPath, mMarkerBounds);
}
Path clipPath = getClipPath();
if (clipPath != null) {
if (mClipRegionPath != clipPath) {
mClipRegionPath = clipPath;
mClipBounds = new RectF();
clipPath.computeBounds(mClipBounds, true);
mClipRegion = getRegion(clipPath, mClipBounds);
}
}
}
Region getRegion(Path path, RectF rectF) {
Region region = new Region();
region.setPath(path,
new Region(
@@ -147,7 +147,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
private SVGLength mbbHeight;
private String mAlign;
private int mMeetOrSlice;
private final Matrix mInvViewBoxMatrix = new Matrix();
final Matrix mInvViewBoxMatrix = new Matrix();
private boolean mInvertible = true;
private boolean mRendered = false;
int mTintColor = 0;
@@ -1158,15 +1158,8 @@ class TSpanView extends TextView {
int x = Math.round(dst[0]);
int y = Math.round(dst[1]);
if (mRegion == null && mFillPath != null) {
mRegion = getRegion(mFillPath);
}
if (mRegion == null && mPath != null) {
mRegion = getRegion(mPath);
}
if (mStrokeRegion == null && mStrokePath != null) {
mStrokeRegion = getRegion(mStrokePath);
}
initBounds();
if (
(mRegion == null || !mRegion.contains(x, y)) &&
(mStrokeRegion == null || !mStrokeRegion.contains(x, y))
@@ -1176,10 +1169,6 @@ class TSpanView extends TextView {
Path clipPath = getClipPath();
if (clipPath != null) {
if (mClipRegionPath != clipPath) {
mClipRegionPath = clipPath;
mClipRegion = getRegion(clipPath);
}
if (!mClipRegion.contains(x, y)) {
return -1;
}
@@ -93,6 +93,10 @@ abstract public class VirtualView extends ReactViewGroup {
Path mStrokePath;
Path mMarkerPath;
RectF mBox;
RectF mFillBounds;
RectF mStrokeBounds;
RectF mMarkerBounds;
RectF mClipBounds;
Region mRegion;
Region mMarkerRegion;
Region mStrokeRegion;
+9 -3
View File
@@ -78,10 +78,16 @@
[self setHitArea:path];
if (!CGRectEqualToRect(bounds, CGRectNull)) {
self.clientRect = bounds;
const CGRect fillBounds = CGPathGetBoundingBox(path);
const CGRect strokeBounds = CGPathGetBoundingBox(self.strokePath);
self.pathBounds = CGRectUnion(fillBounds, strokeBounds);
self.fillBounds = CGPathGetBoundingBox(path);
self.strokeBounds = CGPathGetBoundingBox(self.strokePath);
self.pathBounds = CGRectUnion(self.fillBounds, self.strokeBounds);
CGAffineTransform current = CGContextGetCTM(context);
CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
self.ctm = svgToClientTransform;
self.screenCTM = current;
CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint center = CGPointApplyAffineTransform(mid, transform);
+9
View File
@@ -121,6 +121,8 @@
[self setHitArea:hitAreaPath];
CGPathRelease(hitAreaPath);
self.pathBounds = hitArea;
self.fillBounds = hitArea;
self.strokeBounds = hitArea;
// apply viewBox transform on Image render.
CGRect imageBounds = CGRectMake(0, 0, _imageSize.width, _imageSize.height);
@@ -136,6 +138,13 @@
CGRect bounds = hitArea;
self.clientRect = bounds;
CGAffineTransform current = CGContextGetCTM(context);
CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
self.ctm = svgToClientTransform;
self.screenCTM = current;
CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint center = CGPointApplyAffineTransform(mid, transform);
+7
View File
@@ -95,6 +95,13 @@
}
CGRect bounds = template.clientRect;
self.clientRect = bounds;
CGAffineTransform current = CGContextGetCTM(context);
CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
self.ctm = svgToClientTransform;
self.screenCTM = current;
CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint center = CGPointApplyAffineTransform(mid, transform);
+4
View File
@@ -35,6 +35,8 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
@property (nonatomic, strong) NSString *markerMid;
@property (nonatomic, strong) NSString *markerEnd;
@property (nonatomic, assign) BOOL responsible;
@property (nonatomic, assign) CGAffineTransform ctm;
@property (nonatomic, assign) CGAffineTransform screenCTM;
@property (nonatomic, assign) CGAffineTransform matrix;
@property (nonatomic, assign) CGAffineTransform transforms;
@property (nonatomic, assign) CGAffineTransform invmatrix;
@@ -48,6 +50,8 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
@property (nonatomic, assign) CGPathRef markerPath;
@property (nonatomic, assign) CGRect clientRect;
@property (nonatomic, assign) CGRect pathBounds;
@property (nonatomic, assign) CGRect fillBounds;
@property (nonatomic, assign) CGRect strokeBounds;
@property (nonatomic, assign) CGRect markerBounds;
@property (nonatomic, copy) RCTDirectEventBlock onLayout;
+5 -3
View File
@@ -356,9 +356,9 @@ UInt32 saturate(CGFloat value) {
self.path = CGPathRetain(path);
}
[self setHitArea:path];
const CGRect fillBounds = CGPathGetBoundingBox(path);
const CGRect strokeBounds = CGPathGetBoundingBox(self.strokePath);
self.pathBounds = CGRectUnion(fillBounds, strokeBounds);
self.fillBounds = CGPathGetBoundingBox(path);
self.strokeBounds = CGPathGetBoundingBox(self.strokePath);
self.pathBounds = CGRectUnion(self.fillBounds, self.strokeBounds);
}
const CGRect pathBounds = self.pathBounds;
@@ -366,7 +366,9 @@ UInt32 saturate(CGFloat value) {
CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
CGRect clientRect = CGRectApplyAffineTransform(pathBounds, svgToClientTransform);
self.ctm = svgToClientTransform;
self.clientRect = clientRect;
self.screenCTM = current;
if (_vectorEffect == kRNSVGVectorEffectNonScalingStroke) {
path = CGPathCreateCopyByTransformingPath(path, &svgToClientTransform);
+79 -1
View File
@@ -100,7 +100,7 @@ RCT_EXPORT_METHOD(getTotalLength:(nonnull NSNumber *)reactTag callback:(RCTRespo
withTag:reactTag
success:^(RNSVGRenderable *svg){
CGPathRef target = [svg getPath:nil];
RNSVGPathMeasure *measure = [RNSVGPathMeasure init];
RNSVGPathMeasure *measure = [[RNSVGPathMeasure alloc]init];
[measure extractPathData:target];
CGFloat pathLegth = measure.pathLength;
@@ -134,4 +134,82 @@ RCT_EXPORT_METHOD(getPointAtLength:(nonnull NSNumber *)reactTag options:(NSDicti
attempt:0];
}
RCT_EXPORT_METHOD(getBBox:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
{
[self
withTag:reactTag
success:^(RNSVGRenderable *svg){
BOOL fill = [[options objectForKey:@"fill"] boolValue];
BOOL stroke = [[options objectForKey:@"stroke"] boolValue];
BOOL markers = [[options objectForKey:@"markers"] boolValue];
BOOL clipped = [[options objectForKey:@"clipped"] boolValue];
[svg getPath:nil];
CGRect bounds = CGRectZero;
if (fill) {
bounds = CGRectUnion(bounds, svg.fillBounds);
}
if (stroke) {
bounds = CGRectUnion(bounds, svg.strokeBounds);
}
if (markers) {
bounds = CGRectUnion(bounds, svg.markerBounds);
}
if (clipped) {
CGPathRef clipPath = [svg getClipPath];
CGRect clipBounds = CGPathGetBoundingBox(clipPath);
bounds = CGRectIntersection(bounds, clipBounds);
}
CGPoint origin = bounds.origin;
CGSize size = bounds.size;
callback(@[
[NSNumber numberWithDouble:origin.x],
[NSNumber numberWithDouble:origin.y],
[NSNumber numberWithDouble:size.width],
[NSNumber numberWithDouble:size.height]
]);
}
fail:^{
callback(@[[NSNumber numberWithBool:false]]);
}
attempt:0];
}
RCT_EXPORT_METHOD(getCTM:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback)
{
[self
withTag:reactTag
success:^(RNSVGRenderable *svg){
CGAffineTransform ctm = svg.ctm;
callback(@[
n(ctm.a), n(ctm.c), n(ctm.tx),
n(ctm.b), n(ctm.d), n(ctm.ty)
]);
}
fail:^{
callback(@[[NSNumber numberWithBool:false]]);
}
attempt:0];
}
static NSNumber *n(CGFloat af) {
return [NSNumber numberWithDouble:af];
}
RCT_EXPORT_METHOD(getScreenCTM:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback)
{
[self
withTag:reactTag
success:^(RNSVGRenderable *svg){
CGAffineTransform screenCTM = svg.screenCTM;
callback(@[
n(screenCTM.a), n(screenCTM.c), n(screenCTM.tx),
n(screenCTM.b), n(screenCTM.d), n(screenCTM.ty)
]);
}
fail:^{
callback(@[[NSNumber numberWithBool:false]]);
}
attempt:0];
}
@end
+23 -12
View File
@@ -14,10 +14,10 @@ const touchVals = touchKeys.map(key => SvgTouchableMixin[key]);
const numTouchKeys = touchKeys.length;
interface SVGBoundingBoxOptions {
fill: boolean;
stroke: boolean;
markers: boolean;
clipped: boolean;
fill?: boolean;
stroke?: boolean;
markers?: boolean;
clipped?: boolean;
}
interface DOMPointInit {
@@ -59,31 +59,42 @@ export default class Shape<P> extends Component<P> {
if (!callback) {
return;
}
const { fill = true, stroke = true, markers = true, clipped = true } =
options || {};
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getBBox(handle, options, callback); // TODO
RNSVGRenderableManager.getBBox(
handle,
{
fill,
stroke,
markers,
clipped,
},
callback,
);
};
getCTM = (callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getCTM(handle, callback); // TODO
RNSVGRenderableManager.getCTM(handle, callback);
};
getScreenCTM = (callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getScreenCTM(handle, callback); // TODO
RNSVGRenderableManager.getScreenCTM(handle, callback);
};
isPointInFill = (callback: () => void, options: DOMPointInit) => {
isPointInFill = (options: DOMPointInit, callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.isPointInFill(handle, options, callback);
};
isPointInStroke = (callback: () => void, options: DOMPointInit) => {
isPointInStroke = (options: DOMPointInit, callback: () => void) => {
if (!callback) {
return;
}
@@ -95,13 +106,13 @@ export default class Shape<P> extends Component<P> {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getTotalLength(handle, callback); // TODO
RNSVGRenderableManager.getTotalLength(handle, callback);
};
getPointAtLength = (callback: () => void, options: Object) => {
getPointAtLength = (options: Object, callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getPointAtLength(handle, options, callback); // TODO
RNSVGRenderableManager.getPointAtLength(handle, options, callback);
};
}