SVGRadialGradientElement.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. //
  2. // SVGRadialGradientElement.m
  3. // SVGKit-iOS
  4. //
  5. // Created by lizhuoli on 2018/10/15.
  6. // Copyright © 2018年 na. All rights reserved.
  7. //
  8. #import "SVGRadialGradientElement.h"
  9. #import "SVGElement_ForParser.h"
  10. #import "SVGUtils.h"
  11. #import "SVGGradientLayer.h"
  12. #import "SVGKDefine_Private.h"
  13. // `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.
  14. #define kCAGradientLayerRadial @"radial"
  15. @interface SVGRadialGradientElement ()
  16. @property (nonatomic) BOOL hasSynthesizedProperties;
  17. @property (nonatomic) SVGLength *cx;
  18. @property (nonatomic) SVGLength *cy;
  19. @property (nonatomic) SVGLength *r;
  20. @property (nonatomic) SVGLength *fx;
  21. @property (nonatomic) SVGLength *fy;
  22. @property (nonatomic) SVGLength *fr;
  23. @end
  24. @implementation SVGRadialGradientElement
  25. - (SVGGradientLayer *)newGradientLayerForObjectRect:(CGRect)objectRect viewportRect:(SVGRect)viewportRect transform:(CGAffineTransform)absoluteTransform {
  26. SVGGradientLayer *gradientLayer = [[SVGGradientLayer alloc] init];
  27. BOOL inUserSpace = self.gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE;
  28. CGRect rectForRelativeUnits = inUserSpace ? CGRectFromSVGRect( viewportRect ) : objectRect;
  29. gradientLayer.frame = objectRect;
  30. NSString *cxAttr = [self getAttributeInheritedIfNil:@"cx"];
  31. NSString *cyAttr = [self getAttributeInheritedIfNil:@"cy"];
  32. NSString *rAttr = [self getAttributeInheritedIfNil:@"r"];
  33. NSString *fxAttr = [self getAttributeInheritedIfNil:@"fx"];
  34. NSString *fyAttr = [self getAttributeInheritedIfNil:@"fy"];
  35. NSString *frAttr = [self getAttributeInheritedIfNil:@"fr"];
  36. SVGLength* svgCX = [SVGLength svgLengthFromNSString:cxAttr.length > 0 ? cxAttr : @"50%"];
  37. SVGLength* svgCY = [SVGLength svgLengthFromNSString:cyAttr.length > 0 ? cyAttr : @"50%"];
  38. SVGLength* svgR = [SVGLength svgLengthFromNSString:rAttr.length > 0 ? rAttr : @"50%"];
  39. // focal value
  40. SVGLength* svgFX = fxAttr.length > 0 ? [SVGLength svgLengthFromNSString:fxAttr] : svgCX;
  41. SVGLength* svgFY = fyAttr.length > 0 ? [SVGLength svgLengthFromNSString:fyAttr] : svgCY;
  42. SVGLength* svgFR = [SVGLength svgLengthFromNSString:frAttr.length > 0 ? frAttr : @"0%"];
  43. // 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.
  44. if (fxAttr.length > 0 || fyAttr.length > 0 || frAttr.length > 0) {
  45. 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);
  46. }
  47. self.cx = svgCX;
  48. self.cy = svgCY;
  49. self.r = svgR;
  50. self.fx = svgFX;
  51. self.fy = svgFY;
  52. self.fr = svgFR;
  53. // these should really be two separate code paths (objectBoundingBox and userSpaceOnUse), but we simplify the logic using `rectForRelativeUnits`
  54. CGFloat width = CGRectGetWidth(rectForRelativeUnits);
  55. CGFloat height = CGRectGetHeight(rectForRelativeUnits);
  56. CGFloat cx = [svgCX pixelsValueWithGradientDimension:width];
  57. CGFloat cy = [svgCY pixelsValueWithGradientDimension:height];
  58. CGFloat val = MIN(width, height);
  59. CGFloat radius = [svgR pixelsValueWithGradientDimension:val];
  60. CGFloat fx = [svgFX pixelsValueWithGradientDimension:width];
  61. CGFloat fy = [svgFY pixelsValueWithGradientDimension:height];
  62. CGPoint startPoint = CGPointMake(cx, cy);
  63. CGPoint endPoint = CGPointMake(fx, fy);
  64. CGAffineTransform selfTransform = self.transform;
  65. if (inUserSpace)
  66. {
  67. // work out the new radius
  68. CGFloat rad = radius * 2.f;
  69. CGRect rect = CGRectMake(startPoint.x, startPoint.y, rad, rad);
  70. rect = CGRectApplyAffineTransform(rect, self.transform);
  71. rect = CGRectApplyAffineTransform(rect, absoluteTransform);
  72. radius = CGRectGetHeight(rect) / 2.f;
  73. }
  74. if (!inUserSpace)
  75. {
  76. // SVG spec: transform if width or height is not equal
  77. if(CGRectGetWidth(objectRect) != CGRectGetHeight(objectRect)) {
  78. CGAffineTransform tr = CGAffineTransformMakeTranslation(startPoint.x,
  79. startPoint.y);
  80. if(CGRectGetWidth(objectRect) > CGRectGetHeight(objectRect)) {
  81. tr = CGAffineTransformScale(tr, CGRectGetWidth(objectRect)/CGRectGetHeight(objectRect), 1);
  82. } else {
  83. tr = CGAffineTransformScale(tr, 1.f, CGRectGetHeight(objectRect)/CGRectGetWidth(objectRect));
  84. }
  85. tr = CGAffineTransformTranslate(tr, -startPoint.x, -startPoint.y);
  86. selfTransform = CGAffineTransformConcat(tr, selfTransform);
  87. }
  88. }
  89. CGSize size = CGSizeMake(radius, radius);
  90. startPoint = CGPointApplyAffineTransform(startPoint, selfTransform);
  91. endPoint = CGPointApplyAffineTransform(endPoint, selfTransform);
  92. size = CGSizeApplyAffineTransform(size, selfTransform);
  93. if (inUserSpace)
  94. {
  95. // apply the absolute position
  96. startPoint = CGPointApplyAffineTransform(startPoint, absoluteTransform);
  97. startPoint.x = startPoint.x - CGRectGetMinX(objectRect);
  98. startPoint.y = startPoint.y - CGRectGetMinY(objectRect);
  99. endPoint = CGPointApplyAffineTransform(endPoint, absoluteTransform);
  100. endPoint.x = endPoint.x - CGRectGetMaxX(objectRect) + CGRectGetWidth(objectRect);
  101. endPoint.y = endPoint.y - CGRectGetMaxY(objectRect) + CGRectGetHeight(objectRect);
  102. size = CGSizeApplyAffineTransform(size, selfTransform);
  103. }
  104. CGPoint gradientStartPoint = startPoint;
  105. CGPoint gradientEndPoint = endPoint;
  106. // convert to percent
  107. CGPoint centerPoint = gradientStartPoint;
  108. gradientStartPoint = CGPointMake((centerPoint.x) / CGRectGetWidth(objectRect), centerPoint.y / CGRectGetHeight(objectRect));
  109. gradientEndPoint = CGPointMake((centerPoint.x + size.width) / CGRectGetWidth(objectRect), (centerPoint.y + size.height) / CGRectGetHeight(objectRect));
  110. // 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)
  111. // FIX-ME: built-in value (not match the SVG spec, all the focal value will be ignored)
  112. gradientLayer.startPoint = gradientStartPoint;
  113. gradientLayer.endPoint = gradientEndPoint;
  114. gradientLayer.type = kCAGradientLayerRadial;
  115. // custom value (match the SVG spec)
  116. gradientLayer.gradientElement = self;
  117. gradientLayer.objectRect = objectRect;
  118. gradientLayer.viewportRect = viewportRect;
  119. gradientLayer.absoluteTransform = absoluteTransform;
  120. if (svgR.value <= 0 || self.colors.count == 1) {
  121. // 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>.
  122. SVGGradientStop *lastStop = self.stops.lastObject;
  123. gradientLayer.backgroundColor = CGColorWithSVGColor(lastStop.stopColor);
  124. gradientLayer.opacity = lastStop.stopOpacity;
  125. } else {
  126. [gradientLayer setColors:self.colors];
  127. [gradientLayer setLocations:self.locations];
  128. }
  129. SVGKitLogVerbose(@"[%@] set gradient layer start = %@", [self class], NSStringFromCGPoint(gradientLayer.startPoint));
  130. SVGKitLogVerbose(@"[%@] set gradient layer end = %@", [self class], NSStringFromCGPoint(gradientLayer.endPoint));
  131. SVGKitLogVerbose(@"[%@] set gradient layer colors = %@", [self class], self.colors);
  132. SVGKitLogVerbose(@"[%@] set gradient layer locations = %@", [self class], self.locations);
  133. return gradientLayer;
  134. }
  135. - (void)synthesizeProperties {
  136. if (self.hasSynthesizedProperties)
  137. return;
  138. self.hasSynthesizedProperties = YES;
  139. NSString* gradientID = [self getAttributeNS:@"http://www.w3.org/1999/xlink" localName:@"href"];
  140. if ([gradientID length])
  141. {
  142. if ([gradientID hasPrefix:@"#"])
  143. gradientID = [gradientID substringFromIndex:1];
  144. SVGRadialGradientElement* baseGradient = (SVGRadialGradientElement*) [self.rootOfCurrentDocumentFragment getElementById:gradientID];
  145. NSString* svgNamespace = @"http://www.w3.org/2000/svg";
  146. if (baseGradient)
  147. {
  148. [baseGradient synthesizeProperties];
  149. if (!self.stops && baseGradient.stops)
  150. {
  151. for (SVGGradientStop* stop in baseGradient.stops)
  152. [self addStop:stop];
  153. }
  154. NSArray *keys = [NSArray arrayWithObjects:@"cx", @"cy", @"r", @"fx", @"fy", @"fr", @"gradientUnits", @"gradientTransform", @"spreadMethod", nil];
  155. for (NSString* key in keys)
  156. {
  157. if (![self hasAttribute:key] && [baseGradient hasAttribute:key])
  158. [self setAttributeNS:svgNamespace qualifiedName:key value:[baseGradient getAttribute:key]];
  159. }
  160. }
  161. }
  162. }
  163. @end