/*
 * tkUnixCairoPath.c --
 *
 *     This file implements path drawing API's using the Cairo rendering engine.
 *
 *  TODO: implement text drawing using glyphs instead of the "toy" text API.
 *
 * Copyright (c) 2005-2008  Mats Bengtsson
 *
 */

#include <cairo.h>
#include <cairo-xlib.h>
#include <tkUnixInt.h>
#include "tkIntPath.h"

#define TINT_INT_CALCULATION

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

#define BlueDoubleFromXColorPtr(xc)   ((double) ((xc)->blue >> 8) / 255.0)
#define GreenDoubleFromXColorPtr(xc)  ((double) ((xc)->green >> 8) / 255.0)
#define RedDoubleFromXColorPtr(xc)    ((double) ((xc)->red >> 8) / 255.0)

MODULE_SCOPE int gAntiAlias;
MODULE_SCOPE int gSurfaceCopyPremultiplyAlpha;
MODULE_SCOPE int gDepixelize;

static union {
    short set;
    char little;
} kEndianess;

/*
 * @@@ Need to use cairo_image_surface_create_for_data() here since
 *     prior to 1.2 there doesn't exist any cairo_image_surface_get_data()
 *     accessor.
 */
typedef struct PathSurfaceCairoRecord {
    unsigned char* data;
    cairo_format_t format;
    int width;
    int height;
    int stride; /* number of bytes between the start of rows in the buffer */
} PathSurfaceCairoRecord;

/*
 * This is used as a place holder for platform dependent
 * stuff between each call.
 */
typedef struct TkPathContext_ {
    cairo_t* c;
    cairo_surface_t* surface;
    PathSurfaceCairoRecord* record;     /* NULL except for memory surfaces.
					 * Skip when cairo 1.2 widely spread. */
    int		    widthCode;  /* Used to depixelize the strokes:
				 * 0: not integer width
				 * 1: odd integer width
				 * 2: even integer width */
    cairo_matrix_t  def_matrix;	/* For TkPathResetTMatrix() */
} TkPathContext_;

static void TkPathPrepareForStroke(TkPathContext ctx, Tk_PathStyle *style);

static void
CairoSetFill(TkPathContext ctx, Tk_PathStyle *style)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    /* Patch from Tim Edwards to handle color correctly on 64 bit arch. */
    cairo_set_source_rgba(context->c,
	    (double)(GetColorFromPathColor(style->fill)->red) / 0xFFFF,
	    (double)(GetColorFromPathColor(style->fill)->green) / 0xFFFF,
	    (double)(GetColorFromPathColor(style->fill)->blue) / 0xFFFF,
	    style->fillOpacity);
    cairo_set_fill_rule(context->c,
	    (style->fillRule == WindingRule) ? CAIRO_FILL_RULE_WINDING :
		CAIRO_FILL_RULE_EVEN_ODD);
}

TkPathContext
TkPathInit(Tk_Window tkwin, Drawable d)
{
    cairo_t *c;
    cairo_surface_t *surface;
    TkPathContext_ *context = (TkPathContext_ *)
	    ckalloc((unsigned) (sizeof(TkPathContext_)));
    Window dummy;
    int x, y;
    unsigned int width, height, borderWidth, depth;

    /* Find size of Drawable */
    XGetGeometry(Tk_Display(tkwin), d,
	&dummy, &x, &y, &width, &height, &borderWidth, &depth);

    surface = cairo_xlib_surface_create(Tk_Display(tkwin), d, Tk_Visual(tkwin),
		 width, height);
    c = cairo_create(surface);
    context->c = c;
    context->surface = surface;
    context->record = NULL;
    context->widthCode = 0;
    cairo_get_matrix(context->c, &context->def_matrix);
    return (TkPathContext) context;
}

