Wrapping Text With ImageMagick

I’ve already explained that I wanted to create an image slide for a slideshow from some information in a spreadsheet. I decided to use ImageMagick and, in particular, the Wand library for python to help me.

As with all theatre projects this one is collaborative. The original concept for this was James’ and he’d created some examples by hand*. Each slide has some fixed elements and then information specific to the event. Using those he’s created some template images that contain the fixed elements, there are a number of templates depending on the type of event.

Basic SlideEach one has basically the same layout and the same (wavy) line which delineates where I can draw the event elements onto the slide. Then I add the date and time and the title of the event and who is presenting it. All of that is straightforward with either a Caption, or a Text element.

Image AreaThe more challenging part is the lower half of the slide. The spreadsheet contains a link to an event specific image in Google Drive, so I download the image data directly. I want the image to fit into, at most, roughly half of the remaining area, so I resize it keeping the aspect ratio but capping the height and width.

with Image(blob=image_data) as img:
    size = "{}x{}".format(max_width, max_height)
    img.transform(resize=size)
    img_height = img.height # save height
    img_width = img.width # save width
    main_img.composite(img, left=left, top=top)

Slide With Image

Now I want to display the description wrapped around the slide. ImageMagick will easily draw all sorts of amazing text effects (on one line) but text wrapping is more of a challenge. You can use a Caption to wrap text inside a rectangle, but my rectangle now has a corner taken out of it. Also I was asked to truncate the text at a sensible point (e.g. the end of a sentence) if it was too big to fit.

This is the point for some funky arithmetic. Firstly create a Drawing and set the Font you want to use on it. Now text is either going to be next to the picture (a narrow line) or underneath it (a wide one), so using the font size and the height available, and of course the actual image height and width, calculate how many lines you can fit of each width. Now we just need to split the text into that many lines each of the right length.

The simplest way to split the text is to use the python textwrap module but that requires you to specify a fixed maximum number of characters and my font isn’t monospaced. Instead I use the Draw module’s get_font_metrics to do the same job. It requires a dummy image for some reason.

words = para.split(" ")
lines = []
with Image(height=10, width=10) as img:
  line = words.pop(0)
  for word in words:
    line_width = draw.get_font_metrics(img, 
      line + " " + word).text_width
    if line_width < max_width:
      line = line + " " + word
    else:
      lines.append(line)
      line = word
  if line != "":
    lines.append(line)
return lines

Using this I can build up a list of lines of text. Firstly the narrow ones and then the wide ones, discarding any text that won’t fit. Then I can draw them onto the image line by line. Giving me a finished slide.Complete Slide

 

*James is also responsible for the spreadsheet that holds all this data. You should ask him about it, it’s awesomely clever.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s