[cairo] PDF Transparent Gradients

Adrian Johnson ajohnson at redneon.com
Sat Mar 17 22:00:40 PDT 2007


Attached are two patches that implement transparent gradients for the
PDF backend.

The first patch is based on the patch by Miklós Erdélyi at
http://lists.freedesktop.org/archives/cairo/2006-August/007648.html

I have separated out the transparent gradient functions from Miklós'
patch, fixed various bugs and improved compatibility with different PDF
Viewers (Miklós patch only worked with acroread 7).

Other parts of Miklós patch have been rewritten to remove redundant
code. I have not yet added support for the repeat and reflect extended
modes. Selecting these modes will use the image fallbacks.

The second patch fixes some issues with using acroread.

I have tested these patches with:
  - Ghostscript 8.54
  - ESP Ghostscript 8.15.3
  - Evince (with Popper 0.5.4)
  - Xpdf 3.02
  - Adobe Reader 7 and 8
	
Some viewers do not support all the features that cairo supports.

ESP Ghostscript crashed when stroking a line with a gradient pattern.

Xpdf and Poppler do not appear to support text patterns or stroke patterns.

Poppler does not appear to support SMasks. Also, enabling padding with a
radial gradient did not pad out the entire rectangle I was filling.


-------------- next part --------------
>From 4760076394afea1d76f25dc4013b8d03036592e1 Mon Sep 17 00:00:00 2001
From: Adrian Johnson <ajohnson at redneon.com>
Date: Sun, 18 Mar 2007 11:29:14 +1030
Subject: [PATCH] PDF: Add support for transparent gradients
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is based on the gradient patch written by Miklós Erdélyi at
http://lists.freedesktop.org/archives/cairo/2006-August/007648.html
---
 src/cairo-pdf-surface.c |  836 +++++++++++++++++++++++++++++++++++++----------
 1 files changed, 665 insertions(+), 171 deletions(-)

diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 924b80c..4f05a7f 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -64,8 +64,6 @@
  *
  * - Surface patterns.
  *
- * - Alpha channels in gradients.
- *
  * - Should/does cairo support drawing into a scratch surface and then
  *   using that as a fill pattern?  For this backend, that would involve
  *   using a tiling pattern (4.6.2).  How do you create such a scratch
@@ -101,6 +99,18 @@ typedef struct _cairo_pdf_font {
     cairo_pdf_resource_t subset_resource;
 } cairo_pdf_font_t;
 
