mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-08 09:10:44 +00:00
Refactor and simplify, cache path properties.
Remove use of UIBezierPath and categories.
This commit is contained in:
@@ -14,6 +14,6 @@
|
|||||||
|
|
||||||
@property (nonatomic, strong) RNSVGPathParser *d;
|
@property (nonatomic, strong) RNSVGPathParser *d;
|
||||||
|
|
||||||
- (NSArray *)getBezierCurves;
|
- (void)getPathLength:(CGFloat*)length lineCount:(NSInteger*)lineCount lengths:(NSArray* __strong *)lengths lines:(NSArray* __strong *)lines isClosed:(BOOL*)isClosed;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
+251
-5
@@ -7,10 +7,256 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import "RNSVGPath.h"
|
#import "RNSVGPath.h"
|
||||||
|
#import "BezierElement.h"
|
||||||
|
|
||||||
|
/* Bezier logic from PerformanceBezier */
|
||||||
|
/*
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/us/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/">Creative Commons Attribution 3.0 United States License</a>.
|
||||||
|
|
||||||
|
For attribution, please include:
|
||||||
|
|
||||||
|
1. Mention original author "Adam Wulf for Loose Leaf app"
|
||||||
|
2. Link to https://getlooseleaf.com/opensource/
|
||||||
|
3. Link to https://github.com/adamwulf/PerformanceBezier
|
||||||
|
|
||||||
|
*/
|
||||||
|
static CGFloat idealFlatness = .01;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the distance between two points
|
||||||
|
*/
|
||||||
|
CGFloat distance(CGPoint p1, CGPoint p2)
|
||||||
|
{
|
||||||
|
CGFloat dx = p2.x - p1.x;
|
||||||
|
CGFloat dy = p2.y - p1.y;
|
||||||
|
|
||||||
|
return sqrt(dx*dx + dy*dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the dot product of two coordinates
|
||||||
|
*/
|
||||||
|
CGFloat dotProduct(const CGPoint p1, const CGPoint p2) {
|
||||||
|
return p1.x * p2.x + p1.y * p2.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the shortest distance from a point to a line
|
||||||
|
*/
|
||||||
|
CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end){
|
||||||
|
CGPoint v = CGPointMake(end.x - start.x, end.y - start.y);
|
||||||
|
CGPoint w = CGPointMake(point.x - start.x, point.y - start.y);
|
||||||
|
CGFloat c1 = dotProduct(w, v);
|
||||||
|
CGFloat c2 = dotProduct(v, v);
|
||||||
|
CGFloat d;
|
||||||
|
if (c1 <= 0) {
|
||||||
|
d = distance(point, start);
|
||||||
|
}
|
||||||
|
else if (c2 <= c1) {
|
||||||
|
d = distance(point, end);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CGFloat b = c1 / c2;
|
||||||
|
CGPoint Pb = CGPointMake(start.x + b * v.x, start.y + b * v.y);
|
||||||
|
d = distance(point, Pb);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculate the point on a bezier at time t
|
||||||
|
* where 0 < t < 1
|
||||||
|
*/
|
||||||
|
CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t)
|
||||||
|
{
|
||||||
|
CGPoint q;
|
||||||
|
CGFloat mt = 1 - t;
|
||||||
|
|
||||||
|
CGPoint bez1[4];
|
||||||
|
CGPoint bez2[4];
|
||||||
|
|
||||||
|
q.x = mt * bez[1].x + t * bez[2].x;
|
||||||
|
q.y = mt * bez[1].y + t * bez[2].y;
|
||||||
|
bez1[1].x = mt * bez[0].x + t * bez[1].x;
|
||||||
|
bez1[1].y = mt * bez[0].y + t * bez[1].y;
|
||||||
|
bez2[2].x = mt * bez[2].x + t * bez[3].x;
|
||||||
|
bez2[2].y = mt * bez[2].y + t * bez[3].y;
|
||||||
|
|
||||||
|
bez1[2].x = mt * bez1[1].x + t * q.x;
|
||||||
|
bez1[2].y = mt * bez1[1].y + t * q.y;
|
||||||
|
bez2[1].x = mt * q.x + t * bez2[2].x;
|
||||||
|
bez2[1].y = mt * q.y + t * bez2[2].y;
|
||||||
|
|
||||||
|
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
|
||||||
|
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
|
||||||
|
|
||||||
|
return CGPointMake(bez1[3].x, bez1[3].y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subdivide a Bézier (specific division)
|
||||||
|
/*
|
||||||
|
* (c) 2004 Alastair J. Houghton
|
||||||
|
* All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. The name of the author of this software may not be used to endorse
|
||||||
|
* or promote products derived from the software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t)
|
||||||
|
{
|
||||||
|
CGPoint q;
|
||||||
|
CGFloat mt = 1 - t;
|
||||||
|
|
||||||
|
bez1[0].x = bez[0].x;
|
||||||
|
bez1[0].y = bez[0].y;
|
||||||
|
bez2[3].x = bez[3].x;
|
||||||
|
bez2[3].y = bez[3].y;
|
||||||
|
|
||||||
|
q.x = mt * bez[1].x + t * bez[2].x;
|
||||||
|
q.y = mt * bez[1].y + t * bez[2].y;
|
||||||
|
bez1[1].x = mt * bez[0].x + t * bez[1].x;
|
||||||
|
bez1[1].y = mt * bez[0].y + t * bez[1].y;
|
||||||
|
bez2[2].x = mt * bez[2].x + t * bez[3].x;
|
||||||
|
bez2[2].y = mt * bez[2].y + t * bez[3].y;
|
||||||
|
|
||||||
|
bez1[2].x = mt * bez1[1].x + t * q.x;
|
||||||
|
bez1[2].y = mt * bez1[1].y + t * q.y;
|
||||||
|
bez2[1].x = mt * q.x + t * bez2[2].x;
|
||||||
|
bez2[1].y = mt * q.y + t * bez2[2].y;
|
||||||
|
|
||||||
|
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
|
||||||
|
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLine(CGPoint *last, const CGPoint *next, NSMutableArray *lines, CGFloat *length, NSMutableArray *lengths) {
|
||||||
|
NSArray *line = @[[NSValue valueWithCGPoint:*last], [NSValue valueWithCGPoint:*next]];
|
||||||
|
[lines addObject:line];
|
||||||
|
*length += distance(*last, *next);
|
||||||
|
[lengths addObject:[NSNumber numberWithDouble:*length]];
|
||||||
|
*last = *next;
|
||||||
|
}
|
||||||
|
|
||||||
@implementation RNSVGPath
|
@implementation RNSVGPath
|
||||||
{
|
{
|
||||||
CGPathRef _path;
|
CGPathRef _path;
|
||||||
|
NSMutableArray *lengths;
|
||||||
|
NSMutableArray *lines;
|
||||||
|
NSInteger lineCount;
|
||||||
|
CGFloat length;
|
||||||
|
BOOL isClosed;
|
||||||
|
BOOL cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)getPathLength:(CGFloat*)lengthP lineCount:(NSInteger*)lineCountP lengths:(NSArray* __strong *)lengthsP lines:(NSArray* __strong *)linesP isClosed:(BOOL*)isClosedP
|
||||||
|
{
|
||||||
|
if (!cached) {
|
||||||
|
CGPoint origin = CGPointMake (0.0, 0.0);
|
||||||
|
CGPoint last = CGPointMake (0.0, 0.0);
|
||||||
|
lengths = [NSMutableArray array];
|
||||||
|
lines = [NSMutableArray array];
|
||||||
|
isClosed = NO;
|
||||||
|
lineCount = 0;
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
NSArray *elements = [_d getBezierCurves];
|
||||||
|
for (BezierElement *element in elements) {
|
||||||
|
switch (element.elementType)
|
||||||
|
{
|
||||||
|
case kCGPathElementMoveToPoint:
|
||||||
|
origin = last = element.point;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kCGPathElementAddLineToPoint: {
|
||||||
|
CGPoint next = element.point;
|
||||||
|
addLine(&last, &next, lines, &length, lengths);
|
||||||
|
lineCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kCGPathElementAddQuadCurveToPoint:
|
||||||
|
case kCGPathElementAddCurveToPoint:
|
||||||
|
{
|
||||||
|
// handle both curve types gracefully
|
||||||
|
CGPoint curveTo = element.point;
|
||||||
|
CGPoint ctrl1 = element.controlPoint1;
|
||||||
|
CGPoint ctrl2 = element.elementType == kCGPathElementAddQuadCurveToPoint ? ctrl1 : element.controlPoint2;
|
||||||
|
|
||||||
|
// this is the bezier for our current element
|
||||||
|
CGPoint bezier[4] = { last, ctrl1, ctrl2, curveTo };
|
||||||
|
NSValue *arr = [NSValue valueWithBytes:&bezier objCType:@encode(CGPoint[4])];
|
||||||
|
NSMutableArray *curves = [NSMutableArray arrayWithObjects:arr, nil];
|
||||||
|
|
||||||
|
for (NSInteger curveIndex = 0; curveIndex >= 0; curveIndex--) {
|
||||||
|
CGPoint bez[4];
|
||||||
|
[curves[curveIndex] getValue:&bez];
|
||||||
|
[curves removeLastObject];
|
||||||
|
|
||||||
|
// calculate the error rate of the curve vs
|
||||||
|
// a line segement between the start and end points
|
||||||
|
CGPoint onCurve = bezierPointAtT(bez, .5);
|
||||||
|
CGPoint next = bez[3];
|
||||||
|
CGFloat error = distanceOfPointToLine(onCurve, last, next);
|
||||||
|
|
||||||
|
// if the error is less than our accepted level of error
|
||||||
|
// then add a line, else, split the curve in half
|
||||||
|
if (error <= idealFlatness) {
|
||||||
|
addLine(&last, &next, lines, &length, lengths);
|
||||||
|
lineCount++;
|
||||||
|
} else {
|
||||||
|
CGPoint bez1[4], bez2[4];
|
||||||
|
subdivideBezierAtT(bez, bez1, bez2, .5);
|
||||||
|
[curves addObject:[NSValue valueWithBytes:&bez2 objCType:@encode(CGPoint[4])]];
|
||||||
|
[curves addObject:[NSValue valueWithBytes:&bez1 objCType:@encode(CGPoint[4])]];
|
||||||
|
curveIndex += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = curveTo;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case kCGPathElementCloseSubpath: {
|
||||||
|
CGPoint next = origin;
|
||||||
|
addLine(&last, &next, lines, &length, lengths);
|
||||||
|
lineCount++;
|
||||||
|
isClosed = YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cached = YES;
|
||||||
|
}
|
||||||
|
*lineCountP = lineCount;
|
||||||
|
*isClosedP = isClosed;
|
||||||
|
*lengthsP = lengths;
|
||||||
|
*lengthP = length;
|
||||||
|
*linesP = lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setD:(RNSVGPathParser *)d
|
- (void)setD:(RNSVGPathParser *)d
|
||||||
@@ -21,6 +267,9 @@
|
|||||||
|
|
||||||
[self invalidate];
|
[self invalidate];
|
||||||
_d = d;
|
_d = d;
|
||||||
|
lines = nil;
|
||||||
|
lengths = nil;
|
||||||
|
cached = false;
|
||||||
CGPathRelease(_path);
|
CGPathRelease(_path);
|
||||||
_path = CGPathRetain([d getPath]);
|
_path = CGPathRetain([d getPath]);
|
||||||
}
|
}
|
||||||
@@ -30,13 +279,10 @@
|
|||||||
return _path;
|
return _path;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)getBezierCurves
|
|
||||||
{
|
|
||||||
return [_d getBezierCurves];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
|
lines = nil;
|
||||||
|
lengths = nil;
|
||||||
CGPathRelease(_path);
|
CGPathRelease(_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,10 +87,7 @@
|
|||||||
9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; };
|
9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; };
|
||||||
9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; };
|
9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; };
|
||||||
9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; };
|
9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; };
|
||||||
94EB93171FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */; };
|
94C70B1A1FF6B1C0004DFD49 /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C70B171FF6B1C0004DFD49 /* BezierElement.m */; };
|
||||||
94EB93181FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */; };
|
|
||||||
94EB936C1FF4916F00C0B251 /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB936B1FF4916F00C0B251 /* BezierElement.m */; };
|
|
||||||
94EB936D1FF4916F00C0B251 /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EB936B1FF4916F00C0B251 /* BezierElement.m */; };
|
|
||||||
A361E76E1EB0C33D00646005 /* RNSVGTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */; };
|
A361E76E1EB0C33D00646005 /* RNSVGTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */; };
|
||||||
A361E76F1EB0C33D00646005 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; };
|
A361E76F1EB0C33D00646005 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; };
|
||||||
A361E7701EB0C33D00646005 /* RNSVGRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D471CE74E3D00887C2B /* RNSVGRect.m */; };
|
A361E7701EB0C33D00646005 /* RNSVGRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D471CE74E3D00887C2B /* RNSVGRect.m */; };
|
||||||
@@ -295,11 +292,9 @@
|
|||||||
9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathMidLine.m; path = Text/TextPathMidLine.m; sourceTree = "<group>"; };
|
9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathMidLine.m; path = Text/TextPathMidLine.m; sourceTree = "<group>"; };
|
||||||
9494C5361F4C44DD00D5BCFD /* TextPathSide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSide.m; path = Text/TextPathSide.m; sourceTree = "<group>"; };
|
9494C5361F4C44DD00D5BCFD /* TextPathSide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSide.m; path = Text/TextPathSide.m; sourceTree = "<group>"; };
|
||||||
9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSpacing.m; path = Text/TextPathSpacing.m; sourceTree = "<group>"; };
|
9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSpacing.m; path = Text/TextPathSpacing.m; sourceTree = "<group>"; };
|
||||||
|
94C70B151FF6B1BF004DFD49 /* BezierElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BezierElement.h; path = Utils/BezierElement.h; sourceTree = "<group>"; };
|
||||||
|
94C70B171FF6B1C0004DFD49 /* BezierElement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BezierElement.m; path = Utils/BezierElement.m; sourceTree = "<group>"; };
|
||||||
94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
94EB93151FF4196100C0B251 /* UIBezierPath+TextRendering.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath+TextRendering.h"; path = "Utils/UIBezierPath+TextRendering.h"; sourceTree = "<group>"; };
|
|
||||||
94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+TextRendering.m"; path = "Utils/UIBezierPath+TextRendering.m"; sourceTree = "<group>"; };
|
|
||||||
94EB936B1FF4916F00C0B251 /* BezierElement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierElement.m; path = Utils/BezierElement.m; sourceTree = "<group>"; };
|
|
||||||
94EB93701FF4918D00C0B251 /* BezierElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BezierElement.h; path = Utils/BezierElement.h; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -501,6 +496,8 @@
|
|||||||
1039D29A1CE7212C001E90A8 /* Utils */ = {
|
1039D29A1CE7212C001E90A8 /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
94C70B151FF6B1BF004DFD49 /* BezierElement.h */,
|
||||||
|
94C70B171FF6B1C0004DFD49 /* BezierElement.m */,
|
||||||
7F69160D1E3703D800DA6EDC /* RNSVGUnits.h */,
|
7F69160D1E3703D800DA6EDC /* RNSVGUnits.h */,
|
||||||
10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */,
|
10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */,
|
||||||
10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */,
|
10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */,
|
||||||
@@ -514,10 +511,6 @@
|
|||||||
7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */,
|
7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */,
|
||||||
1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */,
|
1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */,
|
||||||
1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */,
|
1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */,
|
||||||
94EB93151FF4196100C0B251 /* UIBezierPath+TextRendering.h */,
|
|
||||||
94EB93161FF4196100C0B251 /* UIBezierPath+TextRendering.m */,
|
|
||||||
94EB936B1FF4916F00C0B251 /* BezierElement.m */,
|
|
||||||
94EB93701FF4918D00C0B251 /* BezierElement.h */,
|
|
||||||
);
|
);
|
||||||
name = Utils;
|
name = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -614,7 +607,6 @@
|
|||||||
10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */,
|
10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */,
|
||||||
10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */,
|
10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */,
|
||||||
9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */,
|
9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */,
|
||||||
94EB93171FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */,
|
|
||||||
1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */,
|
1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */,
|
||||||
9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */,
|
9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */,
|
||||||
10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */,
|
10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */,
|
||||||
@@ -649,7 +641,7 @@
|
|||||||
9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */,
|
9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */,
|
||||||
10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */,
|
10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */,
|
||||||
9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */,
|
9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */,
|
||||||
94EB936C1FF4916F00C0B251 /* BezierElement.m in Sources */,
|
94C70B1A1FF6B1C0004DFD49 /* BezierElement.m in Sources */,
|
||||||
10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */,
|
10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */,
|
||||||
1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */,
|
1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */,
|
||||||
9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */,
|
9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */,
|
||||||
@@ -683,7 +675,6 @@
|
|||||||
A361E7711EB0C33D00646005 /* RNSVGCircleManager.m in Sources */,
|
A361E7711EB0C33D00646005 /* RNSVGCircleManager.m in Sources */,
|
||||||
A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */,
|
A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */,
|
||||||
A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */,
|
A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */,
|
||||||
94EB93181FF4196100C0B251 /* UIBezierPath+TextRendering.m in Sources */,
|
|
||||||
9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */,
|
9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */,
|
||||||
A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */,
|
A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */,
|
||||||
A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */,
|
A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */,
|
||||||
@@ -718,7 +709,6 @@
|
|||||||
9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */,
|
9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */,
|
||||||
9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */,
|
9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */,
|
||||||
9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */,
|
9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */,
|
||||||
94EB936D1FF4916F00C0B251 /* BezierElement.m in Sources */,
|
|
||||||
A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */,
|
A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */,
|
||||||
9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */,
|
9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */,
|
||||||
9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */,
|
9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */,
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
#import "TextPathSpacing.h"
|
#import "TextPathSpacing.h"
|
||||||
#import "TextLengthAdjust.h"
|
#import "TextLengthAdjust.h"
|
||||||
#import "AlignmentBaseline.h"
|
#import "AlignmentBaseline.h"
|
||||||
#import "UIBezierPath+TextRendering.h"
|
|
||||||
|
|
||||||
|
|
||||||
@interface RNSVGTSpan : RNSVGText
|
@interface RNSVGTSpan : RNSVGText
|
||||||
|
|
||||||
|
|||||||
+14
-18
@@ -18,8 +18,8 @@ NSCharacterSet *separators = nil;
|
|||||||
CGPathRef _cache;
|
CGPathRef _cache;
|
||||||
CGFloat _pathLength;
|
CGFloat _pathLength;
|
||||||
RNSVGTextPath *textPath;
|
RNSVGTextPath *textPath;
|
||||||
NSMutableArray *lengths;
|
NSArray *lengths;
|
||||||
NSMutableArray *lines;
|
NSArray *lines;
|
||||||
NSInteger lineCount;
|
NSInteger lineCount;
|
||||||
BOOL isClosed;
|
BOOL isClosed;
|
||||||
}
|
}
|
||||||
@@ -788,7 +788,7 @@ NSCharacterSet *separators = nil;
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
CGFloat totalLength = 0;
|
CGFloat totalLength = 0;
|
||||||
CGFloat prevLength = 0;
|
CGFloat prevLength = 0;
|
||||||
|
|
||||||
// TODO investigate at what lineCount a binary search is faster
|
// TODO investigate at what lineCount a binary search is faster
|
||||||
while (i < lineCount - 1) {
|
while (i < lineCount - 1) {
|
||||||
prevLength = totalLength;
|
prevLength = totalLength;
|
||||||
@@ -799,25 +799,24 @@ NSCharacterSet *separators = nil;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CGFloat length = totalLength - prevLength;
|
CGFloat length = totalLength - prevLength;
|
||||||
CGFloat targetPercent = (midPoint - prevLength) / length;
|
CGFloat targetPercent = (midPoint - prevLength) / length;
|
||||||
|
|
||||||
NSArray * points = [lines objectAtIndex: i];
|
NSArray * points = [lines objectAtIndex: i];
|
||||||
CGPoint p1 = [[points objectAtIndex: 0] CGPointValue];
|
CGPoint p1 = [[points objectAtIndex: 0] CGPointValue];
|
||||||
CGPoint p2 = [[points objectAtIndex: 1] CGPointValue];
|
CGPoint p2 = [[points objectAtIndex: 1] CGPointValue];
|
||||||
|
|
||||||
CGPoint slope;
|
CGPoint slope;
|
||||||
CGPoint mid = InterpolateLineSegment(p1, p2, targetPercent, &slope);
|
CGPoint mid = InterpolateLineSegment(p1, p2, targetPercent, &slope);
|
||||||
|
|
||||||
// Calculate the rotation
|
// Calculate the rotation
|
||||||
double angle = atan2(slope.y, slope.x);
|
double angle = atan2(slope.y, slope.x);
|
||||||
|
|
||||||
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(mid.x, mid.y), transform);
|
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(mid.x, mid.y), transform);
|
||||||
transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(angle + r), transform);
|
transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(angle + r), transform);
|
||||||
transform = CGAffineTransformScale(transform, scaledDirection, side);
|
transform = CGAffineTransformScale(transform, scaledDirection, side);
|
||||||
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-halfWay, dy + baselineShift), transform);
|
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-halfWay, y + dy + baselineShift), transform);
|
||||||
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, y), transform);
|
|
||||||
} else {
|
} else {
|
||||||
transform = CGAffineTransformMakeTranslation(startPoint, y + dy + baselineShift);
|
transform = CGAffineTransformMakeTranslation(startPoint, y + dy + baselineShift);
|
||||||
transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(r), transform);
|
transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(r), transform);
|
||||||
@@ -839,13 +838,13 @@ CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint
|
|||||||
{
|
{
|
||||||
CGFloat dx = p2.x - p1.x;
|
CGFloat dx = p2.x - p1.x;
|
||||||
CGFloat dy = p2.y - p1.y;
|
CGFloat dy = p2.y - p1.y;
|
||||||
|
|
||||||
if (slope)
|
if (slope)
|
||||||
*slope = CGPointMake(dx, dy);
|
*slope = CGPointMake(dx, dy);
|
||||||
|
|
||||||
CGFloat px = p1.x + dx * percent;
|
CGFloat px = p1.x + dx * percent;
|
||||||
CGFloat py = p1.y + dy * percent;
|
CGFloat py = p1.y + dy * percent;
|
||||||
|
|
||||||
return CGPointMake(px, py);
|
return CGPointMake(px, py);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,16 +864,13 @@ CGFloat getTextAnchorOffset(enum TextAnchor textAnchor, CGFloat width)
|
|||||||
|
|
||||||
- (void)setupTextPath:(CGContextRef)context
|
- (void)setupTextPath:(CGContextRef)context
|
||||||
{
|
{
|
||||||
|
lines = nil;
|
||||||
|
lengths = nil;
|
||||||
textPath = nil;
|
textPath = nil;
|
||||||
[self traverseTextSuperviews:^(__kindof RNSVGText *node) {
|
[self traverseTextSuperviews:^(__kindof RNSVGText *node) {
|
||||||
if ([node class] == [RNSVGTextPath class]) {
|
if ([node class] == [RNSVGTextPath class]) {
|
||||||
textPath = (RNSVGTextPath*) node;
|
textPath = (RNSVGTextPath*) node;
|
||||||
RNSVGPath *svgPath = [textPath getPath];
|
[[textPath getPath] getPathLength:&_pathLength lineCount:&lineCount lengths:&lengths lines:&lines isClosed:&isClosed];
|
||||||
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:[svgPath getPath:nil]];
|
|
||||||
|
|
||||||
lines = [NSMutableArray array];
|
|
||||||
lengths = [NSMutableArray array];
|
|
||||||
[bezierPath getTextProperties](&_pathLength, &lineCount, lengths, lines, &isClosed);
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
Erica Sadun, http://ericasadun.com
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
Erica Sadun, http://ericasadun.com
|
||||||
|
https://github.com/erica/iOS-Drawing/tree/master/C08/Quartz%20Book%20Pack/Bezier
|
||||||
|
*/
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
#define NULLPOINT CGRectNull.origin
|
#define NULLPOINT CGRectNull.origin
|
||||||
#define POINT_IS_NULL(_POINT_) CGPointEqualToPoint(_POINT_, NULLPOINT)
|
|
||||||
|
|
||||||
@interface BezierElement : NSObject
|
@interface BezierElement : NSObject
|
||||||
|
|
||||||
@@ -19,8 +17,5 @@
|
|||||||
@property (nonatomic, assign) CGPoint controlPoint1;
|
@property (nonatomic, assign) CGPoint controlPoint1;
|
||||||
@property (nonatomic, assign) CGPoint controlPoint2;
|
@property (nonatomic, assign) CGPoint controlPoint2;
|
||||||
|
|
||||||
// Instance creation
|
|
||||||
+ (instancetype) elementWithPathElement: (CGPathElement) element;
|
|
||||||
|
|
||||||
@end;
|
@end;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
Erica Sadun, http://ericasadun.com
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
Erica Sadun, http://ericasadun.com
|
||||||
|
https://github.com/erica/iOS-Drawing/tree/master/C08/Quartz%20Book%20Pack/Bezier
|
||||||
|
*/
|
||||||
|
|
||||||
#import "BezierElement.h"
|
#import "BezierElement.h"
|
||||||
|
|
||||||
@@ -22,41 +21,5 @@
|
|||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype) elementWithPathElement: (CGPathElement) element
|
|
||||||
{
|
|
||||||
BezierElement *newElement = [[self alloc] init];
|
|
||||||
newElement.elementType = element.type;
|
|
||||||
|
|
||||||
switch (newElement.elementType)
|
|
||||||
{
|
|
||||||
case kCGPathElementCloseSubpath:
|
|
||||||
break;
|
|
||||||
case kCGPathElementMoveToPoint:
|
|
||||||
case kCGPathElementAddLineToPoint:
|
|
||||||
{
|
|
||||||
newElement.point = element.points[0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case kCGPathElementAddQuadCurveToPoint:
|
|
||||||
{
|
|
||||||
newElement.point = element.points[1];
|
|
||||||
newElement.controlPoint1 = element.points[0];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case kCGPathElementAddCurveToPoint:
|
|
||||||
{
|
|
||||||
newElement.point = element.points[2];
|
|
||||||
newElement.controlPoint1 = element.points[0];
|
|
||||||
newElement.controlPoint2 = element.points[1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
+33
-20
@@ -9,14 +9,14 @@
|
|||||||
#import "RNSVGPathParser.h"
|
#import "RNSVGPathParser.h"
|
||||||
#import <React/RCTLog.h>
|
#import <React/RCTLog.h>
|
||||||
#import "math.h"
|
#import "math.h"
|
||||||
|
#import "BezierElement.h"
|
||||||
|
|
||||||
@implementation RNSVGPathParser
|
@implementation RNSVGPathParser
|
||||||
{
|
{
|
||||||
NSString* _d;
|
NSString* _d;
|
||||||
NSString* _originD;
|
NSString* _originD;
|
||||||
NSRegularExpression* _pathRegularExpression;
|
NSRegularExpression* _pathRegularExpression;
|
||||||
NSMutableArray<NSArray *>* _bezierCurves;
|
NSMutableArray<BezierElement*>* _bezierCurves;
|
||||||
NSValue *_lastStartPoint;
|
|
||||||
float _penX;
|
float _penX;
|
||||||
float _penY;
|
float _penY;
|
||||||
float _penDownX;
|
float _penDownX;
|
||||||
@@ -159,8 +159,10 @@
|
|||||||
_pivotY = _penY = y;
|
_pivotY = _penY = y;
|
||||||
CGPathMoveToPoint(path, nil, x, y);
|
CGPathMoveToPoint(path, nil, x, y);
|
||||||
|
|
||||||
_lastStartPoint = [NSValue valueWithCGPoint: CGPointMake(x, y)];
|
BezierElement *newElement = [[BezierElement alloc] init];
|
||||||
[_bezierCurves addObject: @[_lastStartPoint]];
|
newElement.elementType = kCGPathElementMoveToPoint;
|
||||||
|
newElement.point = CGPointMake(x, y);
|
||||||
|
[_bezierCurves addObject:newElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)line:(CGMutablePathRef)path x:(float)x y:(float)y
|
- (void)line:(CGMutablePathRef)path x:(float)x y:(float)y
|
||||||
@@ -174,8 +176,10 @@
|
|||||||
_pivotY = _penY = y;
|
_pivotY = _penY = y;
|
||||||
CGPathAddLineToPoint(path, nil, x, y);
|
CGPathAddLineToPoint(path, nil, x, y);
|
||||||
|
|
||||||
NSValue * destination = [NSValue valueWithCGPoint:CGPointMake(x, y)];
|
BezierElement *newElement = [[BezierElement alloc] init];
|
||||||
[_bezierCurves addObject: @[destination, destination, destination]];
|
newElement.elementType = kCGPathElementAddLineToPoint;
|
||||||
|
newElement.point = CGPointMake(x, y);
|
||||||
|
[_bezierCurves addObject:newElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)curve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey
|
- (void)curve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y c2x:(float)c2x c2y:(float)c2y ex:(float)ex ey:(float)ey
|
||||||
@@ -202,11 +206,12 @@
|
|||||||
_penY = ey;
|
_penY = ey;
|
||||||
CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey);
|
CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey);
|
||||||
|
|
||||||
[_bezierCurves addObject: @[
|
BezierElement *newElement = [[BezierElement alloc] init];
|
||||||
[NSValue valueWithCGPoint:CGPointMake(c1x, c1y)],
|
newElement.elementType = kCGPathElementAddCurveToPoint;
|
||||||
[NSValue valueWithCGPoint:CGPointMake(c2x, c2y)],
|
newElement.controlPoint1 = CGPointMake(c1x, c1y);
|
||||||
[NSValue valueWithCGPoint:CGPointMake(ex, ey)]
|
newElement.controlPoint2 = CGPointMake(c2x, c2y);
|
||||||
]];
|
newElement.point = CGPointMake(ex, ey);
|
||||||
|
[_bezierCurves addObject:newElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)smoothCurve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y ex:(float)ex ey:(float)ey
|
- (void)smoothCurve:(CGMutablePathRef)path c1x:(float)c1x c1y:(float)c1y ex:(float)ex ey:(float)ey
|
||||||
@@ -367,14 +372,20 @@
|
|||||||
float cp2x = x + k * y;
|
float cp2x = x + k * y;
|
||||||
float cp2y = y - k * x;
|
float cp2y = y - k * x;
|
||||||
|
|
||||||
CGPathAddCurveToPoint(path,
|
float c1x = cx + xx * cp1x + yx * cp1y;
|
||||||
nil,
|
float c1y = cy + xy * cp1x + yy * cp1y;
|
||||||
cx + xx * cp1x + yx * cp1y,
|
float c2x = cx + xx * cp2x + yx * cp2y;
|
||||||
cy + xy * cp1x + yy * cp1y,
|
float c2y = cy + xy * cp2x + yy * cp2y;
|
||||||
cx + xx * cp2x + yx * cp2y,
|
float ex = cx + xx * x + yx * y;
|
||||||
cy + xy * cp2x + yy * cp2y,
|
float ey = cy + xy * x + yy * y;
|
||||||
cx + xx * x + yx * y,
|
CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey);
|
||||||
cy + xy * x + yy * y);
|
|
||||||
|
BezierElement *newElement = [[BezierElement alloc] init];
|
||||||
|
newElement.elementType = kCGPathElementAddCurveToPoint;
|
||||||
|
newElement.controlPoint1 = CGPointMake(c1x, c1y);
|
||||||
|
newElement.controlPoint2 = CGPointMake(c2x, c2y);
|
||||||
|
newElement.point = CGPointMake(ex, ey);
|
||||||
|
[_bezierCurves addObject:newElement];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +396,9 @@
|
|||||||
_penY = _penDownY;
|
_penY = _penDownY;
|
||||||
_penDownSet = NO;
|
_penDownSet = NO;
|
||||||
CGPathCloseSubpath(path);
|
CGPathCloseSubpath(path);
|
||||||
[_bezierCurves addObject: @[_lastStartPoint, _lastStartPoint, _lastStartPoint]];
|
BezierElement *newElement = [[BezierElement alloc] init];
|
||||||
|
newElement.elementType = kCGPathElementCloseSubpath;
|
||||||
|
[_bezierCurves addObject:newElement];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//
|
|
||||||
// UIBezierPath+TextRendering.h
|
|
||||||
// RNSVG
|
|
||||||
//
|
|
||||||
// Created by Mikael Sand on 27/12/2017.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
@interface UIBezierPath (TextRendering)
|
|
||||||
- (void (^)(CGFloat *lengthP, NSInteger *lineCountP, NSMutableArray * lengthsP, NSMutableArray * linesP, BOOL *isClosedP)) getTextProperties;
|
|
||||||
@end
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
//
|
|
||||||
// UIBezierPath+TextRendering.m
|
|
||||||
// RNSVG
|
|
||||||
//
|
|
||||||
// Created by Mikael Sand on 27/12/2017.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "UIBezierPath+TextRendering.h"
|
|
||||||
#import "BezierElement.h"
|
|
||||||
|
|
||||||
static CGFloat idealFlatness = .01;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the distance between two points
|
|
||||||
*/
|
|
||||||
CGFloat distance(CGPoint p1, CGPoint p2)
|
|
||||||
{
|
|
||||||
CGFloat dx = p2.x - p1.x;
|
|
||||||
CGFloat dy = p2.y - p1.y;
|
|
||||||
|
|
||||||
return sqrt(dx*dx + dy*dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the dot product of two coordinates
|
|
||||||
*/
|
|
||||||
CGFloat dotProduct(const CGPoint p1, const CGPoint p2) {
|
|
||||||
return p1.x * p2.x + p1.y * p2.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the shortest distance from a point to a line
|
|
||||||
*/
|
|
||||||
CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end){
|
|
||||||
CGPoint v = CGPointMake(end.x - start.x, end.y - start.y);
|
|
||||||
CGPoint w = CGPointMake(point.x - start.x, point.y - start.y);
|
|
||||||
CGFloat c1 = dotProduct(w, v);
|
|
||||||
CGFloat c2 = dotProduct(v, v);
|
|
||||||
CGFloat d;
|
|
||||||
if (c1 <= 0) {
|
|
||||||
d = distance(point, start);
|
|
||||||
}
|
|
||||||
else if (c2 <= c1) {
|
|
||||||
d = distance(point, end);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CGFloat b = c1 / c2;
|
|
||||||
CGPoint Pb = CGPointMake(start.x + b * v.x, start.y + b * v.y);
|
|
||||||
d = distance(point, Pb);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* calculate the point on a bezier at time t
|
|
||||||
* where 0 < t < 1
|
|
||||||
*/
|
|
||||||
CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t)
|
|
||||||
{
|
|
||||||
CGPoint q;
|
|
||||||
CGFloat mt = 1 - t;
|
|
||||||
|
|
||||||
CGPoint bez1[4];
|
|
||||||
CGPoint bez2[4];
|
|
||||||
|
|
||||||
q.x = mt * bez[1].x + t * bez[2].x;
|
|
||||||
q.y = mt * bez[1].y + t * bez[2].y;
|
|
||||||
bez1[1].x = mt * bez[0].x + t * bez[1].x;
|
|
||||||
bez1[1].y = mt * bez[0].y + t * bez[1].y;
|
|
||||||
bez2[2].x = mt * bez[2].x + t * bez[3].x;
|
|
||||||
bez2[2].y = mt * bez[2].y + t * bez[3].y;
|
|
||||||
|
|
||||||
bez1[2].x = mt * bez1[1].x + t * q.x;
|
|
||||||
bez1[2].y = mt * bez1[1].y + t * q.y;
|
|
||||||
bez2[1].x = mt * q.x + t * bez2[2].x;
|
|
||||||
bez2[1].y = mt * q.y + t * bez2[2].y;
|
|
||||||
|
|
||||||
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
|
|
||||||
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
|
|
||||||
|
|
||||||
return CGPointMake(bez1[3].x, bez1[3].y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subdivide a Bézier (specific division)
|
|
||||||
void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t)
|
|
||||||
{
|
|
||||||
CGPoint q;
|
|
||||||
CGFloat mt = 1 - t;
|
|
||||||
|
|
||||||
bez1[0].x = bez[0].x;
|
|
||||||
bez1[0].y = bez[0].y;
|
|
||||||
bez2[3].x = bez[3].x;
|
|
||||||
bez2[3].y = bez[3].y;
|
|
||||||
|
|
||||||
q.x = mt * bez[1].x + t * bez[2].x;
|
|
||||||
q.y = mt * bez[1].y + t * bez[2].y;
|
|
||||||
bez1[1].x = mt * bez[0].x + t * bez[1].x;
|
|
||||||
bez1[1].y = mt * bez[0].y + t * bez[1].y;
|
|
||||||
bez2[2].x = mt * bez[2].x + t * bez[3].x;
|
|
||||||
bez2[2].y = mt * bez[2].y + t * bez[3].y;
|
|
||||||
|
|
||||||
bez1[2].x = mt * bez1[1].x + t * q.x;
|
|
||||||
bez1[2].y = mt * bez1[1].y + t * q.y;
|
|
||||||
bez2[1].x = mt * q.x + t * bez2[2].x;
|
|
||||||
bez2[1].y = mt * q.y + t * bez2[2].y;
|
|
||||||
|
|
||||||
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
|
|
||||||
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addLine(CGPoint *last, const CGPoint *next, NSMutableArray *lines, CGFloat *length, NSMutableArray *lengths) {
|
|
||||||
NSArray *line = @[[NSValue valueWithCGPoint:*last], [NSValue valueWithCGPoint:*next]];
|
|
||||||
[lines addObject:line];
|
|
||||||
*length += distance(*last, *next);
|
|
||||||
[lengths addObject:[NSNumber numberWithDouble:*length]];
|
|
||||||
*last = *next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert one element to BezierElement and save to array
|
|
||||||
void GetBezierElements(void *info, const CGPathElement *element)
|
|
||||||
{
|
|
||||||
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
|
|
||||||
if (element)
|
|
||||||
[bezierElements addObject:[BezierElement elementWithPathElement:*element]];
|
|
||||||
}
|
|
||||||
|
|
||||||
@implementation UIBezierPath (TextRendering)
|
|
||||||
|
|
||||||
// Retrieve array of component elements
|
|
||||||
- (NSArray *) elements
|
|
||||||
{
|
|
||||||
NSMutableArray *elements = [NSMutableArray array];
|
|
||||||
CGPathApply(self.CGPath, (__bridge void *)elements, GetBezierElements);
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void (^)(CGFloat *, NSInteger *, NSMutableArray *, NSMutableArray *, BOOL *)) getTextProperties{
|
|
||||||
return ^(CGFloat *lengthP, NSInteger *lineCountP, NSMutableArray * lengths, NSMutableArray * lines, BOOL *isClosedP) {
|
|
||||||
CGPoint origin = CGPointMake (0.0, 0.0);
|
|
||||||
CGPoint last = CGPointMake (0.0, 0.0);
|
|
||||||
NSInteger lineCount = 0;
|
|
||||||
CGFloat length = 0;
|
|
||||||
BOOL isClosed = NO;
|
|
||||||
NSArray * elements = self.elements;
|
|
||||||
for (BezierElement *element in elements) {
|
|
||||||
switch (element.elementType)
|
|
||||||
{
|
|
||||||
case kCGPathElementMoveToPoint:
|
|
||||||
origin = last = element.point;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case kCGPathElementAddLineToPoint: {
|
|
||||||
CGPoint next = element.point;
|
|
||||||
addLine(&last, &next, lines, &length, lengths);
|
|
||||||
lineCount++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case kCGPathElementAddQuadCurveToPoint:
|
|
||||||
case kCGPathElementAddCurveToPoint:
|
|
||||||
{
|
|
||||||
// handle both curve types gracefully
|
|
||||||
CGPoint curveTo;
|
|
||||||
CGPoint ctrl1;
|
|
||||||
CGPoint ctrl2;
|
|
||||||
if (element.elementType == kCGPathElementAddQuadCurveToPoint) {
|
|
||||||
curveTo = element.point;
|
|
||||||
ctrl1 = element.controlPoint1;
|
|
||||||
ctrl2 = ctrl1;
|
|
||||||
} else if (element.elementType == kCGPathElementAddCurveToPoint) {
|
|
||||||
curveTo = element.point;
|
|
||||||
ctrl1 = element.controlPoint1;
|
|
||||||
ctrl2 = element.controlPoint2;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is the bezier for our current element
|
|
||||||
CGPoint bezier[4] = { last, ctrl1, ctrl2, curveTo };
|
|
||||||
NSValue *arr = [NSValue valueWithBytes:&bezier objCType:@encode(CGPoint[4])];
|
|
||||||
NSMutableArray *curves = [NSMutableArray arrayWithObjects:arr, nil];
|
|
||||||
|
|
||||||
NSInteger count = 1;
|
|
||||||
while (count-- > 0) {
|
|
||||||
CGPoint bez[4];
|
|
||||||
[curves[count] getValue:&bez];
|
|
||||||
[curves removeLastObject];
|
|
||||||
|
|
||||||
// calculate the error rate of the curve vs
|
|
||||||
// a line segement between the start and end points
|
|
||||||
CGPoint onCurve = bezierPointAtT(bez, .5);
|
|
||||||
CGPoint next = bez[3];
|
|
||||||
CGFloat error = distanceOfPointToLine(onCurve, last, next);
|
|
||||||
|
|
||||||
// if the error is less than our accepted level of error
|
|
||||||
// then add a line, else, split the curve in half
|
|
||||||
if (error <= idealFlatness) {
|
|
||||||
addLine(&last, &next, lines, &length, lengths);
|
|
||||||
lineCount++;
|
|
||||||
} else {
|
|
||||||
CGPoint bez1[4], bez2[4];
|
|
||||||
subdivideBezierAtT(bez, bez1, bez2, .5);
|
|
||||||
[curves addObject:[NSValue valueWithBytes:&bez2 objCType:@encode(CGPoint[4])]];
|
|
||||||
[curves addObject:[NSValue valueWithBytes:&bez1 objCType:@encode(CGPoint[4])]];
|
|
||||||
count += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last = curveTo;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case kCGPathElementCloseSubpath: {
|
|
||||||
CGPoint next = origin;
|
|
||||||
addLine(&last, &next, lines, &length, lengths);
|
|
||||||
lineCount++;
|
|
||||||
isClosed = YES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*lineCountP = lineCount;
|
|
||||||
*isClosedP = isClosed;
|
|
||||||
*lengthP = length;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user