[cairo-commit] 4 commits - src/cairo-analysis-surface.c src/cairo-array.c src/cairo-array-private.h src/cairo.h src/cairo-image-source.c src/cairoint.h src/cairo-list-inline.h src/cairo-paginated-surface.c src/cairo-pdf-interchange.c src/cairo-pdf-operators.c src/cairo-pdf-surface.c src/cairo-pdf-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-backend-private.h src/cairo-tag-attributes.c src/cairo-tag-attributes-private.h src/cairo-tag-stack.c src/cairo-tag-stack-private.h src/cairo-traps-compositor.c src/cairo-types-private.h src/cairo-xcb-surface-render.c src/cairo-xlib-source.c test/cairo-test.c test/cairo-test.h test/check-pdf-structure.sh test/meson.build test/pdf-structure.c test/pdf-tagged-text.c test/reference

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Tue Apr 18 11:06:48 UTC 2023


 src/cairo-analysis-surface.c                              |  112 -
 src/cairo-array-private.h                                 |    7 
 src/cairo-array.c                                         |   36 
 src/cairo-image-source.c                                  |    2 
 src/cairo-list-inline.h                                   |  158 +
 src/cairo-paginated-surface.c                             |   20 
 src/cairo-pdf-interchange.c                               | 1427 +++++++++++---
 src/cairo-pdf-operators.c                                 |   14 
 src/cairo-pdf-surface-private.h                           |  199 +
 src/cairo-pdf-surface.c                                   |  441 +++-
 src/cairo-quartz-surface.c                                |    3 
 src/cairo-recording-surface-private.h                     |   24 
 src/cairo-recording-surface.c                             |   76 
 src/cairo-script-surface.c                                |    3 
 src/cairo-spans-compositor.c                              |    2 
 src/cairo-surface-backend-private.h                       |   25 
 src/cairo-tag-attributes-private.h                        |   30 
 src/cairo-tag-attributes.c                                |  145 +
 src/cairo-tag-stack-private.h                             |    9 
 src/cairo-tag-stack.c                                     |   37 
 src/cairo-traps-compositor.c                              |    2 
 src/cairo-types-private.h                                 |   11 
 src/cairo-xcb-surface-render.c                            |    3 
 src/cairo-xlib-source.c                                   |    3 
 src/cairo.h                                               |    2 
 src/cairoint.h                                            |    1 
 test/cairo-test.c                                         |    8 
 test/cairo-test.h                                         |    2 
 test/check-pdf-structure.sh                               |   21 
 test/meson.build                                          |    7 
 test/pdf-structure.c                                      |  568 +++++
 test/pdf-tagged-text.c                                    |   62 
 test/reference/pdf-structure-group-ref.ref.txt            |    6 
 test/reference/pdf-structure-group.ref.txt                |    6 
 test/reference/pdf-structure-multipage-group.ref.txt      |    8 
 test/reference/pdf-structure-multipage-group2.ref.txt     |   10 
 test/reference/pdf-structure-multipage-simple-ref.ref.txt |   12 
 test/reference/pdf-structure-multipage-simple.ref.txt     |   12 
 test/reference/pdf-structure-repeated-group.ref.txt       |    9 
 test/reference/pdf-structure-simple-ref.ref.txt           |   11 
 test/reference/pdf-structure-simple.ref.txt               |   11 
 41 files changed, 3053 insertions(+), 492 deletions(-)

New commits:
commit 17ed347819d3928c0b050835ef3a17a891c124bc
Merge: 5e74744df de9452438
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Tue Apr 18 11:06:46 2023 +0000

    Merge branch 'issue-508' into 'master'
    
    Fix cairo_tag_begin/end in groups
    
    Closes #508
    
    See merge request cairo/cairo!463

commit de9452438e25f6f8ba63c769a5663014fdbb699d
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Fri Mar 3 22:07:25 2023 +1030

    pdf: Don't use snprintf() to print floats
    
    It is not locale independent.

diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index ca83e61ed..459f90a8f 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -2476,7 +2476,6 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 					int                        struct_parents)
 {
     cairo_int_status_t status;
-    char buf[1000];
 
     assert (surface->pdf_stream.active == FALSE);
     assert (surface->group_stream.active == FALSE);
@@ -2488,9 +2487,9 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
     if (is_form) {
 	assert (bbox != NULL);
 
+	cairo_output_stream_t *mem_stream = _cairo_memory_stream_create ();
 	if (is_group) {
-	    snprintf(buf,
-		     sizeof(buf),
+	    _cairo_output_stream_printf (mem_stream,
 		     "   /Type /XObject\n"
 		     "   /Subtype /Form\n"
 		     "   /BBox [ %f %f %f %f ]\n"
@@ -2507,8 +2506,7 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 		     bbox->p2.y,
 		     surface->content_resources.id);
 	} else {
-	    snprintf(buf,
-		     sizeof(buf),
+	    _cairo_output_stream_printf (mem_stream,
 		     "   /Type /XObject\n"
 		     "   /Subtype /Form\n"
 		     "   /BBox [ %f %f %f %f ]\n"
@@ -2520,15 +2518,25 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 		     surface->content_resources.id);
 	}
 	if (struct_parents >= 0) {
-	    snprintf(buf + strlen(buf),
-		     sizeof(buf) - strlen(buf),
+	    _cairo_output_stream_printf (mem_stream,
 		"   /StructParents %d\n", struct_parents);
 	}
+
+	unsigned char *data;
+	unsigned long length;
+	status = _cairo_memory_stream_destroy (mem_stream, &data, &length);
+	if (unlikely (status))
+	    return status;
+
+	char *str = _cairo_strndup ((const char*)data, length); /* Add NULL terminator */
+
 	status =
 	    _cairo_pdf_surface_open_stream (surface,
 					    resource,
 					    surface->compress_streams,
-					    buf);
+					    str);
+	free (str);
+	free (data);
     } else {
 	status =
 	    _cairo_pdf_surface_open_stream (surface,
commit b53b48116e610d61cdf630c24a11b59a18345e16
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Feb 19 21:10:58 2023 +1030

    Make cairo_tag_begin/end work correctly in groups
    
    Fixes #508

diff --git a/src/cairo-analysis-surface.c b/src/cairo-analysis-surface.c
index 0e7ba8a38..6889be38f 100644
--- a/src/cairo-analysis-surface.c
+++ b/src/cairo-analysis-surface.c
@@ -263,7 +263,8 @@ static cairo_int_status_t
 _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
 				    const cairo_pattern_t    *pattern,
 				    cairo_rectangle_int_t    *extents,
-				    unsigned int             *regions_id)
+				    unsigned int             *regions_id,
+				    cairo_analysis_source_t   source_type)
 {
     const cairo_surface_pattern_t *surface_pattern;
     cairo_analysis_surface_t *tmp;
@@ -273,6 +274,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
     cairo_int_status_t analysis_status = CAIRO_INT_STATUS_SUCCESS;
     cairo_bool_t surface_is_unbounded;
     cairo_bool_t unused;
+    cairo_bool_t replay_all;
 
     assert (pattern->type == CAIRO_PATTERN_TYPE_SURFACE);
     surface_pattern = (const cairo_surface_pattern_t *) pattern;
@@ -288,7 +290,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
     tmp = (cairo_analysis_surface_t *)
 	_cairo_analysis_surface_create (surface->target, surface->create_region_ids);
     if (unlikely (tmp->base.status)) {
-	status =tmp->base.status;
+	status = tmp->base.status;
 	goto cleanup1;
     }
     proxy = attach_proxy (source, &tmp->base);
@@ -298,7 +300,6 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
     assert (status == CAIRO_INT_STATUS_SUCCESS);
     _cairo_analysis_surface_set_ctm (&tmp->base, &p2d);
 
-
     source = _cairo_surface_get_source (source, NULL);
     surface_is_unbounded = (pattern->extend == CAIRO_EXTEND_REPEAT
 			    || pattern->extend == CAIRO_EXTEND_REFLECT);
@@ -307,22 +308,54 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
 	status = _cairo_recording_surface_region_array_attach (source, regions_id);
 	if (unlikely (status))
 	    goto cleanup2;
+    }
+
+    replay_all = FALSE;
+    if (surface->target->backend->analyze_recording_surface) {
+	status = surface->target->backend->analyze_recording_surface (
+	    surface->target,
+	    surface_pattern,
+	    surface->create_region_ids ? *regions_id : 0,
+	    source_type,
+	    TRUE);
+        if (status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
+	    /* Ensure all commands are replayed even if previously
+	     * replayed and assigned to a region.*/
+            replay_all = TRUE;
+            status = CAIRO_INT_STATUS_SUCCESS;
+        }
+        if (unlikely (status))
+            goto cleanup3;
+    }
 
+    if (surface->create_region_ids) {
 	status = _cairo_recording_surface_replay_and_create_regions (source,
 								     *regions_id,
 								     &pattern->matrix,
 								     &tmp->base,
-								     surface_is_unbounded);
+								     surface_is_unbounded,
+								     replay_all);
 	if (unlikely (status))
-	    goto cleanup2;
+	    goto cleanup3;
     } else {
-	status = _cairo_recording_surface_replay_with_clip (source,
-							    &pattern->matrix,
-							    &tmp->base,
-							    NULL, /* target clip */
-							    surface_is_unbounded);
+	status = _cairo_recording_surface_replay_with_transform (source,
+								 &pattern->matrix,
+								 &tmp->base,
+								 surface_is_unbounded,
+								 replay_all);
 	if (unlikely (status))
-	    goto cleanup2;
+	    goto cleanup3;
+    }
+
+    if (surface->target->backend->analyze_recording_surface) {
+	status = surface->target->backend->analyze_recording_surface (
+	    surface->target,
+	    surface_pattern,
+	    surface->create_region_ids ? *regions_id : 0,
+	    source_type,
+	    FALSE);
+        if (unlikely (status))
+            goto cleanup3;
     }
 
     /* black background or mime data fills entire extents */
@@ -339,7 +372,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
 	    if (status == CAIRO_INT_STATUS_IMAGE_FALLBACK)
 		status = CAIRO_INT_STATUS_SUCCESS;
 	    if (unlikely (status))
-		goto cleanup2;
+		goto cleanup3;
 	}
     }
 
@@ -363,6 +396,10 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
 	_cairo_box_round_to_rectangle (&tmp->page_bbox, extents);
     }
 
+  cleanup3:
+    if (surface->create_region_ids && unlikely (status)) {
+	_cairo_recording_surface_region_array_remove (source, *regions_id);
+    }
   cleanup2:
     detach_proxy (proxy);
   cleanup1:
@@ -454,7 +491,8 @@ _cairo_analysis_surface_paint (void			*abstract_surface,
 	backend_status = _analyze_recording_surface_pattern (surface,
 							     source,
 							     &rec_extents,
-							     &surface->source_region_id);
+							     &surface->source_region_id,
+							     CAIRO_ANALYSIS_SOURCE_PAINT);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -500,7 +538,8 @@ _cairo_analysis_surface_mask (void			*abstract_surface,
 		    _analyze_recording_surface_pattern (surface,
 							source,
 							&rec_extents,
-							&surface->source_region_id);
+							&surface->source_region_id,
+							CAIRO_ANALYSIS_SOURCE_MASK);
 		if (_cairo_int_status_is_error (backend_source_status))
 		    return backend_source_status;
 
@@ -516,7 +555,8 @@ _cairo_analysis_surface_mask (void			*abstract_surface,
 		    _analyze_recording_surface_pattern (surface,
 							mask,
 							&rec_extents,
-							&surface->mask_region_id);
+							&surface->mask_region_id,
+							CAIRO_ANALYSIS_MASK_MASK);
 		if (_cairo_int_status_is_error (backend_mask_status))
 		    return backend_mask_status;
 
@@ -578,7 +618,8 @@ _cairo_analysis_surface_stroke (void			   *abstract_surface,
 	backend_status = _analyze_recording_surface_pattern (surface,
 							     source,
 							     &rec_extents,
-							     &surface->source_region_id);
+							     &surface->source_region_id,
+							     CAIRO_ANALYSIS_SOURCE_STROKE);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -633,7 +674,8 @@ _cairo_analysis_surface_fill (void			*abstract_surface,
 	backend_status = _analyze_recording_surface_pattern (surface,
 							     source,
 							     &rec_extents,
-							     &surface->source_region_id);
+							     &surface->source_region_id,
+							     CAIRO_ANALYSIS_SOURCE_FILL);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -703,7 +745,8 @@ _cairo_analysis_surface_show_glyphs (void		  *abstract_surface,
 	backend_status = _analyze_recording_surface_pattern (surface,
 							     source,
 							     &rec_extents,
-							     &surface->source_region_id);
+							     &surface->source_region_id,
+							     CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -787,7 +830,8 @@ _cairo_analysis_surface_show_text_glyphs (void			    *abstract_surface,
 	_analyze_recording_surface_pattern (surface,
 					    source,
 					    &rec_extents,
-					    &surface->source_region_id);
+					    &surface->source_region_id,
+					    CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS);
 	_cairo_rectangle_intersect (&extents, &rec_extents);
     }
 
@@ -839,6 +883,25 @@ _cairo_analysis_surface_supports_color_glyph (void                 *abstract_sur
     return TRUE;
 }
 
+static cairo_int_status_t
+_cairo_analysis_surface_command_id (void                 *abstract_surface,
+				    unsigned int          recording_id,
+				    unsigned int          command_id)
+{
+    cairo_analysis_surface_t *surface = abstract_surface;
+    cairo_int_status_t backend_status;
+
+    backend_status = CAIRO_INT_STATUS_SUCCESS;
+    if (surface->target->backend->command_id != NULL) {
+	backend_status =
+	    surface->target->backend->command_id (surface->target,
+						  recording_id,
+						  command_id);
+    }
+
+    return backend_status;
+}
+
 static const cairo_surface_backend_t cairo_analysis_surface_backend = {
     CAIRO_INTERNAL_SURFACE_TYPE_ANALYSIS,
 
@@ -874,7 +937,9 @@ static const cairo_surface_backend_t cairo_analysis_surface_backend = {
     _cairo_analysis_surface_show_text_glyphs,
     NULL, /* get_supported_mime_types */
     _cairo_analysis_surface_tag,
-    _cairo_analysis_surface_supports_color_glyph
+    _cairo_analysis_surface_supports_color_glyph,
+    NULL, /* analyze_recording_surface */
+    _cairo_analysis_surface_command_id,
 };
 
 cairo_surface_t *
@@ -1135,7 +1200,12 @@ static const cairo_surface_backend_t cairo_null_surface_backend = {
     NULL, /* fill_stroke */
     _show_glyphs_return_success,    /* show_glyphs */
     NULL, /* has_show_text_glyphs */
-    NULL  /* show_text_glyphs */
+    NULL, /* show_text_glyphs */
+    NULL, /* get_supported_mime_types */
+    NULL, /* tag */
+    NULL, /* supports_color_glyph */
+    NULL, /* analyze_recording_surface */
+    NULL, /* command_id*/
 };
 
 cairo_surface_t *
diff --git a/src/cairo-array-private.h b/src/cairo-array-private.h
index 8e779d41a..ff5b51f50 100644
--- a/src/cairo-array-private.h
+++ b/src/cairo-array-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
@@ -88,6 +89,12 @@ _cairo_array_size (const cairo_array_t *array);
 cairo_private void
 _cairo_array_sort (const cairo_array_t *array, int (*compar)(const void *, const void *));
 
+cairo_private cairo_bool_t
+_cairo_array_pop_element (cairo_array_t *array, void *dst);
+
+cairo_private void *
+_cairo_array_last_element (cairo_array_t *array);
+
 CAIRO_END_DECLS
 
 #endif /* CAIRO_ARRAY_PRIVATE_H */
diff --git a/src/cairo-array.c b/src/cairo-array.c
index 1c91b7c73..064b63f29 100644
--- a/src/cairo-array.c
+++ b/src/cairo-array.c
@@ -542,3 +542,39 @@ _cairo_array_sort (const cairo_array_t *array, int (*compar)(const void *, const
 {
     qsort (array->elements, array->num_elements, array->element_size, compar);
 }
+
+/**
+ * _cairo_array_pop_element:
+ * @array: a #cairo_array_t
+ * Returns: A TRUE if element successfully popped, FALSE if the array is empty.
+ *
+ * Copy the last element out of the array from index @index into the
+ * location pointed to by @dst and remove the element from the array.
+ **/
+cairo_bool_t
+_cairo_array_pop_element (cairo_array_t *array, void *dst)
+{
+    if (array->num_elements > 0) {
+	_cairo_array_copy_element (array, array->num_elements - 1, dst);
+	array->num_elements--;
+	return TRUE;
+    }
+
+    return FALSE;
+}
+
+/**
+ * _cairo_array_top_element:
+ * @array: a #cairo_array_t
+ * Returns: A pointer to the last of object or NULL if array is empty.
+ *
+ * Get the pointer to the last element of of the array.
+ **/
+void *
+_cairo_array_last_element (cairo_array_t *array)
+{
+    if (array->num_elements > 0)
+	return _cairo_array_index (array, array->num_elements - 1);
+
+    return NULL;
+}
diff --git a/src/cairo-image-source.c b/src/cairo-image-source.c
index aafdaeded..b8c1c88f5 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, FALSE);
+    status = _cairo_recording_surface_replay_with_clip (source, m, clone, NULL);
     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 e079a9a28..ac24745e3 100644
--- a/src/cairo-paginated-surface.c
+++ b/src/cairo-paginated-surface.c
@@ -403,6 +403,7 @@ _paint_page (cairo_paginated_surface_t *surface)
     cairo_int_status_t status;
     cairo_bool_t has_supported, has_page_fallback, has_finegrained_fallback;
     unsigned int regions_id = 0;
+    cairo_bool_t replay_all;
 
     if (unlikely (surface->target->status))
 	return surface->target->status;
@@ -416,13 +417,20 @@ _paint_page (cairo_paginated_surface_t *surface)
     if (unlikely (status))
 	goto FAIL;
 
+    replay_all = FALSE;
+    if (surface->target->backend->analyze_recording_surface &&
+        _cairo_recording_surface_has_tags (surface->recording_surface))
+    {
+        replay_all = TRUE;
+    }
+
     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);
+								 NULL, analysis, FALSE, replay_all);
     if (status)
 	goto FAIL;
 
@@ -467,12 +475,12 @@ _paint_page (cairo_paginated_surface_t *surface)
 	has_finegrained_fallback = FALSE;
     }
 
