[cairo] [PATCH] Enable links to image files in SVG backend

Alexander Shulgin alex.shulgin at gmail.com
Sun Jan 24 11:12:16 PST 2010


OK, since no one seemed to support URI encoding API I decided to drop
it and leave only the bare minimum.  It is pretty useful already, so I
see no reason to postpone this patch any further. :)

---
Added CAIRO_MIME_TYPE_XURI support in SVG backend.

The rationale behind this change is that when someone is trying to
draw on a SVG surface using image surface patterns, the resulting SVG
file can take up to ridiculous 20 megabytes for 3-4 typical
photographic images in a single SVG file.  This also can take
significant amount of CPU time to complete.

The reason for this behaviour is that currently whenever SVG backend
needs to emit an image tag for a surface pattern it takes a snapshot
of the subject surface, encodes it in PNG, then Base64-encodes and
emits the (huge) resulting string.  With use of
cairo_surface_set_mime_data API this can be somewhat improved by
associating JPEG image contents with the corresponding surfaces.
Still this doesn't allow for post-processing of involved photographic
images without regenerating the SVG file.

As SVG specification allows URIs in the image tag's xlink:href
attribute, it is possible instead of embedding encoded image data to
simply link image files residing physically on the same medium as the
generated SVG file: files on disk under common directory, files on a
web server at common base URI, etc.

To make this happen we add new (unofficial) MIME type "text/x-uri" and
let users associate URIs with surfaces through
cairo_surface_set_mime_data() API.  When SVG backend needs to emit
surface contents and it sees "text/x-uri" attached to the surface, it
emits this data instead of taking snapshot.  The URI data is emitted
as is, so correctness check is left solely to the client code.

 doc/public/cairo-sections.txt      |    1 +
 doc/public/tmpl/cairo-surface.sgml |    7 +++
 src/cairo-surface.c                |   16 +++++++-
 src/cairo-svg-surface.c            |   72 ++++++++++++++++++++++++++++++++++--
 src/cairo.h                        |    1 +
 5 files changed, 92 insertions(+), 5 deletions(-)

src/cairo.h: Added #define CAIRO_MIME_TYPE_XURI.

src/cairo-surface.c: cairo_surface_set_mime_data: added list of
supported MIME types to documentation.

src/cairo-svg-surface.c:
  _cairo_svg_surface_emit_surface: added check on
CAIRO_MIME_TYPE_XURI, emit URI instead of surface snapshot if present.
  cairo_svg_surface_create: Described how different MIME types
attached to surface affect resulting SVG text.

diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt
index ff89db7..6409c98 100644
--- a/doc/public/cairo-sections.txt
+++ b/doc/public/cairo-sections.txt
@@ -176,6 +176,7 @@ cairo_svg_version_to_string
 CAIRO_MIME_TYPE_JP2
 CAIRO_MIME_TYPE_JPEG
 CAIRO_MIME_TYPE_PNG
+CAIRO_MIME_TYPE_XURI
 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..5413482 100644
--- a/doc/public/tmpl/cairo-surface.sgml
+++ b/doc/public/tmpl/cairo-surface.sgml
@@ -48,6 +48,13 @@ The Portable Network Graphics image file format
(ISO/IEC 15948). Since 1.10



+<!-- ##### MACRO CAIRO_MIME_TYPE_XURI ##### -->
+<para>
+URI for an image file (unofficial MIME type). Since 1.10
+</para>
+
+
+
 <!-- ##### TYPEDEF cairo_surface_t ##### -->
 <para>

diff --git a/src/cairo-surface.c b/src/cairo-surface.c
index 9aa707f..8868dc8 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,20 @@ _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 (or filename) data can later be 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.
+ *
+ * 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..58be469 100644
--- a/src/cairo-svg-surface.c
+++ b/src/cairo-svg-surface.c
@@ -202,6 +202,22 @@ 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. 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 MIME type #CAIRO_MIME_TYPE_XURI is examined
+ * first. If present, the URI is emitted as is: assuring the
+ * correctness of URI is left to the client code.
+ *
+ * If #CAIRO_MIME_TYPE_XURI is not present, 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 +1184,45 @@ _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: &amp; and &quot;.
+ **/
+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, "&amp;");
+	    else // p == '"'
+		_cairo_output_stream_printf (stream, "&quot;");
+	}
+    }
+
+    /* flush the trailing chars if any */
+    if (p != q)
+	_cairo_output_stream_write (stream, q, p - q);
+}
+
 static cairo_status_t
 _cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
 				 cairo_surface_t *surface)
@@ -1175,6 +1230,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 +1249,17 @@ _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,
+				 &uri, &uri_len);
+    if (uri != NULL) {
+	_cairo_svg_surface_emit_attr_value (document->xml_node_defs,
+					    uri, uri_len);
+    } 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..8f48a99 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -2042,6 +2042,7 @@ 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"

 cairo_public void
 cairo_surface_get_mime_data (cairo_surface_t		*surface,
-- 
1.6.3.3

How's that?  I hope the explanation is long and concise enough. :)

--
Alex


More information about the cairo mailing list