Skip to content

Commit edd053d

Browse files
authored
Merge pull request #10954 from anntzer/share-dviread-caches
Cache various dviread constructs globally.
2 parents eee8901 + cb58589 commit edd053d

File tree

5 files changed

+83
-94
lines changed

5 files changed

+83
-94
lines changed

doc/api/next_api_changes/2018-02-15-AL-deprecations.rst

+9-5
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ The following classes, methods, functions, and attributes are deprecated:
1414
``RcParams.msg_depr_set``, ``RcParams.msg_obsolete``,
1515
``RcParams.msg_backend_obsolete``,
1616
- ``afm.parse_afm``,
17+
- ``backend_pdf.PdfFile.texFontMap``,
1718
- ``backend_pgf.get_texcommand``,
1819
- ``backend_ps.get_bbox``,
20+
- ``backend_qt5.FigureCanvasQT.keyAutoRepeat`` (directly check
21+
``event.guiEvent.isAutoRepeat()`` in the event handler to decide whether to
22+
handle autorepeated key presses).
1923
- ``backend_qt5.error_msg_qt``, ``backend_qt5.exception_handler``,
2024
- ``backend_wx.FigureCanvasWx.macros``,
2125
- ``cbook.GetRealpathAndStat``, ``cbook.Locked``,
@@ -25,21 +29,21 @@ The following classes, methods, functions, and attributes are deprecated:
2529
- ``contour.ContourLabeler.cl``, ``.cl_xy``, and ``.cl_cvalues``,
2630
- ``dates.DateFormatter.strftime_pre_1900``, ``dates.DateFormatter.strftime``,
2731
- ``font_manager.TempCache``,
32+
- ``image._ImageBase.iterpnames``, use the ``interpolation_names`` property
33+
instead. (this affects classes that inherit from ``_ImageBase`` including
34+
:class:`FigureImage`, :class:`BboxImage`, and :class:`AxesImage`),
2835
- ``mathtext.unichr_safe`` (use ``chr`` instead),
36+
- ``patches.Polygon.xy``,
2937
- ``table.Table.get_child_artists`` (use ``get_children`` instead),
3038
- ``testing.compare.ImageComparisonTest``, ``testing.compare.compare_float``,
3139
- ``testing.decorators.CleanupTest``,
3240
``testing.decorators.skip_if_command_unavailable``,
3341
- ``FigureCanvasQT.keyAutoRepeat`` (directly check
3442
``event.guiEvent.isAutoRepeat()`` in the event handler to decide whether to
3543
handle autorepeated key presses).
36-
- ``FigureCanvasWx.macros``,
37-
- ``_ImageBase.iterpnames``, use the ``interpolation_names`` property instead.
38-
(this affects classes that inherit from ``_ImageBase`` including
39-
:class:`FigureImage`, :class:`BboxImage`, and :class:`AxesImage`),
40-
- ``patches.Polygon.xy``,
4144
- ``texmanager.dvipng_hack_alpha``,
4245
- ``text.Annotation.arrow``,
46+
- ``textpath.TextToPath.tex_font_map``,
4347

4448
The following rcParams are deprecated:
4549
- ``examples.directory`` (use ``datapath`` instead),

lib/matplotlib/backends/backend_pdf.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -655,14 +655,11 @@ def fontName(self, fontprop):
655655
return Fx
656656

657657
@property
658+
@cbook.deprecated("3.0")
658659
def texFontMap(self):
659660
# lazy-load texFontMap, it takes a while to parse
660661
# and usetex is a relatively rare use case
661-
if self._texFontMap is None:
662-
self._texFontMap = dviread.PsfontsMap(
663-
dviread.find_tex_file('pdftex.map'))
664-
665-
return self._texFontMap
662+
return dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
666663

667664
def dviFontName(self, dvifont):
668665
"""
@@ -675,7 +672,8 @@ def dviFontName(self, dvifont):
675672
if dvi_info is not None:
676673
return dvi_info.pdfname
677674

678-
psfont = self.texFontMap[dvifont.texname]
675+
tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
676+
psfont = tex_font_map[dvifont.texname]
679677
if psfont.filename is None:
680678
raise ValueError(
681679
"No usable font file found for {} (TeX: {}); "

lib/matplotlib/dviread.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
# iterate over pages:
1010
for page in dvi:
1111
w, h, d = page.width, page.height, page.descent
12-
for x,y,font,glyph,width in page.text:
12+
for x, y, font, glyph, width in page.text:
1313
fontname = font.texname
1414
pointsize = font.size
1515
...
16-
for x,y,height,width in page.boxes:
16+
for x, y, height, width in page.boxes:
1717
...
18-
1918
"""
19+
2020
from collections import namedtuple
2121
import enum
2222
from functools import lru_cache, partial, wraps
@@ -34,6 +34,10 @@
3434