-    if (has_supported) {
-	status = surface->backend->set_paginated_mode (surface->target,
-						       CAIRO_PAGINATED_MODE_RENDER);
-	if (unlikely (status))
-	    goto FAIL;
+    status = surface->backend->set_paginated_mode (surface->target,
+						   CAIRO_PAGINATED_MODE_RENDER);
+    if (unlikely (status))
+	goto FAIL;
 
+    if (has_supported) {
 	status = _cairo_recording_surface_replay_region (surface->recording_surface,
                                                          regions_id,
 							 NULL,
diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
index cb80151d1..2f97b081c 100644
--- a/src/cairo-pdf-interchange.c
+++ b/src/cairo-pdf-interchange.c
@@ -52,6 +52,8 @@
 #include "cairo-array-private.h"
 #include "cairo-error-private.h"
 #include "cairo-output-stream-private.h"
+#include "cairo-recording-surface-private.h"
+#include "cairo-surface-snapshot-inline.h"
 
 #include <time.h>
 
@@ -62,6 +64,275 @@
 #define gmtime_r(T, BUF) (*(BUF) = *gmtime (T))
 #endif
 
+/* #define DEBUG_PDF_INTERCHANGE 1 */
+
+#if DEBUG_PDF_INTERCHANGE
+static void
+print_tree (cairo_pdf_surface_t *surface, cairo_pdf_struct_tree_node_t *node);
+
+static void
+print_command (cairo_pdf_command_t *command, int indent);
+
+static void
+print_command_list(cairo_pdf_command_list_t *command_list);
+#endif
+
+static void
+_cairo_pdf_command_init_key (cairo_pdf_command_entry_t *key)
+{
+    key->base.hash = _cairo_hash_uintptr (_CAIRO_HASH_INIT_VALUE, (uintptr_t)key->recording_id);
+    key->base.hash = _cairo_hash_uintptr (key->base.hash, (uintptr_t)key->command_id);
+}
+
+static cairo_bool_t
+_cairo_pdf_command_equal (const void *key_a, const void *key_b)
+{
+    const cairo_pdf_command_entry_t *a = key_a;
+    const cairo_pdf_command_entry_t *b = key_b;
+
+    return a->recording_id == b->recording_id && a->command_id == b->command_id;
+}
+
+static void
+_cairo_pdf_command_pluck (void *entry, void *closure)
+{
+    cairo_pdf_command_entry_t *dest = entry;
+    cairo_hash_table_t *table = closure;
+
+    _cairo_hash_table_remove (table, &dest->base);
+    free (dest);
+}
+
+static cairo_pdf_struct_tree_node_t *
+lookup_node_for_command (cairo_pdf_surface_t    *surface,
+			 unsigned int            recording_id,
+			 unsigned int            command_id)
+{
+    cairo_pdf_command_entry_t entry_key;
+    cairo_pdf_command_entry_t *entry;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    entry_key.recording_id = recording_id;
+    entry_key.command_id = command_id;
+    _cairo_pdf_command_init_key (&entry_key);
+    entry = _cairo_hash_table_lookup (ic->command_to_node_map, &entry_key.base);
+    assert (entry != NULL);
+    return entry->node;
+}
+
+static cairo_int_status_t
+command_list_add (cairo_pdf_surface_t    *surface,
+		  unsigned int            command_id,
+		  cairo_pdf_operation_t   flags)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_t command;
+    cairo_int_status_t status;
+
+    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
+    if (command_id > num_elements) {
+	void *elements;
+	unsigned additional_elements = command_id - num_elements;
+	status = _cairo_array_allocate (&ic->current_commands->commands, additional_elements, &elements);
+	if (unlikely (status))
+	    return status;
+	memset (elements, 0, additional_elements * sizeof(cairo_pdf_command_t));
+    }
+
+    command.group = NULL;
+    command.node = NULL;
+    command.command_id = command_id;
+    command.mcid_index = 0;
+    command.flags = flags;
+    return _cairo_array_append (&ic->current_commands->commands, &command);
+}
+
+static cairo_int_status_t
+command_list_push_group (cairo_pdf_surface_t    *surface,
+			 unsigned int            command_id,
+			 cairo_surface_t        *recording_surface,
+			 unsigned int            region_id)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_t *command;
+    cairo_pdf_command_list_t *group;
+    cairo_pdf_recording_surface_commands_t recording_commands;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+
+    group = _cairo_malloc (sizeof(cairo_pdf_command_list_t));
+    _cairo_array_init (&group->commands, sizeof(cairo_pdf_command_t));
+    group->parent = ic->current_commands;
+
+    command_list_add (surface, command_id, PDF_GROUP);
+    command = _cairo_array_index (&ic->current_commands->commands, command_id);
+    command->group = group;
+    ic->current_commands = group;
+
+    recording_commands.recording_surface = recording_surface;
+    recording_commands.command_list = group;
+    recording_commands.region_id = region_id;
+    status = _cairo_array_append (&ic->recording_surface_commands, &recording_commands);
+
+    return status;
+}
+
+static void
+command_list_pop_group (cairo_pdf_surface_t    *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    ic->current_commands = ic->current_commands->parent;
+}
+
+static cairo_bool_t
+command_list_is_group (cairo_pdf_surface_t    *surface,
+		       unsigned int            command_id)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_t *command;
+    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
+
+    if (command_id >= num_elements)
+	return FALSE;
+
+    command = _cairo_array_index (&ic->current_commands->commands, command_id);
+    return command->flags == PDF_GROUP;
+}
+
+
+/* Is there any content between current command and next
+ * begin/end/group? */
+static cairo_bool_t
+command_list_has_content (cairo_pdf_surface_t    *surface,
+			  unsigned int            command_id,
+			  unsigned int           *content_command_id)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_t *command;
+    unsigned i;
+    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
+
+    for (i = command_id + 1; i < num_elements; i++) {
+	command = _cairo_array_index (&ic->current_commands->commands, i);
+	switch (command->flags) {
+	    case PDF_CONTENT:
+		if (content_command_id)
+		    *content_command_id = i;
+		return TRUE;
+		break;
+	    case PDF_BEGIN:
+	    case PDF_END:
+	    case PDF_GROUP:
+		return FALSE;
+	    case PDF_NONE:
+		break;
+	}
+    }
+    return FALSE;
+}
+
+static void
+command_list_set_mcid (cairo_pdf_surface_t          *surface,
+		       unsigned int                  command_id,
+		       cairo_pdf_struct_tree_node_t *node,
+		       int                           mcid_index)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_t *command;
+
+    command = _cairo_array_index (&ic->current_commands->commands, command_id);
+    command->node = node;
+    command->mcid_index = mcid_index;
+}
+
+static void
+command_list_set_current_recording_commands (cairo_pdf_surface_t    *surface,
+					     cairo_surface_t        *recording_surface,
+					     unsigned int            region_id)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    unsigned i;
+    cairo_pdf_recording_surface_commands_t *commands;
+    unsigned num_elements = _cairo_array_num_elements (&ic->recording_surface_commands);
+
+    for (i = 0; i < num_elements; i++) {
+	commands = _cairo_array_index (&ic->recording_surface_commands, i);
+	if (commands->region_id == region_id) {
+	    ic->current_commands = commands->command_list;
+	    return;
+	}
+    }
+    ASSERT_NOT_REACHED; /* recording_surface not found */
+}
+
+static void
+update_mcid_order (cairo_pdf_surface_t       *surface,
+		   cairo_pdf_command_list_t  *command_list)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_t *command;
+    cairo_pdf_page_mcid_t *mcid_elem;
+    unsigned i;
+    unsigned num_elements = _cairo_array_num_elements (&command_list->commands);
+
+    for (i = 0; i < num_elements; i++) {
+	command = _cairo_array_index (&command_list->commands, i);
+	if (command->node) {
+	    mcid_elem = _cairo_array_index (&command->node->mcid, command->mcid_index);
+	    mcid_elem->order = ic->mcid_order++;
+	}
+
+	if (command->group)
+	    update_mcid_order (surface, command->group);
+    }
+}
+
+static void
+_cairo_pdf_content_tag_init_key (cairo_pdf_content_tag_t *key)
+{
+    key->base.hash = _cairo_hash_string (key->node->attributes.content.id);
+}
+
+static cairo_bool_t
+_cairo_pdf_content_tag_equal (const void *key_a, const void *key_b)
+{
+    const cairo_pdf_content_tag_t *a = key_a;
+    const cairo_pdf_content_tag_t *b = key_b;
+
+    return strcmp (a->node->attributes.content.id, b->node->attributes.content.id) == 0;
+}
+
+static void
+_cairo_pdf_content_tag_pluck (void *entry, void *closure)
+{
+    cairo_pdf_content_tag_t *content_tag = entry;
+    cairo_hash_table_t *table = closure;
+
+    _cairo_hash_table_remove (table, &content_tag->base);
+    free (content_tag);
+}
+
+static cairo_status_t
+lookup_content_node_for_ref_node (cairo_pdf_surface_t           *surface,
+				  cairo_pdf_struct_tree_node_t  *ref_node,
+				  cairo_pdf_struct_tree_node_t **node)
+{
+    cairo_pdf_content_tag_t entry_key;
+    cairo_pdf_content_tag_t *entry;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    entry_key.node = ref_node;
+    _cairo_pdf_content_tag_init_key (&entry_key);
+    entry = _cairo_hash_table_lookup (ic->content_tag_map, &entry_key.base);
+    if (!entry) {
+	return _cairo_tag_error ("CONTENT_REF ref='%s' not found",
+				 ref_node->attributes.content_ref.ref);
+    }
+
+    *node = entry->node;
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static void
 write_rect_to_pdf_quad_points (cairo_output_stream_t   *stream,
 			       const cairo_rectangle_t *rect,
@@ -96,9 +367,11 @@ static cairo_int_status_t
 add_tree_node (cairo_pdf_surface_t           *surface,
 	       cairo_pdf_struct_tree_node_t  *parent,
 	       const char                    *name,
+	       const char                    *attributes,
 	       cairo_pdf_struct_tree_node_t **new_node)
 {
     cairo_pdf_struct_tree_node_t *node;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
 
     node = _cairo_malloc (sizeof(cairo_pdf_struct_tree_node_t));
     if (unlikely (node == NULL))
@@ -111,21 +384,26 @@ add_tree_node (cairo_pdf_surface_t           *surface,
 
     node->parent = parent;
     cairo_list_init (&node->children);
-    _cairo_array_init (&node->mcid, sizeof(struct page_mcid));
-    node->annot_res.id = 0;
+    _cairo_array_init (&node->mcid, sizeof (cairo_pdf_page_mcid_t));
+    node->annot = NULL;
     node->extents.valid = FALSE;
-    cairo_list_init (&node->extents.link);
 
     cairo_list_add_tail (&node->link, &parent->children);
 
-    *new_node = node;
-    return CAIRO_STATUS_SUCCESS;
-}
+    if (strcmp (node->name, CAIRO_TAG_CONTENT) == 0) {
+	node->type = PDF_NODE_CONTENT;
+	status = _cairo_tag_parse_content_attributes (attributes, &node->attributes.content);
+    } else if (strcmp (node->name, CAIRO_TAG_CONTENT_REF) == 0) {
+	node->type = PDF_NODE_CONTENT_REF;
+	status = _cairo_tag_parse_content_ref_attributes (attributes, &node->attributes.content_ref);
+    } else if (strcmp (node->name, "Artifact") == 0) {
+	node->type = PDF_NODE_ARTIFACT;
+    } else {
+	node->type = PDF_NODE_STRUCT;
+    }
 
-static cairo_bool_t
-is_leaf_node (cairo_pdf_struct_tree_node_t *node)
-{
-    return node->parent && cairo_list_is_empty (&node->children) ;
+    *new_node = node;
+    return status;
 }
 
 static void
@@ -137,23 +415,30 @@ free_node (cairo_pdf_struct_tree_node_t *node)
 	return;
 
     cairo_list_foreach_entry_safe (child, next, cairo_pdf_struct_tree_node_t,
-				   &node->children, link)
+				   &node->children,
+				   link)
     {
 	cairo_list_del (&child->link);
 	free_node (child);
     }
     free (node->name);
     _cairo_array_fini (&node->mcid);
+    if (node->type == PDF_NODE_CONTENT)
+	_cairo_tag_free_content_attributes (&node->attributes.content);
+
+    if (node->type == PDF_NODE_CONTENT_REF)
+	_cairo_tag_free_content_ref_attributes (&node->attributes.content_ref);
+
     free (node);
 }
 
 static cairo_status_t
 add_mcid_to_node (cairo_pdf_surface_t          *surface,
 		  cairo_pdf_struct_tree_node_t *node,
-		  int                           page,
+		  unsigned int                  command_id,
 		  int                          *mcid)
 {
-    struct page_mcid mcid_elem;
+    cairo_pdf_page_mcid_t mcid_elem;
     cairo_int_status_t status;
     cairo_pdf_interchange_t *ic = &surface->interchange;
 
@@ -161,12 +446,33 @@ add_mcid_to_node (cairo_pdf_surface_t          *surface,
     if (unlikely (status))
 	return status;
 
-    mcid_elem.page = page;
+    mcid_elem.order = -1;
+    mcid_elem.page = _cairo_array_num_elements (&surface->pages);
+    mcid_elem.xobject_res = ic->current_recording_surface_res;
     mcid_elem.mcid = _cairo_array_num_elements (&ic->mcid_to_tree) - 1;
+    mcid_elem.child_node = NULL;
+    command_list_set_mcid (surface, command_id, node, _cairo_array_num_elements (&node->mcid));
     *mcid = mcid_elem.mcid;
     return _cairo_array_append (&node->mcid, &mcid_elem);
 }
 
+static cairo_status_t
+add_child_to_mcid_array (cairo_pdf_surface_t          *surface,
+			 cairo_pdf_struct_tree_node_t *node,
+			 unsigned int                  command_id,
+			 cairo_pdf_struct_tree_node_t *child)
+{
+    cairo_pdf_page_mcid_t mcid_elem;
+
+    mcid_elem.order = -1;
+    mcid_elem.page = 0;
+    mcid_elem.xobject_res.id = 0;
+    mcid_elem.mcid = 0;
+    mcid_elem.child_node = child;
+    command_list_set_mcid (surface, command_id, node, _cairo_array_num_elements (&node->mcid));
+    return _cairo_array_append (&node->mcid, &mcid_elem);
+}
+
 static cairo_int_status_t
 add_annotation (cairo_pdf_surface_t           *surface,
 		cairo_pdf_struct_tree_node_t  *node,
@@ -177,7 +483,7 @@ add_annotation (cairo_pdf_surface_t           *surface,
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_pdf_annotation_t *annot;
 
-    annot = malloc (sizeof(cairo_pdf_annotation_t));
+    annot = _cairo_malloc (sizeof (cairo_pdf_annotation_t));
     if (unlikely (annot == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
@@ -187,8 +493,16 @@ add_annotation (cairo_pdf_surface_t           *surface,
 	return status;
     }
 
+    if (annot->link_attrs.link_page == 0)
+	annot->link_attrs.link_page = _cairo_array_num_elements (&surface->pages);
+
     annot->node = node;
 
+    annot->res = _cairo_pdf_surface_new_object (surface);
+    if (annot->res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    node->annot = annot;
     status = _cairo_array_append (&ic->annots, &annot);
 
     return status;
@@ -197,10 +511,7 @@ add_annotation (cairo_pdf_surface_t           *surface,
 static void
 free_annotation (cairo_pdf_annotation_t *annot)
 {
-    _cairo_array_fini (&annot->link_attrs.rects);
-    free (annot->link_attrs.dest);
-    free (annot->link_attrs.uri);
-    free (annot->link_attrs.file);
+    _cairo_tag_free_link_attributes (&annot->link_attrs);
     free (annot);
 }
 
@@ -220,15 +531,72 @@ cairo_pdf_interchange_clear_annotations (cairo_pdf_surface_t *surface)
     _cairo_array_truncate (&ic->annots, 0);
 }
 
+static void
+cairo_pdf_interchange_write_node_mcid (cairo_pdf_surface_t            *surface,
+				       cairo_pdf_page_mcid_t          *mcid_elem,
+				       int                             page)
+{
+    cairo_pdf_page_info_t *page_info;
+
+    page_info = _cairo_array_index (&surface->pages, mcid_elem->page - 1);
+    if (mcid_elem->page == page && mcid_elem->xobject_res.id == 0) {
+	_cairo_output_stream_printf (surface->object_stream.stream, "%d ", mcid_elem->mcid);
+    } else {
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "\n       << /Type /MCR ");
+	if (mcid_elem->page != page) {
+	    _cairo_output_stream_printf (surface->object_stream.stream,
+					 "/Pg %d 0 R ",
+					 page_info->page_res.id);
+	}
+	if (mcid_elem->xobject_res.id != 0) {
+	    _cairo_output_stream_printf (surface->object_stream.stream,
+					 "/Stm %d 0 R ",
+					 mcid_elem->xobject_res.id);
+	}
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "/MCID %d >> ",
+				     mcid_elem->mcid);
+    }
+}
+
+static int
+_mcid_order_compare (const void *a,
+		     const void *b)
+{
+    const cairo_pdf_page_mcid_t *mcid_a = a;
+    const cairo_pdf_page_mcid_t *mcid_b = b;
+
+    if (mcid_a->order < mcid_b->order)
+	return -1;
+    else if (mcid_a->order > mcid_b->order)
+	return 1;
+    else
+	return 0;
+}
+
 static cairo_int_status_t
 cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface,
-					 cairo_pdf_struct_tree_node_t   *node)
+					 cairo_pdf_struct_tree_node_t   *node,
+					 int                             depth)
 {
-    struct page_mcid *mcid_elem;
-    int i, num_mcid, first_page;
-    cairo_pdf_resource_t *page_res;
-    cairo_pdf_struct_tree_node_t *child;
+    cairo_pdf_page_mcid_t *mcid_elem, *child_mcid_elem;
+    unsigned i, j, num_mcid;
+    int first_page = 0;
+    cairo_pdf_page_info_t *page_info;
     cairo_int_status_t status;
+    cairo_bool_t has_children = FALSE;
+
+    /* The Root node is written in cairo_pdf_interchange_write_struct_tree(). */
+    if (!node->parent)
+	return CAIRO_STATUS_SUCCESS;
+
+    if (node->type == PDF_NODE_CONTENT ||
+	node->type == PDF_NODE_CONTENT_REF ||
+	node->type == PDF_NODE_ARTIFACT)
+    {
+	return CAIRO_STATUS_SUCCESS;
+    }
 
     status = _cairo_pdf_surface_object_begin (surface, node->res);
     if (unlikely (status))
@@ -241,57 +609,98 @@ cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface
 				 node->name,
 				 node->parent->res.id);
 
-    if (! cairo_list_is_empty (&node->children)) {
-	if (cairo_list_is_singular (&node->children) && node->annot_res.id == 0) {
-	    child = cairo_list_first_entry (&node->children, cairo_pdf_struct_tree_node_t, link);
-	    _cairo_output_stream_printf (surface->object_stream.stream, "   /K %d 0 R\n", child->res.id);
-	} else {
-	    _cairo_output_stream_printf (surface->object_stream.stream, "   /K [ ");
-	    if (node->annot_res.id != 0) {
+    /* Write /K entry (children of this StructElem) */
+    num_mcid = _cairo_array_num_elements (&node->mcid);
+    if (num_mcid > 0 ) {
+	_cairo_array_sort (&node->mcid, _mcid_order_compare);
+	/* Find the first MCID element and use the page number to set /Pg */
+	for (i = 0; i < num_mcid; i++) {
+	    mcid_elem = _cairo_array_index (&node->mcid, i);
+	    assert (mcid_elem->order != -1);
+	    if (mcid_elem->child_node) {
+		if (mcid_elem->child_node->type == PDF_NODE_CONTENT_REF) {
+		    cairo_pdf_struct_tree_node_t *content_node;
+		    status = lookup_content_node_for_ref_node (surface, mcid_elem->child_node, &content_node);
+		    if (status)
+			return status;
+
+		    /* CONTENT_REF will not have child nodes */
+		    if (_cairo_array_num_elements (&content_node->mcid) > 0) {
+			child_mcid_elem = _cairo_array_index (&content_node->mcid, 0);
+			first_page = child_mcid_elem->page;
+			page_info = _cairo_array_index (&surface->pages, first_page - 1);
+			_cairo_output_stream_printf (surface->object_stream.stream,
+						     "   /Pg %d 0 R\n",
+						     page_info->page_res.id);
+			has_children = TRUE;
+			break;
+		    }
+		} else {
+		    has_children = TRUE;
+		}
+	    } else {
+		first_page = mcid_elem->page;
+		page_info = _cairo_array_index (&surface->pages, first_page - 1);
 		_cairo_output_stream_printf (surface->object_stream.stream,
-					     "<< /Type /OBJR /Obj %d 0 R >> ",
-					     node->annot_res.id);
-	    }
-	    cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
-				      &node->children, link)
-	    {
-		_cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
+					     "   /Pg %d 0 R\n",
+					     page_info->page_res.id);
+		has_children = TRUE;
+		break;
 	    }
-	    _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
 	}
-    } else {
-	num_mcid = _cairo_array_num_elements (&node->mcid);
-	if (num_mcid > 0 ) {
-	    mcid_elem = _cairo_array_index (&node->mcid, 0);
-	    first_page = mcid_elem->page;
-	    page_res = _cairo_array_index (&surface->pages, first_page - 1);
-	    _cairo_output_stream_printf (surface->object_stream.stream, "   /Pg %d 0 R\n", page_res->id);
-
-	    if (num_mcid == 1 && node->annot_res.id == 0) {
-		_cairo_output_stream_printf (surface->object_stream.stream, "   /K %d\n", mcid_elem->mcid);
-	    } else {
-		_cairo_output_stream_printf (surface->object_stream.stream, "   /K [ ");
-		if (node->annot_res.id != 0) {
-		    _cairo_output_stream_printf (surface->object_stream.stream,
-						 "<< /Type /OBJR /Obj %d 0 R >> ",
-						 node->annot_res.id);
-		}
-		for (i = 0; i < num_mcid; i++) {
-		    mcid_elem = _cairo_array_index (&node->mcid, i);
-		    page_res = _cairo_array_index (&surface->pages, mcid_elem->page - 1);
-		    if (mcid_elem->page == first_page) {
-			_cairo_output_stream_printf (surface->object_stream.stream, "%d ", mcid_elem->mcid);
+
+	if (has_children || node->annot) {
+	    _cairo_output_stream_printf (surface->object_stream.stream, "   /K ");
+
+	    if (num_mcid > 1 || node->annot)
+		_cairo_output_stream_printf (surface->object_stream.stream, "[ ");
+
+	    for (i = 0; i < num_mcid; i++) {
+		if (node->annot) {
+		    if (node->annot->link_attrs.link_page != first_page) {
+			page_info = _cairo_array_index (&surface->pages, node->annot->link_attrs.link_page - 1);
+			_cairo_output_stream_printf (surface->object_stream.stream,
+						     "<< /Type /OBJR /Pg %d 0 R /Obj %d 0 R >> ",
+						     page_info->page_res.id,
+						     node->annot->res.id);
 		    } else {
 			_cairo_output_stream_printf (surface->object_stream.stream,
-						     "\n       << /Type /MCR /Pg %d 0 R /MCID %d >> ",
-						     page_res->id,
-						     mcid_elem->mcid);
+						     "<< /Type /OBJR /Obj %d 0 R >> ",
+						     node->annot->res.id);
+		    }
+		}
+		mcid_elem = _cairo_array_index (&node->mcid, i);
+		if (mcid_elem->child_node) {
+		    if (mcid_elem->child_node->type == PDF_NODE_CONTENT_REF) {
+			cairo_pdf_struct_tree_node_t *content_node;
+			status = lookup_content_node_for_ref_node (surface, mcid_elem->child_node, &content_node);
+			if (status)
+			    return status;
+
+			assert (content_node->type == PDF_NODE_CONTENT);
+
+			/* CONTENT_REF will not have child nodes */
+			for (j = 0; j < _cairo_array_num_elements (&content_node->mcid); j++) {
+			    child_mcid_elem = _cairo_array_index (&content_node->mcid, j);
+			    cairo_pdf_interchange_write_node_mcid (surface, child_mcid_elem, first_page);
+			}
+		    } else if (mcid_elem->child_node->type != PDF_NODE_CONTENT) {
+			_cairo_output_stream_printf (surface->object_stream.stream,
+						     " %d 0 R ",
+						     mcid_elem->child_node->res.id);
 		    }
+		} else {
+		    cairo_pdf_interchange_write_node_mcid (surface, mcid_elem, first_page);
 		}
-		_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
 	    }
+
+	    if (num_mcid > 1 || node->annot)
+		_cairo_output_stream_printf (surface->object_stream.stream, "]");
 	}
+
+	_cairo_output_stream_printf (surface->object_stream.stream, "\n");
     }
+
     _cairo_output_stream_printf (surface->object_stream.stream,
 				 ">>\n");
 
@@ -324,7 +733,7 @@ _named_dest_pluck (void *entry, void *closure)
     cairo_hash_table_t *table = closure;
 
     _cairo_hash_table_remove (table, &dest->base);
-    free (dest->attrs.name);
+    _cairo_tag_free_dest_attributes (&dest->attrs);
     free (dest);
 }
 
@@ -335,21 +744,20 @@ cairo_pdf_interchange_write_explicit_dest (cairo_pdf_surface_t *surface,
                                           double               x,
                                           double               y)
 {
-    cairo_pdf_resource_t res;
-    double height;
+    cairo_pdf_page_info_t *page_info;
+
+    page_info = _cairo_array_index (&surface->pages, page - 1);
 
-    _cairo_array_copy_element (&surface->page_heights, page - 1, &height);
-    _cairo_array_copy_element (&surface->pages, page - 1, &res);
     if (has_pos) {
        _cairo_output_stream_printf (surface->object_stream.stream,
                                     "[%d 0 R /XYZ %f %f 0]\n",
-                                    res.id,
+                                    page_info->page_res.id,
                                     x,
-                                    height - y);
+                                    page_info->height - y);
     } else {
        _cairo_output_stream_printf (surface->object_stream.stream,
                                     "[%d 0 R /XYZ null null 0]\n",
-                                    res.id);
+                                    page_info->page_res.id);
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -601,7 +1009,8 @@ cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t   *surface,
 
 static cairo_int_status_t
 cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
-				   cairo_pdf_annotation_t *annot)
+				   cairo_pdf_annotation_t *annot,
+				   cairo_bool_t            struct_parents)
 {
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
     cairo_pdf_interchange_t *ic = &surface->interchange;
@@ -621,23 +1030,19 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
 
 	sp = _cairo_array_num_elements (&ic->parent_tree) - 1;
 
-	node->annot_res = _cairo_pdf_surface_new_object (surface);
-	if (node->annot_res.id == 0)
-	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
-	status = _cairo_array_append (&surface->page_annots, &node->annot_res);
-	if (unlikely (status))
-	    return status;
-
-	status = _cairo_pdf_surface_object_begin (surface, node->annot_res);
+	status = _cairo_pdf_surface_object_begin (surface, annot->res);
 	if (unlikely (status))
 	    return status;
 
 	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "<< /Type /Annot\n"
-				     "   /Subtype /Link\n"
-				     "   /StructParent %d\n",
-				     sp);
+				     "   /Subtype /Link\n");
+
+	if (struct_parents) {
+	    _cairo_output_stream_printf (surface->object_stream.stream,
+					 "   /StructParent %d\n",
+					 sp);
+	}
 
 	height = surface->height;
 	if (num_rects > 0) {
@@ -676,7 +1081,7 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
 	    return status;
 
 	_cairo_output_stream_printf (surface->object_stream.stream,
-				     "   /BS << /W 0 >>"
+				     "   /BS << /W 0 >>\n"
 				     ">>\n");
 
 	_cairo_pdf_surface_object_end (surface);
@@ -689,25 +1094,27 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
 static cairo_int_status_t
 cairo_pdf_interchange_walk_struct_tree (cairo_pdf_surface_t          *surface,
 					cairo_pdf_struct_tree_node_t *node,
-					cairo_int_status_t (*func) (cairo_pdf_surface_t *surface,
-								    cairo_pdf_struct_tree_node_t *node))
+					int                           depth,
+					cairo_int_status_t (*func) (cairo_pdf_surface_t          *surface,
+								    cairo_pdf_struct_tree_node_t *node,
+					                            int                           depth))
 {
     cairo_int_status_t status;
     cairo_pdf_struct_tree_node_t *child;
 
-    if (node->parent) {
-	status = func (surface, node);
-	if (unlikely (status))
-	    return status;
-    }
+    status = func (surface, node, depth);
+    if (unlikely (status))
+	return status;
 
+    depth++;
     cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
 			      &node->children, link)
     {
-	status = cairo_pdf_interchange_walk_struct_tree (surface, child, func);
+	status = cairo_pdf_interchange_walk_struct_tree (surface, child, depth, func);
 	if (unlikely (status))
 	    return status;
     }
+    depth--;
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -722,15 +1129,12 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
     if (cairo_list_is_empty (&ic->struct_root->children))
 	return CAIRO_STATUS_SUCCESS;
 
-    surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
-    if (surface->struct_tree_root.id == 0)
-	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
-    ic->struct_root->res = surface->struct_tree_root;
-
-    cairo_pdf_interchange_walk_struct_tree (surface, ic->struct_root, cairo_pdf_interchange_write_node_object);
-
-    child = cairo_list_first_entry (&ic->struct_root->children, cairo_pdf_struct_tree_node_t, link);
+    status = cairo_pdf_interchange_walk_struct_tree (surface,
+						     ic->struct_root,
+						     0,
+						     cairo_pdf_interchange_write_node_object);
+    if (unlikely (status))
+	return status;
 
     status = _cairo_pdf_surface_object_begin (surface, surface->struct_tree_root);
     if (unlikely (status))
@@ -750,6 +1154,9 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
 	cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
 				  &ic->struct_root->children, link)
 	{
+	    if (child->type == PDF_NODE_CONTENT || child->type == PDF_NODE_ARTIFACT)
+		continue;
+
 	    _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
 	}
 	_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
@@ -763,18 +1170,31 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
 }
 
 static cairo_int_status_t
-cairo_pdf_interchange_write_page_annots (cairo_pdf_surface_t *surface)
+cairo_pdf_interchange_write_annots (cairo_pdf_surface_t *surface,
+				    cairo_bool_t         struct_parents)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
-    int num_elems, i;
+    int num_elems, i, page_num;
+    cairo_pdf_page_info_t *page_info;
+    cairo_pdf_annotation_t *annot;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
 
     num_elems = _cairo_array_num_elements (&ic->annots);
     for (i = 0; i < num_elems; i++) {
-	cairo_pdf_annotation_t * annot;
-
 	_cairo_array_copy_element (&ic->annots, i, &annot);
-	status = cairo_pdf_interchange_write_annot (surface, annot);
+	page_num = annot->link_attrs.link_page;
+	if (page_num > (int)_cairo_array_num_elements (&surface->pages)) {
+	    return _cairo_tag_error ("Link attribute: \"link_page=%d\" page exceeds page count (%d)",
+				     page_num,
+				     _cairo_array_num_elements (&surface->pages));
+	}
+
+	page_info = _cairo_array_index (&surface->pages, page_num - 1);
+	status = _cairo_array_append (&page_info->annots, &annot->res);
+	if (status)
+	    return status;
+
+	status = cairo_pdf_interchange_write_annot (surface, annot, struct_parents);
 	if (unlikely (status))
 	    return status;
     }
@@ -783,42 +1203,71 @@ cairo_pdf_interchange_write_page_annots (cairo_pdf_surface_t *surface)
 }
 
 static cairo_int_status_t
