Displaying text in Pycairo
Martin McBride, 2018-04-14
Tags text font font style text metrics alignment
Categories pycairo

This article describes how to create text in Pycairo. You can find other topics in the main Pycairo article list.
Drawing a simple text string
Here is the code to create a simple text string in our user coordinates (see the previous article). The full code is here:
import cairo WIDTH = 3 HEIGHT = 2 PIXEL_SCALE = 200 surface = cairo.ImageSurface(cairo.FORMAT_RGB24, WIDTH*PIXEL_SCALE, HEIGHT*PIXEL_SCALE) ctx = cairo.Context(surface) ctx.scale(PIXEL_SCALE, PIXEL_SCALE) ctx.rectangle(0, 0, WIDTH, HEIGHT) ctx.set_source_rgb(0.8, 0.8, 1) ctx.fill() # Drawing code ctx.set_source_rgb(1, 0, 0) ctx.set_font_size(0.25) ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(0.5, 0.5) ctx.show_text("Drawing text") # End of drawing code surface.write_to_png('text.png')
Within the drawing code:
set_source_rgb
sets the colour to red.set_font_size
sets the text size to 0.25 units. This gives the approximate text height (the overall page size is 2 by 3 units).select_font_face
selects an Arial font with default slant and weight (see below).move_to
sets the position for the textshow_text
displays the text using the style, colour and position specified.
Here is the result:
The font size is specified in units (depending on the current scaling). If we assume that our units represent inches, the font height would be 0.25 inches. To convert this to points, you multiply it by 72 (a point is 1/72 of an inch), so our font would be 18 pt if we printed the image above at 3 by 2 inches.
Font styles
We will now change the drawing code to try some different font styles:
ctx.set_source_rgb(1, 0, 0) ctx.set_font_size(0.25) ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(0.5, 0.4) ctx.show_text("Arial") ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(0.5, 0.7) ctx.show_text("Arial italic") ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.move_to(0.5, 1.0) ctx.show_text("Arial bold") ctx.select_font_face("Times", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(0.5, 1.3) ctx.show_text("Times") ctx.select_font_face("Courier", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(0.5, 1.6) ctx.show_text("Courier")
We keep the size and colour the same, and display 5 lines of text with different settings:
- Normal Arial text.
- Arial italic, by setting the font face to use
cairo.FONT_SLANT_ITALIC
. - Arial bold, by setting the font face to use
cairo.FONT_WEIGHT_BOLD
. - Times font, by changing the font face.
- Courier font, by changing the font face.
In each case we add 0.3 to the y position in the move_to
call, to move each line below the previous. Here is the result:
Text metrics
It is often useful to know how large the text will be when it is displayed on the page. This is easy to find using the text_extents
function:
xbearing, ybearing, width, height, dx, dy = ctx.text_extents(s)
text_extents
returns a tuple containing 6 values that describe the dimensions of the text. The dimensions are calculated using the selected font and current scaling factor.
In the code above the tuple is unpacked into 6 variables. We are only interested in the width
and height
for now. These specify the width and height of a rectangle than tightly contains all the pixels that will be marked when the text is displayed.
We will look at the other text metrics later in a later article.
Example - text alignment
Let's start by printing 3 lines of text:
xpos = 0.5 ctx.set_source_rgb(0, 0, 0) ctx.set_font_size(0.20) ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(xpos, 0.7) ctx.show_text("Text string") ctx.move_to(xpos, 1.0) ctx.show_text("Longer text string") ctx.move_to(xpos, 1.3) ctx.show_text("Even longer text string")
This will display the text, left aligned (because each move_to
has the same x coordinate, 0.5)
Now we are going to right align the text.
xpos = 2.5 ctx.set_source_rgb(0, 0, 0) ctx.set_font_size(0.20) ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) s = "Text string" xbearing, ybearing, width, height, dx, dy = ctx.text_extents(s) ctx.move_to(xpos - width, 0.7) ctx.show_text(s) s = "Longer text string" xbearing, ybearing, width, height, dx, dy = ctx.text_extents(s) ctx.move_to(xpos - width, 1.0) ctx.show_text(s) s = "Even longer text string" xbearing, ybearing, width, height, dx, dy = ctx.text_extents(s) ctx.move_to(xpos - width, 1.3) ctx.show_text(s)
Notice that we have set xpos
to 2.5. We will align the right edge of each string with that position.
For each of the three strings, we use text_extents
to calculate the metrics, including the width
of the string. We then set the x position to xpos - width
in the move_to
call. This means that each string has a different x position, that has been calculated to that its right hand edge is at x = 2.5
. Here is the result:
If you wanted to centre align the text, you should use xpos - width/2
as the x position (setting xpos
to something suitable, for instance 1.5)
Text paths
You can convert a text string to a path. Once you have the path you can fill it (that will look similar to just displaying text the normal way), or outline it, or both.
Here is how to outline some text:
ctx.set_source_rgb(1, 0, 0) ctx.set_font_size(0.75) ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.move_to(0.3, 1.5) ctx.text_path("Outline") ctx.set_line_width(0.02) ctx.stroke()
All we have done here is call text_path
instead of show_text
. This adds the text letter shapes to the path. We then set the line width and stroke the path. We have made the text quite large so you can see the effect.
You can combine the text path with other shapes:
ctx.set_source_rgb(1, 0, 0) ctx.set_font_size(0.75) ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.rectangle(0.2, 0.8, 2.6, 1) ctx.move_to(0.3, 1.5) ctx.text_path("Outline") ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) ctx.fill()
In this case, we have create a rectangle, with text inside it. The rectangle and text form part of the same path. When we fill it with the even-odd fill rule, we get text shaped holes in the rectangle!