Tk Source Code

Documentation
Login
/*
 * tkMacOSXPath.c --
 *
 *	This file implements path drawing API's using CoreGraphics on Mac OS X.
 *
 * Copyright (c) 2005-2008  Mats Bengtsson
 *
 *
 */

/* This should go into configure.in but don't know how. */
#ifdef USE_PANIC_ON_PHOTO_ALLOC_FAILURE
#undef USE_PANIC_ON_PHOTO_ALLOC_FAILURE
#endif

#include "tkMacOSXInt.h"
#include "tkoPath.h"

#if !__OBJC__
#error Objective-C compiler required
#endif

#import <Cocoa/Cocoa.h>

#define TINT_INT_CALCULATION

/* Seems to work for both Endians. */
#define BlueFloatFromXColorPtr(xc)   ((float) ((xc)->blue >> 8) / 255.0)
#define GreenFloatFromXColorPtr(xc)  ((float) ((xc)->green >> 8) / 255.0)
#define RedFloatFromXColorPtr(xc)    ((float) ((xc)->red >> 8) / 255.0)

#define Blue255FromXColorPtr(xc)   ((xc)->blue >> 8)
#define Green255FromXColorPtr(xc)  ((xc)->green >> 8)
#define Red255FromXColorPtr(xc)    ((xc)->red >> 8)

#ifndef FloatToFixed
#define FloatToFixed(a) ((Fixed)((float) (a) * fixed1))
#endif

extern int Tk_PathAntiAlias;
extern int Tk_PathSurfaceCopyPremultiplyAlpha;
extern int Tk_PathDepixelize;

const CGFloat kValidDomain[2] = { 0, 1 };
const CGFloat kValidRange[8] = { 0, 1, 0, 1, 0, 1, 0, 1 };

/*
 * This is used as a place holder for platform dependent stuff between each call.
 */
typedef struct TkPathContext_ {
    CGContextRef c;
    int saveCount;
    CGrafPtr port;             /* QD graphics port, NULL for bitmaps. */
    char *data;                /* bitmap data, NULL for windows. */
    int widthCode;             /* Used to depixelize the strokes:
                                * 0: not integer width
                                * 1: odd integer width
                                * 2: even integer width */

    /* fields from TK TkMacOSXDrawingContext: */
    NSView *view;
    HIShapeRef clipRgn;
    CGRect portBounds;
    int focusLocked;
    int xOff, yOff;
} TkPathContext_;

#define MAX_NL 32
typedef struct PathATSUIRecord {
    ATSUStyle atsuStyle;
    ATSUTextLayout atsuLayout;
    UniChar *buffer;           /* @@@ Not sure this needs to be cached! */
    int nlc;
    int nl[MAX_NL + 1];
    ATSUTextMeasurement dx[MAX_NL];
    ATSUTextMeasurement dy[MAX_NL];
} PathATSUIRecord;

typedef struct FillInfo {
    double fillOpacity;
    GradientStopArray *stopArrPtr;
} FillInfo;

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXDrawableView --
 *
 *      This function returns the NSView for a given X drawable.
 *
 * Results:
 *      A NSView* or nil.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