TkPathContext
TkPathInitSurface(Display *display, int width, int height)
{
    cairo_t *c;
    cairo_surface_t *surface;
    unsigned char *data;
    int stride;

    /*
     * @@@ Need to use cairo_image_surface_create_for_data() here since
     *     prior to 1.2 there doesn't exist any cairo_image_surface_get_data()
     *     accessor.
     */
    TkPathContext_ *context = (TkPathContext_ *)
	    ckalloc((unsigned) (sizeof(TkPathContext_)));
    PathSurfaceCairoRecord *record = (PathSurfaceCairoRecord *)
	    ckalloc((unsigned) (sizeof(PathSurfaceCairoRecord)));
    stride = 4*width;
    /* Round up to nearest multiple of 16 */
    stride = (stride + (16-1)) & ~(16-1);
    data = (unsigned char *) ckalloc(height*stride);
    memset(data, '\0', height*stride);
    surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32,
		    width, height, stride);
    record->data = data;
    record->format = CAIRO_FORMAT_ARGB32;
    record->width = width;
    record->height = height;
    record->stride = stride;
    c = cairo_create(surface);
    context->c = c;
    context->surface = surface;
    context->record = record;
    context->widthCode = 0;
    cairo_get_matrix(context->c, &context->def_matrix);
    return (TkPathContext) context;
}

void
TkPathPushTMatrix(TkPathContext ctx, TMatrix *m)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    cairo_matrix_t matrix;

    if (m == NULL) {
	return;
    }
    cairo_matrix_init(&matrix, m->a, m->b, m->c, m->d, m->tx, m->ty);
    cairo_transform(context->c, &matrix);
}

void
TkPathResetTMatrix(TkPathContext ctx)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    context->widthCode = 0;
    cairo_set_matrix(context->c, &context->def_matrix);
}

void
TkPathSaveState(TkPathContext ctx)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    cairo_save(context->c);
}

void
TkPathRestoreState(TkPathContext ctx)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    cairo_restore(context->c);
}

void
TkPathBeginPath(TkPathContext ctx, Tk_PathStyle *style)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    int nint;
    double width;

    cairo_new_path(context->c);
    if (style->strokeColor == NULL) {
	context->widthCode = 0;
    } else {
	width = style->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);
    }
    cairo_move_to(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);
    }
    cairo_line_to(context->c, x, y);
}

