[cairo-commit] 9 commits - doc/public src/cairo-analysis-surface.c src/cairo-backend-private.h src/cairo.c src/cairo-default-context.c src/cairo-device.c src/cairo-error-private.h src/cairo-gstate.c src/cairo-gstate-private.h src/cairo.h src/cairoint.h src/cairo-misc.c src/cairo-paginated-private.h src/cairo-paginated-surface.c src/cairo-pdf.h src/cairo-pdf-interchange.c src/cairo-pdf-operators.c src/cairo-pdf-operators-private.h src/cairo-pdf-surface.c src/cairo-pdf-surface-private.h src/cairo-ps-surface.c src/cairo-recording-surface.c src/cairo-recording-surface-private.h src/cairo-region.c src/cairo-spans.c src/cairo-surface-backend-private.h src/cairo-surface.c src/cairo-surface-wrapper.c src/cairo-surface-wrapper-private.h src/cairo-svg-surface.c src/cairo-tag-attributes.c src/cairo-tag-attributes-private.h src/cairo-tag-stack.c src/cairo-tag-stack-private.h src/Makefile.sources src/test-paginated-surface.c src/win32 test/Makefile.am test/Makefile.sources test/pdf-tagged-text.c

Adrian Johnson ajohnson at kemper.freedesktop.org
Sat Oct 1 13:25:54 UTC 2016


 doc/public/cairo-docs.xml                |    1 
 doc/public/cairo-sections.txt            |   15 
 src/Makefile.sources                     |    4 
 src/cairo-analysis-surface.c             |   37 
 src/cairo-backend-private.h              |    3 
 src/cairo-default-context.c              |   21 
 src/cairo-device.c                       |    1 
 src/cairo-error-private.h                |    1 
 src/cairo-gstate-private.h               |    9 
 src/cairo-gstate.c                       |   59 +
 src/cairo-misc.c                         |    2 
 src/cairo-paginated-private.h            |   20 
 src/cairo-paginated-surface.c            |  106 ++
 src/cairo-pdf-interchange.c              | 1465 +++++++++++++++++++++++++++++++
 src/cairo-pdf-operators-private.h        |    8 
 src/cairo-pdf-operators.c                |   37 
 src/cairo-pdf-surface-private.h          |  157 +++
 src/cairo-pdf-surface.c                  |  424 +++++++-
 src/cairo-pdf.h                          |   68 +
 src/cairo-ps-surface.c                   |    4 
 src/cairo-recording-surface-private.h    |   15 
 src/cairo-recording-surface.c            |  185 +++
 src/cairo-region.c                       |    1 
 src/cairo-spans.c                        |    2 
 src/cairo-surface-backend-private.h      |   12 
 src/cairo-surface-wrapper-private.h      |   11 
 src/cairo-surface-wrapper.c              |   47 
 src/cairo-surface.c                      |   37 
 src/cairo-svg-surface.c                  |    4 
 src/cairo-tag-attributes-private.h       |   73 +
 src/cairo-tag-attributes.c               |  570 ++++++++++++
 src/cairo-tag-stack-private.h            |  106 ++
 src/cairo-tag-stack.c                    |  279 +++++
 src/cairo.c                              |  311 ++++++
 src/cairo.h                              |   13 
 src/cairoint.h                           |   17 
 src/test-paginated-surface.c             |    4 
 src/win32/cairo-win32-printing-surface.c |    4 
 test/Makefile.am                         |    1 
 test/Makefile.sources                    |    3 
 test/pdf-tagged-text.c                   |  397 ++++++++
 41 files changed, 4459 insertions(+), 75 deletions(-)

New commits:
commit 23fd706bd1b83a00cdd3d666631e41e2ceab8a70
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 22:48:05 2016 +0930

    add test for PDF document interchange features such as tagged text and links

diff --git a/test/Makefile.am b/test/Makefile.am
index b2fcd27..f03c21b 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -323,6 +323,7 @@ CLEANFILES +=					\
 	ps-surface-source.out.ps		\
 	pdf-features.pdf			\
 	pdf-mime-data.out*			\
+	pdf-tagged-text.out*                    \
 	ps-features.ps				\
 	svg-clip.svg				\
 	svg-surface.svg				\
diff --git a/test/Makefile.sources b/test/Makefile.sources
index 479b2ca..5ead231 100644
--- a/test/Makefile.sources
+++ b/test/Makefile.sources
@@ -417,7 +417,8 @@ quartz_surface_test_sources = quartz-surface-source.c
 pdf_surface_test_sources = \
 	pdf-features.c \
 	pdf-mime-data.c \
-	pdf-surface-source.c
+	pdf-surface-source.c \
+	pdf-tagged-text.c
 
 ps_surface_test_sources = \
 	ps-eps.c \
diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c
new file mode 100644
index 0000000..91b917f
--- /dev/null
+++ b/test/pdf-tagged-text.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright © 2016 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>
+
+#include <cairo.h>
+#include <cairo-pdf.h>
+
+/* This test checks PDF with
+ * - tagged text
+ * - hyperlinks
+ * - document outline
+ * - metadata
+ * - thumbnails
+ * - page labels
+ */
+
+#define BASENAME "pdf-tagged-text.out"
+
+#define PAGE_WIDTH 595
+#define PAGE_HEIGHT 842
+
+#define HEADING1_SIZE 16
+#define HEADING2_SIZE 14
+#define HEADING3_SIZE 12
+#define TEXT_SIZE 12
+#define HEADING_HEIGHT 50
+#define MARGIN 50
+
+struct section {
+    int level;
+    const char *heading;
+    int num_paragraphs;
+};
+
+static const struct section contents[] = {
+    { 0, "Chapter 1",     1 },
+    { 1, "Section 1.1",   4 },
+    { 2, "Section 1.1.1", 3 },
+    { 1, "Section 1.2",   2 },
+    { 2, "Section 1.2.1", 4 },
+    { 2, "Section 1.2.2", 4 },
+    { 1, "Section 1.3",   2 },
+    { 0, "Chapter 2",     1 },
+    { 1, "Section 2.1",   4 },
+    { 2, "Section 2.1.1", 3 },
+    { 1, "Section 2.2",   2 },
+    { 2, "Section 2.2.1", 4 },
+    { 2, "Section 2.2.2", 4 },
+    { 1, "Section 2.3",   2 },
+    { 0, "Chapter 3",     1 },
+    { 1, "Section 3.1",   4 },
+    { 2, "Section 3.1.1", 3 },
+    { 1, "Section 3.2",   2 },
+    { 2, "Section 3.2.1", 4 },
+    { 2, "Section 3.2.2", 4 },
+    { 1, "Section 3.3",   2 },
+    { 0, NULL }
+};
+
+static const char *ipsum_lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing"
+    " elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
+    " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi"
+    " ut aliquip ex ea commodo consequat. Duis aute irure dolor in"
+    " reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla"
+    " pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa"
+    " qui officia deserunt mollit anim id est laborum.";
+
+static const char *roman_numerals[] = {
+    "i", "ii", "iii", "iv", "v"
+};
+
+#define MAX_PARAGRAPH_LINES 20
+
+static int paragraph_num_lines;
+static char *paragraph_text[MAX_PARAGRAPH_LINES];
+static double paragraph_height;
+static double line_height;
+static double y_pos;
+static int outline_parents[10];
+static int page_num;
+
+static void
+layout_paragraph (cairo_t *cr)
+{
+    char *text, *begin, *end, *prev_end;
+    cairo_text_extents_t text_extents;
+    cairo_font_extents_t font_extents;
+
+    cairo_select_font_face (cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+    cairo_set_font_size(cr, TEXT_SIZE);
+    cairo_font_extents (cr, &font_extents);
+    line_height = font_extents.height;
+    paragraph_height = 0;
+    paragraph_num_lines = 0;
+    text = strdup (ipsum_lorem);
+    begin = text;
+    end = text;
+    while (*begin) {
+	end = strchr(end, ' ');
+	if (!end) {
+	    paragraph_text[paragraph_num_lines++] = strdup (begin);
+	    break;
+	}
+	*end = 0;
+	cairo_text_extents (cr, begin, &text_extents);
+	*end = ' ';
+	if (text_extents.width + 2*MARGIN > PAGE_WIDTH) {
+	    paragraph_text[paragraph_num_lines++] = strndup (begin, prev_end - begin);
+	    begin = prev_end + 1;
+	}
+	prev_end = end;
+	end++;
+    }
+    paragraph_height = line_height * (paragraph_num_lines + 1);
+    free (text);
+}
+
+static void
+draw_paragraph (cairo_t *cr)
+{
+    int i;
+
+    cairo_select_font_face (cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+    cairo_set_font_size(cr, TEXT_SIZE);
+    cairo_tag_begin (cr, "P", NULL);
+    for (i = 0; i < paragraph_num_lines; i++) {
+	cairo_move_to (cr, MARGIN, y_pos);
+	cairo_show_text (cr, paragraph_text[i]);
+	y_pos += line_height;
+    }
+    cairo_tag_end (cr, "P");
+    y_pos += line_height;
+}
+
+static void
+draw_page_num (cairo_surface_t *surface, cairo_t *cr, const char *prefix, int num)
+{
+    char buf[100];
+
+    buf[0] = 0;
+    if (prefix)
+	strcat (buf, prefix);
+
+    if (num)
+	sprintf (buf + strlen(buf), "%d", num);
+
+    cairo_save (cr);
+    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+    cairo_set_font_size(cr, 12);
+    cairo_move_to (cr, PAGE_WIDTH/2, PAGE_HEIGHT - MARGIN);
+    cairo_show_text (cr, buf);
+    cairo_restore (cr);
+    cairo_pdf_surface_set_page_label (surface, buf);
+}
+
+static void
+draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
+{
+    char buf[100];
+
+    sprintf(buf, "dest='%s'", section->heading);
+    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+    switch (section->level) {
+	case 0:
+	    cairo_set_font_size(cr, HEADING1_SIZE);
+	    break;
+	case 1:
+	    cairo_set_font_size(cr, HEADING2_SIZE);
+	    break;
+	case 2:
+	    cairo_set_font_size(cr, HEADING3_SIZE);
+	    break;
+    }
+
+    if (y_pos + HEADING_HEIGHT + MARGIN > PAGE_HEIGHT) {
+	cairo_show_page (cr);
+	draw_page_num (surface, cr, roman_numerals[page_num++], 0);
+	y_pos = MARGIN;
+    }
+    cairo_move_to (cr, MARGIN, y_pos);
+    cairo_save (cr);
+    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_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;
+}
+
+static void
+draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
+{
+    int flags, i;
+    char buf[100];
+
+    cairo_tag_begin (cr, "Sect", NULL);
+    sprintf(buf, "name='%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);
+	draw_page_num (surface, cr, NULL, page_num++);
+	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_show_text (cr, section->heading);
+	cairo_tag_end (cr, CAIRO_TAG_DEST);
+	cairo_tag_end (cr, "H1");
+	y_pos = MARGIN + HEADING_HEIGHT;
+	flags = CAIRO_BOOKMARK_FLAG_BOLD | CAIRO_BOOKMARK_FLAG_OPEN;
+	outline_parents[0] = cairo_pdf_surface_add_outline (surface,
+							    CAIRO_PDF_OUTLINE_ROOT,
+							    section->heading,
+							    section->heading,
+							    flags);
+    } else {
+	if (section->level == 1) {
+	    cairo_set_font_size(cr, HEADING2_SIZE);
+	    flags = 0;
+	} else {
+	    cairo_set_font_size(cr, HEADING3_SIZE);
+	    flags = CAIRO_BOOKMARK_FLAG_ITALIC;
+	}
+
+	if (y_pos + HEADING_HEIGHT + paragraph_height + MARGIN > PAGE_HEIGHT) {
+	    cairo_show_page (cr);
+	    draw_page_num (surface, cr, NULL, page_num++);
+	    y_pos = MARGIN;
+	}
+	cairo_move_to (cr, MARGIN, y_pos);
+	if (section->level == 1)
+	    cairo_tag_begin (cr, "H2", NULL);
+	else
+	    cairo_tag_begin (cr, "H3", NULL);
+	cairo_tag_begin (cr, CAIRO_TAG_DEST, buf);
+	cairo_show_text (cr, section->heading);
+	cairo_tag_end (cr, CAIRO_TAG_DEST);
+	if (section->level == 1)
+	    cairo_tag_end (cr, "H2");
+	else
+	    cairo_tag_end (cr, "H3");
+	y_pos += HEADING_HEIGHT;
+	outline_parents[section->level] = cairo_pdf_surface_add_outline (surface,
+									 outline_parents[section->level - 1],
+									 section->heading,
+									 section->heading,
+									 flags);
+    }
+
+    for (i = 0; i < section->num_paragraphs; i++) {
+	if (y_pos + paragraph_height + MARGIN > PAGE_HEIGHT) {
+	    cairo_show_page (cr);
+	    draw_page_num (surface, cr, NULL, page_num++);
+		y_pos = MARGIN;
+	}
+	draw_paragraph (cr);
+    }
+    cairo_tag_end (cr, "Sect");
+}
+
+static void
+draw_cover (cairo_surface_t *surface, cairo_t *cr)
+{
+    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
+    cairo_set_font_size(cr, 16);
+    cairo_move_to (cr, PAGE_WIDTH/3, PAGE_HEIGHT/2);
+    cairo_tag_begin (cr, "Span", NULL);
+    cairo_show_text (cr, "PDF Features Test");
+    cairo_tag_end (cr, "Span");
+
+    draw_page_num (surface, cr, "cover", 0);
+}
+
+static void
+create_document (cairo_surface_t *surface, cairo_t *cr)
+{
+    layout_paragraph (cr);
+
+    cairo_pdf_surface_set_thumbnail_size (surface, PAGE_WIDTH/10, PAGE_HEIGHT/10);
+
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_TITLE, "PDF Features Test");
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_AUTHOR, "cairo test suite");
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_SUBJECT, "cairo test");
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_KEYWORDS,
+				    "tags, links, outline, page labels, metadata, thumbnails");
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATOR, "pdf-features");
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATE_DATE, "2016-01-01T12:34:56+10:30");
+    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_MOD_DATE, "2016-06-21T05:43:21Z");
+
+    cairo_tag_begin (cr, "Document", NULL);
+
+    draw_cover (surface, cr);
+    cairo_show_page (cr);
+
+    page_num = 0;
+    draw_page_num (surface, cr, roman_numerals[page_num++], 0);
+    y_pos = MARGIN;
+
+    cairo_pdf_surface_add_outline (surface,
+				   CAIRO_PDF_OUTLINE_ROOT,
+				   "Contents", "TOC", CAIRO_BOOKMARK_FLAG_BOLD);
+
+    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='TOC'");
+    cairo_tag_begin (cr, "TOC", NULL);
+    const struct section *sect = contents;
+    while (sect->heading) {
+	draw_contents (surface, cr, sect);
+	sect++;
+    }
+    cairo_tag_end (cr, "TOC");
+    cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+    page_num = 1;
+    sect = contents;
+    while (sect->heading) {
+	draw_section (surface, cr, sect);
+	sect++;
+    }
+
+    cairo_tag_end (cr, "Document");
+}
+
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+    cairo_surface_t *surface;
+    cairo_t *cr;
+    cairo_status_t status, status2;
+    char *filename;
+    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
+
+    if (! cairo_test_is_target_enabled (ctx, "pdf"))
+	return CAIRO_TEST_UNTESTED;
+
+    xasprintf (&filename, "%s/%s.pdf", path, BASENAME);
+    surface = cairo_pdf_surface_create (filename, PAGE_WIDTH, PAGE_HEIGHT);
+
+    cr = cairo_create (surface);
+    create_document (surface, 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",
+			filename, cairo_status_to_string (status));
+	return CAIRO_TEST_FAILURE;
+    }
+
+    free (filename);
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+CAIRO_TEST (pdf_tagged_text,
+	    "Check tagged text, hyperlinks and PDF document features",
+	    "pdf", /* keywords */
+	    NULL, /* requirements */
+	    0, 0,
+	    preamble, NULL)
commit 2d6a0f5d16d61c8f4236760c71061a0c4c3a199c
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 22:46:49 2016 +0930

    pdf: thumbnail API

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index 51bf48c..7b04ae7 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -81,6 +81,7 @@ cairo_pdf_surface_set_size
 cairo_pdf_surface_add_outline
 cairo_pdf_surface_set_metadata
 cairo_pdf_surface_set_page_label
+cairo_pdf_surface_set_thumbnail_size
 </SECTION>
 
 <SECTION>
diff --git a/src/cairo-paginated-private.h b/src/cairo-paginated-private.h
index 29eefc7..b85a5db 100644
--- a/src/cairo-paginated-private.h
+++ b/src/cairo-paginated-private.h
@@ -77,7 +77,23 @@ struct _cairo_paginated_surface_backend {
 				     cairo_bool_t    fallbacks_required);
 
     cairo_bool_t
-    (*supports_fine_grained_fallbacks) (void		    *surface);
+    (*supports_fine_grained_fallbacks) (void	    *surface);
+
+    /* Optional. Indicates whether the page requires a thumbnail image to be
+     * supplied. If a thumbnail is required, set width, heigh to size required
+     * and return TRUE.
+     */
+    cairo_bool_t
+    (*requires_thumbnail_image) (void	*surface,
+				 int    *width,
+				 int    *height);
+
+    /* If thumbbail image requested, this function will be called before
+     * _show_page().
+     */
+    cairo_warn cairo_int_status_t
+    (*set_thumbnail_image) (void	          *surface,
+			    cairo_image_surface_t *image);
 };
 
 /* A #cairo_paginated_surface_t provides a very convenient wrapper that
diff --git a/src/cairo-paginated-surface.c b/src/cairo-paginated-surface.c
index 749f0de..e9454d4 100644
--- a/src/cairo-paginated-surface.c
+++ b/src/cairo-paginated-surface.c
@@ -291,6 +291,63 @@ _cairo_paginated_surface_release_source_image (void	  *abstract_surface,
 }
 
 static cairo_int_status_t
+_paint_thumbnail_image (cairo_paginated_surface_t *surface,
+			int                        width,
+			int                        height)
+{
+    cairo_surface_pattern_t pattern;
+    cairo_rectangle_int_t extents;
+    double x_scale;
+    double y_scale;
+    cairo_surface_t *image = NULL;
+    cairo_surface_t *opaque = NULL;
+    cairo_status_t status = CAIRO_STATUS_SUCCESS;
+
+    _cairo_surface_get_extents (surface->target, &extents);
+    x_scale = (double)width / extents.width;
+    y_scale = (double)height / extents.height;
+
+    image = _cairo_paginated_surface_create_image_surface (surface, width, height);
+    cairo_surface_set_device_scale (image, x_scale, y_scale);
+    cairo_surface_set_device_offset (image, -extents.x*x_scale, -extents.y*y_scale);
+    status = _cairo_recording_surface_replay (surface->recording_surface, image);
+    if (unlikely (status))
+	goto cleanup;
+
+    /* flatten transparency */
+
+    opaque = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
+    if (unlikely (opaque->status)) {
+	status = opaque->status;
+	goto cleanup;
+    }
+
+    status = _cairo_surface_paint (opaque,
+				   CAIRO_OPERATOR_SOURCE,
+				   &_cairo_pattern_white.base,
+				   NULL);
+    if (unlikely (status))
+	goto cleanup;
+
+    _cairo_pattern_init_for_surface (&pattern, image);
+    pattern.base.filter = CAIRO_FILTER_NEAREST;
+    status = _cairo_surface_paint (opaque, CAIRO_OPERATOR_OVER, &pattern.base, NULL);
+    _cairo_pattern_fini (&pattern.base);
+    if (unlikely (status))
+	goto cleanup;
+
+    status = surface->backend->set_thumbnail_image (surface->target, (cairo_image_surface_t *)opaque);
+
+  cleanup:
+    if (image)
+	cairo_surface_destroy (image);
+    if (opaque)
+	cairo_surface_destroy (opaque);
+
+    return status;
+}
+
+static cairo_int_status_t
 _paint_fallback_image (cairo_paginated_surface_t *surface,
 		       cairo_rectangle_int_t     *rect)
 {
@@ -460,6 +517,13 @@ _paint_page (cairo_paginated_surface_t *surface)
 	}
     }
 
+    if (surface->backend->requires_thumbnail_image) {
+	int width, height;
+
+	if (surface->backend->requires_thumbnail_image (surface->target, &width, &height))
+	    _paint_thumbnail_image (surface, width, height);
+    }
+
   FAIL:
     cairo_surface_destroy (analysis);
 
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 960e07c..c1fd11a 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -330,6 +330,10 @@ struct _cairo_pdf_surface {
     cairo_pdf_resource_t docinfo_res;
     cairo_pdf_resource_t page_labels_res;
 
+    int thumbnail_width;
+    int thumbnail_height;
+    cairo_image_surface_t *thumbnail_image;
+
     cairo_surface_t *paginated_surface;
 };
 
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 9000554..e60d0b9 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -453,6 +453,9 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->names_dict_res.id = 0;
     surface->docinfo_res.id = 0;
     surface->page_labels_res.id = 0;
+    surface->thumbnail_width = 0;
+    surface->thumbnail_height = 0;
+    surface->thumbnail_image = NULL;
 
     surface->paginated_surface =  _cairo_paginated_surface_create (
 	                                  &surface->base,
@@ -832,6 +835,32 @@ cairo_pdf_surface_set_page_label (cairo_surface_t *surface,
     pdf_surface->current_page_label = utf8 ? strdup (utf8) : NULL;
 }
 
+/**
+ * cairo_pdf_surface_set_thumbnail_size:
+ * @surface: a PDF #cairo_surface_t
+ * @width: Thumbnail width.
+ * @height: Thumbnail height
+ *
+ * Set the thumbnail image size for the current and all subsequent
+ * pages. Setting a width or height of 0 disables thumbnails for the
+ * current and subsequent pages.
+ *
+ * Since: 1.16
+ **/
+void
+cairo_pdf_surface_set_thumbnail_size (cairo_surface_t *surface,
+				      int              width,
+				      int              height)
+{
+    cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */
+
+    if (! _extract_pdf_surface (surface, &pdf_surface))
+	return;
+
+    pdf_surface->thumbnail_width = width;
+    pdf_surface->thumbnail_height = height;
+}
+
 static void
 _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
 {
@@ -862,6 +891,9 @@ _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
     _cairo_array_truncate (&surface->smask_groups, 0);
     _cairo_array_truncate (&surface->knockout_group, 0);
     _cairo_array_truncate (&surface->page_annots, 0);
+
+    cairo_surface_destroy (&surface->thumbnail_image->base);
+    surface->thumbnail_image = NULL;
 }
 
 static void
@@ -2365,6 +2397,33 @@ _cairo_pdf_surface_supports_fine_grained_fallbacks (void *abstract_surface)
     return TRUE;
 }
 