-cairo_pdf_interchange_write_page_parent_elems (cairo_pdf_surface_t *surface)
+cairo_pdf_interchange_write_content_parent_elems (cairo_pdf_surface_t *surface)
 {
     int num_elems, i;
     cairo_pdf_struct_tree_node_t *node;
-    cairo_pdf_resource_t res;
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
 
-    surface->page_parent_tree = -1;
     num_elems = _cairo_array_num_elements (&ic->mcid_to_tree);
-    if (num_elems > 0) {
-	res = _cairo_pdf_surface_new_object (surface);
-	if (res.id == 0)
-	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
-	status = _cairo_pdf_surface_object_begin (surface, res);
-	if (unlikely (status))
-	    return status;
+    status = _cairo_pdf_surface_object_begin (surface, ic->content_parent_res);
+    if (unlikely (status))
+	return status;
 
-	_cairo_output_stream_printf (surface->object_stream.stream,
+    _cairo_output_stream_printf (surface->object_stream.stream,
 				     "[\n");
-	for (i = 0; i < num_elems; i++) {
-	    _cairo_array_copy_element (&ic->mcid_to_tree, i, &node);
-	    _cairo_output_stream_printf (surface->object_stream.stream, "  %d 0 R\n", node->res.id);
-	}
-	_cairo_output_stream_printf (surface->object_stream.stream,
-				     "]\n");
-	_cairo_pdf_surface_object_end (surface);
-
-	status = _cairo_array_append (&ic->parent_tree, &res);
-	surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
+    for (i = 0; i < num_elems; i++) {
+	_cairo_array_copy_element (&ic->mcid_to_tree, i, &node);
+	_cairo_output_stream_printf (surface->object_stream.stream, "  %d 0 R\n", node->res.id);
     }
+    _cairo_output_stream_printf (surface->object_stream.stream,
+				 "]\n");
+    _cairo_pdf_surface_object_end (surface);
 
     return status;
 }
 
+static cairo_int_status_t
+cairo_pdf_interchange_apply_extents_from_content_ref (cairo_pdf_surface_t            *surface,
+						      cairo_pdf_struct_tree_node_t   *node,
+						      int                             depth)
+{
+    cairo_int_status_t status;
+
+    if (node->type != PDF_NODE_CONTENT_REF)
+	return CAIRO_STATUS_SUCCESS;
+
+    cairo_pdf_struct_tree_node_t *content_node;
+    status = lookup_content_node_for_ref_node (surface, node, &content_node);
+    if (status)
+	return status;
+
+    /* Merge extents with all parent nodes */
+    node = node->parent;
+    while (node) {
+	if (node->extents.valid) {
+	    _cairo_rectangle_union (&node->extents.extents, &content_node->extents.extents);
+	} else {
+	    node->extents = content_node->extents;
+	}
+	node = node->parent;
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_update_extents (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    return cairo_pdf_interchange_walk_struct_tree (surface,
+						   ic->struct_root,
+						   0,
+						   cairo_pdf_interchange_apply_extents_from_content_ref);
+}
+
 static cairo_int_status_t
 cairo_pdf_interchange_write_parent_tree (cairo_pdf_surface_t *surface)
 {
@@ -1178,6 +1627,7 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
     int i;
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_int_status_t status;
+    cairo_pdf_page_info_t *page_info;
 
     if (ic->num_dests == 0) {
 	ic->dests_res.id = 0;
@@ -1205,10 +1655,8 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
 				 "<< /Names [\n");
     for (i = 0; i < ic->num_dests; i++) {
 	cairo_pdf_named_dest_t *dest = ic->sorted_dests[i];
-	cairo_pdf_resource_t page_res;
 	double x = 0;
 	double y = 0;
-	double height;
 
 	if (dest->attrs.internal)
 	    continue;
@@ -1224,14 +1672,13 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
 	if (dest->attrs.y_valid)
 	    y = dest->attrs.y;
 
-	_cairo_array_copy_element (&surface->pages, dest->page - 1, &page_res);
-	_cairo_array_copy_element (&surface->page_heights, dest->page - 1, &height);
+	page_info = _cairo_array_index (&surface->pages, dest->page - 1);
 	_cairo_output_stream_printf (surface->object_stream.stream,
 				     "   (%s) [%d 0 R /XYZ %f %f 0]\n",
 				     dest->attrs.name,
-				     page_res.id,
+				     page_info->page_res.id,
 				     x,
-				     height - y);
+				     page_info->height - y);
     }
     _cairo_output_stream_printf (surface->object_stream.stream,
 				 "  ]\n"
@@ -1345,32 +1792,79 @@ _cairo_pdf_interchange_begin_structure_tag (cairo_pdf_surface_t    *surface,
 					    const char             *name,
 					    const char             *attributes)
 {
-    int page_num, mcid;
+    int mcid;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
     cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_command_entry_t *command_entry;
+    cairo_pdf_struct_tree_node_t *parent_node;
+    unsigned int content_command_id;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
-	status = add_tree_node (surface, ic->current_node, name, &ic->current_node);
+	ic->content_emitted = FALSE;
+	status = add_tree_node (surface, ic->current_analyze_node, name, attributes, &ic->current_analyze_node);
 	if (unlikely (status))
 	    return status;
 
-	_cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, ic->current_node);
+	status = command_list_add (surface, ic->command_id, PDF_BEGIN);
+	if (unlikely (status))
+	    return status;
+
+	/* Add to command_id to node map. */
+	command_entry = _cairo_malloc (sizeof(cairo_pdf_command_entry_t));
+	command_entry->recording_id = ic->recording_id;
+	command_entry->command_id = ic->command_id;
+	command_entry->node = ic->current_analyze_node;
+	_cairo_pdf_command_init_key (command_entry);
+	status = _cairo_hash_table_insert (ic->command_to_node_map, &command_entry->base);
+	if (unlikely(status))
+	    return status;
 
 	if (tag_type & TAG_TYPE_LINK) {
-	    status = add_annotation (surface, ic->current_node, name, attributes);
+	    status = add_annotation (surface, ic->current_analyze_node, name, attributes);
 	    if (unlikely (status))
 		return status;
+	}
 
-	    cairo_list_add_tail (&ic->current_node->extents.link, &ic->extents_list);
+	if (ic->current_analyze_node->type == PDF_NODE_CONTENT) {
+	    cairo_pdf_content_tag_t *content = _cairo_malloc (sizeof(cairo_pdf_content_tag_t));
+	    content->node = ic->current_analyze_node;
+	    _cairo_pdf_content_tag_init_key (content);
+	    status = _cairo_hash_table_insert (ic->content_tag_map, &content->base);
+	    if (unlikely (status))
+		return status;
 	}
 
+	ic->content_emitted = FALSE;
+
     } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
-	ic->current_node = _cairo_tag_stack_top_elem (&ic->render_tag_stack)->data;
-	assert (ic->current_node != NULL);
-	if (is_leaf_node (ic->current_node)) {
-	    page_num = _cairo_array_num_elements (&surface->pages);
-	    add_mcid_to_node (surface, ic->current_node, page_num, &mcid);
-	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, name, mcid);
+	if (ic->marked_content_open) {
+	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+	    ic->marked_content_open = FALSE;
+	    if (unlikely (status))
+		return status;
+	}
+
+	ic->current_render_node = lookup_node_for_command (surface, ic->recording_id, ic->command_id);
+	if (ic->current_render_node->type == PDF_NODE_ARTIFACT) {
+	    if (command_list_has_content (surface, ic->command_id, NULL)) {
+		status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, name, -1);
+		ic->marked_content_open = TRUE;
+	    }
+	} else if (ic->current_render_node->type == PDF_NODE_CONTENT_REF) {
+	    parent_node = ic->current_render_node->parent;
+	    add_child_to_mcid_array (surface, parent_node, ic->command_id, ic->current_render_node);
+	} else {
+	    parent_node = ic->current_render_node->parent;
+	    add_child_to_mcid_array (surface, parent_node, ic->command_id, ic->current_render_node);
+	    if (command_list_has_content (surface, ic->command_id, &content_command_id)) {
+		add_mcid_to_node (surface, ic->current_render_node, content_command_id, &mcid);
+		const char *tag_name = name;
+		if (ic->current_render_node->type == PDF_NODE_CONTENT)
+		    tag_name = ic->current_render_node->attributes.content.tag_name;
+
+		status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, tag_name, mcid);
+		ic->marked_content_open = TRUE;
+	    }
 	}
     }
 
@@ -1402,15 +1896,13 @@ _cairo_pdf_interchange_begin_dest_tag (cairo_pdf_surface_t    *surface,
 	dest->page = _cairo_array_num_elements (&surface->pages);
 	init_named_dest_key (dest);
 	status = _cairo_hash_table_insert (ic->named_dests, &dest->base);
-	if (unlikely (status))
-	{
+	if (unlikely (status)) {
 	    free (dest->attrs.name);
 	    free (dest);
 	    return status;
 	}
 
 	_cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, dest);
-	cairo_list_add_tail (&dest->extents.link, &ic->extents_list);
 	ic->num_dests++;
     }
 
@@ -1425,22 +1917,21 @@ _cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
     cairo_tag_type_t tag_type;
     cairo_pdf_interchange_t *ic = &surface->interchange;
-    void *ptr;
+
+    if (ic->ignore_current_surface)
+        return CAIRO_STATUS_SUCCESS;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	status = _cairo_tag_stack_push (&ic->analysis_tag_stack, name, attributes);
 
     } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
 	status = _cairo_tag_stack_push (&ic->render_tag_stack, name, attributes);
-	_cairo_array_copy_element (&ic->push_data, ic->push_data_index++, &ptr);
-	_cairo_tag_stack_set_top_data (&ic->render_tag_stack, ptr);
     }
-
     if (unlikely (status))
 	return status;
 
     tag_type = _cairo_tag_get_type (name);
-    if (tag_type & TAG_TYPE_STRUCTURE) {
+    if (tag_type & (TAG_TYPE_STRUCTURE|TAG_TYPE_CONTENT|TAG_TYPE_CONTENT_REF|TAG_TYPE_ARTIFACT)) {
 	status = _cairo_pdf_interchange_begin_structure_tag (surface, tag_type, name, attributes);
 	if (unlikely (status))
 	    return status;
@@ -1452,11 +1943,6 @@ _cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
 	    return status;
     }
 
-    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
-	ptr = _cairo_tag_stack_top_elem (&ic->analysis_tag_stack)->data;
-	status = _cairo_array_append (&ic->push_data, &ptr);
-    }
-
     return status;
 }
 
@@ -1465,59 +1951,39 @@ _cairo_pdf_interchange_end_structure_tag (cairo_pdf_surface_t    *surface,
 					  cairo_tag_type_t        tag_type,
 					  cairo_tag_stack_elem_t *elem)
 {
-    const cairo_pdf_struct_tree_node_t *node;
-    struct tag_extents *tag, *next;
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+    int mcid;
+    unsigned int content_command_id;
 
-    assert (elem->data != NULL);
-    node = elem->data;
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
-	if (tag_type & TAG_TYPE_LINK) {
-	    cairo_list_foreach_entry_safe (tag, next, struct tag_extents,
-					   &ic->extents_list, link) {
-		if (tag == &node->extents) {
-		    cairo_list_del (&tag->link);
-		    break;
-		}
-	    }
-	}
+	assert (ic->current_analyze_node->parent != NULL);
+	status = command_list_add (surface, ic->command_id, PDF_END);
+	if (unlikely (status))
+	    return status;
+
+	ic->content_emitted = FALSE;
+	ic->current_analyze_node = ic->current_analyze_node->parent;
+
     } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
-	if (is_leaf_node (ic->current_node)) {
+	if (ic->marked_content_open) {
 	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+	    ic->marked_content_open = FALSE;
 	    if (unlikely (status))
 		return status;
 	}
-    }
-
-    ic->current_node = ic->current_node->parent;
-    assert (ic->current_node != NULL);
-
-    return status;
-}
-
-static cairo_int_status_t
-_cairo_pdf_interchange_end_dest_tag (cairo_pdf_surface_t    *surface,
-				     cairo_tag_type_t        tag_type,
-				     cairo_tag_stack_elem_t *elem)
-{
-    struct tag_extents *tag, *next;
-    cairo_pdf_named_dest_t *dest;
-    cairo_pdf_interchange_t *ic = &surface->interchange;
-
-    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
-	assert (elem->data != NULL);
-	dest = (cairo_pdf_named_dest_t *) elem->data;
-	cairo_list_foreach_entry_safe (tag, next, struct tag_extents,
-					   &ic->extents_list, link) {
-	    if (tag == &dest->extents) {
-		cairo_list_del (&tag->link);
-		break;
-	    }
+	ic->current_render_node = ic->current_render_node->parent;
+	if (ic->current_render_node->parent &&
+	    command_list_has_content (surface, ic->command_id, &content_command_id))
+	{
+	    add_mcid_to_node (surface, ic->current_render_node, content_command_id, &mcid);
+	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
+						     ic->current_render_node->name, mcid);
+	    ic->marked_content_open = TRUE;
 	}
     }
 
-    return CAIRO_STATUS_SUCCESS;
+    return status;
 }
 
 cairo_int_status_t
@@ -1529,80 +1995,346 @@ _cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
     cairo_tag_type_t tag_type;
     cairo_tag_stack_elem_t *elem;
 
-    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
+    if (ic->ignore_current_surface)
+        return CAIRO_STATUS_SUCCESS;
+
+    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	status = _cairo_tag_stack_pop (&ic->analysis_tag_stack, name, &elem);
-    else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER)
-	status = _cairo_tag_stack_pop (&ic->render_tag_stack, name, &elem);
 
+    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
+	status = _cairo_tag_stack_pop (&ic->render_tag_stack, name, &elem);
+    }
     if (unlikely (status))
 	return status;
 
     tag_type = _cairo_tag_get_type (name);
-    if (tag_type & TAG_TYPE_STRUCTURE) {
+    if (tag_type & (TAG_TYPE_STRUCTURE|TAG_TYPE_CONTENT|TAG_TYPE_CONTENT_REF|TAG_TYPE_ARTIFACT)) {
 	status = _cairo_pdf_interchange_end_structure_tag (surface, tag_type, elem);
 	if (unlikely (status))
 	    goto cleanup;
     }
 
-    if (tag_type & TAG_TYPE_DEST) {
-	status = _cairo_pdf_interchange_end_dest_tag (surface, tag_type, elem);
-	if (unlikely (status))
-	    goto cleanup;
-    }
-
   cleanup:
     _cairo_tag_stack_free_elem (elem);
 
     return status;
 }
 
+cairo_int_status_t
+_cairo_pdf_interchange_command_id (cairo_pdf_surface_t  *surface,
+				   unsigned int          recording_id,
+				   unsigned int          command_id)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    int mcid;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+
+    ic->recording_id = recording_id;
+    ic->command_id = command_id;
+
+    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER && ic->current_render_node) {
+	/* TODO If the group does not have tags we don't need to close the current tag. */
+	if (command_list_is_group (surface, command_id)) {
+	    if (ic->marked_content_open) {
+		status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+		ic->marked_content_open = FALSE;
+	    }
+	    if (command_list_has_content (surface, command_id, NULL)) {
+		ic->render_next_command_has_content = TRUE;
+	    }
+	} else if (ic->render_next_command_has_content) {
+	    add_mcid_to_node (surface, ic->current_render_node, ic->command_id, &mcid);
+	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
+						     ic->current_render_node->name, mcid);
+	    ic->marked_content_open = TRUE;
+	    ic->render_next_command_has_content = FALSE;
+	}
+    }
+
+    return status;
+}
+
+/* Check if this use of recording surface is or will need to be part of the the struct tree */
+cairo_bool_t
+_cairo_pdf_interchange_struct_tree_requires_recording_surface (
+    cairo_pdf_surface_t           *surface,
+    const cairo_surface_pattern_t *recording_surface_pattern,
+    cairo_analysis_source_t        source_type)
+{
+    cairo_surface_t *recording_surface = recording_surface_pattern->surface;
+    cairo_surface_t *free_me = NULL;
+    cairo_bool_t requires_recording = FALSE;
+
+    if (recording_surface_pattern->base.extend != CAIRO_EXTEND_NONE)
+	return FALSE;
+
+    if (_cairo_surface_is_snapshot (recording_surface))
+	free_me = recording_surface = _cairo_surface_snapshot_get_target (recording_surface);
+
+    if (_cairo_recording_surface_has_tags (recording_surface)) {
+	/* Check if tags are to be ignored in this source */
+	switch (source_type) {
+	    case CAIRO_ANALYSIS_SOURCE_PAINT:
+	    case CAIRO_ANALYSIS_SOURCE_FILL:
+		requires_recording = TRUE;
+		break;
+	    case CAIRO_ANALYSIS_SOURCE_MASK: /* TODO: allow SOURCE_MASK with solid MASK_MASK */
+	    case CAIRO_ANALYSIS_MASK_MASK:
+	    case CAIRO_ANALYSIS_SOURCE_STROKE:
+	    case CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS:
+	    case CAIRO_ANALYSIS_SOURCE_NONE:
+		break;
+	}
+    }
+
+    cairo_surface_destroy (free_me);
+    return requires_recording;
+}
+
+/* Called at the start of a recording group during analyze. This will
+ * be called during the analysis of the drawing operation. */
+cairo_int_status_t
+_cairo_pdf_interchange_recording_source_surface_begin (
+    cairo_pdf_surface_t           *surface,
+    const cairo_surface_pattern_t *recording_surface_pattern,
+    unsigned int                   region_id,
+    cairo_analysis_source_t        source_type)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_recording_surface_stack_entry_t element;
+    cairo_int_status_t status;
+
+    /* A new recording surface is being replayed */
+    ic->ignore_current_surface = TRUE;
+    if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
+								       recording_surface_pattern,
+								       source_type))
+    {
+	ic->ignore_current_surface = FALSE;
+    }
+
+    element.ignore_surface = ic->ignore_current_surface;
+    element.current_node = ic->current_analyze_node;
+    ic->content_emitted = FALSE;
+
+    /* Push to stack so that the current source identifiers can be
+     * restored after this recording surface has ended. */
+    status = _cairo_array_append (&ic->recording_surface_stack, &element);
+    if (status)
+	return status;
+
+    if (ic->ignore_current_surface)
+	return CAIRO_STATUS_SUCCESS;
+
+    status = command_list_push_group (surface, ic->command_id, recording_surface_pattern->surface, region_id);
+    if (unlikely (status))
+	return status;
+
+    return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
+}
+
+/* Called at the end of a recording group during analyze. */
+cairo_int_status_t
+_cairo_pdf_interchange_recording_source_surface_end (
+    cairo_pdf_surface_t           *surface,
+    const cairo_surface_pattern_t *recording_surface_pattern,
+    unsigned int                   region_id,
+    cairo_analysis_source_t        source_type)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_recording_surface_stack_entry_t element;
+    cairo_recording_surface_stack_entry_t *element_ptr;
+
+    if (!ic->ignore_current_surface)
+	command_list_pop_group (surface);
+
+    if (_cairo_array_pop_element (&ic->recording_surface_stack, &element)) {
+	element_ptr = _cairo_array_last_element (&ic->recording_surface_stack);
+	if (element_ptr) {
+	    ic->ignore_current_surface = element_ptr->ignore_surface;
+	    assert (ic->current_analyze_node == element_ptr->current_node);
+	} else {
+	    /* Back at the page content. */
+	    ic->ignore_current_surface = FALSE;
+	}
+	ic->content_emitted = FALSE;
+	return CAIRO_STATUS_SUCCESS;
+    }
+    ASSERT_NOT_REACHED; /* _recording_source_surface_begin/end mismatch */
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+/* Called at the start of a recording group during render. This will
+ * be called after the end of page content. */
+cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_begin (cairo_pdf_surface_t     *surface,
+						     cairo_surface_t         *recording_surface,
+						     int                      region_id,
+						     cairo_pdf_resource_t     surface_resource,
+						     int                     *struct_parents)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status;
+
+    /* When
+     * _cairo_pdf_interchange_struct_tree_requires_recording_surface()
+     * is false, the region_id of the recording surface is set to 0.
+     */
+    if (region_id == 0) {
+	ic->ignore_current_surface = TRUE;
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    command_list_set_current_recording_commands (surface, recording_surface, region_id);
+
+    ic->ignore_current_surface = FALSE;
+    _cairo_array_truncate (&ic->mcid_to_tree, 0);
+    ic->current_recording_surface_res = surface_resource;
+
+    ic->content_parent_res = _cairo_pdf_surface_new_object (surface);
+    if (ic->content_parent_res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = _cairo_array_append (&ic->parent_tree, &ic->content_parent_res);
+    if (unlikely (status))
+	return status;
+
+    *struct_parents = _cairo_array_num_elements (&ic->parent_tree) - 1;
+
+    ic->render_next_command_has_content = FALSE;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+/* Called at the end of a recording group during render. */
+cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_end (cairo_pdf_surface_t     *surface,
+						   cairo_surface_t         *recording_surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    if (ic->ignore_current_surface)
+	return CAIRO_STATUS_SUCCESS;
+
+    ic->current_recording_surface_res.id = 0;
+    return cairo_pdf_interchange_write_content_parent_elems (surface);
+}
+
+static void _add_operation_extents_to_dest_tag (cairo_tag_stack_elem_t *elem,
+						void                   *closure)
+{
+    const cairo_rectangle_int_t *extents = (const cairo_rectangle_int_t *) closure;
+    cairo_pdf_named_dest_t *dest;
+
+    if (_cairo_tag_get_type (elem->name)  & TAG_TYPE_DEST) {
+	if (elem->data) {
+	    dest = (cairo_pdf_named_dest_t *) elem->data;
+	    if (dest->extents.valid) {
+		_cairo_rectangle_union (&dest->extents.extents, extents);
+	    } else {
+		dest->extents.extents = *extents;
+		dest->extents.valid = TRUE;
+	    }
+	}
+    }
+}
+
 cairo_int_status_t
 _cairo_pdf_interchange_add_operation_extents (cairo_pdf_surface_t         *surface,
 					      const cairo_rectangle_int_t *extents)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
-    struct tag_extents *tag;
 
+    /* Add extents to current node and all DEST tags on the stack */
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
-	cairo_list_foreach_entry (tag, struct tag_extents, &ic->extents_list, link) {
-	    if (tag->valid) {
-		_cairo_rectangle_union (&tag->extents, extents);
+	if (ic->current_analyze_node) {
+	    if (ic->current_analyze_node->extents.valid) {
+		_cairo_rectangle_union (&ic->current_analyze_node->extents.extents, extents);
 	    } else {
-		tag->extents = *extents;
-		tag->valid = TRUE;
+		ic->current_analyze_node->extents.extents = *extents;
+		ic->current_analyze_node->extents.valid = TRUE;
 	    }
 	}
+
+	_cairo_tag_stack_foreach (&ic->analysis_tag_stack,
+				  _add_operation_extents_to_dest_tag,
+				  (void*)extents);
     }
 
     return CAIRO_STATUS_SUCCESS;
 }
 
+cairo_int_status_t
+_cairo_pdf_interchange_add_content (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+
+    if (ic->ignore_current_surface)
+        return CAIRO_STATUS_SUCCESS;
+
+    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
+	status = command_list_add (surface, ic->command_id, PDF_CONTENT);
+	if (unlikely (status))
+	    return status;
+    }
+
+    return status;
+}
+
+/* Called at the start of 1emiting the page content during analyze or render */
 cairo_int_status_t
 _cairo_pdf_interchange_begin_page_content (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
-    int page_num, mcid;
+    int mcid;
+    unsigned int content_command_id;
+    cairo_pdf_command_list_t *page_commands;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
-	_cairo_array_truncate (&ic->mcid_to_tree, 0);
-	_cairo_array_truncate (&ic->push_data, 0);
-	ic->begin_page_node = ic->current_node;
+	status = _cairo_array_allocate (&ic->page_commands, 1, (void**)&page_commands);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_array_init (&page_commands->commands, sizeof(cairo_pdf_command_t));
+	page_commands->parent = NULL;
+	ic->current_commands = page_commands;
+	ic->ignore_current_surface = FALSE;
     } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
-	ic->push_data_index = 0;
-	ic->current_node = ic->begin_page_node;
-	if (ic->end_page_node && is_leaf_node (ic->end_page_node)) {
-	    page_num = _cairo_array_num_elements (&surface->pages);
-	    add_mcid_to_node (surface, ic->end_page_node, page_num, &mcid);
-	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
-						     ic->end_page_node->name,
-						     mcid);
+	ic->current_commands = _cairo_array_last_element (&ic->page_commands);
+	/* Each page has its own parent tree to map MCID to nodes. */
+	_cairo_array_truncate (&ic->mcid_to_tree, 0);
+	ic->ignore_current_surface = FALSE;
+	ic->content_parent_res = _cairo_pdf_surface_new_object (surface);
+	if (ic->content_parent_res.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	status = _cairo_array_append (&ic->parent_tree, &ic->content_parent_res);
+	if (unlikely (status))
+	    return status;
+
+	surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
+
+	if (ic->next_page_render_node && ic->next_page_render_node->parent &&
+	    command_list_has_content (surface, -1, &content_command_id))
+	{
+	    add_mcid_to_node (surface, ic->next_page_render_node, content_command_id, &mcid);
+	    const char *tag_name = ic->next_page_render_node->name;
+	    if (ic->next_page_render_node->type == PDF_NODE_CONTENT)
+		tag_name = ic->next_page_render_node->attributes.content.tag_name;
+
+	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, tag_name, mcid);
+	    ic->marked_content_open = TRUE;
 	}
+	ic->render_next_command_has_content = FALSE;
     }
 
     return status;
 }
 
+/* Called at the end of emiting the page content during analyze or render */
 cairo_int_status_t
 _cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
 {
@@ -1610,9 +2342,12 @@ _cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
-	ic->end_page_node = ic->current_node;
-	if (is_leaf_node (ic->current_node))
+	/* If a content tag is open across pages, the old page needs an EMC emitted. */
+	if (ic->marked_content_open) {
 	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+	    ic->marked_content_open = FALSE;
+	}
+	ic->next_page_render_node = ic->current_render_node;
     }
 
     return status;
