3 Copyright (c) 2004 Apple, Inc. All rights reserved.
5 #import <WebKit/WebAssertions.h>
6 #import <WebKit/WebGraphicsBridge.h>
7 #import <WebKit/WebImageData.h>
8 #import <WebKit/WebImageDecoder.h>
9 #import <WebKit/WebImageRenderer.h>
10 #import <WebKit/WebImageRendererFactory.h>
11 #import <WebKit/WebKitSystemBits.h>
13 #import <WebCore/WebCoreImageRenderer.h>
15 #import <CoreGraphics/CGContextPrivate.h>
16 #import <CoreGraphics/CGContextGState.h>
17 #import <CoreGraphics/CGColorSpacePrivate.h>
21 #import <ImageIO/CGImageSourcePrivate.h>
23 static CFDictionaryRef imageSourceOptions;
25 // Forward declarations of internal methods.
26 @interface WebImageData (WebInternal)
27 - (void)_commonTermination;
28 - (void)_invalidateImages;
29 - (void)_invalidateImageProperties;
30 - (int)_repetitionCount;
31 - (float)_frameDuration;
32 - (void)_stopAnimation;
34 - (CFDictionaryRef)_imageSourceOptions;
35 -(void)_createPDFWithData:(NSData *)data;
36 - (CGPDFDocumentRef)_PDFDocumentRef;
37 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context;
38 - (void)_cacheImages:(size_t)optionalIndex allImages:(BOOL)allImages;
42 @implementation WebImageData
46 // Currently threaded decoding doesn't play well with the WebCore cache. Until
47 // those issues are resolved threaded decoding is OFF by default, even on dual CPU
49 //[WebImageRendererFactory setShouldUseThreadedDecoding:(WebNumberOfCPUs() >= 2 ? YES : NO)];
50 [WebImageRendererFactory setShouldUseThreadedDecoding:NO];
57 if ([WebImageRendererFactory shouldUseThreadedDecoding])
58 decodeLock = [[NSLock alloc] init];
60 imageSource = CGImageSourceCreateIncremental ([self _imageSourceOptions]);
65 - (void)setIsPDF:(BOOL)f
75 - (void)_commonTermination
79 [self _invalidateImages];
80 [self _invalidateImageProperties];
83 CFRelease (fileProperties);
86 CFRelease (imageSource);
88 if (animatingRenderers)
89 CFRelease (animatingRenderers);
91 free (frameDurations);
99 [self _commonTermination];
106 [self _commonTermination];
110 - copyWithZone:(NSZone *)zone
114 copy = [[WebImageData alloc] init];
115 CFRetain (imageSource);
116 copy->imageSource = imageSource;
122 - (size_t)numberOfImages
125 return CGImageSourceGetCount(imageSource);
129 - (size_t)currentFrame
134 - (void)_invalidateImages
138 for (i = 0; i < imagesSize; i++) {
140 CFRelease (images[i]);
147 CFRelease(solidColor);
153 - (void)_invalidateImageProperties
156 for (i = 0; i < imagePropertiesSize; i++) {
157 if (imageProperties[i])
158 CFRelease (imageProperties[i]);
160 free (imageProperties);
162 imagePropertiesSize = 0;
165 - (CFDictionaryRef)_imageSourceOptions
167 if (!imageSourceOptions) {
168 const void * keys[2] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32 };
169 const void * values[2] = { kCFBooleanTrue, kCFBooleanTrue };
170 imageSourceOptions = CFDictionaryCreate (NULL, keys, values, 2,
171 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
173 return imageSourceOptions;
176 - (CGImageRef)_noColorCorrectionImage:(CGImageRef)image withProperties:(CFDictionaryRef)props;
178 CGColorSpaceRef uncorrectedColorSpace = 0;
179 CGImageRef noColorCorrectionImage = 0;
181 if (!CFDictionaryGetValue (props, kCGImagePropertyProfileName)) {
182 CFStringRef colorModel = CFDictionaryGetValue (props, kCGImagePropertyColorModel);
185 if (CFStringCompare (colorModel, CFSTR("RGB"), 0) == kCFCompareEqualTo)
186 uncorrectedColorSpace = CGColorSpaceCreateDisplayRGB();
187 else if (CFStringCompare (colorModel, CFSTR("Gray"), 0) == kCFCompareEqualTo)
188 uncorrectedColorSpace = CGColorSpaceCreateDisplayGray();
191 if (uncorrectedColorSpace) {
192 noColorCorrectionImage = CGImageCreateCopyWithColorSpace (image, uncorrectedColorSpace);
193 CFRelease (uncorrectedColorSpace);
196 return noColorCorrectionImage;
199 - (CGImageRef)imageAtIndex:(size_t)index
201 if (index >= [self numberOfImages])
204 if (!images || images[index] == 0){
205 [self _cacheImages:index allImages:NO];
208 return images[index];
211 - (CFDictionaryRef)fileProperties
213 if (!fileProperties) {
214 fileProperties = CGImageSourceCopyProperties (imageSource, 0);
217 return fileProperties;
220 - (CFDictionaryRef)propertiesAtIndex:(size_t)index
222 size_t num = [self numberOfImages];
224 // Number of images changed!
225 if (imagePropertiesSize && num > imagePropertiesSize) {
227 [self _invalidateImageProperties];
230 if (imageProperties == 0 && num) {
231 imageProperties = (CFDictionaryRef *)malloc (num * sizeof(CFDictionaryRef));
233 for (i = 0; i < num; i++) {
234 #if USE_DEPRECATED_IMAGESOURCE_API
235 imageProperties[i] = CGImageSourceGetPropertiesAtIndex (imageSource, i, 0);
236 if (imageProperties[i])
237 CFRetain (imageProperties[i]);
239 imageProperties[i] = CGImageSourceCopyPropertiesAtIndex (imageSource, i, 0);
242 imagePropertiesSize = num;
246 // If image properties are nil, try to get them again. May have attempted to
247 // get them before enough data was available in the header.
248 if (imageProperties[index] == 0) {
249 #if USE_DEPRECATED_IMAGESOURCE_API
250 imageProperties[index] = CGImageSourceGetPropertiesAtIndex (imageSource, index, 0);
251 if (imageProperties[index])
252 CFRetain (imageProperties[index]);
254 imageProperties[index] = CGImageSourceCopyPropertiesAtIndex (imageSource, index, 0);
258 return imageProperties[index];
264 - (void)_checkSolidColor: (CGImageRef)image
268 CFRelease(solidColor);
272 // Currently we only check for solid color in the important special case of a 1x1 image
273 if( image && CGImageGetWidth(image)==1 && CGImageGetHeight(image)==1 ) {
274 float pixel[4]; // RGBA
275 CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
276 CGContextRef bmap = CGBitmapContextCreate(&pixel,1,1,8*sizeof(float),sizeof(pixel),space,
277 kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents);
279 CGContextSetCompositeOperation(bmap, kCGCompositeCopy);
280 CGRect dst = {{0,0},{1,1}};
281 CGContextDrawImage(bmap,dst,image);
283 solidColor = CGColorCreate(space,pixel);
286 /*NSLog(@"WebImageData %p: 1x1 image has color {%f,%f,%f,%f} => %p",
287 self,pixel[0],pixel[1],pixel[2],pixel[3],solidColor);*/
289 ERROR("Couldn't create CGBitmapContext");
295 - (void)_cacheImages:(size_t)optionalIndex allImages:(BOOL)allImages
299 imagesSize = [self numberOfImages];
307 from = optionalIndex;
308 to = optionalIndex+1;
310 for (i = from; i < to; i++) {
312 images = (CGImageRef *)calloc (imagesSize, sizeof(CGImageRef *));
315 images[i] = CGImageSourceCreateImageAtIndex (imageSource, i, [self _imageSourceOptions]);
318 if (from==0 && to>0) {
319 // Loaded image 0. Check whether it's a solid color:
320 [self _checkSolidColor: images[0]];
324 // Called from decoder thread.
325 - (void)decodeData:(CFDataRef)data isComplete:(BOOL)isComplete callback:(id)callback
331 [self _createPDFWithData:(NSData *)data];
335 // The work of decoding is actually triggered by image creation.
336 CGImageSourceUpdateData (imageSource, data, isComplete);
337 [self _invalidateImages];
338 [self _cacheImages:0 allImages:YES];
344 if (isComplete && callback) {
345 if ([self _PDFDocumentRef]) {
346 [WebImageDecoder decodeComplete:callback status:kCGImageStatusComplete];
349 [WebImageDecoder decodeComplete:callback status:kCGImageStatusInvalidData];
354 // Use status from first image to trigger appropriate notification back to WebCore
357 CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, 0);
359 // Lie about status. If we have all the data, go ahead and say we're complete
360 // as long we are have at least some valid bands (i.e. >= kCGImageStatusIncomplete).
361 // We have to lie because CG incorrectly reports the status.
362 if (isComplete && imageStatus >= kCGImageStatusIncomplete)
363 imageStatus = kCGImageStatusComplete;
365 // Only send bad status if we've read the whole image.
366 if (isComplete || (!isComplete && imageStatus >= kCGImageStatusIncomplete))
367 [WebImageDecoder decodeComplete:callback status:imageStatus];
372 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)callback
374 CFDataRef data = CFDataCreate (NULL, bytes, length);
377 [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
382 [self _createPDFWithData:(NSData *)data];
386 [self _invalidateImages];
387 CGImageSourceUpdateData (imageSource, data, isComplete);
396 - (void)_fillSolidColorInRect:(CGRect)rect compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext
398 /*NSLog(@"WebImageData %p: filling with color %p, in {%.0f,%.0f, %.0f x %.0f}",
399 self,solidColor,rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);*/
401 CGContextSaveGState (aContext);
402 CGContextSetFillColorWithColor(aContext, solidColor);
403 CGContextSetCompositeOperation (aContext, op);
404 CGContextFillRect (aContext, rect);
405 CGContextRestoreGState (aContext);
409 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
412 [self _PDFDrawFromRect:NSMakeRect(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
413 toRect:NSMakeRect(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
422 CGImageRef image = [self imageAtIndex:index];
429 if( isSolidColor && index==0 ) {
430 [self _fillSolidColorInRect: ir compositeOperation: op context: aContext];
433 CGContextSaveGState (aContext);
435 // Get the height of the portion of the image that is currently decoded. This
436 // could be less that the actual height.
437 float h = CGImageGetHeight(image);
439 // Is the amount of available bands less than what we need to draw? If so,
442 CGSize actualSize = [self size];
443 if (h != actualSize.height) {
444 float proportionLoaded = h/actualSize.height;
445 fr.size.height = fr.size.height * proportionLoaded;
446 ir.size.height = ir.size.height * proportionLoaded;
451 CGContextSetCompositeOperation (aContext, op);
452 CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
453 CGContextScaleCTM (aContext, 1, -1);
454 CGContextTranslateCTM (aContext, 0, -ir.size.height);
456 // Translated to origin, now draw at 0,0.
457 ir.origin.x = ir.origin.y = 0;
459 // If we're drawing a sub portion of the image then create
460 // a image for the sub portion and draw that.
461 // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
462 if (clipped == NO && (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height)) {
463 image = CGImageCreateWithImageInRect (image, fr);
465 CGContextDrawImage (aContext, ir, image);
469 // otherwise draw the whole image.
471 CGContextDrawImage (aContext, ir, image);
474 CGContextRestoreGState (aContext);
481 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
483 [self drawImageAtIndex:index inRect:ir fromRect:fr adjustedSize:[self size] compositeOperation:op context:aContext];
486 static void drawPattern (void * info, CGContextRef context)
488 WebImageData *data = (WebImageData *)info;
490 CGImageRef image = (CGImageRef)[data imageAtIndex:[data currentFrame]];
491 float w = CGImageGetWidth(image);
492 float h = CGImageGetHeight(image);
493 CGContextDrawImage (context, CGRectMake(0, [data size].height-h, w, h), image);
496 CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
498 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
504 size_t frame = [self currentFrame];
505 CGImageRef image = [self imageAtIndex:frame];
507 ERROR ("unable to find image");
512 if( frame == 0 && isSolidColor ) {
513 [self _fillSolidColorInRect: rect compositeOperation: kCGCompositeSover context: aContext];
516 CGSize tileSize = [self size];
517 NSSize imageSize = NSMakeSize(CGImageGetWidth(image),CGImageGetHeight(image));
519 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
521 oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, tileSize.width) - tileSize.width, tileSize.width);
522 oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, tileSize.height) - tileSize.height, tileSize.height);
523 oneTileRect.size = imageSize;
525 // If the single image draw covers the whole area, then just draw once.
526 if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
529 fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
530 fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
531 fromRect.size = rect.size;
535 [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
540 // Compute the appropriate phase relative to the top level view in the window.
541 // Conveniently, the oneTileRect we computed above has the appropriate origin.
542 NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
544 // WebCore may translate the focus, and thus need an extra phase correction
545 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
546 originInWindow.x += extraPhase.x;
547 originInWindow.y += extraPhase.y;
548 CGSize phase = CGSizeMake(fmodf(originInWindow.x, tileSize.width), fmodf(originInWindow.y, tileSize.height));
550 // Possible optimization: We may want to cache the CGPatternRef
551 CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, imageSize.width, imageSize.height), CGAffineTransformIdentity, tileSize.width, tileSize.height,
552 kCGPatternTilingConstantSpacing, true, &patternCallbacks);
554 CGContextSaveGState (aContext);
556 CGContextSetPatternPhase(aContext, phase);
558 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
559 CGContextSetFillColorSpace(aContext, patternSpace);
560 CGColorSpaceRelease(patternSpace);
562 float patternAlpha = 1;
563 CGContextSetFillPattern(aContext, pattern, &patternAlpha);
565 CGContextSetCompositeOperation (aContext, kCGCompositeSover);
567 CGContextFillRect (aContext, rect);
569 CGPatternRelease (pattern);
571 CGContextRestoreGState (aContext);
574 ERROR ("unable to create pattern");
584 return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
590 float w = 0.f, h = 0.f;
594 CGRect mediaBox = [_PDFDoc mediaBox];
595 return mediaBox.size;
601 CFDictionaryRef properties = [self propertiesAtIndex:0];
603 CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
605 CFNumberGetValue (num, kCFNumberFloat32Type, &w);
606 num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
608 CFNumberGetValue (num, kCFNumberFloat32Type, &h);
622 - (float)_floatProperty:(CFStringRef)property type:(CFStringRef)type at:(size_t)i
626 CFDictionaryRef properties = [self propertiesAtIndex:i];
632 CFDictionaryRef typeProperties = CFDictionaryGetValue (properties, type);
633 if (!typeProperties) {
638 CFNumberRef num = CFDictionaryGetValue (typeProperties, property);
645 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
652 - (float)_floatFileProperty:(CFStringRef)property type:(CFStringRef)type hasProperty:(BOOL *)hasProperty;
658 CFDictionaryRef properties = [self fileProperties];
665 properties = CFDictionaryGetValue (properties, type);
672 CFNumberRef num = CFDictionaryGetValue (properties, property);
679 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
688 #define MINIMUM_DURATION (1.0/30.0)
690 - (float)_frameDurationAt:(size_t)i
692 float duration = [self _floatProperty:kCGImagePropertyGIFDelayTime type:kCGImagePropertyGIFDictionary at:i];
693 if (duration < MINIMUM_DURATION) {
695 Many annoying ads specify a 0 duration to make an image flash
696 as quickly as possible. However a zero duration is faster than
697 the refresh rate. We need to pick a minimum duration.
699 Browsers handle the minimum time case differently. IE seems to use something
700 close to 1/30th of a second. Konqueror uses 0. The ImageMagick library
701 uses 1/100th. The units in the GIF specification are 1/100th of second.
702 We will use 1/30th of second as the minimum time.
704 duration = MINIMUM_DURATION;
709 - (float)_frameDuration
711 size_t num = [self numberOfImages];
712 if (frameDurationsSize && num > frameDurationsSize) {
713 free (frameDurations);
715 frameDurationsSize = 0;
718 if (!frameDurations) {
721 frameDurations = (float *)malloc (sizeof(float) * num);
722 for (i = 0; i < num; i++) {
723 frameDurations[i] = [self _frameDurationAt:i];
725 frameDurationsSize = num;
727 else if (frameDurations[currentFrame] == 0.f) {
728 frameDurations[currentFrame] = [self _frameDurationAt:currentFrame];
731 return frameDurations[currentFrame];
734 - (int)_repetitionCount
739 // No property means loop once.
740 // A property with value 0 means loops forever.
741 count = [self _floatFileProperty:kCGImagePropertyGIFLoopCount type:kCGImagePropertyGIFDictionary hasProperty:&hasProperty];
748 - (BOOL)isAnimationFinished
750 return animationFinished;
753 static NSMutableSet *activeAnimations;
755 + (void)stopAnimationsInView:(NSView *)aView
757 NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
758 WebImageData *animation;
759 NSMutableSet *renderersToStop = nil;
761 // Determine all the renderers that are drawing animations in the view.
762 // A set of sets, one set of renderers for each image being animated
763 // in the view. It is necessary to gather the all renderers to stop
764 // before actually stopping them because the process of stopping them
765 // will modify the active animations and animating renderer collections.
766 while ((animation = [objectEnumerator nextObject])) {
767 NSSet *renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
768 if (renderersInView) {
769 if (!renderersToStop)
770 renderersToStop = [[NSMutableSet alloc] init];
771 [renderersToStop unionSet:renderersInView];
775 // Now tell them all to stop drawing.
776 [renderersToStop makeObjectsPerformSelector:@selector(stopAnimation)];
777 [renderersToStop release];
780 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
782 if (!animatingRenderers)
783 animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
785 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
787 renderers = [[NSMutableSet alloc] init];
788 CFDictionaryAddValue(animatingRenderers, view, renderers);
792 [renderers addObject:r];
794 if (!activeAnimations)
795 activeAnimations = [[NSMutableSet alloc] init];
797 [activeAnimations addObject:self];
800 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
802 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
804 while ((view = [viewEnumerator nextObject])) {
805 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
806 [renderers removeObject:r];
807 if ([renderers count] == 0) {
808 CFDictionaryRemoveValue (animatingRenderers, view);
812 if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
813 [activeAnimations removeObject:self];
814 [self _stopAnimation];
818 - (void)resetAnimation
820 [self _stopAnimation];
822 repetitionsComplete = 0;
823 animationFinished = NO;
826 - (void)_stopAnimation
828 // This timer is used to animate all occurences of this image. Don't invalidate
829 // the timer unless all renderers have stopped drawing.
830 [frameTimer invalidate];
831 [frameTimer release];
835 - (void)_nextFrame:(id)context
837 // Release the timer that just fired.
838 [frameTimer release];
842 if (currentFrame >= [self numberOfImages]) {
843 repetitionsComplete += 1;
844 if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
845 animationFinished = YES;
852 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
854 while ((view = [viewEnumerator nextObject])) {
855 NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
856 WebImageRenderer *renderer;
857 NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
858 while ((renderer = [rendererEnumerator nextObject])) {
859 [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
864 - (BOOL)shouldAnimate
866 return [self numberOfImages] > 1 && ![self isAnimationFinished];
871 if (frameTimer && [frameTimer isValid])
873 frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
875 selector:@selector(_nextFrame:)
880 -(void)_createPDFWithData:(NSData *)data
883 _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
887 - (CGPDFDocumentRef)_PDFDocumentRef
889 return [_PDFDoc documentRef];
892 - (void)_PDFDrawInContext:(CGContextRef)context
894 CGPDFDocumentRef document = [self _PDFDocumentRef];
895 if (document != NULL) {
896 CGRect mediaBox = [_PDFDoc mediaBox];
898 CGContextSaveGState(context);
899 // Rotate translate image into position according to doc properties.
900 [_PDFDoc adjustCTM:context];
902 // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
904 CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
906 CGContextRestoreGState(context);
910 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
912 float hScale, vScale;
914 CGContextSaveGState(context);
916 CGContextSetCompositeOperation (context, op);
918 // Scale and translate so the document is rendered in the correct location.
919 hScale = dstRect.size.width / srcRect.size.width;
920 vScale = dstRect.size.height / srcRect.size.height;
922 CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
923 CGContextScaleCTM (context, hScale, vScale);
925 // Reverse if flipped image.
927 CGContextScaleCTM(context, 1, -1);
928 CGContextTranslateCTM (context, 0, -dstRect.size.height);
931 // Clip to destination in case we are imaging part of the source only
932 CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
935 [self _PDFDrawInContext:context];
937 // done with our fancy transforms
938 CGContextRestoreGState(context);