From a37c3ceee6f624984e438c9fd7fc19d3e672929d Mon Sep 17 00:00:00 2001 From: Horcrux Date: Sat, 17 Sep 2016 15:42:27 +0800 Subject: [PATCH] finish basic text support on iOS --- elements/Span.js | 25 +++++-- ios/Elements/RNSVGGroup.h | 1 + ios/Elements/RNSVGGroup.m | 5 +- ios/Elements/RNSVGPath.m | 1 + ios/Text/RNSVGBezierPath.m | 1 + ios/Text/RNSVGSpan.h | 6 +- ios/Text/RNSVGSpan.m | 67 +++++++++++++++++- ios/Text/RNSVGText.h | 5 +- ios/Text/RNSVGText.m | 104 +++++++++++++--------------- ios/ViewManagers/RNSVGSpanManager.m | 1 + lib/extract/extractText.js | 9 ++- 11 files changed, 150 insertions(+), 75 deletions(-) diff --git a/elements/Span.js b/elements/Span.js index e9a0fd2c..6a427763 100644 --- a/elements/Span.js +++ b/elements/Span.js @@ -2,7 +2,7 @@ import React, {PropTypes} from 'react'; import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; import {SpanAttributes} from '../lib/attributes'; import Shape from './Shape'; -import {numberProp} from '../lib/props'; +import {pathProps, numberProp, fontProps} from '../lib/props'; // Span components are only for internal use for Text. @@ -10,15 +10,26 @@ class Span extends Shape { static displayName = 'Span'; static propTypes = { - content: PropTypes.string.isRequired, - dx: numberProp, - dy: numberProp, - px: numberProp, - py: numberProp + ...pathProps, + frame: PropTypes.shape({ + content: PropTypes.string.isRequired, + dx: numberProp, + dy: numberProp, + px: numberProp, + py: numberProp, + font: PropTypes.shape(fontProps) + }) }; render() { - return ; + return ; } } diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index d111da22..411db7f1 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -14,4 +14,5 @@ #import "RNSVGRenderable.h" @interface RNSVGGroup : RNSVGRenderable + @end diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index 4352eb22..492abf38 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -14,7 +14,6 @@ { RNSVGSvgView* svg = [self getSvgView]; [self clip:context]; - [self traverseSubviews:^(RNSVGNode *node) { if (node.responsible && !svg.responsible) { svg.responsible = YES; @@ -83,7 +82,7 @@ } -- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray *)mergeList +- (void)mergeProperties:(RNSVGNode *)target mergeList:(NSArray *)mergeList { [self traverseSubviews:^(RNSVGNode *node) { [node mergeProperties:target mergeList:mergeList]; @@ -99,7 +98,7 @@ }]; } -- (void)traverseSubviews:(BOOL (^)(RNSVGNode *node))block +- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGNode *node))block { for (RNSVGNode *node in self.subviews) { if ([node isKindOfClass:[RNSVGNode class]]) { diff --git a/ios/Elements/RNSVGPath.m b/ios/Elements/RNSVGPath.m index c71a4081..72d54e5c 100644 --- a/ios/Elements/RNSVGPath.m +++ b/ios/Elements/RNSVGPath.m @@ -30,6 +30,7 @@ { // todo: add detection if path has changed since last update. self.d = [self getPath:context]; + CGPathRef path = self.d; if ((!self.fill && !self.stroke) || !path) { return; diff --git a/ios/Text/RNSVGBezierPath.m b/ios/Text/RNSVGBezierPath.m index f416d140..0359a5ea 100644 --- a/ios/Text/RNSVGBezierPath.m +++ b/ios/Text/RNSVGBezierPath.m @@ -30,6 +30,7 @@ - (instancetype)initWithBezierCurves:(NSArray *)bezierCurves { + if (self = [super init]) { _bezierCurves = bezierCurves; _bezierIndex = 0; diff --git a/ios/Text/RNSVGSpan.h b/ios/Text/RNSVGSpan.h index a4866b82..acf5d7c4 100644 --- a/ios/Text/RNSVGSpan.h +++ b/ios/Text/RNSVGSpan.h @@ -9,14 +9,14 @@ #import #import #import "RNSVGPath.h" -#import "RNSVGTextFrame.h" @interface RNSVGSpan : RNSVGPath @property (nonatomic, assign) CGFloat *dx; @property (nonatomic, assign) CGFloat *dy; -@property (nonatomic, assign) NSString *px; -@property (nonatomic, assign) NSString *py; +@property (nonatomic, strong) NSString *px; +@property (nonatomic, strong) NSString *py; @property (nonatomic, assign) CTFontRef font; +@property (nonatomic, strong) NSString *content; @end diff --git a/ios/Text/RNSVGSpan.m b/ios/Text/RNSVGSpan.m index ed7a51ed..f18e22f6 100644 --- a/ios/Text/RNSVGSpan.m +++ b/ios/Text/RNSVGSpan.m @@ -6,17 +6,82 @@ * LICENSE file in the root directory of this source tree. */ + #import "RNSVGSpan.h" #import "RNSVGBezierPath.h" -#import @implementation RNSVGSpan - (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; + + CGAffineTransform offset = CGAffineTransformMakeTranslation(px, size + py); + + CGPathAddPath(path, &offset, linePath); + CGPathRelease(linePath); + } + 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 12827675..e70d5c11 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -7,10 +7,9 @@ */ #import -#import "RNSVGPath.h" -#import "RNSVGTextFrame.h" +#import "RNSVGGroup.h" -@interface RNSVGText : RNSVGRenderable +@interface RNSVGText : RNSVGGroup @property (nonatomic, assign) CTTextAlignment alignment; @property (nonatomic, copy) NSArray *path; diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index b48a2116..87a7671c 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -39,9 +39,56 @@ // RNSVGFreeTextFrame(_textFrame); //} +- (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); +} + - (CGPathRef)getPath:(CGContextRef)context { + CGMutablePathRef path = CGPathCreateMutable(); + CGPathRef collection = [super getPath:context]; + CGFloat shift = [self getShift:context path:collection]; + + CGAffineTransform align = CGAffineTransformMakeTranslation(shift, 0); + CGPathAddPath(path, &align, collection); + CGPathRelease(collection); + + return (CGPathRef)CFAutorelease(path); +} + +- (CGFloat)getShift:(CGContextRef)context path:(CGPathRef)path +{ + if (!path) { + path = [super getPath: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; +} + +//- (CGPathRef)getPath:(CGContextRef)context +//{ +// CGMutablePathRef path = CGPathCreateMutable(); // RNSVGTextFrame frame = self.textFrame; // for (int i = 0; i < frame.count; i++) { // CGFloat shift; @@ -66,59 +113,6 @@ // CGPathRelease(line); // } - 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); - - RNSVGBezierPath *bezierPath = [[RNSVGBezierPath alloc] initWithBezierCurves:self.path]; - - for(CFIndex i = 0; i < runGlyphCount; ++i) { - CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); - CGPoint point = positions[i]; - - if (letter) { - CGAffineTransform transform; - - // draw glyphs along path - if (self.path) { - transform = [bezierPath transformAtDistance:point.x]; - - // break loop if line reaches the end of the Path. - if (!transform.a || !transform.d) { - CGPathRelease(letter); - break; - } - transform = CGAffineTransformScale(transform, 1.0, -1.0); - } else { - transform = CGAffineTransformTranslate(upsideDown, point.x, point.y); - } - - - CGPathAddPath(path, &transform, letter); - } - - CGPathRelease(letter); - } - - return path; -} - +// return (CGPathRef)CFAutorelease(path); +//} @end diff --git a/ios/ViewManagers/RNSVGSpanManager.m b/ios/ViewManagers/RNSVGSpanManager.m index 94e0631c..f3fd9410 100644 --- a/ios/ViewManagers/RNSVGSpanManager.m +++ b/ios/ViewManagers/RNSVGSpanManager.m @@ -25,5 +25,6 @@ RCT_EXPORT_VIEW_PROPERTY(dy, CGFloat) RCT_EXPORT_VIEW_PROPERTY(px, NSString) RCT_EXPORT_VIEW_PROPERTY(py, NSString) RCT_REMAP_VIEW_PROPERTY(font, font, RNSVGFont) +RCT_EXPORT_VIEW_PROPERTY(content, NSString) @end diff --git a/lib/extract/extractText.js b/lib/extract/extractText.js index 266b180a..b71ae979 100644 --- a/lib/extract/extractText.js +++ b/lib/extract/extractText.js @@ -1,7 +1,7 @@ import SerializablePath from '../SerializablePath'; import _ from 'lodash'; import React, {Children} from 'react'; -import {fontAndRenderPropsKeys} from '../props'; +import {fontAndRenderPropsKeys, fontPropsKeys} from '../props'; import Span from '../../elements/Span'; const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm%]*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i; @@ -209,8 +209,11 @@ export default function(props) { ...extractFont(frame.props) } }; - - return ; + + return ; }); return {