+static cairo_bool_t
+_cairo_pdf_surface_requires_thumbnail_image (void *abstract_surface,
+					     int  *width,
+					     int  *height)
+{
+    cairo_pdf_surface_t *surface = abstract_surface;
+
+    if (surface->thumbnail_width > 0 && surface->thumbnail_height > 0) {
+	*width = surface->thumbnail_width;
+	*height = surface->thumbnail_height;
+	return TRUE;
+    }
+
+    return FALSE;
+}
+
+static cairo_int_status_t
+_cairo_pdf_surface_set_thumbnail_image (void                  *abstract_surface,
+					cairo_image_surface_t *image)
+{
+    cairo_pdf_surface_t *surface = abstract_surface;
+
+    surface->thumbnail_image = 	(cairo_image_surface_t *)cairo_surface_reference(&image->base);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static cairo_int_status_t
 _cairo_pdf_surface_add_padded_image_surface (cairo_pdf_surface_t          *surface,
 					     const cairo_pattern_t        *source,
@@ -6590,7 +6649,7 @@ _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;
+    cairo_pdf_resource_t knockout, res, thumbnail_res;
     cairo_pdf_resource_t *page;
     cairo_int_status_t status;
     unsigned int i, len, page_num, num_annots;
@@ -6651,6 +6710,16 @@ _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;
+	_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);
     _cairo_pdf_surface_update_object (surface, *page);
@@ -6693,6 +6762,12 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 	_cairo_output_stream_printf (surface->output, "]\n");
     }
 
+    if (thumbnail_res.id) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /Thumb %d 0 R\n",
+				     thumbnail_res.id);
+    }
+
     _cairo_output_stream_printf (surface->output,
 				 ">>\n"
 				 "endobj\n");
@@ -8209,4 +8284,6 @@ cairo_pdf_surface_paginated_backend = {
     NULL, /* set_bounding_box */
     _cairo_pdf_surface_has_fallback_images,
     _cairo_pdf_surface_supports_fine_grained_fallbacks,
+    _cairo_pdf_surface_requires_thumbnail_image,
+    _cairo_pdf_surface_set_thumbnail_image,
 };
diff --git a/src/cairo-pdf.h b/src/cairo-pdf.h
index 9bf69fe..62bf41f 100644
--- a/src/cairo-pdf.h
+++ b/src/cairo-pdf.h
@@ -148,6 +148,11 @@ void
 cairo_pdf_surface_set_page_label (cairo_surface_t *surface,
                                   const char      *utf8);
 
+void
+cairo_pdf_surface_set_thumbnail_size (cairo_surface_t *surface,
+				      int              width,
+				      int              height);
+
 CAIRO_END_DECLS
 
 #else  /* CAIRO_HAS_PDF_SURFACE */
commit 26b3f83ff652a284b79557ec1555b398c566a7eb
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 22:44:22 2016 +0930

    pdf: page label API

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index 200c9b2..51bf48c 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -80,6 +80,7 @@ cairo_pdf_version_to_string
 cairo_pdf_surface_set_size
 cairo_pdf_surface_add_outline
 cairo_pdf_surface_set_metadata
+cairo_pdf_surface_set_page_label
 </SECTION>
 
 <SECTION>
diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
index a84f0cc..622f3b3 100644
--- a/src/cairo-pdf-interchange.c
+++ b/src/cairo-pdf-interchange.c
@@ -599,6 +599,117 @@ cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
     return status;
 }
 
+/*
+ * Split a page label into a text prefix and numeric suffix. Leading '0's are
+ * included in the prefix. eg
+ *  "3"     => NULL,    3
+ *  "cover" => "cover", 0
+ *  "A-2"   => "A-",    2
+ *  "A-002" => "A-00",  2
+ */
+static char *
+split_label (const char* label, int *num)
+{
+    int len, i;
+
+    *num = 0;
+    len = strlen (label);
+    if (len == 0)
+	return NULL;
+
+    i = len;
+    while (i > 0 && _cairo_isdigit (label[i-1]))
+	   i--;
+
+    while (i < len && label[i] == '0')
+	i++;
+
+    if (i < len)
+	sscanf (label + i, "%d", num);
+
+    if (i > 0)
+	return strndup (label, i);
+
+    return NULL;
+}
+
+/* strcmp that handles NULL arguments */
+static cairo_bool_t
+strcmp_null (const char *s1, const char *s2)
+{
+    if (s1 && s2)
+	return strcmp (s1, s2) == 0;
+
+    if (!s1 && !s2)
+	return TRUE;
+
+    return FALSE;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
+{
+    int num_elems, i;
+    char *label;
+    char *prefix;
+    char *prev_prefix;
+    int num, prev_num;
+    cairo_int_status_t status;
+
+    num_elems = _cairo_array_num_elements (&surface->page_labels);
+    if (num_elems > 0) {
+	surface->page_labels_res = _cairo_pdf_surface_new_object (surface);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n"
+				     "<< /Nums [\n",
+				     surface->page_labels_res.id);
+	prefix = NULL;
+	prev_prefix = NULL;
+	num = 0;
+	prev_num = 0;
+	for (i = 0; i < num_elems; i++) {
+	    _cairo_array_copy_element (&surface->page_labels, i, &label);
+	    if (label) {
+		prefix = split_label (label, &num);
+	    } else {
+		prefix = NULL;
+		num = i + 1;
+	    }
+
+	    if (!strcmp_null (prefix, prev_prefix) || num != prev_num + 1) {
+		_cairo_output_stream_printf (surface->output,  "   %d << ", i);
+
+		if (num)
+		    _cairo_output_stream_printf (surface->output,  "/S /D /St %d ", num);
+
+		if (prefix) {
+		    char *s;
+		    status = _cairo_utf8_to_pdf_string (prefix, &s);
+		    if (unlikely (status))
+			return status;
+
+		    _cairo_output_stream_printf (surface->output,  "/P %s ", s);
+		    free (s);
+		}
+
+		_cairo_output_stream_printf (surface->output,  ">>\n");
+	    }
+	    free (prev_prefix);
+	    prev_prefix = prefix;
+	    prefix = NULL;
+	    prev_num = num;
+	}
+	free (prefix);
+	free (prev_prefix);
+	_cairo_output_stream_printf (surface->output,
+				     "  ]\n"
+				     ">>\n"
+				     "endobj\n");
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static void
 _collect_dest (void *entry, void *closure)
 {
@@ -1077,6 +1188,10 @@ _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface)
     if (unlikely (status))
 	return status;
 
+    status = cairo_pdf_interchange_write_page_labels (surface);
+    if (unlikely (status))
+	return status;
+
     status = cairo_pdf_interchange_write_names_dict (surface);
     if (unlikely (status))
 	return status;
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index a3d45d0..960e07c 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -323,9 +323,12 @@ struct _cairo_pdf_surface {
     int page_parent_tree; /* -1 if not used */
     cairo_array_t page_annots;
     cairo_bool_t tagged;
+    char *current_page_label;
+    cairo_array_t page_labels;
     cairo_pdf_resource_t outlines_dict_res;
     cairo_pdf_resource_t names_dict_res;
     cairo_pdf_resource_t docinfo_res;
+    cairo_pdf_resource_t page_labels_res;
 
     cairo_surface_t *paginated_surface;
 };
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 3a125e7..9000554 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -447,8 +447,12 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->page_parent_tree = -1;
     _cairo_array_init (&surface->page_annots, sizeof (cairo_pdf_resource_t));
     surface->tagged = FALSE;
+    surface->current_page_label = NULL;
+    _cairo_array_init (&surface->page_labels, sizeof (char *));
     surface->outlines_dict_res.id = 0;
     surface->names_dict_res.id = 0;
+    surface->docinfo_res.id = 0;
+    surface->page_labels_res.id = 0;
 
     surface->paginated_surface =  _cairo_paginated_surface_create (
 	                                  &surface->base,
@@ -806,6 +810,28 @@ cairo_pdf_surface_set_metadata (cairo_surface_t      *surface,
 	status = _cairo_surface_set_error (surface, status);
 }
 
+/**
+ * cairo_pdf_surface_set_page_label:
+ * @surface: a PDF #cairo_surface_t
+ * @utf8: The page label.
+ *
+ * Set page label for the current page.
+ *
+ * Since: 1.16
+ **/
+void
+cairo_pdf_surface_set_page_label (cairo_surface_t *surface,
+                                  const char      *utf8)
+{
+    cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */
+
+    if (! _extract_pdf_surface (surface, &pdf_surface))
+	return;
+
+    free (pdf_surface->current_page_label);
+    pdf_surface->current_page_label = utf8 ? strdup (utf8) : NULL;
+}
+
 static void
 _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
 {
@@ -2158,6 +2184,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     cairo_status_t status, status2;
     int size, i;
     cairo_pdf_jbig2_global_t *global;
+    char *label;
 
     status = surface->base.status;
     if (status == CAIRO_STATUS_SUCCESS)
@@ -2255,6 +2282,13 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     }
     _cairo_array_fini (&surface->jbig2_global);
 
+    size = _cairo_array_num_elements (&surface->page_labels);
+    for (i = 0; i < size; i++) {
+	_cairo_array_copy_element (&surface->page_labels, i, &label);
+	free (label);
+    }
+    _cairo_array_fini (&surface->page_labels);
+
     _cairo_array_truncate (&surface->page_surfaces, 0);
 
     _cairo_surface_clipper_reset (&surface->clipper);
@@ -4783,6 +4817,9 @@ _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_labels, &surface->current_page_label);
+    surface->current_page_label = NULL;
+
     status = _cairo_pdf_interchange_end_page_content (surface);
     if (unlikely (status))
 	return status;
@@ -6173,6 +6210,12 @@ _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface)
 				     surface->outlines_dict_res.id);
     }
 
