SVGRadialGradientElement.m 9.2 KB

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