[cairo-commit] 4 commits - src/cairo-analysis-surface.c src/cairo-analysis-surface-private.h src/cairo-debug.c src/cairo-image-source.c src/cairoint.h src/cairo-paginated-surface.c src/cairo-pattern.c src/cairo-pattern-private.h src/cairo-pdf-surface.c src/cairo-pdf-surface-private.h src/cairo-ps-surface.c src/cairo-ps-surface-private.h src/cairo-quartz-surface.c src/cairo-recording-surface.c src/cairo-recording-surface-private.h src/cairo-script-surface.c src/cairo-spans-compositor.c src/cairo-surface-wrapper.c src/cairo-surface-wrapper-private.h src/cairo-traps-compositor.c src/cairo-xcb-surface-render.c src/cairo-xlib-source.c src/win32 test/create-regions.c test/meson.build test/pdf-tagged-text.c test/reference

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon Jan 16 08:59:35 UTC 2023


 src/cairo-analysis-surface-private.h      |   15 
 src/cairo-analysis-surface.c              |  113 +++++-
 src/cairo-debug.c                         |   98 +++++
 src/cairo-image-source.c                  |    2 
 src/cairo-paginated-surface.c             |   27 +
 src/cairo-pattern-private.h               |    6 
 src/cairo-pattern.c                       |    1 
 src/cairo-pdf-surface-private.h           |    7 
 src/cairo-pdf-surface.c                   |   29 +
 src/cairo-ps-surface-private.h            |    1 
 src/cairo-ps-surface.c                    |   34 +
 src/cairo-quartz-surface.c                |    3 
 src/cairo-recording-surface-private.h     |   63 +++
 src/cairo-recording-surface.c             |  527 ++++++++++++++++++++++++++++--
 src/cairo-script-surface.c                |   10 
 src/cairo-spans-compositor.c              |    2 
 src/cairo-surface-wrapper-private.h       |   95 +++--
 src/cairo-surface-wrapper.c               |  130 ++++---
 src/cairo-traps-compositor.c              |    2 
 src/cairo-xcb-surface-render.c            |    3 
 src/cairo-xlib-source.c                   |    3 
 src/cairoint.h                            |    6 
 src/win32/cairo-win32-printing-surface.c  |    1 
 test/create-regions.c                     |  435 ++++++++++++++++++++++++
 test/meson.build                          |    1 
 test/pdf-tagged-text.c                    |    5 
 test/reference/create-regions.pdf.ref.png |binary
 test/reference/create-regions.ps.ref.png  |binary
 test/reference/create-regions.svg.ref.png |binary
 29 files changed, 1442 insertions(+), 177 deletions(-)

New commits:
commit 745c3717aa8e91237bf90de1b2e908c72499aa0f
Merge: 6abc8076c 714635825
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Mon Jan 16 08:59:32 2023 +0000

    Merge branch 'fix-shared-recording-surface' into 'master'
    
    Fix shared use of recording surfaces
    
    See merge request cairo/cairo!391

commit 7146358250975ec0f29b8ba80e80a26c52526bdc
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Jan 1 15:14:08 2023 +1030

    Fix shared use of recording surface with paginated targets
    
    The problem is _cairo_recording_surface_replay_and_create_regions()
    stores the cairo_recording_region_type_t in the same structure as the
    recording commands. This does not work well when the recording surface
    is used as source by multiple surfaces
    
    Fix this by moving the cairo_recording_region_type_t into a separate
    struct cairo_recording_regions_array_t. This struct is stored in a
    list that allows multiple create regions results to be store in the
    surface.
    
    The new function _cairo_recording_surface_region_array_attach() is
    used to create a new cairo_recording_regions_array_t, attach it to the
    recording surface and return a unique region id.
    
    The _cairo_recording_surface_replay_and_create_regions() and
    _cairo_recording_surface_replay_region() functions use this region id
    to identify the cairo_recording_regions_array_t.
    
    To handle nested recording surfaces, when replaying a recording, the
    region id is passed to the target as an extra parameter in the surface
    pattern. The wrapper surface makes a temporary copy of the pattern to
    ensure the snapshot pattern in the recording surface is not modified.
    
    cairo_recording_regions_array_t has a reference count so the target
    can hold on to the cairo_recording_regions_array_t after the paginated
    surface has called _cairo_recording_surface_region_array_remove().

diff --git a/src/cairo-analysis-surface-private.h b/src/cairo-analysis-surface-private.h
index 1e054c209..6489cceb8 100644
--- a/src/cairo-analysis-surface-private.h
+++ b/src/cairo-analysis-surface-private.h
@@ -38,7 +38,8 @@
 #include "cairoint.h"
 
 cairo_private cairo_surface_t *
