Add text path for iOS

Add text path for iOS
Add clipPath Text support (iOS)
This commit is contained in:
Horcrux
2016-05-13 10:00:47 +08:00
parent 04b7fa068e
commit b81eba5684
8 changed files with 301 additions and 9 deletions

View File

@@ -124,6 +124,13 @@ class ClipPathElement extends Component{
<Ellipse cx="60" cy="70" rx="20" ry="10" /> <Ellipse cx="60" cy="70" rx="20" ry="10" />
<Rect x="65" y="15" width="30" height="30" /> <Rect x="65" y="15" width="30" height="30" />
<Polygon points="20,60 20,80 50,70" /> <Polygon points="20,60 20,80 50,70" />
<Text
x="50"
y="30"
fontSize="32"
fonWeight="bold"
textAnchor="middle"
>Q</Text>
</ClipPath> </ClipPath>
</Defs> </Defs>
<Rect <Rect

View File

@@ -28,6 +28,7 @@
10A063041CC7320C0000CEEF /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063011CC7320C0000CEEF /* RNSVGPath.m */; }; 10A063041CC7320C0000CEEF /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063011CC7320C0000CEEF /* RNSVGPath.m */; };
10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063031CC7320C0000CEEF /* RNSVGSvgView.m */; }; 10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063031CC7320C0000CEEF /* RNSVGSvgView.m */; };
10B898DF1CE45973003CD3D4 /* RNSVGGlyphCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */; }; 10B898DF1CE45973003CD3D4 /* RNSVGGlyphCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */; };
10B898E11CE490FC003CD3D4 /* UIBezierPath-Points.m in Sources */ = {isa = PBXBuildFile; fileRef = 10B898E01CE490FC003CD3D4 /* UIBezierPath-Points.m */; };
10C068671CCF0F87007C6982 /* RNSVGShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068651CCF0F87007C6982 /* RNSVGShape.m */; }; 10C068671CCF0F87007C6982 /* RNSVGShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068651CCF0F87007C6982 /* RNSVGShape.m */; };
10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068691CCF1061007C6982 /* RNSVGShapeManager.m */; }; 10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068691CCF1061007C6982 /* RNSVGShapeManager.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -91,6 +92,8 @@
10A063031CC7320C0000CEEF /* RNSVGSvgView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSvgView.m; sourceTree = "<group>"; }; 10A063031CC7320C0000CEEF /* RNSVGSvgView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSvgView.m; sourceTree = "<group>"; };
10B898DD1CE4591F003CD3D4 /* RNSVGGlyphCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGGlyphCache.h; sourceTree = "<group>"; }; 10B898DD1CE4591F003CD3D4 /* RNSVGGlyphCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGGlyphCache.h; sourceTree = "<group>"; };
10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGGlyphCache.m; sourceTree = "<group>"; }; 10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGGlyphCache.m; sourceTree = "<group>"; };
10B898E01CE490FC003CD3D4 /* UIBezierPath-Points.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath-Points.m"; sourceTree = "<group>"; };
10B898E21CE4910A003CD3D4 /* UIBezierPath-Points.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath-Points.h"; sourceTree = "<group>"; };
10C068641CCF0F87007C6982 /* RNSVGShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGShape.h; sourceTree = SOURCE_ROOT; }; 10C068641CCF0F87007C6982 /* RNSVGShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGShape.h; sourceTree = SOURCE_ROOT; };
10C068651CCF0F87007C6982 /* RNSVGShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGShape.m; sourceTree = SOURCE_ROOT; }; 10C068651CCF0F87007C6982 /* RNSVGShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGShape.m; sourceTree = SOURCE_ROOT; };
10C068681CCF1061007C6982 /* RNSVGShapeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGShapeManager.h; sourceTree = "<group>"; }; 10C068681CCF1061007C6982 /* RNSVGShapeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGShapeManager.h; sourceTree = "<group>"; };
@@ -138,6 +141,8 @@
0CF68AF71AF0549300FF9E5C /* RCTConvert+RNSVG.m */, 0CF68AF71AF0549300FF9E5C /* RCTConvert+RNSVG.m */,
10B898DD1CE4591F003CD3D4 /* RNSVGGlyphCache.h */, 10B898DD1CE4591F003CD3D4 /* RNSVGGlyphCache.h */,
10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */, 10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */,
10B898E21CE4910A003CD3D4 /* UIBezierPath-Points.h */,
10B898E01CE490FC003CD3D4 /* UIBezierPath-Points.m */,
0CF68AC21AF0540F00FF9E5C /* Products */, 0CF68AC21AF0540F00FF9E5C /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@@ -259,6 +264,7 @@
0CF68B0E1AF0549300FF9E5C /* RNSVGRadialGradient.m in Sources */, 0CF68B0E1AF0549300FF9E5C /* RNSVGRadialGradient.m in Sources */,
10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */, 10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */,
0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */, 0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */,
10B898E11CE490FC003CD3D4 /* UIBezierPath-Points.m in Sources */,
0CF68B101AF0549300FF9E5C /* RCTConvert+RNSVG.m in Sources */, 0CF68B101AF0549300FF9E5C /* RCTConvert+RNSVG.m in Sources */,
10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */, 10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */,
108FD88C1CDAF09B00A65FB3 /* RNSVGImageManager.m in Sources */, 108FD88C1CDAF09B00A65FB3 /* RNSVGImageManager.m in Sources */,

