Skip to content

Commit a33bf58

Browse files
committed
Merge pull request #5295 from mdboom/too-many-open-files
MNT: Reduce number of font file handles opened
2 parents 0b4a897 + 5e93dfc commit a33bf58

13 files changed

+71
-79
lines changed

INSTALL

+4
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ libpng 1.2 (or later)
214214
``cycler`` 0.9 or later
215215
Composable cycle class used for constructing style-cycles
216216

217+
`functools32`
218+
Required for compatibility if running on versions of Python before
219+
Python 3.2.
220+
217221

218222
Optional GUI framework
219223
^^^^^^^^^^^^^^^^^^^^^^

lib/matplotlib/backends/backend_agg.py

+7-15
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
FigureManagerBase, FigureCanvasBase
3333
from matplotlib.cbook import is_string_like, maxdict, restrict_dict
3434
from matplotlib.figure import Figure
35-
from matplotlib.font_manager import findfont
36-
from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
35+
from matplotlib.font_manager import findfont, get_font
36+
from matplotlib.ft2font import LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
3737
LOAD_DEFAULT, LOAD_NO_AUTOHINT
3838
from matplotlib.mathtext import MathTextParser
3939
from matplotlib.path import Path
@@ -82,7 +82,6 @@ class RendererAgg(RendererBase):
8282
# renderer at a time
8383

8484
lock = threading.RLock()
85-
_fontd = maxdict(50)
8685
def __init__(self, width, height, dpi):
8786
if __debug__: verbose.report('RendererAgg.__init__', 'debug-annoying')
8887
RendererBase.__init__(self)
@@ -192,6 +191,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
192191

193192
flags = get_hinting_flag()
194193
font = self._get_agg_font(prop)
194+
195195
if font is None: return None
196196
if len(s) == 1 and ord(s) > 127:
197197
font.load_char(ord(s), flags=flags)
@@ -273,18 +273,10 @@ def _get_agg_font(self, prop):
273273
if __debug__: verbose.report('RendererAgg._get_agg_font',
274274
'debug-annoying')
275275

276-
key = hash(prop)
277-
font = RendererAgg._fontd.get(key)
278-
279-
if font is None:
280-
fname = findfont(prop)
281-
font = RendererAgg._fontd.get(fname)
282-
if font is None:
283-
font = FT2Font(
284-
fname,
285-
hinting_factor=rcParams['text.hinting_factor'])
286-
RendererAgg._fontd[fname] = font
287-
RendererAgg._fontd[key] = font
276+
fname = findfont(prop)
277+
font = get_font(
278+
fname,
279+
hinting_factor=rcParams['text.hinting_factor'])
288280

289281
font.clear()
290282
size = prop.get_size_in_points()

lib/matplotlib/backends/backend_pdf.py

+6-14
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
from matplotlib.cbook import Bunch, is_string_like, \
3636
get_realpath_and_stat, is_writable_file_like, maxdict
3737
from matplotlib.figure import Figure
38-
from matplotlib.font_manager import findfont, is_opentype_cff_font
38+
from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font
3939
from matplotlib.afm import AFM
4040
import matplotlib.type1font as type1font
4141
import matplotlib.dviread as dviread
42-
from matplotlib.ft2font import FT2Font, FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \
42+
from matplotlib.ft2font import FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \
4343
LOAD_NO_HINTING, KERNING_UNFITTED
4444
from matplotlib.mathtext import MathTextParser
4545
from matplotlib.transforms import Affine2D, BboxBase
@@ -757,7 +757,7 @@ def createType1Descriptor(self, t1font, fontfile):
757757
if 0:
758758
flags |= 1 << 18
759759

760-
ft2font = FT2Font(fontfile)
760+
ft2font = get_font(fontfile)
761761

