From 395aa946a9f159f906a57f5c01a87819e2a2ed7a Mon Sep 17 00:00:00 2001 From: nulano Date: Sat, 20 Jun 2020 12:54:53 +0200 Subject: [PATCH 01/10] add getbbox and getlength, with tests Squashed commits: [ec9ec31b] add tests for invalid anchor (cherry picked from commit 9e50a6a47f79876ee56942152047f03fff03c49b) [386a9170] fix lint and docs (cherry picked from commit 2d0d5282fcfc3ee332a41e60b865ee766445cc3d) [29f5d4c9] restore and document previous getsize behaviour see discussion in issue 4789 (cherry picked from commit 9fbc94571ce0ed42fdd11e99f343a1613c9dc6d3) [0ffd51a0] add getbbox and getlength, with tests (cherry picked from commit c5f63737476a998c81e589e5819d21ca69bb7b46) --- Tests/fonts/LICENSE.txt | 1 + Tests/fonts/OpenSansCondensed-LightItalic.ttf | Bin 0 -> 90120 bytes .../test_combine_multiline_lm_center.png | Bin 0 -> 4132 bytes .../images/test_combine_multiline_lm_left.png | Bin 0 -> 4117 bytes .../test_combine_multiline_lm_right.png | Bin 0 -> 4139 bytes .../test_combine_multiline_mm_center.png | Bin 0 -> 4107 bytes .../images/test_combine_multiline_mm_left.png | Bin 0 -> 4100 bytes .../test_combine_multiline_mm_right.png | Bin 0 -> 4102 bytes .../test_combine_multiline_rm_center.png | Bin 0 -> 4105 bytes .../images/test_combine_multiline_rm_left.png | Bin 0 -> 4110 bytes .../test_combine_multiline_rm_right.png | Bin 0 -> 4101 bytes Tests/test_imagefont.py | 67 ++++++-- Tests/test_imagefontctl.py | 85 +++++++++++ docs/reference/ImageDraw.rst | 9 ++ src/PIL/ImageDraw.py | 8 +- src/PIL/ImageFont.py | 143 ++++++++++++++++++ src/_imagingft.c | 44 ++++++ 17 files changed, 344 insertions(+), 13 deletions(-) create mode 100644 Tests/fonts/OpenSansCondensed-LightItalic.ttf create mode 100644 Tests/images/test_combine_multiline_lm_center.png create mode 100644 Tests/images/test_combine_multiline_lm_left.png create mode 100644 Tests/images/test_combine_multiline_lm_right.png create mode 100644 Tests/images/test_combine_multiline_mm_center.png create mode 100644 Tests/images/test_combine_multiline_mm_left.png create mode 100644 Tests/images/test_combine_multiline_mm_right.png create mode 100644 Tests/images/test_combine_multiline_rm_center.png create mode 100644 Tests/images/test_combine_multiline_rm_left.png create mode 100644 Tests/images/test_combine_multiline_rm_right.png diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 528eed9ef22..538862b97b3 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -9,6 +9,7 @@ ter-x20b.pcf, from http://terminus-font.sourceforge.net/ 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) 10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base diff --git a/Tests/fonts/OpenSansCondensed-LightItalic.ttf b/Tests/fonts/OpenSansCondensed-LightItalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b4ee4951f33ada1a1c4ec71349d9755ea95063fd GIT binary patch literal 90120 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�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 literal 0 HcmV?d00001 diff --git a/Tests/images/test_combine_multiline_lm_right.png b/Tests/images/test_combine_multiline_lm_right.png new file mode 100644 index 0000000000000000000000000000000000000000..73e2ca9d04d5b019cbcbe944060b764b2ea71aee GIT binary patch literal 4139 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 literal 0 HcmV?d00001 diff --git a/Tests/images/test_combine_multiline_mm_center.png b/Tests/images/test_combine_multiline_mm_center.png new file mode 100644 index 0000000000000000000000000000000000000000..46ec20173395cbe8f1f0327e488078017e3d0339 GIT binary patch literal 4107 zcmeHKS6EY79!Jr^23STI`Y4uFN)!Y^2z;(60zw?6lLQNGzz{kl5Kweg2BnB3hL#<{ z1f&P)9X^^75+MYUgh&g7DiDZ-5caS;`|UpL!#>Qs%|mi;PQH82zx>Mo#$C6wl-;Ae zM@mXc*6OOo4JoN@Nn4*?+rf%F=ht-b-D_oW(LN$$juYwAzp~l1SY}IeOPe@uu&k`4 zc_Qvsa$oKvz3klmgEc=ohCKYReeYd;D_U;u)KP`7GEsI;Z|VvA1l_dKrQSBCURj)7 zE7Q=Ra(QD+Y@|xDqjYd1)HD(s%}Bv!1ffhv%JSMb};{Zu-(M1#bv4tlF7D<{O1b$JB!WEi)#wGCCGVaE0;~pK}{1LMUoGGfMAvSR5@4Ul?AWuSHKWeqH8uoAB-#oI7W4Zx0XZ zIzHBsSe&14bL=1#D{!s~7~xY$S#7sdq+QDeQoHg>O1{z{jdi>7 zqeL`ZAL$;>3C*@lRM+$9{CF>iO!gY2dd4g0h^Dd=4gJ}#qH&DBU(|^DdXZ#?vtxXT z(}EDMRS14~>N`|{3TFp))M1w8qVs{ST)up{9=kIAx!e!@1B0$GV`F2Xk*m!9e$*g! zqPS7wS{pXU>rR(P2C}OH%e=ZT%)RTg6Mv1fEHOgb;I)aSn5Et~Hi^L|npd7hh!;Da z$g1E|e@aeSdQYRdVfT&4>bOpDWQZJfipN0ypMM(}RaH^18aZ zb{kJLRb>@9UDW6e(aOvKy7jTFr>Cb5R3Yg3kn;bpk#>8^%gX_}x@j~m9UT*NfwMlT5(TOq1{gb!ZKh8U!dsRN=F$m;H+C4Skup_Dc<->3r`jj8|`t zm5lrsfZ#Nvz&qHmmPfnqwm<6{9ZAx3{dP$G~D#%p=uTt;fn?B ztYin#Bpd4>wR)eAubJ@x**FlLYo$MK_R`6L-3l_F%Dm~}TpS^*{%gzL#42lmQQlz@ zz`j~gsaPyNKOCW%Sph^Za=v=xtQyZ%?Hr}Nm<6P;F;2mB(a_O<^ zlH<~q=(P!g;Iu9KOn)VsJ4uQ$gPLp+tLsbdPuY5#>+*6|B}PqT+w_Fsy^+Fs)F>w% z-LUq>gwkU)_6!u`9L^kiY)AZ+u!Uh|c`*8yK9r~6xzLc{pI$2VBg@}^0c z3H5&lkD1IQ7z6Zhy#mMjD6malI{HQ*B?~*P=pL0UX@?;Y2rH{D&_zNUCpmIQymZJk zlMj#zqmvh=O`~Z92)%A_qqIX^OIv$OY$tm%Zx#c2hAwq#4t`cMwgTG4b66%X7++O_ zVneW4iFi%ryuYdBER)5;~?)62!f~O}e*F%dx<@%*(cqMgk}x!t9H3b6){QA8PzQjm2PGzj-tG z>x@Q>co7(7Mive@ga{~YbrYC+nB{&0OhNl|m4&+Ho)fFR?A9ZccLMpKz?L`WpK}1n zt7>X$_*>*x=5(J48t(-2jC#-r2o~I!iNChiM+&pf-n}y2CXoMz;?x){5B%KzOewhY z`4*jswdp3|{tEI>GLi|Vy|}LN`v)|y*gHCsy-h-<0_MsGfKxSARD=OvHJ5xFI|MTg zK8Sf9T&SI9vEsmZI zon|uS+u)INl7ZclS`5j6$KdQm`Qk%O!2h46VcGlXK7=~x8E^cYWi%mj2E&_P_2 zm=_>wS3l^}?~T%LEsnOy6qf_1e;_SmI783-c(ULMeSg$Ky)f;EaX|abwVj!xqoW{q zZFM>0CWC^MmZk^NTBlE^xsh60T2i%~@TsYpf$d6`a3gc`O)5>7CWe=R_&-cqzr^Fo zkRpv608IKZ{C8BU-trSGeYK&HBIrJQC1S5@N)2yFJ*Pv&Z6kAw_^VR(alzc literal 0 HcmV?d00001 diff --git a/Tests/images/test_combine_multiline_mm_left.png b/Tests/images/test_combine_multiline_mm_left.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2fa7a3959230a7d438fa692d9114e5b06d293f GIT binary patch literal 4100 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 KSMo2rM*I^!sCPC1 literal 0 HcmV?d00001 diff --git a/Tests/images/test_combine_multiline_rm_left.png b/Tests/images/test_combine_multiline_rm_left.png new file mode 100644 index 0000000000000000000000000000000000000000..901410fab4ec39c441258accfc5a604be95afc6a GIT binary patch literal 4110 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 wpq)|Jux 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 literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index e47cb05c1ab..5dd062459c4 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -41,6 +41,7 @@ class TestImageFont: "getters": (13, 16), "mask": (107, 13), "multiline-anchor": 6, + "getlength": (36, 27, 27, 33), }, (">=2.7",): { "multiline": 6.2, @@ -48,6 +49,7 @@ class TestImageFont: "getters": (12, 16), "mask": (108, 13), "multiline-anchor": 4, + "getlength": (36, 21, 24, 33), }, "Default": { "multiline": 0.5, @@ -55,6 +57,7 @@ class TestImageFont: "getters": (12, 16), "mask": (108, 13), "multiline-anchor": 4, + "getlength": (36, 24, 24, 33), }, } @@ -198,6 +201,34 @@ 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 + ) + + if self.LAYOUT_ENGINE == ImageFont.LAYOUT_BASIC: + length = f.getlength(text, mode) + assert length == self.metrics["getlength"][length_basic_index] + else: + # disable kerning, kerning metrics changed + length = f.getlength(text, mode, features=["-kern"]) + assert length == length_raqm + def test_render_multiline(self): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -754,27 +785,42 @@ 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) + f = ImageFont.truetype( "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE ) + # test getbbox + assert f.getbbox(text, anchor=anchor) == (left, top, left + width, top + height) + + # test render im = Image.new("RGB", (200, 200), "white") d = ImageDraw.Draw(im) d.line(((0, 100), (200, 100)), "gray") @@ -831,6 +877,7 @@ 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.multiline_text((0, 0), "foo\nbar", anchor=anchor) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 0012d6bd0a6..b585a2aa24e 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -209,6 +209,57 @@ 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): + try: + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + assert ttf.getlength(text, mode, 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") + + @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) def test_anchor_ttb(anchor): if parse_version(features.version_module("freetype2")) < parse_version("2.5.1"): @@ -298,6 +349,37 @@ 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") + 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") @@ -308,6 +390,9 @@ 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") ) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index c2615f0db78..d338e9ab8d9 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -403,6 +403,15 @@ Methods Return the size of the given string, in pixels. + You can use :meth:`.FreeTypeFont.getlength` to measure text length + with 1/64 pixel precision. + + .. 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:`.FreeTypeFont.getbbox` with ``anchor='lt'`` instead. + + :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`. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 820dcc31db4..1379cf1f774 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -385,6 +385,9 @@ def multiline_text( elif anchor[1] in "tb": raise ValueError("anchor not supported for multiline text") + if font is None: + font = self.getfont() + widths = [] max_width = 0 lines = self._multiline_split(text) @@ -392,13 +395,12 @@ def multiline_text( self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing ) for line in lines: - line_width, line_height = self.textsize( + line_width = font.getlength( line, - font, + self.fontmode, direction=direction, features=features, language=language, - stroke_width=stroke_width, ) widths.append(line_width) max_width = max(max_width, line_width) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 1fe19ac528f..71e63b2138c 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -215,6 +215,140 @@ def getmetrics(self): """ 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 if 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 + + .. 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 + ` + Requires libraqm. + + :return: Width for horizontal, height for vertical text. + """ + return ( + self.font.getlength(text, mode == "1", 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 + if 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 + ` + 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 == "1", 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( self, text, direction=None, features=None, language=None, stroke_width=0 ): @@ -222,6 +356,15 @@ def getsize( Returns width and height (in pixels) of given text if rendered in font with 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 direction: Direction of the text. It can be 'rtl' (right to diff --git a/src/_imagingft.c b/src/_imagingft.c index 2435fbab4e7..05608f28f7c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -607,6 +607,49 @@ text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *featu 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? */ + 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|izOz:getlength", &string, &mask, &dir, &features, &lang)) { + return NULL; + } + + horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; + + count = text_layout(string, self, dir, features, lang, &glyph_info, mask); + 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* font_getsize(FontObject* self, PyObject* args) { @@ -1176,6 +1219,7 @@ font_dealloc(FontObject* self) static PyMethodDef font_methods[] = { {"render", (PyCFunction) font_render, METH_VARARGS}, {"getsize", (PyCFunction) font_getsize, METH_VARARGS}, + {"getlength", (PyCFunction) font_getlength, METH_VARARGS}, #if FREETYPE_MAJOR > 2 ||\ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) From 1551e120ae0a295ae98c65ce5d39c90656d79514 Mon Sep 17 00:00:00 2001 From: nulano Date: Wed, 7 Oct 2020 22:43:29 +0100 Subject: [PATCH 02/10] add textlength and textbbox to ImageDraw --- .../test_combine_multiline_lm_center.png | Bin 4132 -> 4160 bytes .../images/test_combine_multiline_lm_left.png | Bin 4117 -> 4223 bytes .../test_combine_multiline_lm_right.png | Bin 4139 -> 4170 bytes .../test_combine_multiline_mm_center.png | Bin 4107 -> 4243 bytes .../images/test_combine_multiline_mm_left.png | Bin 4100 -> 4216 bytes .../test_combine_multiline_mm_right.png | Bin 4102 -> 4247 bytes .../test_combine_multiline_rm_center.png | Bin 4105 -> 4160 bytes .../images/test_combine_multiline_rm_left.png | Bin 4110 -> 4149 bytes .../test_combine_multiline_rm_right.png | Bin 4101 -> 4215 bytes Tests/test_imagefont.py | 26 ++- Tests/test_imagefontctl.py | 24 ++- src/PIL/ImageDraw.py | 153 ++++++++++++++++-- 12 files changed, 185 insertions(+), 18 deletions(-) diff --git a/Tests/images/test_combine_multiline_lm_center.png b/Tests/images/test_combine_multiline_lm_center.png index f293f8d5f1581d7b588ac9bdd85d2e7a0c3dfd99..7b1e9c4e42f69874b4c508d16bc85527d5d902b7 100644 GIT binary patch literal 4160 zcmeHLXHb*r7G^;}v4E)PrA0)SCZN(J0iq((!$nF!q*-VxVQB$EV8sPgRuH6j3nERB z7D#|lMS2&31P}-B#H&Qs3h**_8bhXq6hxVX3u z8yZ}{$;I`{%f07*Zg54A{5p+`ONig_`c=z-bQ(GE;c-7s{R-Lo-ErPng={z8L&@x4 zVx5*{=ikQ3HaHEQ;(l|Hd&TJbh3|8*Z_JJtH=mk5VY#nwGg4SE|FmehK5uijxa5A> zFsE!cp$gu2V-t{_-F2MzaRFF))^<<}+{NG3g;+L%g~3eF1574UPPQDwsKu3*mgebS zHMSTWKaZaf{GA1{AB&4oix+!^a|WJyvL9Q@`&3ccX7X0IzkCu; z)fTv@ zzFa(hyRfYL7BK`XpWs2pGj#>EeW^t_krNUNoFFQd>KMGzl7_0QuB=o~Fk;fi(0y<8 zWYWDFzoYsz3-?`aJd@m=0qaRo$+ai==UI9Tf3(Mu{m1Gb^SJh8X0+Wqf_jo-@;*O* zv1YZKz}}{oRV;oj8Y5Nv#C)(m+jJBRX>;=qr9C0Z-rsipvO>4il_3U4C0CLf*Z)vgDB|x#{_nfrR0jtK%iRZl%|OIRYUA@- zZ*nO#VYm*wf3U--`@O`lm+rjS5hI1O<+OmAwDN}ULt%XlxT*HB(U zex_GqKmtIjqsB>tZ)KB3Dq)`hZlkrB4RC|hpyRv)js7y3K?c|4EAF;N>qD?>?_MFG z@x#tQkvw8{WiDOV4Klh<@1!4Ny!PF@nQwPvXrmR>mr}ZPJoV!V1#5A)BHJ<-3cMN2 zC8XCJHCism*|3Ndgk%y5`Uulq30ysL&o-e69)rp<5Lufv4V0v&_dRHarxW>mQzU0| zI%db7wOCNxt!RDg7T%>RH6jwbkdri<$mq}MNK~|A6JU;^Rogi;1(4V;LBFHK?6mboHQe%}ZhzZaj#}LC?Woj7c>Z zJ$3AoIx04>@DC0f?H>dz!#MGL8)Abx%xG6ACZV^U9h0d9xN{QCbhhrz_r6wBQ>(TM zVlG;loA&|8wzs#t(lnH9N=|cf7mbaKM3in{_L{7(t=whrFa#x_svFSIwLX0rM0yp2 zOa0NPrGo=|3`M-}imLh^vFl?crs0`>@$QXj|J?Gy;@RU4$M|0*$}@8OR=YIj98hm> zynx166ciK?(2PvGJ)K!uSz#1kwcT?7^z)~UQbedDWv-7N(dz67w8}B>j`;ZaVDQ}? zC7-r)Ni&HHLj}VOA}1vKlf)uDz~@{&QMg~64CYsD%#VQ4%5#j9X0z?kLv_RU0Q~cl2Qm5Of-yM#Z5mF ze#S->24}4e`1EKerC})vNW4ESC_B7IXJ_fR>CY6G4Y9(wxHv!!3Cz-`k&$!8iCVkM zP1P6;Pg1}R4L9c^W0o1bKH>^#+cb=J>RU3&($PlmQ}vziz50YtttL36BxLh@WP$>c zPB7uHSqxAEX(?XL!n){ILWU&Fe<@l5Ryx4Lcj!Z5p;lbOE7^uOBYFAx?WT@?LstCu zb&kZ4E!R?S*X9lHRtdDFrRBX`G-Dh~s~DyO5J$`1fm?Nqe*+E!YOL@WT3n7qH13O< zy$_nt3S|d+u6L^RXwAzn_(C~{SU~(61RKDD&J-0NcVHhQia63G_x^VzZbA62*F+sw z(1#>`B1~_!6bx!tWeOB#00~18S?ajO+W zf_qYOvqGSaic6yv1&=YUfBvJ4kzfHnJGHhjAXb-|IeVqV&4CS+B2Bu+YET*uo=tHD z#vlwc|@k*9aj!RD_mc<;_ zSTuJN237V0)7l+bnQlc>zZU&I=7{sOXNNOv0pZNFnS-VpWXpQ~GY52p`)hm``p<=~ zgOF01{0t25WA$t*DYqMu0j1DWRo!p0Q!uGA4Q~P$26D~VORia1>-rb)<$9hd5yf@{ z&Xc_THb$qN)nW!%`@#Gzh?in+9dTY68&zf67!E%Dr;2x3bpbSn)SqL#s_egKupJ#8 zxxn^c>qagq0s|n-Wnu)FUT3eC7~E#tu0|AVei2oFXyds}!%d8X5Q>d?{rQ$Tg+T%+pCVqa*IvjDe8`h(>YT~*6 zsZ~-zL7~HO608K>*ouXS!(yH_E1&r!b3PGtrPP=wz5A|FYRkPN)oocj8xu8wK1~mh zNF>8gt!Zm6ix{vn#ZtPBMB=~;7^9{P_=80*f=nGRvct`W!c5r=qZtes@hXH)NpjTb z{dU9;T~jSQsSsy)1De6%ZP1N4WLxFs?73bw9W8e8&h?&5?FSwnfC%eDNJs6pUfq+o zy{@wP>NcmMK)As-a3Qw28UDAxasyTdka9b%kkE&K=^!~3kRLl)tjWPV3!vh3V*Ash z(&NC1mVCi31_bJtjT#o!4qEF_CZ=b2PSmlUpHM((QsP-kfd+)t;?S+&CNmkd#ZT; z{J9;fw&L;K#MIQ({QUflaSQ`=cpGdEpaC#%cXxM?MnHkvYYQp?%ac*cb(cBiNMNG? zL;9t@S7thaV3CUaF%|~0w(EX@Re?3oiRcBV`j8~8FLd9<8ENMRuof>{DdxpHok$0Z z@6@=Q#Z4xYv&NBCZQyPcI)D1m&*LWqf3aZGvHpfgu+eMjX7JAdm!Tfw`p0WdVgCY0 CJA=Fc literal 4132 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 literal 4117 zcmeH~X;4#H8isMy78jsxG>pj7sDR2Ui|nLZWDpw@F_1{uTa+M6SR^43z(xfT0TlsR zLO=vmWDzjzL>pVSunUm@iR^?OLJ|WZnM2i7&CJiKnxFniZdG#cx#xWE_df4A@t0vX zQv2lhNk~XY* zFp1N__azN4>)vM^>s#Wqh;rIQm{aHQ{?N{OE}gbOTfjf*%p!T3W|1!zto=NEgZ9kc z-oBTSpWAo}v$#V-h=|#JSLNz2fB$~@UV{JZg2$j=&=Ci!Wc9tf{4kgc@aXkkn;CMn z2+HDhnJsH-YSKDuFwk8IZ`eI~R*{igz5dQ96#W&VXdx+%*K$hb$5@XYo{|u!kgxCm z_$;;~TUvyk$d*}yOG5tj&%ZB^LoCAATgBvp-_j|(KuNpu+q%2UE*udyPQ@ygmSa&rRj*nq zd_yev7_AGPA>obC(s!n~?SXPI2z2Ew5Z*#2FJhO4rlYbe3s0^NJ$Fm~& zokpyYx=`N=V(TN71UVfg^RPhe*h@dhSf5>Fyqn*cFLSTL*iWQyaOY7D4xfwQM3i^xS*u^tFmWyBBUgORU%+t$QmEPS4 z{lmuN)gf|n=}(_#2e+T9JtA)}h_!~%o}I~m{`{E5)F8METpcj+DKX0k3rfWY z4JrU#I!LGE*?k4R^wQCKyt1yx``lme#7Zlok;pWWj17iHo_-XvwaH&*QKNm6V|ec) z{|5pJkKXk4HN!7ulgaLGZcN6twiq1F7>F&7<+GxrSxu2877yO|c*n`!_gKYVt-Iyp zE2EOd7#iDk?CPY;>D2J~l4^9sA*cQ>X?4hlV>8GyH&<8JQAU1iCK%_fB&7CTv{(d+ zOyQlZC?qMujAN`zg`&;nWb?Jw+dFnn(=t*tU1|b7qY&G@Oe;MbFcJo&biLJU9l5b? zD?b0_RZ)&<@MLTKa#KWSrePdoE|(t;YAJ0t4b%!4ty9f%31=778Y?8Z?F;@|m=$2U z`cYOam?A1)?)$|kd zWFU(fZsCHCm^X~A3^uNMguOa>9xUdus19|_(a}*=Rkf^P?c(bp77ktE)#l_);*P~B zpN(P0RiDr-hTY0Q7xzfx~rS`Shji;H*Bj; zdYa<~X3(Ch(cce7M!5u8PjHK1BnBuJO_dzA!G39<55!4P-ML3vQ%h?Rh<`fub!n*^ z#cwE0%lQe)EOgGn(b16jZRHM6_+gLoLwUo~f^4X8r5RBK^Ebb}I9x3!C%3&C_7E`g z4Y%SAFq@ffGV4jRiW!=}Uukh*o!y_p&I+hIC1|+xOm~dz=Y~M^*W;JHih&!DNTgB~ z%*v{{xU4B0SY5qX>hHR_rKP2-%z#Gjpce^7GL7Vq;uGn$-^LSCLVu7}__n!BRlezG z6J+d_fH=o^4pcL%4b25TP>d{TZ>W2kMkYf8Mrr_5iptA%8@)Kwop9*Zt%*jUZE;PF zu|*@6)#O{&NNH-a47n%g7B;q6&4R(Vc?RCmq7>l=k%{v=sL9CoAEsD{g`Bp94jt+U zu$WZ|ajA9&mSb*`;jede=^5X80&DLsQ&B*t(T^%dP~$rSZ8ArAUYglJzNnq@rkA}4$$bkhFrTt6mRj{)H+9W z!8F$=Q{ZZqA=5Nl*bg>|@~Hw=bk(KpM?e)JOJikJ(aq@rAamU+387B9)z?7aWKzjF zlwZm*&Z2J<$&e+L(3*%ma>P=twric2Heog|P7i+Cr*b=ozKTs|rC&XG@IYEIX#s)0 za-~nW5Ae6E;SWn49c=G&Yq~3b^kC!ul5=3RI^1HWNyHYH&Yv65NZ4dm`5>kfmwi%G zQ@hPMv_vE3v%idv$z}i4M>!FV&E^4dy@F=ATrSEr)G>H)DBmV|vS1iXwcxZ{_@{XE zQVuAY8;9^X-O%pwF7K}FT96*=>t|XNIIAtn>d~tR6w1i+&%0Ey$SeVCGq@Mx-?3Lc zJ8(L~TdlIqm84aDbKo^CYjhdhFg`vG#KFxEYBhQRa`Q(*;1p((%a5AD+ML$}4qxF# zqLPJah8ytyV2QJ{Wh{fi0PzXz`NF%Sy}i&b<4Jlt9hiq^R;d&|n+IaTkXn4)LZRY9 zd220AUD=OO#V((R=a|>#eid70fJHggLM%h)J}XCkQ?ZB~32NyiL!pF(y|QX9QGzKp zJ2o~JXktn@jZ8^e4{SXK01G_!%q|*~4+IM~YD>S%_&7%{RYOCg$S$LgmU2#3N=nMm z&@eHb${lp>w&1(yZGO3yeE9I;>!m9TmEAeSwKg^z#0)dAS*o+419{nj+fRi%?(#Czj3OR4iBB_ zB4xmfj_BX8OEDvWIq9__#w)%>rV?DvJy>0P#V0-&TU}pln4G0ni zY6!wu^W@1{u)$&%k=@Yf@D8nN0}iQxLZMVttN|2l&J~w|2&lj>GeLgCWTEotlp7V(LCoC(0?EnA( diff --git a/Tests/images/test_combine_multiline_lm_right.png b/Tests/images/test_combine_multiline_lm_right.png index 73e2ca9d04d5b019cbcbe944060b764b2ea71aee..7caf5cb742a27f001106cc3ade926f2f177e71fb 100644 GIT binary patch literal 4170 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 diff --git a/Tests/images/test_combine_multiline_mm_center.png b/Tests/images/test_combine_multiline_mm_center.png index 46ec20173395cbe8f1f0327e488078017e3d0339..a859e9570c840c2f3ff8c1078a4c216707dfe86f 100644 GIT binary patch literal 4243 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 T-aH8YY>?KX<8lAr0RHTE literal 4107 zcmeHKS6EY79!Jr^23STI`Y4uFN)!Y^2z;(60zw?6lLQNGzz{kl5Kweg2BnB3hL#<{ z1f&P)9X^^75+MYUgh&g7DiDZ-5caS;`|UpL!#>Qs%|mi;PQH82zx>Mo#$C6wl-;Ae zM@mXc*6OOo4JoN@Nn4*?+rf%F=ht-b-D_oW(LN$$juYwAzp~l1SY}IeOPe@uu&k`4 zc_Qvsa$oKvz3klmgEc=ohCKYReeYd;D_U;u)KP`7GEsI;Z|VvA1l_dKrQSBCURj)7 zE7Q=Ra(QD+Y@|xDqjYd1)HD(s%}Bv!1ffhv%JSMb};{Zu-(M1#bv4tlF7D<{O1b$JB!WEi)#wGCCGVaE0;~pK}{1LMUoGGfMAvSR5@4Ul?AWuSHKWeqH8uoAB-#oI7W4Zx0XZ zIzHBsSe&14bL=1#D{!s~7~xY$S#7sdq+QDeQoHg>O1{z{jdi>7 zqeL`ZAL$;>3C*@lRM+$9{CF>iO!gY2dd4g0h^Dd=4gJ}#qH&DBU(|^DdXZ#?vtxXT z(}EDMRS14~>N`|{3TFp))M1w8qVs{ST)up{9=kIAx!e!@1B0$GV`F2Xk*m!9e$*g! zqPS7wS{pXU>rR(P2C}OH%e=ZT%)RTg6Mv1fEHOgb;I)aSn5Et~Hi^L|npd7hh!;Da z$g1E|e@aeSdQYRdVfT&4>bOpDWQZJfipN0ypMM(}RaH^18aZ zb{kJLRb>@9UDW6e(aOvKy7jTFr>Cb5R3Yg3kn;bpk#>8^%gX_}x@j~m9UT*NfwMlT5(TOq1{gb!ZKh8U!dsRN=F$m;H+C4Skup_Dc<->3r`jj8|`t zm5lrsfZ#Nvz&qHmmPfnqwm<6{9ZAx3{dP$G~D#%p=uTt;fn?B ztYin#Bpd4>wR)eAubJ@x**FlLYo$MK_R`6L-3l_F%Dm~}TpS^*{%gzL#42lmQQlz@ zz`j~gsaPyNKOCW%Sph^Za=v=xtQyZ%?Hr}Nm<6P;F;2mB(a_O<^ zlH<~q=(P!g;Iu9KOn)VsJ4uQ$gPLp+tLsbdPuY5#>+*6|B}PqT+w_Fsy^+Fs)F>w% z-LUq>gwkU)_6!u`9L^kiY)AZ+u!Uh|c`*8yK9r~6xzLc{pI$2VBg@}^0c z3H5&lkD1IQ7z6Zhy#mMjD6malI{HQ*B?~*P=pL0UX@?;Y2rH{D&_zNUCpmIQymZJk zlMj#zqmvh=O`~Z92)%A_qqIX^OIv$OY$tm%Zx#c2hAwq#4t`cMwgTG4b66%X7++O_ zVneW4iFi%ryuYdBER)5;~?)62!f~O}e*F%dx<@%*(cqMgk}x!t9H3b6){QA8PzQjm2PGzj-tG z>x@Q>co7(7Mive@ga{~YbrYC+nB{&0OhNl|m4&+Ho)fFR?A9ZccLMpKz?L`WpK}1n zt7>X$_*>*x=5(J48t(-2jC#-r2o~I!iNChiM+&pf-n}y2CXoMz;?x){5B%KzOewhY z`4*jswdp3|{tEI>GLi|Vy|}LN`v)|y*gHCsy-h-<0_MsGfKxSARD=OvHJ5xFI|MTg zK8Sf9T&SI9vEsmZI zon|uS+u)INl7ZclS`5j6$KdQm`Qk%O!2h46VcGlXK7=~x8E^cYWi%mj2E&_P_2 zm=_>wS3l^}?~T%LEsnOy6qf_1e;_SmI783-c(ULMeSg$Ky)f;EaX|abwVj!xqoW{q zZFM>0CWC^MmZk^NTBlE^xsh60T2i%~@TsYpf$d6`a3gc`O)5>7CWe=R_&-cqzr^Fo zkRpv608IKZ{C8BU-trSGeYK&HBIrJQC1S5@N)2yFJ*Pv&Z6kAw_^VR(alzc diff --git a/Tests/images/test_combine_multiline_mm_left.png b/Tests/images/test_combine_multiline_mm_left.png index ad2fa7a3959230a7d438fa692d9114e5b06d293f..aadb5191f0e7df6ae1838ca6b04745268de17728 100644 GIT binary patch literal 4216 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 sq)Epb{z}gH3sL0nTmDag9FofH^0IWV!Z literal 4102 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