3535
_log = logging.getLogger(__name__)
3636

37+
# Many dvi related files are looked for by external processes, require
38+
# additional parsing, and are used many times per rendering, which is why they
39+
# are cached using lru_cache().
40+
3741
# Dvi is a bytecode format documented in
3842
# http://mirrors.ctan.org/systems/knuth/dist/texware/dvitype.web
3943
# http://texdoc.net/texmf-dist/doc/generic/knuth/texware/dvitype.pdf
@@ -808,14 +812,14 @@ class PsfontsMap(object):
808812
"""
809813
__slots__ = ('_font', '_filename')
810814

811-
def __init__(self, filename):
815+
@lru_cache()
816+
def __new__(cls, filename):
817+
self = object.__new__(cls)
812818
self._font = {}
813-
self._filename = filename
814-
if isinstance(filename, bytes):
815-
encoding = sys.getfilesystemencoding() or 'utf-8'
816-
self._filename = filename.decode(encoding, errors='replace')
819+
self._filename = os.fsdecode(filename)
817820
with open(filename, 'rb') as file:
818821
self._parse(file)
822+
return self
819823

820824
def __getitem__(self, texname):
821825
assert isinstance(texname, bytes)
@@ -956,7 +960,8 @@ def __init__(self, filename):
956960
def __iter__(self):
957961
yield from self.encoding
958962

959-
def _parse(self, file):
963+
@staticmethod
964+
def _parse(file):
960965
result = []
961966

962967
lines = (line.split(b'%', 1)[0].strip() for line in file)
@@ -975,6 +980,7 @@ def _parse(self, file):
975980
return re.findall(br'/([^][{}<>\s]+)', data)
976981

977982

983+
@lru_cache()
978984
def find_tex_file(filename, format=None):
979985
"""
980986
Find a file in the texmf tree.
@@ -1016,10 +1022,6 @@ def find_tex_file(filename, format=None):
10161022
return result.decode('ascii')
10171023

10181024

1019-
# With multiple text objects per figure (e.g., tick labels) we may end
1020-
# up reading the same tfm and vf files many times, so we implement a
1021-
# simple cache. TODO: is this worth making persistent?
1022-
10231025
@lru_cache()
10241026
def _fontfile(cls, suffix, texname):
10251027
filename = find_tex_file(texname + suffix)

lib/matplotlib/texmanager.py

-4
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,13 @@
2929
"""
3030

3131
import copy
32-
import distutils.version
3332
import glob
3433
import hashlib
3534
import logging
3635
import os
3736
from pathlib import Path
3837
import re
39-
import shutil
4038
import subprocess
41-
import sys
42-
import warnings
4339

4440
import numpy as np
4541

lib/matplotlib/textpath.py

+55-66
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
from collections import OrderedDict
2+
import functools
23
import urllib.parse
34
import warnings
45

56
import numpy as np
67

7-
from matplotlib.path import Path
8-
from matplotlib import rcParams
9-
import matplotlib.font_manager as font_manager
10-
from matplotlib.ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
11-
from matplotlib.ft2font import LOAD_TARGET_LIGHT
12-
from matplotlib.mathtext import MathTextParser
13-
import matplotlib.dviread as dviread
8+
from matplotlib import cbook, dviread, font_manager, rcParams
149
from matplotlib.font_manager import FontProperties, get_font
10+
from matplotlib.ft2font import (
11+
KERNING_DEFAULT, LOAD_NO_HINTING, LOAD_TARGET_LIGHT)
12+
from matplotlib.mathtext import MathTextParser
13+
from matplotlib.path import Path
1514
from matplotlib.transforms import Affine2D
1615

1716

17+
@functools.lru_cache(1)
18+
def _get_adobe_standard_encoding():
19+
enc_name = dviread.find_tex_file('8a.enc')
20+
enc = dviread.Encoding(enc_name)
21+
return {c: i for i, c in enumerate(enc.encoding)}
22+
23+
1824
class TextToPath(object):
1925
"""
2026
A class that convert a given text to a path using ttf fonts.
@@ -25,19 +31,12 @@ class TextToPath(object):
2531

2632
def __init__(self):
2733
self.mathtext_parser = MathTextParser('path')
28-
self.tex_font_map = None
29-
30-
from matplotlib.cbook import maxdict
31-
self._ps_fontd = maxdict(50)
32-
3334
self._texmanager = None
3435

35-
self._adobe_standard_encoding = None
36-
37-
def _get_adobe_standard_encoding(self):
38-
enc_name = dviread.find_tex_file('8a.enc')
39-
enc = dviread.Encoding(enc_name)
40-
return {c: i for i, c in enumerate(enc.encoding)}
36+
@property
37+
@cbook.deprecated("3.0")
38+
def tex_font_map(self):
39+
return dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
4140

