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;