Sat, 20 Nov 2004

Rotated text in GTK+ and GDK [17:50]

Many years ago, when Microsoft first added TrueType support to Windows, I remember being very impressed by a demo where a text string was rotated and drawn in different colors. Simple stuff, really, but something that we've never been able to do in GTK+ because of the limitations of the ancient X drawing API. (Crude support was added for rotated glyphs for core X fonts eventually, but who wants rotated, non-antialiased text? Can you imagine anything more ugly?) A lot of the pieces have been falling into place over the last few years: GTK+-2.4 required Xft and no longer support core X fonts. Pango-1.6 added PangoMatrix and implemented rotated rendering for the FT2 backend. Over the summer, I added PangoRenderer to Pango, which abstracts out all the positioning, attribute parsing, and so forth in rendering a PangoLayout, making adding rotated rendering for additional backends a lot easier. What was left was writing a PangoRenderer for GDK and some API to rotate GtkLabel. I finally got around to that this week.

rotated text screenshot     rotated label screenshot

I've always been just a little confused by transformations when drawing; if I want cairo_rectangle (cr, 0, 0, 1, 1) to drawn a 50 by 50 square at 100 by 100, is that?

cairo_translate (cr, 100, 100);
cairo_scale (cr, 50, 50);

Or is it?

cairo_scale (cr, 50, 50);
cairo_translate (cr, 100, 100);

When I added PangoMatrix to Pango, I decided I was fed up by continually having to try everything both ways, sat down, worked through various examples, and really got it straight in my head. And because of that, I got all the transformations used in the rotated text example above right the first time. A new experience. So, what's the secret? What helps me is to keep two distinct but equivalent formulations in mind. (I'm talking in terms of Cairo here, but Cairo, Java2D, gnome-print, Postscript, etc all borrowed Postscript's conventions, so they are the same.) The first formulation defines how the matrices work.

The current transformation matrix gives the transformation from user coordinates (the ones you pass to drawing functions) to device coordinates (the ones that determine what pixels are drawn). Mathematically:

p_device = M * p_user

When you call a Cairo transformation function, that gets added on the user coordinate side. Mathematically:

p_device = M_orig * M_new * p_user

So, say we call cairo_translate (cr, 100, 100) then cairo_scale (cr, 50, 50). We get a transformation that first scales by 50 times in each direction, then translates by 100, 100. That formulation is good to have available, especially if you need to handle the matrices directly (as when implementing bits of Pango or Cairo.) And you can use to check to see which order of the two I quoted is right by taking the point 1,1 and putting it through the two transformations. But it's not entirely intuitive: you have to think about the transformations in the reverse order they appear in the code. For, writing code what I find easier is:

When you call a Cairo transformation function, that scales/ rotates/translates the axes of the user space coordinate system with respect to themselves.

With that formulation, in mind, it's easier to see that the order we need is the first one. cairo_translate (cr, 100, 100) shifts the coordinate origin to 100, 100. cairo_scale (cr, 50, 50); then scales up the coordinate axes by 50 times in each direction, leaving the coordinate origin in the same place. (This all would be clearer with a diagram...)

Now if I only could shake the feeling that by writing this I'm courting a collision with a graphics system that uses the opposite convention...