void
TkPathQuadBezier(TkPathContext ctx, double ctrlX, double ctrlY,
    double x, double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    double cx, cy;
    double x31, y31, x32, y32;

    if (gDepixelize) {
	x = PATH_DEPIXELIZE(context->widthCode, x);
	y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    cairo_get_current_point(context->c, &cx, &cy);

    /*
     * Conversion of quadratic bezier curve to
     * cubic bezier curve: (mozilla/svg)
     * Unchecked! Must be an approximation!
     */
    x31 = cx + (ctrlX - cx) * 2 / 3;
    y31 = cy + (ctrlY - cy) * 2 / 3;
    x32 = ctrlX + (x - ctrlX) / 3;
    y32 = ctrlY + (y - ctrlY) / 3;

    cairo_curve_to(context->c, x31, y31, x32, y32, x, y);
}

void
TkPathCurveTo(TkPathContext ctx, double x1, double y1,
    double x2, double y2, double x, double y)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    if (gDepixelize) {
	x = PATH_DEPIXELIZE(context->widthCode, x);
	y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    cairo_curve_to(context->c, x1, y1, x2, y2, 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;

    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;

    if (gDepixelize) {
	x = PATH_DEPIXELIZE(context->widthCode, x);
	y = PATH_DEPIXELIZE(context->widthCode, y);
    }
    cairo_rectangle(context->c, x, y, width, height);
}

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

    if (rx == ry) {
	cairo_move_to(context->c, cx+rx, cy);
	cairo_arc(context->c, cx, cy, rx, 0.0, 2*M_PI);
	cairo_close_path(context->c);
    } else {
	cairo_save(context->c);
	cairo_translate(context->c, cx, cy);
	cairo_scale(context->c, rx, ry);
	cairo_move_to(context->c, 1.0, 0.0);
	cairo_arc(context->c, 0.0, 0.0, 1.0, 0.0, 2*M_PI);
	cairo_close_path(context->c);
	cairo_restore(context->c);
    }
}

static cairo_filter_t
convertInterpolationToCairoFilter(int interpolation)
{
    switch (interpolation) {
	case kPathImageInterpolationNone:
	    return CAIRO_FILTER_FAST;
	case kPathImageInterpolationFast:
	    return CAIRO_FILTER_GOOD;
	case kPathImageInterpolationBest:
	    return CAIRO_FILTER_BEST;
	default:
	    return CAIRO_FILTER_GOOD;
    }
}

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;
    Tk_PhotoImageBlock block;
    cairo_surface_t *surface;
    cairo_format_t format;
    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;
    double width, height;
    cairo_filter_t filter;

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

    /*
     * @format: the format of pixels in the buffer
     * @width: the width of the image to be stored in the buffer
     * @height: the eight of the image to be stored in the buffer
     * @stride: the number of bytes between the start of rows
     *   in the buffer. Having this be specified separate from @width
     *   allows for padding at the end of rows, or for writing
     *   to a subportion of a larger image.
     */

    /*
     * cairo_format_t
     * @CAIRO_FORMAT_ARGB32: each pixel is a 32-bit quantity, with
     *   alpha in the upper 8 bits, then red, then green, then blue.
     *   The 32-bit quantities are stored native-endian. Pre-multiplied
     *   alpha is used. (That is, 50% transparent red is 0x80800000,
     *   not 0x80ff0000.)
     */
    if (block.pixelSize == 4) {
	format = CAIRO_FORMAT_ARGB32;

	/*
	 * 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.
	 *
	 * We need to copy pixel data from the source using the photo offsets
	 * to cairos ARGB format which is in *native* endian order; Switch!
	 */
	srcR = block.offset[0];
	srcG = block.offset[1];
	srcB = block.offset[2];
	srcA = block.offset[3];
	dstR = 1;
	dstG = 2;
	dstB = 3;
	dstA = 0;
	if (!kEndianess.set) {
	    kEndianess.set = 1;
	}
	if (kEndianess.little) {
	    dstR = 3-dstR, dstG = 3-dstG, dstB = 3-dstB, dstA = 3-dstA;
	}

	data = (unsigned char *) ckalloc(pitch*iheight);
	ptr = data;

	if (tintColor && tintAmount > 0.0) {
#ifdef TINT_INT_CALCULATION
	    /* calculate with integer arithmetic */
	    uint32_t tintR, tintG, tintB, uAmount, uRemain;
	    if (tintAmount > 1.0)
		tintAmount = 1.0;
	    uAmount = (uint32_t)(tintAmount * 256.0);
	    uRemain = 256 - uAmount;
	    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);

		    if (a != 255) {
			/* Cairo expects RGB premultiplied by alpha */
			r = r * a / 255;
			g = g * a / 255;
			b = b * a / 255;
		    }

		    /* 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;
		    srcPtr += 4;
		    dstPtr += 4;
		}
	    }
#else
	    double tintR, tintG, tintB;
	    if (tintAmount > 1.0)
		tintAmount = 1.0;
	    tintR = RedDoubleFromXColorPtr(tintColor);
	    tintG = GreenDoubleFromXColorPtr(tintColor);
	    tintB = BlueDoubleFromXColorPtr(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);
		    int a = *(srcPtr+srcA);
		    /* 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);

		    if (a != 255) {
			/* Cairo expects RGB premultiplied by alpha */
			r = r * a / 255;
			g = g * a / 255;
			b = b * a / 255;
		    }

		    /* 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) = a;
		    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++) {
		    unsigned int alpha = *(srcPtr+srcA);
		    *(dstPtr+dstA) = alpha;
		    if (alpha == 255) {
			*(dstPtr+dstR) = *(srcPtr+srcR);
			*(dstPtr+dstG) = *(srcPtr+srcG);
			*(dstPtr+dstB) = *(srcPtr+srcB);
		    } else {
			/* Cairo expects RGB premultiplied by alpha */
			*(dstPtr+dstR) = alpha * *(srcPtr+srcR) / 255;
			*(dstPtr+dstG) = alpha * *(srcPtr+srcG) / 255;
			*(dstPtr+dstB) = alpha * *(srcPtr+srcB) / 255;
		    }
		    srcPtr += 4;
		    dstPtr += 4;
		}
	    }
	}
    } else if (block.pixelSize == 3) {
	/* Could do something about this? */
	fprintf(stderr,
	    "TkPathImage: unaccepted pixel format: 1 pixel is 3 bytes\n");
	return;
    } else {
	fprintf(stderr,
	    "TkPathImage: unaccepted pixel format: 1 pixel is %d bytes\n",
	    block.pixelSize);
	return;
    }
    surface = cairo_image_surface_create_for_data(ptr, format,
	    (int) iwidth, (int) iheight, pitch); /* stride */

    filter = convertInterpolationToCairoFilter(interpolation);
    if (width == (double)iwidth && height == (double)iheight && !srcRegion) {
	cairo_set_source_surface(context->c, surface, x, y);
	cairo_pattern_set_filter(cairo_get_source(context->c), filter);
	cairo_paint_with_alpha(context->c, fillOpacity);
    } else if (srcRegion) {
	/* crop x0, y0 positions: */
	int xcrop = srcRegion->x1;
	int ycrop = srcRegion->y1;
	double xscale, yscale, xoffs, yoffs;
	cairo_matrix_t matrix;
	cairo_pattern_t *pattern;

	width = (width0 == 0.0) ? srcRegion->x2 - srcRegion->x1 : width0;
	height = (height0 == 0.0) ? srcRegion->y2 - srcRegion->y1 : height0;
	/* scale image: */
	xscale = width / (srcRegion->x2 - srcRegion->x1);
	yscale = height / (srcRegion->y2 - srcRegion->y1);
	xoffs = xcrop*xscale;
	yoffs = ycrop*yscale;

	pattern = cairo_pattern_create_for_surface(surface);
	cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);

	cairo_translate(context->c, (x-xoffs), (y-yoffs));

	cairo_matrix_init_scale(&matrix, 1.0/xscale, 1.0/yscale);
	cairo_pattern_set_matrix(pattern, &matrix);

	cairo_set_source(context->c, pattern);

	cairo_pattern_set_filter(cairo_get_source(context->c), filter);
	cairo_rectangle(context->c, xoffs, yoffs, width, height);
	cairo_fill(context->c);

	cairo_pattern_destroy(pattern);
    } else {
	cairo_save(context->c);
	cairo_translate(context->c, x, y);
	cairo_scale(context->c, width/iwidth, height/iheight);
	cairo_set_source_surface(context->c, surface, 0, 0);
	cairo_pattern_set_filter(cairo_get_source(context->c), filter);
	cairo_paint_with_alpha(context->c, fillOpacity);
	cairo_restore(context->c);
    }
    cairo_surface_destroy(surface);
    if (data) {
	ckfree((char *)data);
    }
}