+typedef struct _cairo_pdf_rgb_linear_function {
+    cairo_pdf_resource_t resource;
+    double               color1[3];
+    double               color2[3];
+} cairo_pdf_rgb_linear_function_t;
+
+typedef struct _cairo_pdf_alpha_linear_function {
+    cairo_pdf_resource_t resource;
+    double               alpha1;
+    double               alpha2;
+} cairo_pdf_alpha_linear_function_t;
+
 typedef struct _cairo_pdf_surface {
     cairo_surface_t base;
 
@@ -117,6 +127,9 @@ typedef struct _cairo_pdf_surface {
     cairo_array_t xobjects;
     cairo_array_t streams;
     cairo_array_t alphas;
+    cairo_array_t smasks;
+    cairo_array_t rgb_linear_functions;
+    cairo_array_t alpha_linear_functions;
 
     cairo_scaled_font_subsets_t *font_subsets;
     cairo_array_t fonts;
@@ -133,6 +146,16 @@ typedef struct _cairo_pdf_surface {
         cairo_output_stream_t *old_output;
     } current_stream;
 
+    struct {
+        cairo_pattern_type_t type;
+        double red;
+        double green;
+        double blue;
+        int alpha;
+        cairo_pdf_resource_t smask;
+        cairo_pdf_resource_t pattern;
+    } emitted_pattern;
+
     cairo_bool_t has_clip;
 
     cairo_paginated_mode_t paginated_mode;
@@ -226,27 +249,31 @@ _cairo_pdf_surface_add_pattern (cairo_pd
     _cairo_array_append (&surface->patterns, &pattern);
 }
 
-static cairo_pdf_resource_t
+static void
+_cairo_pdf_surface_add_smask (cairo_pdf_surface_t  *surface,
+                              cairo_pdf_resource_t  smask)
+{
+    /* XXX: Should be checking the return value here. */
+    _cairo_array_append (&surface->smasks, &smask);
+}
+
+static int
 _cairo_pdf_surface_add_alpha (cairo_pdf_surface_t *surface, double alpha)
 {
-    cairo_pdf_resource_t resource;
     int num_alphas, i;
     double other;
 
     num_alphas = _cairo_array_num_elements (&surface->alphas);
     for (i = 0; i < num_alphas; i++) {
 	_cairo_array_copy_element (&surface->alphas, i, &other);
-	if (alpha == other) {
-	    resource.id  = i;
-	    return resource;
-	}
+	if (alpha == other)
+	    return i;
     }
 
     /* XXX: Should be checking the return value here. */
     _cairo_array_append (&surface->alphas, &alpha);
 
-    resource.id = _cairo_array_num_elements (&surface->alphas) - 1;
-    return resource;
+    return _cairo_array_num_elements (&surface->alphas) - 1;
 }
 
 static cairo_surface_t *
@@ -276,6 +303,14 @@ _cairo_pdf_surface_create_for_stream_int
     _cairo_array_init (&surface->xobjects, sizeof (cairo_pdf_resource_t));
     _cairo_array_init (&surface->streams, sizeof (cairo_pdf_resource_t));
     _cairo_array_init (&surface->alphas, sizeof (double));
+    _cairo_array_init (&surface->smasks, sizeof (cairo_pdf_resource_t));
+    _cairo_array_init (&surface->rgb_linear_functions, sizeof (cairo_pdf_rgb_linear_function_t));
+    _cairo_array_init (&surface->alpha_linear_functions, sizeof (cairo_pdf_alpha_linear_function_t));
+
+
+    /* Add alpha=1 as the first element as this is the most frequently
+     * referenced value. */
+    _cairo_pdf_surface_add_alpha (surface, 1);
 
     surface->font_subsets = _cairo_scaled_font_subsets_create (PDF_SURFACE_MAX_GLYPHS_PER_FONT,
                                                                PDF_SURFACE_MAX_GLYPHS_PER_FONT);
@@ -577,6 +612,9 @@ _cairo_pdf_surface_finish (void *abstrac
     _cairo_array_fini (&surface->xobjects);
     _cairo_array_fini (&surface->streams);
     _cairo_array_fini (&surface->alphas);
+    _cairo_array_fini (&surface->smasks);
+    _cairo_array_fini (&surface->rgb_linear_functions);
+    _cairo_array_fini (&surface->alpha_linear_functions);
 
     if (surface->font_subsets) {
 	_cairo_scaled_font_subsets_destroy (surface->font_subsets);
@@ -610,6 +648,36 @@ _cairo_pdf_surface_resume_content_stream
     _cairo_pdf_surface_add_stream (surface, stream);
 }
 
+static cairo_pdf_resource_t
+_cairo_pdf_surface_begin_group (cairo_pdf_surface_t *surface)
+{
+    cairo_pdf_resource_t group;
+
+    _cairo_pdf_surface_pause_content_stream (surface);
+    group = _cairo_pdf_surface_open_stream (surface,
+                                            TRUE,
+                                            "   /Type /XObject\r\n"
+                                            "   /Subtype /Form\r\n"
+                                            "   /BBox [ 0 0 %f %f ]\r\n"
+                                            "   /Group <<\r\n"
+                                            "      /Type /Group\r\n"
+                                            "      /S /Transparency\r\n"
+                                            "      /CS /DeviceRGB\r\n"
+                                            "   >>\r\n",
+                                            surface->width,
+                                            surface->height);
+
+    _cairo_array_append (&surface->xobjects, &group);
+    return group;
+}
+
+static void
+_cairo_pdf_surface_end_group (cairo_pdf_surface_t *surface)
+{
+    _cairo_pdf_surface_close_stream (surface);
+    _cairo_pdf_surface_resume_content_stream (surface);
+}
+
 static cairo_int_status_t
 _cairo_pdf_surface_start_page (void *abstract_surface)
 {
@@ -728,9 +796,9 @@ _cairo_pdf_surface_emit_smask (cairo_pdf
 /* Emit image data into the given surface, providing a resource that
  * can be used to reference the data in image_ret. */
 static cairo_status_t
-_cairo_pdf_surface_emit_image (cairo_pdf_surface_t		*surface,
-	    cairo_image_surface_t	*image,
-	    cairo_pdf_resource_t	*image_ret)
+_cairo_pdf_surface_emit_image (cairo_pdf_surface_t   *surface,
+                               cairo_image_surface_t *image,
+                               cairo_pdf_resource_t  *image_ret)
 {
     cairo_status_t status = CAIRO_STATUS_SUCCESS;
     char *rgb, *compressed;
@@ -842,32 +910,26 @@ _cairo_pdf_surface_emit_image (cairo_pdf
 
 static cairo_status_t
 _cairo_pdf_surface_emit_solid_pattern (cairo_pdf_surface_t *surface,
-		    cairo_solid_pattern_t *pattern)
+                                       cairo_solid_pattern_t *pattern)
 {
-    cairo_pdf_resource_t alpha;
+    int alpha;
 
     alpha = _cairo_pdf_surface_add_alpha (surface, pattern->color.alpha);
 
-    /* With some work, we could separate the stroking
-     * or non-stroking color here as actually needed. */
-    _cairo_output_stream_printf (surface->output,
-				 "%f %f %f RG "
-				 "%f %f %f rg "
-				 "/a%d gs\r\n",
-				 pattern->color.red,
-				 pattern->color.green,
-				 pattern->color.blue,
-				 pattern->color.red,
-				 pattern->color.green,
-				 pattern->color.blue,
-				 alpha.id);
+    surface->emitted_pattern.type = CAIRO_PATTERN_TYPE_SOLID;
+    surface->emitted_pattern.red = pattern->color.red;
+    surface->emitted_pattern.green = pattern->color.green;
+    surface->emitted_pattern.blue = pattern->color.blue;
+    surface->emitted_pattern.alpha = alpha;
+    surface->emitted_pattern.smask.id = 0;
+    surface->emitted_pattern.pattern.id = 0;
 
     return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_status_t
-_cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t	*surface,
-		      cairo_surface_pattern_t	*pattern)
+_cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t	 *surface,
+                                         cairo_surface_pattern_t *pattern)
 {
     cairo_pdf_resource_t stream;
     cairo_surface_t *pat_surface;
@@ -875,7 +937,7 @@ _cairo_pdf_surface_emit_surface_pattern
     cairo_image_surface_t *image;
     void *image_extra;
     cairo_status_t status = CAIRO_STATUS_SUCCESS;
-    cairo_pdf_resource_t alpha, image_resource = {0}; /* squelch bogus compiler warning */
+    cairo_pdf_resource_t image_resource = {0}; /* squelch bogus compiler warning */
     cairo_matrix_t cairo_p2d, pdf_p2d;
     cairo_extend_t extend = cairo_pattern_get_extend (&pattern->base);
     double xstep, ystep;
@@ -1012,14 +1074,10 @@ _cairo_pdf_surface_emit_surface_pattern
 
     _cairo_pdf_surface_add_pattern (surface, stream);
 
-    alpha = _cairo_pdf_surface_add_alpha (surface, 1.0);
-    /* With some work, we could separate the stroking
-     * or non-stroking pattern here as actually needed. */
-    _cairo_output_stream_printf (surface->output,
-				 "/Pattern CS /res%d SCN "
-				 "/Pattern cs /res%d scn "
-				 "/a%d gs\r\n",
-				 stream.id, stream.id, alpha.id);
+    surface->emitted_pattern.type = CAIRO_PATTERN_TYPE_SURFACE;
+    surface->emitted_pattern.alpha = _cairo_pdf_surface_add_alpha (surface, 1.0);
+    surface->emitted_pattern.smask.id = 0;
+    surface->emitted_pattern.pattern = stream;
 
  BAIL:
     _cairo_surface_release_source_image (pat_surface, image, image_extra);
@@ -1030,56 +1088,128 @@ _cairo_pdf_surface_emit_surface_pattern
 }
 
 typedef struct _cairo_pdf_color_stop {
-    double	  		offset;
-    cairo_pdf_resource_t	gradient;
-    unsigned char		color_char[4];
+    double offset;
+    double color[4];
+    cairo_pdf_resource_t resource;
 } cairo_pdf_color_stop_t;
 
 static cairo_pdf_resource_t
-_cairo_pdf_surface_emit_linear_colorgradient (cairo_pdf_surface_t		*surface,
-			   cairo_pdf_color_stop_t	*stop1,
-			   cairo_pdf_color_stop_t	*stop2)
+cairo_pdf_surface_emit_rgb_linear_function (cairo_pdf_surface_t    *surface,
+                                            cairo_pdf_color_stop_t *stop1,
+                                            cairo_pdf_color_stop_t *stop2)
 {
-    cairo_pdf_resource_t function = _cairo_pdf_surface_new_object (surface);
+    int num_elems, i;
+    cairo_pdf_rgb_linear_function_t elem;
+    cairo_pdf_resource_t function;
+
+    num_elems = _cairo_array_num_elements (&surface->rgb_linear_functions);
+    for (i = 0; i < num_elems; i++) {
+	_cairo_array_copy_element (&surface->rgb_linear_functions, i, &elem);
+        if (memcmp (&elem.color1[0], &stop1->color[0], sizeof (double)*3) != 0)
+            continue;
+        if (memcmp (&elem.color2[0], &stop2->color[0], sizeof (double)*3) != 0)
+            continue;
+
+        return elem.resource;
+    }
+
+    function = _cairo_pdf_surface_new_object (surface);
 
     _cairo_output_stream_printf (surface->output,
 				 "%d 0 obj\r\n"
-				 "<< /FunctionType 0\r\n"
+				 "<< /FunctionType 2\r\n"
 				 "   /Domain [ 0 1 ]\r\n"
-				 "   /Size [ 2 ]\r\n"
-				 "   /BitsPerSample 8\r\n"
-				 "   /Range [ 0 1 0 1 0 1 ]\r\n"
-				 "   /Length 6\r\n"
+				 "   /C0 [ %f %f %f ]\r\n"
+				 "   /C1 [ %f %f %f ]\r\n"
+				 "   /N 1\r\n"
 				 ">>\r\n"
-				 "stream\r\n",
-				 function.id);
+				 "endobj\r\n",
+				 function.id,
+                                 stop1->color[0],
+                                 stop1->color[1],
+                                 stop1->color[2],
+                                 stop2->color[0],
+                                 stop2->color[1],
+                                 stop2->color[2]);
+
+    elem.resource = function;
+    memcpy (&elem.color1[0], &stop1->color[0], sizeof (double)*3);
+    memcpy (&elem.color2[0], &stop2->color[0], sizeof (double)*3);
+
+    /* XXX: Should be checking the return value here. */
+    _cairo_array_append (&surface->rgb_linear_functions, &elem);
+
+    return function;
+}
+
+static cairo_pdf_resource_t
+cairo_pdf_surface_emit_alpha_linear_function (cairo_pdf_surface_t    *surface,
+                                              cairo_pdf_color_stop_t *stop1,
+                                              cairo_pdf_color_stop_t *stop2)
+{
+    int num_elems, i;
+    cairo_pdf_alpha_linear_function_t elem;
+    cairo_pdf_resource_t function;
+
+    num_elems = _cairo_array_num_elements (&surface->alpha_linear_functions);
+    for (i = 0; i < num_elems; i++) {
+	_cairo_array_copy_element (&surface->alpha_linear_functions, i, &elem);
+        if (elem.alpha1 != stop1->color[3])
+            continue;
+        if (elem.alpha2 != stop2->color[3])
+            continue;
+
+        return elem.resource;
+    }
+
+    function = _cairo_pdf_surface_new_object (surface);
 
-    _cairo_output_stream_write (surface->output, stop1->color_char, 3);
-    _cairo_output_stream_write (surface->output, stop2->color_char, 3);
     _cairo_output_stream_printf (surface->output,
-				 "\r\n"
-				 "endstream\r\n"
-				 "endobj\r\n");
+				 "%d 0 obj\r\n"
+				 "<< /FunctionType 2\r\n"
+				 "   /Domain [ 0 1 ]\r\n"
+				 "   /C0 [ %f ]\r\n"
+				 "   /C1 [ %f ]\r\n"
+				 "   /N 1\r\n"
+				 ">>\r\n"
+				 "endobj\r\n",
+				 function.id,
+                                 stop1->color[3],
+                                 stop2->color[3]);
+
+    elem.resource = function;
+    elem.alpha1 = stop1->color[3];
+    elem.alpha2 = stop2->color[3];
+
+    /* XXX: Should be checking the return value here. */
+    _cairo_array_append (&surface->alpha_linear_functions, &elem);
 
     return function;
 }
 
 static cairo_pdf_resource_t
-_cairo_pdf_surface_emit_stitched_colorgradient (cairo_pdf_surface_t   *surface,
-			    unsigned int 	   n_stops,
-			    cairo_pdf_color_stop_t stops[])
+_cairo_pdf_surface_emit_stitched_colorgradient (cairo_pdf_surface_t    *surface,
+                                                unsigned int 	        n_stops,
+                                                cairo_pdf_color_stop_t *stops,
+                                                cairo_bool_t	        is_alpha)
 {
     cairo_pdf_resource_t function;
     unsigned int i;
 
-    /* emit linear gradients between pairs of subsequent stops... */
+/* emit linear gradients between pairs of subsequent stops... */
     for (i = 0; i < n_stops-1; i++) {
-	stops[i].gradient = _cairo_pdf_surface_emit_linear_colorgradient (surface,
-						       &stops[i],
-						       &stops[i+1]);
+        if (is_alpha) {
+            stops[i].resource = cairo_pdf_surface_emit_alpha_linear_function (surface,
+                                                                              &stops[i],
+                                                                              &stops[i+1]);
+        } else {
+            stops[i].resource = cairo_pdf_surface_emit_rgb_linear_function (surface,
+                                                                            &stops[i],
+                                                                            &stops[i+1]);
+        }
     }
 
-    /* ... and stitch them together */
+/* ... and stitch them together */
     function = _cairo_pdf_surface_new_object (surface);
     _cairo_output_stream_printf (surface->output,
 				 "%d 0 obj\r\n"
@@ -1090,30 +1220,24 @@ _cairo_pdf_surface_emit_stitched_colorgr
     _cairo_output_stream_printf (surface->output,
 				 "   /Functions [ ");
     for (i = 0; i < n_stops-1; i++)
-    {
         _cairo_output_stream_printf (surface->output,
-				     "%d 0 R ", stops[i].gradient.id);
-    }
+                                     "%d 0 R ", stops[i].resource.id);
     _cairo_output_stream_printf (surface->output,
 		    		 "]\r\n");
 
     _cairo_output_stream_printf (surface->output,
 				 "   /Bounds [ ");
     for (i = 1; i < n_stops-1; i++)
-    {
         _cairo_output_stream_printf (surface->output,
 				     "%f ", stops[i].offset);
-    }
     _cairo_output_stream_printf (surface->output,
 		    		 "]\r\n");
 
     _cairo_output_stream_printf (surface->output,
 				 "   /Encode [ ");
     for (i = 1; i < n_stops; i++)
-    {
         _cairo_output_stream_printf (surface->output,
 				     "0 1 ");
-    }
     _cairo_output_stream_printf (surface->output,
 	    			 "]\r\n");
 
@@ -1126,29 +1250,36 @@ _cairo_pdf_surface_emit_stitched_colorgr
 
 #define COLOR_STOP_EPSILON 1e-6
 
-static cairo_pdf_resource_t
-_cairo_pdf_surface_emit_pattern_stops (cairo_pdf_surface_t *surface, cairo_gradient_pattern_t *pattern)
+static void
+_cairo_pdf_surface_emit_pattern_stops (cairo_pdf_surface_t      *surface,
+                                       cairo_gradient_pattern_t *pattern,
+                                       cairo_pdf_resource_t     *color_function,
+                                       cairo_pdf_resource_t     *alpha_function)
 {
-    cairo_pdf_resource_t    function;
     cairo_pdf_color_stop_t *allstops, *stops;
-    unsigned int i, n_stops;
+    unsigned int n_stops;
+    unsigned int i;
+    cairo_bool_t emit_alpha = FALSE;
 
-    function = _cairo_pdf_surface_new_object (surface);
+    color_function->id = 0;
+    alpha_function->id = 0;
 
     allstops = malloc ((pattern->n_stops + 2) * sizeof (cairo_pdf_color_stop_t));
     if (allstops == NULL) {
 	_cairo_error (CAIRO_STATUS_NO_MEMORY);
-	function.id = 0;
-	return function;
+	return;
     }
+
     stops = &allstops[1];
     n_stops = pattern->n_stops;
 
-    for (i = 0; i < pattern->n_stops; i++) {
-	stops[i].color_char[0] = pattern->stops[i].color.red   >> 8;
-	stops[i].color_char[1] = pattern->stops[i].color.green >> 8;
-	stops[i].color_char[2] = pattern->stops[i].color.blue  >> 8;
-	stops[i].color_char[3] = pattern->stops[i].color.alpha >> 8;
+    for (i = 0; i < n_stops; i++) {
+	stops[i].color[0] = pattern->stops[i].color.red / 65535.0;
+	stops[i].color[1] = pattern->stops[i].color.green / 65535.0;
+	stops[i].color[2] = pattern->stops[i].color.blue / 65535.0;
+	stops[i].color[3] = pattern->stops[i].color.alpha / 65535.0;
+        if (!CAIRO_ALPHA_IS_OPAQUE (stops[i].color[3]))
+            emit_alpha = TRUE;
 	stops[i].offset = _cairo_fixed_to_double (pattern->stops[i].x);
     }
 
@@ -1157,44 +1288,136 @@ _cairo_pdf_surface_emit_pattern_stops (c
     if (stops[0].offset > COLOR_STOP_EPSILON) {
 	    memcpy (allstops, stops, sizeof (cairo_pdf_color_stop_t));
 	    stops = allstops;
-	    stops[0].offset = 0.0;
 	    n_stops++;
     }
+    stops[0].offset = 0.0;
+
     if (stops[n_stops-1].offset < 1.0 - COLOR_STOP_EPSILON) {
 	    memcpy (&stops[n_stops],
 		    &stops[n_stops - 1],
 		    sizeof (cairo_pdf_color_stop_t));
-	    stops[n_stops].offset = 1.0;
 	    n_stops++;
     }
+    stops[n_stops-1].offset = 1.0;
 
     if (n_stops == 2) {
 	/* no need for stitched function */
-	function = _cairo_pdf_surface_emit_linear_colorgradient (surface, &stops[0], &stops[1]);
+        *color_function = cairo_pdf_surface_emit_rgb_linear_function (surface,
+                                                                      &stops[0],
+                                                                      &stops[1]);
+        if (emit_alpha) {
+            *alpha_function = cairo_pdf_surface_emit_alpha_linear_function (surface,
+                                                                            &stops[0],
+                                                                            &stops[1]);
+        }
     } else {
-	/* multiple stops: stitch. XXX possible optimization: regulary spaced
+        /* multiple stops: stitch. XXX possible optimization: regulary spaced
 	 * stops do not require stitching. XXX */
-	function = _cairo_pdf_surface_emit_stitched_colorgradient (surface,
-					       n_stops,
-					       stops);
+        *color_function = _cairo_pdf_surface_emit_stitched_colorgradient (surface,
+                                                                          n_stops,
+                                                                          stops,
+                                                                          FALSE);
+        if (emit_alpha) {
+            *alpha_function = _cairo_pdf_surface_emit_stitched_colorgradient (surface,
+                                                                              n_stops,
+                                                                              stops,
+                                                                              TRUE);
+        }
     }
-
     free (allstops);
+}
 
-    return function;
+static cairo_pdf_resource_t
+cairo_pdf_surface_emit_transparency_group (cairo_pdf_surface_t  *surface,
+                                           cairo_pdf_resource_t  gradient_mask)
+{
+    cairo_pdf_resource_t xobj_resource, smask_resource, gstate_resource;
+
+    xobj_resource = _cairo_pdf_surface_open_stream (surface,
+                                                    TRUE,
+                                                    "   /Type /XObject\r\n"
+                                                    "   /Subtype /Form\r\n"
+                                                    "   /FormType 1\r\n"
+                                                    "   /BBox [ 0 0 %f %f ]\r\n"
+                                                    "   /Resources\r\n"
+                                                    "      << /ExtGState\r\n"
+                                                    "            << /a0 << /ca 1 /CA 1 >>"
+                                                    "      >>\r\n"
+                                                    "         /Pattern\r\n"
+                                                    "            << /res%d %d 0 R >>\r\n"
+                                                    "      >>\r\n"
+                                                    "   /Group\r\n"
+                                                    "      << /Type /Group\r\n"
+                                                    "         /S /Transparency\r\n"
+                                                    "         /CS /DeviceGray\r\n"
+                                                    "      >>\r\n",
+                                                    surface->width,
+                                                    surface->height,
+                                                    gradient_mask.id,
+                                                    gradient_mask.id);
+
+    _cairo_output_stream_printf (surface->output,
+                                 "q\r\n"
+                                 "/a0 gs\r\n"
+                                 "/Pattern cs /res%d scn\r\n"
+                                 "0 0 %f %f re\r\n"
+                                 "f\r\n"
+                                 "Q\r\n",
+                                 gradient_mask.id,
+                                 surface->width,
+                                 surface->height);
+
+    _cairo_pdf_surface_close_stream (surface);
+
+    smask_resource = _cairo_pdf_surface_new_object (surface);
+    _cairo_output_stream_printf (surface->output,
+                                 "%d 0 obj\r\n"
+                                 "<< /Type /Mask\r\n"
+                                 "   /S /Luminosity\r\n"
+                                 "   /G %d 0 R\r\n"
+                                 "   /BC [ 0.0 ]\r\n"
+                                 ">>\r\n"
+                                 "endobj\r\n",
+                                 smask_resource.id,
+                                 xobj_resource.id);
+
+    /* Create GState which uses the transparency group as an SMask. */
+    gstate_resource = _cairo_pdf_surface_new_object (surface);
+    _cairo_output_stream_printf (surface->output,
+                                 "%d 0 obj\r\n"
+                                 "<< /Type /ExtGState\r\n"
+                                 "   /SMask %d 0 R\r\n"
+                                 "   /ca 1\r\n"
+                                 "   /CA 1\r\n"
+                                 "   /AIS false\r\n"
+                                 ">>\r\n"
+                                 "endobj\r\n",
+                                 gstate_resource.id,
+                                 smask_resource.id);
+
+    _cairo_pdf_surface_add_smask (surface, gstate_resource);
+
+    return gstate_resource;
 }
 
 static cairo_status_t
-_cairo_pdf_surface_emit_linear_pattern (cairo_pdf_surface_t *surface, cairo_linear_pattern_t *pattern)
+_cairo_pdf_surface_emit_linear_pattern (cairo_pdf_surface_t    *surface,
+                                        cairo_linear_pattern_t *pattern)
 {
-    cairo_pdf_resource_t function, pattern_resource, alpha;
+    cairo_pdf_resource_t pattern_resource, smask;
+    cairo_pdf_resource_t color_function, alpha_function;
     double x0, y0, x1, y1;
     cairo_matrix_t p2u;
+    cairo_extend_t extend;
 
+    extend = cairo_pattern_get_extend (&pattern->base.base);
     _cairo_pdf_surface_pause_content_stream (surface);
 
-    function = _cairo_pdf_surface_emit_pattern_stops (surface, &pattern->base);
-    if (function.id == 0)
+    _cairo_pdf_surface_emit_pattern_stops (surface,
+                                           &pattern->base,
+                                           &color_function,
+                                           &alpha_function);
+    if (color_function.id == 0)
 	return CAIRO_STATUS_NO_MEMORY;
 
     p2u = pattern->base.base.matrix;
@@ -1212,35 +1435,77 @@ _cairo_pdf_surface_emit_linear_pattern (
 				 "%d 0 obj\r\n"
 				 "<< /Type /Pattern\r\n"
 				 "   /PatternType 2\r\n"
-				 "   /Matrix [ 1 0 0 -1 0 %f ]\r\n"
+                                 "   /Matrix [ 1 0 0 -1 0 %f ]\r\n"
 				 "   /Shading\r\n"
 				 "      << /ShadingType 2\r\n"
 				 "         /ColorSpace /DeviceRGB\r\n"
 				 "         /Coords [ %f %f %f %f ]\r\n"
-				 "         /Function %d 0 R\r\n"
-				 "         /Extend [ true true ]\r\n"
-				 "      >>\r\n"
-				 ">>\r\n"
-				 "endobj\r\n",
+				 "         /Function %d 0 R\r\n",
 				 pattern_resource.id,
-				 surface->height,
+                                 surface->height,
 				 x0, y0, x1, y1,
-				 function.id);
+				 color_function.id);
 
-    _cairo_pdf_surface_add_pattern (surface, pattern_resource);
-
-    alpha = _cairo_pdf_surface_add_alpha (surface, 1.0);
+    if (extend == CAIRO_EXTEND_PAD) {
+        _cairo_output_stream_printf (surface->output,
+                                     "         /Extend [ true true ]\r\n");
+    } else {
+        _cairo_output_stream_printf (surface->output,
+                                     "         /Extend [ false false ]\r\n");
+    }
 
-    /* Use pattern */
-    /* With some work, we could separate the stroking
-     * or non-stroking pattern here as actually needed. */
     _cairo_output_stream_printf (surface->output,
-				 "/Pattern CS /res%d SCN "
-				 "/Pattern cs /res%d scn "
-				 "/a%d gs\r\n",
-				 pattern_resource.id,
-				 pattern_resource.id,
-				 alpha.id);
+				 "      >>\r\n"
+				 ">>\r\n"
+				 "endobj\r\n");
+
+    if (alpha_function.id == 0)
+    {
+        surface->emitted_pattern.smask.id = 0;
+    }
+    else
+    {
+	cairo_pdf_resource_t mask_resource;
+
+	/* Create pattern for SMask. */
+	mask_resource = _cairo_pdf_surface_new_object (surface);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\r\n"
+				     "<< /Type /Pattern\r\n"
+				     "   /PatternType 2\r\n"
+                                     "   /Matrix [ 1 0 0 -1 0 %f ]\r\n"
+				     "   /Shading\r\n"
+				     "      << /ShadingType 2\r\n"
+				     "         /ColorSpace /DeviceGray\r\n"
+				     "         /Coords [ %f %f %f %f ]\r\n"
+				     "         /Function %d 0 R\r\n",
+				     mask_resource.id,
+                                     surface->height,
+				     x0, y0, x1, y1,
+				     alpha_function.id);
+
+        if (extend == CAIRO_EXTEND_PAD) {
+            _cairo_output_stream_printf (surface->output,
+                                         "         /Extend [ true true ]\r\n");
+        } else {
+            _cairo_output_stream_printf (surface->output,
+                                         "         /Extend [ false false ]\r\n");
+        }
+
+        _cairo_output_stream_printf (surface->output,
+				     "      >>\r\n"
+				     ">>\r\n"
+				     "endobj\r\n");
+	_cairo_pdf_surface_add_pattern (surface, mask_resource);
+
+	smask = cairo_pdf_surface_emit_transparency_group (surface, mask_resource);
+        surface->emitted_pattern.smask = smask;
+    }
+
+    surface->emitted_pattern.type = CAIRO_PATTERN_TYPE_LINEAR;
+    surface->emitted_pattern.alpha = _cairo_pdf_surface_add_alpha (surface, 1);
+    surface->emitted_pattern.pattern = pattern_resource;
+    _cairo_pdf_surface_add_pattern (surface, pattern_resource);
 
     _cairo_pdf_surface_resume_content_stream (surface);
 
@@ -1248,16 +1513,23 @@ _cairo_pdf_surface_emit_linear_pattern (
 }
 
 static cairo_status_t
-_cairo_pdf_surface_emit_radial_pattern (cairo_pdf_surface_t *surface, cairo_radial_pattern_t *pattern)
+_cairo_pdf_surface_emit_radial_pattern (cairo_pdf_surface_t    *surface,
+                                        cairo_radial_pattern_t *pattern)
 {
-    cairo_pdf_resource_t function, pattern_resource, alpha;
+    cairo_pdf_resource_t pattern_resource, smask;
+    cairo_pdf_resource_t color_function, alpha_function;
     double x0, y0, x1, y1, r0, r1;
-    cairo_matrix_t p2u;
+    cairo_matrix_t p2u, pdf_p2d;
+    cairo_extend_t extend;
 
+    extend = cairo_pattern_get_extend (&pattern->base.base);
     _cairo_pdf_surface_pause_content_stream (surface);
 
-    function = _cairo_pdf_surface_emit_pattern_stops (surface, &pattern->base);
-    if (function.id == 0)
+    _cairo_pdf_surface_emit_pattern_stops (surface,
+                                           &pattern->base,
+                                           &color_function,
+                                           &alpha_function);
+    if (color_function.id == 0)
 	return CAIRO_STATUS_NO_MEMORY;
 
     p2u = pattern->base.base.matrix;
@@ -1266,57 +1538,95 @@ _cairo_pdf_surface_emit_radial_pattern (
     x0 = _cairo_fixed_to_double (pattern->gradient.c1.x);
     y0 = _cairo_fixed_to_double (pattern->gradient.c1.y);
     r0 = _cairo_fixed_to_double (pattern->gradient.c1.radius);
-    cairo_matrix_transform_point (&p2u, &x0, &y0);
     x1 = _cairo_fixed_to_double (pattern->gradient.c2.x);
     y1 = _cairo_fixed_to_double (pattern->gradient.c2.y);
     r1 = _cairo_fixed_to_double (pattern->gradient.c2.radius);
-    cairo_matrix_transform_point (&p2u, &x1, &y1);
 
-    /* FIXME: This is surely crack, but how should you scale a radius
-     * in a non-orthogonal coordinate system? */
-    cairo_matrix_transform_distance (&p2u, &r0, &r1);
-
-    /* FIXME: There is a difference between the cairo gradient extend
-     * semantics and PDF extend semantics. PDFs extend=false means
-     * that nothing is painted outside the gradient boundaries,
-     * whereas cairo takes this to mean that the end color is padded
-     * to infinity. Setting extend=true in PDF gives the cairo default
-     * behavoir, not yet sure how to implement the cairo mirror and
-     * repeat behaviour. */
+    cairo_matrix_init_identity (&pdf_p2d);
+    cairo_matrix_translate (&pdf_p2d, 0.0, surface->height);
+    cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
+    cairo_matrix_multiply (&pdf_p2d, &p2u, &pdf_p2d);
+
     pattern_resource = _cairo_pdf_surface_new_object (surface);
     _cairo_output_stream_printf (surface->output,
 				 "%d 0 obj\r\n"
 				 "<< /Type /Pattern\r\n"
 				 "   /PatternType 2\r\n"
-				 "   /Matrix [ 1 0 0 -1 0 %f ]\r\n"
-				 "   /Shading\r\n"
-				 "      << /ShadingType 3\r\n"
-				 "         /ColorSpace /DeviceRGB\r\n"
+                                 "   /Matrix [ %f %f %f %f %f %f ]\r\n"
+                                 "   /Shading\r\n"
+                                 "      << /ShadingType 3\r\n"
+                                 "         /ColorSpace /DeviceRGB\r\n"
 				 "         /Coords [ %f %f %f %f %f %f ]\r\n"
-				 "         /Function %d 0 R\r\n"
-				 "         /Extend [ true true ]\r\n"
-				 "      >>\r\n"
-				 ">>\r\n"
-				 "endobj\r\n",
+				 "         /Function %d 0 R\r\n",
 				 pattern_resource.id,
-				 surface->height,
+				 pdf_p2d.xx, pdf_p2d.yx,
+				 pdf_p2d.xy, pdf_p2d.yy,
+				 pdf_p2d.x0, pdf_p2d.y0,
 				 x0, y0, r0, x1, y1, r1,
-				 function.id);
-
-    _cairo_pdf_surface_add_pattern (surface, pattern_resource);
+				 color_function.id);
 
-    alpha = _cairo_pdf_surface_add_alpha (surface, 1.0);
+    if (extend == CAIRO_EXTEND_PAD) {
+        _cairo_output_stream_printf (surface->output,
+                                     "         /Extend [ true true ]\r\n");
+    } else {
+        _cairo_output_stream_printf (surface->output,
+                                     "         /Extend [ false false ]\r\n");
+    }
 
-    /* Use pattern */
-    /* With some work, we could separate the stroking
-     * or non-stroking pattern here as actually needed. */
     _cairo_output_stream_printf (surface->output,
-				 "/Pattern CS /res%d SCN "
-				 "/Pattern cs /res%d scn "
-				 "/a%d gs\r\n",
-				 pattern_resource.id,
-				 pattern_resource.id,
-				 alpha.id);
+				 "      >>\r\n"
+				 ">>\r\n"
+				 "endobj\r\n");
+
+    if (alpha_function.id == 0)
+    {
+        surface->emitted_pattern.smask.id = 0;
+    }
+    else
+    {
+	cairo_pdf_resource_t mask_resource;
+
+        /* Create pattern for SMask. */
+	mask_resource = _cairo_pdf_surface_new_object (surface);
+	_cairo_output_stream_printf (surface->output,
+				     "%d 0 obj\r\n"
+				     "<< /Type /Pattern\r\n"
+				     "   /PatternType 2\r\n"
+				     "   /Matrix [ %f %f %f %f %f %f ]\r\n"
+				     "   /Shading\r\n"
+				     "      << /ShadingType 3\r\n"
+				     "         /ColorSpace /DeviceGray\r\n"
+				     "         /Coords [ %f %f %f %f %f %f ]\r\n"
+				     "         /Function %d 0 R\r\n",
+				     mask_resource.id,
+                                     pdf_p2d.xx, pdf_p2d.yx,
+                                     pdf_p2d.xy, pdf_p2d.yy,
+                                     pdf_p2d.x0, pdf_p2d.y0,
+				     x0, y0, r0, x1, y1, r1,
+				     alpha_function.id);
+
+        if (extend == CAIRO_EXTEND_PAD) {
+            _cairo_output_stream_printf (surface->output,
+                                         "         /Extend [ true true ]\r\n");
+        } else {
+            _cairo_output_stream_printf (surface->output,
+                                         "         /Extend [ false false ]\r\n");
+        }
+
+        _cairo_output_stream_printf (surface->output,
+                                     "      >>\r\n"
+				     ">>\r\n"
+				     "endobj\r\n");
+
+	smask = cairo_pdf_surface_emit_transparency_group (surface, mask_resource);
+        surface->emitted_pattern.smask = smask;
+    }
+
+    surface->emitted_pattern.type = CAIRO_PATTERN_TYPE_RADIAL;
+    surface->emitted_pattern.alpha = _cairo_pdf_surface_add_alpha (surface, 1.0);
+    surface->emitted_pattern.pattern = pattern_resource;
+
+    _cairo_pdf_surface_add_pattern (surface, pattern_resource);
 
     _cairo_pdf_surface_resume_content_stream (surface);
 
@@ -1345,6 +1655,46 @@ _cairo_pdf_surface_emit_pattern (cairo_p
     return CAIRO_STATUS_PATTERN_TYPE_MISMATCH;
 }
 
+static cairo_status_t
+_cairo_pdf_surface_select_pattern (cairo_pdf_surface_t *surface,
+                                   cairo_bool_t         is_stroke)
+{
+    if (surface->emitted_pattern.type == CAIRO_PATTERN_TYPE_SOLID) {
+	_cairo_output_stream_printf (surface->output,
+                                     "%f %f %f ",
+                                     surface->emitted_pattern.red,
+                                     surface->emitted_pattern.green,
+                                     surface->emitted_pattern.blue);
+
+        if (is_stroke)
+            _cairo_output_stream_printf (surface->output, "RG ");
+        else
+            _cairo_output_stream_printf (surface->output, "rg ");
+
+        _cairo_output_stream_printf (surface->output,
+                                     "/a%d gs\r\n",
+                                     surface->emitted_pattern.alpha);
+    } else {
+        if (is_stroke) {
+            _cairo_output_stream_printf (surface->output,
+                                         "/Pattern CS /res%d SCN ",
+                                         surface->emitted_pattern.pattern);
+        } else {
+            _cairo_output_stream_printf (surface->output,
+                                         "/Pattern cs /res%d scn ",
+                                         surface->emitted_pattern.pattern);
+        }
+
+        _cairo_output_stream_printf (surface->output,
+                                     "/a%d gs ",
+                                     surface->emitted_pattern.alpha );
+
+        _cairo_output_stream_printf (surface->output, "\r\n");
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
 static cairo_int_status_t
 _cairo_pdf_surface_copy_page (void *abstract_surface)
 {
@@ -1546,10 +1896,10 @@ _cairo_pdf_surface_write_info (cairo_pdf
 static void
 _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
 {
-    cairo_pdf_resource_t page, *res;
+    cairo_pdf_resource_t page, *res, smask;
     cairo_pdf_font_t font;
     int num_pages, num_fonts, i;
-    int num_alphas, num_resources;
+    int num_alphas, num_smasks, num_resources;
     double alpha;
 
     _cairo_pdf_surface_update_object (surface, surface->pages_resource);
@@ -1571,19 +1921,25 @@ _cairo_pdf_surface_write_pages (cairo_pd
     _cairo_output_stream_printf (surface->output, "   /Resources <<\r\n");
 
     num_alphas =  _cairo_array_num_elements (&surface->alphas);
-    if (num_alphas > 0) {
+    num_smasks =  _cairo_array_num_elements (&surface->smasks);
+    if (num_alphas > 0 || num_smasks > 0) {
 	_cairo_output_stream_printf (surface->output,
 				     "      /ExtGState <<\r\n");
 
 	for (i = 0; i < num_alphas; i++) {
-	    /* With some work, we could separate the stroking
-	     * or non-stroking alpha here as actually needed. */
 	    _cairo_array_copy_element (&surface->alphas, i, &alpha);
 	    _cairo_output_stream_printf (surface->output,
 					 "         /a%d << /CA %f /ca %f >>\r\n",
 					 i, alpha, alpha);
 	}
 
+        for (i = 0; i < num_smasks; i++) {
+	    _cairo_array_copy_element (&surface->smasks, i, &smask);
+	    _cairo_output_stream_printf (surface->output,
+					 "         /sm%d %d 0 R\r\n",
+					 smask.id, smask.id);
+	}
+
 	_cairo_output_stream_printf (surface->output,
 				     "      >>\r\n");
     }
@@ -1619,14 +1975,20 @@ _cairo_pdf_surface_write_pages (cairo_pd
 				     " >>\r\n");
     }
 
-    _cairo_output_stream_printf (surface->output,"      /Font <<\r\n");
     num_fonts = _cairo_array_num_elements (&surface->fonts);
-    for (i = 0; i < num_fonts; i++) {
-	_cairo_array_copy_element (&surface->fonts, i, &font);
-	_cairo_output_stream_printf (surface->output, "         /CairoFont-%d-%d %d 0 R\r\n",
-				     font.font_id, font.subset_id, font.subset_resource.id);
+    if (num_fonts > 0) {
+        _cairo_output_stream_printf (surface->output,"      /Font <<\r\n");
+        num_fonts = _cairo_array_num_elements (&surface->fonts);
+        for (i = 0; i < num_fonts; i++) {
+            _cairo_array_copy_element (&surface->fonts, i, &font);
+            _cairo_output_stream_printf (surface->output,
+                                         "         /CairoFont-%d-%d %d 0 R\r\n",
+                                         font.font_id,
+                                         font.subset_id,
+                                         font.subset_resource.id);
+        }
+        _cairo_output_stream_printf (surface->output, "      >>\r\n");
     }
-    _cairo_output_stream_printf (surface->output, "      >>\r\n");
 
     _cairo_output_stream_printf (surface->output,
 				 "   >>\r\n");
@@ -2583,11 +2945,49 @@ _surface_pattern_supported (cairo_surfac
 }
 
 static cairo_bool_t
+_gradient_pattern_supported (cairo_pattern_t *pattern)
+{
+    cairo_extend_t extend;
+
+    extend = cairo_pattern_get_extend (pattern);
+
+    if (extend == CAIRO_EXTEND_REPEAT ||
+        extend == CAIRO_EXTEND_REFLECT) {
+        return FALSE;
+    }
+
+    /* Radial gradients are currently only supported when one circle
+     * is inside the other. */
+    if (pattern->type == CAIRO_PATTERN_TYPE_RADIAL) {
+        double x1, y1, x2, y2, r1, r2, d;
+        cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern;
+
+        x1 = _cairo_fixed_to_double (radial->gradient.c1.x);
+        y1 = _cairo_fixed_to_double (radial->gradient.c1.y);
+        r1 = _cairo_fixed_to_double (radial->gradient.c1.radius);
+        x2 = _cairo_fixed_to_double (radial->gradient.c2.x);
+        y2 = _cairo_fixed_to_double (radial->gradient.c2.y);
+        r2 = _cairo_fixed_to_double (radial->gradient.c2.radius);
+
+        d = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
+        if (d > fabs(r2 - r1)) {
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static cairo_bool_t
 _pattern_supported (cairo_pattern_t *pattern)
 {
     if (pattern->type == CAIRO_PATTERN_TYPE_SOLID)
 	return TRUE;
 
+    if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR ||
+	pattern->type == CAIRO_PATTERN_TYPE_RADIAL)
+	return _gradient_pattern_supported (pattern);
+
     if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE)
 	return _surface_pattern_supported ((cairo_surface_pattern_t *) pattern);
 
@@ -2650,6 +3050,7 @@ _cairo_pdf_surface_paint (void			*abstra
 			  cairo_pattern_t	*source)
 {
     cairo_pdf_surface_t *surface = abstract_surface;
+    cairo_pdf_resource_t group = {0}; /* squelch bogus compiler warning */
     cairo_status_t status;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
@@ -2669,10 +3070,33 @@ _cairo_pdf_surface_paint (void			*abstra
     if (status)
 	return status;
 
+    if (surface->emitted_pattern.smask.id != 0) {
+	group = _cairo_pdf_surface_begin_group (surface);
+        _cairo_output_stream_printf (surface->output,
+                                     "1 0 0 -1 0 %f cm\r\n",
+                                     surface->height);
+    } else {
+        _cairo_output_stream_printf (surface->output, "q ");
+    }
+
+    _cairo_pdf_surface_select_pattern (surface, FALSE);
+
     _cairo_output_stream_printf (surface->output,
 				 "0 0 %f %f re f\r\n",
 				 surface->width, surface->height);
 
+    if (surface->emitted_pattern.smask.id != 0) {
+        _cairo_pdf_surface_end_group (surface);
+
+        _cairo_output_stream_printf (surface->output,
+                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
+                                     surface->height,
+                                     surface->emitted_pattern.smask,
+                                     group.id);
+    } else {
+        _cairo_output_stream_printf (surface->output, "Q\r\n");
+    }
+
     return _cairo_output_stream_get_status (surface->output);
 }
 
@@ -2770,6 +3194,7 @@ _cairo_pdf_surface_stroke (void			*abstr
 			   cairo_antialias_t	 antialias)
 {
     cairo_pdf_surface_t *surface = abstract_surface;
+    cairo_pdf_resource_t group = {0}; /* squelch bogus compiler warning */
     pdf_path_info_t info;
     cairo_status_t status;
 
@@ -2782,6 +3207,17 @@ _cairo_pdf_surface_stroke (void			*abstr
     if (status)
 	return status;
 
+    if (surface->emitted_pattern.smask.id != 0) {
+	group = _cairo_pdf_surface_begin_group (surface);
+        _cairo_output_stream_printf (surface->output,
+                                     "1 0 0 -1 0 %f cm\r\n",
+                                     surface->height);
+    } else {
+        _cairo_output_stream_printf (surface->output, "q ");
+    }
+
+    _cairo_pdf_surface_select_pattern (surface, TRUE);
+
     status = _cairo_pdf_surface_emit_stroke_style (surface,
 						   style);
     if (status)
@@ -2805,6 +3241,18 @@ _cairo_pdf_surface_stroke (void			*abstr
 
     _cairo_output_stream_printf (surface->output, "S Q\r\n");
 
+    if (surface->emitted_pattern.smask.id != 0) {
+        _cairo_pdf_surface_end_group (surface);
+
+        _cairo_output_stream_printf (surface->output,
+                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
+                                     surface->height,
+                                     surface->emitted_pattern.smask,
+                                     group.id);
+    } else {
+        _cairo_output_stream_printf (surface->output, "Q\r\n");
+    }
+
     return status;
 }
 
@@ -2818,6 +3266,7 @@ _cairo_pdf_surface_fill (void			*abstrac
 			 cairo_antialias_t	 antialias)
 {
     cairo_pdf_surface_t *surface = abstract_surface;
+    cairo_pdf_resource_t group = {0}; /* squelch bogus compiler warning */
     const char *pdf_operator;
     cairo_status_t status;
     pdf_path_info_t info;
@@ -2831,9 +3280,18 @@ _cairo_pdf_surface_fill (void			*abstrac
     if (status)
 	return status;
 
+    if (surface->emitted_pattern.smask.id != 0) {
+	group = _cairo_pdf_surface_begin_group (surface);
+        _cairo_output_stream_printf (surface->output,
+                                     "1 0 0 -1 0 %f cm\r\n",
+                                     surface->height);
+    } else {
+        _cairo_output_stream_printf (surface->output, "q ");
+    }
+
+    _cairo_pdf_surface_select_pattern (surface, FALSE);
     info.output = surface->output;
     info.ctm_inverse = NULL;
-
     status = _cairo_path_fixed_interpret (path,
 					  CAIRO_DIRECTION_FORWARD,
 					  _cairo_pdf_path_move_to,
@@ -2857,6 +3315,18 @@ _cairo_pdf_surface_fill (void			*abstrac
 				 "%s\r\n",
 				 pdf_operator);
 
+    if (surface->emitted_pattern.smask.id != 0) {
+        _cairo_pdf_surface_end_group (surface);
+
+        _cairo_output_stream_printf (surface->output,
+                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
+                                     surface->height,
+                                     surface->emitted_pattern.smask,
+                                     group.id);
+    } else {
+        _cairo_output_stream_printf (surface->output, "Q\r\n");
+    }
+
     return status;
 }
 
@@ -2869,6 +3339,7 @@ _cairo_pdf_surface_show_glyphs (void			*
 				cairo_scaled_font_t	*scaled_font)
 {
     cairo_pdf_surface_t *surface = abstract_surface;
+    cairo_pdf_resource_t group = {0}; /* squelch bogus compiler warning */
     unsigned int current_subset_id = (unsigned int)-1;
     unsigned int font_id, subset_id, subset_glyph_index;
     cairo_bool_t diagonal;
@@ -2884,6 +3355,17 @@ _cairo_pdf_surface_show_glyphs (void			*
     if (status)
 	return status;
 
+    if (surface->emitted_pattern.smask.id != 0) {
+	group = _cairo_pdf_surface_begin_group (surface);
+        _cairo_output_stream_printf (surface->output,
+                                     "1 0 0 -1 0 %f cm\r\n",
+                                     surface->height);
+    } else {
+        _cairo_output_stream_printf (surface->output, "q ");
+    }
+
+    _cairo_pdf_surface_select_pattern (surface, FALSE);
+
     _cairo_output_stream_printf (surface->output,
 				 "BT\r\n");
 
@@ -2928,6 +3410,18 @@ _cairo_pdf_surface_show_glyphs (void			*
     _cairo_output_stream_printf (surface->output,
 				 "ET\r\n");
 
+    if (surface->emitted_pattern.smask.id != 0) {
+        _cairo_pdf_surface_end_group (surface);
+
+        _cairo_output_stream_printf (surface->output,
+                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
+                                     surface->height,
+                                     surface->emitted_pattern.smask,
+                                     group.id);
+    } else {
+        _cairo_output_stream_printf (surface->output, "Q\r\n");
+    }
+
     return _cairo_output_stream_get_status (surface->output);
 }
 
-- 
1.4.3.4

-------------- next part --------------
>From e2ffa70d39f70b5f92579a149b923e0e49e37c68 Mon Sep 17 00:00:00 2001
From: Adrian Johnson <ajohnson at redneon.com>
Date: Sun, 18 Mar 2007 11:31:17 +1030
Subject: [PATCH] [PATCH] PDF: Change CTM to identity

Some PDF viewers forget the CTM when drawing gradient patterns
with SMasks. This patch works around these bugs by using the default
identity matrix for the CTM. All paths are transformed from
cairo to pdf coordinates before writing to the pdf file.
---
 src/cairo-pdf-surface.c |  163 +++++++++++++++++++++++------------------------
 1 files changed, 79 insertions(+), 84 deletions(-)

diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 4f05a7f..3ee2b8e 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -120,6 +120,7 @@ typedef struct _cairo_pdf_surface {
 
     double width;
     double height;
+    cairo_matrix_t cairo_to_pdf;
 
     cairo_array_t objects;
     cairo_array_t pages;
@@ -296,6 +297,7 @@ _cairo_pdf_surface_create_for_stream_int
 
     surface->width = width;
     surface->height = height;
+    cairo_matrix_init (&surface->cairo_to_pdf, 1, 0, 0, -1, 0, height);
 
     _cairo_array_init (&surface->objects, sizeof (cairo_pdf_object_t));
     _cairo_array_init (&surface->pages, sizeof (cairo_pdf_resource_t));
@@ -694,10 +696,6 @@ _cairo_pdf_surface_start_page (void *abs
 
     _cairo_pdf_surface_add_stream (surface, stream);
 
-    _cairo_output_stream_printf (surface->output,
-				 "1 0 0 -1 0 %f cm\r\n",
-				 surface->height);
-
     return CAIRO_STATUS_SUCCESS;
 }
 
@@ -1406,8 +1404,8 @@ _cairo_pdf_surface_emit_linear_pattern (
 {
     cairo_pdf_resource_t pattern_resource, smask;
     cairo_pdf_resource_t color_function, alpha_function;
-    double x0, y0, x1, y1;
-    cairo_matrix_t p2u;
+    double x1, y1, x2, y2;
+    cairo_matrix_t pat_to_pdf;
     cairo_extend_t extend;
 
     extend = cairo_pattern_get_extend (&pattern->base.base);
@@ -1420,31 +1418,32 @@ _cairo_pdf_surface_emit_linear_pattern (
     if (color_function.id == 0)
 	return CAIRO_STATUS_NO_MEMORY;
 
-    p2u = pattern->base.base.matrix;
-    cairo_matrix_invert (&p2u);
+     pat_to_pdf = pattern->base.base.matrix;
+     cairo_matrix_invert (&pat_to_pdf);
+     cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &surface->cairo_to_pdf);
 
-    x0 = _cairo_fixed_to_double (pattern->gradient.p1.x);
-    y0 = _cairo_fixed_to_double (pattern->gradient.p1.y);
-    cairo_matrix_transform_point (&p2u, &x0, &y0);
-    x1 = _cairo_fixed_to_double (pattern->gradient.p2.x);
-    y1 = _cairo_fixed_to_double (pattern->gradient.p2.y);
-    cairo_matrix_transform_point (&p2u, &x1, &y1);
+     x1 = _cairo_fixed_to_double (pattern->gradient.p1.x);
+     y1 = _cairo_fixed_to_double (pattern->gradient.p1.y);
+     x2 = _cairo_fixed_to_double (pattern->gradient.p2.x);
+     y2 = _cairo_fixed_to_double (pattern->gradient.p2.y);
 
     pattern_resource = _cairo_pdf_surface_new_object (surface);
     _cairo_output_stream_printf (surface->output,
 				 "%d 0 obj\r\n"
 				 "<< /Type /Pattern\r\n"
 				 "   /PatternType 2\r\n"
-                                 "   /Matrix [ 1 0 0 -1 0 %f ]\r\n"
+                                 "   /Matrix [ %f %f %f %f %f %f ]\r\n"
 				 "   /Shading\r\n"
 				 "      << /ShadingType 2\r\n"
 				 "         /ColorSpace /DeviceRGB\r\n"
 				 "         /Coords [ %f %f %f %f ]\r\n"
 				 "         /Function %d 0 R\r\n",
 				 pattern_resource.id,
-                                 surface->height,
-				 x0, y0, x1, y1,
-				 color_function.id);
+                                 pat_to_pdf.xx, pat_to_pdf.yx,
+                                 pat_to_pdf.xy, pat_to_pdf.yy,
+                                 pat_to_pdf.x0, pat_to_pdf.y0,
+                                 x1, y1, x2, y2,
+                                 color_function.id);
 
     if (extend == CAIRO_EXTEND_PAD) {
         _cairo_output_stream_printf (surface->output,
@@ -1473,15 +1472,17 @@ _cairo_pdf_surface_emit_linear_pattern (
 				     "%d 0 obj\r\n"
 				     "<< /Type /Pattern\r\n"
 				     "   /PatternType 2\r\n"
-                                     "   /Matrix [ 1 0 0 -1 0 %f ]\r\n"
+                                     "   /Matrix [ %f %f %f %f %f %f ]\r\n"
 				     "   /Shading\r\n"
 				     "      << /ShadingType 2\r\n"
 				     "         /ColorSpace /DeviceGray\r\n"
 				     "         /Coords [ %f %f %f %f ]\r\n"
 				     "         /Function %d 0 R\r\n",
 				     mask_resource.id,
-                                     surface->height,
-				     x0, y0, x1, y1,
+                                     pat_to_pdf.xx, pat_to_pdf.yx,
+                                     pat_to_pdf.xy, pat_to_pdf.yy,
+                                     pat_to_pdf.x0, pat_to_pdf.y0,
+                                     x1, y1, x2, y2,
 				     alpha_function.id);
 
         if (extend == CAIRO_EXTEND_PAD) {
@@ -1518,8 +1519,8 @@ _cairo_pdf_surface_emit_radial_pattern (
 {
     cairo_pdf_resource_t pattern_resource, smask;
     cairo_pdf_resource_t color_function, alpha_function;
-    double x0, y0, x1, y1, r0, r1;
-    cairo_matrix_t p2u, pdf_p2d;
+    double x1, y1, x2, y2, r1, r2;
+    cairo_matrix_t pat_to_pdf;
     cairo_extend_t extend;
 
     extend = cairo_pattern_get_extend (&pattern->base.base);
@@ -1532,20 +1533,16 @@ _cairo_pdf_surface_emit_radial_pattern (
     if (color_function.id == 0)
 	return CAIRO_STATUS_NO_MEMORY;
 
-    p2u = pattern->base.base.matrix;
-    cairo_matrix_invert (&p2u);
+    pat_to_pdf = pattern->base.base.matrix;
+    cairo_matrix_invert (&pat_to_pdf);
+    cairo_matrix_multiply (&pat_to_pdf, &pat_to_pdf, &surface->cairo_to_pdf);
 
-    x0 = _cairo_fixed_to_double (pattern->gradient.c1.x);
-    y0 = _cairo_fixed_to_double (pattern->gradient.c1.y);
-    r0 = _cairo_fixed_to_double (pattern->gradient.c1.radius);
-    x1 = _cairo_fixed_to_double (pattern->gradient.c2.x);
-    y1 = _cairo_fixed_to_double (pattern->gradient.c2.y);
-    r1 = _cairo_fixed_to_double (pattern->gradient.c2.radius);
-
-    cairo_matrix_init_identity (&pdf_p2d);
-    cairo_matrix_translate (&pdf_p2d, 0.0, surface->height);
-    cairo_matrix_scale (&pdf_p2d, 1.0, -1.0);
-    cairo_matrix_multiply (&pdf_p2d, &p2u, &pdf_p2d);
+    x1 = _cairo_fixed_to_double (pattern->gradient.c1.x);
+    y1 = _cairo_fixed_to_double (pattern->gradient.c1.y);
+    r1 = _cairo_fixed_to_double (pattern->gradient.c1.radius);
+    x2 = _cairo_fixed_to_double (pattern->gradient.c2.x);
+    y2 = _cairo_fixed_to_double (pattern->gradient.c2.y);
+    r2 = _cairo_fixed_to_double (pattern->gradient.c2.radius);
 
     pattern_resource = _cairo_pdf_surface_new_object (surface);
     _cairo_output_stream_printf (surface->output,
@@ -1559,10 +1556,10 @@ _cairo_pdf_surface_emit_radial_pattern (
 				 "         /Coords [ %f %f %f %f %f %f ]\r\n"
 				 "         /Function %d 0 R\r\n",
 				 pattern_resource.id,
-				 pdf_p2d.xx, pdf_p2d.yx,
-				 pdf_p2d.xy, pdf_p2d.yy,
-				 pdf_p2d.x0, pdf_p2d.y0,
-				 x0, y0, r0, x1, y1, r1,
+                                 pat_to_pdf.xx, pat_to_pdf.yx,
+                                 pat_to_pdf.xy, pat_to_pdf.yy,
+                                 pat_to_pdf.x0, pat_to_pdf.y0,
+                                 x1, y1, r1, x2, y2, r2,
 				 color_function.id);
 
     if (extend == CAIRO_EXTEND_PAD) {
@@ -1599,10 +1596,10 @@ _cairo_pdf_surface_emit_radial_pattern (
 				     "         /Coords [ %f %f %f %f %f %f ]\r\n"
 				     "         /Function %d 0 R\r\n",
 				     mask_resource.id,
-                                     pdf_p2d.xx, pdf_p2d.yx,
-                                     pdf_p2d.xy, pdf_p2d.yy,
-                                     pdf_p2d.x0, pdf_p2d.y0,
-				     x0, y0, r0, x1, y1, r1,
+                                     pat_to_pdf.xx, pat_to_pdf.yx,
+                                     pat_to_pdf.xy, pat_to_pdf.yy,
+                                     pat_to_pdf.x0, pat_to_pdf.y0,
+                                     x1, y1, r1, x2, y2, r2,
 				     alpha_function.id);
 
         if (extend == CAIRO_EXTEND_PAD) {
@@ -1739,6 +1736,7 @@ _cairo_pdf_surface_get_extents (void
 
 typedef struct _pdf_path_info {
     cairo_output_stream_t   *output;
+    cairo_matrix_t	    *cairo_to_pdf;
     cairo_matrix_t	    *ctm_inverse;
 } pdf_path_info_t;
 
@@ -1749,6 +1747,8 @@ _cairo_pdf_path_move_to (void *closure,
     double x = _cairo_fixed_to_double (point->x);
     double y = _cairo_fixed_to_double (point->y);
 
+    if (info->cairo_to_pdf)
+        cairo_matrix_transform_point (info->cairo_to_pdf, &x, &y);
     if (info->ctm_inverse)
 	cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
 
@@ -1765,6 +1765,8 @@ _cairo_pdf_path_line_to (void *closure,
     double x = _cairo_fixed_to_double (point->x);
     double y = _cairo_fixed_to_double (point->y);
 
+    if (info->cairo_to_pdf)
+        cairo_matrix_transform_point (info->cairo_to_pdf, &x, &y);
     if (info->ctm_inverse)
 	cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
 
@@ -1787,6 +1789,11 @@ _cairo_pdf_path_curve_to (void
     double dx = _cairo_fixed_to_double (d->x);
     double dy = _cairo_fixed_to_double (d->y);
 
+    if (info->cairo_to_pdf) {
+        cairo_matrix_transform_point (info->cairo_to_pdf, &bx, &by);
+        cairo_matrix_transform_point (info->cairo_to_pdf, &cx, &cy);
+        cairo_matrix_transform_point (info->cairo_to_pdf, &dx, &dy);
+    }
     if (info->ctm_inverse) {
 	cairo_matrix_transform_point (info->ctm_inverse, &bx, &by);
 	cairo_matrix_transform_point (info->ctm_inverse, &cx, &cy);
@@ -1835,6 +1842,7 @@ _cairo_pdf_surface_intersect_clip_path (
     }
 
     info.output = surface->output;
+    info.cairo_to_pdf = &surface->cairo_to_pdf;
     info.ctm_inverse = NULL;
 
     status = _cairo_path_fixed_interpret (path,
@@ -2499,6 +2507,7 @@ _cairo_pdf_surface_emit_outline_glyph (c
 				 -_cairo_fixed_to_double (scaled_glyph->bbox.p1.y));
 
     info.output = surface->output;
+    info.cairo_to_pdf = &surface->cairo_to_pdf;
     info.ctm_inverse = NULL;
 
     status = _cairo_path_fixed_interpret (scaled_glyph->path,
@@ -3070,14 +3079,10 @@ _cairo_pdf_surface_paint (void			*abstra
     if (status)
 	return status;
 
-    if (surface->emitted_pattern.smask.id != 0) {
+    if (surface->emitted_pattern.smask.id != 0)
 	group = _cairo_pdf_surface_begin_group (surface);
-        _cairo_output_stream_printf (surface->output,
-                                     "1 0 0 -1 0 %f cm\r\n",
-                                     surface->height);
-    } else {
+    else
         _cairo_output_stream_printf (surface->output, "q ");
-    }
 
     _cairo_pdf_surface_select_pattern (surface, FALSE);
 
@@ -3089,8 +3094,7 @@ _cairo_pdf_surface_paint (void			*abstra
         _cairo_pdf_surface_end_group (surface);
 
         _cairo_output_stream_printf (surface->output,
-                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
-                                     surface->height,
+                                     "q /sm%d gs /res%d Do Q\r\n",
                                      surface->emitted_pattern.smask,
                                      group.id);
     } else {
@@ -3197,6 +3201,7 @@ _cairo_pdf_surface_stroke (void			*abstr
     cairo_pdf_resource_t group = {0}; /* squelch bogus compiler warning */
     pdf_path_info_t info;
     cairo_status_t status;
+    cairo_matrix_t m;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
 	return _cairo_pdf_surface_analyze_operation (surface, op, source);
@@ -3207,14 +3212,10 @@ _cairo_pdf_surface_stroke (void			*abstr
     if (status)
 	return status;
 
-    if (surface->emitted_pattern.smask.id != 0) {
-	group = _cairo_pdf_surface_begin_group (surface);
-        _cairo_output_stream_printf (surface->output,
-                                     "1 0 0 -1 0 %f cm\r\n",
-                                     surface->height);
-    } else {
+    if (surface->emitted_pattern.smask.id != 0)
+        group = _cairo_pdf_surface_begin_group (surface);
+    else
         _cairo_output_stream_printf (surface->output, "q ");
-    }
 
     _cairo_pdf_surface_select_pattern (surface, TRUE);
 
@@ -3224,12 +3225,14 @@ _cairo_pdf_surface_stroke (void			*abstr
 	return status;
 
     info.output = surface->output;
+    info.cairo_to_pdf = NULL;
     info.ctm_inverse = ctm_inverse;
 
+    cairo_matrix_multiply (&m, ctm, &surface->cairo_to_pdf);
     _cairo_output_stream_printf (surface->output,
 				 "q %f %f %f %f %f %f cm\r\n",
-				 ctm->xx, ctm->yx, ctm->xy, ctm->yy,
-				 ctm->x0, ctm->y0);
+				 m.xx, m.yx, m.xy, m.yy,
+				 m.x0, m.y0);
 
     status = _cairo_path_fixed_interpret (path,
 					  CAIRO_DIRECTION_FORWARD,
@@ -3245,8 +3248,7 @@ _cairo_pdf_surface_stroke (void			*abstr
         _cairo_pdf_surface_end_group (surface);
 
         _cairo_output_stream_printf (surface->output,
-                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
-                                     surface->height,
+                                     "q /sm%d gs /res%d Do Q\r\n",
                                      surface->emitted_pattern.smask,
                                      group.id);
     } else {
@@ -3280,18 +3282,17 @@ _cairo_pdf_surface_fill (void			*abstrac
     if (status)
 	return status;
 
-    if (surface->emitted_pattern.smask.id != 0) {
-	group = _cairo_pdf_surface_begin_group (surface);
-        _cairo_output_stream_printf (surface->output,
-                                     "1 0 0 -1 0 %f cm\r\n",
-                                     surface->height);
-    } else {
+    if (surface->emitted_pattern.smask.id != 0)
+        group = _cairo_pdf_surface_begin_group (surface);
+    else
         _cairo_output_stream_printf (surface->output, "q ");
-    }
 
     _cairo_pdf_surface_select_pattern (surface, FALSE);
+
     info.output = surface->output;
+    info.cairo_to_pdf = &surface->cairo_to_pdf;
     info.ctm_inverse = NULL;
+
     status = _cairo_path_fixed_interpret (path,
 					  CAIRO_DIRECTION_FORWARD,
 					  _cairo_pdf_path_move_to,
@@ -3319,8 +3320,7 @@ _cairo_pdf_surface_fill (void			*abstrac
         _cairo_pdf_surface_end_group (surface);
 
         _cairo_output_stream_printf (surface->output,
-                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
-                                     surface->height,
+                                     "q /sm%d gs /res%d Do Q\r\n",
                                      surface->emitted_pattern.smask,
                                      group.id);
     } else {
@@ -3355,14 +3355,10 @@ _cairo_pdf_surface_show_glyphs (void			*
     if (status)
 	return status;
 
-    if (surface->emitted_pattern.smask.id != 0) {
+    if (surface->emitted_pattern.smask.id != 0)
 	group = _cairo_pdf_surface_begin_group (surface);
-        _cairo_output_stream_printf (surface->output,
-                                     "1 0 0 -1 0 %f cm\r\n",
-                                     surface->height);
-    } else {
+    else
         _cairo_output_stream_printf (surface->output, "q ");
-    }
 
     _cairo_pdf_surface_select_pattern (surface, FALSE);
 
@@ -3391,18 +3387,18 @@ _cairo_pdf_surface_show_glyphs (void			*
             _cairo_output_stream_printf (surface->output,
                                          "%f %f %f %f %f %f Tm <%02x> Tj\r\n",
                                          scaled_font->scale.xx,
-                                         scaled_font->scale.yx,
+                                         -scaled_font->scale.yx,
                                          -scaled_font->scale.xy,
-                                         -scaled_font->scale.yy,
+                                         scaled_font->scale.yy,
                                          glyphs[i].x,
-                                         glyphs[i].y,
+                                         surface->height - glyphs[i].y,
                                          subset_glyph_index);
 	    current_subset_id = subset_id;
         } else {
             _cairo_output_stream_printf (surface->output,
                                          "%f %f Td <%02x> Tj\r\n",
                                          (glyphs[i].x - glyphs[i-1].x)/scaled_font->scale.xx,
-                                         (glyphs[i].y - glyphs[i-1].y)/-scaled_font->scale.yy,
+                                         -(glyphs[i].y - glyphs[i-1].y)/scaled_font->scale.yy,
                                          subset_glyph_index);
         }
     }
@@ -3414,8 +3410,7 @@ _cairo_pdf_surface_show_glyphs (void			*
         _cairo_pdf_surface_end_group (surface);
 
         _cairo_output_stream_printf (surface->output,
-                                     "q 1 0 0 -1 0 %f cm /sm%d gs /res%d Do Q\r\n",
-                                     surface->height,
+                                     "q /sm%d gs /res%d Do Q\r\n",
                                      surface->emitted_pattern.smask,
                                      group.id);
     } else {
-- 
1.4.3.4



More information about the cairo mailing list