Reviewed by Vicki.
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebImageRenderer.m
1
2 /*      
3         WebImageRenderer.m
4         Copyright (c) 2002, 2003, Apple, Inc. All rights reserved.
5 */
6 #import <WebKit/WebImageRenderer.h>
7
8 #import <WebKit/WebAssertions.h>
9 #import <WebKit/WebImageRendererFactory.h>
10 #import <WebKit/WebGraphicsBridge.h>
11 #import <WebKit/WebHTMLView.h>
12 #import <WebKit/WebImageView.h>
13 #import <WebKit/WebNSObjectExtras.h>
14
15 #import <WebCore/WebCoreImageRenderer.h>
16
17 #import <CoreGraphics/CGContextPrivate.h>
18
19
20 #ifdef USE_CGIMAGEREF
21
22 #import <WebKit/WebImageData.h>
23
24 // Forward declarations of internal methods.
25 @interface WebImageRenderer (WebInternal)
26 - (void)_startOrContinueAnimationIfNecessary;
27 @end
28
29 @implementation WebImageRenderer
30
31 - (id)initWithMIMEType:(NSString *)MIME
32 {
33     self = [super init];
34     if (self != nil) {
35         MIMEType = [MIME copy];
36     }
37     return self;
38 }
39
40 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
41 {
42     self = [super init];
43     if (self != nil) {
44         MIMEType = [MIME copy];
45         imageData = [[WebImageData alloc] init];
46         [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
47     }
48     return self;
49 }
50
51 - (id)initWithContentsOfFile:(NSString *)filename
52 {
53     self = [super init];
54
55     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
56     NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
57
58     imageData = [[WebImageData alloc] init];
59     NSData *data = [NSData dataWithContentsOfFile:imagePath];
60     [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
61         
62
63     return self;
64 }
65
66 - (void)dealloc
67 {
68     [MIMEType release];
69     [imageData release];
70     [nsimage release];
71     [TIFFData release];
72     [super dealloc];
73 }
74
75 - copyWithZone:(NSZone *)zone
76 {
77     WebImageRenderer *copy;
78
79     copy = [[WebImageRenderer alloc] init];
80     copy->MIMEType = [MIMEType copy];
81     copy->adjustedSize = adjustedSize;
82     copy->isSizeAdjusted = isSizeAdjusted;
83     copy->imageData = [imageData retain];
84         
85     return copy;
86 }
87
88 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
89 {
90     return [self copyWithZone:0];
91 }
92
93 - (void)resize:(NSSize)s
94 {
95     isSizeAdjusted = YES;
96     adjustedSize = s;
97 }
98
99 - (NSSize)size
100 {
101     if (isSizeAdjusted)
102         return adjustedSize;
103         
104     if (!imageData)
105         return NSZeroSize;
106         
107     CGSize sz = [imageData size];
108     return NSMakeSize(sz.width, sz.height);
109 }
110
111 - (NSString *)MIMEType
112 {
113     return MIMEType;
114 }
115
116 - (int)frameCount
117 {
118     return [imageData numberOfImages];
119 }
120
121
122 - (BOOL)isNull
123 {
124     return [imageData isNull];
125 }
126
127 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
128 {
129     if (!imageData) {
130         imageData = [[WebImageData alloc] init];
131         if ([MIMEType isEqual:@"application/pdf"]) {
132             [imageData setIsPDF:YES];
133         }
134     }
135     return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
136 }
137
138 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
139 {
140     CGContextRef aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
141     NSCompositingOperation op = NSCompositeSourceOver;
142
143     [self drawImageInRect:ir fromRect:fr compositeOperator:op context:aContext];
144 }
145
146 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
147 {
148     if (aContext == 0)
149         aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
150     
151     NSCompositingOperation op = (NSCompositingOperation)operator;
152         
153     if (isSizeAdjusted) {
154         [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height) 
155                 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height) 
156                 adjustedSize:CGSizeMake(adjustedSize.width, adjustedSize.height)
157                 compositeOperation:op context:aContext];
158     }
159     else {
160         [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height) 
161                 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height) 
162                 compositeOperation:op context:aContext];
163     }
164
165     targetAnimationRect = ir;
166     [self _startOrContinueAnimationIfNecessary];
167 }
168
169 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
170 {
171     if (aContext == 0)
172         aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
173
174     [imageData tileInRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
175             fromPoint:CGPointMake(point.x, point.y) context:aContext];
176             
177     targetAnimationRect = rect;
178     [self _startOrContinueAnimationIfNecessary];
179 }
180
181 - (void)_startOrContinueAnimationIfNecessary
182 {
183     NSView *targetView = [NSView focusView];
184     
185     // Only animate if we're drawing into a WebHTMLView or WebImageView.  This fixes problems
186     // like <rdar://problem/3966973>, which describes a third party application that renders thumbnails of
187     // the page into a alternate view.
188     if (([targetView isKindOfClass:[WebHTMLView class]] || [targetView isKindOfClass:[WebImageView class]]) 
189             && [imageData shouldAnimate] && [MIMEType isEqual:@"image/gif"]) {
190         [imageData addAnimatingRenderer:self inView:targetView];
191         [imageData animate];
192     }
193 }
194
195 + (void)stopAnimationsInView:(NSView *)aView
196 {
197     [WebImageData stopAnimationsInView:aView];
198 }
199
200 - (void)resetAnimation
201 {
202     [imageData resetAnimation];
203 }
204
205
206 - (void)stopAnimation
207 {
208     [imageData removeAnimatingRenderer:self];
209 }
210
211 - (NSRect)targetAnimationRect
212 {
213     return targetAnimationRect;
214 }
215
216 - (void)increaseUseCount
217 {
218 }
219
220 - (void)decreaseUseCount
221 {
222 }
223
224 - (void)flushRasterCache
225 {
226 }
227
228 - (CGImageRef)imageRef
229 {
230     return [imageData imageAtIndex:0];
231 }
232
233 - (NSData *)TIFFRepresentation
234 {
235     if (!TIFFData) {
236         CGImageRef image = [imageData imageAtIndex:0];
237         if (!image)
238             return 0;
239             
240         CFMutableDataRef data = 0;
241         CGImageDestinationRef destination = 0;
242         
243         data = CFDataCreateMutable(NULL, 0);
244         // FIXME:  Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
245         destination = CGImageDestinationCreateWithData (data, CFSTR("public.tiff"), 1, NULL);
246         if (destination) {
247             CGImageDestinationAddImage (destination, image, NULL);
248             if (!CGImageDestinationFinalize (destination)) {
249                 ERROR ("Unable to create image\n");
250             }
251             CFRelease (destination);
252         }
253
254         TIFFData = (NSData *)data;
255     }
256     
257     return TIFFData;
258 }
259
260 - (NSImage *)image
261 {
262     if (!nsimage)
263         nsimage = [[NSImage alloc] initWithData:[self TIFFRepresentation]];
264     return nsimage;
265 }
266
267 @end
268
269 #else
270
271 @interface WebInternalImage : NSImage <NSCopying>
272 {
273     NSTimer *frameTimer;
274     NSView *frameView;
275     NSRect imageRect;
276     NSRect targetRect;
277
278     int loadStatus;
279
280     NSColor *patternColor;
281     int patternColorLoadStatus;
282
283     int repetitionsComplete;
284     BOOL animationFinished;
285     
286     NSPoint tilePoint;
287     BOOL animatedTile;
288
289     int compositeOperator;
290     id redirectContext;
291     CGContextRef context;
292     BOOL needFlushRasterCache;
293     BOOL rasterFlushing;
294     NSImageCacheMode rasterFlushingOldMode;
295     
296     NSString *MIMEType;
297     BOOL isNull;
298     int useCount;
299
300     CGImageRef cachedImageRef;
301     
302     id _PDFDoc;
303         
304 @public    
305     NSData *originalData;
306 }
307
308 - (id)initWithMIMEType:(NSString *)MIME;
309 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME;
310
311 - (void)releasePatternColor;
312
313 - (NSString *)MIMEType;
314 - (int)frameCount;
315
316 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
317 - (void)resize:(NSSize)s;
318 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr;
319 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context;
320 - (void)stopAnimation;
321 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext;
322 - (BOOL)isNull;
323 - (void)increaseUseCount;
324 - (void)decreaseUseCount;
325 - (WebImageRenderer *)createRendererIfNeeded;
326 - (void)flushRasterCache;
327 - (CGImageRef)imageRef;
328
329 + (void)stopAnimationsInView:(NSView *)aView;
330 - (void)resetAnimation;
331
332 - (void)startAnimationIfNecessary;
333 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext;
334 - (void)_endRedirectContext:(NSGraphicsContext *)aContext;
335 - (void)_needsRasterFlush;
336 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped;
337
338 @end
339
340 extern NSString *NSImageLoopCount;
341
342 /*
343     We need to get the AppKit to redirect drawing to our
344     CGContext.  There is currently (on Panther) no public
345     way to make this happen.  So we create a NSCGSContext
346     subclass and twiddle it's _cgsContext ivar directly.
347     Very fragile, but the only way...
348 */
349 @interface NSCGSContext : NSGraphicsContext {
350     CGContextRef _cgsContext;
351 }
352 @end
353
354 static CGImageRef _createImageRef(NSBitmapImageRep *rep);
355
356 @interface NSBitmapImageRep (AppKitInternals)
357 - (CGImageRef)_CGImageRef;
358 @end
359
360 @interface NSFocusStack : NSObject
361 @end
362
363 @interface WebImageContext : NSCGSContext {
364   @private
365     NSFocusStack* _focusStack;
366     NSRect        _bounds;
367     BOOL          _isFlipped;
368 }
369 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context;
370 - (NSRect)bounds;
371 @end
372
373
374 @implementation WebImageContext
375
376 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context {
377     
378     self = [super init];
379     if (self != nil) {
380         _bounds     = b;
381         _isFlipped  = YES;
382         if (context) {
383             _cgsContext = CGContextRetain(context);
384         }
385     }
386     
387     return self;
388 }
389
390 - (void)dealloc
391 {
392     [_focusStack release];
393     if (_cgsContext) {
394         CGContextRelease(_cgsContext);
395         _cgsContext = 0; // super dealloc may also release
396     }
397     [super dealloc];
398 }
399
400 - (void)finalize
401 {
402     if (_cgsContext) {
403         CGContextRelease(_cgsContext);
404         _cgsContext = 0; // super finalize may also release
405     }
406     [super finalize];
407 }
408
409 - (void)saveGraphicsState
410 {
411     if (_cgsContext) {
412         CGContextSaveGState(_cgsContext);
413     }
414 }
415
416 - (void)restoreGraphicsState
417 {
418     if (_cgsContext) {
419         CGContextRestoreGState(_cgsContext);
420     }
421 }
422
423 - (BOOL)isDrawingToScreen
424 {
425     return NO;
426 }
427
428 - (void *)focusStack
429 {
430     if (!_focusStack) _focusStack = [[NSFocusStack allocWithZone:NULL] init];
431     return _focusStack;
432 }
433
434 - (void)setFocusStack:(void *)stack
435 {
436     id oldstack = _focusStack;
437     _focusStack = [(id)stack retain];
438     [oldstack release];
439 }
440
441 - (NSRect)bounds
442 {
443     return _bounds;
444 }
445
446 - (BOOL)isFlipped
447 {
448     return _isFlipped;
449 }
450
451 @end
452
453 #define MINIMUM_DURATION (1.0/30.0)
454
455 @implementation WebInternalImage
456
457 static NSMutableSet *activeImageRenderers;
458
459 + (void)stopAnimationsInView:(NSView *)aView
460 {
461     NSEnumerator *objectEnumerator = [activeImageRenderers objectEnumerator];
462     WebInternalImage *renderer;
463     NSMutableSet *renderersToStop = [[NSMutableSet alloc] init];
464
465     while ((renderer = [objectEnumerator nextObject])) {
466         if (renderer->frameView == aView) {
467             [renderersToStop addObject: renderer];
468         }
469     }
470
471     objectEnumerator = [renderersToStop objectEnumerator];
472     while ((renderer = [objectEnumerator nextObject])) {
473         if (renderer->frameView == aView) {
474             [renderer stopAnimation];
475         }
476     }
477     [renderersToStop release];
478 }
479
480 - (id)initWithMIMEType:(NSString *)MIME
481 {
482     self = [super init];
483     if (self != nil) {
484         // Work around issue with flipped images and TIFF by never using the image cache.
485         // See bug 3344259 and related bugs.
486         [self setCacheMode:NSImageCacheNever];
487
488         loadStatus = NSImageRepLoadStatusUnknownType;
489         MIMEType = [MIME copy];
490         isNull = YES;
491         compositeOperator = (int)NSCompositeSourceOver;
492     }
493     
494     return self;
495 }
496
497 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
498 {
499     WebInternalImage *result = nil;
500
501     NS_DURING
502     
503         result = [super initWithData:data];
504         if (result != nil) {
505             // Work around issue with flipped images and TIFF by never using the image cache.
506             // See bug 3344259 and related bugs.
507             [result setCacheMode:NSImageCacheNever];
508     
509             result->loadStatus = NSImageRepLoadStatusUnknownType;
510             result->MIMEType = [MIME copy];
511             result->isNull = [data length] == 0;
512             result->compositeOperator = (int)NSCompositeSourceOver;
513         }
514
515     NS_HANDLER
516
517         result = nil;
518
519     NS_ENDHANDLER
520
521     return result;
522 }
523
524 - (id)initWithContentsOfFile:(NSString *)filename
525 {
526     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
527     NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
528     WebInternalImage *result = nil;
529
530     NS_DURING
531
532         result = [super initWithContentsOfFile:imagePath];
533         if (result != nil) {
534             // Work around issue with flipped images and TIFF by never using the image cache.
535             // See bug 3344259 and related bugs.
536             [result setCacheMode:NSImageCacheNever];
537     
538             result->loadStatus = NSImageRepLoadStatusUnknownType;
539             result->compositeOperator = (int)NSCompositeSourceOver;
540         }
541         
542     NS_HANDLER
543
544         result = nil;
545
546     NS_ENDHANDLER
547
548     return result;
549 }
550
551 - (void)increaseUseCount
552 {
553     useCount++;
554 }
555
556 - (void)decreaseUseCount
557 {
558     useCount--;
559 }
560
561 - (WebImageRenderer *)createRendererIfNeeded
562 {
563     // If an animated image appears multiple times in a given page, we
564     // must create multiple WebCoreImageRenderers so that each copy
565     // animates. However, we don't want to incur the expense of
566     // re-decoding for the very first use on a page, since QPixmap
567     // assignment always calls this method, even when just fetching
568     // the image from the cache for the first time for a page.
569     if (originalData && useCount) {
570         return [[[WebImageRendererFactory sharedFactory] imageRendererWithData:originalData MIMEType:MIMEType] retain];
571     }
572     return nil;
573 }
574
575 - copyWithZone:(NSZone *)zone
576 {
577     // FIXME: If we copy while doing an incremental load, it won't work.
578     WebInternalImage *copy;
579
580     copy = [super copyWithZone:zone];
581     copy->MIMEType = [MIMEType copy];
582     copy->originalData = [originalData retain];
583     copy->frameTimer = nil;
584     copy->frameView = nil;
585     copy->patternColor = nil;
586     copy->compositeOperator = compositeOperator;
587     copy->context = 0;
588
589     return copy;
590 }
591
592 - (BOOL)isNull
593 {
594     return isNull;
595 }
596
597 - (void)_adjustSizeToPixelDimensions
598 {
599     // Force the image to use the pixel size and ignore the dpi.
600     // Ignore any absolute size in the image and always use pixel dimensions.
601     NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
602     NSSize size = NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
603     [imageRep setSize:size];
604         
605     [self setScalesWhenResized:YES];
606     [self setSize:size];
607 }
608
609 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
610 {
611     NSArray *reps = [self representations];
612     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
613     
614     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]]) {
615         NSData *data = [[NSData alloc] initWithBytes:bytes length:length];
616
617         NS_DURING
618             // Get rep again to avoid bogus compiler warning.
619             NSBitmapImageRep *aRep = [[self representations] objectAtIndex:0];
620
621             loadStatus = [aRep incrementalLoadFromData:data complete:isComplete];
622         NS_HANDLER
623             loadStatus = NSImageRepLoadStatusInvalidData; // Arbitrary choice; any error will do.
624         NS_ENDHANDLER
625
626         // Hold onto the original data in case we need to copy this image.  (Workaround for appkit NSImage
627         // copy flaw).
628         if (isComplete && [self frameCount] > 1)
629             originalData = data;
630         else
631             [data release];
632
633         switch (loadStatus) {
634         case NSImageRepLoadStatusUnknownType:       // not enough data to determine image format. please feed me more data
635             //printf ("NSImageRepLoadStatusUnknownType size %d, isComplete %d\n", length, isComplete);
636             return NO;
637         case NSImageRepLoadStatusReadingHeader:     // image format known, reading header. not yet valid. more data needed
638             //printf ("NSImageRepLoadStatusReadingHeader size %d, isComplete %d\n", length, isComplete);
639             return NO;
640         case NSImageRepLoadStatusWillNeedAllData:   // can't read incrementally. will wait for complete data to become avail.
641             //printf ("NSImageRepLoadStatusWillNeedAllData size %d, isComplete %d\n", length, isComplete);
642             return NO;
643         case NSImageRepLoadStatusInvalidData:       // image decompression encountered error.
644             //printf ("NSImageRepLoadStatusInvalidData size %d, isComplete %d\n", length, isComplete);
645             return NO;
646         case NSImageRepLoadStatusUnexpectedEOF:     // ran out of data before full image was decompressed.
647             //printf ("NSImageRepLoadStatusUnexpectedEOF size %d, isComplete %d\n", length, isComplete);
648             return NO;
649         case NSImageRepLoadStatusCompleted:         // all is well, the full pixelsHigh image is valid.
650             //printf ("NSImageRepLoadStatusCompleted size %d, isComplete %d\n", length, isComplete);
651             [self _adjustSizeToPixelDimensions];        
652             isNull = NO;
653             return YES;
654         default:
655             [self _adjustSizeToPixelDimensions];
656             //printf ("incrementalLoadWithBytes: size %d, isComplete %d\n", length, isComplete);
657             // We have some valid data.  Return YES so we can attempt to draw what we've got.
658             isNull = NO;
659             return YES;
660         }
661     }
662     else {
663         if (isComplete) {
664             originalData = [[NSData alloc] initWithBytes:bytes length:length];
665             if ([MIMEType isEqual:@"application/pdf"]) {
666                 Class repClass = [NSImageRep imageRepClassForData:originalData];
667                 if (repClass) {
668                     NSImageRep *rep = [[repClass alloc] initWithData:originalData];
669                     [self addRepresentation:rep];
670                 }
671             }
672             isNull = NO;
673             return YES;
674         }
675     }
676     return NO;
677 }
678
679 - (void)dealloc
680 {
681     ASSERT(frameTimer == nil);
682     ASSERT(frameView == nil);
683     [patternColor release];
684     [MIMEType release];
685     [originalData release];
686     
687     if (context) {
688         CGContextRelease(context);
689         context = 0;
690     }
691
692     if (cachedImageRef) {
693         CGImageRelease (cachedImageRef);
694         cachedImageRef = 0;
695     }
696     
697     [_PDFDoc release];
698
699     [super dealloc];
700 }
701
702 - (void)finalize
703 {
704     ASSERT(frameTimer == nil);
705     ASSERT(frameView == nil);
706
707     if (context) {
708         CGContextRelease(context);
709     }
710
711     if (cachedImageRef) {
712         CGImageRelease (cachedImageRef);
713         cachedImageRef = 0;
714     }
715     
716     [super finalize];
717 }
718
719 - (id)firstRepProperty:(NSString *)propertyName
720 {
721     id firstRep = [[self representations] objectAtIndex:0];
722     id property = nil;
723     if ([firstRep respondsToSelector:@selector(valueForProperty:)])
724         property = [firstRep valueForProperty:propertyName];
725     return property;
726 }
727
728 - (int)frameCount
729 {
730     id property = [self firstRepProperty:NSImageFrameCount];
731     return property ? [property intValue] : 1;
732 }
733
734 - (int)currentFrame
735 {
736     id property = [self firstRepProperty:NSImageCurrentFrame];
737     return property ? [property intValue] : 1;
738 }
739
740 - (void)setCurrentFrame:(int)frame
741 {
742     NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
743     [imageRep setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:frame]];
744 }
745
746 - (float)unadjustedFrameDuration
747 {
748     id property = [self firstRepProperty:NSImageCurrentFrameDuration];
749     return property ? [property floatValue] : 0.0;
750 }
751
752 - (float)frameDuration
753 {
754     float duration = [self unadjustedFrameDuration];
755     if (duration < MINIMUM_DURATION) {
756         /*
757             Many annoying ads specify a 0 duration to make an image flash
758             as quickly as possible.  However a zero duration is faster than
759             the refresh rate.  We need to pick a minimum duration.
760             
761             Browsers handle the minimum time case differently.  IE seems to use something
762             close to 1/30th of a second.  Konqueror uses 0.  The ImageMagick library
763             uses 1/100th.  The units in the GIF specification are 1/100th of second.
764             We will use 1/30th of second as the minimum time.
765         */
766         duration = MINIMUM_DURATION;
767     }
768     return duration;
769 }
770
771 - (int)repetitionCount
772 {
773     id property = [self firstRepProperty:NSImageLoopCount];
774     int count = property ? [property intValue] : 0;
775     return count;
776 }
777
778 - (void)scheduleFrame
779 {
780     if (frameTimer && [frameTimer isValid])
781         return;
782     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self frameDuration]
783                                                     target:self
784                                                   selector:@selector(nextFrame:)
785                                                   userInfo:nil
786                                                    repeats:NO] retain];
787 }
788
789 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext
790 {
791     NSGraphicsContext *oldContext = 0;
792     if (aContext) {
793         oldContext = [NSGraphicsContext currentContext];
794         // Assumes that we are redirecting to a CGBitmapContext.
795         size_t w = CGBitmapContextGetWidth (aContext);
796         size_t h = CGBitmapContextGetHeight (aContext);
797         redirectContext = [[WebImageContext alloc] initWithBounds:NSMakeRect(0, 0, (float)w, (float)h) context:aContext];
798         [NSGraphicsContext setCurrentContext:redirectContext];
799     }
800     return oldContext; 
801 }
802
803 - (void)_endRedirectContext:(NSGraphicsContext *)aContext
804 {
805     if (aContext) {
806         [NSGraphicsContext setCurrentContext:aContext];
807         [redirectContext autorelease];
808         redirectContext = 0;
809     }
810 }
811
812 - (void)_needsRasterFlush
813 {
814 #if 0
815     if (needFlushRasterCache && [MIMEType isEqual: @"application/pdf"]) {
816         // FIXME:  At this point we need to flush the cached rasterized PDF.
817     }
818 #endif
819 }
820
821 - (void)_adjustColorSpace
822 {
823 #if COLORMATCH_EVERYTHING
824     NSArray *reps = [self representations];
825     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
826     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
827         [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
828         [[imageRep colorSpaceName] isEqualToString:NSDeviceRGBColorSpace]) {
829         [imageRep setColorSpaceName:NSCalibratedRGBColorSpace];
830     }
831 #else
832     NSArray *reps = [self representations];
833     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
834     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
835         [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
836         [[imageRep colorSpaceName] isEqualToString:NSCalibratedRGBColorSpace]) {
837         [imageRep setColorSpaceName:NSDeviceRGBColorSpace];
838     }
839 #endif
840 }
841
842
843 - (void)drawClippedToValidInRect:(NSRect)ir fromRect:(NSRect)fr
844 {
845     [self _adjustColorSpace];
846
847     if (loadStatus >= 0) {
848         // The last line might be a partial line, so the number of complete lines is the number
849         // we get from NSImage minus one.
850         int numCompleteLines = loadStatus - 1;
851         if (numCompleteLines <= 0) {
852             return;
853         }
854         int pixelsHigh = [[[self representations] objectAtIndex:0] pixelsHigh];
855         if (pixelsHigh > numCompleteLines) {
856             // Figure out how much of the image is OK to draw.  We can't simply
857             // use numCompleteLines because the image may be scaled.
858             float clippedImageHeight = floor([self size].height * numCompleteLines / pixelsHigh);
859             
860             // Figure out how much of the source is OK to draw from.
861             float clippedSourceHeight = clippedImageHeight - fr.origin.y;
862             if (clippedSourceHeight < 1) {
863                 return;
864             }
865             
866             // Figure out how much of the destination we are going to draw to.
867             float clippedDestinationHeight = ir.size.height * clippedSourceHeight / fr.size.height;
868
869             // Reduce heights of both rectangles without changing their positions.
870             // In the flipped case, just adjusting the height is sufficient.
871             ASSERT([self isFlipped]);
872             ASSERT([[NSView focusView] isFlipped]);
873             ir.size.height = clippedDestinationHeight;
874             fr.size.height = clippedSourceHeight;
875         }
876     }
877     
878     if (context) {
879         NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
880         [self _needsRasterFlush];
881
882         // If we have PDF then draw the PDF ourselves, bypassing the NSImage caching mechanisms,
883         // but only do this when we're rendering to an offscreen context.  NSImage will always
884         // cache the PDF image at it's native resolution, thus, causing artifacts when the image
885         // is drawn into a scaled or rotated context.
886         if ([MIMEType isEqual:@"application/pdf"])
887             [self _PDFDrawFromRect:fr toRect:ir operation:compositeOperator alpha:1.0 flipped:YES];
888         else
889             [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
890
891         [self _endRedirectContext:oldContext];
892     }
893     else {
894         [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
895     }
896 }
897
898 - (CGPDFDocumentRef)_PDFDocumentRef
899 {
900     if (!_PDFDoc) {
901         _PDFDoc = [[WebPDFDocument alloc] initWithData:originalData];
902     }
903         
904     return [_PDFDoc documentRef];
905 }
906
907 - (void)_PDFDraw
908 {
909     CGPDFDocumentRef document = [self _PDFDocumentRef];
910     if (document != NULL) {
911         CGContextRef _context  = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
912         CGRect       mediaBox = [_PDFDoc mediaBox];
913         
914         CGContextSaveGState(_context);
915         // Rotate translate image into position according to doc properties.
916         [_PDFDoc adjustCTM:_context];   
917
918         // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
919         // at 1, not 0.
920         CGContextDrawPDFDocument(_context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
921
922         CGContextRestoreGState(_context);
923     }
924 }
925
926 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped
927 {
928     // FIXME:  The rasterized PDF should be drawn into a cache, and the raster then composited.
929     
930     CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
931     float hScale, vScale;
932
933     CGContextSaveGState(_context);
934
935     [[NSGraphicsContext currentContext] setCompositingOperation:op];
936     // Scale and translate so the document is rendered in the correct location.
937     hScale = dstRect.size.width  / srcRect.size.width;
938     vScale = dstRect.size.height / srcRect.size.height;
939
940     CGContextTranslateCTM (_context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
941     CGContextScaleCTM (_context, hScale, vScale);
942
943     // Reverse if flipped image.
944     if (flipped) {
945         CGContextScaleCTM(_context, 1, -1);
946         CGContextTranslateCTM (_context, 0, -(dstRect.origin.y + dstRect.size.height));
947     }
948
949     // Clip to destination in case we are imaging part of the source only
950     CGContextClipToRect(_context, CGRectIntegral(*(CGRect*)&srcRect));
951
952     // and draw
953     [self _PDFDraw];
954
955     // done with our fancy transforms
956     CGContextRestoreGState(_context);
957
958     return YES;
959 }
960
961 - (void)resetAnimation
962 {
963     [self stopAnimation];
964     [self setCurrentFrame:0];
965     repetitionsComplete = 0;
966     animationFinished = NO;
967 }
968
969 - (void)nextFrame:(id)context
970 {
971     int currentFrame;
972     
973     // Release the timer that just fired.
974     [frameTimer release];
975     frameTimer = nil;
976     
977     currentFrame = [self currentFrame] + 1;
978     if (currentFrame >= [self frameCount]) {
979         repetitionsComplete += 1;
980         if ([self repetitionCount] && repetitionsComplete >= [self repetitionCount]) {
981             animationFinished = YES;
982             return;
983         }
984         currentFrame = 0;
985     }
986     [self setCurrentFrame:currentFrame];
987     
988     // Release the tiling pattern so next frame will update the pattern if we're tiling.
989     [patternColor release];
990     patternColor = nil;
991     
992     [frameView setNeedsDisplayInRect:targetRect];
993 }
994
995 // Will be called when the containing view is displayed by WebCore RenderImage (via QPainter).
996 // If the image is an animated image it will begin animating.  If the image is already animating,
997 // it's frame will have been advanced by nextFrame:.
998 //
999 // Also used to draw the image by WebImageView.
1000 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1001 {
1002     if (animatedTile){
1003         [self tileInRect:ir fromPoint:tilePoint context:context];
1004     }
1005     else {
1006         [self drawClippedToValidInRect:ir fromRect:fr];
1007         imageRect = fr;
1008         targetRect = ir;
1009         [self startAnimationIfNecessary];
1010     }
1011 }
1012
1013 - (void)flushRasterCache
1014 {
1015     needFlushRasterCache = YES;
1016 }
1017
1018 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
1019 {
1020     compositeOperator = operator;
1021     
1022     if (aContext != context) {
1023         if (aContext) {
1024             CGContextRetain(aContext);
1025         }
1026         if (context) {
1027             CGContextRelease(context);
1028         }
1029         context = aContext;
1030     }
1031         
1032     [self drawImageInRect:ir fromRect:fr];
1033 }
1034
1035 - (void)startAnimationIfNecessary
1036 {
1037     if ([self frameCount] > 1 && !animationFinished) {
1038         NSView *newView = [NSView focusView];
1039         if (newView != frameView){
1040             [frameView release];
1041             frameView = [newView retain];
1042         }
1043         [self scheduleFrame];
1044         if (!activeImageRenderers) {
1045             activeImageRenderers = [[NSMutableSet alloc] init];
1046         }
1047         [activeImageRenderers addObject:self];
1048     }
1049 }
1050
1051 - (void)stopAnimation
1052 {
1053     [frameTimer invalidate];
1054     [frameTimer release];
1055     frameTimer = nil;
1056     
1057     [frameView release];
1058     frameView = nil;
1059
1060     [activeImageRenderers removeObject:self];
1061 }
1062
1063 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
1064 {
1065     // These calculations are only correct for the flipped case.
1066     ASSERT([self isFlipped]);
1067     ASSERT([[NSView focusView] isFlipped]);
1068
1069     [self _adjustColorSpace];
1070
1071     NSSize size = [self size];
1072
1073     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
1074     NSRect oneTileRect;
1075     oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, size.width) - size.width, size.width);
1076     oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, size.height) - size.height, size.height);
1077 // I think this is a simpler way to say the same thing.  Also, if either point.x or point.y is negative, both
1078 // methods would end up with the wrong answer.  For example, fmod(-22,5) is -2, which is the correct delta to
1079 // the start of the pattern, but fmod(-(-23), 5) is 3.  This is the delta to the *end* of the pattern
1080 // instead of the start, so onTileRect will be too far right.
1081 //    oneTileRect.origin.x = rect.origin.x - fmodf(point.x, size.width);
1082 //    oneTileRect.origin.y = rect.origin.y - fmodf(point.y, size.height);
1083     oneTileRect.size = size;
1084
1085     // Compute the appropriate phase relative to the top level view in the window.
1086     // Conveniently, the oneTileRect we computed above has the appropriate origin.
1087     NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
1088     // WebCore may translate the focus, and thus need an extra phase correction
1089     NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
1090     originInWindow.x += extraPhase.x;
1091     originInWindow.y += extraPhase.y;
1092     CGSize phase = CGSizeMake(fmodf(originInWindow.x, size.width), fmodf(originInWindow.y, size.height));
1093     
1094     // If the single image draw covers the whole area, then just draw once.
1095     if (NSContainsRect(oneTileRect, rect)) {
1096         NSRect fromRect;
1097
1098         fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
1099         fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
1100         fromRect.size = rect.size;
1101         
1102         [self drawClippedToValidInRect:rect fromRect:fromRect];
1103         return;
1104     }
1105
1106     // If we only have a partial image, just do nothing, because CoreGraphics will not help us tile
1107     // with a partial image. But maybe later we can fix this by constructing a pattern image that's
1108     // transparent where needed.
1109     if (loadStatus != NSImageRepLoadStatusCompleted) {
1110         return;
1111     }
1112     
1113     // Since we need to tile, construct an NSColor so we can get CoreGraphics to do it for us.
1114     if (patternColorLoadStatus != loadStatus) {
1115         [patternColor release];
1116         patternColor = nil;
1117     }
1118     if (patternColor == nil) {
1119         patternColor = [[NSColor colorWithPatternImage:self] retain];
1120         patternColorLoadStatus = loadStatus;
1121     }
1122         
1123     NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
1124     [self _needsRasterFlush];
1125     
1126     [NSGraphicsContext saveGraphicsState];
1127     
1128     CGContextSetPatternPhase((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], phase);    
1129     [patternColor set];
1130     [NSBezierPath fillRect:rect];
1131     
1132     [NSGraphicsContext restoreGraphicsState];
1133
1134     [self _endRedirectContext:oldContext];
1135
1136     animatedTile = YES;
1137     tilePoint = point;
1138     targetRect = rect;
1139     [self startAnimationIfNecessary];
1140 }
1141
1142 - (void)resize:(NSSize)s
1143 {
1144     [self setScalesWhenResized:YES];
1145     [self setSize:s];
1146 }
1147
1148 - (NSString *)MIMEType
1149 {
1150     return MIMEType;
1151 }
1152
1153 // Try hard to get a CGImageRef.  First try to snag one from the
1154 // NSBitmapImageRep, then try to create one.  In some cases we may
1155 // not be able to create one.
1156 - (CGImageRef)imageRef
1157 {
1158     CGImageRef ref = 0;
1159     
1160     if (cachedImageRef)
1161         return cachedImageRef;
1162         
1163     if ([[self representations] count] > 0) {
1164         NSBitmapImageRep *rep = [[self representations] objectAtIndex:0];
1165         
1166         if ([rep respondsToSelector:@selector(_CGImageRef)])
1167             ref =  [rep _CGImageRef];
1168             
1169         if (!ref) {
1170             cachedImageRef = _createImageRef(rep);
1171             ref = cachedImageRef;
1172         }
1173     }
1174     return ref;
1175 }
1176
1177 - (void)releasePatternColor
1178 {
1179     [patternColor release];
1180     patternColor = nil;
1181 }
1182
1183 @end
1184
1185 @implementation WebImageRenderer
1186
1187 - (id)initWithMIMEType:(NSString *)MIME
1188 {
1189     WebInternalImage *i = [[WebInternalImage alloc] initWithMIMEType:MIME];
1190     if (i == nil) {
1191         [self dealloc];
1192         return nil;
1193     }
1194     [self init];
1195     image = i;
1196     return self;
1197 }
1198
1199 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
1200 {
1201     WebInternalImage *i = [[WebInternalImage alloc] initWithData:data MIMEType:MIME];
1202     if (i == nil) {
1203         [self dealloc];
1204         return nil;
1205     }
1206     [self init];
1207     image = i;
1208     return self;
1209 }
1210
1211 - (id)initWithContentsOfFile:(NSString *)filename
1212 {
1213     WebInternalImage *i = [[WebInternalImage alloc] initWithContentsOfFile:filename];
1214     if (i == nil) {
1215         [self dealloc];
1216         return nil;
1217     }
1218     [self init];
1219     image = i;
1220     return self;
1221 }
1222
1223 - (void)dealloc
1224 {
1225     [image releasePatternColor];
1226     [image release];
1227     [super dealloc];
1228 }
1229
1230 - (NSImage *)image
1231 {
1232     return image;
1233 }
1234
1235 - (NSString *)MIMEType
1236 {
1237     return [image MIMEType];
1238 }
1239
1240 - (NSData *)TIFFRepresentation
1241 {
1242     return [image TIFFRepresentation];
1243 }
1244
1245 - (int)frameCount
1246 {
1247     return [image frameCount];
1248 }
1249
1250 - (void)setOriginalData:(NSData *)data
1251 {
1252     NSData *oldData = image->originalData;
1253     image->originalData = [data retain];
1254     [oldData release];
1255 }
1256
1257 + (void)stopAnimationsInView:(NSView *)aView
1258 {
1259     [WebInternalImage stopAnimationsInView:aView];
1260 }
1261
1262 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
1263 {
1264     return [image incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
1265 }
1266
1267 - (NSSize)size
1268 {
1269     if (!image)
1270         return NSZeroSize;
1271         
1272     return [image size];
1273 }
1274
1275 - (void)resize:(NSSize)s
1276 {
1277     [image resize:s];
1278 }
1279
1280 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1281 {
1282     [image drawImageInRect:ir fromRect:fr];
1283 }
1284
1285 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context
1286 {
1287     [image drawImageInRect:ir fromRect:fr compositeOperator:compsiteOperator context:context];
1288 }
1289
1290 - (void)resetAnimation
1291 {
1292     [image resetAnimation];
1293 }
1294
1295 - (void)stopAnimation
1296 {
1297     [image stopAnimation];
1298 }
1299
1300 - (void)tileInRect:(NSRect)r fromPoint:(NSPoint)p context:(CGContextRef)context
1301 {
1302     [image tileInRect:r fromPoint:p context:context];
1303 }
1304
1305 - (BOOL)isNull
1306 {
1307     return image == nil || [image isNull];
1308 }
1309
1310 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
1311 {
1312     WebImageRenderer *newRenderer = [image createRendererIfNeeded];
1313     if (newRenderer) {
1314         return newRenderer;
1315     }
1316     [self retain];
1317     return self;
1318 }
1319
1320 - (void)increaseUseCount
1321 {
1322     [image increaseUseCount];
1323 }
1324
1325 - (void)decreaseUseCount
1326 {
1327     [image decreaseUseCount];
1328 }
1329
1330 - (void)flushRasterCache
1331 {
1332     [image flushRasterCache];
1333 }
1334
1335 - (CGImageRef)imageRef
1336 {
1337     return [image imageRef];
1338 }
1339
1340 - (id)copyWithZone:(NSZone *)zone
1341 {
1342     WebImageRenderer *copy = [[WebImageRenderer alloc] init];
1343     copy->image = [image copy];
1344     return copy;
1345 }
1346
1347 @end
1348
1349 static CGImageRef _createImageRef(NSBitmapImageRep *rep) {
1350     BOOL isPlanar = [rep isPlanar];
1351     if (isPlanar)
1352         return 0;
1353         
1354     const unsigned char *bitmapData = [rep bitmapData];
1355     int pixelsWide = [rep pixelsWide];
1356     int pixelsHigh = [rep pixelsHigh];
1357     int bitsPerSample = [rep bitsPerSample];
1358     int bitsPerPixel = [rep bitsPerPixel];
1359     int bytesPerRow = [rep bytesPerRow];
1360     BOOL hasAlpha = [rep hasAlpha];
1361     CGImageRef image;
1362     CGDataProviderRef dataProvider;
1363
1364     CGColorSpaceRef colorSpace = WebCGColorSpaceCreateRGB();
1365     dataProvider = CGDataProviderCreateWithData(NULL, bitmapData, pixelsHigh * bytesPerRow, NULL);
1366
1367     image = CGImageCreate(pixelsWide, pixelsHigh, bitsPerSample, bitsPerPixel, bytesPerRow, colorSpace,
1368                           hasAlpha ? kCGImageAlphaPremultipliedLast : kCGImageAlphaNone,
1369                           dataProvider, NULL, false /*shouldInterpolate*/, kCGRenderingIntentDefault);
1370
1371     CGDataProviderRelease(dataProvider);
1372     CGColorSpaceRelease(colorSpace);    
1373
1374     return image;
1375 }
1376
1377 #endif
1378
1379 //------------------------------------------------------------------------------------
1380
1381 @implementation WebPDFDocument
1382
1383 static void ReleasePDFDocumentData(void *info, const void *data, size_t size) {
1384     [(NSData*)info autorelease];
1385 }
1386
1387 - (id) initWithData:(NSData*)data
1388 {
1389     self = [super init];
1390     if (self != nil)
1391     {
1392         if (data != nil) {
1393             CGDataProviderRef dataProvider = CGDataProviderCreateWithData([data retain], [data bytes], [data length], ReleasePDFDocumentData);
1394             _document = CGPDFDocumentCreateWithProvider(dataProvider);
1395             CGDataProviderRelease(dataProvider);
1396         }
1397         _currentPage = -1;
1398         [self setCurrentPage:0];
1399     }
1400     return self;
1401 }
1402
1403 - (void)dealloc
1404 {
1405     if (_document != NULL) CGPDFDocumentRelease(_document);
1406     [super dealloc];
1407 }
1408
1409 - (void)finalize
1410 {
1411     if (_document != NULL) CGPDFDocumentRelease(_document);
1412     [super finalize];
1413 }
1414
1415 - (CGPDFDocumentRef) documentRef
1416 {
1417     return _document;
1418 }
1419
1420 - (CGRect) mediaBox
1421 {
1422     return _mediaBox;
1423 }
1424
1425 - (NSRect) bounds
1426 {
1427     NSRect rotatedRect;
1428
1429     // rotate the media box and calculate bounding box
1430     float sina   = sin (_rotation);
1431     float cosa   = cos (_rotation);
1432     float width  = NSWidth (_cropBox);
1433     float height = NSHeight(_cropBox);
1434
1435     // calculate rotated x and y axis
1436     NSPoint rx = NSMakePoint( width  * cosa, width  * sina);
1437     NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1438
1439     // find delta width and height of rotated points
1440     rotatedRect.origin      = _cropBox.origin;
1441     rotatedRect.size.width  = ceil(fabs(rx.x - ry.x));
1442     rotatedRect.size.height = ceil(fabs(ry.y - rx.y));
1443
1444     return rotatedRect;
1445 }
1446
1447 - (void) adjustCTM:(CGContextRef)context
1448 {
1449     // rotate the crop box and calculate bounding box
1450     float sina   = sin (-_rotation);
1451     float cosa   = cos (-_rotation);
1452     float width  = NSWidth (_cropBox);
1453     float height = NSHeight(_cropBox);
1454
1455     // calculate rotated x and y edges of the corp box. if they're negative, it means part of the image has
1456     // been rotated outside of the bounds and we need to shift over the image so it lies inside the bounds again
1457     NSPoint rx = NSMakePoint( width  * cosa, width  * sina);
1458     NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1459
1460     // adjust so we are at the crop box origin
1461     CGContextTranslateCTM(context, floor(-MIN(0,MIN(rx.x, ry.x))), floor(-MIN(0,MIN(rx.y, ry.y))));
1462
1463     // rotate -ve to remove rotation
1464     CGContextRotateCTM(context, -_rotation);
1465
1466     // shift so we are completely within media box
1467     CGContextTranslateCTM(context, _mediaBox.origin.x - _cropBox.origin.x, _mediaBox.origin.y - _cropBox.origin.y);
1468 }
1469
1470 - (void) setCurrentPage:(int)page
1471 {
1472     if (page != _currentPage && page >= 0 && page < [self pageCount]) {
1473
1474         CGRect r;
1475
1476         _currentPage = page;
1477
1478         // get media box (guaranteed)
1479         _mediaBox = CGPDFDocumentGetMediaBox(_document, page + 1);
1480
1481         // get crop box (not always there). if not, use _mediaBox
1482         r = CGPDFDocumentGetCropBox(_document, page + 1);
1483         if (!CGRectIsEmpty(r)) {
1484             _cropBox = NSMakeRect(r.origin.x, r.origin.y, r.size.width, r.size.height);
1485         } else {
1486             _cropBox = NSMakeRect(_mediaBox.origin.x, _mediaBox.origin.y, _mediaBox.size.width, _mediaBox.size.height);
1487         }
1488
1489         // get page rotation angle
1490         _rotation = CGPDFDocumentGetRotationAngle(_document, page + 1) * M_PI / 180.0;  // to radians
1491     }
1492 }
1493
1494 - (int) currentPage
1495 {
1496     return _currentPage;
1497 }
1498
1499 - (int) pageCount
1500 {
1501     return CGPDFDocumentGetNumberOfPages(_document);
1502 }
1503
1504 @end
1505
1506 CGColorSpaceRef WebCGColorSpaceCreateRGB(void)
1507 {
1508 #ifdef COLORMATCH_EVERYTHING
1509 #if BUILDING_ON_PANTHER
1510     return CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
1511 #else // !BUILDING_ON_PANTHER
1512     return CGColorSpaceCreateDeviceRGB();
1513 #endif // BUILDING_ON_PANTHER
1514 #else // !COLORMATCH_EVERYTHING
1515 #if BUILDING_ONPANTHER
1516     return CGColorSpaceCreateDeviceRGB();
1517 #else // !BUILDING_ON_PANTHER
1518     return CGColorSpaceCreateDisplayRGB();
1519 #endif // BUILDING_ON_PANTHER
1520 #endif    
1521 }
1522
1523 CGColorSpaceRef WebCGColorSpaceCreateGray(void)
1524 {
1525 #ifdef COLORMATCH_EVERYTHING
1526 #if BUILDING_ON_PANTHER
1527     return CGColorSpaceCreateWithName(kCGColorSpaceUserGray);
1528 #else // !BUILDING_ON_PANTHER
1529     return CGColorSpaceCreateDeviceGray();
1530 #endif // BUILDING_ON_PANTHER
1531 #else // !COLORMATCH_EVERYTHING
1532 #if BUILDING_ONPANTHER
1533     return CGColorSpaceCreateDeviceGray();
1534 #else // !BUILDING_ON_PANTHER
1535     return CGColorSpaceCreateDisplayGray();
1536 #endif // BUILDING_ON_PANTHER
1537 #endif    
1538 }
1539
1540 CGColorSpaceRef WebCGColorSpaceCreateCMYK(void)
1541 {
1542 #ifdef COLORMATCH_EVERYTHING
1543 #if BUILDING_ON_PANTHER
1544     return CGColorSpaceCreateWithName(kCGColorSpaceUserCMYK);
1545 #else // !BUILDING_ON_PANTHER
1546     return CGColorSpaceCreateDeviceCMYK();
1547 #endif // BUILDING_ON_PANTHER
1548 #else // !COLORMATCH_EVERYTHING
1549 #if BUILDING_ONPANTHER
1550     return CGColorSpaceCreateDeviceCMYK();
1551 #else // !BUILDING_ON_PANTHER
1552     // FIXME: no device CMYK
1553     return CGColorSpaceCreateDeviceCMYK();
1554 #endif // BUILDING_ON_PANTHER
1555 #endif    
1556 }