diff --git a/elements/Shape.js b/elements/Shape.js index 25d13461..feb829a7 100644 --- a/elements/Shape.js +++ b/elements/Shape.js @@ -12,7 +12,7 @@ class Shape extends Component { this.state = this.touchableGetInitialState(); } - extractProps = (props, options) => { + extractProps = (props = {}, options) => { let extractedProps = extractProps(props, options); if (extractedProps.touchable && !extractedProps.disabled) { _.assign(extractedProps, { diff --git a/elements/TextPath.js b/elements/TextPath.js index cce88e0d..5b1232e0 100644 --- a/elements/TextPath.js +++ b/elements/TextPath.js @@ -4,6 +4,7 @@ import {TextPathAttributes} from '../lib/attributes'; import extractText from '../lib/extract/extractText'; import Shape from './Shape'; import {pathProps, fontProps} from '../lib/props'; +import TSpan from './TSpan'; const idExpReg = /^#(.+)$/; @@ -18,23 +19,29 @@ class TextPath extends Shape { }; render() { - let {props} = this; - let matched = props.href.match(idExpReg); - let href; + let {children, href, ...props} = this.props; + if (href) { + let matched = href.match(idExpReg); - if (matched) { - href = matched[1]; + if (matched) { + href = matched[1]; + + return ; + } } - if (!href) { - console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"'); - } - - return ; + console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"'); + return {children} } + } const RNSVGTextPath = createReactNativeComponentClass({ diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index 28a908f2..ff8fac01 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -15,7 +15,7 @@ @interface RNSVGGroup : RNSVGPath -- (void)pathRenderLayerTo:(CGContextRef)contex; -- (void)renderLayerToWithTransform:(CGContextRef)context transform:(CGAffineTransform)transform; +- (void)renderPathTo:(CGContextRef)context; +- (void)renderGroupTo:(CGContextRef)context; @end diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index a2866276..1200876a 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -12,27 +12,21 @@ - (void)renderLayerTo:(CGContextRef)context { - [self renderLayerToWithTransform:context transform:CGAffineTransformIdentity]; + [self clip:context]; + [self renderGroupTo:context]; } -- (void)renderLayerToWithTransform:(CGContextRef)context transform:(CGAffineTransform)transform +- (void)renderGroupTo:(CGContextRef)context { RNSVGSvgView* svg = [self getSvgView]; - [self clip:context]; - - CGContextConcatCTM(context, transform); [self traverseSubviews:^(RNSVGNode *node) { if (node.responsible && !svg.responsible) { svg.responsible = YES; - return NO; } - return YES; - }]; - - [self traverseSubviews:^(RNSVGNode *node) { + [node mergeProperties:self mergeList:self.attributeList inherited:YES]; [node renderTo:context]; - + if ([node isKindOfClass: [RNSVGRenderable class]]) { RNSVGRenderable *renderable = node; [self concatLayoutBoundingBox:[renderable getLayoutBoundingBox]]; @@ -41,7 +35,7 @@ }]; } -- (void)pathRenderLayerTo:(CGContextRef)context +- (void)renderPathTo:(CGContextRef)context { [super renderLayerTo:context]; } diff --git a/ios/Elements/RNSVGPath.h b/ios/Elements/RNSVGPath.h index e917282a..92461cd1 100644 --- a/ios/Elements/RNSVGPath.h +++ b/ios/Elements/RNSVGPath.h @@ -7,11 +7,13 @@ */ #import - +#import "RNSVGPathParser.h" #import "RNSVGRenderable.h" @interface RNSVGPath : RNSVGRenderable -@property (nonatomic, assign) CGPathRef d; +@property (nonatomic, strong) RNSVGPathParser *d; + +- (NSArray *)getBezierCurves; @end diff --git a/ios/Elements/RNSVGPath.m b/ios/Elements/RNSVGPath.m index 32e24e17..c2ee7815 100644 --- a/ios/Elements/RNSVGPath.m +++ b/ios/Elements/RNSVGPath.m @@ -9,26 +9,35 @@ #import "RNSVGPath.h" @implementation RNSVGPath +{ + CGPathRef _path; +} -- (void)setD:(CGPathRef)d +- (void)setD:(RNSVGPathParser *)d { if (d == _d) { return; } [self invalidate]; - CGPathRelease(_d); - _d = CGPathRetain(d); + _d = d; + CGPathRelease(_path); + _path = CGPathRetain([d getPath]); } - (CGPathRef)getPath:(CGContextRef)context { - return self.d; + return _path; +} + +- (NSArray *)getBezierCurves +{ + return [_d getBezierCurves]; } - (void)dealloc { - CGPathRelease(_d); + CGPathRelease(_path); } @end diff --git a/ios/Text/RNSVGTSpan.h b/ios/Text/RNSVGTSpan.h index f40bf197..3e04fc0b 100644 --- a/ios/Text/RNSVGTSpan.h +++ b/ios/Text/RNSVGTSpan.h @@ -8,7 +8,6 @@ #import #import -#import "RNSVGPath.h" #import "RNSVGText.h" @interface RNSVGTSpan : RNSVGText diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 634218db..ca9a7f66 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -10,23 +10,29 @@ #import "RNSVGTSpan.h" #import "RNSVGBezierPath.h" #import "RNSVGText.h" +#import "RNSVGTextPath.h" @implementation RNSVGTSpan +{ + RNSVGBezierPath *_bezierPath; +} - (void)renderLayerTo:(CGContextRef)context { if (self.content) { - [self pathRenderLayerTo:context]; + [self renderPathTo:context]; } else { - [super renderLayerTo:context]; + [self clip:context]; + [self renderGroupTo:context]; } } - (CGPathRef)getPath:(CGContextRef)context { if (!self.content) { - return [self getTextGroupPath:context]; + return [self getGroupPath:context]; } + [self initialTextPath]; [self setContextBoundingBox:CGContextGetClipBoundingBox(context)]; CGMutablePathRef path = CGPathCreateMutable(); @@ -49,7 +55,7 @@ CTLineRef line = CTLineCreateWithAttributedString(attrString); CGMutablePathRef linePath = [self getLinePath:line]; - CGAffineTransform offset = CGAffineTransformMakeTranslation(0, CTFontGetSize(font)); + CGAffineTransform offset = CGAffineTransformMakeTranslation(0, _bezierPath ? 0 : CTFontGetSize(font) * 1.1); CGPathAddPath(path, &offset, linePath); // clean up @@ -57,7 +63,6 @@ CFRelease(line); CGPathRelease(linePath); [self resetTextPathAttributes]; - return (CGPathRef)CFAutorelease(path); } @@ -81,21 +86,63 @@ CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName); CGFloat lineStartX; + CGFloat lastX; for(CFIndex i = 0; i < runGlyphCount; i++) { RNSVGGlyphPoint computedPoint = [self getComputedGlyphPoint:i glyphOffset:positions[i]]; + if (!i) { lineStartX = computedPoint.x; + lastX = lineStartX; + } + + CGAffineTransform textPathTransform = [self getTextPathTransform:computedPoint.x]; + + if (!textPathTransform.a || !textPathTransform.d) { + return path; } - CGAffineTransform transform = CGAffineTransformTranslate(upsideDown, computedPoint.x, -computedPoint.y); CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); + CGAffineTransform transform; + + if (_bezierPath) { + transform = CGAffineTransformScale(textPathTransform, 1.0, -1.0); + } else { + transform = CGAffineTransformTranslate(upsideDown, computedPoint.x, -computedPoint.y); + } + CGPathAddPath(path, &transform, letter); + lastX += CGPathGetBoundingBox(letter).size.width; CGPathRelease(letter); } - [self getTextRoot].lastX = lineStartX + CGPathGetBoundingBox(path).size.width; + [self getTextRoot].lastX = lastX; + return path; } +- (void)initialTextPath +{ + __block RNSVGBezierPath *bezierPath; + [self traverseTextSuperviews:^(__kindof RNSVGText *node) { + if ([node class] == [RNSVGTextPath class]) { + RNSVGTextPath *textPath = node; + bezierPath = [node getBezierPath]; + return NO; + } + return YES; + }]; + + _bezierPath = bezierPath; +} + +- (CGAffineTransform)getTextPathTransform:(CGFloat)distance +{ + if (_bezierPath) { + return [_bezierPath transformAtDistance:distance]; + } + + return CGAffineTransformIdentity; + +} @end diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index 3aa36d0a..6610f899 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -27,7 +27,9 @@ - (CTFontRef)getComputedFont; - (RNSVGGlyphPoint)getComputedGlyphPoint:(NSUInteger *)index glyphOffset:(CGPoint)glyphOffset; - (RNSVGText *)getTextRoot; -- (CGPathRef)getTextGroupPath:(CGContextRef)context; +- (CGAffineTransform)getTextPathTransform:(CGFloat)distance; +- (CGPathRef)getGroupPath:(CGContextRef)context; - (void)resetTextPathAttributes; +- (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block; @end diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index eefcca94..e529dcf3 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -7,7 +7,7 @@ */ #import "RNSVGText.h" -#import "RNSVGBezierPath.h" +#import "RNSVGTextPath.h" #import #import @@ -24,33 +24,39 @@ - (void)renderLayerTo:(CGContextRef)context { + [self clip:context]; CGContextSaveGState(context); CGAffineTransform transform = CGAffineTransformMakeTranslation([self getShift:context path:nil], 0); - [super renderLayerToWithTransform:context transform:transform]; + CGContextConcatCTM(context, transform); + [self renderGroupTo:context]; + [self resetTextPathAttributes]; CGContextRestoreGState(context); } - (CGPathRef)getPath:(CGContextRef)context { - CGMutablePathRef shape = [self getTextGroupPath:context]; - CGAffineTransform translation = CGAffineTransformMakeTranslation([self getShift:context path:shape], 0); - CGMutablePathRef path = CGPathCreateCopyByTransformingPath(shape, &translation); - return (CGPathRef)CFAutorelease(path); + CGPathRef path = [self getGroupPath:context]; + CGAffineTransform transform = CGAffineTransformMakeTranslation([self getShift:context path:path], 0); + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &transform); + + // check memory leaks here + //CGPathRelease(path); + return (CGPathRef)CFAutorelease(transformedPath); } -- (CGPathRef)getTextGroupPath:(CGContextRef)context +- (CGPathRef)getGroupPath:(CGContextRef)context { - CGPathRef path = [super getPath:context]; + CGPathRef groupPath = [super getPath:context]; [self resetTextPathAttributes]; - return path; + return groupPath; } - (CGFloat)getShift:(CGContextRef)context path:(CGPathRef)path { - if (!path) { - path = [self getTextGroupPath:context]; + if (path == nil) { + path = [self getGroupPath:context]; } - + CGFloat width = CGRectGetWidth(CGPathGetBoundingBox(path)); switch ([self getComputedTextAnchor]) { @@ -74,7 +80,6 @@ child = [child.subviews objectAtIndex:0]; } } - return anchor; } @@ -112,7 +117,7 @@ - (CTFontRef)getComputedFont { NSMutableDictionary *fontDict = [[NSMutableDictionary alloc] init]; - [self traverseTextSuperviews:^(RNSVGText *node) { + [self traverseTextSuperviews:^(__kindof RNSVGText *node) { return [self extendFontFromInheritedFont:fontDict inheritedFont:node.font]; }]; @@ -132,38 +137,44 @@ } fontFamily = fontFamilyFound ? fontFamily : nil; - return (__bridge CTFontRef)[RCTFont updateFont:nil withFamily:fontFamily size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"] variant:nil scaleMultiplier:1.0]; + return (__bridge CTFontRef)[RCTFont updateFont:nil + withFamily:fontFamily + size:fontDict[@"fontSize"] + weight:fontDict[@"fontWeight"] + style:fontDict[@"fontStyle"] + variant:nil scaleMultiplier:1.0]; } - (RNSVGGlyphPoint)getComputedGlyphPoint:(NSUInteger *)index glyphOffset:(CGPoint)glyphOffset { - RNSVGGlyphPoint __block point; + __block RNSVGGlyphPoint point; point.isDeltaXSet = point.isDeltaYSet = point.isPositionXSet = point.isPositionYSet = NO; - [self traverseTextSuperviews:^(RNSVGText *node) { - NSUInteger index = node.lastIndex; - - if (!point.isPositionXSet && node.positionX && !index) { - point.positionX = [self getWidthRelatedValue:node.positionX]; - point.isPositionXSet = YES; + [self traverseTextSuperviews:^(__kindof RNSVGText *node) { + if ([node class] != [RNSVGTextPath class]) { + NSUInteger index = node.lastIndex; + + if (!point.isPositionXSet && node.positionX && !index) { + point.positionX = [self getWidthRelatedValue:node.positionX]; + point.isPositionXSet = YES; + } + + if (!point.isDeltaXSet && node.deltaX.count > index) { + point.deltaX = [[node.deltaX objectAtIndex:index] floatValue]; + point.isDeltaXSet = YES; + } + + if (!point.isPositionYSet && node.positionY && !index) { + point.positionY = [self getHeightRelatedValue:node.positionY]; + point.isPositionYSet = YES; + } + + if (!point.isDeltaYSet && node.deltaY.count > index) { + point.deltaY = [[node.deltaY objectAtIndex:index] floatValue]; + point.isDeltaYSet = YES; + } + node.lastIndex++; } - - if (!point.isDeltaXSet && node.deltaX.count > index) { - point.deltaX = [[node.deltaX objectAtIndex:index] floatValue]; - point.isDeltaXSet = YES; - } - - if (!point.isPositionYSet && node.positionY && !index) { - point.positionY = [self getHeightRelatedValue:node.positionY]; - point.isPositionYSet = YES; - } - - if (!point.isDeltaYSet && node.deltaY.count > index) { - point.deltaY = [[node.deltaY objectAtIndex:index] floatValue]; - point.isDeltaYSet = YES; - } - - node.lastIndex++; return YES; }]; @@ -192,14 +203,16 @@ point.x = lastX + glyphOffset.x; point.y = lastY + glyphOffset.y; - + return point; } + - (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block { RNSVGText *targetView = self; - block(targetView); + block(self); + while (targetView && [targetView class] != [RNSVGText class]) { if (![targetView isKindOfClass:[RNSVGText class]]) { //todo: throw exception here diff --git a/ios/Text/RNSVGTextPath.h b/ios/Text/RNSVGTextPath.h index dd200227..7082fcc5 100644 --- a/ios/Text/RNSVGTextPath.h +++ b/ios/Text/RNSVGTextPath.h @@ -8,11 +8,13 @@ #import #import -#import "RNSVGPath.h" #import "RNSVGText.h" +#import "RNSVGBezierPath.h" @interface RNSVGTextPath : RNSVGText @property (nonatomic, strong) NSString *href; +- (RNSVGBezierPath *)getBezierPath; + @end diff --git a/ios/Text/RNSVGTextPath.m b/ios/Text/RNSVGTextPath.m index 87e81c26..1b6acd71 100644 --- a/ios/Text/RNSVGTextPath.m +++ b/ios/Text/RNSVGTextPath.m @@ -10,14 +10,30 @@ #import "RNSVGTextPath.h" #import "RNSVGBezierPath.h" -@class RNSVGText; @implementation RNSVGTextPath +- (void)renderLayerTo:(CGContextRef)context +{ + [self renderGroupTo:context]; +} + - (CGPathRef)getPath:(CGContextRef)context { - CGMutablePathRef path = CGPathCreateMutable(); + return [self getGroupPath:context]; +} - return (CGPathRef)CFAutorelease(path); +- (RNSVGBezierPath *)getBezierPath +{ + RNSVGSvgView *svg = [self getSvgView]; + RNSVGNode *template = [svg getDefinedTemplate:self.href]; + + if ([template class] != [RNSVGPath class]) { + // warning about this. + return nil; + } + + RNSVGPath *path = template; + return [[RNSVGBezierPath alloc] initWithBezierCurves:[path getBezierCurves]]; } @end diff --git a/ios/Utils/RCTConvert+RNSVG.h b/ios/Utils/RCTConvert+RNSVG.h index 610be33f..6072e224 100644 --- a/ios/Utils/RCTConvert+RNSVG.h +++ b/ios/Utils/RCTConvert+RNSVG.h @@ -14,13 +14,14 @@ #import "RNSVGCGFCRule.h" #import "RNSVGVBMOS.h" #import "RNSVGTextAnchor.h" +#import "RNSVGPathParser.h" @class RNSVGBrush; @interface RCTConvert (RNSVG) + (RNSVGTextAnchor)RNSVGTextAnchor:(id)json; -+ (CGPathRef)CGPath:(NSString *)d; ++ (RNSVGPathParser *)CGPath:(NSString *)d; + (CTTextAlignment)CTTextAlignment:(id)json; + (RNSVGCGFCRule)RNSVGCGFCRule:(id)json; + (RNSVGVBMOS)RNSVGVBMOS:(id)json; @@ -28,7 +29,6 @@ + (RNSVGBrush *)RNSVGBrush:(id)json; -+ (NSArray *)RNSVGBezier:(id)json; + (CGRect)CGRect:(id)json offset:(NSUInteger)offset; + (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset; + (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset; diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index e1778da0..32afb846 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -15,13 +15,12 @@ #import "RNSVGCGFCRule.h" #import "RNSVGVBMOS.h" #import -#import "RNSVGPathParser.h" @implementation RCTConvert (RNSVG) -+ (CGPathRef)CGPath:(NSString *)d ++ (RNSVGPathParser *)CGPath:(NSString *)d { - return [[[RNSVGPathParser alloc] initWithPathString: d] getPath]; + return [[RNSVGPathParser alloc] initWithPathString: d]; } RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ diff --git a/ios/Utils/RNSVGPathParser.h b/ios/Utils/RNSVGPathParser.h index 1815390c..da53782b 100644 --- a/ios/Utils/RNSVGPathParser.h +++ b/ios/Utils/RNSVGPathParser.h @@ -6,12 +6,13 @@ * LICENSE file in the root directory of this source tree. */ -#import -#import + +#import @interface RNSVGPathParser : NSObject - (instancetype) initWithPathString:(NSString *)d; - (CGPathRef)getPath; +- (NSArray *)getBezierCurves; @end diff --git a/ios/Utils/RNSVGPathParser.m b/ios/Utils/RNSVGPathParser.m index 3c07a9ca..f74fcc7d 100644 --- a/ios/Utils/RNSVGPathParser.m +++ b/ios/Utils/RNSVGPathParser.m @@ -14,6 +14,8 @@ NSString* _d; NSString* _originD; NSRegularExpression* _pathRegularExpression; + NSMutableArray* _bezierCurves; + NSValue *_lastStartPoint; double _penX; double _penY; double _penDownX; @@ -39,8 +41,9 @@ { CGMutablePathRef path = CGPathCreateMutable(); NSArray* results = [_pathRegularExpression matchesInString:_d options:0 range:NSMakeRange(0, [_d length])]; - + _bezierCurves = [[NSMutableArray alloc] init]; int count = [results count]; + if (count) { NSUInteger i = 0; #define NEXT_VALUE [self getNextValue:results[i++]] @@ -117,6 +120,15 @@ return (CGPathRef)CFAutorelease(path); } +- (NSArray *)getBezierCurves +{ + if (!_bezierCurves) { + CGPathRelease([self getPath]); + } + + return [_bezierCurves copy]; +} + - (NSString *)getNextValue:(NSTextCheckingResult *)result { if (!result) { @@ -145,6 +157,9 @@ _pivotX = _penX = x; _pivotY = _penY = y; CGPathMoveToPoint(path, nil, x, y); + + _lastStartPoint = [NSValue valueWithCGPoint: CGPointMake(x, y)]; + [_bezierCurves addObject: @[_lastStartPoint]]; } - (void)line:(CGPathRef)path x:(double)x y:(double)y @@ -157,6 +172,13 @@ _pivotX = _penX = x; _pivotY = _penY = y; CGPathAddLineToPoint(path, nil, x, y); + + NSValue * destination = [NSValue valueWithCGPoint:CGPointMake(x, y)]; + [_bezierCurves addObject: @[ + destination, + _lastStartPoint, + destination + ]]; } - (void)curve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y c2x:(double)c2x c2y:(double)c2y ex:(double)ex ey:(double)ey @@ -182,6 +204,13 @@ _penX = ex; _penY = ey; CGPathAddCurveToPoint(path, nil, c1x, c1y, c2x, c2y, ex, ey); + + + [_bezierCurves addObject: @[ + [NSValue valueWithCGPoint:CGPointMake(c1x, c1y)], + [NSValue valueWithCGPoint:CGPointMake(c2x, c2y)], + [NSValue valueWithCGPoint:CGPointMake(ex, ey)] + ]]; } - (void)smoothCurve:(CGPathRef)path c1x:(double)c1x c1y:(double)c1y ex:(double)ex ey:(double)ey @@ -302,11 +331,7 @@ _penX = _pivotX = x; _penY = _pivotY = y; - if (rx != ry || rad != 0) { - [self arcToBezier:path cx:cx cy:cy rx:rx ry:ry sa:sa ea:ea clockwise:clockwise rad:rad]; - } else { - CGPathAddArc(path, nil, cx, cy, rx, sa, ea, !clockwise); - } + [self arcToBezier:path cx:cx cy:cy rx:rx ry:ry sa:sa ea:ea clockwise:clockwise rad:rad]; } - (void)arcToBezier:(CGPathRef)path cx:(double)cx cy:(double)cy rx:(double)rx ry:(double)ry sa:(double)sa ea:(double)ea clockwise:(BOOL)clockwise rad:(double)rad @@ -364,6 +389,7 @@ _penY = _penDownY; _penDownSet = NO; CGPathCloseSubpath(path); + [_bezierCurves addObject: @[]]; } } diff --git a/lib/attributes.js b/lib/attributes.js index feab328d..18a777cd 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -98,9 +98,9 @@ const TextAttributes = merge({ positionY: true }, RenderableAttributes); -const TextPathAttributes = { +const TextPathAttributes = merge({ href: true -}; +}, RenderableAttributes); const TSpanAttibutes = merge({ content: true diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 4f0d356e..2e00bd76 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -79,7 +79,7 @@ function parseDelta(delta) { } } -export default function(props, isText) { +export default function(props, container) { const { x, y, @@ -95,7 +95,7 @@ export default function(props, isText) { let content = null; if (typeof children === 'string') { - if (isText) { + if (container) { children = {children}; } else { content = children;