refactor text render

Support G inherit props.
Refactor text render. Text glyphs will perfectly draw along the path
This commit is contained in:
Horcrux
2016-07-22 23:49:15 +08:00
parent 63f793c54e
commit 7e13b801e1
41 changed files with 467 additions and 477 deletions

View File

@@ -29,8 +29,8 @@ export {
Text, Text,
Stroking, Stroking,
G, G,
//Use, Use,
//Symbol, Symbol,
Gradients, Gradients,
Clipping, Clipping,
Image, Image,

View File

@@ -68,6 +68,7 @@ class GTransform extends Component{
<G <G
rotate="50" rotate="50"
origin="100, 50" origin="100, 50"
scale="0.75"
id="group" id="group"
> >
<Line <Line
@@ -96,7 +97,7 @@ class GTransform extends Component{
> >
Text grouped with shapes</Text> Text grouped with shapes</Text>
</G> </G>
<Use href="url(#group)" x="5" rotate="0" /> <Use href="url(#group)" x="5" y="20" rotate="-50" stroke="red" />
</Svg>; </Svg>;
} }
} }
@@ -106,30 +107,35 @@ const icon = <Svg
width="20" width="20"
> >
<G <G
r="3"
fill="purple" fill="purple"
stroke="pink" stroke="pink"
strokeWidth="1"
> >
<Circle <Circle
cx="5" cx="5"
cy="5" cy="5"
r="3"
/> />
<Circle <Circle
cx="5" cx="5"
cy="15" cy="15"
r="3"
/> />
<Circle <Circle
cx="10" cx="10"
cy="10" cy="10"
fill="green" fill="green"
r="3"
/> />
<Circle <Circle
cx="15" cx="15"
cy="5" cy="5"
r="3"
/> />
<Circle <Circle
cx="15" cx="15"
cy="15" cy="15"
r="3"
/> />
</G> </G>
</Svg>; </Svg>;

View File

@@ -19,7 +19,7 @@ class StrokeExample extends Component{
static title = 'The stroke property defines the color of a line, text or outline of an element'; static title = 'The stroke property defines the color of a line, text or outline of an element';
render() { render() {
return <Svg height="80" width="225"> return <Svg height="80" width="225">
<G fill="none"> <G>
<Path stroke="red" d="M5 20 l215 0" /> <Path stroke="red" d="M5 20 l215 0" />
<Path stroke="black" d="M5 40 l215 0" /> <Path stroke="black" d="M5 40 l215 0" />
<Path stroke="blue" d="M5 60 l215 0" /> <Path stroke="blue" d="M5 60 l215 0" />
@@ -32,10 +32,12 @@ class StrokeLinecap extends Component{
static title = 'The strokeLinecap property defines different types of endings to an open path'; static title = 'The strokeLinecap property defines different types of endings to an open path';
render() { render() {
return <Svg height="80" width="225"> return <Svg height="80" width="225">
<G fill="none" stroke="black"> <G stroke="red">
<Path strokeLinecap="butt" strokeWidth="8" d="M5 20 l215 0" /> <G strokeWidth="8">
<Path strokeLinecap="round" strokeWidth="8" d="M5 40 l215 0" /> <Path strokeLinecap="butt" d="M5 20 l215 0" />
<Path strokeLinecap="square" strokeWidth="8" d="M5 60 l215 0" /> <Path strokeLinecap="round" d="M5 40 l215 0" />
<Path strokeLinecap="square" d="M5 60 l215 0" />
</G>
</G> </G>
</Svg>; </Svg>;
} }
@@ -142,7 +144,6 @@ const icon = <Svg
</Svg>; </Svg>;
const samples = [StrokeExample, StrokeLinecap, StrokeDasharray, StrokeDashoffset, StrokePattern]; const samples = [StrokeExample, StrokeLinecap, StrokeDasharray, StrokeDashoffset, StrokePattern];
export { export {
icon, icon,
samples samples

View File

@@ -21,19 +21,19 @@ class SymbolExample extends Component{
</Symbol> </Symbol>
<Use <Use
href="#symbol" href="url(#symbol)"
x="0" x="0"
y="0" y="0"
/> />
<Use <Use
href="#symbol" href="url(#symbol)"
x="0" x="0"
y="50" y="50"
width="75" width="75"
height="38" height="38"
/> />
<Use <Use
href="#symbol" href="url(#symbol)"
x="0" x="0"
y="100" y="100"
width="50" width="50"
@@ -53,14 +53,14 @@ const icon = <Svg
</Symbol> </Symbol>
<Use <Use
href="#symbol" href="url(#symbol)"
x="0" x="0"
y="0" y="0"
width="20" width="20"
height="10" height="10"
/> />
<Use <Use
href="#symbol" href="url(#symbol)"
x="0" x="0"
y="12" y="12"
width="20" width="20"

View File

@@ -6,7 +6,9 @@ import Svg, {
Text, Text,
LinearGradient, LinearGradient,
Stop, Stop,
Defs Defs,
Path,
G
} from 'react-native-svg'; } from 'react-native-svg';
class TextExample extends Component{ class TextExample extends Component{
@@ -119,22 +121,33 @@ class TextPath extends Component{
static title = 'Draw text along path'; static title = 'Draw text along path';
render() { render() {
return <Svg const path = `
height="60" M 0 20
width="200" h 10
>
<Text
fill="red"
path={`
M 10 20
C 20 10 30 0 40 10 C 20 10 30 0 40 10
C 50 20 60 30 70 20 C 50 20 60 30 70 20
C 80 10 90 10 90 10 C 80 10 90 10 90 10
C 110 20 120 30 120 20 C 110 20 120 30 120 20
C 140 10 150 10 150 10 C 140 10 150 10 150 10
`} a 50 50 0 1 1 20 110
y="20" `;
return <Svg
height="60"
width="200"
>
<G y="20">
<Text
fill="blue"
path={path}
>We go up, then we go down, then up again</Text> >We go up, then we go down, then up again</Text>
<Path
d={path}
fill="none"
stroke="red"
strokeWidth="1"
/>
</G>
</Svg>; </Svg>;
} }
} }

View File

@@ -27,8 +27,8 @@ class UseExample extends Component{
</G> </G>
</G> </G>
</Defs> </Defs>
<Use href="#shape" x="20" y="0"/> <Use href="url(#shape)" x="20" y="0"/>
<Use href="#shape" x="170"y="0" /> <Use href="url(#shape)" x="170"y="0" />
</Svg>; </Svg>;
} }
} }
@@ -43,9 +43,9 @@ class UseShapes extends Component{
<G id="shape"> <G id="shape">
<Rect x="0" y="0" width="50" height="50" /> <Rect x="0" y="0" width="50" height="50" />
</G> </G>
<Use href="#shape" x="75" y="50" fill="#0f0"/> <Use href="url(#shape)" x="75" y="50" fill="#0f0"/>
<Use href="#shape" x="110" y="0" stroke="#0ff" fill="#8a3" rotation="45" origin="25, 25"/> <Use href="url(#shape)" x="110" y="0" stroke="#0ff" fill="#8a3" rotation="45" origin="25, 25"/>
<Use href="#shape" x="150" y="50" stroke="#0f0" fill="none"/> <Use href="url(#shape)" x="150" y="50" stroke="#0f0" fill="none"/>
</Svg>; </Svg>;
} }
} }
@@ -60,7 +60,7 @@ const icon = <Svg
stroke="#8a3" stroke="#8a3"
id="line" id="line"
/> />
<Use href="#line" x="10" stroke="#3a8" /> <Use href="url(#line)" x="10" stroke="#3a8" />
</Svg>; </Svg>;
const samples = [UseExample, UseShapes]; const samples = [UseExample, UseShapes];

View File

@@ -49,7 +49,6 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
protected @Nullable Path mClipPath; protected @Nullable Path mClipPath;
protected @Nullable String mClipPathRef; protected @Nullable String mClipPathRef;
private static final int PATH_TYPE_ARC = 4;
private static final int PATH_TYPE_CLOSE = 1; private static final int PATH_TYPE_CLOSE = 1;
private static final int PATH_TYPE_CURVETO = 3; private static final int PATH_TYPE_CURVETO = 3;
private static final int PATH_TYPE_LINETO = 2; private static final int PATH_TYPE_LINETO = 2;
@@ -233,30 +232,6 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
data[i++] * mScale, data[i++] * mScale,
data[i++] * mScale); data[i++] * mScale);
break; break;
case PATH_TYPE_ARC:
{
float x = data[i++] * mScale;
float y = data[i++] * mScale;
float r = data[i++] * mScale;
float start = (float) Math.toDegrees(data[i++]);
float end = (float) Math.toDegrees(data[i++]);
boolean clockwise = data[i++] == 1f;
float sweep = end - start;
if (Math.abs(sweep) > 360) {
sweep = 360;
} else {
sweep = modulus(sweep, 360);
}
if (!clockwise && sweep < 360) {
start = end;
sweep = 360 - sweep;
}
RectF oval = new RectF(x - r, y - r, x + r, y + r);
path.addArc(oval, start, sweep);
break;
}
default: default:
throw new JSApplicationIllegalArgumentException( throw new JSApplicationIllegalArgumentException(
"Unrecognized drawing instruction " + type); "Unrecognized drawing instruction " + type);