NSView *
TkMacOSXDrawableView(
    MacDrawable * macWin)
{
NSView *result = nil;

    if(!macWin) {
        result = nil;
    } else if(!macWin->toplevel) {
        result = macWin->view;
    } else if(!(macWin->toplevel->flags & TK_EMBEDDED)) {
        result = macWin->toplevel->view;
    } else {
TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr);
        if(contWinPtr) {
            result = TkMacOSXDrawableView(contWinPtr->privatePtr);
        }
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpClipDrawableToRect --
 *
 *      Clip all drawing into the drawable d to the given rectangle.
 *      If width or height are negative, reset to no clipping.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Subsequent drawing into d is offset and clipped as specified.
 *
 *----------------------------------------------------------------------
 */

void
TkpClipDrawableToRect(
    Display * display,
    Drawable d,
    int x,
    int y,
    int width,
    int height)
{
    MacDrawable *macDraw = (MacDrawable *) d;
    NSView *view = TkMacOSXDrawableView(macDraw);

    if(macDraw->drawRgn) {
        CFRelease(macDraw->drawRgn);
        macDraw->drawRgn = NULL;
    }
    if(width >= 0 && height >= 0) {
    CGRect drawRect = CGRectMake(x + macDraw->xOff, y + macDraw->yOff,
            width, height);
    HIShapeRef drawRgn = HIShapeCreateWithRect(&drawRect);

        if(macDraw->winPtr && macDraw->flags & TK_CLIP_INVALID) {
            TkMacOSXUpdateClipRgn(macDraw->winPtr);
        }
        if(macDraw->visRgn) {
            macDraw->drawRgn = HIShapeCreateIntersection(macDraw->visRgn,
                drawRgn);
            CFRelease(drawRgn);
        } else {
            macDraw->drawRgn = drawRgn;
        }
        if(view && view !=[NSView focusView] &&[view lockFocusIfCanDraw]) {
            drawRect.origin.y =[view bounds].size.height -
                (drawRect.origin.y + drawRect.size.height);
            NSRectClip(NSRectFromCGRect(drawRect));
            macDraw->flags |= TK_FOCUSED_VIEW;
        }
    } else {
        if(view && (macDraw->flags & TK_FOCUSED_VIEW)) {
            [view unlockFocus];
            macDraw->flags &= ~TK_FOCUSED_VIEW;
        }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXGetClipRgn --
 *
 *	Get the clipping region needed to restrict drawing to the given
 *	drawable.
 *
 * Results:
 *	Clipping region. If non-NULL, CFRelease it when done.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

HIShapeRef
TkMacOSXGetClipRgn(
    Drawable drawable)
{              /* Drawable. */
MacDrawable *macDraw = (MacDrawable *) drawable;
HIShapeRef clipRgn = NULL;

    if(macDraw->winPtr && macDraw->flags & TK_CLIP_INVALID) {
        TkMacOSXUpdateClipRgn(macDraw->winPtr);
#ifdef TK_MAC_DEBUG_DRAWING
        TkMacOSXDbgMsg("%s visRgn  ", macDraw->winPtr->pathName);
        TkMacOSXDebugFlashRegion(drawable, macDraw->visRgn);
#endif /* TK_MAC_DEBUG_DRAWING */
    }

    if(macDraw->drawRgn) {
        clipRgn = HIShapeCreateCopy(macDraw->drawRgn);
    } else if(macDraw->visRgn) {
        clipRgn = HIShapeCreateCopy(macDraw->visRgn);
    }

    return clipRgn;
}

/* copied from tk8.5.16/macosx/tkMacOSXSubwindows.c */
NSView *
TkpMacOSXDrawableView(
    MacDrawable * macWin)
{
NSView *result = nil;

    if(!macWin) {
        result = nil;
    } else if(!macWin->toplevel) {
        result = macWin->view;
    } else if(!(macWin->toplevel->flags & TK_EMBEDDED)) {
        result = macWin->toplevel->view;
    } else {
TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr);
        if(contWinPtr) {
            result = TkpMacOSXDrawableView(contWinPtr->privatePtr);
        }
    }
    return result;
}

void
PathSetUpCGContext(
    Drawable d,
    TkPathContext_ * dcPtr)
{
CGrafPtr port;
MacDrawable *macDraw = (MacDrawable *) d;
int dontDraw = 0;

    dcPtr->c = NULL;
    dcPtr->view = NULL;
    dcPtr->clipRgn = NULL;
    dcPtr->focusLocked = 0;

    port = TkMacOSXGetDrawablePort(d);

    dcPtr->clipRgn = TkMacOSXGetClipRgn(d);
    if(!dontDraw) {
        dontDraw = dcPtr->clipRgn ? HIShapeIsEmpty(dcPtr->clipRgn) : 0;
    }
    if(dontDraw) {
        goto end;
    }

NSView *view = TkpMacOSXDrawableView(macDraw);
    if(view) {
NSView *fView =[NSView focusView];
        if(view != fView) {
            dcPtr->focusLocked =[view lockFocusIfCanDraw];
            dontDraw = !dcPtr->focusLocked;
        } else {
            dontDraw = ![view canDraw];
        }
        if(dontDraw) {
            goto end;
        }
        [[view window] disableFlushWindow];
        dcPtr->view = view;
NSGraphicsContext *currentGraphicsContext =[NSGraphicsContext currentContext];
        dcPtr->c = (CGContextRef)[currentGraphicsContext graphicsPort];
        dcPtr->portBounds = NSRectToCGRect([view bounds]);
        if(dcPtr->clipRgn) {
        }
    } else {
        Tcl_Panic("PathSetUpCGContext(): " "no NSView to draw into !");
    }

    /*
     * Core Graphics defines the origin to be the bottom left
     * corner of the CGContext and the positive y-axis points up.
     * Move the origin and flip the y-axis for all subsequent
     * Core Graphics drawing operations.
     */

    CGContextSaveGState(dcPtr->c);
    dcPtr->saveCount = 1;
CGRect cgbounds = CGContextGetClipBoundingBox(dcPtr->c);
    dcPtr->portBounds = NSRectToCGRect([view bounds]);
    dcPtr->portBounds.origin.x += macDraw->xOff;
    dcPtr->portBounds.origin.y += macDraw->yOff;

    dcPtr->xOff = macDraw->xOff;
    dcPtr->yOff = macDraw->yOff;

  end:
    if(dontDraw && dcPtr->clipRgn) {
        CFRelease(dcPtr->clipRgn);
        dcPtr->clipRgn = NULL;
    }
}

void
PathReleaseCGContext(
    TkPathContext_ * dcPtr)
{
    if(dcPtr->c) {
        CGContextSynchronize(dcPtr->c);
      [[dcPtr->view window] setViewsNeedDisplay:YES];
        [[dcPtr->view window] enableFlushWindow];
        if(dcPtr->focusLocked) {
            [dcPtr->view unlockFocus];
        }
        while(dcPtr->saveCount > 0) {
            CGContextRestoreGState(dcPtr->c);
            dcPtr->saveCount--;
        }
    }
    if(dcPtr->clipRgn) {
        CFRelease(dcPtr->clipRgn);
        dcPtr->clipRgn = NULL;
    }
}

CGColorSpaceRef
GetTheColorSpaceRef(
    void)
{
    static CGColorSpaceRef deviceRGB = NULL;
    if(deviceRGB == NULL) {
        deviceRGB = CGColorSpaceCreateDeviceRGB();
    }
    return deviceRGB;
}

static LookupTable LineCapStyleLookupTable[] = {
    {CapNotLast, kCGLineCapButt},
    {CapButt, kCGLineCapButt},
    {CapRound, kCGLineCapRound},
    {CapProjecting, kCGLineCapSquare}
};

static LookupTable LineJoinStyleLookupTable[] = {
    {JoinMiter, kCGLineJoinMiter},
    {JoinRound, kCGLineJoinRound},
    {JoinBevel, kCGLineJoinBevel}
};

void
PathSetCGContextStyle(
    CGContextRef c,
    Tk_PathStyle * style)
{
Tk_PathDash *dashPtr;
int fill = 0, stroke = 0;

    /** Drawing attribute functions. **/

    /* Set the line width in the current graphics state to `width'. */
    CGContextSetLineWidth(c, style->strokeWidth);

    /* Set the line cap in the current graphics state to `cap'. */
    CGContextSetLineCap(c,
        TkPathTableLookup(LineCapStyleLookupTable, 4, style->capStyle));

    /* Set the line join in the current graphics state to `join'. */
    CGContextSetLineJoin(c,
        TkPathTableLookup(LineJoinStyleLookupTable, 3, style->joinStyle));

    /* Set the miter limit in the current graphics state to `limit'. */
    CGContextSetMiterLimit(c, style->miterLimit);

    /* Set the line dash patttern in the current graphics state. */
    dashPtr = style->dashPtr;
    if((dashPtr != NULL) && (dashPtr->number != 0)) {
CGFloat *dashes = (CGFloat *) ckalloc(dashPtr->number * sizeof(CGFloat));
int i;

        for(i = 0; i < dashPtr->number; i++)
            dashes[i] = dashPtr->array[i] * style->strokeWidth;
        CGContextSetLineDash(c, 0.0, dashes, dashPtr->number);
        ckfree((char *)dashes);
    }

    /* Set the current fill colorspace in the context `c' to `DeviceRGB' and
     * set the components of the current fill color to `(red, green, blue,
     * alpha)'. */
    if(GetColorFromPathColor(style->fill) != NULL) {
        fill = 1;
        CGContextSetRGBFillColor(c,
            RedFloatFromXColorPtr(style->fill->color),
            GreenFloatFromXColorPtr(style->fill->color),
            BlueFloatFromXColorPtr(style->fill->color), style->fillOpacity);
    }

    /* Set the current stroke colorspace in the context `c' to `DeviceRGB' and
     * set the components of the current stroke color to `(red, green, blue,
     * alpha)'. */
    if(style->strokeColor != NULL) {
        stroke = 1;
        CGContextSetRGBStrokeColor(c,
            RedFloatFromXColorPtr(style->strokeColor),
            GreenFloatFromXColorPtr(style->strokeColor),
            BlueFloatFromXColorPtr(style->strokeColor), style->strokeOpacity);
    }
    if(stroke && fill) {
        CGContextSetTextDrawingMode(c, kCGTextFillStroke);
    } else if(stroke) {
        CGContextSetTextDrawingMode(c, kCGTextStroke);
    } else if(fill) {
        CGContextSetTextDrawingMode(c, kCGTextFill);
    }
}

/* Various ATSUI support functions. */

static OSStatus
CreateATSUIStyle(
    const char *fontFamily,
    float fontSize,
    Boolean isBold,
    Boolean isItalic,
    ATSUStyle * atsuStylePtr)
{
    OSStatus err = noErr;
    ATSUStyle style;
    ATSUFontID atsuFont;
    Fixed atsuSize;
    Boolean isUnderline = false;
    static const ATSUAttributeTag tags[] =
        { kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag,
            kATSUQDUnderlineTag };
    static const ByteCount sizes[] =
        { sizeof(ATSUFontID), sizeof(Fixed), sizeof(Boolean), sizeof(Boolean),
            sizeof(Boolean) };
    const ATSUAttributeValuePtr values[] =
        { &atsuFont, &atsuSize, &isBold, &isItalic, &isUnderline };

    *atsuStylePtr = NULL;
    style = NULL;
    atsuFont = 0;
    atsuSize = FloatToFixed(fontSize);
    {
        err =
            ATSUFindFontFromName((Ptr) fontFamily, strlen(fontFamily),
            kFontFamilyName, kFontNoPlatformCode, kFontNoScriptCode,
            kFontNoLanguageCode, &atsuFont);
    }
    if(err != noErr) {
        return err;
    }
    err = ATSUCreateStyle(&style);
    if(err != noErr) {
        if(style)
            ATSUDisposeStyle(style);
        return err;
    }
    err = ATSUSetAttributes(style, sizeof(tags) / sizeof(tags[0]),
        tags, sizes, values);
    if(err != noErr) {
        if(style)
            ATSUDisposeStyle(style);
        return err;
    }
    *atsuStylePtr = style;
    return noErr;
}

static OSStatus
CreateLayoutForString(
    UniChar * buffer,
    CFIndex length,
    ATSUStyle atsuStyle,
    ATSUTextLayout * layoutPtr)
{
ATSUTextLayout layout = NULL;
OSStatus err = noErr;

    *layoutPtr = NULL;
    err = ATSUCreateTextLayoutWithTextPtr(buffer, 0,
        length, length, 1, (unsigned long *)&length, &atsuStyle, &layout);
    if(err == noErr) {
        *layoutPtr = layout;
    }
    ATSUSetTransientFontMatching(layout, true);
    return err;
}

TkPathContext
TkPathInit(
    Tk_Window tkwin,
    Drawable d)
{
TkPathContext_ *context = (TkPathContext_ *) ckalloc(sizeof(TkPathContext_));
    bzero(context, sizeof(TkPathContext_));

    PathSetUpCGContext(d, context);
    context->port = TkMacOSXGetDrawablePort(d);
    context->data = NULL;
    context->widthCode = 0;
    return (TkPathContext) context;
}

TkPathContext
TkPathInitSurface(
    Display * display,
    int width,
    int height)
{
CGContextRef cgContext;
TkPathContext_ *context =
    (TkPathContext_ *) ckalloc((unsigned)(sizeof(TkPathContext_)));
size_t bytesPerRow;
char *data;

    /* Move up into own function */
    bzero(context, sizeof(TkPathContext_));
    bytesPerRow = 4 * width;
    /* Round up to nearest multiple of 16 */
    bytesPerRow = (bytesPerRow + (16 - 1)) & ~(16 - 1);
    data = ckalloc(height * bytesPerRow);

    /* Make it RGBA with 32 bit depth. */
    cgContext = CGBitmapContextCreate(data, width, height, 8, bytesPerRow,
        GetTheColorSpaceRef(), kCGImageAlphaPremultipliedLast);
    if(cgContext == NULL) {
        ckfree((char *)context);
        return (TkPathContext) NULL;
    }
    CGContextClearRect(cgContext, CGRectMake(0, 0, width, height));
    CGContextTranslateCTM(cgContext, 0, height);
    CGContextScaleCTM(cgContext, 1, -1);
    context->c = cgContext;
    context->port = NULL;
    context->data = data;
    context->clipRgn = NULL;
    context->saveCount = 0;
    context->xOff = context->yOff = 0;
    return (TkPathContext) context;
}

void
TkPathPushTMatrix(
    TkPathContext ctx,
    TMatrix * mPtr)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
CGAffineTransform transform;

    if(mPtr == NULL) {
        return;
    }
    /* Return the transform [ a b c d tx ty ]. */
    transform = CGAffineTransformMake(
        (float)mPtr->a, (float)mPtr->b,
        (float)mPtr->c, (float)mPtr->d, (float)mPtr->tx, (float)mPtr->ty);
    CGContextConcatCTM(context->c, transform);
}

void
TkPathResetTMatrix(
    TkPathContext ctx)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    context->widthCode = 0;
    while(context->saveCount > 0) {
        CGContextRestoreGState(context->c);
        context->saveCount--;
    }
    CGContextSaveGState(context->c);
    context->saveCount++;

    if(context->data != NULL) {
        /* surface bitmap */
        return;
    }

CGAffineTransform t;
    t.a = 1.0;
    t.b = 0.0;
    t.c = 0.0;
    t.d = (-1.0);
    t.tx = 0.0;
    t.ty = context->portBounds.size.height;

    CGContextConcatCTM(context->c, t);

CGRect r;
    HIShapeGetBounds(context->clipRgn, &r);

    HIShapeReplacePathInCGContext(context->clipRgn, context->c);
    CGContextEOClip(context->c);

    CGContextTranslateCTM(context->c, context->xOff, context->yOff);

    CGContextSetShouldAntialias(context->c, Tk_PathAntiAlias);
    CGContextSetInterpolationQuality(context->c, kCGInterpolationHigh);
}

