/* 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;
}
static cairo_int_status_t
fixup_unbounded_mask (const cairo_spans_compositor_t *compositor,
		      const cairo_composite_rectangles_t *extents,
		      cairo_boxes_t *boxes)
{
    cairo_composite_rectangles_t composite;
    cairo_surface_t *clip;
    cairo_int_status_t status;

    TRACE((stderr, "%s\n", __FUNCTION__));

    clip = get_clip_surface (compositor, extents->surface, extents->clip,
			     &extents->unbounded);
    if (unlikely (clip->status)) {
	if ((cairo_int_status_t)clip->status == CAIRO_INT_STATUS_NOTHING_TO_DO)
	    return CAIRO_STATUS_SUCCESS;

	return clip->status;
    }

    status = _cairo_composite_rectangles_init_for_boxes (&composite,
							 extents->surface,
							 CAIRO_OPERATOR_CLEAR,
							 &_cairo_pattern_clear.base,
							 boxes,
							 NULL);
    if (unlikely (status))
	goto cleanup_clip;

    _cairo_pattern_init_for_surface (&composite.mask_pattern.surface, clip);
    composite.mask_pattern.base.filter = CAIRO_FILTER_NEAREST;
    composite.mask_pattern.base.extend = CAIRO_EXTEND_NONE;

    status = composite_boxes (compositor, &composite, boxes);

    _cairo_pattern_fini (&composite.mask_pattern.base);
    _cairo_composite_rectangles_fini (&composite);

cleanup_clip:
    cairo_surface_destroy (clip);
    return status;
}
static cairo_status_t
fixup_unbounded (const cairo_composite_rectangles_t *extents)
{
    cairo_image_surface_t *dst = (cairo_image_surface_t *)extents->surface;
    pixman_image_t *mask;
    int mask_x, mask_y;

    if (! _cairo_clip_is_region (extents->clip)) {
	cairo_image_surface_t *clip;

	clip = get_clip_surface (dst, extents, &mask_x, &mask_y);
	if (unlikely (clip->base.status))
	    return clip->base.status;

	mask = pixman_image_ref (clip->pixman_image);
	cairo_surface_destroy (&clip->base);
    } else {
	mask_x = mask_y = 0;
	mask = _pixman_image_for_color (CAIRO_COLOR_WHITE);
	if (unlikely (mask == NULL))
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }

    /* top */
    if (extents->bounded.y != extents->unbounded.y) {
	int x = extents->unbounded.x;
	int y = extents->unbounded.y;
	int width = extents->unbounded.width;
	int height = extents->bounded.y - y;

	pixman_image_composite32 (PIXMAN_OP_OUT_REVERSE,
				  mask, NULL, dst->pixman_image,
				  x - mask_x, y - mask_y,
				  0, 0,
				  x, y,
				  width, height);
    }

    /* left */
    if (extents->bounded.x != extents->unbounded.x) {
	int x = extents->unbounded.x;
	int y = extents->bounded.y;
	int width = extents->bounded.x - x;
	int height = extents->bounded.height;

	pixman_image_composite32 (PIXMAN_OP_OUT_REVERSE,
				  mask, NULL, dst->pixman_image,
				  x - mask_x, y - mask_y,
				  0, 0,
				  x, y,
				  width, height);
    }

    /* right */
    if (extents->bounded.x + extents->bounded.width != extents->unbounded.x + extents->unbounded.width) {
	int x = extents->bounded.x + extents->bounded.width;
	int y = extents->bounded.y;
	int width = extents->unbounded.x + extents->unbounded.width - x;
	int height = extents->bounded.height;

	pixman_image_composite32 (PIXMAN_OP_OUT_REVERSE,
				  mask, NULL, dst->pixman_image,
				  x - mask_x, y - mask_y,
				  0, 0,
				  x, y,
				  width, height);
    }

    /* bottom */
    if (extents->bounded.y + extents->bounded.height != extents->unbounded.y + extents->unbounded.height) {
	int x = extents->unbounded.x;
	int y = extents->bounded.y + extents->bounded.height;
	int width = extents->unbounded.width;
	int height = extents->unbounded.y + extents->unbounded.height - y;

	pixman_image_composite32 (PIXMAN_OP_OUT_REVERSE,
				  mask, NULL, dst->pixman_image,
				  x - mask_x, y - mask_y,
				  0, 0,
				  x, y,
				  width, height);
    }

    pixman_image_unref (mask);

    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