View File

@@ -26,7 +26,8 @@
{ {
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
for (RNSVGNode *node in self.subviews) { for (RNSVGNode *node in self.subviews) {
CGPathAddPath(path, nil, [node getPath:context]); CGAffineTransform transform = node.transform;
CGPathAddPath(path, &transform, [node getPath:context]);
} }
return path; return path;

View File

@@ -7,7 +7,7 @@
*/ */
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "UIBezierPath-Points.h"
#import "RNSVGPath.h" #import "RNSVGPath.h"
#import "RNSVGTextFrame.h" #import "RNSVGTextFrame.h"
#import "RNSVGGlyphCache.h" #import "RNSVGGlyphCache.h"
@@ -16,5 +16,6 @@
@property (nonatomic, assign) CTTextAlignment alignment; @property (nonatomic, assign) CTTextAlignment alignment;
@property (nonatomic, assign) RNSVGTextFrame textFrame; @property (nonatomic, assign) RNSVGTextFrame textFrame;
@property (nonatomic, assign) CGPathRef path;
@end @end

View File

@@ -39,15 +39,25 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
_textFrame = frame; _textFrame = frame;
} }
- (void)setPath:(CGPathRef)path
{
if (path == _path) {
return;
}
[self invalidate];
CGPathRelease(_path);
_path = CGPathRetain(path);
}
- (void)dealloc - (void)dealloc
{ {
CGPathRelease(_path);
RNSVGFreeTextFrame(_textFrame); RNSVGFreeTextFrame(_textFrame);
} }
- (void)renderLayerTo:(CGContextRef)context - (void)renderLayerTo:(CGContextRef)context
{ {
self.d = [self getPath: context]; self.d = [self getPath: context];
[super renderLayerTo:context]; [super renderLayerTo:context];
} }
@@ -55,15 +65,15 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
{ {
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
RNSVGTextFrame frame = self.textFrame; RNSVGTextFrame frame = self.textFrame;
for (int i = 0; i < frame.count; i++) { for (int i = 0; i < frame.count; i++) {
CGFloat shift; CGFloat shift;
CGFloat width = frame.widths[i];
switch (self.alignment) { switch (self.alignment) {
case kCTTextAlignmentRight: case kCTTextAlignmentRight:
shift = frame.widths[i]; shift = width;
break; break;
case kCTTextAlignmentCenter: case kCTTextAlignmentCenter:
shift = (frame.widths[i] / 2); shift = width / 2;
break; break;
default: default:
shift = 0; shift = 0;
@@ -71,7 +81,7 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
} }
// We should consider snapping this shift to device pixels to improve rendering quality // We should consider snapping this shift to device pixels to improve rendering quality
// when a line has subpixel width. // when a line has subpixel width.
CGAffineTransform offset = CGAffineTransformMakeTranslation(-shift, frame.baseLine + frame.lineHeight * i); CGAffineTransform offset = CGAffineTransformMakeTranslation(-shift, frame.baseLine + frame.lineHeight * i + (self.path == NULL ? 0 : -frame.lineHeight));
CGPathAddPath(path, &offset, [self setLinePath:frame.lines[i]]); CGPathAddPath(path, &offset, [self setLinePath:frame.lines[i]]);
} }
@@ -80,6 +90,7 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
- (CGPathRef)setLinePath:(CTLineRef)line - (CGPathRef)setLinePath:(CTLineRef)line
{ {
CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0); CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
RNSVGGlyphCache *cache = [[RNSVGGlyphCache alloc] init]; RNSVGGlyphCache *cache = [[RNSVGGlyphCache alloc] init];
@@ -101,12 +112,31 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs);
CFDictionaryRef attributes = CTRunGetAttributes(run); CFDictionaryRef attributes = CTRunGetAttributes(run);
CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName); CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName);
for(CFIndex j = 0; j < runGlyphCount; ++j, ++glyphIndex) { for(CFIndex j = 0; j < runGlyphCount; ++j, ++glyphIndex) {
CGPathRef letter = [cache pathForGlyph:glyphs[j] fromFont:runFont]; CGPathRef letter = [cache pathForGlyph:glyphs[j] fromFont:runFont];
CGPoint point = positions[j]; CGPoint point = positions[j];
if (letter != NULL) { if (letter != NULL) {
CGAffineTransform transform = CGAffineTransformTranslate(upsideDown, point.x, point.y); CGAffineTransform transform;
// draw glyphs along path
if (self.path != NULL) {
CGPoint slope;
CGRect bounding = CGPathGetBoundingBox(letter);
UIBezierPath* path = [UIBezierPath bezierPathWithCGPath:self.path];
CGFloat percentConsumed = (point.x + bounding.size.width) / path.length;
if (percentConsumed >= 1.0f) {
continue;
}
CGPoint targetPoint = [path pointAtPercent:percentConsumed withSlope: &slope];
float angle = atan(slope.y / slope.x); // + M_PI;
if (slope.x < 0) angle += M_PI; // going left, update the angle
transform = CGAffineTransformMakeTranslation(targetPoint.x - bounding.size.width, targetPoint.y);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
} else {
transform = CGAffineTransformTranslate(upsideDown, point.x, point.y);
}
CGPathAddPath(path, &transform, letter); CGPathAddPath(path, &transform, letter);
} }
} }