@@ -1621,15 +2356,7 @@ _cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
 cairo_int_status_t
 _cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface)
 {
-    cairo_int_status_t status;
-
-    status = cairo_pdf_interchange_write_page_annots (surface);
-    if (unlikely (status))
-	return status;
-
-    cairo_pdf_interchange_clear_annotations (surface);
-
-    return cairo_pdf_interchange_write_page_parent_elems (surface);
+    return cairo_pdf_interchange_write_content_parent_elems (surface);
 }
 
 cairo_int_status_t
@@ -1638,15 +2365,41 @@ _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface)
     cairo_pdf_interchange_t *ic = &surface->interchange;
     cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
     cairo_tag_stack_structure_type_t tag_type;
+    cairo_bool_t write_struct_tree = FALSE;
+
+    status = cairo_pdf_interchange_update_extents (surface);
+    if (unlikely (status))
+	return status;
 
     tag_type = _cairo_tag_stack_get_structure_type (&ic->analysis_tag_stack);
     if (tag_type == TAG_TREE_TYPE_TAGGED || tag_type == TAG_TREE_TYPE_STRUCTURE ||
-	tag_type == TAG_TREE_TYPE_LINK_ONLY) {
+	tag_type == TAG_TREE_TYPE_LINK_ONLY)
+    {
+	write_struct_tree = TRUE;
+    }
+
+    status = cairo_pdf_interchange_write_annots (surface, write_struct_tree);
+    if (unlikely (status))
+	return status;
+
+    if (write_struct_tree) {
+	surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
+	if (surface->struct_tree_root.id == 0)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	ic->struct_root->res = surface->struct_tree_root;
 
 	status = cairo_pdf_interchange_write_parent_tree (surface);
 	if (unlikely (status))
 	    return status;
 
+	unsigned num_pages = _cairo_array_num_elements (&ic->page_commands);
+	for (unsigned i = 0; i < num_pages; i++) {
+	    cairo_pdf_command_list_t *command_list;
+	    command_list = _cairo_array_index (&ic->page_commands, i);
+	    update_mcid_order (surface, command_list);
+	}
+
 	status = cairo_pdf_interchange_write_struct_tree (surface);
 	if (unlikely (status))
 	    return status;
@@ -1724,16 +2477,27 @@ _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
 
     _cairo_tag_stack_init (&ic->analysis_tag_stack);
     _cairo_tag_stack_init (&ic->render_tag_stack);
-    _cairo_array_init (&ic->push_data, sizeof(void *));
     ic->struct_root = calloc (1, sizeof(cairo_pdf_struct_tree_node_t));
     if (unlikely (ic->struct_root == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
+    ic->struct_root->res.id = 0;
     cairo_list_init (&ic->struct_root->children);
-    _cairo_array_init (&ic->struct_root->mcid, sizeof(struct page_mcid));
-    ic->current_node = ic->struct_root;
-    ic->begin_page_node = NULL;
-    ic->end_page_node = NULL;
+    _cairo_array_init (&ic->struct_root->mcid, sizeof(cairo_pdf_page_mcid_t));
+
+    ic->current_analyze_node = ic->struct_root;
+    ic->current_render_node = NULL;
+    ic->next_page_render_node = ic->struct_root;
+    _cairo_array_init (&ic->recording_surface_stack, sizeof(cairo_recording_surface_stack_entry_t));
+    ic->current_recording_surface_res.id = 0;
+    ic->command_to_node_map = _cairo_hash_table_create (_cairo_pdf_command_equal);
+    if (unlikely (ic->command_to_node_map == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    ic->content_tag_map = _cairo_hash_table_create (_cairo_pdf_content_tag_equal);
+    if (unlikely (ic->content_tag_map == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
     _cairo_array_init (&ic->parent_tree, sizeof(cairo_pdf_resource_t));
     _cairo_array_init (&ic->mcid_to_tree, sizeof(cairo_pdf_struct_tree_node_t *));
     _cairo_array_init (&ic->annots, sizeof(cairo_pdf_annotation_t *));
@@ -1743,9 +2507,18 @@ _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
     if (unlikely (ic->named_dests == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
+    _cairo_array_init (&ic->page_commands, sizeof(cairo_pdf_command_list_t));
+    ic->current_commands = NULL;
+    _cairo_array_init (&ic->recording_surface_commands, sizeof(cairo_pdf_recording_surface_commands_t));
+
     ic->num_dests = 0;
     ic->sorted_dests = NULL;
     ic->dests_res.id = 0;
+    ic->ignore_current_surface = FALSE;
+    ic->content_emitted = FALSE;
+    ic->marked_content_open = FALSE;
+    ic->render_next_command_has_content = FALSE;
+    ic->mcid_order = 0;
 
     _cairo_array_init (&ic->outline, sizeof(cairo_pdf_outline_entry_t *));
     outline_root = calloc (1, sizeof(cairo_pdf_outline_entry_t));
@@ -1772,9 +2545,7 @@ _cairo_pdf_interchange_free_outlines (cairo_pdf_surface_t *surface)
 
 	_cairo_array_copy_element (&ic->outline, i, &outline);
 	free (outline->name);
-	free (outline->link_attrs.dest);
-	free (outline->link_attrs.uri);
-	free (outline->link_attrs.file);
+	_cairo_tag_free_link_attributes (&outline->link_attrs);
 	free (outline);
     }
     _cairo_array_fini (&ic->outline);
@@ -1789,14 +2560,46 @@ _cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
 
     _cairo_tag_stack_fini (&ic->analysis_tag_stack);
     _cairo_tag_stack_fini (&ic->render_tag_stack);
-    _cairo_array_fini (&ic->push_data);
-    free_node (ic->struct_root);
     _cairo_array_fini (&ic->mcid_to_tree);
     cairo_pdf_interchange_clear_annotations (surface);
     _cairo_array_fini (&ic->annots);
+
+    _cairo_array_fini (&ic->recording_surface_stack);
     _cairo_array_fini (&ic->parent_tree);
+
+    _cairo_hash_table_foreach (ic->command_to_node_map,
+			       _cairo_pdf_command_pluck,
+			       ic->command_to_node_map);
+    _cairo_hash_table_destroy (ic->command_to_node_map);
+
     _cairo_hash_table_foreach (ic->named_dests, _named_dest_pluck, ic->named_dests);
     _cairo_hash_table_destroy (ic->named_dests);
+
+    _cairo_hash_table_foreach (ic->content_tag_map, _cairo_pdf_content_tag_pluck, ic->content_tag_map);
+    _cairo_hash_table_destroy(ic->content_tag_map);
+
+    free_node (ic->struct_root);
+
+    num_elems = _cairo_array_num_elements (&ic->recording_surface_commands);
+    for (i = 0; i < num_elems; i++) {
+	cairo_pdf_recording_surface_commands_t *recording_command;
+	cairo_pdf_command_list_t *command_list;
+
+	recording_command = _cairo_array_index (&ic->recording_surface_commands, i);
+	command_list = recording_command->command_list;
+	_cairo_array_fini (&command_list->commands);
+	free (command_list);
+    }
+    _cairo_array_fini (&ic->recording_surface_commands);
+
+    num_elems = _cairo_array_num_elements (&ic->page_commands);
+    for (i = 0; i < num_elems; i++) {
+	cairo_pdf_command_list_t *command_list;
+	command_list = _cairo_array_index (&ic->page_commands, i);
+	_cairo_array_fini (&command_list->commands);
+    }
+    _cairo_array_fini (&ic->page_commands);
+
     free (ic->sorted_dests);
     _cairo_pdf_interchange_free_outlines (surface);
     free (ic->docinfo.title);
@@ -2074,3 +2877,123 @@ _cairo_pdf_interchange_set_custom_metadata (cairo_pdf_surface_t  *surface,
 
     return status;
 }
+
+#if DEBUG_PDF_INTERCHANGE
+static cairo_int_status_t
+print_node (cairo_pdf_surface_t          *surface,
+	    cairo_pdf_struct_tree_node_t *node,
+	    int                           depth)
+{
+    if (node == NULL) {
+	printf("%*sNode: ptr: NULL\n", depth*2, "");
+    } else if (node == surface->interchange.struct_root) {
+       printf("%*sNode: ptr: %p root\n", depth*2, "", node);
+    } else {
+       printf("%*sNode: ptr: %p name: '%s'\n", depth*2, "", node, node->name);
+    }
+    depth++;
+    printf("%*sType: ", depth*2, "");
+    switch (node->type) {
+	case PDF_NODE_STRUCT:
+	    printf("STRUCT\n");
+	    break;
+	case PDF_NODE_CONTENT:
+	    printf("CONTENT\n");
+	    printf("%*sContent.id: %s\n", depth*2, "", node->attributes.content.id);
+	    printf("%*sContent.tag_name: %s\n", depth*2, "", node->attributes.content.tag_name);
+	    break;
+	case PDF_NODE_CONTENT_REF:
+	    printf("CONTENT_REF\n");
+	    printf("%*sContent_Ref.ref: %s\n", depth*2, "", node->attributes.content_ref.ref);
+	    break;
+	case PDF_NODE_ARTIFACT:
+	    printf("ARTIFACT\n");
+	    break;
+    }
+    printf("%*sres: %d\n", depth*2, "", node->res.id);
+    printf("%*sparent: %p\n", depth*2, "", node->parent);
+    printf("%*sannot:", depth*2, "");
+    if (node->annot)
+	printf(" node: %p res: %d", node->annot->node,  node->annot->res.id);
+    printf("\n");
+    printf("%*sextents: ", depth*2, "");
+    if (node->extents.valid) {
+	printf("x: %d  y: %d  w: %d  h: %d\n",
+	       node->extents.extents.x,
+	       node->extents.extents.y,
+	       node->extents.extents.width,
+	       node->extents.extents.height);
+    } else {
+	printf("not valid\n");
+    }
+
+    printf("%*smcid: ", depth*2, "");
+    int num_mcid = _cairo_array_num_elements (&node->mcid);
+    for (int i = 0; i < num_mcid; i++) {
+	cairo_pdf_page_mcid_t *mcid_elem = _cairo_array_index (&node->mcid, i);
+	if (mcid_elem->child_node) {
+	    printf("(order: %d, %p) ", mcid_elem->order, mcid_elem->child_node);
+	} else {
+	    printf("(order: %d, pg: %d, xobject_res: %d, mcid: %d) ",
+		   mcid_elem->order, mcid_elem->page, mcid_elem->xobject_res.id, mcid_elem->mcid);
+	}
+    }
+    printf("\n");
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+print_tree (cairo_pdf_surface_t          *surface,
+	    cairo_pdf_struct_tree_node_t *node)
+{
+    printf("Structure Tree:\n");
+    cairo_pdf_interchange_walk_struct_tree (surface, node, 0, print_node);
+}
+
+static void
+print_command (cairo_pdf_command_t *command, int indent)
+{
+    printf("%*s%d  ", indent*2, "", command->command_id);
+    switch (command->flags) {
+	case PDF_CONTENT:
+	    printf("CONTENT");
+	    break;
+	case PDF_BEGIN:
+	    printf("BEGIN");
+	    break;
+	case PDF_END:
+	    printf("END");
+		break;
+	case PDF_GROUP:
+	    printf("GROUP: %p", command->group);
+	    break;
+	case PDF_NONE:
+	    printf("NONE");
+	    break;
+    }
+    printf("  node: %p index: %d\n", command->node, command->mcid_index);
+}
+
+static void
+print_commands (cairo_pdf_command_list_t *command_list, int indent)
+{
+    cairo_pdf_command_t *command;
+    unsigned i;
+    unsigned num_elements = _cairo_array_num_elements (&command_list->commands);
+
+    for (i = 0; i < num_elements; i++) {
+	command = _cairo_array_index (&command_list->commands, i);
+	print_command (command, indent);
+	if (command->flags == PDF_GROUP)
+	    print_commands (command->group, indent + 1);
+    }
+}
+
+static void
+print_command_list(cairo_pdf_command_list_t *command_list)
+{
+    printf("Command List: %p\n", command_list);
+    print_commands (command_list, 0);
+    printf("end\n");
+}
+#endif
diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index 328e893d7..176f45b48 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -1568,10 +1568,16 @@ _cairo_pdf_operators_tag_begin (cairo_pdf_operators_t *pdf_operators,
 	    return status;
     }
 
-    _cairo_output_stream_printf (pdf_operators->stream,
-				 "/%s << /MCID %d >> BDC\n",
-				 tag_name,
-				 mcid);
+    if (mcid >= 0) {
+	_cairo_output_stream_printf (pdf_operators->stream,
+				     "/%s << /MCID %d >> BDC\n",
+				     tag_name,
+				     mcid);
+    } else {
+	_cairo_output_stream_printf (pdf_operators->stream,
+				     "/%s BMC\n",
+				     tag_name);
+    }
 
     return _cairo_output_stream_get_status (pdf_operators->stream);
 }
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index c97affe6b..13ccc5001 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -74,6 +74,10 @@ typedef struct _cairo_pdf_source_surface_entry {
     unsigned char *unique_id;
     unsigned long unique_id_length;
     cairo_operator_t operator;
+
+    /* If not 0, this is the recording surface region id of the source surface. */
+    int          region_id;
+
     cairo_bool_t interpolate;
     cairo_bool_t stencil_mask;
     cairo_bool_t smask;
@@ -110,6 +114,9 @@ typedef struct _cairo_pdf_pattern {
     cairo_operator_t operator;
     cairo_bool_t is_shading;
 
+    /* Index into nodes array in cairo_pdf_surface_node_entry_t or -1 if not used */
+    int          region_id;
+
     /* PDF pattern space is the pattern matrix concatenated with the
      * initial space of the parent object. If the parent object is the
      * page, the initial space does not include the Y-axis flipping
@@ -161,38 +168,87 @@ typedef struct _cairo_pdf_jbig2_global {
     cairo_bool_t emitted;
 } cairo_pdf_jbig2_global_t;
 
-/* cairo-pdf-interchange.c types */
+typedef struct _cairo_pdf_page_info {
+    double width;
+    double height;
+    cairo_pdf_resource_t page_res;
+    cairo_pdf_resource_t content;
+    cairo_pdf_resource_t resources;
+    cairo_pdf_resource_t thumbnail;
+    cairo_array_t annots; /* <cairo_pdf_resource_t> */
+    int struct_parents;
+} cairo_pdf_page_info_t;
 
-struct page_mcid {
-    int page;
-    int mcid;
-};
 
-struct tag_extents {
+/* cairo-pdf-interchange.c types */
+
+typedef struct _pdf_tag_extents {
     cairo_rectangle_int_t extents;
     cairo_bool_t valid;
-    cairo_list_t link;
-};
+} cairo_pdf_tag_extents_t;
+
+typedef struct _pdf_page_mcid {
+    int order;
+    int page;
+    cairo_pdf_resource_t xobject_res; /* 0 if not in an XObject */
+    int mcid;
+    struct _cairo_pdf_struct_tree_node *child_node;
+} cairo_pdf_page_mcid_t;
+
+/* The non PDF_NODE_STRUCT types are excluded from the struct tree embedded in
+ * the PDF. */
+typedef enum _cairo_pdf_tree_node_type {
+    PDF_NODE_STRUCT,
+    PDF_NODE_CONTENT,
+    PDF_NODE_CONTENT_REF,
+    PDF_NODE_ARTIFACT,
+} cairo_pdf_tree_node_type_t;
 
 typedef struct _cairo_pdf_struct_tree_node {
+    cairo_hash_entry_t hash;
+    cairo_pdf_tree_node_type_t type;
     char *name;
     cairo_pdf_resource_t res;
     struct _cairo_pdf_struct_tree_node *parent;
     cairo_list_t children;
-    cairo_array_t mcid; /* array of struct page_mcid */
-    cairo_pdf_resource_t annot_res; /* 0 if no annot */
-    struct tag_extents extents;
-    cairo_list_t link;
+    cairo_array_t mcid; /* array of cairo_pdf_page_mcid_t */
+    struct _cairo_pdf_annotation *annot;
+    cairo_pdf_tag_extents_t extents;
+
+    union {
+	cairo_content_attrs_t content; /* type == PDF_NODE_CONTENT */
+	cairo_content_ref_attrs_t content_ref; /* type == PDF_NODE_CONTENT_REF */
+    } attributes;
+
+    cairo_list_t link; /* linked list of parent's children */
 } cairo_pdf_struct_tree_node_t;
 
+typedef struct _cairo_pdf_command_entry {
+    cairo_hash_entry_t base;
+    unsigned int recording_id;
+    unsigned int command_id;
+    cairo_pdf_struct_tree_node_t *node;
+} cairo_pdf_command_entry_t;
+
+typedef struct _cairo_recording_surface_stack_entry {
+    cairo_bool_t ignore_surface;
+    cairo_pdf_struct_tree_node_t *current_node;
+} cairo_recording_surface_stack_entry_t;
+
+typedef struct _cairo_pdf_content_tag {
+    cairo_hash_entry_t base;
+    cairo_pdf_struct_tree_node_t *node;
+} cairo_pdf_content_tag_t;
+
 typedef struct _cairo_pdf_annotation {
     cairo_pdf_struct_tree_node_t *node; /* node containing the annotation */
     cairo_link_attrs_t link_attrs;
+    cairo_pdf_resource_t res;
 } cairo_pdf_annotation_t;
 
 typedef struct _cairo_pdf_named_dest {
     cairo_hash_entry_t base;
-    struct tag_extents extents;
+    cairo_pdf_tag_extents_t extents;
     cairo_dest_attrs_t attrs;
     int page;
 } cairo_pdf_named_dest_t;
@@ -233,19 +289,65 @@ struct metadata {
     char *value;
 };
 
+typedef enum _cairo_pdf_operation_flags_t {
+    PDF_NONE = 0,
+    PDF_CONTENT,
+    PDF_BEGIN,
+    PDF_END,
+    PDF_GROUP,
+} cairo_pdf_operation_flags_t;
+
+typedef struct _pdf_command_list {
+    cairo_array_t commands;
+    struct _pdf_command_list *parent;
+} cairo_pdf_command_list_t;
+
+typedef struct _pdf_operation {
+    cairo_pdf_command_list_t *group;
+    cairo_pdf_struct_tree_node_t *node;
+    unsigned int command_id;
+    int mcid_index;
+    cairo_pdf_operation_flags_t flags;
+} cairo_pdf_command_t;
+
+typedef struct _pdf_recording_surface_commands {
+    cairo_surface_t *recording_surface;
+    cairo_pdf_command_list_t *command_list;
+    unsigned int region_id;
+} cairo_pdf_recording_surface_commands_t;
+
 typedef struct _cairo_pdf_interchange {
     cairo_tag_stack_t analysis_tag_stack;
     cairo_tag_stack_t render_tag_stack;
-    cairo_array_t push_data; /* records analysis_tag_stack data field for each push */
-    int push_data_index;
     cairo_pdf_struct_tree_node_t *struct_root;
-    cairo_pdf_struct_tree_node_t *current_node;
-    cairo_pdf_struct_tree_node_t *begin_page_node;
-    cairo_pdf_struct_tree_node_t *end_page_node;
-    cairo_array_t parent_tree; /* parent tree resources */
-    cairo_array_t mcid_to_tree; /* mcid to tree node mapping for current page */
+
+    /* Current position in the tree during the analysis stage and across
+     * pages as each page adds to the tree */
+    cairo_pdf_struct_tree_node_t *current_analyze_node;
+
+    /* Currently open tag content containing content. NULL if no content tag open.
+     * A content containg tag may be open across pages */
+    cairo_pdf_struct_tree_node_t *current_render_node;
+    cairo_pdf_struct_tree_node_t *next_page_render_node;
+
+    cairo_array_t recording_surface_stack; /* cairo_recording_surface_stack_entry_t */
+    cairo_pdf_resource_t current_recording_surface_res;
+    cairo_hash_table_t *command_to_node_map; /* <cairo_pdf_surface_node_entry_t> */
+    cairo_bool_t ignore_current_surface;
+    cairo_hash_table_t *content_tag_map; /* <char*,cairo_pdf_content_tag_t> */
+
+    cairo_array_t parent_tree; /* <cairo_pdf_resource_t> */
     cairo_array_t annots; /* array of pointers to cairo_pdf_annotation_t */
+    cairo_pdf_resource_t content_parent_res;
     cairo_pdf_resource_t parent_tree_res;
+
+    /* mcid to tree node for current page or group */
+    cairo_array_t mcid_to_tree; /* <cairo_pdf_struct_tree_node_t *> */
+
+    cairo_array_t page_commands; /* <cairo_pdf_command_list_t> */
+    cairo_pdf_command_list_t *current_commands; /* <cairo_pdf_command_list_t> */
+    cairo_array_t recording_surface_commands;  /* <cairo_pdf_recording_surface_commands_t> */
+
     cairo_list_t extents_list;
     cairo_hash_table_t *named_dests;
     int num_dests;
@@ -255,6 +357,12 @@ typedef struct _cairo_pdf_interchange {
     cairo_array_t outline; /* array of pointers to cairo_pdf_outline_entry_t; */
     struct docinfo docinfo;
     cairo_array_t custom_metadata; /* array of struct metadata */
+    cairo_bool_t content_emitted;
+    cairo_bool_t marked_content_open;
+    unsigned int recording_id;
+    unsigned int command_id;
+    cairo_bool_t render_next_command_has_content;
+    int mcid_order;
 
 } cairo_pdf_interchange_t;
 
@@ -283,18 +391,18 @@ struct _cairo_pdf_surface {
     cairo_matrix_t cairo_to_pdf;
     cairo_bool_t in_xobject;
 
-    cairo_array_t objects;
-    cairo_array_t pages;
+    cairo_array_t objects; /* cairo_pdf_resource_t - list of every resource in the PDF */
+    cairo_array_t pages; /* <cairo_pdf_page_info_t> */
     cairo_array_t rgb_linear_functions;
     cairo_array_t alpha_linear_functions;
     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_hash_table_t *all_surfaces; /* cairo_pdf_source_surface_entry_t* */
+    int           duplicate_surface_number;
     cairo_array_t smask_groups;
     cairo_array_t knockout_group;
     cairo_array_t jbig2_global;
-    cairo_array_t page_heights;
     cairo_hash_table_t *color_glyphs;
 
     cairo_scaled_font_subsets_t *font_subsets;
@@ -374,6 +482,8 @@ struct _cairo_pdf_surface {
     cairo_image_surface_t *thumbnail_image;
 
     cairo_surface_t *paginated_surface;
+
+    cairo_bool_t debug;
 };
 
 cairo_private cairo_pdf_resource_t
@@ -410,14 +520,55 @@ _cairo_pdf_surface_object_begin (cairo_pdf_surface_t *surface,
 cairo_private void
 _cairo_pdf_surface_object_end (cairo_pdf_surface_t *surface);
 
+cairo_private cairo_bool_t
+_cairo_pdf_interchange_struct_tree_requires_recording_surface (
+    cairo_pdf_surface_t           *surface,
+    const cairo_surface_pattern_t *recording_surface,
+    cairo_analysis_source_t        source_type);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_recording_source_surface_begin (
+    cairo_pdf_surface_t           *surface,
+    const cairo_surface_pattern_t *recording_surface_pattern,
+    unsigned int                   region_id,
+    cairo_analysis_source_t        source_type);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_recording_source_surface_end (
+    cairo_pdf_surface_t           *surface,
+    const cairo_surface_pattern_t *recording_surface_pattern,
+    unsigned int                   region_id,
+    cairo_analysis_source_t        source_type);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_begin (
+    cairo_pdf_surface_t     *surface,
+    cairo_surface_t         *recording_surface,
+    int                      region_id,
+    cairo_pdf_resource_t     surface_resource,
+    int                     *struct_parents);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_end (
+    cairo_pdf_surface_t     *surface,
+    cairo_surface_t         *recording_surface);
+
 cairo_private cairo_int_status_t
 _cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
 				const char          *name);
 
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_command_id (cairo_pdf_surface_t  *surface,
+				   unsigned int          recording_id,
+				   unsigned int          command_id);
+
 cairo_private cairo_int_status_t
 _cairo_pdf_interchange_add_operation_extents (cairo_pdf_surface_t         *surface,
 					      const cairo_rectangle_int_t *extents);
 
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_add_content (cairo_pdf_surface_t         *surface);
+
 cairo_private cairo_int_status_t
 _cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface);
 
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 2b1bf72e4..ca83e61ed 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -471,7 +471,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->surface_bounded = TRUE;
 
     _cairo_array_init (&surface->objects, sizeof (cairo_pdf_object_t));
-    _cairo_array_init (&surface->pages, sizeof (cairo_pdf_resource_t));
+    _cairo_array_init (&surface->pages, sizeof (cairo_pdf_page_info_t));
     _cairo_array_init (&surface->rgb_linear_functions, sizeof (cairo_pdf_rgb_linear_function_t));
     _cairo_array_init (&surface->alpha_linear_functions, sizeof (cairo_pdf_alpha_linear_function_t));
     _cairo_array_init (&surface->fonts, sizeof (cairo_pdf_font_t));
@@ -482,7 +482,6 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     _cairo_array_init (&surface->page_surfaces, sizeof (cairo_pdf_source_surface_t));
     _cairo_array_init (&surface->doc_surfaces, sizeof (cairo_pdf_source_surface_t));
     _cairo_array_init (&surface->jbig2_global, sizeof (cairo_pdf_jbig2_global_t));
-    _cairo_array_init (&surface->page_heights, sizeof (double));
     surface->all_surfaces = _cairo_hash_table_create (_cairo_pdf_source_surface_equal);
     if (unlikely (surface->all_surfaces == NULL)) {
 	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -495,6 +494,8 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
 	goto BAIL1;
     }
 
+    surface->duplicate_surface_number = 0;
+
     _cairo_pdf_group_resources_init (&surface->resources);
 
     surface->font_subsets = _cairo_scaled_font_subsets_create_composite ();
@@ -565,8 +566,11 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->thumbnail_height = 0;
     surface->thumbnail_image = NULL;
 
-    if (getenv ("CAIRO_DEBUG_PDF") != NULL)
+    surface->debug = FALSE;
+    if (getenv ("CAIRO_DEBUG_PDF") != NULL) {
+	surface->debug = TRUE;
 	surface->compress_streams = FALSE;
+    }
 
     surface->paginated_surface =  _cairo_paginated_surface_create (
 	                                  &surface->base,
@@ -1457,6 +1461,9 @@ _cairo_pdf_source_surface_equal (const void *key_a, const void *key_b)
     if (a->interpolate != b->interpolate)
 	return FALSE;
 
+    if (a->region_id != b->region_id)
+	return FALSE;
+
     if (a->unique_id && b->unique_id && a->unique_id_length == b->unique_id_length)
 	return (memcmp (a->unique_id, b->unique_id, a->unique_id_length) == 0);
 
@@ -1472,6 +1479,9 @@ _cairo_pdf_source_surface_init_key (cairo_pdf_source_surface_entry_t *key)
     } else {
 	key->base.hash = key->id;
     }
+    key->base.hash = _cairo_hash_bytes (key->base.hash,
+					&key->region_id,
+					sizeof(key->region_id));
 }
 
 static cairo_bool_t
@@ -1601,6 +1611,11 @@ _get_source_surface_extents (cairo_surface_t         *source,
  * @surface: [in] the pdf surface
  * @source_surface: [in] A #cairo_surface_t to use as the source surface
  * @source_pattern: [in] A #cairo_pattern_t of type SURFACE or RASTER_SOURCE to use as the source
+ * @region_id: [in] Surfaces containing tags set this to the recording
+ * surface region id. When tags are used in a XObject, PDF requires a
+ * separate object for each use (section 14.7.4.2) @region_id is used
+ * as a key to ensure a separate object is emitted for each use. Set
+ * to 0 for surfaces without tags.
  * @op: [in] the operator used to composite this source
  * @filter: [in] filter type of the source pattern
  * @stencil_mask: [in] if true, the surface will be written to the PDF as an /ImageMask
@@ -1628,6 +1643,7 @@ static cairo_int_status_t
 _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 				       cairo_surface_t	                 *source_surface,
 				       const cairo_pattern_t	         *source_pattern,
+				       int                                region_id,
 				       cairo_operator_t                   op,
 				       cairo_filter_t		          filter,
 				       cairo_bool_t                       stencil_mask,
@@ -1653,6 +1669,7 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
     cairo_rectangle_int_t op_extents;
     double x, y;
     cairo_bool_t subsurface;
+    cairo_bool_t emit_image;
 
     switch (filter) {
     default:
@@ -1700,10 +1717,21 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 	*source_extents = op_extents;
 
     surface_key.id  = source_surface->unique_id;
-    surface_key.interpolate = interpolate;
+
+    /* Recording surfaces do not use interpolate. Ensure it is always
+     * false for recording surfaces. This is because pdf-interchange
+     * needs to lookup recording surfaces in the hash table using
+     * interpolate = FALSE in the key since it does not know the
+     * interpolate value passed to this function.
+     */
+    emit_image = source_surface->type != CAIRO_SURFACE_TYPE_RECORDING;
+    surface_key.interpolate = emit_image ? interpolate : FALSE;
+
     cairo_surface_get_mime_data (source_surface, CAIRO_MIME_TYPE_UNIQUE_ID,
 				 (const unsigned char **) &surface_key.unique_id,
 				 &surface_key.unique_id_length);
+
+    surface_key.region_id = region_id;
     _cairo_pdf_source_surface_init_key (&surface_key);
     surface_entry = _cairo_hash_table_lookup (surface->all_surfaces, &surface_key.base);
     if (surface_entry) {
@@ -1746,9 +1774,12 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 
     if (pdf_source)
 	*pdf_source = surface_entry;
+
     surface_entry->id = surface_key.id;
+    surface_entry->region_id = region_id;
     surface_entry->operator = op;
-    surface_entry->interpolate = interpolate;
+    surface_entry->interpolate = emit_image ? interpolate : FALSE;
+    surface_entry->emit_image = emit_image;
     surface_entry->stencil_mask = stencil_mask;
     surface_entry->smask = smask;
     surface_entry->need_transp_group = need_transp_group;
@@ -1810,11 +1841,6 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 	goto fail3;
     }
 
-    /* Test if surface will be emitted as image or recording */
-    status = _cairo_pdf_surface_emit_surface (surface, &src_surface, TRUE, &surface_entry->emit_image);
-    if (unlikely (status))
-	goto fail3;
-
     if (surface_entry->bounded) {
 	status = _cairo_array_append (&surface->page_surfaces, &src_surface);
 	if (unlikely (status))
@@ -1858,6 +1884,7 @@ static cairo_int_status_t
 _cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t	   *surface,
 					       const cairo_pattern_t	   *pattern,
 					       cairo_operator_t	            op,
+					       cairo_analysis_source_t      source_type,
 					       const cairo_rectangle_int_t *extents,
 					       cairo_bool_t                 is_shading,
 					       cairo_pdf_resource_t	   *pattern_res,
@@ -1865,6 +1892,7 @@ _cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t	   *surface,
 {
     cairo_pdf_pattern_t pdf_pattern;
     cairo_int_status_t status;
+    int region_id = 0;
 
     pdf_pattern.is_shading = is_shading;
     pdf_pattern.operator = op;
@@ -1923,6 +1951,17 @@ _cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t	   *surface,
      * Y-axis. */
     pdf_pattern.inverted_y_axis = pdf_pattern.gstate_res.id ? TRUE : surface->in_xobject;
 
+    if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern;
+	if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
+									   surface_pattern,
+									   source_type))
+	{
+	    region_id = surface_pattern->region_array_id;
+	}
+    }
+    pdf_pattern.region_id = region_id;
+
     status = _cairo_array_append (&surface->page_patterns, &pdf_pattern);
     if (unlikely (status)) {
 	cairo_pattern_destroy (pdf_pattern.pattern);
@@ -1954,6 +1993,7 @@ _cairo_pdf_surface_add_pdf_shading (cairo_pdf_surface_t		*surface,
     return _cairo_pdf_surface_add_pdf_pattern_or_shading (surface,
 							  pattern,
 							  op,
+							  CAIRO_ANALYSIS_SOURCE_NONE,
 							  extents,
 							  TRUE,
 							  shading_res,
@@ -1964,6 +2004,7 @@ static cairo_int_status_t
 _cairo_pdf_surface_add_pdf_pattern (cairo_pdf_surface_t		*surface,
 				    const cairo_pattern_t	*pattern,
 				    cairo_operator_t	         op,
+				    cairo_analysis_source_t      source_type,
 				    const cairo_rectangle_int_t	*extents,
 				    cairo_pdf_resource_t	*pattern_res,
 				    cairo_pdf_resource_t	*gstate_res)
@@ -1971,6 +2012,7 @@ _cairo_pdf_surface_add_pdf_pattern (cairo_pdf_surface_t		*surface,
     return _cairo_pdf_surface_add_pdf_pattern_or_shading (surface,
 							  pattern,
 							  op,
+							  source_type,
 							  extents,
 							  FALSE,
 							  pattern_res,
@@ -2242,6 +2284,7 @@ _cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
 
     surface->group_stream.mem_stream = NULL;
     surface->group_stream.stream = NULL;
+    surface->reset_gs_required = FALSE;
 
     return status;
 }
@@ -2249,7 +2292,7 @@ _cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
 static cairo_int_status_t
 _cairo_pdf_surface_open_object_stream (cairo_pdf_surface_t *surface)
 {
-    if (surface->pdf_version < CAIRO_PDF_VERSION_1_5) {
+    if (surface->debug || surface->pdf_version < CAIRO_PDF_VERSION_1_5) {
 	/* Object streams not supported. All objects will be written
 	 * directly to the file. */
 	assert (surface->pdf_stream.active == FALSE);
@@ -2305,7 +2348,9 @@ _cairo_pdf_surface_object_end (cairo_pdf_surface_t *surface)
     }
 }
 
-static int _cairo_xref_stream_object_compare (const void *a, const void *b)
+static int
+_cairo_xref_stream_object_compare (const void *a,
+				   const void *b)
 {
     const cairo_xref_stream_object_t *a_obj = a;
     const cairo_xref_stream_object_t *b_obj = b;
@@ -2427,9 +2472,11 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 					const cairo_box_double_t  *bbox,
 					cairo_pdf_resource_t      *resource,
 					cairo_bool_t               is_form,
-					cairo_bool_t               is_group)
+					cairo_bool_t               is_group,
+					int                        struct_parents)
 {
     cairo_int_status_t status;
+    char buf[1000];
 
     assert (surface->pdf_stream.active == FALSE);
     assert (surface->group_stream.active == FALSE);
@@ -2442,40 +2489,46 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t       *surface,
 	assert (bbox != NULL);
 
 	if (is_group) {
-	    status =
-		_cairo_pdf_surface_open_stream (surface,
-						resource,
-						surface->compress_streams,
-						"   /Type /XObject\n"
-						"   /Subtype /Form\n"
-						"   /BBox [ %f %f %f %f ]\n"
-						"   /Group <<\n"
-						"      /Type /Group\n"
-						"      /S /Transparency\n"
-						"      /I true\n"
-						"      /CS /DeviceRGB\n"
-						"   >>\n"
-						"   /Resources %d 0 R\n",
-						bbox->p1.x,
-						bbox->p1.y,
-						bbox->p2.x,
-						bbox->p2.y,
-						surface->content_resources.id);
+	    snprintf(buf,
+		     sizeof(buf),
+		     "   /Type /XObject\n"
+		     "   /Subtype /Form\n"
+		     "   /BBox [ %f %f %f %f ]\n"
+		     "   /Group <<\n"
+		     "      /Type /Group\n"
+		     "      /S /Transparency\n"
+		     "      /I true\n"
+		     "      /CS /DeviceRGB\n"
+		     "   >>\n"
+		     "   /Resources %d 0 R\n",
+		     bbox->p1.x,
+		     bbox->p1.y,
+		     bbox->p2.x,
+		     bbox->p2.y,
+		     surface->content_resources.id);
 	} else {
-	    status =
-		_cairo_pdf_surface_open_stream (surface,
-						resource,
-						surface->compress_streams,
-						"   /Type /XObject\n"
-						"   /Subtype /Form\n"
-						"   /BBox [ %f %f %f %f ]\n"
-						"   /Resources %d 0 R\n",
-						bbox->p1.x,
-						bbox->p1.y,
-						bbox->p2.x,
-						bbox->p2.y,
-						surface->content_resources.id);
+	    snprintf(buf,
+		     sizeof(buf),
+		     "   /Type /XObject\n"
+		     "   /Subtype /Form\n"
+		     "   /BBox [ %f %f %f %f ]\n"
+		     "   /Resources %d 0 R\n",
+		     bbox->p1.x,
+		     bbox->p1.y,
+		     bbox->p2.x,
+		     bbox->p2.y,
+		     surface->content_resources.id);
 	}
+	if (struct_parents >= 0) {
+	    snprintf(buf + strlen(buf),
+		     sizeof(buf) - strlen(buf),
+		"   /StructParents %d\n", struct_parents);
+	}
+	status =
+	    _cairo_pdf_surface_open_stream (surface,
+					    resource,
+					    surface->compress_streams,
+					    buf);
     } else {
 	status =
 	    _cairo_pdf_surface_open_stream (surface,
@@ -2550,6 +2603,73 @@ _cairo_pdf_color_glyph_pluck (void *entry, void *closure)
     free (glyph_entry);
 }
 
+static cairo_int_status_t
+_cairo_pdf_surface_write_page_dicts (cairo_pdf_surface_t *surface)
+{
+    cairo_int_status_t status;
+    cairo_pdf_page_info_t *page_info;
+    int num_annots;
+    cairo_pdf_resource_t res;
+
+    for (unsigned i = 0; i < _cairo_array_num_elements (&surface->pages); i++) {
+	page_info = _cairo_array_index (&surface->pages, i);
+
+	status = _cairo_pdf_surface_object_begin (surface, page_info->page_res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     "<< /Type /Page %% %d\n"
+				     "   /Parent %d 0 R\n"
+				     "   /MediaBox [ 0 0 %f %f ]\n"
+				     "   /Contents %d 0 R\n"
+				     "   /Group <<\n"
+				     "      /Type /Group\n"
+				     "      /S /Transparency\n"
+				     "      /I true\n"
+				     "      /CS /DeviceRGB\n"
+				     "   >>\n"
+				     "   /Resources %d 0 R\n",
+				     i + 1,
+				     surface->pages_resource.id,
+				     page_info->width,
+				     page_info->height,
+				     page_info->content.id,
+				     page_info->resources.id);
+
+	if (page_info->struct_parents >= 0) {
+	    _cairo_output_stream_printf (surface->object_stream.stream,
+					 "   /StructParents %d\n",
+					 page_info->struct_parents);
+	}
+
+	num_annots = _cairo_array_num_elements (&page_info->annots);
+	if (num_annots > 0) {
+	    _cairo_output_stream_printf (surface->object_stream.stream,
+					 "   /Annots [ ");
+	    for (int j = 0; j < num_annots; j++) {
+		_cairo_array_copy_element (&page_info->annots, j, &res);
+		_cairo_output_stream_printf (surface->object_stream.stream,
+					     "%d 0 R ",
+					     res.id);
+	    }
+	    _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
+	}
+
+	if (page_info->thumbnail.id) {
+	    _cairo_output_stream_printf (surface->object_stream.stream,
+					 "   /Thumb %d 0 R\n",
+					 page_info->thumbnail.id);
+	}
+
+	_cairo_output_stream_printf (surface->object_stream.stream,
+				     ">>\n");
+	_cairo_pdf_surface_object_end (surface);
+    }
+
+    return status;
+}
+
 static cairo_status_t
 _cairo_pdf_surface_finish (void *abstract_surface)
 {
@@ -2594,6 +2714,10 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     if (unlikely (status))
 	return status;
 
+    status = _cairo_pdf_surface_write_page_dicts (surface);
+    if (unlikely (status))
+	return status;
+
     catalog = _cairo_pdf_surface_new_object (surface);
     if (catalog.id == 0)
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -2606,7 +2730,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     if (unlikely (status))
 	return status;
 
-    if (surface->pdf_version >= CAIRO_PDF_VERSION_1_5)
+    if (!surface->debug && surface->pdf_version >= CAIRO_PDF_VERSION_1_5)
     {
 	xref_res = _cairo_pdf_surface_new_object (surface);
 	status = _cairo_pdf_surface_write_xref_stream (surface,
@@ -2669,6 +2793,11 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     _cairo_pdf_group_resources_fini (&surface->resources);
 
     _cairo_array_fini (&surface->objects);
+    size = _cairo_array_num_elements (&surface->pages);
+    for (i = 0; i < size; i++) {
+	cairo_pdf_page_info_t *page_info = _cairo_array_index (&surface->pages, i);
+	_cairo_array_fini (&page_info->annots);
+    }
     _cairo_array_fini (&surface->pages);
     _cairo_array_fini (&surface->rgb_linear_functions);
     _cairo_array_fini (&surface->alpha_linear_functions);
@@ -2705,7 +2834,6 @@ _cairo_pdf_surface_finish (void *abstract_surface)
 	    return _cairo_error (CAIRO_STATUS_JBIG2_GLOBAL_MISSING);
     }
     _cairo_array_fini (&surface->jbig2_global);
-    _cairo_array_fini (&surface->page_heights);
 
     size = _cairo_array_num_elements (&surface->page_labels);
     for (i = 0; i < size; i++) {
@@ -2725,7 +2853,7 @@ static cairo_int_status_t
 _cairo_pdf_surface_start_page (void *abstract_surface)
 {
     cairo_pdf_surface_t *surface = abstract_surface;
-    cairo_pdf_resource_t page;
+    cairo_pdf_page_info_t page_info;
     cairo_int_status_t status;
 
     /* Document header */
@@ -2758,11 +2886,19 @@ _cairo_pdf_surface_start_page (void *abstract_surface)
     _cairo_pdf_group_resources_clear (&surface->resources);
     surface->in_xobject = FALSE;
 
-    page = _cairo_pdf_surface_new_object (surface);
-    if (page.id == 0)
+    page_info.page_res = _cairo_pdf_surface_new_object (surface);
+    if (page_info.page_res.id == 0)
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-    status = _cairo_array_append (&surface->pages, &page);
+    page_info.width = surface->width;
+    page_info.height = surface->height;
+    page_info.content.id = 0;
+    page_info.resources.id = 0;
+    page_info.thumbnail.id = 0;
+    page_info.struct_parents = -1;
+    _cairo_array_init (&page_info.annots, sizeof (cairo_pdf_resource_t));
+
+    status = _cairo_array_append (&surface->pages, &page_info);
     if (unlikely (status))
 	return status;
 
@@ -2777,13 +2913,17 @@ _cairo_pdf_surface_has_fallback_images (void		*abstract_surface,
     cairo_pdf_surface_t *surface = abstract_surface;
     cairo_box_double_t bbox;
 
+    status = _cairo_pdf_interchange_end_page_content (surface);
+    if (unlikely (status))
+	return status;
+
     surface->has_fallback_images = has_fallbacks;
     surface->in_xobject = has_fallbacks;
     bbox.p1.x = 0;
     bbox.p1.y = 0;
     bbox.p2.x = surface->width;
     bbox.p2.y = surface->height;
-    status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, has_fallbacks, has_fallbacks);
+    status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, has_fallbacks, has_fallbacks, -1);
     if (unlikely (status))
 	return status;
 
@@ -2883,6 +3023,7 @@ _cairo_pdf_surface_add_padded_image_surface (cairo_pdf_surface_t          *surfa
     status = _cairo_pdf_surface_add_source_surface (surface,
 						    pad_image,
 						    NULL,
+						    -1 , /* node_surface_index */
 						    CAIRO_OPERATOR_OVER, /* not used for images */
 						    source->filter,
 						    FALSE, /* stencil mask */
@@ -3722,6 +3863,7 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
     cairo_bool_t is_subsurface;
     cairo_bool_t transparency_group;
     cairo_recording_surface_t *recording;
+    int struct_parents = -1;
 
     assert (pdf_source->type == CAIRO_PATTERN_TYPE_SURFACE);
 
@@ -3780,11 +3922,20 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
 			   _cairo_recording_surface_has_only_bilevel_alpha (recording) &&
 			   _cairo_recording_surface_has_only_op_over (recording));
 
+    status = _cairo_pdf_interchange_emit_recording_surface_begin (surface,
+								  pdf_source->surface,
+								  pdf_source->hash_entry->region_id,
+								  pdf_source->hash_entry->surface_res,
+								  &struct_parents);
+    if (unlikely (status))
+	goto err;
+
     status = _cairo_pdf_surface_open_content_stream (surface,
 						     &bbox,
 						     &pdf_source->hash_entry->surface_res,
 						     TRUE,
-						     transparency_group);
+						     transparency_group,
+						     struct_parents);
     if (unlikely (status))
 	goto err;
 
@@ -3823,6 +3974,10 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
     surface->paginated_mode = old_paginated_mode;
     surface->surface_extents = old_surface_extents;
     surface->surface_bounded = old_surface_bounded;
+    surface->reset_gs_required = FALSE;
+
+    if (pdf_source->hash_entry->region_id > 0)
+	status = _cairo_pdf_interchange_emit_recording_surface_end (surface, pdf_source->surface);
 
 err:
     cairo_surface_destroy (free_me);
@@ -3964,6 +4119,7 @@ _cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t	*surface,
 	status = _cairo_pdf_surface_add_source_surface (surface,
 							NULL,
 							pattern,
+							pdf_pattern->region_id,
 							pdf_pattern->operator,
 							pattern->filter,
 							FALSE, /* stencil mask */
@@ -5108,6 +5264,7 @@ static cairo_int_status_t
 _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t          *surface,
 					  cairo_operator_t              op,
 					  const cairo_pattern_t        *source,
+					  cairo_analysis_source_t       source_type,
 					  const cairo_rectangle_int_t  *extents,
 					  double                        alpha,
 					  cairo_pdf_resource_t	       *smask_res,
@@ -5119,6 +5276,7 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t          *surface,
     double x_offset;
     double y_offset;
     cairo_pdf_source_surface_entry_t *pdf_source;
+    int region_id = 0;
 
     if (source->extend == CAIRO_EXTEND_PAD &&
 	!(source->type == CAIRO_PATTERN_TYPE_SURFACE &&
@@ -5132,9 +5290,19 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t          *surface,
 							      &y_offset,
 							      NULL);
     } else {
+	if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	    cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+	    if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
+									       surface_pattern,
+									       source_type))
+	    {
+		region_id = surface_pattern->region_array_id;
+	    }
+	}
 	status = _cairo_pdf_surface_add_source_surface (surface,
 							NULL,
 							source,
+							region_id,
 							op,
 							source->filter,
 							stencil_mask,
@@ -5269,6 +5437,7 @@ static cairo_int_status_t
 _cairo_pdf_surface_paint_pattern (cairo_pdf_surface_t          *surface,
 				  cairo_operator_t              op,
 				  const cairo_pattern_t        *source,
+				  cairo_analysis_source_t       source_type,
 				  const cairo_rectangle_int_t  *extents,
 				  double                        alpha,
 				  cairo_bool_t                  mask)
@@ -5279,6 +5448,7 @@ _cairo_pdf_surface_paint_pattern (cairo_pdf_surface_t          *surface,
 	return _cairo_pdf_surface_paint_surface_pattern (surface,
 							 op,
 							 source,
+							 source_type,
 							 extents,
 							 alpha,
 							 NULL,
@@ -5474,10 +5644,6 @@ _cairo_pdf_surface_show_page (void *abstract_surface)
     cairo_pdf_surface_t *surface = abstract_surface;
     cairo_int_status_t status;
 
-    status = _cairo_array_append (&surface->page_heights, &surface->height);
-    if (unlikely (status))
-	return status;
-
     status = _cairo_array_append (&surface->page_labels, &surface->current_page_label);
     if (unlikely (status))
 	return status;
@@ -5530,7 +5696,7 @@ _cairo_pdf_surface_get_font_options (void                  *abstract_surface,
 static cairo_int_status_t
 _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
 {
-    cairo_pdf_resource_t page;
+    cairo_pdf_page_info_t *page_info;
     int num_pages, i;
     cairo_int_status_t status;
 
@@ -5544,8 +5710,8 @@ _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
 
     num_pages = _cairo_array_num_elements (&surface->pages);
     for (i = 0; i < num_pages; i++) {
-	_cairo_array_copy_element (&surface->pages, i, &page);
-	_cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", page.id);
+	page_info = _cairo_array_index (&surface->pages, i);
+	_cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", page_info->page_res.id);
     }
 
     _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
@@ -6605,7 +6771,7 @@ cairo_pdf_surface_emit_color_glyph (cairo_pdf_surface_t *surface,
 	goto cleanup;
 
     status = _cairo_recording_surface_replay_and_create_regions (glyph_surface, regions_id,
-                                                                 NULL, analysis, TRUE);
+                                                                 NULL, analysis, TRUE, FALSE);
     if (status)
 	goto cleanup;
 
@@ -6667,6 +6833,7 @@ cairo_pdf_surface_emit_color_glyph (cairo_pdf_surface_t *surface,
     _cairo_pdf_surface_paint_surface_pattern (surface,
 					      CAIRO_OPERATOR_OVER,
 					      &surface_pattern.base,
+					      CAIRO_ANALYSIS_SOURCE_NONE,
 					      &extents,
 					      1.0, /* alpha */
 					      NULL, /* smask_res */
@@ -6738,6 +6905,7 @@ cairo_pdf_surface_emit_color_glyph_image (cairo_pdf_surface_t *surface,
     _cairo_pdf_surface_paint_surface_pattern (surface,
 					      CAIRO_OPERATOR_OVER,
 					      image_pattern,
+					      CAIRO_ANALYSIS_SOURCE_NONE,
 					      &extents,
 					      1.0, /* alpha */
 					      NULL, /* smask_res */
@@ -7267,6 +7435,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t	*surface,
 	status = _cairo_pdf_surface_paint_pattern (surface,
 						   CAIRO_OPERATOR_OVER,
 						   group->mask,
+						   CAIRO_ANALYSIS_MASK_MASK,
 						   &group->extents,
 						   1.0, /* alpha */
 						   FALSE); /* mask */
@@ -7279,6 +7448,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t	*surface,
 	gstate_res.id = 0;
 	status = _cairo_pdf_surface_add_pdf_pattern (surface, group->mask,
 						     CAIRO_OPERATOR_OVER,
+						     CAIRO_ANALYSIS_MASK_MASK,
 						     NULL,
 						     &pattern_res, &gstate_res);
 	if (unlikely (status))
@@ -7344,6 +7514,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t	*surface,
 	status = _cairo_pdf_surface_paint_pattern (surface,
 						   CAIRO_OPERATOR_OVER,
 						   group->source,
+						   CAIRO_ANALYSIS_MASK_MASK,
 						   &group->extents,
 						   1.0, /* alpha */
 						   FALSE); /* mask */
@@ -7356,6 +7527,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t	*surface,
 	gstate_res.id = 0;
 	status = _cairo_pdf_surface_add_pdf_pattern (surface, group->source,
 						     CAIRO_OPERATOR_OVER,
+						     CAIRO_ANALYSIS_MASK_MASK,
 						     NULL,
 						     &pattern_res, &gstate_res);
 	if (unlikely (status))
@@ -7593,10 +7765,12 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
 static cairo_int_status_t
 _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 {
-    cairo_pdf_resource_t knockout, res, thumbnail_res;
-    cairo_pdf_resource_t *page;
+    cairo_pdf_resource_t knockout, res;
     cairo_int_status_t status;
-    unsigned int i, len, page_num, num_annots;
+    unsigned int i, len;
+    cairo_pdf_page_info_t *page_info;
+
+    page_info = _cairo_array_last_element (&surface->pages);
 
     status = _cairo_pdf_surface_open_object_stream (surface);
     if (unlikely (status))
@@ -7642,7 +7816,7 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 	    return status;
 
 	_cairo_pdf_group_resources_clear (&surface->resources);
-	status = _cairo_pdf_surface_open_content_stream (surface, NULL, NULL, FALSE, FALSE);
+	status = _cairo_pdf_surface_open_content_stream (surface, NULL, NULL, FALSE, FALSE, -1);
 	if (unlikely (status))
 	    return status;
 
@@ -7658,70 +7832,18 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 	    return status;
     }
 
-    thumbnail_res.id = 0;
     if (surface->thumbnail_image) {
 	cairo_pdf_source_surface_entry_t entry;
 
 	memset (&entry, 0, sizeof (entry));
-	thumbnail_res = _cairo_pdf_surface_new_object (surface);
-	entry.surface_res = thumbnail_res;
+	page_info->thumbnail = _cairo_pdf_surface_new_object (surface);
+	entry.surface_res = page_info->thumbnail;
 	_cairo_pdf_surface_emit_image (surface, surface->thumbnail_image, &entry);
     }
 
-    page_num = _cairo_array_num_elements (&surface->pages);
-    page = _cairo_array_index (&surface->pages, page_num - 1);
-
-    status = _cairo_pdf_surface_object_begin (surface, *page);
-    if (unlikely (status))
-	return status;
-
-    _cairo_output_stream_printf (surface->object_stream.stream,
-				 "<< /Type /Page %% %d\n"
-				 "   /Parent %d 0 R\n"
-				 "   /MediaBox [ 0 0 %f %f ]\n"
-				 "   /Contents %d 0 R\n"
-				 "   /Group <<\n"
-				 "      /Type /Group\n"
-				 "      /S /Transparency\n"
-				 "      /I true\n"
-				 "      /CS /DeviceRGB\n"
-				 "   >>\n"
-				 "   /Resources %d 0 R\n",
-				 page_num,
-				 surface->pages_resource.id,
-				 surface->width,
-				 surface->height,
-				 surface->content.id,
-				 surface->content_resources.id);
-
-    if (surface->page_parent_tree >= 0) {
-	_cairo_output_stream_printf (surface->object_stream.stream,
-				     "   /StructParents %d\n",
-				     surface->page_parent_tree);
-    }
-
-    num_annots = _cairo_array_num_elements (&surface->page_annots);
-    if (num_annots > 0) {
-	_cairo_output_stream_printf (surface->object_stream.stream,
-				     "   /Annots [ ");
-	for (i = 0; i < num_annots; i++) {
-	    _cairo_array_copy_element (&surface->page_annots, i, &res);
-	    _cairo_output_stream_printf (surface->object_stream.stream,
-					 "%d 0 R ",
-					 res.id);
-	}
-	_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
-    }
-
-    if (thumbnail_res.id) {
-	_cairo_output_stream_printf (surface->object_stream.stream,
-				     "   /Thumb %d 0 R\n",
-				     thumbnail_res.id);
-    }
-
-    _cairo_output_stream_printf (surface->object_stream.stream,
-				 ">>\n");
-    _cairo_pdf_surface_object_end (surface);
+    page_info->content = surface->content;
+    page_info->resources = surface->content_resources;
+    page_info->struct_parents = surface->page_parent_tree;
 
     status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface, FALSE);
     if (unlikely (status))
@@ -7968,7 +8090,7 @@ _cairo_pdf_surface_start_fallback (cairo_pdf_surface_t *surface)
     bbox.p1.y = 0;
     bbox.p2.x = surface->width;
     bbox.p2.y = surface->height;
-    status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE);
+    status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE, -1);
     if (unlikely (status))
 	return status;
 
@@ -8115,6 +8237,7 @@ _cairo_pdf_surface_emit_combined_smask (cairo_pdf_surface_t         *surface,
 	status = _cairo_pdf_surface_add_source_surface (surface,
 							NULL,
 							mask,
+							-1, /* node_surface_index */
 							op,
 							source->filter,
 							FALSE, /* stencil mask */
@@ -8135,7 +8258,9 @@ _cairo_pdf_surface_emit_combined_smask (cairo_pdf_surface_t         *surface,
 	return status;
 
     _cairo_output_stream_printf (surface->output, "q\n");
-    status = _cairo_pdf_surface_paint_surface_pattern (surface, op, source, extents,
+    status = _cairo_pdf_surface_paint_surface_pattern (surface, op, source,
+						       CAIRO_ANALYSIS_SOURCE_NONE,
+						       extents,
 						       1.0, /* alpha */
 						       need_smask ? &pdf_source->surface_res : NULL,
 						       FALSE);
@@ -8200,7 +8325,9 @@ _cairo_pdf_surface_emit_stencil_mask (cairo_pdf_surface_t         *surface,
 	return status;
 
     _cairo_output_stream_printf (surface->output, "q\n");
-    status = _cairo_pdf_surface_paint_surface_pattern (surface, op, mask, extents, 1.0, NULL, TRUE);
+    status = _cairo_pdf_surface_paint_surface_pattern (surface, op, mask,
+						       CAIRO_ANALYSIS_SOURCE_NONE,
+						       extents, 1.0, NULL, TRUE);
     if (unlikely (status))
 	return status;
 
@@ -8282,6 +8409,7 @@ _cairo_pdf_surface_paint (void			*abstract_surface,
 	status = _cairo_pdf_surface_paint_pattern (surface,
 						   op,
 						   source,
+						   CAIRO_ANALYSIS_SOURCE_PAINT,
 						   &extents.bounded,
 						   1.0, /* alpha */
 						   FALSE); /* mask */
@@ -8296,6 +8424,7 @@ _cairo_pdf_surface_paint (void			*abstract_surface,
     pattern_res.id = 0;
     gstate_res.id = 0;
     status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+						 CAIRO_ANALYSIS_SOURCE_PAINT,
 						 &extents.bounded,
 						 &pattern_res, &gstate_res);
     if (unlikely (status))
@@ -8467,6 +8596,7 @@ _cairo_pdf_surface_mask (void			*abstract_surface,
 	status = _cairo_pdf_surface_paint_pattern (surface,
 						   op,
 						   source,
+						   CAIRO_ANALYSIS_SOURCE_MASK,
 						   &extents.bounded,
 						   alpha,
 						   FALSE); /* mask */
@@ -8596,6 +8726,7 @@ _cairo_pdf_surface_stroke (void			*abstract_surface,
     pattern_res.id = 0;
     gstate_res.id = 0;
     status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+						 CAIRO_ANALYSIS_SOURCE_STROKE,
 						 &extents.bounded,
 						 &pattern_res, &gstate_res);
     if (unlikely (status))
@@ -8754,6 +8885,7 @@ _cairo_pdf_surface_fill (void			*abstract_surface,
 	status = _cairo_pdf_surface_paint_pattern (surface,
 						   op,
 						   source,
+						   CAIRO_ANALYSIS_SOURCE_FILL,
 						   &extents.bounded,
 						   1.0, /* alpha */
 						   FALSE); /* mask */
@@ -8768,6 +8900,7 @@ _cairo_pdf_surface_fill (void			*abstract_surface,
     pattern_res.id = 0;
     gstate_res.id = 0;
     status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+						 CAIRO_ANALYSIS_SOURCE_FILL,
 						 &extents.bounded,
 						 &pattern_res, &gstate_res);
     if (unlikely (status))
@@ -8947,6 +9080,7 @@ _cairo_pdf_surface_fill_stroke (void			*abstract_surface,
     gstate_res.id = 0;
     status = _cairo_pdf_surface_add_pdf_pattern (surface, fill_source,
 						 fill_op,
+						 CAIRO_ANALYSIS_SOURCE_FILL,
 						 &extents.bounded,
 						 &fill_pattern_res,
 						 &gstate_res);
@@ -8960,6 +9094,7 @@ _cairo_pdf_surface_fill_stroke (void			*abstract_surface,
     status = _cairo_pdf_surface_add_pdf_pattern (surface,
 						 stroke_source,
 						 stroke_op,
+						 CAIRO_ANALYSIS_SOURCE_STROKE,
 						 &extents.bounded,
 						 &stroke_pattern_res,
 						 &gstate_res);
@@ -9038,6 +9173,10 @@ _cairo_pdf_surface_show_text_glyphs (void			*abstract_surface,
     if (unlikely (status))
 	return status;
 
+    status = _cairo_pdf_interchange_add_content (surface);
+    if (unlikely (status))
+	return status;
+
     status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
     if (unlikely (status))
 	return status;
@@ -9063,6 +9202,7 @@ _cairo_pdf_surface_show_text_glyphs (void			*abstract_surface,
     pattern_res.id = 0;
     gstate_res.id = 0;
     status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+						 CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS,
 						 &extents.bounded,
 						 &pattern_res, &gstate_res);
     if (unlikely (status))
@@ -9191,9 +9331,10 @@ _cairo_pdf_surface_tag (void			   *abstract_surface,
 			cairo_bool_t                begin,
 			const char                 *tag_name,
 			const char                 *attributes)
+
 {
     cairo_pdf_surface_t *surface = abstract_surface;
-    cairo_int_status_t status = 0;
+    cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
 
     if (begin)
 	status = _cairo_pdf_interchange_tag_begin (surface, tag_name, attributes);
@@ -9203,6 +9344,16 @@ _cairo_pdf_surface_tag (void			   *abstract_surface,
     return status;
 }
 
+static cairo_int_status_t
+_cairo_pdf_surface_command_id (void                 *abstract_surface,
+			       unsigned int          recording_id,
+			       unsigned int          command_id)
+{
+    cairo_pdf_surface_t *surface = abstract_surface;
+
+    return _cairo_pdf_interchange_command_id (surface, recording_id, command_id);
+}
+
 /* The Type 3 font subset support will the embed the
  * CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE image if vector operations
  * are not supported. The only case we don't currently handle is if a
@@ -9265,6 +9416,33 @@ _cairo_pdf_surface_supports_color_glyph (void                  *abstract_surface
     return glyph_entry->supported;
 }
 
+static cairo_int_status_t
+_cairo_pdf_surface_analyze_recording_surface(void		           *abstract_surface,
+					     const cairo_surface_pattern_t *recording_surface_pattern,
+					     unsigned int                   region_id,
+					     cairo_analysis_source_t        source_type,
+					     cairo_bool_t                   begin)
+{
+    cairo_pdf_surface_t *surface = abstract_surface;
+    cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
+
+    if (begin) {
+	status = _cairo_pdf_interchange_recording_source_surface_begin (
+	    surface,
+	    recording_surface_pattern,
+	    region_id,
+	    source_type);
+    } else {
+	status = _cairo_pdf_interchange_recording_source_surface_end (
+	    surface,
+	    recording_surface_pattern,
+	    region_id,
+	    source_type);
+    }
+
+    return status;
+}
+
 static cairo_int_status_t
 _cairo_pdf_surface_set_paginated_mode (void			*abstract_surface,
 				       cairo_paginated_mode_t	 paginated_mode)
@@ -9273,6 +9451,7 @@ _cairo_pdf_surface_set_paginated_mode (void			*abstract_surface,
     cairo_int_status_t status;
 
     surface->paginated_mode = paginated_mode;
+
     status = _cairo_pdf_interchange_begin_page_content (surface);
     if (unlikely (status))
 	return status;
@@ -9324,6 +9503,8 @@ static const cairo_surface_backend_t cairo_pdf_surface_backend = {
     _cairo_pdf_surface_get_supported_mime_types,
     _cairo_pdf_surface_tag,
     _cairo_pdf_surface_supports_color_glyph,
+    _cairo_pdf_surface_analyze_recording_surface,
+    _cairo_pdf_surface_command_id,
 };
 
 static const cairo_paginated_surface_backend_t
diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c
index d8a8065d0..6da1b6ffa 100644
--- a/src/cairo-quartz-surface.c
+++ b/src/cairo-quartz-surface.c
@@ -722,8 +722,7 @@ _cairo_surface_to_cgimage (cairo_surface_t       *source,
 	status = _cairo_recording_surface_replay_with_clip (source,
 							    matrix,
 							    &surface->base,
-							    NULL,
-							    FALSE);
+							    NULL);
 	if (unlikely (status)) {
 	    cairo_surface_destroy (&surface->base);
 	    return status;
diff --git a/src/cairo-recording-surface-private.h b/src/cairo-recording-surface-private.h
index 7d4de1ed9..acace4ec8 100644
--- a/src/cairo-recording-surface-private.h
+++ b/src/cairo-recording-surface-private.h
@@ -155,6 +155,7 @@ typedef struct _cairo_recording_surface {
     cairo_bool_t optimize_clears;
     cairo_bool_t has_bilevel_alpha;
     cairo_bool_t has_only_op_over;
+    cairo_bool_t has_tags;
 
     struct bbtree {
 	cairo_box_t extents;
@@ -204,18 +205,26 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t     *surf
                                                        cairo_bool_t        *foreground_used);
 
 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,
-                                           cairo_bool_t surface_is_unbounded);
+_cairo_recording_surface_replay_with_transform (cairo_surface_t          *surface,
+						const cairo_matrix_t     *surface_transform,
+						cairo_surface_t          *target,
+						cairo_bool_t              surface_is_unbounded,
+						cairo_bool_t              replay_all);
+
+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);
 
 cairo_private 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);
+						    cairo_bool_t          surface_is_unbounded,
+						    cairo_bool_t          replay_all);
+
 cairo_private cairo_status_t
 _cairo_recording_surface_replay_region (cairo_surface_t			*surface,
                                         unsigned int                     regions_id,
@@ -239,6 +248,9 @@ _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_bool_t
+_cairo_recording_surface_has_tags (cairo_surface_t *surface);
+
 cairo_private cairo_status_t
 _cairo_recording_surface_region_array_attach (cairo_surface_t *surface,
                                               unsigned int    *id);
diff --git a/src/cairo-recording-surface.c b/src/cairo-recording-surface.c
index 2912f5ede..5735c7ca3 100644
--- a/src/cairo-recording-surface.c
+++ b/src/cairo-recording-surface.c
@@ -103,6 +103,7 @@ typedef struct _cairo_recording_surface_replay_params {
     unsigned int regions_id;
     const cairo_color_t *foreground_color;
     cairo_bool_t foreground_used;
+    cairo_bool_t replay_all;
 } cairo_recording_surface_replay_params_t;
 
 static const cairo_surface_backend_t cairo_recording_surface_backend;
@@ -436,6 +437,7 @@ cairo_recording_surface_create (cairo_content_t		 content,
     surface->optimize_clears = TRUE;
     surface->has_bilevel_alpha = FALSE;
     surface->has_only_op_over = FALSE;
+    surface->has_tags = FALSE;
 
     CAIRO_MUTEX_INIT (surface->mutex);
 
@@ -1190,6 +1192,8 @@ _cairo_recording_surface_tag (void			 *abstract_surface,
 
     TRACE ((stderr, "%s: surface=%d\n", __FUNCTION__, surface->base.unique_id));
 
+    surface->has_tags = TRUE;
+
     command = calloc (1, sizeof (cairo_command_tag_t));
     if (unlikely (command == NULL)) {
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -1635,6 +1639,7 @@ _cairo_recording_surface_snapshot (void *abstract_other)
     surface->unbounded = other->unbounded;
     surface->has_bilevel_alpha = other->has_bilevel_alpha;
     surface->has_only_op_over = other->has_only_op_over;
+    surface->has_tags = other->has_tags;
 
     surface->base.is_clear = other->base.is_clear;
 
@@ -1644,8 +1649,6 @@ _cairo_recording_surface_snapshot (void *abstract_other)
     surface->indices = NULL;
     surface->num_indices = 0;
     surface->optimize_clears = TRUE;
-    surface->has_bilevel_alpha = other->has_bilevel_alpha;
-    surface->has_only_op_over = other->has_only_op_over;
 
     CAIRO_MUTEX_INIT (surface->mutex);
 
@@ -1715,6 +1718,8 @@ static const cairo_surface_backend_t cairo_recording_surface_backend = {
     NULL, /* get_supported_mime_types */
     _cairo_recording_surface_tag,
     _cairo_recording_surface_supports_color_glyph,
+    NULL, /* analyze_recording_surface */
+    NULL, /* command_id */
 };
 
 static unsigned int
@@ -1918,7 +1923,7 @@ _cairo_recording_surface_get_visible_commands (cairo_recording_surface_t *surfac
     cairo_box_t box;
 
     if (surface->commands.num_elements == 0)
-	    return 0;
+	return 0;
 
     _cairo_box_from_rectangle (&box, extents);
 
@@ -2078,7 +2083,7 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
     if (regions_array)
 	region_elements = _cairo_array_index (&regions_array->regions, 0);
 
-    if (extents.width < r->width || extents.height < r->height) {
+    if (!params->replay_all && (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;
@@ -2106,6 +2111,13 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 		continue;
 	}
 
+
+	if (params->target->backend->command_id) {
+	    status = params->target->backend->command_id (params->target, params->regions_id, i);
+	    if (unlikely (status))
+		return status;
+	}
+
 	switch (command->header.type) {
 	case CAIRO_COMMAND_PAINT:
 	    if (region_element)
@@ -2457,6 +2469,7 @@ _cairo_recording_surface_replay (cairo_surface_t *surface,
     params.region = CAIRO_RECORDING_REGION_ALL;
     params.regions_id = 0;
     params.foreground_color = NULL;
+    params.replay_all = FALSE;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
 }
@@ -2480,6 +2493,7 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t     *surf
     params.regions_id = 0;
     params.foreground_color = foreground_color;
     params.foreground_used = FALSE;
+    params.replay_all = FALSE;
 
     status = _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
     *foreground_used = params.foreground_used;
@@ -2487,12 +2501,34 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t     *surf
     return status;
 }
 
+cairo_status_t
+_cairo_recording_surface_replay_with_transform (cairo_surface_t *surface,
+						const cairo_matrix_t *surface_transform,
+						cairo_surface_t *target,
+						cairo_bool_t surface_is_unbounded,
+						cairo_bool_t replay_all)
+{
+    cairo_recording_surface_replay_params_t params;
+
+    params.surface_extents = NULL;
+    params.surface_transform = surface_transform;
+    params.target = target;
+    params.target_clip = NULL;
+    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;
+    params.replay_all = replay_all;
+
+    return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
+}
+
 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,
-                                           cairo_bool_t surface_is_unbounded)
+					   const cairo_clip_t *target_clip)
 {
     cairo_recording_surface_replay_params_t params;
 
@@ -2500,11 +2536,12 @@ _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 = surface_is_unbounded;
+    params.surface_is_unbounded = FALSE;
     params.type = CAIRO_RECORDING_REPLAY;
     params.region = CAIRO_RECORDING_REGION_ALL;
     params.regions_id = 0;
     params.foreground_color = NULL;
+    params.replay_all = FALSE;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
 }
@@ -2520,7 +2557,8 @@ _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_bool_t surface_is_unbounded,
+						    cairo_bool_t replay_all)
 {
     cairo_recording_surface_replay_params_t params;
 
@@ -2533,6 +2571,7 @@ _cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
     params.region = CAIRO_RECORDING_REGION_ALL;
     params.regions_id = regions_id;
     params.foreground_color = NULL;
+    params.replay_all = replay_all;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
 }
@@ -2555,6 +2594,7 @@ _cairo_recording_surface_replay_region (cairo_surface_t          *surface,
     params.region = region;
     params.regions_id = regions_id;
     params.foreground_color = NULL;
+    params.replay_all = FALSE;
 
     return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
 }
@@ -2702,6 +2742,21 @@ _cairo_recording_surface_has_only_op_over (cairo_recording_surface_t *surface)
     return surface->has_only_op_over;
 }
 
+cairo_bool_t
+_cairo_recording_surface_has_tags (cairo_surface_t *surface)
+{
+    cairo_recording_surface_t *record;
+
+    if (surface->status || ! _cairo_surface_is_recording (surface)) {
+	_cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+	return FALSE;
+    }
+
+    record = (cairo_recording_surface_t *)surface;
+
+    return record->has_tags;
+}
+
 static void
 print_indent (FILE *file, int indent)
 {
@@ -2875,7 +2930,10 @@ _cairo_debug_print_recording_surface (FILE            *file,
 
 	    case CAIRO_COMMAND_TAG:
 		print_indent (file, indent);
-		fprintf(file, "%d TAG\n", i);
+		fprintf(file, "%d %s %s '%s'\n",
+			i,
+			command->tag.begin ? "BEGIN TAG" : "END TAG",
+			command->tag.tag_name, command->tag.attributes);
 		break;
 
 	    default:
diff --git a/src/cairo-spans-compositor.c b/src/cairo-spans-compositor.c
index 49c5999d2..50c92b25c 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, FALSE);
+							    m, dst, recording_clip);
 	_cairo_clip_destroy (recording_clip);
 
 	return status;
diff --git a/src/cairo-surface-backend-private.h b/src/cairo-surface-backend-private.h
index 15032de20..856c596c8 100644
--- a/src/cairo-surface-backend-private.h
+++ b/src/cairo-surface-backend-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
@@ -40,6 +41,7 @@
 
 #include "cairo-compiler-private.h"
 #include "cairo-error-private.h"
+#include "cairo-pattern-private.h"
 
 CAIRO_BEGIN_DECLS
 
@@ -211,6 +213,29 @@ struct _cairo_surface_backend {
     (*supports_color_glyph) (void                       *surface,
                              cairo_scaled_font_t        *scaled_font,
                              unsigned long               glyph_index);
+
+    /* Paginated surfaces only. During the analysis stage, if any
+     * recording surfaces used as a source are to be replayed, this
+     * function will be called at the begining and end of the replay.
+     *
+     * @recording_surface - the recording surface used as a source
+     * @source_type - a type indicating the combination of drawing
+     * operation and source type
+     * @begin - TRUE when called before the replay, FALSE when called
+     * after the replay has finished.
+     */
+    cairo_warn cairo_int_status_t
+    (*analyze_recording_surface)(void	                       *surface,
+				 const cairo_surface_pattern_t *recording_surface_pattern,
+				 unsigned int                   region_id,
+                                 cairo_analysis_source_t        source_type,
+                                 cairo_bool_t                   begin);
+
+    cairo_warn cairo_int_status_t
+    (*command_id)		(void			*surface,
+				 unsigned int            recording_id,
+				 unsigned int            command_id);
+
 };
 
 cairo_private cairo_status_t
diff --git a/src/cairo-tag-attributes-private.h b/src/cairo-tag-attributes-private.h
index 3f5fa5b64..1b770aef9 100644
--- a/src/cairo-tag-attributes-private.h
+++ b/src/cairo-tag-attributes-private.h
@@ -49,6 +49,15 @@ typedef enum {
     TAG_LINK_FILE,
 } cairo_tag_link_type_t;
 
+typedef struct _cairo_content_attrs {
+    char *id;
+    char *tag_name;
+} cairo_content_attrs_t;
+
+typedef struct _cairo_content_ref_attrs {
+    char *ref;
+} cairo_content_ref_attrs_t;
+
 typedef struct _cairo_link_attrs {
     cairo_tag_link_type_t link_type;
     cairo_array_t rects;
@@ -58,6 +67,9 @@ typedef struct _cairo_link_attrs {
     int page;
     cairo_bool_t has_pos;
     cairo_point_double_t pos;
+    char *id;
+    char *ref;
+    int link_page;
 } cairo_link_attrs_t;
 
 typedef struct _cairo_dest_attrs {
@@ -90,10 +102,28 @@ _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *li
 cairo_private cairo_int_status_t
 _cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *dest_attrs);
 
+cairo_private cairo_int_status_t
+_cairo_tag_parse_content_attributes (const char *attributes, cairo_content_attrs_t *content_attrs);
+
+cairo_private cairo_int_status_t
+_cairo_tag_parse_content_ref_attributes (const char *attributes, cairo_content_ref_attrs_t *content_ref_attrs);
+
 cairo_private cairo_int_status_t
 _cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *dest_attrs);
 
 cairo_private cairo_int_status_t
 _cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *dest_attrs);
 
+cairo_private void
+_cairo_tag_free_link_attributes (cairo_link_attrs_t *link_attrs);
+
+cairo_private void
+_cairo_tag_free_dest_attributes (cairo_dest_attrs_t *link_attrs);
+
+cairo_private void
+_cairo_tag_free_content_attributes (cairo_content_attrs_t *link_attrs);
+
+cairo_private void
+_cairo_tag_free_content_ref_attributes (cairo_content_ref_attrs_t *link_attrs);
+
 #endif /* CAIRO_TAG_ATTRIBUTES_PRIVATE_H */
diff --git a/src/cairo-tag-attributes.c b/src/cairo-tag-attributes.c
index 85467ad73..4fde1b5b8 100644
--- a/src/cairo-tag-attributes.c
+++ b/src/cairo-tag-attributes.c
@@ -59,6 +59,25 @@ typedef struct _attribute_spec {
     int array_size; /* 0 = scalar, -1 = variable size array */
 } attribute_spec_t;
 
+/*
+ * id      [optional] content id
+ * content [optional] One or more content ids
+ */
+static const attribute_spec_t _content_attrib_spec[] = {
+    { "tag_name", ATTRIBUTE_STRING },
+    { "id",       ATTRIBUTE_STRING },
+    { NULL }
+};
+
+/*
+ * id      [optional] content id
+ * content [optional] One or more content ids
+ */
+static const attribute_spec_t _content_ref_attrib_spec[] = {
+    { "ref",       ATTRIBUTE_STRING },
+    { NULL }
+};
+
 /*
  * name [required] Unique name of this destination (UTF-8)
  * x    [optional] x coordinate of destination on page. Default is x coord of
@@ -68,7 +87,7 @@ typedef struct _attribute_spec {
  * internal [optional] If true, the name may be optimized out of the PDF where
  *                     possible. Default false.
  */
-static attribute_spec_t _dest_attrib_spec[] = {
+static const attribute_spec_t _dest_attrib_spec[] = {
     { "name",     ATTRIBUTE_STRING },
     { "x",        ATTRIBUTE_FLOAT },
     { "y",        ATTRIBUTE_FLOAT },
@@ -103,7 +122,7 @@ static attribute_spec_t _dest_attrib_spec[] = {
  *     page - Page number in the PDF file to link to
  *     pos  - [optional] Position of destination on page. Default is 0,0.
  */
-static attribute_spec_t _link_attrib_spec[] =
+static const attribute_spec_t _link_attrib_spec[] =
 {
     { "rect", ATTRIBUTE_FLOAT, -1 },
     { "dest", ATTRIBUTE_STRING },
@@ -111,6 +130,9 @@ static attribute_spec_t _link_attrib_spec[] =
     { "file", ATTRIBUTE_STRING },
     { "page", ATTRIBUTE_INT },
     { "pos",  ATTRIBUTE_FLOAT, 2 },
+    { "id",   ATTRIBUTE_STRING },
+    { "ref",  ATTRIBUTE_STRING },
+    { "link_page", ATTRIBUTE_INT },
     { NULL }
 };
 
@@ -131,7 +153,7 @@ static attribute_spec_t _link_attrib_spec[] =
  *   DamagedRowsBeforeError - Number of damages rows tolerated before an error
  *                            occurs. Default: 0.
  */
-static attribute_spec_t _ccitt_params_spec[] =
+static const attribute_spec_t _ccitt_params_spec[] =
 {
     { "Columns",                ATTRIBUTE_INT },
     { "Rows",                   ATTRIBUTE_INT },
@@ -152,7 +174,7 @@ static attribute_spec_t _ccitt_params_spec[] =
  *          ury - upper right y xoordinate
  *        all coordinates are in PostScript coordinates.
  */
-static attribute_spec_t _eps_params_spec[] =
+static const attribute_spec_t _eps_params_spec[] =
 {
     { "bbox", ATTRIBUTE_FLOAT, 4 },
     { NULL }
@@ -362,7 +384,7 @@ parse_name (const char *attributes, const char *p, const char **end, char **s)
 	    attributes, p);
 
     p2 = p;
-    while (_cairo_isalpha (*p2) || _cairo_isdigit (*p2))
+    while (_cairo_isalpha (*p2) || _cairo_isdigit (*p2) || *p2  == '_')
 	p2++;
 
     len = p2 - p;
@@ -376,9 +398,9 @@ parse_name (const char *attributes, const char *p, const char **end, char **s)
 }
 
 static cairo_int_status_t
-parse_attributes (const char *attributes, attribute_spec_t *attrib_def, cairo_list_t *list)
+parse_attributes (const char *attributes, const attribute_spec_t *attrib_def, cairo_list_t *list)
 {
-    attribute_spec_t *def;
+    const attribute_spec_t *def;
     attribute_t *attrib;
     char *name = NULL;
     cairo_int_status_t status;
@@ -489,6 +511,73 @@ free_attributes_list (cairo_list_t *list)
     }
 }
 
+cairo_int_status_t
+_cairo_tag_parse_content_attributes (const char *attributes, cairo_content_attrs_t *content_attrs)
+{
+    cairo_list_t list;
+    cairo_int_status_t status;
+    attribute_t *attr;
+
+    cairo_list_init (&list);
+    status = parse_attributes (attributes, _content_attrib_spec, &list);
+    if (unlikely (status))
+	goto cleanup;
+
+    memset (content_attrs, 0, sizeof (cairo_content_attrs_t));
+    cairo_list_foreach_entry (attr, attribute_t, &list, link)
+    {
+	if (strcmp (attr->name, "tag_name") == 0) {
+	    content_attrs->tag_name = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "id") == 0) {
+	    content_attrs->id = strdup (attr->scalar.s);
+	}
+    }
+
+    if (! content_attrs->tag_name) {
+	status = _cairo_tag_error ("CONTENT attributes: \"%s\" missing tag_name attribute",
+				   attributes);
+    } else if (! content_attrs->tag_name) {
+	status = _cairo_tag_error ("CONTENT attributes: \"%s\" missing id attribute",
+				   attributes);
+    }
+
+  cleanup:
+    free_attributes_list (&list);
+
+    return status;
+}
+
+cairo_int_status_t
+_cairo_tag_parse_content_ref_attributes (const char *attributes, cairo_content_ref_attrs_t *content_ref_attrs)
+{
+    cairo_list_t list;
+    cairo_int_status_t status;
+    attribute_t *attr;
+
+    cairo_list_init (&list);
+    status = parse_attributes (attributes, _content_ref_attrib_spec, &list);
+    if (unlikely (status))
+	goto cleanup;
+
+    memset (content_ref_attrs, 0, sizeof (cairo_content_ref_attrs_t));
+    cairo_list_foreach_entry (attr, attribute_t, &list, link)
+    {
+	if (strcmp (attr->name, "ref") == 0) {
+	    content_ref_attrs->ref = strdup (attr->scalar.s);
+	}
+    }
+
+    if (! content_ref_attrs->ref) {
+	status = _cairo_tag_error ("CONTENT_REF  attributes: \"%s\" missing ref attribute",
+				   attributes);
+    }
+
+  cleanup:
+    free_attributes_list (&list);
+
+    return status;
+}
+
 cairo_int_status_t
 _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs)
 {
@@ -496,7 +585,6 @@ _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *li
     cairo_int_status_t status;
     attribute_t *attr;
     attrib_val_t val;
-    cairo_bool_t has_rect = FALSE;
     cairo_bool_t invalid_combination = FALSE;
 
     cairo_list_init (&list);
@@ -554,7 +642,16 @@ _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *li
 		if (unlikely (status))
 		    goto cleanup;
 	    }
-	    has_rect = TRUE;
+	} else if (strcmp (attr->name, "id") == 0) {
+	    link_attrs->id = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "ref") == 0) {
+	    link_attrs->ref = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "link_page") == 0) {
+	    link_attrs->link_page = attr->scalar.i;
+	    if (link_attrs->link_page < 1) {
+		status = _cairo_tag_error ("Link attributes: \"%s\" page must be >= 1", attributes);
+		goto cleanup;
+	    }
 	}
     }
 
@@ -723,3 +820,33 @@ _cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *eps_par
 
     return status;
 }
+
+void
+_cairo_tag_free_link_attributes (cairo_link_attrs_t *link_attrs)
+{
+    _cairo_array_fini (&link_attrs->rects);
+    free (link_attrs->dest);
+    free (link_attrs->uri);
+    free (link_attrs->file);
+    free (link_attrs->id);
+    free (link_attrs->ref);
+}
+
+void
+_cairo_tag_free_dest_attributes (cairo_dest_attrs_t *dest_attrs)
+{
+    free (dest_attrs->name);
+}
+
+void
+_cairo_tag_free_content_attributes (cairo_content_attrs_t *content_attrs)
+{
+    free (content_attrs->id);
+    free (content_attrs->tag_name);
+}
+
+void
+_cairo_tag_free_content_ref_attributes (cairo_content_ref_attrs_t *content_ref_attrs)
+{
+    free (content_ref_attrs->ref);
+}
diff --git a/src/cairo-tag-stack-private.h b/src/cairo-tag-stack-private.h
index fbb2c0e25..49145bf1d 100644
--- a/src/cairo-tag-stack-private.h
+++ b/src/cairo-tag-stack-private.h
@@ -46,6 +46,9 @@ typedef enum {
     TAG_TYPE_STRUCTURE = 1,
     TAG_TYPE_LINK = 2,
     TAG_TYPE_DEST = 4,
+    TAG_TYPE_CONTENT = 8,
+    TAG_TYPE_CONTENT_REF = 16,
+    TAG_TYPE_ARTIFACT = 16,
 } cairo_tag_type_t;
 
 /* The type of the structure tree. */
@@ -98,6 +101,12 @@ _cairo_tag_stack_pop (cairo_tag_stack_t *stack,
 cairo_private cairo_tag_stack_elem_t *
 _cairo_tag_stack_top_elem (cairo_tag_stack_t *stack);
 
+cairo_private void
+_cairo_tag_stack_foreach (cairo_tag_stack_t *stack,
+			  void (*func)(cairo_tag_stack_elem_t *elem,
+				       void *closure),
+			  void *closure);
+
 cairo_private void
 _cairo_tag_stack_free_elem (cairo_tag_stack_elem_t *elem);
 
diff --git a/src/cairo-tag-stack.c b/src/cairo-tag-stack.c
index 7341aa41a..65444c907 100644
--- a/src/cairo-tag-stack.c
+++ b/src/cairo-tag-stack.c
@@ -99,6 +99,9 @@ static const char * _cairo_tag_stack_struct_pdf_list[] =
     "Formula",
     "Form",
 
+    /* Section 14.8.2.2.2 - Artifacts */
+    "Artifact",
+
     NULL
 };
 
@@ -106,6 +109,8 @@ static const char * _cairo_tag_stack_struct_pdf_list[] =
 static const char * _cairo_tag_stack_cairo_tag_list[] =
 {
     CAIRO_TAG_DEST,
+    CAIRO_TAG_CONTENT,
+    CAIRO_TAG_CONTENT_REF,
     NULL
 };
 
@@ -166,6 +171,17 @@ _cairo_tag_stack_push (cairo_tag_stack_t *stack,
 	return _cairo_tag_error ("Invalid tag: %s", name);
     }
 
+    cairo_tag_stack_elem_t *top = _cairo_tag_stack_top_elem (stack);
+    if (top &&
+	(strcmp (top->name, CAIRO_TAG_CONTENT) == 0 ||
+	 strcmp (top->name, CAIRO_TAG_CONTENT_REF) == 0 ||
+	 strcmp (top->name, "Artifact") == 0))
+    {
+	return _cairo_tag_error ("%s tag can not contain nested tags",
+				 (strcmp (top->name, CAIRO_TAG_CONTENT) == 0) ? "CAIRO_TAG_CONTENT" :
+				 ((strcmp (top->name, CAIRO_TAG_CONTENT_REF) == 0) ? "CAIRO_TAG_CONTENT_REF" : top->name));
+    }
+
     if (stack->type == TAG_TREE_TYPE_NO_TAGS) {
 	if (name_in_list (name, _cairo_tag_stack_tagged_pdf_top_level_element_list))
 	    stack->type = TAG_TREE_TYPE_TAGGED;
@@ -256,6 +272,19 @@ _cairo_tag_stack_top_elem (cairo_tag_stack_t *stack)
     return cairo_list_last_entry (&stack->list, cairo_tag_stack_elem_t, link);
 }
 
+void
+_cairo_tag_stack_foreach (cairo_tag_stack_t *stack,
+			  void (*func)(cairo_tag_stack_elem_t *elem,
+				       void *closure),
+			  void *closure)
+{
+    cairo_tag_stack_elem_t *elem;
+
+    cairo_list_foreach_entry (elem, cairo_tag_stack_elem_t, &stack->list, link) {
+	func (elem, closure);
+    }
+}
+
 void
 _cairo_tag_stack_free_elem (cairo_tag_stack_elem_t *elem)
 {
@@ -274,9 +303,15 @@ _cairo_tag_get_type (const char *name)
     if (strcmp(name, "Link") == 0)
 	return (TAG_TYPE_LINK | TAG_TYPE_STRUCTURE);
 
-    if (strcmp(name, "cairo.dest") == 0)
+    if (strcmp(name, CAIRO_TAG_DEST) == 0)
 	return TAG_TYPE_DEST;
 
+    if (strcmp(name, CAIRO_TAG_CONTENT) == 0)
+	return TAG_TYPE_CONTENT;
+
+    if (strcmp(name, CAIRO_TAG_CONTENT_REF) == 0)
+	return TAG_TYPE_CONTENT_REF;
+
     return TAG_TYPE_STRUCTURE;
 }
 
diff --git a/src/cairo-traps-compositor.c b/src/cairo-traps-compositor.c
index d1402d2a3..3414fc268 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, FALSE);
+							    m, dst, recording_clip);
 	_cairo_clip_destroy (recording_clip);
 
 	return status;
diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h
index 736a1bcb3..74a366c23 100644
--- a/src/cairo-types-private.h
+++ b/src/cairo-types-private.h
@@ -437,6 +437,17 @@ typedef struct _cairo_unscaled_font {
     cairo_reference_count_t		 ref_count;
     const cairo_unscaled_font_backend_t	*backend;
 } cairo_unscaled_font_t;
+
+typedef enum _cairo_analysis_source {
+    CAIRO_ANALYSIS_SOURCE_PAINT,
+    CAIRO_ANALYSIS_SOURCE_MASK,
+    CAIRO_ANALYSIS_MASK_MASK,
+    CAIRO_ANALYSIS_SOURCE_FILL,
+    CAIRO_ANALYSIS_SOURCE_STROKE,
+    CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS,
+    CAIRO_ANALYSIS_SOURCE_NONE /* Used when analysis_source is not applicable. */
+} cairo_analysis_source_t;
+
 CAIRO_END_DECLS
 
 #endif /* CAIRO_TYPES_PRIVATE_H */
diff --git a/src/cairo-xcb-surface-render.c b/src/cairo-xcb-surface-render.c
index a4441dc46..ab3d6881f 100644
--- a/src/cairo-xcb-surface-render.c
+++ b/src/cairo-xcb-surface-render.c
@@ -1112,8 +1112,7 @@ record_to_picture (cairo_surface_t *target,
 
     status = _cairo_recording_surface_replay_with_clip (source,
 							&matrix, tmp,
-							NULL,
-                                                        FALSE);
+							NULL);
     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 63e155e00..4c3b99d9e 100644
--- a/src/cairo-xlib-source.c
+++ b/src/cairo-xlib-source.c
@@ -920,8 +920,7 @@ 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,
-							FALSE);
+							NULL);
     cairo_surface_destroy (recording);
     if (unlikely (status)) {
 	cairo_surface_destroy (&src->base);
diff --git a/src/cairo.h b/src/cairo.h
index eef4c442b..a2c955281 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -1039,6 +1039,8 @@ cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list);
 
 #define CAIRO_TAG_DEST "cairo.dest"
 #define CAIRO_TAG_LINK "Link"
+#define CAIRO_TAG_CONTENT "cairo.content"
+#define CAIRO_TAG_CONTENT_REF "cairo.content_ref"
 
 cairo_public void
 cairo_tag_begin (cairo_t *cr, const char *tag_name, const char *attributes);
diff --git a/src/cairoint.h b/src/cairoint.h
index e5c281842..ddbbe7baa 100644
--- a/src/cairoint.h
+++ b/src/cairoint.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
diff --git a/test/cairo-test.c b/test/cairo-test.c
index 5a2cf1a74..49dfadb15 100644
--- a/test/cairo-test.c
+++ b/test/cairo-test.c
@@ -185,7 +185,7 @@ _cairo_test_init (cairo_test_context_t *ctx,
 	ctx->own_targets = FALSE;
 
 	ctx->srcdir = parent->srcdir;
-	ctx->refdir = parent->refdir;
+	ctx->refdir = xstrdup (parent->refdir);
     } else {
 	int tmp_num_targets;
 	cairo_bool_t tmp_limited_targets;
@@ -204,7 +204,10 @@ _cairo_test_init (cairo_test_context_t *ctx,
                 ctx->srcdir = "srcdir";
 #endif
         }
-	ctx->refdir = getenv ("CAIRO_REF_DIR");
+
+	ctx->refdir = xstrdup (getenv ("CAIRO_REF_DIR"));
+        if (ctx->refdir == NULL)
+            xasprintf (&ctx->refdir, "%s/reference", ctx->srcdir);
     }
 
 #ifdef HAVE_UNISTD_H
@@ -246,6 +249,7 @@ cairo_test_fini (cairo_test_context_t *ctx)
 	fclose (ctx->log_file);
     ctx->log_file = NULL;
 
+    free (ctx->refdir);
     free (ctx->ref_name);
     cairo_surface_destroy (ctx->ref_image);
     cairo_surface_destroy (ctx->ref_image_flattened);
diff --git a/test/cairo-test.h b/test/cairo-test.h
index adff2583c..b70654654 100644
--- a/test/cairo-test.h
+++ b/test/cairo-test.h
@@ -239,7 +239,7 @@ struct _cairo_test_context {
     FILE *log_file;
     const char *output;
     const char *srcdir; /* directory containing sources and input data */
-    const char *refdir; /* directory containing reference images */
+    char *refdir; /* directory containing reference images */
 
     char *ref_name; /* cache of the current reference image */
     cairo_surface_t *ref_image;
diff --git a/test/check-pdf-structure.sh b/test/check-pdf-structure.sh
new file mode 100755
index 000000000..31ae5ac2c
--- /dev/null
+++ b/test/check-pdf-structure.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+if test $# -ne 4 ; then
+   echo "Usage: $0 <pdf-file> <pdfinfo-output> <pdfinfo-ref> <diff-output>"
+   exit 3
+fi
+
+# Check for pdfinfo version >= 21.10.00
+if pdfinfo -v 2>& 1 | awk '/pdfinfo version/ { split($3,v,/[.]/); if (v[1] > 21 || (v[1] == 21 && v[2] >= 10) ) { print "yes" }  } ' | grep -q  'yes'; then
+    pdfinfo -struct-text "$1" > "$2"
+    if test -f "$3" ; then
+        diff -u "$3" "$2" > "$4"
+        # diff exit codes: 0 = match, 1 = different, 2 = error
+        exit $?
+    else
+        exit 3 # missing ref file
+    fi
+fi
+
+ # pdfinfo missing or wrong version
+exit 4
diff --git a/test/meson.build b/test/meson.build
index 548e25b4d..1d76d5daa 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -452,6 +452,10 @@ test_pdf_sources = [
   'pdf-tagged-text.c',
 ]
 
+test_pdf_structure_sources = [
+  'pdf-structure.c',
+]
+
 test_ps_sources = [
   'ps-eps.c',
   'ps-features.c',
@@ -555,6 +559,9 @@ endif
 
 if feature_conf.get('CAIRO_HAS_PDF_SURFACE', 0) == 1
   test_sources += test_pdf_sources
+  if host_machine.system() != 'windows'
+    test_sources += test_pdf_structure_sources
+  endif
   has_multipage_surfaces = true
   add_fallback_resolution = true
   build_any2ppm = true
diff --git a/test/pdf-structure.c b/test/pdf-structure.c
new file mode 100644
index 000000000..ee4efe511
--- /dev/null
+++ b/test/pdf-structure.c
@@ -0,0 +1,568 @@
+/*
+ * 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 "cairo-test.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* __unix__ */
+#endif
+
+#include <cairo.h>
+#include <cairo-pdf.h>
+
+/* Test PDF logical structure
+ */
+
+#define BASENAME "pdf-structure"
+
+#define PAGE_WIDTH 595
+#define PAGE_HEIGHT 842
+
+#define PDF_VERSION CAIRO_PDF_VERSION_1_4
+
+struct pdf_structure_test {
+    const char *name;
+    void (*func)(cairo_t *cr);
+};
+
+static void
+text(cairo_t *cr, const char *text)
+{
+    double x, y;
+
+    cairo_show_text (cr, text);
+    cairo_get_current_point (cr, &x, &y);
+    cairo_move_to (cr, 20, y + 15);
+}
+
+static void
+test_simple (cairo_t *cr)
+{
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    text (cr, "Heading");
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para3");
+
+    cairo_tag_begin (cr, "Note", "");
+    text (cr, "Note");
+    cairo_tag_end (cr, "Note");
+
+    text (cr, "Para4");
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static void
+test_simple_ref (cairo_t *cr)
+{
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading'");
+    text (cr, "Heading");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para1'");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para2'");
+    text (cr, "Para3");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='Note' id='note'");
+    text (cr, "Note");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para3'");
+    text (cr, "Para4");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_tag_begin (cr, "P", "");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para1'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_begin (cr, "P", "");
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para2'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+
+    cairo_tag_begin (cr, "Note", "");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='note'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, "Note");
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para3'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static void
+test_group (cairo_t *cr)
+{
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    text (cr, "Heading");
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_push_group (cr);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, "P");
+
+    cairo_pop_group_to_source (cr);
+    cairo_paint (cr);
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static void
+test_group_ref (cairo_t *cr)
+{
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading'");
+    text (cr, "Heading");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_push_group (cr);
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para'");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_pop_group_to_source (cr);
+    cairo_paint (cr);
+
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_tag_begin (cr, "P", "");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+
+}
+
+static void
+test_repeated_group (cairo_t *cr)
+{
+    cairo_pattern_t *pat;
+
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    text (cr, "Heading");
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_push_group (cr);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, "P");
+
+    pat = cairo_pop_group (cr);
+
+    cairo_set_source (cr, pat);
+    cairo_paint (cr);
+
+    cairo_translate (cr, 0, 100);
+    cairo_set_source (cr, pat);
+    cairo_rectangle (cr, 0, 0, 100, 100);
+    cairo_fill (cr);
+
+    cairo_translate (cr, 0, 100);
+    cairo_set_source_rgb (cr, 1, 0, 0);
+    cairo_mask (cr, pat);
+
+    cairo_translate (cr, 0, 100);
+    cairo_set_source_rgb (cr, 0, 1, 0);
+    cairo_move_to (cr, 20, 0);
+    cairo_line_to (cr, 100, 0);
+    cairo_stroke (cr);
+
+    cairo_translate (cr, 0, 100);
+    cairo_set_source_rgb (cr, 0, 0, 1);
+    cairo_move_to (cr, 20, 0);
+    cairo_show_text (cr, "Text");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static void
+test_multipage_simple (cairo_t *cr)
+{
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para1-dest'");
+    text (cr, "Heading1");
+    cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para2-dest'");
+    text (cr, "Heading2");
+    cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_show_page (cr);
+
+    cairo_tag_begin (cr, "P", "");
+
+    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para1-dest' internal");
+    text (cr, "Para1");
+    cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+    cairo_show_page (cr);
+
+    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para2-dest' internal");
+    text (cr, "Para2");
+    cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static void
+test_multipage_simple_ref (cairo_t *cr)
+{
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading1'");
+    text (cr, "Heading1");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading2'");
+    text (cr, "Heading2");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+    cairo_show_page (cr);
+
+    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para1-dest' internal");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para1'");
+    text (cr, "Para1");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+    cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+    cairo_show_page (cr);
+
+    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para2-dest' internal");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para2'");
+    text (cr, "Para2");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+    cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para1-dest' link_page=1");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading1'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para2-dest' link_page=1");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading2'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_tag_begin (cr, "P", "");
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para1'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para2'");
+    cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static void
+test_multipage_group (cairo_t *cr)
+{
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    text (cr, "Heading");
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_push_group (cr);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, "P");
+
+    cairo_pop_group_to_source (cr);
+    cairo_paint (cr);
+    cairo_set_source_rgb (cr, 0, 0, 0);
+
+    cairo_show_page (cr);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para3");
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+/* Same as test_multipage_group but but repeat the group on the second page. */
+static void
+test_multipage_group2 (cairo_t *cr)
+{
+    cairo_tag_begin (cr, "Document", NULL);
+
+    cairo_tag_begin (cr, "H", "");
+    text (cr, "Heading");
+    cairo_tag_end (cr, "H");
+
+    cairo_tag_begin (cr, "Sect", NULL);
+
+    cairo_push_group (cr);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para1");
+    text (cr, "Para2");
+    cairo_tag_end (cr, "P");
+
+    cairo_pop_group_to_source (cr);
+    cairo_paint (cr);
+
+    cairo_show_page (cr);
+
+    cairo_paint (cr);
+    cairo_set_source_rgb (cr, 0, 0, 0);
+
+    cairo_tag_begin (cr, "P", "");
+    text (cr, "Para3");
+    cairo_tag_end (cr, "P");
+
+    cairo_tag_end (cr, "Sect");
+
+    cairo_tag_end (cr, "Document");
+}
+
+static const struct pdf_structure_test pdf_structure_tests[] = {
+    { "simple", test_simple },
+    { "simple-ref", test_simple_ref },
+    { "group", test_group },
+    { "group-ref", test_group_ref },
+    { "repeated-group", test_repeated_group },
+    { "multipage-simple", test_multipage_simple },
+    { "multipage-simple-ref", test_multipage_simple_ref },
+    { "multipage-group", test_multipage_group },
+    { "multipage-group2", test_multipage_group2 },
+};
+
+static cairo_test_status_t
+create_pdf (cairo_test_context_t *ctx, const struct pdf_structure_test *test, const char *output)
+{
+    cairo_surface_t *surface;
+    cairo_t *cr;
+    cairo_status_t status, status2;
+
+    surface = cairo_pdf_surface_create (output, PAGE_WIDTH, PAGE_HEIGHT);
+
+    cairo_pdf_surface_restrict_to_version (surface, PDF_VERSION);
+
+    cr = cairo_create (surface);
+
+    cairo_select_font_face (cr, CAIRO_TEST_FONT_FAMILY " Serif",
+                            CAIRO_FONT_SLANT_NORMAL,
+                            CAIRO_FONT_WEIGHT_NORMAL);
+    cairo_set_font_size (cr, 10);
+    cairo_move_to (cr, 20, 20);
+
+    test->func(cr);
+
+    status = cairo_status (cr);
+    cairo_destroy (cr);
+    cairo_surface_finish (surface);
+    status2 = cairo_surface_status (surface);
+    if (status == CAIRO_STATUS_SUCCESS)
+	status = status2;
+
+    cairo_surface_destroy (surface);
+    if (status) {
+	cairo_test_log (ctx, "Failed to create pdf surface for file %s: %s\n",
+			output, cairo_status_to_string (status));
+	return CAIRO_TEST_FAILURE;
+    }
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+check_pdf (cairo_test_context_t *ctx, const struct pdf_structure_test *test, const char *output)
+{
+    char *command;
+    int ret;
+    cairo_test_status_t result = CAIRO_TEST_FAILURE;
+
+    /* check-pdf-structure.sh <pdf-file> <pdfinfo-output> <pdfinfo-ref> <diff-output> */
+    xasprintf (&command,
+               "%s/check-pdf-structure.sh  %s  %s/%s-%s.out.txt  %s/%s-%s.ref.txt %s/%s-%s.diff.txt ",
+               ctx->srcdir,
+               output,
+               ctx->output, BASENAME, test->name,
+               ctx->refdir, BASENAME, test->name,
+               ctx->output, BASENAME, test->name);
+
+    ret = system (command);
+    cairo_test_log (ctx, "%s  exit code %d\n", command,
+                    WIFEXITED (ret) ? WEXITSTATUS (ret) : -1);
+
+    if (WIFEXITED (ret)) {
+        if (WEXITSTATUS (ret) == 0)
+            result = CAIRO_TEST_SUCCESS;
+        else if (WEXITSTATUS (ret) == 4)
+            result = CAIRO_TEST_UNTESTED; /* pdfinfo not found, wrong version, missing ref */
+    }
+
+    free (command);
+    return result;
+}
+
+static void
+merge_test_status (cairo_test_status_t *current, cairo_test_status_t new)
+{
+    if (new == CAIRO_TEST_FAILURE || *current == CAIRO_TEST_FAILURE)
+        *current = CAIRO_TEST_FAILURE;
+    else if (new == CAIRO_TEST_UNTESTED)
+        *current = CAIRO_TEST_UNTESTED;
+    else
+        *current = new;
+}
+
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+    int i;
+    char *filename;
+    cairo_test_status_t result, all_results;
+    cairo_bool_t can_check = FALSE;
+
+/* Need a POSIX shell to run the check. */
+#ifdef __unix__
+    can_check = TRUE;
+#endif
+
+    all_results = CAIRO_TEST_SUCCESS;
+    if (! cairo_test_is_target_enabled (ctx, "pdf"))
+	return CAIRO_TEST_UNTESTED;
+
+    for (i = 0; i < ARRAY_LENGTH(pdf_structure_tests); i++) {
+        xasprintf (&filename, "%s/%s-%s.out.pdf",
+                   ctx->output,
+                   BASENAME,
+                   pdf_structure_tests[i].name);
+
+        result = create_pdf (ctx, &pdf_structure_tests[i], filename);
+        merge_test_status (&all_results, result);
+
+        if (can_check && result == CAIRO_TEST_SUCCESS) {
+            result = check_pdf (ctx, &pdf_structure_tests[i], filename);
+            merge_test_status (&all_results, result);
+        } else {
+            merge_test_status (&all_results, CAIRO_TEST_UNTESTED);
+        }
+    }
+
+    free (filename);
+    return all_results;
+}
+
+CAIRO_TEST (pdf_structure,
+	    "Check PDF Structure",
+	    "pdf", /* keywords */
+	    NULL, /* requirements */
+	    0, 0,
+	    preamble, NULL)
diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c
index 1a2f62dac..3883d418e 100644
--- a/test/pdf-tagged-text.c
+++ b/test/pdf-tagged-text.c
@@ -149,7 +149,7 @@ layout_paragraph (cairo_t *cr)
 	*end = ' ';
 	if (text_extents.width + 2*MARGIN > PAGE_WIDTH) {
 	    int len = prev_end - begin;
-	    char *s = malloc (len);
+	    char *s = xmalloc (len);
 	    memcpy (s, begin, len);
 	    s[len-1] = 0;
 	    paragraph_text[paragraph_num_lines++] = s;
@@ -204,9 +204,9 @@ draw_page_num (cairo_surface_t *surface, cairo_t *cr, const char *prefix, int nu
 static void
 draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
 {
-    char buf[100];
+    char *attrib;
 
-    sprintf(buf, "dest='%s'", section->heading);
+    xasprintf (&attrib, "dest='%s'", section->heading);
     cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
     switch (section->level) {
 	case 0:
@@ -230,25 +230,26 @@ draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *sect
     cairo_set_source_rgb (cr, 0, 0, 1);
     cairo_tag_begin (cr, "TOCI", NULL);
     cairo_tag_begin (cr, "Reference", NULL);
-    cairo_tag_begin (cr, CAIRO_TAG_LINK, buf);
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
     cairo_show_text (cr, section->heading);
     cairo_tag_end (cr, CAIRO_TAG_LINK);
     cairo_tag_end (cr, "Reference");
     cairo_tag_end (cr, "TOCI");
     cairo_restore (cr);
     y_pos += HEADING_HEIGHT;
+    free (attrib);
 }
 
 static void
 draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
 {
     int flags, i;
-    char buf[100];
-    char buf2[100];
+    char *name_attrib;
+    char *dest_attrib;
 
     cairo_tag_begin (cr, "Sect", NULL);
-    sprintf(buf, "name='%s'", section->heading);
-    sprintf(buf2, "dest='%s'", section->heading);
+    xasprintf(&name_attrib, "name='%s'", section->heading);
+    xasprintf(&dest_attrib, "dest='%s'", section->heading);
     cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
     if (section->level == 0) {
 	cairo_show_page (cr);
@@ -256,7 +257,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
 	cairo_set_font_size(cr, HEADING1_SIZE);
 	cairo_move_to (cr, MARGIN, MARGIN);
 	cairo_tag_begin (cr, "H1", NULL);
-	cairo_tag_begin (cr, CAIRO_TAG_DEST, buf);
+	cairo_tag_begin (cr, CAIRO_TAG_DEST, name_attrib);
 	cairo_show_text (cr, section->heading);
 	cairo_tag_end (cr, CAIRO_TAG_DEST);
 	cairo_tag_end (cr, "H1");
@@ -265,7 +266,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
 	outline_parents[0] = cairo_pdf_surface_add_outline (surface,
 							    CAIRO_PDF_OUTLINE_ROOT,
 							    section->heading,
-							    buf2,
+							    dest_attrib,
 							    flags);
     } else {
 	if (section->level == 1) {
@@ -286,7 +287,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
 	    cairo_tag_begin (cr, "H2", NULL);
 	else
 	    cairo_tag_begin (cr, "H3", NULL);
-	cairo_tag_begin (cr, CAIRO_TAG_DEST, buf);
+	cairo_tag_begin (cr, CAIRO_TAG_DEST, name_attrib);
 	cairo_show_text (cr, section->heading);
 	cairo_tag_end (cr, CAIRO_TAG_DEST);
 	if (section->level == 1)
@@ -297,7 +298,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
 	outline_parents[section->level] = cairo_pdf_surface_add_outline (surface,
 									 outline_parents[section->level - 1],
 									 section->heading,
-									 buf2,
+									 dest_attrib,
 									 flags);
     }
 
@@ -310,13 +311,15 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
 	draw_paragraph (cr);
     }
     cairo_tag_end (cr, "Sect");
+    free (name_attrib);
+    free (dest_attrib);
 }
 
 static void
 draw_cover (cairo_surface_t *surface, cairo_t *cr)
 {
     cairo_text_extents_t text_extents;
-    char buf[200];
+    char *attrib;
     cairo_rectangle_t url_box;
     const char *cairo_url = "https://www.cairographics.org/";
     const double url_box_margin = 20.0;
@@ -344,10 +347,11 @@ draw_cover (cairo_surface_t *surface, cairo_t *cr)
     url_box.height = -text_extents.height + 2*url_box_margin;
     cairo_rectangle(cr, url_box.x, url_box.y, url_box.width, url_box.height);
     cairo_stroke(cr);
-    snprintf(buf, sizeof(buf), "rect=[%f %f %f %f] uri=\'%s\'",
+    xasprintf(&attrib, "rect=[%f %f %f %f] uri=\'%s\'",
              url_box.x, url_box.y, url_box.width, url_box.height, cairo_url);
-    cairo_tag_begin (cr, CAIRO_TAG_LINK, buf);
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
     cairo_tag_end (cr, CAIRO_TAG_LINK);
+    free (attrib);
 
     /* Create link to not yet emmited page number */
     cairo_tag_begin (cr, CAIRO_TAG_LINK, "page=5");
@@ -419,6 +423,32 @@ create_document (cairo_surface_t *surface, cairo_t *cr)
     cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://127.0.0.1/' rect=[10.0 -10.0 100.0 100.0]");
     cairo_tag_end (cr, CAIRO_TAG_LINK);
 
+
+    /* Distilled from Mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=1725743:
+     * attempting to emit a Destination tag within a pushed group will lead to an
+     * assertion in _cairo_pdf_interchange_end_structure_tag when processing a
+     * following LINK tag that is outside the pushed group.
+     */
+
+    /* PushLayer */
+    cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
+
+    /* Destination */
+    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='a' x=42 y=42");
+    cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+    /* PopLayer */
+    cairo_pop_group_to_source (cr);
+    cairo_paint_with_alpha (cr, 1);
+    cairo_set_source_rgb (cr, 0, 0, 0);
+
+    /* Link */
+    cairo_tag_begin (cr, CAIRO_TAG_LINK, "rect=[100 200 300 400] uri='http://127.0.0.1/'");
+    cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+    /* End of extra Mozilla testcase. */
+
+
     cairo_show_page (cr);
 
     page_num = 0;
@@ -449,6 +479,8 @@ create_document (cairo_surface_t *surface, cairo_t *cr)
 
     cairo_show_page (cr);
 
+    cairo_set_source_rgb (cr, 0, 0, 1);
+
     cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='cover'");
     cairo_move_to (cr, PAGE_WIDTH/3, 2*PAGE_HEIGHT/5);
     cairo_show_text (cr, "link to cover");
diff --git a/test/reference/pdf-structure-group-ref.ref.txt b/test/reference/pdf-structure-group-ref.ref.txt
new file mode 100644
index 000000000..8b1d97fef
--- /dev/null
+++ b/test/reference/pdf-structure-group-ref.ref.txt
@@ -0,0 +1,6 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
diff --git a/test/reference/pdf-structure-group.ref.txt b/test/reference/pdf-structure-group.ref.txt
new file mode 100644
index 000000000..8b1d97fef
--- /dev/null
+++ b/test/reference/pdf-structure-group.ref.txt
@@ -0,0 +1,6 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
diff --git a/test/reference/pdf-structure-multipage-group.ref.txt b/test/reference/pdf-structure-multipage-group.ref.txt
new file mode 100644
index 000000000..61b470ccc
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-group.ref.txt
@@ -0,0 +1,8 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
+    P (block)
+      "Para3"
diff --git a/test/reference/pdf-structure-multipage-group2.ref.txt b/test/reference/pdf-structure-multipage-group2.ref.txt
new file mode 100644
index 000000000..0fbed52f1
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-group2.ref.txt
@@ -0,0 +1,10 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
+    P (block)
+      "Para1Para2"
+    P (block)
+      "Para3"
diff --git a/test/reference/pdf-structure-multipage-simple-ref.ref.txt b/test/reference/pdf-structure-multipage-simple-ref.ref.txt
new file mode 100644
index 000000000..5a22505c9
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-simple-ref.ref.txt
@@ -0,0 +1,12 @@
+Document
+  H (block)
+    Link (inline)
+      Object 21 0
+      "Heading1"
+    Link (inline)
+      Object 24 0
+      "Heading2"
+  Sect
+    P (block)
+      "Para1"
+      "Para2"
diff --git a/test/reference/pdf-structure-multipage-simple.ref.txt b/test/reference/pdf-structure-multipage-simple.ref.txt
new file mode 100644
index 000000000..4d535fbf8
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-simple.ref.txt
@@ -0,0 +1,12 @@
+Document
+  H (block)
+    Link (inline)
+      Object 6 0
+      "Heading1"
+    Link (inline)
+      Object 8 0
+      "Heading2"
+  Sect
+    P (block)
+      "Para1"
+      "Para2"
diff --git a/test/reference/pdf-structure-repeated-group.ref.txt b/test/reference/pdf-structure-repeated-group.ref.txt
new file mode 100644
index 000000000..5292f0127
--- /dev/null
+++ b/test/reference/pdf-structure-repeated-group.ref.txt
@@ -0,0 +1,9 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
+    P (block)
+      "Para1Para2"
+    "Para1Para2Text"
diff --git a/test/reference/pdf-structure-simple-ref.ref.txt b/test/reference/pdf-structure-simple-ref.ref.txt
new file mode 100644
index 000000000..0b0541c0a
--- /dev/null
+++ b/test/reference/pdf-structure-simple-ref.ref.txt
@@ -0,0 +1,11 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
+    P (block)
+      "Para3"
+      Note (inline)
+        "Note"
+      "Para4"
diff --git a/test/reference/pdf-structure-simple.ref.txt b/test/reference/pdf-structure-simple.ref.txt
new file mode 100644
index 000000000..0b0541c0a
--- /dev/null
+++ b/test/reference/pdf-structure-simple.ref.txt
@@ -0,0 +1,11 @@
+Document
+  H (block)
+    "Heading"
+  Sect
+    P (block)
+      "Para1Para2"
+    P (block)
+      "Para3"
+      Note (inline)
+        "Note"
+      "Para4"
commit e7ed40a71dac04cb4c608b409b04577d01f08454
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sun Feb 19 19:10:18 2023 +1030

    cairo-list-inline.h: fixes and documentation
    
    - Fix a bug in cairo_list_is_singular
    - Rename cairo_list_swap() to cairo_list_move_list()
      to better describe what it is doing.

diff --git a/src/cairo-list-inline.h b/src/cairo-list-inline.h
index 0955178d2..7d68917f0 100644
--- a/src/cairo-list-inline.h
+++ b/src/cairo-list-inline.h
@@ -39,6 +39,14 @@
 
 #include "cairo-list-private.h"
 
+/**
+ * cairo_list_entry:
+ * @ptr: the pointer to the #cairo_list_t member.
+ * @type: the type of the struct.
+ * @member: the name of the list_head within the struct.
+ *
+ * Return value: the pointer the struct containing the @member that @ptr points to.
+ **/
 #define cairo_list_entry(ptr, type, member) \
 	cairo_container_of(ptr, type, member)
 
@@ -48,25 +56,91 @@
 #define cairo_list_last_entry(ptr, type, member) \
 	cairo_list_entry((ptr)->prev, type, member)
 
-#define cairo_list_foreach(pos, head)			\
+/**
+ * cairo_list_foreach:
+ * @pos: a #cairo_list_t* to use as a loop variable.
+ * @head: the list.
+ *
+ * Iterate the list. @pos points to the #cairo_list_t member of the entry struct.
+ **/
+#define cairo_list_foreach(pos, head)                                   \
 	for (pos = (head)->next; pos != (head);	pos = pos->next)
 
+/**
+ * cairo_list_foreach_entry:
+ * @pos: a variable of type T * to use as a loop variable.
+ * @type: the type of the entry struct
+ * @head: the list
+ * @member: the name of the #cairo_list_t member of the entry
+ *
+ * Iterate the list of type T.
+ **/
 #define cairo_list_foreach_entry(pos, type, head, member)		\
 	for (pos = cairo_list_entry((head)->next, type, member);\
 	     &pos->member != (head);					\
 	     pos = cairo_list_entry(pos->member.next, type, member))
 
+/**
+ * cairo_list_foreach_entry_safe:
+ * @pos: a variable of type T * to use as a loop variable.
+ * @n: a variable of type T * that point to the next item after @pos.
+ * @type: the type of the entry struct
+ * @head: the list
+ * @member: the name of the #cairo_list_t member of the entry
+ *
+ * Iterate the list of type T. It is safe to remove items while
+ * iterating. @n is a temporary variable required to support safe
+ * iterating.
+ *
+ * <informalexample><programlisting>
+ *      struct foo {
+ *          int a;
+ *          cairo_list_t list;
+ *      }
+ *
+ *      struct foo linked_list;
+ *      cairo_list_init (&linked_list);
+ *      ... calls to cairo_list_add (entry, &linked_list);
+ *
+ *      struct foo *pos, *next;
+ *      cairo_list_foreach_entry_safe(pos, next, struct foo, &linked_list, list) {
+ *          printf("%d\n", pos->a);
+ *          cairo_list_del (pos);
+ *      }
+ * </programlisting></informalexample>
+ **/
 #define cairo_list_foreach_entry_safe(pos, n, type, head, member)	\
 	for (pos = cairo_list_entry ((head)->next, type, member),\
 	     n = cairo_list_entry (pos->member.next, type, member);\
 	     &pos->member != (head);					\
 	     pos = n, n = cairo_list_entry (n->member.next, type, member))
 
+/**
+ * cairo_list_foreach_entry:
+ * @pos: a variable of type T * to use as a loop variable.
+ * @type: the type of the entry struct
+ * @head: the list
+ * @member: the name of the #cairo_list_t member of the entry
+ *
+ * Iterate the list of type T in reverse direction.
+ **/
 #define cairo_list_foreach_entry_reverse(pos, type, head, member)	\
 	for (pos = cairo_list_entry((head)->prev, type, member);\
 	     &pos->member != (head);					\
 	     pos = cairo_list_entry(pos->member.prev, type, member))
 
+/**
+ * cairo_list_foreach_entry_safe:
+ * @pos: a variable of type T * to use as a loop variable.
+ * @n: a variable of type T * that point to the next item after @pos.
+ * @type: the type of the entry struct
+ * @head: the list
+ * @member: the name of the #cairo_list_t member of the entry
+ *
+ * Iterate the list of type T in reverse direction. It is safe to
+ * remove items while iterating. @n is a temporary variable required
+ * to support safe iterating.
+ **/
 #define cairo_list_foreach_entry_reverse_safe(pos, n, type, head, member)	\
 	for (pos = cairo_list_entry((head)->prev, type, member),\
 	     n = cairo_list_entry (pos->member.prev, type, member);\
@@ -101,6 +175,13 @@ cairo_list_validate_is_empty (const cairo_list_t *head)
 #define cairo_list_validate_is_empty(head)
 #endif
 
+/**
+ * cairo_list_init:
+ * @entry: list entry to initialize
+ *
+ * Initializes the list entry to point to itself. The result is an
+ * empty list.
+ **/
 static inline void
 cairo_list_init (cairo_list_t *entry)
 {
@@ -119,6 +200,13 @@ __cairo_list_add (cairo_list_t *entry,
     prev->next = entry;
 }
 
+/**
+ * cairo_list_add:
+ * @entry: new entry
+ * @head: linked list head
+ *
+ * Insert a @entry at the start of the list.
+ **/
 static inline void
 cairo_list_add (cairo_list_t *entry, cairo_list_t *head)
 {
@@ -128,6 +216,13 @@ cairo_list_add (cairo_list_t *entry, cairo_list_t *head)
     cairo_list_validate (head);
 }
 
+/**
+ * cairo_list_add_tail:
+ * @entry: new entry
+ * @head: linked list head
+ *
+ * Append a @entry to the end of the list.
+ **/
 static inline void
 cairo_list_add_tail (cairo_list_t *entry, cairo_list_t *head)
 {
@@ -150,6 +245,12 @@ _cairo_list_del (cairo_list_t *entry)
     __cairo_list_del (entry->prev, entry->next);
 }
 
+/**
+ * cairo_list_del:
+ * @entry: entry to remove
+ *
+ * Remove @entry from the list it is in.
+ **/
 static inline void
 cairo_list_del (cairo_list_t *entry)
 {
@@ -157,6 +258,13 @@ cairo_list_del (cairo_list_t *entry)
     cairo_list_init (entry);
 }
 
+/**
+ * cairo_list_move:
+ * @entry: entry to move
+ * @head: linked list to move @entry to
+ *
+ * Remove @entry from the list it is in and insert it at the start of @head list.
+ **/
 static inline void
 cairo_list_move (cairo_list_t *entry, cairo_list_t *head)
 {
@@ -166,6 +274,13 @@ cairo_list_move (cairo_list_t *entry, cairo_list_t *head)
     cairo_list_validate (head);
 }
 
+/**
+ * cairo_list_move_tail:
+ * @entry: entry tp move
+ * @head: linked list to move @entry to
+ *
+ * Remove @entry from the list it is in and append it to the end of @head list.
+ **/
 static inline void
 cairo_list_move_tail (cairo_list_t *entry, cairo_list_t *head)
 {
@@ -175,13 +290,27 @@ cairo_list_move_tail (cairo_list_t *entry, cairo_list_t *head)
     cairo_list_validate (head);
 }
 
+/**
+ * cairo_list_move_list:
+ * @old: List to move
+ * @new: List to move to. Should be empty,
+ *
+ * Move @old list to @new list, fixing up the references.
+ **/
 static inline void
-cairo_list_swap (cairo_list_t *entry, cairo_list_t *other)
+cairo_list_move_list (cairo_list_t *old, cairo_list_t *new)
 {
-    __cairo_list_add (entry, other->prev, other->next);
-    cairo_list_init (other);
+    __cairo_list_add (new, old->prev, old->next);
+    cairo_list_init (old);
 }
 
+/**
+ * cairo_list_is_first:
+ * @entry: entry to check
+ * @head: linked list
+ *
+ * Return %TRUE if @entry is the first item in @head.
+ **/
 static inline cairo_bool_t
 cairo_list_is_first (const cairo_list_t *entry,
 	             const cairo_list_t *head)
@@ -190,6 +319,13 @@ cairo_list_is_first (const cairo_list_t *entry,
     return entry->prev == head;
 }
 
+/**
+ * cairo_list_is_last:
+ * @entry: entry to check
+ * @head: linked list
+ *
+ * Return %TRUE if @entry is the last item in @head.
+ **/
 static inline cairo_bool_t
 cairo_list_is_last (const cairo_list_t *entry,
 	            const cairo_list_t *head)
@@ -198,6 +334,12 @@ cairo_list_is_last (const cairo_list_t *entry,
     return entry->next == head;
 }
 
+/**
+ * cairo_list_is_empty:
+ * @head: linked list
+ *
+ * Return %TRUE if @head is empty.
+ **/
 static inline cairo_bool_t
 cairo_list_is_empty (const cairo_list_t *head)
 {
@@ -205,11 +347,17 @@ cairo_list_is_empty (const cairo_list_t *head)
     return head->next == head;
 }
 
+/**
+ * cairo_list_is_singular:
+ * @head: linked list
+ *
+ * Return %TRUE if @head has only one entry.
+ **/
 static inline cairo_bool_t
 cairo_list_is_singular (const cairo_list_t *head)
 {
     cairo_list_validate (head);
-    return head->next == head || head->next == head->prev;
+    return head->next != head && head->next == head->prev;
 }
 
 #endif /* CAIRO_LIST_INLINE_H */
diff --git a/src/cairo-script-surface.c b/src/cairo-script-surface.c
index 058626321..c3f1b05bb 100644
--- a/src/cairo-script-surface.c
+++ b/src/cairo-script-surface.c
@@ -2239,8 +2239,7 @@ _cairo_script_surface_finish (void *abstract_surface)
 		    cairo_list_del (&surface->operand.link);
 		} else {
 		    link->operand.type = DEFERRED;
-		    cairo_list_swap (&link->operand.link,
-				     &surface->operand.link);
+		    cairo_list_move_list (&surface->operand.link, &link->operand.link);
 		    cairo_list_add (&link->link, &ctx->deferred);
 		}
 	    }


More information about the cairo-commit mailing list