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