/* Handles compositing with a clip surface when we have to do the operation
 * in two pieces and combine them together.
 */
static cairo_status_t
clip_and_composite_combine (const cairo_composite_rectangles_t*extents,
			    draw_func_t			 draw_func,
			    void			*draw_closure)
{
    cairo_image_surface_t *dst = (cairo_image_surface_t *)extents->surface;
    cairo_image_surface_t *tmp, *clip;
    int clip_x, clip_y;
    cairo_status_t status;

    tmp = (cairo_image_surface_t *)
	_cairo_surface_create_similar_solid (&dst->base, dst->base.content,
					     extents->bounded.width,
					     extents->bounded.height,
					     CAIRO_COLOR_TRANSPARENT);
    if (unlikely (tmp->base.status))
	return tmp->base.status;

    pixman_image_composite32 (PIXMAN_OP_SRC,
			      dst->pixman_image, NULL, tmp->pixman_image,
			      extents->bounded.x,      extents->bounded.y,
			      0, 0,
			      0, 0,
			      extents->bounded.width,  extents->bounded.height);

    status = draw_func (tmp, draw_closure,
			extents->op, &extents->source_pattern.base,
			extents->bounded.x, extents->bounded.y,
			&extents->bounded);
    if (unlikely (status))
	goto error;

    clip = (cairo_image_surface_t *)
	get_clip_surface (dst, extents, &clip_x, &clip_y);
    if (unlikely (clip->base.status))
	goto error;

    pixman_image_composite32 (PIXMAN_OP_OUT_REVERSE,
			      clip->pixman_image, NULL, dst->pixman_image,
			      extents->bounded.x - clip_x, extents->bounded.y - clip_y,
			      0,      0,
			      extents->bounded.x, extents->bounded.y,
			      extents->bounded.width, extents->bounded.height);
    pixman_image_composite32 (PIXMAN_OP_ADD,
			      tmp->pixman_image, clip->pixman_image, dst->pixman_image,
			      0,  0,
			      extents->bounded.x - clip_x, extents->bounded.y - clip_y,
			      extents->bounded.x, extents->bounded.y,
			      extents->bounded.width, extents->bounded.height);

    cairo_surface_destroy (&clip->base);

 error:
    cairo_surface_destroy (&tmp->base);

    return status;
}
cairo_surface_t *
_cairo_surface_wrapper_create_similar (cairo_surface_wrapper_t *wrapper,
				       cairo_content_t	content,
				       int		width,
				       int		height)
{
    return _cairo_surface_create_similar_solid (wrapper->target,
						content, width, height,
						CAIRO_COLOR_TRANSPARENT,
						TRUE);
}
static cairo_image_surface_t *
create_composite_mask (cairo_image_surface_t	*dst,
		       void			*draw_closure,
		       draw_func_t		 draw_func,
		       const cairo_composite_rectangles_t *extents)
{
    cairo_image_surface_t *surface;
    cairo_int_status_t status;

    surface = (cairo_image_surface_t *)
	_cairo_surface_create_similar_solid (&dst->base,
					     CAIRO_CONTENT_ALPHA,
					     extents->bounded.width,
					     extents->bounded.height,
					     CAIRO_COLOR_TRANSPARENT);
    if (unlikely (surface->base.status))
	return surface;

    status = draw_func (surface, draw_closure,
			CAIRO_OPERATOR_ADD, &_cairo_pattern_white.base,
			extents->bounded.x, extents->bounded.y,
			&extents->bounded);
    if (unlikely (status))
	goto error;

    if (extents->clip->boxes) {
	status = combine_in_boxes (surface,
				   extents->clip->boxes,
				   extents->clip->num_boxes,
				   &extents->bounded);
	if (unlikely (status))
	    goto error;
    }

    if (extents->clip->path) {
	status = _cairo_clip_combine_with_surface (extents->clip,
						   &surface->base,
						   extents->bounded.x,
						   extents->bounded.y);
	if (unlikely (status))
	    goto error;
    }

    return surface;

error:
    cairo_surface_destroy (&surface->base);
    return (cairo_image_surface_t *)_cairo_surface_create_in_error (status);
}
static cairo_image_surface_t *
get_clip_surface (cairo_image_surface_t	*dst,
		  const cairo_composite_rectangles_t *extents,
		  int *clip_x,
		  int *clip_y)
{
    cairo_image_surface_t *surface;
    cairo_int_status_t status;

    surface = (cairo_image_surface_t *)
	_cairo_surface_create_similar_solid (&dst->base,
					     CAIRO_CONTENT_ALPHA,
					     extents->unbounded.width,
					     extents->unbounded.height,
					     CAIRO_COLOR_WHITE);
    if (unlikely (surface->base.status))
	return surface;

    if (extents->clip->boxes) {
	status = combine_in_boxes (surface,
				   extents->clip->boxes,
				   extents->clip->num_boxes,
				   &extents->unbounded);
	if (unlikely (status))
	    goto error;
    }

    if (extents->clip->path) {
	status = _cairo_clip_combine_with_surface (extents->clip,
						   &surface->base,
						   extents->unbounded.x,
						   extents->unbounded.y);
	if (unlikely (status))
	    goto error;
    }

    *clip_x = extents->unbounded.x;
    *clip_y = extents->unbounded.y;
    return surface;

error:
    cairo_surface_destroy (&surface->base);
    return (cairo_image_surface_t *)_cairo_surface_create_in_error (status);
}
Esempio n. 5
0
static cairo_status_t
_cairo_clip_intersect_mask (cairo_clip_t      *clip,
			    cairo_traps_t     *traps,
			    cairo_antialias_t antialias,
			    cairo_surface_t   *target)
{
    cairo_pattern_union_t pattern;
    cairo_box_t extents;
    cairo_rectangle_int16_t surface_rect, target_rect;
    cairo_surface_t *surface;
    cairo_status_t status;

    /* Represent the clip as a mask surface.  We create a new surface
     * the size of the intersection of the old mask surface and the
     * extents of the new clip path. */

    _cairo_traps_extents (traps, &extents);
    _cairo_box_round_to_rectangle (&extents, &surface_rect);

    if (clip->surface != NULL)
	_cairo_rectangle_intersect (&surface_rect, &clip->surface_rect);

    /* Intersect with the target surface rectangle so we don't use
     * more memory and time than we need to. */

    status = _cairo_surface_get_extents (target, &target_rect);
    if (!status)
	_cairo_rectangle_intersect (&surface_rect, &target_rect);

    surface = _cairo_surface_create_similar_solid (target,
						   CAIRO_CONTENT_ALPHA,
						   surface_rect.width,
						   surface_rect.height,
						   CAIRO_COLOR_WHITE);
    if (surface->status)
	return CAIRO_STATUS_NO_MEMORY;

    /* Render the new clipping path into the new mask surface. */

    _cairo_traps_translate (traps, -surface_rect.x, -surface_rect.y);
    _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE);

    status = _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_IN,
						  &pattern.base,
						  surface,
						  antialias,
						  0, 0,
						  0, 0,
						  surface_rect.width,
						  surface_rect.height,
						  traps->traps,
						  traps->num_traps);

    _cairo_pattern_fini (&pattern.base);

    if (status) {
	cairo_surface_destroy (surface);
	return status;
    }

    /* If there was a clip surface already, combine it with the new
     * mask surface using the IN operator, so we get the intersection
     * of the old and new clipping paths. */

    if (clip->surface != NULL) {
	_cairo_pattern_init_for_surface (&pattern.surface, clip->surface);

	status = _cairo_surface_composite (CAIRO_OPERATOR_IN,
					   &pattern.base,
					   NULL,
					   surface,
					   surface_rect.x - clip->surface_rect.x,
					   surface_rect.y - clip->surface_rect.y,
					   0, 0,
					   0, 0,
					   surface_rect.width,
					   surface_rect.height);

	_cairo_pattern_fini (&pattern.base);

	if (status) {
	    cairo_surface_destroy (surface);
	    return status;
	}

	cairo_surface_destroy (clip->surface);
    }

    clip->surface = surface;
    clip->surface_rect = surface_rect;
    clip->serial = _cairo_surface_allocate_clip_serial (target);

    return status;
}
static cairo_status_t
_cairo_default_context_push_group (void *abstract_cr, cairo_content_t content)
{
    cairo_default_context_t *cr = abstract_cr;
    cairo_surface_t *group_surface;
    cairo_clip_t *clip;
    cairo_status_t status;

    clip = _cairo_gstate_get_clip (cr->gstate);
    if (_cairo_clip_is_all_clipped (clip)) {
        group_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
        status = group_surface->status;
        if (unlikely (status))
            goto bail;
    } else {
        cairo_surface_t *parent_surface;
        cairo_rectangle_int_t extents;
        cairo_bool_t bounded, is_empty;

        parent_surface = _cairo_gstate_get_target (cr->gstate);

        if (unlikely (parent_surface->status))
            return parent_surface->status;
        if (unlikely (parent_surface->finished))
            return _cairo_error (CAIRO_STATUS_SURFACE_FINISHED);

        /* Get the extents that we'll use in creating our new group surface */
        bounded = _cairo_surface_get_extents (parent_surface, &extents);
        if (clip)
            /* XXX: This assignment just fixes a compiler warning? */
            is_empty = _cairo_rectangle_intersect (&extents,
                                                   _cairo_clip_get_extents (clip));

        if (!bounded) {
            /* XXX: Generic solution? */
            group_surface = cairo_recording_surface_create (content, NULL);
            extents.x = extents.y = 0;
        } else {
            group_surface = _cairo_surface_create_similar_solid (parent_surface,
                            content,
                            extents.width,
                            extents.height,
                            CAIRO_COLOR_TRANSPARENT);
        }
        status = group_surface->status;
        if (unlikely (status))
            goto bail;

        /* Set device offsets on the new surface so that logically it appears at
         * the same location on the parent surface -- when we pop_group this,
         * the source pattern will get fixed up for the appropriate target surface
         * device offsets, so we want to set our own surface offsets from /that/,
         * and not from the device origin. */
        cairo_surface_set_device_offset (group_surface,
                                         parent_surface->device_transform.x0 - extents.x,
                                         parent_surface->device_transform.y0 - extents.y);

        /* If we have a current path, we need to adjust it to compensate for
         * the device offset just applied. */
        _cairo_path_fixed_translate (cr->path,
                                     _cairo_fixed_from_int (-extents.x),
                                     _cairo_fixed_from_int (-extents.y));
    }

    /* create a new gstate for the redirect */
    status = _cairo_gstate_save (&cr->gstate, &cr->gstate_freelist);
    if (unlikely (status))
        goto bail;

    status = _cairo_gstate_redirect_target (cr->gstate, group_surface);

bail:
    cairo_surface_destroy (group_surface);
    return status;
}
Esempio n. 7
0
static cairo_status_t
_cairo_clip_intersect_mask (cairo_clip_t      *clip,
			    cairo_traps_t     *traps,
			    cairo_antialias_t antialias,
			    cairo_surface_t   *target)
{
    cairo_pattern_union_t pattern;
    cairo_box_t extents;
    cairo_rectangle_int_t surface_rect, target_rect;
    cairo_surface_t *surface;
    cairo_status_t status;

    if (clip->all_clipped)
	return CAIRO_STATUS_SUCCESS;

    /* Represent the clip as a mask surface.  We create a new surface
     * the size of the intersection of the old mask surface and the
     * extents of the new clip path. */

    _cairo_traps_extents (traps, &extents);
    _cairo_box_round_to_rectangle (&extents, &surface_rect);

    if (clip->surface != NULL)
	_cairo_rectangle_intersect (&surface_rect, &clip->surface_rect);

    /* Intersect with the target surface rectangle so we don't use
     * more memory and time than we need to. */

    status = _cairo_surface_get_extents (target, &target_rect);
    if (!status)
	_cairo_rectangle_intersect (&surface_rect, &target_rect);

    if (surface_rect.width == 0 || surface_rect.height == 0) {
	surface = NULL;
	status = CAIRO_STATUS_SUCCESS;
	if (clip->surface != NULL)
	    cairo_surface_destroy (clip->surface);
	goto DONE;
    }

    _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE,
			       CAIRO_CONTENT_COLOR);
    /* The clipping operation should ideally be something like the following to
     * avoid having to do as many passes over the data

	if (clip->surface != NULL) {
	    _cairo_pattern_init_for_surface (&pattern.surface, clip->surface);
	} else {
	    _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE,
			       CAIRO_CONTENT_COLOR);
	}
	status = _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_IN,
						  &pattern.base,
						  surface,
						  antialias,
						  0, 0,
						  0, 0,
						  surface_rect.width,
						  surface_rect.height,
						  traps->traps,
						  traps->num_traps);

	However this operation is not accelerated by pixman

	I believe the best possible operation would probably an unbounded SRC
	operator.  Using SRC we could potentially avoid having to initialize
	the surface which would be ideal from an efficiency point of view.
	However, _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_SOURCE) is
	bounded by the mask.

    */

    surface = _cairo_surface_create_similar_solid (target,
						   CAIRO_CONTENT_ALPHA,
						   surface_rect.width,
						   surface_rect.height,
						   CAIRO_COLOR_TRANSPARENT,
						   &pattern.base);
    if (surface->status) {
	_cairo_pattern_fini (&pattern.base);
	return surface->status;
    }

    /* Render the new clipping path into the new mask surface. */

    _cairo_traps_translate (traps, -surface_rect.x, -surface_rect.y);

    status = _cairo_surface_composite_trapezoids (CAIRO_OPERATOR_ADD,
						  &pattern.base,
						  surface,
						  antialias,
						  0, 0,
						  0, 0,
						  surface_rect.width,
						  surface_rect.height,
						  traps->traps,
						  traps->num_traps);

    _cairo_pattern_fini (&pattern.base);

    if (status) {
	cairo_surface_destroy (surface);
	return status;
    }

    /* If there was a clip surface already, combine it with the new
     * mask surface using the IN operator, so we get the intersection
     * of the old and new clipping paths. */

    if (clip->surface != NULL) {
	_cairo_pattern_init_for_surface (&pattern.surface, clip->surface);

	status = _cairo_surface_composite (CAIRO_OPERATOR_IN,
					   &pattern.base,
					   NULL,
					   surface,
					   surface_rect.x - clip->surface_rect.x,
					   surface_rect.y - clip->surface_rect.y,
					   0, 0,
					   0, 0,
					   surface_rect.width,
					   surface_rect.height);

	_cairo_pattern_fini (&pattern.base);

	if (status) {
	    cairo_surface_destroy (surface);
	    return status;
	}

	cairo_surface_destroy (clip->surface);
    }

 DONE:
    clip->surface = surface;
    clip->surface_rect = surface_rect;
    clip->serial = _cairo_surface_allocate_clip_serial (target);

    if (surface_rect.width == 0 || surface_rect.height == 0)
	_cairo_clip_set_all_clipped (clip, target);

    return status;
}
Esempio n. 8
0
static cairo_status_t
_cairo_clip_intersect_mask_using_spans (cairo_clip_t       *clip,
					cairo_path_fixed_t *path,
					cairo_fill_rule_t   fill_rule,
					double		    tolerance,
					cairo_antialias_t   antialias,
					cairo_surface_t    *target)
{
    cairo_span_renderer_t *renderer = NULL;
    cairo_pattern_union_t pattern;
    cairo_rectangle_int_t surface_rect;
    cairo_surface_t *surface = NULL;
    cairo_status_t status;
    cairo_operator_t op;
    cairo_composite_rectangles_t rects;

    if (clip->all_clipped)
	return CAIRO_STATUS_SUCCESS;

    _cairo_pattern_init_solid (&pattern.solid, CAIRO_COLOR_WHITE,
			       CAIRO_CONTENT_COLOR);

    /* If we have a clip surface we're going to use IN to combine our
     * new clip with the old clip.  The ADD is done to a transparent
     * surface, as that's a fast way of doing it currently.  We should
     * really be using SOURCE instead, but _cairo_surface_composite()
     * checks that it's not called with SOURCE or DEST. */
    op = clip->surface ? CAIRO_OPERATOR_IN : CAIRO_OPERATOR_ADD;

    /* Test if the target can composite spans.  We're going to assume
     * this is a good indicator of whether a similar surface is going
     * to be able to composite spans too. */
    if ( !_cairo_surface_check_span_renderer (op,
					      &pattern.base,
					      target,
					      antialias,
					      NULL))
    {
	status = CAIRO_INT_STATUS_UNSUPPORTED;
	goto BAIL;
    }

    status = _cairo_surface_get_extents (target, &surface_rect);
    if (status)
	goto BAIL;

    /* We'll create a new surface the size of the intersection of the
     * old mask surface and the extents of the new clip path. */
    {
	cairo_rectangle_int_t extents;

	_cairo_path_fixed_approximate_clip_extents (path, &extents);
	if (! _cairo_rectangle_intersect (&surface_rect, &extents))
	    goto SUCCESS;

	if (clip->surface != NULL &&
	    ! _cairo_rectangle_intersect (&surface_rect, &clip->surface_rect))
	    goto SUCCESS;
    }

    /* Make the new mask surface and optionally initialise it from the
     * previous clip if we have one. */
    surface = _cairo_surface_create_similar_solid (target,
						   CAIRO_CONTENT_ALPHA,
						   surface_rect.width,
						   surface_rect.height,
						   CAIRO_COLOR_TRANSPARENT);
    if (surface->status) {
	_cairo_pattern_fini (&pattern.base);
	return surface->status;
    }

    if (clip->surface) {
	cairo_surface_pattern_t old_clip;
	_cairo_pattern_init_for_surface (&old_clip, clip->surface);
	status = _cairo_surface_composite (CAIRO_OPERATOR_ADD,
					   &old_clip.base,
					   NULL,
					   surface,
					   surface_rect.x - clip->surface_rect.x,
					   surface_rect.y - clip->surface_rect.y,
					   0, 0,
					   0, 0,
					   surface_rect.width,
					   surface_rect.height);
	_cairo_pattern_fini (&old_clip.base);
	if (status)
	    goto BAIL;
    }

    _cairo_composite_rectangles_init (&rects,
				      surface_rect.x,
				      surface_rect.y,
				      surface_rect.width,
				      surface_rect.height);
    rects.dst.x = 0;
    rects.dst.y = 0;

    /* Render the new clipping path into the new mask surface. We've
     * chosen op to either combine the new clip path with the existing
     * clip mask (if there is one) or just render it. */
    status =_cairo_path_fixed_fill_using_spans (op, &pattern.base,
						path, surface,
						fill_rule, tolerance,
						antialias, &rects);
    if (status)
	goto BAIL;

 SUCCESS:
    if (clip->surface != NULL)
	cairo_surface_destroy (clip->surface);
    clip->surface = surface;
    clip->surface_rect = surface_rect;
    clip->serial = _cairo_surface_allocate_clip_serial (target);
    surface = NULL;

    if (surface_rect.width == 0 || surface_rect.height == 0)
	_cairo_clip_set_all_clipped (clip, target);

 BAIL:
    if (renderer)
	renderer->destroy(renderer);
    if (surface)
	cairo_surface_destroy (surface);
    _cairo_pattern_fini (&pattern.base);
    return status;
}
static cairo_surface_t *
get_clip_surface (const cairo_spans_compositor_t *compositor,
		  cairo_surface_t *dst,
		  const cairo_clip_t *clip,
		  const cairo_rectangle_int_t *extents)
{
    cairo_composite_rectangles_t composite;
    cairo_surface_t *surface;
    cairo_box_t box;
    cairo_polygon_t polygon;
    const cairo_clip_path_t *clip_path;
    cairo_antialias_t antialias;
    cairo_fill_rule_t fill_rule;
    cairo_int_status_t status;

    assert (clip->path);

    surface = _cairo_surface_create_similar_solid (dst,
						   CAIRO_CONTENT_ALPHA,
						   extents->width,
						   extents->height,
						   CAIRO_COLOR_TRANSPARENT);

    _cairo_box_from_rectangle (&box, extents);
    _cairo_polygon_init (&polygon, &box, 1);

    clip_path = clip->path;
    status = _cairo_path_fixed_fill_to_polygon (&clip_path->path,
						clip_path->tolerance,
						&polygon);
    if (unlikely (status))
	goto cleanup_polygon;

    antialias = clip_path->antialias;
    fill_rule = clip_path->fill_rule;

    if (clip->boxes) {
	cairo_polygon_t intersect;
	cairo_boxes_t tmp;

	_cairo_boxes_init_for_array (&tmp, clip->boxes, clip->num_boxes);
	status= _cairo_polygon_init_boxes (&intersect, &tmp);
	if (unlikely (status))
	    goto cleanup_polygon;

	status = _cairo_polygon_intersect (&polygon, fill_rule,
					   &intersect, CAIRO_FILL_RULE_WINDING);
	_cairo_polygon_fini (&intersect);

	if (unlikely (status))
	    goto cleanup_polygon;

	fill_rule = CAIRO_FILL_RULE_WINDING;
    }

    polygon.limits = NULL;
    polygon.num_limits = 0;

    clip_path = clip_path->prev;
    while (clip_path) {
	if (clip_path->antialias == antialias) {
	    cairo_polygon_t next;

	    _cairo_polygon_init (&next, NULL, 0);
	    status = _cairo_path_fixed_fill_to_polygon (&clip_path->path,
							clip_path->tolerance,
							&next);
	    if (likely (status == CAIRO_INT_STATUS_SUCCESS))
		status = _cairo_polygon_intersect (&polygon, fill_rule,
						   &next, clip_path->fill_rule);
	    _cairo_polygon_fini (&next);
	    if (unlikely (status))
		goto cleanup_polygon;

	    fill_rule = CAIRO_FILL_RULE_WINDING;
	}

	clip_path = clip_path->prev;
    }

    _cairo_polygon_translate (&polygon, -extents->x, -extents->y);
    status = _cairo_composite_rectangles_init_for_polygon (&composite, surface,
							   CAIRO_OPERATOR_ADD,
							   &_cairo_pattern_white.base,
							   &polygon,
							   NULL);
    if (unlikely (status))
	goto cleanup_polygon;

    status = composite_polygon (compositor, &composite,
				&polygon, fill_rule, antialias);
    _cairo_composite_rectangles_fini (&composite);
    _cairo_polygon_fini (&polygon);
    if (unlikely (status))
	goto error;

    _cairo_polygon_init (&polygon, &box, 1);

    clip_path = clip->path;
    antialias = clip_path->antialias == CAIRO_ANTIALIAS_DEFAULT ? CAIRO_ANTIALIAS_NONE : CAIRO_ANTIALIAS_DEFAULT;
    clip_path = clip_path->prev;
    while (clip_path) {
	if (clip_path->antialias == antialias) {
	    if (polygon.num_edges == 0) {
		status = _cairo_path_fixed_fill_to_polygon (&clip_path->path,
							    clip_path->tolerance,
							    &polygon);

		fill_rule = clip_path->fill_rule;
		polygon.limits = NULL;
		polygon.num_limits = 0;
	    } else {
		cairo_polygon_t next;

		_cairo_polygon_init (&next, NULL, 0);
		status = _cairo_path_fixed_fill_to_polygon (&clip_path->path,
							    clip_path->tolerance,
							    &next);
		if (likely (status == CAIRO_INT_STATUS_SUCCESS))
		    status = _cairo_polygon_intersect (&polygon, fill_rule,
						       &next, clip_path->fill_rule);
		_cairo_polygon_fini (&next);
		fill_rule = CAIRO_FILL_RULE_WINDING;
	    }
	    if (unlikely (status))
		goto error;
	}

	clip_path = clip_path->prev;
    }

    if (polygon.num_edges) {
	_cairo_polygon_translate (&polygon, -extents->x, -extents->y);
	status = _cairo_composite_rectangles_init_for_polygon (&composite, surface,
							       CAIRO_OPERATOR_IN,
							       &_cairo_pattern_white.base,
							       &polygon,
							       NULL);
	if (unlikely (status))
	    goto cleanup_polygon;

	status = composite_polygon (compositor, &composite,
				    &polygon, fill_rule, antialias);
	_cairo_composite_rectangles_fini (&composite);
	_cairo_polygon_fini (&polygon);
	if (unlikely (status))
	    goto error;
    }

    return surface;

cleanup_polygon:
    _cairo_polygon_fini (&polygon);
error:
    cairo_surface_destroy (surface);
    return _cairo_int_surface_create_in_error (status);
}
Esempio n. 10
0
static cairo_surface_t *
_cairo_clip_path_get_surface (cairo_clip_path_t *clip_path,
			      cairo_surface_t *target)
{
    cairo_surface_t *surface;
    cairo_pattern_union_t pattern;
    cairo_status_t status;
    const cairo_rectangle_int_t *clip_extents = &clip_path->extents;
    cairo_clip_path_t *prev;
    cairo_bool_t need_translate;

    if (clip_path->surface != NULL &&
	clip_path->surface->backend == target->backend)
    {
	return cairo_surface_reference (clip_path->surface);
    }

    surface = _cairo_surface_create_similar_solid (target,
						   CAIRO_CONTENT_ALPHA,
						   clip_extents->width,
						   clip_extents->height,
						   CAIRO_COLOR_TRANSPARENT,
						   FALSE);
    if (surface == NULL) {
	if (clip_path->surface != NULL &&
	    clip_path->surface->backend == &_cairo_image_surface_backend)
	{
	    return cairo_surface_reference (clip_path->surface);
	}

	surface =
	    _cairo_image_surface_create_with_content (CAIRO_CONTENT_ALPHA,
						      clip_extents->width,
						      clip_extents->height);
    }
    if (unlikely (surface->status))
	return surface;

    _cairo_pattern_init_solid (&pattern.solid,
			       CAIRO_COLOR_WHITE,
			       CAIRO_CONTENT_COLOR);

    status = _cairo_clip_path_to_region (clip_path);
    if (unlikely (_cairo_status_is_error (status)))
	goto BAIL;

    need_translate = clip_extents->x | clip_extents->y;
    if (status == CAIRO_STATUS_SUCCESS) {
	if (need_translate) {
	    cairo_region_translate (clip_path->region,
				    -clip_extents->x, -clip_extents->y);
	}
	status = _cairo_surface_fill_region (surface,
					     CAIRO_OPERATOR_SOURCE,
					     CAIRO_COLOR_WHITE,
					     clip_path->region);
	if (need_translate) {
	    cairo_region_translate (clip_path->region,
				    clip_extents->x, clip_extents->y);
	}
	if (unlikely (status))
	    goto BAIL;

	goto DONE;
    } else {
	if (need_translate) {
	    _cairo_path_fixed_translate (&clip_path->path,
					 _cairo_fixed_from_int (-clip_extents->x),
					 _cairo_fixed_from_int (-clip_extents->y));
	}
	status = _cairo_surface_fill (surface,
				      CAIRO_OPERATOR_OVER,
				      &pattern.base,
				      &clip_path->path,
				      clip_path->fill_rule,
				      clip_path->tolerance,
				      clip_path->antialias,
				      NULL);
	if (need_translate) {
	    _cairo_path_fixed_translate (&clip_path->path,
					 _cairo_fixed_from_int (clip_extents->x),
					 _cairo_fixed_from_int (clip_extents->y));
	}

	if (unlikely (status))
	    goto BAIL;
    }

    prev = clip_path->prev;
  NEXT_PATH:
    if (prev != NULL) {
	status = _cairo_clip_path_to_region (prev);
	if (unlikely (_cairo_status_is_error (status)))
	    goto BAIL;

	if (status == CAIRO_STATUS_SUCCESS) {
	    status = _combine_region (surface, prev->region, clip_extents);
	    if (unlikely (status))
		goto BAIL;
	} else if (prev->flags & CAIRO_CLIP_PATH_IS_BOX) {
	    /* a simple box only affects the extents */
	} else if (prev->path.is_rectilinear) {
	    if (need_translate) {
		_cairo_path_fixed_translate (&prev->path,
					     _cairo_fixed_from_int (-clip_extents->x),
					     _cairo_fixed_from_int (-clip_extents->y));
	    }
	    status = _cairo_surface_fill (surface,
					  CAIRO_OPERATOR_IN,
					  &pattern.base,
					  &prev->path,
					  prev->fill_rule,
					  prev->tolerance,
					  prev->antialias,
					  NULL);
	    if (need_translate) {
		_cairo_path_fixed_translate (&prev->path,
					     _cairo_fixed_from_int (clip_extents->x),
					     _cairo_fixed_from_int (clip_extents->y));
	    }

	    if (unlikely (status))
		goto BAIL;

	    prev = prev->prev;
	    goto NEXT_PATH;
	} else {
	    cairo_surface_t *prev_surface;

	    prev_surface = _cairo_clip_path_get_surface (prev, target);
	    _cairo_pattern_init_for_surface (&pattern.surface, prev_surface);
	    cairo_surface_destroy (prev_surface);

	    cairo_matrix_init_translate (&pattern.base.matrix,
					 -prev->extents.x + clip_extents->x,
					 -prev->extents.y + clip_extents->y);
	    status = _cairo_surface_paint (surface,
					   CAIRO_OPERATOR_IN,
					   &pattern.base,
					   NULL);
	    _cairo_pattern_fini (&pattern.base);

	    if (unlikely (status))
		goto BAIL;
	}
    }

  DONE:
    cairo_surface_destroy (clip_path->surface);
    return clip_path->surface = cairo_surface_reference (surface);

  BAIL:
    cairo_surface_destroy (surface);
    return _cairo_surface_create_in_error (status);
}