View File

@@ -4,7 +4,6 @@ import createReactNativeComponentClass from 'react/lib/createReactNativeComponen
import {transformProps} from '../lib/props'; import {transformProps} from '../lib/props';
import {GroupAttributes} from '../lib/attributes'; import {GroupAttributes} from '../lib/attributes';
import extractProps from '../lib/extract/extractProps'; import extractProps from '../lib/extract/extractProps';
import reusableProps from '../lib/reusableProps';
class G extends Component{ class G extends Component{
static displayName = 'G'; static displayName = 'G';
@@ -27,7 +26,6 @@ class G extends Component{
return <RNSVGGroup return <RNSVGGroup
{...extractedProps} {...extractedProps}
ref={ele => this.root = ele} ref={ele => this.root = ele}
mergeList={reusableProps(extractedProps, props)}
> >
{this.props.children} {this.props.children}
</RNSVGGroup>; </RNSVGGroup>;

View File

@@ -1,7 +1,8 @@
import React, {Component, PropTypes} from 'react'; import React, {Component, PropTypes} from 'react';
import ViewBox from './ViewBox'; import ViewBox from './ViewBox';
import Defs from './Defs'; import G from './G';
class SymbolElement extends Component{ class SymbolElement extends Component{
static displayName = 'Symbol'; static displayName = 'Symbol';
static propType = { static propType = {
@@ -9,10 +10,8 @@ class SymbolElement extends Component{
}; };
render() { render() {
let {props} = this; let {props} = this;
return <Defs.Item
id={props.id} return <G id={props.id}>
svgId={props.svgId}
>
<ViewBox <ViewBox
{...props} {...props}
viewbox={props.viewbox} viewbox={props.viewbox}
@@ -20,7 +19,7 @@ class SymbolElement extends Component{
> >
{props.children} {props.children}
</ViewBox> </ViewBox>
</Defs.Item>; </G>;
} }
} }

View File

@@ -5,7 +5,6 @@ import Shape from './Shape';
import React from 'react'; import React from 'react';
import patternReg from '../lib/extract/patternReg'; import patternReg from '../lib/extract/patternReg';
import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass'; import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass';
import reusableProps from '../lib/reusableProps';
import _ from 'lodash'; import _ from 'lodash';
class Defs extends Shape { class Defs extends Shape {
@@ -44,7 +43,6 @@ class Defs extends Shape {
return <RNSVGUse return <RNSVGUse
ref={ele => this.root = ele} ref={ele => this.root = ele}
{...extractedProps} {...extractedProps}
mergeList={reusableProps(extractedProps, props)}
href={href} href={href}
>{props.children}</RNSVGUse>; >{props.children}</RNSVGUse>;
} }

View File

@@ -18,6 +18,8 @@ class ViewBox extends Component{
y = viewbox.y; y = viewbox.y;
} }
console.log(viewbox);
return <G return <G
{...this.props} {...this.props}
x={x} x={x}
@@ -26,6 +28,7 @@ class ViewBox extends Component{
scaleY={scaleY} scaleY={scaleY}
preserveAspectRatio={null} preserveAspectRatio={null}
viewbox={null} viewbox={null}
id={null}
> >
{(!scaleX || !scaleY) ? null : this.props.children} {(!scaleX || !scaleY) ? null : this.props.children}
</G>; </G>;

View File

@@ -11,11 +11,9 @@
#import "RNSVGContainer.h" #import "RNSVGContainer.h"
#import "RNSVGCGFCRule.h" #import "RNSVGCGFCRule.h"
#import "RNSVGSvgView.h" #import "RNSVGSvgView.h"
#import "RNSVGNode.h" #import "RNSVGRenderable.h"
@interface RNSVGGroup : RNSVGNode <RNSVGContainer> @interface RNSVGGroup : RNSVGRenderable <RNSVGContainer>
@property (nonatomic, copy) NSArray<NSString *> *mergeList;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

View File

@@ -17,7 +17,7 @@
[self clip:context]; [self clip:context];
for (RNSVGNode *node in self.subviews) { for (RNSVGNode *node in self.subviews) {
//[node mergeProperties:self mergeList:self.mergeList]; [node inheritProperties:self inheritedList:self.propList];
[node renderTo:context]; [node renderTo:context];
if (node.responsible && !svg.responsible) { if (node.responsible && !svg.responsible) {
@@ -79,4 +79,11 @@
} }
} }
- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray<NSString *> *)inheritedList;
{
for (NSString *key in inheritedList) {
[self inheritProperty:parent propName:key];
}
}
@end @end

View File

@@ -66,6 +66,7 @@
} }
} }
} }
if (self.stroke) { if (self.stroke) {
CGContextSetLineWidth(context, self.strokeWidth); CGContextSetLineWidth(context, self.strokeWidth);
CGContextSetLineCap(context, self.strokeLinecap); CGContextSetLineCap(context, self.strokeLinecap);

View File

@@ -15,6 +15,5 @@
@interface RNSVGUse : RNSVGRenderable @interface RNSVGUse : RNSVGRenderable
@property (nonatomic, strong) NSString *href; @property (nonatomic, strong) NSString *href;
@property (nonatomic, copy) NSArray<NSString *> *mergeList;
@end @end

View File

@@ -10,22 +10,24 @@
@implementation RNSVGUse @implementation RNSVGUse
- (void)setMergeList:(NSArray<NSString *> *)mergeList - (void)setHref:(NSString *)href
{ {
if (mergeList == _mergeList) { if (href == _href) {
return; return;
} }
_mergeList = mergeList;
[self invalidate]; [self invalidate];
_href = href;
} }
- (void)renderLayerTo:(CGContextRef)context - (void)renderLayerTo:(CGContextRef)context
{ {
RNSVGNode* template = [[self getSvgView] getDefinedTemplate:self.href]; RNSVGNode* template = [[self getSvgView] getDefinedTemplate:self.href];
if (template) { if (template) {
[self beginTransparencyLayer:context]; [self beginTransparencyLayer:context];
[self clip:context]; [self clip:context];
[template mergeProperties:self mergeList:self.mergeList]; [template mergeProperties:self mergeList:self.propList];
[template renderTo:context]; [template renderTo:context];
[template resetProperties]; [template resetProperties];
[self endTransparencyLayer:context]; [self endTransparencyLayer:context];

View File

@@ -15,12 +15,12 @@
1023B4901D3DF4C40051496D /* RNSVGDefination.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48F1D3DF4C40051496D /* RNSVGDefination.m */; }; 1023B4901D3DF4C40051496D /* RNSVGDefination.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48F1D3DF4C40051496D /* RNSVGDefination.m */; };
1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4921D3DF5060051496D /* RNSVGUse.m */; }; 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4921D3DF5060051496D /* RNSVGUse.m */; };
1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */; }; 1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */; };
103371321D41C5C90028AF13 /* RNSVGBezierPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 103371311D41C5C90028AF13 /* RNSVGBezierPath.m */; };
1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */; }; 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */; };
1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; }; 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; };
1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2861CE71EB7001E90A8 /* RNSVGPath.m */; }; 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2861CE71EB7001E90A8 /* RNSVGPath.m */; };
1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2881CE71EB7001E90A8 /* RNSVGSvgView.m */; }; 1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2881CE71EB7001E90A8 /* RNSVGSvgView.m */; };
1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2901CE71EC2001E90A8 /* RNSVGText.m */; }; 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2901CE71EC2001E90A8 /* RNSVGText.m */; };
1039D2961CE71EC2001E90A8 /* UIBezierPath-Points.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2931CE71EC2001E90A8 /* UIBezierPath-Points.m */; };
1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.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 */; }; 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2AF1CE72F27001E90A8 /* RNSVGPercentageConverter.m */; };
10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */; }; 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D1D1CE74E3100887C2B /* RNSVGCircleManager.m */; };
@@ -79,6 +79,8 @@
1023B4921D3DF5060051496D /* RNSVGUse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGUse.m; path = Elements/RNSVGUse.m; sourceTree = "<group>"; }; 1023B4921D3DF5060051496D /* RNSVGUse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGUse.m; path = Elements/RNSVGUse.m; sourceTree = "<group>"; };
1023B4941D3DF57D0051496D /* RNSVGUseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGUseManager.h; sourceTree = "<group>"; }; 1023B4941D3DF57D0051496D /* RNSVGUseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGUseManager.h; sourceTree = "<group>"; };
1023B4951D3DF57D0051496D /* RNSVGUseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGUseManager.m; sourceTree = "<group>"; }; 1023B4951D3DF57D0051496D /* RNSVGUseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGUseManager.m; sourceTree = "<group>"; };
103371311D41C5C90028AF13 /* RNSVGBezierPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGBezierPath.m; path = Text/RNSVGBezierPath.m; sourceTree = "<group>"; };
103371331D41D3400028AF13 /* RNSVGBezierPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGBezierPath.h; path = Text/RNSVGBezierPath.h; sourceTree = "<group>"; };
1039D2811CE71EB7001E90A8 /* RNSVGGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGGroup.h; path = Elements/RNSVGGroup.h; sourceTree = "<group>"; }; 1039D2811CE71EB7001E90A8 /* RNSVGGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGGroup.h; path = Elements/RNSVGGroup.h; sourceTree = "<group>"; };
1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGroup.m; path = Elements/RNSVGGroup.m; sourceTree = "<group>"; }; 1039D2821CE71EB7001E90A8 /* RNSVGGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGroup.m; path = Elements/RNSVGGroup.m; sourceTree = "<group>"; };
1039D2831CE71EB7001E90A8 /* RNSVGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGImage.h; path = Elements/RNSVGImage.h; sourceTree = "<group>"; }; 1039D2831CE71EB7001E90A8 /* RNSVGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGImage.h; path = Elements/RNSVGImage.h; sourceTree = "<group>"; };
@@ -90,8 +92,6 @@
1039D28F1CE71EC2001E90A8 /* RNSVGText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGText.h; path = Text/RNSVGText.h; sourceTree = "<group>"; }; 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGText.h; path = Text/RNSVGText.h; sourceTree = "<group>"; };
1039D2901CE71EC2001E90A8 /* RNSVGText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGText.m; path = Text/RNSVGText.m; sourceTree = "<group>"; }; 1039D2901CE71EC2001E90A8 /* RNSVGText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGText.m; path = Text/RNSVGText.m; sourceTree = "<group>"; };
1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextFrame.h; path = Text/RNSVGTextFrame.h; sourceTree = "<group>"; }; 1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGTextFrame.h; path = Text/RNSVGTextFrame.h; sourceTree = "<group>"; };
1039D2921CE71EC2001E90A8 /* UIBezierPath-Points.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath-Points.h"; path = "Text/UIBezierPath-Points.h"; sourceTree = "<group>"; };
1039D2931CE71EC2001E90A8 /* UIBezierPath-Points.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath-Points.m"; path = "Text/UIBezierPath-Points.m"; sourceTree = "<group>"; };
1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTConvert+RNSVG.h"; path = "Utils/RCTConvert+RNSVG.h"; sourceTree = "<group>"; }; 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTConvert+RNSVG.h"; path = "Utils/RCTConvert+RNSVG.h"; sourceTree = "<group>"; };
1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+RNSVG.m"; path = "Utils/RCTConvert+RNSVG.m"; sourceTree = "<group>"; }; 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+RNSVG.m"; path = "Utils/RCTConvert+RNSVG.m"; sourceTree = "<group>"; };
1039D29D1CE72177001E90A8 /* RNSVGCGFCRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGCGFCRule.h; path = Utils/RNSVGCGFCRule.h; sourceTree = "<group>"; }; 1039D29D1CE72177001E90A8 /* RNSVGCGFCRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSVGCGFCRule.h; path = Utils/RNSVGCGFCRule.h; sourceTree = "<group>"; };
@@ -262,11 +262,11 @@
1039D27F1CE71D9B001E90A8 /* Text */ = { 1039D27F1CE71D9B001E90A8 /* Text */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
103371331D41D3400028AF13 /* RNSVGBezierPath.h */,
103371311D41C5C90028AF13 /* RNSVGBezierPath.m */,
1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, 1039D28F1CE71EC2001E90A8 /* RNSVGText.h */,
1039D2901CE71EC2001E90A8 /* RNSVGText.m */, 1039D2901CE71EC2001E90A8 /* RNSVGText.m */,
1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */, 1039D2911CE71EC2001E90A8 /* RNSVGTextFrame.h */,
1039D2921CE71EC2001E90A8 /* UIBezierPath-Points.h */,
1039D2931CE71EC2001E90A8 /* UIBezierPath-Points.m */,
); );
name = Text; name = Text;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -386,7 +386,7 @@
10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */, 10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */,
0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColorBrush.m in Sources */, 0CF68B0F1AF0549300FF9E5C /* RNSVGSolidColorBrush.m in Sources */,
10BA0D3A1CE74E3100887C2B /* RNSVGPathManager.m in Sources */, 10BA0D3A1CE74E3100887C2B /* RNSVGPathManager.m in Sources */,
1039D2961CE71EC2001E90A8 /* UIBezierPath-Points.m in Sources */, 103371321D41C5C90028AF13 /* RNSVGBezierPath.m in Sources */,
10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */, 10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */,
10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */, 10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */,
10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */, 10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */,

