Skip to content

Commit

Permalink
feat: improve marker rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
msand committed Sep 29, 2019
1 parent ceee5ff commit 2e3069d
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 0 deletions.
180 changes: 180 additions & 0 deletions android/src/main/java/com/horcrux/svg/RNSVGMarkerPosition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package com.horcrux.svg;

import java.util.ArrayList;

enum RNSVGMarkerType {
kStartMarker,
kMidMarker,
kEndMarker
}

enum ElementType {
kCGPathElementAddCurveToPoint,
kCGPathElementAddQuadCurveToPoint,
kCGPathElementMoveToPoint,
kCGPathElementAddLineToPoint,
kCGPathElementCloseSubpath
}

class Point {
double x;
double y;
Point(double x, double y){
this.x = x;
this.y = y;
}
}

class SegmentData {
Point start_tangent; // Tangent in the start point of the segment.
Point end_tangent; // Tangent in the end point of the segment.
Point position; // The end point of the segment.
}

class RNSVGMarkerPosition {

static private ArrayList<RNSVGMarkerPosition> positions_;
static private int element_index_;
static private Point origin_;
static private Point subpath_start_;
static private Point in_slope_;
static private Point out_slope_;
@SuppressWarnings("unused")
static private boolean auto_start_reverse_; // TODO

RNSVGMarkerType type;
Point origin;
double angle;

private RNSVGMarkerPosition(RNSVGMarkerType type, Point origin, double angle) {
this.type = type;
this.origin = origin;
this.angle = angle;
}

static ArrayList<RNSVGMarkerPosition> fromPath(ArrayList<PathElement> elements) {
positions_ = new ArrayList<>();
element_index_ = 0;
origin_ = new Point(0, 0);
subpath_start_ = new Point(0, 0);
for (PathElement e : elements) {
UpdateFromPathElement(e);
}
PathIsDone();
return positions_;
}

private static void PathIsDone() {
double angle = CurrentAngle(RNSVGMarkerType.kEndMarker);
positions_.add(new RNSVGMarkerPosition(RNSVGMarkerType.kEndMarker, origin_, angle));
}

private static double BisectingAngle(double in_angle, double out_angle) {
// WK193015: Prevent bugs due to angles being non-continuous.
if (Math.abs(in_angle - out_angle) > 180)
in_angle += 360;
return (in_angle + out_angle) / 2;
}

private static double rad2deg(double rad) {
double RNSVG_radToDeg = 180 / Math.PI;
return rad * RNSVG_radToDeg;
}

private static double SlopeAngleRadians(Point p) {
return Math.atan2(p.y, p.x);
}

private static double CurrentAngle(RNSVGMarkerType type) {
// For details of this calculation, see:
// http://www.w3.org/TR/SVG/single-page.html#painting-MarkerElement
double in_angle = rad2deg(SlopeAngleRadians(in_slope_));
double out_angle = rad2deg(SlopeAngleRadians(out_slope_));
switch (type) {
case kStartMarker:
if (auto_start_reverse_)
out_angle += 180;
return out_angle;
case kMidMarker:
return BisectingAngle(in_angle, out_angle);
case kEndMarker:
return in_angle;
}
return 0;
}

private static Point subtract(Point p1, Point p2) {
return new Point(p2.x - p1.x, p2.y - p1.y);
}

private static boolean isZero(Point p) {
return p.x == 0 && p.y == 0;
}

private static void ComputeQuadTangents(SegmentData data,
Point start,
Point control,
Point end) {
data.start_tangent = subtract(control, start);
data.end_tangent = subtract(end, control);
if (isZero(data.start_tangent))
data.start_tangent = data.end_tangent;
else if (isZero(data.end_tangent))
data.end_tangent = data.start_tangent;
}

private static SegmentData ExtractPathElementFeatures(PathElement element) {
SegmentData data = new SegmentData();
Point[] points = element.points;
switch (element.type) {
case kCGPathElementAddCurveToPoint:
data.position = points[2];
data.start_tangent = subtract(points[0], origin_);
data.end_tangent = subtract(points[2], points[1]);
if (isZero(data.start_tangent))
ComputeQuadTangents(data, points[0], points[1], points[2]);
else if (isZero(data.end_tangent))
ComputeQuadTangents(data, origin_, points[0], points[1]);
break;
case kCGPathElementAddQuadCurveToPoint:
data.position = points[1];
ComputeQuadTangents(data, origin_, points[0], points[1]);
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
data.position = points[0];
data.start_tangent = subtract(data.position, origin_);
data.end_tangent = subtract(data.position, origin_);
break;
case kCGPathElementCloseSubpath:
data.position = subpath_start_;
data.start_tangent = subtract(data.position, origin_);
data.end_tangent = subtract(data.position, origin_);
break;
}
return data;
}

private static void UpdateFromPathElement(PathElement element) {
SegmentData segment_data = ExtractPathElementFeatures(element);
// First update the outgoing slope for the previous element.
out_slope_ = segment_data.start_tangent;
// Record the marker for the previous element.
if (element_index_ > 0) {
RNSVGMarkerType marker_type =
element_index_ == 1 ? RNSVGMarkerType.kStartMarker : RNSVGMarkerType.kMidMarker;
double angle = CurrentAngle(marker_type);
positions_.add(new RNSVGMarkerPosition(marker_type, origin_, angle));
}
// Update the incoming slope for this marker position.
in_slope_ = segment_data.end_tangent;
// Update marker position.
origin_ = segment_data.position;
// If this is a 'move to' segment, save the point for use with 'close'.
if (element.type == ElementType.kCGPathElementMoveToPoint)
subpath_start_ = element.points[0];
else if (element.type == ElementType.kCGPathElementCloseSubpath)
subpath_start_ = new Point(0, 0);
++element_index_;
}
}
3 changes: 3 additions & 0 deletions ios/Utils/RNSVGMarkerPosition.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ + (instancetype) markerPosition:(RNSVGMarkerType)type origin:(CGPoint)origin ang

+ (NSArray<RNSVGMarkerPosition*>*) fromCGPath:(CGPathRef)path {
positions_ = [[NSMutableArray alloc] init];
element_index_ = 0;
origin_ = RNSVGNULLPOINT;
subpath_start_ = RNSVGNULLPOINT;
CGPathApply(path, (__bridge void *)positions_, UpdateFromPathElement);
PathIsDone();
return positions_;
Expand Down

0 comments on commit 2e3069d

Please sign in to comment.