Merge pull request #4959 from nulano/anchor-part3
|
@ -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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|
BIN
Tests/fonts/OpenSansCondensed-LightItalic.ttf
Normal file
BIN
Tests/images/test_combine_multiline_lm_center.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_lm_left.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_lm_right.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_mm_center.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_mm_left.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_mm_right.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_rm_center.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_rm_left.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_combine_multiline_rm_right.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
|
@ -41,6 +41,7 @@ class TestImageFont:
|
||||||
"getters": (13, 16),
|
"getters": (13, 16),
|
||||||
"mask": (107, 13),
|
"mask": (107, 13),
|
||||||
"multiline-anchor": 6,
|
"multiline-anchor": 6,
|
||||||
|
"getlength": (36, 27, 27, 33),
|
||||||
},
|
},
|
||||||
(">=2.7",): {
|
(">=2.7",): {
|
||||||
"multiline": 6.2,
|
"multiline": 6.2,
|
||||||
|
@ -48,6 +49,7 @@ class TestImageFont:
|
||||||
"getters": (12, 16),
|
"getters": (12, 16),
|
||||||
"mask": (108, 13),
|
"mask": (108, 13),
|
||||||
"multiline-anchor": 4,
|
"multiline-anchor": 4,
|
||||||
|
"getlength": (36, 21, 24, 33),
|
||||||
},
|
},
|
||||||
"Default": {
|
"Default": {
|
||||||
"multiline": 0.5,
|
"multiline": 0.5,
|
||||||
|
@ -55,6 +57,7 @@ class TestImageFont:
|
||||||
"getters": (12, 16),
|
"getters": (12, 16),
|
||||||
"mask": (108, 13),
|
"mask": (108, 13),
|
||||||
"multiline-anchor": 4,
|
"multiline-anchor": 4,
|
||||||
|
"getlength": (36, 24, 24, 33),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +201,37 @@ class TestImageFont:
|
||||||
# Epsilon ~.5 fails with FreeType 2.7
|
# Epsilon ~.5 fails with FreeType 2.7
|
||||||
assert_image_similar(im, target_img, self.metrics["textsize"])
|
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):
|
def test_render_multiline(self):
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -753,23 +787,36 @@ class TestImageFont:
|
||||||
self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
|
self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"anchor",
|
"anchor, left, left_old, top",
|
||||||
(
|
(
|
||||||
# test horizontal anchors
|
# test horizontal anchors
|
||||||
"ls",
|
("ls", 0, 0, -36),
|
||||||
"ms",
|
("ms", -64, -65, -36),
|
||||||
"rs",
|
("rs", -128, -129, -36),
|
||||||
# test vertical anchors
|
# test vertical anchors
|
||||||
"ma",
|
("ma", -64, -65, 16),
|
||||||
"mt",
|
("mt", -64, -65, 0),
|
||||||
"mm",
|
("mm", -64, -65, -17),
|
||||||
"mb",
|
("mb", -64, -65, -44),
|
||||||
"md",
|
("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"
|
name, text = "quick", "Quick"
|
||||||
path = f"Tests/images/test_anchor_{name}_{anchor}.png"
|
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(
|
f = ImageFont.truetype(
|
||||||
"Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE
|
"Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE
|
||||||
)
|
)
|
||||||
|
@ -780,6 +827,8 @@ class TestImageFont:
|
||||||
d.line(((100, 0), (100, 200)), "gray")
|
d.line(((100, 0), (100, 200)), "gray")
|
||||||
d.text((100, 100), text, fill="black", anchor=anchor, font=f)
|
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:
|
with Image.open(path) as expected:
|
||||||
assert_image_similar(im, expected, 7)
|
assert_image_similar(im, expected, 7)
|
||||||
|
|
||||||
|
@ -830,14 +879,26 @@ class TestImageFont:
|
||||||
|
|
||||||
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
|
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
|
||||||
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor))
|
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.text((0, 0), "hello", anchor=anchor))
|
||||||
|
pytest.raises(
|
||||||
|
ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor)
|
||||||
|
)
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
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"]:
|
for anchor in ["lt", "lb"]:
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
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")
|
@skip_unless_feature("freetype2")
|
||||||
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
||||||
|
@ -870,7 +931,6 @@ class TestImageFont:
|
||||||
assert_image_similar(im, expected, max(self.metrics["multiline"], 3))
|
assert_image_similar(im, expected, max(self.metrics["multiline"], 3))
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.5.0")
|
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||||
@pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
|
|
||||||
def test_cbdt(self):
|
def test_cbdt(self):
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
|
@ -891,7 +951,6 @@ class TestImageFont:
|
||||||
pytest.skip("freetype compiled without libpng or unsupported")
|
pytest.skip("freetype compiled without libpng or unsupported")
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.5.0")
|
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||||
@pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
|
|
||||||
def test_cbdt_mask(self):
|
def test_cbdt_mask(self):
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
|
|
|
@ -213,6 +213,59 @@ def test_language():
|
||||||
assert_image_similar(im, target_img, 0.5)
|
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:
|
# FreeType 2.5.1 README: Miscellaneous Changes:
|
||||||
# Improved computation of emulated vertical metrics for TrueType fonts.
|
# Improved computation of emulated vertical metrics for TrueType fonts.
|
||||||
@skip_unless_feature_version(
|
@skip_unless_feature_version(
|
||||||
|
@ -302,6 +355,39 @@ def test_combine(name, text, dir, anchor, epsilon):
|
||||||
assert_image_similar(im, expected, 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():
|
def test_anchor_invalid_ttb():
|
||||||
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||||
im = Image.new("RGB", (100, 100), "white")
|
im = Image.new("RGB", (100, 100), "white")
|
||||||
|
@ -312,17 +398,34 @@ def test_anchor_invalid_ttb():
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
|
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
|
||||||
)
|
)
|
||||||
|
pytest.raises(
|
||||||
|
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb")
|
||||||
|
)
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb")
|
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(
|
pytest.raises(
|
||||||
ValueError,
|
ValueError,
|
||||||
lambda: d.multiline_text(
|
lambda: d.multiline_text(
|
||||||
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
|
(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
|
# ttb multiline text does not support anchors at all
|
||||||
pytest.raises(
|
pytest.raises(
|
||||||
ValueError,
|
ValueError,
|
||||||
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
|
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"),
|
||||||
|
)
|
||||||
|
|
|
@ -421,6 +421,15 @@ Methods
|
||||||
|
|
||||||
Return the size of the given string, in pixels.
|
Return the size of the given string, in pixels.
|
||||||
|
|
||||||
|
Use :py:meth:`textlength()` to measure the offset of following text with
|
||||||
|
1/64 pixel precision.
|
||||||
|
Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor.
|
||||||
|
|
||||||
|
.. note:: For historical reasons this function measures text height from
|
||||||
|
the ascender line instead of the top, see :ref:`text-anchors`.
|
||||||
|
If you wish to measure text height from the top, it is recommended
|
||||||
|
to use :meth:`textbbox` with ``anchor='lt'`` instead.
|
||||||
|
|
||||||
:param text: Text to be measured. If it contains any newline characters,
|
:param text: Text to be measured. If it contains any newline characters,
|
||||||
the text is passed on to :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`.
|
the text is passed on to :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`.
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
|
@ -460,6 +469,16 @@ Methods
|
||||||
|
|
||||||
Return the size of the given string, in pixels.
|
Return the size of the given string, in pixels.
|
||||||
|
|
||||||
|
Use :py:meth:`textlength()` to measure the offset of following text with
|
||||||
|
1/64 pixel precision.
|
||||||
|
Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor.
|
||||||
|
|
||||||
|
.. note:: For historical reasons this function measures text height as the
|
||||||
|
distance between the top ascender line and bottom descender line,
|
||||||
|
not the top and bottom of the text, see :ref:`text-anchors`.
|
||||||
|
If you wish to measure text height from the top to the bottom of text,
|
||||||
|
it is recommended to use :meth:`multiline_textbbox` instead.
|
||||||
|
|
||||||
:param text: Text to be measured.
|
:param text: Text to be measured.
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
|
@ -494,6 +513,164 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.2.0
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False)
|
||||||
|
|
||||||
|
Returns length (in pixels with 1/64 precision) of given text when rendered
|
||||||
|
in font with provided direction, features, and language.
|
||||||
|
|
||||||
|
This is the amount by which following text should be offset.
|
||||||
|
Text bounding box may extend past the length in some fonts,
|
||||||
|
e.g. when using italics or accents.
|
||||||
|
|
||||||
|
The result is returned as a float; it is a whole number if using basic layout.
|
||||||
|
|
||||||
|
Note that the sum of two lengths may not equal the length of a concatenated
|
||||||
|
string due to kerning. If you need to adjust for kerning, include the following
|
||||||
|
character and subtract its length.
|
||||||
|
|
||||||
|
For example, instead of
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
hello = draw.textlength("Hello", font)
|
||||||
|
world = draw.textlength("World", font)
|
||||||
|
hello_world = hello + world # not adjusted for kerning
|
||||||
|
assert hello_world == draw.textlength("HelloWorld", font) # may fail
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
hello = draw.textlength("HelloW", font) - draw.textlength("W", font) # adjusted for kerning
|
||||||
|
world = draw.textlength("World", font)
|
||||||
|
hello_world = hello + world # adjusted for kerning
|
||||||
|
assert hello_world == draw.textlength("HelloWorld", font) # True
|
||||||
|
|
||||||
|
or disable kerning with (requires libraqm)
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
hello = draw.textlength("Hello", font, features=["-kern"])
|
||||||
|
world = draw.textlength("World", font, features=["-kern"])
|
||||||
|
hello_world = hello + world # kerning is disabled, no need to adjust
|
||||||
|
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param text: Text to be measured. May not contain any newline characters.
|
||||||
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
|
Requires libraqm.
|
||||||
|
:param features: A list of OpenType font features to be used during text
|
||||||
|
layout. This is usually used to turn on optional
|
||||||
|
font features that are not enabled by default,
|
||||||
|
for example ``"dlig"`` or ``"ss01"``, but can be also
|
||||||
|
used to turn off default font features, for
|
||||||
|
example ``"-liga"`` to disable ligatures or ``"-kern"``
|
||||||
|
to disable kerning. To get all supported
|
||||||
|
features, see `OpenType docs`_.
|
||||||
|
Requires libraqm.
|
||||||
|
:param language: Language of the text. Different languages may use
|
||||||
|
different glyph shapes or ligatures. This parameter tells
|
||||||
|
the font which language the text is in, and to apply the
|
||||||
|
correct substitutions as appropriate, if available.
|
||||||
|
It should be a `BCP 47 language code`_.
|
||||||
|
Requires libraqm.
|
||||||
|
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||||
|
|
||||||
|
Returns bounding box (in pixels) of given text relative to given anchor
|
||||||
|
when rendered in font with provided direction, features, and language.
|
||||||
|
Only supported for TrueType fonts.
|
||||||
|
|
||||||
|
Use :py:meth:`textlength` to get the offset of following text with
|
||||||
|
1/64 pixel precision. The bounding box includes extra margins for
|
||||||
|
some fonts, e.g. italics or accents.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param xy: The anchor coordinates of the text.
|
||||||
|
:param text: Text to be measured. If it contains any newline characters,
|
||||||
|
the text is passed on to
|
||||||
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`.
|
||||||
|
:param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance.
|
||||||
|
:param anchor: The text anchor alignment. Determines the relative location of
|
||||||
|
the anchor to the text. The default alignment is top left.
|
||||||
|
See :ref:`text-anchors` for valid values. This parameter is
|
||||||
|
ignored for non-TrueType fonts.
|
||||||
|
:param spacing: If the text is passed on to
|
||||||
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,
|
||||||
|
the number of pixels between lines.
|
||||||
|
:param align: If the text is passed on to
|
||||||
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,
|
||||||
|
``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
||||||
|
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
||||||
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
|
Requires libraqm.
|
||||||
|
:param features: A list of OpenType font features to be used during text
|
||||||
|
layout. This is usually used to turn on optional
|
||||||
|
font features that are not enabled by default,
|
||||||
|
for example ``"dlig"`` or ``"ss01"``, but can be also
|
||||||
|
used to turn off default font features, for
|
||||||
|
example ``"-liga"`` to disable ligatures or ``"-kern"``
|
||||||
|
to disable kerning. To get all supported
|
||||||
|
features, see `OpenType docs`_.
|
||||||
|
Requires libraqm.
|
||||||
|
:param language: Language of the text. Different languages may use
|
||||||
|
different glyph shapes or ligatures. This parameter tells
|
||||||
|
the font which language the text is in, and to apply the
|
||||||
|
correct substitutions as appropriate, if available.
|
||||||
|
It should be a `BCP 47 language code`_.
|
||||||
|
Requires libraqm.
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||||
|
|
||||||
|
Returns bounding box (in pixels) of given text relative to given anchor
|
||||||
|
when rendered in font with provided direction, features, and language.
|
||||||
|
Only supported for TrueType fonts.
|
||||||
|
|
||||||
|
Use :py:meth:`textlength` to get the offset of following text with
|
||||||
|
1/64 pixel precision. The bounding box includes extra margins for
|
||||||
|
some fonts, e.g. italics or accents.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param xy: The anchor coordinates of the text.
|
||||||
|
:param text: Text to be measured.
|
||||||
|
:param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance.
|
||||||
|
:param anchor: The text anchor alignment. Determines the relative location of
|
||||||
|
the anchor to the text. The default alignment is top left.
|
||||||
|
See :ref:`text-anchors` for valid values. This parameter is
|
||||||
|
ignored for non-TrueType fonts.
|
||||||
|
:param spacing: The number of pixels between lines.
|
||||||
|
:param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
||||||
|
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
||||||
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
|
Requires libraqm.
|
||||||
|
:param features: A list of OpenType font features to be used during text
|
||||||
|
layout. This is usually used to turn on optional
|
||||||
|
font features that are not enabled by default,
|
||||||
|
for example ``"dlig"`` or ``"ss01"``, but can be also
|
||||||
|
used to turn off default font features, for
|
||||||
|
example ``"-liga"`` to disable ligatures or ``"-kern"``
|
||||||
|
to disable kerning. To get all supported
|
||||||
|
features, see `OpenType docs`_.
|
||||||
|
Requires libraqm.
|
||||||
|
:param language: Language of the text. Different languages may use
|
||||||
|
different glyph shapes or ligatures. This parameter tells
|
||||||
|
the font which language the text is in, and to apply the
|
||||||
|
correct substitutions as appropriate, if available.
|
||||||
|
It should be a `BCP 47 language code`_.
|
||||||
|
Requires libraqm.
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||||
|
|
||||||
.. py:method:: getdraw(im=None, hints=None)
|
.. py:method:: getdraw(im=None, hints=None)
|
||||||
|
|
||||||
.. warning:: This method is experimental.
|
.. warning:: This method is experimental.
|
||||||
|
|
|
@ -411,13 +411,8 @@ class ImageDraw:
|
||||||
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
|
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
|
||||||
)
|
)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line_width, line_height = self.textsize(
|
line_width = self.textlength(
|
||||||
line,
|
line, font, direction=direction, features=features, language=language
|
||||||
font,
|
|
||||||
direction=direction,
|
|
||||||
features=features,
|
|
||||||
language=language,
|
|
||||||
stroke_width=stroke_width,
|
|
||||||
)
|
)
|
||||||
widths.append(line_width)
|
widths.append(line_width)
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
|
@ -505,6 +500,172 @@ class ImageDraw:
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
return max_width, len(lines) * line_spacing - spacing
|
return max_width, len(lines) * line_spacing - spacing
|
||||||
|
|
||||||
|
def textlength(
|
||||||
|
self,
|
||||||
|
text,
|
||||||
|
font=None,
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
embedded_color=False,
|
||||||
|
):
|
||||||
|
"""Get the length of a given string, in pixels with 1/64 precision."""
|
||||||
|
if self._multiline_check(text):
|
||||||
|
raise ValueError("can't measure length of multiline text")
|
||||||
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
|
raise ValueError("Embedded color supported only in RGB and RGBA modes")
|
||||||
|
|
||||||
|
if font is None:
|
||||||
|
font = self.getfont()
|
||||||
|
mode = "RGBA" if embedded_color else self.fontmode
|
||||||
|
try:
|
||||||
|
return font.getlength(text, mode, direction, features, language)
|
||||||
|
except AttributeError:
|
||||||
|
size = self.textsize(
|
||||||
|
text, font, direction=direction, features=features, language=language
|
||||||
|
)
|
||||||
|
if direction == "ttb":
|
||||||
|
return size[1]
|
||||||
|
return size[0]
|
||||||
|
|
||||||
|
def textbbox(
|
||||||
|
self,
|
||||||
|
xy,
|
||||||
|
text,
|
||||||
|
font=None,
|
||||||
|
anchor=None,
|
||||||
|
spacing=4,
|
||||||
|
align="left",
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
embedded_color=False,
|
||||||
|
):
|
||||||
|
"""Get the bounding box of a given string, in pixels."""
|
||||||
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
|
raise ValueError("Embedded color supported only in RGB and RGBA modes")
|
||||||
|
|
||||||
|
if self._multiline_check(text):
|
||||||
|
return self.multiline_textbbox(
|
||||||
|
xy,
|
||||||
|
text,
|
||||||
|
font,
|
||||||
|
anchor,
|
||||||
|
spacing,
|
||||||
|
align,
|
||||||
|
direction,
|
||||||
|
features,
|
||||||
|
language,
|
||||||
|
stroke_width,
|
||||||
|
embedded_color,
|
||||||
|
)
|
||||||
|
|
||||||
|
if font is None:
|
||||||
|
font = self.getfont()
|
||||||
|
mode = "RGBA" if embedded_color else self.fontmode
|
||||||
|
bbox = font.getbbox(
|
||||||
|
text, mode, direction, features, language, stroke_width, anchor
|
||||||
|
)
|
||||||
|
return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]
|
||||||
|
|
||||||
|
def multiline_textbbox(
|
||||||
|
self,
|
||||||
|
xy,
|
||||||
|
text,
|
||||||
|
font=None,
|
||||||
|
anchor=None,
|
||||||
|
spacing=4,
|
||||||
|
align="left",
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
embedded_color=False,
|
||||||
|
):
|
||||||
|
if direction == "ttb":
|
||||||
|
raise ValueError("ttb direction is unsupported for multiline text")
|
||||||
|
|
||||||
|
if anchor is None:
|
||||||
|
anchor = "la"
|
||||||
|
elif len(anchor) != 2:
|
||||||
|
raise ValueError("anchor must be a 2 character string")
|
||||||
|
elif anchor[1] in "tb":
|
||||||
|
raise ValueError("anchor not supported for multiline text")
|
||||||
|
|
||||||
|
widths = []
|
||||||
|
max_width = 0
|
||||||
|
lines = self._multiline_split(text)
|
||||||
|
line_spacing = (
|
||||||
|
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
|
||||||
|
)
|
||||||
|
for line in lines:
|
||||||
|
line_width = self.textlength(
|
||||||
|
line,
|
||||||
|
font,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
embedded_color=embedded_color,
|
||||||
|
)
|
||||||
|
widths.append(line_width)
|
||||||
|
max_width = max(max_width, line_width)
|
||||||
|
|
||||||
|
top = xy[1]
|
||||||
|
if anchor[1] == "m":
|
||||||
|
top -= (len(lines) - 1) * line_spacing / 2.0
|
||||||
|
elif anchor[1] == "d":
|
||||||
|
top -= (len(lines) - 1) * line_spacing
|
||||||
|
|
||||||
|
bbox = None
|
||||||
|
|
||||||
|
for idx, line in enumerate(lines):
|
||||||
|
left = xy[0]
|
||||||
|
width_difference = max_width - widths[idx]
|
||||||
|
|
||||||
|
# first align left by anchor
|
||||||
|
if anchor[0] == "m":
|
||||||
|
left -= width_difference / 2.0
|
||||||
|
elif anchor[0] == "r":
|
||||||
|
left -= width_difference
|
||||||
|
|
||||||
|
# then align by align parameter
|
||||||
|
if align == "left":
|
||||||
|
pass
|
||||||
|
elif align == "center":
|
||||||
|
left += width_difference / 2.0
|
||||||
|
elif align == "right":
|
||||||
|
left += width_difference
|
||||||
|
else:
|
||||||
|
raise ValueError('align must be "left", "center" or "right"')
|
||||||
|
|
||||||
|
bbox_line = self.textbbox(
|
||||||
|
(left, top),
|
||||||
|
line,
|
||||||
|
font,
|
||||||
|
anchor,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
stroke_width=stroke_width,
|
||||||
|
embedded_color=embedded_color,
|
||||||
|
)
|
||||||
|
if bbox is None:
|
||||||
|
bbox = bbox_line
|
||||||
|
else:
|
||||||
|
bbox = (
|
||||||
|
min(bbox[0], bbox_line[0]),
|
||||||
|
min(bbox[1], bbox_line[1]),
|
||||||
|
max(bbox[2], bbox_line[2]),
|
||||||
|
max(bbox[3], bbox_line[3]),
|
||||||
|
)
|
||||||
|
|
||||||
|
top += line_spacing
|
||||||
|
|
||||||
|
if bbox is None:
|
||||||
|
return xy[0], xy[1], xy[0], xy[1]
|
||||||
|
return bbox
|
||||||
|
|
||||||
|
|
||||||
def Draw(im, mode=None):
|
def Draw(im, mode=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -215,6 +215,147 @@ class FreeTypeFont:
|
||||||
"""
|
"""
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
|
def getlength(self, text, mode="", direction=None, features=None, language=None):
|
||||||
|
"""
|
||||||
|
Returns length (in pixels with 1/64 precision) of given text when rendered
|
||||||
|
in font with provided direction, features, and language.
|
||||||
|
|
||||||
|
This is the amount by which following text should be offset.
|
||||||
|
Text bounding box may extend past the length in some fonts,
|
||||||
|
e.g. when using italics or accents.
|
||||||
|
|
||||||
|
The result is returned as a float; it is a whole number if using basic layout.
|
||||||
|
|
||||||
|
Note that the sum of two lengths may not equal the length of a concatenated
|
||||||
|
string due to kerning. If you need to adjust for kerning, include the following
|
||||||
|
character and subtract its length.
|
||||||
|
|
||||||
|
For example, instead of
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
hello = font.getlength("Hello")
|
||||||
|
world = font.getlength("World")
|
||||||
|
hello_world = hello + world # not adjusted for kerning
|
||||||
|
assert hello_world == font.getlength("HelloWorld") # may fail
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
|
||||||
|
world = font.getlength("World")
|
||||||
|
hello_world = hello + world # adjusted for kerning
|
||||||
|
assert hello_world == font.getlength("HelloWorld") # True
|
||||||
|
|
||||||
|
or disable kerning with (requires libraqm)
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
hello = draw.textlength("Hello", font, features=["-kern"])
|
||||||
|
world = draw.textlength("World", font, features=["-kern"])
|
||||||
|
hello_world = hello + world # kerning is disabled, no need to adjust
|
||||||
|
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"])
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param text: Text to measure.
|
||||||
|
:param mode: Used by some graphics drivers to indicate what mode the
|
||||||
|
driver prefers; if empty, the renderer may return either
|
||||||
|
mode. Note that the mode is always a string, to simplify
|
||||||
|
C-level implementations.
|
||||||
|
|
||||||
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
|
Requires libraqm.
|
||||||
|
|
||||||
|
:param features: A list of OpenType font features to be used during text
|
||||||
|
layout. This is usually used to turn on optional
|
||||||
|
font features that are not enabled by default,
|
||||||
|
for example 'dlig' or 'ss01', but can be also
|
||||||
|
used to turn off default font features for
|
||||||
|
example '-liga' to disable ligatures or '-kern'
|
||||||
|
to disable kerning. To get all supported
|
||||||
|
features, see
|
||||||
|
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
||||||
|
Requires libraqm.
|
||||||
|
|
||||||
|
:param language: Language of the text. Different languages may use
|
||||||
|
different glyph shapes or ligatures. This parameter tells
|
||||||
|
the font which language the text is in, and to apply the
|
||||||
|
correct substitutions as appropriate, if available.
|
||||||
|
It should be a `BCP 47 language code
|
||||||
|
<https://www.w3.org/International/articles/language-tags/>`
|
||||||
|
Requires libraqm.
|
||||||
|
|
||||||
|
:return: Width for horizontal, height for vertical text.
|
||||||
|
"""
|
||||||
|
return self.font.getlength(text, mode, direction, features, language) / 64
|
||||||
|
|
||||||
|
def getbbox(
|
||||||
|
self,
|
||||||
|
text,
|
||||||
|
mode="",
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
anchor=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Returns bounding box (in pixels) of given text relative to given anchor
|
||||||
|
when rendered in font with provided direction, features, and language.
|
||||||
|
|
||||||
|
Use :py:meth:`getlength()` to get the offset of following text with
|
||||||
|
1/64 pixel precision. The bounding box includes extra margins for
|
||||||
|
some fonts, e.g. italics or accents.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param text: Text to render.
|
||||||
|
:param mode: Used by some graphics drivers to indicate what mode the
|
||||||
|
driver prefers; if empty, the renderer may return either
|
||||||
|
mode. Note that the mode is always a string, to simplify
|
||||||
|
C-level implementations.
|
||||||
|
|
||||||
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
|
Requires libraqm.
|
||||||
|
|
||||||
|
:param features: A list of OpenType font features to be used during text
|
||||||
|
layout. This is usually used to turn on optional
|
||||||
|
font features that are not enabled by default,
|
||||||
|
for example 'dlig' or 'ss01', but can be also
|
||||||
|
used to turn off default font features for
|
||||||
|
example '-liga' to disable ligatures or '-kern'
|
||||||
|
to disable kerning. To get all supported
|
||||||
|
features, see
|
||||||
|
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
|
||||||
|
Requires libraqm.
|
||||||
|
|
||||||
|
:param language: Language of the text. Different languages may use
|
||||||
|
different glyph shapes or ligatures. This parameter tells
|
||||||
|
the font which language the text is in, and to apply the
|
||||||
|
correct substitutions as appropriate, if available.
|
||||||
|
It should be a `BCP 47 language code
|
||||||
|
<https://www.w3.org/International/articles/language-tags/>`
|
||||||
|
Requires libraqm.
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
:param anchor: The text anchor alignment. Determines the relative location of
|
||||||
|
the anchor to the text. The default alignment is top left.
|
||||||
|
See :ref:`text-anchors` for valid values.
|
||||||
|
|
||||||
|
:return: ``(left, top, right, bottom)`` bounding box
|
||||||
|
"""
|
||||||
|
size, offset = self.font.getsize(
|
||||||
|
text, mode, direction, features, language, anchor
|
||||||
|
)
|
||||||
|
left, top = offset[0] - stroke_width, offset[1] - stroke_width
|
||||||
|
width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
|
||||||
|
return left, top, left + width, top + height
|
||||||
|
|
||||||
def getsize(
|
def getsize(
|
||||||
self, text, direction=None, features=None, language=None, stroke_width=0
|
self, text, direction=None, features=None, language=None, stroke_width=0
|
||||||
):
|
):
|
||||||
|
@ -222,6 +363,15 @@ class FreeTypeFont:
|
||||||
Returns width and height (in pixels) of given text if rendered in font with
|
Returns width and height (in pixels) of given text if rendered in font with
|
||||||
provided direction, features, and language.
|
provided direction, features, and language.
|
||||||
|
|
||||||
|
Use :py:meth:`getlength()` to measure the offset of following text with
|
||||||
|
1/64 pixel precision.
|
||||||
|
Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor.
|
||||||
|
|
||||||
|
.. note:: For historical reasons this function measures text height from
|
||||||
|
the ascender line instead of the top, see :ref:`text-anchors`.
|
||||||
|
If you wish to measure text height from the top, it is recommended
|
||||||
|
to use the bottom value of :meth:`getbbox` with ``anchor='lt'`` instead.
|
||||||
|
|
||||||
:param text: Text to measure.
|
:param text: Text to measure.
|
||||||
|
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
|
|
|
@ -594,6 +594,54 @@ text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *featu
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
font_getlength(FontObject* self, PyObject* args)
|
||||||
|
{
|
||||||
|
int length; /* length along primary axis, in 26.6 precision */
|
||||||
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
|
size_t i, count; /* glyph_info index and length */
|
||||||
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
|
const char *mode = NULL;
|
||||||
|
const char *dir = NULL;
|
||||||
|
const char *lang = NULL;
|
||||||
|
PyObject *features = Py_None;
|
||||||
|
PyObject *string;
|
||||||
|
|
||||||
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = 0;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
if (horizontal_dir) {
|
||||||
|
length += glyph_info[i].x_advance;
|
||||||
|
} else {
|
||||||
|
length -= glyph_info[i].y_advance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glyph_info) {
|
||||||
|
PyMem_Free(glyph_info);
|
||||||
|
glyph_info = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromLong(length);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_getsize(FontObject* self, PyObject* args)
|
font_getsize(FontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
|
@ -1286,6 +1334,7 @@ font_dealloc(FontObject* self)
|
||||||
static PyMethodDef font_methods[] = {
|
static PyMethodDef font_methods[] = {
|
||||||
{"render", (PyCFunction) font_render, METH_VARARGS},
|
{"render", (PyCFunction) font_render, METH_VARARGS},
|
||||||
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
|
{"getsize", (PyCFunction) font_getsize, METH_VARARGS},
|
||||||
|
{"getlength", (PyCFunction) font_getlength, METH_VARARGS},
|
||||||
#if FREETYPE_MAJOR > 2 ||\
|
#if FREETYPE_MAJOR > 2 ||\
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
||||||
|
|