mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-20 22:05:14 +00:00
Add startOffset support
This commit is contained in:
@@ -3,7 +3,7 @@ import createReactNativeComponentClass from 'react-native/Libraries/Renderer/src
|
|||||||
import {TextPathAttributes} from '../lib/attributes';
|
import {TextPathAttributes} from '../lib/attributes';
|
||||||
import extractText from '../lib/extract/extractText';
|
import extractText from '../lib/extract/extractText';
|
||||||
import Shape from './Shape';
|
import Shape from './Shape';
|
||||||
import {pathProps, fontProps} from '../lib/props';
|
import {pathProps, fontProps, numberProp} from '../lib/props';
|
||||||
import TSpan from './TSpan';
|
import TSpan from './TSpan';
|
||||||
|
|
||||||
const idExpReg = /^#(.+)$/;
|
const idExpReg = /^#(.+)$/;
|
||||||
@@ -15,11 +15,11 @@ class TextPath extends Shape {
|
|||||||
...pathProps,
|
...pathProps,
|
||||||
...fontProps,
|
...fontProps,
|
||||||
href: PropTypes.string.isRequired,
|
href: PropTypes.string.isRequired,
|
||||||
textAnchor: PropTypes.oneOf(['start', 'middle', 'end'])
|
startOffset: numberProp
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {children, href, ...props} = this.props;
|
let {children, href, startOffset, ...props} = this.props;
|
||||||
if (href) {
|
if (href) {
|
||||||
let matched = href.match(idExpReg);
|
let matched = href.match(idExpReg);
|
||||||
|
|
||||||
@@ -33,7 +33,10 @@ class TextPath extends Shape {
|
|||||||
x: null,
|
x: null,
|
||||||
y: null
|
y: null
|
||||||
})}
|
})}
|
||||||
{...extractText({children}, true)}
|
{...extractText({
|
||||||
|
children,
|
||||||
|
startOffset
|
||||||
|
}, true)}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
|
|
||||||
@interface RNSVGBezierPath : NSObject
|
@interface RNSVGBezierPath : NSObject
|
||||||
|
|
||||||
- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves;
|
+ (BOOL) hasReachedEnd:(CGAffineTransform)transform;
|
||||||
|
+ (BOOL) hasReachedStart:(CGAffineTransform) transform;
|
||||||
|
|
||||||
|
- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray<NSArray *> *)bezierCurves startOffset:(CGFloat)startOffset;
|
||||||
- (CGAffineTransform)transformAtDistance:(CGFloat)distance;
|
- (CGAffineTransform)transformAtDistance:(CGFloat)distance;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -17,54 +17,70 @@
|
|||||||
@implementation RNSVGBezierPath
|
@implementation RNSVGBezierPath
|
||||||
{
|
{
|
||||||
NSArray<NSArray *> *_bezierCurves;
|
NSArray<NSArray *> *_bezierCurves;
|
||||||
NSUInteger _bezierIndex;
|
int _currentBezierIndex;
|
||||||
CGFloat _offset;
|
CGFloat _startOffset;
|
||||||
CGFloat _lastX;
|
CGFloat _lastOffset;
|
||||||
|
CGFloat _lastRecord;
|
||||||
CGFloat _lastDistance;
|
CGFloat _lastDistance;
|
||||||
CGPoint _lastPoint;
|
CGPoint _lastPoint;
|
||||||
CGPoint _P0;
|
CGPoint _P0;
|
||||||
CGPoint _P1;
|
CGPoint _P1;
|
||||||
CGPoint _P2;
|
CGPoint _P2;
|
||||||
CGPoint _P3;
|
CGPoint _P3;
|
||||||
|
BOOL _reachedEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves
|
- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray<NSArray *> *)bezierCurves startOffset:(CGFloat)startOffset
|
||||||
{
|
{
|
||||||
|
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_bezierCurves = bezierCurves;
|
_bezierCurves = bezierCurves;
|
||||||
_bezierIndex = 0;
|
_currentBezierIndex = _lastOffset = _lastRecord = _lastDistance = 0;
|
||||||
_offset = 0;
|
_startOffset = startOffset;
|
||||||
_lastX = 0;
|
[self setControlPoints];
|
||||||
_lastDistance = 0;
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat Bezier(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) {
|
static CGAffineTransform getReachedEndTansform() {
|
||||||
|
return CGAffineTransformMakeScale(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CGAffineTransform getUnreadedTransform() {
|
||||||
|
return CGAffineTransformMakeScale(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (BOOL) hasReachedEnd:(CGAffineTransform)transform
|
||||||
|
{
|
||||||
|
return transform.a == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (BOOL) hasReachedStart:(CGAffineTransform) transform
|
||||||
|
{
|
||||||
|
return transform.d == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CGFloat calculateBezier(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) {
|
||||||
return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3;
|
return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGPoint)pointForOffset:(CGFloat)t {
|
- (CGPoint)pointAtOffset:(CGFloat)t {
|
||||||
CGFloat x = Bezier(t, _P0.x, _P1.x, _P2.x, _P3.x);
|
CGFloat x = calculateBezier(t, _P0.x, _P1.x, _P2.x, _P3.x);
|
||||||
CGFloat y = Bezier(t, _P0.y, _P1.y, _P2.y, _P3.y);
|
CGFloat y = calculateBezier(t, _P0.y, _P1.y, _P2.y, _P3.y);
|
||||||
return CGPointMake(x, y);
|
return CGPointMake(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat BezierPrime(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) {
|
static CGFloat calculateBezierPrime(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) {
|
||||||
return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3;
|
return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGFloat)angleForOffset:(CGFloat)t {
|
- (CGFloat)angleAtOffset:(CGFloat)t {
|
||||||
CGFloat dx = BezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x);
|
CGFloat dx = calculateBezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x);
|
||||||
CGFloat dy = BezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y);
|
CGFloat dy = calculateBezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y);
|
||||||
return atan2(dy, dx);
|
return atan2(dy, dx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat Distance(CGPoint a, CGPoint b) {
|
static CGFloat calculateDistance(CGPoint a, CGPoint b) {
|
||||||
CGFloat dx = a.x - b.x;
|
return hypot(a.x - b.x, a.y - b.y);
|
||||||
CGFloat dy = a.y - b.y;
|
|
||||||
return hypot(dx, dy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplistic routine to find the offset along Bezier that is
|
// Simplistic routine to find the offset along Bezier that is
|
||||||
@@ -76,65 +92,66 @@ static CGFloat Distance(CGPoint a, CGPoint b) {
|
|||||||
// curve might loop back on leading to incorrect results. Tuning
|
// curve might loop back on leading to incorrect results. Tuning
|
||||||
// kStep is good start.
|
// kStep is good start.
|
||||||
- (CGFloat)offsetAtDistance:(CGFloat)distance
|
- (CGFloat)offsetAtDistance:(CGFloat)distance
|
||||||
fromPoint:(CGPoint)point
|
fromPoint:(CGPoint)point
|
||||||
offset:(CGFloat)offset {
|
offset:(CGFloat)offset {
|
||||||
const CGFloat kStep = 0.0005; // 0.0001 - 0.001 work well
|
|
||||||
|
const CGFloat kStep = 0.001; // 0.0001 - 0.001 work well
|
||||||
CGFloat newDistance = 0;
|
CGFloat newDistance = 0;
|
||||||
CGFloat newOffset = offset + kStep;
|
CGFloat newOffset = offset + kStep;
|
||||||
while (newDistance <= distance && newOffset < 1.0) {
|
while (newDistance <= distance && newOffset < 1.0) {
|
||||||
newOffset += kStep;
|
newOffset += kStep;
|
||||||
newDistance = Distance(point, [self pointForOffset:newOffset]);
|
newDistance = calculateDistance(point, [self pointAtOffset:newOffset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastDistance = newDistance;
|
_lastDistance = newDistance;
|
||||||
return newOffset;
|
return newOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setControlPoints
|
- (void)setControlPoints
|
||||||
{
|
{
|
||||||
NSArray *bezier = [_bezierCurves objectAtIndex:_bezierIndex];
|
NSArray *bezier = [_bezierCurves objectAtIndex:_currentBezierIndex++];
|
||||||
_bezierIndex++;
|
|
||||||
|
// set start point
|
||||||
if (bezier.count == 1) {
|
if (bezier.count == 1) {
|
||||||
_lastPoint = _P0 = [(NSValue *)[bezier objectAtIndex:0] CGPointValue];
|
_lastPoint = _P0 = [[bezier objectAtIndex:0] CGPointValue];
|
||||||
[self setControlPoints];
|
[self setControlPoints];
|
||||||
} else if (bezier.count == 3) {
|
} else if (bezier.count == 3) {
|
||||||
_P1 = [(NSValue *)[bezier objectAtIndex:0] CGPointValue];
|
_P1 = [[bezier objectAtIndex:0] CGPointValue];
|
||||||
_P2 = [(NSValue *)[bezier objectAtIndex:1] CGPointValue];
|
_P2 = [[bezier objectAtIndex:1] CGPointValue];
|
||||||
_P3 = [(NSValue *)[bezier objectAtIndex:2] CGPointValue];
|
_P3 = [[bezier objectAtIndex:2] CGPointValue];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (CGAffineTransform)transformAtDistance:(CGFloat)distance
|
- (CGAffineTransform)transformAtDistance:(CGFloat)distance
|
||||||
{
|
{
|
||||||
if (_offset == 0) {
|
distance += _startOffset;
|
||||||
if (_bezierCurves.count == _bezierIndex) {
|
if (_reachedEnd) {
|
||||||
return CGAffineTransformMakeScale(0, 0);
|
return getReachedEndTansform();
|
||||||
} else {
|
} else if (distance < 0) {
|
||||||
[self setControlPoints];
|
return getUnreadedTransform();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CGFloat offset = [self offsetAtDistance:distance - _lastX
|
CGFloat offset = [self offsetAtDistance:distance - _lastRecord
|
||||||
fromPoint:_lastPoint
|
fromPoint:_lastPoint
|
||||||
offset:_offset];
|
offset:_lastOffset];
|
||||||
CGPoint glyphPoint = [self pointForOffset:offset];
|
|
||||||
CGFloat angle = [self angleForOffset:offset];
|
|
||||||
|
|
||||||
if (offset < 1) {
|
if (offset < 1) {
|
||||||
_offset = offset;
|
CGPoint glyphPoint = [self pointAtOffset:offset];
|
||||||
|
_lastOffset = offset;
|
||||||
_lastPoint = glyphPoint;
|
_lastPoint = glyphPoint;
|
||||||
|
_lastRecord = distance;
|
||||||
|
return CGAffineTransformRotate(CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y), [self angleAtOffset:offset]);
|
||||||
|
} else if (_bezierCurves.count == _currentBezierIndex) {
|
||||||
|
_reachedEnd = YES;
|
||||||
|
return getReachedEndTansform();
|
||||||
} else {
|
} else {
|
||||||
_offset = 0;
|
_lastOffset = 0;
|
||||||
_lastPoint = _P0 = _P3;
|
_lastPoint = _P0 = _P3;
|
||||||
_lastX += _lastDistance;
|
_lastRecord += _lastDistance;
|
||||||
return [self transformAtDistance:distance];
|
[self setControlPoints];
|
||||||
|
return [self transformAtDistance:distance - _startOffset];
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastX = distance;
|
|
||||||
CGAffineTransform transform = CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y);
|
|
||||||
transform = CGAffineTransformRotate(transform, angle);
|
|
||||||
|
|
||||||
return transform;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -14,4 +14,6 @@
|
|||||||
|
|
||||||
@property (nonatomic, strong) NSString *content;
|
@property (nonatomic, strong) NSString *content;
|
||||||
|
|
||||||
|
- (CGAffineTransform)getTextPathTransform:(CGFloat)distance;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -32,8 +32,9 @@
|
|||||||
if (!self.content) {
|
if (!self.content) {
|
||||||
return [self getGroupPath:context];
|
return [self getGroupPath:context];
|
||||||
}
|
}
|
||||||
[self initialTextPath];
|
|
||||||
[self setContextBoundingBox:CGContextGetClipBoundingBox(context)];
|
[self setContextBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
|
|
||||||
|
[self initialTextPath];
|
||||||
CGMutablePathRef path = CGPathCreateMutable();
|
CGMutablePathRef path = CGPathCreateMutable();
|
||||||
|
|
||||||
if ([self.content isEqualToString:@""]) {
|
if ([self.content isEqualToString:@""]) {
|
||||||
@@ -98,8 +99,10 @@
|
|||||||
|
|
||||||
CGAffineTransform textPathTransform = [self getTextPathTransform:computedPoint.x];
|
CGAffineTransform textPathTransform = [self getTextPathTransform:computedPoint.x];
|
||||||
|
|
||||||
if (!textPathTransform.a || !textPathTransform.d) {
|
if ([RNSVGBezierPath hasReachedEnd:textPathTransform]) {
|
||||||
break;
|
break;
|
||||||
|
} else if ([RNSVGBezierPath hasReachedStart:textPathTransform]) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CGAffineTransform transform;
|
CGAffineTransform transform;
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
- (CTFontRef)getComputedFont;
|
- (CTFontRef)getComputedFont;
|
||||||
- (RNSVGGlyphPoint)getComputedGlyphPoint:(NSUInteger *)index glyphOffset:(CGPoint)glyphOffset;
|
- (RNSVGGlyphPoint)getComputedGlyphPoint:(NSUInteger *)index glyphOffset:(CGPoint)glyphOffset;
|
||||||
- (RNSVGText *)getTextRoot;
|
- (RNSVGText *)getTextRoot;
|
||||||
- (CGAffineTransform)getTextPathTransform:(CGFloat)distance;
|
|
||||||
- (CGPathRef)getGroupPath:(CGContextRef)context;
|
- (CGPathRef)getGroupPath:(CGContextRef)context;
|
||||||
- (void)resetTextPathAttributes;
|
- (void)resetTextPathAttributes;
|
||||||
- (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block;
|
- (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
@interface RNSVGTextPath : RNSVGText
|
@interface RNSVGTextPath : RNSVGText
|
||||||
|
|
||||||
@property (nonatomic, strong) NSString *href;
|
@property (nonatomic, strong) NSString *href;
|
||||||
|
@property (nonatomic, strong) NSString *startOffset;
|
||||||
|
|
||||||
- (RNSVGBezierPath *)getBezierPath;
|
- (RNSVGBezierPath *)getBezierPath;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
- (void)renderLayerTo:(CGContextRef)context
|
- (void)renderLayerTo:(CGContextRef)context
|
||||||
{
|
{
|
||||||
|
[self setContextBoundingBox:CGContextGetClipBoundingBox(context)];
|
||||||
[self renderGroupTo:context];
|
[self renderGroupTo:context];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
RNSVGPath *path = template;
|
RNSVGPath *path = template;
|
||||||
return [[RNSVGBezierPath alloc] initWithBezierCurves:[path getBezierCurves]];
|
CGFloat startOffset = [self getWidthRelatedValue:self.startOffset];
|
||||||
|
return [[RNSVGBezierPath alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves] startOffset:startOffset];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ RCT_EXPORT_MODULE()
|
|||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(href, NSString)
|
RCT_EXPORT_VIEW_PROPERTY(href, NSString)
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(startOffset, NSString)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -99,7 +99,8 @@ const TextAttributes = merge({
|
|||||||
}, RenderableAttributes);
|
}, RenderableAttributes);
|
||||||
|
|
||||||
const TextPathAttributes = merge({
|
const TextPathAttributes = merge({
|
||||||
href: true
|
href: true,
|
||||||
|
startOffset: true
|
||||||
}, RenderableAttributes);
|
}, RenderableAttributes);
|
||||||
|
|
||||||
const TSpanAttibutes = merge({
|
const TSpanAttibutes = merge({
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ export default function(props, container) {
|
|||||||
y,
|
y,
|
||||||
dx,
|
dx,
|
||||||
dy,
|
dy,
|
||||||
textAnchor
|
textAnchor,
|
||||||
|
startOffset
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
||||||
@@ -121,6 +122,7 @@ export default function(props, container) {
|
|||||||
content,
|
content,
|
||||||
deltaX,
|
deltaX,
|
||||||
deltaY,
|
deltaY,
|
||||||
|
startOffset: (startOffset || 0).toString(),
|
||||||
positionX: _.isNil(x) ? null : x,
|
positionX: _.isNil(x) ? null : x,
|
||||||
positionY: _.isNil(y) ? null : y
|
positionY: _.isNil(y) ? null : y
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user