gfx/cairo/quartz-minimize-gradient-repeat.patch

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/gfx/cairo/quartz-minimize-gradient-repeat.patch	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,561 @@
     1.4 +# HG changeset patch
     1.5 +# User Robert O'Callahan <robert@ocallahan.org>
     1.6 +# Date 1249558989 -43200
     1.7 +# Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105
     1.8 +# Parent  963b9451ad305924738d05d997a640698cd3af91
     1.9 +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
    1.10 +
    1.11 +diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
    1.12 +--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
    1.13 ++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
    1.14 +@@ -710,82 +710,100 @@ CreateGradientFunction (const cairo_grad
    1.15 +     return CGFunctionCreate (pat,
    1.16 + 			     1,
    1.17 + 			     input_value_range,
    1.18 + 			     4,
    1.19 + 			     gradient_output_value_ranges,
    1.20 + 			     &gradient_callbacks);
    1.21 + }
    1.22 + 
    1.23 ++static void
    1.24 ++UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, CGPoint *start,
    1.25 ++                                     double dx, double dy,
    1.26 ++                                     double x, double y)
    1.27 ++{
    1.28 ++    /* Compute a parameter t such that a line perpendicular to the (dx,dy)
    1.29 ++       vector, passing through (start->x + dx*t, start->y + dy*t), also
    1.30 ++       passes through (x,y).
    1.31 ++
    1.32 ++       Let px = x - start->x, py = y - start->y.
    1.33 ++       t is given by
    1.34 ++         (px - dx*t)*dx + (py - dy*t)*dy = 0
    1.35 ++
    1.36 ++       Solving for t we get
    1.37 ++         numerator = dx*px + dy*py
    1.38 ++         denominator = dx^2 + dy^2
    1.39 ++         t = numerator/denominator
    1.40 ++
    1.41 ++       In CreateRepeatingLinearGradientFunction we know the length of (dx,dy)
    1.42 ++       is not zero. (This is checked in _cairo_quartz_setup_linear_source.)
    1.43 ++    */
    1.44 ++    double px = x - start->x;
    1.45 ++    double py = y - start->y;
    1.46 ++    double numerator = dx*px + dy*py;
    1.47 ++    double denominator = dx*dx + dy*dy;
    1.48 ++    double t = numerator/denominator;
    1.49 ++
    1.50 ++    if (*min_t > t) {
    1.51 ++        *min_t = t;
    1.52 ++    }
    1.53 ++    if (*max_t < t) {
    1.54 ++        *max_t = t;
    1.55 ++    }
    1.56 ++}
    1.57 ++
    1.58 + static CGFunctionRef
    1.59 + CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
    1.60 + 				       const cairo_gradient_pattern_t *gpat,
    1.61 + 				       CGPoint *start, CGPoint *end,
    1.62 +-				       CGAffineTransform matrix)
    1.63 ++				       cairo_rectangle_int_t *extents)
    1.64 + {
    1.65 +     cairo_pattern_t *pat;
    1.66 +     float input_value_range[2];
    1.67 ++    double t_min = 0.;
    1.68 ++    double t_max = 0.;
    1.69 ++    double dx = end->x - start->x;
    1.70 ++    double dy = end->y - start->y;
    1.71 ++    double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
    1.72 + 
    1.73 +-    CGPoint mstart, mend;
    1.74 ++    if (!extents) {
    1.75 ++        extents = &surface->extents;
    1.76 ++    }
    1.77 ++    bounds_x1 = extents->x;
    1.78 ++    bounds_y1 = extents->y;
    1.79 ++    bounds_x2 = extents->x + extents->width;
    1.80 ++    bounds_y2 = extents->y + extents->height;
    1.81 ++    _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
    1.82 ++                                          &bounds_x1, &bounds_y1,
    1.83 ++                                          &bounds_x2, &bounds_y2,
    1.84 ++                                          NULL);
    1.85 + 
    1.86 +-    double dx, dy;
    1.87 +-    int x_rep_start = 0, x_rep_end = 0;
    1.88 +-    int y_rep_start = 0, y_rep_end = 0;
    1.89 ++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    1.90 ++                                         bounds_x1, bounds_y1);
    1.91 ++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    1.92 ++                                         bounds_x2, bounds_y1);
    1.93 ++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    1.94 ++                                         bounds_x2, bounds_y2);
    1.95 ++    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    1.96 ++                                         bounds_x1, bounds_y2);
    1.97 + 
    1.98 +-    int rep_start, rep_end;
    1.99 +-
   1.100 +-    // figure out how many times we'd need to repeat the gradient pattern
   1.101 +-    // to cover the whole (transformed) surface area
   1.102 +-    mstart = CGPointApplyAffineTransform (*start, matrix);
   1.103 +-    mend = CGPointApplyAffineTransform (*end, matrix);
   1.104 +-
   1.105 +-    dx = fabs (mend.x - mstart.x);
   1.106 +-    dy = fabs (mend.y - mstart.y);
   1.107 +-
   1.108 +-    if (dx > 1e-6) {
   1.109 +-	x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx);
   1.110 +-	x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx);
   1.111 +-
   1.112 +-	if (mend.x < mstart.x) {
   1.113 +-	    int swap = x_rep_end;
   1.114 +-	    x_rep_end = x_rep_start;
   1.115 +-	    x_rep_start = swap;
   1.116 +-	}
   1.117 +-    }
   1.118 +-
   1.119 +-    if (dy > 1e-6) {
   1.120 +-	y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy);
   1.121 +-	y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy);
   1.122 +-
   1.123 +-	if (mend.y < mstart.y) {
   1.124 +-	    int swap = y_rep_end;
   1.125 +-	    y_rep_end = y_rep_start;
   1.126 +-	    y_rep_start = swap;
   1.127 +-	}
   1.128 +-    }
   1.129 +-
   1.130 +-    rep_start = MAX(x_rep_start, y_rep_start);
   1.131 +-    rep_end = MAX(x_rep_end, y_rep_end);
   1.132 +-
   1.133 +-    // extend the line between start and end by rep_start times from the start
   1.134 +-    // and rep_end times from the end
   1.135 +-
   1.136 +-    dx = end->x - start->x;
   1.137 +-    dy = end->y - start->y;
   1.138 +-
   1.139 +-    start->x = start->x - dx * rep_start;
   1.140 +-    start->y = start->y - dy * rep_start;
   1.141 +-
   1.142 +-    end->x = end->x + dx * rep_end;
   1.143 +-    end->y = end->y + dy * rep_end;
   1.144 ++    /* Move t_min and t_max to the nearest usable integer to try to avoid
   1.145 ++       subtle variations due to numerical instability, especially accidentally
   1.146 ++       cutting off a pixel. Extending the gradient repetitions is always safe. */
   1.147 ++    t_min = floor (t_min);
   1.148 ++    t_max = ceil (t_max);
   1.149 ++    end->x = start->x + dx*t_max;
   1.150 ++    end->y = start->y + dy*t_max;
   1.151 ++    start->x = start->x + dx*t_min;
   1.152 ++    start->y = start->y + dy*t_min;
   1.153 + 
   1.154 +     // set the input range for the function -- the function knows how to
   1.155 +     // map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT.
   1.156 +-    input_value_range[0] = 0.0 - 1.0 * rep_start;
   1.157 +-    input_value_range[1] = 1.0 + 1.0 * rep_end;
   1.158 ++    input_value_range[0] = t_min;
   1.159 ++    input_value_range[1] = t_max;
   1.160 + 
   1.161 +     if (_cairo_pattern_create_copy (&pat, &gpat->base))
   1.162 + 	/* quartz doesn't deal very well with malloc failing, so there's
   1.163 + 	 * not much point in us trying either */
   1.164 + 	return NULL;
   1.165 + 
   1.166 +     return CGFunctionCreate (pat,
   1.167 + 			     1,
   1.168 +@@ -840,35 +858,43 @@ UpdateRadialParameterToIncludePoint(doub
   1.169 +     }
   1.170 + }
   1.171 + 
   1.172 + /* This must only be called when one of the circles properly contains the other */
   1.173 + static CGFunctionRef
   1.174 + CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface,
   1.175 +                                        const cairo_gradient_pattern_t *gpat,
   1.176 +                                        CGPoint *start, double *start_radius,
   1.177 +-                                       CGPoint *end, double *end_radius)
   1.178 ++                                       CGPoint *end, double *end_radius,
   1.179 ++                                       cairo_rectangle_int_t *extents)
   1.180 + {
   1.181 +-    CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
   1.182 +-    CGAffineTransform transform;
   1.183 +     cairo_pattern_t *pat;
   1.184 +     float input_value_range[2];
   1.185 +     CGPoint *inner;
   1.186 +     double *inner_radius;
   1.187 +     CGPoint *outer;
   1.188 +     double *outer_radius;
   1.189 +     /* minimum and maximum t-parameter values that will make our gradient
   1.190 +        cover the clipBox */
   1.191 +     double t_min, t_max, t_temp;
   1.192 +     /* outer minus inner */
   1.193 +     double dr, dx, dy;
   1.194 ++    double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
   1.195 + 
   1.196 +-    _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform);
   1.197 +-    /* clip is in cairo device coordinates; get it into cairo user space */
   1.198 +-    clip = CGRectApplyAffineTransform (clip, transform);
   1.199 ++    if (!extents) {
   1.200 ++        extents = &surface->extents;
   1.201 ++    }
   1.202 ++    bounds_x1 = extents->x;
   1.203 ++    bounds_y1 = extents->y;
   1.204 ++    bounds_x2 = extents->x + extents->width;
   1.205 ++    bounds_y2 = extents->y + extents->height;
   1.206 ++    _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
   1.207 ++                                          &bounds_x1, &bounds_y1,
   1.208 ++                                          &bounds_x2, &bounds_y2,
   1.209 ++                                          NULL);
   1.210 + 
   1.211 +     if (*start_radius < *end_radius) {
   1.212 +         /* end circle contains start circle */
   1.213 +         inner = start;
   1.214 +         outer = end;
   1.215 +         inner_radius = start_radius;
   1.216 +         outer_radius = end_radius;
   1.217 +     } else {
   1.218 +@@ -878,36 +904,37 @@ CreateRepeatingRadialGradientFunction (c
   1.219 +         inner_radius = end_radius;
   1.220 +         outer_radius = start_radius;
   1.221 +     }
   1.222 + 
   1.223 +     dr = *outer_radius - *inner_radius;
   1.224 +     dx = outer->x - inner->x;
   1.225 +     dy = outer->y - inner->y;
   1.226 + 
   1.227 ++    /* We can't round or fudge t_min here, it has to be as accurate as possible. */
   1.228 +     t_min = -(*inner_radius/dr);
   1.229 +     inner->x += t_min*dx;
   1.230 +     inner->y += t_min*dy;
   1.231 +     *inner_radius = 0.;
   1.232 + 
   1.233 +     t_temp = 0.;
   1.234 +     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   1.235 +-                                        clip.origin.x, clip.origin.y);
   1.236 ++                                        bounds_x1, bounds_y1);
   1.237 +     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   1.238 +-                                        clip.origin.x + clip.size.width, clip.origin.y);
   1.239 ++                                        bounds_x2, bounds_y1);
   1.240 +     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   1.241 +-                                        clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
   1.242 ++                                        bounds_x2, bounds_y2);
   1.243 +     UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   1.244 +-                                        clip.origin.x, clip.origin.y + clip.size.height);
   1.245 ++                                        bounds_x1, bounds_y2);
   1.246 +     /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0.
   1.247 +        But for the parameter values we use with Quartz, t_min means radius 0.
   1.248 +-       Also, add a small fudge factor to avoid rounding issues. Since the
   1.249 +-       circles are alway expanding and containing the earlier circles, this is
   1.250 +-       OK. */
   1.251 +-    t_temp += 1e-6;
   1.252 ++       Since the circles are alway expanding and contain the earlier circles,
   1.253 ++       it's safe to extend t_max/t_temp as much as we want, so round t_temp up
   1.254 ++       to the nearest integer. This may help us give stable results. */
   1.255 ++    t_temp = ceil (t_temp);
   1.256 +     t_max = t_min + t_temp;
   1.257 +     outer->x = inner->x + t_temp*dx;
   1.258 +     outer->y = inner->y + t_temp*dy;
   1.259 +     *outer_radius = t_temp*dr;
   1.260 + 
   1.261 +     /* set the input range for the function -- the function knows how to
   1.262 +        map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */
   1.263 +     if (*start_radius < *end_radius) {
   1.264 +@@ -1218,33 +1245,57 @@ _cairo_quartz_setup_fallback_source (cai
   1.265 +     surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h);
   1.266 +     surface->sourceImage = img;
   1.267 +     surface->sourceImageSurface = fallback;
   1.268 +     surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0);
   1.269 + 
   1.270 +     return DO_IMAGE;
   1.271 + }
   1.272 + 
   1.273 ++/*
   1.274 ++Quartz does not support repeating radients. We handle repeating gradients
   1.275 ++by manually extending the gradient and repeating color stops. We need to
   1.276 ++minimize the number of repetitions since Quartz seems to sample our color
   1.277 ++function across the entire range, even if part of that range is not needed
   1.278 ++for the visible area of the gradient, and it samples with some fixed resolution,
   1.279 ++so if the gradient range is too large it samples with very low resolution and
   1.280 ++the gradient is very coarse. CreateRepeatingLinearGradientFunction and
   1.281 ++CreateRepeatingRadialGradientFunction compute the number of repetitions needed
   1.282 ++based on the extents of the object (the clip region cannot be used here since
   1.283 ++we don't want the rasterization of the entire gradient to depend on the
   1.284 ++clip region).
   1.285 ++*/
   1.286 + static cairo_quartz_action_t
   1.287 + _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface,
   1.288 +-				   const cairo_linear_pattern_t *lpat)
   1.289 ++				   const cairo_linear_pattern_t *lpat,
   1.290 ++				   cairo_rectangle_int_t *extents)
   1.291 + {
   1.292 +     const cairo_pattern_t *abspat = &lpat->base.base;
   1.293 +     cairo_matrix_t mat;
   1.294 +     CGPoint start, end;
   1.295 +     CGFunctionRef gradFunc;
   1.296 +     CGColorSpaceRef rgb;
   1.297 +     bool extend = abspat->extend == CAIRO_EXTEND_PAD;
   1.298 + 
   1.299 +     if (lpat->base.n_stops == 0) {
   1.300 + 	CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
   1.301 + 	CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
   1.302 + 	return DO_SOLID;
   1.303 +     }
   1.304 + 
   1.305 ++    if (lpat->p1.x == lpat->p2.x &&
   1.306 ++        lpat->p1.y == lpat->p2.y) {
   1.307 ++	/* Quartz handles cases where the vector has no length very
   1.308 ++	 * differently from pixman.
   1.309 ++	 * Whatever the correct behaviour is, let's at least have only pixman's
   1.310 ++	 * implementation to worry about.
   1.311 ++	 */
   1.312 ++	return _cairo_quartz_setup_fallback_source (surface, abspat);
   1.313 ++    }
   1.314 ++
   1.315 +     mat = abspat->matrix;
   1.316 +     cairo_matrix_invert (&mat);
   1.317 +     _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
   1.318 + 
   1.319 +     rgb = CGColorSpaceCreateDeviceRGB();
   1.320 + 
   1.321 +     start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
   1.322 + 			 _cairo_fixed_to_double (lpat->p1.y));
   1.323 +@@ -1254,33 +1305,34 @@ _cairo_quartz_setup_linear_source (cairo
   1.324 +     if (abspat->extend == CAIRO_EXTEND_NONE ||
   1.325 +         abspat->extend == CAIRO_EXTEND_PAD) 
   1.326 +     {
   1.327 + 	gradFunc = CreateGradientFunction (&lpat->base);
   1.328 +     } else {
   1.329 + 	gradFunc = CreateRepeatingLinearGradientFunction (surface,
   1.330 + 						          &lpat->base,
   1.331 + 						          &start, &end,
   1.332 +-						          surface->sourceTransform);
   1.333 ++						          extents);
   1.334 +     }
   1.335 + 
   1.336 +     surface->sourceShading = CGShadingCreateAxial (rgb,
   1.337 + 						   start, end,
   1.338 + 						   gradFunc,
   1.339 + 						   extend, extend);
   1.340 + 
   1.341 +     CGColorSpaceRelease(rgb);
   1.342 +     CGFunctionRelease(gradFunc);
   1.343 + 
   1.344 +     return DO_SHADING;
   1.345 + }
   1.346 + 
   1.347 + static cairo_quartz_action_t
   1.348 + _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface,
   1.349 +-				   const cairo_radial_pattern_t *rpat)
   1.350 ++				   const cairo_radial_pattern_t *rpat,
   1.351 ++				   cairo_rectangle_int_t *extents)
   1.352 + {
   1.353 +     const cairo_pattern_t *abspat = &rpat->base.base;
   1.354 +     cairo_matrix_t mat;
   1.355 +     CGPoint start, end;
   1.356 +     CGFunctionRef gradFunc;
   1.357 +     CGColorSpaceRef rgb;
   1.358 +     bool extend = abspat->extend == CAIRO_EXTEND_PAD;
   1.359 +     double c1x = _cairo_fixed_to_double (rpat->c1.x);
   1.360 +@@ -1322,17 +1374,18 @@ _cairo_quartz_setup_radial_source (cairo
   1.361 +     if (abspat->extend == CAIRO_EXTEND_NONE ||
   1.362 +         abspat->extend == CAIRO_EXTEND_PAD)
   1.363 +     {
   1.364 + 	gradFunc = CreateGradientFunction (&rpat->base);
   1.365 +     } else {
   1.366 + 	gradFunc = CreateRepeatingRadialGradientFunction (surface,
   1.367 + 						          &rpat->base,
   1.368 + 						          &start, &r1,
   1.369 +-						          &end, &r2);
   1.370 ++						          &end, &r2,
   1.371 ++						          extents);
   1.372 +     }
   1.373 + 
   1.374 +     surface->sourceShading = CGShadingCreateRadial (rgb,
   1.375 + 						    start,
   1.376 + 						    r1,
   1.377 + 						    end,
   1.378 + 						    r2,
   1.379 + 						    gradFunc,
   1.380 +@@ -1341,17 +1394,18 @@ _cairo_quartz_setup_radial_source (cairo
   1.381 +     CGColorSpaceRelease(rgb);
   1.382 +     CGFunctionRelease(gradFunc);
   1.383 + 
   1.384 +     return DO_SHADING;
   1.385 + }
   1.386 + 
   1.387 + static cairo_quartz_action_t
   1.388 + _cairo_quartz_setup_source (cairo_quartz_surface_t *surface,
   1.389 +-			    const cairo_pattern_t *source)
   1.390 ++			    const cairo_pattern_t *source,
   1.391 ++			    cairo_rectangle_int_t *extents)
   1.392 + {
   1.393 +     assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern));
   1.394 + 
   1.395 +     surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext);
   1.396 +     CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter));
   1.397 + 
   1.398 +     if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
   1.399 + 	cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source;
   1.400 +@@ -1367,24 +1421,22 @@ _cairo_quartz_setup_source (cairo_quartz
   1.401 + 				  solid->color.blue,
   1.402 + 				  solid->color.alpha);
   1.403 + 
   1.404 + 	return DO_SOLID;
   1.405 +     }
   1.406 + 
   1.407 +     if (source->type == CAIRO_PATTERN_TYPE_LINEAR) {
   1.408 + 	const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source;
   1.409 +-	return _cairo_quartz_setup_linear_source (surface, lpat);
   1.410 +-
   1.411 ++	return _cairo_quartz_setup_linear_source (surface, lpat, extents);
   1.412 +     }
   1.413 + 
   1.414 +     if (source->type == CAIRO_PATTERN_TYPE_RADIAL) {
   1.415 + 	const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source;
   1.416 +-	return _cairo_quartz_setup_radial_source (surface, rpat);
   1.417 +-
   1.418 ++	return _cairo_quartz_setup_radial_source (surface, rpat, extents);
   1.419 +     }
   1.420 + 
   1.421 +     if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
   1.422 + 	(source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
   1.423 +     {
   1.424 + 	const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source;
   1.425 + 	cairo_surface_t *pat_surf = spat->surface;
   1.426 + 	CGImageRef img;
   1.427 +@@ -1852,17 +1904,17 @@ _cairo_quartz_surface_paint (void *abstr
   1.428 +     if (IS_EMPTY(surface))
   1.429 + 	return CAIRO_STATUS_SUCCESS;
   1.430 + 
   1.431 +     if (op == CAIRO_OPERATOR_DEST)
   1.432 + 	return CAIRO_STATUS_SUCCESS;
   1.433 + 
   1.434 +     CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
   1.435 + 
   1.436 +-    action = _cairo_quartz_setup_source (surface, source);
   1.437 ++    action = _cairo_quartz_setup_source (surface, source, NULL);
   1.438 + 
   1.439 +     if (action == DO_SOLID || action == DO_PATTERN) {
   1.440 + 	CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
   1.441 + 							  surface->extents.y,
   1.442 + 							  surface->extents.width,
   1.443 + 							  surface->extents.height));
   1.444 +     } else if (action == DO_SHADING) {
   1.445 + 	CGContextSaveGState (surface->cgContext);
   1.446 +@@ -1886,16 +1938,35 @@ _cairo_quartz_surface_paint (void *abstr
   1.447 +     }
   1.448 + 
   1.449 +     _cairo_quartz_teardown_source (surface, source);
   1.450 + 
   1.451 +     ND((stderr, "-- paint\n"));
   1.452 +     return rv;
   1.453 + }
   1.454 + 
   1.455 ++static cairo_bool_t
   1.456 ++_cairo_quartz_source_needs_extents (const cairo_pattern_t *source)
   1.457 ++{
   1.458 ++    /* For repeating gradients we need to manually extend the gradient and
   1.459 ++       repeat stops, since Quartz doesn't support repeating gradients natively.
   1.460 ++       We need to minimze the number of repeated stops, and since rasterization
   1.461 ++       depends on the number of repetitions we use (even if some of the
   1.462 ++       repetitions go beyond the extents of the object or outside the clip
   1.463 ++       region), it's important to use the same number of repetitions when
   1.464 ++       rendering an object no matter what the clip region is. So the
   1.465 ++       computation of the repetition count cannot depended on the clip region,
   1.466 ++       and should only depend on the object extents, so we need to compute
   1.467 ++       the object extents for repeating gradients. */
   1.468 ++    return (source->type == CAIRO_PATTERN_TYPE_LINEAR ||
   1.469 ++            source->type == CAIRO_PATTERN_TYPE_RADIAL) &&
   1.470 ++           (source->extend == CAIRO_EXTEND_REPEAT ||
   1.471 ++            source->extend == CAIRO_EXTEND_REFLECT);
   1.472 ++}
   1.473 ++
   1.474 + static cairo_int_status_t
   1.475 + _cairo_quartz_surface_fill (void *abstract_surface,
   1.476 + 			     cairo_operator_t op,
   1.477 + 			     const cairo_pattern_t *source,
   1.478 + 			     cairo_path_fixed_t *path,
   1.479 + 			     cairo_fill_rule_t fill_rule,
   1.480 + 			     double tolerance,
   1.481 + 			     cairo_antialias_t antialias,
   1.482 +@@ -1926,17 +1997,27 @@ _cairo_quartz_surface_fill (void *abstra
   1.483 + 	return CAIRO_STATUS_SUCCESS;
   1.484 +     }
   1.485 + 
   1.486 +     CGContextSaveGState (surface->cgContext);
   1.487 + 
   1.488 +     CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
   1.489 +     CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
   1.490 + 
   1.491 +-    action = _cairo_quartz_setup_source (surface, source);
   1.492 ++    if (_cairo_quartz_source_needs_extents (source))
   1.493 ++    {
   1.494 ++        /* We don't need precise extents since these are only used to
   1.495 ++           compute the number of gradient reptitions needed to cover the
   1.496 ++           object. */
   1.497 ++        cairo_rectangle_int_t path_extents;
   1.498 ++        _cairo_path_fixed_approximate_fill_extents (path, &path_extents);
   1.499 ++        action = _cairo_quartz_setup_source (surface, source, &path_extents);
   1.500 ++    } else {
   1.501 ++        action = _cairo_quartz_setup_source (surface, source, NULL);
   1.502 ++    }
   1.503 + 
   1.504 +     CGContextBeginPath (surface->cgContext);
   1.505 + 
   1.506 +     stroke.cgContext = surface->cgContext;
   1.507 +     stroke.ctm_inverse = NULL;
   1.508 +     rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
   1.509 +     if (rv)
   1.510 +         goto BAIL;
   1.511 +@@ -2059,17 +2140,24 @@ _cairo_quartz_surface_stroke (void *abst
   1.512 + 
   1.513 + 	CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes);
   1.514 + 	if (fdash != sdash)
   1.515 + 	    free (fdash);
   1.516 +     }
   1.517 + 
   1.518 +     CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
   1.519 + 
   1.520 +-    action = _cairo_quartz_setup_source (surface, source);
   1.521 ++    if (_cairo_quartz_source_needs_extents (source))
   1.522 ++    {
   1.523 ++        cairo_rectangle_int_t path_extents;
   1.524 ++        _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents);
   1.525 ++        action = _cairo_quartz_setup_source (surface, source, &path_extents);
   1.526 ++    } else {
   1.527 ++        action = _cairo_quartz_setup_source (surface, source, NULL);
   1.528 ++    }
   1.529 + 
   1.530 +     CGContextBeginPath (surface->cgContext);
   1.531 + 
   1.532 +     stroke.cgContext = surface->cgContext;
   1.533 +     stroke.ctm_inverse = ctm_inverse;
   1.534 +     rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
   1.535 +     if (rv)
   1.536 + 	goto BAIL;
   1.537 +@@ -2180,17 +2268,26 @@ _cairo_quartz_surface_show_glyphs (void 
   1.538 +     if (op == CAIRO_OPERATOR_DEST)
   1.539 + 	return CAIRO_STATUS_SUCCESS;
   1.540 + 
   1.541 +     if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ)
   1.542 + 	return CAIRO_INT_STATUS_UNSUPPORTED;
   1.543 + 
   1.544 +     CGContextSaveGState (surface->cgContext);
   1.545 + 
   1.546 +-    action = _cairo_quartz_setup_source (surface, source);
   1.547 ++    if (_cairo_quartz_source_needs_extents (source))
   1.548 ++    {
   1.549 ++        cairo_rectangle_int_t glyph_extents;
   1.550 ++        _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs,
   1.551 ++                                                 &glyph_extents);
   1.552 ++        action = _cairo_quartz_setup_source (surface, source, &glyph_extents);
   1.553 ++    } else {
   1.554 ++        action = _cairo_quartz_setup_source (surface, source, NULL);
   1.555 ++    }
   1.556 ++
   1.557 +     if (action == DO_SOLID || action == DO_PATTERN) {
   1.558 + 	CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill);
   1.559 +     } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) {
   1.560 + 	CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip);
   1.561 + 	isClipping = TRUE;
   1.562 +     } else {
   1.563 + 	if (action != DO_NOTHING)
   1.564 + 	    rv = CAIRO_INT_STATUS_UNSUPPORTED;

mercurial