1#import "NSCoderExtensions.h" 2#import "NSNumberExtensions.h" 3#import "CPTUtilities.h" 4 5void MyCGPathApplierFunc(void *info, const CGPathElement *element); 6 7@implementation NSCoder(CPTExtensions) 8 9#pragma mark - 10#pragma mark Encoding 11 12/** @brief Encodes a CGFloat and associates it with the string key. 13 * @param number The number to encode. 14 * @param key The key to associate with the number. 15 **/ 16-(void)encodeCGFloat:(CGFloat)number forKey:(NSString *)key 17{ 18#if CGFLOAT_IS_DOUBLE 19 [self encodeDouble:number forKey:key]; 20#else 21 [self encodeFloat:number forKey:key]; 22#endif 23} 24 25/** @brief Encodes a point and associates it with the string key. 26 * @param point The point to encode. 27 * @param key The key to associate with the point. 28 **/ 29-(void)encodeCPTPoint:(CGPoint)point forKey:(NSString *)key 30{ 31 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.x", key]; 32 [self encodeCGFloat:point.x forKey:newKey]; 33 [newKey release]; 34 35 newKey = [[NSString alloc] initWithFormat:@"%@.y", key]; 36 [self encodeCGFloat:point.y forKey:newKey]; 37 [newKey release]; 38} 39 40/** @brief Encodes a size and associates it with the string key. 41 * @param size The size to encode. 42 * @param key The key to associate with the number. 43 **/ 44-(void)encodeCPTSize:(CGSize)size forKey:(NSString *)key 45{ 46 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.width", key]; 47 [self encodeCGFloat:size.width forKey:newKey]; 48 [newKey release]; 49 50 newKey = [[NSString alloc] initWithFormat:@"%@.height", key]; 51 [self encodeCGFloat:size.height forKey:newKey]; 52 [newKey release]; 53} 54 55/** @brief Encodes a rectangle and associates it with the string key. 56 * @param rect The rectangle to encode. 57 * @param key The key to associate with the rectangle. 58 **/ 59-(void)encodeCPTRect:(CGRect)rect forKey:(NSString *)key 60{ 61 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.origin", key]; 62 [self encodeCPTPoint:rect.origin forKey:newKey]; 63 [newKey release]; 64 65 newKey = [[NSString alloc] initWithFormat:@"%@.size", key]; 66 [self encodeCPTSize:rect.size forKey:newKey]; 67 [newKey release]; 68} 69 70/** @brief Encodes a color space and associates it with the string key. 71 * @param colorSpace The CGColorSpaceRef to encode. 72 * @param key The key to associate with the color space. 73 * @note The current implementation only works with named color spaces. 74 **/ 75-(void)encodeCGColorSpace:(CGColorSpaceRef)colorSpace forKey:(NSString *)key 76{ 77#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 78 NSLog(@"Color space encoding is not supported on iOS. Decoding will return a generic RGB color space."); 79#else 80 if ( colorSpace ) { 81 CFDataRef iccProfile = CGColorSpaceCopyICCProfile(colorSpace); 82 [self encodeObject:(NSData *)iccProfile forKey:key]; 83 CFRelease(iccProfile); 84 } 85#endif 86} 87 88void MyCGPathApplierFunc(void *info, const CGPathElement *element) 89{ 90 NSMutableDictionary *elementData = [[NSMutableDictionary alloc] init]; 91 92 [elementData setObject:[NSNumber numberWithInt:element->type] forKey:@"type"]; 93 94 switch ( element->type ) { 95 case kCGPathElementAddCurveToPoint: // 3 points 96 [elementData setObject:[NSNumber numberWithCGFloat:element->points[2].x] forKey:@"point3.x"]; 97 [elementData setObject:[NSNumber numberWithCGFloat:element->points[2].y] forKey:@"point3.y"]; 98 99 case kCGPathElementAddQuadCurveToPoint: // 2 points 100 [elementData setObject:[NSNumber numberWithCGFloat:element->points[1].x] forKey:@"point2.x"]; 101 [elementData setObject:[NSNumber numberWithCGFloat:element->points[1].y] forKey:@"point2.y"]; 102 103 case kCGPathElementMoveToPoint: // 1 point 104 case kCGPathElementAddLineToPoint: // 1 point 105 [elementData setObject:[NSNumber numberWithCGFloat:element->points[0].x] forKey:@"point1.x"]; 106 [elementData setObject:[NSNumber numberWithCGFloat:element->points[0].y] forKey:@"point1.y"]; 107 break; 108 109 case kCGPathElementCloseSubpath: // 0 points 110 break; 111 112 default: 113 // unknown element type 114 break; 115 } 116 117 NSMutableArray *pathData = (NSMutableArray *)info; 118 [pathData addObject:elementData]; 119 120 [elementData release]; 121} 122 123/** @brief Encodes a path and associates it with the string key. 124 * @param path The CGPathRef to encode. 125 * @param key The key to associate with the path. 126 **/ 127-(void)encodeCGPath:(CGPathRef)path forKey:(NSString *)key 128{ 129 NSMutableArray *pathData = [[NSMutableArray alloc] init]; 130 131 // walk the path and gather data for each element 132 CGPathApply(path, pathData, &MyCGPathApplierFunc); 133 134 // encode data count 135 NSUInteger dataCount = pathData.count; 136 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.count", key]; 137 [self encodeInteger:dataCount forKey:newKey]; 138 [newKey release]; 139 140 // encode data elements 141 for ( NSUInteger i = 0; i < dataCount; i++ ) { 142 NSDictionary *elementData = [pathData objectAtIndex:i]; 143 144 CGPathElementType type = [[elementData objectForKey:@"type"] intValue]; 145 newKey = [[NSString alloc] initWithFormat:@"%@[%u].type", key, i]; 146 [self encodeInteger:type forKey:newKey]; 147 [newKey release]; 148 149 CGPoint point; 150 151 switch ( type ) { 152 case kCGPathElementAddCurveToPoint: // 3 points 153 point.x = [[elementData objectForKey:@"point3.x"] cgFloatValue]; 154 point.y = [[elementData objectForKey:@"point3.y"] cgFloatValue]; 155 newKey = [[NSString alloc] initWithFormat:@"%@[%u].point3", key, i]; 156 [self encodeCPTPoint:point forKey:newKey]; 157 [newKey release]; 158 159 case kCGPathElementAddQuadCurveToPoint: // 2 points 160 point.x = [[elementData objectForKey:@"point2.x"] cgFloatValue]; 161 point.y = [[elementData objectForKey:@"point2.y"] cgFloatValue]; 162 newKey = [[NSString alloc] initWithFormat:@"%@[%u].point2", key, i]; 163 [self encodeCPTPoint:point forKey:newKey]; 164 [newKey release]; 165 166 case kCGPathElementMoveToPoint: // 1 point 167 case kCGPathElementAddLineToPoint: // 1 point 168 point.x = [[elementData objectForKey:@"point1.x"] cgFloatValue]; 169 point.y = [[elementData objectForKey:@"point1.y"] cgFloatValue]; 170 newKey = [[NSString alloc] initWithFormat:@"%@[%u].point1", key, i]; 171 [self encodeCPTPoint:point forKey:newKey]; 172 [newKey release]; 173 break; 174 175 case kCGPathElementCloseSubpath: // 0 points 176 break; 177 } 178 } 179 180 [pathData release]; 181} 182 183/** @brief Encodes an image and associates it with the string key. 184 * @param image The CGImageRef to encode. 185 * @param key The key to associate with the image. 186 **/ 187-(void)encodeCGImage:(CGImageRef)image forKey:(NSString *)key 188{ 189 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.width", key]; 190 [self encodeInteger:CGImageGetWidth(image) forKey:newKey]; 191 [newKey release]; 192 193 newKey = [[NSString alloc] initWithFormat:@"%@.height", key]; 194 [self encodeInteger:CGImageGetHeight(image) forKey:newKey]; 195 [newKey release]; 196 197 newKey = [[NSString alloc] initWithFormat:@"%@.bitsPerComponent", key]; 198 [self encodeInteger:CGImageGetBitsPerComponent(image) forKey:newKey]; 199 [newKey release]; 200 201 newKey = [[NSString alloc] initWithFormat:@"%@.bitsPerPixel", key]; 202 [self encodeInteger:CGImageGetBitsPerPixel(image) forKey:newKey]; 203 [newKey release]; 204 205 newKey = [[NSString alloc] initWithFormat:@"%@.bytesPerRow", key]; 206 [self encodeInteger:CGImageGetBytesPerRow(image) forKey:newKey]; 207 [newKey release]; 208 209 newKey = [[NSString alloc] initWithFormat:@"%@.colorSpace", key]; 210 CGColorSpaceRef colorSpace = CGImageGetColorSpace(image); 211 [self encodeCGColorSpace:colorSpace forKey:newKey]; 212 [newKey release]; 213 214 newKey = [[NSString alloc] initWithFormat:@"%@.bitmapInfo", key]; 215 [self encodeInteger:CGImageGetBitmapInfo(image) forKey:newKey]; 216 [newKey release]; 217 218 CGDataProviderRef provider = CGImageGetDataProvider(image); 219 CFDataRef providerData = CGDataProviderCopyData(provider); 220 newKey = [[NSString alloc] initWithFormat:@"%@.provider", key]; 221 [self encodeObject:(NSData *)providerData forKey:newKey]; 222 if ( providerData ) { 223 CFRelease(providerData); 224 } 225 [newKey release]; 226 227 const CGFloat * decodeArray = CGImageGetDecode(image); 228 if ( decodeArray ) { 229 size_t numberOfComponents = CGColorSpaceGetNumberOfComponents(colorSpace); 230 newKey = [[NSString alloc] initWithFormat:@"%@.numberOfComponents", key]; 231 [self encodeInteger:numberOfComponents forKey:newKey]; 232 [newKey release]; 233 234 for ( size_t i = 0; i < numberOfComponents; i++ ) { 235 newKey = [[NSString alloc] initWithFormat:@"%@.decode[%u].lower", key, i]; 236 [self encodeCGFloat:decodeArray[i * 2] forKey:newKey]; 237 [newKey release]; 238 239 newKey = [[NSString alloc] initWithFormat:@"%@.decode[%u].upper", key, i]; 240 [self encodeCGFloat:decodeArray[i * 2 + 1] forKey:newKey]; 241 [newKey release]; 242 } 243 } 244 245 newKey = [[NSString alloc] initWithFormat:@"%@.shouldInterpolate", key]; 246 [self encodeBool:CGImageGetShouldInterpolate(image) forKey:newKey]; 247 [newKey release]; 248 249 newKey = [[NSString alloc] initWithFormat:@"%@.renderingIntent", key]; 250 [self encodeInteger:CGImageGetRenderingIntent(image) forKey:newKey]; 251 [newKey release]; 252} 253 254/** @brief Encodes an NSDecimal and associates it with the string key. 255 * @param number The number to encode. 256 * @param key The key to associate with the number. 257 **/ 258-(void)encodeDecimal:(NSDecimal)number forKey:(NSString *)key 259{ 260 [self encodeObject:[NSDecimalNumber decimalNumberWithDecimal:number] forKey:key]; 261} 262 263#pragma mark - 264#pragma mark Decoding 265 266/** @brief Decodes and returns a number that was previously encoded with encodeCGFloat:forKey: and associated with the string key. 267 * @param key The key associated with the number. 268 * @return The number as a CGFloat. 269 **/ 270-(CGFloat)decodeCGFloatForKey:(NSString *)key 271{ 272#if CGFLOAT_IS_DOUBLE 273 return [self decodeDoubleForKey:key]; 274#else 275 return [self decodeFloatForKey:key]; 276#endif 277} 278 279/** @brief Decodes and returns a point that was previously encoded with encodeCPTPoint:forKey: and associated with the string key. 280 * @param key The key associated with the point. 281 * @return The point. 282 **/ 283-(CGPoint)decodeCPTPointForKey:(NSString *)key 284{ 285 CGPoint point; 286 287 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.x", key]; 288 point.x = [self decodeCGFloatForKey:newKey]; 289 [newKey release]; 290 291 newKey = [[NSString alloc] initWithFormat:@"%@.y", key]; 292 point.y = [self decodeCGFloatForKey:newKey]; 293 [newKey release]; 294 295 return point; 296} 297 298/** @brief Decodes and returns a size that was previously encoded with encodeCPTSize:forKey: and associated with the string key. 299 * @param key The key associated with the size. 300 * @return The size. 301 **/ 302-(CGSize)decodeCPTSizeForKey:(NSString *)key 303{ 304 CGSize size; 305 306 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.width", key]; 307 size.width = [self decodeCGFloatForKey:newKey]; 308 [newKey release]; 309 310 newKey = [[NSString alloc] initWithFormat:@"%@.height", key]; 311 size.height = [self decodeCGFloatForKey:newKey]; 312 [newKey release]; 313 314 return size; 315} 316 317/** @brief Decodes and returns a rectangle that was previously encoded with encodeCPTRect:forKey: and associated with the string key. 318 * @param key The key associated with the rectangle. 319 * @return The rectangle. 320 **/ 321-(CGRect)decodeCPTRectForKey:(NSString *)key; 322{ 323 CGRect rect; 324 325 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.origin", key]; 326 rect.origin = [self decodeCPTPointForKey:newKey]; 327 [newKey release]; 328 329 newKey = [[NSString alloc] initWithFormat:@"%@.size", key]; 330 rect.size = [self decodeCPTSizeForKey:newKey]; 331 [newKey release]; 332 333 return rect; 334} 335 336/** @brief Decodes and returns an new color space object that was previously encoded with encodeCGColorSpace:forKey: and associated with the string key. 337 * @param key The key associated with the color space. 338 * @return The new path. 339 * @note The current implementation only works with named color spaces. 340 **/ 341-(CGColorSpaceRef)newCGColorSpaceDecodeForKey:(NSString *)key 342{ 343 CGColorSpaceRef colorSpace = NULL; 344 345#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 346 NSLog(@"Color space decoding is not supported on iOS. Using generic RGB color space."); 347 colorSpace = CGColorSpaceCreateDeviceRGB(); 348#else 349 NSData *iccProfile = [self decodeObjectForKey:key]; 350 if ( iccProfile ) { 351 colorSpace = CGColorSpaceCreateWithICCProfile((CFDataRef)iccProfile); 352 } 353 else { 354 NSLog(@"Color space not available for key '%@'. Using generic RGB color space.", key); 355 colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); 356 } 357#endif 358 359 return colorSpace; 360} 361 362/** @brief Decodes and returns an new path object that was previously encoded with encodeCGPath:forKey: and associated with the string key. 363 * @param key The key associated with the path. 364 * @return The new path. 365 **/ 366-(CGPathRef)newCGPathDecodeForKey:(NSString *)key 367{ 368 CGMutablePathRef newPath = CGPathCreateMutable(); 369 370 // decode count 371 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.count", key]; 372 NSUInteger count = [self decodeIntegerForKey:newKey]; 373 [newKey release]; 374 375 // decode elements 376 for ( NSUInteger i = 0; i < count; i++ ) { 377 newKey = [[NSString alloc] initWithFormat:@"%@[%u].type", key, i]; 378 CGPathElementType type = [self decodeIntegerForKey:newKey]; 379 [newKey release]; 380 381 CGPoint point1, point2, point3; 382 383 switch ( type ) { 384 case kCGPathElementAddCurveToPoint: // 3 points 385 newKey = [[NSString alloc] initWithFormat:@"%@[%u].point3", key, i]; 386 point3 = [self decodeCPTPointForKey:newKey]; 387 [newKey release]; 388 389 case kCGPathElementAddQuadCurveToPoint: // 2 points 390 newKey = [[NSString alloc] initWithFormat:@"%@[%u].point2", key, i]; 391 point2 = [self decodeCPTPointForKey:newKey]; 392 [newKey release]; 393 394 case kCGPathElementMoveToPoint: // 1 point 395 case kCGPathElementAddLineToPoint: // 1 point 396 newKey = [[NSString alloc] initWithFormat:@"%@[%u].point1", key, i]; 397 point1 = [self decodeCPTPointForKey:newKey]; 398 [newKey release]; 399 break; 400 401 case kCGPathElementCloseSubpath: // 0 points 402 break; 403 404 default: 405 // unknown element type 406 break; 407 } 408 409 switch ( type ) { 410 case kCGPathElementMoveToPoint: 411 CGPathMoveToPoint(newPath, NULL, point1.x, point1.y); 412 break; 413 414 case kCGPathElementAddLineToPoint: 415 CGPathAddLineToPoint(newPath, NULL, point1.x, point1.y); 416 break; 417 418 case kCGPathElementAddQuadCurveToPoint: 419 CGPathAddQuadCurveToPoint(newPath, NULL, point1.x, point1.y, point2.x, point2.y); 420 break; 421 422 case kCGPathElementAddCurveToPoint: 423 CGPathAddCurveToPoint(newPath, NULL, point1.x, point1.y, point2.x, point2.y, point3.x, point3.y); 424 break; 425 426 case kCGPathElementCloseSubpath: 427 CGPathCloseSubpath(newPath); 428 break; 429 430 default: 431 // unknown element type 432 break; 433 } 434 } 435 436 return newPath; 437} 438 439/** @brief Decodes and returns an new image object that was previously encoded with encodeCGImage:forKey: and associated with the string key. 440 * @param key The key associated with the image. 441 * @return The new image. 442 **/ 443-(CGImageRef)newCGImageDecodeForKey:(NSString *)key 444{ 445 NSString *newKey = [[NSString alloc] initWithFormat:@"%@.width", key]; 446 size_t width = [self decodeIntegerForKey:newKey]; 447 [newKey release]; 448 449 newKey = [[NSString alloc] initWithFormat:@"%@.height", key]; 450 size_t height = [self decodeIntegerForKey:newKey]; 451 [newKey release]; 452 453 newKey = [[NSString alloc] initWithFormat:@"%@.bitsPerComponent", key]; 454 size_t bitsPerComponent = [self decodeIntegerForKey:newKey]; 455 [newKey release]; 456 457 newKey = [[NSString alloc] initWithFormat:@"%@.bitsPerPixel", key]; 458 size_t bitsPerPixel = [self decodeIntegerForKey:newKey]; 459 [newKey release]; 460 461 newKey = [[NSString alloc] initWithFormat:@"%@.bytesPerRow", key]; 462 size_t bytesPerRow = [self decodeIntegerForKey:newKey]; 463 [newKey release]; 464 465 newKey = [[NSString alloc] initWithFormat:@"%@.colorSpace", key]; 466 CGColorSpaceRef colorSpace = [self newCGColorSpaceDecodeForKey:newKey]; 467 [newKey release]; 468 469 newKey = [[NSString alloc] initWithFormat:@"%@.bitmapInfo", key]; 470 CGBitmapInfo bitmapInfo = [self decodeIntegerForKey:newKey]; 471 [newKey release]; 472 473 newKey = [[NSString alloc] initWithFormat:@"%@.provider", key]; 474 CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[self decodeObjectForKey:newKey]); 475 [newKey release]; 476 477 newKey = [[NSString alloc] initWithFormat:@"%@.numberOfComponents", key]; 478 size_t numberOfComponents = [self decodeIntegerForKey:newKey]; 479 [newKey release]; 480 481 CGFloat * decodeArray = NULL; 482 if ( numberOfComponents ) { 483 decodeArray = malloc(numberOfComponents * 2 * sizeof(CGFloat)); 484 485 for ( size_t i = 0; i < numberOfComponents; i++ ) { 486 newKey = [[NSString alloc] initWithFormat:@"%@.decode[%u].lower", key, i]; 487 decodeArray[i * 2] = [self decodeCGFloatForKey:newKey]; 488 [newKey release]; 489 490 newKey = [[NSString alloc] initWithFormat:@"%@.decode[%u].upper", key, i]; 491 decodeArray[i * 2 + 1] = [self decodeCGFloatForKey:newKey]; 492 [newKey release]; 493 } 494 } 495 496 newKey = [[NSString alloc] initWithFormat:@"%@.shouldInterpolate", key]; 497 bool shouldInterpolate = [self decodeBoolForKey:newKey]; 498 [newKey release]; 499 500 newKey = [[NSString alloc] initWithFormat:@"%@.renderingIntent", key]; 501 CGColorRenderingIntent intent = [self decodeIntegerForKey:newKey]; 502 [newKey release]; 503 504 CGImageRef newImage = CGImageCreate(width, 505 height, 506 bitsPerComponent, 507 bitsPerPixel, 508 bytesPerRow, 509 colorSpace, 510 bitmapInfo, 511 provider, 512 decodeArray, 513 shouldInterpolate, 514 intent); 515 516 CGColorSpaceRelease(colorSpace); 517 CGDataProviderRelease(provider); 518 if ( decodeArray ) free(decodeArray); 519 520 return newImage; 521} 522 523/** @brief Decodes and returns a decimal number that was previously encoded with encodeDecimal:forKey: and associated with the string key. 524 * @param key The key associated with the number. 525 * @return The number as an NSDecimal. 526 **/ 527-(NSDecimal)decodeDecimalForKey:(NSString *)key; 528{ 529 NSDecimal result; 530 531 NSNumber *number = [self decodeObjectForKey:key]; 532 if ( [number respondsToSelector:@selector(decimalValue)] ) { 533 result = [number decimalValue]; 534 } 535 else { 536 result = CPTDecimalNaN(); 537 } 538 539 return result; 540} 541 542@end 543