13e1f65f1ec69acf0fb478dad0409ee6f6d20df0
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebImageRenderer.m
1 /*      
2         WebImageRenderer.m
3         Copyright (c) 2002, 2003, Apple, Inc. All rights reserved.
4 */
5 #import <WebKit/WebImageRenderer.h>
6
7 #import <WebKit/WebAssertions.h>
8 #import <WebKit/WebImageRendererFactory.h>
9 #import <WebKit/WebGraphicsBridge.h>
10 #import <WebKit/WebHTMLView.h>
11 #import <WebKit/WebImageView.h>
12 #import <WebKit/WebNSObjectExtras.h>
13
14 #import <WebCore/WebCoreImageRenderer.h>
15
16 #import <CoreGraphics/CGContextPrivate.h>
17
18
19 #ifdef USE_CGIMAGEREF
20
21 #import <WebKit/WebImageData.h>
22
23 // Forward declarations of internal methods.
24 @interface WebImageRenderer (WebInternal)
25 - (void)_startOrContinueAnimationIfNecessary;
26 @end
27
28 @implementation WebImageRenderer
29
30 - (id)initWithMIMEType:(NSString *)MIME
31 {
32     self = [super init];
33     if (self != nil) {
34         MIMEType = [MIME copy];
35     }
36     return self;
37 }
38
39 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
40 {
41     self = [super init];
42     if (self != nil) {
43         MIMEType = [MIME copy];
44         imageData = [[WebImageData alloc] init];
45         [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
46     }
47     return self;
48 }
49
50 - (id)initWithContentsOfFile:(NSString *)filename
51 {
52     self = [super init];
53
54     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
55     NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
56
57     imageData = [[WebImageData alloc] init];
58     NSData *data = [NSData dataWithContentsOfFile:imagePath];
59     [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
60         
61
62     return self;
63 }
64
65 - (void)dealloc
66 {
67     [MIMEType release];
68     [imageData release];
69     [nsimage release];
70     [TIFFData release];
71     [super dealloc];
72 }
73
74 - copyWithZone:(NSZone *)zone
75 {
76     WebImageRenderer *copy;
77
78     copy = [[WebImageRenderer alloc] init];
79     copy->MIMEType = [MIMEType copy];
80     copy->adjustedSize = adjustedSize;
81     copy->isSizeAdjusted = isSizeAdjusted;
82     copy->imageData = [imageData retain];
83         
84     return copy;
85 }
86
87 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
88 {
89     return [self copyWithZone:0];
90 }
91
92 - (void)resize:(NSSize)s
93 {
94     isSizeAdjusted = YES;
95     adjustedSize = s;
96 }
97
98 - (NSSize)size
99 {
100     if (isSizeAdjusted)
101         return adjustedSize;
102         
103     if (!imageData)
104         return NSZeroSize;
105         
106     CGSize sz = [imageData size];
107     return NSMakeSize(sz.width, sz.height);
108 }
109
110 - (NSString *)MIMEType
111 {
112     return MIMEType;
113 }
114
115 - (int)frameCount
116 {
117     return [imageData numberOfImages];
118 }
119
120
121 - (BOOL)isNull
122 {
123     return [imageData isNull];
124 }
125
126 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
127 {
128     if (!imageData) {
129         imageData = [[WebImageData alloc] init];
130         if ([MIMEType isEqual:@"application/pdf"]) {
131             [imageData setIsPDF:YES];
132         }
133     }
134     return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
135 }
136
137 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
138 {
139     CGContextRef aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
140     NSCompositingOperation op = NSCompositeSourceOver;
141
142     [self drawImageInRect:ir fromRect:fr compositeOperator:op context:aContext];
143 }
144
145 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
146 {
147     if (aContext == 0)
148         aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
149     
150     NSCompositingOperation op = (NSCompositingOperation)operator;
151         
152     if (isSizeAdjusted) {
153         [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height) 
154                 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height) 
155                 adjustedSize:CGSizeMake(adjustedSize.width, adjustedSize.height)
156                 compositeOperation:op context:aContext];
157     }
158     else {
159         [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height) 
160                 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height) 
161                 compositeOperation:op context:aContext];
162     }
163
164     targetAnimationRect = ir;
165     [self _startOrContinueAnimationIfNecessary];
166 }
167
168 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
169 {
170     if (aContext == 0)
171         aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
172
173     [imageData tileInRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
174             fromPoint:CGPointMake(point.x, point.y) context:aContext];
175             
176     targetAnimationRect = rect;
177     [self _startOrContinueAnimationIfNecessary];
178 }
179
180 - (void)_startOrContinueAnimationIfNecessary
181 {
182     NSView *targetView = [NSView focusView];
183     
184     // Only animate if we're drawing into a WebHTMLView or WebImageView.  This fixes problems
185     // like <rdar://problem/3966973>, which describes a third party application that renders thumbnails of
186     // the page into a alternate view.
187     if (([targetView isKindOfClass:[WebHTMLView class]] || [targetView isKindOfClass:[WebImageView class]]) 
188             && [imageData shouldAnimate] && [MIMEType isEqual:@"image/gif"]) {
189         [imageData addAnimatingRenderer:self inView:targetView];
190         [imageData animate];
191     }
192 }
193
194 + (void)stopAnimationsInView:(NSView *)aView
195 {
196     [WebImageData stopAnimationsInView:aView];
197 }
198
199 - (void)resetAnimation
200 {
201     [imageData resetAnimation];
202 }
203
204
205 - (void)stopAnimation
206 {
207     [imageData removeAnimatingRenderer:self];
208 }
209
210 - (NSRect)targetAnimationRect
211 {
212     return targetAnimationRect;
213 }
214
215 - (void)increaseUseCount
216 {
217 }
218
219 - (void)decreaseUseCount
220 {
221 }
222
223 - (void)flushRasterCache
224 {
225 }
226
227 - (CGImageRef)imageRef
228 {
229     return [imageData imageAtIndex:0];
230 }
231
232 - (NSData *)TIFFRepresentation
233 {
234     if (!TIFFData) {
235         CGImageRef image = [imageData imageAtIndex:0];
236         if (!image)
237             return 0;
238             
239         CFMutableDataRef data = 0;
240         CGImageDestinationRef destination = 0;
241         
242         data = CFDataCreateMutable(NULL, 0);
243         // FIXME:  Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
244         destination = CGImageDestinationCreateWithData (data, CFSTR("public.tiff"), 1, NULL);
245         if (destination) {
246             CGImageDestinationAddImage (destination, image, NULL);
247             if (!CGImageDestinationFinalize (destination)) {
248                 ERROR ("Unable to create image\n");
249             }
250             CFRelease (destination);
251         }
252
253         TIFFData = (NSData *)data;
254     }
255     
256     return TIFFData;
257 }
258
259 - (NSImage *)image
260 {
261     if (!nsimage)
262         nsimage = [[NSImage alloc] initWithData:[self TIFFRepresentation]];
263     return nsimage;
264 }
265
266 @end
267
268 #else
269
270 @interface WebInternalImage : NSImage <NSCopying>
271 {
272     NSTimer *frameTimer;
273     NSView *frameView;
274     NSRect imageRect;
275     NSRect targetRect;
276
277     int loadStatus;
278
279     NSColor *patternColor;
280     int patternColorLoadStatus;
281
282     int repetitionsComplete;
283     BOOL animationFinished;
284     
285     NSPoint tilePoint;
286     BOOL animatedTile;
287
288     int compositeOperator;
289     id redirectContext;
290     CGContextRef context;
291     BOOL needFlushRasterCache;
292     BOOL rasterFlushing;
293     NSImageCacheMode rasterFlushingOldMode;
294     
295     NSString *MIMEType;
296     BOOL isNull;
297     int useCount;
298
299     CGImageRef cachedImageRef;
300     
301     id _PDFDoc;
302         
303 @public    
304     NSData *originalData;
305 }
306
307 - (id)initWithMIMEType:(NSString *)MIME;
308 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME;
309
310 - (void)releasePatternColor;
311
312 - (NSString *)MIMEType;
313 - (int)frameCount;
314
315 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
316 - (void)resize:(NSSize)s;
317 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr;
318 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context;
319 - (void)stopAnimation;
320 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext;
321 - (BOOL)isNull;
322 - (void)increaseUseCount;
323 - (void)decreaseUseCount;
324 - (WebImageRenderer *)createRendererIfNeeded;
325 - (void)flushRasterCache;
326 - (CGImageRef)imageRef;
327
328 + (void)stopAnimationsInView:(NSView *)aView;
329 - (void)resetAnimation;
330
331 - (void)startAnimationIfNecessary;
332 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext;
333 - (void)_endRedirectContext:(NSGraphicsContext *)aContext;
334 - (void)_needsRasterFlush;
335 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped;
336
337 @end
338
339 extern NSString *NSImageLoopCount;
340
341 /*
342     We need to get the AppKit to redirect drawing to our
343     CGContext.  There is currently (on Panther) no public
344     way to make this happen.  So we create a NSCGSContext
345     subclass and twiddle it's _cgsContext ivar directly.
346     Very fragile, but the only way...
347 */
348 @interface NSCGSContext : NSGraphicsContext {
349     CGContextRef _cgsContext;
350 }
351 @end
352
353 static CGImageRef _createImageRef(NSBitmapImageRep *rep);
354
355 @interface NSBitmapImageRep (AppKitInternals)
356 - (CGImageRef)_CGImageRef;
357 @end
358
359 @interface NSFocusStack : NSObject
360 @end
361
362 @interface WebImageContext : NSCGSContext {
363   @private
364     NSFocusStack* _focusStack;
365     NSRect        _bounds;
366     BOOL          _isFlipped;
367 }
368 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context;
369 - (NSRect)bounds;
370 @end
371
372
373 @implementation WebImageContext
374
375 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context {
376     
377     self = [super init];
378     if (self != nil) {
379         _bounds     = b;
380         _isFlipped  = YES;
381         if (context) {
382             _cgsContext = CGContextRetain(context);
383         }
384     }
385     
386     return self;
387 }
388
389 - (void)dealloc
390 {
391     [_focusStack release];
392     if (_cgsContext) {
393         CGContextRelease(_cgsContext);
394         _cgsContext = 0; // super dealloc may also release
395     }
396     [super dealloc];
397 }
398
399 - (void)finalize
400 {
401     if (_cgsContext) {
402         CGContextRelease(_cgsContext);
403         _cgsContext = 0; // super finalize may also release
404     }
405     [super finalize];
406 }
407
408 - (void)saveGraphicsState
409 {
410     if (_cgsContext) {
411         CGContextSaveGState(_cgsContext);
412     }
413 }
414
415 - (void)restoreGraphicsState
416 {
417     if (_cgsContext) {
418         CGContextRestoreGState(_cgsContext);
419     }
420 }
421
422 - (BOOL)isDrawingToScreen
423 {
424     return NO;
425 }
426
427 - (void *)focusStack
428 {
429     if (!_focusStack) _focusStack = [[NSFocusStack allocWithZone:NULL] init];
430     return _focusStack;
431 }
432
433 - (void)setFocusStack:(void *)stack
434 {
435     id oldstack = _focusStack;
436     _focusStack = [(id)stack retain];
437     [oldstack release];
438 }
439
440 - (NSRect)bounds
441 {
442     return _bounds;
443 }
444
445 - (BOOL)isFlipped
446 {
447     return _isFlipped;
448 }
449
450 @end
451
452 #define MINIMUM_DURATION (1.0/30.0)
453
454 @implementation WebInternalImage
455
456 static NSMutableSet *activeImageRenderers;
457
458 + (void)stopAnimationsInView:(NSView *)aView
459 {
460     NSEnumerator *objectEnumerator = [activeImageRenderers objectEnumerator];
461     WebInternalImage *renderer;
462     NSMutableSet *renderersToStop = [[NSMutableSet alloc] init];
463
464     while ((renderer = [objectEnumerator nextObject])) {
465         if (renderer->frameView == aView) {
466             [renderersToStop addObject: renderer];
467         }
468     }
469
470     objectEnumerator = [renderersToStop objectEnumerator];
471     while ((renderer = [objectEnumerator nextObject])) {
472         if (renderer->frameView == aView) {
473             [renderer stopAnimation];
474         }
475     }
476     [renderersToStop release];
477 }
478
479 - (id)initWithMIMEType:(NSString *)MIME
480 {
481     self = [super init];
482     if (self != nil) {
483         // Work around issue with flipped images and TIFF by never using the image cache.
484         // See bug 3344259 and related bugs.
485         [self setCacheMode:NSImageCacheNever];
486
487         loadStatus = NSImageRepLoadStatusUnknownType;
488         MIMEType = [MIME copy];
489         isNull = YES;
490         compositeOperator = (int)NSCompositeSourceOver;
491     }
492     
493     return self;
494 }
495
496 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
497 {
498     WebInternalImage *result = nil;
499
500     NS_DURING
501     
502         result = [super initWithData:data];
503         if (result != nil) {
504             // Work around issue with flipped images and TIFF by never using the image cache.
505             // See bug 3344259 and related bugs.
506             [result setCacheMode:NSImageCacheNever];
507     
508             result->loadStatus = NSImageRepLoadStatusUnknownType;
509             result->MIMEType = [MIME copy];
510             result->isNull = [data length] == 0;
511             result->compositeOperator = (int)NSCompositeSourceOver;
512         }
513
514     NS_HANDLER
515
516         result = nil;
517
518     NS_ENDHANDLER
519
520     return result;
521 }
522
523 - (id)initWithContentsOfFile:(NSString *)filename
524 {
525     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
526     NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
527     WebInternalImage *result = nil;
528
529     NS_DURING
530
531         result = [super initWithContentsOfFile:imagePath];
532         if (result != nil) {
533             // Work around issue with flipped images and TIFF by never using the image cache.
534             // See bug 3344259 and related bugs.
535             [result setCacheMode:NSImageCacheNever];
536     
537             result->loadStatus = NSImageRepLoadStatusUnknownType;
538             result->compositeOperator = (int)NSCompositeSourceOver;
539         }
540         
541     NS_HANDLER
542
543         result = nil;
544
545     NS_ENDHANDLER
546
547     return result;
548 }
549
550 - (void)increaseUseCount
551 {
552     useCount++;
553 }
554
555 - (void)decreaseUseCount
556 {
557     useCount--;
558 }
559
560 - (WebImageRenderer *)createRendererIfNeeded
561 {
562     // If an animated image appears multiple times in a given page, we
563     // must create multiple WebCoreImageRenderers so that each copy
564     // animates. However, we don't want to incur the expense of
565     // re-decoding for the very first use on a page, since QPixmap
566     // assignment always calls this method, even when just fetching
567     // the image from the cache for the first time for a page.
568     if (originalData && useCount) {
569         return [[[WebImageRendererFactory sharedFactory] imageRendererWithData:originalData MIMEType:MIMEType] retain];
570     }
571     return nil;
572 }
573
574 - copyWithZone:(NSZone *)zone
575 {
576     // FIXME: If we copy while doing an incremental load, it won't work.
577     WebInternalImage *copy;
578
579     copy = [super copyWithZone:zone];
580     copy->MIMEType = [MIMEType copy];
581     copy->originalData = [originalData retain];
582     copy->frameTimer = nil;
583     copy->frameView = nil;
584     copy->patternColor = nil;
585     copy->compositeOperator = compositeOperator;
586     copy->context = 0;
587
588     return copy;
589 }
590
591 - (BOOL)isNull
592 {
593     return isNull;
594 }
595
596 - (void)_adjustSizeToPixelDimensions
597 {
598     // Force the image to use the pixel size and ignore the dpi.
599     // Ignore any absolute size in the image and always use pixel dimensions.
600     NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
601     NSSize size = NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
602     [imageRep setSize:size];
603         
604     [self setScalesWhenResized:YES];
605     [self setSize:size];
606 }
607
608 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
609 {
610     NSArray *reps = [self representations];
611     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
612     
613     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]]) {
614         NSData *data = [[NSData alloc] initWithBytes:bytes length:length];
615
616         NS_DURING
617             // Get rep again to avoid bogus compiler warning.
618             NSBitmapImageRep *aRep = [[self representations] objectAtIndex:0];
619
620             loadStatus = [aRep incrementalLoadFromData:data complete:isComplete];
621         NS_HANDLER
622             loadStatus = NSImageRepLoadStatusInvalidData; // Arbitrary choice; any error will do.
623         NS_ENDHANDLER
624
625         // Hold onto the original data in case we need to copy this image.  (Workaround for appkit NSImage
626         // copy flaw).
627         if (isComplete && [self frameCount] > 1)
628             originalData = data;
629         else
630             [data release];
631
632         switch (loadStatus) {
633         case NSImageRepLoadStatusUnknownType:       // not enough data to determine image format. please feed me more data
634             //printf ("NSImageRepLoadStatusUnknownType size %d, isComplete %d\n", length, isComplete);
635             return NO;
636         case NSImageRepLoadStatusReadingHeader:     // image format known, reading header. not yet valid. more data needed
637             //printf ("NSImageRepLoadStatusReadingHeader size %d, isComplete %d\n", length, isComplete);
638             return NO;
639         case NSImageRepLoadStatusWillNeedAllData:   // can't read incrementally. will wait for complete data to become avail.
640             //printf ("NSImageRepLoadStatusWillNeedAllData size %d, isComplete %d\n", length, isComplete);
641             return NO;
642         case NSImageRepLoadStatusInvalidData:       // image decompression encountered error.
643             //printf ("NSImageRepLoadStatusInvalidData size %d, isComplete %d\n", length, isComplete);
644             return NO;
645         case NSImageRepLoadStatusUnexpectedEOF:     // ran out of data before full image was decompressed.
646             //printf ("NSImageRepLoadStatusUnexpectedEOF size %d, isComplete %d\n", length, isComplete);
647             return NO;
648         case NSImageRepLoadStatusCompleted:         // all is well, the full pixelsHigh image is valid.
649             //printf ("NSImageRepLoadStatusCompleted size %d, isComplete %d\n", length, isComplete);
650             [self _adjustSizeToPixelDimensions];        
651             isNull = NO;
652             return YES;
653         default:
654             [self _adjustSizeToPixelDimensions];
655             //printf ("incrementalLoadWithBytes: size %d, isComplete %d\n", length, isComplete);
656             // We have some valid data.  Return YES so we can attempt to draw what we've got.
657             isNull = NO;
658             return YES;
659         }
660     }
661     else {
662         if (isComplete) {
663             originalData = [[NSData alloc] initWithBytes:bytes length:length];
664             if ([MIMEType isEqual:@"application/pdf"]) {
665                 Class repClass = [NSImageRep imageRepClassForData:originalData];
666                 if (repClass) {
667                     NSImageRep *rep = [[repClass alloc] initWithData:originalData];
668                     [self addRepresentation:rep];
669                 }
670             }
671             isNull = NO;
672             return YES;
673         }
674     }
675     return NO;
676 }
677
678 - (void)dealloc
679 {
680     ASSERT(frameTimer == nil);
681     ASSERT(frameView == nil);
682     [patternColor release];
683     [MIMEType release];
684     [originalData release];
685     
686     if (context) {
687         CGContextRelease(context);
688         context = 0;
689     }
690
691     if (cachedImageRef) {
692         CGImageRelease (cachedImageRef);
693         cachedImageRef = 0;
694     }
695     
696     [_PDFDoc release];
697
698     [super dealloc];
699 }
700
701 - (void)finalize
702 {
703     ASSERT(frameTimer == nil);
704     ASSERT(frameView == nil);
705
706     if (context) {
707         CGContextRelease(context);
708     }
709
710     if (cachedImageRef) {
711         CGImageRelease (cachedImageRef);
712         cachedImageRef = 0;
713     }
714     
715     [super finalize];
716 }
717
718 - (id)firstRepProperty:(NSString *)propertyName
719 {
720     id firstRep = [[self representations] objectAtIndex:0];
721     id property = nil;
722     if ([firstRep respondsToSelector:@selector(valueForProperty:)])
723         property = [firstRep valueForProperty:propertyName];
724     return property;
725 }
726
727 - (int)frameCount
728 {
729     id property = [self firstRepProperty:NSImageFrameCount];
730     return property ? [property intValue] : 1;
731 }
732
733 - (int)currentFrame
734 {
735     id property = [self firstRepProperty:NSImageCurrentFrame];
736     return property ? [property intValue] : 1;
737 }
738
739 - (void)setCurrentFrame:(int)frame
740 {
741     NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
742     [imageRep setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:frame]];
743 }
744
745 - (float)unadjustedFrameDuration
746 {
747     id property = [self firstRepProperty:NSImageCurrentFrameDuration];
748     return property ? [property floatValue] : 0.0;
749 }
750
751 - (float)frameDuration
752 {
753     float duration = [self unadjustedFrameDuration];
754     if (duration < MINIMUM_DURATION) {
755         /*
756             Many annoying ads specify a 0 duration to make an image flash
757             as quickly as possible.  However a zero duration is faster than
758             the refresh rate.  We need to pick a minimum duration.
759             
760             Browsers handle the minimum time case differently.  IE seems to use something
761             close to 1/30th of a second.  Konqueror uses 0.  The ImageMagick library
762             uses 1/100th.  The units in the GIF specification are 1/100th of second.
763             We will use 1/30th of second as the minimum time.
764         */
765         duration = MINIMUM_DURATION;
766     }
767     return duration;
768 }
769
770 - (int)repetitionCount
771 {
772     id property = [self firstRepProperty:NSImageLoopCount];
773     int count = property ? [property intValue] : 0;
774     return count;
775 }
776
777 - (void)scheduleFrame
778 {
779     if (frameTimer && [frameTimer isValid])
780         return;
781     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self frameDuration]
782                                                     target:self
783                                                   selector:@selector(nextFrame:)
784                                                   userInfo:nil
785                                                    repeats:NO] retain];
786 }
787
788 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext
789 {
790     NSGraphicsContext *oldContext = 0;
791     if (aContext) {
792         oldContext = [NSGraphicsContext currentContext];
793         // Assumes that we are redirecting to a CGBitmapContext.
794         size_t w = CGBitmapContextGetWidth (aContext);
795         size_t h = CGBitmapContextGetHeight (aContext);
796         redirectContext = [[WebImageContext alloc] initWithBounds:NSMakeRect(0, 0, (float)w, (float)h) context:aContext];
797         [NSGraphicsContext setCurrentContext:redirectContext];
798     }
799     return oldContext; 
800 }
801
802 - (void)_endRedirectContext:(NSGraphicsContext *)aContext
803 {
804     if (aContext) {
805         [NSGraphicsContext setCurrentContext:aContext];
806         [redirectContext autorelease];
807         redirectContext = 0;
808     }
809 }
810
811 - (void)_needsRasterFlush
812 {
813 #if 0
814     if (needFlushRasterCache && [MIMEType isEqual: @"application/pdf"]) {
815         // FIXME:  At this point we need to flush the cached rasterized PDF.
816     }
817 #endif
818 }
819
820 - (void)_adjustColorSpace
821 {
822 #if COLORMATCH_EVERYTHING
823     NSArray *reps = [self representations];
824     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
825     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
826         [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
827         [[imageRep colorSpaceName] isEqualToString:NSDeviceRGBColorSpace]) {
828         [imageRep setColorSpaceName:NSCalibratedRGBColorSpace];
829     }
830 #else
831     NSArray *reps = [self representations];
832     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
833     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
834         [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
835         [[imageRep colorSpaceName] isEqualToString:NSCalibratedRGBColorSpace]) {
836         [imageRep setColorSpaceName:NSDeviceRGBColorSpace];
837     }
838 #endif
839 }
840
841
842 - (void)drawClippedToValidInRect:(NSRect)ir fromRect:(NSRect)fr
843 {
844     [self _adjustColorSpace];
845
846     if (loadStatus >= 0) {
847         // The last line might be a partial line, so the number of complete lines is the number
848         // we get from NSImage minus one.
849         int numCompleteLines = loadStatus - 1;
850         if (numCompleteLines <= 0) {
851             return;
852         }
853         int pixelsHigh = [[[self representations] objectAtIndex:0] pixelsHigh];
854         if (pixelsHigh > numCompleteLines) {
855             // Figure out how much of the image is OK to draw.  We can't simply
856             // use numCompleteLines because the image may be scaled.
857             float clippedImageHeight = floor([self size].height * numCompleteLines / pixelsHigh);
858             
859             // Figure out how much of the source is OK to draw from.
860             float clippedSourceHeight = clippedImageHeight - fr.origin.y;
861             if (clippedSourceHeight < 1) {
862                 return;
863             }
864             
865             // Figure out how much of the destination we are going to draw to.
866             float clippedDestinationHeight = ir.size.height * clippedSourceHeight / fr.size.height;
867
868             // Reduce heights of both rectangles without changing their positions.
869             // In the flipped case, just adjusting the height is sufficient.
870             ASSERT([self isFlipped]);
871             ASSERT([[NSView focusView] isFlipped]);
872             ir.size.height = clippedDestinationHeight;
873             fr.size.height = clippedSourceHeight;
874         }
875     }
876     
877     if (context) {
878         NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
879         [self _needsRasterFlush];
880
881         // If we have PDF then draw the PDF ourselves, bypassing the NSImage caching mechanisms,
882         // but only do this when we're rendering to an offscreen context.  NSImage will always
883         // cache the PDF image at it's native resolution, thus, causing artifacts when the image
884         // is drawn into a scaled or rotated context.
885         if ([MIMEType isEqual:@"application/pdf"])
886             [self _PDFDrawFromRect:fr toRect:ir operation:compositeOperator alpha:1.0 flipped:YES];
887         else
888             [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
889
890         [self _endRedirectContext:oldContext];
891     }
892     else {
893         [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
894     }
895 }
896
897 - (CGPDFDocumentRef)_PDFDocumentRef
898 {
899     if (!_PDFDoc) {
900         _PDFDoc = [[WebPDFDocument alloc] initWithData:originalData];
901     }
902         
903     return [_PDFDoc documentRef];
904 }
905
906 - (void)_PDFDraw
907 {
908     CGPDFDocumentRef document = [self _PDFDocumentRef];
909     if (document != NULL) {
910         CGContextRef _context  = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
911         CGRect       mediaBox = [_PDFDoc mediaBox];
912         
913         CGContextSaveGState(_context);
914         // Rotate translate image into position according to doc properties.
915         [_PDFDoc adjustCTM:_context];   
916
917         // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
918         // at 1, not 0.
919         CGContextDrawPDFDocument(_context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
920
921         CGContextRestoreGState(_context);
922     }
923 }
924
925 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped
926 {
927     // FIXME:  The rasterized PDF should be drawn into a cache, and the raster then composited.
928     
929     CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
930     float hScale, vScale;
931
932     CGContextSaveGState(_context);
933
934     CGContextSetCompositeOperation (_context, op);
935
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 }