void
TkPathClosePath(TkPathContext ctx)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    cairo_close_path(context->c);
}

int
TkPathTextConfig(Tcl_Interp *interp, Tk_PathTextStyle *textStylePtr,
		 char *utf8, void **customPtr)
{
    return TCL_OK;
}

static cairo_font_slant_t
convertTkFontSlant2CairoFontSlant(enum FontSlant slant)
{
    switch(slant) {
	case PATH_TEXT_SLANT_NORMAL: return CAIRO_FONT_SLANT_NORMAL;
	case PATH_TEXT_SLANT_ITALIC: return CAIRO_FONT_SLANT_ITALIC;
	case PATH_TEXT_SLANT_OBLIQUE: return CAIRO_FONT_SLANT_OBLIQUE;
	default: return CAIRO_FONT_SLANT_NORMAL;
    }
}

static cairo_font_weight_t
convertTkFontWeight2CairoFontWeight(enum FontWeight weight)
{
    switch(weight) {
	case PATH_TEXT_WEIGHT_NORMAL: return CAIRO_FONT_WEIGHT_NORMAL;
	case PATH_TEXT_WEIGHT_BOLD: return CAIRO_FONT_WEIGHT_BOLD;
	default: return CAIRO_FONT_WEIGHT_NORMAL;
    }
}

static char *
lbstrdup(const char *str, Tcl_DString *dsPtr)
{
    Tcl_DStringInit(dsPtr);
    while (str[0] != '\0') {
	if (str[0] == '\r') {
	    str++;
	    if (str[0] != '\n') {
		Tcl_DStringAppend(dsPtr, "\n", 1);
		continue;
	    }
	}
	if (str[0] == '\t') {
	    Tcl_DStringAppend(dsPtr, "  ", 2);
	    str++;
	    continue;
	}
	Tcl_DStringAppend(dsPtr, str, 1);
	str++;
    }
    return Tcl_DStringValue(dsPtr);
}

static char *
linebreak(char *str, char **nextp)
{
    char *ret;

    if (str == NULL) {
	str = *nextp;
    }
    str += strspn(str, "\n");
    if (*str == '\0') {
	return NULL;
    }
    ret = str;
    str += strcspn(str, "\n");
    if (*str) {
	*str++ = '\0';
    }
    *nextp = str;
    return ret;
}