void
TkPathSaveState(
    TkPathContext ctx)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGContextSaveGState(context->c);
    context->saveCount++;
}

void
TkPathRestoreState(
    TkPathContext ctx)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(context->saveCount > 0) {
        CGContextRestoreGState(context->c);
        context->saveCount--;
    }
}

void
TkPathBeginPath(
    TkPathContext ctx,
    Tk_PathStyle * stylePtr)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
int nint;
double width;
    CGContextBeginPath(context->c);
    PathSetCGContextStyle(context->c, stylePtr);
    if(stylePtr->strokeColor == NULL) {
        context->widthCode = 0;
    } else {
        width = stylePtr->strokeWidth;
        nint = (int)(width + 0.5);
        context->widthCode = fabs(width - nint) > 0.01 ? 0 : 2 - nint % 2;
    }
}

void
TkPathMoveTo(
    TkPathContext ctx,
    double x,
    double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(Tk_PathDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    CGContextMoveToPoint(context->c, x, y);
}

void
TkPathLineTo(
    TkPathContext ctx,
    double x,
    double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(Tk_PathDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    CGContextAddLineToPoint(context->c, x, y);
}

void
TkPathLinesTo(
    TkPathContext ctx,
    double *pts,
    int n)
{
    /* TkPathContext_ *context = (TkPathContext_ *) ctx; */
    /* Add a set of lines to the context's path. */
    /* CGContextAddLines(context->c, const CGPoint points[], size_t count); */
}

void
TkPathQuadBezier(
    TkPathContext ctx,
    double ctrlX,
    double ctrlY,
    double x,
    double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(Tk_PathDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    CGContextAddQuadCurveToPoint(context->c, ctrlX, ctrlY, x, y);
}

void
TkPathCurveTo(
    TkPathContext ctx,
    double ctrlX1,
    double ctrlY1,
    double ctrlX2,
    double ctrlY2,
    double x,
    double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(Tk_PathDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    CGContextAddCurveToPoint(context->c, ctrlX1, ctrlY1, ctrlX2, ctrlY2, x, y);
}

void
TkPathArcTo(
    TkPathContext ctx,
    double rx,
    double ry,
    double phiDegrees,         /* The rotation angle in degrees! */
    char largeArcFlag,
    char sweepFlag,
    double x,
    double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    /* @@@ Should we try to use the native arc functions here? */
    if(Tk_PathDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    TkPathArcToUsingBezier(ctx, rx, ry, phiDegrees, largeArcFlag, sweepFlag, x,
        y);
}

void
TkPathRectangle(
    TkPathContext ctx,
    double x,
    double y,
    double width,
    double height)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGRect r;
    if(Tk_PathDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    r = CGRectMake(x, y, width, height);
    CGContextAddRect(context->c, r);
}

void
TkPathOval(
    TkPathContext ctx,
    double cx,
    double cy,
    double rx,
    double ry)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    CGRect r;
    r = CGRectMake(cx - rx, cy - ry, 2 * rx, 2 * ry);
    CGContextAddEllipseInRect(context->c, r);
}

CGInterpolationQuality
convertInterpolationToCGInterpolation(
    int interpolation)
{
    switch (interpolation) {
    case kPathImageInterpolationNone:
        return kCGInterpolationNone;
    case kPathImageInterpolationFast:
        return kCGInterpolationLow;
    case kPathImageInterpolationBest:
        return kCGInterpolationHigh;
    default:
        return kCGInterpolationMedium;
    }
}

void
TkPathImage(
    TkPathContext ctx,
    Tk_Image image,
    Tk_PhotoHandle photo,
    double x,
    double y,
    double width0,
    double height0,
    double fillOpacity,
    XColor * tintColor,
    double tintAmount,
    int interpolation,
    PathRect * srcRegion)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGImageRef cgImage;
    CGDataProviderRef provider;
    CGColorSpaceRef colorspace;
    CGImageAlphaInfo alphaInfo;
    size_t size;
    Tk_PhotoImageBlock block;
    unsigned char *data = NULL;
    unsigned char *ptr = NULL;
    unsigned char *srcPtr, *dstPtr;
    int srcR, srcG, srcB, srcA; /* The source pixel offsets. */
    int dstR, dstG, dstB, dstA; /* The destination pixel offsets. */
    int pitch;
    int iwidth, iheight;
    int i, j;

    /* Return value? */
    Tk_PhotoGetImage(photo, &block);
    size = block.pitch * block.height;
    iheight = block.height;
    iwidth = block.width;
    pitch = block.pitch;
    double width = (width0 == 0.0) ? (double)iwidth : width0;
    double height = (height0 == 0.0) ? (double)iheight : height0;

    /*
     * The offset array contains the offsets from the address of a pixel to
     * the addresses of the bytes containing the red, green, blue and alpha
     * (transparency) components.  These are normally 0, 1, 2 and 3.
     * @@@ There are more cases to consider than these!
     */
    srcR = dstR = block.offset[0];
    srcG = dstG = block.offset[1];
    srcB = dstB = block.offset[2];
    srcA = dstA = block.offset[3];

    if(srcA == 3) {
        alphaInfo = kCGImageAlphaLast;
    } else if(srcA == 0) {
        alphaInfo = kCGImageAlphaFirst;
    } else {
        /* @@@ What to do here? */
        return;
    }

    if(block.pixelSize == 4) {
        if((srcR == dstR) && (srcG == dstG) && (srcB == dstB) && (srcA == dstA)
            && fillOpacity >= 1.0 && (tintAmount <= 0.0 || tintColor == NULL)) {
            ptr = (unsigned char *)block.pixelPtr;
        } else {
            data = (unsigned char *)ckalloc(pitch * iheight);
            ptr = data;

            if(tintColor && tintAmount > 0.0) {
#ifdef TINT_INT_CALCULATION
    uint32_t tintR, tintG, tintB, uAmount, uRemain, uOpacity;

                if(tintAmount > 1.0)
                    tintAmount = 1.0;
                uAmount = (uint32_t) (tintAmount * 256.0);
                uRemain = 256 - uAmount;
                uOpacity = (uint32_t) (fillOpacity * 256.0);
                tintR = Red255FromXColorPtr(tintColor);
                tintG = Green255FromXColorPtr(tintColor);
                tintB = Blue255FromXColorPtr(tintColor);
                for(i = 0; i < iheight; i++) {
                    srcPtr = block.pixelPtr + i * pitch;
                    dstPtr = ptr + i * pitch;
                    for(j = 0; j < iwidth; j++) {
                        /* extract */
    uint32_t r = *(srcPtr + srcR);
    uint32_t g = *(srcPtr + srcG);
    uint32_t b = *(srcPtr + srcB);
    uint32_t a = *(srcPtr + srcA);

                        /* transform */
    uint32_t lumAmount = ((r * 6966 + g * 23436 + b * 2366) * uAmount) >> 23;   /* 0-256 */
                        r = (uRemain * r + lumAmount * tintR);
                        g = (uRemain * g + lumAmount * tintG);
                        b = (uRemain * b + lumAmount * tintB);

                        /* fix range */
                        r = r > 0xFFFF ? 0xFFFF : r;
                        g = g > 0xFFFF ? 0xFFFF : g;
                        b = b > 0xFFFF ? 0xFFFF : b;

                        /* and put back */
                        *(dstPtr + dstR) = r >> 8;
                        *(dstPtr + dstG) = g >> 8;
                        *(dstPtr + dstB) = b >> 8;
                        *(dstPtr + dstA) = (a * uOpacity) >> 8;
                        srcPtr += 4;
                        dstPtr += 4;
                    }
                }
#else
    float tintR, tintG, tintB;

                if(tintAmount > 1.0)
                    tintAmount = 1.0;
                tintR = RedFloatFromXColorPtr(tintColor);
                tintG = GreenFloatFromXColorPtr(tintColor);
                tintB = BlueFloatFromXColorPtr(tintColor);
                for(i = 0; i < iheight; i++) {
                    srcPtr = block.pixelPtr + i * pitch;
                    dstPtr = ptr + i * pitch;
                    for(j = 0; j < iwidth; j++) {
                        /* extract */
    int r = *(srcPtr + srcR);
    int g = *(srcPtr + srcG);
    int b = *(srcPtr + srcB);

                        /* transform */
    int lum = (int)(0.2126 * r + 0.7152 * g + 0.0722 * b);
                        r = (int)((1.0 - tintAmount) * r +
                            tintAmount * lum * tintR);
                        g = (int)((1.0 - tintAmount) * g +
                            tintAmount * lum * tintG);
                        b = (int)((1.0 - tintAmount) * b +
                            tintAmount * lum * tintB);

                        /* fix range */
                        r = r < 0 ? 0 : r > 255 ? 255 : r;
                        g = g < 0 ? 0 : g > 255 ? 255 : g;
                        b = b < 0 ? 0 : b > 255 ? 255 : b;

                        /* and put back */
                        *(dstPtr + dstR) = r;
                        *(dstPtr + dstG) = g;
                        *(dstPtr + dstB) = b;
                        *(dstPtr + dstA) = *(srcPtr + srcA) * fillOpacity;
                        srcPtr += 4;
                        dstPtr += 4;
                    }
                }
#endif
            } else {
                for(i = 0; i < iheight; i++) {
                    srcPtr = block.pixelPtr + i * pitch;
                    dstPtr = ptr + i * pitch;
                    for(j = 0; j < iwidth; j++) {
                        *(dstPtr + dstR) = *(srcPtr + srcR);
                        *(dstPtr + dstG) = *(srcPtr + srcG);
                        *(dstPtr + dstB) = *(srcPtr + srcB);
                        *(dstPtr + dstA) = *(srcPtr + srcA) * fillOpacity;
                        srcPtr += 4;
                        dstPtr += 4;
                    }
                }
            }
        }
    } else {
        ptr = (unsigned char *)block.pixelPtr;
        return;
    }
    provider = CGDataProviderCreateWithData(NULL, ptr, size, NULL);
    colorspace = CGColorSpaceCreateDeviceRGB();
    cgImage = CGImageCreate(block.width, block.height, 8,       /* bitsPerComponent */
        block.pixelSize * 8,    /* bitsPerPixel */
        block.pitch,    /* bytesPerRow */
        colorspace,     /* colorspace */
        alphaInfo,      /* alphaInfo */
        provider, NULL, interpolation > 0 ? 1 : 0,      /* shouldInterpolate */
        kCGRenderingIntentDefault);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorspace);
    if(width == 0.0) {
        width = (double)block.width;
    }
    if(height == 0.0) {
        height = (double)block.height;
    }

    CGContextSaveGState(context->c);
    context->saveCount++;

    if(srcRegion != NULL) {
        width = (width0 == 0.0) ? srcRegion->x2 - srcRegion->x1 : width0;
        height = (height0 == 0.0) ? srcRegion->y2 - srcRegion->y1 : height0;
    double xscale = width / (srcRegion->x2 - srcRegion->x1);
    double yscale = height / (srcRegion->y2 - srcRegion->y1);
        CGContextSetInterpolationQuality(context->c,
            convertInterpolationToCGInterpolation(interpolation));
        CGContextTranslateCTM(context->c, x, y + height);
        CGContextScaleCTM(context->c, xscale, -yscale);
        CGContextClipToRect(context->c, CGRectMake(0.0, 0.0, width / xscale,
                height / yscale));
        CGContextDrawTiledImage(context->c, CGRectMake(srcRegion->x1,
                fmod(srcRegion->y2, iheight), iwidth, iheight), cgImage);
    } else {
        /* Flip back to an upright coordinate system since CGContextDrawImage expect this. */
        CGContextSetInterpolationQuality(context->c,
            convertInterpolationToCGInterpolation(interpolation));
        CGContextTranslateCTM(context->c, x, y + height);
        CGContextScaleCTM(context->c, 1, -1);
        CGContextDrawImage(context->c, CGRectMake(0.0, 0.0, width, height),
            cgImage);
    }
    CGImageRelease(cgImage);
    CGContextRestoreGState(context->c);
    context->saveCount--;
    if(data) {
        ckfree((char *)data);
    }
}

void
TkPathClosePath(
    TkPathContext ctx)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGContextClosePath(context->c);
}

/*
 * @@@ Problems: don't want Tcl_Interp, finding matching font not while processing options.
 * Separate font style from layout???
 */

Boolean
isItalic(
    enum FontSlant slant)
{
    switch (slant) {
    case PATH_TEXT_SLANT_NORMAL:
        return false;
    case PATH_TEXT_SLANT_ITALIC:
        return true;
    case PATH_TEXT_SLANT_OBLIQUE:
        return true;
    default:
        return false;
    }
}

Boolean
isBold(
    enum FontWeight weight)
{
    switch (weight) {
    case PATH_TEXT_WEIGHT_NORMAL:
        return false;
    case PATH_TEXT_WEIGHT_BOLD:
        return true;
    default:
        return false;
    }
}

int
TkPathTextConfig(
    Tcl_Interp * interp,
    Tk_PathTextStyle * textStylePtr,
    char *utf8,
    void **customPtr)
{
    PathATSUIRecord *recordPtr;
    ATSUStyle atsuStyle = NULL;
    ATSUTextLayout atsuLayout = NULL;
    CFStringRef cf;
    UniChar *buffer;
    CFRange range;
    CFIndex length;
    OSStatus err;
    Tcl_Encoding enc;
    Tcl_DString ds;

    if(utf8 == NULL) {
        return TCL_OK;
    }
    TkPathTextFree(textStylePtr, *customPtr);

    enc = Tcl_GetEncoding(NULL, "utf-8");
    Tcl_DStringInit(&ds);
    Tcl_UtfToExternalDString(enc, utf8, -1, &ds);
    Tcl_FreeEncoding(enc);
    cf = CFStringCreateWithCString(NULL, Tcl_DStringValue(&ds),
        kCFStringEncodingUTF8);
    Tcl_DStringFree(&ds);
    length = CFStringGetLength(cf);
    if(length == 0) {
        return TCL_OK;
    }
    range = CFRangeMake(0, length);
    err =
        CreateATSUIStyle(textStylePtr->fontFamily, textStylePtr->fontSize,
        isBold(textStylePtr->fontWeight), isItalic(textStylePtr->fontSlant),
        &atsuStyle);
    if(err != noErr) {
        Tcl_SetObjResult(interp,
            Tcl_NewStringObj("font style couldn't be created", -1));
        return TCL_ERROR;
    }
    buffer = (UniChar *) ckalloc(length * sizeof(UniChar));
    CFStringGetCharacters(cf, range, buffer);
    err = CreateLayoutForString(buffer, length, atsuStyle, &atsuLayout);
    CFRelease(cf);
    if(err != noErr) {
        Tcl_SetObjResult(interp,
            Tcl_NewStringObj("text layout couldn't be created", -1));
        ckfree((char *)buffer);
        return TCL_ERROR;
    }
    recordPtr = (PathATSUIRecord *) ckalloc(sizeof(PathATSUIRecord));
    recordPtr->atsuStyle = atsuStyle;
    recordPtr->atsuLayout = atsuLayout;
    recordPtr->buffer = buffer;
    int i, j;
    recordPtr->nl[0] = 0;
    for(i = 0, j = 1; i < length; i++) {
        if((j < MAX_NL) && (buffer[i] == '\n')) {
            recordPtr->nl[j++] = i + 1;
            buffer[i] = 0x2028;
        }
    }
    recordPtr->nl[j] = i + 1;
    recordPtr->nlc = j;
    *customPtr = (PathATSUIRecord *) recordPtr;
    return TCL_OK;
}

static void
drawMultilineText(
    PathATSUIRecord * recordPtr)
{
int i;

    for(i = 0; i < recordPtr->nlc; i++) {
        ATSUDrawText(recordPtr->atsuLayout, recordPtr->nl[i],
            recordPtr->nl[i + 1] - recordPtr->nl[i] - 1, recordPtr->dx[i],
            recordPtr->dy[i]);
    }
}

void
TkPathTextDraw(
    TkPathContext ctx,
    Tk_PathStyle * style,
    Tk_PathTextStyle * textStylePtr,
    double x,
    double y,
    int fillOverStroke,
    char *utf8,
    void *custom)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    PathATSUIRecord *recordPtr = (PathATSUIRecord *) custom;
    ByteCount iSize = sizeof(CGContextRef);
    ATSUAttributeTag iTag = kATSUCGContextTag;
    ATSUAttributeValuePtr iValuePtr = &(context->c);

    ATSUSetLayoutControls(recordPtr->atsuLayout, 1, &iTag, &iSize, &iValuePtr);
    CGContextSaveGState(context->c);
    context->saveCount++;
    CGContextTranslateCTM(context->c, x, y);
    CGContextScaleCTM(context->c, 1, -1);
    if((style->strokeColor != NULL)
        && (GetColorFromPathColor(style->fill) != NULL)) {
        CGContextSetTextDrawingMode(context->c,
            fillOverStroke ? kCGTextStroke : kCGTextFill);
        drawMultilineText(recordPtr);
        CGContextSetTextDrawingMode(context->c,
            fillOverStroke ? kCGTextFill : kCGTextStroke);
        drawMultilineText(recordPtr);
    } else {
        drawMultilineText(recordPtr);
    }
    CGContextRestoreGState(context->c);
    context->saveCount--;
}