762762
descriptor = {
763763
'Type': Name('FontDescriptor'),
@@ -817,7 +817,7 @@ def _get_xobject_symbol_name(self, filename, symbol_name):
817817
def embedTTF(self, filename, characters):
818818
"""Embed the TTF font from the named file into the document."""
819819

820-
font = FT2Font(filename)
820+
font = get_font(filename)
821821
fonttype = rcParams['pdf.fonttype']
822822

823823
def cvt(length, upe=font.units_per_EM, nearest=True):
@@ -1526,7 +1526,6 @@ def writeTrailer(self):
15261526

15271527

15281528
class RendererPdf(RendererBase):
1529-
truetype_font_cache = maxdict(50)
15301529
afm_font_cache = maxdict(50)
15311530

15321531
def __init__(self, file, image_dpi):
@@ -2126,15 +2125,8 @@ def _get_font_afm(self, prop):
21262125
return font
21272126

21282127
def _get_font_ttf(self, prop):
2129-
key = hash(prop)
2130-
font = self.truetype_font_cache.get(key)
2131-
if font is None:
2132-
filename = findfont(prop)
2133-
font = self.truetype_font_cache.get(filename)
2134-
if font is None:
2135-
font = FT2Font(filename)
2136-
self.truetype_font_cache[filename] = font
2137-
self.truetype_font_cache[key] = font
2128+
filename = findfont(prop)
2129+
font = get_font(filename)
21382130
font.clear()
21392131
font.set_size(prop.get_size_in_points(), 72)
21402132
return font

lib/matplotlib/backends/backend_pgf.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,9 @@
3636
system_fonts = []
3737
if sys.platform.startswith('win'):
3838
from matplotlib import font_manager
39-
from matplotlib.ft2font import FT2Font
4039
for f in font_manager.win32InstalledFonts():
4140
try:
42-
system_fonts.append(FT2Font(str(f)).family_name)
41+
system_fonts.append(font_manager.get_font(str(f)).family_name)
4342
except:
4443
pass # unknown error, skip this font
4544
else:

lib/matplotlib/backends/backend_ps.py

+5-13
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def _fn_name(): return sys._getframe(1).f_code.co_name
2828
is_writable_file_like, maxdict, file_requires_unicode
2929
from matplotlib.figure import Figure
3030

31-
from matplotlib.font_manager import findfont, is_opentype_cff_font
32-
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING
31+
from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font
32+
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
3333
from matplotlib.ttconv import convert_ttf_to_ps
3434
from matplotlib.mathtext import MathTextParser
3535
from matplotlib._mathtext_data import uni2type1
@@ -199,7 +199,6 @@ class RendererPS(RendererBase):
199199
context instance that controls the colors/styles.
200200
"""
201201

202-
fontd = maxdict(50)
203202
afmfontd = maxdict(50)
204203

205204
def __init__(self, width, height, pswriter, imagedpi=72):
@@ -393,15 +392,8 @@ def _get_font_afm(self, prop):
393392
return font
394393

395394
def _get_font_ttf(self, prop):
396-
key = hash(prop)
397-
font = self.fontd.get(key)
398-
if font is None:
399-
fname = findfont(prop)
400-
font = self.fontd.get(fname)
401-
if font is None:
402-
font = FT2Font(fname)
403-
self.fontd[fname] = font
404-
self.fontd[key] = font
395+
fname = findfont(prop)
396+
font = get_font(fname)
405397
font.clear()
406398
size = prop.get_size_in_points()
407399
font.set_size(size, 72.0)
@@ -1145,7 +1137,7 @@ def print_figure_impl():
11451137
if not rcParams['ps.useafm']:
11461138
for font_filename, chars in six.itervalues(ps_renderer.used_characters):
11471139
if len(chars):
1148-
font = FT2Font(font_filename)
1140+
font = get_font(font_filename)
11491141
cmap = font.get_charmap()
11501142
glyph_ids = []
11511143
for c in chars:

lib/matplotlib/backends/backend_svg.py

+5-12
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
from matplotlib.cbook import is_string_like, is_writable_file_like, maxdict
2020
from matplotlib.colors import rgb2hex
2121
from matplotlib.figure import Figure
22-
from matplotlib.font_manager import findfont, FontProperties
23-
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING
22+
from matplotlib.font_manager import findfont, FontProperties, get_font
23+
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
2424
from matplotlib.mathtext import MathTextParser
2525
from matplotlib.path import Path
2626
from matplotlib import _path
@@ -326,15 +326,8 @@ def _make_flip_transform(self, transform):
326326
.translate(0.0, self.height))
327327

328328
def _get_font(self, prop):
329-
key = hash(prop)
330-
font = self.fontd.get(key)
331-
if font is None:
332-
fname = findfont(prop)
333-
font = self.fontd.get(fname)
334-
if font is None:
335-
font = FT2Font(fname)
336-
self.fontd[fname] = font
337-
self.fontd[key] = font
329+
fname = findfont(prop)
330+
font = get_font(fname)
338331
font.clear()
339332
size = prop.get_size_in_points()
340333
font.set_size(size, 72.0)
@@ -495,7 +488,7 @@ def _write_svgfonts(self):
495488
writer = self.writer
496489
writer.start('defs')
497490
for font_fname, chars in six.iteritems(self._fonts):
498-
font = FT2Font(font_fname)
491+
font = get_font(font_fname)
499492
font.set_size(72, 72)
500493
sfnt = font.get_sfnt()
501494
writer.start('font', id=sfnt[(1, 0, 0, 4)])

lib/matplotlib/font_manager.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@
6363
from matplotlib.fontconfig_pattern import \
6464
parse_fontconfig_pattern, generate_fontconfig_pattern
6565

66+
try:
67+
from functools import lru_cache
68+
except ImportError:
69+
from functools32 import lru_cache
70+
71+
6672
USE_FONTCONFIG = False
6773
verbose = matplotlib.verbose
6874

@@ -733,7 +739,7 @@ def get_name(self):
733739
Return the name of the font that best matches the font
734740
properties.
735741
"""
736-
return ft2font.FT2Font(findfont(self)).family_name
742+
return get_font(findfont(self)).family_name
737743

738744
def get_style(self):
739745
"""
@@ -1336,7 +1342,6 @@ def findfont(self, prop, fontext='ttf', directory=None,
13361342
_lookup_cache[fontext].set(prop, result)
13371343
return result
13381344

1339-
13401345
_is_opentype_cff_font_cache = {}
13411346
def is_opentype_cff_font(filename):
13421347
"""
@@ -1357,6 +1362,10 @@ def is_opentype_cff_font(filename):
13571362
fontManager = None
13581363
_fmcache = None
13591364

1365+
1366+
get_font = lru_cache(64)(ft2font.FT2Font)
1367+
1368+
13601369
# The experimental fontconfig-based backend.
13611370
if USE_FONTCONFIG and sys.platform != 'win32':
13621371
import re

lib/matplotlib/mathtext.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
from matplotlib.afm import AFM
5151
from matplotlib.cbook import Bunch, get_realpath_and_stat, \
5252
is_string_like, maxdict
53-
from matplotlib.ft2font import FT2Font, FT2Image, KERNING_DEFAULT, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
54-
from matplotlib.font_manager import findfont, FontProperties
53+
from matplotlib.ft2font import FT2Image, KERNING_DEFAULT, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
54+
from matplotlib.font_manager import findfont, FontProperties, get_font
5555
from matplotlib._mathtext_data import latex_to_bakoma, \
5656
latex_to_standard, tex2uni, latex_to_cmex, stix_virtual_fonts
5757
from matplotlib import get_data_path, rcParams
@@ -563,7 +563,7 @@ def __init__(self, default_font_prop, mathtext_backend):
563563
self._fonts = {}
564564

565565
filename = findfont(default_font_prop)
566-
default_font = self.CachedFont(FT2Font(filename))
566+
default_font = self.CachedFont(get_font(filename))
567567
self._fonts['default'] = default_font
568568
self._fonts['regular'] = default_font
569569

@@ -576,10 +576,9 @@ def _get_font(self, font):
576576
basename = self.fontmap[font]
577577
else:
578578
basename = font
579-
580579
cached_font = self._fonts.get(basename)
581580
if cached_font is None and os.path.exists(basename):
582-
font = FT2Font(basename)
581+
font = get_font(basename)
583582
cached_font = self.CachedFont(font)
584583
self._fonts[basename] = cached_font
585584
self._fonts[font.postscript_name] = cached_font

lib/matplotlib/tests/__init__.py

-6
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ def setup():
4949
rcParams['text.hinting'] = False
5050
rcParams['text.hinting_factor'] = 8
5151

52-
# Clear the font caches. Otherwise, the hinting mode can travel
53-
# from one test to another.
54-
backend_agg.RendererAgg._fontd.clear()
55-
backend_pdf.RendererPdf.truetype_font_cache.clear()
56-
backend_svg.RendererSVG.fontd.clear()
57-
5852

5953
def assert_str_equal(reference_str, test_str,
6054
format_str=('String {str1} and {str2} do not '

lib/matplotlib/textpath.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
from matplotlib.path import Path
1414
from matplotlib import rcParams
1515
import matplotlib.font_manager as font_manager
16-
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING
16+
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
1717
from matplotlib.ft2font import LOAD_TARGET_LIGHT
1818
from matplotlib.mathtext import MathTextParser
1919
import matplotlib.dviread as dviread
20-
from matplotlib.font_manager import FontProperties
20+
from matplotlib.font_manager import FontProperties, get_font
2121
from matplotlib.transforms import Affine2D
2222
from matplotlib.externals.six.moves.urllib.parse import quote as urllib_quote
2323

@@ -54,7 +54,7 @@ def _get_font(self, prop):
5454
find a ttf font.
5555
"""
5656
fname = font_manager.findfont(prop)
57-
font = FT2Font(fname)
57+
font = get_font(fname)
5858
font.set_size(self.FONT_SCALE, self.DPI)
5959

6060
return font
@@ -334,7 +334,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
334334
font_bunch = self.tex_font_map[dvifont.texname]
335335

336336
if font_and_encoding is None:
337-
font = FT2Font(font_bunch.filename)
337+
font = get_font(font_bunch.filename)
338338

339339
for charmap_name, charmap_code in [("ADOBE_CUSTOM",
340340
1094992451),

lib/mpl_toolkits/tests/__init__.py

-6
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ def setup():
4949
rcParams['text.hinting'] = False
5050
rcParams['text.hinting_factor'] = 8
5151

52-
# Clear the font caches. Otherwise, the hinting mode can travel
53-
# from one test to another.
54-
backend_agg.RendererAgg._fontd.clear()
55-
backend_pdf.RendererPdf.truetype_font_cache.clear()
56-
backend_svg.RendererSVG.fontd.clear()
57-
5852

5953
def assert_str_equal(reference_str, test_str,
6054
format_str=('String {str1} and {str2} do not '

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
'Required dependencies and extensions',
6868
setupext.Numpy(),
6969
setupext.Dateutil(),
70+
setupext.FuncTools32(),
7071
setupext.Pytz(),
7172
setupext.Cycler(),
7273
setupext.Tornado(),

setupext.py

+23
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,29 @@ def get_install_requires(self):
12211221
return [dateutil]
12221222

12231223

1224+
class FuncTools32(SetupPackage):
1225+
name = "functools32"
1226+
1227+
def check(self):
1228+
if sys.version_info[:2] < (3, 2):
1229+
try:
1230+
import functools32
1231+
except ImportError:
1232+
return (
1233+
"functools32 was not found. It is required for for"
1234+
"python versions prior to 3.2")
1235+
1236+
return "using functools32"
1237+
else:
1238+
return "Not required"
1239+
1240+
def get_install_requires(self):
1241+
if sys.version_info[:2] < (3, 2):
1242+
return ['functools32']
1243+
else:
1244+
return []
1245+
1246+
12241247
class Tornado(OptionalPackage):
12251248
name = "tornado"
12261249

0 commit comments

Comments
 (0)