summaryrefslogtreecommitdiffstats
path: root/gfx/cairo/quartz-minimize-gradient-repeat.patch
blob: 9782bef11eb9cb85e97912bc4f364c8c50b2f53f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# HG changeset patch
# User Robert O'Callahan <robert@ocallahan.org>
# Date 1249558989 -43200
# Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105
# Parent  963b9451ad305924738d05d997a640698cd3af91
Bug 508730. Don't repeat a Quartz gradient more times than necessary, to avoid Quartz quality problems when there are lots of repeated color stops. r=jmuizelaar

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