void
TkPathTextFree(
    Tk_PathTextStyle * textStylePtr,
    void *custom)
{
    PathATSUIRecord *recordPtr = (PathATSUIRecord *) custom;
    if(recordPtr) {
        if(recordPtr->atsuStyle) {
            ATSUDisposeStyle(recordPtr->atsuStyle);
        }
        if(recordPtr->atsuLayout) {
            ATSUDisposeTextLayout(recordPtr->atsuLayout);
        }
        if(recordPtr->buffer) {
            ckfree((char *)recordPtr->buffer);
        }
    }
}

PathRect
TkPathTextMeasureBbox(
    Display * display,
    Tk_PathTextStyle * textStylePtr,
    char *utf8,
    double *lineSpacing,
    void *custom)
{
    PathATSUIRecord *recordPtr = (PathATSUIRecord *) custom;
    PathRect r, ri;
    int i;
    ATSTrapezoid b;
    ItemCount numBounds;
    double x = 0.0;
    double y = 0.0;
    double baseX = 0.0;
    double lineSp = 0;

    for(i = 0; i < recordPtr->nlc; i++) {
        b.upperRight.x = b.upperLeft.x = 0;

        ATSUGetGlyphBounds(recordPtr->atsuLayout, 0, 0,
            recordPtr->nl[i], recordPtr->nl[i + 1] - recordPtr->nl[i] - 1,
            kATSUseFractionalOrigins, 1, &b, &numBounds);
        ri.x1 = MIN(Fix2X(b.upperLeft.x), Fix2X(b.lowerLeft.x));
        ri.y1 = MIN(Fix2X(b.upperLeft.y), Fix2X(b.upperRight.y));
        ri.x2 = MAX(Fix2X(b.upperRight.x), Fix2X(b.lowerRight.x));
        ri.y2 = MAX(Fix2X(b.lowerLeft.y), Fix2X(b.lowerRight.y));
        if(i == 0) {
            baseX = ri.x1;
            r.x1 = ri.x1;
            r.y1 = ri.y1;
            r.x2 = ri.x2;
            r.y2 = ri.y2;
        } else {
            x = ri.x1 - baseX;
            ri.x1 -= x;
            ri.y1 += y;
            ri.x2 -= x;
            ri.y2 += y;
            if(r.x1 > ri.x1)
                r.x1 = ri.x1;
            if(r.y1 > ri.y1)
                r.y1 = ri.y1;
            if(r.x2 < ri.x2)
                r.x2 = ri.x2;
            if(r.y2 < ri.y2)
                r.y2 = ri.y2;
        }
        recordPtr->dx[i] = X2Fix(-x);
        recordPtr->dy[i] = X2Fix(-y);
        y = r.y2 - r.y1;
        lineSp += y;
    }

    if(lineSpacing != NULL && i > 0) {
        *lineSpacing = lineSp / i;
    }

    return r;
}