4241
def _get_font(self, prop):
4342
"""
@@ -281,13 +280,6 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
281280

282281
texmanager = self.get_texmanager()
283282

284-
if self.tex_font_map is None:
285-
self.tex_font_map = dviread.PsfontsMap(
286-
dviread.find_tex_file('pdftex.map'))
287-
288-
if self._adobe_standard_encoding is None:
289-
self._adobe_standard_encoding = self._get_adobe_standard_encoding()
290-
291283
fontsize = prop.get_size_in_points()
292284
if hasattr(texmanager, "get_dvi"):
293285
dvifilelike = texmanager.get_dvi(s, self.FONT_SCALE)
@@ -312,46 +304,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
312304
# characters into strings.
313305
# oldfont, seq = None, []
314306
for x1, y1, dvifont, glyph, width in page.text:
315-
font_and_encoding = self._ps_fontd.get(dvifont.texname)
316-
font_bunch = self.tex_font_map[dvifont.texname]
317-
318-
if font_and_encoding is None:
319-
if font_bunch.filename is None:
320-
raise ValueError(
321-
("No usable font file found for %s (%s). "
322-
"The font may lack a Type-1 version.")
323-
% (font_bunch.psname, dvifont.texname))
324-
325-
font = get_font(font_bunch.filename)
326-
327-
for charmap_name, charmap_code in [("ADOBE_CUSTOM",
328-
1094992451),
329-
("ADOBE_STANDARD",
330-
1094995778)]:
331-
try:
332-
font.select_charmap(charmap_code)
333-
except (ValueError, RuntimeError):
334-
pass
335-
else:
336-
break
337-
else:
338-
charmap_name = ""
339-
warnings.warn("No supported encoding in font (%s)." %
340-
font_bunch.filename)
341-
342-
if charmap_name == "ADOBE_STANDARD" and font_bunch.encoding:
343-
enc0 = dviread.Encoding(font_bunch.encoding)
344-
enc = {i: self._adobe_standard_encoding.get(c, None)
345-
for i, c in enumerate(enc0.encoding)}
346-
else:
347-
enc = {}
348-
self._ps_fontd[dvifont.texname] = font, enc
349-
350-
else:
351-
font, enc = font_and_encoding
352-
353-
ft2font_flag = LOAD_TARGET_LIGHT
354-
307+
font, enc = self._get_ps_font_and_encoding(dvifont.texname)
355308
char_id = self._get_char_id_ps(font, glyph)
356309

357310
if char_id not in glyph_map:
@@ -362,12 +315,13 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
362315
else:
363316
charcode = glyph
364317

318+
ft2font_flag = LOAD_TARGET_LIGHT
365319
if charcode is not None:
366320
glyph0 = font.load_char(charcode, flags=ft2font_flag)
367321
else:
368322
warnings.warn("The glyph (%d) of font (%s) cannot be "
369323
"converted with the encoding. Glyph may "
370-
"be wrong" % (glyph, font_bunch.filename))
324+
"be wrong" % (glyph, font.fname))
371325

372326
glyph0 = font.load_char(glyph, flags=ft2font_flag)
373327

@@ -391,6 +345,41 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
391345
return (list(zip(glyph_ids, xpositions, ypositions, sizes)),
392346
glyph_map_new, myrects)
393347

348+
@staticmethod
349+
@functools.lru_cache(50)
350+
def _get_ps_font_and_encoding(texname):
351+
tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
352+
font_bunch = tex_font_map[texname]
353+
if font_bunch.filename is None:
354+
raise ValueError(
355+
("No usable font file found for %s (%s). "
356+
"The font may lack a Type-1 version.")
357+
% (font_bunch.psname, texname))
358+
359+
font = get_font(font_bunch.filename)
360+
361+
for charmap_name, charmap_code in [("ADOBE_CUSTOM", 1094992451),
362+
("ADOBE_STANDARD", 1094995778)]:
363+
try:
364+
font.select_charmap(charmap_code)
365+
except (ValueError, RuntimeError):
366+
pass
367+
else:
368+
break
369+
else:
370+
charmap_name = ""
371+
warnings.warn("No supported encoding in font (%s)." %
372+
font_bunch.filename)
373+
374+
if charmap_name == "ADOBE_STANDARD" and font_bunch.encoding:
375+
enc0 = dviread.Encoding(font_bunch.encoding)
376+
enc = {i: _get_adobe_standard_encoding().get(c, None)
377+
for i, c in enumerate(enc0.encoding)}
378+
else:
379+
enc = {}
380+
381+
return font, enc
382+
394383

395384
text_to_path = TextToPath()
396385

0 commit comments

Comments
 (0)