-_cairo_analysis_surface_create (cairo_surface_t		*target);
+_cairo_analysis_surface_create (cairo_surface_t		*target,
+				cairo_bool_t             create_region_ids);
 
 cairo_private void
 _cairo_analysis_surface_set_ctm (cairo_surface_t *surface,
@@ -64,6 +65,12 @@ cairo_private void
 _cairo_analysis_surface_get_bounding_box (cairo_surface_t *surface,
 					  cairo_box_t     *bbox);
 
+cairo_private unsigned int
+_cairo_analysis_surface_get_source_region_id (cairo_surface_t *surface);
+
+cairo_private unsigned int
+_cairo_analysis_surface_get_mask_region_id (cairo_surface_t *surface);
+
 cairo_private cairo_int_status_t
 _cairo_analysis_surface_merge_status (cairo_int_status_t status_a,
 				      cairo_int_status_t status_b);
@@ -71,4 +78,10 @@ _cairo_analysis_surface_merge_status (cairo_int_status_t status_a,
 cairo_private cairo_surface_t *
 _cairo_null_surface_create (cairo_content_t content);
 
+static inline cairo_bool_t
+_cairo_surface_is_analysis (const cairo_surface_t *surface)
+{
+    return (cairo_internal_surface_type_t)surface->backend->type == CAIRO_INTERNAL_SURFACE_TYPE_ANALYSIS;
+}
+
 #endif /* CAIRO_ANALYSIS_SURFACE_H */
diff --git a/src/cairo-analysis-surface.c b/src/cairo-analysis-surface.c
index 0e22b9aa9..86e10d5b0 100644
--- a/src/cairo-analysis-surface.c
+++ b/src/cairo-analysis-surface.c
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
 /*
  * Copyright © 2006 Keith Packard
  * Copyright © 2007 Adrian Johnson
@@ -59,6 +60,10 @@ typedef struct {
     cairo_region_t fallback_region;
     cairo_box_t page_bbox;
 
+    cairo_bool_t create_region_ids;
+    unsigned source_region_id;
+    unsigned mask_region_id;
+
     cairo_bool_t has_ctm;
     cairo_matrix_t ctm;
 
@@ -257,7 +262,8 @@ _add_operation (cairo_analysis_surface_t *surface,
 static cairo_int_status_t
 _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
 				    const cairo_pattern_t    *pattern,
-				    cairo_rectangle_int_t    *extents)
+				    cairo_rectangle_int_t    *extents,
+				    unsigned int             *regions_id)
 {
     const cairo_surface_pattern_t *surface_pattern;
     cairo_analysis_surface_t *tmp;
@@ -280,7 +286,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
     }
 
     tmp = (cairo_analysis_surface_t *)
-	_cairo_analysis_surface_create (surface->target);
+	_cairo_analysis_surface_create (surface->target, surface->create_region_ids);
     if (unlikely (tmp->base.status)) {
 	status =tmp->base.status;
 	goto cleanup1;
@@ -295,13 +301,29 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
 
     source = _cairo_surface_get_source (source, NULL);
     surface_is_unbounded = (pattern->extend == CAIRO_EXTEND_REPEAT
-				     || pattern->extend == CAIRO_EXTEND_REFLECT);
-    status = _cairo_recording_surface_replay_and_create_regions (source,
-								 &pattern->matrix,
-								 &tmp->base,
-								 surface_is_unbounded);
-    if (unlikely (status))
-	goto cleanup2;
+			    || pattern->extend == CAIRO_EXTEND_REFLECT);
+
+    if (surface->create_region_ids) {
+	status = _cairo_recording_surface_region_array_attach (source, regions_id);
+	if (unlikely (status))
+	    goto cleanup2;
+
+	status = _cairo_recording_surface_replay_and_create_regions (source,
+								     *regions_id,
+								     &pattern->matrix,
+								     &tmp->base,
+								     surface_is_unbounded);
+	if (unlikely (status))
+	    goto cleanup2;
+    } else {
+	status = _cairo_recording_surface_replay_with_clip (source,
+							    &pattern->matrix,
+							    &tmp->base,
+							    NULL, /* target clip */
+							    surface_is_unbounded);
+	if (unlikely (status))
+	    goto cleanup2;
+    }
 
     /* black background or mime data fills entire extents */
     if (!(source->content & CAIRO_CONTENT_ALPHA) || _cairo_surface_has_mime_image (source)) {
@@ -412,6 +434,8 @@ _cairo_analysis_surface_paint (void			*abstract_surface,
     cairo_int_status_t	     backend_status;
     cairo_rectangle_int_t  extents;
 
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
     if (surface->target->backend->paint == NULL) {
 	backend_status = CAIRO_INT_STATUS_UNSUPPORTED;
     } else {
@@ -427,7 +451,10 @@ _cairo_analysis_surface_paint (void			*abstract_surface,
 					       &extents);
     if (backend_status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
 	cairo_rectangle_int_t rec_extents;
-	backend_status = _analyze_recording_surface_pattern (surface, source, &rec_extents);
+	backend_status = _analyze_recording_surface_pattern (surface,
+							     source,
+							     &rec_extents,
+							     &surface->source_region_id);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -445,6 +472,8 @@ _cairo_analysis_surface_mask (void			*abstract_surface,
     cairo_int_status_t	      backend_status;
     cairo_rectangle_int_t   extents;
 
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
     if (surface->target->backend->mask == NULL) {
 	backend_status = CAIRO_INT_STATUS_UNSUPPORTED;
     } else {
@@ -468,7 +497,10 @@ _cairo_analysis_surface_mask (void			*abstract_surface,
 	    src_surface = _cairo_surface_get_source (src_surface, NULL);
 	    if (_cairo_surface_is_recording (src_surface)) {
 		backend_source_status =
-		    _analyze_recording_surface_pattern (surface, source, &rec_extents);
+		    _analyze_recording_surface_pattern (surface,
+							source,
+							&rec_extents,
+							&surface->source_region_id);
 		if (_cairo_int_status_is_error (backend_source_status))
 		    return backend_source_status;
 
@@ -481,7 +513,10 @@ _cairo_analysis_surface_mask (void			*abstract_surface,
 	    mask_surface = _cairo_surface_get_source (mask_surface, NULL);
 	    if (_cairo_surface_is_recording (mask_surface)) {
 		backend_mask_status =
-		    _analyze_recording_surface_pattern (surface, mask, &rec_extents);
+		    _analyze_recording_surface_pattern (surface,
+							mask,
+							&rec_extents,
+							&surface->mask_region_id);
 		if (_cairo_int_status_is_error (backend_mask_status))
 		    return backend_mask_status;
 
@@ -520,6 +555,8 @@ _cairo_analysis_surface_stroke (void			   *abstract_surface,
     cairo_int_status_t	     backend_status;
     cairo_rectangle_int_t    extents;
 
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
     if (surface->target->backend->stroke == NULL) {
 	backend_status = CAIRO_INT_STATUS_UNSUPPORTED;
     } else {
@@ -538,7 +575,10 @@ _cairo_analysis_surface_stroke (void			   *abstract_surface,
 					       &extents);
     if (backend_status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
 	cairo_rectangle_int_t rec_extents;
-	backend_status = _analyze_recording_surface_pattern (surface, source, &rec_extents);
+	backend_status = _analyze_recording_surface_pattern (surface,
+							     source,
+							     &rec_extents,
+							     &surface->source_region_id);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -590,7 +630,10 @@ _cairo_analysis_surface_fill (void			*abstract_surface,
 					       &extents);
     if (backend_status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
 	cairo_rectangle_int_t rec_extents;
-	backend_status = _analyze_recording_surface_pattern (surface, source, &rec_extents);
+	backend_status = _analyze_recording_surface_pattern (surface,
+							     source,
+							     &rec_extents,
+							     &surface->source_region_id);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -619,6 +662,9 @@ _cairo_analysis_surface_show_glyphs (void		  *abstract_surface,
     cairo_int_status_t	     status, backend_status;
     cairo_rectangle_int_t    extents, glyph_extents;
 
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
+
     /* Adapted from _cairo_surface_show_glyphs */
     if (surface->target->backend->show_glyphs != NULL) {
 	backend_status =
@@ -654,7 +700,10 @@ _cairo_analysis_surface_show_glyphs (void		  *abstract_surface,
 					       &extents);
     if (backend_status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
 	cairo_rectangle_int_t rec_extents;
-	backend_status = _analyze_recording_surface_pattern (surface, source, &rec_extents);
+	backend_status = _analyze_recording_surface_pattern (surface,
+							     source,
+							     &rec_extents,
+							     &surface->source_region_id);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -699,6 +748,9 @@ _cairo_analysis_surface_show_text_glyphs (void			    *abstract_surface,
     cairo_int_status_t	     status, backend_status;
     cairo_rectangle_int_t    extents, glyph_extents;
 
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
+
     /* Adapted from _cairo_surface_show_glyphs */
     backend_status = CAIRO_INT_STATUS_UNSUPPORTED;
     if (surface->target->backend->show_text_glyphs != NULL) {
@@ -732,7 +784,10 @@ _cairo_analysis_surface_show_text_glyphs (void			    *abstract_surface,
 					       &extents);
     if (backend_status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
 	cairo_rectangle_int_t rec_extents;
-	backend_status = _analyze_recording_surface_pattern (surface, source, &rec_extents);
+	_analyze_recording_surface_pattern (surface,
+					    source,
+					    &rec_extents,
+					    &surface->source_region_id);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -760,6 +815,8 @@ _cairo_analysis_surface_tag (void	                *abstract_surface,
     cairo_analysis_surface_t *surface = abstract_surface;
     cairo_int_status_t	     backend_status;
 
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
     backend_status = CAIRO_INT_STATUS_SUCCESS;
     if (surface->target->backend->tag != NULL) {
 	backend_status =
@@ -812,7 +869,8 @@ static const cairo_surface_backend_t cairo_analysis_surface_backend = {
 };
 
 cairo_surface_t *
-_cairo_analysis_surface_create (cairo_surface_t		*target)
+_cairo_analysis_surface_create (cairo_surface_t		*target,
+				cairo_bool_t             create_region_ids)
 {
     cairo_analysis_surface_t *surface;
     cairo_status_t status;
@@ -841,6 +899,10 @@ _cairo_analysis_surface_create (cairo_surface_t		*target)
     surface->has_supported = FALSE;
     surface->has_unsupported = FALSE;
 
+    surface->create_region_ids = create_region_ids;
+    surface->source_region_id = 0;
+    surface->mask_region_id = 0;
+
     _cairo_region_init (&surface->supported_region);
     _cairo_region_init (&surface->fallback_region);
 
@@ -918,6 +980,23 @@ _cairo_analysis_surface_get_bounding_box (cairo_surface_t *abstract_surface,
     *bbox = surface->page_bbox;
 }
 
+unsigned int
+_cairo_analysis_surface_get_source_region_id (cairo_surface_t *abstract_surface)
+{
+    cairo_analysis_surface_t	*surface = (cairo_analysis_surface_t *) abstract_surface;
+
+    return surface->source_region_id;
+}
+
+unsigned int
+_cairo_analysis_surface_get_mask_region_id (cairo_surface_t *abstract_surface)
+{
+    cairo_analysis_surface_t	*surface = (cairo_analysis_surface_t *) abstract_surface;
+
+    return surface->mask_region_id;
+}
+
+
 /* null surface type: a surface that does nothing (has no side effects, yay!) */
 
 static cairo_int_status_t
diff --git a/src/cairo-debug.c b/src/cairo-debug.c
index a314eefbf..c83df3f47 100644
--- a/src/cairo-debug.c
+++ b/src/cairo-debug.c
@@ -319,3 +319,101 @@ _cairo_debug_print_rect (FILE *file, const cairo_rectangle_int_t *rect)
 	     rect->x, rect->y,
 	     rect->width, rect->height);
 }
+
+const char *
+_cairo_debug_operator_to_string (cairo_operator_t op)
+{
+    switch (op) {
+        case CAIRO_OPERATOR_CLEAR: return "CLEAR";
+        case CAIRO_OPERATOR_SOURCE: return "SOURCE";
+        case CAIRO_OPERATOR_OVER: return "OVER";
+        case CAIRO_OPERATOR_IN: return "IN";
+        case CAIRO_OPERATOR_OUT: return "OUT";
+        case CAIRO_OPERATOR_ATOP: return "ATOP";
+        case CAIRO_OPERATOR_DEST: return "DEST";
+        case CAIRO_OPERATOR_DEST_OVER: return "DEST_OVER";
+        case CAIRO_OPERATOR_DEST_IN: return "DEST_IN";
+        case CAIRO_OPERATOR_DEST_OUT: return "DEST_OUT";
+        case CAIRO_OPERATOR_DEST_ATOP: return "DEST_ATOP";
+        case CAIRO_OPERATOR_XOR: return "XOR";
+        case CAIRO_OPERATOR_ADD: return "ADD";
+        case CAIRO_OPERATOR_SATURATE: return "SATURATE";
+        case CAIRO_OPERATOR_MULTIPLY: return "MULTIPLY";
+        case CAIRO_OPERATOR_SCREEN: return "SCREEN";
+        case CAIRO_OPERATOR_OVERLAY: return "OVERLAY";
+        case CAIRO_OPERATOR_DARKEN: return "DARKEN";
+        case CAIRO_OPERATOR_LIGHTEN: return "LIGHTEN";
+        case CAIRO_OPERATOR_COLOR_DODGE: return "COLOR_DODGE";
+        case CAIRO_OPERATOR_COLOR_BURN: return "COLOR_BURN";
+        case CAIRO_OPERATOR_HARD_LIGHT: return "HARD_LIGHT";
+        case CAIRO_OPERATOR_SOFT_LIGHT: return "SOFT_LIGHT";
+        case CAIRO_OPERATOR_DIFFERENCE: return "DIFFERENCE";
+        case CAIRO_OPERATOR_EXCLUSION: return "EXCLUSION";
+        case CAIRO_OPERATOR_HSL_HUE: return "HSL_HUE";
+        case CAIRO_OPERATOR_HSL_SATURATION: return "HSL_SATURATION";
+        case CAIRO_OPERATOR_HSL_COLOR: return "HSL_COLOR";
+        case CAIRO_OPERATOR_HSL_LUMINOSITY: return "HSL_LUMINOSITY";
+    }
+    return "UNKNOWN";
+}
+
+const char *
+_cairo_debug_status_to_string (cairo_int_status_t status)
+{
+    switch (status) {
+	case CAIRO_INT_STATUS_SUCCESS: return "SUCCESS";
+	case CAIRO_INT_STATUS_NO_MEMORY: return "NO_MEMORY";
+	case CAIRO_INT_STATUS_INVALID_RESTORE: return "INVALID_RESTORE";
+	case CAIRO_INT_STATUS_INVALID_POP_GROUP: return "INVALID_POP_GROUP";
+	case CAIRO_INT_STATUS_NO_CURRENT_POINT: return "NO_CURRENT_POINT";
+	case CAIRO_INT_STATUS_INVALID_MATRIX: return "INVALID_MATRIX";
+	case CAIRO_INT_STATUS_INVALID_STATUS: return "INVALID_STATUS";
+	case CAIRO_INT_STATUS_NULL_POINTER: return "NULL_POINTER";
+	case CAIRO_INT_STATUS_INVALID_STRING: return "INVALID_STRING";
+	case CAIRO_INT_STATUS_INVALID_PATH_DATA: return "INVALID_PATH_DATA";
+	case CAIRO_INT_STATUS_READ_ERROR: return "READ_ERROR";
+	case CAIRO_INT_STATUS_WRITE_ERROR: return "WRITE_ERROR";
+	case CAIRO_INT_STATUS_SURFACE_FINISHED: return "SURFACE_FINISHED";
+	case CAIRO_INT_STATUS_SURFACE_TYPE_MISMATCH: return "SURFACE_TYPE_MISMATCH";
+	case CAIRO_INT_STATUS_PATTERN_TYPE_MISMATCH: return "PATTERN_TYPE_MISMATCH";
+	case CAIRO_INT_STATUS_INVALID_CONTENT: return "INVALID_CONTENT";
+	case CAIRO_INT_STATUS_INVALID_FORMAT: return "INVALID_FORMAT";
+	case CAIRO_INT_STATUS_INVALID_VISUAL: return "INVALID_VISUAL";
+	case CAIRO_INT_STATUS_FILE_NOT_FOUND: return "FILE_NOT_FOUND";
+	case CAIRO_INT_STATUS_INVALID_DASH: return "INVALID_DASH";
+	case CAIRO_INT_STATUS_INVALID_DSC_COMMENT: return "INVALID_DSC_COMMENT";
+	case CAIRO_INT_STATUS_INVALID_INDEX: return "INVALID_INDEX";
+	case CAIRO_INT_STATUS_CLIP_NOT_REPRESENTABLE: return "CLIP_NOT_REPRESENTABLE";
+	case CAIRO_INT_STATUS_TEMP_FILE_ERROR: return "TEMP_FILE_ERROR";
+	case CAIRO_INT_STATUS_INVALID_STRIDE: return "INVALID_STRIDE";
+	case CAIRO_INT_STATUS_FONT_TYPE_MISMATCH: return "FONT_TYPE_MISMATCH";
+	case CAIRO_INT_STATUS_USER_FONT_IMMUTABLE: return "USER_FONT_IMMUTABLE";
+	case CAIRO_INT_STATUS_USER_FONT_ERROR: return "USER_FONT_ERROR";
+	case CAIRO_INT_STATUS_NEGATIVE_COUNT: return "NEGATIVE_COUNT";
+	case CAIRO_INT_STATUS_INVALID_CLUSTERS: return "INVALID_CLUSTERS";
+	case CAIRO_INT_STATUS_INVALID_SLANT: return "INVALID_SLANT";
+	case CAIRO_INT_STATUS_INVALID_WEIGHT: return "INVALID_WEIGHT";
+	case CAIRO_INT_STATUS_INVALID_SIZE: return "INVALID_SIZE";
+	case CAIRO_INT_STATUS_USER_FONT_NOT_IMPLEMENTED: return "USER_FONT_NOT_IMPLEMENTED";
+	case CAIRO_INT_STATUS_DEVICE_TYPE_MISMATCH: return "DEVICE_TYPE_MISMATCH";
+	case CAIRO_INT_STATUS_DEVICE_ERROR: return "DEVICE_ERROR";
+	case CAIRO_INT_STATUS_INVALID_MESH_CONSTRUCTION: return "INVALID_MESH_CONSTRUCTION";
+	case CAIRO_INT_STATUS_DEVICE_FINISHED: return "DEVICE_FINISHED";
+	case CAIRO_INT_STATUS_JBIG2_GLOBAL_MISSING: return "JBIG2_GLOBAL_MISSING";
+	case CAIRO_INT_STATUS_PNG_ERROR: return "PNG_ERROR";
+	case CAIRO_INT_STATUS_FREETYPE_ERROR: return "FREETYPE_ERROR";
+	case CAIRO_INT_STATUS_WIN32_GDI_ERROR: return "WIN32_GDI_ERROR";
+	case CAIRO_INT_STATUS_TAG_ERROR: return "TAG_ERROR";
+	case CAIRO_INT_STATUS_DWRITE_ERROR: return "DWRITE_ERROR";
+
+	case CAIRO_INT_STATUS_LAST_STATUS: return "LAST_STATUS";
+
+	case CAIRO_INT_STATUS_UNSUPPORTED: return "UNSUPPORTED";
+	case CAIRO_INT_STATUS_DEGENERATE: return "DEGENERATE";
+	case CAIRO_INT_STATUS_NOTHING_TO_DO: return "NOTHING_TO_DO";
+	case CAIRO_INT_STATUS_FLATTEN_TRANSPARENCY: return "FLATTEN_TRANSPARENCY";
+	case CAIRO_INT_STATUS_IMAGE_FALLBACK: return "IMAGE_FALLBACK";
+	case CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN: return "ANALYZE_RECORDING_SURFACE_PATTERN";
+    }
+    return "UNKNOWN";
+}
diff --git a/src/cairo-image-source.c b/src/cairo-image-source.c
index b8c1c88f5..aafdaeded 100644
--- a/src/cairo-image-source.c
+++ b/src/cairo-image-source.c
@@ -1225,7 +1225,7 @@ _pixman_image_for_recording (cairo_image_surface_t *dst,
 
     /* Handle recursion by returning future reads from the current image */
     proxy = attach_proxy (source, clone);
-    status = _cairo_recording_surface_replay_with_clip (source, m, clone, NULL);
+    status = _cairo_recording_surface_replay_with_clip (source, m, clone, NULL, FALSE);
     if (clone->foreground_used)
 	dst->base.foreground_used = clone->foreground_used;
     detach_proxy (source, proxy);
diff --git a/src/cairo-paginated-surface.c b/src/cairo-paginated-surface.c
index 07d86d070..278c1a641 100644
--- a/src/cairo-paginated-surface.c
+++ b/src/cairo-paginated-surface.c
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
 /* cairo - a vector graphics library with display and print output
  *
  * Copyright © 2005 Red Hat, Inc
@@ -401,11 +402,12 @@ _paint_page (cairo_paginated_surface_t *surface)
     cairo_surface_t *analysis;
     cairo_int_status_t status;
     cairo_bool_t has_supported, has_page_fallback, has_finegrained_fallback;
+    unsigned int regions_id = 0;
 
     if (unlikely (surface->target->status))
 	return surface->target->status;
 
-    analysis = _cairo_analysis_surface_create (surface->target);
+    analysis = _cairo_analysis_surface_create (surface->target, TRUE);
     if (unlikely (analysis->status))
 	return _cairo_surface_set_error (surface->target, analysis->status);
 
@@ -414,21 +416,26 @@ _paint_page (cairo_paginated_surface_t *surface)
     if (unlikely (status))
 	goto FAIL;
 
+    status = _cairo_recording_surface_region_array_attach (surface->recording_surface, &regions_id);
+    if (status)
+	goto FAIL;
+
     status = _cairo_recording_surface_replay_and_create_regions (surface->recording_surface,
+                                                                 regions_id,
 								 NULL, analysis, FALSE);
     if (status)
 	goto FAIL;
 
     assert (analysis->status == CAIRO_STATUS_SUCCESS);
 
-     if (surface->backend->set_bounding_box) {
-	 cairo_box_t bbox;
+    if (surface->backend->set_bounding_box) {
+        cairo_box_t bbox;
 
-	 _cairo_analysis_surface_get_bounding_box (analysis, &bbox);
-	 status = surface->backend->set_bounding_box (surface->target, &bbox);
-	 if (unlikely (status))
-	     goto FAIL;
-     }
+        _cairo_analysis_surface_get_bounding_box (analysis, &bbox);
+        status = surface->backend->set_bounding_box (surface->target, &bbox);
+        if (unlikely (status))
+            goto FAIL;
+    }
 
     if (surface->backend->set_fallback_images_required) {
 	cairo_bool_t has_fallbacks = _cairo_analysis_surface_has_unsupported (analysis);
@@ -467,6 +474,7 @@ _paint_page (cairo_paginated_surface_t *surface)
 	    goto FAIL;
 
 	status = _cairo_recording_surface_replay_region (surface->recording_surface,
+                                                         regions_id,
 							 NULL,
 							 surface->target,
 							 CAIRO_RECORDING_REGION_NATIVE);
@@ -525,6 +533,9 @@ _paint_page (cairo_paginated_surface_t *surface)
     }
 
   FAIL:
+    if (regions_id)
+        _cairo_recording_surface_region_array_remove (surface->recording_surface, regions_id);
+
     cairo_surface_destroy (analysis);
 
     return _cairo_surface_set_error (surface->target, status);
diff --git a/src/cairo-pattern-private.h b/src/cairo-pattern-private.h
index f6138fb70..0a5e41e8b 100644
--- a/src/cairo-pattern-private.h
+++ b/src/cairo-pattern-private.h
@@ -87,6 +87,12 @@ typedef struct _cairo_surface_pattern {
     cairo_pattern_t base;
 
     cairo_surface_t *surface;
+
+    /* This field is only used by the wrapper surface for retreiving
+     * the region id from the target during create regions and passing
+     * the region id to the target surface during playback.
+     */
+    unsigned int region_array_id;
 } cairo_surface_pattern_t;
 
 typedef struct _cairo_gradient_stop {
diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c
index 64db0a336..070309ac6 100644
--- a/src/cairo-pattern.c
+++ b/src/cairo-pattern.c
@@ -561,6 +561,7 @@ _cairo_pattern_init_for_surface (cairo_surface_pattern_t *pattern,
     _cairo_pattern_init (&pattern->base, CAIRO_PATTERN_TYPE_SURFACE);
 
     pattern->surface = cairo_surface_reference (surface);
+    pattern->region_array_id = 0;
 }
 
 static void
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 87f5ffa25..0781d9c53 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -95,6 +95,7 @@ typedef struct _cairo_pdf_source_surface_entry {
 typedef struct _cairo_pdf_source_surface {
     cairo_pattern_type_t type;
     cairo_surface_t *surface;
+    unsigned int region_id;
     cairo_pattern_t *raster_pattern;
     cairo_pdf_source_surface_entry_t *hash_entry;
 } cairo_pdf_source_surface_t;
@@ -279,9 +280,9 @@ struct _cairo_pdf_surface {
     cairo_array_t pages;
     cairo_array_t rgb_linear_functions;
     cairo_array_t alpha_linear_functions;
-    cairo_array_t page_patterns;
-    cairo_array_t page_surfaces;
-    cairo_array_t doc_surfaces;
+    cairo_array_t page_patterns; /* cairo_pdf_pattern_t */
+    cairo_array_t page_surfaces; /* cairo_pdf_source_surface_t */
+    cairo_array_t doc_surfaces; /* cairo_pdf_source_surface_t */
     cairo_hash_table_t *all_surfaces;
     cairo_array_t smask_groups;
     cairo_array_t knockout_group;
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 6fcd14f02..e3a27ed81 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -54,6 +54,7 @@
 #include "cairo-error-private.h"
 #include "cairo-image-surface-inline.h"
 #include "cairo-image-info-private.h"
+#include "cairo-recording-surface-inline.h"
 #include "cairo-recording-surface-private.h"
 #include "cairo-output-stream-private.h"
 #include "cairo-paginated-private.h"
@@ -1014,7 +1015,13 @@ _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
     size = _cairo_array_num_elements (&surface->page_surfaces);
     for (i = 0; i < size; i++) {
 	src_surface = (cairo_pdf_source_surface_t *) _cairo_array_index (&surface->page_surfaces, i);
-	cairo_surface_destroy (src_surface->surface);
+	if (src_surface->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) {
+	    cairo_pattern_destroy (src_surface->raster_pattern);
+	} else {
+	    if (_cairo_surface_is_recording (src_surface->surface) && src_surface->region_id != 0)
+		_cairo_recording_surface_region_array_remove (src_surface->surface, src_surface->region_id);
+	    cairo_surface_destroy (src_surface->surface);
+	}
     }
     _cairo_array_truncate (&surface->page_surfaces, 0);
 
@@ -1723,6 +1730,7 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
     _cairo_pdf_source_surface_init_key (surface_entry);
 
     src_surface.hash_entry = surface_entry;
+    src_surface.region_id = 0;
     if (source_pattern && source_pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) {
 	src_surface.type = CAIRO_PATTERN_TYPE_RASTER_SOURCE;
 	src_surface.surface = NULL;
@@ -1734,6 +1742,16 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 	src_surface.type = CAIRO_PATTERN_TYPE_SURFACE;
 	src_surface.surface = cairo_surface_reference (source_surface);
 	src_surface.raster_pattern = NULL;
+	if (source_pattern) {
+	    cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source_pattern;
+	    src_surface.region_id = surface_pattern->region_array_id;
+	    if (_cairo_surface_is_recording (surface_pattern->surface) &&
+		surface_pattern->region_array_id != 0)
+	    {
+		_cairo_recording_surface_region_array_reference (surface_pattern->surface,
+								 surface_pattern->region_array_id);
+	    }
+	}
     }
 
     surface_entry->surface_res = _cairo_pdf_surface_new_object (surface);
@@ -2593,7 +2611,13 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     size = _cairo_array_num_elements (&surface->doc_surfaces);
     for (i = 0; i < size; i++) {
 	_cairo_array_copy_element (&surface->doc_surfaces, i, &doc_surface);
-	cairo_surface_destroy (doc_surface.surface);
+	if (doc_surface.type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) {
+	    cairo_pattern_destroy (doc_surface.raster_pattern);
+	} else {
+	    if (_cairo_surface_is_recording (doc_surface.surface) && doc_surface.region_id != 0)
+		_cairo_recording_surface_region_array_remove (doc_surface.surface, doc_surface.region_id);
+	    cairo_surface_destroy (doc_surface.surface);
+	}
     }
     _cairo_array_fini (&surface->doc_surfaces);
     _cairo_hash_table_foreach (surface->all_surfaces,
@@ -3720,6 +3744,7 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
     }
 
     status = _cairo_recording_surface_replay_region (source,
+						     pdf_source->region_id,
 						     is_subsurface ? extents : NULL,
 						     &surface->base,
 						     CAIRO_RECORDING_REGION_NATIVE);
diff --git a/src/cairo-ps-surface-private.h b/src/cairo-ps-surface-private.h
index f18403190..e825b57fd 100644
--- a/src/cairo-ps-surface-private.h
+++ b/src/cairo-ps-surface-private.h
@@ -56,6 +56,7 @@ typedef struct _cairo_ps_form {
     cairo_bool_t is_image;
     int id;
     cairo_surface_t *src_surface;
+    unsigned int regions_id;
     cairo_rectangle_int_t src_surface_extents;
     cairo_bool_t src_surface_bounded;
     cairo_filter_t filter;
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index beccc0151..a5008df88 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -66,18 +66,19 @@
 #include "cairo-composite-rectangles-private.h"
 #include "cairo-default-context-private.h"
 #include "cairo-error-private.h"
+#include "cairo-image-info-private.h"
 #include "cairo-image-surface-inline.h"
 #include "cairo-list-inline.h"
-#include "cairo-scaled-font-subsets-private.h"
+#include "cairo-output-stream-private.h"
 #include "cairo-paginated-private.h"
+#include "cairo-recording-surface-inline.h"
 #include "cairo-recording-surface-private.h"
+#include "cairo-scaled-font-subsets-private.h"
 #include "cairo-surface-clipper-private.h"
 #include "cairo-surface-snapshot-inline.h"
 #include "cairo-surface-subsurface-private.h"
-#include "cairo-output-stream-private.h"
-#include "cairo-type3-glyph-surface-private.h"
-#include "cairo-image-info-private.h"
 #include "cairo-tag-attributes-private.h"
+#include "cairo-type3-glyph-surface-private.h"
 
 #include <stdio.h>
 #include <ctype.h>
@@ -176,6 +177,7 @@ typedef enum {
 typedef struct  {
     /* input params */
     cairo_surface_t *src_surface;
+    unsigned int regions_id;
     cairo_operator_t op;
     const cairo_rectangle_int_t *src_surface_extents;
     cairo_bool_t src_surface_bounded;
@@ -286,6 +288,8 @@ _cairo_ps_form_pluck (void *entry, void *closure)
 
     _cairo_hash_table_remove (patterns, &surface_entry->base);
     free (surface_entry->unique_id);
+    if (_cairo_surface_is_recording (surface_entry->src_surface) && surface_entry->regions_id != 0)
+	_cairo_recording_surface_region_array_remove (surface_entry->src_surface, surface_entry->regions_id);
     cairo_surface_destroy (surface_entry->src_surface);
     free (surface_entry);
 }
@@ -3308,6 +3312,7 @@ _cairo_ps_surface_emit_eps (cairo_ps_surface_t          *surface,
 static cairo_status_t
 _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
 					  cairo_surface_t             *recording_surface,
+					  unsigned int                 regions_id,
 					  const cairo_rectangle_int_t *recording_extents,
 					  cairo_bool_t                 subsurface)
 {
@@ -3378,6 +3383,7 @@ _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
     }
 
     status = _cairo_recording_surface_replay_region (recording_surface,
+						     regions_id,
 						     subsurface ? recording_extents : NULL,
 						     &surface->base,
 						     CAIRO_RECORDING_REGION_NATIVE);
@@ -3530,6 +3536,9 @@ _cairo_ps_surface_use_form (cairo_ps_surface_t           *surface,
     source_entry->unique_id = unique_id;
     source_entry->id = surface->num_forms++;
     source_entry->src_surface = cairo_surface_reference (params->src_surface);
+    source_entry->regions_id = params->regions_id;
+    if (_cairo_surface_is_recording (source_entry->src_surface) && source_entry->regions_id != 0)
+	_cairo_recording_surface_region_array_reference (source_entry->src_surface, source_entry->regions_id);
     source_entry->src_surface_extents = *params->src_surface_extents;
     source_entry->src_surface_bounded = params->src_surface_bounded;
     source_entry->required_extents = *params->src_op_extents;
@@ -3662,11 +3671,13 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t          *surface,
 	    cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) params->src_surface;
 	    status = _cairo_ps_surface_emit_recording_surface (surface,
 							       sub->target,
+							       params->regions_id,
 							       &sub->extents,
 							       TRUE);
 	} else {
 	    status = _cairo_ps_surface_emit_recording_surface (surface,
 							       params->src_surface,
+							       params->regions_id,
 							       params->src_op_extents,
 							       FALSE);
 	}
@@ -3709,6 +3720,7 @@ _cairo_ps_form_emit (void *entry, void *closure)
     cairo_output_stream_t *old_stream;
 
     params.src_surface = form->src_surface;
+    params.regions_id = form->regions_id;
     params.op = CAIRO_OPERATOR_OVER;
     params.src_surface_extents = &form->src_surface_extents;
     params.src_surface_bounded = form->src_surface_bounded;
@@ -3850,11 +3862,17 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
     cairo_path_fixed_t path;
     cairo_emit_surface_params_t params;
     cairo_image_surface_t *image = NULL;
+    unsigned int region_id = 0;
 
     status = _cairo_pdf_operators_flush (&surface->pdf_operators);
     if (unlikely (status))
 	return status;
 
+    if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern;
+	region_id = surface_pattern->region_array_id;
+    }
+
     status = _cairo_ps_surface_acquire_source_surface_from_pattern (surface,
 								    pattern,
 								    extents,
@@ -3940,6 +3958,7 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
     cairo_matrix_translate (&ps_p2d, x_offset, y_offset);
 
     params.src_surface = image ? &image->base : source_surface;
+    params.regions_id = image ? 0 : region_id;
     params.op = op;
     params.src_surface_extents = &src_surface_extents;
     params.src_surface_bounded = src_surface_bounded;
@@ -3994,12 +4013,18 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
     cairo_rectangle_int_t src_op_extents;
     cairo_emit_surface_params_t params;
     cairo_extend_t extend = cairo_pattern_get_extend (pattern);
+    unsigned int region_id = 0;
 
     cairo_p2d = pattern->matrix;
     status = cairo_matrix_invert (&cairo_p2d);
     /* cairo_pattern_set_matrix ensures the matrix is invertible */
     assert (status == CAIRO_STATUS_SUCCESS);
 
+    if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern;
+	region_id = surface_pattern->region_array_id;
+    }
+
     status = _cairo_ps_surface_acquire_source_surface_from_pattern (surface,
 								    pattern,
 								    extents,
@@ -4093,6 +4118,7 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
     old_paint_proc = surface->paint_proc;
     surface->paint_proc = TRUE;
     params.src_surface = image ? &image->base : source_surface;
+    params.regions_id = image ? 0 : region_id;
     params.op = op;
     params.src_surface_extents = &pattern_extents;
     params.src_surface_bounded = bounded;
diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c
index aed2cced6..dbb6aa46f 100644
--- a/src/cairo-quartz-surface.c
+++ b/src/cairo-quartz-surface.c
@@ -735,7 +735,8 @@ _cairo_surface_to_cgimage (cairo_surface_t       *source,
 	status = _cairo_recording_surface_replay_with_clip (source,
 							    matrix,
 							    &image_surface->base,
-							    NULL);
+							    NULL,
+							    FALSE);
 	if (unlikely (status)) {
 	    cairo_surface_destroy (&image_surface->base);
 	    return status;
diff --git a/src/cairo-recording-surface-private.h b/src/cairo-recording-surface-private.h
index 3d325383b..7d4de1ed9 100644
--- a/src/cairo-recording-surface-private.h
+++ b/src/cairo-recording-surface-private.h
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
 /* cairo - a vector graphics library with display and print output
  *
  * Copyright © 2005 Red Hat, Inc
@@ -55,14 +56,19 @@ typedef enum {
 } cairo_command_type_t;
 
 typedef enum {
-    CAIRO_RECORDING_REGION_ALL,
+    CAIRO_RECORDING_REGION_ALL = 0,
     CAIRO_RECORDING_REGION_NATIVE,
     CAIRO_RECORDING_REGION_IMAGE_FALLBACK
 } cairo_recording_region_type_t;
 
+typedef enum {
+    CAIRO_RECORDING_REPLAY,
+    CAIRO_RECORDING_CREATE_REGIONS,
+    CAIRO_RECORDING_REPLAY_REGION
+} cairo_recording_replay_type_t;
+
 typedef struct _cairo_command_header {
     cairo_command_type_t	 type;
-    cairo_recording_region_type_t region;
     cairo_operator_t		 op;
     cairo_rectangle_int_t	 extents;
     cairo_clip_t		*clip;
@@ -155,8 +161,27 @@ typedef struct _cairo_recording_surface {
 	struct bbtree *left, *right;
 	cairo_command_header_t *chain;
     } bbtree;
+
+    /* The mutex protects modification to all subsequent fields. */
+    cairo_mutex_t mutex;
+
+    cairo_list_t region_array_list;
+
 } cairo_recording_surface_t;
 
+typedef struct _cairo_recording_region_element {
+    cairo_recording_region_type_t region;
+    unsigned int source_id;
+    unsigned int mask_id;
+} cairo_recording_region_element_t;
+
+typedef struct _cairo_recording_region_array {
+    unsigned int id;
+    cairo_reference_count_t ref_count;
+    cairo_array_t regions; /* cairo_recording_region_element_t */
+    cairo_list_t link;
+} cairo_recording_regions_array_t;
+
 slim_hidden_proto (cairo_recording_surface_create);
 
 cairo_private cairo_int_status_t
@@ -182,18 +207,21 @@ cairo_private cairo_status_t
 _cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
 					   const cairo_matrix_t *surface_transform,
 					   cairo_surface_t *target,
-					   const cairo_clip_t *target_clip);
+					   const cairo_clip_t *target_clip,
+                                           cairo_bool_t surface_is_unbounded);
 
 cairo_private cairo_status_t
-_cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
+_cairo_recording_surface_replay_and_create_regions (cairo_surface_t      *surface,
+                                                    unsigned int          regions_id,
 						    const cairo_matrix_t *surface_transform,
-						    cairo_surface_t *target,
-						    cairo_bool_t surface_is_unbounded);
+						    cairo_surface_t      *target,
+						    cairo_bool_t          surface_is_unbounded);
 cairo_private cairo_status_t
 _cairo_recording_surface_replay_region (cairo_surface_t			*surface,
-					const cairo_rectangle_int_t *surface_extents,
+                                        unsigned int                     regions_id,
+					const cairo_rectangle_int_t     *surface_extents,
 					cairo_surface_t			*target,
-					cairo_recording_region_type_t	region);
+					cairo_recording_region_type_t	 region);
 
 cairo_private cairo_status_t
 _cairo_recording_surface_get_bbox (cairo_recording_surface_t *recording,
@@ -211,4 +239,23 @@ _cairo_recording_surface_has_only_bilevel_alpha (cairo_recording_surface_t *surf
 cairo_private cairo_bool_t
 _cairo_recording_surface_has_only_op_over (cairo_recording_surface_t *surface);
 
+cairo_private cairo_status_t
+_cairo_recording_surface_region_array_attach (cairo_surface_t *surface,
+                                              unsigned int    *id);
+
+cairo_private void
+_cairo_recording_surface_region_array_reference (cairo_surface_t *surface,
+                                                 unsigned int     id);
+
+cairo_private void
+_cairo_recording_surface_region_array_remove (cairo_surface_t *surface,
+                                              unsigned int     id);
+
+cairo_private void
+_cairo_debug_print_recording_surface (FILE            *file,
+				      cairo_surface_t *surface,
+                                      unsigned int     regions_id,
+				      int              indent,
+				      cairo_bool_t     recurse);
+
 #endif /* CAIRO_RECORDING_SURFACE_H */
diff --git a/src/cairo-recording-surface.c b/src/cairo-recording-surface.c
index f8bac4b2e..a7de8df5b 100644
--- a/src/cairo-recording-surface.c
+++ b/src/cairo-recording-surface.c
@@ -86,16 +86,12 @@
 #include "cairo-default-context-private.h"
 #include "cairo-error-private.h"
 #include "cairo-image-surface-private.h"
+#include "cairo-list-inline.h"
 #include "cairo-recording-surface-inline.h"
 #include "cairo-surface-snapshot-inline.h"
 #include "cairo-surface-wrapper-private.h"
 #include "cairo-traps-private.h"
 
-typedef enum {
-    CAIRO_RECORDING_REPLAY,
-    CAIRO_RECORDING_CREATE_REGIONS
-} cairo_recording_replay_type_t;
-
 typedef struct _cairo_recording_surface_replay_params {
     const cairo_rectangle_int_t *surface_extents;
     const cairo_matrix_t *surface_transform;
@@ -104,6 +100,7 @@ typedef struct _cairo_recording_surface_replay_params {
     cairo_bool_t surface_is_unbounded;
     cairo_recording_replay_type_t type;
     cairo_recording_region_type_t region;
+    unsigned int regions_id;
     const cairo_color_t *foreground_color;
     cairo_bool_t foreground_used;
 } cairo_recording_surface_replay_params_t;
@@ -437,6 +434,10 @@ cairo_recording_surface_create (cairo_content_t		 content,
     surface->has_bilevel_alpha = FALSE;
     surface->has_only_op_over = FALSE;
 
+    CAIRO_MUTEX_INIT (surface->mutex);
+
+    cairo_list_init (&surface->region_array_list);
+
     return &surface->base;
 }
 slim_hidden_def (cairo_recording_surface_create);
@@ -454,12 +455,88 @@ _cairo_recording_surface_create_similar (void		       *abstract_surface,
     return cairo_recording_surface_create (content, &extents);
 }
 
+static void
+destroy_pattern_region_array (const cairo_pattern_t *pattern,
+			      unsigned int region_id)
+{
+    if (region_id != 0) {
+	if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	    cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern;
+	    if (_cairo_surface_is_recording (surface_pattern->surface))
+		_cairo_recording_surface_region_array_remove (surface_pattern->surface, region_id);
+	}
+    }
+}
+
+static void
+_cairo_recording_surface_region_array_destroy (cairo_recording_surface_t       *surface,
+					       cairo_recording_regions_array_t *region_array)
+{
+    cairo_command_t **elements;
+    cairo_recording_region_element_t *region_elements;
+    int i, num_elements;
+
+    num_elements = surface->commands.num_elements;
+    elements = _cairo_array_index (&surface->commands, 0);
+    region_elements = _cairo_array_index (&region_array->regions, 0);
+    for (i = 0; i < num_elements; i++) {
+	cairo_command_t *command = elements[i];
+	cairo_recording_region_element_t *region_element = &region_elements[i];
+
+	switch (command->header.type) {
+	    case CAIRO_COMMAND_PAINT:
+		destroy_pattern_region_array (&command->paint.source.base, region_element->source_id);
+		break;
+
+	    case CAIRO_COMMAND_MASK:
+		destroy_pattern_region_array (&command->mask.source.base, region_element->source_id);
+		destroy_pattern_region_array (&command->mask.mask.base, region_element->mask_id);
+		break;
+
+	    case CAIRO_COMMAND_STROKE:
+		destroy_pattern_region_array (&command->stroke.source.base, region_element->source_id);
+		break;
+
+	    case CAIRO_COMMAND_FILL:
+		destroy_pattern_region_array (&command->fill.source.base, region_element->source_id);
+		break;
+
+	    case CAIRO_COMMAND_SHOW_TEXT_GLYPHS:
+		destroy_pattern_region_array (&command->show_text_glyphs.source.base, region_element->source_id);
+		break;
+
+	    case CAIRO_COMMAND_TAG:
+		break;
+
+	    default:
+		ASSERT_NOT_REACHED;
+	}
+    }
+
+    _cairo_array_fini (&region_array->regions);
+    free (region_array);
+}
+
 static cairo_status_t
 _cairo_recording_surface_finish (void *abstract_surface)
 {
     cairo_recording_surface_t *surface = abstract_surface;
     cairo_command_t **elements;
     int i, num_elements;
+    cairo_recording_regions_array_t *region_array, *region_next;
+
+    /* Normally backend surfaces hold a reference to the surface as
+     * well as the region and free the region before the surface. So
+     * the regions should already be freed at this point but just in
+     * case we ensure the regions are freed before destroying the
+     * surface. */
+    cairo_list_foreach_entry_safe (region_array, region_next,
+				   cairo_recording_regions_array_t,
+				   &surface->region_array_list, link)
+    {
+	cairo_list_del (&region_array->link);
+	_cairo_recording_surface_region_array_destroy (surface, region_array);
+    }
 
     num_elements = surface->commands.num_elements;
     elements = _cairo_array_index (&surface->commands, 0);
@@ -660,7 +737,6 @@ _command_init (cairo_recording_surface_t *surface,
 
     command->type = type;
     command->op = op;
-    command->region = CAIRO_RECORDING_REGION_ALL;
 
     command->extents = composite ? composite->unbounded : _cairo_empty_rectangle;
     command->chain = NULL;
@@ -1162,7 +1238,6 @@ _command_init_copy (cairo_recording_surface_t *surface,
 {
     dst->type = src->type;
     dst->op = src->op;
-    dst->region = CAIRO_RECORDING_REGION_ALL;
 
     dst->extents = src->extents;
     dst->chain = NULL;
@@ -1561,6 +1636,10 @@ _cairo_recording_surface_snapshot (void *abstract_other)
     surface->has_bilevel_alpha = other->has_bilevel_alpha;
     surface->has_only_op_over = other->has_only_op_over;
 
+    CAIRO_MUTEX_INIT (surface->mutex);
+
+    cairo_list_init (&surface->region_array_list);
+
     _cairo_array_init (&surface->commands, sizeof (cairo_command_t *));
     status = _cairo_recording_surface_copy (surface, other);
     if (unlikely (status)) {
@@ -1626,6 +1705,123 @@ static const cairo_surface_backend_t cairo_recording_surface_backend = {
     _cairo_recording_surface_tag,
 };
 
+static unsigned int
+_cairo_recording_surface_regions_allocate_unique_id (void)
+{
+    static cairo_atomic_int_t unique_id;
+
+#if CAIRO_NO_MUTEX
+    if (++unique_id == 0)
+	unique_id = 1;
+    return unique_id;
+#else
+    cairo_atomic_int_t old, id;
+
+    do {
+	old = _cairo_atomic_uint_get (&unique_id);
+	id = old + 1;
+	if (id == 0)
+	    id = 1;
+    } while (! _cairo_atomic_uint_cmpxchg (&unique_id, old, id));
+
+    return id;
+#endif
+}
+
+static cairo_recording_regions_array_t *
+_cairo_recording_surface_region_array_find (cairo_recording_surface_t *surface,
+					    unsigned int               id)
+{
+    cairo_recording_regions_array_t *regions;
+
+    cairo_list_foreach_entry (regions, cairo_recording_regions_array_t,
+			      &surface->region_array_list, link)
+    {
+	if (regions->id == id)
+	    return regions;
+    }
+
+    return NULL;
+}
+
+/* Create and initialize a new #cairo_recording_regions_array_t. Attach
+ * it to the recording surface and return its id
+ */
+cairo_status_t
+_cairo_recording_surface_region_array_attach (cairo_surface_t *abstract_surface,
+                                              unsigned int    *id)
+{
+    cairo_recording_regions_array_t *region_array;
+    cairo_recording_surface_t *surface = (cairo_recording_surface_t *) abstract_surface;
+
+    assert (_cairo_surface_is_recording (abstract_surface));
+
+    region_array = _cairo_malloc (sizeof (cairo_recording_regions_array_t));
+    if (region_array == NULL) {
+	*id = 0;
+        return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+    }
+
+    region_array->id = _cairo_recording_surface_regions_allocate_unique_id ();
+
+    CAIRO_REFERENCE_COUNT_INIT (&region_array->ref_count, 1);
+
+    _cairo_array_init (&region_array->regions, sizeof (cairo_recording_region_element_t));
+
+    CAIRO_MUTEX_LOCK (surface->mutex);
+    cairo_list_add (&region_array->link, &surface->region_array_list);
+    CAIRO_MUTEX_UNLOCK (surface->mutex);
+
+    *id = region_array->id;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+void
+_cairo_recording_surface_region_array_remove (cairo_surface_t *abstract_surface,
+                                              unsigned int     id)
+{
+    cairo_recording_regions_array_t *region_array;
+    cairo_recording_surface_t *surface = (cairo_recording_surface_t *) abstract_surface;
+
+    if (id == 0)
+	return;
+
+    assert (_cairo_surface_is_recording (abstract_surface));
+
+    CAIRO_MUTEX_LOCK (surface->mutex);
+    region_array = _cairo_recording_surface_region_array_find (surface, id);
+    if (region_array) {
+	if (_cairo_reference_count_dec_and_test (&region_array->ref_count))
+	    cairo_list_del (&region_array->link);
+	else
+	    region_array = NULL;
+    }
+
+    CAIRO_MUTEX_UNLOCK (surface->mutex);
+
+    if (region_array)
+	_cairo_recording_surface_region_array_destroy (surface, region_array);
+}
+
+void
+_cairo_recording_surface_region_array_reference (cairo_surface_t *abstract_surface,
+						 unsigned int     id)
+{
+    cairo_recording_regions_array_t *region_array;
+    cairo_recording_surface_t *surface = (cairo_recording_surface_t *) abstract_surface;
+
+    assert (_cairo_surface_is_recording (abstract_surface));
+
+    CAIRO_MUTEX_LOCK (surface->mutex);
+    region_array = _cairo_recording_surface_region_array_find (surface, id);
+    if (region_array) {
+	_cairo_reference_count_inc (&region_array->ref_count);
+    }
+
+    CAIRO_MUTEX_UNLOCK (surface->mutex);
+}
+
 cairo_int_status_t
 _cairo_recording_surface_get_path (cairo_surface_t    *abstract_surface,
 				   cairo_path_fixed_t *path)
@@ -1799,8 +1995,8 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 {
     cairo_surface_wrapper_t wrapper;
     cairo_command_t **elements;
-    cairo_bool_t replay_all =
-	params->type == CAIRO_RECORDING_CREATE_REGIONS || params->region == CAIRO_RECORDING_REGION_ALL;
+    cairo_recording_regions_array_t *regions_array = NULL;
+    cairo_recording_region_element_t *region_elements = NULL;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
     cairo_rectangle_int_t extents;
     cairo_bool_t use_indices = FALSE;
@@ -1821,6 +2017,11 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 
     assert (_cairo_surface_is_recording (&surface->base));
 
+    if (params->regions_id != 0) {
+	regions_array = _cairo_recording_surface_region_array_find (surface, params->regions_id);
+	assert (regions_array != NULL);
+    }
+
     _cairo_surface_wrapper_init (&wrapper, params->target);
     if (params->surface_extents)
 	_cairo_surface_wrapper_intersect_extents (&wrapper, params->surface_extents);
@@ -1845,18 +2046,48 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
     surface->has_only_op_over = TRUE;
 
     num_elements = surface->commands.num_elements;
+    if (regions_array) {
+	if (params->type == CAIRO_RECORDING_CREATE_REGIONS) {
+	    /* Re-running create regions with the same region id is not supported. */
+	    assert (_cairo_array_num_elements (&regions_array->regions) == 0);
+	    void *array_elems;
+	    status = _cairo_array_allocate (&regions_array->regions, num_elements, &array_elems);
+	    if (unlikely (status))
+		return status;
+
+	    /* Set regions to CAIRO_RECORDING_REGION_ALL and ids to 0 */
+	    memset (array_elems, 0, num_elements * sizeof (cairo_recording_region_element_t));
+	} else {
+	    assert (_cairo_array_num_elements (&regions_array->regions) == num_elements);
+	}
+    }
+
     elements = _cairo_array_index (&surface->commands, 0);
+    if (regions_array)
+	region_elements = _cairo_array_index (&regions_array->regions, 0);
+
     if (extents.width < r->width || extents.height < r->height) {
 	num_elements =
 	    _cairo_recording_surface_get_visible_commands (surface, &extents);
 	use_indices = num_elements != surface->commands.num_elements;
     }
 
+    cairo_bool_t target_is_analysis = _cairo_surface_is_analysis (params->target);
+
     for (i = 0; i < num_elements; i++) {
 	cairo_command_t *command = elements[use_indices ? surface->indices[i] : i];
+	cairo_recording_region_element_t *region_element = NULL;
+	unsigned int source_region_id = 0;
+	unsigned int mask_region_id = 0;
+
+	if (region_elements)
+	    region_element = &region_elements[use_indices ? surface->indices[i] : i];
 
-	if (! replay_all && command->header.region != params->region)
+	if (region_element && params->type == CAIRO_RECORDING_REPLAY_REGION &&
+            region_element->region != params->region)
+	{
 	    continue;
+	}
 
 	if (! _cairo_rectangle_intersects (&extents, &command->header.extents)) {
 	    if (command->header.type != CAIRO_COMMAND_TAG)
@@ -1865,22 +2096,35 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 
 	switch (command->header.type) {
 	case CAIRO_COMMAND_PAINT:
+	    if (region_element)
+		source_region_id = region_element->source_id;
+
 	    status = _cairo_surface_wrapper_paint (&wrapper,
 						   command->header.op,
 						   &command->paint.source.base,
+						   source_region_id,
 						   command->header.clip);
 	    if (params->type == CAIRO_RECORDING_CREATE_REGIONS) {
 		_cairo_recording_surface_merge_source_attributes (surface,
 								  command->header.op,
 								  &command->paint.source.base);
+		if (region_element && target_is_analysis)
+		    region_element->source_id = _cairo_analysis_surface_get_source_region_id (params->target);
 	    }
 	    break;
 
 	case CAIRO_COMMAND_MASK:
+	    if (region_element) {
+		source_region_id = region_element->source_id;
+		mask_region_id = region_element->mask_id;
+	    }
+
 	    status = _cairo_surface_wrapper_mask (&wrapper,
 						  command->header.op,
 						  &command->mask.source.base,
+						  source_region_id,
 						  &command->mask.mask.base,
+						  mask_region_id,
 						  command->header.clip);
 	    if (params->type == CAIRO_RECORDING_CREATE_REGIONS) {
 		_cairo_recording_surface_merge_source_attributes (surface,
@@ -1889,13 +2133,21 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		_cairo_recording_surface_merge_source_attributes (surface,
 								  command->header.op,
 								  &command->mask.mask.base);
+		if (region_element && target_is_analysis) {
+		    region_element->source_id = _cairo_analysis_surface_get_source_region_id (params->target);
+		    region_element->mask_id = _cairo_analysis_surface_get_mask_region_id (params->target);
+		}
 	    }
 	    break;
 
 	case CAIRO_COMMAND_STROKE:
+	    if (region_element)
+		source_region_id = region_element->source_id;
+
 	    status = _cairo_surface_wrapper_stroke (&wrapper,
 						    command->header.op,
 						    &command->stroke.source.base,
+						    source_region_id,
 						    &command->stroke.path,
 						    &command->stroke.style,
 						    &command->stroke.ctm,
@@ -1907,23 +2159,39 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		_cairo_recording_surface_merge_source_attributes (surface,
 								  command->header.op,
 								  &command->stroke.source.base);
+		if (region_element && target_is_analysis)
+		    region_element->source_id = _cairo_analysis_surface_get_source_region_id (params->target);
 	    }
 	    break;
 
 	case CAIRO_COMMAND_FILL:
 	    status = CAIRO_INT_STATUS_UNSUPPORTED;
-	    if (_cairo_surface_wrapper_has_fill_stroke (&wrapper)) {
-		cairo_command_t *stroke_command;
+	    if (region_element)
+		source_region_id = region_element->source_id;
 
-		stroke_command = NULL;
-		if (params->type != CAIRO_RECORDING_CREATE_REGIONS && i < num_elements - 1)
+	    if (_cairo_surface_wrapper_has_fill_stroke (&wrapper)) {
+		cairo_command_t *stroke_command = NULL;
+		cairo_recording_region_element_t *stroke_region_element = NULL;
+		unsigned stroke_region_id = 0;
+
+		/* The analysis surface does not implement
+		 * fill_stroke.  When creating regions the fill and
+		 * stroke commands are tested separately.
+		 */
+		if (params->type != CAIRO_RECORDING_CREATE_REGIONS && i < num_elements - 1) {
 		    stroke_command = elements[i + 1];
+		    if (region_elements)
+			stroke_region_element = &region_elements[i + 1];
+		}
 
-		if (stroke_command != NULL &&
-		    params->type == CAIRO_RECORDING_REPLAY &&
+		if (stroke_region_element)
+		    stroke_region_id = stroke_region_element->source_id;
+
+		if (stroke_command && stroke_region_element &&
+		    params->type == CAIRO_RECORDING_REPLAY_REGION &&
 		    params->region != CAIRO_RECORDING_REGION_ALL)
 		{
-		    if (stroke_command->header.region != params->region)
+		    if (stroke_region_element->region != params->region)
 			stroke_command = NULL;
 		}
 
@@ -1937,12 +2205,14 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		    status = _cairo_surface_wrapper_fill_stroke (&wrapper,
 								 command->header.op,
 								 &command->fill.source.base,
+								 source_region_id,
 								 command->fill.fill_rule,
 								 command->fill.tolerance,
 								 command->fill.antialias,
 								 &command->fill.path,
 								 stroke_command->header.op,
 								 &stroke_command->stroke.source.base,
+								 stroke_region_id,
 								 &stroke_command->stroke.style,
 								 &stroke_command->stroke.ctm,
 								 &stroke_command->stroke.ctm_inverse,
@@ -1964,6 +2234,7 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		status = _cairo_surface_wrapper_fill (&wrapper,
 						      command->header.op,
 						      &command->fill.source.base,
+						      source_region_id,
 						      &command->fill.path,
 						      command->fill.fill_rule,
 						      command->fill.tolerance,
@@ -1973,14 +2244,20 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		    _cairo_recording_surface_merge_source_attributes (surface,
 								      command->header.op,
 								      &command->fill.source.base);
+		if (region_element && target_is_analysis)
+		    region_element->source_id = _cairo_analysis_surface_get_source_region_id (params->target);
 		}
 	    }
 	    break;
 
 	case CAIRO_COMMAND_SHOW_TEXT_GLYPHS:
+	    if (region_element)
+		source_region_id = region_element->source_id;
+
 	    status = _cairo_surface_wrapper_show_text_glyphs (&wrapper,
 							      command->header.op,
 							      &command->show_text_glyphs.source.base,
+							      source_region_id,
 							      command->show_text_glyphs.utf8, command->show_text_glyphs.utf8_len,
 							      command->show_text_glyphs.glyphs, command->show_text_glyphs.num_glyphs,
 							      command->show_text_glyphs.clusters, command->show_text_glyphs.num_clusters,
@@ -1991,6 +2268,9 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		_cairo_recording_surface_merge_source_attributes (surface,
 								  command->header.op,
 								  &command->show_text_glyphs.source.base);
+		if (region_element && target_is_analysis)
+		    region_element->source_id = _cairo_analysis_surface_get_source_region_id (params->target);
+
 	    }
 	    break;
 
@@ -2009,11 +2289,11 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 	if (unlikely (status == CAIRO_INT_STATUS_NOTHING_TO_DO))
 	    status = CAIRO_INT_STATUS_SUCCESS;
 
-	if (params->type == CAIRO_RECORDING_CREATE_REGIONS && command->header.region != CAIRO_RECORDING_REGION_NATIVE) {
+	if (params->type == CAIRO_RECORDING_CREATE_REGIONS && region_element) {
 	    if (status == CAIRO_INT_STATUS_SUCCESS) {
-		command->header.region = CAIRO_RECORDING_REGION_NATIVE;
+		region_element->region = CAIRO_RECORDING_REGION_NATIVE;
 	    } else if (status == CAIRO_INT_STATUS_IMAGE_FALLBACK) {
-		command->header.region = CAIRO_RECORDING_REGION_IMAGE_FALLBACK;
+		region_element->region = CAIRO_RECORDING_REGION_IMAGE_FALLBACK;
 		status = CAIRO_INT_STATUS_SUCCESS;
 	    } else {
 		assert (_cairo_int_status_is_error (status));
@@ -2042,7 +2322,7 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 {
     cairo_surface_wrapper_t wrapper;
     cairo_command_t **elements, *command;
-    cairo_int_status_t status;
+    cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
 
     if (unlikely (surface->base.status))
 	return surface->base.status;
@@ -2071,6 +2351,7 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 	status = _cairo_surface_wrapper_paint (&wrapper,
 					       command->header.op,
 					       &command->paint.source.base,
+					       0,
 					       command->header.clip);
 	break;
 
@@ -2078,7 +2359,9 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 	status = _cairo_surface_wrapper_mask (&wrapper,
 					      command->header.op,
 					      &command->mask.source.base,
+					      0,
 					      &command->mask.mask.base,
+					      0,
 					      command->header.clip);
 	break;
 
@@ -2086,6 +2369,7 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 	status = _cairo_surface_wrapper_stroke (&wrapper,
 						command->header.op,
 						&command->stroke.source.base,
+						0,
 						&command->stroke.path,
 						&command->stroke.style,
 						&command->stroke.ctm,
@@ -2099,6 +2383,7 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 	status = _cairo_surface_wrapper_fill (&wrapper,
 					      command->header.op,
 					      &command->fill.source.base,
+					      0,
 					      &command->fill.path,
 					      command->fill.fill_rule,
 					      command->fill.tolerance,
@@ -2110,6 +2395,7 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 	status = _cairo_surface_wrapper_show_text_glyphs (&wrapper,
 							  command->header.op,
 							  &command->show_text_glyphs.source.base,
+							  0,
 							  command->show_text_glyphs.utf8, command->show_text_glyphs.utf8_len,
 							  command->show_text_glyphs.glyphs, command->show_text_glyphs.num_glyphs,
 							  command->show_text_glyphs.clusters, command->show_text_glyphs.num_clusters,
@@ -2157,6 +2443,7 @@ _cairo_recording_surface_replay (cairo_surface_t *surface,
     params.surface_is_unbounded = FALSE;
     params.type = CAIRO_RECORDING_REPLAY;
     params.region = CAIRO_RECORDING_REGION_ALL;
+    params.regions_id = 0;
     params.foreground_color = NULL;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
@@ -2178,6 +2465,7 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t     *surf
     params.surface_is_unbounded = FALSE;
     params.type = CAIRO_RECORDING_REPLAY;
     params.region = CAIRO_RECORDING_REGION_ALL;
+    params.regions_id = 0;
     params.foreground_color = foreground_color;
     params.foreground_used = FALSE;
 
@@ -2191,7 +2479,8 @@ cairo_status_t
 _cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
 					   const cairo_matrix_t *surface_transform,
 					   cairo_surface_t *target,
-					   const cairo_clip_t *target_clip)
+					   const cairo_clip_t *target_clip,
+                                           cairo_bool_t surface_is_unbounded)
 {
     cairo_recording_surface_replay_params_t params;
 
@@ -2199,9 +2488,10 @@ _cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
     params.surface_transform = surface_transform;
     params.target = target;
     params.target_clip = target_clip;
-    params.surface_is_unbounded = FALSE;
+    params.surface_is_unbounded = surface_is_unbounded;
     params.type = CAIRO_RECORDING_REPLAY;
     params.region = CAIRO_RECORDING_REGION_ALL;
+    params.regions_id = 0;
     params.foreground_color = NULL;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
@@ -2215,6 +2505,7 @@ _cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
  */
 cairo_status_t
 _cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
+						    unsigned int regions_id,
 						    const cairo_matrix_t *surface_transform,
 						    cairo_surface_t *target,
 						    cairo_bool_t surface_is_unbounded)
@@ -2228,6 +2519,7 @@ _cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
     params.surface_is_unbounded = surface_is_unbounded;
     params.type = CAIRO_RECORDING_CREATE_REGIONS;
     params.region = CAIRO_RECORDING_REGION_ALL;
+    params.regions_id = regions_id;
     params.foreground_color = NULL;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
@@ -2235,6 +2527,7 @@ _cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
 
 cairo_status_t
 _cairo_recording_surface_replay_region (cairo_surface_t          *surface,
+					unsigned int regions_id,
 					const cairo_rectangle_int_t *surface_extents,
 					cairo_surface_t          *target,
 					cairo_recording_region_type_t  region)
@@ -2246,8 +2539,9 @@ _cairo_recording_surface_replay_region (cairo_surface_t          *surface,
     params.target = target;
     params.target_clip = NULL;
     params.surface_is_unbounded = FALSE;
-    params.type = CAIRO_RECORDING_REPLAY;
+    params.type = CAIRO_RECORDING_REPLAY_REGION;
     params.region = region;
+    params.regions_id = regions_id;
     params.foreground_color = NULL;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
@@ -2263,7 +2557,7 @@ _recording_surface_get_ink_bbox (cairo_recording_surface_t *surface,
     cairo_status_t status;
 
     null_surface = _cairo_null_surface_create (surface->base.content);
-    analysis_surface = _cairo_analysis_surface_create (null_surface);
+    analysis_surface = _cairo_analysis_surface_create (null_surface, FALSE);
     cairo_surface_destroy (null_surface);
 
     status = analysis_surface->status;
@@ -2395,3 +2689,186 @@ _cairo_recording_surface_has_only_op_over (cairo_recording_surface_t *surface)
 {
     return surface->has_only_op_over;
 }
+
+static void
+print_indent (FILE *file, int indent)
+{
+    fprintf (file, "%*s", indent * 2, "");
+}
+
+static void
+print_pattern (FILE                  *file,
+	       const cairo_pattern_t *pattern,
+	       unsigned int           region_id,
+	       int                    indent,
+	       cairo_bool_t           recurse)
+{
+    switch (pattern->type) {
+	case CAIRO_PATTERN_TYPE_SOLID: {
+	    cairo_solid_pattern_t *p = (cairo_solid_pattern_t *) pattern;
+	    if (pattern->is_userfont_foreground) {
+		fprintf (file, "solid foreground\n");
+	    } else {
+		fprintf (file, "solid rgba: %f %f %f %f\n",
+			 p->color.red,
+			 p->color.green,
+			 p->color.blue,
+			 p->color.alpha);
+	    }
+	} break;
+	case CAIRO_PATTERN_TYPE_SURFACE: {
+	    cairo_surface_pattern_t *p = (cairo_surface_pattern_t *) pattern;
+	    fprintf (file, "surface ");
+	    if (p->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
+		fprintf (file, "recording id: %d\n", p->surface->unique_id);
+		if (recurse) {
+		    _cairo_debug_print_recording_surface (file, p->surface,
+							  region_id,
+							  indent + 1, recurse);
+		}
+	    } else if (p->surface->type == CAIRO_SURFACE_TYPE_IMAGE) {
+		cairo_image_surface_t *image = (cairo_image_surface_t *)p->surface;
+		fprintf (file, "image format: ");
+		switch (image->format) {
+		    case CAIRO_FORMAT_INVALID: fputs ("INVALID", file); break;
+		    case CAIRO_FORMAT_ARGB32: fputs ("ARGB32", file); break;
+		    case CAIRO_FORMAT_RGB24: fputs ("RGB24", file); break;
+		    case CAIRO_FORMAT_A8: fputs ("A8", file); break;
+		    case CAIRO_FORMAT_A1: fputs ("A1", file); break;
+		    case CAIRO_FORMAT_RGB16_565: fputs ("RGB16_565", file); break;
+		    case CAIRO_FORMAT_RGB30: fputs ("RGB30", file); break;
+		    case CAIRO_FORMAT_RGB96F: fputs ("RGB96F", file); break;
+		    case CAIRO_FORMAT_RGBA128F: fputs ("RGBA128F", file); break;
+		}
+		fprintf (file, " width: %d height: %d\n", image->width, image->height);
+	    } else {
+		fprintf (file, "type %d\n", p->surface->type);
+	    }
+	} break;
+	case CAIRO_PATTERN_TYPE_LINEAR:
+	    fprintf (file, "linear\n");
+	    break;
+	case CAIRO_PATTERN_TYPE_RADIAL:
+	    fprintf (file, "radial\n");
+	    break;
+	case CAIRO_PATTERN_TYPE_MESH:
+	    fprintf (file, "mesh\n");
+	    break;
+	case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
+	    fprintf (file, "raster\n");
+	    break;
+    }
+}
+
+void
+_cairo_debug_print_recording_surface (FILE            *file,
+				      cairo_surface_t *surface,
+                                      unsigned int     regions_id,
+				      int              indent,
+				      cairo_bool_t     recurse)
+{
+    cairo_command_t **elements;
+    cairo_recording_region_element_t *region_elements = NULL;
+    unsigned int i, num_elements;
+    cairo_recording_surface_t *recording_surface;
+    cairo_surface_t *free_me = NULL;
+    char common[100];
+
+    if (_cairo_surface_is_snapshot (surface))
+	free_me = surface = _cairo_surface_snapshot_get_target (surface);
+
+    assert (_cairo_surface_is_recording (surface));
+    recording_surface = (cairo_recording_surface_t *)surface;
+
+    print_indent (file, indent);
+    indent++;
+    fprintf(file, "recording surface id: %d   regions id: %d\n", recording_surface->base.unique_id, regions_id);
+    num_elements = recording_surface->commands.num_elements;
+    elements = _cairo_array_index (&recording_surface->commands, 0);
+
+    if (regions_id != 0) {
+	cairo_recording_regions_array_t *regions_array;
+	regions_array = _cairo_recording_surface_region_array_find (recording_surface, regions_id);
+	assert (regions_array != NULL);
+	assert (_cairo_array_num_elements (&regions_array->regions) == num_elements);
+	region_elements = _cairo_array_index (&regions_array->regions, 0);
+    }
+
+    for (i = 0; i < num_elements; i++) {
+	cairo_command_t *command = elements[i];
+	unsigned int source_region_id = 0;
+	unsigned int mask_region_id = 0;
+
+	common[0] = 0;
+	if (region_elements) {
+	    cairo_recording_region_element_t *region_element = &region_elements[i];
+	    strcpy (common, "region: ");
+	    switch (region_element->region) {
+		case CAIRO_RECORDING_REGION_ALL: strcat (common, "all"); break;
+		case CAIRO_RECORDING_REGION_NATIVE: strcat (common, "native"); break;
+		case CAIRO_RECORDING_REGION_IMAGE_FALLBACK: strcat (common, "fallback"); break;
+	    }
+	    source_region_id  = region_element->source_id;
+	    mask_region_id = region_element->mask_id;
+	}
+	sprintf (common + strlen(common), " op: %s", _cairo_debug_operator_to_string (command->header.op));
+
+	switch (command->header.type) {
+	    case CAIRO_COMMAND_PAINT:
+		print_indent (file, indent);
+		fprintf(file, "%d PAINT %s source: ", i, common);
+		print_pattern (file, &command->paint.source.base, source_region_id, indent + 1, recurse);
+		break;
+
+	    case CAIRO_COMMAND_MASK:
+		print_indent (file, indent);
+		fprintf(file, "%d MASK %s\n", i, common);
+		print_indent (file, indent + 1);
+		fprintf(file, "source: ");
+		print_pattern (file, &command->mask.source.base, source_region_id, indent + 1, recurse);
+		print_indent (file, indent + 1);
+		fprintf(file, "mask: ");
+		print_pattern (file, &command->mask.mask.base, mask_region_id, indent + 1, recurse);
+		break;
+
+	    case CAIRO_COMMAND_STROKE:
+		print_indent (file, indent);
+		fprintf(file, "%d STROKE %s source:", i, common);
+		print_pattern (file, &command->stroke.source.base, source_region_id, indent + 1, recurse);
+		break;
+
+	    case CAIRO_COMMAND_FILL:
+		print_indent (file, indent);
+		fprintf(file, "%d FILL %s source: ", i, common);
+		print_pattern (file, &command->fill.source.base, source_region_id, indent + 1, recurse);
+		break;
+
+	    case CAIRO_COMMAND_SHOW_TEXT_GLYPHS:
+		print_indent (file, indent);
+		fprintf(file, "%d SHOW_TEXT_GLYPHS %s font_type: ", i, common);
+		switch (command->show_text_glyphs.scaled_font->backend->type) {
+		    case CAIRO_FONT_TYPE_TOY: fputs ("toy", file); break;
+		    case CAIRO_FONT_TYPE_FT: fputs ("ft", file); break;
+		    case CAIRO_FONT_TYPE_WIN32: fputs ("win32", file); break;
+		    case CAIRO_FONT_TYPE_QUARTZ: fputs ("quartz", file); break;
+		    case CAIRO_FONT_TYPE_USER: fputs ("user", file); break;
+		    case CAIRO_FONT_TYPE_DWRITE: fputs ("dwrite", file); break;
+		}
+		fprintf (file, " glyphs:");
+		for (unsigned j = 0; j < command->show_text_glyphs.num_glyphs; j++)
+		    fprintf (file, " %ld", command->show_text_glyphs.glyphs[j].index);
+		fprintf (file, " source:");
+		print_pattern (file, &command->show_text_glyphs.source.base, source_region_id, indent + 1, recurse);
+		break;
+
+	    case CAIRO_COMMAND_TAG:
+		print_indent (file, indent);
+		fprintf(file, "%d TAG\n", i);
+		break;
+
+	    default:
+		ASSERT_NOT_REACHED;
+	}
+    }
+    cairo_surface_destroy (free_me);
+}
diff --git a/src/cairo-script-surface.c b/src/cairo-script-surface.c
index 7901b4c35..2baf873de 100644
--- a/src/cairo-script-surface.c
+++ b/src/cairo-script-surface.c
@@ -2509,7 +2509,7 @@ _cairo_script_surface_paint (void			*abstract_surface,
 
     if (_cairo_surface_wrapper_is_active (&surface->wrapper)) {
 	return _cairo_surface_wrapper_paint (&surface->wrapper,
-					     op, source, clip);
+					     op, source, 0, clip);
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -2566,7 +2566,7 @@ _cairo_script_surface_mask (void			*abstract_surface,
 
     if (_cairo_surface_wrapper_is_active (&surface->wrapper)) {
 	return _cairo_surface_wrapper_mask (&surface->wrapper,
-					    op, source, mask, clip);
+					    op, source, 0, mask, 0, clip);
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -2653,7 +2653,7 @@ _cairo_script_surface_stroke (void				*abstract_surface,
 
     if (_cairo_surface_wrapper_is_active (&surface->wrapper)) {
 	return _cairo_surface_wrapper_stroke (&surface->wrapper,
-					      op, source, path,
+					      op, source, 0, path,
 					      style,
 					      ctm, ctm_inverse,
 					      tolerance, antialias,
@@ -2734,7 +2734,7 @@ _cairo_script_surface_fill (void			*abstract_surface,
 
     if (_cairo_surface_wrapper_is_active (&surface->wrapper)) {
 	return _cairo_surface_wrapper_fill (&surface->wrapper,
-					    op, source, path,
+					    op, source, 0, path,
 					    fill_rule,
 					    tolerance,
 					    antialias,
@@ -3585,7 +3585,7 @@ _cairo_script_surface_show_text_glyphs (void			    *abstract_surface,
 
     if (_cairo_surface_wrapper_is_active (&surface->wrapper)){
 	return _cairo_surface_wrapper_show_text_glyphs (&surface->wrapper,
-							op, source,
+							op, source, 0,
 							utf8, utf8_len,
 							glyphs, num_glyphs,
 							clusters, num_clusters,
diff --git a/src/cairo-spans-compositor.c b/src/cairo-spans-compositor.c
index 50c92b25c..49c5999d2 100644
--- a/src/cairo-spans-compositor.c
+++ b/src/cairo-spans-compositor.c
@@ -612,7 +612,7 @@ composite_aligned_boxes (const cairo_spans_compositor_t		*compositor,
 
 	recording_clip = _cairo_clip_from_boxes (boxes);
 	status = _cairo_recording_surface_replay_with_clip (unwrap_source (source),
-							    m, dst, recording_clip);
+							    m, dst, recording_clip, FALSE);
 	_cairo_clip_destroy (recording_clip);
 
 	return status;
diff --git a/src/cairo-surface-wrapper-private.h b/src/cairo-surface-wrapper-private.h
index 380ba099d..016402d7e 100644
--- a/src/cairo-surface-wrapper-private.h
+++ b/src/cairo-surface-wrapper-private.h
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
 /* cairo - a vector graphics library with display and print output
  *
  * Copyright © 2002 University of Southern California
@@ -41,6 +42,7 @@
 
 #include "cairoint.h"
 #include "cairo-types-private.h"
+#include "cairo-recording-surface-private.h"
 #include "cairo-surface-backend-private.h"
 
 CAIRO_BEGIN_DECLS
@@ -54,6 +56,9 @@ struct _cairo_surface_wrapper {
     cairo_rectangle_int_t extents;
     const cairo_clip_t *clip;
 
+    unsigned int source_region_id;
+    unsigned int mask_region_id;
+
     cairo_bool_t needs_transform;
 };
 
@@ -95,60 +100,68 @@ _cairo_surface_wrapper_release_source_image (cairo_surface_wrapper_t *wrapper,
 
 cairo_private cairo_status_t
 _cairo_surface_wrapper_paint (cairo_surface_wrapper_t *wrapper,
-			      cairo_operator_t	 op,
-			      const cairo_pattern_t *source,
-			      const cairo_clip_t	    *clip);
+			      cairo_operator_t	       op,
+			      const cairo_pattern_t   *source,
+			      unsigned int             source_region_id,
+			      const cairo_clip_t      *clip);
 
 cairo_private cairo_status_t
 _cairo_surface_wrapper_mask (cairo_surface_wrapper_t *wrapper,
-			     cairo_operator_t	 op,
-			     const cairo_pattern_t *source,
-			     const cairo_pattern_t *mask,
-			     const cairo_clip_t	    *clip);
+			     cairo_operator_t	      op,
+			     const cairo_pattern_t   *source,
+			     unsigned int             source_region_id,
+			     const cairo_pattern_t   *mask,
+                             unsigned int             mask_region_id,
+			     const cairo_clip_t	     *clip);
 
 cairo_private cairo_status_t
-_cairo_surface_wrapper_stroke (cairo_surface_wrapper_t *wrapper,
-			       cairo_operator_t		 op,
-			       const cairo_pattern_t	*source,
-			       const cairo_path_fixed_t	*path,
-			       const cairo_stroke_style_t	*stroke_style,
-			       const cairo_matrix_t		*ctm,
-			       const cairo_matrix_t		*ctm_inverse,
-			       double			 tolerance,
-			       cairo_antialias_t	 antialias,
-			       const cairo_clip_t		*clip);
+_cairo_surface_wrapper_stroke (cairo_surface_wrapper_t    *wrapper,
+			       cairo_operator_t		   op,
+			       const cairo_pattern_t	  *source,
+			       unsigned int                source_region_id,
+			       const cairo_path_fixed_t	  *path,
+			       const cairo_stroke_style_t *stroke_style,
+			       const cairo_matrix_t	  *ctm,
+			       const cairo_matrix_t	  *ctm_inverse,
+			       double			   tolerance,
+			       cairo_antialias_t	   antialias,
+			       const cairo_clip_t	  *clip);
 
 cairo_private cairo_status_t
-_cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t *wrapper,
-				    cairo_operator_t	     fill_op,
-				    const cairo_pattern_t   *fill_source,
-				    cairo_fill_rule_t	     fill_rule,
-				    double		     fill_tolerance,
-				    cairo_antialias_t	     fill_antialias,
-				    const cairo_path_fixed_t*path,
-				    cairo_operator_t	     stroke_op,
-				    const cairo_pattern_t   *stroke_source,
-				    const cairo_stroke_style_t    *stroke_style,
-				    const cairo_matrix_t	    *stroke_ctm,
-				    const cairo_matrix_t	    *stroke_ctm_inverse,
-				    double		     stroke_tolerance,
-				    cairo_antialias_t	     stroke_antialias,
-				    const cairo_clip_t	    *clip);
+_cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t    *wrapper,
+				    cairo_operator_t	        fill_op,
+				    const cairo_pattern_t      *fill_source,
+				    unsigned int                fill_region_id,
+				    cairo_fill_rule_t	        fill_rule,
+				    double		        fill_tolerance,
+				    cairo_antialias_t	        fill_antialias,
+				    const cairo_path_fixed_t   *path,
+				    cairo_operator_t	        stroke_op,
+				    const cairo_pattern_t      *stroke_source,
+				    unsigned int                stroke_region_id,
+				    const cairo_stroke_style_t *stroke_style,
+				    const cairo_matrix_t       *stroke_ctm,
+				    const cairo_matrix_t       *stroke_ctm_inverse,
+				    double		        stroke_tolerance,
+				    cairo_antialias_t	        stroke_antialias,
+				    const cairo_clip_t	       *clip);
 
 cairo_private cairo_status_t
-_cairo_surface_wrapper_fill (cairo_surface_wrapper_t *wrapper,
-			     cairo_operator_t	 op,
-			     const cairo_pattern_t *source,
-			     const cairo_path_fixed_t	*path,
-			     cairo_fill_rule_t	 fill_rule,
-			     double		 tolerance,
-			     cairo_antialias_t	 antialias,
-			     const cairo_clip_t	*clip);
+_cairo_surface_wrapper_fill (cairo_surface_wrapper_t  *wrapper,
+			     cairo_operator_t	       op,
+			     const cairo_pattern_t    *source,
+			     unsigned int              source_region_id,
+			     const cairo_path_fixed_t *path,
+			     cairo_fill_rule_t	       fill_rule,
+			     double		       tolerance,
+			     cairo_antialias_t	       antialias,
+			     const cairo_clip_t	      *clip);
 
 cairo_private cairo_status_t
-_cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t *wrapper,
+_cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t     *wrapper,
 					 cairo_operator_t	     op,
 					 const cairo_pattern_t	    *source,
+					 unsigned int                source_region_id,
 					 const char		    *utf8,
 					 int			     utf8_len,
 					 const cairo_glyph_t	    *glyphs,
diff --git a/src/cairo-surface-wrapper.c b/src/cairo-surface-wrapper.c
index 7fb417a20..29c19c5a5 100644
--- a/src/cairo-surface-wrapper.c
+++ b/src/cairo-surface-wrapper.c
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
 /* cairo - a vector graphics library with display and print output
  *
  * Copyright © 2005 Red Hat, Inc
@@ -47,12 +48,18 @@
 static void
 _copy_transformed_pattern (cairo_pattern_t *pattern,
 			   const cairo_pattern_t *original,
-			   const cairo_matrix_t  *ctm_inverse)
+			   const cairo_matrix_t  *ctm_inverse,
+			   unsigned int region_id)
 {
     _cairo_pattern_init_static_copy (pattern, original);
 
     if (! _cairo_matrix_is_identity (ctm_inverse))
 	_cairo_pattern_transform (pattern, ctm_inverse);
+
+    if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern;
+	surface_pattern->region_array_id = region_id;
+    }
 }
 
 cairo_status_t
@@ -129,9 +136,10 @@ _cairo_surface_wrapper_get_clip (cairo_surface_wrapper_t *wrapper,
 
 cairo_status_t
 _cairo_surface_wrapper_paint (cairo_surface_wrapper_t *wrapper,
-			      cairo_operator_t	 op,
-			      const cairo_pattern_t *source,
-			      const cairo_clip_t    *clip)
+			      cairo_operator_t	       op,
+			      const cairo_pattern_t   *source,
+			      unsigned int             source_region_id,
+			      const cairo_clip_t      *clip)
 {
     cairo_status_t status;
     cairo_clip_t *dev_clip;
@@ -144,7 +152,7 @@ _cairo_surface_wrapper_paint (cairo_surface_wrapper_t *wrapper,
     if (_cairo_clip_is_all_clipped (dev_clip))
 	return CAIRO_INT_STATUS_NOTHING_TO_DO;
 
-    if (wrapper->needs_transform) {
+    if (wrapper->needs_transform || source_region_id != 0) {
 	cairo_matrix_t m;
 
 	_cairo_surface_wrapper_get_transform (wrapper, &m);
@@ -152,7 +160,7 @@ _cairo_surface_wrapper_paint (cairo_surface_wrapper_t *wrapper,
 	status = cairo_matrix_invert (&m);
 	assert (status == CAIRO_STATUS_SUCCESS);
 
-	_copy_transformed_pattern (&source_copy.base, source, &m);
+	_copy_transformed_pattern (&source_copy.base, source, &m, source_region_id);
 	source = &source_copy.base;
     }
 
@@ -162,13 +170,14 @@ _cairo_surface_wrapper_paint (cairo_surface_wrapper_t *wrapper,
     return status;
 }
 
-
 cairo_status_t
 _cairo_surface_wrapper_mask (cairo_surface_wrapper_t *wrapper,
-			     cairo_operator_t	 op,
-			     const cairo_pattern_t *source,
-			     const cairo_pattern_t *mask,
-			     const cairo_clip_t	    *clip)
+			     cairo_operator_t	      op,
+			     const cairo_pattern_t   *source,
+                             unsigned int             source_region_id,
+			     const cairo_pattern_t   *mask,
+                             unsigned int             mask_region_id,
+			     const cairo_clip_t	     *clip)
 {
     cairo_status_t status;
     cairo_clip_t *dev_clip;
@@ -182,7 +191,7 @@ _cairo_surface_wrapper_mask (cairo_surface_wrapper_t *wrapper,
     if (_cairo_clip_is_all_clipped (dev_clip))
 	return CAIRO_INT_STATUS_NOTHING_TO_DO;
 
-    if (wrapper->needs_transform) {
+    if (wrapper->needs_transform || source_region_id != 0 || mask_region_id != 0) {
 	cairo_matrix_t m;
 
 	_cairo_surface_wrapper_get_transform (wrapper, &m);
@@ -190,10 +199,10 @@ _cairo_surface_wrapper_mask (cairo_surface_wrapper_t *wrapper,
 	status = cairo_matrix_invert (&m);
 	assert (status == CAIRO_STATUS_SUCCESS);
 
-	_copy_transformed_pattern (&source_copy.base, source, &m);
+	_copy_transformed_pattern (&source_copy.base, source, &m, source_region_id);
 	source = &source_copy.base;
 
-	_copy_transformed_pattern (&mask_copy.base, mask, &m);
+	_copy_transformed_pattern (&mask_copy.base, mask, &m, mask_region_id);
 	mask = &mask_copy.base;
     }
 
@@ -204,16 +213,17 @@ _cairo_surface_wrapper_mask (cairo_surface_wrapper_t *wrapper,
 }
 
 cairo_status_t
-_cairo_surface_wrapper_stroke (cairo_surface_wrapper_t *wrapper,
-			       cairo_operator_t		 op,
-			       const cairo_pattern_t	*source,
-			       const cairo_path_fixed_t	*path,
-			       const cairo_stroke_style_t	*stroke_style,
-			       const cairo_matrix_t		*ctm,
-			       const cairo_matrix_t		*ctm_inverse,
-			       double			 tolerance,
-			       cairo_antialias_t	 antialias,
-			       const cairo_clip_t		*clip)
+_cairo_surface_wrapper_stroke (cairo_surface_wrapper_t    *wrapper,
+			       cairo_operator_t		   op,
+			       const cairo_pattern_t	  *source,
+			       unsigned int                source_region_id,
+			       const cairo_path_fixed_t	  *path,
+			       const cairo_stroke_style_t *stroke_style,
+			       const cairo_matrix_t	  *ctm,
+			       const cairo_matrix_t	  *ctm_inverse,
+			       double			   tolerance,
+			       cairo_antialias_t	   antialias,
+			       const cairo_clip_t	  *clip)
 {
     cairo_status_t status;
     cairo_path_fixed_t path_copy, *dev_path = (cairo_path_fixed_t *) path;
@@ -229,7 +239,7 @@ _cairo_surface_wrapper_stroke (cairo_surface_wrapper_t *wrapper,
     if (_cairo_clip_is_all_clipped (dev_clip))
 	return CAIRO_INT_STATUS_NOTHING_TO_DO;
 
-    if (wrapper->needs_transform) {
+    if (wrapper->needs_transform || source_region_id != 0) {
 	cairo_matrix_t m;
 
 	_cairo_surface_wrapper_get_transform (wrapper, &m);
@@ -248,7 +258,7 @@ _cairo_surface_wrapper_stroke (cairo_surface_wrapper_t *wrapper,
 
 	cairo_matrix_multiply (&dev_ctm_inverse, &m, &dev_ctm_inverse);
 
-	_copy_transformed_pattern (&source_copy.base, source, &m);
+	_copy_transformed_pattern (&source_copy.base, source, &m, source_region_id);
 	source = &source_copy.base;
     }
 
@@ -266,21 +276,23 @@ _cairo_surface_wrapper_stroke (cairo_surface_wrapper_t *wrapper,
 }
 
 cairo_status_t
-_cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t *wrapper,
-				    cairo_operator_t	     fill_op,
-				    const cairo_pattern_t   *fill_source,
-				    cairo_fill_rule_t	     fill_rule,
-				    double		     fill_tolerance,
-				    cairo_antialias_t	     fill_antialias,
-				    const cairo_path_fixed_t*path,
-				    cairo_operator_t	     stroke_op,
-				    const cairo_pattern_t   *stroke_source,
-				    const cairo_stroke_style_t    *stroke_style,
-				    const cairo_matrix_t	    *stroke_ctm,
-				    const cairo_matrix_t	    *stroke_ctm_inverse,
-				    double		     stroke_tolerance,
-				    cairo_antialias_t	     stroke_antialias,
-				    const cairo_clip_t	    *clip)
+_cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t    *wrapper,
+				    cairo_operator_t	        fill_op,
+				    const cairo_pattern_t      *fill_source,
+				    unsigned int                fill_region_id,
+				    cairo_fill_rule_t	        fill_rule,
+				    double		        fill_tolerance,
+				    cairo_antialias_t	        fill_antialias,
+				    const cairo_path_fixed_t   *path,
+				    cairo_operator_t	        stroke_op,
+				    const cairo_pattern_t      *stroke_source,
+				    unsigned int                stroke_region_id,
+				    const cairo_stroke_style_t *stroke_style,
+				    const cairo_matrix_t       *stroke_ctm,
+				    const cairo_matrix_t       *stroke_ctm_inverse,
+				    double		        stroke_tolerance,
+				    cairo_antialias_t	        stroke_antialias,
+				    const cairo_clip_t	       *clip)
 {
     cairo_status_t status;
     cairo_path_fixed_t path_copy, *dev_path = (cairo_path_fixed_t *)path;
@@ -297,7 +309,7 @@ _cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t *wrapper,
     if (_cairo_clip_is_all_clipped (dev_clip))
 	return CAIRO_INT_STATUS_NOTHING_TO_DO;
 
-    if (wrapper->needs_transform) {
+    if (wrapper->needs_transform || fill_region_id != 0 || stroke_region_id != 0) {
 	cairo_matrix_t m;
 
 	_cairo_surface_wrapper_get_transform (wrapper, &m);
@@ -316,10 +328,10 @@ _cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t *wrapper,
 
 	cairo_matrix_multiply (&dev_ctm_inverse, &m, &dev_ctm_inverse);
 
-	_copy_transformed_pattern (&stroke_source_copy.base, stroke_source, &m);
+	_copy_transformed_pattern (&stroke_source_copy.base, stroke_source, &m, fill_region_id);
 	stroke_source = &stroke_source_copy.base;
 
-	_copy_transformed_pattern (&fill_source_copy.base, fill_source, &m);
+	_copy_transformed_pattern (&fill_source_copy.base, fill_source, &m, stroke_region_id);
 	fill_source = &fill_source_copy.base;
     }
 
@@ -341,14 +353,15 @@ _cairo_surface_wrapper_fill_stroke (cairo_surface_wrapper_t *wrapper,
 }
 
 cairo_status_t
-_cairo_surface_wrapper_fill (cairo_surface_wrapper_t	*wrapper,
-			     cairo_operator_t	 op,
-			     const cairo_pattern_t *source,
-			     const cairo_path_fixed_t	*path,
-			     cairo_fill_rule_t	 fill_rule,
-			     double		 tolerance,
-			     cairo_antialias_t	 antialias,
-			     const cairo_clip_t	*clip)
+_cairo_surface_wrapper_fill (cairo_surface_wrapper_t  *wrapper,
+			     cairo_operator_t	       op,
+			     const cairo_pattern_t    *source,
+			     unsigned int              source_region_id,
+			     const cairo_path_fixed_t *path,
+			     cairo_fill_rule_t	       fill_rule,
+			     double		       tolerance,
+			     cairo_antialias_t	       antialias,
+			     const cairo_clip_t	      *clip)
 {
     cairo_status_t status;
     cairo_path_fixed_t path_copy, *dev_path = (cairo_path_fixed_t *) path;
@@ -362,7 +375,7 @@ _cairo_surface_wrapper_fill (cairo_surface_wrapper_t	*wrapper,
     if (_cairo_clip_is_all_clipped (dev_clip))
 	return CAIRO_INT_STATUS_NOTHING_TO_DO;
 
-    if (wrapper->needs_transform) {
+    if (wrapper->needs_transform || source_region_id != 0) {
 	cairo_matrix_t m;
 
 	_cairo_surface_wrapper_get_transform (wrapper, &m);
@@ -377,7 +390,7 @@ _cairo_surface_wrapper_fill (cairo_surface_wrapper_t	*wrapper,
 	status = cairo_matrix_invert (&m);
 	assert (status == CAIRO_STATUS_SUCCESS);
 
-	_copy_transformed_pattern (&source_copy.base, source, &m);
+	_copy_transformed_pattern (&source_copy.base, source, &m, source_region_id);
 	source = &source_copy.base;
     }
 
@@ -394,9 +407,10 @@ _cairo_surface_wrapper_fill (cairo_surface_wrapper_t	*wrapper,
 }
 
 cairo_status_t
-_cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t *wrapper,
+_cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t    *wrapper,
 					 cairo_operator_t	     op,
 					 const cairo_pattern_t	    *source,
+					 unsigned int                source_region_id,
 					 const char		    *utf8,
 					 int			     utf8_len,
 					 const cairo_glyph_t	    *glyphs,
@@ -425,7 +439,7 @@ _cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t *wrapper,
     cairo_surface_get_font_options (wrapper->target, &options);
     cairo_font_options_merge (&options, &scaled_font->options);
 
-    if (wrapper->needs_transform) {
+    if (wrapper->needs_transform || source_region_id != 0) {
 	cairo_matrix_t m;
 	int i;
 
@@ -460,7 +474,7 @@ _cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t *wrapper,
 	status = cairo_matrix_invert (&m);
 	assert (status == CAIRO_STATUS_SUCCESS);
 
-	_copy_transformed_pattern (&source_copy.base, source, &m);
+	_copy_transformed_pattern (&source_copy.base, source, &m, source_region_id);
 	source = &source_copy.base;
     } else {
 	if (! cairo_font_options_equal (&options, &scaled_font->options)) {
@@ -622,6 +636,8 @@ _cairo_surface_wrapper_init (cairo_surface_wrapper_t *wrapper,
     wrapper->has_extents = FALSE;
     wrapper->extents.x = wrapper->extents.y = 0;
     wrapper->clip = NULL;
+    wrapper->source_region_id = 0;
+    wrapper->mask_region_id = 0;
 
     wrapper->needs_transform = FALSE;
     if (target) {
diff --git a/src/cairo-traps-compositor.c b/src/cairo-traps-compositor.c
index 3414fc268..d1402d2a3 100644
--- a/src/cairo-traps-compositor.c
+++ b/src/cairo-traps-compositor.c
@@ -1240,7 +1240,7 @@ composite_aligned_boxes (const cairo_traps_compositor_t *compositor,
 
 	recording_clip = _cairo_clip_from_boxes (boxes);
 	status = _cairo_recording_surface_replay_with_clip (recording_pattern_get_surface (source),
-							    m, dst, recording_clip);
+							    m, dst, recording_clip, FALSE);
 	_cairo_clip_destroy (recording_clip);
 
 	return status;
diff --git a/src/cairo-xcb-surface-render.c b/src/cairo-xcb-surface-render.c
index 5993aa378..a119dec6f 100644
--- a/src/cairo-xcb-surface-render.c
+++ b/src/cairo-xcb-surface-render.c
@@ -1112,7 +1112,8 @@ record_to_picture (cairo_surface_t *target,
 
     status = _cairo_recording_surface_replay_with_clip (source,
 							&matrix, tmp,
-							NULL);
+							NULL,
+                                                        FALSE);
     if (unlikely (status)) {
 	cairo_surface_destroy (tmp);
 	return (cairo_xcb_picture_t *) _cairo_surface_create_in_error (status);
diff --git a/src/cairo-xlib-source.c b/src/cairo-xlib-source.c
index 4c3b99d9e..63e155e00 100644
--- a/src/cairo-xlib-source.c
+++ b/src/cairo-xlib-source.c
@@ -920,7 +920,8 @@ record_source (cairo_xlib_surface_t *dst,
     recording = recording_pattern_get_surface (&pattern->base),
     status = _cairo_recording_surface_replay_with_clip (recording,
 							&matrix, &src->base,
-							NULL);
+							NULL,
+							FALSE);
     cairo_surface_destroy (recording);
     if (unlikely (status)) {
 	cairo_surface_destroy (&src->base);
diff --git a/src/cairoint.h b/src/cairoint.h
index 032d42145..65bc16f53 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1855,6 +1855,12 @@ _cairo_debug_print_matrix (FILE *file, const cairo_matrix_t *matrix);
 cairo_private void
 _cairo_debug_print_rect (FILE *file, const cairo_rectangle_int_t *rect);
 
+cairo_private const char *
+_cairo_debug_operator_to_string (cairo_operator_t op);
+
+cairo_private const char *
+_cairo_debug_status_to_string (cairo_int_status_t status);
+
 cairo_private cairo_status_t
 _cairo_bentley_ottmann_tessellate_rectilinear_polygon (cairo_traps_t	 *traps,
 						       const cairo_polygon_t *polygon,
diff --git a/src/win32/cairo-win32-printing-surface.c b/src/win32/cairo-win32-printing-surface.c
index 352137a27..01a8e37aa 100644
--- a/src/win32/cairo-win32-printing-surface.c
+++ b/src/win32/cairo-win32-printing-surface.c
@@ -654,6 +654,7 @@ _cairo_win32_printing_surface_paint_recording_pattern (cairo_win32_printing_surf
 
 	    SaveDC (surface->win32.dc); /* Allow clip path to be reset during replay */
 	    status = _cairo_recording_surface_replay_region (&recording_surface->base,
+							     pattern->region_array_id,
 							     is_subsurface ? &recording_extents : NULL,
 							     &surface->win32.base,
 							     CAIRO_RECORDING_REGION_NATIVE);
commit a2b376ed787fc35270421730863411a0c047f564
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Jan 15 19:25:50 2023 +1030

    pdf-tagged-text depends on PDF

diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c
index 378e6a920..1a2f62dac 100644
--- a/test/pdf-tagged-text.c
+++ b/test/pdf-tagged-text.c
@@ -42,6 +42,9 @@
 #endif
 
 #include <cairo.h>
+
+#if CAIRO_HAS_PDF_SURFACE
+
 #include <cairo-pdf.h>
 
 /* This test checks PDF with
@@ -604,3 +607,5 @@ CAIRO_TEST (pdf_tagged_text,
 	    NULL, /* requirements */
 	    0, 0,
 	    preamble, NULL)
+
+#endif /* CAIRO_HAS_PDF_SURFACE */
commit c9187c529eb8574bfdc67c556cb0944a05e697cd
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Jan 1 13:50:38 2023 +1030

    Add a test that demonstrates a recording surface bug when re-used on different surfaces
    
    There is a bug in the recording surface where if one recording surface
    is used as the same source on different paginated targets that support
    fine grained fallbacks, the output may be incorrect because the
    analysis results of a previous target are re-used on another target.

diff --git a/test/create-regions.c b/test/create-regions.c
new file mode 100644
index 000000000..749595a44
--- /dev/null
+++ b/test/create-regions.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright © 2023 Adrian Johnson
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <cairo.h>
+
+#if CAIRO_HAS_PDF_SURFACE && CAIRO_HAS_PS_SURFACE && CAIRO_HAS_SVG_SURFACE
+
+#include <cairo-pdf.h>
+#include <cairo-ps.h>
+#include <cairo-svg.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+#include "cairo-test.h"
+#include "buffer-diff.h"
+
+/* This test is to ensure that _cairo_recording_surface_replay_and_create_regions()
+ * works with a recording surface source re-used for multiple paginated surfaces.
+ * The prior use of the source with a paginated surface should not affect the region
+ * analysis on subsequent surfaces.
+ *
+ * If test output should only contain fallback images for unsupported
+ * operations.  If the recording surface is incorrectly re-using the
+ * analysis from a different target, some operations may be missing
+ * from the ouput (recording surface marked the operation as supported
+ * when it is not) or some operations may be have fallbacks for
+ * natively suported opetions (recording surface marked a supported
+ * operation as unsupprted).
+ *
+ * To create ref images, run the test for one target at a time to
+ * prevent re-use of the recording surface for different targets.
+ */
+
+
+#define SIZE 40
+#define PAD 25
+#define PAGE_SIZE (SIZE*3 + PAD*4)
+
+/* Apply a slight rotation and use a very low fallback resolution to
+ * ensure fallback images are apparent in the output. */
+#define ROTATE 5
+#define FALLBACK_PPI 18
+
+static void
+create_recordings (cairo_operator_t    op,
+                   cairo_surface_t   **recording,
+                   cairo_surface_t   **recording_group)
+{
+    *recording = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+    cairo_t *cr = cairo_create (*recording);
+
+    cairo_rotate (cr, ROTATE*M_PI/180.0);
+
+    cairo_rectangle (cr, 0, 0, SIZE*3.0/4.0, SIZE*3.0/4.0);
+    cairo_set_source_rgb (cr, 1, 0, 0);
+    cairo_fill (cr);
+
+    cairo_set_operator (cr, op);
+
+    cairo_rectangle (cr, SIZE/4.0, SIZE/4.0, SIZE*3.0/4.0, SIZE*3.0/4.0);
+    cairo_set_source_rgb (cr, 0, 1, 0);
+    cairo_fill (cr);
+
+    cairo_destroy (cr);
+
+    *recording_group = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+    cr = cairo_create (*recording_group);
+
+    cairo_set_source_surface (cr, *recording, 0, 0);
+    cairo_paint (cr);
+
+    cairo_destroy (cr);
+}
+
+static void
+_xunlink (const cairo_test_context_t *ctx, const char *pathname)
+{
+    if (unlink (pathname) < 0 && errno != ENOENT) {
+	cairo_test_log (ctx, "Error: Cannot remove %s: %s\n",
+			pathname, strerror (errno));
+	exit (1);
+    }
+}
+
+static cairo_bool_t
+check_result (cairo_test_context_t *ctx,
+	      const cairo_boilerplate_target_t *target,
+	      const char *test_name,
+	      const char *base_name,
+	      cairo_surface_t *surface)
+{
+    const char *format;
+    char *ref_name;
+    char *png_name;
+    char *diff_name;
+    cairo_surface_t *test_image, *ref_image, *diff_image;
+    buffer_diff_result_t result;
+    cairo_status_t status;
+    cairo_bool_t ret;
+
+    if (target->finish_surface != NULL) {
+	status = target->finish_surface (surface);
+	if (status) {
+	    cairo_test_log (ctx, "Error: Failed to finish surface: %s\n",
+		    cairo_status_to_string (status));
+	    cairo_surface_destroy (surface);
+	    return FALSE;
+	}
+    }
+
+    xasprintf (&png_name,  "%s.out.png", base_name);
+    xasprintf (&diff_name, "%s.diff.png", base_name);
+
+    test_image = target->get_image_surface (surface, 0, PAGE_SIZE, PAGE_SIZE);
+    if (cairo_surface_status (test_image)) {
+	cairo_test_log (ctx, "Error: Failed to extract page: %s\n",
+		        cairo_status_to_string (cairo_surface_status (test_image)));
+	cairo_surface_destroy (test_image);
+	free (png_name);
+	free (diff_name);
+	return FALSE;
+    }
+
+    _xunlink (ctx, png_name);
+    status = cairo_surface_write_to_png (test_image, png_name);
+    if (status) {
+	cairo_test_log (ctx, "Error: Failed to write output image: %s\n",
+		cairo_status_to_string (status));
+	cairo_surface_destroy (test_image);
+	free (png_name);
+	free (diff_name);
+	return FALSE;
+    }
+
+    format = cairo_boilerplate_content_name (target->content);
+    ref_name = cairo_test_reference_filename (ctx,
+					      base_name,
+					      test_name,
+					      target->name,
+					      target->basename,
+					      format,
+					      CAIRO_TEST_REF_SUFFIX,
+					      CAIRO_TEST_PNG_EXTENSION);
+    if (ref_name == NULL) {
+	cairo_test_log (ctx, "Error: Cannot find reference image for %s\n",
+		        base_name);
+	cairo_surface_destroy (test_image);
+	free (png_name);
+	free (diff_name);
+	return FALSE;
+    }
+
+    ref_image = cairo_test_get_reference_image (ctx, ref_name,
+	    target->content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED);
+    if (cairo_surface_status (ref_image)) {
+	cairo_test_log (ctx, "Error: Cannot open reference image for %s: %s\n",
+		        ref_name,
+		cairo_status_to_string (cairo_surface_status (ref_image)));
+	cairo_surface_destroy (ref_image);
+	cairo_surface_destroy (test_image);
+	free (png_name);
+	free (diff_name);
+	free (ref_name);
+	return FALSE;
+    }
+
+    diff_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+	    PAGE_SIZE, PAGE_SIZE);
+
+    ret = TRUE;
+    status = image_diff (ctx,
+	    test_image, ref_image, diff_image,
+	    &result);
+    _xunlink (ctx, diff_name);
+    if (status) {
+	cairo_test_log (ctx, "Error: Failed to compare images: %s\n",
+			cairo_status_to_string (status));
+	ret = FALSE;
+    } else if (image_diff_is_failure (&result, target->error_tolerance))
+    {
+	ret = FALSE;
+
+	status = cairo_surface_write_to_png (diff_image, diff_name);
+	if (status) {
+	    cairo_test_log (ctx, "Error: Failed to write differences image: %s\n",
+		    cairo_status_to_string (status));
+	}
+    }
+
+    cairo_surface_destroy (test_image);
+    cairo_surface_destroy (diff_image);
+    free (png_name);
+    free (diff_name);
+    free (ref_name);
+
+    return ret;
+}
+
+#define TEST_ROWS 2
+#define TEST_COLS 3
+
+static void
+draw (cairo_t *cr, cairo_surface_t *recordings[TEST_ROWS][TEST_COLS])
+{
+    cairo_translate (cr, PAD, PAD);
+    for (int row = 0; row < TEST_ROWS; row++) {
+        cairo_save (cr);
+        for (int col = 0; col < TEST_COLS; col++) {
+            cairo_save (cr);
+            cairo_set_source_surface (cr, recordings[row][col], 0, 0);
+            cairo_paint (cr);
+            cairo_restore (cr);
+            cairo_translate (cr, SIZE + PAD, 0);
+        }
+        cairo_restore (cr);
+        cairo_translate (cr, 0, SIZE + PAD);
+    }
+}
+
+#define NUM_TEST_SURFACES 3
+
+typedef struct _test_surface {
+    const cairo_boilerplate_target_t *target;
+    cairo_surface_t *surface;
+    char *base_name;
+    void *closure;
+} test_surface_t;
+
+static test_surface_t test_surfaces[NUM_TEST_SURFACES];
+
+static void
+init_test_surfaces()
+{
+    memset (test_surfaces, 0, sizeof (*test_surfaces));
+    test_surfaces[0].surface = cairo_pdf_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE);
+    test_surfaces[1].surface = cairo_ps_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE);
+    test_surfaces[2].surface = cairo_svg_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE);
+}
+
+static void
+add_test_surface (const cairo_boilerplate_target_t *target,
+                  cairo_surface_t *surface,
+                  char *base_name,
+                  void *closure)
+{
+    for (int i = 0; i < NUM_TEST_SURFACES; i++) {
+        if (cairo_surface_get_type (test_surfaces[i].surface) == cairo_surface_get_type (surface)) {
+            cairo_surface_destroy (test_surfaces[i].surface);
+            test_surfaces[i].target = target;
+            test_surfaces[i].surface = surface;
+            test_surfaces[i].base_name = base_name;
+            test_surfaces[i].closure = closure;
+            break;
+        }
+    }
+}
+
+static void
+destroy_test_surfaces()
+{
+    for (int i = 0; i < NUM_TEST_SURFACES; i++) {
+        cairo_surface_destroy (test_surfaces[i].surface);
+        if (test_surfaces[i].target && test_surfaces[i].target->cleanup)
+            test_surfaces[i].target->cleanup (test_surfaces[i].closure);
+        if (test_surfaces[i].base_name)
+            free (test_surfaces[i].base_name);
+    }
+}
+
+
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+    cairo_t *cr;
+    cairo_test_status_t ret = CAIRO_TEST_UNTESTED;
+    unsigned int i;
+    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
+    cairo_surface_t *recordings[TEST_ROWS][TEST_COLS];
+    const char *test_name = "create-regions";
+    char *base_name;
+
+    /* Each row displays three recordings. One with operations
+     * supported by all paginated surfaces (OVER), one with operations
+     * supported by PDF but not PS or SVG surfaces (DIFFERENCE), and
+     * one with operations supported by SVG but not PDF or PS
+     * (DEST_XOR).
+     *
+     * The recordings for the first row is a single recording. The
+     * recordings for the second row contains the first row recording
+     * inside another recording to test the use of cloned recording
+     * surfaces.
+     *
+     * We are looking to see that fallback images are only used for
+     * unsupported operations.
+     */
+    create_recordings (CAIRO_OPERATOR_OVER,       &recordings[0][0], &recordings[1][0]);
+    create_recordings (CAIRO_OPERATOR_DIFFERENCE, &recordings[0][1], &recordings[1][1]);
+    create_recordings (CAIRO_OPERATOR_XOR,        &recordings[0][2], &recordings[1][2]);
+
+    init_test_surfaces();
+    for (i = 0; i < ctx->num_targets; i++) {
+	const cairo_boilerplate_target_t *target = ctx->targets_to_test[i];
+	cairo_surface_t *surface = NULL;
+        void *closure;
+	const char *format;
+
+        /* This test only works on surfaces that support fine grained fallbacks */
+        if (! (target->expected_type == CAIRO_SURFACE_TYPE_PDF ||
+               target->expected_type == CAIRO_SURFACE_TYPE_PS ||
+               target->expected_type ==CAIRO_SURFACE_TYPE_SVG))
+	    continue;
+
+	if (! cairo_test_is_target_enabled (ctx, target->name))
+	    continue;
+
+	if (ret == CAIRO_TEST_UNTESTED)
+	    ret = CAIRO_TEST_SUCCESS;
+
+	format = cairo_boilerplate_content_name (target->content);
+        base_name = NULL;
+        xasprintf (&base_name, "%s/%s.%s.%s",
+                   path, test_name,
+                   target->name,
+                   format);
+
+        surface = (target->create_surface) (base_name,
+                                            target->content,
+                                            PAGE_SIZE, PAGE_SIZE,
+                                            PAGE_SIZE, PAGE_SIZE,
+                                            CAIRO_BOILERPLATE_MODE_TEST,
+                                            &closure);
+        if (surface == NULL || cairo_surface_status (surface)) {
+            cairo_test_log (ctx, "Failed to generate surface: %s.%s\n",
+                            target->name,
+                            format);
+            ret = CAIRO_TEST_FAILURE;
+            break;
+        }
+
+        cairo_surface_set_fallback_resolution (surface, FALLBACK_PPI, FALLBACK_PPI);
+        add_test_surface (target, surface, base_name, closure);
+    }
+ 
+    for (int i = 0; i < NUM_TEST_SURFACES; i++) {
+	cairo_status_t status;
+
+        if (test_surfaces[i].target != NULL) {
+            cairo_test_log (ctx,
+                            "Testing create-regions with %s target\n",
+                            test_surfaces[i].target->name);
+            printf ("%s:\t", test_surfaces[i].base_name);
+            fflush (stdout);
+        }
+
+        cr = cairo_create (test_surfaces[i].surface);
+
+        draw (cr, recordings);
+
+        status = cairo_status (cr);
+        cairo_destroy (cr);
+        cairo_surface_finish (test_surfaces[i].surface);
+
+        if (test_surfaces[i].target) {
+            cairo_bool_t pass = FALSE;
+            if (status) {
+                cairo_test_log (ctx, "Error: Failed to create target surface: %s\n",
+                                cairo_status_to_string (status));
+                ret = CAIRO_TEST_FAILURE;
+            } else {
+                /* extract the image and compare it to our reference */
+                if (! check_result (ctx, test_surfaces[i].target, test_name, test_surfaces[i].base_name, test_surfaces[i].surface))
+                    ret = CAIRO_TEST_FAILURE;
+                else
+                    pass = TRUE;
+            }
+
+            if (pass) {
+                printf ("PASS\n");
+            } else {
+                printf ("FAIL\n");
+            }
+            fflush (stdout);
+        }
+    }
+
+    destroy_test_surfaces();
+
+    for (int row = 0; row < TEST_ROWS; row++) {
+        for (int col = 0; col < TEST_COLS; col++) {
+            cairo_surface_destroy (recordings[row][col]);
+        }
+    }
+
+    return ret;
+}
+
+CAIRO_TEST (create_regions,
+	    "Check region analysis when re-used with different surfaces",
+	    "fallback", /* keywords */
+	    NULL, /* requirements */
+	    0, 0,
+	    preamble, NULL)
+
+#endif /* CAIRO_HAS_PDF_SURFACE && CAIRO_HAS_PS_SURFACE && CAIRO_HAS_SVG_SURFACE */
diff --git a/test/meson.build b/test/meson.build
index 28047cb04..e852914da 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -492,6 +492,7 @@ test_multi_page_sources = [
 ]
 
 test_fallback_resolution_sources = [
+  'create-regions.c',
   'fallback-resolution.c',
 ]
 
diff --git a/test/reference/create-regions.pdf.ref.png b/test/reference/create-regions.pdf.ref.png
new file mode 100644
index 000000000..5762fbbc7
Binary files /dev/null and b/test/reference/create-regions.pdf.ref.png differ
diff --git a/test/reference/create-regions.ps.ref.png b/test/reference/create-regions.ps.ref.png
new file mode 100644
index 000000000..0478e249d
Binary files /dev/null and b/test/reference/create-regions.ps.ref.png differ
diff --git a/test/reference/create-regions.svg.ref.png b/test/reference/create-regions.svg.ref.png
new file mode 100644
index 000000000..a89dfb044
Binary files /dev/null and b/test/reference/create-regions.svg.ref.png differ


More information about the cairo-commit mailing list