void
TkPathSurfaceErase(
    TkPathContext ctx,
    double x,
    double y,
    double width,
    double height)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGContextClearRect(context->c, CGRectMake(x, y, width, height));
}

void
TkPathSurfaceToPhoto(
    Tcl_Interp * interp,
    TkPathContext ctx,
    Tk_PhotoHandle photo)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
CGContextRef c = context->c;
Tk_PhotoImageBlock block;
unsigned char *data;
unsigned char *pixel;
int width, height;
int bytesPerRow;

    width = CGBitmapContextGetWidth(c);
    height = CGBitmapContextGetHeight(c);
    data = CGBitmapContextGetData(c);
    bytesPerRow = CGBitmapContextGetBytesPerRow(c);

    Tk_PhotoGetImage(photo, &block);
    pixel = (unsigned char *)attemptckalloc(height * bytesPerRow);
    if(pixel == NULL) {
        return;
    }
    if(Tk_PathSurfaceCopyPremultiplyAlpha) {
        TkPathCopyBitsPremultipliedAlphaRGBA(data, pixel, width, height,
            bytesPerRow);
    } else {
        memcpy(pixel, data, height * bytesPerRow);
    }
    block.pixelPtr = pixel;
    block.width = width;
    block.height = height;
    block.pitch = bytesPerRow;
    block.pixelSize = 4;
    block.offset[0] = 0;
    block.offset[1] = 1;
    block.offset[2] = 2;
    block.offset[3] = 3;
    /* Should change this to check for errors... */
    Tk_PhotoPutBlock(interp, photo, &block, 0, 0, width, height,
        TK_PHOTO_COMPOSITE_OVERLAY);
    ckfree((char *)pixel);
}

