SVGSVGElement.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #import "SVGSVGElement.h"
  2. #import "SVGSVGElement_Mutable.h"
  3. #import "CALayerWithChildHitTest.h"
  4. #import "DOMHelperUtilities.h"
  5. #import "SVGHelperUtilities.h"
  6. #import "SVGElement_ForParser.h" // to resolve Xcode circular dependencies; in long term, parsing SHOULD NOT HAPPEN inside any class whose name starts "SVG" (because those are reserved classes for the SVG Spec)
  7. @interface SVGSVGElement()
  8. #pragma mark - elements REQUIRED to implement the spec but not included in SVG Spec due to bugs in the spec writing!
  9. @property(nonatomic,readwrite) SVGRect requestedViewport;
  10. @end
  11. @implementation SVGSVGElement
  12. @synthesize x;
  13. @synthesize y;
  14. @synthesize width;
  15. @synthesize height;
  16. @synthesize contentScriptType;
  17. @synthesize contentStyleType;
  18. @synthesize viewport;
  19. @synthesize pixelUnitToMillimeterX;
  20. @synthesize pixelUnitToMillimeterY;
  21. @synthesize screenPixelToMillimeterX;
  22. @synthesize screenPixelToMillimeterY;
  23. @synthesize useCurrentView;
  24. @synthesize currentView;
  25. @synthesize currentScale;
  26. @synthesize currentTranslate;
  27. @synthesize source;
  28. @synthesize viewBox = _viewBox; // each SVGElement subclass that conforms to protocol "SVGFitToViewBox" has to re-synthesize this to work around bugs in Apple's Objective-C 2.0 design that don't allow @properties to be extended by categories / protocols
  29. @synthesize preserveAspectRatio; // each SVGElement subclass that conforms to protocol "SVGFitToViewBox" has to re-synthesize this to work around bugs in Apple's Objective-C 2.0 design that don't allow @properties to be extended by categories / protocols
  30. #pragma mark - NON SPEC, violating, properties
  31. -(void)dealloc
  32. {
  33. self.viewBox = SVGRectUninitialized();
  34. }
  35. #pragma mark - CSS Spec methods (via the DocumentCSS protocol)
  36. -(void)loadDefaults
  37. {
  38. self.styleSheets = [[StyleSheetList alloc] init];
  39. }
  40. @synthesize styleSheets;
  41. -(CSSStyleDeclaration *)getOverrideStyle:(Element *)element pseudoElt:(NSString *)pseudoElt
  42. {
  43. NSAssert(FALSE, @"Not implemented yet");
  44. return nil;
  45. }
  46. #pragma mark - SVG Spec methods
  47. -(long) suspendRedraw:(long) maxWaitMilliseconds { NSAssert( FALSE, @"Not implemented yet" ); return 0; }
  48. -(void) unsuspendRedraw:(long) suspendHandleID { NSAssert( FALSE, @"Not implemented yet" ); }
  49. -(void) unsuspendRedrawAll { NSAssert( FALSE, @"Not implemented yet" ); }
  50. -(void) forceRedraw { NSAssert( FALSE, @"Not implemented yet" ); }
  51. -(void) pauseAnimations { NSAssert( FALSE, @"Not implemented yet" ); }
  52. -(void) unpauseAnimations { NSAssert( FALSE, @"Not implemented yet" ); }
  53. -(BOOL) animationsPaused { NSAssert( FALSE, @"Not implemented yet" ); return TRUE; }
  54. -(float) getCurrentTime { NSAssert( FALSE, @"Not implemented yet" ); return 0.0; }
  55. -(void) setCurrentTime:(float) seconds { NSAssert( FALSE, @"Not implemented yet" ); }
  56. -(NodeList*) getIntersectionList:(SVGRect) rect referenceElement:(SVGElement*) referenceElement { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  57. -(NodeList*) getEnclosureList:(SVGRect) rect referenceElement:(SVGElement*) referenceElement { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  58. -(BOOL) checkIntersection:(SVGElement*) element rect:(SVGRect) rect { NSAssert( FALSE, @"Not implemented yet" ); return FALSE; }
  59. -(BOOL) checkEnclosure:(SVGElement*) element rect:(SVGRect) rect { NSAssert( FALSE, @"Not implemented yet" ); return FALSE; }
  60. -(void) deselectAll { NSAssert( FALSE, @"Not implemented yet" );}
  61. -(SVGNumber) createSVGNumber
  62. {
  63. SVGNumber n = { 0 };
  64. return n;
  65. }
  66. -(SVGLength*) createSVGLength
  67. {
  68. return [SVGLength new];
  69. }
  70. -(SVGAngle*) createSVGAngle { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  71. -(SVGPoint*) createSVGPoint { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  72. -(SVGMatrix*) createSVGMatrix { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  73. -(SVGRect) createSVGRect
  74. {
  75. SVGRect r = { 0.0, 0.0, 0.0, 0.0 };
  76. return r;
  77. }
  78. -(SVGTransform*) createSVGTransform { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  79. -(SVGTransform*) createSVGTransformFromMatrix:(SVGMatrix*) matrix { NSAssert( FALSE, @"Not implemented yet" ); return nil; }
  80. -(Element*) getElementById:(NSString*) elementId
  81. {
  82. return [DOMHelperUtilities privateGetElementById:elementId childrenOfElement:self];
  83. }
  84. #pragma mark - Objective C methods needed given our current non-compliant SVG Parser
  85. - (void)postProcessAttributesAddingErrorsTo:(SVGKParseResult *)parseResult {
  86. [super postProcessAttributesAddingErrorsTo:parseResult];
  87. /**
  88. Rather unusually, the official SVG Spec uses an explicit "width=100% height=100%" on every
  89. root SVG tag on every TestSuite file - these ARE NOT PRESENT in any of the Spec examples!
  90. Only in the TestSuite!
  91. Net effect: we have a major problem with calculating the initial viewport. What does
  92. "100%" mean when you're parsing?
  93. Literally, from the spec: NOTHING. It's undefined! It's a hint that requires you to have
  94. a parsed SVG already. c.f. other doc notes below, this is complicated and very badly
  95. documented in the SVG Spec.
  96. -------------
  97. For now, we're going to:
  98. 1. if width or height are percentages, set the viewport to "uninitialized", since they are indeed.
  99. --- within the spec, "100%" doesn't mean anything. Other percentages are horribly vague in what
  100. they might (or might not) mean. And different SVG renderers treat them differently. Because the
  101. Spec is so poor, probably.
  102. 2. Assume that our post-parse renderer code (elsewhere in the library, in SVGKImage I believe)
  103. will correctly modify the viewport afterwards. It will lose the percentage, of course, but frankly
  104. this is enough of a brainfck already that any author who uses percentages in their SVG tag needs
  105. to be coached in better authoring anyway!
  106. --- i.e. their SVG isn't going to render reliably anyway. Whatever they're trying to do, they
  107. should probably do it a different way.
  108. --- and: it's so damn hard to get a working, non-crashing implmementation here htat handles all
  109. edge-cases, that ... screw it. Life's too short.
  110. */
  111. /**
  112. If the width + height are missing, we have to get an image width+height from the USER before we even START parsing.
  113. There is NO SUPPORT IN THE SVG SPEC TO ALLOW THIS. This is strange, but they specified this part badly, so it's not a surprise.
  114. We would need to put extra (NON STANDARD) properties on SVGDocument, for the "viewport width and height",
  115. and then in *this* method, if we're missing a width or height, take the values from the SVGDocument's temporary/default width height
  116. (NB: the input to this method "SVGKParseResult" has a .parsedDocument variable, that's how we'd fetch those values here
  117. */
  118. NSString* stringWidth = [self getAttribute:@"width"];
  119. NSString* stringHeight = [self getAttribute:@"height"];
  120. NSString* pos_x = [self getAttribute:@"x"];
  121. NSString* pos_y = [self getAttribute:@"y"];
  122. if (pos_x == nil || pos_x.length < 1)
  123. self.x = 0; // i.e. undefined
  124. else
  125. self.x = [SVGLength svgLengthFromNSString:pos_x];
  126. if (pos_y == nil || pos_y.length < 1)
  127. self.y = 0; // i.e. undefined
  128. else
  129. self.y = [SVGLength svgLengthFromNSString:pos_y];
  130. if( stringWidth == nil || stringWidth.length < 1 )
  131. self.width = nil; // i.e. undefined
  132. else
  133. self.width = [SVGLength svgLengthFromNSString:[self getAttribute:@"width"]];
  134. if( stringHeight == nil || stringHeight.length < 1 )
  135. self.height = nil; // i.e. undefined
  136. else
  137. self.height = [SVGLength svgLengthFromNSString:[self getAttribute:@"height"]];
  138. /**
  139. WARNING: SVG TestSuite sets SVG element width and height to 100%, which are meaningless
  140. and impossible to calculate at parsetime (they are defined as undefined until you "negotiate"
  141. with the OS / Application / etc -- which won't be possible until you've finished the parse).
  142. So ... they end up being 0 here. To workaround that, we set them to nil if they are percentages
  143. here. ONLY for the SVG tag though.
  144. */
  145. if( self.width.unitType == SVG_LENGTHTYPE_PERCENTAGE )
  146. self.width = nil;
  147. if( self.height.unitType == SVG_LENGTHTYPE_PERCENTAGE )
  148. self.height = nil;
  149. /* set the frameRequestedViewport appropriately (NB: spec doesn't allow for this but it REQUIRES it to be done and saved!) */
  150. if( self.width != nil && self.height != nil )
  151. self.requestedViewport = SVGRectMake( [self.x pixelsValue], [self.y pixelsValue], [self.width pixelsValue], [self.height pixelsValue] );
  152. else
  153. self.requestedViewport = SVGRectUninitialized();
  154. /**
  155. NB: this is VERY CONFUSING due to badly written SVG Spec, but: the viewport MUST NOT be set by the parser,
  156. it MUST ONLY be set by the "renderer" -- and the renderer MAY have decided to use a different viewport from
  157. the one that the SVG file *implies* (e.g. if the user scales the SVG, the viewport WILL BE DIFFERENT,
  158. by definition!
  159. ...However: the renderer will ALWAYS start with the default viewport values (that are calcualted by the parsing process)
  160. and it makes it much cleaner and safer to implement if we have the PARSER set the viewport initially
  161. (and the renderer will IMMEDIATELY overwrite them once the parsing is finished IFF IT NEEDS TO)
  162. */
  163. self.viewport = self.requestedViewport; // renderer can/will change the .viewport, but .requestedViewport can only be set by the PARSER
  164. if( [[self getAttribute:@"viewBox"] length] > 0 )
  165. {
  166. NSArray* boxElements = [[self getAttribute:@"viewBox"] componentsSeparatedByString:@" "];
  167. if ([boxElements count] < 2) {
  168. /* count should be 4 -- maybe they're comma separated like (x,y,w,h) */
  169. boxElements = [[self getAttribute:@"viewBox"] componentsSeparatedByString:@","];
  170. }
  171. _viewBox = SVGRectMake([[boxElements objectAtIndex:0] floatValue], [[boxElements objectAtIndex:1] floatValue], [[boxElements objectAtIndex:2] floatValue], [[boxElements objectAtIndex:3] floatValue]);
  172. }
  173. else
  174. {
  175. self.viewBox = SVGRectUninitialized(); // VERY IMPORTANT: we MUST make it clear this was never initialized, instead of saying its 0,0,0,0 !
  176. }
  177. [SVGHelperUtilities parsePreserveAspectRatioFor:self];
  178. if( stringWidth == nil || stringWidth.length < 1 )
  179. self.width = nil; // i.e. undefined
  180. else
  181. self.width = [SVGLength svgLengthFromNSString:[self getAttribute:@"width"]];
  182. // logging
  183. SVGKitLogVerbose(@"[%@] DEBUG INFO: set document viewBox = %@", [self class], NSStringFromSVGRect(self.viewBox));
  184. }
  185. - (SVGElement *)findFirstElementOfClass:(Class)classParameter {
  186. for (SVGElement *element in self.childNodes)
  187. {
  188. if ([element isKindOfClass:classParameter])
  189. return element;
  190. }
  191. return nil;
  192. }
  193. - (CALayer *) newLayer
  194. {
  195. CALayer* _layer = [CALayerWithChildHitTest layer];
  196. [SVGHelperUtilities configureCALayer:_layer usingElement:self];
  197. /** <SVG> tags know exactly what size/shape their layer needs to be - it's explicit in their width + height attributes! */
  198. CGRect newBoundsFromSVGTag = CGRectFromSVGRect( self.viewport );
  199. _layer.frame = newBoundsFromSVGTag; // assign to FRAME, not to BOUNDS: Apple has some weird bugs where for particular numeric values (!) assignment to bounds will cause the origin to jump away from (0,0)!
  200. return _layer;
  201. }
  202. - (void)layoutLayer:(CALayer *)layer {
  203. /**
  204. According to the SVG spec ... what this method originaly did is illegal. I've deleted all of it, and now a few more SVG's render correctly, that
  205. previously were rendering with strange offsets at the top level
  206. */
  207. }
  208. #pragma mark - elements REQUIRED to implement the spec but not included in SVG Spec due to bugs in the spec writing!
  209. -(double)aspectRatioFromWidthPerHeight
  210. {
  211. return [self.height pixelsValue] == 0 ? 0 : [self.width pixelsValue] / [self.height pixelsValue];
  212. }
  213. -(double)aspectRatioFromViewBox
  214. {
  215. return self.viewBox.height == 0 ? 0 : self.viewBox.width / self.viewBox.height;
  216. }
  217. @end