[cairo-commit] 14 commits - doc/public src/cairo-analysis-surface.c src/cairo.h src/cairoint.h src/cairo-pdf-surface.c src/cairo-pdf-surface-private.h src/cairo-ps-surface.c src/cairo-ps-surface-private.h src/cairo-surface.c src/cairo-tag-attributes.c src/cairo-tag-attributes-private.h test/cairo-test.c test/Makefile.sources test/mime-unique-id.c test/record.c test/reference

Adrian Johnson ajohnson at kemper.freedesktop.org
Thu Nov 9 12:09:33 UTC 2017


 doc/public/cairo-sections.txt                          |    2 
 src/cairo-analysis-surface.c                           |  172 +-
 src/cairo-pdf-surface-private.h                        |    7 
 src/cairo-pdf-surface.c                                |  355 ++--
 src/cairo-ps-surface-private.h                         |   21 
 src/cairo-ps-surface.c                                 | 1332 +++++++++++------
 src/cairo-surface.c                                    |   64 
 src/cairo-tag-attributes-private.h                     |    7 
 src/cairo-tag-attributes.c                             |   47 
 src/cairo.h                                            |    2 
 src/cairoint.h                                         |    3 
 test/Makefile.sources                                  |    2 
 test/cairo-test.c                                      |    5 
 test/mime-unique-id.c                                  |  511 ++++++
 test/record.c                                          |   11 
 test/reference/record-text-transform.ps.argb32.ref.png |binary
 test/reference/record-text-transform.ps.rgb24.ref.png  |binary
 17 files changed, 1941 insertions(+), 600 deletions(-)

New commits:
commit 9bfa9df2bb8557623df7c561613ea6eaa2a67cdd
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: fix padded image crash

diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 154c854b..13b56730 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -2007,11 +2007,9 @@ _cairo_ps_surface_create_padded_image_from_image (cairo_ps_surface_t           *
 	_cairo_fixed_integer_floor(box.p2.y) > w ||
 	_cairo_fixed_integer_floor(box.p2.y) > h)
     {
-	pad_image =
-	    _cairo_image_surface_create_with_pixman_format (NULL,
-							    source->pixman_format,
-							    rect.width, rect.height,
-							    0);
+	pad_image = _cairo_image_surface_create_with_content (source->base.content,
+							      rect.width,
+							      rect.height);
 	if (pad_image->status)
 	    return pad_image->status;
 
commit f7d6d78f9428c6813e79d5f7c1351c354d44da64
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: prevent self-copy infinite loop

diff --git a/src/cairo-ps-surface-private.h b/src/cairo-ps-surface-private.h
index e9e10c9b..0ee0ef9f 100644
--- a/src/cairo-ps-surface-private.h
+++ b/src/cairo-ps-surface-private.h
@@ -103,6 +103,8 @@ typedef struct cairo_ps_surface {
     cairo_array_t dsc_setup_comments;
     cairo_array_t dsc_page_setup_comments;
 
+    cairo_array_t recording_surf_stack;
+
     cairo_array_t *dsc_comment_target;
 
     cairo_ps_level_t ps_level;
@@ -114,7 +116,6 @@ typedef struct cairo_ps_surface {
     cairo_surface_t *paginated_surface;
     cairo_hash_table_t *forms;
     int num_forms;
-
     long total_form_size;
 } cairo_ps_surface_t;
 
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index fe09ed66..154c854b 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -1216,6 +1216,7 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream,
     _cairo_array_init (&surface->dsc_header_comments, sizeof (char *));
     _cairo_array_init (&surface->dsc_setup_comments, sizeof (char *));
     _cairo_array_init (&surface->dsc_page_setup_comments, sizeof (char *));
+    _cairo_array_init (&surface->recording_surf_stack, sizeof (unsigned int));
 
     surface->num_forms = 0;
     surface->forms = _cairo_hash_table_create (_cairo_ps_form_equal);
@@ -1826,6 +1827,8 @@ CLEANUP:
 	free (comments[i]);
     _cairo_array_fini (&surface->dsc_page_setup_comments);
 
+    _cairo_array_fini (&surface->recording_surf_stack);
+
     _cairo_surface_clipper_reset (&surface->clipper);
 
     return status;
@@ -1920,7 +1923,7 @@ _cairo_ps_surface_acquire_source_surface_from_pattern (
 	cairo_surface_get_device_offset (surf, x_offset, y_offset);
 	*source_surface = surf;
     } else if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
-	cairo_surface_t *surf;
+	cairo_surface_t *surf = NULL;
 
 	*source_surface = ((cairo_surface_pattern_t *) pattern)->surface;
 	surf = *source_surface;
@@ -1937,6 +1940,8 @@ _cairo_ps_surface_acquire_source_surface_from_pattern (
 		*x_offset = -sub->extents.x;
 		*y_offset = -sub->extents.y;
 	    }
+
+	    cairo_surface_destroy (surf);
 	} else if (surf->type != CAIRO_SURFACE_TYPE_IMAGE) {
 	    cairo_image_surface_t *image;
 	    void *image_extra;
@@ -3334,6 +3339,21 @@ _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
     cairo_surface_clipper_t old_clipper;
     cairo_int_status_t status;
     cairo_surface_t *free_me = NULL;
+    unsigned int id;
+    int i, recording_surf_stack_size;
+
+    /* Prevent infinite recursion if the recording_surface references a recording
+     * currently being emitted */
+    recording_surf_stack_size = _cairo_array_num_elements (&surface->recording_surf_stack);
+    for (i = 0; i < recording_surf_stack_size; i++) {
+	_cairo_array_copy_element (&surface->recording_surf_stack, i, &id);
+	if (id == recording_surface->unique_id)
+	    return CAIRO_STATUS_SUCCESS;
+    }
+    id = recording_surface->unique_id;
+    status = _cairo_array_append (&surface->recording_surf_stack, &id);
+    if (unlikely (status))
+	return status;
 
     if (_cairo_surface_is_snapshot (recording_surface))
 	free_me = recording_surface = _cairo_surface_snapshot_get_target (recording_surface);
@@ -3406,6 +3426,8 @@ _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
 						  &surface->cairo_to_ps);
     cairo_surface_destroy (free_me);
 
+    _cairo_array_truncate (&surface->recording_surf_stack, recording_surf_stack_size);
+
     return status;
 }
 
commit 71746c37d48a08d56b18d4d82185896ada215194
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    test: use CAIRO_MIME_TYPE_UNIQUE_ID with record-text-transform
    
    The PS output from this test is > 100MB due to the duplicated images.
    Using CAIRO_MIME_TYPE_UNIQUE_ID reduces the PS output to 650k, runs
    considerably faster, and now produces correct output.

diff --git a/test/cairo-test.c b/test/cairo-test.c
index 6f8cb791..d93468bd 100644
--- a/test/cairo-test.c
+++ b/test/cairo-test.c
@@ -1668,6 +1668,7 @@ cairo_test_create_surface_from_png (const cairo_test_context_t *ctx,
 {
     cairo_surface_t *image;
     cairo_status_t status;
+    char *unique_id;
 
     image = cairo_image_surface_create_from_png (filename);
     status = cairo_surface_status (image);
@@ -1683,6 +1684,10 @@ cairo_test_create_surface_from_png (const cairo_test_context_t *ctx,
 	    free (srcdir_filename);
 	}
     }
+    unique_id = strdup(filename);
+    cairo_surface_set_mime_data (image, CAIRO_MIME_TYPE_UNIQUE_ID,
+				 (unsigned char *)unique_id, strlen(unique_id),
+				 free, unique_id);
 
     return image;
 }
diff --git a/test/record.c b/test/record.c
index b1baadac..774c1331 100644
--- a/test/record.c
+++ b/test/record.c
@@ -46,6 +46,8 @@ static uint32_t data[16] = {
     0xff00ff00, 0xff00ff00,		0xff0000ff, 0xff0000ff
 };
 
+static const char *unique_id = "data";
+
 static const char *png_filename = "romedalen.png";
 
 static cairo_t *
@@ -70,6 +72,9 @@ paint_alpha (cairo_t *cr)
 
     surface = cairo_image_surface_create_for_data ((unsigned char *) data,
 						   CAIRO_FORMAT_RGB24, 4, 4, 16);
+    cairo_surface_set_mime_data (surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+				 (unsigned char *)unique_id, strlen(unique_id),
+				 NULL, NULL);
 
     cairo_test_paint_checkered (cr);
 
@@ -106,6 +111,9 @@ paint_alpha_clip (cairo_t *cr)
 
     surface = cairo_image_surface_create_for_data ((unsigned char *) data,
 						   CAIRO_FORMAT_RGB24, 4, 4, 16);
+    cairo_surface_set_mime_data (surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+				 (unsigned char *)unique_id, strlen(unique_id),
+				 NULL, NULL);
 
     cairo_test_paint_checkered (cr);
 
@@ -131,6 +139,9 @@ paint_alpha_clip_mask (cairo_t *cr)
 
     surface = cairo_image_surface_create_for_data ((unsigned char *) data,
 						   CAIRO_FORMAT_RGB24, 4, 4, 16);
+    cairo_surface_set_mime_data (surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+				 (unsigned char *)unique_id, strlen(unique_id),
+				 NULL, NULL);
 
     cairo_test_paint_checkered (cr);
 
diff --git a/test/reference/record-text-transform.ps.argb32.ref.png b/test/reference/record-text-transform.ps.argb32.ref.png
index 1aaaea58..be4ec541 100644
Binary files a/test/reference/record-text-transform.ps.argb32.ref.png and b/test/reference/record-text-transform.ps.argb32.ref.png differ
diff --git a/test/reference/record-text-transform.ps.rgb24.ref.png b/test/reference/record-text-transform.ps.rgb24.ref.png
index 1aaaea58..be4ec541 100644
Binary files a/test/reference/record-text-transform.ps.rgb24.ref.png and b/test/reference/record-text-transform.ps.rgb24.ref.png differ
commit 5ffbaf9e2f7da103da8d015b5f928e25f9433b60
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: add CAIRO_MIME_TYPE_EPS mime type for embedding EPS files

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index 40c21483..a735869e 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -244,6 +244,8 @@ cairo_device_observer_stroke_elapsed
 CAIRO_HAS_MIME_SURFACE
 CAIRO_MIME_TYPE_CCITT_FAX
 CAIRO_MIME_TYPE_CCITT_FAX_PARAMS
+CAIRO_MIME_TYPE_EPS
+CAIRO_MIME_TYPE_EPS_PARAMS
 CAIRO_MIME_TYPE_JBIG2
 CAIRO_MIME_TYPE_JBIG2_GLOBAL
 CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID
diff --git a/src/cairo-ps-surface-private.h b/src/cairo-ps-surface-private.h
index 651acdb3..e9e10c9b 100644
--- a/src/cairo-ps-surface-private.h
+++ b/src/cairo-ps-surface-private.h
@@ -76,6 +76,7 @@ typedef struct cairo_ps_surface {
     cairo_output_stream_t *stream;
 
     cairo_bool_t eps;
+    cairo_bool_t contains_eps;
     cairo_content_t content;
     double width;
     double height;
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 076fe9eb..fe09ed66 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -116,7 +116,9 @@
  *
  * The following mime types are supported: %CAIRO_MIME_TYPE_JPEG,
  * %CAIRO_MIME_TYPE_UNIQUE_ID,
- * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS.
+ * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS,
+ * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS,
+ * %CAIRO_MIME_TYPE_EPS, %CAIRO_MIME_TYPE_EPS_PARAMS.
  *
  * Source surfaces used by the PostScript surface that have a
  * %CAIRO_MIME_TYPE_UNIQUE_ID mime type will be stored in PostScript
@@ -126,6 +128,24 @@
  *
  * The %CAIRO_MIME_TYPE_CCITT_FAX and %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS mime types
  * are documented in [CCITT Fax Images][ccitt].
+ *
+ * # Embedding EPS files # {#eps}
+ *
+ * Encapsulated PostScript files can be embedded in the PS output by
+ * setting the CAIRO_MIME_TYPE_EPS mime data on a surface to the EPS
+ * data and painting the surface.  The EPS will be scaled and
+ * translated to the extents of the surface the EPS data is attached
+ * to.
+ *
+ * The %CAIRO_MIME_TYPE_EPS mime type requires the
+ * %CAIRO_MIME_TYPE_EPS_PARAMS mime data to also be provided in order
+ * to specify the embeddding parameters.  %CAIRO_MIME_TYPE_EPS_PARAMS
+ * mime data must contain a string of the form "bbox=[llx lly urx
+ * ury]" that specifies the bounding box (in PS coordinates) of the
+ * EPS graphics. The parameters are: lower left x, lower left y, upper
+ * right x, upper right y. Normally the bbox data is identical to the
+ * %%%BoundingBox data in the EPS file.
+ *
  **/
 
 /**
@@ -153,6 +173,8 @@ typedef struct  {
     /* input params */
     cairo_surface_t *src_surface;
     cairo_operator_t op;
+    const cairo_rectangle_int_t *src_surface_extents;
+    cairo_bool_t src_surface_bounded;
     const cairo_rectangle_int_t *src_op_extents; /* operation extents in src space */
     cairo_filter_t filter;
     cairo_bool_t stencil_mask; /* TRUE if source is to be used as a mask */
@@ -162,6 +184,7 @@ typedef struct  {
     cairo_bool_t is_image; /* returns TRUE if PS image will be emitted */
                            /*         FALSE if recording will be emitted */
     long approx_size;
+    int eod_count;
 } cairo_emit_surface_params_t;
 
 static const cairo_surface_backend_t cairo_ps_surface_backend;
@@ -354,7 +377,6 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface)
 
     if (surface->eps) {
 	_cairo_output_stream_printf (surface->final_stream,
-				     "save\n"
 				     "50 dict begin\n");
     } else {
 	_cairo_output_stream_printf (surface->final_stream,
@@ -457,6 +479,22 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface)
 				     "  } ifelse\n"
 				     "} def\n");
     }
+    if (surface->contains_eps) {
+	_cairo_output_stream_printf (surface->final_stream,
+				     "/cairo_eps_begin {\n"
+				     "  /cairo_save_state save def\n"
+				     "  /dict_count countdictstack def\n"
+				     "  /op_count count 1 sub def\n"
+				     "  userdict begin\n"
+				     "  /showpage { } def\n"
+				     "  0 g 0 J 1 w 0 j 10 M [ ] 0 d n\n"
+				     "} bind def\n"
+				     "/cairo_eps_end {\n"
+				     "  count op_count sub { pop } repeat\n"
+				     "  countdictstack dict_count sub { end } repeat\n"
+				     "  cairo_save_state restore\n"
+				     "} bind def\n");
+    }
 
     _cairo_output_stream_printf (surface->final_stream,
 				 "%%%%EndProlog\n");
@@ -954,7 +992,7 @@ _cairo_ps_surface_emit_footer (cairo_ps_surface_t *surface)
 
     if (surface->eps) {
 	_cairo_output_stream_printf (surface->final_stream,
-				     "end restore\n");
+				     "end\n");
     }
 
     _cairo_output_stream_printf (surface->final_stream,
@@ -1162,6 +1200,7 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream,
     surface->document_bbox_p2.x = 0;
     surface->document_bbox_p2.y = 0;
     surface->total_form_size = 0;
+    surface->contains_eps = FALSE;
 
     _cairo_surface_clipper_init (&surface->clipper,
 				 _cairo_ps_surface_clipper_intersect_clip_path);
@@ -3160,6 +3199,127 @@ _cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t          *surface,
     return status;
 }
 
