2009/07/25

Pango font rendering on an OpenGL canvas

Nowadays I’m working on the interface of the profile history manager, and I ran into the problem mentioned in the title. The problem is actually that the Gtk2Hs interface seems to have more degrees of freedom than the implementation, and even if it seemingly lets us combine various mechanisms in creative ways, most of these combinations lead to a segfault. In particular, using any incarnation of drawLayout in an OpenGL drawing area suffers from this problem.

After some sweat and tears and frantic search for solutions I decided to use Cairo for rendering. This also has the advantage that the result of the rendering can be cached in memory, so the display can be refreshed very efficiently if the text is static. All we have to do is prepare an image for later rendering.

First we need a Cairo context. As part of my goal was to preferably use the same font as the rest of the interface, I also set it to a sans serif typeface:

cctx <- cairoCreateContext Nothing
fontDesc <- contextGetFontDescription cctx
fontDescriptionSetFamily fontDesc "Sans"
contextSetFontDescription cctx fontDesc


The next step towards consistency is to set the resolution to match that of the screen. This is very important to crazy people like me, who had to change their DPI setting.

screenGetDefault >>= \s -> case s of
Nothing -> return ()
Just scr -> do
res <- Gtk.get scr screenResolution
cairoContextSetResolution cctx res


Okay, the context is sufficiently prepared for our purposes, we can create the layout:

txt <- layoutText cctx "Hello world!"


Next, we need a pixel buffer that can hold the final render. We can easily find out its dimensions by asking for the pixel extents of the layout, and it’s probably better to use the logical extents that also include some padding:

Rectangle _ _ txtWidth txtHeight <- snd <$> layoutGetPixelExtents txt
textSurface <- createImageSurface FormatARGB32 txtWidth txtHeight
textSurfacePixbuf <- pixbufFromImageSurface textSurface


And everything’s ready for rendering, so let’s do it:

renderWith textSurface $ showLayout txt


From this moment the rendering of the text is available in the pixbuf. Rendering it is a piece of cake:

withGLDrawingArea glCanvas $ \glw -> do
-- various opengl commands
gc <- gcNew glw
drawPixbuf glw gc textSurfacePixbuf 0 0 textX textY txtWidth txtHeight RgbDitherNone 0 0
-- more opengl commands
glDrawableSwapBuffers glw


When we know that we won’t need the image any more, we can free up the surface:

surfaceFinish textSurface


For more consistency we could look up the actual rendering settings (antialiasing, hinting) and fonts of the current theme and set everything before rendering. That’s left as an exercise to the reader. I’m too tired. ;)

No comments:

Post a Comment