Gc&a5H1zmVybWfk{$63_yW#%@t&MX| literal 4105 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 KSMo2rM*I^!sCPC1 diff --git a/Tests/images/test_combine_multiline_rm_left.png b/Tests/images/test_combine_multiline_rm_left.png index 901410fab4ec39c441258accfc5a604be95afc6a..b8c3b5b143d2b7b67116daaef4f7b3f119777494 100644 GIT binary patch literal 4149 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 literal 4101 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 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 5dd062459c4..adb75a1ff6d 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -221,12 +221,15 @@ def test_getlength(self, text, mode, font, size, length_basic_index, length_raqm "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 = f.getlength(text, mode) + length = d.textlength(text, f) assert length == self.metrics["getlength"][length_basic_index] else: # disable kerning, kerning metrics changed - length = f.getlength(text, mode, features=["-kern"]) + length = d.textlength(text, f, features=["-kern"]) assert length == length_raqm def test_render_multiline(self): @@ -813,20 +816,20 @@ def test_anchor(self, anchor, left, left_old, top): 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 ) - # test getbbox - assert f.getbbox(text, anchor=anchor) == (left, top, left + width, top + height) - - # test render im = Image.new("RGB", (200, 200), "white") d = ImageDraw.Draw(im) d.line(((0, 100), (200, 100)), "gray") 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) @@ -879,13 +882,24 @@ def test_anchor_invalid(self): 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("raqm") diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index b585a2aa24e..7fa5f76abab 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -222,10 +222,12 @@ def test_language(): ids=("None", "ltr", "rtl2", "rtl", "ttb"), ) def test_getlength(mode, text, direction, expected): - try: - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + im = Image.new(mode, (1, 1), 0) + d = ImageDraw.Draw(im) - assert ttf.getlength(text, mode, direction) == expected + try: + assert d.textlength(text, ttf, direction) == expected except ValueError as ex: if ( direction == "ttb" @@ -374,6 +376,8 @@ def test_combine_multiline(anchor, align): 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: @@ -396,14 +400,28 @@ def test_anchor_invalid_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"), + ) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 1379cf1f774..822ca3edf1b 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -385,9 +385,6 @@ def multiline_text( elif anchor[1] in "tb": raise ValueError("anchor not supported for multiline text") - if font is None: - font = self.getfont() - widths = [] max_width = 0 lines = self._multiline_split(text) @@ -395,12 +392,8 @@ def multiline_text( self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing ) for line in lines: - line_width = font.getlength( - line, - self.fontmode, - direction=direction, - features=features, - language=language, + line_width = self.textlength( + line, font, direction=direction, features=features, language=language ) widths.append(line_width) max_width = max(max_width, line_width) @@ -487,6 +480,148 @@ def multiline_textsize( max_width = max(max_width, line_width) return max_width, len(lines) * line_spacing - spacing + def textlength(self, text, font=None, direction=None, features=None, language=None): + """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 font is None: + font = self.getfont() + try: + return font.getlength(text, self.fontmode, 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, + ): + """Get the bounding box of a given string, in pixels.""" + if self._multiline_check(text): + return self.multiline_textbbox( + xy, + text, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + ) + + if font is None: + font = self.getfont() + bbox = font.getbbox( + text, self.fontmode, 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, + ): + 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 + ) + 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, + ) + 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): """ From c80f12392386cb09c0ab2703333b78a7037410de Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 8 Oct 2020 20:30:39 +0100 Subject: [PATCH 03/10] document ImageDraw.textlength and ImageDraw.textbbox --- docs/reference/ImageDraw.rst | 175 ++++++++++++++++++++++++++++++++++- src/PIL/ImageFont.py | 9 ++ 2 files changed, 181 insertions(+), 3 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index d338e9ab8d9..bed20266a56 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -403,13 +403,14 @@ Methods Return the size of the given string, in pixels. - You can use :meth:`.FreeTypeFont.getlength` to measure text length - with 1/64 pixel precision. + 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:`.FreeTypeFont.getbbox` with ``anchor='lt'`` instead. + to use :meth:`textbbox` with ``anchor='lt'`` instead. :param text: Text to be measured. If it contains any newline characters, @@ -451,6 +452,16 @@ Methods 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 font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param spacing: The number of pixels between lines. @@ -485,6 +496,164 @@ Methods .. versionadded:: 6.2.0 +.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None) + + Returns length (in pixels with 1/64 precision) of given text if 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. + + This method uses :meth:`.FreeTypeFont.getlength` internally and falls back + on :meth:`textlength` for non-TrueType fonts. + + 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. + +.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0) + + Returns bounding box (in pixels) of given text relative to given anchor + if 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. + +.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0) + + Returns bounding box (in pixels) of given text relative to given anchor + if 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. + .. py:method:: getdraw(im=None, hints=None) .. warning:: This method is experimental. diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 71e63b2138c..df79efee22d 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -248,6 +248,15 @@ def getlength(self, text, mode="", direction=None, features=None, language=None) 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. From 9b5931f2126b6616061a195da5a78e99ae44aa45 Mon Sep 17 00:00:00 2001 From: nulano Date: Fri, 9 Oct 2020 16:43:10 +0200 Subject: [PATCH 04/10] typo Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageFont.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index df79efee22d..18a14d4fe19 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -308,7 +308,7 @@ def getbbox( Returns bounding box (in pixels) of given text relative to given anchor if rendered in font with provided direction, features, and language. - Use :py:meth`getlength()` to get the offset of following text with + 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. From 067a2543668ef95951b330db5458a84666b66be1 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 11 Oct 2020 21:55:32 +0200 Subject: [PATCH 05/10] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- docs/reference/ImageDraw.rst | 8 +++----- src/PIL/ImageFont.py | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index bed20266a56..2af8f49c821 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -411,8 +411,6 @@ Methods 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, the text is passed on to :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. @@ -498,7 +496,7 @@ Methods .. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None) - Returns length (in pixels with 1/64 precision) of given text if rendered + 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. @@ -567,7 +565,7 @@ Methods .. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0) Returns bounding box (in pixels) of given text relative to given anchor - if rendered in font with provided direction, features, and language. + 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 @@ -615,7 +613,7 @@ Methods .. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0) Returns bounding box (in pixels) of given text relative to given anchor - if rendered in font with provided direction, features, and language. + 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 diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 18a14d4fe19..4ca9cc656ac 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -217,7 +217,7 @@ def getmetrics(self): def getlength(self, text, mode="", direction=None, features=None, language=None): """ - Returns length (in pixels with 1/64 precision) of given text if rendered + 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. @@ -306,7 +306,7 @@ def getbbox( ): """ Returns bounding box (in pixels) of given text relative to given anchor - if rendered in font with provided direction, features, and language. + 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 From be4d5221adfe7d514305cbc8e5b542bd94889917 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 11 Oct 2020 21:21:38 +0100 Subject: [PATCH 06/10] remove extra information --- docs/reference/ImageDraw.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 2af8f49c821..4f09a8fba50 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -505,9 +505,6 @@ Methods The result is returned as a float; it is a whole number if using basic layout. - This method uses :meth:`.FreeTypeFont.getlength` internally and falls back - on :meth:`textlength` for non-TrueType fonts. - 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. From 845bfda3ff2cf4a91b57511e0d220c00d9f4a1ef Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 12 Oct 2020 05:08:58 +0100 Subject: [PATCH 07/10] fix docs formatting --- docs/reference/ImageDraw.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 4f09a8fba50..79046a6bf02 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -411,6 +411,7 @@ Methods 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, the text is passed on to :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. From d7a08cbd15933b733f737f20987fea188d5bf1e5 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 12 Oct 2020 16:05:19 +0100 Subject: [PATCH 08/10] add color support to new text measuring functions --- docs/reference/ImageDraw.rst | 9 ++++++--- src/PIL/ImageDraw.py | 32 ++++++++++++++++++++++++++++---- src/PIL/ImageFont.py | 6 ++---- src/_imagingft.c | 9 +++++++-- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index a112e8d7b4a..6052d2833fb 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -513,7 +513,7 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None) +.. 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. @@ -577,8 +577,9 @@ Methods 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) +.. 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. @@ -625,8 +626,9 @@ Methods 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) +.. 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. @@ -667,6 +669,7 @@ Methods 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) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index c2c3c14c0b6..b823be9a2aa 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -500,15 +500,26 @@ def multiline_textsize( max_width = max(max_width, line_width) return max_width, len(lines) * line_spacing - spacing - def textlength(self, text, font=None, direction=None, features=None, language=None): + 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, self.fontmode, direction, features, language) + return font.getlength(text, mode, direction, features, language) except AttributeError: size = self.textsize( text, font, direction=direction, features=features, language=language @@ -529,8 +540,12 @@ def textbbox( 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, @@ -543,12 +558,14 @@ def textbbox( 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, self.fontmode, direction, features, language, stroke_width, anchor + 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] @@ -564,6 +581,7 @@ def multiline_textbbox( features=None, language=None, stroke_width=0, + embedded_color=False, ): if direction == "ttb": raise ValueError("ttb direction is unsupported for multiline text") @@ -583,7 +601,12 @@ def multiline_textbbox( ) for line in lines: line_width = self.textlength( - line, font, direction=direction, features=features, language=language + line, + font, + direction=direction, + features=features, + language=language, + embedded_color=embedded_color, ) widths.append(line_width) max_width = max(max_width, line_width) @@ -625,6 +648,7 @@ def multiline_textbbox( features=features, language=language, stroke_width=stroke_width, + embedded_color=embedded_color, ) if bbox is None: bbox = bbox_line diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c83e8fa863f..89d180ce8cb 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -290,9 +290,7 @@ def getlength(self, text, mode="", direction=None, features=None, language=None) :return: Width for horizontal, height for vertical text. """ - return ( - self.font.getlength(text, mode == "1", direction, features, language) / 64 - ) + return self.font.getlength(text, mode, direction, features, language) / 64 def getbbox( self, @@ -352,7 +350,7 @@ def getbbox( :return: ``(left, top, right, bottom)`` bounding box """ size, offset = self.font.getsize( - text, mode == "1", direction, features, language, anchor + 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 diff --git a/src/_imagingft.c b/src/_imagingft.c index bfbbeef6bfe..f082fab8056 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -624,6 +624,8 @@ font_getlength(FontObject* self, PyObject* args) 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; @@ -631,13 +633,16 @@ font_getlength(FontObject* self, PyObject* args) /* calculate size and bearing for a given string */ - if (!PyArg_ParseTuple(args, "O|izOz:getlength", &string, &mask, &dir, &features, &lang)) { + if (!PyArg_ParseTuple(args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) { return NULL; } horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - count = text_layout(string, self, dir, features, lang, &glyph_info, mask); + 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; } From cb3f6c09e70901ed73c5f5e1318dbdba68702ad2 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 12 Oct 2020 16:08:16 +0100 Subject: [PATCH 09/10] add spaces between pytest.mark.parametrize parameters --- Tests/test_imagefont.py | 6 +++--- Tests/test_imagefontctl.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index d2a53e7e7a0..5e83ddefd78 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -203,7 +203,7 @@ def test_textsize_equal(self): assert_image_similar(im, target_img, self.metrics["textsize"]) @pytest.mark.parametrize( - "text,mode,font,size,length_basic_index,length_raqm", + "text, mode, font, size, length_basic_index, length_raqm", ( # basic test ("text", "L", "FreeMono.ttf", 15, 0, 36), @@ -789,7 +789,7 @@ def test_variation_set_by_axes(self): self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) @pytest.mark.parametrize( - "anchor,left,left_old,top", + "anchor, left, left_old, top", ( # test horizontal anchors ("ls", 0, 0, -36), @@ -835,7 +835,7 @@ def test_anchor(self, anchor, left, left_old, top): assert_image_similar(im, expected, 7) @pytest.mark.parametrize( - "anchor,align", + "anchor, align", ( # test horizontal anchors ("lm", "left"), diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index f4ab37c54ac..f538bf71bd1 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -215,7 +215,7 @@ def test_language(): @pytest.mark.parametrize("mode", ("L", "1")) @pytest.mark.parametrize( - "text,direction,expected", + "text, direction, expected", ( ("سلطنة عمان Oman", None, 173.703125), ("سلطنة عمان Oman", "ltr", 173.703125), @@ -327,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 ( @@ -356,7 +356,7 @@ def test_combine(name, text, dir, anchor, epsilon): @pytest.mark.parametrize( - "anchor,align", + "anchor, align", ( ("lm", "left"), # pass with getsize ("lm", "center"), # fail at 2.12 From c277ff430417fa7d117851a609b5c7cccda5a5b9 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 12 Oct 2020 16:14:51 +0100 Subject: [PATCH 10/10] remove old PyPy compatibility code --- Tests/test_imagefont.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 131faa96be3..7824b7f5364 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -931,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( @@ -952,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(