/*
 * 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 "tkIntPath.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 gAntiAlias;
extern int gSurfaceCopyPremultiplyAlpha;
extern int gDepixelize;

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;
    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;
} 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;
    Rect bounds;
    MacDrawable *macDraw = (MacDrawable *) d;
    int dontDraw = 0;

    // printf("\nPathSetUpCGContext()...\n");

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

    port = TkMacOSXGetDrawablePort(d);

#ifdef TKPATH_AQUA_USE_CACHED_CONTEXT
    // Seems that the CG context is cached in MacDrawable but don't know how it works!
    context = macDraw->context;
#else
    dcPtr->focusLocked = 0;

    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) {
            // printf("  view != [NSView focusView]\n");
            dcPtr->focusLocked = [view lockFocusIfCanDraw];
            dontDraw = !dcPtr->focusLocked;
        } else {
            dontDraw = ![view canDraw];
        }
        // printf("  focusLocked:%i view:%p, focusView:%p\n", dcPtr->focusLocked, view, fView);
        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 !");
    }

    // printf("  context:	%p\n", dcPtr->c);
#endif

    /*
     * 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);
    CGRect cgbounds = CGContextGetClipBoundingBox(dcPtr->c);
    dcPtr->portBounds = NSRectToCGRect([view bounds]);
    // printf("  cgbounds: x=%f,y=%f,w=%f,h=%f\n",cgbounds.origin.x,cgbounds.origin.y,cgbounds.size.width,cgbounds.size.height);
    dcPtr->portBounds.origin.x += macDraw->xOff;
    dcPtr->portBounds.origin.y += macDraw->yOff;
    bounds.left = cgbounds.origin.x;
    bounds.top = cgbounds.origin.y;
    bounds.right = cgbounds.origin.x + cgbounds.size.width;
    bounds.bottom = cgbounds.origin.y + cgbounds.size.height;
    // printf("  macDraw Offs:%f,%f\n",(float)macDraw->xOff,(float)macDraw->yOff);
    // printf("  bounds:l=%f,r=%f,t=%f,b=%f\n",(float)bounds.left,(float)bounds.right,(float)bounds.top,(float)bounds.bottom);
    if (!dcPtr->focusLocked) {
        CGContextSaveGState(dcPtr->c);
    }

    CGAffineTransform t = { .a=1.0, .b=0.0, .c=0.0, .d=-1.0, .tx=0.0, .ty=dcPtr->portBounds.size.height};
    CGContextConcatCTM(dcPtr->c, t);

    CGRect r;
    HIShapeGetBounds(dcPtr->clipRgn, &r);
    // printf("  r:x=%f,y=%f,w=%f,h=%f\n", (float)r.origin.x,(float)r.origin.y,(float)r.size.width,(float)r.size.height);

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

    CGContextTranslateCTM(dcPtr->c, macDraw->xOff, macDraw->yOff);

    CGContextSetShouldAntialias(dcPtr->c, gAntiAlias);
    CGContextSetInterpolationQuality(dcPtr->c, kCGInterpolationHigh);

end:
    // printf(dontDraw ? "DON'T DRAW!!!\n" : "");
    if (dontDraw && dcPtr->clipRgn) {
        CFRelease(dcPtr->clipRgn);
        dcPtr->clipRgn = NULL;
    }
    // printf("	...PathSetUpCGContext()\n");
}

void
PathReleaseCGContext(
                     TkPathContext_ *dcPtr)
{
    // printf("\nPathReleaseCGContext()...\n");
    if (dcPtr->c) {
        CGContextSynchronize(dcPtr->c);
        [[dcPtr->view window] setViewsNeedDisplay:YES];
        [[dcPtr->view window] enableFlushWindow];
        if (dcPtr->focusLocked)
        {
	      [dcPtr->view unlockFocus];
        } else {
            CGContextRestoreGState(dcPtr->c);
        }
    }
    if (dcPtr->clipRgn) {
        CFRelease(dcPtr->clipRgn);
        dcPtr->clipRgn = NULL;
    }
    // printf("...PathReleaseCGContext()\n");
}

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

#if 0	// 10.3
/* Cache some common colors to speed things up. */
typedef struct LookupColor {
    int from;
    CGColorRef colorRef;
} LookupTable;
static LookupColor ColorTable[] = {

};
void
PreallocateColorRefs(void)
{

}
#endif

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,
            TableLookup(LineCapStyleLookupTable, 4, style->capStyle));

    /* Set the line join in the current graphics state to `join'. */
    CGContextSetLineJoin(c,
            TableLookup(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);
    }

    /* 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 = 0;
    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;

    // printf("\nTkPathInitSurface()...\n");
    // 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;
    // printf("...TkPathInitSurface()\n");
    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
TkPathSaveState(TkPathContext ctx)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGContextSaveGState(context->c);
}

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

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 (gDepixelize) {
        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 (gDepixelize) {
        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 (gDepixelize) {
        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 (gDepixelize) {
        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 (gDepixelize) {
        x = PATH_DEPIXELIZE(context->widthCode, x);
        y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    TkPathArcToUsingBezier(ctx, rx, ry, phiDegrees, largeArcFlag, sweepFlag, x, y);
}

void
TkPathRect(TkPathContext ctx, double x, double y, double width, double height)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    CGRect r;
    if (gDepixelize) {
        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);
                /* printf("tint:%g,%g,%g,%g amount=%g\n", tintR, tintG, tintB, tintAmount); */
                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);
                /* printf("tint:%g,%g,%g,%g amount=%g\n", tintR, tintG, tintB, tintAmount); */
                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);

    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);
    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 FontSlant 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;

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

    cf = CFStringCreateWithCString(NULL, utf8, kCFStringEncodingUTF8);
    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;
    //printf ("nl array:"); for (i=0; i<=j; i++) printf(" %d", recordPtr->nl[i]);printf("\n");
    *customPtr = (PathATSUIRecord *) recordPtr;
    return TCL_OK;
}

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

    for (i = 0; i < recordPtr->nlc; i++) {
        //printf("drawtext: %i .. %i to %g,%g\n", recordPtr->nl[i], recordPtr->nl[i+1], Fix2X(recordPtr->dx[i]), Fix2X(recordPtr->dy[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);
    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);
}

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, 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;

    for (i = 0; i < recordPtr->nlc; i++) {
        //printf("measure %i: from %d, length %d\n", i, recordPtr->nl[i], recordPtr->nl[i+1] - recordPtr->nl[i] - 1);
        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));
        //printf("textbox %i: %g,%g,%g,%g\n", i, ri.x1,ri.y1,ri.x2,ri.y2);
        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;
            //printf("textbox %i moved: %g,%g,%g,%g\n", i, ri.x1,ri.y1,ri.x2,ri.y2);
            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;
    }

    //printf("textbox: %g, %g, %g, %g\n", r.x1,r.y1,r.x2,r.y2);

    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 *) ckalloc(height*bytesPerRow);
    if (gSurfaceCopyPremultiplyAlpha) {
        PathCopyBitsPremultipliedAlphaRGBA(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);
}

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);
    if (fillRule == WindingRule) {
        CGContextClip(context->c);
    } else if (fillRule == EvenOddRule) {
        CGContextEOClip(context->c);
    }
}

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

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;
#ifdef TKPATH_AQUA_USE_DRAWABLE_CONTEXT

#else
    PathReleaseCGContext(context);
#endif
    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);
    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);
    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);
        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);
    }
}

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

