WKGraphics.mm   [plain text]


//
//  WKGraphics.c
//
//  Copyright (C) 2005, 2006, 2007, 2009 Apple Inc.  All rights reserved.
//

#import "config.h"
#import "WKGraphics.h"

#import "WebCoreSystemInterface.h"
#import "Font.h"
#import "WebCoreThread.h"
#import <ImageIO/ImageIO.h>
#import <wtf/StdLibExtras.h>

using namespace WebCore;

static inline void _FillRectsUsingOperation(CGContextRef context, const CGRect* rects, int count, CGCompositeOperation op)
{
    int i;
    CGRect *integralRects = reinterpret_cast<CGRect *>(alloca(sizeof(CGRect) * count));
    
    assert (integralRects);
    
    for (i = 0; i < count; i++) {
        integralRects[i] = CGRectApplyAffineTransform (rects[i], CGContextGetCTM(context));
        integralRects[i] = CGRectIntegral (integralRects[i]);
        integralRects[i] = CGRectApplyAffineTransform (integralRects[i], CGAffineTransformInvert(CGContextGetCTM(context)));
    }
    CGCompositeOperation oldOp = CGContextGetCompositeOperation(context);
    CGContextSetCompositeOperation(context, op);
    CGContextFillRects(context, integralRects, count);
    CGContextSetCompositeOperation(context, oldOp);
}

static inline void _FillRectUsingOperation(CGContextRef context, CGRect rect, CGCompositeOperation op)
{
    if (rect.size.width > 0 && rect.size.height > 0) {
        _FillRectsUsingOperation (context, &rect, 1, op);
    }
}

void WKDrawFramedRect(CGContextRef context, CGRect aRect)
{
    WKDrawFramedRectWithWidthUsingOperation (context, aRect, 1.0f, kCGCompositeCopy);
}

void WKDrawFramedRectWithWidthUsingOperation(CGContextRef context, CGRect aRect, float frameWidth, CGCompositeOperation op)
{
    if (CGRectIsEmpty(aRect))
    {
	CGContextSaveGState(context);
        
        CGRect rect[4];
        
        // special case the framing 0 width rectangle. use very thin line (close to 1/72 point)
        // with antialiasing turned off. we need to transform this size by the inverse of the CTM to
        // guarantee it's close to 1/72 in the device (untransformed space)
        //
        float width  = frameWidth;
        float height = frameWidth;
        bool zeroWidth = (frameWidth == 0.0);

        if (zeroWidth) {
            CGAffineTransform currentCTM = CGContextGetCTM(context);
            CGSize size = CGSizeApplyAffineTransform(CGSizeMake(1.0f / 71.0f, 1.0f / 71.0f), CGAffineTransformInvert(currentCTM));
            width  = size.width;
            height = size.height;

            CGContextSetShouldAntialias (context, false);
        }

        // calculate top, right, bottom, and left sides of rectangle (non-overlapping)
        //
        rect[0] = CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, height);
        rect[1] = CGRectMake(aRect.origin.x, aRect.origin.y + aRect.size.height - height, aRect.size.width, height);
        rect[2] = CGRectMake(aRect.origin.x, aRect.origin.y + height, width, aRect.size.height - 2 * height);
        rect[3] = CGRectMake(aRect.origin.x + aRect.size.width - width, aRect.origin.y + height, width, aRect.size.height - 2 * height);

        _FillRectsUsingOperation(context, rect, 4, op);
        
        CGContextRestoreGState(context);
    }
}

void WKRectFill(CGContextRef context, CGRect aRect)
{
    if (aRect.size.width > 0 && aRect.size.height > 0) {
	CGContextSaveGState(context);
	_FillRectUsingOperation(context, aRect, kCGCompositeCopy);
	CGContextRestoreGState(context);
    }
}

void WKRectFillList(CGContextRef context, const CGRect *rects, int count)
{
    _FillRectsUsingOperation(context, rects, count, kCGCompositeCopy);
}

void WKRectFillUsingOperation(CGContextRef context, CGRect aRect, CGCompositeOperation op)
{
    if (aRect.size.width > 0 && aRect.size.height > 0.0) {
	CGContextSaveGState(context);
	_FillRectUsingOperation(context, aRect, op);
	CGContextRestoreGState(context);
    }
}

void WKRectFillListUsingOperation(CGContextRef context, const CGRect *rects, int count, CGCompositeOperation op)
{
    _FillRectsUsingOperation(context, rects, count, op);
}

void WKSetCurrentGraphicsContext(CGContextRef context)
{
    WebThreadContext* threadContext =  WebThreadCurrentContext();
    threadContext->currentCGContext = context;
}

