From ad27ca278fc68f1a00b6e7099ecb32f82b79f4e7 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 18 Dec 2012 15:44:16 +0100 Subject: [PATCH] Add Surface.set_mime_data() and .get_mime_data() --- doc/reference/surfaces.rst | 44 ++++++++++++++++++++++++ src/cairomodule.c | 15 +++++++++ src/private.h | 1 + src/surface.c | 80 ++++++++++++++++++++++++++++++++++++++++++++ test/api_test.py | 42 +++++++++++++++++++++++ 5 files changed, 182 insertions(+) diff --git a/doc/reference/surfaces.rst b/doc/reference/surfaces.rst index ef01c8e..36cedf8 100644 --- a/doc/reference/surfaces.rst +++ b/doc/reference/surfaces.rst @@ -169,6 +169,19 @@ 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: str + :returns: bytes 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. + + .. versionadded:: 1.10.1 + .. method:: mark_dirty() Tells cairo that drawing has been done to *Surface* using means other @@ -244,6 +257,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: str + :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 b1f2caa..c3164a7 100644 --- a/src/cairomodule.c +++ b/src/cairomodule.c @@ -48,6 +48,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) @@ -407,6 +411,17 @@ struct cairo_state { (PyObject *)&PycairoXlibSurface_Type); #endif + 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 4a6b6ad..8854b91 100644 --- a/src/private.h +++ b/src/private.h @@ -32,6 +32,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 204af68..90066ca 100644 --- a/src/surface.c +++ b/src/surface.c @@ -160,6 +160,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) { @@ -355,6 +365,74 @@ } #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; + 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 { + if (!PyArg_ParseTuple(args, "Oy#:ImageSurface.set_mime_data", + &mime_type, &buffer, &buffer_len)) + 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; + + 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); + return Py_BuildValue("y#", buffer, buffer_len); +} static PyMethodDef surface_methods[] = { /* methods never exposed in a language binding: @@ -385,6 +463,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 86317b2..25c0d25 100644 --- a/test/api_test.py +++ b/test/api_test.py @@ -3,6 +3,9 @@ - is not able to test the quality of images created. We assume cairo itself tests for this. ''' +import io +import zlib +import base64 import tempfile as tfi import cairo @@ -150,3 +153,42 @@ def test_region(): cairo.RectangleInt(1, 14, 8, 1), cairo.RectangleInt(1, 13, 10, 1), ]) + + +def test_mime_data(): + # A 1x1 pixel white image: + png_bytes = base64.b64decode( + b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQV' + b'QI12P4DwABAQEAG7buVgAAAABJRU5ErkJggg==') + jpeg_bytes = zlib.decompress(base64.b64decode( + b'eJz7f+P/AwYBLzdPNwZGRkYGDyBk+H+bwRnEowj8P8TAzcHACDJHkOH/EQYRIBsV' + b'cP6/xcDBCBJlrLcHqRBAV8EAVcHIylSPVwGbPQEFjPaK9XDrBAipBSq4CQB9jiS0')) + + 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