New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement anchor for truetype text functions #4724
Conversation
see discussion in issue 4789
…l eventually be removed from stdlib
4642275
to
fcfc1e3
Compare
Can I change something here to make this easier to review? I'm hoping this could be merged in time for 8.0.0. See also #3346 (comment). |
Thanks for the PR, it's a big one! That also makes it harder to review. Would it be possible to split it into smaller PRs? |
I think I can split this into three incremental steps (PR2 depends on PR1 changes etc.):
However, I don't think I can write tests for (1) specifically. Would it be OK to merge that under the assumption that the next PR in the sequence adds tests? For example, the test for #4553 makes heavy use of the anchor parameter, see here. I can also add this test to (1) with the anchor test cases commented out, but I think this will only partially test the (1) changes. |
Yeah, that'll probably be okay. At least for refactoring, you don't always need new tests because generally the idea is not to change what is done, but how it's done. Thank you! |
I have now rebased the first part in PR #4910. While writing the changes comment, I realized I had misread the FreeType documentation for |
self.font.getlength(text, mode == "1", direction, features, language) / 64 | ||
) | ||
|
||
def getbbox( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be pedantic to say this should be called getcbox
, since this uses FreeType's FT_Glyph_Get_CBox
, not FT_Outline_Get_BBox
, if some future version of Pillow would ever add a separate function for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to return the bounding box of the drawn text, i.e. the rectangle of pixels that may be modified by draw.text
, not just the union of glyph CBox
es. In fact, this function doesn't even return the tightest possible bound on the glyph CBox
es, since it also includes the pen line (i.e. the origin and glyph advance points).
I think the reason for calling it getbbox
is that this is what it is most often called in Pillow code (see https://pillow.readthedocs.io/en/latest/search.html?q=bbox). Another possible name would be getbounds
to avoid confusion with the similarly named concept in FreeType? However, this doesn't seem to be used anywhere else in code yet (see https://pillow.readthedocs.io/en/latest/search.html?q=bound).
Part two, implementing the anchor parameter, is now rebased in #4930. I think part three can actually be split into two PRs for the two functions, once part two is merged. |
Part three, the finale, is now rebased in #4959, also adding |
All parts of this PR have now been merged, closing. |
I am splitting this PR into multiple parts. I have marked this as draft until all parts are finished and will close this afterwards.
getlength
andgetbbox
functions for TrueType fonts (Add getlength and getbbox functions for TrueType fonts #4959)The following is the original text of this PR:
For ImageFont module: getsize is slow? #4651, Font spacing changes #3977, font.getsize() changes after installing libraqm #4483 (comment), Obtain Character-Level Bounding Boxes of Generated Text #3921, likely others.
Add new function
getlength
andgetbbox
which compute the advance length and rendered bounding box of text respectively.I did not update
getsize
(andgetoffset
) and the relatedImageDraw
functions, as I don't think this is possible while maintaining backwards compatibility. The issue is that whilegetoffset
is correct,getsize
returns the width and vertical distance to the last drawn pixel (i.e. the bottom parameter of the bounding box). The horizontal is sometimes equal to the advance length (now ingetlength
), but breaks with e.g. slanted fonts or odd diacritics (seetest_combine
for double breve). This is because the bbox is always extended at least up to the advance length. The returned vertical distance is from the ascender line to the bottom-most pixel, which is mostly useless without looking atgetoffset
as well, which is confusing.getbbox
is suggested by @wiredfool in Fix return value of FreeTypeFont.textsize() does not include font offsets #784 (comment),getlength
is inspired by my thoughts in Font spacing changes #3977 (comment) where I also described the problems mentioned above.I will gladly improve the API if someone can give a good suggestion.
TODOI have added a note summarising the paragraph above toImageDraw.Draw.textsize
docs should at least point to the new functions, or they should also be added toImageDraw
.Draw.textsize
.getbbox
. I would like to get feedback for the above before adding a multiline version.Code (click to expand)
For RTL Direction not having any effect #4511 (comment), Vertical text alignment ignores yOffset #4553, Absent pixels when drawing font #4569, Incorrect vertical alignment of character when using fontmode = '1' #2293, many others.
Implement
anchor
parameter for text rendering and fix related issues.My suggestion for the anchor API is already documented: https://pillow--4724.org.readthedocs.build/en/4724/handbook/text-anchors.html
From Absent pixels when drawing font #4569 (comment):
There were many places where the original text code was merely rounding glyph positions, often incorrectly. My solution is to instead let FreeType perform rounding using its hinting engine with
FT_Set_Transform
and asking for hinting inFT_GetGlyphCBox
. The limitation is that FreeType converts the value to 16.16 precision internally, thus limiting max line length to 32768 pixels. I don't think this should be an issue (outside of updating the leak tests), but it is possible to extend this range by splitting the text into multiple runs in Pillow code.Additionally, for Vertical text alignment ignores yOffset #4553 and related issues, all glyphs are now checked when calculating offsets for the baseline. See
test_imagefontctl.py:test_combine
for other issues of the same class.These changes required updating most test images for
test_imagefontctl.py
. I believe most of the changes improve kerning or fix Vertical text alignment ignores yOffset #4553 issues. Intest_direction_ttb
, the bottom of the text was even clipped!This was incompatible with the anchor implementation and I believe it to be a bug; I don't think users should need to offset text just to stroke a single word in the middle of a line.
getlength
, such that users can expect its sum to be consistent (see Font spacing changes #3977):-kern
as the only feature supported by basic layout.(The problem is that kerning is scaled twice, see below.)
Pillow/src/_imagingft.c
Lines 578 to 585 in c9745f4