mirror of
https://github.com/python-pillow/Pillow.git
synced 2026-02-04 14:35:54 +03:00
231 lines
7.6 KiB
Python
231 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from PIL import Image, ImageDraw, ImageFont, ImageText, features
|
|
|
|
from .helper import assert_image_similar_tofile, skip_unless_feature
|
|
|
|
FONT_PATH = "Tests/fonts/FreeMono.ttf"
|
|
|
|
|
|
@pytest.fixture(
|
|
scope="module",
|
|
params=[
|
|
pytest.param(ImageFont.Layout.BASIC),
|
|
pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
|
|
],
|
|
)
|
|
def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout:
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture(
|
|
scope="module",
|
|
params=[
|
|
None,
|
|
pytest.param(ImageFont.Layout.BASIC, marks=skip_unless_feature("freetype2")),
|
|
pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
|
|
],
|
|
)
|
|
def font(
|
|
request: pytest.FixtureRequest,
|
|
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
|
|
layout_engine = request.param
|
|
if layout_engine is None:
|
|
return ImageFont.load_default_imagefont()
|
|
else:
|
|
return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
|
|
|
|
|
|
def test_get_length(font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> None:
|
|
factor = 1 if isinstance(font, ImageFont.ImageFont) else 2
|
|
assert ImageText.Text("A", font).get_length() == 6 * factor
|
|
assert ImageText.Text("AB", font).get_length() == 12 * factor
|
|
assert ImageText.Text("M", font).get_length() == 6 * factor
|
|
assert ImageText.Text("y", font).get_length() == 6 * factor
|
|
assert ImageText.Text("a", font).get_length() == 6 * factor
|
|
|
|
text = ImageText.Text("\n", font)
|
|
with pytest.raises(ValueError, match="can't measure length of multiline text"):
|
|
text.get_length()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"text, expected",
|
|
(
|
|
("A", (0, 4, 12, 16)),
|
|
("AB", (0, 4, 24, 16)),
|
|
("M", (0, 4, 12, 16)),
|
|
("y", (0, 7, 12, 20)),
|
|
("a", (0, 7, 12, 16)),
|
|
),
|
|
)
|
|
def test_get_bbox(
|
|
font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
|
|
text: str,
|
|
expected: tuple[int, int, int, int],
|
|
) -> None:
|
|
if isinstance(font, ImageFont.ImageFont):
|
|
expected = (0, 0, expected[2] // 2, 11)
|
|
assert ImageText.Text(text, font).get_bbox() == expected
|
|
|
|
|
|
def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
|
|
if features.check_module("freetype2"):
|
|
font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
|
text = ImageText.Text("Hello World!", font)
|
|
text.embed_color()
|
|
assert text.get_length() == 288
|
|
|
|
im = Image.new("RGB", (300, 64), "white")
|
|
draw = ImageDraw.Draw(im)
|
|
draw.text((10, 10), text, "#fa6")
|
|
|
|
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
|
|
|
|
text = ImageText.Text("", mode="1")
|
|
with pytest.raises(
|
|
ValueError, match="Embedded color supported only in RGB and RGBA modes"
|
|
):
|
|
text.embed_color()
|
|
|
|
|
|
@skip_unless_feature("freetype2")
|
|
def test_stroke() -> None:
|
|
for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
|
|
# Arrange
|
|
im = Image.new("RGB", (120, 130))
|
|
draw = ImageDraw.Draw(im)
|
|
font = ImageFont.truetype(FONT_PATH, 120)
|
|
text = ImageText.Text("A", font)
|
|
text.stroke(2, stroke_fill)
|
|
|
|
# Act
|
|
draw.text((12, 12), text, "#f00")
|
|
|
|
# Assert
|
|
assert_image_similar_tofile(
|
|
im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"data, width, expected",
|
|
(
|
|
("Hello World!", 100, "Hello World!"), # No wrap required
|
|
("Hello World!", 50, "Hello\nWorld!"), # Wrap word to a new line
|
|
# Keep multiple spaces within a line
|
|
("Keep multiple spaces", 90, "Keep multiple\nspaces"),
|
|
(" Keep\n leading space", 100, " Keep\n leading space"),
|
|
),
|
|
)
|
|
@pytest.mark.parametrize("string", (True, False))
|
|
def test_wrap(data: str, width: int, expected: str, string: bool) -> None:
|
|
if string:
|
|
text = ImageText.Text(data)
|
|
assert text.wrap(width) is None
|
|
assert text.text == expected
|
|
else:
|
|
text_bytes = ImageText.Text(data.encode())
|
|
assert text_bytes.wrap(width) is None
|
|
assert text_bytes.text == expected.encode()
|
|
|
|
|
|
def test_wrap_long_word() -> None:
|
|
text = ImageText.Text("Hello World!")
|
|
with pytest.raises(ValueError, match="Word does not fit within line"):
|
|
text.wrap(25)
|
|
|
|
|
|
def test_wrap_unsupported(font: ImageFont.FreeTypeFont) -> None:
|
|
transposed_font = ImageFont.TransposedFont(font)
|
|
text = ImageText.Text("Hello World!", transposed_font)
|
|
with pytest.raises(ValueError, match="TransposedFont not supported"):
|
|
text.wrap(50)
|
|
|
|
text = ImageText.Text("Hello World!", direction="ttb")
|
|
with pytest.raises(ValueError, match="Only ltr direction supported"):
|
|
text.wrap(50)
|
|
|
|
|
|
def test_wrap_height() -> None:
|
|
width = 50 if features.check_module("freetype2") else 60
|
|
text = ImageText.Text("Text does not fit within height")
|
|
wrapped = text.wrap(width, 25 if features.check_module("freetype2") else 40)
|
|
assert wrapped is not None
|
|
assert wrapped.text == " within height"
|
|
assert text.text == "Text does\nnot fit"
|
|
|
|
text = ImageText.Text("Text does not fit\nwithin height")
|
|
wrapped = text.wrap(width, 20)
|
|
assert wrapped is not None
|
|
assert wrapped.text == " not fit\nwithin height"
|
|
assert text.text == "Text does"
|
|
|
|
text = ImageText.Text("Text does not fit\n\nwithin height")
|
|
wrapped = text.wrap(width, 25 if features.check_module("freetype2") else 40)
|
|
assert wrapped is not None
|
|
assert wrapped.text == "\nwithin height"
|
|
assert text.text == "Text does\nnot fit"
|
|
|
|
|
|
def test_wrap_scaling_unsupported() -> None:
|
|
font = ImageFont.load_default_imagefont()
|
|
text = ImageText.Text("Hello World!", font)
|
|
with pytest.raises(ValueError, match="'scaling' only supports FreeTypeFont"):
|
|
text.wrap(50, scaling="shrink")
|
|
|
|
if features.check_module("freetype2"):
|
|
text = ImageText.Text("Hello World!")
|
|
with pytest.raises(ValueError, match="'scaling' requires 'height'"):
|
|
text.wrap(50, scaling="shrink")
|
|
|
|
|
|
@skip_unless_feature("freetype2")
|
|
def test_wrap_shrink() -> None:
|
|
# No scaling required
|
|
text = ImageText.Text("Hello World!")
|
|
assert isinstance(text.font, ImageFont.FreeTypeFont)
|
|
assert text.font.size == 10
|
|
assert text.wrap(50, 50, "shrink") is None
|
|
assert isinstance(text.font, ImageFont.FreeTypeFont)
|
|
assert text.font.size == 10
|
|
|
|
with pytest.raises(ValueError, match="Text could not be scaled"):
|
|
text.wrap(50, 15, ("shrink", 9))
|
|
|
|
assert text.wrap(50, 15, "shrink") is None
|
|
assert text.font.size == 8
|
|
|
|
text = ImageText.Text("Hello World!")
|
|
assert text.wrap(50, 15, ("shrink", 7)) is None
|
|
assert isinstance(text.font, ImageFont.FreeTypeFont)
|
|
assert text.font.size == 8
|
|
|
|
|
|
@skip_unless_feature("freetype2")
|
|
def test_wrap_grow() -> None:
|
|
# No scaling required
|
|
text = ImageText.Text("Hello World!")
|
|
assert isinstance(text.font, ImageFont.FreeTypeFont)
|
|
assert text.font.size == 10
|
|
assert text.wrap(58, 10, "grow") is None
|
|
assert isinstance(text.font, ImageFont.FreeTypeFont)
|
|
assert text.font.size == 10
|
|
|
|
with pytest.raises(ValueError, match="Text could not be scaled"):
|
|
text.wrap(50, 50, ("grow", 12))
|
|
|
|
assert text.wrap(50, 50, "grow") is None
|
|
assert text.font.size == 16
|
|
|
|
text = ImageText.Text("A\nB")
|
|
with pytest.raises(ValueError, match="Text could not be scaled"):
|
|
text.wrap(50, 10, "grow")
|
|
|
|
text = ImageText.Text("Hello World!")
|
|
assert text.wrap(50, 50, ("grow", 18)) is None
|
|
assert isinstance(text.font, ImageFont.FreeTypeFont)
|
|
assert text.font.size == 16
|