Implement TextPath: first step (iOS)

This commit is contained in:
Horcrux 2017-01-09 20:16:20 +08:00
Родитель 1d06134719
Коммит 52dfe21074
18 изменённых файлов: 223 добавлений и 106 удалений

Просмотреть файл

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

Просмотреть файл

@ -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 <RNSVGTextPath
href={href}
{...this.extractProps({
...props,
x: null,
y: null
})}
{...extractText({children}, true)}
/>;
}
}
if (!href) {
console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"');
}
return <RNSVGTextPath
href={href}
{...extractText({children: props.children})}
/>;
console.warn('Invalid `href` prop for `TextPath` element, expected a href like `"#id"`, but got: "' + props.href + '"');
return <TSpan>{children}</TSpan>
}
}
const RNSVGTextPath = createReactNativeComponentClass({

Просмотреть файл

@ -15,7 +15,7 @@
@interface RNSVGGroup : RNSVGPath <RNSVGContainer>
- (void)pathRenderLayerTo:(CGContextRef)contex;
- (void)renderLayerToWithTransform:(CGContextRef)context transform:(CGAffineTransform)transform;
- (void)renderPathTo:(CGContextRef)context;
- (void)renderGroupTo:(CGContextRef)context;
@end

Просмотреть файл

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

Просмотреть файл

@ -7,11 +7,13 @@
*/
#import <Foundation/Foundation.h>
#import "RNSVGPathParser.h"
#import "RNSVGRenderable.h"
@interface RNSVGPath : RNSVGRenderable
@property (nonatomic, assign) CGPathRef d;
@property (nonatomic, strong) RNSVGPathParser *d;
- (NSArray *)getBezierCurves;
@end

Просмотреть файл

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

Просмотреть файл

@ -8,7 +8,6 @@
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import "RNSVGPath.h"
#import "RNSVGText.h"
@interface RNSVGTSpan : RNSVGText

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -7,7 +7,7 @@
*/
#import "RNSVGText.h"
#import "RNSVGBezierPath.h"
#import "RNSVGTextPath.h"
#import <React/RCTFont.h>
#import <CoreText/CoreText.h>
@ -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

Просмотреть файл

@ -8,11 +8,13 @@
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import "RNSVGPath.h"
#import "RNSVGText.h"
#import "RNSVGBezierPath.h"
@interface RNSVGTextPath : RNSVGText
@property (nonatomic, strong) NSString *href;
- (RNSVGBezierPath *)getBezierPath;
@end

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -15,13 +15,12 @@
#import "RNSVGCGFCRule.h"
#import "RNSVGVBMOS.h"
#import <React/RCTFont.h>
#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, (@{

Просмотреть файл

@ -6,12 +6,13 @@
* LICENSE file in the root directory of this source tree.
*/
#import <QuartzCore/QuartzCore.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface RNSVGPathParser : NSObject
- (instancetype) initWithPathString:(NSString *)d;
- (CGPathRef)getPath;
- (NSArray *)getBezierCurves;
@end

Просмотреть файл

@ -14,6 +14,8 @@
NSString* _d;
NSString* _originD;
NSRegularExpression* _pathRegularExpression;
NSMutableArray<NSArray *>* _bezierCurves;
NSValue *_lastStartPoint;
double _penX;
double _penY;
double _penDownX;
@ -39,8 +41,9 @@
{
CGMutablePathRef path = CGPathCreateMutable();
NSArray<NSTextCheckingResult *>* 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: @[]];
}
}

Просмотреть файл

@ -98,9 +98,9 @@ const TextAttributes = merge({
positionY: true
}, RenderableAttributes);
const TextPathAttributes = {
const TextPathAttributes = merge({
href: true
};
}, RenderableAttributes);
const TSpanAttibutes = merge({
content: true

Просмотреть файл

@ -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 = <TSpan>{children}</TSpan>;
} else {
content = children;