composite_aligned_boxes (const cairo_spans_compositor_t		*compositor,
			 const cairo_composite_rectangles_t	*extents,
			 cairo_boxes_t				*boxes)
{
    cairo_surface_t *dst = extents->surface;
    cairo_operator_t op = extents->op;
    const cairo_pattern_t *source = &extents->source_pattern.base;
    cairo_int_status_t status;
    cairo_bool_t need_clip_mask = ! _clip_is_region (extents->clip);
    cairo_bool_t op_is_source;
    cairo_bool_t no_mask;
    cairo_bool_t inplace;

    TRACE ((stderr, "%s: need_clip_mask=%d, is-bounded=%d\n",
	    __FUNCTION__, need_clip_mask, extents->is_bounded));
    if (need_clip_mask && ! extents->is_bounded)
	return CAIRO_INT_STATUS_UNSUPPORTED;

    no_mask = extents->mask_pattern.base.type == CAIRO_PATTERN_TYPE_SOLID &&
	CAIRO_COLOR_IS_OPAQUE (&extents->mask_pattern.solid.color);
    op_is_source = op_reduces_to_source (extents, no_mask);
    inplace = ! need_clip_mask && op_is_source && no_mask;

    TRACE ((stderr, "%s: op-is-source=%d [op=%d], no-mask=%d, inplace=%d\n",
	    __FUNCTION__, op_is_source, op, no_mask, inplace));

    if (op == CAIRO_OPERATOR_SOURCE && (need_clip_mask || ! no_mask)) {
	/* SOURCE with a mask is actually a LERP in cairo semantics */
	if ((compositor->flags & CAIRO_SPANS_COMPOSITOR_HAS_LERP) == 0)
	    return CAIRO_INT_STATUS_UNSUPPORTED;
    }

    /* Are we just copying a recording surface? */
    if (inplace &&
	recording_pattern_contains_sample (&extents->source_pattern.base,
					   &extents->source_sample_area))
    {
	cairo_clip_t *recording_clip;
	const cairo_pattern_t *source = &extents->source_pattern.base;

	/* XXX could also do tiling repeat modes... */

	/* first clear the area about to be overwritten */
	if (! dst->is_clear)
	    status = compositor->fill_boxes (dst,
					     CAIRO_OPERATOR_CLEAR,
					     CAIRO_COLOR_TRANSPARENT,
					     boxes);

	recording_clip = _cairo_clip_from_boxes (boxes);
	status = _cairo_recording_surface_replay_with_clip (unwrap_source (source),
							    &source->matrix,
							    dst, recording_clip);
	_cairo_clip_destroy (recording_clip);

	return status;
    }

    status = CAIRO_INT_STATUS_UNSUPPORTED;
    if (! need_clip_mask && no_mask && source->type == CAIRO_PATTERN_TYPE_SOLID) {
	const cairo_color_t *color;

	color = &((cairo_solid_pattern_t *) source)->color;
	if (op_is_source)
	    op = CAIRO_OPERATOR_SOURCE;
	status = compositor->fill_boxes (dst, op, color, boxes);
    } else if (inplace && source->type == CAIRO_PATTERN_TYPE_SURFACE) {
	status = upload_boxes (compositor, extents, boxes);
    }
    if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
	cairo_surface_t *src;
	cairo_surface_t *mask = NULL;
	int src_x, src_y;
	int mask_x = 0, mask_y = 0;

	/* All typical cases will have been resolved before now... */
	if (need_clip_mask) {
	    mask = get_clip_surface (compositor, dst, extents->clip,
				     &extents->bounded);
	    if (unlikely (mask->status))
		return mask->status;

	    mask_x = -extents->bounded.x;
	    mask_y = -extents->bounded.y;
	}

	/* XXX but this is still ugly */
	if (! no_mask) {
	    src = compositor->pattern_to_surface (dst,
						  &extents->mask_pattern.base,
						  TRUE,
						  &extents->bounded,
						  &extents->mask_sample_area,
						  &src_x, &src_y);
	    if (unlikely (src->status)) {
		cairo_surface_destroy (mask);
		return src->status;
	    }

	    if (mask != NULL) {
		status = compositor->composite_boxes (mask, CAIRO_OPERATOR_IN,
						      src, NULL,
						      src_x, src_y,
						      0, 0,
						      mask_x, mask_y,
						      boxes, &extents->bounded);

		cairo_surface_destroy (src);
	    } else {
		mask = src;
		mask_x = src_x;
		mask_y = src_y;
	    }
	}

	src = compositor->pattern_to_surface (dst, source, FALSE,
					      &extents->bounded,
					      &extents->source_sample_area,
					      &src_x, &src_y);
	if (likely (src->status == CAIRO_STATUS_SUCCESS)) {
	    status = compositor->composite_boxes (dst, op, src, mask,
						  src_x, src_y,
						  mask_x, mask_y,
						  0, 0,
						  boxes, &extents->bounded);
	    cairo_surface_destroy (src);
	} else
	    status = src->status;

	cairo_surface_destroy (mask);
    }

    if (status == CAIRO_INT_STATUS_SUCCESS && ! extents->is_bounded)
	status = fixup_unbounded_boxes (compositor, extents, boxes);

    return status;
}