Pillow icon indicating copy to clipboard operation
Pillow copied to clipboard

Make documentation precise

Open voidplayer opened this issue 3 years ago • 10 comments

tl;dr: please use the type you return in documentation. A migration example for deprecated methods wouldn't hurt either

DeprecationWarning: textsize is deprecated and will be removed in Pillow 10 (2023-07-01). Use textbbox or textlength instead.

I'm getting a deprecation warning for ImageDraw.textsize. Fair enough. I haven't touched this in 7 years but I'll fix it

I'll check the documentation

textsize
...
Return the size of the given string, in pixels.

But i'm using [0]... (Several prints later) oh, it returns (length_x, length_y)

textlength
...
Returns length

It says it returns length, but it sure will return length on both axis like the old one. (tries, it only returns length_x)

Okay, i guess i need textbbox instead

textbbox (xy, text....)
...
Returns bounding box (in pixels)
xy – The anchor coordinates of the text.

Okay, why the hell does it need the coordinates of the text now. How is it relevant? Fine, i don't care, i'll redo the code.. (several tries later) Okay, what is a bounding box. Check code. It returns 4 things now?

(Cries a lot...)

(Sobs) I only wanted the (length_x, length_y) provided by the old method so i can continue with my life and forgets i have to touch this old code of 7 years

PS: Sorry for the overdramatization. It's a pet peeve of mine with python modules. Not all of them, but they tend to be this imprecise on the documentation compared with other languages and i don't understand why. In python defense, it could be worse (Looks at perl modules documentation)

voidplayer avatar Sep 01 '22 11:09 voidplayer

I've created PR #6556 to try and help. You can see a preview of the documentation at https://pillow--6556.org.readthedocs.build/en/6556/reference/ImageDraw.html, where I've added a 'RETURNS:' section at the end of a few more methods.

radarhere avatar Sep 01 '22 12:09 radarhere

Thats an improvement indeed. I wish everything had return types on them

I would also use more precise name convention. Or maybe a link to where is described

for example:

  • xy – The anchor coordinates of the text: tuple (x,y) of the anchor point See Text anchor
  • Anchor: top left pixel
  • length: int with length in x (important because i've seen that other functions return float values) in pixels without padding
  • quick example migration:
#old
size = textsize("Hello World")
length_x = size[0]
length_y = size[1]

#new textlength
length_x = textlength("Hello World")

#new boundingbox
boundingbox = textbbox ((0,0), "Hello World")
length_x = boundingbox[2] - boundingbox[0]
length_y = boundingbox[3] - boundingbox[1]

voidplayer avatar Sep 01 '22 12:09 voidplayer

In defense for the quick dirty migration example:

I tried for a while to use the align parameter and change my code to use the new way the bbox is supposed to be used

But after half an hour, i gave up. Reseted everything i had done and just use my own quick dirty migration. In 1 minute i had everything working again and images generated were pixel perfect identical to the old :)

voidplayer avatar Sep 01 '22 13:09 voidplayer

The ImageDraw.textXXX functions use FreeTypeFont.getXXX functions internally, and those do have documented return values (e.g. FreeTypeFont.getlength). It seems I simply forgot to copy those to the ImageDraw functions when adding them.

Okay, why the hell does it need the coordinates of the text now. How is it relevant? Fine, i don't care, i'll redo the code..

The FreeTypeFont.getbbox function does not need the coordinates.

The problem with the old functions is that different users have a different interpretation of what (length_x, length_y) means, that's why it's now split into two functions, and the bbox is described using four values rather than two. The getlength/textlength is the length that should be used for layout (e.g. placing cursor, word-wrapping, ...), while getbbox/textbbox is used for the placing a border around the text (e.g. background rectangle, crop image to text area only, ...). It is sometimes the case that font.getlength(...) != font.getbbox(...)[2] - font.getbbox(...)[0]. Note that neither of these is suitable for centering text (which is the most common use case I see on StackOverflow), you should use anchors for that.

nulano avatar Sep 01 '22 14:09 nulano

For an exact (but perhaps overly specific and thus not helpful) explanation, the getlength function returns the advance length as seen in the diagrams in the FreeType tutorial, while the getbbox function returns the union of that line and the glyph bounding box from the same diagram.

Here are a few examples that should hold for all fonts:

