Re: Pycairo: Access cairo_surface_set_mime_data through pycairo

Simon Sapin <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
Le 07/08/2012 03:49, [email protected] a écrit :
> On Fri, 2012-08-03 at 12:00 -0700, [email protected]
>> The pdf files I'm generating with pycairo are rather large. Is it
>> possible to expose cairo_surface_set_mime_data through pycairo, so
>> the
>> jpeg data can be embedded instead of converted into png?
>
> Hi Manuel,
>
> The function should appear in the next version of pycairo. You could
> open a bug report if you want to make sure it is not forgotten, and
> submit a patch if you are really keen.

Hi,

Please find attached a patch (with tests and docs) for adding the 
.set_mime_data() and .get_mime_data() methods to cairo.Surface.

See also:
https://github.com/SimonSapin/py2cairo/commit/ebe487cabd40a3eac

Cheers,
-- 
Simon Sapin

-- 
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
mime_data.patch (text/x-patch, 8.9 KB)
From ebe487cabd40a3eac13aad92ff50252600bb9a0e Mon Sep 17 00:00:00 2001
From: Simon Sapin <[email protected]>
Date: Tue, 18 Dec 2012 15:44:16 +0100
Subject: [PATCH] Add Surface.set_mime_data() and .get_mime_data()

---
 doc/reference/surfaces.rst |   49 ++++++++++++++++++++++++
 src/cairomodule.c          |   15 ++++++++
 src/private.h              |    1 +
 src/surface.c              |   88 ++++++++++++++++++++++++++++++++++++++++++++
 test/api_test.py           |   41 +++++++++++++++++++++
 5 files changed, 194 insertions(+)

diff --git a/doc/reference/surfaces.rst b/doc/reference/surfaces.rst
index 63ba990..9b2befd 100644
--- a/doc/reference/surfaces.rst
+++ b/doc/reference/surfaces.rst
@@ -139,6 +139,24 @@ class Surface()
       rendering on them, print surfaces to disable hinting of metrics and so
       forth. The result can then be used with :class:`ScaledFont`.
 
+   .. method:: get_mime_data(mime_type)
+
+      :param mime_type: the MIME type of the image data
+      :type mime_type: string
+      :returns: a read-only Python buffer object or :obj:`None`
+
+      Return mime data previously attached to surface
+      with :meth:`.set_mime_data` using the specified mime type.
+      If no data has been attached with the given mime type,
+      :obj:`None` is returned.
+
+      **Warning:** the returned buffer is only safe to use as long as
+      the *Surface* object is alive and
+      the data has not been removed or overridden
+      through :meth:`set_mime_data`.
+
+      .. versionadded:: 1.10.1
+
    .. method:: mark_dirty()
 
       Tells cairo that drawing has been done to *Surface* using means other
@@ -214,6 +232,37 @@ class Surface()
 
       .. versionadded:: 1.2
 
+   .. method:: set_mime_data(mime_type, data)
+
+      :param mime_type: the MIME type of the image data
+      :type mime_type: string
+      :param data: the image data to attach to the surface
+      :type data: buffer
+
+      Attach an image in the format :obj:`mime_type` to *Surface*.
+      To remove the data from a surface,
+      call this function with same mime type and :obj:`None` 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:
+      ``"image/jpeg"``,
+      ``"image/png"``,
+      ``"image/jp2"``,
+      ``"text/x-uri"``.
+
+      See corresponding backend surface docs for details
+      about which MIME types it can handle.
+      Caution: the associated MIME data will be discarded
+      if you draw on the surface afterwards.
+      Use this function with care.
+
+      .. versionadded:: 1.10.1
+
    .. method:: show_page()
 
       Emits and clears the current page for backends that support multiple
diff --git a/src/cairomodule.c b/src/cairomodule.c
index 67d8091..3e05ba5 100644
--- a/src/cairomodule.c
+++ b/src/cairomodule.c
@@ -49,6 +49,10 @@
 /* A module specific exception */
 PyObject *CairoError = NULL;
 