+    if (surface->page_labels_res.id != 0) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /PageLabels %d 0 R\n",
+				     surface->page_labels_res.id);
+    }
+
     if (surface->names_dict_res.id != 0) {
 	_cairo_output_stream_printf (surface->output,
 				     "   /Names %d 0 R\n",
diff --git a/src/cairo-pdf.h b/src/cairo-pdf.h
index 0589ba5..9bf69fe 100644
--- a/src/cairo-pdf.h
+++ b/src/cairo-pdf.h
@@ -144,6 +144,10 @@ cairo_pdf_surface_set_metadata (cairo_surface_t	     *surface,
 				cairo_pdf_metadata_t  metadata,
                                 const char           *utf8);
 
+void
+cairo_pdf_surface_set_page_label (cairo_surface_t *surface,
+                                  const char      *utf8);
+
 CAIRO_END_DECLS
 
 #else  /* CAIRO_HAS_PDF_SURFACE */
commit 5bfadd5530623d3b12fadf8cd22f95cec4132b65
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 22:41:36 2016 +0930

    pdf: metadata API

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index 7446415..200c9b2 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -70,6 +70,7 @@ cairo_image_surface_get_stride
 CAIRO_HAS_PDF_SURFACE
 CAIRO_PDF_OUTLINE_ROOT
 cairo_pdf_outline_flags_t
+cairo_pdf_metadata_t
 cairo_pdf_surface_create
 cairo_pdf_surface_create_for_stream
 cairo_pdf_surface_restrict_to_version
@@ -78,6 +79,7 @@ cairo_pdf_get_versions
 cairo_pdf_version_to_string
 cairo_pdf_surface_set_size
 cairo_pdf_surface_add_outline
+cairo_pdf_surface_set_metadata
 </SECTION>
 
 <SECTION>
diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
index 38c5bac..a84f0cc 100644
--- a/src/cairo-pdf-interchange.c
+++ b/src/cairo-pdf-interchange.c
@@ -699,6 +699,49 @@ cairo_pdf_interchange_write_names_dict (cairo_pdf_surface_t *surface)
     return CAIRO_STATUS_SUCCESS;
 }
 
+static cairo_int_status_t
+cairo_pdf_interchange_write_docinfo (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    surface->docinfo_res = _cairo_pdf_surface_new_object (surface);
+    if (surface->docinfo_res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Producer (cairo %s (http://cairographics.org))\n",
+				 surface->docinfo_res.id,
+				 cairo_version_string ());
+
+    if (ic->docinfo.title)
+	_cairo_output_stream_printf (surface->output, "   /Title %s\n", ic->docinfo.title);
+
+    if (ic->docinfo.author)
+	_cairo_output_stream_printf (surface->output, "   /Author %s\n", ic->docinfo.author);
+
+    if (ic->docinfo.subject)
+	_cairo_output_stream_printf (surface->output, "   /Subject %s\n", ic->docinfo.subject);
+
+    if (ic->docinfo.keywords)
+	_cairo_output_stream_printf (surface->output, "   /Keywords %s\n", ic->docinfo.keywords);
+
+    if (ic->docinfo.creator)
+	_cairo_output_stream_printf (surface->output, "   /Creator %s\n", ic->docinfo.creator);
+
+    if (ic->docinfo.create_date)
+	_cairo_output_stream_printf (surface->output, "   /CreationDate %s\n", ic->docinfo.create_date);
+
+    if (ic->docinfo.mod_date)
+	_cairo_output_stream_printf (surface->output, "   /ModDate %s\n", ic->docinfo.mod_date);
+
+    _cairo_output_stream_printf (surface->output,
+				 ">>\n"
+				 "endobj\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static void
 init_named_dest_key (cairo_pdf_named_dest_t *dest)
 {
@@ -1035,6 +1078,10 @@ _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface)
 	return status;
 
     status = cairo_pdf_interchange_write_names_dict (surface);
+    if (unlikely (status))
+	return status;
+
+    status = cairo_pdf_interchange_write_docinfo (surface);
 
     return status;
 }
@@ -1075,6 +1122,7 @@ _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
     if (unlikely (outline_root == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
+    memset (&ic->docinfo, 0, sizeof (ic->docinfo));
     status = _cairo_array_append (&ic->outline, &outline_root);
 
     return status;
@@ -1103,6 +1151,13 @@ _cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
 	free (outline);
     }
     _cairo_array_fini (&ic->outline);
+    free (ic->docinfo.title);
+    free (ic->docinfo.author);
+    free (ic->docinfo.subject);
+    free (ic->docinfo.keywords);
+    free (ic->docinfo.creator);
+    free (ic->docinfo.create_date);
+    free (ic->docinfo.mod_date);
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -1171,3 +1226,125 @@ _cairo_pdf_interchange_add_outline (cairo_pdf_surface_t        *surface,
 
     return CAIRO_STATUS_SUCCESS;
 }
+
+/*
+ * Date must be in the following format:
+ *
+ *     YYYY-MM-DDThh:mm:ss[Z+-]hh:mm
+ *
+ * Only the year is required. If a field is included all preceding
+ * fields must be included.
+ */
+static char *
+iso8601_to_pdf_date_string (const char *iso)
+{
+    char buf[40];
+    const char *p;
+    int i;
+
+    /* Check that utf8 contains only the characters "0123456789-T:Z+" */
+    p = iso;
+    while (*p) {
+       if (!_cairo_isdigit (*p) && *p != '-' && *p != 'T' &&
+           *p != ':' && *p != 'Z' && *p != '+')
+           return NULL;
+       p++;
+    }
+
+    p = iso;
+    strcpy (buf, "(");
+
+   /* YYYY (required) */
+    if (strlen (p) < 4)
+       return NULL;
+
+    strncat (buf, p, 4);
+    p += 4;
+
+    /* -MM, -DD, Thh, :mm, :ss */
+    for (i = 0; i < 5; i++) {
+	if (strlen (p) < 3)
+	    goto finish;
+
+	strncat (buf, p + 1, 2);
+	p += 3;
+    }
+
+    /* Z, +, - */
+    if (strlen (p) < 1)
+       goto finish;
+    strncat (buf, p, 1);
+    p += 1;
+
+    /* hh */
+    if (strlen (p) < 2)
+	goto finish;
+
+    strncat (buf, p, 2);
+    strcat (buf, "'");
+    p += 2;
+
+    /* :mm */
+    if (strlen (p) < 3)
+	goto finish;
+
+    strncat (buf, p + 1, 3);
+
+  finish:
+    strcat (buf, ")");
+    return strdup (buf);
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_set_metadata (cairo_pdf_surface_t  *surface,
+				     cairo_pdf_metadata_t  metadata,
+				     const char           *utf8)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_status_t status;
+    char *s = NULL;
+
+    if (utf8) {
+	if (metadata == CAIRO_PDF_METADATA_CREATE_DATE ||
+	    metadata == CAIRO_PDF_METADATA_MOD_DATE) {
+	    s = iso8601_to_pdf_date_string (utf8);
+	} else {
+	    status = _cairo_utf8_to_pdf_string (utf8, &s);
+	    if (unlikely (status))
+		return status;
+	}
+    }
+
+    switch (metadata) {
+	case CAIRO_PDF_METADATA_TITLE:
+	    free (ic->docinfo.title);
+	    ic->docinfo.title = s;
+	    break;
+	case CAIRO_PDF_METADATA_AUTHOR:
+	    free (ic->docinfo.author);
+	    ic->docinfo.author = s;
+	    break;
+	case CAIRO_PDF_METADATA_SUBJECT:
+	    free (ic->docinfo.subject);
+	    ic->docinfo.subject = s;
+	    break;
+	case CAIRO_PDF_METADATA_KEYWORDS:
+	    free (ic->docinfo.keywords);
+	    ic->docinfo.keywords = s;
+	    break;
+	case CAIRO_PDF_METADATA_CREATOR:
+	    free (ic->docinfo.creator);
+	    ic->docinfo.creator = s;
+	    break;
+	case CAIRO_PDF_METADATA_CREATE_DATE:
+	    free (ic->docinfo.create_date);
+	    ic->docinfo.create_date = s;
+	    break;
+	case CAIRO_PDF_METADATA_MOD_DATE:
+	    free (ic->docinfo.mod_date);
+	    ic->docinfo.mod_date = s;
+	    break;
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 010b127..a3d45d0 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -206,6 +206,16 @@ typedef struct _cairo_pdf_outline_entry {
     int count;
 } cairo_pdf_outline_entry_t;
 
+struct docinfo {
+    char *title;
+    char *author;
+    char *subject;
+    char *keywords;
+    char *creator;
+    char *create_date;
+    char *mod_date;
+};
+
 typedef struct _cairo_pdf_interchange {
     cairo_tag_stack_t analysis_tag_stack;
     cairo_tag_stack_t render_tag_stack;
@@ -225,6 +235,7 @@ typedef struct _cairo_pdf_interchange {
     cairo_pdf_resource_t dests_res;
     int annot_page;
     cairo_array_t outline; /* array of pointers to cairo_pdf_outline_entry_t; */
+    struct docinfo docinfo;
 
 } cairo_pdf_interchange_t;
 
@@ -314,6 +325,7 @@ struct _cairo_pdf_surface {
     cairo_bool_t tagged;
     cairo_pdf_resource_t outlines_dict_res;
     cairo_pdf_resource_t names_dict_res;
+    cairo_pdf_resource_t docinfo_res;
 
     cairo_surface_t *paginated_surface;
 };
@@ -367,4 +379,9 @@ _cairo_pdf_interchange_add_outline (cairo_pdf_surface_t        *surface,
 				    cairo_pdf_outline_flags_t   flags,
 				    int                        *id);
 
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_set_metadata (cairo_pdf_surface_t  *surface,
+				     cairo_pdf_metadata_t  metadata,
+				     const char           *utf8);
+
 #endif /* CAIRO_PDF_SURFACE_PRIVATE_H */
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 98c780b..3a125e7 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -239,9 +239,6 @@ static void
 _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface);
 
 static cairo_pdf_resource_t
-_cairo_pdf_surface_write_info (cairo_pdf_surface_t *surface);
-
-static cairo_pdf_resource_t
 _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface);
 
 static long
@@ -773,6 +770,42 @@ cairo_pdf_surface_add_outline (cairo_surface_t	         *surface,
     return id;
 }
 
+/**
+ * cairo_pdf_surface_set_metadata:
+ * @surface: a PDF #cairo_surface_t
+ * @metadata: The metadata item to set.
+ * @utf8: metadata value
+ *
+ * Set document metadata. The %CAIRO_PDF_METADATA_CREATE_DATE and
+ * %CAIRO_PDF_METADATA_MOD_DATE values must be in ISO-8601 format:
+ * YYYY-MM-DDThh:mm:ss. An optional timezone of the form "[+/-]hh:mm"
+ * or "Z" for UTC time can be appended. All other metadata values can be any UTF-8
+ * string.
+ *
+ * For example:
+ * <informalexample><programlisting>
+ * cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_TITLE, "My Document");
+ * cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATE_DATE, "2015-12-31T23:59+02:00");
+ * </programlisting></informalexample>
+ *
+ * Since: 1.16
+ **/
+void
+cairo_pdf_surface_set_metadata (cairo_surface_t      *surface,
+				cairo_pdf_metadata_t  metadata,
+                                const char           *utf8)
+{
+    cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */
+    cairo_status_t status;
+
+    if (! _extract_pdf_surface (surface, &pdf_surface))
+	return;
+
+    status = _cairo_pdf_interchange_set_metadata (pdf_surface, metadata, utf8);
+    if (status)
+	status = _cairo_surface_set_error (surface, status);
+}
+
 static void
 _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
 {
@@ -2121,7 +2154,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
 {
     cairo_pdf_surface_t *surface = abstract_surface;
     long offset;
-    cairo_pdf_resource_t info, catalog;
+    cairo_pdf_resource_t catalog;
     cairo_status_t status, status2;
     int size, i;
     cairo_pdf_jbig2_global_t *global;
@@ -2136,10 +2169,6 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     if (unlikely (status))
 	return status;
 
-    info = _cairo_pdf_surface_write_info (surface);
-    if (info.id == 0 && status == CAIRO_STATUS_SUCCESS)
-	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
     catalog = _cairo_pdf_surface_write_catalog (surface);
     if (catalog.id == 0 && status == CAIRO_STATUS_SUCCESS)
 	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -2154,7 +2183,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
 				 ">>\n",
 				 surface->next_available_resource.id,
 				 catalog.id,
-				 info.id);
+				 surface->docinfo_res.id);
 
     _cairo_output_stream_printf (surface->output,
 				 "startxref\n"
@@ -4797,28 +4826,6 @@ _cairo_pdf_surface_get_font_options (void                  *abstract_surface,
     _cairo_font_options_set_round_glyph_positions (options, CAIRO_ROUND_GLYPH_POS_OFF);
 }
 
-static cairo_pdf_resource_t
-_cairo_pdf_surface_write_info (cairo_pdf_surface_t *surface)
-{
-    cairo_pdf_resource_t info;
-
-    info = _cairo_pdf_surface_new_object (surface);
-    if (info.id == 0)
-	return info;
-
-    _cairo_output_stream_printf (surface->output,
-				 "%d 0 obj\n"
-				 "<< /Creator (cairo %s (http://cairographics.org))\n"
-				 "   /Producer (cairo %s (http://cairographics.org))\n"
-				 ">>\n"
-				 "endobj\n",
-				 info.id,
-                                 cairo_version_string (),
-                                 cairo_version_string ());
-
-    return info;
-}
-
 static void
 _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
 {
diff --git a/src/cairo-pdf.h b/src/cairo-pdf.h
index e8ea801..0589ba5 100644
--- a/src/cairo-pdf.h
+++ b/src/cairo-pdf.h
@@ -113,6 +113,37 @@ cairo_pdf_surface_add_outline (cairo_surface_t	          *surface,
 			       const char                 *dest,
 			       cairo_pdf_outline_flags_t  flags);
 
+/**
+ * cairo_pdf_metadata_t:
+ * @CAIRO_PDF_METADATA_TITLE: The document title (Since 1.16)
+ * @CAIRO_PDF_METADATA_AUTHOR: The document author (Since 1.16)
+ * @CAIRO_PDF_METADATA_SUBJECT: The document subject (Since 1.16)
+ * @CAIRO_PDF_METADATA_KEYWORDS: The document keywords (Since 1.16)
+ * @CAIRO_PDF_METADATA_CREATOR: The document creator (Since 1.16)
+ * @CAIRO_PDF_METADATA_TITLE: The document title (Since 1.16)
+ * @CAIRO_PDF_METADATA_CREATE_DATE: The document creation date (Since 1.16)
+ * @CAIRO_PDF_METADATA_MOD_DATE: The document modification date (Since 1.16)
+ *
+ * #cairo_pdf_metadata_t is used by the
+ * cairo_pdf_surface_set_metadata() function specify the metadata to set.
+ *
+ * Since: 1.16
+ **/
+typedef enum _cairo_pdf_metadata {
+    CAIRO_PDF_METADATA_TITLE,
+    CAIRO_PDF_METADATA_AUTHOR,
+    CAIRO_PDF_METADATA_SUBJECT,
+    CAIRO_PDF_METADATA_KEYWORDS,
+    CAIRO_PDF_METADATA_CREATOR,
+    CAIRO_PDF_METADATA_CREATE_DATE,
+    CAIRO_PDF_METADATA_MOD_DATE,
+} cairo_pdf_metadata_t;
+
+void
+cairo_pdf_surface_set_metadata (cairo_surface_t	     *surface,
+				cairo_pdf_metadata_t  metadata,
+                                const char           *utf8);
+
 CAIRO_END_DECLS
 
 #else  /* CAIRO_HAS_PDF_SURFACE */
commit dfc7b9e6698d5923a858ae9a77345c983ab51e4c
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 22:28:02 2016 +0930

    pdf: add document outline API

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index fdffc03..7446415 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -68,6 +68,8 @@ cairo_image_surface_get_stride
 <SECTION>
 <FILE>cairo-pdf</FILE>
 CAIRO_HAS_PDF_SURFACE
+CAIRO_PDF_OUTLINE_ROOT
+cairo_pdf_outline_flags_t
 cairo_pdf_surface_create
 cairo_pdf_surface_create_for_stream
 cairo_pdf_surface_restrict_to_version
@@ -75,6 +77,7 @@ cairo_pdf_version_t
 cairo_pdf_get_versions
 cairo_pdf_version_to_string
 cairo_pdf_surface_set_size
+cairo_pdf_surface_add_outline
 </SECTION>
 
 <SECTION>
diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
index 95c8ba6..38c5bac 100644
--- a/src/cairo-pdf-interchange.c
+++ b/src/cairo-pdf-interchange.c
@@ -506,6 +506,99 @@ cairo_pdf_interchange_write_parent_tree (cairo_pdf_surface_t *surface)
     return CAIRO_STATUS_SUCCESS;
 }
 
+static cairo_int_status_t
+cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
+{
+    int num_elems, i;
+    cairo_pdf_outline_entry_t *outline;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status;
+    char *name = NULL;
+    char *dest = NULL;
+
+    num_elems = _cairo_array_num_elements (&ic->outline);
+    if (num_elems < 2)
+	return CAIRO_INT_STATUS_SUCCESS;
+
+    _cairo_array_copy_element (&ic->outline, 0, &outline);
+    outline->res = _cairo_pdf_surface_new_object (surface);
+    surface->outlines_dict_res = outline->res;
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Type /Outlines\n"
+				 "   /First %d 0 R\n"
+				 "   /Last %d 0 R\n"
+				 "   /Count %d\n"
+				 ">>\n"
+				 "endobj\n",
+				 outline->res.id,
+				 outline->first_child->res.id,
+				 outline->last_child->res.id,
+				 outline->count);
+
+    for (i = 1; i < num_elems; i++) {
+	_cairo_array_copy_element (&ic->outline, i, &outline);
+	_cairo_pdf_surface_update_object (surface, outline->res);
+	status = _cairo_utf8_to_pdf_string (outline->name, &name);
+	if (unlikely (status))
+	    return status;
+
+	status = _cairo_utf8_to_pdf_string (outline->dest, &dest);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n"
+				     "<< /Title %s\n"
+				     "   /Parent %d 0 R\n",
+				     outline->res.id,
+				     name,
+				     outline->parent->res.id);
+
+	if (outline->prev) {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /Prev %d 0 R\n",
+					 outline->prev->res.id);
+	}
+
+	if (outline->next) {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /Next %d 0 R\n",
+					 outline->next->res.id);
+	}
+
+	if (outline->first_child) {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /First %d 0 R\n"
+					 "   /Last %d 0 R\n"
+					 "   /Count %d\n",
+					 outline->first_child->res.id,
+					 outline->last_child->res.id,
+					 outline->count);
+	}
+
+	if (outline->flags) {
+	    int flags = 0;
+	    if (outline->flags & CAIRO_BOOKMARK_FLAG_ITALIC)
+		flags |= 1;
+	    if (outline->flags & CAIRO_BOOKMARK_FLAG_BOLD)
+		flags |= 2;
+	    _cairo_output_stream_printf (surface->output,
+					 "   /F %d\n",
+					 flags);
+	}
+
+	_cairo_output_stream_printf (surface->output,
+				     "   /Dest %s\n"
+				     ">>\n"
+				     "endobj\n",
+				     dest);
+	free (dest);
+    }
+
+    return status;
+}
+
 static void
 _collect_dest (void *entry, void *closure)
 {
@@ -937,6 +1030,10 @@ _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface)
     if (_cairo_tag_stack_get_structure_type (&ic->analysis_tag_stack) == TAG_TREE_TYPE_TAGGED)
 	surface->tagged = TRUE;
 
+    status = cairo_pdf_interchange_write_outline (surface);
+    if (unlikely (status))
+	return status;
+
     status = cairo_pdf_interchange_write_names_dict (surface);
 
     return status;
@@ -946,6 +1043,8 @@ cairo_int_status_t
 _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_outline_entry_t *outline_root;
+    cairo_int_status_t status;
 
     _cairo_tag_stack_init (&ic->analysis_tag_stack);
     _cairo_tag_stack_init (&ic->render_tag_stack);
@@ -971,13 +1070,21 @@ _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
     ic->sorted_dests = NULL;
     ic->dests_res.id = 0;
 
-    return CAIRO_STATUS_SUCCESS;
+    _cairo_array_init (&ic->outline, sizeof(cairo_pdf_outline_entry_t *));
+    outline_root = calloc (1, sizeof(cairo_pdf_outline_entry_t));
+    if (unlikely (outline_root == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = _cairo_array_append (&ic->outline, &outline_root);
+
+    return status;
 }
 
 cairo_int_status_t
 _cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_interchange_t *ic = &surface->interchange;
+    unsigned i;
 
     _cairo_tag_stack_fini (&ic->analysis_tag_stack);
     _cairo_tag_stack_fini (&ic->render_tag_stack);
@@ -989,5 +1096,78 @@ _cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
     _cairo_hash_table_destroy (ic->named_dests);
     free (ic->sorted_dests);
 
+    for (i = 0; i < _cairo_array_num_elements (&ic->outline); i++) {
+	cairo_pdf_outline_entry_t *outline;
+
+	_cairo_array_copy_element (&ic->outline, i, &outline);
+	free (outline);
+    }
+    _cairo_array_fini (&ic->outline);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_add_outline (cairo_pdf_surface_t        *surface,
+				    int                         parent_id,
+				    const char                 *name,
+				    const char                 *dest,
+				    cairo_pdf_outline_flags_t   flags,
+				    int                        *id)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_outline_entry_t *outline;
+    cairo_pdf_outline_entry_t *parent;
+    cairo_int_status_t status;
+
+    if (parent_id < 0 || parent_id >= (int)_cairo_array_num_elements (&ic->outline))
+	return CAIRO_STATUS_SUCCESS;
+
+    outline = _cairo_malloc (sizeof(cairo_pdf_outline_entry_t));
+    if (unlikely (outline == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    outline->res = _cairo_pdf_surface_new_object (surface);
+    if (outline->res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    outline->name = strdup (name);
+    outline->dest = strdup (dest);
+    outline->flags = flags;
+    outline->count = 0;
+
+    _cairo_array_copy_element (&ic->outline, parent_id, &parent);
+
+    outline->parent = parent;
+    outline->first_child = NULL;
+    outline->last_child = NULL;
+    outline->next = NULL;
+    if (parent->last_child) {
+	parent->last_child->next = outline;
+	outline->prev = parent->last_child;
+	parent->last_child = outline;
+    } else {
+	parent->first_child = outline;
+	parent->last_child = outline;
+	outline->prev = NULL;
+    }
+
+    *id = _cairo_array_num_elements (&ic->outline);
+    status = _cairo_array_append (&ic->outline, &outline);
+    if (unlikely (status))
+	return status;
+
+    /* Update Count */
+    outline = outline->parent;
+    while (outline) {
+	if (outline->flags & CAIRO_BOOKMARK_FLAG_OPEN) {
+	    outline->count++;
+	} else {
+	    outline->count--;
+	    break;
+	}
+	outline = outline->parent;
+    }
+
     return CAIRO_STATUS_SUCCESS;
 }
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index cbe4599..010b127 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -193,6 +193,19 @@ typedef struct _cairo_pdf_named_dest {
     cairo_bool_t referenced;
 } cairo_pdf_named_dest_t;
 
+typedef struct _cairo_pdf_outline_entry {
+    char *name;
+    char *dest;
+    cairo_pdf_outline_flags_t flags;
+    cairo_pdf_resource_t res;
+    struct _cairo_pdf_outline_entry *parent;
+    struct _cairo_pdf_outline_entry *first_child;
+    struct _cairo_pdf_outline_entry *last_child;
+    struct _cairo_pdf_outline_entry *next;
+    struct _cairo_pdf_outline_entry *prev;
+    int count;
+} cairo_pdf_outline_entry_t;
+
 typedef struct _cairo_pdf_interchange {
     cairo_tag_stack_t analysis_tag_stack;
     cairo_tag_stack_t render_tag_stack;
@@ -211,6 +224,7 @@ typedef struct _cairo_pdf_interchange {
     cairo_pdf_named_dest_t **sorted_dests;
     cairo_pdf_resource_t dests_res;
     int annot_page;
+    cairo_array_t outline; /* array of pointers to cairo_pdf_outline_entry_t; */
 
 } cairo_pdf_interchange_t;
 
@@ -298,6 +312,7 @@ struct _cairo_pdf_surface {
     int page_parent_tree; /* -1 if not used */
     cairo_array_t page_annots;
     cairo_bool_t tagged;
+    cairo_pdf_resource_t outlines_dict_res;
     cairo_pdf_resource_t names_dict_res;
 
     cairo_surface_t *paginated_surface;
@@ -344,5 +359,12 @@ _cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface);
 cairo_private cairo_int_status_t
 _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface);
 
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_add_outline (cairo_pdf_surface_t        *surface,
+				    int                         parent_id,
+				    const char                 *name,
+				    const char                 *dest,
+				    cairo_pdf_outline_flags_t   flags,
+				    int                        *id);
 
 #endif /* CAIRO_PDF_SURFACE_PRIVATE_H */
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index a32dfb6..98c780b 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -450,6 +450,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->page_parent_tree = -1;
     _cairo_array_init (&surface->page_annots, sizeof (cairo_pdf_resource_t));
     surface->tagged = FALSE;
+    surface->outlines_dict_res.id = 0;
     surface->names_dict_res.id = 0;
 
     surface->paginated_surface =  _cairo_paginated_surface_create (
@@ -720,6 +721,58 @@ cairo_pdf_surface_set_size (cairo_surface_t	*surface,
 	status = _cairo_surface_set_error (surface, status);
 }
 
+/**
+ * CAIRO_PDF_OUTLINE_ROOT:
+ *
+ * The root outline item in cairo_pdf_surface_add_outline().
+ *
+ * Since: 1.16
+ **/
+
+/**
+ * cairo_pdf_surface_add_outline:
+ * @surface: a PDF #cairo_surface_t
+ * @parent_id: the id of the parent item or %CAIRO_PDF_OUTLINE_ROOT if this is a top level item.
+ * @utf8: the name of the outline
+ * @dest: the name of the destination
+ * @flags: outline item flags
+ *
+ * Add an item to the document outline hierarchy with the name @utf8 that links to the
+ * destinaton @dest. Destinations are created using
+ * cairo_tag_begin()/cairo_tag_end() with the
+ * %CAIRO_TAG_DEST. The item will be a child of the item with id @parent_id. Use %CAIRO_PDF_OUTLINE_ROOT
+ * as the parent id of top level items.
+ *
+ * Return value: the id for the added item.
+ *
+ * Since: 1.16
+ **/
+int
+cairo_pdf_surface_add_outline (cairo_surface_t	         *surface,
+			       int                        parent_id,
+			       const char                *utf8,
+			       const char                *dest,
+			       cairo_pdf_outline_flags_t  flags)
+{
+    cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */
+    cairo_status_t status;
+    int id = 0;
+
+    if (! _extract_pdf_surface (surface, &pdf_surface))
+	return 0;
+
+    status = _cairo_pdf_interchange_add_outline (pdf_surface,
+						 parent_id,
+						 utf8,
+						 dest,
+						 flags,
+						 &id);
+    if (status)
+	status = _cairo_surface_set_error (surface, status);
+
+    return id;
+}
+
 static void
 _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
 {
@@ -6107,6 +6160,12 @@ _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface)
 	}
     }
 
+    if (surface->outlines_dict_res.id != 0) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /Outlines %d 0 R\n",
+				     surface->outlines_dict_res.id);
+    }
+
     if (surface->names_dict_res.id != 0) {
 	_cairo_output_stream_printf (surface->output,
 				     "   /Names %d 0 R\n",
diff --git a/src/cairo-pdf.h b/src/cairo-pdf.h
index 1bc8524..e8ea801 100644
--- a/src/cairo-pdf.h
+++ b/src/cairo-pdf.h
@@ -85,6 +85,34 @@ cairo_pdf_surface_set_size (cairo_surface_t	*surface,
 			    double		 width_in_points,
 			    double		 height_in_points);
 
+/**
+ * cairo_pdf_outline_flags_t:
+ * @CAIRO_BOOKMARK_FLAG_OPEN: The outline item defaults to open in the PDF viewer (Since 1.16)
+ * @CAIRO_BOOKMARK_FLAG_BOLD: The outline item is displayed by the viewer in bold text (Since 1.16)
+ * @CAIRO_BOOKMARK_FLAG_ITALIC: The outline item is displayed by the viewer in italic text (Since 1.16)
+ *
+ * #cairo_pdf_outline_flags_t is used by the
+ * cairo_pdf_surface_add_outline() function specify the attributes of
+ * an outline item. These flags may be bitwise-or'd to produce any
+ * combination of flags.
+ *
+ * Since: 1.16
+ **/
+typedef enum _cairo_pdf_outline_flags {
+    CAIRO_BOOKMARK_FLAG_OPEN   = 0x1,
+    CAIRO_BOOKMARK_FLAG_BOLD   = 0x2,
+    CAIRO_BOOKMARK_FLAG_ITALIC = 0x4,
+} cairo_pdf_outline_flags_t;
+
+#define CAIRO_PDF_OUTLINE_ROOT 0
+
+cairo_public int
+cairo_pdf_surface_add_outline (cairo_surface_t	          *surface,
+			       int                         parent_id,
+			       const char                 *utf8,
+			       const char                 *dest,
+			       cairo_pdf_outline_flags_t  flags);
+
 CAIRO_END_DECLS
 
 #else  /* CAIRO_HAS_PDF_SURFACE */
commit dcbfb726478f5ab2277bd52813b40e565612566a
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 22:05:42 2016 +0930

    pdf: structured text and hyperlink support

diff --git a/doc/public/cairo-docs.xml b/doc/public/cairo-docs.xml
index d54e63b..73c9813 100644
--- a/doc/public/cairo-docs.xml
+++ b/doc/public/cairo-docs.xml
@@ -18,6 +18,7 @@
     <xi:include href="xml/cairo-transforms.xml"/>
     <xi:include href="xml/cairo-text.xml"/>
     <xi:include href="xml/cairo-raster-source.xml"/>
+    <xi:include href="xml/cairo-tag.xml"/>
   </chapter>
   <chapter id="cairo-fonts">
     <title>Fonts</title>
diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index 4beaa0a..fdffc03 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -408,6 +408,14 @@ cairo_raster_source_finish_func_t
 </SECTION>
 
 <SECTION>
+<FILE>cairo-tag</FILE>
+CAIRO_TAG_DEST
+CAIRO_TAG_LINK
+cairo_tag_begin
+cairo_tag_end
+</SECTION>
+
+<SECTION>
 <FILE>cairo-matrix</FILE>
 cairo_matrix_t
 cairo_matrix_init
diff --git a/src/Makefile.sources b/src/Makefile.sources
index fac24d7..b368f27 100644
--- a/src/Makefile.sources
+++ b/src/Makefile.sources
@@ -279,8 +279,8 @@ _cairo_deflate_stream_sources = cairo-deflate-stream.c
 cairo_sources += $(_cairo_deflate_stream_sources)
 
 cairo_pdf_headers = cairo-pdf.h
-cairo_pdf_private = cairo-pdf-surface-private.h
-cairo_pdf_sources = cairo-pdf-surface.c
+cairo_pdf_private = cairo-pdf-surface-private.h cairo-tag-stack-private.h cairo-tag-attributes-private.h
+cairo_pdf_sources = cairo-pdf-surface.c cairo-pdf-interchange.c cairo-tag-stack.c cairo-tag-attributes.c
 
 cairo_svg_headers = cairo-svg.h
 cairo_svg_private = cairo-svg-surface-private.h
diff --git a/src/cairo-device.c b/src/cairo-device.c
index bacf93b..73e5040 100644
--- a/src/cairo-device.c
+++ b/src/cairo-device.c
@@ -162,6 +162,7 @@ _cairo_device_create_in_error (cairo_status_t status)
     case CAIRO_STATUS_PNG_ERROR:
     case CAIRO_STATUS_FREETYPE_ERROR:
     case CAIRO_STATUS_WIN32_GDI_ERROR:
+    case CAIRO_STATUS_TAG_ERROR:
     default:
 	_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
 	return (cairo_device_t *) &_nil_device;
diff --git a/src/cairo-error-private.h b/src/cairo-error-private.h
index 25dac7d..1ab57dd 100644
--- a/src/cairo-error-private.h
+++ b/src/cairo-error-private.h
@@ -97,6 +97,7 @@ enum _cairo_int_status {
     CAIRO_INT_STATUS_PNG_ERROR,
     CAIRO_INT_STATUS_FREETYPE_ERROR,
     CAIRO_INT_STATUS_WIN32_GDI_ERROR,
+    CAIRO_INT_STATUS_TAG_ERROR,
 
     CAIRO_INT_STATUS_LAST_STATUS,
 
diff --git a/src/cairo-misc.c b/src/cairo-misc.c
index 35025ad..e9b0ab6 100644
--- a/src/cairo-misc.c
+++ b/src/cairo-misc.c
@@ -164,6 +164,8 @@ cairo_status_to_string (cairo_status_t status)
 	return "error occurred in libfreetype";
     case CAIRO_STATUS_WIN32_GDI_ERROR:
 	return "error occurred in the Windows Graphics Device Interface";
+    case CAIRO_STATUS_TAG_ERROR:
+	return "invalid tag name, attributes, or nesting";
     default:
     case CAIRO_STATUS_LAST_STATUS:
 	return "<unknown error status>";
diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
new file mode 100644
index 0000000..95c8ba6
--- /dev/null
+++ b/src/cairo-pdf-interchange.c
@@ -0,0 +1,993 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2016 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Adrian Johnson.
+ *
+ * Contributor(s):
+ *	Adrian Johnson <ajohnson at redneon.com>
+ */
+
+
+/* PDF Document Interchange features:
+ *  - metadata
+ *  - document outline
+ *  - tagged pdf
+ *  - hyperlinks
+ *  - page labels
+ */
+
+#include "cairoint.h"
+
+#include "cairo-pdf.h"
+#include "cairo-pdf-surface-private.h"
+
+#include "cairo-array-private.h"
+#include "cairo-error-private.h"
+#include "cairo-output-stream-private.h"
+
+static void
+write_rect_to_pdf_quad_points (cairo_output_stream_t   *stream,
+			       const cairo_rectangle_t *rect,
+			       double                   surface_height)
+{
+    _cairo_output_stream_printf (stream,
+				 "%f %f %f %f %f %f %f %f",
+				 rect->x,
+				 surface_height - rect->y,
+				 rect->x + rect->width,
+				 surface_height - rect->y,
+				 rect->x + rect->width,
+				 surface_height - (rect->y + rect->height),
+				 rect->x,
+				 surface_height - (rect->y + rect->height));
+}
+
+static void
+write_rect_int_to_pdf_bbox (cairo_output_stream_t       *stream,
+			    const cairo_rectangle_int_t *rect,
+			    double                       surface_height)
+{
+    _cairo_output_stream_printf (stream,
+				 "%d %f %d %f",
+				 rect->x,
+				 surface_height - (rect->y + rect->height),
+				 rect->x + rect->width,
+				 surface_height - rect->y);
+}
+
+static cairo_int_status_t
+add_tree_node (cairo_pdf_surface_t           *surface,
+	       cairo_pdf_struct_tree_node_t  *parent,
+	       const char                    *name,
+	       cairo_pdf_struct_tree_node_t **new_node)
+{
+    cairo_pdf_struct_tree_node_t *node;
+
+    node = malloc (sizeof(cairo_pdf_struct_tree_node_t));
+    if (unlikely (node == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    node->name = strdup (name);
+    node->res = _cairo_pdf_surface_new_object (surface);
+    if (node->res.id == 0)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    node->parent = parent;
+    cairo_list_init (&node->children);
+    _cairo_array_init (&node->mcid, sizeof(struct page_mcid));
+    memset (&node->annot, 0, sizeof(node->annot));
+    cairo_list_init (&node->children);
+
+    cairo_list_add_tail (&node->link, &parent->children);
+
+    *new_node = node;
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_bool_t
+is_leaf_node (cairo_pdf_struct_tree_node_t *node)
+{
+    return node->parent && cairo_list_is_empty (&node->children) ;
+}
+
+static void
+free_node (cairo_pdf_struct_tree_node_t *node)
+{
+    cairo_pdf_struct_tree_node_t *child, *next;
+
+    if (!node)
+	return;
+
+    cairo_list_foreach_entry_safe (child, next, cairo_pdf_struct_tree_node_t,
+				   &node->children, link)
+    {
+	cairo_list_del (&child->link);
+	free_node (child);
+    }
+    free (node->name);
+    _cairo_array_fini (&node->mcid);
+    free (node);
+}
+
+static cairo_status_t
+add_mcid_to_node (cairo_pdf_surface_t          *surface,
+		  cairo_pdf_struct_tree_node_t *node,
+		  int                           page,
+		  int                          *mcid)
+{
+    struct page_mcid mcid_elem;
+    cairo_int_status_t status;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    status = _cairo_array_append (&ic->mcid_to_tree, &node);
+    if (unlikely (status))
+	return status;
+
+    mcid_elem.page = page;
+    mcid_elem.mcid = _cairo_array_num_elements (&ic->mcid_to_tree) - 1;
+    *mcid = mcid_elem.mcid;
+    return _cairo_array_append (&node->mcid, &mcid_elem);
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface,
+					 cairo_pdf_struct_tree_node_t   *node)
+{
+    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_surface_update_object (surface, node->res);
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Type /StructElem\n"
+				 "   /S /%s\n"
+				 "   /P %d 0 R\n",
+				 node->res.id,
+				 node->name,
+				 node->parent->res.id);
+
+    if (! cairo_list_is_empty (&node->children)) {
+	if (cairo_list_is_singular (&node->children)) {
+	    child = cairo_list_first_entry (&node->children, cairo_pdf_struct_tree_node_t, link);
+	    _cairo_output_stream_printf (surface->output, "   /K %d 0 R\n", child->res.id);
+	} else {
+	    _cairo_output_stream_printf (surface->output, "   /K [ ");
+
+	    cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
+				      &node->children, link)
+	    {
+		_cairo_output_stream_printf (surface->output, "%d 0 R ", child->res.id);
+	    }
+	    _cairo_output_stream_printf (surface->output, "]\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->output, "   /Pg %d 0 R\n", page_res->id);
+
+	    if (num_mcid == 1 && node->annot.res.id == 0) {
+		_cairo_output_stream_printf (surface->output, "   /K %d\n", mcid_elem->mcid);
+	    } else {
+		_cairo_output_stream_printf (surface->output, "   /K [ ");
+		if (node->annot.res.id != 0) {
+		    _cairo_output_stream_printf (surface->output,
+						 "%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->output, "%d ", mcid_elem->mcid);
+		    } else {
+			_cairo_output_stream_printf (surface->output,
+						     "\n       << /Type /MCR /Pg %d 0 R /MCID %d >> ",
+						     page_res->id,
+						     mcid_elem->mcid);
+		    }
+		}
+		_cairo_output_stream_printf (surface->output, "]\n");
+	    }
+	}
+    }
+    _cairo_output_stream_printf (surface->output,
+				 ">>\n"
+				 "endobj\n");
+
+    return _cairo_output_stream_get_status (surface->output);
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_annot (cairo_pdf_surface_t            *surface,
+				   cairo_pdf_struct_tree_node_t   *node)
+{
+    cairo_pdf_resource_t res;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    int sp;
+    char *dest = NULL;
+    int i, num_rects, num_mcid;
+    struct page_mcid *mcid_elem;
+
+    num_mcid = _cairo_array_num_elements (&node->mcid);
+    if (num_mcid == 0 )
+	return status;
+
+    mcid_elem = _cairo_array_index (&node->mcid, 0);
+    if (mcid_elem->page != ic->annot_page)
+	return status;
+
+    num_rects = _cairo_array_num_elements (&node->annot.link_attrs.rects);
+    if (strcmp (node->name, CAIRO_TAG_LINK) == 0 &&
+	node->annot.link_attrs.link_type != TAG_LINK_EMPTY &&
+	(node->annot.extents.valid || num_rects > 0))
+    {
+	res = _cairo_pdf_surface_new_object (surface);
+
+	status = _cairo_array_append (&ic->parent_tree, &res);
+	if (unlikely (status))
+	    return status;
+
+	sp = _cairo_array_num_elements (&ic->parent_tree) - 1;
+
+	status = _cairo_array_append (&surface->page_annots, &res);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_pdf_surface_update_object (surface, res);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n"
+				     "<< /Type /Annot\n"
+				     "   /Subtype /Link\n"
+				     "   /StructParent %d\n",
+				     res.id,
+				     sp);
+
+	if (num_rects > 0) {
+	    cairo_rectangle_int_t bbox_rect;
+
+	    _cairo_output_stream_printf (surface->output,
+					 "   /QuadPoints [ ");
+	    for (i = 0; i < num_rects; i++) {
+		cairo_rectangle_t rectf;
+		cairo_rectangle_int_t recti;
+
+		_cairo_array_copy_element (&node->annot.link_attrs.rects, i, &rectf);
+		_cairo_rectangle_int_from_double (&recti, &rectf);
+		if (i == 0)
+		    bbox_rect = recti;
+		else
+		    _cairo_rectangle_union (&bbox_rect, &recti);
+
+		write_rect_to_pdf_quad_points (surface->output, &rectf, node->annot.page_height);
+		_cairo_output_stream_printf (surface->output, " ");
+	    }
+	    _cairo_output_stream_printf (surface->output,
+					 "]\n"
+					 "   /Rect [ ");
+	    write_rect_int_to_pdf_bbox (surface->output, &bbox_rect, node->annot.page_height);
+	    _cairo_output_stream_printf (surface->output, " ]\n");
+	} else {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /Rect [ ");
+	    write_rect_int_to_pdf_bbox (surface->output, &node->annot.extents.extents, node->annot.page_height);
+	    _cairo_output_stream_printf (surface->output, " ]\n");
+	}
+
+	if (node->annot.link_attrs.dest) {
+	    status = _cairo_utf8_to_pdf_string (node->annot.link_attrs.dest, &dest);
+	    if (unlikely (status))
+		return status;
+	}
+
+	if (node->annot.link_attrs.link_type == TAG_LINK_DEST) {
+	    if (node->annot.link_attrs.dest) {
+		_cairo_output_stream_printf (surface->output,
+					     "   /Dest %s\n",
+					     dest);
+	    } else {
+		cairo_pdf_resource_t res;
+		int page = node->annot.link_attrs.page;
+
+		if (page < 1 || page > (int)_cairo_array_num_elements (&surface->pages))
+		    return CAIRO_INT_STATUS_TAG_ERROR;
+
+		_cairo_array_copy_element (&surface->pages, page - 1, &res);
+		_cairo_output_stream_printf (surface->output,
+					     "   /Dest [%d 0 R /XYZ %f %f 0]\n",
+					     res.id,
+					     node->annot.link_attrs.pos.x,
+					     node->annot.page_height - node->annot.link_attrs.pos.y);
+	    }
+	} else if (node->annot.link_attrs.link_type == TAG_LINK_URI) {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /A <<\n"
+					 "      /Type /Action\n"
+					 "      /S /URI\n"
+					 "      /URI (%s)\n"
+					 "   >>\n",
+					 node->annot.link_attrs.uri);
+	} else if (node->annot.link_attrs.link_type == TAG_LINK_FILE) {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /A <<\n"
+					 "      /Type /Action\n"
+					 "      /S /GoToR\n"
+					 "      /F (%s)\n",
+					 node->annot.link_attrs.file);
+	    if (node->annot.link_attrs.dest) {
+		_cairo_output_stream_printf (surface->output,
+					     "      /D %s\n",
+					     dest);
+	    } else {
+		_cairo_output_stream_printf (surface->output,
+					     "      /D [%d %f %f ]\n",
+					     node->annot.link_attrs.page,
+					     node->annot.link_attrs.pos.x,
+					     node->annot.link_attrs.pos.y);
+	    }
+	    _cairo_output_stream_printf (surface->output,
+					 "   >>\n");
+	}
+
+	_cairo_output_stream_printf (surface->output,
+				     "   /BS << /W 0 >>"
+				     ">>\n"
+				     "endobj\n");
+
+	status = _cairo_output_stream_get_status (surface->output);
+
+	free (dest);
+    }
+
+    return status;
+}
+
+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))
+{
+    cairo_int_status_t status;
+    cairo_pdf_struct_tree_node_t *child;
+
+    if (node->parent) {
+	status = func (surface, node);
+	if (unlikely (status))
+	    return status;
+    }
+
+    cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
+			      &node->children, link)
+    {
+	status = cairo_pdf_interchange_walk_struct_tree (surface, child, func);
+	if (unlikely (status))
+	    return status;
+    }
+
+    return status;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_pdf_struct_tree_node_t *child;
+
+    if (cairo_list_is_empty (&ic->struct_root->children))
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
+    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);
+    _cairo_pdf_surface_update_object (surface, surface->struct_tree_root);
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Type /StructTreeRoot\n"
+				 "   /ParentTree %d 0 R\n"
+				 "   /K [ %d 0 R ]\n"
+				 ">>\n"
+				 "endobj\n",
+				 surface->struct_tree_root.id,
+				 ic->parent_tree_res.id,
+				 child->res.id);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_page_annots (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    _cairo_array_truncate (&surface->page_annots, 0);
+    ic->annot_page = _cairo_array_num_elements (&surface->pages);
+
+    cairo_pdf_interchange_walk_struct_tree (surface, ic->struct_root, cairo_pdf_interchange_write_annot);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_page_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);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n"
+				     "[\n",
+				     res.id);
+	for (i = 0; i < num_elems; i++) {
+	    _cairo_array_copy_element (&ic->mcid_to_tree, i, &node);
+	    _cairo_output_stream_printf (surface->output, "  %d 0 R\n", node->res.id);
+	}
+	_cairo_output_stream_printf (surface->output,
+				     "]\n"
+				     "endobj\n");
+	status = _cairo_array_append (&ic->parent_tree, &res);
+	surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
+    }
+
+    return status;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_parent_tree (cairo_pdf_surface_t *surface)
+{
+    int num_elems, i;
+    cairo_pdf_resource_t *res;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    num_elems = _cairo_array_num_elements (&ic->parent_tree);
+    if (num_elems > 0) {
+	ic->parent_tree_res = _cairo_pdf_surface_new_object (surface);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n"
+				     "<< /Nums [\n",
+				     ic->parent_tree_res.id);
+	for (i = 0; i < num_elems; i++) {
+	    res = _cairo_array_index (&ic->parent_tree, i);
+	    if (res->id) {
+		_cairo_output_stream_printf (surface->output,
+					     "   %d %d 0 R\n",
+					     i,
+					     res->id);
+	    }
+	}
+	_cairo_output_stream_printf (surface->output,
+				     "  ]\n"
+				     ">>\n"
+				     "endobj\n");
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+_collect_dest (void *entry, void *closure)
+{
+    cairo_pdf_named_dest_t *dest = entry;
+    cairo_pdf_surface_t *surface = closure;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    ic->sorted_dests[ic->num_dests++] = dest;
+}
+
+static int
+_dest_compare (const void *a, const void *b)
+{
+    const cairo_pdf_named_dest_t * const *dest_a = a;
+    const cairo_pdf_named_dest_t * const *dest_b = b;
+
+    return strcmp ((*dest_a)->attrs.name, (*dest_b)->attrs.name);
+}
+
+static cairo_int_status_t
+_cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
+{
+    int i;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    if (ic->num_dests == 0) {
+	ic->dests_res.id = 0;
+        return CAIRO_STATUS_SUCCESS;
+    }
+
+    ic->sorted_dests = calloc (ic->num_dests, sizeof (cairo_pdf_named_dest_t *));
+    if (unlikely (ic->sorted_dests == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    ic->num_dests = 0;
+    _cairo_hash_table_foreach (ic->named_dests, _collect_dest, surface);
+    qsort (ic->sorted_dests, ic->num_dests, sizeof (cairo_pdf_named_dest_t *), _dest_compare);
+
+    ic->dests_res = _cairo_pdf_surface_new_object (surface);
+    _cairo_output_stream_printf (surface->output,
+				 "%d 0 obj\n"
+				 "<< /Names [\n",
+				 ic->dests_res.id);
+    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 = y;
+
+	if (dest->extents.valid) {
+	    x = dest->extents.extents.x;
+	    y = dest->extents.extents.y;
+	}
+
+	if (dest->attrs.x_valid)
+	    x = dest->attrs.x;
+
+	if (dest->attrs.y_valid)
+	    y = dest->attrs.y;
+
+	_cairo_array_copy_element (&surface->pages, dest->page - 1, &page_res);
+	_cairo_output_stream_printf (surface->output,
+				     "   (%s) [ %d 0 R /XYZ %f %f ]\n",
+				     dest->attrs.name,
+				     page_res.id,
+				     x,
+				     surface->height - y);
+    }
+    _cairo_output_stream_printf (surface->output,
+				     "  ]\n"
+				     ">>\n"
+				     "endobj\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+cairo_pdf_interchange_write_names_dict (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status;
+
+    status = _cairo_pdf_interchange_write_document_dests (surface);
+    if (unlikely (status))
+	return status;
+
+    surface->names_dict_res.id = 0;
+    if (ic->dests_res.id != 0) {
+	surface->names_dict_res = _cairo_pdf_surface_new_object (surface);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\n"
+				     "<< /Dests %d 0 R >>\n"
+				     "endobj\n",
+				     surface->names_dict_res.id,
+				     ic->dests_res.id);
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+init_named_dest_key (cairo_pdf_named_dest_t *dest)
+{
+    dest->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE,
+					 dest->attrs.name,
+					 strlen (dest->attrs.name));
+}
+
+static cairo_bool_t
+_named_dest_equal (const void *key_a, const void *key_b)
+{
+    const cairo_pdf_named_dest_t *a = key_a;
+    const cairo_pdf_named_dest_t *b = key_b;
+
+    return strcmp (a->attrs.name, b->attrs.name) == 0;
+}
+
+static void
+_named_dest_pluck (void *entry, void *closure)
+{
+    cairo_pdf_named_dest_t *dest = entry;
+    cairo_hash_table_t *table = closure;
+
+    _cairo_hash_table_remove (table, &dest->base);
+    free (dest->attrs.name);
+    free (dest);
+}
+
+static cairo_int_status_t
+_cairo_pdf_interchange_begin_structure_tag (cairo_pdf_surface_t    *surface,
+					    cairo_tag_type_t        tag_type,
+					    const char             *name,
+					    const char             *attributes)
+{
+    int page_num, mcid;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
+	status = add_tree_node (surface, ic->current_node, name, &ic->current_node);
+	if (unlikely (status))
+	    return status;
+
+	_cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, ic->current_node);
+
+	if (tag_type & TAG_TYPE_LINK) {
+	    status = _cairo_tag_parse_link_attributes (attributes, &ic->current_node->annot.link_attrs);
+	    if (unlikely (status))
+		return status;
+
+	    ic->current_node->annot.page_height = surface->height;
+	    cairo_list_add_tail (&ic->current_node->annot.extents.link, &ic->extents_list);
+	}
+
+    } 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);
+	}
+    }
+
+    return status;
+}
+
+static cairo_int_status_t
+_cairo_pdf_interchange_begin_dest_tag (cairo_pdf_surface_t    *surface,
+				       cairo_tag_type_t        tag_type,
+				       const char             *name,
+				       const char             *attributes)
+{
+    cairo_pdf_named_dest_t *dest;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+
+    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
+	dest = calloc (1, sizeof (cairo_pdf_named_dest_t));
+	if (unlikely (dest == NULL))
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+	status = _cairo_tag_parse_dest_attributes (attributes, &dest->attrs);
+	if (unlikely (status))
+	    return status;
+
+	dest->page_height = surface->height;
+	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))
+	    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++;
+    }
+
+    return status;
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
+				  const char             *name,
+				  const char             *attributes)
+{
+    cairo_int_status_t status;
+    cairo_tag_type_t tag_type;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    void *ptr;
+
+    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) {
+	status = _cairo_pdf_interchange_begin_structure_tag (surface, tag_type, name, attributes);
+	if (unlikely (status))
+	    return status;
+    }
+
+    if (tag_type & TAG_TYPE_DEST) {
+	status = _cairo_pdf_interchange_begin_dest_tag (surface, tag_type, name, attributes);
+	if (unlikely (status))
+	    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;
+}
+
+static cairo_int_status_t
+_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;
+
+    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->annot.extents) {
+		    cairo_list_del (&tag->link);
+		    break;
+		}
+	    }
+	}
+    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
+	if (is_leaf_node (ic->current_node)) {
+	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+	    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;
+	    }
+	}
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
+				const char          *name)
+{
+    cairo_int_status_t status;
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    cairo_tag_type_t tag_type;
+    cairo_tag_stack_elem_t *elem;
+
+    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);
+
+    if (unlikely (status))
+	return status;
+
+    tag_type = _cairo_tag_get_type (name);
+    if (tag_type & TAG_TYPE_STRUCTURE) {
+	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_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;
+
+    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);
+	    } else {
+		tag->extents = *extents;
+		tag->valid = TRUE;
+	    }
+	}
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+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;
+
+    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;
+    } 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);
+	}
+    }
+
+    return status;
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+    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))
+	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+    }
+
+    return status;
+}
+
+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;
+
+    return cairo_pdf_interchange_write_page_parent_elems (surface);
+}
+
+cairo_int_status_t
+_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;
+
+    status = cairo_pdf_interchange_write_parent_tree (surface);
+    if (unlikely (status))
+	return status;
+
+    status = cairo_pdf_interchange_write_struct_tree (surface);
+    if (unlikely (status))
+	return status;
+
+    if (_cairo_tag_stack_get_structure_type (&ic->analysis_tag_stack) == TAG_TREE_TYPE_TAGGED)
+	surface->tagged = TRUE;
+
+    status = cairo_pdf_interchange_write_names_dict (surface);
+
+    return status;
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    _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);
+
+    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->parent_tree, sizeof(cairo_pdf_resource_t));
+    _cairo_array_init (&ic->mcid_to_tree, sizeof(cairo_pdf_struct_tree_node_t *));
+    ic->parent_tree_res.id = 0;
+    cairo_list_init (&ic->extents_list);
+    ic->named_dests = _cairo_hash_table_create (_named_dest_equal);
+    if (unlikely (ic->named_dests == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    ic->num_dests = 0;
+    ic->sorted_dests = NULL;
+    ic->dests_res.id = 0;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_int_status_t
+_cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_interchange_t *ic = &surface->interchange;
+
+    _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_array_fini (&ic->parent_tree);
+    _cairo_hash_table_foreach (ic->named_dests, _named_dest_pluck, ic->named_dests);
+    _cairo_hash_table_destroy (ic->named_dests);
+    free (ic->sorted_dests);
+
+    return CAIRO_STATUS_SUCCESS;
+}
diff --git a/src/cairo-pdf-operators-private.h b/src/cairo-pdf-operators-private.h
index 4314a04..ed8be7f 100644
--- a/src/cairo-pdf-operators-private.h
+++ b/src/cairo-pdf-operators-private.h
@@ -173,4 +173,12 @@ _cairo_pdf_operators_show_text_glyphs (cairo_pdf_operators_t	  *pdf_operators,
 				       cairo_text_cluster_flags_t  cluster_flags,
 				       cairo_scaled_font_t	  *scaled_font);
 
+cairo_private cairo_int_status_t
+_cairo_pdf_operators_tag_begin (cairo_pdf_operators_t *pdf_operators,
+				const char            *tag_name,
+				int                    mcid);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_operators_tag_end (cairo_pdf_operators_t *pdf_operators);
+
 #endif /* CAIRO_PDF_OPERATORS_H */
diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index dec8ca0..99a8dc8 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -1554,4 +1554,41 @@ _cairo_pdf_operators_show_text_glyphs (cairo_pdf_operators_t	  *pdf_operators,
     return _cairo_output_stream_get_status (pdf_operators->stream);
 }
 
+cairo_int_status_t
+_cairo_pdf_operators_tag_begin (cairo_pdf_operators_t *pdf_operators,
+				const char            *tag_name,
+				int                    mcid)
+{
+    cairo_status_t status;
+
+    if (pdf_operators->in_text_object) {
+	status = _cairo_pdf_operators_end_text (pdf_operators);
+	if (unlikely (status))
+	    return status;
+    }
+
+    _cairo_output_stream_printf (pdf_operators->stream,
+				 "/%s << /MCID %d >> BDC\n",
+				 tag_name,
+				 mcid);
+
+    return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
+cairo_int_status_t
+_cairo_pdf_operators_tag_end (cairo_pdf_operators_t *pdf_operators)
+{
+    cairo_status_t status;
+
+    if (pdf_operators->in_text_object) {
+	status = _cairo_pdf_operators_end_text (pdf_operators);
+	if (unlikely (status))
+	    return status;
+    }
+
+    _cairo_output_stream_printf (pdf_operators->stream, "EMC\n");
+
+    return _cairo_output_stream_get_status (pdf_operators->stream);
+}
+
 #endif /* CAIRO_HAS_PDF_OPERATORS */
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 0229dbc..cbe4599 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -48,6 +48,8 @@
 #include "cairo-surface-clipper-private.h"
 #include "cairo-pdf-operators-private.h"
 #include "cairo-path-fixed-private.h"
+#include "cairo-tag-attributes-private.h"
+#include "cairo-tag-stack-private.h"
 
 typedef struct _cairo_pdf_resource {
     unsigned int id;
@@ -154,6 +156,66 @@ typedef struct _cairo_pdf_jbig2_global {
     cairo_bool_t emitted;
 } cairo_pdf_jbig2_global_t;
 
+/* cairo-pdf-interchange.c types */
+
+struct page_mcid {
+    int page;
+    int mcid;
+};
+
+struct tag_extents {
+    cairo_rectangle_int_t extents;
+    cairo_bool_t valid;
+    cairo_list_t link;
+};
+
+typedef struct _cairo_pdf_struct_tree_node {
+    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 */
+    struct {
+	struct tag_extents extents;
+	cairo_pdf_resource_t res;
+	cairo_link_attrs_t link_attrs;
+	double page_height;
+    } annot;
+    cairo_list_t link;
+} cairo_pdf_struct_tree_node_t;
+
+typedef struct _cairo_pdf_named_dest {
+    cairo_hash_entry_t base;
+    struct tag_extents extents;
+    cairo_dest_attrs_t attrs;
+    int page;
+    double page_height;
+    cairo_bool_t referenced;
+} cairo_pdf_named_dest_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 */
+    cairo_pdf_resource_t parent_tree_res;
+    cairo_list_t extents_list;
+    cairo_hash_table_t *named_dests;
+    int num_dests;
+    cairo_pdf_named_dest_t **sorted_dests;
+    cairo_pdf_resource_t dests_res;
+    int annot_page;
+
+} cairo_pdf_interchange_t;
+
+/* pdf surface data */
+
 typedef struct _cairo_pdf_surface cairo_pdf_surface_t;
 
 struct _cairo_pdf_surface {
@@ -186,6 +248,7 @@ struct _cairo_pdf_surface {
 
     cairo_pdf_resource_t next_available_resource;
     cairo_pdf_resource_t pages_resource;
+    cairo_pdf_resource_t struct_tree_root;
 
     cairo_pdf_version_t pdf_version;
     cairo_bool_t compress_content;
@@ -231,7 +294,55 @@ struct _cairo_pdf_surface {
     double current_color_blue;
     double current_color_alpha;
 
+    cairo_pdf_interchange_t interchange;
+    int page_parent_tree; /* -1 if not used */
+    cairo_array_t page_annots;
+    cairo_bool_t tagged;
+    cairo_pdf_resource_t names_dict_res;
+
     cairo_surface_t *paginated_surface;
 };
 
+cairo_private cairo_pdf_resource_t
+_cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface);
+
+cairo_private void
+_cairo_pdf_surface_update_object (cairo_pdf_surface_t	*surface,
+				  cairo_pdf_resource_t	 resource);
+
+cairo_int_status_t
+_cairo_utf8_to_pdf_string (const char *utf8, char **str_out);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_init (cairo_pdf_surface_t *surface);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_begin_page_content (cairo_pdf_surface_t *surface);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
+				  const char             *name,
+				  const char             *attributes);
+
+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_add_operation_extents (cairo_pdf_surface_t         *surface,
+					      const cairo_rectangle_int_t *extents);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface);
+
+
 #endif /* CAIRO_PDF_SURFACE_PRIVATE_H */
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 3f879fb..a32dfb6 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -209,9 +209,6 @@ typedef struct _cairo_pdf_alpha_linear_function {
     double               alpha2;
 } cairo_pdf_alpha_linear_function_t;
 
-static cairo_pdf_resource_t
-_cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface);
-
 static void
 _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface);
 