# old functions
offset_x, offset_y = font.getoffset(text)
size_x, size_y = font.getsize(text)

# new functions
length = font.getlength(text)
left, top, right, bottom = font.getbbox(text)  # these are relative to the point specified by the anchor kwarg, top-left by default

assert left <= 0
assert top <= 0
assert right >= 0
assert bottom >= 0
assert length <= (right - left)  # if horizontal text direction
assert length <= (bottom - top)  # if vertical text direction
assert offset_x == left
assert offset_y == top
assert size_x == right - left  # confusing if left != 0
assert size_y == bottom        # confusing if top != 0

These only hold for simple fonts:

assert left == 0  # if simple font, not true for e.g. italics
assert top == 0  # if simple font, not true for e.g. text containing letters with an accent
assert right == int(length)  # if simple font and horizontal text direction
assert bottom == int(length)  # if simple font and vertical text direction

nulano avatar Sep 01 '22 14:09 nulano

The FreeTypeFont.getbbox function does not need the coordinates.

Then FreeTypeFont.getbox should get recommended along, instead of ImageDraw.textbbox that need the coordinates ;)

The getlength/textlength is the length that should be used for layout (e.g. placing cursor, word-wrapping, ...), while getbbox/textbbox is used for the placing a border around the text (e.g. background rectangle, crop image to text area only, ...).

These feels something the docs could use. A reason next to the deprecation note would also be nice :)

It is sometimes the case that font.getlength(...) != font.getbbox(...)[2] - font.getbbox(...)[0]. Note that neither of these is suitable for centering text (which is the most common use case I see on StackOverflow), you should use anchors for that.

Here are a few examples that should hold for all fonts:

Please, notice that this is only an example of the migration of the old function. Not an explanation of how the new function works. How do i reliably get the old info, so i can plug it in and i dont need to rethink old working code

If theres an specific use case that is not always true, it can be added as well. Please notice that this example is not true when... whatever

At the very least it helps understand the most basic usage of the old vs the new

Your example could make it to the docs too. It gives a top view of the function, which is also great :)

Btw, im not questioning the choices, im just trying to make docs more clear :)

voidplayer avatar Sep 01 '22 16:09 voidplayer

I'm not entirely sure I understood your comment, but to replace the old function, you could use

left, top, right, bottom = font.getbbox(text)
size = (right - left, bottom)      # this matches the old function, but it is sometimes not the actual text size
assert size == font.getsize(text)  # always True

However, if your old code is not also using font.getoffset, it is very likely it currently contains a bug, and will not work correctly with e.g. italic fonts or non-English text. That is the reason for the deprecation.

This is because the actual text size (including pixels above and to the left of the xy coordinate) is (right - left, bottom - top). If instead you wanted the bottom right coordinate, you'd use (right, bottom).

nulano avatar Sep 01 '22 16:09 nulano

left, top, right, bottom = font.getbbox(text)
size = (right - left, bottom)
assert size == font.getsize(text)  # always True

This is a good example code indeed

However, if your old code is not also using font.getoffset, it is very likely it currently contains a bug, and will not work correctly with e.g. italic fonts or non-English text. That is the reason for the deprecation.

In my case i dont use getoffset, but i am using default font, english text, without italics. Im using the size, to align a few numbers right, so 1-3 digits max, so very unlikely that i personally hit a bug :)

But all these explanations are great!. It would be nice if they made it to the docs. Including the reasoning for the deprecation. So people can tell can tell if an easy fix like mine is going to be enough, or you have to redo the code with all those considerations

voidplayer avatar Sep 01 '22 16:09 voidplayer

Im using the size, to align a few numbers right, so 1-3 digits max

It sounds like draw.text(..., anchor='ra') should be able to replace your calculation, however, that is not supported with the default font from ImageFont.load_default. But, if you are using that font, then both of left and top are 0, so you can simplify the above to size = (right, bottom), or just font.getbbox(text)[2:].

nulano avatar Sep 01 '22 16:09 nulano

Yeah, i see that now

I have a few other things that use those values tho, for drawing lines and other things

In the end, it was easier for me to just get the old values and put them inplace than to understand and redo everything

Thanks for everything!

I just wanted show the difficulties i had faced trying to fix the deprecation note

Maybe it will help the next person that comes along and improves the documentation!

voidplayer avatar Sep 01 '22 17:09 voidplayer