2023-12-21 14:13:31 +03:00
|
|
|
|
from __future__ import annotations
|
2024-01-20 14:23:03 +03:00
|
|
|
|
|
2024-01-01 08:03:43 +03:00
|
|
|
|
import struct
|
|
|
|
|
from io import BytesIO
|
2023-08-26 10:00:34 +03:00
|
|
|
|
|
2024-01-20 14:23:03 +03:00
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from PIL import Image, ImageDraw, ImageFont, _util, features
|
2023-08-26 10:00:34 +03:00
|
|
|
|
|
2023-08-26 10:01:01 +03:00
|
|
|
|
from .helper import assert_image_equal_tofile
|
|
|
|
|
|
2024-05-27 10:08:13 +03:00
|
|
|
|
fonts = [ImageFont.load_default_imagefont()]
|
|
|
|
|
if not features.check_module("freetype2"):
|
|
|
|
|
default_font = ImageFont.load_default()
|
|
|
|
|
if isinstance(default_font, ImageFont.ImageFont):
|
|
|
|
|
fonts.append(default_font)
|
2023-08-26 10:00:34 +03:00
|
|
|
|
|
|
|
|
|
|
2024-05-27 10:08:13 +03:00
|
|
|
|
@pytest.mark.parametrize("font", fonts)
|
|
|
|
|
def test_default_font(font: ImageFont.ImageFont) -> None:
|
2023-08-26 10:00:34 +03:00
|
|
|
|
# Arrange
|
|
|
|
|
txt = 'This is a "better than nothing" default font.'
|
|
|
|
|
im = Image.new(mode="RGB", size=(300, 100))
|
|
|
|
|
draw = ImageDraw.Draw(im)
|
|
|
|
|
|
|
|
|
|
# Act
|
2024-05-27 10:08:13 +03:00
|
|
|
|
draw.text((10, 10), txt, font=font)
|
2023-08-26 10:00:34 +03:00
|
|
|
|
|
|
|
|
|
# Assert
|
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
|
|
|
|
|
|
|
|
|
|
2024-05-27 10:08:13 +03:00
|
|
|
|
def test_without_freetype() -> None:
|
|
|
|
|
original_core = ImageFont.core
|
|
|
|
|
if features.check_module("freetype2"):
|
2024-06-24 01:04:53 +03:00
|
|
|
|
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
|
2024-05-27 10:08:13 +03:00
|
|
|
|
try:
|
2024-06-24 01:04:53 +03:00
|
|
|
|
with pytest.raises(ImportError):
|
|
|
|
|
ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
|
|
|
|
|
2024-05-27 10:08:13 +03:00
|
|
|
|
assert isinstance(ImageFont.load_default(), ImageFont.ImageFont)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ImportError):
|
|
|
|
|
ImageFont.load_default(size=14)
|
|
|
|
|
finally:
|
|
|
|
|
ImageFont.core = original_core
|
2023-08-26 10:01:01 +03:00
|
|
|
|
|
|
|
|
|
|
2024-05-27 10:08:13 +03:00
|
|
|
|
@pytest.mark.parametrize("font", fonts)
|
|
|
|
|
def test_unicode(font: ImageFont.ImageFont) -> None:
|
2023-08-26 10:00:34 +03:00
|
|
|
|
# should not segfault, should return UnicodeDecodeError
|
|
|
|
|
# issue #2826
|
|
|
|
|
with pytest.raises(UnicodeEncodeError):
|
|
|
|
|
font.getbbox("’")
|
|
|
|
|
|
|
|
|
|
|
2024-05-27 10:08:13 +03:00
|
|
|
|
@pytest.mark.parametrize("font", fonts)
|
|
|
|
|
def test_textbbox(font: ImageFont.ImageFont) -> None:
|
2023-08-26 10:00:34 +03:00
|
|
|
|
im = Image.new("RGB", (200, 200))
|
|
|
|
|
d = ImageDraw.Draw(im)
|
2024-05-27 10:08:13 +03:00
|
|
|
|
assert d.textlength("test", font=font) == 24
|
|
|
|
|
assert d.textbbox((0, 0), "test", font=font) == (0, 0, 24, 11)
|
2024-01-01 08:03:43 +03:00
|
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
|
def test_decompression_bomb() -> None:
|
2024-01-01 08:03:43 +03:00
|
|
|
|
glyph = struct.pack(">hhhhhhhhhh", 1, 0, 0, 0, 256, 256, 0, 0, 256, 256)
|
|
|
|
|
fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256)
|
|
|
|
|
|
|
|
|
|
font = ImageFont.ImageFont()
|
|
|
|
|
font._load_pilfont_data(fp, Image.new("L", (256, 256)))
|
|
|
|
|
with pytest.raises(Image.DecompressionBombError):
|
|
|
|
|
font.getmask("A" * 1_000_000)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.timeout(4)
|
2024-01-31 12:12:58 +03:00
|
|
|
|
def test_oom() -> None:
|
2024-01-01 13:35:42 +03:00
|
|
|
|
glyph = struct.pack(
|
|
|
|
|
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767
|
|
|
|
|
)
|
2024-01-01 08:03:43 +03:00
|
|
|
|
fp = BytesIO(b"PILfont\n\nDATA\n" + glyph * 256)
|
|
|
|
|
|
|
|
|
|
font = ImageFont.ImageFont()
|
|
|
|
|
font._load_pilfont_data(fp, Image.new("L", (1, 1)))
|
|
|
|
|
font.getmask("A" * 1_000_000)
|