Creating an Image with Wrapped Text using the Python Imaging Library (Shallow Thoughts)

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Sat, 04 May 2024

Creating an Image with Wrapped Text using the Python Imaging Library

I stumbled onto the page for this year's Asimov's Magazine Readers' Award Finalists. They offer all the stories right there -- but only as PDF. I prefer reading fiction on my ebook reader (a Kobo Clara with 6" screen), away from the computer. I spend too much time sitting at the computer as it is. But trying to read a PDF on a 6" screen is just painful.

The open-source ebook program Calibre has a command-line program called ebook-convert that can convert some PDF to epub. It did an okay job in this case — except that the PDFs had the wrong author name (they all have the same author, so I'm guessing it's the name of the person who prepared the PDFs for Asimov's), and the wrong title information (or maybe just no title), and ebook-convert compounded that error by generating cover images for each work that had the wrong title and author.

I went through the files and fixed each one's title and author metadata using my epubtag.py Python script. But what about the cover images? I wasn't eager to spend the time GIMPing up a cover image by hand for each of the stories.

Bad covers are a pretty common problem with ebooks, especially ebooks downloaded for free from sites like Project Gutenberg (commercial books you buy or check out from the library usually have a good cover image). So I've long wanted a script that could generate a cover image with the proper author and title. I expected that was going to be a simple task, half an hour or so. I know you can generate images with text on them using ImageMagick, and I was pretty sure PIL, the Python Imaging Library, could too. And there must be examples all over the web for how to do this, right?

Well, it's true that there are examples all over the web. It turns out most of them don't work. First, there were font problems: both PIL and ImageMagick encourage specifying a path to a .ttf file rather than the name of a system font, so it's difficult to write platform-independent code. (I later discovered by experimentation that PIL can accept some font file names without requiring the full path, e.g. 'FreeSerifBold.ttf' instead of 'usr/share/fonts/truetype/freefont/FreeSerifBold.ttf', though I've found no documentation on which names work and which don't.)

Worse, neither PIL nor ImageMagick offers a way to wrap long lines. Since I want to use a large font on the cover, many titles will need to wrap.

I didn't want to reinvent the wheel if I didn't have to, and I was sure there must be hundreds of examples of code to wrap long text lines in PIL. For instance, stackoverflow thread gives lots of ways.

But none of the examples actually worked. To get the width and height of the rendered text, most examples used getsize, which doesn't actually exist in the PIL I have (really an extension of PIL called Pillow).

After spending four hours (divided between PIL and ImageMagick, which had similar problems) on this problem I'd thought would be easy, I was about ready to give up. I went for a bike ride instead, and that helped. When I got back, energized, I tried searching for pil getsize replacement and learned that getsize has been replaced with getbbox, which returns a 4-tuple, left, top, right, bottom.

Once I knew about getbbox, everything fell into place. I was able to write a workable cover script fairly quickly, just wrapping by number of characters using the textwrap package, based on assumptions about the font and image sizes I'm using. (I still needed getbbox to get the line height.)

The following day, I had some spare time and decided I hated leaving the job half-done, so I wrote a function that wraps properly, adjusting for the font and spacing of the characters used. It isn't perfect and I'm sure it has some bugs, but at least it's a working example in case anyone else needs this functionality: text_wrapped_image.py.

And here's the book cover function in my epubtag script: epubtag.py). For now it still uses the simpler version of draw_text_wrapped; I'll replace it eventually.

By the way, my favorite so far of the Asimov's shorts (though I've only read a few of them) is the short story "Hope is the Thing With Feathers" by Karawynn Long.

Tags: , ,
[ 13:52 May 04, 2024    More programming | permalink to this entry | ]

Comments via Disqus:

blog comments powered by Disqus