+/* The '|' character is not used in PS (including ASCII85).  We can
+ * speed up the search by first searching for the first char before
+ * comparing strings.
+ */
+#define SUBFILE_FILTER_EOD "|EOD|"
+
+/* Count number of non overlapping occurrences of SUBFILE_FILTER_EOD in data. */
+static int
+count_eod_strings (const unsigned char *data, unsigned long data_len)
+{
+    const unsigned char *p = data;
+    const unsigned char *end;
+    int first_char, len, count;
+    const char *eod_str = SUBFILE_FILTER_EOD;
+
+    first_char = eod_str[0];
+    len = strlen (eod_str);
+    p = data;
+    end = data + data_len - len + 1;
+    count = 0;
+    while (p < end) {
+	p = memchr (p, first_char, end - p);
+	if (!p)
+	    break;
+
+	if (memcmp (p, eod_str, len) == 0) {
+	    count++;
+	    p += len;
+	}
+    }
+
+    return count;
+}
+
+static cairo_status_t
+_cairo_ps_surface_emit_eps (cairo_ps_surface_t          *surface,
+			    cairo_emit_surface_mode_t    mode,
+			    cairo_emit_surface_params_t *params)
+{
+    cairo_status_t status;
+    const unsigned char *eps_data = NULL;
+    unsigned long eps_data_len;
+    const unsigned char *eps_params_string = NULL;
+    unsigned long eps_params_string_len;
+    char *params_string = NULL;
+    cairo_eps_params_t eps_params;
+    cairo_matrix_t mat;
+    double eps_width, eps_height;
+
+    if (unlikely (params->src_surface->status))
+	return params->src_surface->status;
+
+    /* We only embed EPS with level 3 as we may use ReusableStreamDecode and we
+     * don't know what level the EPS file requires. */
+    if (surface->ps_level == CAIRO_PS_LEVEL_2)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_EPS,
+				 &eps_data, &eps_data_len);
+    if (eps_data == NULL)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_EPS_PARAMS,
+				 &eps_params_string, &eps_params_string_len);
+    if (eps_params_string == NULL)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    /* ensure params_string is null terminated */
+    params_string = malloc (eps_params_string_len + 1);
+    memcpy (params_string, eps_params_string, eps_params_string_len);
+    params_string[eps_params_string_len] = 0;
+    status = _cairo_tag_parse_eps_params (params_string, &eps_params);
+    if (unlikely(status))
+	return status;
+
+    /* At this point we know emitting EPS will succeed. */
+    if (mode == CAIRO_EMIT_SURFACE_ANALYZE) {
+	params->is_image = FALSE;
+	params->approx_size = eps_data_len;
+	surface->contains_eps = TRUE;
+
+	/* Find number of occurences of SUBFILE_FILTER_EOD in the EPS data.
+	 * We will need it before emitting the data if a ReusableStream is used.
+         */
+	params->eod_count = count_eod_strings (eps_data, eps_data_len);
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    surface->ps_level_used = CAIRO_PS_LEVEL_3;
+    _cairo_output_stream_printf (surface->stream, "cairo_eps_begin\n");
+
+    eps_width = eps_params.bbox.p2.x - eps_params.bbox.p1.x;
+    eps_height = eps_params.bbox.p2.y - eps_params.bbox.p1.y;
+    cairo_matrix_init_translate (&mat,
+				 params->src_surface_extents->x,
+				 params->src_surface_extents->y);
+    cairo_matrix_scale (&mat,
+			params->src_surface_extents->width/eps_width,
+			params->src_surface_extents->height/eps_height);
+    cairo_matrix_scale (&mat, 1, -1);
+    cairo_matrix_translate (&mat, -eps_params.bbox.p1.x, -eps_params.bbox.p2.y);
+
+    if (! _cairo_matrix_is_identity (&mat)) {
+	_cairo_output_stream_printf (surface->stream, "[ ");
+	_cairo_output_stream_print_matrix (surface->stream, &mat);
+	_cairo_output_stream_printf (surface->stream, " ] concat\n");
+    }
+
+    _cairo_output_stream_printf (surface->stream,
+				 "%f %f %f %f rectclip\n",
+				 eps_params.bbox.p1.x,
+				 eps_params.bbox.p1.y,
+				 eps_width,
+				 eps_height);
+
+    _cairo_output_stream_write (surface->stream, eps_data, eps_data_len);
+    _cairo_output_stream_printf (surface->stream, "\ncairo_eps_end\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static cairo_status_t
 _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
 					  cairo_surface_t             *recording_surface,
@@ -3457,6 +3617,14 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t          *surface,
 	    return status;
     }
 
+    status = _cairo_ps_surface_emit_eps (surface, mode, params);
+    if (status == CAIRO_INT_STATUS_SUCCESS) {
+	params->is_image = FALSE;
+	goto surface_emitted;
+    }
+    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+	return status;
+
     status = _cairo_ps_surface_emit_jpeg_image (surface, mode, params);
     if (status == CAIRO_INT_STATUS_SUCCESS) {
 	params->is_image = TRUE;
@@ -3527,11 +3695,6 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t          *surface,
     return status;
 }
 
-/* The '|' character is not used in PS (including ASCII85) so we can
- * use it as a /SubFileDecode EOD marker and assume EODCount will be 0.
- */
-#define SUBFILE_FILTER_EOD "|EOD|"
-
 static void
 _cairo_ps_form_emit (void *entry, void *closure)
 {
@@ -3771,6 +3934,8 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
 
     params.src_surface = image ? &image->base : source_surface;
     params.op = op;
+    params.src_surface_extents = &src_surface_extents;
+    params.src_surface_bounded = src_surface_bounded;
     params.src_op_extents = &src_op_extents;
     params.filter = pattern->filter;
     params.stencil_mask = stencil_mask;
@@ -3920,6 +4085,8 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
 
     params.src_surface = image ? &image->base : source_surface;
     params.op = op;
+    params.src_surface_extents = &pattern_extents;
+    params.src_surface_bounded = bounded;
     params.src_op_extents = &src_op_extents;
     params.filter = pattern->filter;
     params.stencil_mask = FALSE;
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index e04c478f..961894a1 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -1281,6 +1281,24 @@ _cairo_surface_has_mime_image (cairo_surface_t *surface)
  **/
 
 /**
+ * CAIRO_MIME_TYPE_EPS:
+ *
+ * Encapsulated PostScript file.
+ * [Encapsulated PostScript File Format Specification](http://wwwimages.adobe.com/content/dam/Adobe/endevnet/postscript/pdfs/5002.EPSF_Spec.pdf)
+ *
+ * Since: 1.16
+ **/
+
+/**
+ * CAIRO_MIME_TYPE_EPS_PARAMS:
+ *
+ * Embedding parameters Encapsulated PostScript data.
+ * See [Embedding EPS files][eps].
+ *
+ * Since: 1.16
+ **/
+
+/**
  * CAIRO_MIME_TYPE_JBIG2:
  *
  * Joint Bi-level Image Experts Group image coding standard (ISO/IEC 11544).
diff --git a/src/cairo-tag-attributes-private.h b/src/cairo-tag-attributes-private.h
index 30bb48ed..3f5fa5b6 100644
--- a/src/cairo-tag-attributes-private.h
+++ b/src/cairo-tag-attributes-private.h
@@ -39,6 +39,7 @@
 
 #include "cairo-array-private.h"
 #include "cairo-error-private.h"
+#include "cairo-types-private.h"
 
 typedef enum {
     TAG_LINK_INVALID = 0,
@@ -79,6 +80,9 @@ typedef struct _cairo_ccitt_params {
     int damaged_rows_before_error;
 } cairo_ccitt_params_t;
 
+typedef struct _cairo_eps_params {
+    cairo_box_double_t bbox;
+} cairo_eps_params_t;
 
 cairo_private cairo_int_status_t
 _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs);
@@ -89,4 +93,7 @@ _cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *de
 cairo_private cairo_int_status_t
 _cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *dest_attrs);
 
+cairo_private cairo_int_status_t
+_cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *dest_attrs);
+
 #endif /* CAIRO_TAG_ATTRIBUTES_PRIVATE_H */
diff --git a/src/cairo-tag-attributes.c b/src/cairo-tag-attributes.c
index 64173402..05902b9c 100644
--- a/src/cairo-tag-attributes.c
+++ b/src/cairo-tag-attributes.c
@@ -143,6 +143,20 @@ static attribute_spec_t _ccitt_params_spec[] =
     { NULL }
 };
 
+/*
+ * bbox - Bounding box of EPS file. The format is [ llx lly urx ury ]
+ *          llx - lower left x xoordinate
+ *          lly - lower left y xoordinate
+ *          urx - upper right x xoordinate
+ *          ury - upper right y xoordinate
+ *        all cordinates are in PostScript coordinates.
+ */
+static attribute_spec_t _eps_params_spec[] =
+{
+    { "bbox", ATTRIBUTE_FLOAT, 4 },
+    { NULL }
+};
+
 typedef union {
     cairo_bool_t b;
     int i;
@@ -649,3 +663,36 @@ _cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *cci
 
     return status;
 }
+
+cairo_int_status_t
+_cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *eps_params)
+{
+    cairo_list_t list;
+    cairo_int_status_t status;
+    attribute_t *attr;
+    attrib_val_t val;
+
+    cairo_list_init (&list);
+    status = parse_attributes (attributes, _eps_params_spec, &list);
+    if (unlikely (status))
+	goto cleanup;
+
+    cairo_list_foreach_entry (attr, attribute_t, &list, link)
+    {
+	if (strcmp (attr->name, "bbox") == 0) {
+	    _cairo_array_copy_element (&attr->array, 0, &val);
+	    eps_params->bbox.p1.x = val.f;
+	    _cairo_array_copy_element (&attr->array, 1, &val);
+	    eps_params->bbox.p1.y = val.f;
+	    _cairo_array_copy_element (&attr->array, 2, &val);
+	    eps_params->bbox.p2.x = val.f;
+	    _cairo_array_copy_element (&attr->array, 3, &val);
+	    eps_params->bbox.p2.y = val.f;
+	}
+    }
+
+  cleanup:
+    free_attributes_list (&list);
+
+    return status;
+}
diff --git a/src/cairo.h b/src/cairo.h
index 671be5a8..e4815725 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -2455,6 +2455,8 @@ cairo_surface_set_user_data (cairo_surface_t		 *surface,
 #define CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID "application/x-cairo.jbig2-global-id"
 #define CAIRO_MIME_TYPE_CCITT_FAX "image/g3fax"
 #define CAIRO_MIME_TYPE_CCITT_FAX_PARAMS "application/x-cairo.ccitt.params"
+#define CAIRO_MIME_TYPE_EPS "application/postscript"
+#define CAIRO_MIME_TYPE_EPS_PARAMS "application/x-cairo.eps.params"
 
 cairo_public void
 cairo_surface_get_mime_data (cairo_surface_t		*surface,
commit 7d3ba77b6cd20f68cfbda3d8008811265aa030cb
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: use Reusable streams for forms in Level 3
    
    to avoid emitting image data as strings

diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 68d66937..076fe9eb 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -3400,6 +3400,10 @@ _cairo_ps_surface_emit_form (cairo_ps_surface_t          *surface,
     if (test || status)
 	return status;
 
+    /* _cairo_ps_form_emit will use Level 3 if permitted by ps_level */
+    if (surface->ps_level == CAIRO_PS_LEVEL_3)
+	surface->ps_level_used = CAIRO_PS_LEVEL_3;
+
     _cairo_output_stream_printf (surface->stream,
 				 "/cairoform-%d /Form findresource execform\n",
 				 ps_form->id);
@@ -3523,6 +3527,11 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t          *surface,
     return status;
 }
 
+/* The '|' character is not used in PS (including ASCII85) so we can
+ * use it as a /SubFileDecode EOD marker and assume EODCount will be 0.
+ */
+#define SUBFILE_FILTER_EOD "|EOD|"
+
 static void
 _cairo_ps_form_emit (void *entry, void *closure)
 {
@@ -3536,7 +3545,6 @@ _cairo_ps_form_emit (void *entry, void *closure)
     params.src_op_extents = &form->required_extents;
     params.filter = form->filter;
     params.stencil_mask = FALSE;
-    params.paint_proc = TRUE;
     params.is_image = form->is_image;
     params.approx_size = 0;
     cairo_output_stream_t *old_stream;
@@ -3546,9 +3554,24 @@ _cairo_ps_form_emit (void *entry, void *closure)
 				 form->id);
 
     _cairo_output_stream_printf (surface->final_stream,
-				 "/cairo_paint_form-%d {\n"
-				 "5 dict begin\n",
+				 "/cairo_paint_form-%d",
 				 form->id);
+    if (surface->ps_level == CAIRO_PS_LEVEL_3) {
+	params.paint_proc = FALSE;
+	_cairo_output_stream_printf (surface->final_stream,
+				     "\n"
+				     "currentfile\n"
+				     "<< /Filter /SubFileDecode\n"
+				     "   /DecodeParms << /EODString (%s) /EODCount 0 >>\n"
+				     ">> /ReusableStreamDecode filter\n",
+				     SUBFILE_FILTER_EOD);
+    } else {
+	params.paint_proc = TRUE;
+	_cairo_output_stream_printf (surface->final_stream,
+				     " {\n");
+    }
+    _cairo_output_stream_printf (surface->final_stream,
+				 "5 dict begin\n");
 
     old_stream = surface->stream;
     surface->stream = surface->final_stream;
@@ -3561,8 +3584,19 @@ _cairo_ps_form_emit (void *entry, void *closure)
     _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->stream);
 
     _cairo_output_stream_printf (surface->final_stream,
-				 "end\n"
-				 "} bind def\n"
+				 "end\n");
+    if (surface->ps_level == CAIRO_PS_LEVEL_3) {
+	_cairo_output_stream_printf (surface->final_stream,
+				     "%s\n"
+				     "def\n",
+				     SUBFILE_FILTER_EOD);
+    } else {
+	_cairo_output_stream_printf (surface->final_stream,
+				     "} bind def\n");
+    }
+
+    _cairo_output_stream_printf (surface->final_stream,
+				 "\n"
 				 "/cairoform-%d\n"
 				 "<<\n"
 				 "  /FormType 1\n",
@@ -3582,11 +3616,18 @@ _cairo_ps_form_emit (void *entry, void *closure)
 
     _cairo_output_stream_printf (surface->final_stream,
 				 "  /Matrix [ 1 0 0 1 0 0 ]\n"
-				 "  /PaintProc { pop cairo_paint_form-%d } bind\n"
-				 ">>\n"
-				 "/Form defineresource pop\n",
+				 "  /PaintProc { pop cairo_paint_form-%d",
 				 form->id);
 
+    if (surface->ps_level == CAIRO_PS_LEVEL_3) {
+	_cairo_output_stream_printf (surface->final_stream,
+				     " dup 0 setfileposition cvx exec");
+    }
+    _cairo_output_stream_printf (surface->final_stream,
+				 " } bind\n"
+				 ">>\n"
+				 "/Form defineresource pop\n");
+
     _cairo_output_stream_printf (surface->final_stream,
 				 "%%%%EndResource\n");
     if (status)
diff --git a/test/mime-unique-id.c b/test/mime-unique-id.c
index 4a4a75f9..bdd05613 100755
--- a/test/mime-unique-id.c
+++ b/test/mime-unique-id.c
@@ -80,7 +80,7 @@
  * surfaces are still embedded only once, update the expected sizes.
  */
 #define PS2_EXPECTED_SIZE 417510
-#define PS3_EXPECTED_SIZE 380912
+#define PS3_EXPECTED_SIZE 381554
 #define PDF_EXPECTED_SIZE 347182
 #define SIZE_TOLERANCE      5000
 
commit b1c7a087b5da9a3bfa1e05e2e99861e07723c6f8
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: use forms for surfaces with UNIQUE_ID mime type
    
    to ensure the surfaces are emitted only once.
    
    fixes mime-unique-id PS output

diff --git a/src/cairo-ps-surface-private.h b/src/cairo-ps-surface-private.h
index 7cd275d7..651acdb3 100644
--- a/src/cairo-ps-surface-private.h
+++ b/src/cairo-ps-surface-private.h
@@ -49,6 +49,19 @@
 
 #include <time.h>
 
+typedef struct _cairo_ps_form {
+    cairo_hash_entry_t base;
+    unsigned char *unique_id;
+    unsigned long unique_id_length;
+    cairo_bool_t is_image;
+    int id;
+    cairo_surface_t *src_surface;
+    cairo_filter_t filter;
+
+    /* Union of source extents required for all operations using this form */
+    cairo_rectangle_int_t required_extents;
+} cairo_ps_form_t;
+
 typedef struct cairo_ps_surface {
     cairo_surface_t base;
 
@@ -71,8 +84,6 @@ typedef struct cairo_ps_surface {
     cairo_bool_t surface_bounded;
     cairo_matrix_t cairo_to_ps;
 
-    cairo_bool_t use_string_datasource;
-
     cairo_bool_t current_pattern_is_solid_color;
     cairo_color_t current_color;
 
@@ -100,6 +111,10 @@ typedef struct cairo_ps_surface {
 
     cairo_pdf_operators_t pdf_operators;
     cairo_surface_t *paginated_surface;
+    cairo_hash_table_t *forms;
+    int num_forms;
+
+    long total_form_size;
 } cairo_ps_surface_t;
 
 #endif /* CAIRO_PS_SURFACE_PRIVATE_H */
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 7e18f571..68d66937 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -85,6 +85,13 @@
 #include <zlib.h>
 #include <errno.h>
 
+/* Forms are emitted at the start and stored in memory so we limit the
+ * total size of all forms to prevent running out of memory. If this
+ * limit is exceeded, surfaces that would be stored in forms are
+ * emitted each time the surface is used. */
+#define MAX_L2_FORM_DATA (256*1024)
+#define MAX_L3_FORM_DATA (2*1024*1024) /* Assume Level 3 printers have more memory */
+
 /* #define DEBUG_PS 1 */
 
 #if DEBUG_PS
@@ -111,6 +118,12 @@
  * %CAIRO_MIME_TYPE_UNIQUE_ID,
  * %CAIRO_MIME_TYPE_CCITT_FAX, %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS.
  *
+ * Source surfaces used by the PostScript surface that have a
+ * %CAIRO_MIME_TYPE_UNIQUE_ID mime type will be stored in PostScript
+ * printer memory for the duration of the print
+ * job. %CAIRO_MIME_TYPE_UNIQUE_ID should only be used for small
+ * frequently used sources.
+ *
  * The %CAIRO_MIME_TYPE_CCITT_FAX and %CAIRO_MIME_TYPE_CCITT_FAX_PARAMS mime types
  * are documented in [CCITT Fax Images][ccitt].
  **/
@@ -128,7 +141,28 @@ typedef enum {
     CAIRO_PS_COMPRESS_NONE,
     CAIRO_PS_COMPRESS_LZW,
     CAIRO_PS_COMPRESS_DEFLATE
- } cairo_ps_compress_t;
+} cairo_ps_compress_t;
+
+typedef enum {
+    CAIRO_EMIT_SURFACE_ANALYZE,
+    CAIRO_EMIT_SURFACE_EMIT,
+    CAIRO_EMIT_SURFACE_EMIT_FORM
+} cairo_emit_surface_mode_t;
+
+typedef struct  {
+    /* input params */
+    cairo_surface_t *src_surface;
+    cairo_operator_t op;
+    const cairo_rectangle_int_t *src_op_extents; /* operation extents in src space */
+    cairo_filter_t filter;
+    cairo_bool_t stencil_mask; /* TRUE if source is to be used as a mask */
+    cairo_bool_t paint_proc; /* TRUE if surface will be used in a PaintProc */
+
+    /* output params */
+    cairo_bool_t is_image; /* returns TRUE if PS image will be emitted */
+                           /*         FALSE if recording will be emitted */
+    long approx_size;
+} cairo_emit_surface_params_t;
 
 static const cairo_surface_backend_t cairo_ps_surface_backend;
 static const cairo_paginated_surface_backend_t cairo_ps_surface_paginated_backend;
@@ -137,6 +171,9 @@ static cairo_bool_t
 _cairo_ps_surface_get_extents (void		       *abstract_surface,
 			       cairo_rectangle_int_t   *rectangle);
 
+static void
+_cairo_ps_form_emit (void *entry, void *closure);
+
 static const cairo_ps_level_t _cairo_ps_levels[] =
 {
     CAIRO_PS_LEVEL_2,
@@ -194,6 +231,40 @@ typedef struct _cairo_page_media {
 } cairo_page_media_t;
 
 static void
+_cairo_ps_form_init_key (cairo_ps_form_t *key)
+{
+    key->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE,
+					key->unique_id, key->unique_id_length);
+}
+
+static cairo_bool_t
+_cairo_ps_form_equal (const void *key_a, const void *key_b)
+{
+    const cairo_ps_form_t *a = key_a;
+    const cairo_ps_form_t *b = key_b;
+
+    if (a->filter != b->filter)
+	return FALSE;
+
+    if (a->unique_id_length != b->unique_id_length)
+	return FALSE;
+
+    return memcmp (a->unique_id, b->unique_id, a->unique_id_length) == 0;
+}
+
+static void
+_cairo_ps_form_pluck (void *entry, void *closure)
+{
+    cairo_ps_form_t *surface_entry = entry;
+    cairo_hash_table_t *patterns = closure;
+
+    _cairo_hash_table_remove (patterns, &surface_entry->base);
+    free (surface_entry->unique_id);
+    cairo_surface_destroy (surface_entry->src_surface);
+    free (surface_entry);
+}
+
+static void
 _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface)
 {
     char ctime_buf[26];
@@ -389,8 +460,6 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface)
 
     _cairo_output_stream_printf (surface->final_stream,
 				 "%%%%EndProlog\n");
-    _cairo_output_stream_printf (surface->final_stream,
-				 "%%%%BeginSetup\n");
 
     num_comments = _cairo_array_num_elements (&surface->dsc_setup_comments);
     if (num_comments) {
@@ -848,6 +917,16 @@ _cairo_ps_surface_emit_font_subsets (cairo_ps_surface_t *surface)
 						    surface);
 }
 
+
+static cairo_int_status_t
+_cairo_ps_surface_emit_forms (cairo_ps_surface_t *surface)
+{
+    _cairo_hash_table_foreach (surface->forms,
+			       _cairo_ps_form_emit,
+			       surface);
+    return surface->base.status;
+}
+
 static cairo_status_t
 _cairo_ps_surface_emit_body (cairo_ps_surface_t *surface)
 {
@@ -1077,12 +1156,12 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream,
     surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE;
     surface->force_fallbacks = FALSE;
     surface->content = CAIRO_CONTENT_COLOR_ALPHA;
-    surface->use_string_datasource = FALSE;
     surface->current_pattern_is_solid_color = FALSE;
     surface->document_bbox_p1.x = 0;
     surface->document_bbox_p1.y = 0;
     surface->document_bbox_p2.x = 0;
     surface->document_bbox_p2.y = 0;
+    surface->total_form_size = 0;
 
     _cairo_surface_clipper_init (&surface->clipper,
 				 _cairo_ps_surface_clipper_intersect_clip_path);
@@ -1099,6 +1178,13 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream,
     _cairo_array_init (&surface->dsc_setup_comments, sizeof (char *));
     _cairo_array_init (&surface->dsc_page_setup_comments, sizeof (char *));
 
+    surface->num_forms = 0;
+    surface->forms = _cairo_hash_table_create (_cairo_ps_form_equal);
+    if (unlikely (surface->forms == NULL)) {
+	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+	goto CLEANUP_FONT_SUBSETS;
+    }
+
     surface->dsc_comment_target = &surface->dsc_header_comments;
 
     surface->paginated_surface = _cairo_paginated_surface_create (
@@ -1112,6 +1198,7 @@ _cairo_ps_surface_create_for_stream_internal (cairo_output_stream_t *stream,
 	return surface->paginated_surface;
     }
 
+ CLEANUP_FONT_SUBSETS:
     _cairo_scaled_font_subsets_destroy (surface->font_subsets);
  CLEANUP_OUTPUT_STREAM:
     status_ignored = _cairo_output_stream_destroy (surface->stream);
@@ -1634,10 +1721,17 @@ _cairo_ps_surface_finish (void *abstract_surface)
 
     _cairo_ps_surface_emit_header (surface);
 
+    _cairo_output_stream_printf (surface->final_stream,
+				 "%%%%BeginSetup\n");
+
     status = _cairo_ps_surface_emit_font_subsets (surface);
     if (unlikely (status))
 	goto CLEANUP;
 
+    status = _cairo_ps_surface_emit_forms (surface);
+    if (unlikely (status))
+	goto CLEANUP;
+
     _cairo_output_stream_printf (surface->final_stream,
 				 "%%%%EndSetup\n");
 
@@ -1648,6 +1742,10 @@ _cairo_ps_surface_finish (void *abstract_surface)
     _cairo_ps_surface_emit_footer (surface);
 
 CLEANUP:
+    _cairo_hash_table_foreach (surface->forms,
+			       _cairo_ps_form_pluck,
+			       surface->forms);
+    _cairo_hash_table_destroy (surface->forms);
     _cairo_scaled_font_subsets_destroy (surface->font_subsets);
 
     status2 = _cairo_output_stream_destroy (surface->stream);
@@ -1737,7 +1835,8 @@ color_is_gray (double red, double green, double blue)
 /**
  * _cairo_ps_surface_acquire_source_surface_from_pattern:
  * @surface: [in] the ps surface
- * @pattern: [in] A #cairo_pattern_t of type SURFACE or RASTER_SOURCE to use as the source
+ * @pattern: [in] A #cairo_pattern_t of type SURFACE or RASTER_SOURCE to use
+ *                as the source
  * @extents: [in] extents of the operation that is using this source
  * @src_surface_extents: [out] return source surface extents
  * @src_surface_bounded: [out] return TRUE if source surface is bounded
@@ -1749,15 +1848,16 @@ color_is_gray (double red, double green, double blue)
  * Acquire source surface or raster source pattern.
  **/
 static cairo_status_t
-_cairo_ps_surface_acquire_source_surface_from_pattern (cairo_ps_surface_t           *surface,
-						       const cairo_pattern_t        *pattern,
-						       const cairo_rectangle_int_t  *extents,
-						       cairo_rectangle_int_t        *src_surface_extents,
-						       cairo_bool_t                 *src_surface_bounded,
-						       cairo_rectangle_int_t        *src_op_extents,
-						       cairo_surface_t             **source_surface,
-						       double                       *x_offset,
-						       double                       *y_offset)
+_cairo_ps_surface_acquire_source_surface_from_pattern (
+    cairo_ps_surface_t           *surface,
+    const cairo_pattern_t        *pattern,
+    const cairo_rectangle_int_t  *extents,
+    cairo_rectangle_int_t        *src_surface_extents,
+    cairo_bool_t                 *src_surface_bounded,
+    cairo_rectangle_int_t        *src_op_extents,
+    cairo_surface_t             **source_surface,
+    double                       *x_offset,
+    double                       *y_offset)
 {
     cairo_status_t status;
     cairo_box_t bbox;
@@ -2420,11 +2520,9 @@ get_interpolate (cairo_filter_t	filter)
 }
 
 static cairo_status_t
-_cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
-			      cairo_image_surface_t *image_surf,
-			      cairo_operator_t	     op,
-			      cairo_filter_t         filter,
-			      cairo_bool_t           stencil_mask)
+_cairo_ps_surface_emit_image (cairo_ps_surface_t          *surface,
+			      cairo_emit_surface_mode_t    mode,
+			      cairo_emit_surface_params_t *params)
 {
     cairo_status_t status;
     unsigned char *data;
@@ -2440,10 +2538,16 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
     const char *interpolate;
     cairo_ps_compress_t compress;
     const char *compress_filter;
+    cairo_image_surface_t *image_surf;
     cairo_image_surface_t *image;
+    void *image_extra;
 
-    if (image_surf->base.status)
-	return image_surf->base.status;
+    if (params->src_surface->status)
+	return params->src_surface->status;
+
+    status = _cairo_surface_acquire_source_image (params->src_surface, &image_surf, &image_extra);
+    if (unlikely (status))
+	return status;
 
     image  = image_surf;
     if (image->format != CAIRO_FORMAT_RGB24 &&
@@ -2454,16 +2558,16 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 	cairo_surface_t *surf;
 	cairo_surface_pattern_t pattern;
 
-	surf = _cairo_image_surface_create_with_content (image_surf->base.content,
-							 image_surf->width,
-							 image_surf->height);
+	surf = _cairo_image_surface_create_with_content (image->base.content,
+							 image->width,
+							 image->height);
 	image = (cairo_image_surface_t *) surf;
 	if (surf->status) {
 	    status = surf->status;
 	    goto bail0;
 	}
 
-	_cairo_pattern_init_for_surface (&pattern, &image_surf->base);
+	_cairo_pattern_init_for_surface (&pattern, &image->base);
 	status = _cairo_surface_paint (surf,
 				       CAIRO_OPERATOR_SOURCE, &pattern.base,
 				       NULL);
@@ -2472,9 +2576,9 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
             goto bail0;
     }
     ps_image = image;
-    interpolate = get_interpolate (filter);
+    interpolate = get_interpolate (params->filter);
 
-    if (stencil_mask) {
+    if (params->stencil_mask) {
 	use_mask = FALSE;
 	color = CAIRO_IMAGE_IS_MONOCHROME;
 	transparency = CAIRO_IMAGE_HAS_BILEVEL_ALPHA;
@@ -2485,7 +2589,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 	   current image over a white (or black for CONTENT_COLOR
 	   surfaces) RGB surface to eliminate it. */
 
-	if (op == CAIRO_OPERATOR_SOURCE ||
+	if (params->op == CAIRO_OPERATOR_SOURCE ||
 	    transparency == CAIRO_IMAGE_HAS_ALPHA ||
 	    (transparency == CAIRO_IMAGE_HAS_BILEVEL_ALPHA &&
 	     surface->ps_level == CAIRO_PS_LEVEL_2))
@@ -2535,7 +2639,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 
     i = 0;
     for (y = 0; y < ps_image->height; y++) {
-	if (stencil_mask || use_mask) {
+	if (params->stencil_mask || use_mask) {
 	    /* mask row */
 	    if (ps_image->format == CAIRO_FORMAT_A1) {
 		pixel8 = (uint8_t *) (ps_image->data + y * ps_image->stride);
@@ -2576,7 +2680,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 		    i++;
 	    }
 	}
-	if (stencil_mask)
+	if (params->stencil_mask)
 	    continue;
 
 	/* image row*/
@@ -2641,7 +2745,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 	surface->ps_level_used = CAIRO_PS_LEVEL_3;
     }
 
-    if (surface->use_string_datasource) {
+    if (params->paint_proc) {
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator later. */
 	_cairo_output_stream_printf (surface->stream,
@@ -2684,7 +2788,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 				     color == CAIRO_IMAGE_IS_MONOCHROME ? 1 : 8,
 				     color == CAIRO_IMAGE_IS_COLOR ? "0 1 0 1 0 1" : "0 1");
 
-	if (surface->use_string_datasource) {
+	if (params->paint_proc) {
 	    _cairo_output_stream_printf (surface->stream,
 					 "    /DataSource { cairo_data_source } /%s filter\n",
 					 compress_filter);
@@ -2718,11 +2822,20 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 				     -ps_image->height,
 				     ps_image->height);
     } else {
-	if (!stencil_mask) {
+	const char *decode;
+
+	if (!params->stencil_mask) {
 	    _cairo_output_stream_printf (surface->stream,
 					 "%s setcolorspace\n",
 					 color == CAIRO_IMAGE_IS_COLOR ? "/DeviceRGB" : "/DeviceGray");
 	}
+	if (params->stencil_mask)
+	    decode = "1 0";
+	else if (color == CAIRO_IMAGE_IS_COLOR)
+	    decode = "0 1 0 1 0 1";
+	else
+	    decode ="0 1";
+
 	_cairo_output_stream_printf (surface->stream,
 				     "<<\n"
 				     "  /ImageType 1\n"
@@ -2735,8 +2848,8 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 				     ps_image->height,
 				     interpolate,
 				     color == CAIRO_IMAGE_IS_MONOCHROME ? 1 : 8,
-				     stencil_mask ? "1 0" : color == CAIRO_IMAGE_IS_COLOR ? "0 1 0 1 0 1" : "0 1");
-	if (surface->use_string_datasource) {
+				     decode);
+	if (params->paint_proc) {
 	    _cairo_output_stream_printf (surface->stream,
 					 "  /DataSource { cairo_data_source } /%s filter\n",
 					 compress_filter);
@@ -2753,11 +2866,11 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 				     ps_image->width,
 				     -ps_image->height,
 				     ps_image->height,
-				     surface->use_string_datasource ? "" : "cairo_",
-				     stencil_mask ? "imagemask" : "image");
+				     params->paint_proc ? "" : "cairo_",
+				     params->stencil_mask ? "imagemask" : "image");
     }
 
-    if (!surface->use_string_datasource) {
+    if (!params->paint_proc) {
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator. */
 	status = _cairo_ps_surface_emit_base85_string (surface,
@@ -2781,13 +2894,15 @@ bail0:
     if (image != image_surf)
 	cairo_surface_destroy (&image->base);
 
+    _cairo_surface_release_source_image (params->src_surface, image_surf, image_extra);
+
     return status;
 }
 
-static cairo_status_t
-_cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
-				   cairo_surface_t	 *source,
-				   cairo_filter_t	  filter)
+static cairo_int_status_t
+_cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t          *surface,
+				   cairo_emit_surface_mode_t    mode,
+				   cairo_emit_surface_params_t *params)
 {
     cairo_status_t status;
     const unsigned char *mime_data;
@@ -2796,10 +2911,11 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
     const char *colorspace;
     const char *decode;
 
-    cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JPEG,
+    if (unlikely (params->src_surface->status))
+	return params->src_surface->status;
+
+    cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_JPEG,
 				 &mime_data, &mime_data_length);
-    if (unlikely (source->status))
-	return source->status;
     if (mime_data == NULL)
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
@@ -2824,11 +2940,18 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 	    return CAIRO_INT_STATUS_UNSUPPORTED;
     }
 
-    if (surface->use_string_datasource) {
+    /* At this point we know emitting jpeg will succeed. */
+    if (mode == CAIRO_EMIT_SURFACE_ANALYZE) {
+	params->is_image = TRUE;
+	params->approx_size = mime_data_length;
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    if (params->paint_proc) {
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator later. */
 	_cairo_output_stream_printf (surface->stream,
-				     "/CairoImageData [\n");
+				     "/CairoData [\n");
 
 	status = _cairo_ps_surface_emit_base85_string (surface,
 						       mime_data,
@@ -2841,7 +2964,7 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 	_cairo_output_stream_printf (surface->stream,
 				     "] def\n");
 	_cairo_output_stream_printf (surface->stream,
-				     "/CairoImageDataIndex 0 def\n");
+				     "/CairoDataIndex 0 def\n");
     } else {
 	_cairo_output_stream_printf (surface->stream,
 				     "/cairo_ascii85_file currentfile /ASCII85Decode filter def\n");
@@ -2860,10 +2983,10 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 				 info.width,
 				 info.height,
 				 info.bits_per_component,
-				 get_interpolate (filter),
+				 get_interpolate (params->filter),
                                  decode);
 
-    if (surface->use_string_datasource) {
+    if (params->paint_proc) {
 	_cairo_output_stream_printf (surface->stream,
 				     "  /DataSource { cairo_data_source } /DCTDecode filter\n");
     } else {
@@ -2878,9 +3001,9 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 				 info.width,
 				 -info.height,
 				 info.height,
-				 surface->use_string_datasource ? "" : "cairo_");
+				 params->paint_proc ? "" : "cairo_");
 
-    if (!surface->use_string_datasource) {
+    if (!params->paint_proc) {
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator. */
 	status = _cairo_ps_surface_emit_base85_string (surface,
@@ -2893,52 +3016,57 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
     return status;
 }
 
-static cairo_status_t
-_cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t   *surface,
-				    cairo_surface_t	 *source,
-				    cairo_filter_t	  filter,
-				    cairo_bool_t          stencil_mask)
+static cairo_int_status_t
+_cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t          *surface,
+				    cairo_emit_surface_mode_t    mode,
+				    cairo_emit_surface_params_t *params)
 {
     cairo_status_t status;
     const unsigned char *ccitt_data;
     unsigned long ccitt_data_len;
-    const unsigned char *ccitt_params_string;
-    unsigned long ccitt_params_string_len;
-    char *params;
+    const unsigned char *ccitt_params_data;
+    unsigned long ccitt_params_data_len;
+    char *ccitt_params_string;
     cairo_ccitt_params_t ccitt_params;
 
-    cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_CCITT_FAX,
+    if (unlikely (params->src_surface->status))
+	return params->src_surface->status;
+
+    cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_CCITT_FAX,
 				 &ccitt_data, &ccitt_data_len);
-    if (unlikely (source->status))
-	return source->status;
     if (ccitt_data == NULL)
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
-    cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_CCITT_FAX_PARAMS,
-				 &ccitt_params_string, &ccitt_params_string_len);
-    if (unlikely (source->status))
-	return source->status;
-    if (ccitt_params_string == NULL)
+    cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_CCITT_FAX_PARAMS,
+				 &ccitt_params_data, &ccitt_params_data_len);
+    if (ccitt_params_data == NULL)
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
     /* ensure params_string is null terminated */
-    params = malloc (ccitt_params_string_len + 1);
-    memcpy (params, ccitt_params_string, ccitt_params_string_len);
-    params[ccitt_params_string_len] = 0;
-    status = _cairo_tag_parse_ccitt_params (params, &ccitt_params);
+    ccitt_params_string = malloc (ccitt_params_data_len + 1);
+    memcpy (ccitt_params_string, ccitt_params_data, ccitt_params_data_len);
+    ccitt_params_string[ccitt_params_data_len] = 0;
+    status = _cairo_tag_parse_ccitt_params (ccitt_params_string, &ccitt_params);
     if (unlikely(status))
-	return source->status;
+	return status;
 
-    free (params);
+    free (ccitt_params_string);
 
     if (ccitt_params.columns <= 0 || ccitt_params.rows <= 0)
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
-    if (surface->use_string_datasource) {
+    /* At this point we know emitting ccitt will succeed. */
+    if (mode == CAIRO_EMIT_SURFACE_ANALYZE) {
+	params->is_image = TRUE;
+	params->approx_size = ccitt_data_len;
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    if (params->paint_proc) {
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator later. */
 	_cairo_output_stream_printf (surface->stream,
-				     "/CairoImageData [\n");
+				     "/CairoData [\n");
 
 	status = _cairo_ps_surface_emit_base85_string (surface,
 						       ccitt_data,
@@ -2951,13 +3079,13 @@ _cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t   *surface,
 	_cairo_output_stream_printf (surface->stream,
 				     "] def\n");
 	_cairo_output_stream_printf (surface->stream,
-				     "/CairoImageDataIndex 0 def\n");
+				     "/CairoDataIndex 0 def\n");
     } else {
 	_cairo_output_stream_printf (surface->stream,
 				     "/cairo_ascii85_file currentfile /ASCII85Decode filter def\n");
     }
 
-    if (!stencil_mask) {
+    if (!params->stencil_mask) {
 	_cairo_output_stream_printf (surface->stream,
 				     "/DeviceGray setcolorspace\n");
     }
@@ -2972,9 +3100,9 @@ _cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t   *surface,
 				 "  /Decode [ 0 1 ]\n",
 				 ccitt_params.columns,
 				 ccitt_params.rows,
-				 get_interpolate (filter));
+				 get_interpolate (params->filter));
 
-    if (surface->use_string_datasource) {
+    if (params->paint_proc) {
 	_cairo_output_stream_printf (surface->stream,
 				     "  /DataSource { cairo_data_source }\n");
     } else {
@@ -3016,10 +3144,10 @@ _cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t   *surface,
 				 ccitt_params.columns,
 				 -ccitt_params.rows,
 				 ccitt_params.rows,
-				 surface->use_string_datasource ? "" : "cairo_",
-				 stencil_mask ? "imagemask" : "image");
+				 params->paint_proc ? "" : "cairo_",
+				 params->stencil_mask ? "imagemask" : "image");
 
-    if (!surface->use_string_datasource) {
+    if (!params->paint_proc) {
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator. */
 	status = _cairo_ps_surface_emit_base85_string (surface,
@@ -3163,55 +3291,307 @@ _cairo_ps_surface_emit_solid_pattern (cairo_ps_surface_t    *surface,
 				     red, green, blue);
 }
 
-static cairo_status_t
+/*
+ * PS Forms are used for sources that have CAIRO_MIME_TYPE_UNIQUE_ID. They will be
+ * emitted once in the PS header and can be rendered with the 'execform' operator.
+ *
+ * This function tries adding the source the form hash table. If the source does not
+ * have CAIRO_MIME_TYPE_UNIQUE_ID, CAIRO_INT_STATUS_UNSUPPORTED is returned.
+
+ * @source: [in] the source for the form
+ * @params: [in] source parameters
+ * @test: [in] if TRUE, test if form will be used (excludes size check)
+ * @ps_form [out] the new or exisiting entry int the hash table.
+ *                image or recording.
+ */
+static cairo_int_status_t
+_cairo_ps_surface_use_form (cairo_ps_surface_t           *surface,
+			    cairo_emit_surface_params_t  *params,
+			    cairo_bool_t                  test,
+			    cairo_ps_form_t             **ps_form)
+{
+    cairo_ps_form_t source_key;
+    cairo_ps_form_t *source_entry;
+    unsigned char *unique_id = NULL;
+    unsigned long unique_id_length = 0;
+    cairo_status_t status;
+    long max_size;
+
+    if (params->op != CAIRO_OPERATOR_OVER || params->stencil_mask)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    if (params->src_surface->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    cairo_surface_get_mime_data (params->src_surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+				 (const unsigned char **) &source_key.unique_id,
+				 &source_key.unique_id_length);
+    if (source_key.unique_id == NULL || source_key.unique_id_length == 0)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    if (test)
+	return CAIRO_STATUS_SUCCESS;
+
+    source_key.filter = params->filter;
+    _cairo_ps_form_init_key (&source_key);
+    source_entry = _cairo_hash_table_lookup (surface->forms, &source_key.base);
+    if (source_entry) {
+	_cairo_rectangle_union (&source_entry->required_extents, params->src_op_extents);
+	*ps_form = source_entry;
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    if (surface->ps_level == CAIRO_PS_LEVEL_3)
+	max_size = MAX_L3_FORM_DATA;
+    else
+	max_size = MAX_L3_FORM_DATA;
+
+    /* Don't add any more Forms if we exceed the form memory limit */
+    if (surface->total_form_size + params->approx_size > max_size)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    surface->total_form_size += params->approx_size > max_size;
+    unique_id = _cairo_malloc (source_key.unique_id_length);
+    if (unique_id == NULL)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    unique_id_length = source_key.unique_id_length;
+    memcpy (unique_id, source_key.unique_id, unique_id_length);
+
+    source_entry = calloc (sizeof (cairo_ps_form_t), 1);
+    if (source_entry == NULL) {
+	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+	goto fail;
+    }
+
+    source_entry->unique_id_length = unique_id_length;
+    source_entry->unique_id = unique_id;
+    source_entry->id = surface->num_forms++;
+    source_entry->src_surface = cairo_surface_reference (params->src_surface);
+    source_entry->required_extents = *params->src_op_extents;
+    source_entry->filter = params->filter;
+    source_entry->is_image = params->is_image;
+    _cairo_ps_form_init_key (source_entry);
+    status = _cairo_hash_table_insert (surface->forms, &source_entry->base);
+    if (unlikely(status))
+	goto fail;
+
+    *ps_form = source_entry;
+    return CAIRO_STATUS_SUCCESS;
+
+  fail:
+    free (unique_id);
+    free (source_entry);
+    return status;
+}
+
+static cairo_int_status_t
+_cairo_ps_surface_emit_form (cairo_ps_surface_t          *surface,
+			     cairo_emit_surface_params_t *params,
+			     cairo_bool_t                 test)
+{
+    cairo_ps_form_t *ps_form;
+    cairo_status_t status;
+
+    status = _cairo_ps_surface_use_form (surface,
+					 params,
+					 test,
+					 &ps_form);
+    if (test || status)
+	return status;
+
+    _cairo_output_stream_printf (surface->stream,
+				 "/cairoform-%d /Form findresource execform\n",
+				 ps_form->id);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+/* Emit a surface. This function has three modes.
+ *
+ * CAIRO_EMIT_SURFACE_ANALYZE: This will determine the surface type to
+ * be emitted and approximate size. is_image is set to TRUE if the
+ * emitted surface is an image surface (including mime images). This
+ * is used by the caller to setup the correct CTM. approx_size is set
+ * to the approximate size of the emitted surface and is used as an
+ * input by the emit mode.
+ *
+ * CAIRO_EMIT_SURFACE_EMIT: Emits the surface will be emitted. The
+ * approx_size and the surface unique id values are used to determine
+ * if a Form should be used. If a form is used, the exec form
+ * operation is emitted and the surface is added to the forms hash
+ * table.
+ *
+ * CAIRO_EMIT_SURFACE_EMIT_FORM: Emits the form definition for the surface.
+ *
+ * Usage is:
+ * 1) Setup input params and call with ANALYZE.
+ * 2) Setup CTM for surface and call with EMIT using same params struct.
+ * The EMIT_FORM mode is used when emitting the form definitions.
+ */
+static cairo_int_status_t
 _cairo_ps_surface_emit_surface (cairo_ps_surface_t          *surface,
-				const cairo_pattern_t       *source_pattern,
-				cairo_surface_t             *source_surface,
-				cairo_operator_t	     op,
-				const cairo_rectangle_int_t *src_surface_extents,
-				cairo_bool_t                 src_surface_bounded,
-				const cairo_rectangle_int_t *src_op_extents,
-				cairo_bool_t                 stencil_mask)
+				cairo_emit_surface_mode_t    mode,
+				cairo_emit_surface_params_t *params)
 {
     cairo_int_status_t status;
+    cairo_output_stream_t *old_stream;
+    cairo_bool_t use_form;
+
+    /* Try emitting as a form. Returns unsupported if the surface is
+     * deemed unsuitable for a form. */
+    use_form = FALSE;
+    if (mode == CAIRO_EMIT_SURFACE_ANALYZE || mode == CAIRO_EMIT_SURFACE_EMIT) {
+	status = _cairo_ps_surface_emit_form (surface,
+					      params,
+					      mode == CAIRO_EMIT_SURFACE_ANALYZE);
+	use_form = (status == CAIRO_INT_STATUS_SUCCESS);
+	if (status != CAIRO_INT_STATUS_SUCCESS && status != CAIRO_INT_STATUS_UNSUPPORTED)
+	    return status;
 
-    if (source_pattern->type == CAIRO_PATTERN_TYPE_SURFACE &&
-	source_pattern->extend != CAIRO_EXTEND_PAD && src_surface_bounded)
-    {
-	cairo_surface_t *surf = ((cairo_surface_pattern_t *) source_pattern)->surface;
-
-	status = _cairo_ps_surface_emit_jpeg_image (surface, surf, source_pattern->filter);
-	if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+	if (mode == CAIRO_EMIT_SURFACE_EMIT && status == CAIRO_INT_STATUS_SUCCESS)
 	    return status;
+    }
+
+    status = _cairo_ps_surface_emit_jpeg_image (surface, mode, params);
+    if (status == CAIRO_INT_STATUS_SUCCESS) {
+	params->is_image = TRUE;
+	goto surface_emitted;
+    }
+    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+	return status;
+
+    status = _cairo_ps_surface_emit_ccitt_image (surface, mode, params);
+    if (status == CAIRO_INT_STATUS_SUCCESS) {
+	params->is_image = TRUE;
+	goto surface_emitted;
+    }
+    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+	return status;
 
-	status = _cairo_ps_surface_emit_ccitt_image (surface, surf, source_pattern->filter, stencil_mask);
-	if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+    if (mode == CAIRO_EMIT_SURFACE_ANALYZE) {
+	/* Find size of image or recording surface by emitting to a memory stream */
+	status = _cairo_pdf_operators_flush (&surface->pdf_operators);
+	if (unlikely (status))
 	    return status;
+
+	old_stream = surface->stream;
+	surface->stream = _cairo_memory_stream_create ();
+	_cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->stream);
     }
 
-    if (source_surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
-	if (source_surface->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) {
-	    cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) source_surface;
-	    status = _cairo_ps_surface_emit_recording_surface (surface, sub->target, &sub->extents, TRUE);
+    if (params->src_surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
+	params->is_image = FALSE;
+	if (params->src_surface->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) {
+	    cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) params->src_surface;
+	    status = _cairo_ps_surface_emit_recording_surface (surface,
+							       sub->target,
+							       &sub->extents,
+							       TRUE);
 	} else {
-	    status = _cairo_ps_surface_emit_recording_surface (surface, source_surface, src_op_extents, FALSE);
+	    status = _cairo_ps_surface_emit_recording_surface (surface,
+							       params->src_surface,
+							       params->src_op_extents,
+							       FALSE);
 	}
     } else {
-	cairo_image_surface_t *image;
-	void *image_extra;
+	params->is_image = TRUE;
+	status = _cairo_ps_surface_emit_image (surface, mode, params);
+    }
+
+    if (mode == CAIRO_EMIT_SURFACE_ANALYZE) {
+	unsigned char *data;
+	unsigned long length;
+
+	status = _cairo_pdf_operators_flush (&surface->pdf_operators);
+	if (unlikely (status))
+	    return status;
 
-	status = _cairo_surface_acquire_source_image (source_surface, &image, &image_extra);
+	status = _cairo_memory_stream_destroy (surface->stream, &data, &length);
+	free (data);
 	if (unlikely (status))
 	    return status;
 
-	status = _cairo_ps_surface_emit_image (surface, image,
-					       op, source_pattern->filter, stencil_mask);
-	_cairo_surface_release_source_image (source_surface, image, image_extra);
+	params->approx_size = length;
+	surface->stream = old_stream;
+	_cairo_pdf_operators_set_stream (&surface->pdf_operators,
+					 surface->stream);
     }
 
+  surface_emitted:
+
     return status;
 }
 
+static void
+_cairo_ps_form_emit (void *entry, void *closure)
+{
+    cairo_ps_form_t *form = entry;
+    cairo_ps_surface_t *surface = closure;
+    cairo_emit_surface_params_t params;
+    cairo_int_status_t status;
+
+    params.src_surface = form->src_surface;
+    params.op = CAIRO_OPERATOR_OVER;
+    params.src_op_extents = &form->required_extents;
+    params.filter = form->filter;
+    params.stencil_mask = FALSE;
+    params.paint_proc = TRUE;
+    params.is_image = form->is_image;
+    params.approx_size = 0;
+    cairo_output_stream_t *old_stream;
+
+    _cairo_output_stream_printf (surface->final_stream,
+				 "%%%%BeginResource: form cairoform-%d\n",
+				 form->id);
+
+    _cairo_output_stream_printf (surface->final_stream,
+				 "/cairo_paint_form-%d {\n"
+				 "5 dict begin\n",
+				 form->id);
+
+    old_stream = surface->stream;
+    surface->stream = surface->final_stream;
+    _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->stream);
+    status = _cairo_ps_surface_emit_surface (surface,
+					     CAIRO_EMIT_SURFACE_EMIT_FORM,
+					     &params);
+    status = _cairo_pdf_operators_flush (&surface->pdf_operators);
+    surface->stream = old_stream;
+    _cairo_pdf_operators_set_stream (&surface->pdf_operators, surface->stream);
+
+    _cairo_output_stream_printf (surface->final_stream,
+				 "end\n"
+				 "} bind def\n"
+				 "/cairoform-%d\n"
+				 "<<\n"
+				 "  /FormType 1\n",
+				 form->id);
+
+    if (form->is_image) {
+	_cairo_output_stream_printf (surface->final_stream,
+				     "  /BBox [ 0 0 1 1 ]\n");
+    } else {
+	_cairo_output_stream_printf (surface->final_stream,
+				     "  /BBox [ %d %d %d %d ]\n",
+				     form->required_extents.x,
+				     form->required_extents.y,
+				     form->required_extents.x + form->required_extents.width,
+				     form->required_extents.y + form->required_extents.height);
+    }
+
+    _cairo_output_stream_printf (surface->final_stream,
+				 "  /Matrix [ 1 0 0 1 0 0 ]\n"
+				 "  /PaintProc { pop cairo_paint_form-%d } bind\n"
+				 ">>\n"
+				 "/Form defineresource pop\n",
+				 form->id);
+
+    _cairo_output_stream_printf (surface->final_stream,
+				 "%%%%EndResource\n");
+    if (status)
+	surface->base.status = status;
+}
 
 static void
 _path_fixed_init_rectangle (cairo_path_fixed_t *path,
@@ -3257,6 +3637,7 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
     cairo_status_t status;
     cairo_matrix_t cairo_p2d, ps_p2d;
     cairo_path_fixed_t path;
+    cairo_emit_surface_params_t params;
     cairo_image_surface_t *image = NULL;
 
     status = _cairo_pdf_operators_flush (&surface->pdf_operators);
@@ -3346,9 +3727,21 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
     ps_p2d = surface->cairo_to_ps;
     cairo_matrix_multiply (&ps_p2d, &cairo_p2d, &ps_p2d);
     cairo_matrix_translate (&ps_p2d, x_offset, y_offset);
-    if (!(pattern->type == CAIRO_PATTERN_TYPE_SURFACE &&
-          ((cairo_surface_pattern_t *)pattern)->surface->type == CAIRO_SURFACE_TYPE_RECORDING))
-    {
+
+    params.src_surface = image ? &image->base : source_surface;
+    params.op = op;
+    params.src_op_extents = &src_op_extents;
+    params.filter = pattern->filter;
+    params.stencil_mask = stencil_mask;
+    params.paint_proc = FALSE;
+    params.is_image = FALSE;
+    params.approx_size = 0;
+
+    status = _cairo_ps_surface_emit_surface (surface, CAIRO_EMIT_SURFACE_ANALYZE, &params);
+    if (unlikely (status))
+	goto release_source;
+
+    if (params.is_image) {
 	cairo_matrix_translate (&ps_p2d, 0.0, src_surface_extents.height);
 	cairo_matrix_scale (&ps_p2d, 1.0, -1.0);
 	cairo_matrix_scale (&ps_p2d, src_surface_extents.width, src_surface_extents.height);
@@ -3360,14 +3753,7 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
 	_cairo_output_stream_printf (surface->stream, " ] concat\n");
     }
 
-    status = _cairo_ps_surface_emit_surface (surface,
-					     pattern,
-					     image ? &image->base : source_surface,
-					     op,
-					     &src_surface_extents,
-					     image ? TRUE : src_surface_bounded,
-					     &src_op_extents,
-					     stencil_mask);
+    status = _cairo_ps_surface_emit_surface (surface, CAIRO_EMIT_SURFACE_EMIT, &params);
 
   release_source:
     if (image)
@@ -3389,12 +3775,11 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
     cairo_rectangle_int_t pattern_extents;
     cairo_bool_t bounded;
     cairo_matrix_t cairo_p2d, ps_p2d;
-    cairo_bool_t old_use_string_datasource;
     double x_offset, y_offset;
     cairo_surface_t *source_surface;
     cairo_image_surface_t *image = NULL;
-    void *image_extra;
     cairo_rectangle_int_t src_op_extents;
+    cairo_emit_surface_params_t params;
     cairo_extend_t extend = cairo_pattern_get_extend (pattern);
 
     cairo_p2d = pattern->matrix;
@@ -3489,16 +3874,27 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
 				 pattern_extents.x, pattern_extents.y,
 				 pattern_extents.width, pattern_extents.height);
 
+    if (extend == CAIRO_EXTEND_REPEAT || extend == CAIRO_EXTEND_REFLECT)
+	src_op_extents = pattern_extents;
 
-    if (((cairo_surface_pattern_t *)pattern)->surface->type != CAIRO_SURFACE_TYPE_RECORDING)
-    {
+    params.src_surface = image ? &image->base : source_surface;
+    params.op = op;
+    params.src_op_extents = &src_op_extents;
+    params.filter = pattern->filter;
+    params.stencil_mask = FALSE;
+    params.paint_proc = TRUE;
+    params.is_image = FALSE;
+    params.approx_size = 0;
+    status = _cairo_ps_surface_emit_surface (surface, CAIRO_EMIT_SURFACE_ANALYZE, &params);
+    if (unlikely (status))
+	goto release_source;
+
+    if (params.is_image) {
 	_cairo_output_stream_printf (surface->stream,
 				     "[ %d 0 0 %d 0 0 ] concat\n",
 				     pattern_extents.width, pattern_extents.height);
     }
 
-    old_use_string_datasource = surface->use_string_datasource;
-    surface->use_string_datasource = TRUE;
     if (op == CAIRO_OPERATOR_SOURCE) {
 	_cairo_output_stream_printf (surface->stream,
 				     "%d g %d %d %f %f rectfill\n",
@@ -3507,21 +3903,10 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
 				     xstep, ystep);
     }
 
-    if (extend == CAIRO_EXTEND_REPEAT || extend == CAIRO_EXTEND_REFLECT)
-	src_op_extents = pattern_extents;
-
-    status = _cairo_ps_surface_emit_surface (surface,
-					     pattern,
-					     image ? &image->base : source_surface,
-					     op,
-					     &pattern_extents,
-					     bounded,
-					     &src_op_extents,
-					     FALSE);
+    status = _cairo_ps_surface_emit_surface (surface, CAIRO_EMIT_SURFACE_EMIT, &params);
     if (unlikely (status))
 	goto release_source;
 
-    surface->use_string_datasource = old_use_string_datasource;
     _cairo_output_stream_printf (surface->stream,
 				 " Q } bind def\n");
 
diff --git a/test/mime-unique-id.c b/test/mime-unique-id.c
index 1d7babe0..4a4a75f9 100755
--- a/test/mime-unique-id.c
+++ b/test/mime-unique-id.c
@@ -79,8 +79,8 @@
  * If the size check fails, manually check the output and if the
  * surfaces are still embedded only once, update the expected sizes.
  */
-#define PS2_EXPECTED_SIZE 315362
-#define PS3_EXPECTED_SIZE 315362
+#define PS2_EXPECTED_SIZE 417510
+#define PS3_EXPECTED_SIZE 380912
 #define PDF_EXPECTED_SIZE 347182
 #define SIZE_TOLERANCE      5000
 
commit 638d64a702449fee130b103e623c8fa3b8c06e9b
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: don't acquire image or snapshot in acquire_source_image_from_pattern
    
    otherwise emit_surface may not see the mime data

diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 0e804da3..7e18f571 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -1745,7 +1745,6 @@ color_is_gray (double red, double green, double blue)
  * @source_surface: [out] returns surface of type image surface or recording surface
  * @x_offset: [out] return x offset of surface
  * @y_offset: [out] return y offset of surface
- * @image_extra: [out] returns image extra for image type surface
  *
  * Acquire source surface or raster source pattern.
  **/
@@ -1758,25 +1757,39 @@ _cairo_ps_surface_acquire_source_surface_from_pattern (cairo_ps_surface_t
 						       cairo_rectangle_int_t        *src_op_extents,
 						       cairo_surface_t             **source_surface,
 						       double                       *x_offset,
-						       double                       *y_offset,
-						       void                        **image_extra)
+						       double                       *y_offset)
 {
-    cairo_status_t          status;
-    cairo_image_surface_t  *image;
+    cairo_status_t status;
+    cairo_box_t bbox;
 
     *x_offset = 0;
     *y_offset = 0;
-    *image_extra = NULL;
-    switch (pattern->type) {
-    case CAIRO_PATTERN_TYPE_SURFACE: {
-	cairo_box_t bbox;
-	cairo_surface_t *surf = ((cairo_surface_pattern_t *) pattern)->surface;
 
+    /* get the operation extents in pattern space */
+    _cairo_box_from_rectangle (&bbox, extents);
+    _cairo_matrix_transform_bounding_box_fixed (&pattern->matrix, &bbox, NULL);
+    _cairo_box_round_to_rectangle (&bbox, src_op_extents);
+
+    if (pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) {
+	cairo_surface_t *surf;
+
+	surf = _cairo_raster_source_pattern_acquire (pattern, &surface->base, src_op_extents);
+	if (!surf)
+	    return CAIRO_INT_STATUS_UNSUPPORTED;
+
+	*src_surface_bounded = _cairo_surface_get_extents (surf, src_surface_extents);
+	cairo_surface_get_device_offset (surf, x_offset, y_offset);
+	*source_surface = surf;
+    } else if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	cairo_surface_t *surf;
+
+	*source_surface = ((cairo_surface_pattern_t *) pattern)->surface;
+	surf = *source_surface;
+	*src_surface_bounded = _cairo_surface_get_extents (surf, src_surface_extents);
 	if (surf->type == CAIRO_SURFACE_TYPE_RECORDING) {
-	    if (_cairo_surface_is_snapshot (surf)) {
+	    if (_cairo_surface_is_snapshot (surf))
 		surf = _cairo_surface_snapshot_get_target (surf);
-		*image_extra = surf;
-	    }
+
 	    if (surf->backend->type == CAIRO_SURFACE_TYPE_SUBSURFACE) {
 		cairo_surface_subsurface_t *sub = (cairo_surface_subsurface_t *) surf;
 
@@ -1784,89 +1797,32 @@ _cairo_ps_surface_acquire_source_surface_from_pattern (cairo_ps_surface_t
 		*src_surface_bounded = TRUE;
 		*x_offset = -sub->extents.x;
 		*y_offset = -sub->extents.y;
-	    } else {
-		*src_surface_bounded = _cairo_surface_get_extents (surf, src_surface_extents);
 	    }
-	    *source_surface = surf;
-	    _cairo_box_from_rectangle (&bbox, extents);
-	    _cairo_matrix_transform_bounding_box_fixed (&pattern->matrix, &bbox, NULL);
-	    _cairo_box_round_to_rectangle (&bbox, src_op_extents);
+	} else if (surf->type != CAIRO_SURFACE_TYPE_IMAGE) {
+	    cairo_image_surface_t *image;
+	    void *image_extra;
 
-	    return CAIRO_STATUS_SUCCESS;
-	} else {
-	    status =  _cairo_surface_acquire_source_image (surf, &image, image_extra);
-	    if (unlikely (status)) {
-		cairo_surface_destroy (*image_extra);
+	    status = _cairo_surface_acquire_source_image (surf, &image, &image_extra);
+	    if (unlikely (status))
 		return status;
-	    }
-    }
-    } break;
-
-    case CAIRO_PATTERN_TYPE_RASTER_SOURCE: {
-	cairo_surface_t *surf;
-	cairo_box_t box;
-	cairo_rectangle_int_t rect;
 
-	/* get the operation extents in pattern space */
-	_cairo_box_from_rectangle (&box, extents);
-	_cairo_matrix_transform_bounding_box_fixed (&pattern->matrix, &box, NULL);
-	_cairo_box_round_to_rectangle (&box, &rect);
-	surf = _cairo_raster_source_pattern_acquire (pattern, &surface->base, &rect);
-	if (!surf)
-	    return CAIRO_INT_STATUS_UNSUPPORTED;
-	assert (_cairo_surface_is_image (surf));
-	image = (cairo_image_surface_t *) surf;
-	cairo_surface_get_device_offset (surf, x_offset, y_offset);
-    } break;
-
-    case CAIRO_PATTERN_TYPE_SOLID:
-    case CAIRO_PATTERN_TYPE_LINEAR:
-    case CAIRO_PATTERN_TYPE_RADIAL:
-    case CAIRO_PATTERN_TYPE_MESH:
-    default:
+	    *src_surface_bounded = _cairo_surface_get_extents (&image->base, src_surface_extents);
+	    _cairo_surface_release_source_image (surf, image, image_extra);
+	}
+    } else {
 	ASSERT_NOT_REACHED;
-	break;
     }
 
-    src_surface_extents->x = 0;
-    src_surface_extents->y = 0;
-    src_surface_extents->width = image->width;
-    src_surface_extents->height = image->height;
-    *src_surface_bounded = TRUE;
-    *source_surface = &image->base;
     return CAIRO_STATUS_SUCCESS;
 }
 
 static void
 _cairo_ps_surface_release_source_surface_from_pattern (cairo_ps_surface_t           *surface,
 						       const cairo_pattern_t        *pattern,
-						       cairo_surface_t              *source_surface,
-						       void                         *image_extra)
+						       cairo_surface_t              *source_surface)
 {
-    switch (pattern->type) {
-    case CAIRO_PATTERN_TYPE_SURFACE: {
-	cairo_surface_pattern_t *surf_pat = (cairo_surface_pattern_t *) pattern;
-	if (surf_pat->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
-	    cairo_surface_destroy (image_extra);
-	} else {
-	    cairo_image_surface_t *image  = (cairo_image_surface_t *) source_surface;
-	    _cairo_surface_release_source_image (surf_pat->surface, image, image_extra);
-	}
-    } break;
-
-    case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
+    if  (pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE)
 	_cairo_raster_source_pattern_release (pattern, source_surface);
-	break;
-
-    case CAIRO_PATTERN_TYPE_SOLID:
-    case CAIRO_PATTERN_TYPE_LINEAR:
-    case CAIRO_PATTERN_TYPE_RADIAL:
-    case CAIRO_PATTERN_TYPE_MESH:
-    default:
-
-	ASSERT_NOT_REACHED;
-	break;
-    }
 }
 
 /**
@@ -1946,8 +1902,8 @@ _cairo_ps_surface_analyze_surface_pattern_transparency (cairo_ps_surface_t
     cairo_rectangle_int_t src_op_extents;
     cairo_surface_t *source_surface;
     double x_offset, y_offset;
-    void *image_extra;
     cairo_image_surface_t *image;
+    void *image_extra;
     cairo_int_status_t status;
     cairo_image_transparency_t transparency;
 
@@ -1959,13 +1915,14 @@ _cairo_ps_surface_analyze_surface_pattern_transparency (cairo_ps_surface_t
 								    &src_op_extents,
 								    &source_surface,
 								    &x_offset,
-								    &y_offset,
-								    &image_extra);
+								    &y_offset);
+    if (unlikely (status))
+	return status;
+
+    status = _cairo_surface_acquire_source_image (source_surface, &image, &image_extra);
     if (unlikely (status))
 	return status;
 
-    assert (_cairo_surface_is_image (source_surface));
-    image = (cairo_image_surface_t *) source_surface;
     if (image->base.status)
 	return image->base.status;
 
@@ -1992,7 +1949,8 @@ _cairo_ps_surface_analyze_surface_pattern_transparency (cairo_ps_surface_t
 	ASSERT_NOT_REACHED;
     }
 
-    _cairo_ps_surface_release_source_surface_from_pattern (surface, pattern, source_surface, image_extra);
+    _cairo_surface_release_source_image (source_surface, image, image_extra);
+    _cairo_ps_surface_release_source_surface_from_pattern (surface, pattern, source_surface);
 
     return status;
 }
@@ -3087,6 +3045,10 @@ _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
     cairo_content_t old_content;
     cairo_surface_clipper_t old_clipper;
     cairo_int_status_t status;
+    cairo_surface_t *free_me = NULL;
+
+    if (_cairo_surface_is_snapshot (recording_surface))
+	free_me = recording_surface = _cairo_surface_snapshot_get_target (recording_surface);
 
     old_content = surface->content;
     old_width = surface->width;
@@ -3154,6 +3116,7 @@ _cairo_ps_surface_emit_recording_surface (cairo_ps_surface_t          *surface,
 
     _cairo_pdf_operators_set_cairo_to_pdf_matrix (&surface->pdf_operators,
 						  &surface->cairo_to_ps);
+    cairo_surface_destroy (free_me);
 
     return status;
 }
@@ -3234,10 +3197,16 @@ _cairo_ps_surface_emit_surface (cairo_ps_surface_t          *surface,
 	    status = _cairo_ps_surface_emit_recording_surface (surface, source_surface, src_op_extents, FALSE);
 	}
     } else {
-	cairo_image_surface_t *image = (cairo_image_surface_t *) source_surface;
+	cairo_image_surface_t *image;
+	void *image_extra;
+
+	status = _cairo_surface_acquire_source_image (source_surface, &image, &image_extra);
+	if (unlikely (status))
+	    return status;
 
 	status = _cairo_ps_surface_emit_image (surface, image,
 					       op, source_pattern->filter, stencil_mask);
+	_cairo_surface_release_source_image (source_surface, image, image_extra);
     }
 
     return status;
@@ -3285,7 +3254,6 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
     cairo_rectangle_int_t src_op_extents;
     cairo_surface_t *source_surface;
     double x_offset, y_offset;
-    void *image_extra;
     cairo_status_t status;
     cairo_matrix_t cairo_p2d, ps_p2d;
     cairo_path_fixed_t path;
@@ -3303,8 +3271,7 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
 								    &src_op_extents,
 								    &source_surface,
 								    &x_offset,
-								    &y_offset,
-								    &image_extra);
+								    &y_offset);
     if (unlikely (status))
 	return status;
 
@@ -3406,7 +3373,7 @@ _cairo_ps_surface_paint_surface (cairo_ps_surface_t     *surface,
     if (image)
 	cairo_surface_destroy (&image->base);
 
-    _cairo_ps_surface_release_source_surface_from_pattern (surface, pattern, source_surface, image_extra);
+    _cairo_ps_surface_release_source_surface_from_pattern (surface, pattern, source_surface);
 
     return status;
 }
@@ -3442,8 +3409,7 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
 								    &bounded,
 								    &src_op_extents,
 								    &source_surface,
-								    &x_offset, &y_offset,
-								    &image_extra);
+								    &x_offset, &y_offset);
     if (unlikely (status))
 	return status;
 
@@ -3648,7 +3614,7 @@ _cairo_ps_surface_emit_surface_pattern (cairo_ps_surface_t      *surface,
     if (image)
 	cairo_surface_destroy (&image->base);
 
-    _cairo_ps_surface_release_source_surface_from_pattern (surface, pattern, source_surface, image_extra);
+    _cairo_ps_surface_release_source_surface_from_pattern (surface, pattern, source_surface);
 
     return status;
 }
commit fcc037a76ecd70050e324d0e3f7decd81f2f0a05
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: use << >> for dictionaries instead of dict begin end

diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 0a65cd6e..0e804da3 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -2709,17 +2709,16 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
     if (use_mask) {
 	_cairo_output_stream_printf (surface->stream,
 				     "%s setcolorspace\n"
-				     "5 dict dup begin\n"
-				     "  /ImageType 3 def\n"
-				     "  /InterleaveType 2 def\n"
-				     "  /DataDict 8 dict def\n"
-				     "    DataDict begin\n"
-				     "    /ImageType 1 def\n"
-				     "    /Width %d def\n"
-				     "    /Height %d def\n"
-				     "    /Interpolate %s def\n"
-				     "    /BitsPerComponent %d def\n"
-				     "    /Decode [ %s ] def\n",
+				     "<<\n"
+				     "  /ImageType 3\n"
+				     "  /InterleaveType 2\n"
+				     "  /DataDict <<\n"
+				     "    /ImageType 1\n"
+				     "    /Width %d\n"
+				     "    /Height %d\n"
+				     "    /Interpolate %s\n"
+				     "    /BitsPerComponent %d\n"
+				     "    /Decode [ %s ]\n",
 				     color == CAIRO_IMAGE_IS_COLOR ? "/DeviceRGB" : "/DeviceGray",
 				     ps_image->width,
 				     ps_image->height,
@@ -2729,28 +2728,27 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 
 	if (surface->use_string_datasource) {
 	    _cairo_output_stream_printf (surface->stream,
-					 "    /DataSource { cairo_data_source } /%s filter def\n",
+					 "    /DataSource { cairo_data_source } /%s filter\n",
 					 compress_filter);
 	} else {
 	    _cairo_output_stream_printf (surface->stream,
-					 "    /DataSource cairo_ascii85_file /%s filter def\n",
+					 "    /DataSource cairo_ascii85_file /%s filter\n",
 					 compress_filter);
 	}
 
 	_cairo_output_stream_printf (surface->stream,
-				     "    /ImageMatrix [ %d 0 0 %d 0 %d ] def\n"
-				     "  end\n"
-				     "  /MaskDict 8 dict def\n"
-				     "     MaskDict begin\n"
-				     "    /ImageType 1 def\n"
-				     "    /Width %d def\n"
-				     "    /Height %d def\n"
-				     "    /Interpolate %s def\n"
-				     "    /BitsPerComponent 1 def\n"
-				     "    /Decode [ 1 0 ] def\n"
-				     "    /ImageMatrix [ %d 0 0 %d 0 %d ] def\n"
-				     "  end\n"
-				     "end\n"
+				     "    /ImageMatrix [ %d 0 0 %d 0 %d ]\n"
+				     "  >>\n"
+				     "  /MaskDict <<\n"
+				     "    /ImageType 1\n"
+				     "    /Width %d\n"
+				     "    /Height %d\n"
+				     "    /Interpolate %s\n"
+				     "    /BitsPerComponent 1\n"
+				     "    /Decode [ 1 0 ]\n"
+				     "    /ImageMatrix [ %d 0 0 %d 0 %d ]\n"
+				     "  >>\n"
+				     ">>\n"
 				     "image\n",
 				     ps_image->width,
 				     -ps_image->height,
@@ -2768,13 +2766,13 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 					 color == CAIRO_IMAGE_IS_COLOR ? "/DeviceRGB" : "/DeviceGray");
 	}
 	_cairo_output_stream_printf (surface->stream,
-				     "8 dict dup begin\n"
-				     "  /ImageType 1 def\n"
-				     "  /Width %d def\n"
-				     "  /Height %d def\n"
-				     "  /Interpolate %s def\n"
-				     "  /BitsPerComponent %d def\n"
-				     "  /Decode [ %s ] def\n",
+				     "<<\n"
+				     "  /ImageType 1\n"
+				     "  /Width %d\n"
+				     "  /Height %d\n"
+				     "  /Interpolate %s\n"
+				     "  /BitsPerComponent %d\n"
+				     "  /Decode [ %s ]\n",
 				     ps_image->width,
 				     ps_image->height,
 				     interpolate,
@@ -2782,17 +2780,17 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 				     stencil_mask ? "1 0" : color == CAIRO_IMAGE_IS_COLOR ? "0 1 0 1 0 1" : "0 1");
 	if (surface->use_string_datasource) {
 	    _cairo_output_stream_printf (surface->stream,
-					 "  /DataSource { cairo_data_source } /%s filter def\n",
+					 "  /DataSource { cairo_data_source } /%s filter\n",
 					 compress_filter);
 	} else {
 	    _cairo_output_stream_printf (surface->stream,
-					 "  /DataSource cairo_ascii85_file /%s filter def\n",
+					 "  /DataSource cairo_ascii85_file /%s filter\n",
 					 compress_filter);
 	}
 
 	_cairo_output_stream_printf (surface->stream,
-				     "  /ImageMatrix [ %d 0 0 %d 0 %d ] def\n"
-				     "end\n"
+				     "  /ImageMatrix [ %d 0 0 %d 0 %d ]\n"
+				     ">>\n"
 				     "%s%s\n",
 				     ps_image->width,
 				     -ps_image->height,
@@ -2893,13 +2891,13 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 
     _cairo_output_stream_printf (surface->stream,
 				 "%s setcolorspace\n"
-				 "8 dict dup begin\n"
-				 "  /ImageType 1 def\n"
-				 "  /Width %d def\n"
-				 "  /Height %d def\n"
-				 "  /BitsPerComponent %d def\n"
-				 "  /Interpolate %s def\n"
-				 "  /Decode [ %s ] def\n",
+				 "<<\n"
+				 "  /ImageType 1\n"
+				 "  /Width %d\n"
+				 "  /Height %d\n"
+				 "  /BitsPerComponent %d\n"
+				 "  /Interpolate %s\n"
+				 "  /Decode [ %s ]\n",
 				 colorspace,
 				 info.width,
 				 info.height,
@@ -2909,15 +2907,15 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 
     if (surface->use_string_datasource) {
 	_cairo_output_stream_printf (surface->stream,
-				     "  /DataSource { cairo_data_source } /DCTDecode filter def\n");
+				     "  /DataSource { cairo_data_source } /DCTDecode filter\n");
     } else {
 	_cairo_output_stream_printf (surface->stream,
-				     "  /DataSource cairo_ascii85_file /DCTDecode filter def\n");
+				     "  /DataSource cairo_ascii85_file /DCTDecode filter\n");
     }
 
     _cairo_output_stream_printf (surface->stream,
-				 "  /ImageMatrix [ %d 0 0 %d 0 %d ] def\n"
-				 "end\n"
+				 "  /ImageMatrix [ %d 0 0 %d 0 %d ]\n"
+				 ">>\n"
 				 "%simage\n",
 				 info.width,
 				 -info.height,
commit 632232c3ce465e31929ab026012c731491492d6a
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: remove unused prolog

diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 901c61d7..0a65cd6e 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -319,9 +319,6 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface)
 				 "/W* { eoclip } bind def\n"
 				 "/BT { } bind def\n"
 				 "/ET { } bind def\n"
-				 "/pdfmark where { pop globaldict /?pdfmark /exec load put }\n"
-				 "    { globaldict begin /?pdfmark /pop load def /pdfmark\n"
-				 "    /cleartomark load def end } ifelse\n"
 				 "/BDC { mark 3 1 roll /BDC pdfmark } bind def\n"
 				 "/EMC { mark /EMC pdfmark } bind def\n"
 				 "/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def\n"
commit 9d372ca91a067b00fc1f66f466ab795206f5ef14
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    ps: emit base85 strings instead of strings of base85
    
    When image data is emitted as strings (required when an image is used
    in a PaintProc), the base85 encoded data was emitted inside PS strings
    (...) and the image filters included an ASCI85Decode filter.
    
    This has been changed to emit the strings as ASCII85 strings <~...~>
    and remove the ASCII85Decode filter since the base85 is decoded when
    the string is parsed.
    
    Also factor out the string data source procedure into the prolog.

diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 905ee2c8..901c61d7 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -346,6 +346,11 @@ _cairo_ps_surface_emit_header (cairo_ps_surface_t *surface)
 				 "/g { setgray } bind def\n"
 				 "/rg { setrgbcolor } bind def\n"
 				 "/d1 { setcachedevice } bind def\n"
+				 "/cairo_data_source {\n"
+				 "  CairoDataIndex CairoData length lt\n"
+				 "    { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }\n"
+				 "    { () } ifelse\n"
+				 "} def\n"
 				 "/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def\n"
 				 "/cairo_image { image cairo_flush_ascii85_file } def\n"
 				 "/cairo_imagemask { imagemask cairo_flush_ascii85_file } def\n");
@@ -2177,14 +2182,9 @@ _cairo_ps_surface_operation_supported (cairo_ps_surface_t    *surface,
 
 /* The "standard" implementation limit for PostScript string sizes is
  * 65535 characters (see PostScript Language Reference, Appendix
- * B). We go one short of that because we sometimes need two
- * characters in a string to represent a single ASCII85 byte, (for the
- * escape sequences "\\", "\(", and "\)") and we must not split these
- * across two strings. So we'd be in trouble if we went right to the
- * limit and one of these escape sequences just happened to land at
- * the end.
+ * B).
  */
-#define STRING_ARRAY_MAX_STRING_SIZE (65535-1)
+#define STRING_ARRAY_MAX_STRING_SIZE 65535
 #define STRING_ARRAY_MAX_COLUMN	     72
 
 typedef struct _string_array_stream {
@@ -2192,64 +2192,62 @@ typedef struct _string_array_stream {
     cairo_output_stream_t *output;
     int column;
     int string_size;
+    int tuple_count;
     cairo_bool_t use_strings;
 } string_array_stream_t;
 
 static cairo_status_t
-_string_array_stream_write (cairo_output_stream_t *base,
-			    const unsigned char   *data,
-			    unsigned int	   length)
+_base85_string_wrap_stream_write (cairo_output_stream_t *base,
+				  const unsigned char   *data,
+				  unsigned int	   length)
 {
     string_array_stream_t *stream = (string_array_stream_t *) base;
     unsigned char c;
-    const unsigned char backslash = '\\';
 
     if (length == 0)
 	return CAIRO_STATUS_SUCCESS;
 
     while (length--) {
-	if (stream->string_size == 0 && stream->use_strings) {
-	    _cairo_output_stream_printf (stream->output, "(");
-	    stream->column++;
-	}
-
-	c = *data++;
-	if (stream->use_strings) {
-	    switch (c) {
-	    case '\\':
-	    case '(':
-	    case ')':
-		_cairo_output_stream_write (stream->output, &backslash, 1);
-		stream->column++;
-		stream->string_size++;
-		break;
+	if (stream->column == 0) {
+	    if (stream->use_strings) {
+		_cairo_output_stream_printf (stream->output, "<~");
+		stream->column = 2;
+	    } else {
+		_cairo_output_stream_printf (stream->output, " ");
+		stream->column = 1;
 	    }
 	}
-	/* Have to be careful to never split the final ~> sequence. */
-        if (c == '~') {
-	    _cairo_output_stream_write (stream->output, &c, 1);
-	    stream->column++;
-	    stream->string_size++;
-
-	    if (length-- == 0)
-		break;
 
-	    c = *data++;
-	}
+	c = *data++;
 	_cairo_output_stream_write (stream->output, &c, 1);
 	stream->column++;
-	stream->string_size++;
 
+	/* Base85 encodes each 4 byte tuple with a 5 ASCII character
+	 * tuple, except for 'z' with represents 4 zero bytes. We need
+	 * to keep track of the string length after decoding.
+	 */
+	if (c == 'z') {
+	    stream->string_size += 4;
+	    stream->tuple_count = 0;
+	} else {
+	    if (++stream->tuple_count == 5) {
+		stream->string_size += 4;
+		stream->tuple_count = 0;
+	    }
+	}
+
+	/* Split string at tuple boundary when there is not enough
+	 * space for another tuple */
 	if (stream->use_strings &&
-	    stream->string_size >= STRING_ARRAY_MAX_STRING_SIZE)
+	    stream->tuple_count == 0 &&
+	    stream->string_size > STRING_ARRAY_MAX_STRING_SIZE - 4)
 	{
-	    _cairo_output_stream_printf (stream->output, ")\n");
+	    _cairo_output_stream_printf (stream->output, "~>\n");
 	    stream->string_size = 0;
 	    stream->column = 0;
 	}
 	if (stream->column >= STRING_ARRAY_MAX_COLUMN) {
 	    _cairo_output_stream_printf (stream->output, "\n ");
-	    stream->string_size += 2;
 	    stream->column = 1;
 	}
     }
@@ -2258,35 +2256,28 @@ _string_array_stream_write (cairo_output_stream_t *base,
 }
 
 static cairo_status_t
-_string_array_stream_close (cairo_output_stream_t *base)
+_base85_string_wrap_stream_close (cairo_output_stream_t *base)
 {
-    cairo_status_t status;
     string_array_stream_t *stream = (string_array_stream_t *) base;
 
-    if (stream->use_strings)
-	_cairo_output_stream_printf (stream->output, ")\n");
-
-    status = _cairo_output_stream_get_status (stream->output);
+    if (!stream->use_strings || stream->string_size != 0)
+	_cairo_output_stream_printf (stream->output, "~>");
 
-    return status;
+    return _cairo_output_stream_get_status (stream->output);
 }
 
-/* A string_array_stream wraps an existing output stream. It takes the
- * data provided to it and output one or more consecutive string
- * objects, each within the standard PostScript implementation limit
- * of 65k characters.
- *
- * The strings are each separated by a space character for easy
- * inclusion within an array object, (but the array delimiters are not
- * added by the string_array_stream).
- *
+/* A _base85_strings_stream wraps an existing output stream. It takes
+ * base85 encoded data and splits it into strings each limited to
+ * STRING_ARRAY_MAX_STRING_SIZE bytes when decoded. Each string is
+ * enclosed in "<~" and "~>".
+
  * The string array stream is also careful to wrap the output within
- * STRING_ARRAY_MAX_COLUMN columns (+/- 1). The stream also adds
- * necessary escaping for special characters within a string,
- * (specifically '\', '(', and ')').
+ * STRING_ARRAY_MAX_COLUMN columns. Wrapped lines start with a space
+ * in case an encoded line starts with %% which could be interpreted
+ * as a DSC comment.
  */
 static cairo_output_stream_t *
-_string_array_stream_create (cairo_output_stream_t *output)
+_base85_strings_stream_create (cairo_output_stream_t *output)
 {
     string_array_stream_t *stream;
 
@@ -2297,23 +2288,26 @@ _string_array_stream_create (cairo_output_stream_t *output)
     }
 
     _cairo_output_stream_init (&stream->base,
-			       _string_array_stream_write,
+			       _base85_string_wrap_stream_write,
 			       NULL,
-			       _string_array_stream_close);
+			       _base85_string_wrap_stream_close);
     stream->output = output;
     stream->column = 0;
     stream->string_size = 0;
+    stream->tuple_count = 0;
     stream->use_strings = TRUE;
 
     return &stream->base;
 }
 
-/* A base85_array_stream wraps an existing output stream. It wraps the
- * output within STRING_ARRAY_MAX_COLUMN columns (+/- 1). The output
- * is not enclosed in strings like string_array_stream.
+/* A base85_wrap_stream wraps an existing output stream. It wraps the
+ * output within STRING_ARRAY_MAX_COLUMN columns. A base85 EOD "~>" is
+ * appended to the end. Wrapped lines start with a space in case an
+ * encoded line starts with %% which could be interpreted as a DSC
+ * comment.
  */
 static cairo_output_stream_t *
-_base85_array_stream_create (cairo_output_stream_t *output)
+_base85_wrap_stream_create (cairo_output_stream_t *output)
 {
     string_array_stream_t *stream;
 
@@ -2324,12 +2318,13 @@ _base85_array_stream_create (cairo_output_stream_t *output)
     }
 
     _cairo_output_stream_init (&stream->base,
-			       _string_array_stream_write,
+			       _base85_string_wrap_stream_write,
 			       NULL,
-			       _string_array_stream_close);
+			       _base85_string_wrap_stream_close);
     stream->output = output;
     stream->column = 0;
     stream->string_size = 0;
+    stream->tuple_count = 0;
     stream->use_strings = FALSE;
 
     return &stream->base;
@@ -2391,9 +2386,9 @@ _cairo_ps_surface_emit_base85_string (cairo_ps_surface_t    *surface,
     cairo_status_t status, status2;
 
     if (use_strings)
-	string_array_stream = _string_array_stream_create (surface->stream);
+	string_array_stream = _base85_strings_stream_create (surface->stream);
     else
-	string_array_stream = _base85_array_stream_create (surface->stream);
+	string_array_stream = _base85_wrap_stream_create (surface->stream);
 
     status = _cairo_output_stream_get_status (string_array_stream);
     if (unlikely (status))
@@ -2440,9 +2435,6 @@ _cairo_ps_surface_emit_base85_string (cairo_ps_surface_t    *surface,
 	    break;
     }
     status = _cairo_output_stream_destroy (base85_stream);
-
-    /* Mark end of base85 data */
-    _cairo_output_stream_printf (string_array_stream, "~>");
     status2 = _cairo_output_stream_destroy (string_array_stream);
     if (status == CAIRO_STATUS_SUCCESS)
 	status = status2;
@@ -2698,7 +2690,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 	/* Emit the image data as a base85-encoded string which will
 	 * be used as the data source for the image operator later. */
 	_cairo_output_stream_printf (surface->stream,
-				     "/CairoImageData [\n");
+				     "/CairoData [\n");
 
 	status = _cairo_ps_surface_emit_base85_string (surface,
 						       data,
@@ -2711,7 +2703,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 	_cairo_output_stream_printf (surface->stream,
 				     "] def\n");
 	_cairo_output_stream_printf (surface->stream,
-				     "/CairoImageDataIndex 0 def\n");
+				     "/CairoDataIndex 0 def\n");
     } else {
 	_cairo_output_stream_printf (surface->stream,
 				     "/cairo_ascii85_file currentfile /ASCII85Decode filter def\n");
@@ -2740,12 +2732,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 
 	if (surface->use_string_datasource) {
 	    _cairo_output_stream_printf (surface->stream,
-					 "    /DataSource {\n"
-					 "      CairoImageData CairoImageDataIndex get\n"
-					 "	/CairoImageDataIndex CairoImageDataIndex 1 add def\n"
-					 "	CairoImageDataIndex CairoImageData length 1 sub gt\n"
-					 "       { /CairoImageDataIndex 0 def } if\n"
-					 "    } /ASCII85Decode filter /%s filter def\n",
+					 "    /DataSource { cairo_data_source } /%s filter def\n",
 					 compress_filter);
 	} else {
 	    _cairo_output_stream_printf (surface->stream,
@@ -2798,12 +2785,7 @@ _cairo_ps_surface_emit_image (cairo_ps_surface_t    *surface,
 				     stencil_mask ? "1 0" : color == CAIRO_IMAGE_IS_COLOR ? "0 1 0 1 0 1" : "0 1");
 	if (surface->use_string_datasource) {
 	    _cairo_output_stream_printf (surface->stream,
-					 "  /DataSource {\n"
-					 "    CairoImageData CairoImageDataIndex get\n"
-					 "    /CairoImageDataIndex CairoImageDataIndex 1 add def\n"
-					 "    CairoImageDataIndex CairoImageData length 1 sub gt\n"
-					 "     { /CairoImageDataIndex 0 def } if\n"
-					 "  } /ASCII85Decode filter /%s filter def\n",
+					 "  /DataSource { cairo_data_source } /%s filter def\n",
 					 compress_filter);
 	} else {
 	    _cairo_output_stream_printf (surface->stream,
@@ -2930,12 +2912,7 @@ _cairo_ps_surface_emit_jpeg_image (cairo_ps_surface_t    *surface,
 
     if (surface->use_string_datasource) {
 	_cairo_output_stream_printf (surface->stream,
-				     "  /DataSource {\n"
-				     "    CairoImageData CairoImageDataIndex get\n"
-				     "    /CairoImageDataIndex CairoImageDataIndex 1 add def\n"
-				     "    CairoImageDataIndex CairoImageData length 1 sub gt\n"
-				     "     { /CairoImageDataIndex 0 def } if\n"
-				     "  } /ASCII85Decode filter /DCTDecode filter def\n");
+				     "  /DataSource { cairo_data_source } /DCTDecode filter def\n");
     } else {
 	_cairo_output_stream_printf (surface->stream,
 				     "  /DataSource cairo_ascii85_file /DCTDecode filter def\n");
@@ -3046,43 +3023,38 @@ _cairo_ps_surface_emit_ccitt_image (cairo_ps_surface_t   *surface,
 
     if (surface->use_string_datasource) {
 	_cairo_output_stream_printf (surface->stream,
-				     "  /DataSource {\n"
-				     "    CairoImageData CairoImageDataIndex get\n"
-				     "    /CairoImageDataIndex CairoImageDataIndex 1 add def\n"
-				     "    CairoImageDataIndex CairoImageData length 1 sub gt\n"
-				     "     { /CairoImageDataIndex 0 def } if\n"
-				     "  } /ASCII85Decode filter");
+				     "  /DataSource { cairo_data_source }\n");
     } else {
 	_cairo_output_stream_printf (surface->stream,
-				     "  /DataSource cairo_ascii85_file");
+				     "  /DataSource cairo_ascii85_file\n");
     }
 
     _cairo_output_stream_printf (surface->stream,
-				 " << /Columns %d /Rows %d /K %d",
+				 "  << /Columns %d /Rows %d /K %d\n",
 				 ccitt_params.columns,
 				 ccitt_params.rows,
 				 ccitt_params.k);
 
     if (ccitt_params.end_of_line)
-	_cairo_output_stream_printf (surface->stream, " /EndOfLine true");
+	_cairo_output_stream_printf (surface->stream, "     /EndOfLine true\n");
 
     if (ccitt_params.encoded_byte_align)
-	_cairo_output_stream_printf (surface->stream, " /EncodedByteAlign true");
+	_cairo_output_stream_printf (surface->stream, "     /EncodedByteAlign true\n");
 
     if (!ccitt_params.end_of_block)
-	_cairo_output_stream_printf (surface->stream, " /EndOfBlock false");
+	_cairo_output_stream_printf (surface->stream, "     /EndOfBlock false\n");
 
     if (ccitt_params.black_is_1)
-	_cairo_output_stream_printf (surface->stream, " /BlackIs1 true");
+	_cairo_output_stream_printf (surface->stream, "     /BlackIs1 true\n");
 
     if (ccitt_params.damaged_rows_before_error > 0) {
 	_cairo_output_stream_printf (surface->stream,
-				     " /DamagedRowsBeforeError %d",
+				     "     /DamagedRowsBeforeError %d\n",
 				     ccitt_params.damaged_rows_before_error);
     }
 
     _cairo_output_stream_printf (surface->stream,
-				 " >> /CCITTFaxDecode filter\n");
+				 "  >> /CCITTFaxDecode filter\n");
 
     _cairo_output_stream_printf (surface->stream,
 				 "  /ImageMatrix [ %d 0 0 %d 0 %d ]\n"
commit d5cb45013bf10d97657cea105683bf5ccb21c2d7
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    pdf: fix mime-unique-id jpeg attached to recording test
    
    - Restructure the emit_surface code so that mime types are checked first.
    
    - Add a test parameter to emit_surface to test if the surface will be emitted
      as an image or recording instead checking the surface type as the attached
      mime may override this.
    
    - Mark surface as not clear when mime is attached to avoid optimizing away
      "clear" surfaces that have mime attached.
    
    - Include entire surface in analysis if mime attached (also fixes bug with
      calculating the extents CONTENT_COLOR surfaces)

diff --git a/src/cairo-analysis-surface.c b/src/cairo-analysis-surface.c
index a968f401..c07e068f 100644
--- a/src/cairo-analysis-surface.c
+++ b/src/cairo-analysis-surface.c
@@ -138,81 +138,6 @@ detach_proxy (cairo_surface_t *proxy)
 }
 
 static cairo_int_status_t
-_analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
-				    const cairo_pattern_t    *pattern,
-				    cairo_rectangle_int_t    *extents)
-{
-    const cairo_surface_pattern_t *surface_pattern;
-    cairo_analysis_surface_t *tmp;
-    cairo_surface_t *source, *proxy;
-    cairo_matrix_t p2d;
-    cairo_status_t status, analysis_status;
-    cairo_bool_t surface_is_unbounded;
-    cairo_bool_t unused;
-
-    assert (pattern->type == CAIRO_PATTERN_TYPE_SURFACE);
-    surface_pattern = (const cairo_surface_pattern_t *) pattern;
-    assert (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING);
-    source = surface_pattern->surface;
-
-    proxy = _cairo_surface_has_snapshot (source, &proxy_backend);
-    if (proxy != NULL) {
-	/* nothing untoward found so far */
-	return CAIRO_STATUS_SUCCESS;
-    }
-
-    tmp = (cairo_analysis_surface_t *)
-	_cairo_analysis_surface_create (surface->target);
-    if (unlikely (tmp->base.status))
-	return tmp->base.status;
-    proxy = attach_proxy (source, &tmp->base);
-
-    p2d = pattern->matrix;
-    status = cairo_matrix_invert (&p2d);
-    assert (status == CAIRO_STATUS_SUCCESS);
-    _cairo_analysis_surface_set_ctm (&tmp->base, &p2d);
-
-    source = _cairo_surface_get_source (source, NULL);
-    surface_is_unbounded = (pattern->extend == CAIRO_EXTEND_REPEAT
-				     || pattern->extend == CAIRO_EXTEND_REFLECT);
-    status = _cairo_recording_surface_replay_and_create_regions (source,
-								 &pattern->matrix,
-								 &tmp->base,
-								 surface_is_unbounded);
-    if (tmp->has_supported) {
-	surface->has_supported = TRUE;
-	unused = cairo_region_union (&surface->supported_region, &tmp->supported_region);
-    }
-
-    if (tmp->has_unsupported) {
-	surface->has_unsupported = TRUE;
-	unused = cairo_region_union (&surface->fallback_region, &tmp->fallback_region);
-    }
-
-    analysis_status = tmp->has_unsupported ? CAIRO_INT_STATUS_IMAGE_FALLBACK : CAIRO_INT_STATUS_SUCCESS;
-
-    if (pattern->extend != CAIRO_EXTEND_NONE) {
-	_cairo_unbounded_rectangle_init (extents);
-    } else if (source->content & CAIRO_CONTENT_ALPHA) {
-	status = cairo_matrix_invert (&tmp->ctm);
-	_cairo_matrix_transform_bounding_box_fixed (&tmp->ctm,
-						    &tmp->page_bbox, NULL);
-	_cairo_box_round_to_rectangle (&tmp->page_bbox, extents);
-    } else {
-	/* black background fills entire extents */
-	_cairo_surface_get_extents (source, extents);
-    }
-
-    detach_proxy (proxy);
-    cairo_surface_destroy (&tmp->base);
-
-    if (unlikely (status))
-	return status;
-
-    return analysis_status;
-}
-
-static cairo_int_status_t
 _add_operation (cairo_analysis_surface_t *surface,
 		cairo_rectangle_int_t    *rect,
 		cairo_int_status_t        backend_status)
@@ -329,6 +254,103 @@ _add_operation (cairo_analysis_surface_t *surface,
 	return status;
 }
 
+static cairo_int_status_t
+_analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
+				    const cairo_pattern_t    *pattern,
+				    cairo_rectangle_int_t    *extents)
+{
+    const cairo_surface_pattern_t *surface_pattern;
+    cairo_analysis_surface_t *tmp;
+    cairo_surface_t *source, *proxy;
+    cairo_matrix_t p2d;
+    cairo_int_status_t status, analysis_status;
+    cairo_bool_t surface_is_unbounded;
+    cairo_bool_t unused;
+
+    assert (pattern->type == CAIRO_PATTERN_TYPE_SURFACE);
+    surface_pattern = (const cairo_surface_pattern_t *) pattern;
+    assert (surface_pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING);
+    source = surface_pattern->surface;
+
+    proxy = _cairo_surface_has_snapshot (source, &proxy_backend);
+    if (proxy != NULL) {
+	/* nothing untoward found so far */
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    tmp = (cairo_analysis_surface_t *)
+	_cairo_analysis_surface_create (surface->target);
+    if (unlikely (tmp->base.status)) {
+	status =tmp->base.status;
+	goto cleanup1;
+    }
+    proxy = attach_proxy (source, &tmp->base);
+
+    p2d = pattern->matrix;
+    status = cairo_matrix_invert (&p2d);
+    assert (status == CAIRO_INT_STATUS_SUCCESS);
+    _cairo_analysis_surface_set_ctm (&tmp->base, &p2d);
+
+
+    source = _cairo_surface_get_source (source, NULL);
+    surface_is_unbounded = (pattern->extend == CAIRO_EXTEND_REPEAT
+				     || pattern->extend == CAIRO_EXTEND_REFLECT);
+    status = _cairo_recording_surface_replay_and_create_regions (source,
+								 &pattern->matrix,
+								 &tmp->base,
+								 surface_is_unbounded);
+    if (unlikely (status))
+	goto cleanup2;
+
+    /* black background or mime data fills entire extents */
+    if (!(source->content & CAIRO_CONTENT_ALPHA) || _cairo_surface_has_mime_image (source)) {
+	cairo_rectangle_int_t rect;
+
+	if (_cairo_surface_get_extents (source, &rect)) {
+	    cairo_box_t bbox;
+
+	    _cairo_box_from_rectangle (&bbox, &rect);
+	    _cairo_matrix_transform_bounding_box_fixed (&p2d, &bbox, NULL);
+	    _cairo_box_round_to_rectangle (&bbox, &rect);
+	    status = _add_operation (tmp, &rect, CAIRO_INT_STATUS_SUCCESS);
+	    if (status == CAIRO_INT_STATUS_IMAGE_FALLBACK)
+		status = CAIRO_INT_STATUS_SUCCESS;
+	    if (unlikely (status))
+		goto cleanup2;
+	}
+    }
+
+    if (tmp->has_supported) {
+	surface->has_supported = TRUE;
+	unused = cairo_region_union (&surface->supported_region, &tmp->supported_region);
+    }
+
+    if (tmp->has_unsupported) {
+	surface->has_unsupported = TRUE;
+	unused = cairo_region_union (&surface->fallback_region, &tmp->fallback_region);
+    }
+
+    analysis_status = tmp->has_unsupported ? CAIRO_INT_STATUS_IMAGE_FALLBACK : CAIRO_INT_STATUS_SUCCESS;
+    if (pattern->extend != CAIRO_EXTEND_NONE) {
+	_cairo_unbounded_rectangle_init (extents);
+    } else {
+	status = cairo_matrix_invert (&tmp->ctm);
+	_cairo_matrix_transform_bounding_box_fixed (&tmp->ctm,
+						    &tmp->page_bbox, NULL);
+	_cairo_box_round_to_rectangle (&tmp->page_bbox, extents);
+    }
+
+  cleanup2:
+    detach_proxy (proxy);
+  cleanup1:
+    cairo_surface_destroy (&tmp->base);
+
+    if (unlikely (status))
+	return status;
+
+    return analysis_status;
+}
+
 static cairo_status_t
 _cairo_analysis_surface_finish (void *abstract_surface)
 {
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index aaec5045..07f9c308 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -268,6 +268,12 @@ static cairo_int_status_t
 _cairo_pdf_surface_close_stream (cairo_pdf_surface_t	*surface);
 
 static cairo_int_status_t
+_cairo_pdf_surface_emit_surface (cairo_pdf_surface_t        *surface,
+				 cairo_pdf_source_surface_t *source,
+				 cairo_bool_t                test,
+				 cairo_bool_t               *is_image);
+
+static cairo_int_status_t
 _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface);
 
 static void
@@ -1646,6 +1652,11 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 	goto fail3;
     }
 
+    /* Test if surface will be emitted as image or recording */
+    status = _cairo_pdf_surface_emit_surface (surface, &src_surface, TRUE, &surface_entry->emit_image);
+    if (unlikely (status))
+	goto fail3;
+
     if (surface_entry->bounded) {
 	status = _cairo_array_append (&surface->page_surfaces, &src_surface);
 	if (unlikely (status))
@@ -2890,7 +2901,8 @@ _cairo_pdf_surface_lookup_jbig2_global (cairo_pdf_surface_t       *surface,
 static cairo_int_status_t
 _cairo_pdf_surface_emit_jbig2_image (cairo_pdf_surface_t              *surface,
 				     cairo_surface_t	              *source,
-				     cairo_pdf_source_surface_entry_t *surface_entry)
+				     cairo_pdf_source_surface_entry_t *surface_entry,
+				     cairo_bool_t                      test)
 {
     cairo_int_status_t status;
     const unsigned char *mime_data;
@@ -2913,6 +2925,10 @@ _cairo_pdf_surface_emit_jbig2_image (cairo_pdf_surface_t              *surface,
     if (status)
 	return status;
 
+    /* At this point we know emitting jbig2 will succeed. */
+    if (test)
+	return CAIRO_STATUS_SUCCESS;
+
     cairo_surface_get_mime_data (source, CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID,
 				 &global_id, &global_id_length);
     if (global_id && global_id_length > 0) {
@@ -2998,7 +3014,8 @@ _cairo_pdf_surface_emit_jbig2_image (cairo_pdf_surface_t              *surface,
 static cairo_int_status_t
 _cairo_pdf_surface_emit_jpx_image (cairo_pdf_surface_t              *surface,
 				   cairo_surface_t	            *source,
-				   cairo_pdf_source_surface_entry_t *surface_entry)
+				   cairo_pdf_source_surface_entry_t *surface_entry,
+				   cairo_bool_t                      test)
 {
     cairo_int_status_t status;
     const unsigned char *mime_data;
@@ -3029,6 +3046,10 @@ _cairo_pdf_surface_emit_jpx_image (cairo_pdf_surface_t              *surface,
     else
 	smask_buf[0] = 0;
 
+    /* At this point we know emitting jpx will succeed. */
+    if (test)
+	return CAIRO_STATUS_SUCCESS;
+
     if (surface_entry->stencil_mask) {
 	status = _cairo_pdf_surface_open_stream (surface,
 						 &surface_entry->surface_res,
@@ -3073,7 +3094,8 @@ _cairo_pdf_surface_emit_jpx_image (cairo_pdf_surface_t              *surface,
 static cairo_int_status_t
 _cairo_pdf_surface_emit_jpeg_image (cairo_pdf_surface_t              *surface,
 				    cairo_surface_t	             *source,
-				    cairo_pdf_source_surface_entry_t *surface_entry)
+				    cairo_pdf_source_surface_entry_t *surface_entry,
+				    cairo_bool_t                      test)
 {
     cairo_int_status_t status;
     const unsigned char *mime_data;
@@ -3113,6 +3135,10 @@ _cairo_pdf_surface_emit_jpeg_image (cairo_pdf_surface_t              *surface,
 	    return CAIRO_INT_STATUS_UNSUPPORTED;
     }
 
+    /* At this point we know emitting jpeg will succeed. */
+    if (test)
+	return CAIRO_STATUS_SUCCESS;
+
     if (surface_entry->smask_res.id)
 	snprintf(smask_buf, sizeof(smask_buf), "   /SMask %d 0 R\n", surface_entry->smask_res.id);
     else
@@ -3168,7 +3194,8 @@ _cairo_pdf_surface_emit_jpeg_image (cairo_pdf_surface_t              *surface,
 static cairo_int_status_t
 _cairo_pdf_surface_emit_ccitt_image (cairo_pdf_surface_t              *surface,
 				     cairo_surface_t	             *source,
-				     cairo_pdf_source_surface_entry_t *surface_entry)
+				     cairo_pdf_source_surface_entry_t *surface_entry,
+				     cairo_bool_t                      test)
 {
     cairo_status_t status;
     const unsigned char *ccitt_data;
@@ -3203,6 +3230,10 @@ _cairo_pdf_surface_emit_ccitt_image (cairo_pdf_surface_t              *surface,
 
     free (params);
 
+    /* At this point we know emitting jbig2 will succeed. */
+    if (test)
+	return CAIRO_STATUS_SUCCESS;
+
     p = buf;
     *p = 0;
     end = buf + sizeof(buf) - 1;
@@ -3273,54 +3304,6 @@ _cairo_pdf_surface_emit_ccitt_image (cairo_pdf_surface_t              *surface,
 }
 
 static cairo_int_status_t
-_cairo_pdf_surface_emit_image_surface (cairo_pdf_surface_t        *surface,
-				       cairo_pdf_source_surface_t *source)
-{
-    cairo_image_surface_t *image;
-    void *image_extra;
-    cairo_int_status_t status;
-
-    if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
-	status = _cairo_pdf_surface_emit_jbig2_image (surface, source->surface, source->hash_entry);
-	if (status != CAIRO_INT_STATUS_UNSUPPORTED)
-	    return status;
-
-	status = _cairo_pdf_surface_emit_jpx_image (surface, source->surface, source->hash_entry);
-	if (status != CAIRO_INT_STATUS_UNSUPPORTED)
-	    return status;
-
-	status = _cairo_pdf_surface_emit_jpeg_image (surface, source->surface, source->hash_entry);
-	if (status != CAIRO_INT_STATUS_UNSUPPORTED)
-	    return status;
-
-	status = _cairo_pdf_surface_emit_ccitt_image (surface, source->surface, source->hash_entry);
-	if (status != CAIRO_INT_STATUS_UNSUPPORTED)
-	    return status;
-    }
-
-    if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
-	status = _cairo_surface_acquire_source_image (source->surface, &image, &image_extra);
-    } else {
-	status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface, source->raster_pattern,
-								       &image, &image_extra);
-    }
-    if (unlikely (status))
-	return status;
-
-    status = _cairo_pdf_surface_emit_image (surface,
-					    image,
-					    source->hash_entry);
-
-    if (source->type == CAIRO_PATTERN_TYPE_SURFACE)
-	_cairo_surface_release_source_image (source->surface, image, image_extra);
-    else
-	_cairo_pdf_surface_release_source_image_from_pattern (surface, source->raster_pattern,
-							      image, image_extra);
-
-    return status;
-}
-
-static cairo_int_status_t
 _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
 					   cairo_pdf_source_surface_t *pdf_source)
 {
@@ -3441,15 +3424,107 @@ err:
     return status;
 }
 
+/**
+ * _cairo_pdf_surface_emit_surface:
+ * @surface: [in] the pdf surface
+ * @source: [in] #cairo_pdf_source_surface_t containing the surface to write
+ * @test: [in] if true, test what type of surface will be emitted.
+ * @is_image: [out] if @test is true, returns TRUE if the surface will be emitted
+ *  as an Image XObject.
+ *
+ * If @test is FALSE, emit @src_surface as an XObject.
+ * If @test is TRUE, don't emit anything. Set @is_image based on the output that would be emitted.
+ **/
 static cairo_int_status_t
 _cairo_pdf_surface_emit_surface (cairo_pdf_surface_t        *surface,
-				 cairo_pdf_source_surface_t *src_surface)
+				 cairo_pdf_source_surface_t *source,
+				 cairo_bool_t                test,
+				 cairo_bool_t               *is_image)
 {
-    if (src_surface->type == CAIRO_PATTERN_TYPE_SURFACE &&
-	src_surface->surface->type == CAIRO_SURFACE_TYPE_RECORDING)
-	return _cairo_pdf_surface_emit_recording_surface (surface, src_surface);
+    cairo_image_surface_t *image;
+    void *image_extra;
+    cairo_int_status_t status;
+
+    /* Try all the supported mime types and recording type, falling through
+     * each option if unsupported */
+    if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	status = _cairo_pdf_surface_emit_jbig2_image (surface,
+						      source->surface,
+						      source->hash_entry,
+						      test);
+	if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
+	    *is_image = TRUE;
+	    return status;
+	}
+
+	status = _cairo_pdf_surface_emit_jpx_image (surface,
+						    source->surface,
+						    source->hash_entry,
+						    test);
+	if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
+	    *is_image = TRUE;
+	    return status;
+	}
+
+	status = _cairo_pdf_surface_emit_jpeg_image (surface,
+						     source->surface,
+						     source->hash_entry,
+						     test);
+	if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
+	    *is_image = TRUE;
+	    return status;
+	}
+
+	status = _cairo_pdf_surface_emit_ccitt_image (surface,
+						      source->surface,
+						      source->hash_entry,
+						      test);
+	if (status != CAIRO_INT_STATUS_UNSUPPORTED) {
+	    *is_image = TRUE;
+	    return status;
+	}
+
+	if (source->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
+	    if (test) {
+		*is_image = FALSE;
+		return CAIRO_INT_STATUS_SUCCESS;
+	    } else {
+		return _cairo_pdf_surface_emit_recording_surface (surface, source);
+	    }
+	}
+    }
+
+    /* The only option left is to emit as an image surface */
+
+    if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	status = _cairo_surface_acquire_source_image (source->surface, &image, &image_extra);
+    } else {
+	status = _cairo_pdf_surface_acquire_source_image_from_pattern (surface,
+								       source->raster_pattern,
+								       &image,
+								       &image_extra);
+    }
+    if (unlikely (status))
+	return status;
+
+    if (test) {
+	*is_image = TRUE;
+    } else {
+	status = _cairo_pdf_surface_emit_image (surface,
+						image,
+						source->hash_entry);
+    }
+
+    if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+	_cairo_surface_release_source_image (source->surface, image, image_extra);
+    } else {
+	_cairo_pdf_surface_release_source_image_from_pattern (surface,
+							      source->raster_pattern,
+							      image,
+							      image_extra);
+    }
 
-    return _cairo_pdf_surface_emit_image_surface (surface, src_surface);
+    return status;
 }
 
 static cairo_int_status_t
@@ -3596,8 +3671,7 @@ _cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t	*surface,
 
     cairo_matrix_multiply (&pdf_p2d, &cairo_p2d, &mat);
     cairo_matrix_translate (&pdf_p2d, x_offset, y_offset);
-    if (((cairo_surface_pattern_t *)pattern)->surface->type != CAIRO_SURFACE_TYPE_RECORDING)
-    {
+    if (pdf_source->emit_image) {
 	cairo_matrix_translate (&pdf_p2d, 0.0, pdf_source->extents.height);
 	cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
     }
@@ -3625,17 +3699,17 @@ _cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t	*surface,
     if (unlikely (status))
 	return status;
 
-    if (((cairo_surface_pattern_t *) pattern)->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
+    if (pdf_source->emit_image) {
 	snprintf(draw_surface,
 		 sizeof (draw_surface),
-		 "/x%d Do",
+		 "q %d 0 0 %d 0 0 cm /x%d Do Q",
+		 pdf_source->extents.width,
+		 pdf_source->extents.height,
 		 pdf_source->surface_res.id);
     } else {
 	snprintf(draw_surface,
 		 sizeof (draw_surface),
-		 "q %d 0 0 %d 0 0 cm /x%d Do Q",
-		 pdf_source->extents.width,
-		 pdf_source->extents.height,
+		 "/x%d Do",
 		 pdf_source->surface_res.id);
     }
 
@@ -6729,6 +6803,7 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
     cairo_pdf_source_surface_t src_surface;
     unsigned int pattern_index, group_index, surface_index, doc_surface_index;
     cairo_int_status_t status;
+    cairo_bool_t is_image;
 
     /* Writing out PDF_MASK groups will cause additional smask groups
      * to be appended to surface->smask_groups. Additional patterns
@@ -6762,7 +6837,7 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
 
 	for (; surface_index < _cairo_array_num_elements (&surface->page_surfaces); surface_index++) {
 	    _cairo_array_copy_element (&surface->page_surfaces, surface_index, &src_surface);
-	    status = _cairo_pdf_surface_emit_surface (surface, &src_surface);
+	    status = _cairo_pdf_surface_emit_surface (surface, &src_surface, FALSE, &is_image);
 	    if (unlikely (status))
 		return status;
 	}
@@ -6770,7 +6845,7 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
 	if (finish) {
 	    for (; doc_surface_index < _cairo_array_num_elements (&surface->doc_surfaces); doc_surface_index++) {
 		_cairo_array_copy_element (&surface->doc_surfaces, doc_surface_index, &src_surface);
-		status = _cairo_pdf_surface_emit_surface (surface, &src_surface);
+		status = _cairo_pdf_surface_emit_surface (surface, &src_surface, FALSE, &is_image);
 		if (unlikely (status))
 		    return status;
 	    }
@@ -6915,7 +6990,7 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 
 static cairo_int_status_t
 _cairo_pdf_surface_analyze_surface_pattern_transparency (cairo_pdf_surface_t      *surface,
-							 cairo_surface_pattern_t *pattern)
+							 cairo_surface_pattern_t  *pattern)
 {
     cairo_image_surface_t  *image;
     void		   *image_extra;
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index 6e69faba..e04c478f 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -1224,6 +1224,44 @@ _cairo_mime_data_destroy (void *ptr)
     free (mime_data);
 }
 
+
+static const char *_cairo_surface_image_mime_types[] = {
+    CAIRO_MIME_TYPE_JPEG,
+    CAIRO_MIME_TYPE_PNG,
+    CAIRO_MIME_TYPE_JP2,
+    CAIRO_MIME_TYPE_JBIG2,
+    CAIRO_MIME_TYPE_CCITT_FAX,
+};
+
+cairo_bool_t
+_cairo_surface_has_mime_image (cairo_surface_t *surface)
+{
+    cairo_user_data_slot_t *slots;
+    int i, j, num_slots;
+
+    /* Prevent reads of the array during teardown */
+    if (! CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&surface->ref_count))
+	return FALSE;
+
+    /* The number of mime-types attached to a surface is usually small,
+     * typically zero. Therefore it is quicker to do a strcmp() against
+     * each key than it is to intern the string (i.e. compute a hash,
+     * search the hash table, and do a final strcmp).
+     */
+    num_slots = surface->mime_data.num_elements;
+    slots = _cairo_array_index (&surface->mime_data, 0);
+    for (i = 0; i < num_slots; i++) {
+	if (slots[i].key != NULL) {
+	    for (j = 0; j < ARRAY_LENGTH (_cairo_surface_image_mime_types); j++) {
+		if (strcmp ((char *) slots[i].key, _cairo_surface_image_mime_types[j]) == 0)
+		    return TRUE;
+	    }
+	}
+    }
+
+    return FALSE;
+}
+
 /**
  * CAIRO_MIME_TYPE_CCITT_FAX:
  *
@@ -1405,6 +1443,8 @@ cairo_surface_set_mime_data (cairo_surface_t		*surface,
 	return _cairo_surface_set_error (surface, status);
     }
 
+    surface->is_clear = FALSE;
+
     return CAIRO_STATUS_SUCCESS;
 }
 slim_hidden_def (cairo_surface_set_mime_data);
@@ -1479,6 +1519,8 @@ _cairo_surface_copy_mime_data (cairo_surface_t *dst,
 				    _cairo_mime_data_reference,
 				    NULL);
 
+    dst->is_clear = FALSE;
+
     return CAIRO_STATUS_SUCCESS;
 }
 
diff --git a/src/cairoint.h b/src/cairoint.h
index 6f8303c3..c44242a6 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1336,6 +1336,9 @@ _cairo_stroke_style_dash_approximate (const cairo_stroke_style_t *style,
 
 /* cairo-surface.c */
 
+cairo_private cairo_bool_t
+_cairo_surface_has_mime_image (cairo_surface_t *surface);
+
 cairo_private cairo_status_t
 _cairo_surface_copy_mime_data (cairo_surface_t *dst,
 			       cairo_surface_t *src);
diff --git a/test/mime-unique-id.c b/test/mime-unique-id.c
index 78957fc5..1d7babe0 100755
--- a/test/mime-unique-id.c
+++ b/test/mime-unique-id.c
@@ -72,7 +72,7 @@
  * file size due to changes to the PS/PDF surfaces while being small
  * enough to catch any attempt to embed the surface more than
  * once. The compressed size of each surface embedded in PDF is:
- * - image:    111,952
+ * - image:    108,774
  * - jpeg:      11,400
  * - recording: 17,518
  *
@@ -81,7 +81,7 @@
  */
 #define PS2_EXPECTED_SIZE 315362
 #define PS3_EXPECTED_SIZE 315362
-#define PDF_EXPECTED_SIZE 142968
+#define PDF_EXPECTED_SIZE 347182
 #define SIZE_TOLERANCE      5000
 
 static const char *png_filename = "romedalen.png";
commit bff47b43c4b0501c0255e9ba191904bea13ddf5c
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    pdf: fix mime-unique-id unbounded recording test
    
    PDF XObjects need to specify the bounding box. Emit unbounded surfaces
    when finishing as at this point the extents of all uses of the
    unbounded surface are known.

diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index 75d5259c..51be6825 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -80,8 +80,10 @@ typedef struct _cairo_pdf_source_surface_entry {
     cairo_pdf_resource_t surface_res;
     cairo_pdf_resource_t smask_res;
 
-    /* Extents of the source surface. If bounded is false,
-     * extents is the ink extents. */
+    /* True if surface will be emitted as an Image XObject. */
+    cairo_bool_t emit_image;
+
+    /* Extents of the source surface. */
     cairo_bool_t bounded;
     cairo_rectangle_int_t extents;
 
@@ -262,6 +264,7 @@ struct _cairo_pdf_surface {
     cairo_array_t alpha_linear_functions;
     cairo_array_t page_patterns;
     cairo_array_t page_surfaces;
+    cairo_array_t doc_surfaces;
     cairo_hash_table_t *all_surfaces;
     cairo_array_t smask_groups;
     cairo_array_t knockout_group;
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 4d32e777..aaec5045 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -280,6 +280,10 @@ static long
 _cairo_pdf_surface_write_xref (cairo_pdf_surface_t *surface);
 
 static cairo_int_status_t
+_cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface,
+						    cairo_bool_t         finish);
+
+static cairo_int_status_t
 _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface);
 
 static cairo_int_status_t
@@ -421,6 +425,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
 
     _cairo_array_init (&surface->page_patterns, sizeof (cairo_pdf_pattern_t));
     _cairo_array_init (&surface->page_surfaces, sizeof (cairo_pdf_source_surface_t));
+    _cairo_array_init (&surface->doc_surfaces, sizeof (cairo_pdf_source_surface_t));
     _cairo_array_init (&surface->jbig2_global, sizeof (cairo_pdf_jbig2_global_t));
     _cairo_array_init (&surface->page_heights, sizeof (double));
     surface->all_surfaces = _cairo_hash_table_create (_cairo_pdf_source_surface_equal);
@@ -1401,6 +1406,8 @@ _get_source_surface_extents (cairo_surface_t         *source,
 			     cairo_bool_t            *bounded,
 			     cairo_bool_t            *subsurface)
 {
+    cairo_int_status_t status;
+
     *bounded = TRUE;
     *subsurface = FALSE;
     if (source->type == CAIRO_SURFACE_TYPE_RECORDING) {
@@ -1415,16 +1422,24 @@ _get_source_surface_extents (cairo_surface_t         *source,
 	    *extents = sub->extents;
 	    *subsurface = TRUE;
 	} else {
+	    cairo_box_t box;
+
 	    *bounded = _cairo_surface_get_extents (source, extents);
+	    if (! *bounded) {
+		status = _cairo_recording_surface_get_ink_bbox ((cairo_recording_surface_t *)source,
+								&box, NULL);
+		if (unlikely (status)) {
+		    cairo_surface_destroy (free_me);
+		    return status;
+		}
+		_cairo_box_round_to_rectangle (&box, extents);
+	    }
 	}
 	cairo_surface_destroy (free_me);
-
-	return CAIRO_STATUS_SUCCESS;
+    } else {
+	*bounded =  _cairo_surface_get_extents (source, extents);
     }
 
-    if (! _cairo_surface_get_extents (source, extents))
-	return CAIRO_INT_STATUS_UNSUPPORTED;
-
     return CAIRO_STATUS_SUCCESS;
 }
 
@@ -1631,9 +1646,15 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t	         *surface,
 	goto fail3;
     }
 
-    status = _cairo_array_append (&surface->page_surfaces, &src_surface);
-    if (unlikely (status))
-	goto fail3;
+    if (surface_entry->bounded) {
+	status = _cairo_array_append (&surface->page_surfaces, &src_surface);
+	if (unlikely (status))
+	    goto fail3;
+    } else {
+	status = _cairo_array_append (&surface->doc_surfaces, &src_surface);
+	if (unlikely (status))
+	    goto fail3;
+    }
 
     status = _cairo_hash_table_insert (surface->all_surfaces,
 				       &surface_entry->base);
@@ -2179,6 +2200,11 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     cairo_pdf_jbig2_global_t *global;
     char *label;
 
+    _cairo_pdf_surface_clear (surface);
+
+    /* Emit unbounded surfaces */
+    _cairo_pdf_surface_write_patterns_and_smask_groups (surface, TRUE);
+
     status = surface->base.status;
     if (status == CAIRO_STATUS_SUCCESS)
 	status = _cairo_pdf_surface_emit_font_subsets (surface);
@@ -2243,7 +2269,6 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     if (status == CAIRO_STATUS_SUCCESS)
 	status = status2;
 
-    _cairo_pdf_surface_clear (surface);
     _cairo_pdf_group_resources_fini (&surface->resources);
 
     _cairo_array_fini (&surface->objects);
@@ -2252,6 +2277,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     _cairo_array_fini (&surface->alpha_linear_functions);
     _cairo_array_fini (&surface->page_patterns);
     _cairo_array_fini (&surface->page_surfaces);
+    _cairo_array_fini (&surface->doc_surfaces);
     _cairo_hash_table_foreach (surface->all_surfaces,
 			       _cairo_pdf_source_surface_entry_pluck,
 			       surface->all_surfaces);
@@ -2283,8 +2309,6 @@ _cairo_pdf_surface_finish (void *abstract_surface)
     }
     _cairo_array_fini (&surface->page_labels);
 
-    _cairo_array_truncate (&surface->page_surfaces, 0);
-
     _cairo_surface_clipper_reset (&surface->clipper);
 
     return _cairo_pdf_interchange_fini (surface);
@@ -4655,12 +4679,21 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t          *surface,
     pdf_p2d = surface->cairo_to_pdf;
     cairo_matrix_multiply (&pdf_p2d, &cairo_p2d, &pdf_p2d);
     cairo_matrix_translate (&pdf_p2d, x_offset, y_offset);
-    if (!(source->type == CAIRO_PATTERN_TYPE_SURFACE &&
-	  ((cairo_surface_pattern_t *)source)->surface->type == CAIRO_SURFACE_TYPE_RECORDING))
-    {
-	cairo_matrix_translate (&pdf_p2d, 0.0, pdf_source->extents.height);
+    if (pdf_source->emit_image) {
+	int width, height;
+
+	if (pdf_source->bounded) {
+	    width = pdf_source->extents.width;
+	    height = pdf_source->extents.height;
+	} else {
+	    /* We can't scale an image to an unbounded surface size so just set the size to 1 */
+	    width = 1;
+	    height = 1;
+	}
+
+	cairo_matrix_translate (&pdf_p2d, 0.0, height);
 	cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
-	cairo_matrix_scale (&pdf_p2d, pdf_source->extents.width, pdf_source->extents.height);
+	cairo_matrix_scale (&pdf_p2d, width, height);
     }
 
     status = _cairo_pdf_operators_flush (&surface->pdf_operators);
@@ -6688,12 +6721,13 @@ RESTORE_SIZE:
 }
 
 static cairo_int_status_t
-_cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface)
+_cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface,
+						    cairo_bool_t         finish)
 {
     cairo_pdf_pattern_t pattern;
     cairo_pdf_smask_group_t *group;
     cairo_pdf_source_surface_t src_surface;
-    unsigned int pattern_index, group_index, surface_index;
+    unsigned int pattern_index, group_index, surface_index, doc_surface_index;
     cairo_int_status_t status;
 
     /* Writing out PDF_MASK groups will cause additional smask groups
@@ -6706,9 +6740,11 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
     pattern_index = 0;
     group_index = 0;
     surface_index = 0;
+    doc_surface_index = 0;
     while ((pattern_index < _cairo_array_num_elements (&surface->page_patterns)) ||
 	   (group_index < _cairo_array_num_elements (&surface->smask_groups)) ||
-	   (surface_index < _cairo_array_num_elements (&surface->page_surfaces)))
+	   (surface_index < _cairo_array_num_elements (&surface->page_surfaces)) ||
+	   (finish && (doc_surface_index < _cairo_array_num_elements (&surface->doc_surfaces))))
     {
 	for (; group_index < _cairo_array_num_elements (&surface->smask_groups); group_index++) {
 	    _cairo_array_copy_element (&surface->smask_groups, group_index, &group);
@@ -6730,6 +6766,15 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
 	    if (unlikely (status))
 		return status;
 	}
+
+	if (finish) {
+	    for (; doc_surface_index < _cairo_array_num_elements (&surface->doc_surfaces); doc_surface_index++) {
+		_cairo_array_copy_element (&surface->doc_surfaces, doc_surface_index, &src_surface);
+		status = _cairo_pdf_surface_emit_surface (surface, &src_surface);
+		if (unlikely (status))
+		    return status;
+	    }
+	}
     }
 
     return CAIRO_STATUS_SUCCESS;
@@ -6861,7 +6906,7 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 				 ">>\n"
 				 "endobj\n");
 
-    status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface);
+    status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface, FALSE);
     if (unlikely (status))
 	return status;
 
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index 459b84c9..6e69faba 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -1025,8 +1025,6 @@ _cairo_surface_finish (cairo_surface_t *surface)
 {
     cairo_status_t status;
 
-    surface->finished = TRUE;
-
     /* call finish even if in error mode */
     if (surface->backend->finish) {
 	status = surface->backend->finish (surface);
@@ -1034,6 +1032,8 @@ _cairo_surface_finish (cairo_surface_t *surface)
 	    _cairo_surface_set_error (surface, status);
     }
 
+    surface->finished = TRUE;
+
     assert (surface->snapshot_of == NULL);
     assert (!_cairo_surface_has_snapshots (surface));
 }
commit cf9a07035292f3a1a3d057cfbb9dac1447a11235
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    pdf: fix mime-unique-id bounded recording test
    
    The embedded bounded recording surface was being clipped to the
    extents of its first use.

diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 058451b3..4d32e777 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -493,6 +493,9 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t	*output,
     surface->thumbnail_height = 0;
     surface->thumbnail_image = NULL;
 
+    if (getenv ("CAIRO_DEBUG_PDF") != NULL)
+	surface->compress_content = FALSE;
+
     surface->paginated_surface =  _cairo_paginated_surface_create (
 	                                  &surface->base,
 					  CAIRO_CONTENT_COLOR_ALPHA,
@@ -1398,8 +1401,6 @@ _get_source_surface_extents (cairo_surface_t         *source,
 			     cairo_bool_t            *bounded,
 			     cairo_bool_t            *subsurface)
 {
-    cairo_int_status_t status;
-
     *bounded = TRUE;
     *subsurface = FALSE;
     if (source->type == CAIRO_SURFACE_TYPE_RECORDING) {
@@ -1414,17 +1415,7 @@ _get_source_surface_extents (cairo_surface_t         *source,
 	    *extents = sub->extents;
 	    *subsurface = TRUE;
 	} else {
-	    cairo_box_t box;
-
-	    if (! _cairo_surface_get_extents (source, extents)) {
-		    status = _cairo_recording_surface_get_ink_bbox ((cairo_recording_surface_t *)source,
-								    &box, NULL);
-		    if (unlikely (status)) {
-			cairo_surface_destroy (free_me);
-			return status;
-		    }
-		    _cairo_box_round_to_rectangle (&box, extents);
-	    }
+	    *bounded = _cairo_surface_get_extents (source, extents);
 	}
 	cairo_surface_destroy (free_me);
 
@@ -1751,11 +1742,10 @@ _cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t	   *surface,
     return CAIRO_INT_STATUS_SUCCESS;
 }
 
-/* Get BBox in PDF coordinates from extents in cairo coordinates */
+/* Get BBox from extents */
 static void
-_get_bbox_from_extents (double                       surface_height,
-		       const cairo_rectangle_int_t *extents,
-		       cairo_box_double_t          *bbox)
+_get_bbox_from_extents (const cairo_rectangle_int_t  *extents,
+			cairo_box_double_t           *bbox)
 {
     bbox->p1.x = extents->x;
     bbox->p1.y = extents->y;
@@ -3310,7 +3300,6 @@ static cairo_int_status_t
 _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
 					   cairo_pdf_source_surface_t *pdf_source)
 {
-    double old_width, old_height;
     cairo_rectangle_int_t old_surface_extents;
     cairo_bool_t old_surface_bounded;
     cairo_paginated_mode_t old_paginated_mode;
@@ -3322,16 +3311,18 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
     cairo_surface_t *free_me = NULL;
     cairo_surface_t *source;
     const cairo_rectangle_int_t *extents;
-    int width;
-    int height;
     cairo_bool_t is_subsurface;
     cairo_bool_t transparency_group;
     cairo_recording_surface_t *recording;
 
     assert (pdf_source->type == CAIRO_PATTERN_TYPE_SURFACE);
-    extents = &pdf_source->hash_entry->required_extents;
-    width = pdf_source->hash_entry->extents.width;
-    height = pdf_source->hash_entry->extents.height;
+
+    if (pdf_source->hash_entry->bounded) {
+	extents = &pdf_source->hash_entry->extents;
+    } else {
+	extents = &pdf_source->hash_entry->required_extents;
+    }
+
     is_subsurface = FALSE;
     source = pdf_source->surface;
     if (_cairo_surface_is_snapshot (source))
@@ -3342,16 +3333,12 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
 
 	source = sub->target;
 	extents = &sub->extents;
-	width = extents->width;
-	height = extents->height;
 	is_subsurface = TRUE;
     }
 
     assert (source->type == CAIRO_SURFACE_TYPE_RECORDING);
     recording = (cairo_recording_surface_t *) source;
 
-    old_width = surface->width;
-    old_height = surface->height;
     old_in_xobject = surface->in_xobject;
     old_surface_extents = surface->surface_extents;
     old_surface_bounded = surface->surface_bounded;
@@ -3364,7 +3351,7 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
     _cairo_pdf_operators_reset (&surface->pdf_operators);
     surface->in_xobject = TRUE;
     surface->surface_extents = *extents;
-    surface->surface_bounded = FALSE;
+    surface->surface_bounded = TRUE;
 
     /* Patterns are emitted after fallback images. The paginated mode
      * needs to be set to _RENDER while the recording surface is replayed
@@ -3372,14 +3359,7 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
      */
     surface->paginated_mode = CAIRO_PAGINATED_MODE_RENDER;
     _cairo_pdf_group_resources_clear (&surface->resources);
-    if (is_subsurface) {
-	bbox.p1.x = extents->x;
-	bbox.p1.y = extents->y;
-	bbox.p2.x = extents->x + extents->width;
-	bbox.p2.y = extents->y + extents->height;
-    } else {
-	_get_bbox_from_extents (height, extents, &bbox);
-    }
+    _get_bbox_from_extents (extents, &bbox);
 
     /* We can optimize away the transparency group allowing the viewer
      * to replay the group in place when:
@@ -3392,8 +3372,11 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
 			   _cairo_recording_surface_has_only_bilevel_alpha (recording) &&
 			   _cairo_recording_surface_has_only_op_over (recording));
 
-    status = _cairo_pdf_surface_open_content_stream (surface, &bbox, &pdf_source->hash_entry->surface_res,
-						     TRUE, transparency_group);
+    status = _cairo_pdf_surface_open_content_stream (surface,
+						     &bbox,
+						     &pdf_source->hash_entry->surface_res,
+						     TRUE,
+						     transparency_group);
     if (unlikely (status))
 	goto err;
 
@@ -3403,10 +3386,12 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t        *surface,
 	    goto err;
 
 	_cairo_output_stream_printf (surface->output,
-				     "q /a%d gs 0 0 0 rg 0 0 %f %f re f Q\n",
+				     "q /a%d gs 0 0 0 rg %d %d %d %d re f Q\n",
 				     alpha,
-				     surface->width,
-				     surface->height);
+				     extents->x,
+				     extents->y,
+				     extents->width,
+				     extents->height);
     }
 
     status = _cairo_recording_surface_replay_region (source,
@@ -3593,7 +3578,7 @@ _cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t	*surface,
 	cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
     }
 
-    _get_bbox_from_extents (pattern_extents.height, &pattern_extents, &bbox);
+    _get_bbox_from_extents (&pattern_extents, &bbox);
     _cairo_pdf_surface_update_object (surface, pdf_pattern->pattern_res);
     status = _cairo_pdf_surface_open_stream (surface,
 				             &pdf_pattern->pattern_res,
@@ -4123,7 +4108,7 @@ cairo_pdf_surface_emit_transparency_group (cairo_pdf_surface_t  *surface,
 	 * coordinates. The color and alpha shading patterns painted
 	 * in the XObject below contain the cairo pattern to pdf page
 	 * matrix in the /Matrix entry of the pattern. */
-	_get_bbox_from_extents (pdf_pattern->height, &pdf_pattern->extents, &box);
+	_get_bbox_from_extents (&pdf_pattern->extents, &box);
 	x1 = box.p1.x;
 	y1 = box.p1.y;
 	x2 = box.p2.x;
@@ -6430,7 +6415,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t	*surface,
     cairo_box_double_t bbox;
 
     /* Create mask group */
-    _get_bbox_from_extents (group->height, &group->extents, &bbox);
+    _get_bbox_from_extents (&group->extents, &bbox);
     status = _cairo_pdf_surface_open_group (surface, &bbox, NULL);
     if (unlikely (status))
 	return status;
@@ -6640,7 +6625,7 @@ _cairo_pdf_surface_write_smask_group (cairo_pdf_surface_t     *surface,
 	goto RESTORE_SIZE;
     }
 
-    _get_bbox_from_extents (group->height, &group->extents, &bbox);
+    _get_bbox_from_extents (&group->extents, &bbox);
     status = _cairo_pdf_surface_open_group (surface, &bbox, &group->group_res);
     if (unlikely (status))
 	return status;
@@ -6771,7 +6756,7 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
 	extents.y = 0;
 	extents.width = ceil (surface->width);
 	extents.height = ceil (surface->height);
-	_get_bbox_from_extents (surface->height, &extents, &bbox);
+	_get_bbox_from_extents (&extents, &bbox);
 	status = _cairo_pdf_surface_open_knockout_group (surface, &bbox);
 	if (unlikely (status))
 	    return status;
commit 5e4707a10d476604614bddcc9ba8b06f6002e6d6
Author: Adrian Johnson <ajohnson at redneon.com>
Date:   Thu Nov 9 20:52:36 2017 +1030

    Add mime-unique-id test
    
    to check that PS/PDF embeds images with CAIRO_MIME_TYPE_UNIQUE_ID only
    once.

diff --git a/test/Makefile.sources b/test/Makefile.sources
index 1ae62464..a3134d51 100644
--- a/test/Makefile.sources
+++ b/test/Makefile.sources
@@ -440,7 +440,7 @@ xlib_surface_test_sources = \
 
 xlib_xrender_surface_test_sources = get-xrender-format.c
 
-multi_page_surface_test_sources = multi-page.c
+multi_page_surface_test_sources = multi-page.c mime-unique-id.c
 
 fallback_resolution_test_sources = fallback-resolution.c
 
diff --git a/test/mime-unique-id.c b/test/mime-unique-id.c
new file mode 100755
index 00000000..78957fc5
--- /dev/null
+++ b/test/mime-unique-id.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright © 2017 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>
+ */
+
+
+/* Check that source surfaces with same CAIRO_MIME_TYPE_UNIQUE_ID are
+ * embedded only once in PDF/PS.
+ *
+ * To exercise all the surface embedding code in PS/PDF, four types of
+ * source surfaces are painted on each page, each with its own UNIQUE_ID:
+ * - an image surface
+ * - a recording surface with a jpeg mime attached
+ * - a bounded recording surface
+ * - an unbounded recording surface.
+ *
+ * Four pages are generated. Each source is clipped starting with the
+ * smallest area on the first page increasing to the unclipped size on
+ * the last page. This is to ensure the output does not embed the
+ * source clipped to a smaller size than used on subsequent pages.
+ *
+ * The test verifies the use of UNIQUE_ID by comparing the file size
+ * with the expected size.
+ */
+
+#include "cairo-test.h"
+
+#include <math.h>
+#include <stdio.h>
+
+#include <cairo.h>
+
+#if CAIRO_HAS_PS_SURFACE
+#include <cairo-ps.h>
+#endif
+
+#if CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+
+#define NUM_PAGES 4
+
+#define WIDTH  275
+#define HEIGHT 275
+
+#define BASENAME "mime-unique-id"
+
+
+/* Expected file size to check that surfaces are embedded only once.
+ * SIZE_TOLERANCE should be large enough to allow some variation in
+ * file size due to changes to the PS/PDF surfaces while being small
+ * enough to catch any attempt to embed the surface more than
+ * once. The compressed size of each surface embedded in PDF is:
+ * - image:    111,952
+ * - jpeg:      11,400
+ * - recording: 17,518
+ *
+ * If the size check fails, manually check the output and if the
+ * surfaces are still embedded only once, update the expected sizes.
+ */
+#define PS2_EXPECTED_SIZE 315362
+#define PS3_EXPECTED_SIZE 315362
+#define PDF_EXPECTED_SIZE 142968
+#define SIZE_TOLERANCE      5000
+
+static const char *png_filename = "romedalen.png";
+static const char *jpeg_filename = "romedalen.jpg";
+
+static cairo_test_status_t
+create_image_surface (cairo_test_context_t *ctx, cairo_surface_t **surface)
+{
+    cairo_status_t status;
+    const char *unique_id = "image";
+
+    *surface = cairo_test_create_surface_from_png (ctx, png_filename);
+    status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+					  (unsigned char *)unique_id,
+					  strlen (unique_id),
+					  NULL, NULL);
+    if (status) {
+	cairo_surface_destroy (*surface);
+	return cairo_test_status_from_status (ctx, status);
+    }
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+create_recording_surface_with_mime_jpg (cairo_test_context_t *ctx, cairo_surface_t **surface)
+{
+    cairo_status_t status;
+    FILE *f;
+    unsigned char *data;
+    long len;
+    const char *unique_id = "jpeg";
+    cairo_rectangle_t extents = { 0, 0, 1, 1 };
+
+    *surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &extents);
+    f = fopen (jpeg_filename, "rb");
+    if (f == NULL) {
+	cairo_test_log (ctx, "Unable to open file %s\n", jpeg_filename);
+	return CAIRO_TEST_FAILURE;
+    }
+
+    fseek (f, 0, SEEK_END);
+    len = ftell(f);
+    fseek (f, 0, SEEK_SET);
+    data = malloc (len);
+    if (fread(data, len, 1, f) != 1) {
+	cairo_test_log (ctx, "Unable to read file %s\n", jpeg_filename);
+	return CAIRO_TEST_FAILURE;
+    }
+
+    fclose(f);
+    status = cairo_surface_set_mime_data (*surface,
+					  CAIRO_MIME_TYPE_JPEG,
+					  data, len,
+					  free, data);
+    if (status) {
+	cairo_surface_destroy (*surface);
+	return cairo_test_status_from_status (ctx, status);
+    }
+
+    status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+					  (unsigned char *)unique_id,
+					  strlen (unique_id),
+					  NULL, NULL);
+    if (status) {
+	cairo_surface_destroy (*surface);
+	return cairo_test_status_from_status (ctx, status);
+    }
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+static void
+draw_tile (cairo_t *cr)
+{
+    cairo_move_to (cr, 10 + 5, 10);
+    cairo_arc (cr, 10, 10, 5, 0, 2*M_PI);
+    cairo_close_path (cr);
+    cairo_set_source_rgb (cr, 1, 0, 0);
+    cairo_fill (cr);
+
+    cairo_move_to (cr, 30, 10-10*0.43);
+    cairo_line_to (cr, 25, 10+10*0.43);
+    cairo_line_to (cr, 35, 10+10*0.43);
+    cairo_close_path (cr);
+    cairo_set_source_rgb (cr, 0, 1, 0);
+    cairo_fill (cr);
+
+    cairo_rectangle (cr, 5, 25, 10, 10);
+    cairo_set_source_rgb (cr, 0, 0, 0);
+    cairo_fill (cr);
+
+    cairo_save (cr);
+    cairo_translate (cr, 30, 30);
+    cairo_rotate (cr, M_PI/4.0);
+    cairo_rectangle (cr, -5, -5, 10, 10);
+    cairo_set_source_rgb (cr, 1, 0, 1);
+    cairo_fill (cr);
+    cairo_restore (cr);
+}
+
+#define RECORDING_SIZE 800
+#define TILE_SIZE 40
+
+static cairo_test_status_t
+create_recording_surface (cairo_test_context_t *ctx, cairo_surface_t **surface, cairo_bool_t bounded)
+{
+    cairo_status_t status;
+    int x, y;
+    cairo_t *cr;
+    cairo_matrix_t ctm;
+    int start, size;
+    const char *bounded_id = "recording bounded";
+    const char *unbounded_id = "recording unbounded";
+    cairo_rectangle_t extents = { 0, 0, RECORDING_SIZE, RECORDING_SIZE };
+
+    if (bounded) {
+	*surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &extents);
+	start = 0;
+	size = RECORDING_SIZE;
+    } else {
+	*surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+	start = RECORDING_SIZE / 2;
+	size = RECORDING_SIZE * 2;
+    }
+
+    /* Draw each tile instead of creating a cairo pattern to make size
+     * of the emitted recording as large as possible.
+     */
+    cr = cairo_create (*surface);
+    cairo_set_source_rgb (cr, 1, 1, 0);
+    cairo_paint (cr);
+    cairo_get_matrix (cr, &ctm);
+    for (y = start; y < size; y += TILE_SIZE) {
+	for (x = start; x < size; x += TILE_SIZE) {
+	    draw_tile (cr);
+	    cairo_translate (cr, TILE_SIZE, 0);
+	}
+	cairo_matrix_translate (&ctm, 0, TILE_SIZE);
+	cairo_set_matrix (cr, &ctm);
+    }
+    cairo_destroy (cr);
+
+    status = cairo_surface_set_mime_data (*surface, CAIRO_MIME_TYPE_UNIQUE_ID,
+					  (unsigned char *)(bounded ? bounded_id : unbounded_id),
+					  strlen (bounded ? bounded_id : unbounded_id),
+					  NULL, NULL);
+    if (status) {
+	cairo_surface_destroy (*surface);
+	return cairo_test_status_from_status (ctx, status);
+    }
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+/* Draw @source scaled to fit @rect and clipped to a rectangle
+ * @clip_margin units smaller on each side.  @rect will be stroked
+ * with a solid line and the clip rect stroked with a dashed line.
+ */
+static void
+draw_surface (cairo_t *cr, cairo_surface_t *source, cairo_rectangle_int_t *rect, int clip_margin)
+{
+    cairo_surface_type_t type;
+    int width, height;
+    cairo_rectangle_t extents;
+    const double dashes[2] = { 2, 2 };
+
+    type = cairo_surface_get_type (source);
+    if (type == CAIRO_SURFACE_TYPE_IMAGE) {
+	width = cairo_image_surface_get_width (source);
+	height = cairo_image_surface_get_height (source);
+    } else {
+	if (cairo_recording_surface_get_extents (source, &extents)) {
+	    width = extents.width;
+	    height = extents.height;
+	} else {
+	    width = RECORDING_SIZE;
+	    height = RECORDING_SIZE;
+	}
+    }
+
+    cairo_save (cr);
+    cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
+    cairo_stroke (cr);
+    cairo_rectangle (cr,
+		     rect->x + clip_margin,
+		     rect->y + clip_margin,
+		     rect->width - clip_margin*2,
+		     rect->height - clip_margin*2);
+    cairo_set_dash (cr, dashes, 2, 0);
+    cairo_stroke_preserve (cr);
+    cairo_clip (cr);
+
+    cairo_translate (cr, rect->x, rect->y);
+    cairo_scale (cr, (double)rect->width/width, (double)rect->height/height);
+    cairo_set_source_surface (cr, source, 0, 0);
+    cairo_paint (cr);
+
+    cairo_restore (cr);
+}
+
+static cairo_test_status_t
+draw_pages (cairo_test_context_t *ctx, cairo_surface_t *surface)
+{
+    cairo_t *cr;
+    int i;
+    cairo_rectangle_int_t img_rect;
+    cairo_rectangle_int_t jpg_rect;
+    cairo_rectangle_int_t bounded_rect;
+    cairo_rectangle_int_t unbounded_rect;
+    int clip_margin;
+    cairo_surface_t *source;
+    cairo_test_status_t status;
+
+    cr = cairo_create (surface);
+
+    /* target area to fill with the image source */
+    img_rect.x = 25;
+    img_rect.y = 25;
+    img_rect.width = 100;
+    img_rect.height = 100;
+
+    /* target area to fill with the recording with jpeg mime source */
+    jpg_rect.x = 150;
+    jpg_rect.y = 25;
+    jpg_rect.width = 100;
+    jpg_rect.height = 100;
+
+    /* target area to fill with the bounded recording source */
+    bounded_rect.x = 25;
+    bounded_rect.y = 150;
+    bounded_rect.width = 100;
+    bounded_rect.height = 100;
+
+    /* target area to fill with the unbounded recording source */
+    unbounded_rect.x = 150;
+    unbounded_rect.y = 150;
+    unbounded_rect.width = 100;
+    unbounded_rect.height = 100;
+
+    /* Draw the image and recording surface on each page. The sources
+     * are clipped starting with a small clip area on the first page
+     * and increasing to the source size on last page to ensure the
+     * embedded source is not clipped to the area used on the first
+     * page.
+     *
+     * The sources are created each time they are used to ensure
+     * CAIRO_MIME_TYPE_UNIQUE_ID is tested.
+     */
+    for (i = 0; i < NUM_PAGES; i++) {
+	clip_margin = (NUM_PAGES - i - 1) * 5;
+
+	status = create_image_surface (ctx, &source);
+	if (status)
+	    return status;
+	draw_surface (cr, source, &img_rect, clip_margin);
+	cairo_surface_destroy (source);
+
+	status = create_recording_surface_with_mime_jpg (ctx, &source);
+	if (status)
+	    return status;
+	draw_surface (cr, source, &jpg_rect, clip_margin);
+	cairo_surface_destroy (source);
+
+	status = create_recording_surface (ctx, &source, TRUE);
+	if (status)
+	    return status;
+	draw_surface (cr, source, &bounded_rect, clip_margin);
+	cairo_surface_destroy (source);
+
+	status = create_recording_surface (ctx, &source, FALSE);
+	if (status)
+	    return status;
+	draw_surface (cr, source, &unbounded_rect, clip_margin);
+	cairo_surface_destroy (source);
+
+	cairo_show_page (cr);
+    }
+
+    cairo_destroy (cr);
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+check_file_size (cairo_test_context_t *ctx, const char *filename, long expected_size)
+{
+    FILE *f;
+    long size;
+
+    f = fopen (filename, "rb");
+    if (f == NULL) {
+	cairo_test_log (ctx, "Unable to open file %s\n", filename);
+	return CAIRO_TEST_FAILURE;
+    }
+
+    fseek (f, 0, SEEK_END);
+    size = ftell (f);
+    fclose(f);
+
+    if (labs(size - expected_size) > SIZE_TOLERANCE) {
+	cairo_test_log (ctx,
+			"mime-unique-id: File %s has size %ld. Expected size %ld +/- %ld."
+			" Check if surfaces are embedded once.\n",
+			filename, size, expected_size, (long)SIZE_TOLERANCE);
+	return CAIRO_TEST_FAILURE;
+    }
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+    cairo_surface_t *surface;
+    cairo_status_t status;
+    char *filename;
+    cairo_test_status_t result = CAIRO_TEST_UNTESTED;
+    cairo_test_status_t test_status;
+    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
+
+#if CAIRO_HAS_PS_SURFACE
+    if (cairo_test_is_target_enabled (ctx, "ps2"))
+    {
+	xasprintf (&filename, "%s/%s.ps2.out.ps", path, BASENAME);
+	surface = cairo_ps_surface_create (filename, WIDTH, HEIGHT);
+	status = cairo_surface_status (surface);
+	if (status) {
+	    cairo_test_log (ctx, "Failed to create ps surface for file %s: %s\n",
+			    filename, cairo_status_to_string (status));
+	    test_status = CAIRO_TEST_FAILURE;
+	    goto ps2_finish;
+	}
+
+	cairo_ps_surface_restrict_to_level (surface, CAIRO_PS_LEVEL_2);
+
+	test_status = draw_pages (ctx, surface);
+	cairo_surface_destroy (surface);
+
+	if (test_status == CAIRO_TEST_SUCCESS)
+	    test_status = check_file_size (ctx, filename, PS2_EXPECTED_SIZE);
+
+      ps2_finish:
+	cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
+			ctx->test->name,
+			"ps2",
+			test_status ? "FAIL" : "PASS");
+
+	if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
+	    result = test_status;
+
+	free (filename);
+    }
+
+    if (cairo_test_is_target_enabled (ctx, "ps3"))
+    {
+	xasprintf (&filename, "%s/%s.ps3.out.ps", path, BASENAME);
+	surface = cairo_ps_surface_create (filename, WIDTH, HEIGHT);
+	status = cairo_surface_status (surface);
+	if (status) {
+	    cairo_test_log (ctx, "Failed to create ps surface for file %s: %s\n",
+			    filename, cairo_status_to_string (status));
+	    test_status = CAIRO_TEST_FAILURE;
+	    goto ps3_finish;
+	}
+
+	test_status = draw_pages (ctx, surface);
+	cairo_surface_destroy (surface);
+
+	if (test_status == CAIRO_TEST_SUCCESS)
+	    test_status = check_file_size (ctx, filename, PS3_EXPECTED_SIZE);
+
+      ps3_finish:
+	cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
+			ctx->test->name,
+			"ps3",
+			test_status ? "FAIL" : "PASS");
+
+	if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
+	    result = test_status;
+
+	free (filename);
+    }
+#endif
+
+#if CAIRO_HAS_PDF_SURFACE
+    if (cairo_test_is_target_enabled (ctx, "pdf"))
+    {
+	xasprintf (&filename, "%s/%s.pdf.out.pdf", path, BASENAME);
+	surface = cairo_pdf_surface_create (filename, WIDTH, HEIGHT);
+	status = cairo_surface_status (surface);
+	if (status) {
+	    cairo_test_log (ctx, "Failed to create pdf surface for file %s: %s\n",
+			    filename, cairo_status_to_string (status));
+	    test_status = CAIRO_TEST_FAILURE;
+	    goto pdf_finish;
+	}
+
+	test_status = draw_pages (ctx, surface);
+	cairo_surface_destroy (surface);
+
+	if (test_status == CAIRO_TEST_SUCCESS)
+	    test_status = check_file_size (ctx, filename, PDF_EXPECTED_SIZE);
+
+
+      pdf_finish:
+	cairo_test_log (ctx, "TEST: %s TARGET: %s RESULT: %s\n",
+			ctx->test->name,
+			"pdf",
+			test_status ? "FAIL" : "PASS");
+
+	if (result == CAIRO_TEST_UNTESTED || test_status == CAIRO_TEST_FAILURE)
+	    result = test_status;
+
+	free (filename);
+    }
+#endif
+
+    return result;
+}
+
+CAIRO_TEST (mime_unique_id,
+	    "Check that paginated surfaces embed only one copy of surfaces with the same CAIRO_MIME_TYPE_UNIQUE_ID.",
+	    "paginated", /* keywords */
+	    "target=vector", /* requirements */
+	    0, 0,
+	    preamble, NULL)


More information about the cairo-commit mailing list