CGContextRef WKGetCurrentGraphicsContext(void)
{
    WebThreadContext* threadContext =  WebThreadCurrentContext();
    return threadContext->currentCGContext;
}

static NSString *imageResourcePath(const char* imageFile, bool is2x)
{
    NSString *fileName = is2x ? [NSString stringWithFormat:@"%s@2x", imageFile] : [NSString stringWithUTF8String:imageFile];
#if PLATFORM(IOS_SIMULATOR)
    NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebCore"];
    return [bundle pathForResource:fileName ofType:@"png"];
#else
    // Workaround for <rdar://problem/7780665> CFBundleCopyResourceURL takes a long time on iPhone 3G.
    NSString *imageDirectory = @"/System/Library/PrivateFrameworks/WebCore.framework";
    return [NSString stringWithFormat:@"%@/%@.png", imageDirectory, fileName];
#endif
}

CGImageRef WKGraphicsCreateImageFromBundleWithName (const char *image_file)
{
    if (!image_file)
        return NULL;
    
    CGImageRef image = NULL;
    NSData *imageData = nil;
    
    if (wkGetScreenScaleFactor() == 2.0f) {
        NSString* full2xPath = imageResourcePath(image_file, true);
        imageData = [NSData dataWithContentsOfFile:full2xPath];
    }
    if (!imageData) {
        // We got here either because we didn't request hi-dpi or the @2x file doesn't exist.
        NSString* full1xPath = imageResourcePath(image_file, false);
        imageData = [NSData dataWithContentsOfFile:full1xPath];
    }
    
    if (imageData) {
        RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateWithCFData((CFDataRef)imageData));
        image = CGImageCreateWithPNGDataProvider(dataProvider.get(), NULL, NO, kCGRenderingIntentDefault);
    }

    return image;
}

static void WKDrawPatternBitmap(void *info, CGContextRef c) 
{
    CGImageRef image = (CGImageRef)info;
    CGFloat scale = wkGetScreenScaleFactor();
    CGContextDrawImage(c, CGRectMake(0, 0, CGImageGetWidth(image) / scale, CGImageGetHeight(image) / scale), image);    
}

static void WKReleasePatternBitmap(void *info) 
{
    CGImageRelease(reinterpret_cast<CGImageRef>(info));
}

static const CGPatternCallbacks WKPatternBitmapCallbacks = 
{
    0, WKDrawPatternBitmap, WKReleasePatternBitmap
};

CGPatternRef WKCreatePatternFromCGImage(CGImageRef imageRef)
{
    // retain image since it's freed by our callback
    CGImageRetain(imageRef);

    CGFloat scale = wkGetScreenScaleFactor();
    return CGPatternCreate((void*)imageRef, CGRectMake(0, 0, CGImageGetWidth(imageRef) / scale, CGImageGetHeight(imageRef) / scale), CGAffineTransformIdentity, CGImageGetWidth(imageRef) / scale, CGImageGetHeight(imageRef) / scale, kCGPatternTilingConstantSpacing, 1 /*isColored*/, &WKPatternBitmapCallbacks);
}

void WKSetPattern(CGContextRef context, CGPatternRef pattern, bool fill, bool stroke) 
{
    if (pattern == NULL)
        return;

    CGFloat patternAlpha = 1;
    CGColorSpaceRef colorspace = CGColorSpaceCreatePattern(NULL);
    if (fill) {
        CGContextSetFillColorSpace(context, colorspace);
        CGContextSetFillPattern(context, pattern, &patternAlpha);
    }
    if (stroke) {
        CGContextSetStrokeColorSpace(context, colorspace);
        CGContextSetStrokePattern(context, pattern, &patternAlpha);
    }
    CGColorSpaceRelease(colorspace);
}

void WKFontAntialiasingStateSaver::setup(bool isLandscapeOrientation)
{
#if !PLATFORM(IOS_SIMULATOR)
    m_oldAntialiasingStyle = CGContextGetFontAntialiasingStyle(m_context);

    if (m_useOrientationDependentFontAntialiasing)
        CGContextSetFontAntialiasingStyle(m_context, isLandscapeOrientation ? kCGFontAntialiasingStyleFilterLight : kCGFontAntialiasingStyleUnfiltered);
#else
    UNUSED_PARAM(isLandscapeOrientation);
#endif
}

void WKFontAntialiasingStateSaver::restore()
{
#if !PLATFORM(IOS_SIMULATOR)
    if (m_useOrientationDependentFontAntialiasing)
        CGContextSetFontAntialiasingStyle(m_context, m_oldAntialiasingStyle);
#endif
}