Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Tests/fonts/LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee

All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.

OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)

DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.

Expand Down
Binary file added Tests/fonts/OpenSansCondensed-LightItalic.ttf
Binary file not shown.
Binary file added Tests/images/test_combine_multiline_lm_center.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_lm_left.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_lm_right.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_mm_center.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_mm_left.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_mm_right.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_rm_center.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_rm_left.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_combine_multiline_rm_right.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 72 additions & 13 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,23 @@ class TestImageFont:
"getters": (13, 16),
"mask": (107, 13),
"multiline-anchor": 6,
"getlength": (36, 27, 27, 33),
},
(">=2.7",): {
"multiline": 6.2,
"textsize": 2.5,
"getters": (12, 16),
"mask": (108, 13),
"multiline-anchor": 4,
"getlength": (36, 21, 24, 33),
},
"Default": {
"multiline": 0.5,
"textsize": 0.5,
"getters": (12, 16),
"mask": (108, 13),
"multiline-anchor": 4,
"getlength": (36, 24, 24, 33),
},
}

Expand Down Expand Up @@ -198,6 +201,37 @@ def test_textsize_equal(self):
# Epsilon ~.5 fails with FreeType 2.7
assert_image_similar(im, target_img, self.metrics["textsize"])

@pytest.mark.parametrize(
"text, mode, font, size, length_basic_index, length_raqm",
(
# basic test
("text", "L", "FreeMono.ttf", 15, 0, 36),
("text", "1", "FreeMono.ttf", 15, 0, 36),
# issue 4177
("rrr", "L", "DejaVuSans.ttf", 18, 1, 22.21875),
("rrr", "1", "DejaVuSans.ttf", 18, 2, 22.21875),
# test 'l' not including extra margin
# using exact value 2047 / 64 for raqm, checked with debugger
("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 3, 31.984375),
("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 3, 31.984375),
),
)
def test_getlength(self, text, mode, font, size, length_basic_index, length_raqm):
f = ImageFont.truetype(
"Tests/fonts/" + font, size, layout_engine=self.LAYOUT_ENGINE
)

im = Image.new(mode, (1, 1), 0)
d = ImageDraw.Draw(im)

if self.LAYOUT_ENGINE == ImageFont.LAYOUT_BASIC:
length = d.textlength(text, f)
assert length == self.metrics["getlength"][length_basic_index]
else:
# disable kerning, kerning metrics changed
length = d.textlength(text, f, features=["-kern"])
assert length == length_raqm

def test_render_multiline(self):
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
Expand Down Expand Up @@ -753,23 +787,36 @@ def test_variation_set_by_axes(self):
self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)

@pytest.mark.parametrize(
"anchor",
"anchor, left, left_old, top",
(
# test horizontal anchors
"ls",
"ms",
"rs",
("ls", 0, 0, -36),
("ms", -64, -65, -36),
("rs", -128, -129, -36),
# test vertical anchors
"ma",
"mt",
"mm",
"mb",
"md",
("ma", -64, -65, 16),
("mt", -64, -65, 0),
("mm", -64, -65, -17),
("mb", -64, -65, -44),
("md", -64, -65, -51),
),
ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"),
)
def test_anchor(self, anchor):
def test_anchor(self, anchor, left, left_old, top):
name, text = "quick", "Quick"
path = f"Tests/images/test_anchor_{name}_{anchor}.png"

freetype = parse_version(features.version_module("freetype2"))
if freetype < parse_version("2.4"):
width, height = (129, 44)
left = left_old
elif self.LAYOUT_ENGINE == ImageFont.LAYOUT_RAQM:
width, height = (129, 44)
else:
width, height = (128, 44)

bbox_expected = (left, top, left + width, top + height)

f = ImageFont.truetype(
"Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE
)
Expand All @@ -780,11 +827,13 @@ def test_anchor(self, anchor):
d.line(((100, 0), (100, 200)), "gray")
d.text((100, 100), text, fill="black", anchor=anchor, font=f)

assert d.textbbox((0, 0), text, f, anchor=anchor) == bbox_expected

with Image.open(path) as expected:
assert_image_similar(im, expected, 7)

@pytest.mark.parametrize(
"anchor,align",
"anchor, align",
(
# test horizontal anchors
("lm", "left"),
Expand Down Expand Up @@ -830,14 +879,26 @@ def test_anchor_invalid(self):

for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor))
pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor))
pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor))
pytest.raises(
ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor)
)
pytest.raises(
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)
for anchor in ["lt", "lb"]:
pytest.raises(
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)

