mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-20 22:05:14 +00:00
Add text path for iOS
Add text path for iOS Add clipPath Text support (iOS)
This commit is contained in:
@@ -124,6 +124,13 @@ class ClipPathElement extends Component{
|
||||
<Ellipse cx="60" cy="70" rx="20" ry="10" />
|
||||
<Rect x="65" y="15" width="30" height="30" />
|
||||
<Polygon points="20,60 20,80 50,70" />
|
||||
<Text
|
||||
x="50"
|
||||
y="30"
|
||||
fontSize="32"
|
||||
fonWeight="bold"
|
||||
textAnchor="middle"
|
||||
>Q</Text>
|
||||
</ClipPath>
|
||||
</Defs>
|
||||
<Rect
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
10A063041CC7320C0000CEEF /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063011CC7320C0000CEEF /* RNSVGPath.m */; };
|
||||
10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 10A063031CC7320C0000CEEF /* RNSVGSvgView.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 */; };
|
||||
10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C068691CCF1061007C6982 /* RNSVGShapeManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -91,6 +92,8 @@
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@@ -138,6 +141,8 @@
|
||||
0CF68AF71AF0549300FF9E5C /* RCTConvert+RNSVG.m */,
|
||||
10B898DD1CE4591F003CD3D4 /* RNSVGGlyphCache.h */,
|
||||
10B898DE1CE45973003CD3D4 /* RNSVGGlyphCache.m */,
|
||||
10B898E21CE4910A003CD3D4 /* UIBezierPath-Points.h */,
|
||||
10B898E01CE490FC003CD3D4 /* UIBezierPath-Points.m */,
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -259,6 +264,7 @@
|
||||
0CF68B0E1AF0549300FF9E5C /* RNSVGRadialGradient.m in Sources */,
|
||||
10A063051CC7320C0000CEEF /* RNSVGSvgView.m in Sources */,
|
||||
0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */,
|
||||
10B898E11CE490FC003CD3D4 /* UIBezierPath-Points.m in Sources */,
|
||||
0CF68B101AF0549300FF9E5C /* RCTConvert+RNSVG.m in Sources */,
|
||||
10C0686A1CCF1061007C6982 /* RNSVGShapeManager.m in Sources */,
|
||||
108FD88C1CDAF09B00A65FB3 /* RNSVGImageManager.m in Sources */,
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
{
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
for (RNSVGNode *node in self.subviews) {
|
||||
CGPathAddPath(path, nil, [node getPath:context]);
|
||||
CGAffineTransform transform = node.transform;
|
||||
CGPathAddPath(path, &transform, [node getPath:context]);
|
||||
}
|
||||
|
||||
return path;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "UIBezierPath-Points.h"
|
||||
#import "RNSVGPath.h"
|
||||
#import "RNSVGTextFrame.h"
|
||||
#import "RNSVGGlyphCache.h"
|
||||
@@ -16,5 +16,6 @@
|
||||
|
||||
@property (nonatomic, assign) CTTextAlignment alignment;
|
||||
@property (nonatomic, assign) RNSVGTextFrame textFrame;
|
||||
@property (nonatomic, assign) CGPathRef path;
|
||||
|
||||
@end
|
||||
|
||||
@@ -39,15 +39,25 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
|
||||
_textFrame = frame;
|
||||
}
|
||||
|
||||
- (void)setPath:(CGPathRef)path
|
||||
{
|
||||
if (path == _path) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGPathRelease(_path);
|
||||
_path = CGPathRetain(path);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGPathRelease(_path);
|
||||
RNSVGFreeTextFrame(_textFrame);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
self.d = [self getPath: context];
|
||||
|
||||
[super renderLayerTo:context];
|
||||
}
|
||||
|
||||
@@ -55,15 +65,15 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
|
||||
{
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
RNSVGTextFrame frame = self.textFrame;
|
||||
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CGFloat shift;
|
||||
CGFloat width = frame.widths[i];
|
||||
switch (self.alignment) {
|
||||
case kCTTextAlignmentRight:
|
||||
shift = frame.widths[i];
|
||||
shift = width;
|
||||
break;
|
||||
case kCTTextAlignmentCenter:
|
||||
shift = (frame.widths[i] / 2);
|
||||
shift = width / 2;
|
||||
break;
|
||||
default:
|
||||
shift = 0;
|
||||
@@ -71,7 +81,7 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
|
||||
}
|
||||
// We should consider snapping this shift to device pixels to improve rendering quality
|
||||
// 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]]);
|
||||
}
|
||||
|
||||
@@ -80,6 +90,7 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
|
||||
|
||||
- (CGPathRef)setLinePath:(CTLineRef)line
|
||||
{
|
||||
|
||||
CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0);
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
RNSVGGlyphCache *cache = [[RNSVGGlyphCache alloc] init];
|
||||
@@ -101,12 +112,31 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
|
||||
CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs);
|
||||
CFDictionaryRef attributes = CTRunGetAttributes(run);
|
||||
CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName);
|
||||
|
||||
for(CFIndex j = 0; j < runGlyphCount; ++j, ++glyphIndex) {
|
||||
CGPathRef letter = [cache pathForGlyph:glyphs[j] fromFont:runFont];
|
||||
CGPoint point = positions[j];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
27
ios/UIBezierPath-Points.h
Normal file
27
ios/UIBezierPath-Points.h
Normal 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
219
ios/UIBezierPath-Points.m
Normal 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
|
||||
@@ -22,5 +22,6 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
|
||||
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, RNSVGTextFrame)
|
||||
RCT_EXPORT_VIEW_PROPERTY(path, CGPath)
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user