void
TkPathClipToPath(
    TkPathContext ctx,
    int fillRule)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    /* If you need to grow the clipping path after it’s shrunk, you must save the
     * graphics state before you clip, then restore the graphics state to restore the current
     * clipping path. */
    CGContextSaveGState(context->c);
    context->saveCount++;
    if(fillRule == WindingRule) {
        CGContextClip(context->c);
    } else if(fillRule == EvenOddRule) {
        CGContextEOClip(context->c);
    }
}

void
TkPathReleaseClipToPath(
    TkPathContext ctx)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGContextRestoreGState(context->c);
    context->saveCount--;
}

void
TkPathStroke(
    TkPathContext ctx,
    Tk_PathStyle * style)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGContextStrokePath(context->c);
}

void
TkPathFill(
    TkPathContext ctx,
    Tk_PathStyle * style)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(style->fillRule == WindingRule) {
        CGContextFillPath(context->c);
    } else if(style->fillRule == EvenOddRule) {
        CGContextEOFillPath(context->c);
    }
}

void
TkPathFillAndStroke(
    TkPathContext ctx,
    Tk_PathStyle * style)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    if(style->fillRule == WindingRule) {
        CGContextDrawPath(context->c, kCGPathFillStroke);
    } else if(style->fillRule == EvenOddRule) {
        CGContextDrawPath(context->c, kCGPathEOFillStroke);
    }
}

