SVGKImage.m 34 KB

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