View File

@@ -67,15 +67,22 @@
- (void)removeDefination; - (void)removeDefination;
/** /**
* Just for template node to merge target node`s properties into owned properties * just for template node to merge target node`s properties into owned properties
*/ */
- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList; - (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList;
/** /**
* Just for template node to reset all owned properties once after rendered. * just for template node to reset all owned properties once after rendered.
*/ */
- (void)resetProperties; - (void)resetProperties;
/**
* inherit properties from parent g element
*/
- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray<NSString *> *)inheritedList;
- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName;
- (void)beginTransparencyLayer:(CGContextRef)context; - (void)beginTransparencyLayer:(CGContextRef)context;
- (void)endTransparencyLayer:(CGContextRef)context; - (void)endTransparencyLayer:(CGContextRef)context;

View File

@@ -211,6 +211,16 @@
// abstract // abstract
} }
- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName
{
// abstract
}
- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray<NSString *> *)inheritedList;
{
// abstract
}
- (void)dealloc - (void)dealloc
{ {
CGPathRelease(_clipPath); CGPathRelease(_clipPath);

View File

@@ -27,6 +27,7 @@
@property (nonatomic, assign) RNSVGCGFloatArray strokeDasharray; @property (nonatomic, assign) RNSVGCGFloatArray strokeDasharray;
@property (nonatomic, assign) CGFloat strokeDashoffset; @property (nonatomic, assign) CGFloat strokeDashoffset;
@property (nonatomic, assign) CGMutablePathRef hitArea; @property (nonatomic, assign) CGMutablePathRef hitArea;
@property (nonatomic, copy) NSArray<NSString *> *propList;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

View File

@@ -88,6 +88,15 @@
_strokeMiterlimit = strokeMiterlimit; _strokeMiterlimit = strokeMiterlimit;
} }
- (void)setPropList:(NSArray<NSString *> *)propList
{
if (propList == _propList) {
return;
}
_propList = propList;
[self invalidate];
}
- (void)dealloc - (void)dealloc
{ {
CGPathRelease(_hitArea); CGPathRelease(_hitArea);
@@ -131,17 +140,28 @@
- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList - (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList
{ {
[self mergeProperties:target mergeList:mergeList inherited:NO];
}
- (void)mergeProperties:(__kindof RNSVGNode *)target mergeList:(NSArray<NSString *> *)mergeList inherited:(BOOL)inherited
{
if (mergeList.count == 0) { if (mergeList.count == 0) {
return; return;
} }
if (!inherited) {
originProperties = [[NSMutableDictionary alloc] init]; originProperties = [[NSMutableDictionary alloc] init];
changedList = mergeList; changedList = mergeList;
}
for (NSString *key in mergeList) { for (NSString *key in mergeList) {
if (inherited) {
[self inheritProperty:target propName:key];
} else {
[originProperties setValue:[self valueForKey:key] forKey:key]; [originProperties setValue:[self valueForKey:key] forKey:key];
[self setValue:[target valueForKey:key] forKey:key]; [self setValue:[target valueForKey:key] forKey:key];
} }
}
} }
- (void)resetProperties - (void)resetProperties
@@ -155,6 +175,23 @@
changedList = nil; changedList = nil;
} }
- (void)inheritProperty:(__kindof RNSVGNode *)parent propName:(NSString *)propName
{
if (![self.propList containsObject:propName]) {
// add prop to propList
NSMutableArray *copy = [self.propList mutableCopy];
[copy addObject:propName];
self.propList = [copy copy];
[self setValue:[parent valueForKey:propName] forKey:propName];
}
}
- (void)inheritProperties:(__kindof RNSVGNode *)parent inheritedList:(NSArray<NSString *> *)inheritedList
{
[self mergeProperties:parent mergeList:inheritedList inherited:YES];
}
- (void)renderLayerTo:(CGContextRef)context - (void)renderLayerTo:(CGContextRef)context
{ {
// abstract // abstract

View File

@@ -0,0 +1,17 @@
/**
* 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 <UIKit/UIKit.h>
@interface RNSVGBezierPath : NSObject
- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves;
- (CGAffineTransform)transformAtDistance:(CGFloat)distance;
@end

147
ios/Text/RNSVGBezierPath.m Normal file
View File

@@ -0,0 +1,147 @@
/**
* 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.
*/
/**
* based on
*/
#import "RNSVGBezierPath.h"
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
@implementation RNSVGBezierPath
{
NSArray<NSArray *> *_bezierCurves;
NSUInteger _bezierIndex;
CGFloat _offset;
CGFloat _lastX;
CGFloat _lastDistance;
CGPoint _lastPoint;
CGPoint _P0;
CGPoint _P1;
CGPoint _P2;
CGPoint _P3;
}
- (instancetype)initWithBezierCurves:(NSArray *)bezierCurves
{
if (self = [super init]) {
_bezierCurves = bezierCurves;
_bezierIndex = 0;
_offset = 0;
_lastX = 0;
_lastDistance = 0;
}
return self;
}
- (instancetype)initWithControlPoints:(CGPoint)P0 P1:(CGPoint)P1 P2:(CGPoint)P2 P3:(CGPoint)P3
{
if (self = [super init]) {
_P0 = P0;
_P1 = P1;
_P2 = P2;
_P3 = P3;
}
return self;
}
static CGFloat Bezier(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);
return CGPointMake(x, y);
}
static CGFloat BezierPrime(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);
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);
}
// Simplistic routine to find the offset along Bezier that is
// `distance` away from `point`. `offset` is the offset used to
// generate `point`, and saves us the trouble of recalculating it
// This routine just walks forward until it finds a point at least
// `distance` away. Good optimizations here would reduce the number
// of guesses, but this is tricky since if we go too far out, the
// curve might loop back on leading to incorrect results. Tuning
// kStep is good start.
- (CGFloat)offsetAtDistance:(CGFloat)distance
fromPoint:(CGPoint)point
offset:(CGFloat)offset {
const CGFloat kStep = 0.0005; // 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]);
}
_lastDistance = newDistance;
return newOffset;
}
- (void)setControlPoints
{
NSArray *bezier = [_bezierCurves objectAtIndex:_bezierIndex];
_bezierIndex++;
if (bezier.count == 1) {
_lastPoint = _P0 = [(NSValue *)[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];
}
}
- (CGAffineTransform)transformAtDistance:(CGFloat)distance
{
if (_offset == 0) {
[self setControlPoints];
}
CGFloat offset = [self offsetAtDistance:distance - _lastX
fromPoint:_lastPoint
offset:_offset];
CGPoint glyphPoint = [self pointForOffset:offset];
CGFloat angle = [self angleForOffset:offset];
if (offset < 1) {
_offset = offset;
_lastPoint = glyphPoint;
} else {
_offset = 0;
_lastPoint = _P0 = _P3;
_lastX += _lastDistance;
return [self transformAtDistance:distance];
}
_lastX = distance;
CGAffineTransform transform = CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y);
transform = CGAffineTransformRotate(transform, angle);
return transform;
}
@end

