Cairo with Ruby - Converting code from Cairo to RCairo

Cairo Graphics is a powerful 2D graphics library. Although Cairo is a C library, there are bindings available for many languages. The RCairo gem allows Ruby programs to use Cairo.

In a previous post, I ported Cairo sample code from the C API to using the RCairo gem. This post includes a general guide for converting Cairo C code into Ruby code using RCairo.

At a very High Level

Most of the differences that you will see come from the fact that Ruby is object-oriented and C is not. This means that the Ruby code is organised into classes and namespaces whereas the C code is not. Also, some differences come because Ruby can infer things from the parameters being passed to it. Some of this is simply polymorphism (the same function can receive different types of parameters and the code can figure out how to behave depending on the type of the parameters) and some of it is because it’s easy in Ruby to do some_array.size while in C, you would likely be expected to pass the size (or count) as another parameter.

With that knowledge, let’s get started.

Getting started – create and new

In normal C Cairo, the way to work is:

  • Create a surface
  • Create a Cairo context for that surface
  • Use that Cairo surface for the drawing (move_to, line_to, set_line_width, stroke, fill, etc.)

On the Ruby side, remember that we work with classes, so, naturally things like surface, context, etc. are all classes and there are methods defined on them.

This would likely be the starting point for your C code:

1
2
3
4
5
6
cairo_surface_t *surface; /* A pointer for the surface */
cairo_t *cr; /* A pointer for the context */

/* Create surface and then the context */
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 120, 120);
cr = cairo_create (surface);

Naturally, the _create functions become constructors – so, they become Something.new(..) in Ruby. The code below is what the Ruby equivalent will look like.

1
2
surface = Cairo::ImageSurface.new(Cairo::Format::ARGB32, 120, 120) # New image surface
cr = Cairo::Context.new(surface) # New context

So, this gives us a few things to hold on to:

  • Create functions become constructors: any function that was originally _create is almost certainly now a ClassName.new
  • Class Names are in CamelCase: in general, something like image_surface will be written in CamelCase since it’s a class name. All Ruby classes must begin with an upper-case letter and it’s convention to use CamelCase. That’s how you know that it will become ImageSurface.
  • Classes are namespaced: actually, everything is namespaced under the Cairo module so that it does not clash with other usage of ImageSurface or Context and so on. So, you end up with Cairo::Context and Cairo::ImageSurface.
  • Constants are also namespaced: Likewise, constants like CAIRO_FORMAT_ARGB32 are defined under the Cairo module and become Cairo::Format::ARGB32

There is one extra thing to be aware of. Since it’s possible to have multiple constructors with different parameters, you might (in Ruby) have a few options. For example, you can construct an ImageSurface using any of these forms for .new:

  • Cairo::ImageSurface.new(width, height)
  • Cairo::ImageSurface.new(format, width, height) – this matches the C code we have above
  • Cairo::ImageSurface.new(data, format, width, height, stride) – this corresponds to cairo_image_surface_create_for_data

As if this wasn’t complicated enough, you also have two more ways to create an ImageSurface:

While it might sound complicated at the start and you might wonder how you can be expected to remember all this, my advice is simple:

  • Don’t. Don’t try to force yourself to remember. Look it up from the RCairo documentation.
  • Also, if you look at it, you will notice that RCairo simplifies some of the original long API
    • cairo_image_surface_createCairo::ImageSurface.new – that’s simple!
    • cairo_image_surface_create_for_data -would-become→ Cairo::ImageSurface.new_for_data(different param list) but since we can have multiple signatures for the same function, it’s just simpler to have it as Cairo::ImageSurface.new(different parameter list). After all, it returns a new image surface.
    • cairo_image_surface_create_from_pngCairo::ImageSurface.new_from_png(filename) but new_from_png is cumbersome and becomes just from_png giving Cairo::ImageSurface.from_png (return a Cairo ImageSurface from a PNG)
    • cairo_image_surface_create_from_png_streamCairo::ImageSurface.new_from_png_stream(stream) but new_from_png_stream is cumbersome; also, the input is a PNG whether it is a filename or a stream. So, it’s conceptually simpler to think of it as just from_png giving Cairo::ImageSurface.from_png (return a Cairo ImageSurface from a PNG)

Eventually, as you program, you will get used to this.

Drawing things

For simplicity, let’s assume that you already have an ImageSurface (called surface) and a Cairo context on it (called cr). So, how do we draw on it? This is the really simple part.

Before we go on, one note on maths. The way you have maths constants and functions is different between Ruby and C. In Ruby, mathematics functions are under Math giving you most commonly:

  • Constants like M_PI become Math::PI
  • Functions like sqrt become Math.sqrt

Right – moving along. Most of the code that works with a context looks like some of the lines below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cairo_set_line_width (cr, 10.0);
cairo_arc (cr, xc, yc, radius, angle1, angle2);
cairo_stroke (cr);

/* draw helping lines */
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 6.0);

cairo_arc (cr, xc, yc, 10.0, 0, 2*M_PI);
cairo_fill (cr);

