Skip to content

Reduce number of font file handles opened #5295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ libpng 1.2 (or later)
``cycler`` 0.9 or later
Composable cycle class used for constructing style-cycles

`functools32`
Required for compatibility if running on versions of Python before
Python 3.2.


Optional GUI framework
^^^^^^^^^^^^^^^^^^^^^^
Expand Down
22 changes: 7 additions & 15 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
FigureManagerBase, FigureCanvasBase
from matplotlib.cbook import is_string_like, maxdict, restrict_dict
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont
from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
from matplotlib.font_manager import findfont, get_font
from matplotlib.ft2font import LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
LOAD_DEFAULT, LOAD_NO_AUTOHINT
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
Expand Down Expand Up @@ -81,7 +81,6 @@ class RendererAgg(RendererBase):
# renderer at a time

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

flags = get_hinting_flag()
font = self._get_agg_font(prop)

if font is None: return None
if len(s) == 1 and ord(s) > 127:
font.load_char(ord(s), flags=flags)
Expand Down Expand Up @@ -272,18 +272,10 @@ def _get_agg_font(self, prop):
if __debug__: verbose.report('RendererAgg._get_agg_font',
'debug-annoying')

key = hash(prop)
font = RendererAgg._fontd.get(key)

if font is None:
fname = findfont(prop)
font = RendererAgg._fontd.get(fname)
if font is None:
font = FT2Font(
fname,
hinting_factor=rcParams['text.hinting_factor'])
RendererAgg._fontd[fname] = font
RendererAgg._fontd[key] = font
fname = findfont(prop)
font = get_font(
fname,
hinting_factor=rcParams['text.hinting_factor'])

font.clear()
size = prop.get_size_in_points()
Expand Down
20 changes: 6 additions & 14 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@
from matplotlib.cbook import Bunch, is_string_like, \
get_realpath_and_stat, is_writable_file_like, maxdict
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont, is_opentype_cff_font
from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font
from matplotlib.afm import AFM
import matplotlib.type1font as type1font
import matplotlib.dviread as dviread
from matplotlib.ft2font import FT2Font, FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \
from matplotlib.ft2font import FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \
LOAD_NO_HINTING, KERNING_UNFITTED
from matplotlib.mathtext import MathTextParser
from matplotlib.transforms import Affine2D, BboxBase
Expand Down Expand Up @@ -757,7 +757,7 @@ def createType1Descriptor(self, t1font, fontfile):
if 0:
flags |= 1 << 18

ft2font = FT2Font(fontfile)
ft2font = get_font(fontfile)

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

font = FT2Font(filename)
font = get_font(filename)
fonttype = rcParams['pdf.fonttype']

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


class RendererPdf(RendererBase):
truetype_font_cache = maxdict(50)
afm_font_cache = maxdict(50)

def __init__(self, file, image_dpi):
Expand Down Expand Up @@ -2126,15 +2125,8 @@ def _get_font_afm(self, prop):
return font

def _get_font_ttf(self, prop):
key = hash(prop)
font = self.truetype_font_cache.get(key)
if font is None:
filename = findfont(prop)
font = self.truetype_font_cache.get(filename)
if font is None:
font = FT2Font(filename)
self.truetype_font_cache[filename] = font
self.truetype_font_cache[key] = font
filename = findfont(prop)
font = get_font(filename)
font.clear()
font.set_size(prop.get_size_in_points(), 72)
return font
Expand Down
3 changes: 1 addition & 2 deletions lib/matplotlib/backends/backend_pgf.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@
system_fonts = []
if sys.platform.startswith('win'):
from matplotlib import font_manager
from matplotlib.ft2font import FT2Font
for f in font_manager.win32InstalledFonts():
try:
system_fonts.append(FT2Font(str(f)).family_name)
system_fonts.append(font_manager.get_font(str(f)).family_name)
except:
pass # unknown error, skip this font
else:
Expand Down
18 changes: 5 additions & 13 deletions lib/matplotlib/backends/backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def _fn_name(): return sys._getframe(1).f_code.co_name
is_writable_file_like, maxdict, file_requires_unicode
from matplotlib.figure import Figure