@@ -262,7 +259,7 @@ _cairo_pdf_source_surface_equal (const void *key_a, const void *key_b);
 static const cairo_surface_backend_t cairo_pdf_surface_backend;
 static const cairo_paginated_surface_backend_t cairo_pdf_surface_paginated_backend;
 
-static cairo_pdf_resource_t
+cairo_pdf_resource_t
 _cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface)
 {
     cairo_pdf_resource_t resource;
@@ -283,7 +280,7 @@ _cairo_pdf_surface_new_object (cairo_pdf_surface_t *surface)
     return resource;
 }
 
-static void
+void
 _cairo_pdf_surface_update_object (cairo_pdf_surface_t	*surface,
 				  cairo_pdf_resource_t	 resource)
 {
@@ -416,6 +413,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
         goto BAIL2;
     }
 
+    surface->struct_tree_root.id = 0;
     surface->pdf_version = CAIRO_PDF_VERSION_1_5;
     surface->compress_content = TRUE;
     surface->pdf_stream.active = FALSE;
@@ -445,6 +443,15 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
 						    surface);
     _cairo_pdf_operators_enable_actual_text(&surface->pdf_operators, TRUE);
 
+    status = _cairo_pdf_interchange_init (surface);
+    if (unlikely (status))
+	goto BAIL2;
+
+    surface->page_parent_tree = -1;
+    _cairo_array_init (&surface->page_annots, sizeof (cairo_pdf_resource_t));
+    surface->tagged = FALSE;
+    surface->names_dict_res.id = 0;
+
     surface->paginated_surface =  _cairo_paginated_surface_create (
 	                                  &surface->base,
 					  CAIRO_CONTENT_COLOR_ALPHA,
@@ -742,6 +749,7 @@ _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface)
     }
     _cairo_array_truncate (&surface->smask_groups, 0);
     _cairo_array_truncate (&surface->knockout_group, 0);