static void
multiline_show_text(TkPathContext ctx, double x, double y, double dy,
		    char *utf8)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    char *token, *savep;
    Tcl_DString ds;
    char *str = lbstrdup(utf8, &ds);

    for (token = linebreak(str, &savep); token;
	 token = linebreak(NULL, &savep), y += dy) {
	cairo_move_to(context->c, x, y);
	cairo_show_text(context->c, token);
    }
    Tcl_DStringFree(&ds);
}

static void
multiline_text_path(TkPathContext ctx, double x, double y, double dy,
		    char *utf8)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    char *token, *savep;
    Tcl_DString ds;
    char *str = lbstrdup(utf8, &ds);

    for (token = linebreak(str, &savep); token;
	 token = linebreak(NULL, &savep), y += dy) {
	cairo_move_to(context->c, x, y);
	cairo_text_path(context->c, token);
    }
    Tcl_DStringFree(&ds);
}

void
TkPathTextDraw(TkPathContext ctx, Tk_PathStyle *style,
    Tk_PathTextStyle *textStylePtr,
    double x, double y, int fillOverStroke, char *utf8, void *custom)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    cairo_font_extents_t fontExtents;
    int hasStroke = (style->strokeColor != NULL);
    int hasFill = (GetColorFromPathColor(style->fill) != NULL);
    Tcl_Encoding enc;
    Tcl_DString ds;
    char *fullUtf;

    enc = Tcl_GetEncoding(NULL, "utf-8");
    Tcl_DStringInit(&ds);
    fullUtf = Tcl_UtfToExternalDString(enc, utf8, -1, &ds);
    Tcl_FreeEncoding(enc);

    cairo_select_font_face(context->c, textStylePtr->fontFamily,
	    convertTkFontSlant2CairoFontSlant(textStylePtr->fontSlant),
	    convertTkFontWeight2CairoFontWeight(textStylePtr->fontWeight));
    cairo_set_font_size(context->c, textStylePtr->fontSize);
    cairo_font_extents(context->c, &fontExtents);

    if (hasStroke && hasFill) {
	multiline_text_path(ctx, x, y,
		fontExtents.ascent + fontExtents.descent, fullUtf);
	if (fillOverStroke) {
	    TkPathPrepareForStroke(ctx, style);
	    cairo_stroke_preserve(context->c);
	    CairoSetFill(ctx, style);
	    cairo_fill(context->c);
	} else {
	    TkPathFillAndStroke(ctx, style);
	}
    } else if (hasFill) {
	CairoSetFill(ctx, style);
	multiline_show_text(ctx, x, y,
		fontExtents.ascent + fontExtents.descent, fullUtf);
    } else if (hasStroke) {
	multiline_text_path(ctx, x, y,
		fontExtents.ascent + fontExtents.descent, fullUtf);
	TkPathStroke(ctx, style);
    }

    Tcl_DStringFree(&ds);
}

void
TkPathTextFree(Tk_PathTextStyle *textStylePtr, void *custom)
{
    /* Empty. */
}

PathRect
TkPathTextMeasureBbox(Display *display, Tk_PathTextStyle *textStylePtr,
    char *utf8, double *lineSpacing, void *custom)
{
    cairo_t *c;
    cairo_surface_t *surface;
    cairo_text_extents_t extents;
    cairo_font_extents_t fontExtents;
    PathRect r;
    int lc;
    char *token, *savep;
    double x;
    Tcl_Encoding enc;
    Tcl_DString ds;
    char *fullUtf;

    enc = Tcl_GetEncoding(NULL, "utf-8");
    Tcl_DStringInit(&ds);
    fullUtf = Tcl_UtfToExternalDString(enc, utf8, -1, &ds);
    Tcl_FreeEncoding(enc);

    /*
     * @@@ Not very happy about this but it seems that there is no way to
     *     measure text without having a surface (drawable) in cairo.
     */
    surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
    c = cairo_create(surface);
    cairo_select_font_face(c, textStylePtr->fontFamily,
	    convertTkFontSlant2CairoFontSlant(textStylePtr->fontSlant),
	    convertTkFontWeight2CairoFontWeight(textStylePtr->fontWeight));
    cairo_set_font_size(c, textStylePtr->fontSize);

    cairo_font_extents(c, &fontExtents);

    r.x2 = 0.0;
    for (lc = 0, token = linebreak(fullUtf, &savep); token;
	 lc++, token = linebreak(NULL, &savep)) {
	cairo_text_extents(c, token, &extents);
	x = extents.x_bearing + extents.width;
	if (x > r.x2)
	    r.x2 = x;
    }
    r.y1 = -fontExtents.ascent;
    r.x1 = 0.0;
    r.y2 = lc * (fontExtents.ascent + fontExtents.descent) - fontExtents.ascent;

    if (lineSpacing != NULL) {
	*lineSpacing = fontExtents.ascent + fontExtents.descent;
    }

    cairo_destroy(c);
    cairo_surface_destroy(surface);
    Tcl_DStringFree(&ds);

    return r;
}

