Interfacing Raku to Gnome GTK+

Cairo’s Drawing Model

In order to explain the operations used by cairo, we first delve into a model of how cairo handles drawing. There are only a few concepts involved, which are then applied over and over by the different methods. First I’ll describe the nouns: destination, source, mask, path, and context. After that I’ll describe the verbs which offer ways to manipulate the nouns and draw the graphics you wish to create.

Nouns

Cairo’s nouns are somewhat abstract. To make them concrete I’m including diagrams that depict how they interact. The first three nouns are the three layers in the diagrams you see in this section. The fourth noun, the path, is drawn on the middle layer when it is relevant. The final noun, the context, isn’t shown.

Destination

The destination is the surface on which you're drawing. It may be tied to an array of pixels like in this tutorial, or it might be tied to a SVG or PDF file, or something else. This surface collects the elements of your graphic as you apply them, allowing you to build up a complex work as though painting on a canvas.

Source

The source is the "paint" you're about to work with. I show this as it is—plain black for several examples—but translucent to show lower layers. Unlike real paint, it doesn't have to be a single color; it can be a pattern or even a previously created destination surface (see How do I paint from one surface to another?). Also unlike real paint it can contain transparency information—the Alpha channel.

Mask

The mask is the most important piece: it controls where you apply the source to the destination. I will show it as a yellow layer with holes where it lets the source through. When you apply a drawing verb, it's like you stamp the source to the destination. Anywhere the mask allows, the source is copied. Anywhere the mask disallows, nothing happens.

Path

The path is somewhere between part of the mask and part of the context. I will show it as thin green lines on the mask layer. It is manipulated by path verbs, then used by drawing verbs.

Context

The context keeps track of everything that verbs affect. It tracks one source, one destination, and one mask. It also tracks several helper variables like your line width and style, your font face and size, and more. Most importantly it tracks the path, which is turned into a mask by drawing verbs.

Before you can start to draw something with cairo, you need to create the context. The context is stored in cairo’s central data type, called cairo_t. When you create a cairo context, it must be tied to a specific surface—for example, an image surface if you want to create a PNG file. There is also a data type for the surface, called cairo_surface_t. You can initialize your cairo context like this:

my Gnome::Cairo::ImageSurface $surface .= new(
  :format(CAIRO_FORMAT_ARGB32), :width(120), :height(120)
);
my Gnome::Cairo $context .= new(:$surface);

Note: You need to import (use) Gnome::Cairo::Enums and Gnome::Cairo::Types most of the time. Also for the code above you need to import Gnome::Cairo::ImageSurface and Gnome::Cairo too.

The cairo context in this example is tied to an image surface of dimension 120 x 120 and 32 bits per pixel to store RGB and Alpha information. Surfaces can be created specific to most cairo backends, see the manual for details.

Verbs

The reason you are using cairo in a program is to draw. Cairo internally draws with one fundamental drawing operation: the source and mask are freely placed somewhere over the destination. Then the layers are all pressed together and the paint from the source is transferred to the destination wherever the mask allows it. To that extent the following five drawing verbs, or operations, are all similar. They differ by how they construct the mask.

Stroke

The .stroke() operation takes a virtual pen along the path. It allows the source to transfer through the mask in a thin (or thick) line around the path, according to the pen’s line width, dash style, and line caps.


with my Gnome::Cairo $context .= new(:$surface) {
  .set-line-width(10);
  .set-source-rgba( 0, 0, 0.4, 1);
  .rectangle( 30, 30, 60, 60);
  .stroke;
}

Fill

The .fill() operation instead uses the path like the lines of a coloring book, and allows the source through the mask within the hole whose boundaries are the path. For complex paths (paths with multiple closed sub-paths—like a donut—or paths that self-intersect) this is influenced by the fill rule. Note that while stroking the path transfers the source for half of the line width on each side of the path, filling a path fills directly up to the edge of the path and no further.


with $context .= new(:$surface) {
  .set-source-rgba( 0, 0, 0.4, 1);
  .rectangle( 30, 30, 60, 60);
  .fill;
}

Show Text / Glyphs

The .show-text() operation forms the mask from text. It may be easier to think of .show-text() as a shortcut for creating a path with .text-path() and then using .fill() to transfer it. Be aware .show_text() caches glyphs so is much more efficient if you work with a lot of text.


with $context .= new(:$surface) {
  .set-source-rgba( 0.0, 0.0, 0.4, 1);
  .select-font-face(
    "Georgia", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD
  );
  .set-font-size(100);
  my cairo_text_extents_t $text-extends = .text-extents("a");
  .move-to(
    0.5 * $width - $text-extends.width/2 - $text-extends.x_bearing,
    0.5 * $height - $text-extends.height/2 - $text-extends.y_bearing
  );
  .show-text("a");
}

Paint

The .paint() operation uses a mask that transfers the entire source to the destination. Some people consider this an infinitely large mask, and others consider it no mask; the result is the same. The related operation .paint-with-alpha() similarly allows transfer of the full source to destination, but it transfers only the provided percentage of the color.


with $context .= new(:$surface) {
  .set-source-rgba( 0.0, 0.0, 0.4, 1);
  .paint-with-alpha(0.4);
}

Mask

The .mask() and .mask-surface() operations allow transfer according to the transparency/opacity of a second source pattern or surface. Where the pattern or surface is opaque, the current source is transferred to the destination. Where the pattern or surface is transparent, nothing is transferred.


my Gnome::Cairo::Pattern $linpat;
with $linpat .= new(:linear( 20, 20, 100, 100)) {
  .add_color_stop_rgb( 0, 0, 0.3, 0.8);
  .add_color_stop_rgb( 1, 0, 0.8, 0.3);
}

my Gnome::Cairo::Pattern $radpat;
with $radpat .= new(:radial( 60, 60, 30, 60, 60, 90)) {
  .add_color_stop_rgba( 0, 0, 0, 0, 1);
  .add_color_stop_rgba( 0.5, 0, 0, 0, 0);
}

with $context .= new(:$surface) {
  .set-source($linpat);
  .mask($radpat);
}

Note: For this to work you also must import the Gnome::Cairo::Pattern module.