Skip to content

Document and clarify _imagingft - #9908

Open
akx wants to merge 10 commits into
python-pillow:mainfrom
akx:ft-clarify
Open

akx wants to merge 10 commits into
python-pillow:mainfrom
akx:ft-clarify

Conversation

@akx

@akx akx commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Refs #9898 - had to figure out what's happening in _imagingft to get to the bottom of the kerning woes, and these came out of that. The actual bugfixes will be in another PR stacked on this.

Basically:

  • there was some type confusion with FreeType's 26.6 fixed-point values; some comments noted values were in 26.6 when they are in pixels, or the other way around. This uses FT's FT_F26Dot6 type (an alias to long, since C doesn't have real newtypes, more's the shame) to make it more obvious to the reader.
  • adds macros to work with said data type, to avoid unclear * 64s or << 6s. Not truly type-safe since, well, see above about newtypes, but better than throwing magic numbers around.
  • adds doc comments for the internal functions, because the calling conventions weren't clear
  • changes the core getfont()'s signature to make all arguments required (C interface is internal #9879 in mind, that should be fine to do), since calling getfont() without encoding could handily segfault (since encoding in C was left uninitialized, set to stack garbage).
  • fixes _imagingft.pyi's types to match the reality of the module.

@akx
akx force-pushed the ft-clarify branch 3 times, most recently from 23b2e18 to aabb26a Compare August 26, 2026 19:25
@akx akx mentioned this pull request Aug 26, 2026
@radarhere

Copy link
Copy Markdown
Member

adds tests that prove that the typings were wrong

Looks to me like test_bytearray_not_supported() passes without the rest of your changes.

@akx

akx commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Er, yes..? The test passes (even on main) because font.getlength, font.getbbox, font.getmask don't actually accept bytearrays. If you remove the pytest.raises, it will fail.

On main, trying to pass in a bytearray fails:

$ uv pip install -e .
$ uv run python
Python 3.14.6 (main, Jun 11 2026, 03:55:33) [Clang 22.1.3 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from PIL import ImageFont
>>> f = ImageFont.load_default()
>>> f.font.getsize(bytearray(b"f"))
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    f.font.getsize(bytearray(b"f"))
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
TypeError: expected string or bytes
>>>

even though the typings say they should be accepted:

~/b/Pillow (main) $ grep -C2 getsize src/PIL/_imagingft.pyi
    def getsize(
        self,
        string: str | bytes | bytearray,

Comment thread src/_imagingft.c Outdated
Comment thread src/_imagingft.c Outdated
@radarhere

Copy link
Copy Markdown
Member

Oh, right, most PR tests fail without the other changes, but that was not your intention here.

@akx
akx force-pushed the ft-clarify branch 2 times, most recently from 030e19a to 6428532 Compare August 27, 2026 13:28
@akx
akx requested a review from radarhere August 27, 2026 14:05
Comment thread src/_imagingft.c Outdated
@akx
akx force-pushed the ft-clarify branch 2 times, most recently from e918a3a to 5bcbe16 Compare September 7, 2026 10:00
Comment thread Tests/test_imagefont.py Outdated
@akx
akx force-pushed the ft-clarify branch 3 times, most recently from 9e51b32 to a403f4c Compare September 7, 2026 14:04
Comment thread Tests/test_imagefont.py Outdated
Comment thread src/PIL/_imagingft.pyi Outdated
Comment thread src/PIL/_imagingft.pyi Outdated
Comment thread src/PIL/_imagingft.pyi Outdated
@radarhere

Copy link
Copy Markdown
Member
  • changes the core getfont()'s signature to make all arguments required (C interface is internal #9879 in mind, that should be fine to do), since calling getfont() without encoding could handily segfault (since encoding in C was left uninitialized, set to stack garbage).

You've initialized encoding to NULL in this PR. I would think that should be enough. The only time in main it is used is

Pillow/src/_imagingft.c

Lines 227 to 231 in 0253ef0

if (!error && encoding && strlen((char *)encoding) == 4) {
FT_Encoding encoding_tag =
FT_MAKE_TAG(encoding[0], encoding[1], encoding[2], encoding[3]);
error = FT_Select_Charmap(self->face, encoding_tag);
}

But you are ensuring that it is always populated from Python bcause you are concerned about someone writing future code that isn't aware that it might be NULL? I don't think I share that concern.

@akx

akx commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

You've initialized encoding to NULL in this PR. I would think that should be enough.

I can re-add the | into the parse string if you like so those args aren't necessary to pass, but given we've established these are internal (or, in python-cryptography terms, "hazardous materials") APIs, if Pillow ever wants to add more args here, a single calling convention will break hazmat-calling external users break more obviously. If... if that makes sense?

@radarhere

Copy link
Copy Markdown
Member

a single calling convention will break hazmat-calling external users break more obviously

I don't think we need to take action to help such users. But I also have a feeling that there are a bunch of arguments in our C API that are optional that don't need to be (since we always provide values for them in our Python code). See what you think of akx#30

akx and others added 10 commits September 15, 2026 09:20
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
Avoids a segfault when `encoding` and `font_bytes` weren't passed at all (they were left uninitialized)
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
* Make font bytes optional
* Remove keyword support from core.getfont

Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants