Many graphics packages allow the user to select where they would like to draw the border of a region around a shape; either along the inside, outside or centre of the shape. For example, this shows the same square with the border drawn along the centre, inside and outside respectively:
I could scale the path up/down based on the stroke's width, but I wanted to check if there was built-in support for this first.
I'm using Ruby, but if there's a C method for this, it's likely available in the Ruby bindings as well.
Is there a method to draw a stroke around the outside or inside of a path, rather than along the centre, in Cairo?
No, there is no such method built-in.
One could likely approximate this with a temporary surface that is later used as a mask. For example, to do "outside", you first fill a temporary surface with "transparent", then stroke with twice your desired line width some "opaque", and finally fill the shape with "transparent" to get rid of the inner part of the line width. The resulting surface can then be used as a mask.
"Inside" would be similar, but with an extra trick: Again, transparent surface and stroke with twice the line width. Now the outside part of this stroke needs to be removed. For this, one needs a path with a winding rule of even-odd. Add a surface-sized rectangle to this path inverts the path, thus allowing to remove everything outside via a fill. For a non-zero winding rule... I do not have any immediate ideas (well, another temporary surface that is then inverted via a full-surface-paint with operator SUBTRACT?).
Sample code for drawing outside of the path (see comments):
static void draw_outside_of_path(cairo_t *cr) {
double line_width = cairo_get_line_width(cr);
cairo_pattern_t *mask;
cairo_push_group_with_content(cr, CAIRO_CONTENT_ALPHA);
cairo_set_line_width(cr, 2 * line_width);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_stroke_preserve(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_fill_preserve(cr);
mask = cairo_pop_group(cr);
cairo_mask(cr, mask);
cairo_pattern_destroy(mask);
}