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);
_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
_createis almost certainly now a
- Class Names are in CamelCase: in general, something like
image_surfacewill 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
- Classes are namespaced: actually, everything is namespaced under the
Cairomodule so that it does not clash with other usage of
Contextand so on. So, you end up with
- Constants are also namespaced: Likewise, constants like
CAIRO_FORMAT_ARGB32are defined under the
Cairomodule and become
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
As if this wasn’t complicated enough, you also have two more ways to create an ImageSurface:
- Cairo::ImageSurface.from_png(filename) –
- Cairo::ImageSurface.from_png(stream) – this corresponds to
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::ImageSurface.new– that’s simple!
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::ImageSurface.new_from_png(filename)but new_from_png is cumbersome and becomes just
Cairo::ImageSurface.from_png(return a Cairo ImageSurface from a PNG)
new_from_png_streamis 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
Cairo::ImageSurface.from_png(return a Cairo ImageSurface from a PNG)
Eventually, as you program, you will get used to this.
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
- Functions like
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
- Next, if there are multiple lines on the same line separated by ‘;’ in C, split them to separate lines
- If your context is called
crand the code looks like the above, replace
- Next, we know that the first parameter of the functions is
cr– remove that be replacing
- There are functions like
cairo_stroke(cr)which should become
cr.strokewith 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:
One of the places where you see a small change is when looking at APIs like
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:
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(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].heightsince 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_ODDand so on. There is one thing you have to be careful of. Some constants like
Cairo::FILL_RULE_EVEN_ODDwhile some constants like
Cairo::Format::ARGB32instead. 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_rgbaon 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
- Finally, some attributes are directly assigned to the object, e.g.
pattern.extend = Cairo::EXTEND_REPEATor
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
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.