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