SVGSVGElement.m 12 KB

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