void
TkPathSurfaceErase(TkPathContext ctx, double dx, double dy,
    double dwidth, double dheight)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    unsigned char *data, *dst;
    int i;
    int x, y, width, height;
    int xend, yend;
    int stride;
    int bwidth;

    /*
     * Had to do it directly on the bits. Assuming CAIRO_FORMAT_ARGB32
     * cairos ARGB format is in *native* endian order; Switch!
     * Be careful not to address the bitmap outside its limits.
     */
    data = context->record->data;
    stride = context->record->stride;
    x = (int) (dx + 0.5);
    y = (int) (dy + 0.5);
    width = (int) (dwidth + 0.5);
    height = (int) (dheight + 0.5);
    x = MAX(0, MIN(context->record->width, x));
    y = MAX(0, MIN(context->record->height, y));
    width = MAX(0, width);
    height = MAX(0, height);
    xend = MIN(x + width, context->record->width);
    yend = MIN(y + height, context->record->height);
    bwidth = 4*(xend - x);

    for (i = y; i < yend; i++) {
	dst = data + i*stride + 4*x;
	memset(dst, '\0', bwidth);
    }
}

void
TkPathSurfaceToPhoto(Tcl_Interp *interp, TkPathContext ctx,
    Tk_PhotoHandle photo)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    cairo_surface_t *surface = context->surface;
    Tk_PhotoImageBlock block;
    unsigned char *data;
    unsigned char *pixel;
    int width, height;
    int stride; /* Bytes per row. */

    width = cairo_image_surface_get_width(surface);
    height = cairo_image_surface_get_height(surface);
    data = context->record->data;
    stride = context->record->stride;

    Tk_PhotoGetImage(photo, &block);
    pixel = (unsigned char *) attemptckalloc(height*stride);
    if (pixel == NULL) {
	return;
    }

    if (gSurfaceCopyPremultiplyAlpha) {
	if (!kEndianess.set) {
	    kEndianess.set = 1;
	}
	if (kEndianess.little) {
	    PathCopyBitsPremultipliedAlphaBGRA(data, pixel, width, height,
		    stride);
	} else {
	    PathCopyBitsPremultipliedAlphaARGB(data, pixel, width, height,
		    stride);
	}
    } else {
	if (!kEndianess.set) {
	    kEndianess.set = 1;
	}
	if (kEndianess.little) {
	    PathCopyBitsBGRA(data, pixel, width, height, stride);
	} else {
	    PathCopyBitsARGB(data, pixel, width, height, stride);
	}
    }
    block.pixelPtr = pixel;
    block.width = width;
    block.height = height;
    block.pitch = stride;
    block.pixelSize = 4;
    block.offset[0] = 0;
    block.offset[1] = 1;
    block.offset[2] = 2;
    block.offset[3] = 3;
    Tk_PhotoPutBlock(interp, photo, &block, 0, 0, width, height,
	    TK_PHOTO_COMPOSITE_OVERLAY);
    ckfree((char *) pixel);
}

void
TkPathClipToPath(TkPathContext ctx, int fillRule)
{
    /* Clipping to path is done by default. */
    /* Note: cairo_clip does not consume the current path */
    /* cairo_clip(context->c); */
}

void
TkPathReleaseClipToPath(TkPathContext ctx)
{
    /* cairo_reset_clip(context->c); */
}

static void
TkPathPrepareForStroke(TkPathContext ctx, Tk_PathStyle *style)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    Tk_PathDash *dashPtr;

    /* Patch from Tim Edwards to handle color correctly on 64 bit arch. */
    cairo_set_source_rgba(context->c,
	    (double)(style->strokeColor->red) / 0xFFFF,
	    (double)(style->strokeColor->green) / 0xFFFF,
	    (double)(style->strokeColor->blue) / 0xFFFF,
	    style->strokeOpacity);
    cairo_set_line_width(context->c, style->strokeWidth);

    switch (style->capStyle) {
	case CapNotLast:
	case CapButt:
	    cairo_set_line_cap(context->c, CAIRO_LINE_CAP_BUTT);
	    break;
	case CapRound:
	    cairo_set_line_cap(context->c, CAIRO_LINE_CAP_ROUND);
	    break;
	default:
	    cairo_set_line_cap(context->c, CAIRO_LINE_CAP_SQUARE);
	    break;
    }
    switch (style->joinStyle) {
	case JoinMiter:
	    cairo_set_line_join(context->c, CAIRO_LINE_JOIN_MITER);
	    break;
	case JoinRound:
	    cairo_set_line_join(context->c, CAIRO_LINE_JOIN_ROUND);
	    break;
	default:
	    cairo_set_line_join(context->c, CAIRO_LINE_JOIN_BEVEL);
	    break;
    }
    cairo_set_miter_limit(context->c, style->miterLimit);

    dashPtr = style->dashPtr;
    if ((dashPtr != NULL) && (dashPtr->number != 0)) {
	int i;
	double *dashes = (double *) ckalloc(dashPtr->number * sizeof(double));

	for (i = 0; i < dashPtr->number; i++) {
	    dashes[i] = dashPtr->array[i] * style->strokeWidth;
	}
	cairo_set_dash(context->c, dashes, dashPtr->number, style->offset);
	ckfree((char *) dashes);
    } else {
	cairo_set_dash(context->c, NULL, 0, 0);
    }
}

void
TkPathStroke(TkPathContext ctx, Tk_PathStyle *style)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    TkPathPrepareForStroke(ctx, style);
    cairo_stroke(context->c);
}

void
TkPathFill(TkPathContext ctx, Tk_PathStyle *style)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    CairoSetFill(ctx, style);
    cairo_fill(context->c);
}

void
TkPathFillAndStroke(TkPathContext ctx, Tk_PathStyle *style)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    CairoSetFill(ctx, style);
    cairo_fill_preserve(context->c);
    TkPathStroke(ctx, style);
}

void
TkPathEndPath(TkPathContext ctx)
{
    /* Empty ??? */
}

void
TkPathFree(TkPathContext ctx)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    cairo_destroy(context->c);
    cairo_surface_destroy(context->surface);
    if (context->record) {
	ckfree((char *) context->record->data);
	ckfree((char *) context->record);
    }
    ckfree((char *) context);
}

int
TkPathDrawingDestroysPath(void)
{
    return 1;
}

int
TkPathPixelAlign(void)
{
    return 0;
}

int
TkPathGetCurrentPosition(TkPathContext ctx, PathPoint *pt)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;

    cairo_get_current_point(context->c, &(pt->x), &(pt->y));
    return TCL_OK;
}

int
TkPathBoundingBox(TkPathContext ctx, PathRect *rPtr)
{
    return TCL_ERROR;
}

static int
GetCairoExtend(int method)
{
    cairo_extend_t extend;

    switch (method) {
	case kPathGradientMethodPad:
	    extend = CAIRO_EXTEND_PAD;
	    break;
	case kPathGradientMethodRepeat:
	    extend = CAIRO_EXTEND_REPEAT;
	    break;
	case kPathGradientMethodReflect:
	    extend = CAIRO_EXTEND_REFLECT;
	    break;
	default:
	    extend = CAIRO_EXTEND_NONE;
	    break;
    }
    return extend;
}

void TkPathPaintLinearGradient(TkPathContext ctx, PathRect *bbox,
    LinearGradientFill *fillPtr, int fillRule, double fillOpacity,
    TMatrix *mPtr)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    int i;
    int nstops;
    PathRect *tPtr;		/* The transition line. */
    GradientStop *stop;
    GradientStopArray *stopArrPtr;
    cairo_pattern_t *pattern;

    if (fillPtr->units == kPathGradientUnitsBoundingBox) {
	if (bbox->x2 - bbox->x1 == 0 || bbox->y2 - bbox->y1 == 0) {
	    return;
	}
    }

    stopArrPtr = fillPtr->stopArrPtr;
    tPtr = fillPtr->transitionPtr;
    nstops = stopArrPtr->nstops;

    /*
     * The current path is consumed by filling.
     * Need therefore to save the current context and restore after.
     */
    cairo_save(context->c);

    pattern = cairo_pattern_create_linear(tPtr->x1, tPtr->y1,
		    tPtr->x2, tPtr->y2);

    /*
     * 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) {
	cairo_translate(context->c, bbox->x1, bbox->y1);
	cairo_scale(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1);
    }
    if (mPtr) {
	cairo_matrix_t matrix;
	cairo_matrix_init(&matrix, mPtr->a, mPtr->b, mPtr->c, mPtr->d,
		mPtr->tx, mPtr->ty);
	cairo_pattern_set_matrix(pattern, &matrix);
    }

    for (i = 0; i < nstops; i++) {
	stop = stopArrPtr->stops[i];
	cairo_pattern_add_color_stop_rgba(pattern, stop->offset,
		RedDoubleFromXColorPtr(stop->color),
		GreenDoubleFromXColorPtr(stop->color),
		BlueDoubleFromXColorPtr(stop->color),
		stop->opacity * fillOpacity);
    }
    cairo_set_source(context->c, pattern);
    cairo_set_fill_rule(context->c,
	    (fillRule == WindingRule) ? CAIRO_FILL_RULE_WINDING :
		CAIRO_FILL_RULE_EVEN_ODD);
    cairo_pattern_set_extend(pattern, GetCairoExtend(fillPtr->method));
    cairo_fill_preserve(context->c);

    cairo_pattern_destroy(pattern);
    cairo_restore(context->c);
}

void
TkPathPaintRadialGradient(TkPathContext ctx, PathRect *bbox,
    RadialGradientFill *fillPtr, int fillRule, double fillOpacity,
    TMatrix *mPtr)
{
    TkPathContext_ *context = (TkPathContext_ *) ctx;
    int i;
    int nstops;
    GradientStop *stop;
    cairo_pattern_t *pattern;
    GradientStopArray *stopArrPtr;
    RadialTransition *tPtr;

    if (fillPtr->units == kPathGradientUnitsBoundingBox) {
	if (bbox->x2 - bbox->x1 == 0 || bbox->y2 - bbox->y1 == 0) {
	    return;
	}
    }

    stopArrPtr = fillPtr->stopArrPtr;
    nstops = stopArrPtr->nstops;
    tPtr = fillPtr->radialPtr;

    /*
     * The current path is consumed by filling.
     * Need therefore to save the current context and restore after.
     */
    cairo_save(context->c);
    pattern = cairo_pattern_create_radial(
	    tPtr->focalX, tPtr->focalY, 0.0,
	    tPtr->centerX, tPtr->centerY, tPtr->radius);

    if (fillPtr->units == kPathGradientUnitsBoundingBox) {
	cairo_translate(context->c, bbox->x1, bbox->y1);
	cairo_scale(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1);
    }
    if (mPtr) {
	cairo_matrix_t matrix;
	cairo_matrix_init(&matrix, mPtr->a, mPtr->b, mPtr->c, mPtr->d,
		mPtr->tx, mPtr->ty);
	cairo_pattern_set_matrix(pattern, &matrix);
    }

    for (i = 0; i < nstops; i++) {
	stop = stopArrPtr->stops[i];
	cairo_pattern_add_color_stop_rgba(pattern, stop->offset,
		RedDoubleFromXColorPtr(stop->color),
		GreenDoubleFromXColorPtr(stop->color),
		BlueDoubleFromXColorPtr(stop->color),
		stop->opacity * fillOpacity);
    }
    cairo_set_source(context->c, pattern);
    cairo_set_fill_rule(context->c,
	    (fillRule == WindingRule) ? CAIRO_FILL_RULE_WINDING :
		CAIRO_FILL_RULE_EVEN_ODD);
    cairo_pattern_set_extend(pattern, GetCairoExtend(fillPtr->method));
    cairo_fill_preserve(context->c);

    cairo_pattern_destroy(pattern);
    cairo_restore(context->c);
}

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

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