Error with Python ctypes and librsvg
Asked Answered
R

2

4

I'm trying to wrap basic function of librsvg with ctypes for Python, but I'm getting a segfault.

C:

// pycairo excerpt
typedef struct {
  PyObject_HEAD
  cairo_t *ctx;
  PyObject *base; /* base object used to create context, or NULL */
} PycairoContext;

// librsvg excerpt
RsvgHandle * rsvg_handle_new_from_file (const gchar * file_name, GError ** error);
// ...
gboolean rsvg_handle_render_cairo (RsvgHandle * handle, cairo_t * cr);

Python ctypes:

from ctypes import *
from ctypes import util


librsvg = cdll.LoadLibrary('/brew/lib/librsvg-2.2.dylib')
libgobject = cdll.LoadLibrary('/brew/lib/libgobject-2.0.dylib')

libgobject.g_type_init()


class RSVGDimensionData(Structure):

    _fields_ = (
        ('width', c_int),
        ('height', c_int),
        ('em', c_double),
        ('ex', c_double)
    )

class PycairoContext(Structure):

    _fields_ = (
        ('PyObject_HEAD', c_byte * object.__basicsize__),
        ('ctx', c_void_p),
        ('base', c_void_p)
    )


class RSVGHandle(object):

    def __init__(self, path):
        self.path = path
        self.error = ''
        self.handle = librsvg.rsvg_handle_new_from_file(self.path, self.error)

    def render_cairo(self, context):
        context.save()
        z = PycairoContext.from_address(id(context))
        librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)
        context.restore()


import cairo

h = RSVGHandle('bank.svg')
s = cairo.ImageSurface(cairo.FORMAT_ARGB32, 100, 100)
ctx = cairo.Context(s)


# segmentation fault....
h.render_cairo(ctx)

The error is happening in this line: librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)

Any idea about what's wrong with this?

Redwine answered 26/5, 2011 at 17:38 Comment(1)
As an immediate solution for OSX, running your code under 32 bits should "solve" the problem. arch -i386 python x.pyDiaghilev
D
6

The problem is that the specification of the return type is not defined; using c_void_p on the result alone isn't enough to solve the problem in that case. You need to put

librsvg.rsvg_handle_new_from_file.restype = c_void_p

in an appropriate place. Then it (also) works in OSX either in 32 bits or 64 bits.

But I've found more helpful to augment the basic wrapping in order to handle possible errors when creating a handle from a file. Following is a basic wrapper that does that. It also replicates in a mostly identical way the basic use of the standard rsvg bindings.

from ctypes import CDLL, POINTER, Structure, byref, util
from ctypes import c_bool, c_byte, c_void_p, c_int, c_double, c_uint32, c_char_p

class _PycairoContext(Structure):
    _fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__),
                ("ctx", c_void_p),
                ("base", c_void_p)]

class _RsvgProps(Structure):
    _fields_ = [("width", c_int), ("height", c_int),
                ("em", c_double), ("ex", c_double)]

class _GError(Structure):
    _fields_ = [("domain", c_uint32), ("code", c_int), ("message", c_char_p)]


def _load_rsvg(rsvg_lib_path=None, gobject_lib_path=None):
    if rsvg_lib_path is None:
        rsvg_lib_path = util.find_library('rsvg-2')
    if gobject_lib_path is None:
        gobject_lib_path = util.find_library('gobject-2.0')
    l = CDLL(rsvg_lib_path)
    g = CDLL(gobject_lib_path)
    g.g_type_init()

    l.rsvg_handle_new_from_file.argtypes = [c_char_p, POINTER(POINTER(_GError))]
    l.rsvg_handle_new_from_file.restype = c_void_p
    l.rsvg_handle_render_cairo.argtypes = [c_void_p, c_void_p]
    l.rsvg_handle_render_cairo.restype = c_bool
    l.rsvg_handle_get_dimensions.argtypes = [c_void_p, POINTER(_RsvgProps)]

    return l    

_librsvg = _load_rsvg()


