michael@0: # HG changeset patch michael@0: # User Robert O'Callahan michael@0: # Date 1249558989 -43200 michael@0: # Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105 michael@0: # Parent 963b9451ad305924738d05d997a640698cd3af91 michael@0: 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 michael@0: michael@0: diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c michael@0: --- a/gfx/cairo/cairo/src/cairo-quartz-surface.c michael@0: +++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c michael@0: @@ -710,82 +710,100 @@ CreateGradientFunction (const cairo_grad michael@0: return CGFunctionCreate (pat, michael@0: 1, michael@0: input_value_range, michael@0: 4, michael@0: gradient_output_value_ranges, michael@0: &gradient_callbacks); michael@0: } michael@0: michael@0: +static void michael@0: +UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, CGPoint *start, michael@0: + double dx, double dy, michael@0: + double x, double y) michael@0: +{ michael@0: + /* Compute a parameter t such that a line perpendicular to the (dx,dy) michael@0: + vector, passing through (start->x + dx*t, start->y + dy*t), also michael@0: + passes through (x,y). michael@0: + michael@0: + Let px = x - start->x, py = y - start->y. michael@0: + t is given by michael@0: + (px - dx*t)*dx + (py - dy*t)*dy = 0 michael@0: + michael@0: + Solving for t we get michael@0: + numerator = dx*px + dy*py michael@0: + denominator = dx^2 + dy^2 michael@0: + t = numerator/denominator michael@0: + michael@0: + In CreateRepeatingLinearGradientFunction we know the length of (dx,dy) michael@0: + is not zero. (This is checked in _cairo_quartz_setup_linear_source.) michael@0: + */ michael@0: + double px = x - start->x; michael@0: + double py = y - start->y; michael@0: + double numerator = dx*px + dy*py; michael@0: + double denominator = dx*dx + dy*dy; michael@0: + double t = numerator/denominator; michael@0: + michael@0: + if (*min_t > t) { michael@0: + *min_t = t; michael@0: + } michael@0: + if (*max_t < t) { michael@0: + *max_t = t; michael@0: + } michael@0: +} michael@0: + michael@0: static CGFunctionRef michael@0: CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface, michael@0: const cairo_gradient_pattern_t *gpat, michael@0: CGPoint *start, CGPoint *end, michael@0: - CGAffineTransform matrix) michael@0: + cairo_rectangle_int_t *extents) michael@0: { michael@0: cairo_pattern_t *pat; michael@0: float input_value_range[2]; michael@0: + double t_min = 0.; michael@0: + double t_max = 0.; michael@0: + double dx = end->x - start->x; michael@0: + double dy = end->y - start->y; michael@0: + double bounds_x1, bounds_x2, bounds_y1, bounds_y2; michael@0: michael@0: - CGPoint mstart, mend; michael@0: + if (!extents) { michael@0: + extents = &surface->extents; michael@0: + } michael@0: + bounds_x1 = extents->x; michael@0: + bounds_y1 = extents->y; michael@0: + bounds_x2 = extents->x + extents->width; michael@0: + bounds_y2 = extents->y + extents->height; michael@0: + _cairo_matrix_transform_bounding_box (&gpat->base.matrix, michael@0: + &bounds_x1, &bounds_y1, michael@0: + &bounds_x2, &bounds_y2, michael@0: + NULL); michael@0: michael@0: - double dx, dy; michael@0: - int x_rep_start = 0, x_rep_end = 0; michael@0: - int y_rep_start = 0, y_rep_end = 0; michael@0: + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, michael@0: + bounds_x1, bounds_y1); michael@0: + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, michael@0: + bounds_x2, bounds_y1); michael@0: + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, michael@0: + bounds_x2, bounds_y2); michael@0: + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, michael@0: + bounds_x1, bounds_y2); michael@0: michael@0: - int rep_start, rep_end; michael@0: - michael@0: - // figure out how many times we'd need to repeat the gradient pattern michael@0: - // to cover the whole (transformed) surface area michael@0: - mstart = CGPointApplyAffineTransform (*start, matrix); michael@0: - mend = CGPointApplyAffineTransform (*end, matrix); michael@0: - michael@0: - dx = fabs (mend.x - mstart.x); michael@0: - dy = fabs (mend.y - mstart.y); michael@0: - michael@0: - if (dx > 1e-6) { michael@0: - x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx); michael@0: - x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx); michael@0: - michael@0: - if (mend.x < mstart.x) { michael@0: - int swap = x_rep_end; michael@0: - x_rep_end = x_rep_start; michael@0: - x_rep_start = swap; michael@0: - } michael@0: - } michael@0: - michael@0: - if (dy > 1e-6) { michael@0: - y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy); michael@0: - y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy); michael@0: - michael@0: - if (mend.y < mstart.y) { michael@0: - int swap = y_rep_end; michael@0: - y_rep_end = y_rep_start; michael@0: - y_rep_start = swap; michael@0: - } michael@0: - } michael@0: - michael@0: - rep_start = MAX(x_rep_start, y_rep_start); michael@0: - rep_end = MAX(x_rep_end, y_rep_end); michael@0: - michael@0: - // extend the line between start and end by rep_start times from the start michael@0: - // and rep_end times from the end michael@0: - michael@0: - dx = end->x - start->x; michael@0: - dy = end->y - start->y; michael@0: - michael@0: - start->x = start->x - dx * rep_start; michael@0: - start->y = start->y - dy * rep_start; michael@0: - michael@0: - end->x = end->x + dx * rep_end; michael@0: - end->y = end->y + dy * rep_end; michael@0: + /* Move t_min and t_max to the nearest usable integer to try to avoid michael@0: + subtle variations due to numerical instability, especially accidentally michael@0: + cutting off a pixel. Extending the gradient repetitions is always safe. */ michael@0: + t_min = floor (t_min); michael@0: + t_max = ceil (t_max); michael@0: + end->x = start->x + dx*t_max; michael@0: + end->y = start->y + dy*t_max; michael@0: + start->x = start->x + dx*t_min; michael@0: + start->y = start->y + dy*t_min; michael@0: michael@0: // set the input range for the function -- the function knows how to michael@0: // map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. michael@0: - input_value_range[0] = 0.0 - 1.0 * rep_start; michael@0: - input_value_range[1] = 1.0 + 1.0 * rep_end; michael@0: + input_value_range[0] = t_min; michael@0: + input_value_range[1] = t_max; michael@0: michael@0: if (_cairo_pattern_create_copy (&pat, &gpat->base)) michael@0: /* quartz doesn't deal very well with malloc failing, so there's michael@0: * not much point in us trying either */ michael@0: return NULL; michael@0: michael@0: return CGFunctionCreate (pat, michael@0: 1, michael@0: @@ -840,35 +858,43 @@ UpdateRadialParameterToIncludePoint(doub michael@0: } michael@0: } michael@0: michael@0: /* This must only be called when one of the circles properly contains the other */ michael@0: static CGFunctionRef michael@0: CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface, michael@0: const cairo_gradient_pattern_t *gpat, michael@0: CGPoint *start, double *start_radius, michael@0: - CGPoint *end, double *end_radius) michael@0: + CGPoint *end, double *end_radius, michael@0: + cairo_rectangle_int_t *extents) michael@0: { michael@0: - CGRect clip = CGContextGetClipBoundingBox (surface->cgContext); michael@0: - CGAffineTransform transform; michael@0: cairo_pattern_t *pat; michael@0: float input_value_range[2]; michael@0: CGPoint *inner; michael@0: double *inner_radius; michael@0: CGPoint *outer; michael@0: double *outer_radius; michael@0: /* minimum and maximum t-parameter values that will make our gradient michael@0: cover the clipBox */ michael@0: double t_min, t_max, t_temp; michael@0: /* outer minus inner */ michael@0: double dr, dx, dy; michael@0: + double bounds_x1, bounds_x2, bounds_y1, bounds_y2; michael@0: michael@0: - _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform); michael@0: - /* clip is in cairo device coordinates; get it into cairo user space */ michael@0: - clip = CGRectApplyAffineTransform (clip, transform); michael@0: + if (!extents) { michael@0: + extents = &surface->extents; michael@0: + } michael@0: + bounds_x1 = extents->x; michael@0: + bounds_y1 = extents->y; michael@0: + bounds_x2 = extents->x + extents->width; michael@0: + bounds_y2 = extents->y + extents->height; michael@0: + _cairo_matrix_transform_bounding_box (&gpat->base.matrix, michael@0: + &bounds_x1, &bounds_y1, michael@0: + &bounds_x2, &bounds_y2, michael@0: + NULL); michael@0: michael@0: if (*start_radius < *end_radius) { michael@0: /* end circle contains start circle */ michael@0: inner = start; michael@0: outer = end; michael@0: inner_radius = start_radius; michael@0: outer_radius = end_radius; michael@0: } else { michael@0: @@ -878,36 +904,37 @@ CreateRepeatingRadialGradientFunction (c michael@0: inner_radius = end_radius; michael@0: outer_radius = start_radius; michael@0: } michael@0: michael@0: dr = *outer_radius - *inner_radius; michael@0: dx = outer->x - inner->x; michael@0: dy = outer->y - inner->y; michael@0: michael@0: + /* We can't round or fudge t_min here, it has to be as accurate as possible. */ michael@0: t_min = -(*inner_radius/dr); michael@0: inner->x += t_min*dx; michael@0: inner->y += t_min*dy; michael@0: *inner_radius = 0.; michael@0: michael@0: t_temp = 0.; michael@0: UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: - clip.origin.x, clip.origin.y); michael@0: + bounds_x1, bounds_y1); michael@0: UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: - clip.origin.x + clip.size.width, clip.origin.y); michael@0: + bounds_x2, bounds_y1); michael@0: UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: - clip.origin.x + clip.size.width, clip.origin.y + clip.size.height); michael@0: + bounds_x2, bounds_y2); michael@0: UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: - clip.origin.x, clip.origin.y + clip.size.height); michael@0: + bounds_x1, bounds_y2); michael@0: /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0. michael@0: But for the parameter values we use with Quartz, t_min means radius 0. michael@0: - Also, add a small fudge factor to avoid rounding issues. Since the michael@0: - circles are alway expanding and containing the earlier circles, this is michael@0: - OK. */ michael@0: - t_temp += 1e-6; michael@0: + Since the circles are alway expanding and contain the earlier circles, michael@0: + it's safe to extend t_max/t_temp as much as we want, so round t_temp up michael@0: + to the nearest integer. This may help us give stable results. */ michael@0: + t_temp = ceil (t_temp); michael@0: t_max = t_min + t_temp; michael@0: outer->x = inner->x + t_temp*dx; michael@0: outer->y = inner->y + t_temp*dy; michael@0: *outer_radius = t_temp*dr; michael@0: michael@0: /* set the input range for the function -- the function knows how to michael@0: map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */ michael@0: if (*start_radius < *end_radius) { michael@0: @@ -1218,33 +1245,57 @@ _cairo_quartz_setup_fallback_source (cai michael@0: surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h); michael@0: surface->sourceImage = img; michael@0: surface->sourceImageSurface = fallback; michael@0: surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0); michael@0: michael@0: return DO_IMAGE; michael@0: } michael@0: michael@0: +/* michael@0: +Quartz does not support repeating radients. We handle repeating gradients michael@0: +by manually extending the gradient and repeating color stops. We need to michael@0: +minimize the number of repetitions since Quartz seems to sample our color michael@0: +function across the entire range, even if part of that range is not needed michael@0: +for the visible area of the gradient, and it samples with some fixed resolution, michael@0: +so if the gradient range is too large it samples with very low resolution and michael@0: +the gradient is very coarse. CreateRepeatingLinearGradientFunction and michael@0: +CreateRepeatingRadialGradientFunction compute the number of repetitions needed michael@0: +based on the extents of the object (the clip region cannot be used here since michael@0: +we don't want the rasterization of the entire gradient to depend on the michael@0: +clip region). michael@0: +*/ michael@0: static cairo_quartz_action_t michael@0: _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, michael@0: - const cairo_linear_pattern_t *lpat) michael@0: + const cairo_linear_pattern_t *lpat, michael@0: + cairo_rectangle_int_t *extents) michael@0: { michael@0: const cairo_pattern_t *abspat = &lpat->base.base; michael@0: cairo_matrix_t mat; michael@0: CGPoint start, end; michael@0: CGFunctionRef gradFunc; michael@0: CGColorSpaceRef rgb; michael@0: bool extend = abspat->extend == CAIRO_EXTEND_PAD; michael@0: michael@0: if (lpat->base.n_stops == 0) { michael@0: CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); michael@0: CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); michael@0: return DO_SOLID; michael@0: } michael@0: michael@0: + if (lpat->p1.x == lpat->p2.x && michael@0: + lpat->p1.y == lpat->p2.y) { michael@0: + /* Quartz handles cases where the vector has no length very michael@0: + * differently from pixman. michael@0: + * Whatever the correct behaviour is, let's at least have only pixman's michael@0: + * implementation to worry about. michael@0: + */ michael@0: + return _cairo_quartz_setup_fallback_source (surface, abspat); michael@0: + } michael@0: + michael@0: mat = abspat->matrix; michael@0: cairo_matrix_invert (&mat); michael@0: _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); michael@0: michael@0: rgb = CGColorSpaceCreateDeviceRGB(); michael@0: michael@0: start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x), michael@0: _cairo_fixed_to_double (lpat->p1.y)); michael@0: @@ -1254,33 +1305,34 @@ _cairo_quartz_setup_linear_source (cairo michael@0: if (abspat->extend == CAIRO_EXTEND_NONE || michael@0: abspat->extend == CAIRO_EXTEND_PAD) michael@0: { michael@0: gradFunc = CreateGradientFunction (&lpat->base); michael@0: } else { michael@0: gradFunc = CreateRepeatingLinearGradientFunction (surface, michael@0: &lpat->base, michael@0: &start, &end, michael@0: - surface->sourceTransform); michael@0: + extents); michael@0: } michael@0: michael@0: surface->sourceShading = CGShadingCreateAxial (rgb, michael@0: start, end, michael@0: gradFunc, michael@0: extend, extend); michael@0: michael@0: CGColorSpaceRelease(rgb); michael@0: CGFunctionRelease(gradFunc); michael@0: michael@0: return DO_SHADING; michael@0: } michael@0: michael@0: static cairo_quartz_action_t michael@0: _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, michael@0: - const cairo_radial_pattern_t *rpat) michael@0: + const cairo_radial_pattern_t *rpat, michael@0: + cairo_rectangle_int_t *extents) michael@0: { michael@0: const cairo_pattern_t *abspat = &rpat->base.base; michael@0: cairo_matrix_t mat; michael@0: CGPoint start, end; michael@0: CGFunctionRef gradFunc; michael@0: CGColorSpaceRef rgb; michael@0: bool extend = abspat->extend == CAIRO_EXTEND_PAD; michael@0: double c1x = _cairo_fixed_to_double (rpat->c1.x); michael@0: @@ -1322,17 +1374,18 @@ _cairo_quartz_setup_radial_source (cairo michael@0: if (abspat->extend == CAIRO_EXTEND_NONE || michael@0: abspat->extend == CAIRO_EXTEND_PAD) michael@0: { michael@0: gradFunc = CreateGradientFunction (&rpat->base); michael@0: } else { michael@0: gradFunc = CreateRepeatingRadialGradientFunction (surface, michael@0: &rpat->base, michael@0: &start, &r1, michael@0: - &end, &r2); michael@0: + &end, &r2, michael@0: + extents); michael@0: } michael@0: michael@0: surface->sourceShading = CGShadingCreateRadial (rgb, michael@0: start, michael@0: r1, michael@0: end, michael@0: r2, michael@0: gradFunc, michael@0: @@ -1341,17 +1394,18 @@ _cairo_quartz_setup_radial_source (cairo michael@0: CGColorSpaceRelease(rgb); michael@0: CGFunctionRelease(gradFunc); michael@0: michael@0: return DO_SHADING; michael@0: } michael@0: michael@0: static cairo_quartz_action_t michael@0: _cairo_quartz_setup_source (cairo_quartz_surface_t *surface, michael@0: - const cairo_pattern_t *source) michael@0: + const cairo_pattern_t *source, michael@0: + cairo_rectangle_int_t *extents) michael@0: { michael@0: assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern)); michael@0: michael@0: surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext); michael@0: CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter)); michael@0: michael@0: if (source->type == CAIRO_PATTERN_TYPE_SOLID) { michael@0: cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source; michael@0: @@ -1367,24 +1421,22 @@ _cairo_quartz_setup_source (cairo_quartz michael@0: solid->color.blue, michael@0: solid->color.alpha); michael@0: michael@0: return DO_SOLID; michael@0: } michael@0: michael@0: if (source->type == CAIRO_PATTERN_TYPE_LINEAR) { michael@0: const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source; michael@0: - return _cairo_quartz_setup_linear_source (surface, lpat); michael@0: - michael@0: + return _cairo_quartz_setup_linear_source (surface, lpat, extents); michael@0: } michael@0: michael@0: if (source->type == CAIRO_PATTERN_TYPE_RADIAL) { michael@0: const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source; michael@0: - return _cairo_quartz_setup_radial_source (surface, rpat); michael@0: - michael@0: + return _cairo_quartz_setup_radial_source (surface, rpat, extents); michael@0: } michael@0: michael@0: if (source->type == CAIRO_PATTERN_TYPE_SURFACE && michael@0: (source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT))) michael@0: { michael@0: const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source; michael@0: cairo_surface_t *pat_surf = spat->surface; michael@0: CGImageRef img; michael@0: @@ -1852,17 +1904,17 @@ _cairo_quartz_surface_paint (void *abstr michael@0: if (IS_EMPTY(surface)) michael@0: return CAIRO_STATUS_SUCCESS; michael@0: michael@0: if (op == CAIRO_OPERATOR_DEST) michael@0: return CAIRO_STATUS_SUCCESS; michael@0: michael@0: CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); michael@0: michael@0: - action = _cairo_quartz_setup_source (surface, source); michael@0: + action = _cairo_quartz_setup_source (surface, source, NULL); michael@0: michael@0: if (action == DO_SOLID || action == DO_PATTERN) { michael@0: CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x, michael@0: surface->extents.y, michael@0: surface->extents.width, michael@0: surface->extents.height)); michael@0: } else if (action == DO_SHADING) { michael@0: CGContextSaveGState (surface->cgContext); michael@0: @@ -1886,16 +1938,35 @@ _cairo_quartz_surface_paint (void *abstr michael@0: } michael@0: michael@0: _cairo_quartz_teardown_source (surface, source); michael@0: michael@0: ND((stderr, "-- paint\n")); michael@0: return rv; michael@0: } michael@0: michael@0: +static cairo_bool_t michael@0: +_cairo_quartz_source_needs_extents (const cairo_pattern_t *source) michael@0: +{ michael@0: + /* For repeating gradients we need to manually extend the gradient and michael@0: + repeat stops, since Quartz doesn't support repeating gradients natively. michael@0: + We need to minimze the number of repeated stops, and since rasterization michael@0: + depends on the number of repetitions we use (even if some of the michael@0: + repetitions go beyond the extents of the object or outside the clip michael@0: + region), it's important to use the same number of repetitions when michael@0: + rendering an object no matter what the clip region is. So the michael@0: + computation of the repetition count cannot depended on the clip region, michael@0: + and should only depend on the object extents, so we need to compute michael@0: + the object extents for repeating gradients. */ michael@0: + return (source->type == CAIRO_PATTERN_TYPE_LINEAR || michael@0: + source->type == CAIRO_PATTERN_TYPE_RADIAL) && michael@0: + (source->extend == CAIRO_EXTEND_REPEAT || michael@0: + source->extend == CAIRO_EXTEND_REFLECT); michael@0: +} michael@0: + michael@0: static cairo_int_status_t michael@0: _cairo_quartz_surface_fill (void *abstract_surface, michael@0: cairo_operator_t op, michael@0: const cairo_pattern_t *source, michael@0: cairo_path_fixed_t *path, michael@0: cairo_fill_rule_t fill_rule, michael@0: double tolerance, michael@0: cairo_antialias_t antialias, michael@0: @@ -1926,17 +1997,27 @@ _cairo_quartz_surface_fill (void *abstra michael@0: return CAIRO_STATUS_SUCCESS; michael@0: } michael@0: michael@0: CGContextSaveGState (surface->cgContext); michael@0: michael@0: CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); michael@0: CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); michael@0: michael@0: - action = _cairo_quartz_setup_source (surface, source); michael@0: + if (_cairo_quartz_source_needs_extents (source)) michael@0: + { michael@0: + /* We don't need precise extents since these are only used to michael@0: + compute the number of gradient reptitions needed to cover the michael@0: + object. */ michael@0: + cairo_rectangle_int_t path_extents; michael@0: + _cairo_path_fixed_approximate_fill_extents (path, &path_extents); michael@0: + action = _cairo_quartz_setup_source (surface, source, &path_extents); michael@0: + } else { michael@0: + action = _cairo_quartz_setup_source (surface, source, NULL); michael@0: + } michael@0: michael@0: CGContextBeginPath (surface->cgContext); michael@0: michael@0: stroke.cgContext = surface->cgContext; michael@0: stroke.ctm_inverse = NULL; michael@0: rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); michael@0: if (rv) michael@0: goto BAIL; michael@0: @@ -2059,17 +2140,24 @@ _cairo_quartz_surface_stroke (void *abst michael@0: michael@0: CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes); michael@0: if (fdash != sdash) michael@0: free (fdash); michael@0: } michael@0: michael@0: CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); michael@0: michael@0: - action = _cairo_quartz_setup_source (surface, source); michael@0: + if (_cairo_quartz_source_needs_extents (source)) michael@0: + { michael@0: + cairo_rectangle_int_t path_extents; michael@0: + _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents); michael@0: + action = _cairo_quartz_setup_source (surface, source, &path_extents); michael@0: + } else { michael@0: + action = _cairo_quartz_setup_source (surface, source, NULL); michael@0: + } michael@0: michael@0: CGContextBeginPath (surface->cgContext); michael@0: michael@0: stroke.cgContext = surface->cgContext; michael@0: stroke.ctm_inverse = ctm_inverse; michael@0: rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); michael@0: if (rv) michael@0: goto BAIL; michael@0: @@ -2180,17 +2268,26 @@ _cairo_quartz_surface_show_glyphs (void michael@0: if (op == CAIRO_OPERATOR_DEST) michael@0: return CAIRO_STATUS_SUCCESS; michael@0: michael@0: if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ) michael@0: return CAIRO_INT_STATUS_UNSUPPORTED; michael@0: michael@0: CGContextSaveGState (surface->cgContext); michael@0: michael@0: - action = _cairo_quartz_setup_source (surface, source); michael@0: + if (_cairo_quartz_source_needs_extents (source)) michael@0: + { michael@0: + cairo_rectangle_int_t glyph_extents; michael@0: + _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs, michael@0: + &glyph_extents); michael@0: + action = _cairo_quartz_setup_source (surface, source, &glyph_extents); michael@0: + } else { michael@0: + action = _cairo_quartz_setup_source (surface, source, NULL); michael@0: + } michael@0: + michael@0: if (action == DO_SOLID || action == DO_PATTERN) { michael@0: CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill); michael@0: } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) { michael@0: CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip); michael@0: isClipping = TRUE; michael@0: } else { michael@0: if (action != DO_NOTHING) michael@0: rv = CAIRO_INT_STATUS_UNSUPPORTED;