From ebe487cabd40a3eac13aad92ff50252600bb9a0e 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 | 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