mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-19 21:45:10 +00:00
support stroke in pattern.
support stroke="url(#pattern)"
This commit is contained in:
@@ -185,7 +185,7 @@ class FillGradientWithOpacity extends Component{
|
||||
}
|
||||
|
||||
class FillGradientInRect extends Component{
|
||||
static title = 'Fill a radial gradient inside a rect';
|
||||
static title = 'Fill a radial gradient inside a rect and stroke it';
|
||||
render() {
|
||||
return <Svg
|
||||
height="150"
|
||||
@@ -205,7 +205,7 @@ class FillGradientInRect extends Component{
|
||||
/>
|
||||
</RadialGradient>
|
||||
</Defs>
|
||||
<Rect x="0" y="0" width="300" height="150" fill="url(#grad)" />
|
||||
<Rect x="5" y="5" width="290" height="130" fill="url(#grad)" stroke="pink" strokeWidth="5" />
|
||||
</Svg>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class RectExample extends Component{
|
||||
fill="rgb(0,0,255)"
|
||||
strokeWidth="3"
|
||||
stroke="rgb(0,0,0)"
|
||||
strokeDasharray="5,10"
|
||||
/>
|
||||
</Svg>;
|
||||
}
|
||||
@@ -39,10 +40,10 @@ class RectStrokeFill extends Component{
|
||||
width="75"
|
||||
height="75"
|
||||
fill="blue"
|
||||
fillOpacity="0.1"
|
||||
stroke="pink"
|
||||
fillOpacity="0.5"
|
||||
stroke="red"
|
||||
strokeWidth="5"
|
||||
strokeOpacity="0.9"
|
||||
strokeOpacity="0.5"
|
||||
/>
|
||||
</Svg>;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ import React, {
|
||||
|
||||
import Svg, {
|
||||
Path,
|
||||
G
|
||||
Rect,
|
||||
G,
|
||||
Defs,
|
||||
Stop,
|
||||
RadialGradient,
|
||||
Polyline
|
||||
} from 'react-native-svg';
|
||||
|
||||
class StrokeExample extends Component{
|
||||
@@ -59,6 +64,48 @@ class StrokeDasharray extends Component{
|
||||
}
|
||||
}
|
||||
|
||||
class StrokePattern extends Component{
|
||||
static title = 'Stroke with gradient.';
|
||||
render() {
|
||||
return <Svg height="80" width="200">
|
||||
<Defs>
|
||||
<RadialGradient id="grad" cx="50%" cy="50%" rx="80%" ry="80%" fx="50%" fy="50%">
|
||||
<Stop
|
||||
offset="50%"
|
||||
stopColor="#fff"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
<Stop
|
||||
offset="100%"
|
||||
stopColor="#f00"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
</RadialGradient>
|
||||
</Defs>
|
||||
<Rect
|
||||
x="5"
|
||||
y="5"
|
||||
height="70"
|
||||
width="190"
|
||||
fill="blue"
|
||||
stroke="url(#grad)"
|
||||
strokeWidth="5"
|
||||
strokeDasharray="10"
|
||||
/>
|
||||
|
||||
<Polyline
|
||||
strokeDasharray="20,10,5,5,5,10"
|
||||
points="10,10 20,12 30,20 40,60 60,70 90,55"
|
||||
fill="none"
|
||||
stroke="url(#grad)"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="5"
|
||||
/>
|
||||
</Svg>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const icon = <Svg
|
||||
height="20"
|
||||
width="20"
|
||||
@@ -70,7 +117,7 @@ const icon = <Svg
|
||||
</G>
|
||||
</Svg>;
|
||||
|
||||
const samples = [StrokeExample, StrokeWidth, StrokeLinecap, StrokeDasharray];
|
||||
const samples = [StrokeExample, StrokeWidth, StrokeLinecap, StrokeDasharray, StrokePattern];
|
||||
|
||||
export {
|
||||
icon,
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, {
|
||||
Children
|
||||
} from 'react-native';
|
||||
import {NativeGroup} from './G';
|
||||
import {set, remove} from '../lib/extract/extractFill';
|
||||
import {set, remove} from '../lib/extract/patterns';
|
||||
import percentFactory from '../lib/percentFactory';
|
||||
import percentToFloat from '../lib/percentToFloat';
|
||||
import Stop from './Stop';
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
*/
|
||||
- (BOOL)applyFillColor:(CGContextRef)context;
|
||||
|
||||
- (BOOL)applyStrokeColor:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* paint fills the context with a brush. The context is assumed to
|
||||
* be clipped.
|
||||
|
||||
@@ -24,6 +24,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)applyStrokeColor:(CGContextRef)context
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
|
||||
@@ -35,4 +35,10 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)applyStrokeColor:(CGContextRef)context
|
||||
{
|
||||
CGContextSetStrokeColorWithColor(context, _color);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -70,12 +70,12 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
@"center": @(kCTTextAlignmentCenter),
|
||||
@"right": @(kCTTextAlignmentRight),
|
||||
@"justify": @(kCTTextAlignmentJustified),
|
||||
}), kCTTextAlignmentNatural, integerValue)
|
||||
}), kCTTextAlignmentNatural, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{
|
||||
@"evenodd": @(kRNSVGCGFCRuleEvenodd),
|
||||
@"nonzero": @(kRNSVGCGFCRuleNonzero),
|
||||
}), kRNSVGCGFCRuleNonzero, intValue)
|
||||
}), kRNSVGCGFCRuleNonzero, intValue)
|
||||
|
||||
// This takes a tuple of text lines and a font to generate a CTLine for each text line.
|
||||
// This prepares everything for rendering a frame of text in RNSVGText.
|
||||
@@ -150,6 +150,7 @@ RCT_ENUM_CONVERTER(RNSVGCGFCRule, (@{
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
NSUInteger type = [self NSUInteger:arr.firstObject];
|
||||
|
||||
switch (type) {
|
||||
case 0: // solid color
|
||||
// These are probably expensive allocations since it's often the same value.
|
||||
|
||||
@@ -32,10 +32,13 @@
|
||||
}
|
||||
|
||||
CGPathDrawingMode mode = kCGPathStroke;
|
||||
BOOL fillColor = YES;
|
||||
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = self.fillRule == kRNSVGCGFCRuleEvenodd ? kCGPathEOFill : kCGPathFill;
|
||||
} else {
|
||||
fillColor = [self.fill applyFillColor:context];
|
||||
|
||||
if (!fillColor) {
|
||||
if (self.clipPath) {
|
||||
[self clip:context];
|
||||
}
|
||||
@@ -51,23 +54,46 @@
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeLinecap);
|
||||
CGContextSetLineJoin(context, self.strokeLinejoin);
|
||||
RNSVGCGFloatArray dash = self.strokeDash;
|
||||
|
||||
// TODO: render as web svgs do
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
|
||||
if (!fillColor) {
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextReplacePathWithStrokedPath(context);
|
||||
CGContextClip(context);
|
||||
}
|
||||
|
||||
if ([self.stroke applyStrokeColor:context]) {
|
||||
|
||||
if (mode == kCGPathFill) {
|
||||
mode = kCGPathFillStroke;
|
||||
} else if (mode == kCGPathEOFill) {
|
||||
mode = kCGPathEOFillStroke;
|
||||
}
|
||||
} else {
|
||||
// draw fill
|
||||
[self clip:context];
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextDrawPath(context, mode);
|
||||
|
||||
// draw stroke
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextReplacePathWithStrokedPath(context);
|
||||
CGContextClip(context);
|
||||
[self.stroke paint:context];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self clip:context];
|
||||
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextDrawPath(context, mode);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
@property (nonatomic, strong) RNSVGBrush *fill;
|
||||
@property (nonatomic, assign) RNSVGCGFCRule fillRule;
|
||||
@property (nonatomic, assign) CGColorRef stroke;
|
||||
@property (nonatomic, strong) RNSVGBrush *stroke;
|
||||
@property (nonatomic, assign) CGFloat strokeWidth;
|
||||
@property (nonatomic, assign) CGLineCap strokeLinecap;
|
||||
@property (nonatomic, assign) CGLineJoin strokeLinejoin;
|
||||
|
||||
@@ -16,14 +16,10 @@
|
||||
_fill = fill;
|
||||
}
|
||||
|
||||
- (void)setStroke:(CGColorRef)stroke
|
||||
- (void)setStroke:(RNSVGBrush *)stroke
|
||||
{
|
||||
if (stroke == _stroke) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGColorRelease(_stroke);
|
||||
_stroke = CGColorRetain(stroke);
|
||||
_stroke = stroke;
|
||||
}
|
||||
|
||||
- (void)setStrokeWidth:(CGFloat)strokeWidth
|
||||
@@ -58,7 +54,6 @@
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_stroke);
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
if ([self.stroke applyStrokeColor:context] == NO) {
|
||||
[self.stroke paint:context];
|
||||
}
|
||||
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeLinecap);
|
||||
CGContextSetLineJoin(context, self.strokeLinejoin);
|
||||
|
||||
@@ -20,12 +20,12 @@ RCT_EXPORT_MODULE()
|
||||
return [RNSVGRenderable new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(fill, RNSVGBrush)
|
||||
RCT_EXPORT_VIEW_PROPERTY(fillRule, RNSVGCGFCRule)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stroke, RNSVGBrush)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeLinecap, CGLineCap)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeLinejoin, CGLineJoin)
|
||||
RCT_EXPORT_VIEW_PROPERTY(fill, RNSVGBrush)
|
||||
RCT_EXPORT_VIEW_PROPERTY(fillRule, RNSVGCGFCRule)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stroke, CGColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeDash, RNSVGCGFloatArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clipPath, CGPath)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clipRule, RNSVGCGFCRule)
|
||||
|
||||
@@ -26,7 +26,7 @@ function applyBoundingBoxToBrushData(brushData, props) {
|
||||
}
|
||||
|
||||
export default function (colorOrBrush, props) {
|
||||
if (colorOrBrush == null) {
|
||||
if (!colorOrBrush) {
|
||||
return null;
|
||||
}
|
||||
if (colorOrBrush._brush) {
|
||||
|
||||
@@ -1,63 +1,19 @@
|
||||
import rgba from '../rgba';
|
||||
import {LinearGradientGenerator} from '../../elements/LinearGradient';
|
||||
import {RadialGradientGenerator} from '../../elements/RadialGradient';
|
||||
import extractBrush from './extractBrush';
|
||||
import fillReg from './patternReg';
|
||||
|
||||
let fillPatterns = {};
|
||||
|
||||
function isGradient(obj) {
|
||||
return obj instanceof LinearGradientGenerator || obj instanceof RadialGradientGenerator;
|
||||
}
|
||||
|
||||
function set(id, pattern) {
|
||||
fillPatterns[id] = pattern;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
delete fillPatterns[id];
|
||||
}
|
||||
import patterns from './patterns';
|
||||
|
||||
const fillRules = {
|
||||
evenodd: 0,
|
||||
nonzero: 1
|
||||
};
|
||||
|
||||
function fillFilter(props) {
|
||||
function fillFilter(props, dimensions) {
|
||||
let {fill} = props;
|
||||
|
||||
if (fill === 'none') {
|
||||
return null;
|
||||
} if (fill) {
|
||||
|
||||
if (isGradient(fill)) {
|
||||
return fill;
|
||||
}
|
||||
|
||||
let fillOpacity = +props.fillOpacity;
|
||||
if (isNaN(fillOpacity)) {
|
||||
fillOpacity = 1;
|
||||
}
|
||||
|
||||
// 尝试匹配 fill="url(#pattern)"
|
||||
let matched = fill.match(fillReg);
|
||||
|
||||
if (matched) {
|
||||
let patternName = `${matched[1]}:${props.svgId}`;
|
||||
let pattern = fillPatterns[patternName];
|
||||
|
||||
if (pattern) {
|
||||
if (pattern.length === 2) {
|
||||
let dimensions = this.getBoundingBox();
|
||||
return pattern(dimensions, fillOpacity);
|
||||
} else {
|
||||
return pattern(fillOpacity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return rgba(props.fill, fillOpacity).rgbaString();
|
||||
} else if (fill) {
|
||||
return patterns(fill, +props.fillOpacity, dimensions, props.svgId);
|
||||
} else if (props.fill === undefined) {
|
||||
let fillOpacity = +props.fillOpacity;
|
||||
if (isNaN(fillOpacity)) {
|
||||
@@ -67,20 +23,14 @@ function fillFilter(props) {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function(props) {
|
||||
let fill = fillFilter.call(this, props);
|
||||
export default function(props, dimensions) {
|
||||
let fill = extractBrush(fillFilter(props, dimensions), props);
|
||||
let fillRule = fillRules[props.fillRule] === 0 ? 0 : 1;
|
||||
|
||||
return {
|
||||
fill: extractBrush(fill, props),
|
||||
fill,
|
||||
fillRule
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
set,
|
||||
remove
|
||||
}
|
||||
|
||||
@@ -14,16 +14,18 @@ export default function(props, options = {stroke: true, join: true, transform: t
|
||||
opacity: +props.opacity || 1
|
||||
};
|
||||
|
||||
let dimensions = this.getBoundingBox ? this.getBoundingBox() : null;
|
||||
|
||||
if (props.clipPath) {
|
||||
_.assign(extractedProps, extractClipping(props));
|
||||
}
|
||||
|
||||
if (options.stroke) {
|
||||
_.assign(extractedProps, extractStroke(props));
|
||||
_.assign(extractedProps, extractStroke(props, dimensions));
|
||||
}
|
||||
|
||||
if (options.fill) {
|
||||
_.assign(extractedProps, extractFill.call(this, props));
|
||||
_.assign(extractedProps, extractFill(props, dimensions));
|
||||
}
|
||||
|
||||
if (options.transform) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import rgba from '../rgba';
|
||||
import extractBrush from './extractBrush';
|
||||
import patterns from './patterns';
|
||||
|
||||
let separator = /\s*,\s*/;
|
||||
|
||||
const caps = {
|
||||
@@ -14,9 +15,10 @@ const joins = {
|
||||
round: 1
|
||||
};
|
||||
|
||||
function strokeFilter(props) {
|
||||
if (!props.stroke && !props.strokeLinecap && !props.strokeOpacity &&
|
||||
!props.strokeLinejoin && !props.strokeDasharray && !props.strokeWidth) {
|
||||
function strokeFilter(props, dimensions) {
|
||||
let strokeWidth = +props.strokeWidth;
|
||||
let {stroke} = props;
|
||||
if (!strokeWidth && !stroke) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -26,30 +28,25 @@ function strokeFilter(props) {
|
||||
strokeDasharray = strokeDasharray.split(separator).map(dash => +dash);
|
||||
}
|
||||
|
||||
|
||||
if (!stroke) {
|
||||
stroke = '#000';
|
||||
}
|
||||
|
||||
// TODO: propTypes check
|
||||
return {
|
||||
stroke: rgba(props.stroke, props.strokeOpacity).rgbaArray(),
|
||||
stroke: patterns(stroke, +props.strokeOpacity, dimensions, props.svgId),
|
||||
strokeLinecap: caps[props.strokeLinecap] || 2,
|
||||
strokeLinejoin: joins[props.strokeLinejoin] || 0,
|
||||
strokeDash: strokeDasharray || null,
|
||||
strokeWidth: +props.strokeWidth || 1
|
||||
strokeWidth: strokeWidth || 1
|
||||
};
|
||||
}
|
||||
|
||||
export default function(props) {
|
||||
let strokeProps = strokeFilter(props);
|
||||
if (!strokeProps) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let {stroke} = strokeProps;
|
||||
return {
|
||||
export default function(props, dimensions) {
|
||||
let strokeProps = strokeFilter(props, dimensions);
|
||||
return strokeProps ? {
|
||||
...strokeProps,
|
||||
stroke: stroke ? [stroke[0] / 255, stroke[1] / 255, stroke[2] / 255, stroke[3]] : null
|
||||
};
|
||||
|
||||
// TODO: implement brush on stroke prop
|
||||
//return {
|
||||
// ...strokeProps,
|
||||
// stroke: extractBrush(strokeProps.stroke, props)
|
||||
//};
|
||||
stroke: extractBrush(strokeProps.stroke, props)
|
||||
} : null;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default /^url\(#(\w+?)\)$/;
|
||||
53
lib/extract/patterns.js
Normal file
53
lib/extract/patterns.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import rgba from '../rgba';
|
||||
let patterns = {};
|
||||
let patternReg = /^url\(#(\w+?)\)$/;
|
||||
|
||||
import {LinearGradientGenerator} from '../../elements/LinearGradient';
|
||||
import {RadialGradientGenerator} from '../../elements/RadialGradient';
|
||||
|
||||
|
||||
function isGradient(obj) {
|
||||
return obj instanceof LinearGradientGenerator || obj instanceof RadialGradientGenerator;
|
||||
}
|
||||
|
||||
function set(id, pattern) {
|
||||
patterns[id] = pattern;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
delete patterns[id];
|
||||
}
|
||||
|
||||
export {
|
||||
set,
|
||||
remove
|
||||
}
|
||||
|
||||
export default function(patternSting, opacity, dimensions, svgId) {
|
||||
if (isGradient(patternSting)) {
|
||||
return patternSting;
|
||||
}
|
||||
|
||||
if (isNaN(opacity)) {
|
||||
opacity = 1;
|
||||
}
|
||||
|
||||
// 尝试匹配 "url(#pattern)"
|
||||
let matched = patternSting.match(patternReg);
|
||||
|
||||
if (matched) {
|
||||
let patternName = `${matched[1]}:${svgId}`;
|
||||
let pattern = patterns[patternName];
|
||||
|
||||
if (pattern) {
|
||||
if (pattern.length === 2) {
|
||||
return pattern(dimensions, opacity);
|
||||
} else {
|
||||
return pattern(opacity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return rgba(patternSting, opacity).rgbaString();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"name": "react-native-svg",
|
||||
"description": "react native svg library",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user