diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index ff89db7..1e45c59 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -176,6 +176,8 @@ cairo_svg_version_to_string
CAIRO_MIME_TYPE_JP2
CAIRO_MIME_TYPE_JPEG
CAIRO_MIME_TYPE_PNG
+CAIRO_MIME_TYPE_XURI
+CAIRO_MIME_TYPE_XURI_ENCODED
cairo_surface_t
cairo_content_t
cairo_surface_create_similar
diff --git a/doc/public/tmpl/cairo-surface.sgml b/doc/public/tmpl/cairo-surface.sgml
index 4503e40..3d0596e 100644
--- a/doc/public/tmpl/cairo-surface.sgml
+++ b/doc/public/tmpl/cairo-surface.sgml
@@ -48,6 +48,20 @@ The Portable Network Graphics image file format (ISO/IEC 15948). Since 1.10
+
+
+URI for an image file (unofficial MIME type). Since 1.10
+
+
+
+
+
+
+Percent-encoded URI for an image file (unofficial MIME type). Since 1.10
+
+
+
+
diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index 9aa707f..7ce6235 100644
--- a/src/cairo-surface.c
+++ b/src/cairo-surface.c
@@ -770,7 +770,7 @@ _cairo_mime_data_destroy (void *ptr)
/**
* cairo_surface_set_mime_data:
* @surface: a #cairo_surface_t
- * @mime_type: the mime type of the image data
+ * @mime_type: the MIME type of the image data
* @data: the image data to attach to the surface
* @length: the length of the image data
* @destroy: a #cairo_destroy_func_t which will be called when the
@@ -782,6 +782,21 @@ _cairo_mime_data_destroy (void *ptr)
* the data from a surface, call this function with same mime type
* and %NULL for @data.
*
+ * The attached image data can be later used by backends which support
+ * it (currently: PDF, PS, SVG and Win32 Printing surfaces) to emit
+ * this data instead of making a snapshot of the @surface. This
+ * approach tends to be faster and requires less memory and disk
+ * space.
+ *
+ * The recognized MIME types are the following: #CAIRO_MIME_TYPE_JPEG,
+ * #CAIRO_MIME_TYPE_PNG, #CAIRO_MIME_TYPE_JP2, #CAIRO_MIME_TYPE_XURI,
+ * #CAIRO_MIME_TYPE_XURI_ENCODED.
+ *
+ * See corresponding backend surface docs for details about which MIME
+ * types it can handle. Caution: the associated MIME data might be
+ * totally unrelated to the actual content of the @surface, especially
+ * if you draw on the @surface afterwards. Use this function with care.
+ *
* Since: 1.10
*
* Return value: %CAIRO_STATUS_SUCCESS or %CAIRO_STATUS_NO_MEMORY if a
diff --git a/src/cairo-svg-surface.c b/src/cairo-svg-surface.c
index fa25b82..7964aa3 100644
--- a/src/cairo-svg-surface.c
+++ b/src/cairo-svg-surface.c
@@ -202,6 +202,33 @@ cairo_svg_surface_create_for_stream (cairo_write_func_t write_func,
* Creates a SVG surface of the specified size in points to be written
* to @filename.
*
+ * The SVG surface backend recognizes the following MIME types for the
+ * data attached to a surface (see cairo_surface_set_mime_data()) when
+ * it is used as a source pattern for drawing on this surface:
+ * #CAIRO_MIME_TYPE_JPEG, #CAIRO_MIME_TYPE_PNG, #CAIRO_MIME_TYPE_XURI,
+ * #CAIRO_MIME_TYPE_XURI_ENCODED. If any of them is specified, the SVG
+ * backend emits a href with the content of MIME data instead of a
+ * surface snapshot (PNG, Base64-encoded) in the corresponding image
+ * tag.
+ *
+ * The unofficial #CAIRO_MIME_TYPE_XURI_ENCODED MIME type takes
+ * precedence over any other MIME type associated with a surface. This
+ * MIME type is most suitable for complex URIs which may occur in a
+ * server application. The URI is emitted as is: assuring the
+ * correctness of URI is left to the client code.
+ *
+ * The unofficial #CAIRO_MIME_TYPE_XURI MIME type is examined if
+ * #CAIRO_MIME_TYPE_XURI_ENCODED is not specified. This data is used
+ * the same way, however any non-printable characters are
+ * percent-encoded according to RFC 3986 before emitting. This MIME
+ * type is most suitable for simple URIs like files on disk: the
+ * percent-encoding takes care of filenames which do not form valid
+ * URIs as is.
+ *
+ * If none of the above is specified, but #CAIRO_MIME_TYPE_JPEG or
+ * #CAIRO_MIME_TYPE_PNG is specified, the corresponding data is
+ * Base64-encoded and emitted.
+ *
* Return value: a pointer to the newly created surface. The caller
* owns the surface and should call cairo_surface_destroy() when done
* with it.
@@ -1168,6 +1195,81 @@ _cairo_svg_surface_emit_operator_for_style (cairo_output_stream_t *output,
}
}
+/**
+ * _cairo_svg_surface_emit_attr_value:
+ *
+ * Write the value to output the stream as a sequence of characters,
+ * while escaping those which have special meaning in the XML
+ * attribute's value context: & and ".
+ **/
+static void
+_cairo_svg_surface_emit_attr_value (cairo_output_stream_t *stream,
+ const unsigned char *value,
+ unsigned int length)
+{
+ const unsigned char *p;
+ const unsigned char *q;
+ unsigned int i;
+
+ /* we'll accumulate non-special chars in [q, p) range */
+ p = value;
+ q = p;
+ for (i = 0; i < length; i++, p++) {
+ if (*p == '&' || *p == '"') {
+ /* flush what's left before special char */
+ if (p != q) {
+ _cairo_output_stream_write (stream, q, p - q);
+ q = p + 1;
+ }
+
+ if (*p == '&')
+ _cairo_output_stream_printf (stream, "&");
+ else // p == '"'
+ _cairo_output_stream_printf (stream, """);
+ }
+ }
+
+ /* flush the trailing chars if any */
+ if (p != q)
+ _cairo_output_stream_write (stream, q, p - q);
+}
+
+/* Generated file. See generate-uri-allowed-set-lookup-table.c */
+#include "uri-allowed-set-lookup-table.inc"
+
+/**
+ * _cairo_svg_surface_uri_encode:
+ *
+ * Percent-encode the URI string. Percent sign itself is not in
+ * allowed set so it gets encoded.
+ *
+ * RFC: http://tools.ietf.org/html/rfc3986
+ **/
+static cairo_status_t
+_cairo_svg_surface_uri_encode (const unsigned char *uri, unsigned int length,
+ unsigned char **enc, unsigned int *enc_len)
+{
+ unsigned char *p;
+ int i;
+
+ *enc = malloc (length*3 + 1 /* to fit nul-byte from last snprintf */);
+ if (!*enc)
+ return CAIRO_STATUS_NO_MEMORY;
+
+ p = *enc;
+ for (i = 0; i < length; i++)
+ if (!uri_allowed_set[uri[i]]) {
+ snprintf (p, 4 /* "%XX\0" */, "%%%2X", uri[i]);
+ p += 3;
+ } else {
+ *(p++) = uri[i];
+ }
+
+ *enc_len = p - *enc;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
static cairo_status_t
_cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
cairo_surface_t *surface)
@@ -1175,6 +1277,8 @@ _cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
cairo_rectangle_int_t extents;
cairo_bool_t is_bounded;
cairo_status_t status;
+ const unsigned char *uri;
+ unsigned int uri_len;
if (_cairo_user_data_array_get_data (&surface->user_data,
(cairo_user_data_key_t *) document))
@@ -1192,10 +1296,34 @@ _cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
_cairo_output_stream_printf (document->xml_node_defs, " xlink:href=\"");
- status = _cairo_surface_base64_encode (surface,
- document->xml_node_defs);
- if (unlikely (status))
- return status;
+ cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_XURI_ENCODED,
+ &uri, &uri_len);
+ if (uri)
+ _cairo_svg_surface_emit_attr_value (document->xml_node_defs,
+ uri, uri_len);
+ else {
+ cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_XURI,
+ &uri, &uri_len);
+ if (uri) {
+ unsigned char *enc;
+ unsigned int enc_len;
+
+ status = _cairo_svg_surface_uri_encode (uri, uri_len,
+ &enc, &enc_len);
+ if (unlikely (status))
+ return status;
+
+ _cairo_svg_surface_emit_attr_value (document->xml_node_defs,
+ enc, enc_len);
+
+ free (enc);
+ } else {
+ status = _cairo_surface_base64_encode (surface,
+ document->xml_node_defs);
+ if (unlikely (status))
+ return status;
+ }
+ }
_cairo_output_stream_printf (document->xml_node_defs, "\"/>\n");
diff --git a/src/cairo.h b/src/cairo.h
index 8be8043..16cafaf 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -2042,6 +2042,8 @@ cairo_surface_set_user_data (cairo_surface_t *surface,
#define CAIRO_MIME_TYPE_JPEG "image/jpeg"
#define CAIRO_MIME_TYPE_PNG "image/png"
#define CAIRO_MIME_TYPE_JP2 "image/jp2"
+#define CAIRO_MIME_TYPE_XURI "text/x-uri"
+#define CAIRO_MIME_TYPE_XURI_ENCODED "text/x-uri-encoded"
cairo_public void
cairo_surface_get_mime_data (cairo_surface_t *surface,