+/* A dict of python strings to PyCapsule objects containing pointers
+ * to CAIRO_MIME_TYPE_* constants. */
+PyObject *Pycairo_mime_type_map = NULL;
+
 int
 Pycairo_Check_Status (cairo_status_t status) {
   if (PyErr_Occurred() != NULL)
@@ -359,6 +363,17 @@
   if (PyModule_AddObject(m, "Error", CairoError) < 0)
     return;
 
+  if (Pycairo_mime_type_map == NULL) {
+    Pycairo_mime_type_map = PyDict_New();
+    #define ADD_MIME_TYPE(x) \
+    PyDict_SetItemString(Pycairo_mime_type_map, x, \
+                         PyCapsule_New(x, NULL, NULL));
+    ADD_MIME_TYPE(CAIRO_MIME_TYPE_JPEG);
+    ADD_MIME_TYPE(CAIRO_MIME_TYPE_PNG);
+    ADD_MIME_TYPE(CAIRO_MIME_TYPE_JP2);
+    ADD_MIME_TYPE(CAIRO_MIME_TYPE_URI);
+  }
+
     /* constants */
 #if CAIRO_HAS_ATSUI_FONT
   PyModule_AddIntConstant(m, "HAS_ATSUI_FONT", 1);
diff --git a/src/private.h b/src/private.h
index bf6ffa5..fe693b8 100644
--- a/src/private.h
+++ b/src/private.h
@@ -43,6 +43,7 @@
 
 
 extern PyObject *CairoError;
+extern PyObject *Pycairo_mime_type_map;
 
 extern PyTypeObject PycairoContext_Type;
 PyObject *PycairoContext_FromContext (cairo_t *ctx, PyTypeObject *type,
diff --git a/src/surface.c b/src/surface.c
index a75a737..33f16e9 100644
--- a/src/surface.c
+++ b/src/surface.c
@@ -149,6 +149,16 @@
   return CAIRO_STATUS_SUCCESS;
 }
 
+/* for use with
+ * cairo_surface_set_mime_data()
+ */
+void
+_decref_func (void *closure) {
+  PyGILState_STATE gstate = PyGILState_Ensure();
+  Py_DECREF((PyObject *)closure);
+  PyGILState_Release(gstate);
+}
+
 static void
 surface_dealloc (PycairoSurface *o) {
   if (o->surface) {
@@ -329,6 +339,82 @@
 }
 #endif  /* CAIRO_HAS_PNG_FUNCTIONS */
 
+const char*
+_get_mime_type(PyObject *obj) {
+  PyObject *capsule;
+  const char *result;
+
+  capsule = PyObject_GetItem(Pycairo_mime_type_map, obj);
+  if (capsule == NULL) {
+    return NULL;
+  }
+
+  result = PyCapsule_GetPointer(capsule, NULL);
+  Py_DECREF(capsule);
+  return result;
+}
+
+static PyObject *
+surface_set_mime_data (PycairoSurface *o, PyObject *args) {
+  PyObject *obj, *mime_type;
+  const char *mime_type_c;
+  const unsigned char *buffer;
+  unsigned long buffer_len;
+  int res;
+  cairo_status_t status;
+
+  if (!PyArg_ParseTuple(args, "OO:ImageSurface.set_mime_data",
+                        &mime_type, &obj))
+    return NULL;
+
+  mime_type_c = _get_mime_type(mime_type);
+  if (mime_type_c == NULL)
+    return NULL;
+
+  if (obj == Py_None) {
+    buffer = NULL;
+    buffer_len = 0;
+  } else {
+    res = PyObject_AsReadBuffer (
+      obj, (const void **)&buffer, (Py_ssize_t*)&buffer_len);
+    if (res == -1)
+      return NULL;
+  }
+
+  status = cairo_surface_set_mime_data (
+    o->surface, mime_type_c, buffer, buffer_len, _decref_func, obj);
+  RETURN_NULL_IF_CAIRO_ERROR(status);
+  if (obj != Py_None) {
+    Py_INCREF(obj);
+  }
+  Py_RETURN_NONE;
+}
+
+static PyObject *
+surface_get_mime_data (PycairoSurface *o, PyObject *args) {
+  PyObject *mime_type;
+  const char *mime_type_c;
+  const unsigned char *buffer;
+  unsigned long buffer_len;
+  int res;
+  cairo_status_t status;
+
+  if (!PyArg_ParseTuple(args, "O:ImageSurface.get_mime_data", &mime_type))
+    return NULL;
+
+  mime_type_c = _get_mime_type(mime_type);
+  if (mime_type_c == NULL) {
+    PyErr_Clear();
+    Py_RETURN_NONE;
+  }
+
+  cairo_surface_get_mime_data (o->surface, mime_type_c, &buffer, &buffer_len);
+  if (buffer == NULL) {
+    /* No data */
+    Py_RETURN_NONE;
+  }
+  return PyBuffer_FromMemory((void *)buffer, (Py_ssize_t)buffer_len);
+}
 
 static PyMethodDef surface_methods[] = {
   /* methods never exposed in a language binding:
@@ -357,6 +443,8 @@
 #ifdef CAIRO_HAS_PNG_FUNCTIONS
   {"write_to_png",   (PyCFunction)surface_write_to_png,       METH_VARARGS},
 #endif
+  {"set_mime_data",  (PyCFunction)surface_set_mime_data,      METH_VARARGS},
+  {"get_mime_data",  (PyCFunction)surface_get_mime_data,      METH_VARARGS},
   {NULL, NULL, 0, NULL},
 };
 
diff --git a/test/api_test.py b/test/api_test.py
index 38c6979..5908d1e 100644
--- a/test/api_test.py
+++ b/test/api_test.py
@@ -7,6 +7,7 @@
 from __future__ import absolute_import # new in 2.5, redundant in 2.7/3.0
 from __future__ import print_function  # new in 2.6, redundant in 3.0
 
+import io
 import tempfile as tfi
 
 import cairo
@@ -92,3 +93,43 @@ def test_surface():
 
 def test_text():
   pass
+
+
+def test_mime_data():
+    # A 1x1 pixel white image:
+    png_bytes = (
+        b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQV'
+        b'QI12P4DwABAQEAG7buVgAAAABJRU5ErkJggg=='.decode('base64'))
+    jpeg_bytes = (
+        b'eJz7f+P/AwYBLzdPNwZGRkYGDyBk+H+bwRnEowj8P8TAzcHACDJHkOH/EQYRIBsV'
+        b'cP6/xcDBCBJlrLcHqRBAV8EAVcHIylSPVwGbPQEFjPaK9XDrBAipBSq4CQB9jiS0'
+        .decode('base64').decode('zlib'))
+
+    def render(image, surface_type):
+        file_like = io.BytesIO()
+        surface = surface_type(file_like, 100, 100)
+        context = cairo.Context(surface)
+        context.set_source_surface(image, 0, 0)
+        context.paint()
+        surface.finish()
+        pdf_bytes = file_like.getvalue()
+        return pdf_bytes
+
+    image = cairo.ImageSurface.create_from_png(io.BytesIO(png_bytes))
+    assert image.get_mime_data('image/jpeg') is None
+
+    pdf_bytes = render(image, cairo.PDFSurface)
+    assert pdf_bytes.startswith(b'%PDF')
+    assert b'/Filter /DCTDecode' not in pdf_bytes
+
+    image.set_mime_data('image/jpeg', jpeg_bytes)
+    jpeg_bytes = jpeg_bytes[:]  # Copy, drop a reference to the old object.
+    assert image.get_mime_data('image/jpeg')[:] == jpeg_bytes
+
+    pdf_bytes = render(image, cairo.PDFSurface)
+    assert pdf_bytes.startswith(b'%PDF')
+    # JPEG-encoded image:
+    assert b'/Filter /DCTDecode' in pdf_bytes
+
+    image.set_mime_data('image/jpeg', None)
+    assert image.get_mime_data('image/jpeg') is None
-- 
1.7.10
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.