+    _cairo_array_truncate (&surface->page_annots, 0);
 }
 
 static void
@@ -2071,6 +2079,10 @@ _cairo_pdf_surface_finish (void *abstract_surface)
 
     _cairo_pdf_surface_write_pages (surface);
 
+    status = _cairo_pdf_interchange_write_document_objects (surface);
+    if (unlikely (status))
+	return status;
+
     info = _cairo_pdf_surface_write_info (surface);
     if (info.id == 0 && status == CAIRO_STATUS_SUCCESS)
 	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -2145,6 +2157,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     _cairo_array_fini (&surface->smask_groups);
     _cairo_array_fini (&surface->fonts);
     _cairo_array_fini (&surface->knockout_group);
+    _cairo_array_fini (&surface->page_annots);
 
     if (surface->font_subsets) {
 	_cairo_scaled_font_subsets_destroy (surface->font_subsets);
@@ -2164,13 +2177,15 @@ _cairo_pdf_surface_finish (void *abstract_surface)
 
     _cairo_surface_clipper_reset (&surface->clipper);
 
-    return status;
+    return _cairo_pdf_interchange_fini (surface);
 }
 
 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_int_status_t status;
 
     /* Document header */
     if (! surface->header_emitted) {
@@ -2196,6 +2211,14 @@ _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)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = _cairo_array_append (&surface->pages, &page);
+    if (unlikely (status))
+	return status;
+
     return CAIRO_STATUS_SUCCESS;
 }
 
@@ -4678,6 +4701,10 @@ _cairo_pdf_surface_show_page (void *abstract_surface)
     cairo_pdf_surface_t *surface = abstract_surface;
     cairo_int_status_t status;
 
+    status = _cairo_pdf_interchange_end_page_content (surface);
+    if (unlikely (status))
+	return status;
+
     status = _cairo_pdf_surface_close_content_stream (surface);
     if (unlikely (status))
 	return status;
@@ -4769,8 +4796,8 @@ _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
 				 "endobj\n");
 }
 