27
ios/UIBezierPath-Points.h Normal file
View File

@@ -0,0 +1,27 @@
/**
* 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.
*/
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface UIBezierPath (Points)
@property (nonatomic, readonly) NSArray *points;
@property (nonatomic, readonly) NSArray *bezierElements;
@property (nonatomic, readonly) CGFloat length;
- (NSArray *) pointPercentArray;
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope;
+ (UIBezierPath *) pathWithPoints: (NSArray *) points;
+ (UIBezierPath *) pathWithElements: (NSArray *) elements;
@end

219
ios/UIBezierPath-Points.m Normal file
View File

@@ -0,0 +1,219 @@
/**
* 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.
*/
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import "UIBezierPath-Points.h"
#define POINTSTRING(_CGPOINT_) (NSStringFromCGPoint(_CGPOINT_))
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]
#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue]
// Return distance between two points
static float distance (CGPoint p1, CGPoint p2)
{
float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
return sqrt(dx*dx + dy*dy);
}
@implementation UIBezierPath (Points)
void getPointsFromBezier(void *info, const CGPathElement *element)
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
CGPathElementType type = element->type;
CGPoint *points = element->points;
if (type != kCGPathElementCloseSubpath)
{
if ((type == kCGPathElementAddLineToPoint) ||
(type == kCGPathElementMoveToPoint))
[bezierPoints addObject:VALUE(0)];
else if (type == kCGPathElementAddQuadCurveToPoint)
[bezierPoints addObject:VALUE(1)];
else if (type == kCGPathElementAddCurveToPoint)
[bezierPoints addObject:VALUE(2)];
}
}
- (NSArray *)points
{
NSMutableArray *points = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)points, getPointsFromBezier);
return points;
}
// Return a Bezier path buit with the supplied points
+ (UIBezierPath *) pathWithPoints: (NSArray *) points
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (points.count == 0) return path;
[path moveToPoint:POINT(0)];
for (int i = 1; i < points.count; i++)
[path addLineToPoint:POINT(i)];
return path;
}
- (CGFloat) length
{
NSArray *points = self.points;
float totalPointLength = 0.0f;
for (int i = 1; i < points.count; i++)
totalPointLength += distance(POINT(i), POINT(i-1));
return totalPointLength;
}
- (NSArray *) pointPercentArray
{
// Use total length to calculate the percent of path consumed at each control point
NSArray *points = self.points;
int pointCount = points.count;
float totalPointLength = self.length;
float distanceTravelled = 0.0f;
NSMutableArray *pointPercentArray = [NSMutableArray array];
[pointPercentArray addObject:@(0.0)];
for (int i = 1; i < pointCount; i++)
{
distanceTravelled += distance(POINT(i), POINT(i-1));
[pointPercentArray addObject:@(distanceTravelled / totalPointLength)];
}
// Add a final item just to stop with. Probably not needed.
[pointPercentArray addObject:[NSNumber numberWithFloat:1.1f]]; // 110%
return pointPercentArray;
}
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope
{
NSArray *points = self.points;
NSArray *percentArray = self.pointPercentArray;
CFIndex lastPointIndex = points.count - 1;
if (!points.count) {
return CGPointZero;
}
// Check for 0% and 100%
if (percent <= 0.0f) {
return POINT(0);
}
if (percent >= 1.0f) {
return POINT(lastPointIndex);
}
// Find a corresponding pair of points in the path
CFIndex index = 1;
while ((index < percentArray.count) &&
(percent > ((NSNumber *)percentArray[index]).floatValue)) {
index++;
}
// This should not happen.
if (index > lastPointIndex) {
return POINT(lastPointIndex);
}
// Calculate the intermediate distance between the two points
CGPoint point1 = POINT(index -1);
CGPoint point2 = POINT(index);
float percent1 = [[percentArray objectAtIndex:index - 1] floatValue];
float percent2 = [[percentArray objectAtIndex:index] floatValue];
float percentOffset = (percent - percent1) / (percent2 - percent1);
float dx = point2.x - point1.x;
float dy = point2.y - point1.y;
// Store dy, dx for retrieving arctan
if (slope) {
*slope = CGPointMake(dx, dy);
}
// Calculate new point
CGFloat newX = point1.x + (percentOffset * dx);
CGFloat newY = point1.y + (percentOffset * dy);
CGPoint targetPoint = CGPointMake(newX, newY);
return targetPoint;
}
void getBezierElements(void *info, const CGPathElement *element)
{
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
CGPathElementType type = element->type;
CGPoint *points = element->points;
switch (type)
{
case kCGPathElementCloseSubpath:
[bezierElements addObject:@[@(type)]];
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
[bezierElements addObject:@[@(type), VALUE(0)]];
break;
case kCGPathElementAddQuadCurveToPoint:
[bezierElements addObject:@[@(type), VALUE(0), VALUE(1)]];
break;
case kCGPathElementAddCurveToPoint:
[bezierElements addObject:@[@(type), VALUE(0), VALUE(1), VALUE(2)]];
break;
}
}
- (NSArray *) bezierElements
{
NSMutableArray *elements = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)elements, getBezierElements);
return elements;
}
+ (UIBezierPath *) pathWithElements: (NSArray *) elements
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (elements.count == 0) return path;
for (NSArray *points in elements)
{
if (!points.count) continue;
CGPathElementType elementType = [points[0] integerValue];
switch (elementType)
{
case kCGPathElementCloseSubpath:
[path closePath];
break;
case kCGPathElementMoveToPoint:
if (points.count == 2)
[path moveToPoint:POINT(1)];
break;
case kCGPathElementAddLineToPoint:
if (points.count == 2)
[path addLineToPoint:POINT(1)];
break;
case kCGPathElementAddQuadCurveToPoint:
if (points.count == 3)
[path addQuadCurveToPoint:POINT(2) controlPoint:POINT(1)];
break;
case kCGPathElementAddCurveToPoint:
if (points.count == 4)
[path addCurveToPoint:POINT(3) controlPoint1:POINT(1) controlPoint2:POINT(2)];
break;
}
}
return path;
}
@end

View File

@@ -22,5 +22,6 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment) RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, RNSVGTextFrame) RCT_REMAP_VIEW_PROPERTY(frame, textFrame, RNSVGTextFrame)
RCT_EXPORT_VIEW_PROPERTY(path, CGPath)
@end @end