from matplotlib.font_manager import findfont, is_opentype_cff_font
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING
from matplotlib.font_manager import findfont, is_opentype_cff_font, get_font
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
from matplotlib.ttconv import convert_ttf_to_ps
from matplotlib.mathtext import MathTextParser
from matplotlib._mathtext_data import uni2type1
Expand Down Expand Up @@ -199,7 +199,6 @@ class RendererPS(RendererBase):
context instance that controls the colors/styles.
"""

fontd = maxdict(50)
afmfontd = maxdict(50)

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

def _get_font_ttf(self, prop):
key = hash(prop)
font = self.fontd.get(key)
if font is None:
fname = findfont(prop)
font = self.fontd.get(fname)
if font is None:
font = FT2Font(fname)
self.fontd[fname] = font
self.fontd[key] = font
fname = findfont(prop)
font = get_font(fname)
font.clear()
size = prop.get_size_in_points()
font.set_size(size, 72.0)
Expand Down Expand Up @@ -1145,7 +1137,7 @@ def print_figure_impl():
if not rcParams['ps.useafm']:
for font_filename, chars in six.itervalues(ps_renderer.used_characters):
if len(chars):
font = FT2Font(font_filename)
font = get_font(font_filename)
cmap = font.get_charmap()
glyph_ids = []
for c in chars:
Expand Down
17 changes: 5 additions & 12 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
from matplotlib.cbook import is_string_like, is_writable_file_like, maxdict
from matplotlib.colors import rgb2hex
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont, FontProperties
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING
from matplotlib.font_manager import findfont, FontProperties, get_font
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
from matplotlib import _path
Expand Down Expand Up @@ -326,15 +326,8 @@ def _make_flip_transform(self, transform):
.translate(0.0, self.height))

def _get_font(self, prop):
key = hash(prop)
font = self.fontd.get(key)
if font is None:
fname = findfont(prop)
font = self.fontd.get(fname)
if font is None:
font = FT2Font(fname)
self.fontd[fname] = font
self.fontd[key] = font
fname = findfont(prop)
font = get_font(fname)
font.clear()
size = prop.get_size_in_points()
font.set_size(size, 72.0)
Expand Down Expand Up @@ -495,7 +488,7 @@ def _write_svgfonts(self):
writer = self.writer
writer.start('defs')
for font_fname, chars in six.iteritems(self._fonts):
font = FT2Font(font_fname)
font = get_font(font_fname)
font.set_size(72, 72)
sfnt = font.get_sfnt()
writer.start('font', id=sfnt[(1, 0, 0, 4)])
Expand Down
13 changes: 11 additions & 2 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
from matplotlib.fontconfig_pattern import \
parse_fontconfig_pattern, generate_fontconfig_pattern

try:
from functools import lru_cache
except ImportError:
from functools32 import lru_cache


USE_FONTCONFIG = False
verbose = matplotlib.verbose

Expand Down Expand Up @@ -733,7 +739,7 @@ def get_name(self):
Return the name of the font that best matches the font
properties.
"""
return ft2font.FT2Font(findfont(self)).family_name
return get_font(findfont(self)).family_name

def get_style(self):
"""
Expand Down Expand Up @@ -1336,7 +1342,6 @@ def findfont(self, prop, fontext='ttf', directory=None,
_lookup_cache[fontext].set(prop, result)
return result


_is_opentype_cff_font_cache = {}
def is_opentype_cff_font(filename):
"""
Expand All @@ -1357,6 +1362,10 @@ def is_opentype_cff_font(filename):
fontManager = None
_fmcache = None


get_font = lru_cache(64)(ft2font.FT2Font)


# The experimental fontconfig-based backend.
if USE_FONTCONFIG and sys.platform != 'win32':
import re
Expand Down
9 changes: 4 additions & 5 deletions lib/matplotlib/mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
from matplotlib.afm import AFM
from matplotlib.cbook import Bunch, get_realpath_and_stat, \
is_string_like, maxdict
from matplotlib.ft2font import FT2Font, FT2Image, KERNING_DEFAULT, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
from matplotlib.font_manager import findfont, FontProperties
from matplotlib.ft2font import FT2Image, KERNING_DEFAULT, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
from matplotlib.font_manager import findfont, FontProperties, get_font
from matplotlib._mathtext_data import latex_to_bakoma, \
latex_to_standard, tex2uni, latex_to_cmex, stix_virtual_fonts
from matplotlib import get_data_path, rcParams
Expand Down Expand Up @@ -563,7 +563,7 @@ def __init__(self, default_font_prop, mathtext_backend):
self._fonts = {}

