From 0f4d99e723eaa253bba7c7b07cd4911152c77d1b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 19 Oct 2017 15:05:45 -0400 Subject: [PATCH 1/3] Apply hinting factor rcParam in all cases. This is only explicitly used in Agg, so any other users get the default in the C++ code. Thus if you modify this setting and use some other backend, you get very weird behaviour. Positioning sometimes breaks and glyphs are shrunk or cropped. --- lib/matplotlib/backends/backend_agg.py | 4 +--- lib/matplotlib/font_manager.py | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 48ac40051f24..739a26b38d49 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -267,9 +267,7 @@ def _get_agg_font(self, prop): Get the font for text instance t, cacheing for efficiency """ fname = findfont(prop) - font = get_font( - fname, - hinting_factor=rcParams['text.hinting_factor']) + font = get_font(fname) font.clear() size = prop.get_size_in_points() diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 9602db76c9fd..202ee9a3adfc 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1366,7 +1366,12 @@ def is_opentype_cff_font(filename): _fmcache = None -get_font = lru_cache(64)(ft2font.FT2Font) +_get_font = lru_cache(64)(ft2font.FT2Font) + +def get_font(filename, hinting_factor=None): + if hinting_factor is None: + hinting_factor = rcParams['text.hinting_factor'] + return _get_font(filename, hinting_factor) # The experimental fontconfig-based backend. From cd371192aaf36ec34dc955cbd24c7389ce137936 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 20 Oct 2017 04:47:12 -0400 Subject: [PATCH 2/3] Un-static-ify FreeType font transforms. Static initializers are only applied the first time, so any subsequent calls with alternate hinting_factor would use the first value. FreeType copies the matrix values into an internal structure, so these do not really need to be static. --- src/ft2font.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 6160e51636de..08d00e4646a3 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -515,7 +515,7 @@ FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_) : image(), face( face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; } - static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; + FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); } @@ -548,7 +548,7 @@ void FT2Font::set_size(double ptsize, double dpi) { int error = FT_Set_Char_Size( face, (long)(ptsize * 64), 0, (unsigned int)(dpi * hinting_factor), (unsigned int)dpi); - static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; + FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 }; FT_Set_Transform(face, &transform, 0); if (error) { From 6a685d2d3e6be4271db55c56eceb90c2a973c511 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 20 Oct 2017 20:45:37 -0400 Subject: [PATCH 3/3] Add tests that hinting_factor works. --- lib/matplotlib/tests/test_font_manager.py | 20 ++++++++++++++++++++ lib/matplotlib/tests/test_text.py | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index f403f4fc44a7..239757369838 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -7,6 +7,7 @@ import tempfile import warnings +import numpy as np import pytest from matplotlib.font_manager import ( @@ -86,3 +87,22 @@ def test_otf(): @pytest.mark.skipif(not has_fclist, reason='no fontconfig installed') def test_get_fontconfig_fonts(): assert len(get_fontconfig_fonts()) > 1 + + +@pytest.mark.parametrize('factor', [2, 4, 6, 8]) +def test_hinting_factor(factor): + font = findfont(FontProperties(family=["sans-serif"])) + + font1 = get_font(font, hinting_factor=1) + font1.clear() + font1.set_size(12, 100) + font1.set_text('abc') + expected = font1.get_width_height() + + hinted_font = get_font(font, hinting_factor=factor) + hinted_font.clear() + hinted_font.set_size(12, 100) + hinted_font.set_text('abc') + # Check that hinting only changes text layout by a small (10%) amount. + np.testing.assert_allclose(hinted_font.get_width_height(), expected, + rtol=0.1) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 01d692f76f61..a609dc208c9e 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -2,6 +2,8 @@ unicode_literals) import six + +import io import warnings import numpy as np @@ -448,3 +450,17 @@ def test_nonfinite_pos(): ax.text(0, np.nan, 'nan') ax.text(np.inf, 0, 'inf') fig.canvas.draw() + + +def test_hinting_factor_backends(): + plt.rcParams['text.hinting_factor'] = 1 + fig = plt.figure() + t = fig.text(0.5, 0.5, 'some text') + + fig.savefig(io.BytesIO(), format='svg') + expected = t.get_window_extent().intervalx + + fig.savefig(io.BytesIO(), format='png') + # Backends should apply hinting_factor consistently (within 10%). + np.testing.assert_allclose(t.get_window_extent().intervalx, expected, + rtol=0.1)