void
TkPathEndPath(
    TkPathContext ctx)
{
    /* TkPathContext_ *context = (TkPathContext_ *) ctx; */
    /* Empty ??? */
}

void
TkPathFree(
    TkPathContext ctx)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
    PathReleaseCGContext(context);
    if(context->data) {
        ckfree(context->data);
    }
    ckfree((char *)ctx);
}

int
TkPathDrawingDestroysPath(
    void)
{
    return 1;
}

int
TkPathPixelAlign(
    void)
{
    return 0;
}

/* TkPathGetCurrentPosition --
 *
 * 		Returns the current pen position in untransformed coordinates!
 */

int
TkPathGetCurrentPosition(
    TkPathContext ctx,
    PathPoint * ptPtr)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
CGPoint cgpt;

    cgpt = CGContextGetPathCurrentPoint(context->c);
    ptPtr->x = cgpt.x;
    ptPtr->y = cgpt.y;
    return TCL_OK;
}

int
TkPathBoundingBox(
    TkPathContext ctx,
    PathRect * rPtr)
{
TkPathContext_ *context = (TkPathContext_ *) ctx;
CGRect cgRect;

    /* This one is not very useful since it includes the control points. */
    cgRect = CGContextGetPathBoundingBox(context->c);
    rPtr->x1 = cgRect.origin.x;
    rPtr->y1 = cgRect.origin.y;
    rPtr->x2 = cgRect.origin.x + cgRect.size.width;
    rPtr->y2 = cgRect.origin.y + cgRect.size.height;
    return TCL_OK;
}

/*
 * Using CGShading for fill gradients.
 */