View File

@@ -7,7 +7,6 @@
*/ */
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "UIBezierPath-Points.h"
#import "RNSVGPath.h" #import "RNSVGPath.h"
#import "RNSVGTextFrame.h" #import "RNSVGTextFrame.h"
@@ -15,6 +14,6 @@
@property (nonatomic, assign) CTTextAlignment alignment; @property (nonatomic, assign) CTTextAlignment alignment;
@property (nonatomic, assign) RNSVGTextFrame textFrame; @property (nonatomic, assign) RNSVGTextFrame textFrame;
@property (nonatomic, assign) CGPathRef path; @property (nonatomic, copy) NSArray<NSArray *> *path;
@end @end

View File

@@ -7,7 +7,7 @@
*/ */
#import "RNSVGText.h" #import "RNSVGText.h"
#import "RNSVGBezierPath.h"
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
@implementation RNSVGText @implementation RNSVGText
@@ -39,19 +39,17 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
_textFrame = frame; _textFrame = frame;
} }
- (void)setPath:(CGPathRef)path - (void)setPath:(NSArray *)path
{ {
if (path == _path) { if (path == _path) {
return; return;
} }
[self invalidate]; [self invalidate];
CGPathRelease(_path); _path = path;
_path = CGPathRetain(path);
} }
- (void)dealloc - (void)dealloc
{ {
CGPathRelease(_path);
RNSVGFreeTextFrame(_textFrame); RNSVGFreeTextFrame(_textFrame);
} }
@@ -95,14 +93,10 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
{ {
CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0); CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
CTLineGetGlyphRuns(line);
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
CFIndex runCount = CFArrayGetCount(glyphRuns);
CFIndex glyphIndex = 0;
for(CFIndex i = 0; i < runCount; ++i) { CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
// For each run, we need to get the glyphs, their font (to get the path) and their locations. CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, 0);
CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, i);
CFIndex runGlyphCount = CTRunGetGlyphCount(run); CFIndex runGlyphCount = CTRunGetGlyphCount(run);
CGPoint positions[runGlyphCount]; CGPoint positions[runGlyphCount];
CGGlyph glyphs[runGlyphCount]; CGGlyph glyphs[runGlyphCount];
@@ -113,31 +107,18 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
CFDictionaryRef attributes = CTRunGetAttributes(run); CFDictionaryRef attributes = CTRunGetAttributes(run);
CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName); CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName);
for(CFIndex j = 0; j < runGlyphCount; ++j, ++glyphIndex) { RNSVGBezierPath *bezierPath = [[RNSVGBezierPath alloc] initWithBezierCurves:self.path];
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[j], nil);
CGPoint point = positions[j]; for(CFIndex i = 0; i < runGlyphCount; ++i) {
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil);
CGPoint point = positions[i];
if (letter) { if (letter) {
CGAffineTransform transform; CGAffineTransform transform;
// draw glyphs along path // draw glyphs along path
if (self.path) { if (self.path) {
CGPoint slope; transform = [bezierPath transformAtDistance:point.x];
CGRect bounding = CGPathGetBoundingBox(letter);
UIBezierPath* pathAlong = [UIBezierPath bezierPathWithCGPath:self.path];
CGFloat percentConsumed = (point.x + bounding.size.width) / pathAlong.length;
if (percentConsumed >= 1.0f) {
CGPathRelease(letter);
continue;
}
CGPoint targetPoint = [pathAlong pointAtPercent:percentConsumed withSlope: &slope];
float angle = atan(slope.y / slope.x); // + M_PI;
if (slope.x < 0) {
angle += M_PI; // going left, update the angle
}
transform = CGAffineTransformMakeTranslation(targetPoint.x - bounding.size.width, targetPoint.y);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0); transform = CGAffineTransformScale(transform, 1.0, -1.0);
} else { } else {
transform = CGAffineTransformTranslate(upsideDown, point.x, point.y); transform = CGAffineTransformTranslate(upsideDown, point.x, point.y);
@@ -148,7 +129,6 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
CGPathRelease(letter); CGPathRelease(letter);
} }
}
return path; return path;
} }

