123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- //
- // SVGRadialGradientElement.m
- // SVGKit-iOS
- //
- // Created by lizhuoli on 2018/10/15.
- // Copyright © 2018年 na. All rights reserved.
- //
- #import "SVGRadialGradientElement.h"
- #import "SVGElement_ForParser.h"
- #import "SVGUtils.h"
- #import "SVGGradientLayer.h"
- // `kCAGradientLayerRadial` this symbol is available since iOS 3.2/tvOS 9.0/macOS 10.6, but it's not externed to public header until Xcode 10 with iOS 12 SDK, so we define it for user who still use old SDK version.
- #define kCAGradientLayerRadial @"radial"
- @interface SVGRadialGradientElement ()
- @property (nonatomic) BOOL hasSynthesizedProperties;
- @property (nonatomic) SVGLength *cx;
- @property (nonatomic) SVGLength *cy;
- @property (nonatomic) SVGLength *r;
- @property (nonatomic) SVGLength *fx;
- @property (nonatomic) SVGLength *fy;
- @property (nonatomic) SVGLength *fr;
- @end
- @implementation SVGRadialGradientElement
- - (SVGGradientLayer *)newGradientLayerForObjectRect:(CGRect)objectRect viewportRect:(SVGRect)viewportRect transform:(CGAffineTransform)absoluteTransform {
- SVGGradientLayer *gradientLayer = [[SVGGradientLayer alloc] init];
- BOOL inUserSpace = self.gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE;
- CGRect rectForRelativeUnits = inUserSpace ? CGRectFromSVGRect( viewportRect ) : objectRect;
-
- gradientLayer.frame = objectRect;
-
- NSString *cxAttr = [self getAttributeInheritedIfNil:@"cx"];
- NSString *cyAttr = [self getAttributeInheritedIfNil:@"cy"];
- NSString *rAttr = [self getAttributeInheritedIfNil:@"r"];
- NSString *fxAttr = [self getAttributeInheritedIfNil:@"fx"];
- NSString *fyAttr = [self getAttributeInheritedIfNil:@"fy"];
- NSString *frAttr = [self getAttributeInheritedIfNil:@"fr"];
- SVGLength* svgCX = [SVGLength svgLengthFromNSString:cxAttr.length > 0 ? cxAttr : @"50%"];
- SVGLength* svgCY = [SVGLength svgLengthFromNSString:cyAttr.length > 0 ? cyAttr : @"50%"];
- SVGLength* svgR = [SVGLength svgLengthFromNSString:rAttr.length > 0 ? rAttr : @"50%"];
- // focal value
- SVGLength* svgFX = fxAttr.length > 0 ? [SVGLength svgLengthFromNSString:fxAttr] : svgCX;
- SVGLength* svgFY = fyAttr.length > 0 ? [SVGLength svgLengthFromNSString:fyAttr] : svgCY;
- SVGLength* svgFR = [SVGLength svgLengthFromNSString:frAttr.length > 0 ? frAttr : @"0%"];
- // This is a tempory workaround. Apple's `CAGradientLayer` does not support focal point for radial gradient. We have to use the low-level API `CGContextDrawRadialGradient` and using custom software-render for focal point. So it does not works for `SVGLayredView` which is hardware-render by CA render server.
- if (fxAttr.length > 0 || fyAttr.length > 0 || frAttr.length > 0) {
- SVGKitLogWarn(@"The radialGradient element #%@ contains focal value: (fx:%@, fy: %@, fr:%@). The focul value is only supported on `SVGFastimageView` and it will be ignored when rendering in SVGLayredView.", [self getAttribute:@"id"], fxAttr, fyAttr, frAttr);
- }
- self.cx = svgCX;
- self.cy = svgCY;
- self.r = svgR;
- self.fx = svgFX;
- self.fy = svgFY;
- self.fr = svgFR;
-
- // these should really be two separate code paths (objectBoundingBox and userSpaceOnUse), but we simplify the logic using `rectForRelativeUnits`
- CGFloat width = CGRectGetWidth(rectForRelativeUnits);
- CGFloat height = CGRectGetHeight(rectForRelativeUnits);
- CGFloat cx = [svgCX pixelsValueWithGradientDimension:width];
- CGFloat cy = [svgCY pixelsValueWithGradientDimension:height];
- CGFloat val = MIN(width, height);
- CGFloat radius = [svgR pixelsValueWithGradientDimension:val];
-
- CGFloat fx = [svgFX pixelsValueWithGradientDimension:width];
- CGFloat fy = [svgFY pixelsValueWithGradientDimension:height];
-
- CGPoint startPoint = CGPointMake(cx, cy);
- CGPoint endPoint = CGPointMake(fx, fy);
- CGAffineTransform selfTransform = self.transform;
-
- if (inUserSpace)
- {
- // work out the new radius
- CGFloat rad = radius * 2.f;
- CGRect rect = CGRectMake(startPoint.x, startPoint.y, rad, rad);
- rect = CGRectApplyAffineTransform(rect, self.transform);
- rect = CGRectApplyAffineTransform(rect, absoluteTransform);
- radius = CGRectGetHeight(rect) / 2.f;
- }
-
- if (!inUserSpace)
- {
- // SVG spec: transform if width or height is not equal
- if(CGRectGetWidth(objectRect) != CGRectGetHeight(objectRect)) {
- CGAffineTransform tr = CGAffineTransformMakeTranslation(startPoint.x,
- startPoint.y);
- if(CGRectGetWidth(objectRect) > CGRectGetHeight(objectRect)) {
- tr = CGAffineTransformScale(tr, CGRectGetWidth(objectRect)/CGRectGetHeight(objectRect), 1);
- } else {
- tr = CGAffineTransformScale(tr, 1.f, CGRectGetHeight(objectRect)/CGRectGetWidth(objectRect));
- }
- tr = CGAffineTransformTranslate(tr, -startPoint.x, -startPoint.y);
- selfTransform = CGAffineTransformConcat(tr, selfTransform);
- }
- }
-
- CGSize size = CGSizeMake(radius, radius);
- startPoint = CGPointApplyAffineTransform(startPoint, selfTransform);
- endPoint = CGPointApplyAffineTransform(endPoint, selfTransform);
- size = CGSizeApplyAffineTransform(size, selfTransform);
- if (inUserSpace)
- {
- // apply the absolute position
- startPoint = CGPointApplyAffineTransform(startPoint, absoluteTransform);
- startPoint.x = startPoint.x - CGRectGetMinX(objectRect);
- startPoint.y = startPoint.y - CGRectGetMinY(objectRect);
- endPoint = CGPointApplyAffineTransform(endPoint, absoluteTransform);
- endPoint.x = endPoint.x - CGRectGetMaxX(objectRect) + CGRectGetWidth(objectRect);
- endPoint.y = endPoint.y - CGRectGetMaxY(objectRect) + CGRectGetHeight(objectRect);
- size = CGSizeApplyAffineTransform(size, selfTransform);
- }
-
- CGPoint gradientStartPoint = startPoint;
- CGPoint gradientEndPoint = endPoint;
-
- // convert to percent
- CGPoint centerPoint = gradientStartPoint;
- gradientStartPoint = CGPointMake((centerPoint.x) / CGRectGetWidth(objectRect), centerPoint.y / CGRectGetHeight(objectRect));
- gradientEndPoint = CGPointMake((centerPoint.x + size.width) / CGRectGetWidth(objectRect), (centerPoint.y + size.height) / CGRectGetHeight(objectRect));
- // Suck. When using `SVGLayredImageView`, the layer rendering is submitted to CA render server, and your custom `renderInContex:` code will not work. So we just set both built-in value (CAGradientLayer property) && custom value (SVGGradientLayer property)
- // FIX-ME: built-in value (not match the SVG spec, all the focal value will be ignored)
- gradientLayer.startPoint = gradientStartPoint;
- gradientLayer.endPoint = gradientEndPoint;
- gradientLayer.type = kCAGradientLayerRadial;
- // custom value (match the SVG spec)
- gradientLayer.gradientElement = self;
- gradientLayer.objectRect = objectRect;
- gradientLayer.viewportRect = viewportRect;
- gradientLayer.absoluteTransform = absoluteTransform;
-
- if (svgR.value <= 0 || self.colors.count == 1) {
- // SVG spec: <r> A value of lower or equal to zero will cause the area to be painted as a single color using the color and opacity of the last gradient <stop>.
- SVGGradientStop *lastStop = self.stops.lastObject;
- gradientLayer.backgroundColor = CGColorWithSVGColor(lastStop.stopColor);
- gradientLayer.opacity = lastStop.stopOpacity;
- } else {
- [gradientLayer setColors:self.colors];
- [gradientLayer setLocations:self.locations];
- }
-
- SVGKitLogVerbose(@"[%@] set gradient layer start = %@", [self class], NSStringFromCGPoint(gradientLayer.startPoint));
- SVGKitLogVerbose(@"[%@] set gradient layer end = %@", [self class], NSStringFromCGPoint(gradientLayer.endPoint));
- SVGKitLogVerbose(@"[%@] set gradient layer colors = %@", [self class], self.colors);
- SVGKitLogVerbose(@"[%@] set gradient layer locations = %@", [self class], self.locations);
-
- return gradientLayer;
- }
- - (void)synthesizeProperties {
- if (self.hasSynthesizedProperties)
- return;
- self.hasSynthesizedProperties = YES;
-
- NSString* gradientID = [self getAttributeNS:@"http://www.w3.org/1999/xlink" localName:@"href"];
-
- if ([gradientID length])
- {
- if ([gradientID hasPrefix:@"#"])
- gradientID = [gradientID substringFromIndex:1];
-
- SVGRadialGradientElement* baseGradient = (SVGRadialGradientElement*) [self.rootOfCurrentDocumentFragment getElementById:gradientID];
- NSString* svgNamespace = @"http://www.w3.org/2000/svg";
-
- if (baseGradient)
- {
- [baseGradient synthesizeProperties];
-
- if (!self.stops && baseGradient.stops)
- {
- for (SVGGradientStop* stop in baseGradient.stops)
- [self addStop:stop];
- }
- NSArray *keys = [NSArray arrayWithObjects:@"cx", @"cy", @"r", @"fx", @"fy", @"fr", @"gradientUnits", @"gradientTransform", @"spreadMethod", nil];
-
- for (NSString* key in keys)
- {
- if (![self hasAttribute:key] && [baseGradient hasAttribute:key])
- [self setAttributeNS:svgNamespace qualifiedName:key value:[baseGradient getAttribute:key]];
- }
-
- }
- }
- }
- @end
|