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

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 # HG changeset patch
     2 # User Robert O'Callahan <robert@ocallahan.org>
     3 # Date 1249558989 -43200
     4 # Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105
     5 # Parent  963b9451ad305924738d05d997a640698cd3af91
     6 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
     8 diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
     9 --- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
    10 +++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
    11 @@ -710,82 +710,100 @@ CreateGradientFunction (const cairo_grad
    12      return CGFunctionCreate (pat,
    13  			     1,
    14  			     input_value_range,
    15  			     4,
    16  			     gradient_output_value_ranges,
    17  			     &gradient_callbacks);
    18  }
    20 +static void
    21 +UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, CGPoint *start,
    22 +                                     double dx, double dy,
    23 +                                     double x, double y)
    24 +{
    25 +    /* Compute a parameter t such that a line perpendicular to the (dx,dy)
    26 +       vector, passing through (start->x + dx*t, start->y + dy*t), also
    27 +       passes through (x,y).
    28 +
    29 +       Let px = x - start->x, py = y - start->y.
    30 +       t is given by
    31 +         (px - dx*t)*dx + (py - dy*t)*dy = 0
    32 +
    33 +       Solving for t we get
    34 +         numerator = dx*px + dy*py
    35 +         denominator = dx^2 + dy^2
    36 +         t = numerator/denominator
    37 +
    38 +       In CreateRepeatingLinearGradientFunction we know the length of (dx,dy)
    39 +       is not zero. (This is checked in _cairo_quartz_setup_linear_source.)
    40 +    */
    41 +    double px = x - start->x;
    42 +    double py = y - start->y;
    43 +    double numerator = dx*px + dy*py;
    44 +    double denominator = dx*dx + dy*dy;
    45 +    double t = numerator/denominator;
    46 +
    47 +    if (*min_t > t) {
    48 +        *min_t = t;
    49 +    }
    50 +    if (*max_t < t) {
    51 +        *max_t = t;
    52 +    }
    53 +}
    54 +
    55  static CGFunctionRef
    56  CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
    57  				       const cairo_gradient_pattern_t *gpat,
    58  				       CGPoint *start, CGPoint *end,
    59 -				       CGAffineTransform matrix)
    60 +				       cairo_rectangle_int_t *extents)
    61  {
    62      cairo_pattern_t *pat;
    63      float input_value_range[2];
    64 +    double t_min = 0.;
    65 +    double t_max = 0.;
    66 +    double dx = end->x - start->x;
    67 +    double dy = end->y - start->y;
    68 +    double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
    70 -    CGPoint mstart, mend;
    71 +    if (!extents) {
    72 +        extents = &surface->extents;
    73 +    }
    74 +    bounds_x1 = extents->x;
    75 +    bounds_y1 = extents->y;
    76 +    bounds_x2 = extents->x + extents->width;
    77 +    bounds_y2 = extents->y + extents->height;
    78 +    _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
    79 +                                          &bounds_x1, &bounds_y1,
    80 +                                          &bounds_x2, &bounds_y2,
    81 +                                          NULL);
    83 -    double dx, dy;
    84 -    int x_rep_start = 0, x_rep_end = 0;
    85 -    int y_rep_start = 0, y_rep_end = 0;
    86 +    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    87 +                                         bounds_x1, bounds_y1);
    88 +    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    89 +                                         bounds_x2, bounds_y1);
    90 +    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    91 +                                         bounds_x2, bounds_y2);
    92 +    UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
    93 +                                         bounds_x1, bounds_y2);
    95 -    int rep_start, rep_end;
    96 -
    97 -    // figure out how many times we'd need to repeat the gradient pattern
    98 -    // to cover the whole (transformed) surface area
    99 -    mstart = CGPointApplyAffineTransform (*start, matrix);
   100 -    mend = CGPointApplyAffineTransform (*end, matrix);
   101 -
   102 -    dx = fabs (mend.x - mstart.x);
   103 -    dy = fabs (mend.y - mstart.y);
   104 -
   105 -    if (dx > 1e-6) {
   106 -	x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx);
   107 -	x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx);
   108 -
   109 -	if (mend.x < mstart.x) {
   110 -	    int swap = x_rep_end;
   111 -	    x_rep_end = x_rep_start;
   112 -	    x_rep_start = swap;
   113 -	}
   114 -    }
   115 -
   116 -    if (dy > 1e-6) {
   117 -	y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy);
   118 -	y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy);
   119 -
   120 -	if (mend.y < mstart.y) {
   121 -	    int swap = y_rep_end;
   122 -	    y_rep_end = y_rep_start;
   123 -	    y_rep_start = swap;
   124 -	}
   125 -    }
   126 -
   127 -    rep_start = MAX(x_rep_start, y_rep_start);
   128 -    rep_end = MAX(x_rep_end, y_rep_end);
   129 -
   130 -    // extend the line between start and end by rep_start times from the start
   131 -    // and rep_end times from the end
   132 -
   133 -    dx = end->x - start->x;
   134 -    dy = end->y - start->y;
   135 -
   136 -    start->x = start->x - dx * rep_start;
   137 -    start->y = start->y - dy * rep_start;
   138 -
   139 -    end->x = end->x + dx * rep_end;
   140 -    end->y = end->y + dy * rep_end;
   141 +    /* Move t_min and t_max to the nearest usable integer to try to avoid
   142 +       subtle variations due to numerical instability, especially accidentally
   143 +       cutting off a pixel. Extending the gradient repetitions is always safe. */
   144 +    t_min = floor (t_min);
   145 +    t_max = ceil (t_max);
   146 +    end->x = start->x + dx*t_max;
   147 +    end->y = start->y + dy*t_max;
   148 +    start->x = start->x + dx*t_min;
   149 +    start->y = start->y + dy*t_min;
   151      // set the input range for the function -- the function knows how to
   152      // map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT.
   153 -    input_value_range[0] = 0.0 - 1.0 * rep_start;
   154 -    input_value_range[1] = 1.0 + 1.0 * rep_end;
   155 +    input_value_range[0] = t_min;
   156 +    input_value_range[1] = t_max;
   158      if (_cairo_pattern_create_copy (&pat, &gpat->base))
   159  	/* quartz doesn't deal very well with malloc failing, so there's
   160  	 * not much point in us trying either */
   161  	return NULL;
   163      return CGFunctionCreate (pat,
   164  			     1,
   165 @@ -840,35 +858,43 @@ UpdateRadialParameterToIncludePoint(doub
   166      }
   167  }
   169  /* This must only be called when one of the circles properly contains the other */
   170  static CGFunctionRef
   171  CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface,
   172                                         const cairo_gradient_pattern_t *gpat,
   173                                         CGPoint *start, double *start_radius,
   174 -                                       CGPoint *end, double *end_radius)
   175 +                                       CGPoint *end, double *end_radius,
   176 +                                       cairo_rectangle_int_t *extents)
   177  {
   178 -    CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
   179 -    CGAffineTransform transform;
   180      cairo_pattern_t *pat;
   181      float input_value_range[2];
   182      CGPoint *inner;
   183      double *inner_radius;
   184      CGPoint *outer;
   185      double *outer_radius;
   186      /* minimum and maximum t-parameter values that will make our gradient
   187         cover the clipBox */
   188      double t_min, t_max, t_temp;
   189      /* outer minus inner */
   190      double dr, dx, dy;
   191 +    double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
   193 -    _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform);
   194 -    /* clip is in cairo device coordinates; get it into cairo user space */
   195 -    clip = CGRectApplyAffineTransform (clip, transform);
   196 +    if (!extents) {
   197 +        extents = &surface->extents;
   198 +    }
   199 +    bounds_x1 = extents->x;
   200 +    bounds_y1 = extents->y;
   201 +    bounds_x2 = extents->x + extents->width;
   202 +    bounds_y2 = extents->y + extents->height;
   203 +    _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
   204 +                                          &bounds_x1, &bounds_y1,
   205 +                                          &bounds_x2, &bounds_y2,
   206 +                                          NULL);
   208      if (*start_radius < *end_radius) {
   209          /* end circle contains start circle */
   210          inner = start;
   211          outer = end;
   212          inner_radius = start_radius;
   213          outer_radius = end_radius;
   214      } else {
   215 @@ -878,36 +904,37 @@ CreateRepeatingRadialGradientFunction (c
   216          inner_radius = end_radius;
   217          outer_radius = start_radius;
   218      }
   220      dr = *outer_radius - *inner_radius;
   221      dx = outer->x - inner->x;
   222      dy = outer->y - inner->y;
   224 +    /* We can't round or fudge t_min here, it has to be as accurate as possible. */
   225      t_min = -(*inner_radius/dr);
   226      inner->x += t_min*dx;
   227      inner->y += t_min*dy;
   228      *inner_radius = 0.;
   230      t_temp = 0.;
   231      UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   232 -                                        clip.origin.x, clip.origin.y);
   233 +                                        bounds_x1, bounds_y1);
   234      UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   235 -                                        clip.origin.x + clip.size.width, clip.origin.y);
   236 +                                        bounds_x2, bounds_y1);
   237      UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   238 -                                        clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
   239 +                                        bounds_x2, bounds_y2);
   240      UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
   241 -                                        clip.origin.x, clip.origin.y + clip.size.height);
   242 +                                        bounds_x1, bounds_y2);
   243      /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0.
   244         But for the parameter values we use with Quartz, t_min means radius 0.
   245 -       Also, add a small fudge factor to avoid rounding issues. Since the
   246 -       circles are alway expanding and containing the earlier circles, this is
   247 -       OK. */
   248 -    t_temp += 1e-6;
   249 +       Since the circles are alway expanding and contain the earlier circles,
   250 +       it's safe to extend t_max/t_temp as much as we want, so round t_temp up
   251 +       to the nearest integer. This may help us give stable results. */
   252 +    t_temp = ceil (t_temp);
   253      t_max = t_min + t_temp;
   254      outer->x = inner->x + t_temp*dx;
   255      outer->y = inner->y + t_temp*dy;
   256      *outer_radius = t_temp*dr;
   258      /* set the input range for the function -- the function knows how to
   259         map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */
   260      if (*start_radius < *end_radius) {
   261 @@ -1218,33 +1245,57 @@ _cairo_quartz_setup_fallback_source (cai
   262      surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h);
   263      surface->sourceImage = img;
   264      surface->sourceImageSurface = fallback;
   265      surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0);
   267      return DO_IMAGE;
   268  }
   270 +/*
   271 +Quartz does not support repeating radients. We handle repeating gradients
   272 +by manually extending the gradient and repeating color stops. We need to
   273 +minimize the number of repetitions since Quartz seems to sample our color
   274 +function across the entire range, even if part of that range is not needed
   275 +for the visible area of the gradient, and it samples with some fixed resolution,
   276 +so if the gradient range is too large it samples with very low resolution and
   277 +the gradient is very coarse. CreateRepeatingLinearGradientFunction and
   278 +CreateRepeatingRadialGradientFunction compute the number of repetitions needed
   279 +based on the extents of the object (the clip region cannot be used here since
   280 +we don't want the rasterization of the entire gradient to depend on the
   281 +clip region).
   282 +*/
   283  static cairo_quartz_action_t
   284  _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface,
   285 -				   const cairo_linear_pattern_t *lpat)
   286 +				   const cairo_linear_pattern_t *lpat,
   287 +				   cairo_rectangle_int_t *extents)
   288  {
   289      const cairo_pattern_t *abspat = &lpat->base.base;
   290      cairo_matrix_t mat;
   291      CGPoint start, end;
   292      CGFunctionRef gradFunc;
   293      CGColorSpaceRef rgb;
   294      bool extend = abspat->extend == CAIRO_EXTEND_PAD;
   296      if (lpat->base.n_stops == 0) {
   297  	CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
   298  	CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
   299  	return DO_SOLID;
   300      }
   302 +    if (lpat->p1.x == lpat->p2.x &&
   303 +        lpat->p1.y == lpat->p2.y) {
   304 +	/* Quartz handles cases where the vector has no length very
   305 +	 * differently from pixman.
   306 +	 * Whatever the correct behaviour is, let's at least have only pixman's
   307 +	 * implementation to worry about.
   308 +	 */
   309 +	return _cairo_quartz_setup_fallback_source (surface, abspat);
   310 +    }
   311 +
   312      mat = abspat->matrix;
   313      cairo_matrix_invert (&mat);
   314      _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
   316      rgb = CGColorSpaceCreateDeviceRGB();
   318      start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
   319  			 _cairo_fixed_to_double (lpat->p1.y));
   320 @@ -1254,33 +1305,34 @@ _cairo_quartz_setup_linear_source (cairo
   321      if (abspat->extend == CAIRO_EXTEND_NONE ||
   322          abspat->extend == CAIRO_EXTEND_PAD) 
   323      {
   324  	gradFunc = CreateGradientFunction (&lpat->base);
   325      } else {
   326  	gradFunc = CreateRepeatingLinearGradientFunction (surface,
   327  						          &lpat->base,
   328  						          &start, &end,
   329 -						          surface->sourceTransform);
   330 +						          extents);
   331      }
   333      surface->sourceShading = CGShadingCreateAxial (rgb,
   334  						   start, end,
   335  						   gradFunc,
   336  						   extend, extend);
   338      CGColorSpaceRelease(rgb);
   339      CGFunctionRelease(gradFunc);
   341      return DO_SHADING;
   342  }
   344  static cairo_quartz_action_t
   345  _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface,
   346 -				   const cairo_radial_pattern_t *rpat)
   347 +				   const cairo_radial_pattern_t *rpat,
   348 +				   cairo_rectangle_int_t *extents)
   349  {
   350      const cairo_pattern_t *abspat = &rpat->base.base;
   351      cairo_matrix_t mat;
   352      CGPoint start, end;
   353      CGFunctionRef gradFunc;
   354      CGColorSpaceRef rgb;
   355      bool extend = abspat->extend == CAIRO_EXTEND_PAD;
   356      double c1x = _cairo_fixed_to_double (rpat->c1.x);
   357 @@ -1322,17 +1374,18 @@ _cairo_quartz_setup_radial_source (cairo
   358      if (abspat->extend == CAIRO_EXTEND_NONE ||
   359          abspat->extend == CAIRO_EXTEND_PAD)
   360      {
   361  	gradFunc = CreateGradientFunction (&rpat->base);
   362      } else {
   363  	gradFunc = CreateRepeatingRadialGradientFunction (surface,
   364  						          &rpat->base,
   365  						          &start, &r1,
   366 -						          &end, &r2);
   367 +						          &end, &r2,
   368 +						          extents);
   369      }
   371      surface->sourceShading = CGShadingCreateRadial (rgb,
   372  						    start,
   373  						    r1,
   374  						    end,
   375  						    r2,
   376  						    gradFunc,
   377 @@ -1341,17 +1394,18 @@ _cairo_quartz_setup_radial_source (cairo
   378      CGColorSpaceRelease(rgb);
   379      CGFunctionRelease(gradFunc);
   381      return DO_SHADING;
   382  }
   384  static cairo_quartz_action_t
   385  _cairo_quartz_setup_source (cairo_quartz_surface_t *surface,
   386 -			    const cairo_pattern_t *source)
   387 +			    const cairo_pattern_t *source,
   388 +			    cairo_rectangle_int_t *extents)
   389  {
   390      assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern));
   392      surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext);
   393      CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter));
   395      if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
   396  	cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source;
   397 @@ -1367,24 +1421,22 @@ _cairo_quartz_setup_source (cairo_quartz
   398  				  solid->color.blue,
   399  				  solid->color.alpha);
   401  	return DO_SOLID;
   402      }
   404      if (source->type == CAIRO_PATTERN_TYPE_LINEAR) {
   405  	const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source;
   406 -	return _cairo_quartz_setup_linear_source (surface, lpat);
   407 -
   408 +	return _cairo_quartz_setup_linear_source (surface, lpat, extents);
   409      }
   411      if (source->type == CAIRO_PATTERN_TYPE_RADIAL) {
   412  	const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source;
   413 -	return _cairo_quartz_setup_radial_source (surface, rpat);
   414 -
   415 +	return _cairo_quartz_setup_radial_source (surface, rpat, extents);
   416      }
   418      if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
   419  	(source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
   420      {
   421  	const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source;
   422  	cairo_surface_t *pat_surf = spat->surface;
   423  	CGImageRef img;
   424 @@ -1852,17 +1904,17 @@ _cairo_quartz_surface_paint (void *abstr
   425      if (IS_EMPTY(surface))
   426  	return CAIRO_STATUS_SUCCESS;
   428      if (op == CAIRO_OPERATOR_DEST)
   429  	return CAIRO_STATUS_SUCCESS;
   431      CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
   433 -    action = _cairo_quartz_setup_source (surface, source);
   434 +    action = _cairo_quartz_setup_source (surface, source, NULL);
   436      if (action == DO_SOLID || action == DO_PATTERN) {
   437  	CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
   438  							  surface->extents.y,
   439  							  surface->extents.width,
   440  							  surface->extents.height));
   441      } else if (action == DO_SHADING) {
   442  	CGContextSaveGState (surface->cgContext);
   443 @@ -1886,16 +1938,35 @@ _cairo_quartz_surface_paint (void *abstr
   444      }
   446      _cairo_quartz_teardown_source (surface, source);
   448      ND((stderr, "-- paint\n"));
   449      return rv;
   450  }
   452 +static cairo_bool_t
   453 +_cairo_quartz_source_needs_extents (const cairo_pattern_t *source)
   454 +{
   455 +    /* For repeating gradients we need to manually extend the gradient and
   456 +       repeat stops, since Quartz doesn't support repeating gradients natively.
   457 +       We need to minimze the number of repeated stops, and since rasterization
   458 +       depends on the number of repetitions we use (even if some of the
   459 +       repetitions go beyond the extents of the object or outside the clip
   460 +       region), it's important to use the same number of repetitions when
   461 +       rendering an object no matter what the clip region is. So the
   462 +       computation of the repetition count cannot depended on the clip region,
   463 +       and should only depend on the object extents, so we need to compute
   464 +       the object extents for repeating gradients. */
   465 +    return (source->type == CAIRO_PATTERN_TYPE_LINEAR ||
   466 +            source->type == CAIRO_PATTERN_TYPE_RADIAL) &&
   467 +           (source->extend == CAIRO_EXTEND_REPEAT ||
   468 +            source->extend == CAIRO_EXTEND_REFLECT);
   469 +}
   470 +
   471  static cairo_int_status_t
   472  _cairo_quartz_surface_fill (void *abstract_surface,
   473  			     cairo_operator_t op,
   474  			     const cairo_pattern_t *source,
   475  			     cairo_path_fixed_t *path,
   476  			     cairo_fill_rule_t fill_rule,
   477  			     double tolerance,
   478  			     cairo_antialias_t antialias,
   479 @@ -1926,17 +1997,27 @@ _cairo_quartz_surface_fill (void *abstra
   480  	return CAIRO_STATUS_SUCCESS;
   481      }
   483      CGContextSaveGState (surface->cgContext);
   485      CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
   486      CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
   488 -    action = _cairo_quartz_setup_source (surface, source);
   489 +    if (_cairo_quartz_source_needs_extents (source))
   490 +    {
   491 +        /* We don't need precise extents since these are only used to
   492 +           compute the number of gradient reptitions needed to cover the
   493 +           object. */
   494 +        cairo_rectangle_int_t path_extents;
   495 +        _cairo_path_fixed_approximate_fill_extents (path, &path_extents);
   496 +        action = _cairo_quartz_setup_source (surface, source, &path_extents);
   497 +    } else {
   498 +        action = _cairo_quartz_setup_source (surface, source, NULL);
   499 +    }
   501      CGContextBeginPath (surface->cgContext);
   503      stroke.cgContext = surface->cgContext;
   504      stroke.ctm_inverse = NULL;
   505      rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
   506      if (rv)
   507          goto BAIL;
   508 @@ -2059,17 +2140,24 @@ _cairo_quartz_surface_stroke (void *abst
   510  	CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes);
   511  	if (fdash != sdash)
   512  	    free (fdash);
   513      }
   515      CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
   517 -    action = _cairo_quartz_setup_source (surface, source);
   518 +    if (_cairo_quartz_source_needs_extents (source))
   519 +    {
   520 +        cairo_rectangle_int_t path_extents;
   521 +        _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents);
   522 +        action = _cairo_quartz_setup_source (surface, source, &path_extents);
   523 +    } else {
   524 +        action = _cairo_quartz_setup_source (surface, source, NULL);
   525 +    }
   527      CGContextBeginPath (surface->cgContext);
   529      stroke.cgContext = surface->cgContext;
   530      stroke.ctm_inverse = ctm_inverse;
   531      rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
   532      if (rv)
   533  	goto BAIL;
   534 @@ -2180,17 +2268,26 @@ _cairo_quartz_surface_show_glyphs (void 
   535      if (op == CAIRO_OPERATOR_DEST)
   536  	return CAIRO_STATUS_SUCCESS;
   538      if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ)
   539  	return CAIRO_INT_STATUS_UNSUPPORTED;
   541      CGContextSaveGState (surface->cgContext);
   543 -    action = _cairo_quartz_setup_source (surface, source);
   544 +    if (_cairo_quartz_source_needs_extents (source))
   545 +    {
   546 +        cairo_rectangle_int_t glyph_extents;
   547 +        _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs,
   548 +                                                 &glyph_extents);
   549 +        action = _cairo_quartz_setup_source (surface, source, &glyph_extents);
   550 +    } else {
   551 +        action = _cairo_quartz_setup_source (surface, source, NULL);
   552 +    }
   553 +
   554      if (action == DO_SOLID || action == DO_PATTERN) {
   555  	CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill);
   556      } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) {
   557  	CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip);
   558  	isClipping = TRUE;
   559      } else {
   560  	if (action != DO_NOTHING)
   561  	    rv = CAIRO_INT_STATUS_UNSUPPORTED;

mercurial