View File

@@ -1,27 +0,0 @@
/**
* 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.
*/
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface UIBezierPath (Points)
@property (nonatomic, readonly) NSArray *points;
@property (nonatomic, readonly) NSArray *bezierElements;
@property (nonatomic, readonly) CGFloat length;
- (NSArray *) pointPercentArray;
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope;
+ (UIBezierPath *) pathWithPoints: (NSArray *) points;
+ (UIBezierPath *) pathWithElements: (NSArray *) elements;
@end

View File

@@ -1,219 +0,0 @@
/**
* 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.
*/
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import "UIBezierPath-Points.h"
#define POINTSTRING(_CGPOINT_) (NSStringFromCGPoint(_CGPOINT_))
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]
#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue]
// Return distance between two points
static float distance (CGPoint p1, CGPoint p2)
{
float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
return sqrt(dx*dx + dy*dy);
}
@implementation UIBezierPath (Points)
void getPointsFromBezier(void *info, const CGPathElement *element)
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
CGPathElementType type = element->type;
CGPoint *points = element->points;
if (type != kCGPathElementCloseSubpath)
{
if ((type == kCGPathElementAddLineToPoint) ||
(type == kCGPathElementMoveToPoint))
[bezierPoints addObject:VALUE(0)];
else if (type == kCGPathElementAddQuadCurveToPoint)
[bezierPoints addObject:VALUE(1)];
else if (type == kCGPathElementAddCurveToPoint)
[bezierPoints addObject:VALUE(2)];
}
}
- (NSArray *)points
{
NSMutableArray *points = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)points, getPointsFromBezier);
return points;
}
// Return a Bezier path buit with the supplied points
+ (UIBezierPath *) pathWithPoints: (NSArray *) points
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (points.count == 0) return path;
[path moveToPoint:POINT(0)];
for (int i = 1; i < points.count; i++)
[path addLineToPoint:POINT(i)];
return path;
}
- (CGFloat) length
{
NSArray *points = self.points;
float totalPointLength = 0.0f;
for (int i = 1; i < points.count; i++)
totalPointLength += distance(POINT(i), POINT(i-1));
return totalPointLength;
}
- (NSArray *) pointPercentArray
{
// Use total length to calculate the percent of path consumed at each control point
NSArray *points = self.points;
NSUInteger pointCount = points.count;
float totalPointLength = self.length;
float distanceTravelled = 0.0f;
NSMutableArray *pointPercentArray = [NSMutableArray array];
[pointPercentArray addObject:@(0.0)];
for (int i = 1; i < pointCount; i++)
{
distanceTravelled += distance(POINT(i), POINT(i-1));
[pointPercentArray addObject:@(distanceTravelled / totalPointLength)];
}
// Add a final item just to stop with. Probably not needed.
[pointPercentArray addObject:[NSNumber numberWithFloat:1.1f]]; // 110%
return pointPercentArray;
}
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope
{
NSArray *points = self.points;
NSArray *percentArray = self.pointPercentArray;
CFIndex lastPointIndex = points.count - 1;
if (!points.count) {
return CGPointZero;
}
// Check for 0% and 100%
if (percent <= 0.0f) {
return POINT(0);
}
if (percent >= 1.0f) {
return POINT(lastPointIndex);
}
// Find a corresponding pair of points in the path
CFIndex index = 1;
while ((index < percentArray.count) &&
(percent > ((NSNumber *)percentArray[index]).floatValue)) {
index++;
}
// This should not happen.
if (index > lastPointIndex) {
return POINT(lastPointIndex);
}
// Calculate the intermediate distance between the two points
CGPoint point1 = POINT(index -1);
CGPoint point2 = POINT(index);
float percent1 = [[percentArray objectAtIndex:index - 1] floatValue];
float percent2 = [[percentArray objectAtIndex:index] floatValue];
float percentOffset = (percent - percent1) / (percent2 - percent1);
float dx = point2.x - point1.x;
float dy = point2.y - point1.y;
// Store dy, dx for retrieving arctan
if (slope) {
*slope = CGPointMake(dx, dy);
}
// Calculate new point
CGFloat newX = point1.x + (percentOffset * dx);
CGFloat newY = point1.y + (percentOffset * dy);
CGPoint targetPoint = CGPointMake(newX, newY);
return targetPoint;
}
void getBezierElements(void *info, const CGPathElement *element)
{
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
CGPathElementType type = element->type;
CGPoint *points = element->points;
switch (type)
{
case kCGPathElementCloseSubpath:
[bezierElements addObject:@[@(type)]];
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
[bezierElements addObject:@[@(type), VALUE(0)]];
break;
case kCGPathElementAddQuadCurveToPoint:
[bezierElements addObject:@[@(type), VALUE(0), VALUE(1)]];
break;
case kCGPathElementAddCurveToPoint:
[bezierElements addObject:@[@(type), VALUE(0), VALUE(1), VALUE(2)]];
break;
}
}
- (NSArray *) bezierElements
{
NSMutableArray *elements = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)elements, getBezierElements);
return elements;
}
+ (UIBezierPath *) pathWithElements: (NSArray *) elements
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (elements.count == 0) return path;
for (NSArray *points in elements)
{
if (!points.count) continue;
CGPathElementType elementType = [points[0] integerValue];
switch (elementType)
{
case kCGPathElementCloseSubpath:
[path closePath];
break;
case kCGPathElementMoveToPoint:
if (points.count == 2)
[path moveToPoint:POINT(1)];
break;
case kCGPathElementAddLineToPoint:
if (points.count == 2)
[path addLineToPoint:POINT(1)];
break;
case kCGPathElementAddQuadCurveToPoint:
if (points.count == 3)
[path addQuadCurveToPoint:POINT(2) controlPoint:POINT(1)];
break;
case kCGPathElementAddCurveToPoint:
if (points.count == 4)
[path addCurveToPoint:POINT(3) controlPoint1:POINT(1) controlPoint2:POINT(2)];
break;
}
}
return path;
}
@end