-static cairo_int_status_t
-_utf8_to_pdf_string (const char *utf8, char **str_out)
+cairo_int_status_t
+_cairo_utf8_to_pdf_string (const char *utf8, char **str_out)
 {
     int i;
     int len;
@@ -5115,7 +5142,7 @@ _cairo_pdf_surface_emit_cff_font (cairo_pdf_surface_t		*surface,
     if (subset->family_name_utf8) {
 	char *pdf_str;
 
-	status = _utf8_to_pdf_string (subset->family_name_utf8, &pdf_str);
+	status = _cairo_utf8_to_pdf_string (subset->family_name_utf8, &pdf_str);
 	if (likely (status == CAIRO_INT_STATUS_SUCCESS)) {
 	    _cairo_output_stream_printf (surface->output,
 					 "   /FontFamily %s\n",
@@ -5561,7 +5588,7 @@ _cairo_pdf_surface_emit_truetype_font_subset (cairo_pdf_surface_t		*surface,
     if (subset.family_name_utf8) {
 	char *pdf_str;
 
-	status = _utf8_to_pdf_string (subset.family_name_utf8, &pdf_str);
+	status = _cairo_utf8_to_pdf_string (subset.family_name_utf8, &pdf_str);
 	if (likely (status == CAIRO_INT_STATUS_SUCCESS)) {
 	    _cairo_output_stream_printf (surface->output,
 					 "   /FontFamily %s\n",
@@ -6066,12 +6093,30 @@ _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface)
     _cairo_output_stream_printf (surface->output,
 				 "%d 0 obj\n"
 				 "<< /Type /Catalog\n"
-				 "   /Pages %d 0 R\n"
-				 ">>\n"
-				 "endobj\n",
+				 "   /Pages %d 0 R\n",
 				 catalog.id,
 				 surface->pages_resource.id);
 
+    if (surface->struct_tree_root.id != 0) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /StructTreeRoot %d 0 R\n",
+				     surface->struct_tree_root.id);
+	if (surface->tagged) {
+	    _cairo_output_stream_printf (surface->output,
+					 "   /MarkInfo << /Marked true >>\n");
+	}
+    }
+
+    if (surface->names_dict_res.id != 0) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /Names %d 0 R\n",
+				     surface->names_dict_res.id);
+    }
+
+    _cairo_output_stream_printf (surface->output,
+				 ">>\n"
+				 "endobj\n");
+
     return catalog;
 }
 
@@ -6436,9 +6481,14 @@ _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 page, knockout, res;
+    cairo_pdf_resource_t knockout, res;
+    cairo_pdf_resource_t *page;
     cairo_int_status_t status;
-    unsigned int i, len;
+    unsigned int i, len, page_num, num_annots;
+
+    status = _cairo_pdf_interchange_write_page_objects (surface);
+    if (unlikely (status))
+	return status;
 
     _cairo_pdf_group_resources_clear (&surface->resources);
     if (surface->has_fallback_images) {
@@ -6492,10 +6542,9 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 	    return status;
     }
 
-    page = _cairo_pdf_surface_new_object (surface);
-    if (page.id == 0)
-	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
+    page_num = _cairo_array_num_elements (&surface->pages);
+    page = _cairo_array_index (&surface->pages, page_num - 1);
+    _cairo_pdf_surface_update_object (surface, *page);
     _cairo_output_stream_printf (surface->output,
 				 "%d 0 obj\n"
 				 "<< /Type /Page\n"
@@ -6508,19 +6557,36 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 				 "      /I true\n"
 				 "      /CS /DeviceRGB\n"
 				 "   >>\n"
-				 "   /Resources %d 0 R\n"
-				 ">>\n"
-				 "endobj\n",
-				 page.id,
+				 "   /Resources %d 0 R\n",
+				 page->id,
 				 surface->pages_resource.id,
 				 surface->width,
 				 surface->height,
 				 surface->content.id,
 				 surface->content_resources.id);
 
-    status = _cairo_array_append (&surface->pages, &page);
-    if (unlikely (status))
-	return status;
+    if (surface->page_parent_tree >= 0) {
+	_cairo_output_stream_printf (surface->output,
+				     "   /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->output,
+				     "   /Annots [ ");
+	for (i = 0; i < num_annots; i++) {
+	    _cairo_array_copy_element (&surface->page_annots, i, &res);
+	    _cairo_output_stream_printf (surface->output,
+					 "%d 0 R ",
+					 res.id);
+	}
+	_cairo_output_stream_printf (surface->output, "]\n");
+    }
+
+    _cairo_output_stream_printf (surface->output,
+				 ">>\n"
+				 "endobj\n");
 
     status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface);
     if (unlikely (status))
@@ -6764,7 +6830,11 @@ _cairo_pdf_surface_start_fallback (cairo_pdf_surface_t *surface)
     bbox.p1.y = 0;
     bbox.p2.x = surface->width;
     bbox.p2.y = surface->height;
-    return _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE);
+    status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE);
+    if (unlikely (status))
+	return status;
+
+    return _cairo_pdf_interchange_begin_page_content (surface);
 }
 
 /* If source is an opaque image and mask is an image and both images
@@ -7040,6 +7110,10 @@ _cairo_pdf_surface_paint (void			*abstract_surface,
     if (unlikely (status))
 	return status;
 
+    status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
+    if (unlikely (status))
+	return status;
+
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
 	goto cleanup;
@@ -7164,6 +7238,10 @@ _cairo_pdf_surface_mask (void			*abstract_surface,
     if (unlikely (status))
 	return status;
 
+    status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
+    if (unlikely (status))
+	return status;
+
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	cairo_int_status_t source_status, mask_status;
 
@@ -7333,6 +7411,10 @@ _cairo_pdf_surface_stroke (void			*abstract_surface,
 	    goto cleanup;
     }
 
+    status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
+    if (unlikely (status))
+	goto cleanup;
+
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
 	goto cleanup;
@@ -7467,6 +7549,10 @@ _cairo_pdf_surface_fill (void			*abstract_surface,
 	    goto cleanup;
     }
 
+    status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
+    if (unlikely (status))
+	goto cleanup;
+
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
 	goto cleanup;
@@ -7685,6 +7771,10 @@ _cairo_pdf_surface_fill_stroke (void			*abstract_surface,
 	    goto cleanup;
     }
 
+    status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
+    if (unlikely (status))
+	goto cleanup;
+
     fill_pattern_res.id = 0;
     gstate_res.id = 0;
     status = _cairo_pdf_surface_add_pdf_pattern (surface, fill_source,
@@ -7780,6 +7870,10 @@ _cairo_pdf_surface_show_text_glyphs (void			*abstract_surface,
     if (unlikely (status))
 	return status;
 
+    status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
+    if (unlikely (status))
+	return status;
+
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 	status = _cairo_pdf_surface_analyze_operation (surface, op, source, &extents.bounded);
 	goto cleanup;
@@ -7918,12 +8012,39 @@ _cairo_pdf_surface_get_supported_mime_types (void		 *abstract_surface)
 }
 
 static cairo_int_status_t
+_cairo_pdf_surface_tag (void			   *abstract_surface,
+			cairo_bool_t                begin,
+			const char                 *tag_name,
+			const char                 *attributes,
+			const cairo_pattern_t	   *source,
+			const cairo_stroke_style_t *style,
+			const cairo_matrix_t	   *ctm,
+			const cairo_matrix_t	   *ctm_inverse,
+			const cairo_clip_t	   *clip)
+{
+    cairo_pdf_surface_t *surface = abstract_surface;
+    cairo_int_status_t status = 0;
+
+    if (begin)
+	status = _cairo_pdf_interchange_tag_begin (surface, tag_name, attributes);
+    else
+	status = _cairo_pdf_interchange_tag_end (surface, tag_name);
+
+    return status;
+}
+
+static cairo_int_status_t
 _cairo_pdf_surface_set_paginated_mode (void			*abstract_surface,
 				       cairo_paginated_mode_t	 paginated_mode)
 {
     cairo_pdf_surface_t *surface = 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;
+
     if (paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
 	surface->surface_extents.x = 0;
 	surface->surface_extents.y = 0;
@@ -7969,6 +8090,7 @@ static const cairo_surface_backend_t cairo_pdf_surface_backend = {
     _cairo_pdf_surface_has_show_text_glyphs,
     _cairo_pdf_surface_show_text_glyphs,
     _cairo_pdf_surface_get_supported_mime_types,
+    _cairo_pdf_surface_tag,
 };
 
 static const cairo_paginated_surface_backend_t
diff --git a/src/cairo-region.c b/src/cairo-region.c
index b738c44..c1d35e1 100644
--- a/src/cairo-region.c
+++ b/src/cairo-region.c
@@ -110,6 +110,7 @@ _cairo_region_create_in_error (cairo_status_t status)
     case CAIRO_STATUS_PNG_ERROR:
     case CAIRO_STATUS_FREETYPE_ERROR:
     case CAIRO_STATUS_WIN32_GDI_ERROR:
+    case CAIRO_STATUS_TAG_ERROR:
     default:
 	_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
 	return (cairo_region_t *) &_cairo_region_nil;
diff --git a/src/cairo-spans.c b/src/cairo-spans.c
index d20cd5a..59452c0 100644
--- a/src/cairo-spans.c
+++ b/src/cairo-spans.c
@@ -131,6 +131,7 @@ _cairo_scan_converter_create_in_error (cairo_status_t status)
     case CAIRO_STATUS_PNG_ERROR:
     case CAIRO_STATUS_FREETYPE_ERROR:
     case CAIRO_STATUS_WIN32_GDI_ERROR:
+    case CAIRO_STATUS_TAG_ERROR:
     default:
 	break;
     }
@@ -247,6 +248,7 @@ _cairo_span_renderer_create_in_error (cairo_status_t status)
     case CAIRO_STATUS_PNG_ERROR: RETURN_NIL;
     case CAIRO_STATUS_FREETYPE_ERROR: RETURN_NIL;
     case CAIRO_STATUS_WIN32_GDI_ERROR: RETURN_NIL;
+    case CAIRO_STATUS_TAG_ERROR: RETURN_NIL;
     default:
 	break;
     }
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index 8a83d55..8f05db8 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -2764,6 +2764,7 @@ _cairo_surface_create_in_error (cairo_status_t status)
     case CAIRO_STATUS_PNG_ERROR:
     case CAIRO_STATUS_FREETYPE_ERROR:
     case CAIRO_STATUS_WIN32_GDI_ERROR:
+    case CAIRO_STATUS_TAG_ERROR:
     default:
 	_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
 	return (cairo_surface_t *) &_cairo_surface_nil;
diff --git a/src/cairo-tag-attributes-private.h b/src/cairo-tag-attributes-private.h
new file mode 100644
index 0000000..69fd246
--- /dev/null
+++ b/src/cairo-tag-attributes-private.h
@@ -0,0 +1,73 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2016 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Adrian Johnson.
+ *
+ * Contributor(s):
+ *	Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#ifndef CAIRO_TAG_ATTRIBUTES_PRIVATE_H
+#define CAIRO_TAG_ATTRIBUTES_PRIVATE_H
+
+typedef enum {
+    TAG_LINK_INVALID = 0,
+    TAG_LINK_EMPTY,
+    TAG_LINK_DEST,
+    TAG_LINK_URI,
+    TAG_LINK_FILE,
+} cairo_tag_link_type_t;
+
+typedef struct _cairo_link_attrs {
+    cairo_tag_link_type_t link_type;
+    cairo_array_t rects; /* array of cairo_rectangle_t */
+    char *dest;
+    char *uri;
+    char *file;
+    int page;
+    cairo_point_double_t pos;
+} cairo_link_attrs_t;
+
+typedef struct _cairo_dest_attrs {
+    char *name;
+    double x;
+    double y;
+    cairo_bool_t x_valid;
+    cairo_bool_t y_valid;
+    cairo_bool_t internal;
+} cairo_dest_attrs_t;
+
+cairo_private cairo_int_status_t
+_cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs);
+
+cairo_private cairo_int_status_t
+_cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *dest_attrs);
+
+#endif /* CAIRO_TAG_ATTRIBUTES_PRIVATE_H */
diff --git a/src/cairo-tag-attributes.c b/src/cairo-tag-attributes.c
new file mode 100644
index 0000000..5343308
--- /dev/null
+++ b/src/cairo-tag-attributes.c
@@ -0,0 +1,570 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2016 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Adrian Johnson.
+ *
+ * Contributor(s):
+ *	Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "cairoint.h"
+
+#include "cairo-array-private.h"
+#include "cairo-list-inline.h"
+#include "cairo-tag-attributes-private.h"
+
+#include <string.h>
+
+typedef enum {
+    ATTRIBUTE_BOOL,  /* Either true/false or 1/0 may be used. */
+    ATTRIBUTE_INT,
+    ATTRIBUTE_FLOAT, /* Decimal separator is in current locale. */
+    ATTRIBUTE_STRING, /* Enclose in single quotes. String escapes:
+                       *   \'  - single quote
+                       *   \\  - backslash
+                       */
+} attribute_type_t;
+
+typedef struct _attribute_spec {
+    const char *name;
+    attribute_type_t type;
+    int array_size; /* 0 = scalar, -1 = variable size array */
+} attribute_spec_t;
+
+/*
+ * name [required] Unique name of this destination (UTF-8)
+ * x    [optional] x coordinate of destination on page. Default is x coord of
+ *                 extents of operations enclosed by the dest begin/end tags.
+ * y    [optional] y coordinate of destination on page. Default is y coord of
+ *                 extents of operations enclosed by the dest begin/end tags.
+ * internal [optional] If true, the name may be optimized out of the PDF where
+ *                     possible. Default false.
+ */
+static attribute_spec_t _dest_attrib_spec[] = {
+    { "name",     ATTRIBUTE_STRING },
+    { "x",        ATTRIBUTE_FLOAT },
+    { "y",        ATTRIBUTE_FLOAT },
+    { "internal", ATTRIBUTE_BOOL },
+    { NULL }
+};
+
+/*
+ * rect [optional] One or more rectangles to define link region. Default
+ *                 is the extents of the operations enclosed by the link begin/end tags.
+ *                 Each rectangle is specified by four array elements: x, y, width, height.
+ *                 ie the array size must be a multiple of four.
+ *
+ * Internal Links
+ * --------------
+ * either:
+ *   dest - name of dest tag in the PDF file to link to (UTF8)
+ * or
+ *   page - Page number in the PDF file to link to
+ *   pos  - [optional] Position of destination on page. Default is 0,0.
+ *
+ * URI Links
+ * ---------
+ * uri [required] Uniform resource identifier (ASCII).
+
+ * File Links
+ * ----------
+ * file - [required] File name of PDF file to link to.
+ *   either:
+ *     dest - name of dest tag in the PDF file to link to (UTF8)
+ *   or
+ *     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[] =
+{
+    { "rect", ATTRIBUTE_FLOAT, -1 },
+    { "dest", ATTRIBUTE_STRING },
+    { "uri",  ATTRIBUTE_STRING },
+    { "file", ATTRIBUTE_STRING },
+    { "page", ATTRIBUTE_INT },
+    { "pos",  ATTRIBUTE_FLOAT, 2 },
+    { NULL }
+};
+
+typedef union {
+    cairo_bool_t b;
+    int i;
+    double f;
+    char *s;
+} attrib_val_t;
+
+typedef struct _attribute {
+    char *name;
+    attribute_type_t type;
+    int array_len; /* 0 = scalar */
+    attrib_val_t scalar;
+    cairo_array_t array; /* array of attrib_val_t */
+    cairo_list_t link;
+} attribute_t;
+
+static const char *
+skip_space (const char *p)
+{
+    while (_cairo_isspace (*p))
+	p++;
+
+    return p;
+}
+
+static const char *
+parse_bool (const char *p, cairo_bool_t *b)
+{
+    if (*p == '1') {
+	*b = TRUE;
+	return p + 1;
+    } else if (*p == '0') {
+	*b = FALSE;
+	return p + 1;
+    } else if (strcmp (p, "true") == 0) {
+	*b = TRUE;
+	return p + 4;
+    } else if (strcmp (p, "false") == 0) {
+	*b = FALSE;
+	return p + 5;
+    }
+
+    return NULL;
+}
+
+static const char *
+parse_int (const char *p, int *i)
+{
+    int n;
+
+    if (sscanf(p, "%d%n", i, &n) > 0)
+	return p + n;
+
+    return NULL;
+}
+
+static const char *
+parse_float (const char *p, double *d)
+{
+    int n;
+
+    if (sscanf(p, "%lf%n", d, &n) > 0)
+	return p + n;
+
+    return NULL;
+}
+
+static const char *
+decode_string (const char *p, int *len, char *s)
+{
+    if (*p != '\'')
+	return NULL;
+
+    p++;
+    if (! *p)
+	return NULL;
+
+    *len = 0;
+    while (*p) {
+	if (*p == '\\') {
+	    p++;
+	    if (*p) {
+		if (s)
+		    *s++ = *p;
+		p++;
+		(*len)++;
+	    }
+	} else if (*p == '\'') {
+	    return p + 1;
+	} else {
+	    if (s)
+		*s++ = *p;
+	    p++;
+	    (*len)++;
+	}
+    }
+
+    return NULL;
+}
+
+static const char *
+parse_string (const char *p, char **s)
+{
+    const char *end;
+    int len;
+
+    end = decode_string (p, &len, NULL);
+    if (!end)
+	return NULL;
+
+    *s = malloc (len + 1);
+    decode_string (p, &len, *s);
+    (*s)[len] = 0;
+
+    return end;
+}
+
+static const char *
+parse_scalar (const char *p, attribute_type_t type, attrib_val_t *scalar)
+{
+    switch (type) {
+	case ATTRIBUTE_BOOL:
+	    return parse_bool (p, &scalar->b);
+	case ATTRIBUTE_INT:
+	    return parse_int (p, &scalar->i);
+	case ATTRIBUTE_FLOAT:
+	    return parse_float (p, &scalar->f);
+	case ATTRIBUTE_STRING:
+	    return parse_string (p, &scalar->s);
+    }
+
+    return NULL;
+}
+
+static cairo_int_status_t
+parse_array (const char *p, attribute_type_t type, cairo_array_t *array, const char **end)
+{
+    attrib_val_t val;
+    cairo_int_status_t status;
+
+    p = skip_space (p);
+    if (! *p)
+	return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+    if (*p++ != '[')
+	return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+    while (TRUE) {
+	p = skip_space (p);
+	if (! *p)
+	    return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+	if (*p == ']') {
+	    *end = p + 1;
+	    return CAIRO_INT_STATUS_SUCCESS;
+	}
+
+	p = parse_scalar (p, type, &val);
+	if (!p)
+	    return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+	status = _cairo_array_append (array, &val);
+	if (unlikely (status))
+	    return status;
+    }
+
+    return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+}
+
+static cairo_int_status_t
+parse_name (const char *p, const char **end, char **s)
+{
+    const char *p2;
+    char *name;
+    int len;
+
+    if (! _cairo_isalpha (*p))
+	return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+    p2 = p;
+    while (_cairo_isalpha (*p2) || _cairo_isdigit (*p2))
+	p2++;
+
+    len = p2 - p;
+    name = malloc (len + 1);
+    if (unlikely (name == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    memcpy (name, p, len);
+    name[len] = 0;
+    *s = name;
+    *end = p2;
+
+    return CAIRO_INT_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+parse_attributes (const char *attributes, attribute_spec_t *attrib_def, cairo_list_t *list)
+{
+    attribute_spec_t *def;
+    attribute_t *attrib;
+    char *name;
+    cairo_int_status_t status;
+    const char *p = attributes;
+
+    if (! p)
+	return _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+    while (*p) {
+	p = skip_space (p);
+	if (! *p)
+	    break;
+
+	status = parse_name (p, &p, &name);
+	if (status)
+	    return status;
+
+	def = attrib_def;
+	while (def->name) {
+	    if (strcmp (name, def->name) == 0)
+		break;
+	    def++;
+	}
+	if (! def->name) {
+	    status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+	    goto fail1;
+	}
+
+	attrib = calloc (1, sizeof (attribute_t));
+	if (unlikely (attrib == NULL)) {
+	    status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+	    goto fail1;
+	}
+
+	attrib->name = name;
+	attrib->type = def->type;
+	_cairo_array_init (&attrib->array, sizeof(attrib_val_t));
+
+	p = skip_space (p);
+	if (def->type == ATTRIBUTE_BOOL && *p != '=') {
+	    attrib->scalar.b = TRUE;
+	} else {
+	    if (*p++ != '=') {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto fail2;
+	    }
+
+	    if (def->array_size == 0) {
+		p = parse_scalar (p, def->type, &attrib->scalar);
+		if (!p) {
+		    status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		    goto fail2;
+		}
+
+		attrib->array_len = 0;
+	    } else {
+		status = parse_array (p, def->type, &attrib->array, &p);
+		if (unlikely (status))
+		    goto fail2;
+
+		attrib->array_len = _cairo_array_num_elements (&attrib->array);
+		if (def->array_size > 0 && attrib->array_len != def->array_size) {
+		    status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		    goto fail2;
+		}
+	    }
+	}
+
+	cairo_list_add_tail (&attrib->link, list);
+    }
+
+    return CAIRO_INT_STATUS_SUCCESS;
+
+  fail2:
+    _cairo_array_fini (&attrib->array);
+    free (attrib);
+  fail1:
+    free (name);
+
+    return status;
+}
+
+static void
+free_attributes_list (cairo_list_t *list)
+{
+    attribute_t *attr, *next;
+
+    cairo_list_foreach_entry_safe (attr, next, attribute_t, list, link)
+    {
+	cairo_list_del (&attr->link);
+	free (attr->name);
+	_cairo_array_fini (&attr->array);
+	free (attr);
+    }
+}
+
+static attribute_t *
+find_attribute (cairo_list_t *list, const char *name)
+{
+    attribute_t *attr;
+
+    cairo_list_foreach_entry (attr, attribute_t, list, link)
+    {
+	if (strcmp (attr->name, name) == 0)
+	    return attr;
+    }
+
+    return NULL;
+}
+
+cairo_int_status_t
+_cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs)
+{
+    cairo_list_t list;
+    cairo_int_status_t status;
+    attribute_t *attr;
+    attrib_val_t val;
+
+    cairo_list_init (&list);
+    status = parse_attributes (attributes, _link_attrib_spec, &list);
+    if (unlikely (status))
+	return status;
+
+    memset (link_attrs, 0, sizeof (cairo_link_attrs_t));
+    _cairo_array_init (&link_attrs->rects, sizeof (cairo_rectangle_t));
+    if (find_attribute (&list, "uri")) {
+	link_attrs->link_type = TAG_LINK_URI;
+    } else if (find_attribute (&list, "file")) {
+	link_attrs->link_type = TAG_LINK_FILE;
+    } else if (find_attribute (&list, "dest")) {
+	link_attrs->link_type = TAG_LINK_DEST;
+    } else if (find_attribute (&list, "page")) {
+	link_attrs->link_type = TAG_LINK_DEST;
+    } else {
+	link_attrs->link_type = TAG_LINK_EMPTY;
+	goto cleanup;
+    }
+
+    cairo_list_foreach_entry (attr, attribute_t, &list, link)
+    {
+	if (strcmp (attr->name, "uri") == 0) {
+	    if (link_attrs->link_type != TAG_LINK_URI) {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto cleanup;
+	    }
+
+	    link_attrs->uri = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "file") == 0) {
+	    if (link_attrs->link_type != TAG_LINK_FILE) {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto cleanup;
+	    }
+
+	    link_attrs->file = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "dest") == 0) {
+	    if (! (link_attrs->link_type == TAG_LINK_DEST ||
+		   link_attrs->link_type != TAG_LINK_FILE)) {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto cleanup;
+	    }
+
+	    link_attrs->dest = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "page") == 0) {
+	    if (! (link_attrs->link_type == TAG_LINK_DEST ||
+		   link_attrs->link_type != TAG_LINK_FILE)) {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto cleanup;
+	    }
+
+	    link_attrs->page = attr->scalar.i;
+
+	} else if (strcmp (attr->name, "pos") == 0) {
+	    if (! (link_attrs->link_type == TAG_LINK_DEST ||
+		   link_attrs->link_type != TAG_LINK_FILE)) {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto cleanup;
+	    }
+
+	    _cairo_array_copy_element (&attr->array, 0, &val);
+	    link_attrs->pos.x = val.f;
+	    _cairo_array_copy_element (&attr->array, 1, &val);
+	    link_attrs->pos.y = val.f;
+	} else if (strcmp (attr->name, "rect") == 0) {
+	    cairo_rectangle_t rect;
+	    int i;
+	    int num_elem = _cairo_array_num_elements (&attr->array);
+	    if (num_elem == 0 || num_elem % 4 != 0) {
+		status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+		goto cleanup;
+	    }
+
+	    for (i = 0; i < num_elem; i += 4) {
+		_cairo_array_copy_element (&attr->array, i, &val);
+		rect.x = val.f;
+		_cairo_array_copy_element (&attr->array, i+1, &val);
+		rect.y = val.f;
+		_cairo_array_copy_element (&attr->array, i+2, &val);
+		rect.width = val.f;
+		_cairo_array_copy_element (&attr->array, i+3, &val);
+		rect.height = val.f;
+		status = _cairo_array_append (&link_attrs->rects, &rect);
+		if (unlikely (status))
+		    goto cleanup;
+	    }
+	}
+    }
+
+  cleanup:
+    free_attributes_list (&list);
+    if (unlikely (status)) {
+	free (link_attrs->dest);
+	free (link_attrs->uri);
+	free (link_attrs->file);
+	_cairo_array_fini (&link_attrs->rects);
+    }
+
+    return status;
+}
+
+cairo_int_status_t
+_cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *dest_attrs)
+{
+    cairo_list_t list;
+    cairo_int_status_t status;
+    attribute_t *attr;
+
+    memset (dest_attrs, 0, sizeof (cairo_dest_attrs_t));
+    cairo_list_init (&list);
+    status = parse_attributes (attributes, _dest_attrib_spec, &list);
+    if (unlikely (status))
+	goto cleanup;
+
+    cairo_list_foreach_entry (attr, attribute_t, &list, link)
+    {
+	if (strcmp (attr->name, "name") == 0) {
+	    dest_attrs->name = strdup (attr->scalar.s);
+	} else if (strcmp (attr->name, "x") == 0) {
+	    dest_attrs->x = attr->scalar.f;
+	    dest_attrs->x_valid = TRUE;
+	} else if (strcmp (attr->name, "y") == 0) {
+	    dest_attrs->y = attr->scalar.f;
+	    dest_attrs->y_valid = TRUE;
+	} else if (strcmp (attr->name, "internal") == 0) {
+	    dest_attrs->internal = attr->scalar.b;
+	}
+    }
+
+    if (! dest_attrs->name)
+	status = _cairo_error (CAIRO_INT_STATUS_TAG_ERROR);
+
+  cleanup:
+    free_attributes_list (&list);
+
+    return status;
+}
diff --git a/src/cairo-tag-stack-private.h b/src/cairo-tag-stack-private.h
new file mode 100644
index 0000000..9528cb4
--- /dev/null
+++ b/src/cairo-tag-stack-private.h
@@ -0,0 +1,106 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2016 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Adrian Johnson.
+ *
+ * Contributor(s):
+ *	Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#ifndef CAIRO_TAG_STACK_PRIVATE_H
+#define CAIRO_TAG_STACK_PRIVATE_H
+
+#include "cairo-list-inline.h"
+
+/* The type of a single tag */
+typedef enum {
+    TAG_TYPE_INVALID = 0,
+    TAG_TYPE_STRUCTURE = 1,
+    TAG_TYPE_LINK = 2,
+    TAG_TYPE_DEST = 4,
+} cairo_tag_type_t;
+
+/* The type of the structure tree. */
+typedef enum _cairo_tag_stack_structure_type {
+    TAG_TREE_TYPE_TAGGED, /* compliant with Tagged PDF */
+    TAG_TREE_TYPE_STRUCTURE, /* valid structure but not 'Tagged PDF' compliant */
+    TAG_TREE_TYPE_LINK_ONLY, /* contains Link tags only */
+    TAG_TREE_TYPE_NO_TAGS, /* no tags used */
+    TAG_TREE_TYPE_INVALID, /* invalid tag structure */
+} cairo_tag_stack_structure_type_t;
+
+typedef struct _cairo_tag_stack_elem {
+    char *name;
+    char *attributes;
+    void *data;
+    cairo_list_t link;
+
+} cairo_tag_stack_elem_t;
+
+typedef struct _cairo_tag_stack {
+    cairo_list_t list;
+    cairo_tag_stack_structure_type_t type;
+    int size;
+
+} cairo_tag_stack_t;
+
+cairo_private void
+_cairo_tag_stack_init (cairo_tag_stack_t *stack);
+
+cairo_private void
+_cairo_tag_stack_fini (cairo_tag_stack_t *stack);
+
+cairo_private cairo_tag_stack_structure_type_t
+_cairo_tag_stack_get_structure_type (cairo_tag_stack_t *stack);
+
+cairo_private cairo_int_status_t
+_cairo_tag_stack_push (cairo_tag_stack_t *stack,
+		       const char        *name,
+		       const char        *attributes);
+
+cairo_private void
+_cairo_tag_stack_set_top_data (cairo_tag_stack_t *stack,
+			       void              *data);
+
+cairo_private cairo_int_status_t
+_cairo_tag_stack_pop (cairo_tag_stack_t *stack,
+		      const char *name,
+		      cairo_tag_stack_elem_t **elem);
+
+cairo_private cairo_tag_stack_elem_t *
+_cairo_tag_stack_top_elem (cairo_tag_stack_t *stack);
+
+cairo_private void
+_cairo_tag_stack_free_elem (cairo_tag_stack_elem_t *elem);
+
+cairo_private cairo_tag_type_t
+_cairo_tag_get_type (const char *name);
+
+#endif /* CAIRO_TAG_STACK_PRIVATE_H */
diff --git a/src/cairo-tag-stack.c b/src/cairo-tag-stack.c
new file mode 100644
index 0000000..858221e
--- /dev/null
+++ b/src/cairo-tag-stack.c
@@ -0,0 +1,279 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2016 Adrian Johnson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Adrian Johnson.
+ *
+ * Contributor(s):
+ *	Adrian Johnson <ajohnson at redneon.com>
+ */
+
+#include "cairoint.h"
+
+#include "cairo-tag-stack-private.h"
+
+/* Tagged PDF must have one of these tags at the top level */
+static const char * _cairo_tag_stack_tagged_pdf_top_level_element_list[] =
+{
+    "Document",
+    "Part",
+    "Art",
+    "Sect",
+    "Div",
+    NULL
+};
+
+/* List of valid tag names. Table numbers reference PDF 32000 */
+static const char * _cairo_tag_stack_struct_pdf_list[] =
+{
+    /* Table 333 - Grouping Elements */
+    "Document",
+    "Part",
+    "Art",
+    "Sect",
+    "Div",
+    "BlockQuote",
+    "Caption",
+    "TOC",
+    "TOCI",
+    "Index",
+    "NonStruct",
+    "Private",
+
+    /* Table 335 - Standard structure types for paragraphlike elements */
+    "P", "H",
+    "H1", "H2", "H3", "H4", "H5", "H6",
+
+    /* Table 336 - Standard structure types for list elements */
+    "L", "LI", "Lbl", "LBody",
+
+    /* Table 337 - Standard structure types for table elements */
+    "Table",
+    "TR", "TH", "TD",
+    "THead", "TBody", "TFoot",
+
+    /* Table 338 - Standard structure types for inline-level structure elements */
+    "Span",
+    "Quote",
+    "Note",
+    "Reference",
+    "BibEntry",
+    "Code",
+    "Link", /* CAIRO_TAG_LINK */
+    "Annot",
+    "Ruby",
+    "Warichu",
+
+    /* Table 339 - Standard structure types for Ruby and Warichu elements */
+    "RB", "RT", "RP",
+    "WT", "WP",
+
+    /* Table 340 - Standard structure types for illustration elements */
+    "Figure",
+    "Formula",
+    "Form",
+
+    NULL
+};
+
+/* List of cairo specific tag names */
+static const char * _cairo_tag_stack_cairo_tag_list[] =
+{
+    CAIRO_TAG_DEST,
+    NULL
+};
+
+void
+_cairo_tag_stack_init (cairo_tag_stack_t *stack)
+{
+    cairo_list_init (&stack->list);
+    stack->type = TAG_TREE_TYPE_NO_TAGS;
+    stack->size = 0;
+}
+
+void
+_cairo_tag_stack_fini (cairo_tag_stack_t *stack)
+{
+    while (! cairo_list_is_empty (&stack->list)) {
+	cairo_tag_stack_elem_t *elem;
+
+	elem = cairo_list_first_entry (&stack->list, cairo_tag_stack_elem_t, link);
+	cairo_list_del (&elem->link);
+	free (elem->name);
+	free (elem->attributes);
+	free (elem);
+    }
+}
+
+cairo_tag_stack_structure_type_t
+_cairo_tag_stack_get_structure_type (cairo_tag_stack_t *stack)
+{
+    return stack->type;
+}
+
+static cairo_bool_t
+name_in_list (const char *name, const char **list)
+{
+    if (! name)
+	return FALSE;
+
+    while (*list) {
+	if (strcmp (name, *list) == 0)
+	    return TRUE;
+	list++;
+    }
+
+    return FALSE;
+}
+
+cairo_int_status_t
+_cairo_tag_stack_push (cairo_tag_stack_t *stack,
+		       const char        *name,
+		       const char        *attributes)
+{
+    cairo_tag_stack_elem_t *elem;
+
+    if (! name_in_list (name, _cairo_tag_stack_struct_pdf_list) &&
+	! name_in_list (name, _cairo_tag_stack_cairo_tag_list))
+    {
+	stack->type = TAG_TYPE_INVALID;
+	return _cairo_error (CAIRO_STATUS_TAG_ERROR);
+    }
+
+    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;
+	else if (strcmp (name, "Link") == 0)
+	    stack->type = TAG_TREE_TYPE_LINK_ONLY;
+	else if (name_in_list (name, _cairo_tag_stack_struct_pdf_list))
+	    stack->type = TAG_TREE_TYPE_STRUCTURE;
+    } else {
+	if (stack->type == TAG_TREE_TYPE_LINK_ONLY &&
+	    name_in_list (name, _cairo_tag_stack_struct_pdf_list))
+	{
+	    stack->type = TAG_TREE_TYPE_STRUCTURE;
+	}
+    }
+
+    elem = malloc (sizeof(cairo_tag_stack_elem_t));
+    if (unlikely (elem == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    elem->name = strdup (name);
+    if (unlikely (elem->name == NULL))
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    if (attributes) {
+	elem->attributes = strdup (attributes);
+	if (unlikely (elem->attributes == NULL))
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+    } else {
+	elem->attributes = NULL;
+    }
+
+    elem->data = NULL;
+
+    cairo_list_add_tail (&elem->link, &stack->list);
+    stack->size++;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_private void
+_cairo_tag_stack_set_top_data (cairo_tag_stack_t *stack,
+			       void        *data)
+{
+    cairo_tag_stack_elem_t *top;
+
+    top = _cairo_tag_stack_top_elem (stack);
+    if (top)
+	top->data = data;
+}
+
+cairo_int_status_t
+_cairo_tag_stack_pop (cairo_tag_stack_t *stack,
+		      const char *name,
+		      cairo_tag_stack_elem_t **elem)
+{
+    cairo_tag_stack_elem_t *top;
+
+    top = _cairo_tag_stack_top_elem (stack);
+    if (!top) {
+	stack->type = TAG_TYPE_INVALID;
+	return _cairo_error (CAIRO_STATUS_TAG_ERROR);
+    }
+
+    cairo_list_del (&top->link);
+    stack->size--;
+    if (strcmp (top->name, name) != 0) {
+	stack->type = TAG_TYPE_INVALID;
+	_cairo_tag_stack_free_elem (top);
+	return _cairo_error (CAIRO_STATUS_TAG_ERROR);
+    }
+
+    if (elem)
+	*elem = top;
+    else
+	_cairo_tag_stack_free_elem (top);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_tag_stack_elem_t *
+_cairo_tag_stack_top_elem (cairo_tag_stack_t *stack)
+{
+    if (cairo_list_is_empty (&stack->list))
+	return NULL;
+
+    return cairo_list_last_entry (&stack->list, cairo_tag_stack_elem_t, link);
+}
+
+void
+_cairo_tag_stack_free_elem (cairo_tag_stack_elem_t *elem)
+{
+    free (elem->name);
+    free (elem->attributes);
+    free (elem);
+}
+
+cairo_tag_type_t
+_cairo_tag_get_type (const char *name)
+{
+    if (! name_in_list (name, _cairo_tag_stack_struct_pdf_list) &&
+	! name_in_list (name, _cairo_tag_stack_cairo_tag_list))
+	return TAG_TYPE_INVALID;
+
+    if (strcmp(name, "Link") == 0)
+	return (TAG_TYPE_LINK | TAG_TYPE_STRUCTURE);
+
+    if (strcmp(name, "cairo.dest") == 0)
+	return TAG_TYPE_DEST;
+
+    return TAG_TYPE_STRUCTURE;
+}
diff --git a/src/cairo.c b/src/cairo.c
index 76d8c56..02b9171 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -107,6 +107,220 @@
  * space</firstterm>.
  **/
 
+/**
+ * SECTION:cairo-tag
+ * @Title: Tags and Links
+ * @Short_Description: Hyperlinks and document structure
+ * @See_Also: #cairo_pdf_surface_t
+ *
+ * The tag functions provide the ability to specify hyperlinks and
+ * document logical structure on supported backends. The following tags are supported:
+ * * [Link][link] - Create a hyperlink
+ * * [Destinations][dest] - Create a hyperlink destination
+ * * [Document Structure Tags][doc-struct] - Create PDF Document Structure
+ *
+ * # Link Tags # {#link}
+ * A hyperlink is specified by enclosing the hyperlink text with the %CAIRO_TAG_LINK tag.
+ *
+ * For example:
+ * <informalexample><programlisting>
+ * cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://cairographics.org'");
+ * cairo_move_to (cr, 50, 50);
+ * cairo_show_text (cr, "This is a link to the cairo website.");
+ * cairo_tag_end (cr, CAIRO_TAG_LINK);
+ * </programlisting></informalexample>
+ *
+ * The PDF backend uses one or more rectangles to define the clickable
+ * area of the link.  By default cairo will use the extents of the
+ * drawing operations enclosed by the begin/end link tags to define the
+ * clickable area. In some cases, such as a link split across two
+ * lines, the default rectangle is undesirable.
+ *
+ * @rect: [optional] The "rect" attribute allows the application to
+ * specify one or more rectangles that form the clickable region.  The
+ * value of this attribute is an array of floats. Each rectangle is
+ * specified by four elements in the array: x, y, width, height. The
+ * array size must be a multiple of four.
+ *
+ * An example of creating a link with user specified clickable region:
+ * <informalexample><programlisting>
+ * cairo_font_extents_t font_extents;
+ * cairo_text_extents_t text1_extents;
+ * cairo_text_extents_t text2_extents;
+ * char attribs[100];
+ * const char *text1 = "This link is split";
+ * const char *text2 = "across two lines";
+ *
+ * cairo_font_extents (cr, &font_extents);
+ * cairo_move_to (cr, 450, 50);
+ * cairo_text_extents (cr, text1, &text1_extents);
+ * cairo_move_to (cr, 50, 70);
+ * cairo_text_extents (cr, text2, &text2_extents);
+ * sprintf (attribs,
+ *          "rect=[%f %f %f %f %f %f %f %f] uri='http://cairographics.org'",
+ *          text1_extents.x_bearing,
+ *          text1_extents.y_bearing,
+ *          text1_extents.width,
+ *          text1_extents.height,
+ *          text2_extents.x_bearing,
+ *          text2_extents.y_bearing,
+ *          text2_extents.width,
+ *          text2_extents.height);
+ *
+ * cairo_tag_begin (cr, CAIRO_TAG_LINK, attribs);
+ * cairo_show_text (cr, "This is a link to the cairo website");
+ * cairo_move_to (cr, 450, 50);
+ * cairo_show_text (cr, text1);
+ * cairo_move_to (cr, 50, 70);
+ * cairo_show_text (cr, text2);
+ * cairo_tag_end (cr, CAIRO_TAG_LINK);
+ * </programlisting></informalexample>
+ *
+ * There are three types of links. Each type has its own attributes as detailed below.
+ * * [Internal Links][internal-link] - A link to a location in the same document
+ * * [URI Links][uri-link] - A link to a Uniform resource identifier
+ * * [File Links][file-link] - A link to a location in another document
+ *
+ * ## Internal Links ## {#internal-link}
+ * An internal link is a link to a location in the same document. The destination
+ * is specified with either:
+ *
+ * @dest: a UTF-8 string specifying the destination in the PDF file to link
+ * to. Destinations are created with the %CAIRO_TAG_DEST tag.
+ *
+ * or the two attributes:
+ *
+ * @page: An integer specifying the page number in the PDF file to link to.
+ *
+ * @pos: [optional] An array of two floats specifying the x,y position
+ * on the page. Default is 0,0.
+ *
+ * An example of the link attributes to link to a page and x,y position:
+ * <programlisting>
+ * "page=3 pos=[3.1 6.2]"
+ * </programlisting>
+ *
+ * ## URI Links ## {#uri-link}
+ * A URI link is a link to a Uniform Resource Identifier ([RFC 2396](http://tools.ietf.org/html/rfc2396)).
+ *
+ * A URI is specified with the following attribute:
+ *
+ * @uri: An ASCII string specifying the URI.
+ *
+ * An example of the link attributes to the cairo website:
+ * <programlisting>
+ * "uri='http://cairographics.org'"
+ * </programlisting>
+ *
+ * ## File Links ## {#file-link}
+ * A file link is a link a location in another PDF file.
+ *
+ * The file attribute (required) specifies the name of the PDF file:
+ *
+ * @file: File name of PDF file to link to.
+ *
+ * The position is specified by either:
+ *
+ *  @dest: a UTF-8 string specifying the named destination in the PDF file.
+ *
+ * or
+ *
+ *  @page: An integer specifying the page number in the PDF file.
+ *
+ *  @pos: [optional] An array of two floats specifying the x,y position
+ *  on the page. Default is 0,0.
+ *
+ * An example of the link attributes to PDF file:
+ * <programlisting>
+ * "file='document.pdf' page=16 pos=[25 40]"
+ * </programlisting>
+ *
+ * # Destination Tags # {#dest}
+
+ * A destination is specified by enclosing the destination drawing
+ * operations with the %CAIRO_TAG_DEST tag.
+ *
+ * @name: [required] A UTF-8 string specifying the name of this destination.
+ *
+ * @x: [optional] A float specifying the x coordinate of destination
+ *                 position on this page. If not specified the default
+ *                 x coordinate is the left side of the extents of the
+ *                 operations enclosed by the %CAIRO_TAG_DEST begin/end tags. If
+ *                 no operations are enclosed, the x coordidate is 0.
+ *
+ * @y: [optional] A float specifying the y coordinate of destination
+ *                 position on this page. If not specified the default
+ *                 y coordinate is the top of the extents of the
+ *                 operations enclosed by the %CAIRO_TAG_DEST begin/end tags. If
+ *                 no operations are enclosed, the y coordidate is 0.
+ *
+ * @internal: A boolean that if true, the destination name may be
+ *            ommitted from PDF where possible. In this case, links
+ *            refer directly to the page and position instead of via
+ *            the named destination table. Note that if this
+ *            destination is referenced by another PDF (see [File Links][file-link]),
+ *            this attribute must be false. Default is false.
+ *
+ * <informalexample><programlisting>
+ * /* Create a hyperlink */
+ * cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='mydest' internal");
+ * cairo_move_to (cr, 50, 50);
+ * cairo_show_text (cr, "This is a hyperlink.");
+ * cairo_tag_end (cr, CAIRO_TAG_LINK);
+ *
+ * /* Create a destination */
+ * cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='mydest'");
+ * cairo_move_to (cr, 50, 250);
+ * cairo_show_text (cr, "This paragraph is the destination of the above link.");
+ * cairo_tag_end (cr, CAIRO_TAG_DEST);
+ * </programlisting></informalexample>
+ *
+ * # Document Structure (PDF) # {#doc-struct}
+ *
+ * The document structure tags provide a means of specifying structural information
+ * such as headers, paragraphs, tables, and figures. The inclusion of structural information faciliates:
+ * * Extraction of text and graphics for copy and paste
+ * * Reflow of text and graphics in the viewer
+ * * Proccessing text eg searching and indexing
+ * * Conversion to other formats
+ * * Accessability support
+ *
+ * The list of structure types is specified in section 14.8.4 of the
+ * [PDF Reference](http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf).
+ *
+ * Note the PDF "Link" structure tag is the same as the cairo %CAIRO_TAG_LINK tag.
+ *
+ * The following example creates a document structure for a document containing two section, each with
+ * a header and a paragraph.
+ *
+ * <informalexample><programlisting>
+ * cairo_tag_begin (cr, "Document", NULL);
+ *
+ * cairo_tag_begin (cr, "Sect", NULL);
+ * cairo_tag_begin (cr, "H1", NULL);
+ * cairo_show_text (cr, "Heading 1");
+ * cairo_tag_end (cr, "H1");
+ *
+ * cairo_tag_begin (cr, "P", NULL);
+ * cairo_show_text (cr, "Paragraph 1");
+ * cairo_tag_end (cr, "P");
+ * cairo_tag_end (cr, "Sect");
+ *
+ * cairo_tag_begin (cr, "Sect", NULL);
+ * cairo_tag_begin (cr, "H1", NULL);
+ * cairo_show_text (cr, "Heading 2");
+ * cairo_tag_end (cr, "H1");
+ *
+ * cairo_tag_begin (cr, "P", NULL);
+ * cairo_show_text (cr, "Paragraph 2");
+ * cairo_tag_end (cr, "P");
+ * cairo_tag_end (cr, "Sect");
+ *
+ * cairo_tag_end (cr, "Document");
+ * </programlisting></informalexample>
+ *
+ **/
+
 #define DEFINE_NIL_CONTEXT(status)					\
     {									\
 	CAIRO_REFERENCE_COUNT_INVALID,	/* ref_count */			\
@@ -156,7 +370,8 @@ static const cairo_t _cairo_nil[] = {
     DEFINE_NIL_CONTEXT (CAIRO_STATUS_JBIG2_GLOBAL_MISSING),
     DEFINE_NIL_CONTEXT (CAIRO_STATUS_PNG_ERROR),
     DEFINE_NIL_CONTEXT (CAIRO_STATUS_FREETYPE_ERROR),
-    DEFINE_NIL_CONTEXT (CAIRO_STATUS_WIN32_GDI_ERROR)
+    DEFINE_NIL_CONTEXT (CAIRO_STATUS_WIN32_GDI_ERROR),
+    DEFINE_NIL_CONTEXT (CAIRO_STATUS_TAG_ERROR)
 
 };
 COMPILE_TIME_ASSERT (ARRAY_LENGTH (_cairo_nil) == CAIRO_STATUS_LAST_STATUS - 1);
@@ -2667,6 +2882,24 @@ cairo_copy_clip_rectangle_list (cairo_t *cr)
 }
 
 /**
+ * CAIRO_TAG_DEST:
+ *
+ * Create a destination for a hyperlink. Destination tag attributes
+ * are detailed at [Destinations][dests].
+ *
+ * Since: 1.16
+ **/
+
+/**
+ * CAIRO_TAG_LINK:
+ *
+ * Create hyperlink. Link tag attributes are detailed at
+ * [Links][links].
+ *
+ * Since: 1.16
+ **/
+
+/**
  * cairo_tag_begin:
  * @cr: a cairo context
  * @tag_name: tag name
@@ -2676,6 +2909,30 @@ cairo_copy_clip_rectangle_list (cairo_t *cr)
  * cairo_tag_end() with the same @tag_name to mark the end of the
  * structure.
  *
+ * The attributes string is of the form "key1=value2 key2=value2 ...".
+ * Values may be boolean (true/false or 1/0), integer, float, string,
+ * or an array.
+ *
+ * String values are enclosed in single quotes
+ * ('). Single quotes and backslashes inside the string should be
+ * escaped with a backslash.
+ *
+ * Boolean values may be set to true by only
+ * specifying the key. eg the attribute string "key" is the equivalent
+ * to "key=true".
+ *
+ * Arrays are enclosed in '[]'. eg "rect=[1.2 4.3 2.0 3.0]".
+ *
+ * If no attributes are required, @attributes can be an empty string or NULL.
+ *
+ * See [Tags and Links Description][cairo-Tags-and-Links.description]
+ * for the list of tags and attributes.
+ *
+ * Invalid nesting of tags or invalid attributes will cause @cr to
+ * shutdown with a status of %CAIRO_STATUS_TAG_ERROR.
+ *
+ * See cairo_tag_end().
+ *
  * Since: 1.16
  **/
 void
@@ -2698,6 +2955,9 @@ cairo_tag_begin (cairo_t *cr, const char *tag_name, const char *attributes)
  *
  * Marks the end of the @tag_name structure.
  *
+ * Invalid nesting of tags will cause @cr to shutdown with a status of
+ * %CAIRO_STATUS_TAG_ERROR.
+ *
  * See cairo_tag_begin().
  *
  * Since: 1.16
diff --git a/src/cairo.h b/src/cairo.h
index 9763768..32fc88b 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -295,6 +295,7 @@ typedef struct _cairo_user_data_key {
  * @CAIRO_STATUS_PNG_ERROR: error occurred in libpng while reading from or writing to a PNG file (Since 1.16)
  * @CAIRO_STATUS_FREETYPE_ERROR: error occurred in libfreetype (Since 1.16)
  * @CAIRO_STATUS_WIN32_GDI_ERROR: error occurred in the Windows Graphics Device Interface (Since 1.16)
+ * @CAIRO_STATUS_TAG_ERROR: invalid tag name, attributes, or nesting (Since 1.16)
  * @CAIRO_STATUS_LAST_STATUS: this is a special value indicating the number of
  *   status values defined in this enumeration.  When using this value, note
  *   that the version of cairo at run-time may have additional status values
@@ -354,6 +355,7 @@ typedef enum _cairo_status {
     CAIRO_STATUS_PNG_ERROR,
     CAIRO_STATUS_FREETYPE_ERROR,
     CAIRO_STATUS_WIN32_GDI_ERROR,
+    CAIRO_STATUS_TAG_ERROR,
 
     CAIRO_STATUS_LAST_STATUS
 } cairo_status_t;
@@ -1026,6 +1028,9 @@ cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list);
 
 /* Logical structure tagging functions */
 
+#define CAIRO_TAG_DEST "cairo.dest"
+#define CAIRO_TAG_LINK "Link"
+
 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 f1cd075..493d461 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -282,6 +282,12 @@ _cairo_isdigit (int c)
     return (c >= '0' && c <= '9');
 }
 
+static inline int cairo_const
+_cairo_isalpha (int c)
+{
+    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
 #include "cairo-types-private.h"
 #include "cairo-cache-private.h"
 #include "cairo-reference-count-private.h"
commit 25da407a5f1d136345759c0d0a2a1d985eb2b392
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 17:30:13 2016 +0930

    Support tag operations in analysis and paginated surface

diff --git a/src/cairo-analysis-surface.c b/src/cairo-analysis-surface.c
index 58b02e7..a968f40 100644
--- a/src/cairo-analysis-surface.c
+++ b/src/cairo-analysis-surface.c
@@ -728,6 +728,39 @@ _cairo_analysis_surface_show_text_glyphs (void			    *abstract_surface,
     return _add_operation (surface, &extents, backend_status);
 }
 
+static cairo_int_status_t
+_cairo_analysis_surface_tag (void	                *abstract_surface,
+			     cairo_bool_t                begin,
+			     const char                 *tag_name,
+			     const char                 *attributes,
+			     const cairo_pattern_t	*source,
+			     const cairo_stroke_style_t	*stroke_style,
+			     const cairo_matrix_t	*ctm,
+			     const cairo_matrix_t	*ctm_inverse,
+			     const cairo_clip_t	        *clip)
+{
+    cairo_analysis_surface_t *surface = abstract_surface;
+    cairo_int_status_t	     backend_status;
+
+    backend_status = CAIRO_INT_STATUS_SUCCESS;
+    if (surface->target->backend->tag != NULL) {
+	backend_status =
+	    surface->target->backend->tag (surface->target,
+					   begin,
+					   tag_name,
+					   attributes,
+					   source,
+					   stroke_style,
+					   ctm,
+					   ctm_inverse,
+					   clip);
+	if (_cairo_int_status_is_error (backend_status))
+	    return backend_status;
+    }
+
+    return backend_status;
+}
+
 static const cairo_surface_backend_t cairo_analysis_surface_backend = {
     CAIRO_INTERNAL_SURFACE_TYPE_ANALYSIS,
 
@@ -760,7 +793,9 @@ static const cairo_surface_backend_t cairo_analysis_surface_backend = {
     NULL, /* fill_stroke */
     _cairo_analysis_surface_show_glyphs,
     _cairo_analysis_surface_has_show_text_glyphs,
-    _cairo_analysis_surface_show_text_glyphs
+    _cairo_analysis_surface_show_text_glyphs,
+    NULL, /* get_supported_mime_types */
+    _cairo_analysis_surface_tag
 };
 
 cairo_surface_t *
diff --git a/src/cairo-paginated-private.h b/src/cairo-paginated-private.h
index b827fab..29eefc7 100644
--- a/src/cairo-paginated-private.h
+++ b/src/cairo-paginated-private.h
@@ -56,7 +56,7 @@ struct _cairo_paginated_surface_backend {
      * CAIRO_PAGINATED_MODE_RENDER. See more details in the
      * documentation for _cairo_paginated_surface_create below.
      */
-    void
+    cairo_warn cairo_int_status_t
     (*set_paginated_mode)	(void			*surface,
 				 cairo_paginated_mode_t	 mode);
 
diff --git a/src/cairo-paginated-surface.c b/src/cairo-paginated-surface.c
index 68fa37c..749f0de 100644
--- a/src/cairo-paginated-surface.c
+++ b/src/cairo-paginated-surface.c
@@ -352,8 +352,11 @@ _paint_page (cairo_paginated_surface_t *surface)
     if (unlikely (analysis->status))
 	return _cairo_surface_set_error (surface->target, analysis->status);
 
-    surface->backend->set_paginated_mode (surface->target,
+    status = surface->backend->set_paginated_mode (surface->target,
 	                                  CAIRO_PAGINATED_MODE_ANALYZE);
+    if (unlikely (status))
+	goto FAIL;
+
     status = _cairo_recording_surface_replay_and_create_regions (surface->recording_surface,
 								 NULL, analysis, FALSE);
     if (status)
@@ -401,8 +404,10 @@ _paint_page (cairo_paginated_surface_t *surface)
     }
 
     if (has_supported) {
-	surface->backend->set_paginated_mode (surface->target,
-		                              CAIRO_PAGINATED_MODE_RENDER);
+	status = surface->backend->set_paginated_mode (surface->target,
+						       CAIRO_PAGINATED_MODE_RENDER);
+	if (unlikely (status))
+	    goto FAIL;
 
 	status = _cairo_recording_surface_replay_region (surface->recording_surface,
 							 NULL,
@@ -417,8 +422,10 @@ _paint_page (cairo_paginated_surface_t *surface)
 	cairo_rectangle_int_t extents;
 	cairo_bool_t is_bounded;
 
-	surface->backend->set_paginated_mode (surface->target,
-		                              CAIRO_PAGINATED_MODE_FALLBACK);
+	status = surface->backend->set_paginated_mode (surface->target,
+						       CAIRO_PAGINATED_MODE_FALLBACK);
+	if (unlikely (status))
+	    goto FAIL;
 
 	is_bounded = _cairo_surface_get_extents (surface->target, &extents);
 	if (! is_bounded) {
@@ -435,8 +442,10 @@ _paint_page (cairo_paginated_surface_t *surface)
         cairo_region_t *region;
         int num_rects, i;
 
-	surface->backend->set_paginated_mode (surface->target,
+	status = surface->backend->set_paginated_mode (surface->target,
 		                              CAIRO_PAGINATED_MODE_FALLBACK);
+	if (unlikely (status))
+	    goto FAIL;
 
 	region = _cairo_analysis_surface_get_unsupported (analysis);
 
@@ -660,6 +669,26 @@ _cairo_paginated_surface_get_supported_mime_types (void *abstract_surface)
     return NULL;
 }
 
+static cairo_int_status_t
+_cairo_paginated_surface_tag (void			 *abstract_surface,
+			      cairo_bool_t                begin,
+			      const char                 *tag_name,
+			      const char                 *attributes,
+			      const cairo_pattern_t	 *source,
+			      const cairo_stroke_style_t *style,
+			      const cairo_matrix_t	 *ctm,
+			      const cairo_matrix_t	 *ctm_inverse,
+			      const cairo_clip_t	 *clip)
+{
+    cairo_paginated_surface_t *surface = abstract_surface;
+
+    return _cairo_surface_tag (surface->recording_surface,
+			       begin, tag_name, attributes,
+			       source, style,
+			       ctm, ctm_inverse,
+			       clip);
+}
+
 static cairo_surface_t *
 _cairo_paginated_surface_snapshot (void *abstract_other)
 {
@@ -714,4 +743,5 @@ static const cairo_surface_backend_t cairo_paginated_surface_backend = {
     _cairo_paginated_surface_has_show_text_glyphs,
     _cairo_paginated_surface_show_text_glyphs,
     _cairo_paginated_surface_get_supported_mime_types,
+    _cairo_paginated_surface_tag,
 };
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 944e9d6..3f879fb 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -7917,7 +7917,7 @@ _cairo_pdf_surface_get_supported_mime_types (void		 *abstract_surface)
     return _cairo_pdf_supported_mime_types;
 }
 
-static void
+static cairo_int_status_t
 _cairo_pdf_surface_set_paginated_mode (void			*abstract_surface,
 				       cairo_paginated_mode_t	 paginated_mode)
 {
@@ -7930,6 +7930,8 @@ _cairo_pdf_surface_set_paginated_mode (void			*abstract_surface,
 	surface->surface_extents.width  = ceil (surface->width);
 	surface->surface_extents.height = ceil (surface->height);
     }
+
+    return CAIRO_INT_STATUS_SUCCESS;
 }
 
 static const cairo_surface_backend_t cairo_pdf_surface_backend = {
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 240c8e2..1e020c0 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -4513,7 +4513,7 @@ _cairo_ps_surface_get_supported_mime_types (void		 *abstract_surface)
     return _cairo_ps_supported_mime_types;
 }
 
-static void
+static cairo_int_status_t
 _cairo_ps_surface_set_paginated_mode (void			*abstract_surface,
 				      cairo_paginated_mode_t	 paginated_mode)
 {
@@ -4536,6 +4536,8 @@ _cairo_ps_surface_set_paginated_mode (void			*abstract_surface,
 	    _cairo_surface_clipper_reset (&surface->clipper);
 	}
     }
+
+    return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_int_status_t
diff --git a/src/cairo-svg-surface.c b/src/cairo-svg-surface.c
index 2e023b3..2a020c0 100644
--- a/src/cairo-svg-surface.c
+++ b/src/cairo-svg-surface.c
@@ -2852,13 +2852,15 @@ _cairo_svg_document_finish (cairo_svg_document_t *document)
     return status;
 }
 
-static void
+static cairo_int_status_t
 _cairo_svg_surface_set_paginated_mode (void			*abstract_surface,
 				       cairo_paginated_mode_t	 paginated_mode)
 {
     cairo_svg_surface_t *surface = abstract_surface;
 
     surface->paginated_mode = paginated_mode;
+
+    return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_bool_t
diff --git a/src/test-paginated-surface.c b/src/test-paginated-surface.c
index a0d4c1c..a9f073f 100644
--- a/src/test-paginated-surface.c
+++ b/src/test-paginated-surface.c
@@ -232,13 +232,15 @@ _test_paginated_surface_show_text_glyphs (void			    *abstract_surface,
 }
 
 
-static void
+static cairo_int_status_t
 _test_paginated_surface_set_paginated_mode (void			*abstract_surface,
 					    cairo_paginated_mode_t	 mode)
 {
     test_paginated_surface_t *surface = abstract_surface;
 
     surface->paginated_mode = mode;
+
+    return CAIRO_STATUS_SUCCESS;
 }
 
 static const cairo_surface_backend_t test_paginated_surface_backend = {
diff --git a/src/win32/cairo-win32-printing-surface.c b/src/win32/cairo-win32-printing-surface.c
index 983ef59..68321c8 100644
--- a/src/win32/cairo-win32-printing-surface.c
+++ b/src/win32/cairo-win32-printing-surface.c
@@ -2090,13 +2090,15 @@ _cairo_win32_printing_surface_start_page (void *abstract_surface)
     return CAIRO_STATUS_SUCCESS;
 }
 
-static void
+static cairo_int_status_t
 _cairo_win32_printing_surface_set_paginated_mode (void *abstract_surface,
                                                   cairo_paginated_mode_t paginated_mode)
 {
     cairo_win32_printing_surface_t *surface = abstract_surface;
 
     surface->paginated_mode = paginated_mode;
+
+    return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_bool_t
commit 3bd5efa1b651503ed9f175f9ea62fff53f0b2882
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 17:26:16 2016 +0930

    Add tag functions to recording surface and surface-wrapper

diff --git a/src/cairo-recording-surface-private.h b/src/cairo-recording-surface-private.h
index 021b73c..c1827f5 100644
--- a/src/cairo-recording-surface-private.h
+++ b/src/cairo-recording-surface-private.h
@@ -49,6 +49,9 @@ typedef enum {
     CAIRO_COMMAND_STROKE,
     CAIRO_COMMAND_FILL,
     CAIRO_COMMAND_SHOW_TEXT_GLYPHS,
+
+    /* cairo_tag_begin()/cairo_tag_end() */
+    CAIRO_COMMAND_TAG,
 } cairo_command_type_t;
 
 typedef enum {
@@ -112,6 +115,17 @@ typedef struct _cairo_command_show_text_glyphs {
     cairo_scaled_font_t		*scaled_font;
 } cairo_command_show_text_glyphs_t;
 
+typedef struct _cairo_command_tag {
+    cairo_command_header_t       header;
+    cairo_bool_t                 begin;
+    char                        *tag_name;
+    char                        *attributes;
+    cairo_pattern_union_t	 source;
+    cairo_stroke_style_t	 style;
+    cairo_matrix_t		 ctm;
+    cairo_matrix_t		 ctm_inverse;
+} cairo_command_tag_t;
+
 typedef union _cairo_command {
     cairo_command_header_t      header;
 
@@ -120,6 +134,7 @@ typedef union _cairo_command {
     cairo_command_stroke_t			stroke;
     cairo_command_fill_t			fill;
     cairo_command_show_text_glyphs_t		show_text_glyphs;
+    cairo_command_tag_t                         tag;
 } cairo_command_t;
 
 typedef struct _cairo_recording_surface {
diff --git a/src/cairo-recording-surface.c b/src/cairo-recording-surface.c
index 6591313..8444952 100644
--- a/src/cairo-recording-surface.c
+++ b/src/cairo-recording-surface.c
@@ -483,7 +483,16 @@ _cairo_recording_surface_finish (void *abstract_surface)
 	    cairo_scaled_font_destroy (command->show_text_glyphs.scaled_font);
 	    break;
 
-	default:
+	case CAIRO_COMMAND_TAG:
+	    free (command->tag.tag_name);
+	    if (command->tag.begin) {
+		free (command->tag.attributes);
+		_cairo_pattern_fini (&command->tag.source.base);
+		_cairo_stroke_style_fini (&command->tag.style);
+	    }
+	    break;
+
+	    default:
 	    ASSERT_NOT_REACHED;
 	}
 
@@ -1076,6 +1085,90 @@ CLEANUP_COMPOSITE:
     return status;
 }
 
+static cairo_int_status_t
+_cairo_recording_surface_tag (void			 *abstract_surface,
+			      cairo_bool_t                begin,
+			      const char                 *tag_name,
+			      const char                 *attributes,
+			      const cairo_pattern_t	 *source,
+			      const cairo_stroke_style_t *style,
+			      const cairo_matrix_t	 *ctm,
+			      const cairo_matrix_t	 *ctm_inverse,
+			      const cairo_clip_t	 *clip)
+{
+    cairo_status_t status;
+    cairo_recording_surface_t *surface = abstract_surface;
+    cairo_command_tag_t *command;
+    cairo_composite_rectangles_t composite;
+
+    TRACE ((stderr, "%s: surface=%d\n", __FUNCTION__, surface->base.unique_id));
+
+    status = _cairo_composite_rectangles_init_for_paint (&composite,
+							 &surface->base,
+							 CAIRO_OPERATOR_SOURCE,
+							 source ? source : &_cairo_pattern_black.base,
+							 clip);
+    if (unlikely (status))
+	return status;
+
+    command = calloc (1, sizeof (cairo_command_tag_t));
+    if (unlikely (command == NULL)) {
+	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+	goto CLEANUP_COMPOSITE;
+    }
+
+    status = _command_init (surface,
+			    &command->header, CAIRO_COMMAND_TAG, CAIRO_OPERATOR_SOURCE,
+			    &composite);
+    if (unlikely (status))
+	goto CLEANUP_COMMAND;
+
+    command->begin = begin;
+    command->tag_name = strdup (tag_name);
+    if (begin) {
+	if (attributes)
+	    command->attributes = strdup (attributes);
+
+	status = _cairo_pattern_init_snapshot (&command->source.base, source);
+	if (unlikely (status))
+	    goto CLEANUP_STRINGS;
+
+	status = _cairo_stroke_style_init_copy (&command->style, style);
+	if (unlikely (status))
+	    goto CLEANUP_SOURCE;
+
+	command->ctm = *ctm;
+	command->ctm_inverse = *ctm_inverse;
+    }
+
+    status = _cairo_recording_surface_commit (surface, &command->header);
+    if (unlikely (status)) {
+	if (begin)
+	    goto CLEANUP_STRINGS;
+	else
+	    goto CLEANUP_STYLE;
+    }
+
+    _cairo_recording_surface_destroy_bbtree (surface);
+
+    _cairo_composite_rectangles_fini (&composite);
+    return CAIRO_STATUS_SUCCESS;
+
+  CLEANUP_STYLE:
+    _cairo_stroke_style_fini (&command->style);
+  CLEANUP_SOURCE:
+    _cairo_pattern_fini (&command->source.base);
+  CLEANUP_STRINGS:
+    free (command->tag_name);
+    free (command->attributes);
+  CLEANUP_COMMAND:
+    _cairo_clip_destroy (command->header.clip);
+    free (command);
+  CLEANUP_COMPOSITE:
+    _cairo_composite_rectangles_fini (&composite);
+    return status;
+}
+
 static void
 _command_init_copy (cairo_recording_surface_t *surface,
 		    cairo_command_header_t *dst,
@@ -1342,6 +1435,63 @@ err:
 }
 
 static cairo_status_t
+_cairo_recording_surface_copy__tag (cairo_recording_surface_t *surface,
+				    const cairo_command_t *src)
+{
+    cairo_command_tag_t *command;
+    cairo_status_t status;
+
+    command = calloc (1, sizeof (*command));
+    if (unlikely (command == NULL)) {
+	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+	goto err;
+    }
+
+    _command_init_copy (surface, &command->header, &src->header);
+
+    command->begin = src->tag.begin;
+    command->tag_name = strdup (src->tag.tag_name);
+    if (src->tag.begin) {
+	if (src->tag.attributes)
+	    command->attributes = strdup (src->tag.attributes);
+
+	status = _cairo_pattern_init_copy (&command->source.base,
+					   &src->stroke.source.base);
+	if (unlikely (status))
+	    goto err_command;
+
+	status = _cairo_stroke_style_init_copy (&command->style,
+						&src->stroke.style);
+	if (unlikely (status))
+	    goto err_source;
+
+	command->ctm = src->stroke.ctm;
+	command->ctm_inverse = src->stroke.ctm_inverse;
+    }
+
+    status = _cairo_recording_surface_commit (surface, &command->header);
+    if (unlikely (status)) {
+	if (src->tag.begin)
+	    goto err_command;
+	else
+	    goto err_style;
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+
+err_style:
+    _cairo_stroke_style_fini (&command->style);
+err_source:
+    _cairo_pattern_fini (&command->source.base);
+err_command:
+    free(command->tag_name);
+    free(command->attributes);
+    free(command);
+err:
+    return status;
+}
+
+static cairo_status_t
 _cairo_recording_surface_copy (cairo_recording_surface_t *dst,
 			       cairo_recording_surface_t *src)
 {
@@ -1375,6 +1525,10 @@ _cairo_recording_surface_copy (cairo_recording_surface_t *dst,
 	    status = _cairo_recording_surface_copy__glyphs (dst, command);
 	    break;
 
+	case CAIRO_COMMAND_TAG:
+	    status = _cairo_recording_surface_copy__tag (dst, command);
+	    break;
+
 	default:
 	    ASSERT_NOT_REACHED;
 	}
@@ -1490,6 +1644,8 @@ static const cairo_surface_backend_t cairo_recording_surface_backend = {
     NULL,
     _cairo_recording_surface_has_show_text_glyphs,
     _cairo_recording_surface_show_text_glyphs,
+    NULL, /* get_supported_mime_types */
+    _cairo_recording_surface_tag,
 };
 
 cairo_int_status_t
@@ -1554,6 +1710,9 @@ _cairo_recording_surface_get_path (cairo_surface_t    *abstract_surface,
 	    break;
 	}
 
+	case CAIRO_COMMAND_TAG:
+	    break;
+
 	default:
 	    ASSERT_NOT_REACHED;
 	}
@@ -1851,6 +2010,18 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t	*surface,
 	    }
 	    break;
 
+	case CAIRO_COMMAND_TAG:
+	    status = _cairo_surface_wrapper_tag (&wrapper,
+						 command->tag.begin,
+						 command->tag.tag_name,
+						 command->tag.attributes,
+						 &command->tag.source.base,
+						 &command->tag.style,
+						 &command->tag.ctm,
+						 &command->tag.ctm_inverse,
+						 command->header.clip);
+	    break;
+
 	default:
 	    ASSERT_NOT_REACHED;
 	}
@@ -1958,6 +2129,18 @@ _cairo_recording_surface_replay_one (cairo_recording_surface_t	*surface,
 							  command->header.clip);
 	break;
 
+    case CAIRO_COMMAND_TAG:
+	status = _cairo_surface_wrapper_tag (&wrapper,
+					     command->tag.begin,
+					     command->tag.tag_name,
+					     command->tag.attributes,
+					     &command->tag.source.base,
+					     &command->tag.style,
+					     &command->tag.ctm,
+					     &command->tag.ctm_inverse,
+					     command->header.clip);
+	break;
+
     default:
 	ASSERT_NOT_REACHED;
     }
diff --git a/src/cairo-surface-wrapper-private.h b/src/cairo-surface-wrapper-private.h
index e412fc6..fd22bd7 100644
--- a/src/cairo-surface-wrapper-private.h
+++ b/src/cairo-surface-wrapper-private.h
@@ -159,6 +159,17 @@ _cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t *wrapper,
 					 cairo_scaled_font_t	    *scaled_font,
 					 const cairo_clip_t	    *clip);
 
+cairo_private cairo_status_t
+_cairo_surface_wrapper_tag (cairo_surface_wrapper_t     *wrapper,
+			    cairo_bool_t                 begin,
+			    const char                  *tag_name,
+			    const char                  *attributes,
+			    const cairo_pattern_t	*source,
+			    const cairo_stroke_style_t	*stroke_style,
+			    const cairo_matrix_t	*ctm,
+			    const cairo_matrix_t	*ctm_inverse,
+			    const cairo_clip_t		*clip);
+
 cairo_private cairo_surface_t *
 _cairo_surface_wrapper_create_similar (cairo_surface_wrapper_t *wrapper,
 				       cairo_content_t	content,
diff --git a/src/cairo-surface-wrapper.c b/src/cairo-surface-wrapper.c
index b9b4b44..47155c3 100644
--- a/src/cairo-surface-wrapper.c
+++ b/src/cairo-surface-wrapper.c
@@ -501,6 +501,53 @@ _cairo_surface_wrapper_show_text_glyphs (cairo_surface_wrapper_t *wrapper,
     return status;
 }
 
+cairo_status_t
+_cairo_surface_wrapper_tag (cairo_surface_wrapper_t     *wrapper,
+			    cairo_bool_t                 begin,
+			    const char                  *tag_name,
+			    const char                  *attributes,
+			    const cairo_pattern_t	*source,
+			    const cairo_stroke_style_t	*stroke_style,
+			    const cairo_matrix_t	*ctm,
+			    const cairo_matrix_t	*ctm_inverse,
+			    const cairo_clip_t		*clip)
+{
+    cairo_status_t status;
+    cairo_clip_t *dev_clip;
+    cairo_matrix_t dev_ctm = *ctm;
+    cairo_matrix_t dev_ctm_inverse = *ctm_inverse;
+    cairo_pattern_union_t source_copy;
+
+    if (unlikely (wrapper->target->status))
+	return wrapper->target->status;
+
+    dev_clip = _cairo_surface_wrapper_get_clip (wrapper, clip);
+    if (wrapper->needs_transform) {
+	cairo_matrix_t m;
+
+	_cairo_surface_wrapper_get_transform (wrapper, &m);
+
+	cairo_matrix_multiply (&dev_ctm, &dev_ctm, &m);
+
+	status = cairo_matrix_invert (&m);
+	assert (status == CAIRO_STATUS_SUCCESS);
+
+	cairo_matrix_multiply (&dev_ctm_inverse, &m, &dev_ctm_inverse);
+
+	_copy_transformed_pattern (&source_copy.base, source, &m);
+	source = &source_copy.base;
+    }
+
+    status = _cairo_surface_tag (wrapper->target,
+				 begin, tag_name, attributes,
+				 source, stroke_style,
+				 &dev_ctm, &dev_ctm_inverse,
+				 dev_clip);
+
+    _cairo_clip_destroy (dev_clip);
+    return status;
+}
+
 cairo_surface_t *
 _cairo_surface_wrapper_create_similar (cairo_surface_wrapper_t *wrapper,
 				       cairo_content_t	content,
commit 4e70815b349309e0a82bc8c52663e030c24a1add
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Sat Oct 1 17:14:28 2016 +0930

    Add tag functions to cairo_t and cairo_surface_t
    
    The cairo_tag_begin/cairo_tag_end API is for supporting hyperlinks and
    creating tagged PDF files.
    
    The source, ctm, and stroke style are passed to the backend to allow
    these parameters to be used to specify hyperlink border attributes.

diff --git a/src/cairo-backend-private.h b/src/cairo-backend-private.h
index b05eca5..67607c1 100644
--- a/src/cairo-backend-private.h
+++ b/src/cairo-backend-private.h
@@ -172,6 +172,9 @@ struct _cairo_backend {
 
     cairo_status_t (*copy_page) (void *cr);
     cairo_status_t (*show_page) (void *cr);
+
+    cairo_status_t (*tag_begin) (void *cr, const char *tag_name, const char *attributes);
+    cairo_status_t (*tag_end) (void *cr, const char *tag_name);
 };
 
 static inline void
diff --git a/src/cairo-default-context.c b/src/cairo-default-context.c
index 1e5067b..694eecf 100644
--- a/src/cairo-default-context.c
+++ b/src/cairo-default-context.c
@@ -1156,6 +1156,24 @@ _cairo_default_context_copy_page (void *abstract_cr)
 }
 
 static cairo_status_t
+_cairo_default_context_tag_begin (void *abstract_cr,
+				  const char *tag_name, const char *attributes)
+{
+    cairo_default_context_t *cr = abstract_cr;
+
+    return _cairo_gstate_tag_begin (cr->gstate, tag_name, attributes);
+}
+
+static cairo_status_t
+_cairo_default_context_tag_end (void *abstract_cr,
+				const char *tag_name)
+{
+    cairo_default_context_t *cr = abstract_cr;
+
+    return _cairo_gstate_tag_end (cr->gstate, tag_name);
+}
+
+static cairo_status_t
 _cairo_default_context_show_page (void *abstract_cr)
 {
     cairo_default_context_t *cr = abstract_cr;
@@ -1437,6 +1455,9 @@ static const cairo_backend_t _cairo_default_context_backend = {
 
     _cairo_default_context_copy_page,
     _cairo_default_context_show_page,
+
+    _cairo_default_context_tag_begin,
+    _cairo_default_context_tag_end,
 };
 
 cairo_status_t
diff --git a/src/cairo-gstate-private.h b/src/cairo-gstate-private.h
index b2ccc76..198c669 100644
--- a/src/cairo-gstate-private.h
+++ b/src/cairo-gstate-private.h
@@ -324,6 +324,15 @@ _cairo_gstate_show_surface (cairo_gstate_t	*gstate,
 			    double		height);
 
 cairo_private cairo_status_t
+_cairo_gstate_tag_begin (cairo_gstate_t	*gstate,
+			 const char     *tag_name,
+			 const char     *attributes);
+
+cairo_private cairo_status_t
+_cairo_gstate_tag_end (cairo_gstate_t	*gstate,
+		       const char       *tag_name);
+
+cairo_private cairo_status_t
 _cairo_gstate_set_font_size (cairo_gstate_t *gstate,
 			     double          size);
 
diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index 4c7eb11..95a4ccb 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -1646,6 +1646,65 @@ _cairo_gstate_copy_clip_rectangle_list (cairo_gstate_t *gstate)
     return list;
 }
 
+cairo_status_t
+_cairo_gstate_tag_begin (cairo_gstate_t *gstate,
+			 const char *tag_name, const char *attributes)
+{
+    cairo_pattern_union_t source_pattern;
+    cairo_stroke_style_t style;
+    double dash[2];
+    cairo_status_t status;
+    cairo_matrix_t aggregate_transform;
+    cairo_matrix_t aggregate_transform_inverse;
+
+    status = _cairo_gstate_get_pattern_status (gstate->source);
+    if (unlikely (status))
+	return status;
+
+    cairo_matrix_multiply (&aggregate_transform,
+                           &gstate->ctm,
+                           &gstate->target->device_transform);
+    cairo_matrix_multiply (&aggregate_transform_inverse,
+                           &gstate->target->device_transform_inverse,
+                           &gstate->ctm_inverse);
+
+    memcpy (&style, &gstate->stroke_style, sizeof (gstate->stroke_style));
+    if (_cairo_stroke_style_dash_can_approximate (&gstate->stroke_style, &aggregate_transform, gstate->tolerance)) {
+        style.dash = dash;
+        _cairo_stroke_style_dash_approximate (&gstate->stroke_style, &gstate->ctm, gstate->tolerance,
+					      &style.dash_offset,
+					      style.dash,
+					      &style.num_dashes);
+    }
+
+    _cairo_gstate_copy_transformed_source (gstate, &source_pattern.base);
+
+    return _cairo_surface_tag (gstate->target,
+			       TRUE, /* begin */
+			       tag_name,
+			       attributes ? attributes : "",
+			       &source_pattern.base,
+			       &style,
+			       &aggregate_transform,
+			       &aggregate_transform_inverse,
+			       gstate->clip);
+}
+
+cairo_status_t
+_cairo_gstate_tag_end (cairo_gstate_t *gstate,
+		       const char *tag_name)
+{
+    return _cairo_surface_tag (gstate->target,
+			       FALSE, /* begin */
+			       tag_name,
+			       NULL, /* attributes */
+			       NULL, /* source */
+			       NULL, /* stroke_style */
+			       NULL, /* ctm */
+			       NULL, /* ctm_inverse*/
+			       NULL); /* clip */
+}
+
 static void
 _cairo_gstate_unset_scaled_font (cairo_gstate_t *gstate)
 {
diff --git a/src/cairo-surface-backend-private.h b/src/cairo-surface-backend-private.h
index 955a79f..bcda9ae 100644
--- a/src/cairo-surface-backend-private.h
+++ b/src/cairo-surface-backend-private.h
@@ -200,6 +200,18 @@ struct _cairo_surface_backend {
 
     const char **
     (*get_supported_mime_types)	(void			    *surface);
+
+    cairo_warn cairo_int_status_t
+    (*tag)			(void			*surface,
+				 cairo_bool_t            begin,
+				 const char             *tag_name,
+				 const char             *attributes,
+				 const cairo_pattern_t	*source,
+				 const cairo_stroke_style_t	*style,
+				 const cairo_matrix_t	*ctm,
+				 const cairo_matrix_t	*ctm_inverse,
+				 const cairo_clip_t	*clip);
+
 };
 
 cairo_private cairo_status_t
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index ded146d..8a83d55 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -2632,6 +2632,42 @@ _cairo_surface_show_text_glyphs (cairo_surface_t	    *surface,
     return _cairo_surface_set_error (surface, status);
 }
 
+cairo_status_t
+_cairo_surface_tag (cairo_surface_t	        *surface,
+		    cairo_bool_t                 begin,
+		    const char                  *tag_name,
+		    const char                  *attributes,
+		    const cairo_pattern_t	*source,
+		    const cairo_stroke_style_t	*stroke_style,
+		    const cairo_matrix_t	*ctm,
+		    const cairo_matrix_t	*ctm_inverse,
+		    const cairo_clip_t	        *clip)
+{
+    cairo_int_status_t status;
+
+    TRACE ((stderr, "%s\n", __FUNCTION__));
+    if (unlikely (surface->status))
+	return surface->status;
+    if (unlikely (surface->finished))
+	return _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
+
+    if (surface->backend->tag == NULL)
+	return CAIRO_STATUS_SUCCESS;
+
+    if (begin) {
+	status = _pattern_has_error (source);
+	if (unlikely (status))
+	    return status;
+    }
+
+    status = surface->backend->tag (surface, begin, tag_name, attributes,
+				    source, stroke_style,
+				    ctm, ctm_inverse, clip);
+
+    return _cairo_surface_set_error (surface, status);
+}
+
+
 /**
  * _cairo_surface_set_resolution:
  * @surface: the surface
diff --git a/src/cairo.c b/src/cairo.c
index ec27fe7..76d8c56 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -2667,6 +2667,55 @@ cairo_copy_clip_rectangle_list (cairo_t *cr)
 }
 
 /**
+ * cairo_tag_begin:
+ * @cr: a cairo context
+ * @tag_name: tag name
+ * @attributes: tag attributes
+ *
+ * Marks the beginning of the @tag_name structure. Call
+ * cairo_tag_end() with the same @tag_name to mark the end of the
+ * structure.
+ *
+ * Since: 1.16
+ **/
+void
+cairo_tag_begin (cairo_t *cr, const char *tag_name, const char *attributes)
+{
+    cairo_status_t status;
+
+    if (unlikely (cr->status))
+	return;
+
+    status = cr->backend->tag_begin (cr, tag_name, attributes);
+    if (unlikely (status))
+	_cairo_set_error (cr, status);
+}
+
+/**
+ * cairo_tag_end:
+ * @cr: a cairo context
+ * @tag_name: tag name
+ *
+ * Marks the end of the @tag_name structure.
+ *
+ * See cairo_tag_begin().
+ *
+ * Since: 1.16
+ **/
+cairo_public void
+cairo_tag_end (cairo_t *cr, const char *tag_name)
+{
+    cairo_status_t status;
+
+    if (unlikely (cr->status))
+	return;
+
+    status = cr->backend->tag_end (cr, tag_name);
+    if (unlikely (status))
+	_cairo_set_error (cr, status);
+}
+
+/**
  * cairo_select_font_face:
  * @cr: a #cairo_t
  * @family: a font family name, encoded in UTF-8
diff --git a/src/cairo.h b/src/cairo.h
index a09d839..9763768 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -1024,6 +1024,14 @@ cairo_copy_clip_rectangle_list (cairo_t *cr);
 cairo_public void
 cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list);
 
+/* Logical structure tagging functions */
+
+cairo_public void
+cairo_tag_begin (cairo_t *cr, const char *tag_name, const char *attributes);
+
+cairo_public void
+cairo_tag_end (cairo_t *cr, const char *tag_name);
+
 /* Font/Text functions */
 
 /**
diff --git a/src/cairoint.h b/src/cairoint.h
index efc9ad1..f1cd075 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1418,6 +1418,17 @@ _cairo_surface_show_text_glyphs (cairo_surface_t	    *surface,
 				 const cairo_clip_t		    *clip);
 
 cairo_private cairo_status_t
+_cairo_surface_tag (cairo_surface_t	        *surface,
+		    cairo_bool_t                 begin,
+		    const char                  *tag_name,
+		    const char                  *attributes,
+		    const cairo_pattern_t	*source,
+		    const cairo_stroke_style_t	*stroke_style,
+		    const cairo_matrix_t	*ctm,
+		    const cairo_matrix_t	*ctm_inverse,
+		    const cairo_clip_t	        *clip);
+
+cairo_private cairo_status_t
 _cairo_surface_acquire_source_image (cairo_surface_t         *surface,
 				     cairo_image_surface_t  **image_out,
 				     void                   **image_extra);


More information about the cairo-commit mailing list