filename = findfont(default_font_prop)
default_font = self.CachedFont(FT2Font(filename))
default_font = self.CachedFont(get_font(filename))
self._fonts['default'] = default_font
self._fonts['regular'] = default_font

Expand All @@ -576,10 +576,9 @@ def _get_font(self, font):
basename = self.fontmap[font]
else:
basename = font

cached_font = self._fonts.get(basename)
if cached_font is None and os.path.exists(basename):
font = FT2Font(basename)
font = get_font(basename)
cached_font = self.CachedFont(font)
self._fonts[basename] = cached_font
self._fonts[font.postscript_name] = cached_font
Expand Down
6 changes: 0 additions & 6 deletions lib/matplotlib/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ def setup():
rcParams['text.hinting'] = False
rcParams['text.hinting_factor'] = 8

# Clear the font caches. Otherwise, the hinting mode can travel
# from one test to another.
backend_agg.RendererAgg._fontd.clear()
backend_pdf.RendererPdf.truetype_font_cache.clear()
backend_svg.RendererSVG.fontd.clear()


def assert_str_equal(reference_str, test_str,
format_str=('String {str1} and {str2} do not '
Expand Down
8 changes: 4 additions & 4 deletions lib/matplotlib/textpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
from matplotlib.path import Path
from matplotlib import rcParams
import matplotlib.font_manager as font_manager
from matplotlib.ft2font import FT2Font, KERNING_DEFAULT, LOAD_NO_HINTING
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
from matplotlib.ft2font import LOAD_TARGET_LIGHT
from matplotlib.mathtext import MathTextParser
import matplotlib.dviread as dviread
from matplotlib.font_manager import FontProperties
from matplotlib.font_manager import FontProperties, get_font
from matplotlib.transforms import Affine2D
from matplotlib.externals.six.moves.urllib.parse import quote as urllib_quote

Expand Down Expand Up @@ -54,7 +54,7 @@ def _get_font(self, prop):
find a ttf font.
"""
fname = font_manager.findfont(prop)
font = FT2Font(fname)
font = get_font(fname)
font.set_size(self.FONT_SCALE, self.DPI)

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

if font_and_encoding is None:
font = FT2Font(font_bunch.filename)
font = get_font(font_bunch.filename)

for charmap_name, charmap_code in [("ADOBE_CUSTOM",
1094992451),
Expand Down
6 changes: 0 additions & 6 deletions lib/mpl_toolkits/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ def setup():
rcParams['text.hinting'] = False
rcParams['text.hinting_factor'] = 8

# Clear the font caches. Otherwise, the hinting mode can travel
# from one test to another.
backend_agg.RendererAgg._fontd.clear()
backend_pdf.RendererPdf.truetype_font_cache.clear()
backend_svg.RendererSVG.fontd.clear()


def assert_str_equal(reference_str, test_str,
format_str=('String {str1} and {str2} do not '
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
'Required dependencies and extensions',
setupext.Numpy(),
setupext.Dateutil(),
setupext.FuncTools32(),
setupext.Pytz(),
setupext.Cycler(),
setupext.Tornado(),
Expand Down
23 changes: 23 additions & 0 deletions setupext.py
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,29 @@ def get_install_requires(self):
return [dateutil]


class FuncTools32(SetupPackage):
name = "functools32"

def check(self):
if sys.version_info[:2] < (3, 2):
try:
import functools32
except ImportError:
return (
"functools32 was not found. It is required for for"
"python versions prior to 3.2")

return "using functools32"
else:
return "Not required"

def get_install_requires(self):
if sys.version_info[:2] < (3, 2):
return ['functools32']
else:
return []


class Tornado(OptionalPackage):
name = "tornado"

Expand Down