SVGKImage.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. #import "SVGKImage.h"
  2. #import "SVGDefsElement.h"
  3. #import "SVGDescriptionElement.h"
  4. #import "SVGKParser.h"
  5. #import "SVGTitleElement.h"
  6. #import "SVGPathElement.h"
  7. #import "SVGUseElement.h"
  8. #import "SVGClipPathElement.h"
  9. #import "SVGSwitchElement.h"
  10. #import "NodeList+Mutable.h"
  11. #import "SVGSVGElement_Mutable.h" // so that changing .size can change the SVG's .viewport
  12. #import "SVGKParserSVG.h"
  13. #import "SVGKSourceLocalFile.h" // for convenience constructors that load from filename
  14. #import "SVGKSourceURL.h" // for convenience constructors that load from URL as string
  15. #import "SVGKSourceNSData.h" // for convenience constructors that load from raw incoming NSData
  16. #import "CALayer+RecursiveClone.h"
  17. #if SVGKIT_MAC
  18. #import "SVGKExporterNSImage.h" // needed for .NSImage property
  19. #else
  20. #import "SVGKExporterUIImage.h" // needed for .UIImage property
  21. #endif
  22. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  23. @interface SVGKImageCacheLine : NSObject
  24. @property(nonatomic) int numberOfInstances;
  25. @property(nonatomic,strong) SVGKImage* mainInstance;
  26. @end
  27. @implementation SVGKImageCacheLine
  28. @synthesize numberOfInstances;
  29. @synthesize mainInstance;
  30. @end
  31. #endif
  32. @interface SVGKImage ()
  33. @property(nonatomic) CGSize internalSizeThatWasSetExplicitlyByUser;
  34. @property (nonatomic, strong, readwrite) SVGKParseResult* parseErrorsAndWarnings;
  35. @property (nonatomic, strong, readwrite) SVGKSource* source;
  36. @property (nonatomic, strong, readwrite) SVGDocument* DOMDocument;
  37. @property (nonatomic, strong, readwrite) SVGSVGElement* DOMTree; // needs renaming + (possibly) replacing by DOMDocument
  38. @property (nonatomic, strong, readwrite) CALayer* CALayerTree;
  39. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  40. @property (nonatomic, strong, readwrite) NSString* nameUsedToInstantiate;
  41. #endif
  42. #pragma mark - UIImage methods cloned and re-implemented as SVG intelligent methods
  43. //NOT DEFINED: what is the scale for a SVGKImage? @property(nonatomic,readwrite) CGFloat scale __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
  44. @end
  45. #pragma mark - main class
  46. @implementation SVGKImage
  47. @synthesize DOMDocument, DOMTree, CALayerTree;
  48. @synthesize scale = _scale;
  49. @synthesize source;
  50. @synthesize parseErrorsAndWarnings;
  51. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  52. @synthesize nameUsedToInstantiate = _nameUsedToInstantiate;
  53. static NSMutableDictionary* globalSVGKImageCache;
  54. #pragma mark - Respond to low-memory warnings by dumping the global static cache
  55. +(void) initialize
  56. {
  57. if( self == [SVGKImage class]) // Have to protect against subclasses ADDITIONALLY calling this, as a "[super initialize] line
  58. {
  59. #if SVGKIT_UIKIT
  60. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarningOrBackgroundNotification:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  61. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarningOrBackgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
  62. #endif
  63. }
  64. }
  65. +(void) clearCache {
  66. if ([globalSVGKImageCache count] == 0) return;
  67. SVGKitLogWarn(@"[%@] Low-mem, background or api clear; purging cache of %lu SVGKImages...", self, (unsigned long)[globalSVGKImageCache count] );
  68. [globalSVGKImageCache removeAllObjects]; // once they leave the cache, if they are no longer referred to, they should automatically dealloc
  69. }
  70. +(void) didReceiveMemoryWarningOrBackgroundNotification:(NSNotification*) notification
  71. {
  72. [self clearCache];
  73. }
  74. #endif
  75. #pragma mark - Convenience initializers
  76. + (SVGKImage *)imageNamed:(NSString *)name
  77. {
  78. return [self imageNamed:name inBundle:[NSBundle mainBundle] withCacheKey:@""];
  79. }
  80. + (SVGKImage *)imageNamed:(NSString *)name withCacheKey:(NSString *)key
  81. {
  82. return [self imageNamed:name inBundle:[NSBundle mainBundle] withCacheKey:key];
  83. }
  84. + (SVGKImage *)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle
  85. {
  86. return [self imageNamed:name inBundle:[NSBundle mainBundle] withCacheKey:@""];
  87. }
  88. + (SVGKImage *)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle withCacheKey:(NSString *)key
  89. {
  90. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  91. NSString* cacheName = [key length] > 0 ? key : name;
  92. if( globalSVGKImageCache == nil )
  93. {
  94. globalSVGKImageCache = [NSMutableDictionary new];
  95. }
  96. SVGKImageCacheLine* cacheLine = [globalSVGKImageCache valueForKey:cacheName];
  97. if( cacheLine != nil )
  98. {
  99. cacheLine.numberOfInstances ++;
  100. return cacheLine.mainInstance;
  101. }
  102. #endif
  103. SVGKSource *source = [SVGKSourceLocalFile internalSourceAnywhereInBundle:bundle usingName:name];
  104. /**
  105. Key moment: init and parse the SVGKImage
  106. */
  107. SVGKImage* result = [self imageWithSource:source];
  108. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  109. if( result != nil )
  110. {
  111. result->cameFromGlobalCache = TRUE;
  112. result.nameUsedToInstantiate = cacheName;
  113. SVGKImageCacheLine* newCacheLine = [[SVGKImageCacheLine alloc] init];
  114. newCacheLine.mainInstance = result;
  115. [globalSVGKImageCache setValue:newCacheLine forKey:cacheName];
  116. }
  117. else
  118. {
  119. NSLog(@"[%@] WARNING: not caching the output for new SVG image with name = %@, because it failed to load correctly", [self class], name );
  120. }
  121. #endif
  122. return result;
  123. }
  124. +(SVGKParser *) imageAsynchronouslyNamed:(NSString *)name onCompletion:(SVGKImageAsynchronousLoadingDelegate)blockCompleted
  125. {
  126. return [self imageWithSource:[SVGKSourceLocalFile internalSourceAnywhereInBundleUsingName:name] onCompletion:blockCompleted];
  127. }
  128. +(SVGKParser *) imageWithSource:(SVGKSource *)source onCompletion:(SVGKImageAsynchronousLoadingDelegate)blockCompleted
  129. {
  130. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  131. if( globalSVGKImageCache == nil )
  132. {
  133. globalSVGKImageCache = [NSMutableDictionary new];
  134. }
  135. SVGKImageCacheLine* cacheLine = [globalSVGKImageCache valueForKey:source.keyForAppleDictionaries];
  136. if( cacheLine != nil )
  137. {
  138. cacheLine.numberOfInstances ++;
  139. blockCompleted( cacheLine.mainInstance, /** (TODO: add a way for parse-results to chain each other, and say "I'm the cached version of this OTHER parseresult") original parse result: */ cacheLine.mainInstance.parseErrorsAndWarnings );
  140. return nil;
  141. }
  142. #endif
  143. /**
  144. Key moment: init and parse the SVGKImage
  145. */
  146. SVGKParser* parser = [SVGKParser newParserWithDefaultSVGKParserExtensions:source];
  147. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
  148. ^{
  149. SVGKParseResult* parsedSVG = [parser parseSynchronously];
  150. SVGKImage* finalImage = [[SVGKImage alloc] initWithParsedSVG:parsedSVG fromSource:source];
  151. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  152. if( finalImage != nil )
  153. {
  154. finalImage->cameFromGlobalCache = TRUE;
  155. finalImage.nameUsedToInstantiate = source.keyForAppleDictionaries;
  156. SVGKImageCacheLine* newCacheLine = [[SVGKImageCacheLine alloc] init];
  157. newCacheLine.mainInstance = finalImage;
  158. [globalSVGKImageCache setValue:newCacheLine forKey:source.keyForAppleDictionaries];
  159. }
  160. else
  161. {
  162. NSLog(@"[%@] WARNING: not caching the output for new SVG image with source = %@, because it failed to load correctly", [self class], source );
  163. }
  164. #endif
  165. blockCompleted( finalImage, parsedSVG );
  166. });
  167. return parser;
  168. }
  169. + (SVGKImage*) imageWithContentsOfURL:(NSURL *)url {
  170. NSParameterAssert(url != nil);
  171. @synchronized(self) {
  172. return [[[self class] alloc] initWithContentsOfURL:url];
  173. }
  174. }
  175. + (SVGKImage*) imageWithContentsOfFile:(NSString *)aPath {
  176. @synchronized(self) {
  177. return [[[self class] alloc] initWithContentsOfFile:aPath];
  178. }
  179. }
  180. + (SVGKParser*) imageParserWithContentsOfFileAsynchronously:(NSString *)aPath onCompletion:(SVGKImageAsynchronousLoadingDelegate)blockCompleted {
  181. return [self imageWithSource:[SVGKSourceLocalFile sourceFromFilename:aPath] onCompletion:blockCompleted];
  182. }
  183. + (SVGKImage*) imageWithContentsOfFileAsynchronously:(NSString *)aPath onCompletion:(SVGKImageAsynchronousLoadingDelegate)blockCompleted {
  184. __block SVGKImage *image;
  185. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  186. [self imageWithSource:[SVGKSourceLocalFile sourceFromFilename:aPath] onCompletion:^(SVGKImage *loadedImage, SVGKParseResult *parseResult) {
  187. image = loadedImage;
  188. dispatch_semaphore_signal(semaphore);
  189. }];
  190. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  191. return image;
  192. }
  193. + (SVGKImage*) imageWithSource:(SVGKSource *)newSource
  194. {
  195. NSParameterAssert(newSource != nil);
  196. @synchronized(self) {
  197. return [[[self class] alloc] initWithSource:newSource];
  198. }
  199. }
  200. + (SVGKImage*) imageWithData:(NSData *)newNSData
  201. {
  202. NSParameterAssert(newNSData != nil);
  203. @synchronized(self) {
  204. return [[[self class] alloc] initWithData:newNSData];
  205. }
  206. }
  207. + (SVGKParser*) imageParserWithDataAsynchronously:(NSData *)newNSData onCompletion:(SVGKImageAsynchronousLoadingDelegate)blockCompleted {
  208. NSParameterAssert(newNSData != nil);
  209. SVGKitLogWarn(@"Creating an SVG from raw data; this is not recommended: SVG requires knowledge of at least the URL where it came from (as it can contain relative file-links internally). You should use the method [SVGKImage initWithSource:] instead and specify an SVGKSource with more detail" );
  210. return [self imageWithSource:[SVGKSourceNSData sourceFromData:newNSData URLForRelativeLinks:nil] onCompletion:blockCompleted];
  211. }
  212. + (SVGKImage*) imageWithDataAsynchronously:(NSData *)newNSData onCompletion:(SVGKImageAsynchronousLoadingDelegate)blockCompleted
  213. {
  214. NSParameterAssert(newNSData != nil);
  215. SVGKitLogWarn(@"Creating an SVG from raw data; this is not recommended: SVG requires knowledge of at least the URL where it came from (as it can contain relative file-links internally). You should use the method [SVGKImage initWithSource:] instead and specify an SVGKSource with more detail" );
  216. __block SVGKImage *image;
  217. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  218. [self imageWithSource:[SVGKSourceNSData sourceFromData:newNSData URLForRelativeLinks:nil] onCompletion:^(SVGKImage *loadedImage, SVGKParseResult *parseResult) {
  219. image = loadedImage;
  220. dispatch_semaphore_signal(semaphore);
  221. }];
  222. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  223. return image;
  224. }
  225. -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  226. {
  227. /** Remove and release (if appropriate) all cached render-output */
  228. SVGKitLogVerbose(@"[%@] source data changed; de-caching cached data", [self class] );
  229. self.CALayerTree = nil;
  230. }
  231. /**
  232. Designated Initializer
  233. */
  234. - (id)initWithParsedSVG:(SVGKParseResult *)parseResult fromSource:(SVGKSource*) parseSource
  235. {
  236. self = [super init];
  237. if (self)
  238. {
  239. _internalSizeThatWasSetExplicitlyByUser = CGSizeZero; // mark it explicitly as "uninitialized" = this is important for the getSize method!
  240. _scale = 0.0; // flags it as uninitialized (this is important to know later, when outputting rendered layers)
  241. self.parseErrorsAndWarnings = parseResult;
  242. if( parseErrorsAndWarnings.parsedDocument != nil )
  243. {
  244. self.DOMDocument = parseErrorsAndWarnings.parsedDocument;
  245. self.DOMTree = DOMDocument.rootElement;
  246. self.source = parseSource;
  247. }
  248. else
  249. {
  250. self.DOMDocument = nil;
  251. self.DOMTree = nil;
  252. }
  253. if ( self.DOMDocument == nil )
  254. {
  255. SVGKitLogError(@"[%@] ERROR: failed to init SVGKImage with source = %@, returning nil from init methods. Parser warnings and errors = %@", [self class], parseSource, parseErrorsAndWarnings );
  256. self = nil;
  257. }
  258. [self addObserver:self forKeyPath:@"DOMTree.viewport" options:NSKeyValueObservingOptionOld context:nil];
  259. // [self.DOMTree addObserver:self forKeyPath:@"viewport" options:NSKeyValueObservingOptionOld context:nil];
  260. }
  261. return self;
  262. }
  263. - (id)initWithSource:(SVGKSource *)newSource {
  264. NSAssert( newSource != nil, @"Attempted to init an SVGKImage using a nil SVGKSource");
  265. self = [self initWithParsedSVG:[SVGKParser parseSourceUsingDefaultSVGKParser:newSource] fromSource:newSource];
  266. return self;
  267. }
  268. - (id)initWithContentsOfFile:(NSString *)aPath {
  269. NSParameterAssert(aPath != nil);
  270. return [self initWithSource:[SVGKSourceLocalFile sourceFromFilename:aPath]];
  271. }
  272. - (id)initWithContentsOfURL:(NSURL *)url {
  273. NSParameterAssert(url != nil);
  274. return [self initWithSource:[SVGKSourceURL sourceFromURL:url]];
  275. }
  276. - (id)initWithData:(NSData *)data
  277. {
  278. NSParameterAssert(data != nil);
  279. SVGKitLogWarn(@"Creating an SVG from raw data; this is not recommended: SVG requires knowledge of at least the URL where it came from (as it can contain relative file-links internally). You should use the method [SVGKImage initWithSource:] instead and specify an SVGKSource with more detail" );
  280. return [self initWithSource:[SVGKSourceNSData sourceFromData:data URLForRelativeLinks:nil]];
  281. }
  282. - (void)dealloc
  283. {
  284. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  285. if( self->cameFromGlobalCache )
  286. {
  287. SVGKImageCacheLine* cacheLine = [globalSVGKImageCache valueForKey:self.nameUsedToInstantiate];
  288. cacheLine.numberOfInstances --;
  289. if( cacheLine.numberOfInstances < 1 )
  290. {
  291. [globalSVGKImageCache removeObjectForKey:self.nameUsedToInstantiate];
  292. }
  293. }
  294. #endif
  295. //SOMETIMES CRASHES IN APPLE CODE, CAN'T WORK OUT WHY: [self removeObserver:self forKeyPath:@"DOMTree.viewport"];
  296. @try {
  297. [self removeObserver:self forKeyPath:@"DOMTree.viewport"];
  298. }
  299. @catch (NSException *exception) {
  300. SVGKitLogError(@"Exception removing DOMTree.viewport observer");
  301. }
  302. #if ENABLE_GLOBAL_IMAGE_CACHE_FOR_SVGKIMAGE_IMAGE_NAMED
  303. #endif
  304. }
  305. //TODO mac alternatives to UIKit functions
  306. #pragma mark - UIImage methods we reproduce to make it act like a UIImage
  307. -(BOOL) hasSize
  308. {
  309. if( ! CGSizeEqualToSize(CGSizeZero, self.internalSizeThatWasSetExplicitlyByUser ) )
  310. return true;
  311. if( SVGRectIsInitialized( self.DOMTree.viewport ) )
  312. return true;
  313. if( SVGRectIsInitialized( self.DOMTree.viewBox ) )
  314. return true;
  315. return false;
  316. }
  317. -(CGSize)size
  318. {
  319. /**
  320. c.f. http://t-machine.org/index.php/2013/04/13/svg-spec-missing-documentation-the-viewport-and-svg-width/
  321. 1. if we have an explicit size (something the user set), we return that; it overrides EVERYTHING else
  322. 2. otherwise ... if we have an INTERNAL viewport on the SVG, we return that
  323. 3. otherwise ... spec is UNDEFINED. If we have a viewbox, we return that (SVG spec defaults to 1 unit of viewbox = 1 pixel on screen)
  324. 4. otherwise ... spec is UNDEFINED. We have no viewbox, so we assume viewbox is "the bounding box of the entire SVG content, in SVG units", and use 3. above
  325. */
  326. /* 1. if we have an explicit size (something the user set), we return that; it overrides EVERYTHING else */
  327. if( ! CGSizeEqualToSize(CGSizeZero, self.internalSizeThatWasSetExplicitlyByUser ) )
  328. {
  329. return self.internalSizeThatWasSetExplicitlyByUser;
  330. }
  331. /* 2. otherwise ... if we have an INTERNAL viewport on the SVG, we return that */
  332. if( SVGRectIsInitialized( self.DOMTree.viewport ) )
  333. {
  334. return CGSizeFromSVGRect( self.DOMTree.viewport );
  335. }
  336. /* Calculate a viewbox, either the explicit one from 3. above, or the implicit one from 4. above
  337. */
  338. SVGRect effectiveViewbox;
  339. if( ! SVGRectIsInitialized( self.DOMTree.viewBox ) )
  340. {
  341. /**
  342. This is painful; the only way to calculate this is to recurse down the entire tree and find out the total extent
  343. of every item - taking into account all local and global transforms, etc
  344. We CANNOT USE the CALayerTree as a cheat to do this - because the CALayerTree itself uses the output of this method
  345. to decide how large to output itself!
  346. So, for now, we're going to NSAssert and crash, deliberately, until someone can write a better algorithm (without
  347. editing the source of all the SVG* classes, this is quite a lot of work, I think)
  348. */
  349. NSAssert(FALSE, @"Your SVG file has no internal size, and you have failed to specify a desired size. Therefore, we cannot give you a value for the 'image.size' property - you MUST use an SVG file that has a viewbox property, OR use an SVG file that defines an explicit svg width, OR provide a size of your own choosing (by setting image.size to a value) ... before you call this method" );
  350. effectiveViewbox = SVGRectUninitialized();
  351. }
  352. else
  353. effectiveViewbox = self.DOMTree.viewBox;
  354. /* COMBINED TOGETHER:
  355. 3. otherwise ... spec is UNDEFINED. If we have a viewbox, we return that (SVG spec defaults to 1 unit of viewbox = 1 pixel on screen)
  356. 4. otherwise ... spec is UNDEFINED. We have no viewbox, so we assume viewbox is "the bounding box of the entire SVG content, in SVG units", and use 3. above
  357. */
  358. return CGSizeFromSVGRect( effectiveViewbox );
  359. }
  360. -(void)setSize:(CGSize)newSize
  361. {
  362. self.internalSizeThatWasSetExplicitlyByUser = newSize;
  363. if( ! SVGRectIsInitialized(self.DOMTree.viewBox) && !SVGRectIsInitialized( self.DOMTree.viewport ) )
  364. {
  365. NSLog(@"[%@] WARNING: you have set an explicit image size, but your SVG file has no explicit width or height AND no viewBox. This means the image will NOT BE SCALED - either add a viewBox to your SVG source file, or add an explicit svg width and height -- or: use the .scale method on this class (SVGKImage) instead to scale by desired amount", [self class]);
  366. }
  367. /** "size" is part of SVGKImage, not the SVG spec; we need to update the SVG spec size too (aka the ViewPort)
  368. NB: in SVG world, the DOMTree.viewport is REQUIRED to be deleted if the "rendering agent" (i.e. this library)
  369. uses a different value for viewport.
  370. You can always re-calculate the "original" viewport by looking at self.DOMTree.width and self.DOMTree.height
  371. */
  372. self.DOMTree.viewport = SVGRectMake(0,0,newSize.width,newSize.height); // implicitly resizes all the internal rendering of the SVG
  373. /** invalidate all cached data that's dependent upon SVG's size */
  374. self.CALayerTree = nil; // invalidate the cached copy
  375. }
  376. -(void)setScale:(CGFloat)newScale
  377. {
  378. NSAssert( self.DOMTree != nil, @"Can't set a scale before you've parsed an SVG file; scale is sometimes illegal, depending on the SVG file itself");
  379. NSAssert( ! SVGRectIsInitialized( self.DOMTree.viewBox ), @"image.scale cannot be set because your SVG has an internal viewbox. To resize this SVG, you must instead call image.size = (a new size) to force the svg to scale itself up or down as appropriate");
  380. _scale = newScale;
  381. /** invalidate all cached data that's dependent upon SVG's size */
  382. self.CALayerTree = nil; // invalidate the cached copy
  383. }
  384. -(UIImage *)UIImage
  385. {
  386. #if SVGKIT_MAC
  387. return [SVGKExporterNSImage exportAsNSImage:self antiAliased:TRUE curveFlatnessFactor:1.0f interpolationQuality:kCGInterpolationDefault]; // Apple defaults
  388. #else
  389. return [SVGKExporterUIImage exportAsUIImage:self antiAliased:TRUE curveFlatnessFactor:1.0f interpolationQuality:kCGInterpolationDefault]; // Apple defaults
  390. #endif
  391. }
  392. // the these draw the image 'right side up' in the usual coordinate system with 'point' being the top-left.
  393. - (void)drawAtPoint:(CGPoint)point // mode = kCGBlendModeNormal, alpha = 1.0
  394. {
  395. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  396. }
  397. #pragma mark - unsupported / unimplemented UIImage methods (should add as a feature)
  398. - (void)drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha
  399. {
  400. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  401. }
  402. - (void)drawInRect:(CGRect)rect // mode = kCGBlendModeNormal, alpha = 1.0
  403. {
  404. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  405. }
  406. - (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha
  407. {
  408. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  409. }
  410. - (void)drawAsPatternInRect:(CGRect)rect // draws the image as a CGPattern
  411. // animated images. When set as UIImageView.image, animation will play in an infinite loop until removed. Drawing will render the first image
  412. {
  413. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  414. }
  415. #if SVGKIT_UIKIT
  416. + (UIImage *)animatedImageNamed:(NSString *)name duration:(NSTimeInterval)duration // read sequnce of files with suffix starting at 0 or 1
  417. {
  418. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  419. return nil;
  420. }
  421. + (UIImage *)animatedResizableImageNamed:(NSString *)name capInsets:(UIEdgeInsets)capInsets duration:(NSTimeInterval)duration // squence of files
  422. {
  423. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  424. return nil;
  425. }
  426. + (UIImage *)animatedImageWithImages:(NSArray *)images duration:(NSTimeInterval)duration
  427. {
  428. NSAssert( FALSE, @"Method unsupported / not yet implemented by SVGKit" );
  429. return nil;
  430. }
  431. #endif
  432. #pragma mark - CALayer methods: generate the CALayerTree
  433. - (CALayer *)layerWithIdentifier:(NSString *)identifier
  434. {
  435. return [self layerWithIdentifier:identifier layer:self.CALayerTree];
  436. }
  437. - (CALayer *)layerWithIdentifier:(NSString *)identifier layer:(CALayer *)layer {
  438. if ([[layer valueForKey:kSVGElementIdentifier] isEqualToString:identifier]) {
  439. return layer;
  440. }
  441. for (CALayer *child in layer.sublayers) {
  442. CALayer *resultingLayer = [self layerWithIdentifier:identifier layer:child];
  443. if (resultingLayer)
  444. return resultingLayer;
  445. }
  446. return nil;
  447. }
  448. -(CALayer*) newCopyPositionedAbsoluteLayerWithIdentifier:(NSString *)identifier
  449. {
  450. NSAssert( identifier != nil, @"Requested the layer with NIL identifier - your calling method is broken and should check its arguments more carefully");
  451. CALayer* originalLayer = [self layerWithIdentifier:identifier];
  452. if( originalLayer == nil )
  453. {
  454. SVGKitLogError(@"[%@] ERROR: requested a clone of CALayer with id = %@, but there is no layer with that identifier in the parsed SVG layer stack", [self class], identifier );
  455. return nil;
  456. }
  457. else
  458. return [self newCopyPositionedAbsoluteOfLayer:originalLayer];
  459. }
  460. -(CALayer*) newCopyPositionedAbsoluteOfLayer:(CALayer *)originalLayer
  461. {
  462. return [self newCopyPositionedAbsoluteOfLayer:originalLayer withSubLayers:FALSE];
  463. }
  464. -(CALayer*) newCopyPositionedAbsoluteOfLayer:(CALayer *)originalLayer withSubLayers:(BOOL) recursive
  465. {
  466. /*CALayer* clonedLayer = [[[originalLayer class] alloc] init];
  467. clonedLayer.frame = originalLayer.frame;
  468. if( [originalLayer isKindOfClass:[CAShapeLayer class]] )
  469. {
  470. ((CAShapeLayer*)clonedLayer).path = ((CAShapeLayer*)originalLayer).path;
  471. ((CAShapeLayer*)clonedLayer).lineCap = ((CAShapeLayer*)originalLayer).lineCap;
  472. ((CAShapeLayer*)clonedLayer).lineWidth = ((CAShapeLayer*)originalLayer).lineWidth;
  473. ((CAShapeLayer*)clonedLayer).strokeColor = ((CAShapeLayer*)originalLayer).strokeColor;
  474. ((CAShapeLayer*)clonedLayer).fillColor = ((CAShapeLayer*)originalLayer).fillColor;
  475. }*/
  476. CALayer* clonedLayer = recursive ? [originalLayer cloneRecursively] : [originalLayer cloneShallow];
  477. if( clonedLayer == nil )
  478. return nil;
  479. else
  480. {
  481. /** CALayer has the magic valueForKey method */
  482. NSString* layerID = [originalLayer valueForKey:kSVGElementIdentifier];
  483. if( layerID != nil )
  484. [clonedLayer setValue:layerID forKey:kSVGElementIdentifier];
  485. CGRect lFrame = clonedLayer.frame;
  486. CGFloat xOffset = 0.0;
  487. CGFloat yOffset = 0.0;
  488. CALayer* currentLayer = originalLayer;
  489. if( currentLayer.superlayer == nil )
  490. {
  491. SVGKitLogWarn(@"AWOOGA: layer %@ has no superlayer!", originalLayer );
  492. }
  493. while( currentLayer.superlayer != nil )
  494. {
  495. //DEBUG: SVGKitLogVerbose(@"shifting (%2.2f, %2.2f) to accomodate offset of layer = %@ inside superlayer = %@", currentLayer.superlayer.frame.origin.x, currentLayer.superlayer.frame.origin.y, currentLayer, currentLayer.superlayer );
  496. currentLayer = currentLayer.superlayer;
  497. //DEBUG: SVGKitLogVerbose(@"...next superlayer in positioning absolute = %@, %@", currentLayer, NSStringFromCGRect(currentLayer.frame));
  498. xOffset += currentLayer.frame.origin.x;
  499. yOffset += currentLayer.frame.origin.y;
  500. }
  501. lFrame.origin = CGPointMake( lFrame.origin.x + xOffset, lFrame.origin.y + yOffset );
  502. clonedLayer.frame = lFrame;
  503. return clonedLayer;
  504. }
  505. }
  506. - (CALayer *)newLayerWithElement:(SVGElement <ConverterSVGToCALayer> *)element
  507. {
  508. CALayer *layer = [element newLayer];
  509. layer.hidden = ![self isElementVisible:element];
  510. //DEBUG: SVGKitLogVerbose(@"[%@] DEBUG: converted SVG element (class:%@) to CALayer (class:%@ frame:%@ pointer:%@) for id = %@", [self class], NSStringFromClass([element class]), NSStringFromClass([layer class]), NSStringFromCGRect( layer.frame ), layer, element.identifier);
  511. NodeList* childNodes = element.childNodes;
  512. Node* saveParentNode = nil;
  513. /**
  514. Special handling for <use> tags - they have to masquerade invisibly as the node they are referring to
  515. */
  516. if( [element isKindOfClass:[SVGUseElement class]] )
  517. {
  518. SVGUseElement* useElement = (SVGUseElement*) element;
  519. element = (SVGElement <ConverterSVGToCALayer> *)useElement.instanceRoot.correspondingElement;
  520. saveParentNode = element.parentNode;
  521. element.parentNode = useElement;
  522. NodeList* nodeList = [[NodeList alloc] init];
  523. [nodeList.internalArray addObject:element];
  524. childNodes = nodeList;
  525. }
  526. else
  527. if ( [element isKindOfClass:[SVGSwitchElement class]] )
  528. {
  529. childNodes = [(SVGSwitchElement*) element visibleChildNodes];
  530. }
  531. /**
  532. Special handling for clip-path; need to create their children
  533. */
  534. NSString* clipPath = [element cascadedValueForStylableProperty:@"clip-path" inherit:NO];
  535. if ( [clipPath hasPrefix:@"url"] )
  536. {
  537. NSRange idKeyRange = NSMakeRange(5, clipPath.length - 6);
  538. NSString* _pathId = [clipPath substringWithRange:idKeyRange];
  539. /** Replace the return layer with a special layer using the URL fill */
  540. /** fetch the fill layer by URL using the DOM */
  541. NSAssert( element.rootOfCurrentDocumentFragment != nil, @"This SVG shape has a URL clip-path type; it needs to search for that URL (%@) inside its nearest-ancestor <SVG> node, but the rootOfCurrentDocumentFragment reference was nil (suggests the parser failed, or the SVG file is corrupt)", _pathId );
  542. SVGClipPathElement* clipPathElement = (SVGClipPathElement*) [element.rootOfCurrentDocumentFragment getElementById:_pathId];
  543. NSAssert( clipPathElement != nil, @"This SVG shape has a URL clip-path (%@), but could not find an XML Node with that ID inside the DOM tree (suggests the parser failed, or the SVG file is corrupt)", _pathId );
  544. CALayer *clipLayer = [clipPathElement newLayer];
  545. for (SVGElement *child in clipPathElement.childNodes )
  546. {
  547. if ([child conformsToProtocol:@protocol(ConverterSVGToCALayer)]) {
  548. CALayer *sublayer = [self newLayerWithElement:(SVGElement<ConverterSVGToCALayer> *)child];
  549. if (!sublayer) {
  550. continue;
  551. }
  552. [clipLayer addSublayer:sublayer];
  553. }
  554. }
  555. [clipPathElement layoutLayer:clipLayer toMaskLayer:layer];
  556. SVGKitLogWarn(@"DOESNT WORK, APPLE's API APPEARS BROKEN???? - About to mask layer frame (%@) with a mask of frame (%@)", NSStringFromCGRect(layer.frame), NSStringFromCGRect(clipLayer.frame));
  557. layer.mask = clipLayer;
  558. // because it was created with a +1 retain count
  559. }
  560. /**
  561. Generate child nodes and then re-layout
  562. (parent may have to change its size to fit children)
  563. */
  564. NSUInteger sublayerCount = 0;
  565. for (SVGElement *child in childNodes )
  566. {
  567. if ([child conformsToProtocol:@protocol(ConverterSVGToCALayer)]) {
  568. CALayer *sublayer = [self newLayerWithElement:(SVGElement<ConverterSVGToCALayer> *)child];
  569. if (saveParentNode) { // Use element (offsets) adjust sublayers. TODO: Size adjustment.
  570. CGRect lFrame = sublayer.frame; // https://github.com/SVGKit/SVGKit/issues/384#issuecomment-159151069
  571. lFrame.origin = CGPointMake( lFrame.origin.x + layer.frame.origin.x, lFrame.origin.y + layer.frame.origin.y );
  572. sublayer.frame = lFrame;
  573. //DEBUG SVGKitLogVerbose(@"\t[%@] DEBUG: childern (class:%@) to CALayer (class:%@ frame:%@ pointer:%@) for id = %@", [self class], NSStringFromClass([child class]), NSStringFromClass([sublayer class]), NSStringFromCGRect( sublayer.frame ), sublayer, child.identifier);
  574. }
  575. if (!sublayer) {
  576. continue;
  577. }
  578. sublayerCount++;
  579. [layer addSublayer:sublayer];
  580. }
  581. }
  582. if (saveParentNode)
  583. element.parentNode = saveParentNode;
  584. /**
  585. If none of the child nodes return a CALayer, we're safe to early-out here (and in fact we need to because
  586. calling setNeedsDisplay on an image layer hides the image). We can't just check childNodes.count because
  587. there may be some nodes like whitespace nodes for which we don't create layers.
  588. */
  589. if ( sublayerCount < 1 ) {
  590. return layer;
  591. }
  592. /** ...relayout */
  593. /**
  594. NOTE:
  595. This call (layoutLayer:), and the fact that we call it directly on the "ConverterSVGToCALayer" instance,
  596. is critical to ensuring that SVG <g> tags generate correctly sized/shaped/positioned CALayer's.
  597. It is not used for any other class / SVG Element.
  598. It's only needed by G elements because they have no explicit size, and their extent is defined by
  599. "all the space occupied by my children"
  600. If you refactor this method, or CALayer exporting, please make sure you keep the current behaviour. You can
  601. test it by:
  602. 1. Make an SVG file with a G element wrapping some shape in the middle of screen
  603. 2. Load the file
  604. 3. Select the CALayer for the shape, and clone it (using the category for CAShape in this project)
  605. 4. add the clone to the screen, with its CALayer.position set to 0,0
  606. 5. If the code is correct, it will be positioned in top left corner.
  607. 6. If the code is broken, it will be positioned somewhere in the middle of the screen (probably directly on top of the one you cloned)
  608. --- i.e. you've accidentally embedded the "relative position" into the "absolute position" of the CALayer
  609. */
  610. [element layoutLayer:layer];
  611. [layer setNeedsDisplay];
  612. return layer;
  613. }
  614. -(CALayer *)newCALayerTree
  615. {
  616. if( self.DOMTree == nil )
  617. return nil;
  618. else
  619. {
  620. CALayer* newLayerTree = [self newLayerWithElement:self.DOMTree];
  621. if( 0.0f != self.scale )
  622. {
  623. NSLog(@"[%@] WARNING: because you specified an image.scale (you SHOULD be using SVG viewbox or <svg width> instead!), we are changing the .anchorPoint and the .affineTransform of the returned CALayerTree. Apple's own libraries are EXTREMELY BUGGY if you hand them layers that have these variables changed (some of Apple's libraries completely ignore them, this is a major Known Bug that Apple hasn't fixed in many years). Proceed at your own risk, and warned!", [self class] );
  624. /** Apple's bugs in CALayer are legion, and some have been around for almost 10 years...
  625. When you set the affineTransform on a Layer, if you do not ALSO MANUALLY change the anchorpoint, Apple
  626. renders the layer at the wrong co-ords.
  627. */
  628. newLayerTree.anchorPoint = CGPointApplyAffineTransform( newLayerTree.anchorPoint, CGAffineTransformMakeScale(1.0f/self.scale, 1.0f/self.scale));
  629. newLayerTree.affineTransform = CGAffineTransformMakeScale( self.scale, self.scale );
  630. }
  631. return newLayerTree;
  632. }
  633. }
  634. -(CALayer *)CALayerTree
  635. {
  636. if( CALayerTree == nil )
  637. {
  638. SVGKitLogInfo(@"[%@] WARNING: no CALayer tree found, creating a new one (will cache it once generated)", [self class] );
  639. NSDate* startTime = [NSDate date];
  640. self.CALayerTree = [self newCALayerTree];
  641. SVGKitLogInfo(@"[%@] ...time taken to convert from DOM to fresh CALayers: %2.3f seconds)", [self class], -1.0f * [startTime timeIntervalSinceNow] );
  642. }
  643. else
  644. SVGKitLogVerbose(@"[%@] fetching CALayerTree: re-using cached CALayers (FREE))", [self class] );
  645. return CALayerTree;
  646. }
  647. - (void) addSVGLayerTree:(CALayer*) layer withIdentifier:(NSString*) layerID toDictionary:(NSMutableDictionary*) layersByID
  648. {
  649. // TODO: consider removing this method: it caches the lookup of individual items in the CALayerTree. It's a performance boost, but is it enough to be worthwhile?
  650. [layersByID setValue:layer forKey:layerID];
  651. if ( [layer.sublayers count] < 1 )
  652. {
  653. return;
  654. }
  655. for (CALayer *subLayer in layer.sublayers)
  656. {
  657. NSString* subLayerID = [subLayer valueForKey:kSVGElementIdentifier];
  658. if( subLayerID != nil )
  659. {
  660. SVGKitLogVerbose(@"[%@] element id: %@ => layer: %@", [self class], subLayerID, subLayer);
  661. [self addSVGLayerTree:subLayer withIdentifier:subLayerID toDictionary:layersByID];
  662. }
  663. }
  664. }
  665. - (NSDictionary*) dictionaryOfLayers
  666. {
  667. // TODO: consider removing this method: it caches the lookup of individual items in the CALayerTree. It's a performance boost, but is it enough to be worthwhile?
  668. NSMutableDictionary* layersByElementId = [NSMutableDictionary dictionary];
  669. CALayer* rootLayer = self.CALayerTree;
  670. [self addSVGLayerTree:rootLayer withIdentifier:self.DOMTree.identifier toDictionary:layersByElementId];
  671. SVGKitLogVerbose(@"[%@] ROOT element id: %@ => layer: %@", [self class], self.DOMTree.identifier, rootLayer);
  672. return layersByElementId;
  673. }
  674. #pragma mark - Useful bonus methods, will probably move to a different class at some point
  675. -(void) scaleToFitInside:(CGSize) maxSize
  676. {
  677. NSAssert( [self hasSize], @"Cannot scale this image because the SVG file has infinite size. Either fix the SVG file, or set an explicit size you want it to be exported at (by calling .size = something on this SVGKImage instance");
  678. float wScale = maxSize.width / self.size.width;
  679. float hScale = maxSize.height / self.size.height;
  680. float smallestScaleUp = MIN( wScale, hScale );
  681. if( smallestScaleUp < 1.0f )
  682. smallestScaleUp = MAX( wScale, hScale ); // instead of scaling-up the smallest, scale-down the largest
  683. self.size = CGSizeApplyAffineTransform( self.size, CGAffineTransformMakeScale( smallestScaleUp, smallestScaleUp));
  684. }
  685. -(BOOL) isElementVisible:(SVGElement *) element
  686. {
  687. NSString *display = [element cascadedValueForStylableProperty:@"display" inherit:NO];
  688. if( [display isEqualToString:@"none"] )
  689. return NO;
  690. NSString *visibility = [element cascadedValueForStylableProperty:@"visibility" inherit:NO];
  691. if( [visibility isEqualToString:@"hidden"] )
  692. return NO;
  693. return YES;
  694. }
  695. @end