diff options
Diffstat (limited to 'gfx/cairo/quartz-state.patch')
-rw-r--r-- | gfx/cairo/quartz-state.patch | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/gfx/cairo/quartz-state.patch b/gfx/cairo/quartz-state.patch new file mode 100644 index 000000000..d4c04f706 --- /dev/null +++ b/gfx/cairo/quartz-state.patch @@ -0,0 +1,1190 @@ +diff --git a/gfx/cairo/cairo/src/cairo-quartz-private.h b/gfx/cairo/cairo/src/cairo-quartz-private.h +--- a/gfx/cairo/cairo/src/cairo-quartz-private.h ++++ b/gfx/cairo/cairo/src/cairo-quartz-private.h +@@ -50,30 +50,16 @@ typedef struct cairo_quartz_surface { + CGContextRef cgContext; + CGAffineTransform cgContextBaseCTM; + + void *imageData; + cairo_surface_t *imageSurfaceEquiv; + + cairo_surface_clipper_t clipper; + cairo_rectangle_int_t extents; +- +- /* These are stored while drawing operations are in place, set up +- * by quartz_setup_source() and quartz_finish_source() +- */ +- CGAffineTransform sourceTransform; +- +- CGImageRef sourceImage; +- cairo_surface_t *sourceImageSurface; +- CGRect sourceImageRect; +- +- CGShadingRef sourceShading; +- CGPatternRef sourcePattern; +- +- CGInterpolationQuality oldInterpolationQuality; + } cairo_quartz_surface_t; + + typedef struct cairo_quartz_image_surface { + cairo_surface_t base; + + cairo_rectangle_int_t extents; + + CGImageRef image; +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 +@@ -1333,36 +1333,59 @@ _cairo_quartz_cairo_repeating_surface_pa + return CAIRO_STATUS_SUCCESS; + } + + typedef enum { + DO_SOLID, + DO_SHADING, + DO_PATTERN, + DO_IMAGE, ++ DO_TILED_IMAGE, + DO_UNSUPPORTED, +- DO_NOTHING, +- DO_TILED_IMAGE ++ DO_NOTHING + } cairo_quartz_action_t; + +-static cairo_quartz_action_t ++/* State used during a drawing operation. */ ++typedef struct { ++ CGContextRef context; ++ cairo_quartz_action_t action; ++ ++ // Used with DO_SHADING, DO_IMAGE and DO_TILED_IMAGE ++ CGAffineTransform transform; ++ ++ // Used with DO_IMAGE and DO_TILED_IMAGE ++ CGImageRef image; ++ cairo_surface_t *imageSurface; ++ CGRect imageRect; ++ ++ // Used with DO_SHADING ++ CGShadingRef shading; ++ ++ // Used with DO_PATTERN ++ CGPatternRef pattern; ++} cairo_quartz_drawing_state_t; ++ ++static void + _cairo_quartz_setup_fallback_source (cairo_quartz_surface_t *surface, +- const cairo_pattern_t *source) ++ const cairo_pattern_t *source, ++ cairo_quartz_drawing_state_t *state) + { +- CGRect clipBox = CGContextGetClipBoundingBox (surface->cgContext); ++ CGRect clipBox = CGContextGetClipBoundingBox (state->context); + double x0, y0, w, h; + + cairo_surface_t *fallback; + CGImageRef img; + + cairo_status_t status; + + if (clipBox.size.width == 0.0f || +- clipBox.size.height == 0.0f) +- return DO_NOTHING; ++ clipBox.size.height == 0.0f) { ++ state->action = DO_NOTHING; ++ return; ++ } + + x0 = floor(clipBox.origin.x); + y0 = floor(clipBox.origin.y); + w = ceil(clipBox.origin.x + clipBox.size.width) - x0; + h = ceil(clipBox.origin.y + clipBox.size.height) - y0; + + /* Create a temporary the size of the clip surface, and position + * it so that the device origin coincides with the original surface */ +@@ -1396,73 +1419,79 @@ _cairo_quartz_setup_fallback_source (cai + &fallback->device_transform_inverse); + status = _cairo_surface_paint (fallback, + CAIRO_OPERATOR_SOURCE, + &pattern.base, NULL); + } + #endif + + status = _cairo_surface_to_cgimage (&surface->base, fallback, &img); +- if (status) +- return DO_UNSUPPORTED; +- if (img == NULL) +- return DO_NOTHING; +- +- surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h); +- surface->sourceImage = img; +- surface->sourceImageSurface = fallback; +- surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0); +- +- return DO_IMAGE; ++ if (status) { ++ state->action = DO_UNSUPPORTED; ++ return; ++ } ++ if (img == NULL) { ++ state->action = DO_NOTHING; ++ return; ++ } ++ ++ state->imageRect = CGRectMake (0.0, 0.0, w, h); ++ state->image = img; ++ state->imageSurface = fallback; ++ state->transform = CGAffineTransformMakeTranslation (x0, y0); ++ state->action = 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 ++static void + _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, + const cairo_linear_pattern_t *lpat, +- cairo_rectangle_int_t *extents) ++ cairo_rectangle_int_t *extents, ++ cairo_quartz_drawing_state_t *state) + { + 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; ++ CGContextSetRGBStrokeColor (state->context, 0., 0., 0., 0.); ++ CGContextSetRGBFillColor (state->context, 0., 0., 0., 0.); ++ state->action = DO_SOLID; ++ return; + } + + 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); ++ _cairo_quartz_setup_fallback_source (surface, abspat, state); ++ return; + } + + mat = abspat->matrix; + cairo_matrix_invert (&mat); +- _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); ++ _cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform); + + rgb = CGColorSpaceCreateDeviceRGB(); + + start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x), + _cairo_fixed_to_double (lpat->p1.y)); + end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x), + _cairo_fixed_to_double (lpat->p2.y)); + +@@ -1472,31 +1501,32 @@ _cairo_quartz_setup_linear_source (cairo + gradFunc = CreateGradientFunction (&lpat->base); + } else { + gradFunc = CreateRepeatingLinearGradientFunction (surface, + &lpat->base, + &start, &end, + extents); + } + +- surface->sourceShading = CGShadingCreateAxial (rgb, +- start, end, +- gradFunc, +- extend, extend); ++ state->shading = CGShadingCreateAxial (rgb, ++ start, end, ++ gradFunc, ++ extend, extend); + + CGColorSpaceRelease(rgb); + CGFunctionRelease(gradFunc); + +- return DO_SHADING; ++ state->action = DO_SHADING; + } + +-static cairo_quartz_action_t ++static void + _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, + const cairo_radial_pattern_t *rpat, +- cairo_rectangle_int_t *extents) ++ cairo_rectangle_int_t *extents, ++ cairo_quartz_drawing_state_t *state) + { + 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); +@@ -1505,35 +1535,37 @@ _cairo_quartz_setup_radial_source (cairo + double c2y = _cairo_fixed_to_double (rpat->c2.y); + double r1 = _cairo_fixed_to_double (rpat->r1); + double r2 = _cairo_fixed_to_double (rpat->r2); + double dx = c1x - c2x; + double dy = c1y - c2y; + double centerDistance = sqrt (dx*dx + dy*dy); + + if (rpat->base.n_stops == 0) { +- CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); +- CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); +- return DO_SOLID; ++ CGContextSetRGBStrokeColor (state->context, 0., 0., 0., 0.); ++ CGContextSetRGBFillColor (state->context, 0., 0., 0., 0.); ++ state->action = DO_SOLID; ++ return; + } + + if (r2 <= centerDistance + r1 + 1e-6 && /* circle 2 doesn't contain circle 1 */ + r1 <= centerDistance + r2 + 1e-6) { /* circle 1 doesn't contain circle 2 */ + /* Quartz handles cases where neither circle contains the other very + * differently from pixman. + * Whatever the correct behaviour is, let's at least have only pixman's + * implementation to worry about. + * Note that this also catches the cases where r1 == r2. + */ +- return _cairo_quartz_setup_fallback_source (surface, abspat); ++ _cairo_quartz_setup_fallback_source (surface, abspat, state); ++ return; + } + + mat = abspat->matrix; + cairo_matrix_invert (&mat); +- _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); ++ _cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform); + + rgb = CGColorSpaceCreateDeviceRGB(); + + start = CGPointMake (c1x, c1y); + end = CGPointMake (c2x, c2y); + + if (abspat->extend == CAIRO_EXTEND_NONE || + abspat->extend == CAIRO_EXTEND_PAD) +@@ -1542,111 +1574,146 @@ _cairo_quartz_setup_radial_source (cairo + } else { + gradFunc = CreateRepeatingRadialGradientFunction (surface, + &rpat->base, + &start, &r1, + &end, &r2, + extents); + } + +- surface->sourceShading = CGShadingCreateRadial (rgb, +- start, +- r1, +- end, +- r2, +- gradFunc, +- extend, extend); ++ state->shading = CGShadingCreateRadial (rgb, ++ start, ++ r1, ++ end, ++ r2, ++ gradFunc, ++ extend, extend); + + CGColorSpaceRelease(rgb); + CGFunctionRelease(gradFunc); + +- return DO_SHADING; ++ state->action = DO_SHADING; + } + +-static cairo_quartz_action_t +-_cairo_quartz_setup_source (cairo_quartz_surface_t *surface, +- const cairo_pattern_t *source, +- cairo_rectangle_int_t *extents) ++/** ++ * Sets up internal state to be used to draw the source mask, stored in ++ * cairo_quartz_state_t. Guarantees to call CGContextSaveGState on ++ * surface->cgContext. ++ */ ++static cairo_quartz_drawing_state_t ++_cairo_quartz_setup_state (cairo_quartz_surface_t *surface, ++ const cairo_pattern_t *source, ++ cairo_operator_t op, ++ 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)); ++ CGContextRef context = surface->cgContext; ++ cairo_quartz_drawing_state_t state; ++ cairo_status_t status; ++ ++ state.context = context; ++ state.image = NULL; ++ state.imageSurface = NULL; ++ state.shading = NULL; ++ state.pattern = NULL; ++ ++ // Save before we change the pattern, colorspace, etc. so that ++ // we can restore and make sure that quartz releases our ++ // pattern (which may be stack allocated) ++ CGContextSaveGState(context); ++ ++ CGContextSetInterpolationQuality (context, _cairo_quartz_filter_to_quartz (source->filter)); ++ ++ status = _cairo_quartz_surface_set_cairo_operator (surface, op); ++ if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) { ++ state.action = DO_NOTHING; ++ return state; ++ } ++ if (status) { ++ state.action = DO_UNSUPPORTED; ++ return state; ++ } + + if (source->type == CAIRO_PATTERN_TYPE_SOLID) { + cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source; + +- CGContextSetRGBStrokeColor (surface->cgContext, ++ CGContextSetRGBStrokeColor (context, + solid->color.red, + solid->color.green, + solid->color.blue, + solid->color.alpha); +- CGContextSetRGBFillColor (surface->cgContext, ++ CGContextSetRGBFillColor (context, + solid->color.red, + solid->color.green, + solid->color.blue, + solid->color.alpha); + +- return DO_SOLID; ++ state.action = DO_SOLID; ++ return state; + } + + 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, extents); ++ _cairo_quartz_setup_linear_source (surface, lpat, extents, &state); ++ return state; + } + + 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, extents); ++ _cairo_quartz_setup_radial_source (surface, rpat, extents, &state); ++ return state; + } + + 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; + cairo_matrix_t m = spat->base.matrix; + cairo_rectangle_int_t extents; +- cairo_status_t status; + CGAffineTransform xform; + CGRect srcRect; + cairo_fixed_t fw, fh; + cairo_bool_t is_bounded; + + status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img); +- if (status) +- return DO_UNSUPPORTED; +- if (img == NULL) +- return DO_NOTHING; ++ if (status) { ++ state.action = DO_UNSUPPORTED; ++ return state; ++ } ++ if (img == NULL) { ++ state.action = DO_NOTHING; ++ return state; ++ } + + CGContextSetRGBFillColor (surface->cgContext, 0, 0, 0, 1); + +- surface->sourceImage = img; ++ state.image = img; + + cairo_matrix_invert(&m); +- _cairo_quartz_cairo_matrix_to_quartz (&m, &surface->sourceTransform); ++ _cairo_quartz_cairo_matrix_to_quartz (&m, &state.transform); + + is_bounded = _cairo_surface_get_extents (pat_surf, &extents); + assert (is_bounded); + + if (source->extend == CAIRO_EXTEND_NONE) { +- surface->sourceImageRect = CGRectMake (0, 0, extents.width, extents.height); +- return DO_IMAGE; ++ state.imageRect = CGRectMake (0, 0, extents.width, extents.height); ++ state.action = DO_IMAGE; ++ return state; + } + + /* Quartz seems to tile images at pixel-aligned regions only -- this + * leads to seams if the image doesn't end up scaling to fill the + * space exactly. The CGPattern tiling approach doesn't have this + * problem. Check if we're going to fill up the space (within some + * epsilon), and if not, fall back to the CGPattern type. + */ + +- xform = CGAffineTransformConcat (CGContextGetCTM (surface->cgContext), +- surface->sourceTransform); ++ xform = CGAffineTransformConcat (CGContextGetCTM (context), ++ state.transform); + + srcRect = CGRectMake (0, 0, extents.width, extents.height); + srcRect = CGRectApplyAffineTransform (srcRect, xform); + + fw = _cairo_fixed_from_double (srcRect.size.width); + fh = _cairo_fixed_from_double (srcRect.size.height); + + if ((fw & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON && +@@ -1657,111 +1724,109 @@ _cairo_quartz_setup_source (cairo_quartz + + srcRect.size.width = round(srcRect.size.width); + srcRect.size.height = round(srcRect.size.height); + + xform = CGAffineTransformInvert (xform); + + srcRect = CGRectApplyAffineTransform (srcRect, xform); + +- surface->sourceImageRect = srcRect; +- +- return DO_TILED_IMAGE; ++ state.imageRect = srcRect; ++ state.action = DO_TILED_IMAGE; ++ return state; + } + + /* Fall through to generic SURFACE case */ + } + + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { + CGFloat patternAlpha = 1.0f; + CGColorSpaceRef patternSpace; + CGPatternRef pattern; + cairo_int_status_t status; + + status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, &pattern); +- if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) +- return DO_NOTHING; +- if (status) +- return DO_UNSUPPORTED; +- +- // Save before we change the pattern, colorspace, etc. so that +- // we can restore and make sure that quartz releases our +- // pattern (which may be stack allocated) +- CGContextSaveGState(surface->cgContext); +- +- patternSpace = CGColorSpaceCreatePattern(NULL); +- CGContextSetFillColorSpace (surface->cgContext, patternSpace); +- CGContextSetFillPattern (surface->cgContext, pattern, &patternAlpha); +- CGContextSetStrokeColorSpace (surface->cgContext, patternSpace); +- CGContextSetStrokePattern (surface->cgContext, pattern, &patternAlpha); ++ if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) { ++ state.action = DO_NOTHING; ++ return state; ++ } ++ if (status) { ++ state.action = DO_UNSUPPORTED; ++ return state; ++ } ++ ++ patternSpace = CGColorSpaceCreatePattern (NULL); ++ CGContextSetFillColorSpace (context, patternSpace); ++ CGContextSetFillPattern (context, pattern, &patternAlpha); ++ CGContextSetStrokeColorSpace (context, patternSpace); ++ CGContextSetStrokePattern (context, pattern, &patternAlpha); + CGColorSpaceRelease (patternSpace); + + /* Quartz likes to munge the pattern phase (as yet unexplained + * why); force it to 0,0 as we've already baked in the correct + * pattern translation into the pattern matrix + */ +- CGContextSetPatternPhase (surface->cgContext, CGSizeMake(0,0)); +- +- surface->sourcePattern = pattern; +- +- return DO_PATTERN; ++ CGContextSetPatternPhase (context, CGSizeMake(0,0)); ++ ++ state.pattern = pattern; ++ state.action = DO_PATTERN; ++ return state; + } + +- return DO_UNSUPPORTED; ++ state.action = DO_UNSUPPORTED; ++ return state; + } + ++/** ++ * 1) Tears down internal state used to draw the source ++ * 2) Does CGContextRestoreGState(state->context) ++ */ + static void +-_cairo_quartz_teardown_source (cairo_quartz_surface_t *surface, +- const cairo_pattern_t *source) ++_cairo_quartz_teardown_state (cairo_quartz_drawing_state_t *state) + { +- CGContextSetInterpolationQuality (surface->cgContext, surface->oldInterpolationQuality); +- +- if (surface->sourceImage) { +- CGImageRelease(surface->sourceImage); +- surface->sourceImage = NULL; +- +- cairo_surface_destroy(surface->sourceImageSurface); +- surface->sourceImageSurface = NULL; ++ if (state->image) { ++ CGImageRelease(state->image); + } + +- if (surface->sourceShading) { +- CGShadingRelease(surface->sourceShading); +- surface->sourceShading = NULL; ++ if (state->imageSurface) { ++ cairo_surface_destroy(state->imageSurface); + } + +- if (surface->sourcePattern) { +- CGPatternRelease(surface->sourcePattern); +- // To tear down the pattern and colorspace +- CGContextRestoreGState(surface->cgContext); +- +- surface->sourcePattern = NULL; ++ if (state->shading) { ++ CGShadingRelease(state->shading); + } ++ ++ if (state->pattern) { ++ CGPatternRelease(state->pattern); ++ } ++ ++ CGContextRestoreGState(state->context); + } + + + static void +-_cairo_quartz_draw_image (cairo_quartz_surface_t *surface, cairo_operator_t op, cairo_quartz_action_t action) ++_cairo_quartz_draw_image (cairo_quartz_drawing_state_t *state, cairo_operator_t op) + { +- assert (surface && surface->sourceImage && (action == DO_IMAGE || action == DO_TILED_IMAGE)); +- +- CGContextConcatCTM (surface->cgContext, surface->sourceTransform); +- CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height); +- CGContextScaleCTM (surface->cgContext, 1, -1); +- +- if (action == DO_IMAGE) { +- CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage); +- if (!_cairo_operator_bounded_by_source(op)) { +- CGContextBeginPath (surface->cgContext); +- CGContextAddRect (surface->cgContext, surface->sourceImageRect); +- CGContextAddRect (surface->cgContext, CGContextGetClipBoundingBox (surface->cgContext)); +- CGContextSetRGBFillColor (surface->cgContext, 0, 0, 0, 0); +- CGContextEOFillPath (surface->cgContext); ++ assert (state && state->image && (state->action == DO_IMAGE || state->action == DO_TILED_IMAGE)); ++ ++ CGContextConcatCTM (state->context, state->transform); ++ CGContextTranslateCTM (state->context, 0, state->imageRect.size.height); ++ CGContextScaleCTM (state->context, 1, -1); ++ ++ if (state->action == DO_IMAGE) { ++ CGContextDrawImage (state->context, state->imageRect, state->image); ++ if (!_cairo_operator_bounded_by_source (op)) { ++ CGContextBeginPath (state->context); ++ CGContextAddRect (state->context, state->imageRect); ++ CGContextAddRect (state->context, CGContextGetClipBoundingBox (state->context)); ++ CGContextSetRGBFillColor (state->context, 0, 0, 0, 0); ++ CGContextEOFillPath (state->context); + } + } else +- CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage); ++ CGContextDrawTiledImagePtr (state->context, state->imageRect, state->image); + } + + + /* + * get source/dest image implementation + */ + + /* Read the image from the surface's front buffer */ +@@ -2098,52 +2163,44 @@ _cairo_quartz_surface_get_extents (void + static cairo_int_status_t + _cairo_quartz_surface_paint (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_clip_t *clip) + { + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; +- cairo_quartz_action_t action; ++ cairo_quartz_drawing_state_t state; + + ND((stderr, "%p _cairo_quartz_surface_paint op %d source->type %d\n", surface, op, source->type)); + + if (IS_EMPTY(surface)) + return CAIRO_STATUS_SUCCESS; + + rv = _cairo_surface_clipper_set_clip (&surface->clipper, clip); + if (unlikely (rv)) + return rv; + +- rv = _cairo_quartz_surface_set_cairo_operator (surface, op); +- if (unlikely (rv)) +- return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; +- +- 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); +- CGContextConcatCTM (surface->cgContext, surface->sourceTransform); +- CGContextDrawShading (surface->cgContext, surface->sourceShading); +- CGContextRestoreGState (surface->cgContext); +- } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { +- CGContextSaveGState (surface->cgContext); +- _cairo_quartz_draw_image (surface, op, action); +- CGContextRestoreGState (surface->cgContext); +- } else if (action != DO_NOTHING) { ++ state = _cairo_quartz_setup_state (surface, source, op, NULL); ++ ++ if (state.action == DO_SOLID || state.action == DO_PATTERN) { ++ CGContextFillRect (state.context, CGRectMake(surface->extents.x, ++ surface->extents.y, ++ surface->extents.width, ++ surface->extents.height)); ++ } else if (state.action == DO_SHADING) { ++ CGContextConcatCTM (state.context, state.transform); ++ CGContextDrawShading (state.context, state.shading); ++ } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE) { ++ _cairo_quartz_draw_image (&state, op); ++ } else if (state.action != DO_NOTHING) { + rv = CAIRO_INT_STATUS_UNSUPPORTED; + } + +- _cairo_quartz_teardown_source (surface, source); ++ _cairo_quartz_teardown_state (&state); + + ND((stderr, "-- paint\n")); + return rv; + } + + static cairo_bool_t + _cairo_quartz_source_needs_extents (const cairo_pattern_t *source) + { +@@ -2170,91 +2227,83 @@ _cairo_quartz_surface_fill (void *abstra + cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + cairo_antialias_t antialias, + cairo_clip_t *clip) + { + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; +- cairo_quartz_action_t action; ++ cairo_quartz_drawing_state_t state; + quartz_stroke_t stroke; + CGPathRef path_for_unbounded = NULL; + + ND((stderr, "%p _cairo_quartz_surface_fill op %d source->type %d\n", surface, op, source->type)); + + if (IS_EMPTY(surface)) + return CAIRO_STATUS_SUCCESS; + + rv = _cairo_surface_clipper_set_clip (&surface->clipper, clip); + if (unlikely (rv)) + return rv; + +- rv = _cairo_quartz_surface_set_cairo_operator (surface, op); +- if (unlikely (rv)) +- return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; +- +- CGContextSaveGState (surface->cgContext); +- +- CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); +- + 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); ++ state = _cairo_quartz_setup_state (surface, source, op, &path_extents); + } else { +- action = _cairo_quartz_setup_source (surface, source, NULL); ++ state = _cairo_quartz_setup_state (surface, source, op, NULL); + } + +- CGContextBeginPath (surface->cgContext); +- +- stroke.cgContext = surface->cgContext; ++ CGContextSetShouldAntialias (state.context, (antialias != CAIRO_ANTIALIAS_NONE)); ++ ++ CGContextBeginPath (state.context); ++ ++ stroke.cgContext = state.context; + stroke.ctm_inverse = NULL; + rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); + if (rv) + goto BAIL; + + if (!_cairo_operator_bounded_by_mask(op) && CGContextCopyPathPtr) +- path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); +- +- if (action == DO_SOLID || action == DO_PATTERN) { ++ path_for_unbounded = CGContextCopyPathPtr (state.context); ++ ++ if (state.action == DO_SOLID || state.action == DO_PATTERN) { + if (fill_rule == CAIRO_FILL_RULE_WINDING) +- CGContextFillPath (surface->cgContext); ++ CGContextFillPath (state.context); + else +- CGContextEOFillPath (surface->cgContext); +- } else if (action == DO_SHADING) { ++ CGContextEOFillPath (state.context); ++ } else if (state.action == DO_SHADING) { + + // we have to clip and then paint the shading; we can't fill + // with the shading + if (fill_rule == CAIRO_FILL_RULE_WINDING) +- CGContextClip (surface->cgContext); ++ CGContextClip (state.context); + else +- CGContextEOClip (surface->cgContext); +- +- CGContextConcatCTM (surface->cgContext, surface->sourceTransform); +- CGContextDrawShading (surface->cgContext, surface->sourceShading); +- } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { ++ CGContextEOClip (state.context); ++ ++ CGContextConcatCTM (state.context, state.transform); ++ CGContextDrawShading (state.context, state.shading); ++ } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE) { + if (fill_rule == CAIRO_FILL_RULE_WINDING) +- CGContextClip (surface->cgContext); ++ CGContextClip (state.context); + else +- CGContextEOClip (surface->cgContext); +- +- _cairo_quartz_draw_image (surface, op, action); +- } else if (action != DO_NOTHING) { ++ CGContextEOClip (state.context); ++ ++ _cairo_quartz_draw_image (&state, op); ++ } else if (state.action != DO_NOTHING) { + rv = CAIRO_INT_STATUS_UNSUPPORTED; + } + + BAIL: +- _cairo_quartz_teardown_source (surface, source); +- +- CGContextRestoreGState (surface->cgContext); ++ _cairo_quartz_teardown_state (&state); + + if (path_for_unbounded) { + unbounded_op_data_t ub; + ub.op = UNBOUNDED_STROKE_FILL; + ub.u.stroke_fill.cgPath = path_for_unbounded; + ub.u.stroke_fill.fill_rule = fill_rule; + + _cairo_quartz_fixup_unbounded_operation (surface, &ub, antialias); +@@ -2274,44 +2323,49 @@ _cairo_quartz_surface_stroke (void *abst + cairo_matrix_t *ctm, + cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_antialias_t antialias, + cairo_clip_t *clip) + { + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; +- cairo_quartz_action_t action; ++ cairo_quartz_drawing_state_t state; + quartz_stroke_t stroke; + CGAffineTransform origCTM, strokeTransform; + CGPathRef path_for_unbounded = NULL; + + ND((stderr, "%p _cairo_quartz_surface_stroke op %d source->type %d\n", surface, op, source->type)); + + if (IS_EMPTY(surface)) + return CAIRO_STATUS_SUCCESS; + + rv = _cairo_surface_clipper_set_clip (&surface->clipper, clip); + if (unlikely (rv)) + return rv; + +- rv = _cairo_quartz_surface_set_cairo_operator (surface, op); +- if (unlikely (rv)) +- return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; ++ if (_cairo_quartz_source_needs_extents (source)) ++ { ++ cairo_rectangle_int_t path_extents; ++ _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents); ++ state = _cairo_quartz_setup_state (surface, source, op, &path_extents); ++ } else { ++ state = _cairo_quartz_setup_state (surface, source, op, NULL); ++ } + + // Turning antialiasing off used to cause misrendering with + // single-pixel lines (e.g. 20,10.5 -> 21,10.5 end up being rendered as 2 pixels). + // That's been since fixed in at least 10.5, and in the latest 10.4 dot releases. +- CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); +- CGContextSetLineWidth (surface->cgContext, style->line_width); +- CGContextSetLineCap (surface->cgContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap)); +- CGContextSetLineJoin (surface->cgContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join)); +- CGContextSetMiterLimit (surface->cgContext, style->miter_limit); +- +- origCTM = CGContextGetCTM (surface->cgContext); ++ CGContextSetShouldAntialias (state.context, (antialias != CAIRO_ANTIALIAS_NONE)); ++ CGContextSetLineWidth (state.context, style->line_width); ++ CGContextSetLineCap (state.context, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap)); ++ CGContextSetLineJoin (state.context, _cairo_quartz_cairo_line_join_to_quartz (style->line_join)); ++ CGContextSetMiterLimit (state.context, style->miter_limit); ++ ++ origCTM = CGContextGetCTM (state.context); + + if (style->dash && style->num_dashes) { + #define STATIC_DASH 32 + CGFloat sdash[STATIC_DASH]; + CGFloat *fdash = sdash; + double offset = style->dash_offset; + unsigned int max_dashes = style->num_dashes; + unsigned int k; +@@ -2330,90 +2384,75 @@ _cairo_quartz_surface_stroke (void *abst + if (max_dashes > STATIC_DASH) + fdash = _cairo_malloc_ab (max_dashes, sizeof (CGFloat)); + if (fdash == NULL) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + for (k = 0; k < max_dashes; k++) + fdash[k] = (CGFloat) style->dash[k % style->num_dashes]; + } +- CGContextSetLineDash (surface->cgContext, offset, fdash, max_dashes); ++ CGContextSetLineDash (state.context, offset, fdash, max_dashes); + if (fdash != sdash) + free (fdash); + } else +- CGContextSetLineDash (surface->cgContext, 0, NULL, 0); +- +- CGContextSaveGState (surface->cgContext); +- +- +- 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); +- } ++ CGContextSetLineDash (state.context, 0, NULL, 0); + + _cairo_quartz_cairo_matrix_to_quartz (ctm, &strokeTransform); +- CGContextConcatCTM (surface->cgContext, strokeTransform); +- +- CGContextBeginPath (surface->cgContext); +- +- stroke.cgContext = surface->cgContext; ++ CGContextConcatCTM (state.context, strokeTransform); ++ ++ CGContextBeginPath (state.context); ++ ++ stroke.cgContext = state.context; + stroke.ctm_inverse = ctm_inverse; + rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); + if (rv) + goto BAIL; + + if (!_cairo_operator_bounded_by_mask (op) && CGContextCopyPathPtr) +- path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); +- +- if (action == DO_SOLID || action == DO_PATTERN) { +- CGContextStrokePath (surface->cgContext); +- } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { +- CGContextReplacePathWithStrokedPath (surface->cgContext); +- CGContextClip (surface->cgContext); +- +- CGContextSetCTM (surface->cgContext, origCTM); +- _cairo_quartz_draw_image (surface, op, action); +- } else if (action == DO_SHADING) { +- CGContextReplacePathWithStrokedPath (surface->cgContext); +- CGContextClip (surface->cgContext); +- +- CGContextSetCTM (surface->cgContext, origCTM); +- +- CGContextConcatCTM (surface->cgContext, surface->sourceTransform); +- CGContextDrawShading (surface->cgContext, surface->sourceShading); +- } else if (action != DO_NOTHING) { ++ path_for_unbounded = CGContextCopyPathPtr (state.context); ++ ++ if (state.action == DO_SOLID || state.action == DO_PATTERN) { ++ CGContextStrokePath (state.context); ++ } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE) { ++ CGContextReplacePathWithStrokedPath (state.context); ++ CGContextClip (state.context); ++ ++ CGContextSetCTM (state.context, origCTM); ++ _cairo_quartz_draw_image (&state, op); ++ } else if (state.action == DO_SHADING) { ++ CGContextReplacePathWithStrokedPath (state.context); ++ CGContextClip (state.context); ++ ++ CGContextSetCTM (state.context, origCTM); ++ ++ CGContextConcatCTM (state.context, state.transform); ++ CGContextDrawShading (state.context, state.shading); ++ } else if (state.action != DO_NOTHING) { + rv = CAIRO_INT_STATUS_UNSUPPORTED; ++ goto BAIL; + } + ++ if (path_for_unbounded) { ++ CGContextSetCTM (state.context, origCTM); ++ CGContextConcatCTM (state.context, strokeTransform); ++ ++ CGContextBeginPath (state.context); ++ CGContextAddPath (state.context, path_for_unbounded); ++ CGPathRelease (path_for_unbounded); ++ ++ CGContextReplacePathWithStrokedPath (state.context); ++ ++ CGContextAddRect (state.context, CGContextGetClipBoundingBox (state.context)); ++ ++ CGContextSetRGBFillColor (state.context, 0., 0., 0., 0.); ++ CGContextEOFillPath (state.context); ++ } ++ + BAIL: +- _cairo_quartz_teardown_source (surface, source); +- +- CGContextRestoreGState (surface->cgContext); +- +- if (path_for_unbounded) { +- CGContextSaveGState (surface->cgContext); +- CGContextConcatCTM (surface->cgContext, strokeTransform); +- +- CGContextBeginPath (surface->cgContext); +- CGContextAddPath (surface->cgContext, path_for_unbounded); +- CGPathRelease (path_for_unbounded); +- +- CGContextReplacePathWithStrokedPath (surface->cgContext); +- +- CGContextAddRect (surface->cgContext, CGContextGetClipBoundingBox (surface->cgContext)); +- +- CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); +- CGContextEOFillPath (surface->cgContext); +- +- CGContextRestoreGState (surface->cgContext); +- } ++ _cairo_quartz_teardown_state (&state); + + ND((stderr, "-- stroke\n")); + return rv; + } + + #if CAIRO_HAS_QUARTZ_FONT + static cairo_int_status_t + _cairo_quartz_surface_show_glyphs (void *abstract_surface, +@@ -2429,17 +2468,17 @@ _cairo_quartz_surface_show_glyphs (void + #define STATIC_BUF_SIZE 64 + CGGlyph glyphs_static[STATIC_BUF_SIZE]; + CGSize cg_advances_static[STATIC_BUF_SIZE]; + CGGlyph *cg_glyphs = &glyphs_static[0]; + CGSize *cg_advances = &cg_advances_static[0]; + + cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; +- cairo_quartz_action_t action; ++ cairo_quartz_drawing_state_t state; + float xprev, yprev; + int i; + CGFontRef cgfref = NULL; + + cairo_bool_t isClipping = FALSE; + cairo_bool_t didForceFontSmoothing = FALSE; + + if (IS_EMPTY(surface)) +@@ -2450,65 +2489,59 @@ _cairo_quartz_surface_show_glyphs (void + + if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ) + return CAIRO_INT_STATUS_UNSUPPORTED; + + rv = _cairo_surface_clipper_set_clip (&surface->clipper, clip); + if (unlikely (rv)) + return rv; + +- rv = _cairo_quartz_surface_set_cairo_operator (surface, op); +- if (unlikely (rv)) +- return rv == CAIRO_INT_STATUS_NOTHING_TO_DO ? CAIRO_STATUS_SUCCESS : rv; +- +- CGContextSaveGState (surface->cgContext); +- + 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, NULL); +- action = _cairo_quartz_setup_source (surface, source, &glyph_extents); ++ state = _cairo_quartz_setup_state (surface, source, op, &glyph_extents); + } else { +- action = _cairo_quartz_setup_source (surface, source, NULL); ++ state = _cairo_quartz_setup_state (surface, source, op, 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); ++ if (state.action == DO_SOLID || state.action == DO_PATTERN) { ++ CGContextSetTextDrawingMode (state.context, kCGTextFill); ++ } else if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE || state.action == DO_SHADING) { ++ CGContextSetTextDrawingMode (state.context, kCGTextClip); + isClipping = TRUE; + } else { +- if (action != DO_NOTHING) ++ if (state.action != DO_NOTHING) + rv = CAIRO_INT_STATUS_UNSUPPORTED; + goto BAIL; + } + + /* this doesn't addref */ + cgfref = _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font); +- CGContextSetFont (surface->cgContext, cgfref); +- CGContextSetFontSize (surface->cgContext, 1.0); ++ CGContextSetFont (state.context, cgfref); ++ CGContextSetFontSize (state.context, 1.0); + + switch (scaled_font->options.antialias) { + case CAIRO_ANTIALIAS_SUBPIXEL: +- CGContextSetShouldAntialias (surface->cgContext, TRUE); +- CGContextSetShouldSmoothFonts (surface->cgContext, TRUE); ++ CGContextSetShouldAntialias (state.context, TRUE); ++ CGContextSetShouldSmoothFonts (state.context, TRUE); + if (CGContextSetAllowsFontSmoothingPtr && +- !CGContextGetAllowsFontSmoothingPtr (surface->cgContext)) ++ !CGContextGetAllowsFontSmoothingPtr (state.context)) + { + didForceFontSmoothing = TRUE; +- CGContextSetAllowsFontSmoothingPtr (surface->cgContext, TRUE); ++ CGContextSetAllowsFontSmoothingPtr (state.context, TRUE); + } + break; + case CAIRO_ANTIALIAS_NONE: +- CGContextSetShouldAntialias (surface->cgContext, FALSE); ++ CGContextSetShouldAntialias (state.context, FALSE); + break; + case CAIRO_ANTIALIAS_GRAY: +- CGContextSetShouldAntialias (surface->cgContext, TRUE); +- CGContextSetShouldSmoothFonts (surface->cgContext, FALSE); ++ CGContextSetShouldAntialias (state.context, TRUE); ++ CGContextSetShouldSmoothFonts (state.context, FALSE); + break; + case CAIRO_ANTIALIAS_DEFAULT: + /* Don't do anything */ + break; + } + + if (num_glyphs > STATIC_BUF_SIZE) { + cg_glyphs = (CGGlyph*) _cairo_malloc_ab (num_glyphs, sizeof(CGGlyph)); +@@ -2532,17 +2565,17 @@ _cairo_quartz_surface_show_glyphs (void + textTransform = CGAffineTransformScale (textTransform, 1.0, -1.0); + textTransform = CGAffineTransformConcat (CGAffineTransformMake(scaled_font->ctm.xx, + -scaled_font->ctm.yx, + -scaled_font->ctm.xy, + scaled_font->ctm.yy, + 0., 0.), + textTransform); + +- CGContextSetTextMatrix (surface->cgContext, textTransform); ++ CGContextSetTextMatrix (state.context, textTransform); + + /* Convert our glyph positions to glyph advances. We need n-1 advances, + * since the advance at index 0 is applied after glyph 0. */ + xprev = glyphs[0].x; + yprev = glyphs[0].y; + + cg_glyphs[0] = glyphs[0].index; + +@@ -2569,40 +2602,38 @@ _cairo_quartz_surface_show_glyphs (void + + #if 0 + for (i = 0; i < num_glyphs; i++) { + ND((stderr, "[%d: %d %f,%f]\n", i, cg_glyphs[i], cg_advances[i].width, cg_advances[i].height)); + } + #endif + + /* Translate to the first glyph's position before drawing */ +- ctm = CGContextGetCTM (surface->cgContext); +- CGContextTranslateCTM (surface->cgContext, glyphs[0].x, glyphs[0].y); +- +- CGContextShowGlyphsWithAdvances (surface->cgContext, ++ ctm = CGContextGetCTM (state.context); ++ CGContextTranslateCTM (state.context, glyphs[0].x, glyphs[0].y); ++ ++ CGContextShowGlyphsWithAdvances (state.context, + cg_glyphs, + cg_advances, + num_glyphs); + +- CGContextSetCTM (surface->cgContext, ctm); +- +- if (action == DO_IMAGE || action == DO_TILED_IMAGE) { +- _cairo_quartz_draw_image (surface, op, action); +- } else if (action == DO_SHADING) { +- CGContextConcatCTM (surface->cgContext, surface->sourceTransform); +- CGContextDrawShading (surface->cgContext, surface->sourceShading); ++ CGContextSetCTM (state.context, ctm); ++ ++ if (state.action == DO_IMAGE || state.action == DO_TILED_IMAGE) { ++ _cairo_quartz_draw_image (&state, op); ++ } else if (state.action == DO_SHADING) { ++ CGContextConcatCTM (state.context, state.transform); ++ CGContextDrawShading (state.context, state.shading); + } + + BAIL: +- _cairo_quartz_teardown_source (surface, source); +- + if (didForceFontSmoothing) +- CGContextSetAllowsFontSmoothingPtr (surface->cgContext, FALSE); +- +- CGContextRestoreGState (surface->cgContext); ++ CGContextSetAllowsFontSmoothingPtr (state.context, FALSE); ++ ++ _cairo_quartz_teardown_state (&state); + + if (rv == CAIRO_STATUS_SUCCESS && + cgfref && + !_cairo_operator_bounded_by_mask (op)) + { + unbounded_op_data_t ub; + ub.op = UNBOUNDED_SHOW_GLYPHS; + |