View File

@@ -24,6 +24,7 @@
+ (RNSVGCGFloatArray)RNSVGCGFloatArray:(id)json; + (RNSVGCGFloatArray)RNSVGCGFloatArray:(id)json;
+ (RNSVGBrush *)RNSVGBrush:(id)json; + (RNSVGBrush *)RNSVGBrush:(id)json;
+ (NSArray *)RNSVGBezier:(id)json;
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset; + (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset;
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset; + (CGRect)CGRect:(id)json offset:(NSUInteger)offset;
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset; + (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset;

View File

@@ -44,9 +44,6 @@
case 3: case 3:
CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE); CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE);
break; break;
case 4:
CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
break;
default: default:
RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr); RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr);
CGPathRelease(path); CGPathRelease(path);
@@ -163,6 +160,63 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{
} }
} }
+ (NSArray *)RNSVGBezier:(id)json
{
NSArray *arr = [self NSNumberArray:json];
NSMutableArray<NSArray *> *beziers = [[NSMutableArray alloc] init];
NSUInteger count = [arr count];
#define NEXT_VALUE [self double:arr[i++]]
@try {
NSValue *startPoint = [NSValue valueWithCGPoint: CGPointMake(0, 0)];
NSUInteger i = 0;
while (i < count) {
NSUInteger type = [arr[i++] unsignedIntegerValue];
switch (type) {
case 0:
{
startPoint = [NSValue valueWithCGPoint: CGPointMake(NEXT_VALUE, NEXT_VALUE)];
[beziers addObject: @[startPoint]];
break;
}
case 1:
[beziers addObject: @[]];
break;
case 2:
{
double x = NEXT_VALUE;
double y = NEXT_VALUE;
NSValue * destination = [NSValue valueWithCGPoint:CGPointMake(x, y)];
[beziers addObject: @[
destination,
startPoint,
destination
]];
break;
}
case 3:
[beziers addObject: @[
[NSValue valueWithCGPoint:CGPointMake(NEXT_VALUE, NEXT_VALUE)],
[NSValue valueWithCGPoint:CGPointMake(NEXT_VALUE, NEXT_VALUE)],
[NSValue valueWithCGPoint:CGPointMake(NEXT_VALUE, NEXT_VALUE)],
]];
break;
default:
RCTLogError(@"Invalid RNSVGBezier type %zd at element %zd of %@", type, i, arr);
return NULL;
}
}
}
@catch (NSException *exception) {
RCTLogError(@"Invalid RNSVGBezier format: %@", arr);
return NULL;
}
return beziers;
}
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset + (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
{ {
NSArray *arr = [self NSArray:json]; NSArray *arr = [self NSArray:json];

View File

@@ -19,6 +19,4 @@ RCT_EXPORT_MODULE()
return [RNSVGGroup new]; return [RNSVGGroup new];
} }
RCT_EXPORT_VIEW_PROPERTY(mergeList, NSArray<NSString *>)
@end @end

View File

@@ -31,5 +31,6 @@ RCT_EXPORT_VIEW_PROPERTY(strokeLinejoin, CGLineJoin)
RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, RNSVGCGFloatArray) RCT_EXPORT_VIEW_PROPERTY(strokeDasharray, RNSVGCGFloatArray)
RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat) RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat) RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(propList, NSArray<NSString *>)
@end @end

