Python informer

Improve your Python coding skills

Displaying text in 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 text
  • show_text displays the text using the style, colour and position specified.

Here is the result:

line

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 172 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:

  1. Normal Arial text.
  2. Arial italic, by setting the font face to use cairo.FONT_SLANT_ITALIC.
  3. Arial bold, by setting the font face to use cairo.FONT_WEIGHT_BOLD.
  4. Times font, by changing the font face.
  5. 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:

line

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)

line

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:

line

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.

line

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!

line