cairo_arc (cr, xc, yc, radius, angle1, angle1);
cairo_line_to (cr, xc, yc);
cairo_arc (cr, xc, yc, radius, angle2, angle2);
cairo_line_to (cr, xc, yc);
cairo_stroke (cr);

Since you now have a Cairo::context object called cr, these will be changed to methods on cr instead of passing the cr structure to the function. So, you will have:

  • cairo_line_to(cr, xc, yc) → cr.line_to(xc, yc)
  • cairo_set_line_width(cr, other parameters) → cr.set_line_width(other parameters)
  • cairo_stroke(cr) → cr.stroke [no parameters needed]

If you want to use search-and-replace for copy-pasted code, here’s a guide:

  • First, manually change the constructors from _create to Cairo::SomeThing.new
  • Next, if there are multiple lines on the same line separated by ‘;’ in C, split them to separate lines
  • If your context is called cr and the code looks like the above, replace cairo_ with cr.
  • Next, we know that the first parameter of the functions is cr – remove that be replacing (cr, with (
  • There are functions like cairo_stroke(cr) which should become cr.stroke with no parameters – you could replace (cr) with blank to clear them up
  • Finally, get rid of the all the semicolons by replacing them with blank also

It sounds silly but this approach actually converts most of the simpler code to directly usable Ruby code.

Working with Other Classes

So far, we looked at the two basic classes when working with Cairo:

  • Cairo::Context – which is basically cairo_t
  • Cairo::ImageSurface – which represents an image surface

This naturally continues when you look at other APIs, for example:

  • Cairo::FontExtents
  • Cairo::FontFace
  • Cairo::FontOptions
  • Cairo::Pattern
  • Cairo::Matrix
  • Cairo::Path

One of the places where you see a small change is when looking at APIs like cairo_pattern_create_radial and cairo_pattern_create_linear which should be constructors (since they have _create in the name) but they construct different things (radial pattern vs linear pattern). So, in RCairo, these become separate classes and constructors with different parameters:

  • Cairo::LinearPattern.new
  • Cairo::RadialPattern.new

You’ll need to search within the documentation (links below) to find the matching classes for what you are looking for. Trust me – it gets more obvious and easier as you go along.

Other Small changes

There are a few more changes that happen when we move from the C API to the RCairo API. I’ve not tried to generalise into rules but here are examples and some notes.

  • set_source_surface becomes set_source(image) – there’s no need to do set_source_surface_image since the code automatically determines that based on the kind of source
  • APIs that just ‘get’ values (e.g.@ cairo_image_surface_get_height@) simply become [surface_object].height since height is now an attribute of the surface object
  • In some APIs, you needed to pass the size of the array (e.g. set_dash) – that’s no longer needed
  • We spoke about constants earlier. There are a lot of constants such as CAIRO_FILL_RULE_EVEN_ODD that becomes Cairo::FILL_RULE_EVEN_ODD and so on. There is one thing you have to be careful of. Some constants like CAIRO_FILL_RULE_EVEN_ODD become Cairo::FILL_RULE_EVEN_ODD while some constants like CAIRO_FORMAT_ARGB32 become Cairo::Format::ARGB32 instead. I couldn’t find a good rule or reason for the difference; just assume that you might need to check. Over time, you will get used to it.
  • Naturally, when an API is something like cairo_pattern_add_color_stop_rgba (pat, ...); this becomes a method called add_color_stop_rgba on an object that is of the type Cairo::[some]Pattern. So, you might see code like:
    • pat = Cairo::LinearPattern.new(0.0, 0.0, 0.0, 256.0) followed by
    • pat.add_color_stop_rgba(...).
  • Finally, some attributes are directly assigned to the object, e.g. pattern.extend = Cairo::EXTEND_REPEAT or pattern.matrix = Cairo::Matrix.scale(...) instead of cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); and cairo_pattern_set_matrix (pattern, &matrix); respectively. In most cases, the correct value is directly set on the object by using ‘=’ instead of calling a separate API on it. Watch out for many APIs that use _set_ but be aware that there are exceptions like set_source_surface discussed earlier.

Looking at the documentation

I hope that the discussion up to this point helps you. Do check the samples ported from C to Ruby on this site to see these rules actually applied to C code converted to Ruby. The code is available on GitHub in case you want to grab it.

These are the main sources to look for if you need help:

  • Cairo C API – this is the main API reference manual for Cairo and explains each function and what it does
  • RCairo English Documentation – this is the index/ table of contents page for RCairo in English. If you’re stuck, this is a great place to go and search for the item you’re trying to convert. For example, search for things like “from_png” or “ImageSurface” or “TextExtents” to find the classes and methods that match. This will likely help you out. However, if you click to the next page, you will find only very basic documentation. It will only give you the parameter names and not much information. At that point, you’ll need to refer back to the Cairo C API or the Japanese documentation (next)
  • RCairo Japanese Documentation – this page has the RCairo documentation index/ table of contents for the Japanese documentation. It’s the same as the English page but clicking through will take you to more details in this case. If you can read Japanese, then this is really useful. If not, you will have to use translation utilities to get a better grasp of what the page is saying. In any case, it will still be helpful.

Hope this helps! Leave me a comment if you find something that could be improved or you feel that should be added.

comments powered by Disqus