diff --git a/Example/examples/Text.js b/Example/examples/Text.js index f905c844..032192d8 100644 --- a/Example/examples/Text.js +++ b/Example/examples/Text.js @@ -9,7 +9,8 @@ import Svg, { Defs, Path, G, - TSpan + TSpan, + TextPath } from 'react-native-svg'; class TextExample extends Component{ @@ -133,11 +134,21 @@ class TextPath extends Component{ height="60" width="200" > + + + We go up, then we go down, then up again + > + + We go up, then we go down, + then up again + + { + this.root.setNativeProps(...args); }; render() { - return null; + let props = this.props; + return {this.root = ele;}} + {...this.extractProps({ + ...props, + x: null, + y: null + })} + {...extractText(props)} + />; } } +const RNSVGTSpan = createReactNativeComponentClass({ + validAttributes: TSpanAttibutes, + uiViewClassName: 'RNSVGTSpan' +}); + export default TSpan; diff --git a/elements/Text.js b/elements/Text.js index b8f894ea..95da276f 100644 --- a/elements/Text.js +++ b/elements/Text.js @@ -4,6 +4,7 @@ import extractText from '../lib/extract/extractText'; import {numberProp, pathProps, fontProps} from '../lib/props'; import {TextAttributes} from '../lib/attributes'; import Shape from './Shape'; +import TSpan from './TSpan'; class Text extends Shape { static displayName = 'Text'; @@ -21,7 +22,7 @@ class Text extends Shape { }; render() { - let props = this.props; + const props = this.props; return {this.root = ele;}} @@ -30,7 +31,7 @@ class Text extends Shape { x: null, y: null })} - {...extractText(props)} + {...extractText(props, true)} />; } } diff --git a/elements/TextPath.js b/elements/TextPath.js new file mode 100644 index 00000000..d7476d69 --- /dev/null +++ b/elements/TextPath.js @@ -0,0 +1,52 @@ +import React, {PropTypes} from 'react'; +import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; +import {UseAttributes} from '../lib/attributes'; +import Shape from './Shape'; +import {pathProps, fontProps} from '../lib/props'; + +const idExpReg = /^#(.+)$/; +class TextPath extends Shape { + static displayName = 'Span'; + + static propTypes = { + ...pathProps, + ...fontProps, + href: PropTypes.string.isRequired, + textAnchor: PropTypes.oneOf(['start', 'middle', 'end']) + }; + + render() { + let {props} = this; + let matched = props.href.match(idExpReg); + let href; + + if (matched) { + href = matched[1]; + } + + if (!href) { + console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"'); + } + + return ; + } +} + +const RNSVGTextPath = createReactNativeComponentClass({ + validAttributes: UseAttributes, + uiViewClassName: 'RNSVGTextPath' +}); + +export default TextPath; diff --git a/index.js b/index.js index 86fb4451..02107e05 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,7 @@ import RadialGradient from './elements/RadialGradient'; import Stop from './elements/Stop'; import ClipPath from './elements/ClipPath'; import TSpan from './elements/TSpan'; +import TextPath from './elements/TextPath'; export { Svg, @@ -37,7 +38,8 @@ export { RadialGradient, Stop, ClipPath, - TSpan + TSpan, + TextPath }; export default Svg; diff --git a/ios/RNSVG.xcodeproj/project.pbxproj b/ios/RNSVG.xcodeproj/project.pbxproj index e410f955..0f9f69d0 100644 --- a/ios/RNSVG.xcodeproj/project.pbxproj +++ b/ios/RNSVG.xcodeproj/project.pbxproj @@ -23,8 +23,8 @@ 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2901CE71EC2001E90A8 /* RNSVGText.m */; }; 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */; }; 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */; }; - 107CD53E1D7166B700F0A7AC /* RNSVGSpanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 107CD53D1D7166B700F0A7AC /* RNSVGSpanManager.m */; }; - 107CD5401D71672E00F0A7AC /* RNSVGSpan.m in Sources */ = {isa = PBXBuildFile; fileRef = 107CD53F1D71672E00F0A7AC /* RNSVGSpan.m */; }; + 107CD53E1D7166B700F0A7AC /* RNSVGTSpanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 107CD53D1D7166B700F0A7AC /* RNSVGTSpanManager.m */; }; + 107CD5401D71672E00F0A7AC /* RNSVGTSpan.m in Sources */ = {isa = PBXBuildFile; fileRef = 107CD53F1D71672E00F0A7AC /* RNSVGTSpan.m */; }; 10ABC7331D435915006CCF6E /* RNSVGViewBoxManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ABC7321D435915006CCF6E /* RNSVGViewBoxManager.m */; }; 10ABC7361D43595E006CCF6E /* RNSVGViewBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 10ABC7351D43595E006CCF6E /* RNSVGViewBox.m */; }; 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */; }; @@ -101,10 +101,10 @@ 1039D2A11CE721A7001E90A8 /* RNSVGContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGContainer.h; sourceTree = ""; }; 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGPercentageConverter.h; path = Utils/RNSVGPercentageConverter.h; sourceTree = ""; }; 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGPercentageConverter.m; path = Utils/RNSVGPercentageConverter.m; sourceTree = ""; }; - 107CD53B1D7166A000F0A7AC /* RNSVGSpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGSpan.h; path = Text/RNSVGSpan.h; sourceTree = ""; }; - 107CD53C1D7166B700F0A7AC /* RNSVGSpanManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGSpanManager.h; sourceTree = ""; }; - 107CD53D1D7166B700F0A7AC /* RNSVGSpanManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSpanManager.m; sourceTree = ""; }; - 107CD53F1D71672E00F0A7AC /* RNSVGSpan.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGSpan.m; path = Text/RNSVGSpan.m; sourceTree = ""; }; + 107CD53B1D7166A000F0A7AC /* RNSVGTSpan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTSpan.h; path = Text/RNSVGTSpan.h; sourceTree = ""; }; + 107CD53C1D7166B700F0A7AC /* RNSVGTSpanManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGTSpanManager.h; sourceTree = ""; }; + 107CD53D1D7166B700F0A7AC /* RNSVGTSpanManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGTSpanManager.m; sourceTree = ""; }; + 107CD53F1D71672E00F0A7AC /* RNSVGTSpan.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGTSpan.m; path = Text/RNSVGTSpan.m; sourceTree = ""; }; 10ABC7311D435915006CCF6E /* RNSVGViewBoxManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGViewBoxManager.h; sourceTree = ""; }; 10ABC7321D435915006CCF6E /* RNSVGViewBoxManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGViewBoxManager.m; sourceTree = ""; }; 10ABC7341D43595E006CCF6E /* RNSVGViewBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGViewBox.h; sourceTree = ""; }; @@ -160,6 +160,7 @@ 10FDEEB01D3FB60500A5C46C /* RNSVGBaseBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBaseBrush.h; sourceTree = ""; }; 10FDEEB11D3FB60500A5C46C /* RNSVGBaseBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGBaseBrush.m; sourceTree = ""; }; 10FDEEB31D3FBED400A5C46C /* RNSVGBrushType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBrushType.h; sourceTree = ""; }; + 7FF070191DC249BE000E28A0 /* RNSVGTextAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextAnchor.h; path = Utils/RNSVGTextAnchor.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -222,8 +223,6 @@ 0CF68AF81AF0549300FF9E5C /* ViewManagers */ = { isa = PBXGroup; children = ( - 107CD53C1D7166B700F0A7AC /* RNSVGSpanManager.h */, - 107CD53D1D7166B700F0A7AC /* RNSVGSpanManager.m */, 10ABC7311D435915006CCF6E /* RNSVGViewBoxManager.h */, 10ABC7321D435915006CCF6E /* RNSVGViewBoxManager.m */, 10BEC1BE1D3F680F00FDCB19 /* RNSVGLinearGradientManager.h */, @@ -258,6 +257,8 @@ 10BA0D311CE74E3100887C2B /* RNSVGSvgViewManager.m */, 10BA0D321CE74E3100887C2B /* RNSVGTextManager.h */, 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */, + 107CD53C1D7166B700F0A7AC /* RNSVGTSpanManager.h */, + 107CD53D1D7166B700F0A7AC /* RNSVGTSpanManager.m */, ); path = ViewManagers; sourceTree = ""; @@ -282,8 +283,8 @@ children = ( 103371331D41D3400028AF13 /* RNSVGBezierPath.h */, 103371311D41C5C90028AF13 /* RNSVGBezierPath.m */, - 107CD53B1D7166A000F0A7AC /* RNSVGSpan.h */, - 107CD53F1D71672E00F0A7AC /* RNSVGSpan.m */, + 107CD53B1D7166A000F0A7AC /* RNSVGTSpan.h */, + 107CD53F1D71672E00F0A7AC /* RNSVGTSpan.m */, 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, 1039D2901CE71EC2001E90A8 /* RNSVGText.m */, ); @@ -319,6 +320,7 @@ isa = PBXGroup; children = ( 10ABC7381D43982B006CCF6E /* RNSVGVBMOS.h */, + 7FF070191DC249BE000E28A0 /* RNSVGTextAnchor.h */, 10ABC7371D439779006CCF6E /* RNSVGCGFCRule.h */, 1039D2AE1CE72F27001E90A8 /* RNSVGPercentageConverter.h */, 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */, @@ -400,7 +402,7 @@ 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */, 10BA0D3B1CE74E3100887C2B /* RNSVGRectManager.m in Sources */, 0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */, - 107CD53E1D7166B700F0A7AC /* RNSVGSpanManager.m in Sources */, + 107CD53E1D7166B700F0A7AC /* RNSVGTSpanManager.m in Sources */, 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */, 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */, 10BEC1C61D3F7BD300FDCB19 /* RNSVGBrushConverter.m in Sources */, @@ -409,7 +411,7 @@ 10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */, 0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColorBrush.m in Sources */, 10BA0D3A1CE74E3100887C2B /* RNSVGPathManager.m in Sources */, - 107CD5401D71672E00F0A7AC /* RNSVGSpan.m in Sources */, + 107CD5401D71672E00F0A7AC /* RNSVGTSpan.m in Sources */, 103371321D41C5C90028AF13 /* RNSVGBezierPath.m in Sources */, 10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */, 10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */, diff --git a/ios/Text/RNSVGTSpan.h b/ios/Text/RNSVGTSpan.h new file mode 100644 index 00000000..caaff504 --- /dev/null +++ b/ios/Text/RNSVGTSpan.h @@ -0,0 +1,24 @@ +/** + * 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. + */ + +#import +#import +#import "RNSVGPath.h" +#import "RNSVGText.h" + +@interface RNSVGTSpan : RNSVGPath + +@property (nonatomic, assign) RNSVGTextAnchor textAnchor; +@property (nonatomic, assign) NSArray *deltaX; +@property (nonatomic, assign) NSArray *deltaY; +@property (nonatomic, strong) NSString *positionX; +@property (nonatomic, strong) NSString *positionY; +@property (nonatomic, assign) NSDictionary *font; +@property (nonatomic, strong) NSString *content; + +@end diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m new file mode 100644 index 00000000..21281c91 --- /dev/null +++ b/ios/Text/RNSVGTSpan.m @@ -0,0 +1,104 @@ +/** + * 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. + */ + + +#import "RNSVGTSpan.h" +#import "RNSVGBezierPath.h" + +@class RNSVGText; +@implementation RNSVGTSpan + +- (CGPathRef)getPath:(CGContextRef)context +{ + [self setBoundingBox:CGContextGetClipBoundingBox(context)]; + CGMutablePathRef path = CGPathCreateMutable(); + +// if (![self.content isEqualToString:@""]) { +// // Create a dictionary for this font +// CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{ +// (NSString *)kCTFontAttributeName: (__bridge id)self.font, +// (NSString *)kCTForegroundColorFromContextAttributeName: @YES +// }; +// +// CFStringRef string = (__bridge CFStringRef)self.content; +// CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); +// CTLineRef line = CTLineCreateWithAttributedString(attrString); +// CFRelease(attrString); +// +// CGMutablePathRef linePath = [self setLinePath:line]; +// +// // Set up text frame with font metrics +// CGFloat size = CTFontGetSize(self.font); +// CGFloat px = self.px ? [self getWidthRelatedValue:self.px] : 0; +// CGFloat py = self.py ? [self getHeightRelatedValue:self.py] : 0; +// +// if (self.px) { +// text.offsetX = px; +// } +// +// if (self.py) { +// text.offsetY = py + size * 1.1; +// } +// +// text.offsetX += self.dx; +// text.offsetY += self.dy; +// +// CGAffineTransform offset = CGAffineTransformMakeTranslation(text.offsetX, text.offsetY); +// +// text.offsetX += CTLineGetTypographicBounds(line, nil, nil, nil); +// +// CGPathAddPath(path, &offset, linePath); +// CGPathRelease(linePath); +// } else { +// text.offsetX += self.dx; +// text.offsetY += self.dy; +// } + + return (CGPathRef)CFAutorelease(path); +} + +- (CGMutablePathRef)setLinePath:(CTLineRef)line +{ + CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0); + CGMutablePathRef path = CGPathCreateMutable(); + + CFArrayRef glyphRuns = CTLineGetGlyphRuns(line); + CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, 0); + + CFIndex runGlyphCount = CTRunGetGlyphCount(run); + CGPoint positions[runGlyphCount]; + CGGlyph glyphs[runGlyphCount]; + + // Grab the glyphs, positions, and font + CTRunGetPositions(run, CFRangeMake(0, 0), positions); + CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); + CFDictionaryRef attributes = CTRunGetAttributes(run); + + CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName); + + for(CFIndex i = 0; i < runGlyphCount; ++i) { + CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); + CGPoint point = positions[i]; + + if (letter) { + CGAffineTransform transform; + + transform = CGAffineTransformTranslate(upsideDown, point.x, point.y); + + + CGPathAddPath(path, &transform, letter); + } + + CGPathRelease(letter); + } + + return path; +} + + +@end diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index d1dc0e20..3e1b16c5 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -8,11 +8,17 @@ #import #import "RNSVGGroup.h" +#import "RNSVGTextAnchor.h" @interface RNSVGText : RNSVGGroup -@property (nonatomic, assign) CTTextAlignment alignment; -@property (nonatomic, copy) NSArray *path; +@property (nonatomic, assign) RNSVGTextAnchor textAnchor; +@property (nonatomic, assign) NSArray *deltaX; +@property (nonatomic, assign) NSArray *deltaY; +@property (nonatomic, strong) NSString *positionX; +@property (nonatomic, strong) NSString *positionY; +@property (nonatomic, assign) NSDictionary *font; + @property (nonatomic, assign) CGFloat offsetX; @property (nonatomic, assign) CGFloat offsetY; diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 8c3e12d9..dc18451f 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -8,49 +8,92 @@ #import "RNSVGText.h" #import "RNSVGBezierPath.h" +#import "RCTConvert+RNSVG.h" #import @implementation RNSVGText -- (void)setAlignment:(CTTextAlignment)alignment +- (void)setTextAnchor:(RNSVGTextAnchor)textAnchor { [self invalidate]; - _alignment = alignment; + _textAnchor = textAnchor; } -- (void)setPath:(NSArray *)path -{ - if (path == _path) { - return; - } - [self invalidate]; - _path = path; -} - -- (void)renderLayerTo:(CGContextRef)context -{ - CGFloat shift = [self getShift:context path:nil]; +//- (void)renderLayerTo:(CGContextRef)context +//{ + //CGFloat shift = [self getShift:context path:nil]; // Translate path by alignment offset - CGContextSaveGState(context); - CGContextConcatCTM(context, CGAffineTransformMakeTranslation(-shift, 0)); - [super renderLayerTo:context]; - CGContextRestoreGState(context); -} + //CGContextSaveGState(context); + //CGContextConcatCTM(context, CGAffineTransformMakeTranslation(-shift, 0)); + //[super renderLayerTo:context]; + //CGContextRestoreGState(context); +//} -- (CGPathRef)getPath:(CGContextRef)context +//- (CGPathRef)getPath:(CGContextRef)context +//{ +// CGMutablePathRef path = CGPathCreateMutable(); + +// CGPathRef collection = [self getPathFromSuper:context]; +// +// // get alignment shift and Translate CGPath by it. +// CGFloat shift = [self getShift:context path:collection]; +// CGAffineTransform align = CGAffineTransformMakeTranslation(shift, 0); +// CGPathAddPath(path, &align, collection); +// CGPathRelease(collection); + +// return (CGPathRef)CFAutorelease(path); +//} + +- (CGPathRef)getContentPath:(CGContextRef)context { - + [self setBoundingBox:CGContextGetClipBoundingBox(context)]; CGMutablePathRef path = CGPathCreateMutable(); - CGPathRef collection = [self getPathFromSuper:context]; - // get alignment shift and Translate CGPath by it. - CGFloat shift = [self getShift:context path:collection]; - CGAffineTransform align = CGAffineTransformMakeTranslation(shift, 0); - CGPathAddPath(path, &align, collection); - CGPathRelease(collection); +// if (![self.content isEqualToString:@""]) { +// CGFontRef *font = [RCTConvert RNSVGFont:self.font]; +// NSLog(@"font: %@", font); + // Create a dictionary for this font +// CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{ +// (NSString *)kCTFontAttributeName: (__bridge id)self.font, +// (NSString *)kCTForegroundColorFromContextAttributeName: @YES +// }; +// +// CFStringRef string = (__bridge CFStringRef)self.content; +// CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); +// CTLineRef line = CTLineCreateWithAttributedString(attrString); +// CFRelease(attrString); +// +// CGMutablePathRef linePath = [self setLinePath:line]; +// +// // Set up text frame with font metrics +// CGFloat size = CTFontGetSize(self.font); +// CGFloat px = self.px ? [self getWidthRelatedValue:self.px] : 0; +// CGFloat py = self.py ? [self getHeightRelatedValue:self.py] : 0; +// +// if (self.px) { +// text.offsetX = px; +// } +// +// if (self.py) { +// text.offsetY = py + size * 1.1; +// } +// +// text.offsetX += self.dx; +// text.offsetY += self.dy; +// +// CGAffineTransform offset = CGAffineTransformMakeTranslation(text.offsetX, text.offsetY); +// +// text.offsetX += CTLineGetTypographicBounds(line, nil, nil, nil); +// +// CGPathAddPath(path, &offset, linePath); +// CGPathRelease(linePath); +// } else { +// text.offsetX += self.dx; +// text.offsetY += self.dy; +// } + - - return (CGPathRef)CFAutorelease(path); + return path; } - (CGPathRef)getPathFromSuper:(CGContextRef)context @@ -61,27 +104,27 @@ return path; } -- (CGFloat)getShift:(CGContextRef)context path:(CGPathRef)path -{ - if (!path) { - path = [self getPathFromSuper:context]; - } - - CGFloat width = CGPathGetBoundingBox(path).size.width; - CGFloat shift; - switch (self.alignment) { - case kCTTextAlignmentRight: - shift = width; - break; - case kCTTextAlignmentCenter: - shift = width / 2; - break; - default: - shift = 0; - break; - } - - return shift; -} +//- (CGFloat)getShift:(CGContextRef)context path:(CGPathRef)path +//{ +// if (!path) { +// path = [self getPathFromSuper:context]; +// } +// +// CGFloat width = CGPathGetBoundingBox(path).size.width; +// CGFloat shift; +// switch (self.alignment) { +// case kCTTextAlignmentRight: +// shift = width; +// break; +// case kCTTextAlignmentCenter: +// shift = width / 2; +// break; +// default: +// shift = 0; +// break; +// } +// +// return shift; +//} @end diff --git a/ios/Text/RNSVGTextPath.h b/ios/Text/RNSVGTextPath.h new file mode 100644 index 00000000..293e1ab9 --- /dev/null +++ b/ios/Text/RNSVGTextPath.h @@ -0,0 +1,21 @@ +/** + * 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. + */ + +#import +#import +#import "RNSVGPath.h" +#import "RNSVGText.h" + +@interface RNSVGTextPath : RNSVGPath + +@property (nonatomic, strong) NSString *positionX; +@property (nonatomic, strong) NSString *positionY; +@property (nonatomic, assign) CTFontRef font; +@property (nonatomic, strong) NSString *content; + +@end diff --git a/ios/Text/RNSVGTextPath.m b/ios/Text/RNSVGTextPath.m new file mode 100644 index 00000000..87e81c26 --- /dev/null +++ b/ios/Text/RNSVGTextPath.m @@ -0,0 +1,23 @@ +/** + * 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. + */ + + +#import "RNSVGTextPath.h" +#import "RNSVGBezierPath.h" + +@class RNSVGText; +@implementation RNSVGTextPath + +- (CGPathRef)getPath:(CGContextRef)context +{ + CGMutablePathRef path = CGPathCreateMutable(); + + return (CGPathRef)CFAutorelease(path); +} + +@end diff --git a/ios/Utils/RCTConvert+RNSVG.h b/ios/Utils/RCTConvert+RNSVG.h index a8e284df..b3510344 100644 --- a/ios/Utils/RCTConvert+RNSVG.h +++ b/ios/Utils/RCTConvert+RNSVG.h @@ -12,14 +12,17 @@ #import "RNSVGCGFloatArray.h" #import "RCTConvert.h" #import "RNSVGCGFCRule.h" +#import "RNSVGVBMOS.h" +#import "RNSVGTextAnchor.h" @class RNSVGBrush; @interface RCTConvert (RNSVG) + (CGPathRef)CGPath:(id)json; -+ (CTTextAlignment)CTTextAlignment:(id)json; ++ (RNSVGTextAnchor)RNSVGTextAnchor:(id)json; + (RNSVGCGFCRule)RNSVGCGFCRule:(id)json; ++ (RNSVGVBMOS)RNSVGVBMOS:(id)json; + (CTFontRef)RNSVGFont:(id)json; + (RNSVGCGFloatArray)RNSVGCGFloatArray:(id)json; + (RNSVGBrush *)RNSVGBrush:(id)json; diff --git a/ios/Utils/RCTConvert+RNSVG.m b/ios/Utils/RCTConvert+RNSVG.m index 4e0bd45a..c17131b8 100644 --- a/ios/Utils/RCTConvert+RNSVG.m +++ b/ios/Utils/RCTConvert+RNSVG.m @@ -12,8 +12,6 @@ #import "RNSVGPattern.h" #import "RNSVGSolidColorBrush.h" #import "RCTLog.h" -#import "RNSVGCGFCRule.h" -#import "RNSVGVBMOS.h" #import "RCTFont.h" @implementation RCTConvert (RNSVG) @@ -65,14 +63,6 @@ return (CGPathRef)CFAutorelease(path); } -RCT_ENUM_CONVERTER(CTTextAlignment, (@{ - @"auto": @(kCTTextAlignmentNatural), - @"left": @(kCTTextAlignmentLeft), - @"center": @(kCTTextAlignmentCenter), - @"right": @(kCTTextAlignmentRight), - @"justify": @(kCTTextAlignmentJustified), - }), kCTTextAlignmentNatural, integerValue) - RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{ @"evenodd": @(kRNSVGCGFCRuleEvenodd), @"nonzero": @(kRNSVGCGFCRuleNonzero), @@ -84,16 +74,34 @@ RCT_ENUM_CONVERTER(RNSVGVBMOS, (@{ @"none": @(kRNSVGVBMOSNone) }), kRNSVGVBMOSMeet, intValue) +RCT_ENUM_CONVERTER(RNSVGTextAnchor, (@{ + @"auto": @(kRNSVGTextAnchorAuto), + @"start": @(kRNSVGTextAnchorStart), + @"middle": @(kRNSVGTextAnchorMiddle), + @"end": @(kRNSVGTextAnchorEnd) + }), kRNSVGTextAnchorAuto, intValue) + (CTFontRef)RNSVGFont:(id)json { NSDictionary *dict = [self NSDictionary:json]; NSString *fontFamily = dict[@"fontFamily"]; - if (![[UIFont familyNames] containsObject:fontFamily]) { - fontFamily = nil; + BOOL fontFound = NO; + NSArray *supportedFontFamilyNames = [UIFont familyNames]; + + if ([supportedFontFamilyNames containsObject:fontFamily]) { + fontFound = YES; + } else { + for (NSString *fontFamilyName in supportedFontFamilyNames) { + if ([[UIFont fontNamesForFamilyName: fontFamilyName] containsObject:fontFamily]) { + fontFound = YES; + break; + } + } } + fontFamily = fontFound ? fontFamily : nil; + return (__bridge CTFontRef)[RCTFont updateFont:nil withFamily:fontFamily size:dict[@"fontSize"] weight:dict[@"fontWeight"] style:dict[@"fontStyle"] variant:nil scaleMultiplier:1.0]; } diff --git a/ios/Utils/RNSVGTextAnchor.h b/ios/Utils/RNSVGTextAnchor.h new file mode 100644 index 00000000..06eded9d --- /dev/null +++ b/ios/Utils/RNSVGTextAnchor.h @@ -0,0 +1,14 @@ +/** + * 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. + */ + +typedef CF_ENUM(int32_t, RNSVGTextAnchor) { + kRNSVGTextAnchorAuto, + kRNSVGTextAnchorStart, + kRNSVGTextAnchorMiddle, + kRNSVGTextAnchorEnd +}; diff --git a/ios/ViewManagers/RNSVGImageManager.m b/ios/ViewManagers/RNSVGImageManager.m index 218d463d..8df08ae8 100644 --- a/ios/ViewManagers/RNSVGImageManager.m +++ b/ios/ViewManagers/RNSVGImageManager.m @@ -7,7 +7,7 @@ */ #import "RNSVGImageManager.h" - +#import "RNSVGVBMOS.h" #import "RNSVGImage.h" #import "RCTConvert+RNSVG.h" diff --git a/ios/ViewManagers/RNSVGTSpanManager.h b/ios/ViewManagers/RNSVGTSpanManager.h new file mode 100644 index 00000000..fa4c5f0d --- /dev/null +++ b/ios/ViewManagers/RNSVGTSpanManager.h @@ -0,0 +1,13 @@ +/** + * 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. + */ + +#import "RNSVGTextManager.h" + +@interface RNSVGTSpanManager : RNSVGTextManager + +@end diff --git a/ios/ViewManagers/RNSVGTSpanManager.m b/ios/ViewManagers/RNSVGTSpanManager.m new file mode 100644 index 00000000..d9ddc646 --- /dev/null +++ b/ios/ViewManagers/RNSVGTSpanManager.m @@ -0,0 +1,26 @@ +/** + * 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. + */ + +#import "RNSVGTSpanManager.h" + +#import "RNSVGTSpan.h" +#import "RNSVGTextAnchor.h" +#import "RCTConvert+RNSVG.h" + +@implementation RNSVGTSpanManager + +RCT_EXPORT_MODULE() + +- (RNSVGRenderable *)node +{ + return [RNSVGTSpan new]; +} + +RCT_EXPORT_VIEW_PROPERTY(content, NSString) + +@end diff --git a/ios/ViewManagers/RNSVGTextManager.m b/ios/ViewManagers/RNSVGTextManager.m index 9786ddc0..3cd1befc 100644 --- a/ios/ViewManagers/RNSVGTextManager.m +++ b/ios/ViewManagers/RNSVGTextManager.m @@ -20,7 +20,11 @@ RCT_EXPORT_MODULE() return [RNSVGText new]; } -RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment) -RCT_EXPORT_VIEW_PROPERTY(path, RNSVGBezier) +RCT_EXPORT_VIEW_PROPERTY(textAnchor, RNSVGTextAnchor) +RCT_EXPORT_VIEW_PROPERTY(deltaX, NSArray) +RCT_EXPORT_VIEW_PROPERTY(deltaY, NSArray) +RCT_EXPORT_VIEW_PROPERTY(px, NSString) +RCT_EXPORT_VIEW_PROPERTY(py, NSString) +RCT_EXPORT_VIEW_PROPERTY(font, NSDictionary) @end diff --git a/lib/attributes.js b/lib/attributes.js index 2be83ddb..f26b2e9c 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -92,21 +92,20 @@ const PathAttributes = merge({ } }, RenderableAttributes); -const SpanAttributes = merge({ +const TextAttributes = merge({ font: { diff: fontDiffer }, - content: true, - dx: true, - dy: true, - px: true, - py: true + textAnchor: true, + deltaX: arrayDiffer, + deltaY: arrayDiffer, + positionX: true, + positionY: true }, RenderableAttributes); - -const TextAttributes = merge({ - alignment: true -}, RenderableAttributes); +const TSpanAttibutes = merge({ + content: true +}, TextAttributes); const ClipPathAttributes = { name: true @@ -178,7 +177,7 @@ const RectAttributes = merge({ export { PathAttributes, TextAttributes, - SpanAttributes, + TSpanAttibutes, GroupAttributes, ClipPathAttributes, CircleAttributes, diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index b9c1cb9f..159f6cd1 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -2,18 +2,20 @@ import SerializablePath from '../SerializablePath'; import _ from 'lodash'; import React, {Children} from 'react'; import {fontAndRenderPropsKeys, fontPropsKeys} from '../props'; -import Span from '../../elements/Span'; +import TSpan from '../../elements/TSpan'; const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm%]*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i; const fontFamilyPrefix = /^[\s"']*/; const fontFamilySuffix = /[\s"']*$/; const spaceReg = /\s+/; +const deltaReg = /^((-?\d*(\.\d+)?)+\s*)+$/; const commaReg = /,/; const anchors = { - end: 1, + auto: 0, + start: 1, middle: 2, - start: 0 + end: 3 }; let cachedFontObjectsFromString = {}; @@ -67,161 +69,56 @@ function extractFont(props) { return _.defaults(ownedFont, font); } -function parseText(props, inheritedProps = {}, deltas = []) { - let { - children, - dx = '', - dy = '', +function parseDelta(delta) { + if (typeof delta === 'string' && deltaReg.test(delta)) { + return delta.trim().split(spaceReg).map(d => +d); + } else if (typeof delta === 'number') { + return [delta]; + } else { + []; + } +} + +export default function(props, isText) { + const { x, - y + y, + dx, + dy, + textAnchor } = props; - const spanArray = []; - const deltaXArray = parseDelta(dx); - const deltaYArray = parseDelta(dy); - const maxDeltaLength = Math.max(deltaXArray.length, deltaYArray.length); - - for (let i = 0; i < maxDeltaLength; i++) { - let result = {}; - - if (deltaXArray.length > i && deltaXArray[i]) { - result.x = deltaXArray[i]; - } else { - let inheritedDeltaX = _.get(deltas, `${i}.x`); - if (inheritedDeltaX) { - result.x = inheritedDeltaX; - } - } - - if (deltaYArray.length > i && deltaYArray[i]) { - result.y = deltaYArray[i]; - } else { - let inheritedDeltaY = _.get(deltas, `${i}.y`); - if (inheritedDeltaY) { - result.y = inheritedDeltaY; - } - } - - deltas[i] = result; - } + const deltaX = parseDelta(dx); + const deltaY = parseDelta(dy); + let { children } = props; + let content = null; if (typeof children === 'string') { - - let computedProps = _.reduce(inheritedProps, (prev, value, name) => { - if (!prev.hasOwnProperty(name)) { - prev[name] = value; - } - - return prev; - }, _.pick(props, fontAndRenderPropsKeys)); - - let delta = deltas.shift(); - - while (delta) { - let text; - if (deltas.length) { - text = children.slice(0, 1); - children = children.slice(1); + if (isText) { + children = {children}; + } else { + content = children; + children = null; + } + } else if (Children.count(children) > 1) { + children = Children.map(children, child => { + if (typeof child === 'string') { + return {child}; } else { - text = children; - } - - spanArray.push({ - content: text, - props: computedProps, - deltaX: +delta.x || 0, - deltaY: +delta.y || 0, - positionX: x || null, - positionY: y || null - }); - - x = y = null; - - if (!text) { - return spanArray; - } else { - delta = deltas.shift(); - } - }; - - } else { - inheritedProps = _.assign({}, inheritedProps); - fontAndRenderPropsKeys.forEach(inheritablePropName => { - if (props.hasOwnProperty(inheritablePropName)) { - inheritedProps[inheritablePropName] = props[inheritablePropName]; + return child; } }); - - Children.forEach(children, child => { - spanArray.push(...parseText(child.props, inheritedProps, deltas)); - }) } - - return spanArray; -} - -function parseDelta(delta) { - return delta.toString().trim().split(spaceReg); -} - -export default function(props) { - let frames = parseText(props); - let alignment; - - if (frames[0]) { - let firstSpan = frames[0]; - - if (firstSpan.positionX === null && props.hasOwnProperty('x')) { - firstSpan.positionX = props.x; - } - - if (firstSpan.positionY === null && props.hasOwnProperty('y')) { - firstSpan.positionY = props.y; - } - - if (firstSpan.props.hasOwnProperty('textAnchor')) { - alignment = anchors[firstSpan.props.textAnchor]; - } else if (anchors[props.textAnchor]) { - alignment = anchors[props.textAnchor]; - } - - if (!alignment) { - alignment = 0; - } - } - - let font = { - fontFamily: 'Helvetica Neue', - fontSize: 12, - fontStyle: 'normal', - fontWeight: 'normal', - ...extractFont(props) - } - - - let children = frames.map(frame => { - let spanProps = { - content: frame.content, - dx: frame.deltaX, - dy: frame.deltaY, - px: frame.positionX, - py: frame.positionY, - font: { - ...font, - ...extractFont(frame.props) - } - }; - - return ; - }); - return { - alignment, - children + textAnchor: anchors[textAnchor] || 0, + font: extractFont(props), + children, + content, + deltaX, + deltaY, + positionX: x || null, + positionY: y || null } }