@skip_unless_feature("freetype2")
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
Expand Down Expand Up @@ -870,7 +931,6 @@ def test_standard_embedded_color(self):
assert_image_similar(im, expected, max(self.metrics["multiline"], 3))

@skip_unless_feature_version("freetype2", "2.5.0")
@pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
def test_cbdt(self):
try:
font = ImageFont.truetype(
Expand All @@ -891,7 +951,6 @@ def test_cbdt(self):
pytest.skip("freetype compiled without libpng or unsupported")

@skip_unless_feature_version("freetype2", "2.5.0")
@pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
def test_cbdt_mask(self):
try:
font = ImageFont.truetype(
Expand Down
105 changes: 104 additions & 1 deletion Tests/test_imagefontctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,59 @@ def test_language():
assert_image_similar(im, target_img, 0.5)


@pytest.mark.parametrize("mode", ("L", "1"))
@pytest.mark.parametrize(
"text, direction, expected",
(
("سلطنة عمان Oman", None, 173.703125),
("سلطنة عمان Oman", "ltr", 173.703125),
("Oman سلطنة عمان", "rtl", 173.703125),
("English عربي", "rtl", 123.796875),
("test", "ttb", 80.0),
),
ids=("None", "ltr", "rtl2", "rtl", "ttb"),
)
def test_getlength(mode, text, direction, expected):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode, (1, 1), 0)
d = ImageDraw.Draw(im)

try:
assert d.textlength(text, ttf, direction) == expected
except ValueError as ex:
if (
direction == "ttb"
and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
):
pytest.skip("libraqm 0.7 or greater not available")


@pytest.mark.parametrize("mode", ("L", "1"))
@pytest.mark.parametrize("direction", ("ltr", "ttb"))
@pytest.mark.parametrize(
"text",
("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"),
ids=("caron-above", "caron-below", "double-breve", "overline"),
)
def test_getlength_combine(mode, direction, text):
if text == "i\u0305i" and direction == "ttb":
pytest.skip("fails with this font")

ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)

try:
target = ttf.getlength("ii", mode, direction)
actual = ttf.getlength(text, mode, direction)

assert actual == target
except ValueError as ex:
if (
direction == "ttb"
and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
):
pytest.skip("libraqm 0.7 or greater not available")


# FreeType 2.5.1 README: Miscellaneous Changes:
# Improved computation of emulated vertical metrics for TrueType fonts.
@skip_unless_feature_version(
Expand Down Expand Up @@ -274,7 +327,7 @@ def test_anchor_ttb(anchor):

# this tests various combining characters for anchor alignment and clipping
@pytest.mark.parametrize(
"name,text,anchor,dir,epsilon", combine_tests, ids=[r[0] for r in combine_tests]
"name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests]
)
def test_combine(name, text, dir, anchor, epsilon):
if (
Expand Down Expand Up @@ -302,6 +355,39 @@ def test_combine(name, text, dir, anchor, epsilon):
assert_image_similar(im, expected, epsilon)


@pytest.mark.parametrize(
"anchor, align",
(
("lm", "left"), # pass with getsize
("lm", "center"), # fail at 2.12
("lm", "right"), # fail at 2.57
("mm", "left"), # fail at 2.12
("mm", "center"), # pass with getsize
("mm", "right"), # fail at 2.12
("rm", "left"), # fail at 2.57
("rm", "center"), # fail at 2.12
("rm", "right"), # pass with getsize
),
)
def test_combine_multiline(anchor, align):
# test that multiline text uses getlength, not getsize or getbbox

path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
text = "i\u0305\u035C\ntext" # i with overline and double breve, and a word

im = Image.new("RGB", (400, 400), "white")
d = ImageDraw.Draw(im)
d.line(((0, 200), (400, 200)), "gray")
d.line(((200, 0), (200, 400)), "gray")
bbox = d.multiline_textbbox((200, 200), text, anchor=anchor, font=f, align=align)
d.rectangle(bbox, outline="red")
d.multiline_text((200, 200), text, fill="black", anchor=anchor, font=f, align=align)

with Image.open(path) as expected:
assert_image_similar(im, expected, 0.015)


def test_anchor_invalid_ttb():
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new("RGB", (100, 100), "white")
Expand All @@ -312,17 +398,34 @@ def test_anchor_invalid_ttb():
pytest.raises(
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError,
lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"),
)
pytest.raises(
ValueError,
lambda: d.multiline_text(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
# ttb multiline text does not support anchors at all
pytest.raises(
ValueError,
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)
Loading