Make documentation precise
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)
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.
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]
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 :)
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.
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
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 :)
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).
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
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:].
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!