View File

@@ -22,6 +22,6 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment) RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, RNSVGTextFrame) RCT_REMAP_VIEW_PROPERTY(frame, textFrame, RNSVGTextFrame)
RCT_EXPORT_VIEW_PROPERTY(path, CGPath) RCT_EXPORT_VIEW_PROPERTY(path, RNSVGBezier)
@end @end

View File

@@ -19,6 +19,5 @@ RCT_EXPORT_MODULE()
} }
RCT_EXPORT_VIEW_PROPERTY(href, NSString) RCT_EXPORT_VIEW_PROPERTY(href, NSString)
RCT_EXPORT_VIEW_PROPERTY(mergeList, NSArray<NSString *>)
@end @end

View File

@@ -38,7 +38,7 @@ export default class SerializablePath {
case 's': this.curve(p[i++], p[i++], null, null, p[i++], p[i++]); break; case 's': this.curve(p[i++], p[i++], null, null, p[i++], p[i++]); break;
case 'q': this.curve(p[i++], p[i++], p[i++], p[i++]); break; case 'q': this.curve(p[i++], p[i++], p[i++], p[i++]); break;
case 't': this.curve(p[i++], p[i++]); break; case 't': this.curve(p[i++], p[i++]); break;
case 'a': this.arc(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], p[i + 2]); i += 7; break; case 'a': this.arc(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], +p[i + 2]); i += 7; break;
case 'h': this.line(p[i++], 0); break; case 'h': this.line(p[i++], 0); break;
case 'v': this.line(0, p[i++]); break; case 'v': this.line(0, p[i++]); break;
@@ -48,7 +48,7 @@ export default class SerializablePath {
case 'S': this.curveTo(p[i++], p[i++], null, null, p[i++], p[i++]); break; case 'S': this.curveTo(p[i++], p[i++], null, null, p[i++], p[i++]); break;
case 'Q': this.curveTo(p[i++], p[i++], p[i++], p[i++]); break; case 'Q': this.curveTo(p[i++], p[i++], p[i++], p[i++]); break;
case 'T': this.curveTo(p[i++], p[i++]); break; case 'T': this.curveTo(p[i++], p[i++]); break;
case 'A': this.arcTo(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], p[i + 2]); i += 7; break; case 'A': this.arcTo(p[i + 5], p[i + 6], p[i], p[i + 1], p[i + 3], !+p[i + 4], +p[i + 2]); i += 7; break;
case 'H': this.lineTo(p[i++], this.penY); break; case 'H': this.lineTo(p[i++], this.penY); break;
case 'V': this.lineTo(this.penX, p[i++]); break; case 'V': this.lineTo(this.penX, p[i++]); break;
@@ -276,12 +276,9 @@ export default class SerializablePath {
}; };
onArc = (sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) => { onArc = (sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) => {
if (rx !== ry || rotation) {
return this._arcToBezier( return this._arcToBezier(
sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation
); );
}
this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1);
}; };
onClose = () => { onClose = () => {

View File

@@ -52,6 +52,9 @@ const NodeAttributes = {
clipPath: { clipPath: {
diff: arrayDiffer diff: arrayDiffer
}, },
propList: {
diff: arrayDiffer
},
responsible: true responsible: true
}; };
@@ -77,16 +80,9 @@ const RenderableOnlyAttributes = {
const RenderableAttributes = merge({}, NodeAttributes, RenderableOnlyAttributes); const RenderableAttributes = merge({}, NodeAttributes, RenderableOnlyAttributes);
const GroupAttributes = merge({ const GroupAttributes = RenderableAttributes;
mergeList: {
diff: arrayDiffer
}
}, NodeAttributes);
const UseAttributes = merge({ const UseAttributes = merge({
mergeList: {
diff: arrayDiffer
},
href: true href: true
}, RenderableAttributes); }, RenderableAttributes);

View File

@@ -3,14 +3,12 @@ import _ from 'lodash';
import patternReg from './patternReg'; import patternReg from './patternReg';
export default function (colorOrBrush) { export default function (colorOrBrush) {
if (colorOrBrush === 'none') { if (colorOrBrush === 'none' || _.isNil(colorOrBrush)) {
return null; return null;
} else if (_.isNil(colorOrBrush)) {
colorOrBrush = '#000';
} }
try {
let matched = colorOrBrush.match(patternReg); let matched = colorOrBrush.match(patternReg);
// brush // brush
if (matched) { if (matched) {
return [1, matched[1]]; return [1, matched[1]];
@@ -19,4 +17,8 @@ export default function (colorOrBrush) {
let c = new Color(colorOrBrush).rgbaArray(); let c = new Color(colorOrBrush).rgbaArray();
return [0, c[0] / 255, c[1] / 255, c[2] / 255, c[3]]; return [0, c[0] / 255, c[1] / 255, c[2] / 255, c[3]];
} }
} catch(err) {
console.warn(`"${colorOrBrush}" is not a valid color or brush`);
return null;
}
} }

View File

@@ -1,5 +1,6 @@
import extractBrush from './extractBrush'; import extractBrush from './extractBrush';
import extractOpacity from './extractOpacity'; import extractOpacity from './extractOpacity';
import _ from 'lodash';
const fillRules = { const fillRules = {
evenodd: 0, evenodd: 0,
@@ -8,7 +9,8 @@ const fillRules = {
export default function(props) { export default function(props) {
return { return {
fill: extractBrush(props.fill), // default fill is black
fill: extractBrush(_.isNil(props.fill) ? '#000' : props.fill),
fillOpacity: extractOpacity(props.fillOpacity), fillOpacity: extractOpacity(props.fillOpacity),
fillRule: fillRules[props.fillRule] === 0 ? 0 : 1 fillRule: fillRules[props.fillRule] === 0 ? 0 : 1
}; };

View File

@@ -4,11 +4,29 @@ import extractTransform from './extractTransform';
import extractClipping from './extractClipping'; import extractClipping from './extractClipping';
import extractResponder from './extractResponder'; import extractResponder from './extractResponder';
import extractOpacity from './extractOpacity'; import extractOpacity from './extractOpacity';
import {RenderableOnlyAttributes} from '../attributes';
import _ from 'lodash'; import _ from 'lodash';
export default function(props, options = {stroke: true, transform: true, fill: true, responder: true}) { export default function(props, options = {stroke: true, transform: true, fill: true, responder: true}) {
let propList = [];
Object.keys(RenderableOnlyAttributes).forEach(name => {
if (!_.isNil(props[name])) {
// clipPath prop may provide `clipPathRef` as native prop
if (name === 'clipPath') {
if (extractedProps[name]) {
propList.push(name);
} else if (extractedProps.clipPathRef) {
propList.push('clipPathRef');
}
} else {
propList.push(name);
}
}
});
let extractedProps = { let extractedProps = {
opacity: extractOpacity(props.opacity) opacity: extractOpacity(props.opacity),
propList
}; };
if (props.id) { if (props.id) {

View File

@@ -17,16 +17,11 @@ const joins = {
export default function(props) { export default function(props) {
let {stroke} = props; let {stroke} = props;
if (!stroke) {
return null;
}
let strokeWidth = +props.strokeWidth; let strokeWidth = +props.strokeWidth;
if (_.isNil(props.strokeWidth)) { if (_.isNil(props.strokeWidth)) {
strokeWidth = 1; strokeWidth = null;
} else if (!strokeWidth) {
return;
} }
let strokeDasharray = props.strokeDasharray; let strokeDasharray = props.strokeDasharray;
@@ -40,10 +35,6 @@ export default function(props) {
strokeDasharray.push(strokeDasharray[0]); strokeDasharray.push(strokeDasharray[0]);
} }
if (!stroke) {
stroke = '#000';
}
return { return {
stroke: extractBrush(stroke), stroke: extractBrush(stroke),
strokeOpacity: extractOpacity(props.strokeOpacity), strokeOpacity: extractOpacity(props.strokeOpacity),

View File

@@ -1,4 +1,4 @@
import SerializablePath from 'react-native/Libraries/ART/ARTSerializablePath'; import SerializablePath from '../SerializablePath';
import _ from 'lodash'; import _ from 'lodash';
const newLine = /\n/g; const newLine = /\n/g;
const defaultFontFamily = '"Helvetica Neue", "Helvetica", Arial'; const defaultFontFamily = '"Helvetica Neue", "Helvetica", Arial';
@@ -82,6 +82,7 @@ const anchord = {
}; };
export default function(props) { export default function(props) {
return { return {
alignment: anchord[props.textAnchor] || 0, alignment: anchord[props.textAnchor] || 0,
frame: extractFontAndLines( frame: extractFontAndLines(

View File

@@ -1,22 +0,0 @@
import {RenderableOnlyAttributes} from '../lib/attributes';
import _ from 'lodash';
export default function (extractedProps, originProps) {
let reusableProps = [];
Object.keys(RenderableOnlyAttributes).forEach(name => {
if (!_.isNil(originProps[name])) {
// clipPath prop may provide `clipPathRef` as native prop
if (name === 'clipPath') {
if (extractedProps[name]) {
reusableProps.push(name);
} else if (extractedProps.clipPathRef) {
reusableProps.push('clipPathRef');
}
} else {
reusableProps.push(name);
}
}
});
return reusableProps;
}