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 extractText from '../lib/extract/extractText';
import Shape from './Shape';
import {pathProps, fontProps} from '../lib/props';
import {pathProps, fontProps, numberProp} from '../lib/props';
import TSpan from './TSpan';
const idExpReg = /^#(.+)$/;
@@ -15,11 +15,11 @@ class TextPath extends Shape {
...pathProps,
...fontProps,
href: PropTypes.string.isRequired,
textAnchor: PropTypes.oneOf(['start', 'middle', 'end'])
startOffset: numberProp
};
render() {
let {children, href, ...props} = this.props;
let {children, href, startOffset, ...props} = this.props;
if (href) {
let matched = href.match(idExpReg);
@@ -33,7 +33,10 @@ class TextPath extends Shape {
x: null,
y: null
})}
{...extractText({children}, true)}
{...extractText({
children,
startOffset
}, true)}
/>;
}
}

View File

@@ -11,7 +11,10 @@
@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;
@end

View File

@@ -17,54 +17,70 @@
@implementation RNSVGBezierPath
{
NSArray<NSArray *> *_bezierCurves;
NSUInteger _bezierIndex;
CGFloat _offset;
CGFloat _lastX;
int _currentBezierIndex;
CGFloat _startOffset;
CGFloat _lastOffset;
CGFloat _lastRecord;
CGFloat _lastDistance;
CGPoint _lastPoint;
CGPoint _P0;
CGPoint _P1;
CGPoint _P2;
CGPoint _P3;
BOOL _reachedEnd;
}
- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves
- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray<NSArray *> *)bezierCurves startOffset:(CGFloat)startOffset
{
if (self = [super init]) {
_bezierCurves = bezierCurves;
_bezierIndex = 0;
_offset = 0;
_lastX = 0;
_lastDistance = 0;
_currentBezierIndex = _lastOffset = _lastRecord = _lastDistance = 0;
_startOffset = startOffset;
[self setControlPoints];
}
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;
}
- (CGPoint)pointForOffset:(CGFloat)t {
CGFloat x = Bezier(t, _P0.x, _P1.x, _P2.x, _P3.x);
CGFloat y = Bezier(t, _P0.y, _P1.y, _P2.y, _P3.y);
- (CGPoint)pointAtOffset:(CGFloat)t {
CGFloat x = calculateBezier(t, _P0.x, _P1.x, _P2.x, _P3.x);
CGFloat y = calculateBezier(t, _P0.y, _P1.y, _P2.y, _P3.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;
}
- (CGFloat)angleForOffset:(CGFloat)t {
CGFloat dx = BezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x);
CGFloat dy = BezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y);
- (CGFloat)angleAtOffset:(CGFloat)t {
CGFloat dx = calculateBezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x);
CGFloat dy = calculateBezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y);
return atan2(dy, dx);
}
static CGFloat Distance(CGPoint a, CGPoint b) {
CGFloat dx = a.x - b.x;
CGFloat dy = a.y - b.y;
return hypot(dx, dy);
static CGFloat calculateDistance(CGPoint a, CGPoint b) {
return hypot(a.x - b.x, a.y - b.y);
}
// Simplistic routine to find the offset along Bezier that is
@@ -78,12 +94,13 @@ static CGFloat Distance(CGPoint a, CGPoint b) {
- (CGFloat)offsetAtDistance:(CGFloat)distance
fromPoint:(CGPoint)point
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 newOffset = offset + kStep;
while (newDistance <= distance && newOffset < 1.0) {
newOffset += kStep;
newDistance = Distance(point, [self pointForOffset:newOffset]);
newDistance = calculateDistance(point, [self pointAtOffset:newOffset]);
}
_lastDistance = newDistance;
@@ -92,49 +109,49 @@ static CGFloat Distance(CGPoint a, CGPoint b) {
- (void)setControlPoints
{
NSArray *bezier = [_bezierCurves objectAtIndex:_bezierIndex];
_bezierIndex++;
NSArray *bezier = [_bezierCurves objectAtIndex:_currentBezierIndex++];
// set start point
if (bezier.count == 1) {
_lastPoint = _P0 = [(NSValue *)[bezier objectAtIndex:0] CGPointValue];
_lastPoint = _P0 = [[bezier objectAtIndex:0] CGPointValue];
[self setControlPoints];
} else if (bezier.count == 3) {
_P1 = [(NSValue *)[bezier objectAtIndex:0] CGPointValue];
_P2 = [(NSValue *)[bezier objectAtIndex:1] CGPointValue];
_P3 = [(NSValue *)[bezier objectAtIndex:2] CGPointValue];
_P1 = [[bezier objectAtIndex:0] CGPointValue];
_P2 = [[bezier objectAtIndex:1] CGPointValue];
_P3 = [[bezier objectAtIndex:2] CGPointValue];
}
}
- (CGAffineTransform)transformAtDistance:(CGFloat)distance
{
if (_offset == 0) {
if (_bezierCurves.count == _bezierIndex) {
return CGAffineTransformMakeScale(0, 0);
} else {
[self setControlPoints];
}
distance += _startOffset;
if (_reachedEnd) {
return getReachedEndTansform();
} else if (distance < 0) {
return getUnreadedTransform();
}
CGFloat offset = [self offsetAtDistance:distance - _lastX
CGFloat offset = [self offsetAtDistance:distance - _lastRecord
fromPoint:_lastPoint
offset:_offset];
CGPoint glyphPoint = [self pointForOffset:offset];
CGFloat angle = [self angleForOffset:offset];
offset:_lastOffset];
if (offset < 1) {
_offset = offset;
CGPoint glyphPoint = [self pointAtOffset:offset];
_lastOffset = offset;
_lastPoint = glyphPoint;
_lastRecord = distance;
return CGAffineTransformRotate(CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y), [self angleAtOffset:offset]);
} else if (_bezierCurves.count == _currentBezierIndex) {
_reachedEnd = YES;
return getReachedEndTansform();
} else {
_offset = 0;
_lastOffset = 0;
_lastPoint = _P0 = _P3;
_lastX += _lastDistance;
return [self transformAtDistance:distance];
_lastRecord += _lastDistance;
[self setControlPoints];
return [self transformAtDistance:distance - _startOffset];
}
_lastX = distance;
CGAffineTransform transform = CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y);
transform = CGAffineTransformRotate(transform, angle);
return transform;
}
@end

View File

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

View File

@@ -32,8 +32,9 @@
if (!self.content) {
return [self getGroupPath:context];
}
[self initialTextPath];
[self setContextBoundingBox:CGContextGetClipBoundingBox(context)];
[self initialTextPath];
CGMutablePathRef path = CGPathCreateMutable();
if ([self.content isEqualToString:@""]) {
@@ -98,8 +99,10 @@
CGAffineTransform textPathTransform = [self getTextPathTransform:computedPoint.x];
if (!textPathTransform.a || !textPathTransform.d) {
if ([RNSVGBezierPath hasReachedEnd:textPathTransform]) {
break;
} else if ([RNSVGBezierPath hasReachedStart:textPathTransform]) {
continue;
}
CGAffineTransform transform;

View File

@@ -27,7 +27,6 @@
- (CTFontRef)getComputedFont;
- (RNSVGGlyphPoint)getComputedGlyphPoint:(NSUInteger *)index glyphOffset:(CGPoint)glyphOffset;
- (RNSVGText *)getTextRoot;
- (CGAffineTransform)getTextPathTransform:(CGFloat)distance;
- (CGPathRef)getGroupPath:(CGContextRef)context;
- (void)resetTextPathAttributes;
- (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block;

View File

@@ -14,6 +14,7 @@
@interface RNSVGTextPath : RNSVGText
@property (nonatomic, strong) NSString *href;
@property (nonatomic, strong) NSString *startOffset;
- (RNSVGBezierPath *)getBezierPath;

View File

@@ -14,6 +14,7 @@
- (void)renderLayerTo:(CGContextRef)context
{
[self setContextBoundingBox:CGContextGetClipBoundingBox(context)];
[self renderGroupTo:context];
}
@@ -33,7 +34,8 @@
}
RNSVGPath *path = template;
return [[RNSVGBezierPath alloc] initWithBezierCurves:[path getBezierCurves]];
CGFloat startOffset = [self getWidthRelatedValue:self.startOffset];
return [[RNSVGBezierPath alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves] startOffset:startOffset];
}
@end

View File

@@ -20,5 +20,6 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(href, NSString)
RCT_EXPORT_VIEW_PROPERTY(startOffset, NSString)
@end

View File

@@ -99,7 +99,8 @@ const TextAttributes = merge({
}, RenderableAttributes);
const TextPathAttributes = merge({
href: true
href: true,
startOffset: true
}, RenderableAttributes);
const TSpanAttibutes = merge({

View File

@@ -88,7 +88,8 @@ export default function(props, container) {
y,
dx,
dy,
textAnchor
textAnchor,
startOffset
} = props;
@@ -121,6 +122,7 @@ export default function(props, container) {
content,
deltaX,
deltaY,
startOffset: (startOffset || 0).toString(),
positionX: _.isNil(x) ? null : x,
positionY: _.isNil(y) ? null : y
}