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 #ifdef kImageBytesCutoff
375 // This is a hack to help with testing display of partially-loaded images.
376 // To enable it, define kImageBytesCutoff to be a size smaller than that of the image files
377 // being loaded. They'll never finish loading.
378 if( length > kImageBytesCutoff ) {
379 length = kImageBytesCutoff;
384 CFDataRef data = CFDataCreate (NULL, bytes, length);
387 [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
392 [self _createPDFWithData:(NSData *)data];
396 [self _invalidateImages];
397 CGImageSourceUpdateData (imageSource, data, isComplete);
406 - (void)_fillSolidColorInRect:(CGRect)rect compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext
408 /*NSLog(@"WebImageData %p: filling with color %p, in {%.0f,%.0f, %.0f x %.0f}",
409 self,solidColor,rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);*/
411 CGContextSaveGState (aContext);
412 CGContextSetFillColorWithColor(aContext, solidColor);
413 CGContextSetCompositeOperation (aContext, op);
414 CGContextFillRect (aContext, rect);
415 CGContextRestoreGState (aContext);
419 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
422 [self _PDFDrawFromRect:NSMakeRect(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
423 toRect:NSMakeRect(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
432 CGImageRef image = [self imageAtIndex:index];
439 if( isSolidColor && index==0 ) {
440 [self _fillSolidColorInRect: ir compositeOperation: op context: aContext];
443 CGContextSaveGState (aContext);
445 // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
446 // that is currently decoded. This could be less that the actual height.
447 CGSize selfSize = [self size]; // full image size, in pixels
448 float curHeight = CGImageGetHeight(image); // height of loaded portion, in pixels
450 if( curHeight < selfSize.height ) {
451 adjustedSize.height *= curHeight / selfSize.height;
453 // Is the amount of available bands less than what we need to draw? If so,
454 // we may have to clip 'fr' if it goes outside the available bounds.
455 if( CGRectGetMaxY(fr) > adjustedSize.height ) {
456 float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
457 if( frHeight <= 0 ) {
459 return; // clipped out entirely
461 ir.size.height *= (frHeight / fr.size.height); // scale ir proportionally to fr
462 fr.size.height = frHeight;
467 CGContextSetCompositeOperation (aContext, op);
468 CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
469 CGContextScaleCTM (aContext, 1, -1);
470 CGContextTranslateCTM (aContext, 0, -ir.size.height);
472 // Translated to origin, now draw at 0,0.
473 ir.origin.x = ir.origin.y = 0;
475 // If we're drawing a sub portion of the image then create
476 // a image for the sub portion and draw that.
477 // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
478 if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
479 // Convert ft to image pixel coords:
480 float xscale = adjustedSize.width / selfSize.width;
481 float yscale = adjustedSize.height / curHeight; // yes, curHeight, not selfSize.height!
482 fr.origin.x /= xscale;
483 fr.origin.y /= yscale;
484 fr.size.width /= xscale;
485 fr.size.height /= yscale;
487 image = CGImageCreateWithImageInRect (image, fr);
489 CGContextDrawImage (aContext, ir, image);
493 // otherwise draw the whole image.
495 CGContextDrawImage (aContext, ir, image);
498 CGContextRestoreGState (aContext);
505 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
507 [self drawImageAtIndex:index inRect:ir fromRect:fr adjustedSize:[self size] compositeOperation:op context:aContext];
510 static void drawPattern (void * info, CGContextRef context)
512 WebImageData *data = (WebImageData *)info;
514 CGImageRef image = (CGImageRef)[data imageAtIndex:[data currentFrame]];
515 float w = CGImageGetWidth(image);
516 float h = CGImageGetHeight(image);
517 CGContextDrawImage (context, CGRectMake(0, [data size].height-h, w, h), image);
520 CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
522 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
528 size_t frame = [self currentFrame];
529 CGImageRef image = [self imageAtIndex:frame];
531 ERROR ("unable to find image");
536 if( frame == 0 && isSolidColor ) {
537 [self _fillSolidColorInRect: rect compositeOperation: kCGCompositeSover context: aContext];
540 CGSize tileSize = [self size];
541 NSSize imageSize = NSMakeSize(CGImageGetWidth(image),CGImageGetHeight(image));
543 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
545 oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, tileSize.width) - tileSize.width, tileSize.width);
546 oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, tileSize.height) - tileSize.height, tileSize.height);
547 oneTileRect.size = imageSize;
549 // If the single image draw covers the whole area, then just draw once.
550 if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
553 fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
554 fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
555 fromRect.size = rect.size;
559 [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
564 // Compute the appropriate phase relative to the top level view in the window.
565 // Conveniently, the oneTileRect we computed above has the appropriate origin.
566 NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
568 // WebCore may translate the focus, and thus need an extra phase correction
569 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
570 originInWindow.x += extraPhase.x;
571 originInWindow.y += extraPhase.y;
572 CGSize phase = CGSizeMake(fmodf(originInWindow.x, tileSize.width), fmodf(originInWindow.y, tileSize.height));
574 // Possible optimization: We may want to cache the CGPatternRef
575 CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, imageSize.width, imageSize.height), CGAffineTransformIdentity, tileSize.width, tileSize.height,
576 kCGPatternTilingConstantSpacing, true, &patternCallbacks);
578 CGContextSaveGState (aContext);
580 CGContextSetPatternPhase(aContext, phase);
582 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
583 CGContextSetFillColorSpace(aContext, patternSpace);
584 CGColorSpaceRelease(patternSpace);
586 float patternAlpha = 1;
587 CGContextSetFillPattern(aContext, pattern, &patternAlpha);
589 CGContextSetCompositeOperation (aContext, kCGCompositeSover);
591 CGContextFillRect (aContext, rect);
593 CGPatternRelease (pattern);
595 CGContextRestoreGState (aContext);
598 ERROR ("unable to create pattern");
608 return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
614 float w = 0.f, h = 0.f;
618 CGRect mediaBox = [_PDFDoc mediaBox];
619 return mediaBox.size;
625 CFDictionaryRef properties = [self propertiesAtIndex:0];
627 CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
629 CFNumberGetValue (num, kCFNumberFloat32Type, &w);
630 num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
632 CFNumberGetValue (num, kCFNumberFloat32Type, &h);
646 - (float)_floatProperty:(CFStringRef)property type:(CFStringRef)type at:(size_t)i
650 CFDictionaryRef properties = [self propertiesAtIndex:i];
656 CFDictionaryRef typeProperties = CFDictionaryGetValue (properties, type);
657 if (!typeProperties) {
662 CFNumberRef num = CFDictionaryGetValue (typeProperties, property);
669 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
676 - (float)_floatFileProperty:(CFStringRef)property type:(CFStringRef)type hasProperty:(BOOL *)hasProperty;
682 CFDictionaryRef properties = [self fileProperties];
689 properties = CFDictionaryGetValue (properties, type);
696 CFNumberRef num = CFDictionaryGetValue (properties, property);
703 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
712 #define MINIMUM_DURATION (1.0/30.0)
714 - (float)_frameDurationAt:(size_t)i
716 float duration = [self _floatProperty:kCGImagePropertyGIFDelayTime type:kCGImagePropertyGIFDictionary at:i];
717 if (duration < MINIMUM_DURATION) {
719 Many annoying ads specify a 0 duration to make an image flash
720 as quickly as possible. However a zero duration is faster than
721 the refresh rate. We need to pick a minimum duration.
723 Browsers handle the minimum time case differently. IE seems to use something
724 close to 1/30th of a second. Konqueror uses 0. The ImageMagick library
725 uses 1/100th. The units in the GIF specification are 1/100th of second.
726 We will use 1/30th of second as the minimum time.
728 duration = MINIMUM_DURATION;
733 - (float)_frameDuration
735 size_t num = [self numberOfImages];
736 if (frameDurationsSize && num > frameDurationsSize) {
737 free (frameDurations);
739 frameDurationsSize = 0;
742 if (!frameDurations) {
745 frameDurations = (float *)malloc (sizeof(float) * num);
746 for (i = 0; i < num; i++) {
747 frameDurations[i] = [self _frameDurationAt:i];
749 frameDurationsSize = num;
751 else if (frameDurations[currentFrame] == 0.f) {
752 frameDurations[currentFrame] = [self _frameDurationAt:currentFrame];
755 return frameDurations[currentFrame];
758 - (int)_repetitionCount
763 // No property means loop once.
764 // A property with value 0 means loops forever.
765 count = [self _floatFileProperty:kCGImagePropertyGIFLoopCount type:kCGImagePropertyGIFDictionary hasProperty:&hasProperty];
772 - (BOOL)isAnimationFinished
774 return animationFinished;
777 static NSMutableSet *activeAnimations;
779 + (void)stopAnimationsInView:(NSView *)aView
781 NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
782 WebImageData *animation;
783 NSMutableSet *renderersToStop = nil;
785 // Determine all the renderers that are drawing animations in the view.
786 // A set of sets, one set of renderers for each image being animated
787 // in the view. It is necessary to gather the all renderers to stop
788 // before actually stopping them because the process of stopping them
789 // will modify the active animations and animating renderer collections.
790 while ((animation = [objectEnumerator nextObject])) {
791 NSSet *renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
792 if (renderersInView) {
793 if (!renderersToStop)
794 renderersToStop = [[NSMutableSet alloc] init];
795 [renderersToStop unionSet:renderersInView];
799 // Now tell them all to stop drawing.
800 [renderersToStop makeObjectsPerformSelector:@selector(stopAnimation)];
801 [renderersToStop release];
804 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
806 if (!animatingRenderers)
807 animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
809 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
811 renderers = [[NSMutableSet alloc] init];
812 CFDictionaryAddValue(animatingRenderers, view, renderers);
816 [renderers addObject:r];
818 if (!activeAnimations)
819 activeAnimations = [[NSMutableSet alloc] init];
821 [activeAnimations addObject:self];
824 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
826 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
828 while ((view = [viewEnumerator nextObject])) {
829 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
830 [renderers removeObject:r];
831 if ([renderers count] == 0) {
832 CFDictionaryRemoveValue (animatingRenderers, view);
836 if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
837 [activeAnimations removeObject:self];
838 [self _stopAnimation];
842 - (void)resetAnimation
844 [self _stopAnimation];
846 repetitionsComplete = 0;
847 animationFinished = NO;
850 - (void)_stopAnimation
852 // This timer is used to animate all occurences of this image. Don't invalidate
853 // the timer unless all renderers have stopped drawing.
854 [frameTimer invalidate];
855 [frameTimer release];
859 - (void)_nextFrame:(id)context
861 // Release the timer that just fired.
862 [frameTimer release];
866 if (currentFrame >= [self numberOfImages]) {
867 repetitionsComplete += 1;
868 if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
869 animationFinished = YES;
876 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
878 while ((view = [viewEnumerator nextObject])) {
879 NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
880 WebImageRenderer *renderer;
881 NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
882 while ((renderer = [rendererEnumerator nextObject])) {
883 [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
888 - (BOOL)shouldAnimate
890 return [self numberOfImages] > 1 && ![self isAnimationFinished];
895 if (frameTimer && [frameTimer isValid])
897 frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
899 selector:@selector(_nextFrame:)
904 -(void)_createPDFWithData:(NSData *)data
907 _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
911 - (CGPDFDocumentRef)_PDFDocumentRef
913 return [_PDFDoc documentRef];
916 - (void)_PDFDrawInContext:(CGContextRef)context
918 CGPDFDocumentRef document = [self _PDFDocumentRef];
919 if (document != NULL) {
920 CGRect mediaBox = [_PDFDoc mediaBox];
922 CGContextSaveGState(context);
923 // Rotate translate image into position according to doc properties.
924 [_PDFDoc adjustCTM:context];
926 // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
928 CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
930 CGContextRestoreGState(context);
934 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
936 float hScale, vScale;
938 CGContextSaveGState(context);
940 CGContextSetCompositeOperation (context, op);
942 // Scale and translate so the document is rendered in the correct location.
943 hScale = dstRect.size.width / srcRect.size.width;
944 vScale = dstRect.size.height / srcRect.size.height;
946 CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
947 CGContextScaleCTM (context, hScale, vScale);
949 // Reverse if flipped image.
951 CGContextScaleCTM(context, 1, -1);
952 CGContextTranslateCTM (context, 0, -dstRect.size.height);
955 // Clip to destination in case we are imaging part of the source only
956 CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
959 [self _PDFDrawInContext:context];
961 // done with our fancy transforms
962 CGContextRestoreGState(context);