How to set appropriate line width for drawing text in Python PIL?

python, python-3.x, python-imaging-library

Solution

PIL's `textwrap.wrap()`'s takes a `width` which specifies the maximum number of characters in a line. In my opinion this is poor library design, as it's more useful to specify a maximum number of pixels, or inches. This matters because you probably have a bounding box in pixels, and variable-width fonts mean that a character count is somewhat useless.

One option is to use a fixed-width font. Then the character count is a simple division.

Another is to search for the maximum `width` that does not overflow your box. I'd set this up like a binary search, `start=1`, `end=len(string)`. `pivot=(end+start)/2`. Do the wrapping with `width=pivot`, then find the `max(font.getsize(line) for line in wrapping)`.

- If that `max` is bigger than the bounding box, recurse left. (`end=pivot`)

- Else, repeat for `width=pivot+1`. If that overflows, you found your maximum `width`.

- If it doesn't, recurse right. (`start=pivot`)

This isn't optimal in general because individual lines might need different wrapping widths (and hence why I think this API is terrible), but if you're doing paragraphs then it should be pretty decent.

Problem

I am trying to draw text using PIL into an image with arbitrary resolution. My current code is a result of consulting the following two questions here and here. In both of these answers, the width value for `textwrap.wrap` was set as such: `width=40`. However, with arbitrarily changing parameters `size_x` and `size_y` this results in overfitting or underfitting the scope of image. Ideally, I would need a way to convert the size of the font into height and width pixel values in PIL but I am unsure how to do it. Here is the code as I have it now: ``` from PIL import Image from PIL import ImageFont from PIL import ImageDraw import textwrap size_x = 946 #This value can arbitrarily change size_y = 300 #This value can arbitrarily change font_size = 16 #This value can be adjusted to fit parameters of image if necessary my_text = ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam scelerisque sapien convallis nisl facilisis, sed facilisis odio accumsan. Maecenas vel leo eu turpis porta dictum at vel neque. Donec sagittis felis non tellus lacinia facilisis. Vivamus vel nisi ullamcorper, feugiat lorem sagittis, pellentesque dolor. Curabitur est magna, feugiat ut nibh quis, blandit vestibulum nisl. Sed pulvinar condimentum purus et rutrum. Proin magna arcu, scelerisque at gravida ut, convallis quis orci. Mauris ipsum tortor, laoreet et leo ac, lacinia euismod tellus. Curabitur volutpat nisi a metus faucibus, vel iaculis nisl fermentum. Curabitur et orci id sapien porttitor dignissim at ac dolor. Donec nec mattis nisi. '] tx = Image.new('RGB', (size_x, size_y),color=(255,255,255)) draw = ImageDraw.Draw(tx) my_font = ImageFont.truetype('/Windows/Fonts/arial.ttf',size=font_size) lines = textwrap.wrap(my_text[0], width = 130) #This width value needs to be set automatically y_text = 0 for line in lines: width, height = my_font.getsize(line) draw.text((0, y_text), line, font = my_font, fill = (0,0,0)) y_text += height tx.show() ``` Example image with `width=130` which is filling fairly well. Example image with `width=200` which is overfilling.

Original source

Related problems