class Handle(object):
    def __init__(self, path):
        lib = _librsvg
        err = POINTER(_GError)()
        self.handle = lib.rsvg_handle_new_from_file(path.encode(), byref(err))
        if self.handle is None:
            gerr = err.contents
            raise Exception(gerr.message)
        self.props = _RsvgProps()
        lib.rsvg_handle_get_dimensions(self.handle, byref(self.props))

    def render_cairo(self, ctx):
        """Returns True is drawing succeeded."""
        z = _PycairoContext.from_address(id(ctx))
        return _librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)

Example usage can be seen at https://mcmap.net/q/1331040/-how-can-i-read-svg-data-stroke-in-pycairo.

Diaghilev answered 18/2, 2013 at 18:36 Comment(5)
I'll upvote this as soon as I hear someone has succeeded using this method, otherwise I can't verify this, not being an OSX userGraduated
@JamesHurford that is a little contradictory, because I have used it myself. So you already "heard" someone saying it succeeded.Diaghilev
Yes you're probably right. I was short on time when I read the answer, so my apologies. Have an upvote on me. Good to hear someone has solved this problem as I said beforeGraduated
I was trying to run a script (github.com/skagedal/svgclip) on OS X that has a dependency on pyrsvg, and first I went the way of trying to install gnome-python-desktop or something similar just to get it but I soon found out that it was something extremely hard to do. Using your code was way way simpler. Thanks a lot.Tillford
I just tested this code on OS X 10.11 with Python 3.5 and it works perfectly. The problem was indeed the lack of proper type declarations on both the rsvg_handle_new_from_file and rsvg_handle_render_cairo functions. Thank you @mmgp. Saved me a lot of trouble.Typify
G
3

librsvg.rsvg_handle_render_cairo expects pointers, and is getting integers instead. Not sure of the entire story here but this modification at least does not segfault.

Try this

 librsvg.rsvg_handle_render_cairo(c_void_p(self.handle), c_void_p(z.ctx))

Notice I wrapped the two parameters in c_void_p to make them into void * pointers. Not ideal, but it seems to work.

Graduated answered 8/7, 2011 at 12:0 Comment(10)
It works perfectly for me. Is it just a segfault message or are you actually getting anything useful printed to the console? I suspect you have something wrong with the librsvg C library version you are using.Graduated
I am using librsvg-2.so.2.34.0 and libgobject-2.0.so.0.2800.8Graduated
I also note you seem to be using a Apple Mac, I'm running Gentoo linux amd64. That may give a clue as to what is going on? ie it may be a Mac specific problem.Graduated
I added a line to write it to png and got a copy of the input svg file in the resulting png. I was worried a error was happening silently in the background, ie no output to cairo_surface_t from the svg file.Graduated
I compiled librsvg-2.32.1 (and libgobject-2.0) via homebrew with the following configuration (--disable-dependency-tracking --prefix=/usr/local/Cellar/librsvg/2.32.1 --enable-tools=yes --enable-pixbuf-loader=yes). It is just the sefault message I'm getting in the console which is a "bit" hard to debug.Redwine
You could try updating to librsvg-2.34.0 or later and see if that works. If the later versions don't work for you, file a bug specific to Mac and try a earlier version. If none of this works then you could try getting the latest version from the repository the developers librsvg use.Graduated
yes, I tried that one already, but still the same. I'll definitely file a bug, if there is some more time ;-) Thank you nevertheless for your efforts!Redwine
I was having a very similar issue. wrapping the parameters with the c_void_p seemed to work. Thanks for the tip. Note: just for reference, I'm posting the doc link: docs.python.org/library/ctypes.html#fundamental-data-typesDisaccustom
@ahojnnes it is not a bug, the wrapper is just carelessly done. The restype needs to be specified, using c_void_p on the result alone isn't adequate. See the other answer (if you happen to revisit SO at some point).Diaghilev
I'm glad someone using OSX was able to solve this, though I can't verify this myself, since I don't use OSX.Graduated

© 2022 - 2024 — McMap. All rights reserved.