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 aClassName.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 becomeImageSurface
. - Classes are namespaced: actually, everything is namespaced under the
Cairo
module so that it does not clash with other usage ofImageSurface
orContext
and so on. So, you end up withCairo::Context
andCairo::ImageSurface
. - Constants are also namespaced: Likewise, constants like
CAIRO_FORMAT_ARGB32
are defined under theCairo
module and becomeCairo::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:
- Cairo::ImageSurface.from_png(filename) –
cairo_image_surface_create_from_png
- Cairo::ImageSurface.from_png(stream) – this corresponds to
cairo_image_surface_create_from_png_stream
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_create
→Cairo::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_png
→Cairo::ImageSurface.new_from_png(filename)
but new_from_png is cumbersome and becomes justfrom_png
givingCairo::ImageSurface.from_png
(return a Cairo ImageSurface from a PNG)cairo_image_surface_create_from_png_stream
→Cairo::ImageSurface.new_from_png_stream(stream)
butnew_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 justfrom_png
givingCairo::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
becomeMath::PI
- Functions like
sqrt
becomeMath.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
toCairo::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, replacecairo_
withcr.
- 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 becomecr.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
becomesset_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 becomesCairo::FILL_RULE_EVEN_ODD
and so on. There is one thing you have to be careful of. Some constants likeCAIRO_FILL_RULE_EVEN_ODD
becomeCairo::FILL_RULE_EVEN_ODD
while some constants likeCAIRO_FORMAT_ARGB32
becomeCairo::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 calledadd_color_stop_rgba
on an object that is of the typeCairo::[some]Pattern
. So, you might see code like:pat = Cairo::LinearPattern.new(0.0, 0.0, 0.0, 256.0)
followed bypat.add_color_stop_rgba(...)
.
- Finally, some attributes are directly assigned to the object, e.g.
pattern.extend = Cairo::EXTEND_REPEAT
orpattern.matrix = Cairo::Matrix.scale(...)
instead ofcairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
andcairo_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 likeset_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.