Add startOffset support

This commit is contained in:
Horcrux
2017-01-10 12:30:48 +08:00
parent 84ea887a06
commit b15d0791e0
11 changed files with 99 additions and 65 deletions

View File

@@ -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)}
/>; />;
} }
} }

View File

@@ -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

View File

@@ -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,14 +92,15 @@ 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;
@@ -92,49 +109,49 @@ static CGFloat Distance(CGPoint a, CGPoint b) {
- (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

View File

@@ -14,4 +14,6 @@
@property (nonatomic, strong) NSString *content; @property (nonatomic, strong) NSString *content;
- (CGAffineTransform)getTextPathTransform:(CGFloat)distance;
@end @end

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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({

View File

@@ -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
} }