Skip to content
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

Handle pathlib.Path in FreeTypeFont #7578

Merged
merged 8 commits into from Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions Tests/test_imagefont.py
Expand Up @@ -4,6 +4,7 @@
import shutil
import sys
from io import BytesIO
from pathlib import Path

import pytest
from packaging.version import parse as parse_version
Expand Down Expand Up @@ -76,8 +77,9 @@ def _render(font, layout_engine):
return img


def test_font_with_name(layout_engine):
_render(FONT_PATH, layout_engine)
@pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH)))
def test_font_with_name(layout_engine, font):
_render(font, layout_engine)


def test_font_with_filelike(layout_engine):
Expand Down
14 changes: 13 additions & 1 deletion src/PIL/ImageFont.py
Expand Up @@ -25,12 +25,15 @@
# See the README file for information on usage and redistribution.
#

from __future__ import annotations

import base64
import os
import sys
import warnings
from enum import IntEnum
from io import BytesIO
from pathlib import Path

from . import Image
from ._util import is_directory, is_path
Expand Down Expand Up @@ -185,7 +188,14 @@ def getlength(self, text, *args, **kwargs):
class FreeTypeFont:
"""FreeType font wrapper (requires _imagingft service)"""

def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
def __init__(
self,
font: bytes | str | Path | None = None,
size: float = 10,
index: int = 0,
encoding: str = "",
layout_engine: int = None,
radarhere marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
# FIXME: use service provider instead

self.path = font
Expand Down Expand Up @@ -213,6 +223,8 @@ def load_from_bytes(f):
)

if is_path(font):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at _util.is_path, it checks isinstance(f, (bytes, str, Path)), so bytes are also handled as paths, but next I see: else: load_from_bytes(font).

Do we really want consider bytes as paths? I believe this is obsolete in Python3

Copy link
Member Author

@radarhere radarhere Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#258 was the original request for bytes as paths.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume load_from_bytes is unreachable here, so we need to remove it, or provide a way to reach it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's reachable. The logic hasn't changed since load_from_bytes() was added in #3785.

If it is a path, then pass the path to the C layer. If we're on Windows and the path is not ASCII, then open a file pointer to the path and read the contents of the file pointer with load_from_bytes().
If it is not a path, then it is a file pointer, read the contents of the file pointer with load_from_bytes().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see. I believe there is misnaming here, since f is not a bytes, it's file object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I remember figuring that out after being confused in the past.
Should typing.BinaryIO be added to the type annotation then?

Copy link
Member Author

@radarhere radarhere Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I've pushed a commit. Testing in PyCharm, I find it also includes io.BytesIO

Copy link
Contributor

@nulano nulano Dec 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@radarhere I see you've used typing.IO. This causes a mypy error, see #7642.

Testing with both PyCharm and mypy, I find that a io.BytesIO value is accepted for a typing.BinaryIO type hint (which seems correct here), so I'm not sure I understand your last message.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was concerned about strings being used, but looking now, that doesn't appear to be a problem.

if isinstance(font, Path):
font = str(font)
hugovk marked this conversation as resolved.
Show resolved Hide resolved
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
Expand Down