# CFFI API

cairocffi’s API is made of a number of wrapper classes that provide a more Pythonic interface for various cairo objects. Functions that take a pointer as their first argument become methods, error statuses become exceptions, and reference counting is hidden away.

In order to use other C libraries that use integrate with cairo, or if cairocffi’s API is not sufficient (Consider making a pull request!) you can access cairo’s lower level C pointers and API through CFFI.

## Module-level objects

cairocffi.ffi

  A instance with all of the cairo C API declared.

cairocffi.cairo

  The libcairo library, pre-loaded with . All cairo functions are accessible as attributes of this object:

      import cairocffi
      from cairocffi import cairo as cairo_c, SURFACE_TYPE_XLIB

      if cairo_c.cairo_surface_get_type(surface._pointer) == SURFACE_TYPE_XLIB:
          ...

  See the cairo manual for details.

## Reference counting in cairo

Most cairo objects are reference-counted, and freed when the count reaches zero. cairocffi’s Python wrapper will automatically decrease the reference count when they are garbage-collected. Therefore, care must be taken when creating a wrapper as to the reference count should be increased (for existing cairo objects) or not (for cairo objects that were just created with a refcount of 1.)

## Wrappers

static Surface._from_pointer(pointer, incref)

  Wrap an existing `cairo_surface_t *` cdata pointer.

  Parameters:

    incref (bool) – Whether increase the reference count now.

  Returns:

    A new instance of `Surface` or one of its sub-classes, depending on the surface’s type.

static Pattern._from_pointer(pointer, incref)

  Wrap an existing `cairo_pattern_t *` cdata pointer.

  Parameters:

    incref (bool) – Whether increase the reference count now.

  Returns:

    A new instance of `Pattern` or one of its sub-classes, depending on the pattern’s type.

static FontFace._from_pointer(pointer, incref)

  Wrap an existing `cairo_font_face_t *` cdata pointer.

  Parameters:

    incref (bool) – Whether increase the reference count now.

  Returns:

    A new instance of `FontFace` or one of its sub-classes, depending on the face’s type.

static ScaledFont._from_pointer(pointer, incref)

  Wrap an existing `cairo_scaled_font_t *` cdata pointer.

  Parameters:

    incref (bool) – Whether increase the reference count now.

  Returns:

    A new `ScaledFont` instance.

classmethod Context._from_pointer(pointer, incref)

  Wrap an existing `cairo_t *` cdata pointer.

  Parameters:

    incref (bool) – Whether increase the reference count now.

  Returns:

    A new `Context` instance.

Surface._pointer

  The underlying `cairo_surface_t *` cdata pointer.

Pattern._pointer

  The underlying `cairo_pattern_t *` cdata pointer.

FontFace._pointer

  The underlying `cairo_font_face_t *` cdata pointer.

ScaledFont._pointer

  The underlying `cairo_scaled_font_t *` cdata pointer.

FontOptions._pointer

  The underlying `cairo_scaled_font_t *` cdata pointer.

Matrix._pointer

  The underlying `cairo_matrix_t *` cdata pointer.

Context._pointer

  The underlying `cairo_t *` cdata pointer.

## Converting pycairo wrappers to cairocffi

Some libraries such as PyGTK or PyGObject provide a pycairo `Context` object for you to draw on. It is possible to extract the underlying `cairo_t *` pointer and create a cairocffi wrapper for the same cairo context.

The follwing function does that with unsafe pointer manipulation. It only works on CPython.

    import cairo  # pycairo

    import cairocffi

    def _UNSAFE_pycairo_context_to_cairocffi(pycairo_context):  # noqa: N802
        # Sanity check. Continuing with another type would probably segfault.
        if not isinstance(pycairo_context, cairo.Context):
            raise TypeError('Expected a cairo.Context, got %r' % pycairo_context)

        # On CPython, id() gives the memory address of a Python object.
        # pycairo implements Context as a C struct:
        #     typedef struct {
        #         PyObject_HEAD
        #         cairo_t *ctx;
        #         PyObject *base;
        #     } PycairoContext;
        # Still on CPython, object.__basicsize__ is the size of PyObject_HEAD,
        # ie. the offset to the ctx field.
        # ffi.cast() converts the integer address to a cairo_t** pointer.
        # [0] dereferences that pointer, ie. read the ctx field.
        # The result is a cairo_t* pointer that cairocffi can use.
        return cairocffi.Context._from_pointer(
            cairocffi.ffi.cast('cairo_t **',
                               id(pycairo_context) + object.__basicsize__)[0],
            incref=True)

Converting other types of objects like surfaces is very similar, but left as an exercise to the reader.

## Converting cairocffi wrappers to pycairo

The reverse conversion is also possible. Here we use ctypes rather than CFFI because Python’s C API is sensitive to the GIL.

    import ctypes

    import cairo  # pycairo

    import cairocffi

    pycairo = ctypes.PyDLL(cairo._cairo.__file__)
    pycairo.PycairoContext_FromContext.restype = ctypes.c_void_p
    pycairo.PycairoContext_FromContext.argtypes = 3 * [ctypes.c_void_p]
    ctypes.pythonapi.PyList_Append.argtypes = 2 * [ctypes.c_void_p]

    def _UNSAFE_cairocffi_context_to_pycairo(cairocffi_context):  # noqa: N802
        # Sanity check. Continuing with another type would probably segfault.
        if not isinstance(cairocffi_context, cairocffi.Context):
            raise TypeError('Expected a cairocffi.Context, got %r'
                            % cairocffi_context)

        # Create a reference for PycairoContext_FromContext to take ownership of.
        cairocffi.cairo.cairo_reference(cairocffi_context._pointer)
        # Casting the pointer to uintptr_t (the integer type as wide as a pointer)
        # gets the context’s integer address.
        # On CPython id(cairo.Context) gives the address to the Context type,
        # as expected by PycairoContext_FromContext.
        address = pycairo.PycairoContext_FromContext(
            int(cairocffi.ffi.cast('uintptr_t', cairocffi_context._pointer)),
            id(cairo.Context),
            None)
        assert address
        # This trick uses Python’s C API
        # to get a reference to a Python object from its address.
        temp_list = []
        assert ctypes.pythonapi.PyList_Append(id(temp_list), address) == 0
        return temp_list[0]

## Example: using Pango through CFFI with cairocffi

The program below shows a fairly standard usage of CFFI to access Pango’s C API. The `Context._pointer` pointer can be used directly as an argument to CFFI functions that expect `cairo_t *`. The C definitions are copied from Pango’s and GLib’s documentation.

Using CFFI for accessing Pango (rather than the traditional bindings in PyGTK or PyGObject with introspection) is not only easiest for using together with cairocffi, but also means that all of Pango’s API is within reach, whereas bindings often only expose the high level API.

    import cffi

    import cairocffi

    ffi = cffi.FFI()
    ffi.cdef('''
        /* GLib */
        typedef void cairo_t;
        typedef void* gpointer;
        void g_object_unref (gpointer object);

        /* Pango and PangoCairo */
        typedef ... PangoLayout;
        typedef enum {
            PANGO_ALIGN_LEFT,
            PANGO_ALIGN_CENTER,
            PANGO_ALIGN_RIGHT
        } PangoAlignment;
        int pango_units_from_double (double d);
        PangoLayout * pango_cairo_create_layout (cairo_t *cr);
        void pango_cairo_show_layout (cairo_t *cr, PangoLayout *layout);
        void pango_layout_set_width (PangoLayout *layout, int width);
        void pango_layout_set_alignment (
            PangoLayout *layout, PangoAlignment alignment);
        void pango_layout_set_markup (
            PangoLayout *layout, const char *text, int length);
    ''')
    gobject = ffi.dlopen('gobject-2.0')
    pango = ffi.dlopen('pango-1.0')
    pangocairo = ffi.dlopen('pangocairo-1.0')

    def gobject_ref(pointer):
        return ffi.gc(pointer, gobject.g_object_unref)

    units_from_double = pango.pango_units_from_double

    def write_example_pdf(target):
        pt_per_mm = 72 / 25.4
        width, height = 210 * pt_per_mm, 297 * pt_per_mm  # A4 portrait
        surface = cairocffi.PDFSurface(target, width, height)
        context = cairocffi.Context(surface)
        context.translate(0, 300)
        context.rotate(-0.2)

        layout = gobject_ref(
            pangocairo.pango_cairo_create_layout(context._pointer))
        pango.pango_layout_set_width(layout, units_from_double(width))
        pango.pango_layout_set_alignment(layout, pango.PANGO_ALIGN_CENTER)
        markup = '<span font="italic 30">Hi from Παν語!</span>'
        markup = ffi.new('char[]', markup.encode('utf8'))
        pango.pango_layout_set_markup(layout, markup, -1)
        pangocairo.pango_cairo_show_layout(context._pointer, layout)

    if __name__ == '__main__':
        write_example_pdf(target='pango_example.pdf')