static void
ShadeEvaluate(
    void *info,
    const CGFloat * in,
    CGFloat * out)
{
    FillInfo *fillInfo = (FillInfo *) info;
    GradientStopArray *stopArrPtr = fillInfo->stopArrPtr;
    double fillOpacity = fillInfo->fillOpacity;
    GradientStop **stopPtrPtr = stopArrPtr->stops;
    GradientStop *stop1 = NULL, *stop2 = NULL;
    int nstops = stopArrPtr->nstops;
    int i = 0;
    float par = *in;
    float f1, f2;

    /* Find the two stops for this point. Tricky! */
    while((i < nstops) && ((*stopPtrPtr)->offset < par)) {
        stopPtrPtr++, i++;
    }
    if(i == 0) {
        /* First stop > 0. */
        stop1 = *stopPtrPtr;
        stop2 = stop1;
    } else if(i == nstops) {
        /* We have stepped beyond the last stop; step back! */
        stop1 = *(stopPtrPtr - 1);
        stop2 = stop1;
    } else {
        stop1 = *(stopPtrPtr - 1);
        stop2 = *stopPtrPtr;
    }
    /* Interpolate between the two stops.
     * "If two gradient stops have the same offset value,
     * then the latter gradient stop controls the color value at the
     * overlap point."
     */
    if(fabs(stop2->offset - stop1->offset) < 1e-6) {
        *out++ = RedFloatFromXColorPtr(stop2->color);
        *out++ = GreenFloatFromXColorPtr(stop2->color);
        *out++ = BlueFloatFromXColorPtr(stop2->color);
        *out++ = stop2->opacity * fillOpacity;
    } else {
        f1 = (stop2->offset - par) / (stop2->offset - stop1->offset);
        f2 = (par - stop1->offset) / (stop2->offset - stop1->offset);
        *out++ = f1 * RedFloatFromXColorPtr(stop1->color) +
            f2 * RedFloatFromXColorPtr(stop2->color);
        *out++ = f1 * GreenFloatFromXColorPtr(stop1->color) +
            f2 * GreenFloatFromXColorPtr(stop2->color);
        *out++ = f1 * BlueFloatFromXColorPtr(stop1->color) +
            f2 * BlueFloatFromXColorPtr(stop2->color);
        *out++ = (f1 * stop1->opacity + f2 * stop2->opacity) * fillOpacity;
    }
}

static void
ShadeRelease(
    void *info)
{
    /* Not sure if anything to do here. */
}

void
TkPathPaintLinearGradient(
    TkPathContext ctx,
    PathRect * bbox,
    LinearGradientFill * fillPtr,
    int fillRule,
    double fillOpacity,
    TMatrix * mPtr)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGShadingRef shading;
    CGPoint start, end;
    CGColorSpaceRef colorSpaceRef;
    CGFunctionRef function;
    CGFunctionCallbacks callbacks;
    PathRect *trans = fillPtr->transitionPtr;   /* The transition line. */
    FillInfo fillInfo;

    fillInfo.fillOpacity = fillOpacity;
    fillInfo.stopArrPtr = fillPtr->stopArrPtr;

    callbacks.version = 0;
    callbacks.evaluate = ShadeEvaluate;
    callbacks.releaseInfo = ShadeRelease;
    colorSpaceRef = CGColorSpaceCreateDeviceRGB();

    /*
     * We need to do like this since this is how SVG defines gradient drawing
     * in case the transition vector is in relative coordinates.
     */
    CGContextSaveGState(context->c);
    context->saveCount++;
    if(fillPtr->units == kPathGradientUnitsBoundingBox) {
        CGContextTranslateCTM(context->c, bbox->x1, bbox->y1);
        CGContextScaleCTM(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1);
    }
    function =
        CGFunctionCreate((void *)&fillInfo, 1, kValidDomain, 4, kValidRange,
        &callbacks);
    start = CGPointMake(trans->x1, trans->y1);
    end = CGPointMake(trans->x2, trans->y2);
    shading = CGShadingCreateAxial(colorSpaceRef, start, end, function, 1, 1);
    if(mPtr) {
        /* @@@ I'm not completely sure of the order of transforms here! */
        TkPathPushTMatrix(ctx, mPtr);
    }
    CGContextDrawShading(context->c, shading);
    CGContextRestoreGState(context->c);
    context->saveCount--;
    CGShadingRelease(shading);
    CGFunctionRelease(function);
    CGColorSpaceRelease(colorSpaceRef);
}

void
TkPathPaintRadialGradient(
    TkPathContext ctx,
    PathRect * bbox,
    RadialGradientFill * fillPtr,
    int fillRule,
    double fillOpacity,
    TMatrix * mPtr)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGShadingRef shading;
    CGPoint start, end;
    CGColorSpaceRef colorSpaceRef;
    CGFunctionRef function;
    CGFunctionCallbacks callbacks;
    RadialTransition *tPtr = fillPtr->radialPtr;
    FillInfo fillInfo;

    fillInfo.fillOpacity = fillOpacity;
    fillInfo.stopArrPtr = fillPtr->stopArrPtr;

    callbacks.version = 0;
    callbacks.evaluate = ShadeEvaluate;
    callbacks.releaseInfo = ShadeRelease;
    colorSpaceRef = CGColorSpaceCreateDeviceRGB();

    /*
     * We need to do like this since this is how SVG defines gradient drawing
     * in case the transition vector is in relative coordinates.
     */
    if(fillPtr->units == kPathGradientUnitsBoundingBox) {
        CGContextSaveGState(context->c);
        context->saveCount++;
        CGContextTranslateCTM(context->c, bbox->x1, bbox->y1);
        CGContextScaleCTM(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1);
    }
    function =
        CGFunctionCreate((void *)&fillInfo, 1, kValidDomain, 4, kValidRange,
        &callbacks);
    start = CGPointMake(tPtr->focalX, tPtr->focalY);
    end = CGPointMake(tPtr->centerX, tPtr->centerY);
    shading =
        CGShadingCreateRadial(colorSpaceRef, start, 0.0, end, tPtr->radius,
        function, 1, 1);
    if(mPtr) {
        /* @@@ I'm not completely sure of the order of transforms here! */
        TkPathPushTMatrix(ctx, mPtr);
    }
    CGContextDrawShading(context->c, shading);
    CGShadingRelease(shading);
    CGFunctionRelease(function);
    CGColorSpaceRelease(colorSpaceRef);
    if(fillPtr->units == kPathGradientUnitsBoundingBox) {
        CGContextRestoreGState(context->c);
        context->saveCount--;
    }
}

int
TkPathSetup(
    Tcl_Interp * interp)
{
    return TCL_OK;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */