Add cairo patches for Quartz gradient fixes
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 26 Aug 2009 10:05:44 -0700
changeset 32382 9f811e15ca517c7805e27d5104f5f95b3d082e07
parent 32381 37b976412c043d9436b495730565f1068b0887d0
child 32383 68762e0a8a56b3f38b6910c4fa615365bbfb41aa
push idunknown
push userunknown
push dateunknown
milestone1.9.3a1pre
Add cairo patches for Quartz gradient fixes
gfx/cairo/README
gfx/cairo/quartz-const-globals.patch
gfx/cairo/quartz-fallback.patch
gfx/cairo/quartz-minimize-gradient-repeat.patch
gfx/cairo/quartz-repeating-radial-gradients.patch
--- a/gfx/cairo/README
+++ b/gfx/cairo/README
@@ -49,16 +49,24 @@ wrap-source_image.patch: make sure we do
 zero-sized.patch: deal with zero sized surface in ways less likely to crash.
 
 text-path-filling-threshold.patch: use path filling instead of platform glyph rasterization at a smaller size threshold of 256 device pixels, if the backend supports native filling (which we assume will be fast).
 
 zombie-face.patch: bug 486974; leak and possible crash with @font-face{src:url()}.  Upstream commit: 0238fe2cafea2e1ed19bb222117bd73ee6898d4d
 
 win32-raster.patch: bug 498689; use scanline rasterizer on win32
 
+quartz-falback.patch: try to fix Quartz fallback-to-pixman path; possiby incorrect and obsoleted by Andrea Canciani patch
+
+quartz-repeating-radial-gradients.patch: use Quartz to render repeating radial gradients instead of falling back
+
+quartz-const-globals.patch: make some Quartz color function data const globals instead of local variables
+
+quartz-minimze-gradient-repeat.patch: reduce the number of gradient stop repetitions we use, to improve quality of Quartz's gradient rendering
+
 ==== pixman patches ====
 
 pixman-neon.patch: add ARM NEON optimized compositing functions
 
 endian.patch: include cairo-platform.h for endian macros
 
 ==== disable printing patch ====
 
new file mode 100644
--- /dev/null
+++ b/gfx/cairo/quartz-const-globals.patch
@@ -0,0 +1,134 @@
+# HG changeset patch
+# User Robert O'Callahan <robert@ocallahan.org>
+# Date 1249558626 -43200
+# Node ID 963b9451ad305924738d05d997a640698cd3af91
+# Parent  e564f3ab4ea6e3b5dd9c4e9e6042d3a84c229dde
+Bug 508730. Clean up Quartz gradient code by moving some local variables to static const globals. r=jmuizelaar
+
+diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+@@ -684,51 +684,50 @@ ComputeGradientValue (void *info, const 
+ 	    grad->stops[i-1].color.blue * ap +
+ 	    grad->stops[i].color.blue * bp;
+ 	out[3] =
+ 	    grad->stops[i-1].color.alpha * ap +
+ 	    grad->stops[i].color.alpha * bp;
+     }
+ }
+ 
++static const float gradient_output_value_ranges[8] = {
++    0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f
++};
++static const CGFunctionCallbacks gradient_callbacks = {
++    0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
++};
++
+ static CGFunctionRef
+ CreateGradientFunction (const cairo_gradient_pattern_t *gpat)
+ {
+     cairo_pattern_t *pat;
+     float input_value_range[2] = { 0.f, 1.f };
+-    float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
+-    CGFunctionCallbacks callbacks = {
+-	0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
+-    };
+ 
+     if (_cairo_pattern_create_copy (&pat, &gpat->base))
+ 	/* quartz doesn't deal very well with malloc failing, so there's
+ 	 * not much point in us trying either */
+ 	return NULL;
+ 
+     return CGFunctionCreate (pat,
+ 			     1,
+ 			     input_value_range,
+ 			     4,
+-			     output_value_ranges,
+-			     &callbacks);
++			     gradient_output_value_ranges,
++			     &gradient_callbacks);
+ }
+ 
+ static CGFunctionRef
+ CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
+ 				       const cairo_gradient_pattern_t *gpat,
+ 				       CGPoint *start, CGPoint *end,
+ 				       CGAffineTransform matrix)
+ {
+     cairo_pattern_t *pat;
+     float input_value_range[2];
+-    float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
+-    CGFunctionCallbacks callbacks = {
+-	0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
+-    };
+ 
+     CGPoint mstart, mend;
+ 
+     double dx, dy;
+     int x_rep_start = 0, x_rep_end = 0;
+     int y_rep_start = 0, y_rep_end = 0;
+ 
+     int rep_start, rep_end;
+@@ -787,18 +786,18 @@ CreateRepeatingLinearGradientFunction (c
+ 	/* quartz doesn't deal very well with malloc failing, so there's
+ 	 * not much point in us trying either */
+ 	return NULL;
+ 
+     return CGFunctionCreate (pat,
+ 			     1,
+ 			     input_value_range,
+ 			     4,
+-			     output_value_ranges,
+-			     &callbacks);
++			     gradient_output_value_ranges,
++			     &gradient_callbacks);
+ }
+ 
+ static void
+ UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center,
+                                     double dr, double dx, double dy,
+                                     double x, double y)
+ {
+     /* Compute a parameter t such that a circle centered at
+@@ -847,20 +846,16 @@ CreateRepeatingRadialGradientFunction (c
+                                        const cairo_gradient_pattern_t *gpat,
+                                        CGPoint *start, double *start_radius,
+                                        CGPoint *end, double *end_radius)
+ {
+     CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
+     CGAffineTransform transform;
+     cairo_pattern_t *pat;
+     float input_value_range[2];
+-    float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
+-    CGFunctionCallbacks callbacks = {
+-        0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
+-    };
+     CGPoint *inner;
+     double *inner_radius;
+     CGPoint *outer;
+     double *outer_radius;
+     /* minimum and maximum t-parameter values that will make our gradient
+        cover the clipBox */
+     double t_min, t_max, t_temp;
+     /* outer minus inner */
+@@ -927,18 +922,18 @@ CreateRepeatingRadialGradientFunction (c
+   /* quartz doesn't deal very well with malloc failing, so there's
+    * not much point in us trying either */
+   return NULL;
+ 
+     return CGFunctionCreate (pat,
+            1,
+            input_value_range,
+            4,
+-           output_value_ranges,
+-           &callbacks);
++           gradient_output_value_ranges,
++           &gradient_callbacks);
+ }
+ 
+ /* Obtain a CGImageRef from a #cairo_surface_t * */
+ 
+ static void
+ DataProviderReleaseCallback (void *info, const void *data, size_t size)
+ {
+     cairo_surface_t *surface = (cairo_surface_t *) info;
new file mode 100644
--- /dev/null
+++ b/gfx/cairo/quartz-fallback.patch
@@ -0,0 +1,70 @@
+# HG changeset patch
+# User Robert O'Callahan <robert@ocallahan.org>
+# Date 1250204857 -43200
+# Node ID cc6bebbd93bb9d8606fe06b997f890acc17996fb
+# Parent  caea8b548962f0df38e8e9032e9f57ef0fd099ec
+Bug 507939 - Remove erroneous clip rect fixup which caused repainting errors with repeating radial gradients on Mac. r=jmuizelaar
+
+diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+@@ -1033,38 +1033,29 @@ typedef enum {
+     DO_TILED_IMAGE
+ } cairo_quartz_action_t;
+ 
+ static cairo_quartz_action_t
+ _cairo_quartz_setup_fallback_source (cairo_quartz_surface_t *surface,
+ 				     const cairo_pattern_t *source)
+ {
+     CGRect clipBox = CGContextGetClipBoundingBox (surface->cgContext);
+-    CGAffineTransform ctm;
+     double x0, y0, w, h;
+ 
+     cairo_surface_t *fallback;
+     cairo_t *fallback_cr;
+     CGImageRef img;
+     cairo_pattern_t *source_copy;
+ 
+     cairo_status_t status;
+ 
+     if (clipBox.size.width == 0.0f ||
+ 	clipBox.size.height == 0.0f)
+ 	return DO_NOTHING;
+ 
+-    // the clipBox is in userspace, so:
+-    ctm = CGContextGetCTM (surface->cgContext);
+-    ctm = CGAffineTransformInvert (ctm);
+-    clipBox = CGRectApplyAffineTransform (clipBox, ctm);
+-
+-    // get the Y flip right -- the CTM will always have a Y flip in place
+-    clipBox.origin.y = surface->extents.height - (clipBox.origin.y + clipBox.size.height);
+-
+     x0 = floor(clipBox.origin.x);
+     y0 = floor(clipBox.origin.y);
+     w = ceil(clipBox.origin.x + clipBox.size.width) - x0;
+     h = ceil(clipBox.origin.y + clipBox.size.height) - y0;
+ 
+     /* Create a temporary the size of the clip surface, and position
+      * it so that the device origin coincides with the original surface */
+     fallback = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, (int) w, (int) h);
+@@ -1717,18 +1708,20 @@ _cairo_quartz_surface_paint (void *abstr
+     action = _cairo_quartz_setup_source (surface, source);
+ 
+     if (action == DO_SOLID || action == DO_PATTERN) {
+ 	CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
+ 							  surface->extents.y,
+ 							  surface->extents.width,
+ 							  surface->extents.height));
+     } else if (action == DO_SHADING) {
++	CGContextSaveGState (surface->cgContext);
+ 	CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
+ 	CGContextDrawShading (surface->cgContext, surface->sourceShading);
++	CGContextRestoreGState (surface->cgContext);
+     } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) {
+ 	CGContextSaveGState (surface->cgContext);
+ 
+ 	CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
+ 	CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height);
+ 	CGContextScaleCTM (surface->cgContext, 1, -1);
+ 
+ 	if (action == DO_IMAGE)
new file mode 100644
--- /dev/null
+++ b/gfx/cairo/quartz-minimize-gradient-repeat.patch
@@ -0,0 +1,561 @@
+# HG changeset patch
+# User Robert O'Callahan <robert@ocallahan.org>
+# Date 1249558989 -43200
+# Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105
+# Parent  963b9451ad305924738d05d997a640698cd3af91
+Bug 508730. Don't repeat a Quartz gradient more times than necessary, to avoid Quartz quality problems when there are lots of repeated color stops. r=jmuizelaar
+
+diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+@@ -710,82 +710,100 @@ CreateGradientFunction (const cairo_grad
+     return CGFunctionCreate (pat,
+ 			     1,
+ 			     input_value_range,
+ 			     4,
+ 			     gradient_output_value_ranges,
+ 			     &gradient_callbacks);
+ }
+ 
++static void
++UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, CGPoint *start,
++                                     double dx, double dy,
++                                     double x, double y)
++{
++    /* Compute a parameter t such that a line perpendicular to the (dx,dy)
++       vector, passing through (start->x + dx*t, start->y + dy*t), also
++       passes through (x,y).
++
++       Let px = x - start->x, py = y - start->y.
++       t is given by
++         (px - dx*t)*dx + (py - dy*t)*dy = 0
++
++       Solving for t we get
++         numerator = dx*px + dy*py
++         denominator = dx^2 + dy^2
++         t = numerator/denominator
++
++       In CreateRepeatingLinearGradientFunction we know the length of (dx,dy)
++       is not zero. (This is checked in _cairo_quartz_setup_linear_source.)
++    */
++    double px = x - start->x;
++    double py = y - start->y;
++    double numerator = dx*px + dy*py;
++    double denominator = dx*dx + dy*dy;
++    double t = numerator/denominator;
++
++    if (*min_t > t) {
++        *min_t = t;
++    }
++    if (*max_t < t) {
++        *max_t = t;
++    }
++}
++
+ static CGFunctionRef
+ CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
+ 				       const cairo_gradient_pattern_t *gpat,
+ 				       CGPoint *start, CGPoint *end,
+-				       CGAffineTransform matrix)
++				       cairo_rectangle_int_t *extents)
+ {
+     cairo_pattern_t *pat;
+     float input_value_range[2];
++    double t_min = 0.;
++    double t_max = 0.;
++    double dx = end->x - start->x;
++    double dy = end->y - start->y;
++    double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
+ 
+-    CGPoint mstart, mend;
++    if (!extents) {
++        extents = &surface->extents;
++    }
++    bounds_x1 = extents->x;
++    bounds_y1 = extents->y;
++    bounds_x2 = extents->x + extents->width;
++    bounds_y2 = extents->y + extents->height;
++    _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
++                                          &bounds_x1, &bounds_y1,
++                                          &bounds_x2, &bounds_y2,
++                                          NULL);
+ 
+-    double dx, dy;
+-    int x_rep_start = 0, x_rep_end = 0;
+-    int y_rep_start = 0, y_rep_end = 0;
++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
++                                         bounds_x1, bounds_y1);
++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
++                                         bounds_x2, bounds_y1);
++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
++                                         bounds_x2, bounds_y2);
++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
++                                         bounds_x1, bounds_y2);
+ 
+-    int rep_start, rep_end;
+-
+-    // figure out how many times we'd need to repeat the gradient pattern
+-    // to cover the whole (transformed) surface area
+-    mstart = CGPointApplyAffineTransform (*start, matrix);
+-    mend = CGPointApplyAffineTransform (*end, matrix);
+-
+-    dx = fabs (mend.x - mstart.x);
+-    dy = fabs (mend.y - mstart.y);
+-
+-    if (dx > 1e-6) {
+-	x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx);
+-	x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx);
+-
+-	if (mend.x < mstart.x) {
+-	    int swap = x_rep_end;
+-	    x_rep_end = x_rep_start;
+-	    x_rep_start = swap;
+-	}
+-    }
+-
+-    if (dy > 1e-6) {
+-	y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy);
+-	y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy);
+-
+-	if (mend.y < mstart.y) {
+-	    int swap = y_rep_end;
+-	    y_rep_end = y_rep_start;
+-	    y_rep_start = swap;
+-	}
+-    }
+-
+-    rep_start = MAX(x_rep_start, y_rep_start);
+-    rep_end = MAX(x_rep_end, y_rep_end);
+-
+-    // extend the line between start and end by rep_start times from the start
+-    // and rep_end times from the end
+-
+-    dx = end->x - start->x;
+-    dy = end->y - start->y;
+-
+-    start->x = start->x - dx * rep_start;
+-    start->y = start->y - dy * rep_start;
+-
+-    end->x = end->x + dx * rep_end;
+-    end->y = end->y + dy * rep_end;
++    /* Move t_min and t_max to the nearest usable integer to try to avoid
++       subtle variations due to numerical instability, especially accidentally
++       cutting off a pixel. Extending the gradient repetitions is always safe. */
++    t_min = floor (t_min);
++    t_max = ceil (t_max);
++    end->x = start->x + dx*t_max;
++    end->y = start->y + dy*t_max;
++    start->x = start->x + dx*t_min;
++    start->y = start->y + dy*t_min;
+ 
+     // set the input range for the function -- the function knows how to
+     // map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT.
+-    input_value_range[0] = 0.0 - 1.0 * rep_start;
+-    input_value_range[1] = 1.0 + 1.0 * rep_end;
++    input_value_range[0] = t_min;
++    input_value_range[1] = t_max;
+ 
+     if (_cairo_pattern_create_copy (&pat, &gpat->base))
+ 	/* quartz doesn't deal very well with malloc failing, so there's
+ 	 * not much point in us trying either */
+ 	return NULL;
+ 
+     return CGFunctionCreate (pat,
+ 			     1,
+@@ -840,35 +858,43 @@ UpdateRadialParameterToIncludePoint(doub
+     }
+ }
+ 
+ /* This must only be called when one of the circles properly contains the other */
+ static CGFunctionRef
+ CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface,
+                                        const cairo_gradient_pattern_t *gpat,
+                                        CGPoint *start, double *start_radius,
+-                                       CGPoint *end, double *end_radius)
++                                       CGPoint *end, double *end_radius,
++                                       cairo_rectangle_int_t *extents)
+ {
+-    CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
+-    CGAffineTransform transform;
+     cairo_pattern_t *pat;
+     float input_value_range[2];
+     CGPoint *inner;
+     double *inner_radius;
+     CGPoint *outer;
+     double *outer_radius;
+     /* minimum and maximum t-parameter values that will make our gradient
+        cover the clipBox */
+     double t_min, t_max, t_temp;
+     /* outer minus inner */
+     double dr, dx, dy;
++    double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
+ 
+-    _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform);
+-    /* clip is in cairo device coordinates; get it into cairo user space */
+-    clip = CGRectApplyAffineTransform (clip, transform);
++    if (!extents) {
++        extents = &surface->extents;
++    }
++    bounds_x1 = extents->x;
++    bounds_y1 = extents->y;
++    bounds_x2 = extents->x + extents->width;
++    bounds_y2 = extents->y + extents->height;
++    _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
++                                          &bounds_x1, &bounds_y1,
++                                          &bounds_x2, &bounds_y2,
++                                          NULL);
+ 
+     if (*start_radius < *end_radius) {
+         /* end circle contains start circle */
+         inner = start;
+         outer = end;
+         inner_radius = start_radius;
+         outer_radius = end_radius;
+     } else {
+@@ -878,36 +904,37 @@ CreateRepeatingRadialGradientFunction (c
+         inner_radius = end_radius;
+         outer_radius = start_radius;
+     }
+ 
+     dr = *outer_radius - *inner_radius;
+     dx = outer->x - inner->x;
+     dy = outer->y - inner->y;
+ 
++    /* We can't round or fudge t_min here, it has to be as accurate as possible. */
+     t_min = -(*inner_radius/dr);
+     inner->x += t_min*dx;
+     inner->y += t_min*dy;
+     *inner_radius = 0.;
+ 
+     t_temp = 0.;
+     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+-                                        clip.origin.x, clip.origin.y);
++                                        bounds_x1, bounds_y1);
+     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+-                                        clip.origin.x + clip.size.width, clip.origin.y);
++                                        bounds_x2, bounds_y1);
+     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+-                                        clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
++                                        bounds_x2, bounds_y2);
+     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+-                                        clip.origin.x, clip.origin.y + clip.size.height);
++                                        bounds_x1, bounds_y2);
+     /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0.
+        But for the parameter values we use with Quartz, t_min means radius 0.
+-       Also, add a small fudge factor to avoid rounding issues. Since the
+-       circles are alway expanding and containing the earlier circles, this is
+-       OK. */
+-    t_temp += 1e-6;
++       Since the circles are alway expanding and contain the earlier circles,
++       it's safe to extend t_max/t_temp as much as we want, so round t_temp up
++       to the nearest integer. This may help us give stable results. */
++    t_temp = ceil (t_temp);
+     t_max = t_min + t_temp;
+     outer->x = inner->x + t_temp*dx;
+     outer->y = inner->y + t_temp*dy;
+     *outer_radius = t_temp*dr;
+ 
+     /* set the input range for the function -- the function knows how to
+        map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */
+     if (*start_radius < *end_radius) {
+@@ -1218,33 +1245,57 @@ _cairo_quartz_setup_fallback_source (cai
+     surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h);
+     surface->sourceImage = img;
+     surface->sourceImageSurface = fallback;
+     surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0);
+ 
+     return DO_IMAGE;
+ }
+ 
++/*
++Quartz does not support repeating radients. We handle repeating gradients
++by manually extending the gradient and repeating color stops. We need to
++minimize the number of repetitions since Quartz seems to sample our color
++function across the entire range, even if part of that range is not needed
++for the visible area of the gradient, and it samples with some fixed resolution,
++so if the gradient range is too large it samples with very low resolution and
++the gradient is very coarse. CreateRepeatingLinearGradientFunction and
++CreateRepeatingRadialGradientFunction compute the number of repetitions needed
++based on the extents of the object (the clip region cannot be used here since
++we don't want the rasterization of the entire gradient to depend on the
++clip region).
++*/
+ static cairo_quartz_action_t
+ _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface,
+-				   const cairo_linear_pattern_t *lpat)
++				   const cairo_linear_pattern_t *lpat,
++				   cairo_rectangle_int_t *extents)
+ {
+     const cairo_pattern_t *abspat = &lpat->base.base;
+     cairo_matrix_t mat;
+     CGPoint start, end;
+     CGFunctionRef gradFunc;
+     CGColorSpaceRef rgb;
+     bool extend = abspat->extend == CAIRO_EXTEND_PAD;
+ 
+     if (lpat->base.n_stops == 0) {
+ 	CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
+ 	CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
+ 	return DO_SOLID;
+     }
+ 
++    if (lpat->p1.x == lpat->p2.x &&
++        lpat->p1.y == lpat->p2.y) {
++	/* Quartz handles cases where the vector has no length very
++	 * differently from pixman.
++	 * Whatever the correct behaviour is, let's at least have only pixman's
++	 * implementation to worry about.
++	 */
++	return _cairo_quartz_setup_fallback_source (surface, abspat);
++    }
++
+     mat = abspat->matrix;
+     cairo_matrix_invert (&mat);
+     _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
+ 
+     rgb = CGColorSpaceCreateDeviceRGB();
+ 
+     start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
+ 			 _cairo_fixed_to_double (lpat->p1.y));
+@@ -1254,33 +1305,34 @@ _cairo_quartz_setup_linear_source (cairo
+     if (abspat->extend == CAIRO_EXTEND_NONE ||
+         abspat->extend == CAIRO_EXTEND_PAD) 
+     {
+ 	gradFunc = CreateGradientFunction (&lpat->base);
+     } else {
+ 	gradFunc = CreateRepeatingLinearGradientFunction (surface,
+ 						          &lpat->base,
+ 						          &start, &end,
+-						          surface->sourceTransform);
++						          extents);
+     }
+ 
+     surface->sourceShading = CGShadingCreateAxial (rgb,
+ 						   start, end,
+ 						   gradFunc,
+ 						   extend, extend);
+ 
+     CGColorSpaceRelease(rgb);
+     CGFunctionRelease(gradFunc);
+ 
+     return DO_SHADING;
+ }
+ 
+ static cairo_quartz_action_t
+ _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface,
+-				   const cairo_radial_pattern_t *rpat)
++				   const cairo_radial_pattern_t *rpat,
++				   cairo_rectangle_int_t *extents)
+ {
+     const cairo_pattern_t *abspat = &rpat->base.base;
+     cairo_matrix_t mat;
+     CGPoint start, end;
+     CGFunctionRef gradFunc;
+     CGColorSpaceRef rgb;
+     bool extend = abspat->extend == CAIRO_EXTEND_PAD;
+     double c1x = _cairo_fixed_to_double (rpat->c1.x);
+@@ -1322,17 +1374,18 @@ _cairo_quartz_setup_radial_source (cairo
+     if (abspat->extend == CAIRO_EXTEND_NONE ||
+         abspat->extend == CAIRO_EXTEND_PAD)
+     {
+ 	gradFunc = CreateGradientFunction (&rpat->base);
+     } else {
+ 	gradFunc = CreateRepeatingRadialGradientFunction (surface,
+ 						          &rpat->base,
+ 						          &start, &r1,
+-						          &end, &r2);
++						          &end, &r2,
++						          extents);
+     }
+ 
+     surface->sourceShading = CGShadingCreateRadial (rgb,
+ 						    start,
+ 						    r1,
+ 						    end,
+ 						    r2,
+ 						    gradFunc,
+@@ -1341,17 +1394,18 @@ _cairo_quartz_setup_radial_source (cairo
+     CGColorSpaceRelease(rgb);
+     CGFunctionRelease(gradFunc);
+ 
+     return DO_SHADING;
+ }
+ 
+ static cairo_quartz_action_t
+ _cairo_quartz_setup_source (cairo_quartz_surface_t *surface,
+-			    const cairo_pattern_t *source)
++			    const cairo_pattern_t *source,
++			    cairo_rectangle_int_t *extents)
+ {
+     assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern));
+ 
+     surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext);
+     CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter));
+ 
+     if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
+ 	cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source;
+@@ -1367,24 +1421,22 @@ _cairo_quartz_setup_source (cairo_quartz
+ 				  solid->color.blue,
+ 				  solid->color.alpha);
+ 
+ 	return DO_SOLID;
+     }
+ 
+     if (source->type == CAIRO_PATTERN_TYPE_LINEAR) {
+ 	const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source;
+-	return _cairo_quartz_setup_linear_source (surface, lpat);
+-
++	return _cairo_quartz_setup_linear_source (surface, lpat, extents);
+     }
+ 
+     if (source->type == CAIRO_PATTERN_TYPE_RADIAL) {
+ 	const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source;
+-	return _cairo_quartz_setup_radial_source (surface, rpat);
+-
++	return _cairo_quartz_setup_radial_source (surface, rpat, extents);
+     }
+ 
+     if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
+ 	(source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
+     {
+ 	const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source;
+ 	cairo_surface_t *pat_surf = spat->surface;
+ 	CGImageRef img;
+@@ -1852,17 +1904,17 @@ _cairo_quartz_surface_paint (void *abstr
+     if (IS_EMPTY(surface))
+ 	return CAIRO_STATUS_SUCCESS;
+ 
+     if (op == CAIRO_OPERATOR_DEST)
+ 	return CAIRO_STATUS_SUCCESS;
+ 
+     CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
+ 
+-    action = _cairo_quartz_setup_source (surface, source);
++    action = _cairo_quartz_setup_source (surface, source, NULL);
+ 
+     if (action == DO_SOLID || action == DO_PATTERN) {
+ 	CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
+ 							  surface->extents.y,
+ 							  surface->extents.width,
+ 							  surface->extents.height));
+     } else if (action == DO_SHADING) {
+ 	CGContextSaveGState (surface->cgContext);
+@@ -1886,16 +1938,35 @@ _cairo_quartz_surface_paint (void *abstr
+     }
+ 
+     _cairo_quartz_teardown_source (surface, source);
+ 
+     ND((stderr, "-- paint\n"));
+     return rv;
+ }
+ 
++static cairo_bool_t
++_cairo_quartz_source_needs_extents (const cairo_pattern_t *source)
++{
++    /* For repeating gradients we need to manually extend the gradient and
++       repeat stops, since Quartz doesn't support repeating gradients natively.
++       We need to minimze the number of repeated stops, and since rasterization
++       depends on the number of repetitions we use (even if some of the
++       repetitions go beyond the extents of the object or outside the clip
++       region), it's important to use the same number of repetitions when
++       rendering an object no matter what the clip region is. So the
++       computation of the repetition count cannot depended on the clip region,
++       and should only depend on the object extents, so we need to compute
++       the object extents for repeating gradients. */
++    return (source->type == CAIRO_PATTERN_TYPE_LINEAR ||
++            source->type == CAIRO_PATTERN_TYPE_RADIAL) &&
++           (source->extend == CAIRO_EXTEND_REPEAT ||
++            source->extend == CAIRO_EXTEND_REFLECT);
++}
++
+ static cairo_int_status_t
+ _cairo_quartz_surface_fill (void *abstract_surface,
+ 			     cairo_operator_t op,
+ 			     const cairo_pattern_t *source,
+ 			     cairo_path_fixed_t *path,
+ 			     cairo_fill_rule_t fill_rule,
+ 			     double tolerance,
+ 			     cairo_antialias_t antialias,
+@@ -1926,17 +1997,27 @@ _cairo_quartz_surface_fill (void *abstra
+ 	return CAIRO_STATUS_SUCCESS;
+     }
+ 
+     CGContextSaveGState (surface->cgContext);
+ 
+     CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
+     CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
+ 
+-    action = _cairo_quartz_setup_source (surface, source);
++    if (_cairo_quartz_source_needs_extents (source))
++    {
++        /* We don't need precise extents since these are only used to
++           compute the number of gradient reptitions needed to cover the
++           object. */
++        cairo_rectangle_int_t path_extents;
++        _cairo_path_fixed_approximate_fill_extents (path, &path_extents);
++        action = _cairo_quartz_setup_source (surface, source, &path_extents);
++    } else {
++        action = _cairo_quartz_setup_source (surface, source, NULL);
++    }
+ 
+     CGContextBeginPath (surface->cgContext);
+ 
+     stroke.cgContext = surface->cgContext;
+     stroke.ctm_inverse = NULL;
+     rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
+     if (rv)
+         goto BAIL;
+@@ -2059,17 +2140,24 @@ _cairo_quartz_surface_stroke (void *abst
+ 
+ 	CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes);
+ 	if (fdash != sdash)
+ 	    free (fdash);
+     }
+ 
+     CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
+ 
+-    action = _cairo_quartz_setup_source (surface, source);
++    if (_cairo_quartz_source_needs_extents (source))
++    {
++        cairo_rectangle_int_t path_extents;
++        _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents);
++        action = _cairo_quartz_setup_source (surface, source, &path_extents);
++    } else {
++        action = _cairo_quartz_setup_source (surface, source, NULL);
++    }
+ 
+     CGContextBeginPath (surface->cgContext);
+ 
+     stroke.cgContext = surface->cgContext;
+     stroke.ctm_inverse = ctm_inverse;
+     rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
+     if (rv)
+ 	goto BAIL;
+@@ -2180,17 +2268,26 @@ _cairo_quartz_surface_show_glyphs (void 
+     if (op == CAIRO_OPERATOR_DEST)
+ 	return CAIRO_STATUS_SUCCESS;
+ 
+     if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ)
+ 	return CAIRO_INT_STATUS_UNSUPPORTED;
+ 
+     CGContextSaveGState (surface->cgContext);
+ 
+-    action = _cairo_quartz_setup_source (surface, source);
++    if (_cairo_quartz_source_needs_extents (source))
++    {
++        cairo_rectangle_int_t glyph_extents;
++        _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs,
++                                                 &glyph_extents);
++        action = _cairo_quartz_setup_source (surface, source, &glyph_extents);
++    } else {
++        action = _cairo_quartz_setup_source (surface, source, NULL);
++    }
++
+     if (action == DO_SOLID || action == DO_PATTERN) {
+ 	CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill);
+     } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) {
+ 	CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip);
+ 	isClipping = TRUE;
+     } else {
+ 	if (action != DO_NOTHING)
+ 	    rv = CAIRO_INT_STATUS_UNSUPPORTED;
new file mode 100644
--- /dev/null
+++ b/gfx/cairo/quartz-repeating-radial-gradients.patch
@@ -0,0 +1,305 @@
+# HG changeset patch
+# User Robert O'Callahan <robert@ocallahan.org>
+# Date 1249558156 -43200
+# Node ID e564f3ab4ea6e3b5dd9c4e9e6042d3a84c229dde
+# Parent  6ef9993a30bf2f983c9d64d7441d2e3b6b935de1
+Bug 508227. Don't fallback to Quartz for repeating radial gradients. r=jmuizelaar
+
+diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
+@@ -708,20 +708,20 @@ CreateGradientFunction (const cairo_grad
+ 			     1,
+ 			     input_value_range,
+ 			     4,
+ 			     output_value_ranges,
+ 			     &callbacks);
+ }
+ 
+ static CGFunctionRef
+-CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface,
+-				 const cairo_gradient_pattern_t *gpat,
+-				 CGPoint *start, CGPoint *end,
+-				 CGAffineTransform matrix)
++CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
++				       const cairo_gradient_pattern_t *gpat,
++				       CGPoint *start, CGPoint *end,
++				       CGAffineTransform matrix)
+ {
+     cairo_pattern_t *pat;
+     float input_value_range[2];
+     float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
+     CGFunctionCallbacks callbacks = {
+ 	0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
+     };
+ 
+@@ -791,16 +791,156 @@ CreateRepeatingGradientFunction (cairo_q
+     return CGFunctionCreate (pat,
+ 			     1,
+ 			     input_value_range,
+ 			     4,
+ 			     output_value_ranges,
+ 			     &callbacks);
+ }
+ 
++static void
++UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center,
++                                    double dr, double dx, double dy,
++                                    double x, double y)
++{
++    /* Compute a parameter t such that a circle centered at
++       (center->x + dx*t, center->y + dy*t) with radius dr*t contains the
++       point (x,y).
++
++       Let px = x - center->x, py = y - center->y.
++       Parameter values for which t is on the circle are given by
++         (px - dx*t)^2 + (py - dy*t)^2 = (t*dr)^2
++
++       Solving for t using the quadratic formula, and simplifying, we get
++         numerator = dx*px + dy*py +-
++                     sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 )
++         denominator = dx^2 + dy^2 - dr^2
++         t = numerator/denominator
++
++       In CreateRepeatingRadialGradientFunction we know the outer circle
++       contains the inner circle. Therefore the distance between the circle
++       centers plus the radius of the inner circle is less than the radius of
++       the outer circle. (This is checked in _cairo_quartz_setup_radial_source.)
++       Therefore
++         dx^2 + dy^2 < dr^2
++       So the denominator is negative and the larger solution for t is given by
++         numerator = dx*px + dy*py -
++                     sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 )
++         denominator = dx^2 + dy^2 - dr^2
++         t = numerator/denominator
++       dx^2 + dy^2 < dr^2 also ensures that the operand of sqrt is positive.
++    */
++    double px = x - center->x;
++    double py = y - center->y;
++    double dx_py_minus_dy_px = dx*py - dy*px;
++    double numerator = dx*px + dy*py -
++        sqrt (dr*dr*(px*px + py*py) - dx_py_minus_dy_px*dx_py_minus_dy_px);
++    double denominator = dx*dx + dy*dy - dr*dr;
++    double t = numerator/denominator;
++
++    if (*max_t < t) {
++        *max_t = t;
++    }
++}
++
++/* This must only be called when one of the circles properly contains the other */
++static CGFunctionRef
++CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface,
++                                       const cairo_gradient_pattern_t *gpat,
++                                       CGPoint *start, double *start_radius,
++                                       CGPoint *end, double *end_radius)
++{
++    CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
++    CGAffineTransform transform;
++    cairo_pattern_t *pat;
++    float input_value_range[2];
++    float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
++    CGFunctionCallbacks callbacks = {
++        0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
++    };
++    CGPoint *inner;
++    double *inner_radius;
++    CGPoint *outer;
++    double *outer_radius;
++    /* minimum and maximum t-parameter values that will make our gradient
++       cover the clipBox */
++    double t_min, t_max, t_temp;
++    /* outer minus inner */
++    double dr, dx, dy;
++
++    _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform);
++    /* clip is in cairo device coordinates; get it into cairo user space */
++    clip = CGRectApplyAffineTransform (clip, transform);
++
++    if (*start_radius < *end_radius) {
++        /* end circle contains start circle */
++        inner = start;
++        outer = end;
++        inner_radius = start_radius;
++        outer_radius = end_radius;
++    } else {
++        /* start circle contains end circle */
++        inner = end;
++        outer = start;
++        inner_radius = end_radius;
++        outer_radius = start_radius;
++    }
++
++    dr = *outer_radius - *inner_radius;
++    dx = outer->x - inner->x;
++    dy = outer->y - inner->y;
++
++    t_min = -(*inner_radius/dr);
++    inner->x += t_min*dx;
++    inner->y += t_min*dy;
++    *inner_radius = 0.;
++
++    t_temp = 0.;
++    UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
++                                        clip.origin.x, clip.origin.y);
++    UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
++                                        clip.origin.x + clip.size.width, clip.origin.y);
++    UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
++                                        clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
++    UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
++                                        clip.origin.x, clip.origin.y + clip.size.height);
++    /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0.
++       But for the parameter values we use with Quartz, t_min means radius 0.
++       Also, add a small fudge factor to avoid rounding issues. Since the
++       circles are alway expanding and containing the earlier circles, this is
++       OK. */
++    t_temp += 1e-6;
++    t_max = t_min + t_temp;
++    outer->x = inner->x + t_temp*dx;
++    outer->y = inner->y + t_temp*dy;
++    *outer_radius = t_temp*dr;
++
++    /* set the input range for the function -- the function knows how to
++       map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */
++    if (*start_radius < *end_radius) {
++        input_value_range[0] = t_min;
++        input_value_range[1] = t_max;
++    } else {
++        input_value_range[0] = -t_max;
++        input_value_range[1] = -t_min;
++    }
++
++    if (_cairo_pattern_create_copy (&pat, &gpat->base))
++  /* quartz doesn't deal very well with malloc failing, so there's
++   * not much point in us trying either */
++  return NULL;
++
++    return CGFunctionCreate (pat,
++           1,
++           input_value_range,
++           4,
++           output_value_ranges,
++           &callbacks);
++}
++
+ /* Obtain a CGImageRef from a #cairo_surface_t * */
+ 
+ static void
+ DataProviderReleaseCallback (void *info, const void *data, size_t size)
+ {
+     cairo_surface_t *surface = (cairo_surface_t *) info;
+     cairo_surface_destroy (surface);
+ }
+@@ -1112,23 +1252,24 @@ _cairo_quartz_setup_linear_source (cairo
+     rgb = CGColorSpaceCreateDeviceRGB();
+ 
+     start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
+ 			 _cairo_fixed_to_double (lpat->p1.y));
+     end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x),
+ 		       _cairo_fixed_to_double (lpat->p2.y));
+ 
+     if (abspat->extend == CAIRO_EXTEND_NONE ||
+-	abspat->extend == CAIRO_EXTEND_PAD)
++        abspat->extend == CAIRO_EXTEND_PAD) 
+     {
+ 	gradFunc = CreateGradientFunction (&lpat->base);
+     } else {
+-	gradFunc = CreateRepeatingGradientFunction (surface,
+-						    &lpat->base,
+-						    &start, &end, surface->sourceTransform);
++	gradFunc = CreateRepeatingLinearGradientFunction (surface,
++						          &lpat->base,
++						          &start, &end,
++						          surface->sourceTransform);
+     }
+ 
+     surface->sourceShading = CGShadingCreateAxial (rgb,
+ 						   start, end,
+ 						   gradFunc,
+ 						   extend, extend);
+ 
+     CGColorSpaceRelease(rgb);
+@@ -1142,52 +1283,68 @@ _cairo_quartz_setup_radial_source (cairo
+ 				   const cairo_radial_pattern_t *rpat)
+ {
+     const cairo_pattern_t *abspat = &rpat->base.base;
+     cairo_matrix_t mat;
+     CGPoint start, end;
+     CGFunctionRef gradFunc;
+     CGColorSpaceRef rgb;
+     bool extend = abspat->extend == CAIRO_EXTEND_PAD;
++    double c1x = _cairo_fixed_to_double (rpat->c1.x);
++    double c1y = _cairo_fixed_to_double (rpat->c1.y);
++    double c2x = _cairo_fixed_to_double (rpat->c2.x);
++    double c2y = _cairo_fixed_to_double (rpat->c2.y);
++    double r1 = _cairo_fixed_to_double (rpat->r1);
++    double r2 = _cairo_fixed_to_double (rpat->r2);
++    double dx = c1x - c2x;
++    double dy = c1y - c2y;
++    double centerDistance = sqrt (dx*dx + dy*dy);
+ 
+     if (rpat->base.n_stops == 0) {
+ 	CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
+ 	CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
+ 	return DO_SOLID;
+     }
+ 
+-    if (abspat->extend == CAIRO_EXTEND_REPEAT ||
+-	abspat->extend == CAIRO_EXTEND_REFLECT)
+-    {
+-	/* I started trying to map these to Quartz, but it's much harder
+-	 * then the linear case (I think it would involve doing multiple
+-	 * Radial shadings).  So, instead, let's just render an image
+-	 * for pixman to draw the shading into, and use that.
++    if (r2 <= centerDistance + r1 + 1e-6 && /* circle 2 doesn't contain circle 1 */
++        r1 <= centerDistance + r2 + 1e-6) { /* circle 1 doesn't contain circle 2 */
++	/* Quartz handles cases where neither circle contains the other very
++	 * differently from pixman.
++	 * Whatever the correct behaviour is, let's at least have only pixman's
++	 * implementation to worry about.
++	 * Note that this also catches the cases where r1 == r2.
+ 	 */
+-	return _cairo_quartz_setup_fallback_source (surface, &rpat->base.base);
++	return _cairo_quartz_setup_fallback_source (surface, abspat);
+     }
+ 
+     mat = abspat->matrix;
+     cairo_matrix_invert (&mat);
+     _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
+ 
+     rgb = CGColorSpaceCreateDeviceRGB();
+ 
+-    start = CGPointMake (_cairo_fixed_to_double (rpat->c1.x),
+-			 _cairo_fixed_to_double (rpat->c1.y));
+-    end = CGPointMake (_cairo_fixed_to_double (rpat->c2.x),
+-		       _cairo_fixed_to_double (rpat->c2.y));
++    start = CGPointMake (c1x, c1y);
++    end = CGPointMake (c2x, c2y);
+ 
+-    gradFunc = CreateGradientFunction (&rpat->base);
++    if (abspat->extend == CAIRO_EXTEND_NONE ||
++        abspat->extend == CAIRO_EXTEND_PAD)
++    {
++	gradFunc = CreateGradientFunction (&rpat->base);
++    } else {
++	gradFunc = CreateRepeatingRadialGradientFunction (surface,
++						          &rpat->base,
++						          &start, &r1,
++						          &end, &r2);
++    }
+ 
+     surface->sourceShading = CGShadingCreateRadial (rgb,
+ 						    start,
+-						    _cairo_fixed_to_double (rpat->r1),
++						    r1,
+ 						    end,
+-						    _cairo_fixed_to_double (rpat->r2),
++						    r2,
+ 						    gradFunc,
+ 						    extend, extend);
+ 
+     CGColorSpaceRelease(rgb);
+     CGFunctionRelease(gradFunc);
+ 
+     return DO_SHADING;
+ }