Cache various dviread constructs globally. by anntzer · Pull Request #10954 · matplotlib/matplotlib · GitHub
Skip to content
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
14 changes: 9 additions & 5 deletions doc/api/next_api_changes/2018-02-15-AL-deprecations.rst
10 changes: 4 additions & 6 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,14 +655,11 @@ def fontName(self, fontprop):
return Fx

@property
@cbook.deprecated("3.0")
def texFontMap(self):
# lazy-load texFontMap, it takes a while to parse
# and usetex is a relatively rare use case
if self._texFontMap is None:
self._texFontMap = dviread.PsfontsMap(
dviread.find_tex_file('pdftex.map'))

return self._texFontMap
return dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))

def dviFontName(self, dvifont):
"""
Expand All @@ -675,7 +672,8 @@ def dviFontName(self, dvifont):
if dvi_info is not None:
return dvi_info.pdfname

psfont = self.texFontMap[dvifont.texname]
tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
psfont = tex_font_map[dvifont.texname]
if psfont.filename is None:
raise ValueError(
"No usable font file found for {} (TeX: {}); "
Expand Down
28 changes: 15 additions & 13 deletions lib/matplotlib/dviread.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
# iterate over pages:
for page in dvi:
w, h, d = page.width, page.height, page.descent
for x,y,font,glyph,width in page.text:
for x, y, font, glyph, width in page.text:
fontname = font.texname
pointsize = font.size
...
for x,y,height,width in page.boxes:
for x, y, height, width in page.boxes:
...

"""

from collections import namedtuple
import enum
from functools import lru_cache, partial, wraps
Expand All @@ -34,6 +34,10 @@

_log = logging.getLogger(__name__)

# Many dvi related files are looked for by external processes, require
# additional parsing, and are used many times per rendering, which is why they
# are cached using lru_cache().

# Dvi is a bytecode format documented in
# http://mirrors.ctan.org/systems/knuth/dist/texware/dvitype.web
# http://texdoc.net/texmf-dist/doc/generic/knuth/texware/dvitype.pdf
Expand Down Expand Up @@ -808,14 +812,14 @@ class PsfontsMap(object):
"""
__slots__ = ('_font', '_filename')

def __init__(self, filename):
@lru_cache()
def __new__(cls, filename):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit dodgy. Would a helper function (still using lru_cache) also work here instead of decorating __new__?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like

@lru_cache()
def _psfontmap_factory(filename): return PsfontsMap(filename)

and use the factory function instead? TBH that seems worse to me but if you prefer that form let me know (but I'm not sure I got what you mean).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tacaswell and I talked, and while this, as a gut reaction, feels a bit magical, it's probably the best mix of being direct and magical, versus other solutions. It wouldn't hurt to have a comment or two here, but I'm not going to hold this up for that.

self = object.__new__(cls)
self._font = {}
self._filename = filename
if isinstance(filename, bytes):
encoding = sys.getfilesystemencoding() or 'utf-8'
self._filename = filename.decode(encoding, errors='replace')
self._filename = os.fsdecode(filename)
with open(filename, 'rb') as file:
self._parse(file)
return self

def __getitem__(self, texname):
assert isinstance(texname, bytes)
Expand Down Expand Up @@ -956,7 +960,8 @@ def __init__(self, filename):
def __iter__(self):
yield from self.encoding

def _parse(self, file):
@staticmethod
def _parse(file):
result = []

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


@lru_cache()
def find_tex_file(filename, format=None):
"""
Find a file in the texmf tree.
Expand Down Expand Up @@ -1016,10 +1022,6 @@ def find_tex_file(filename, format=None):
return result.decode('ascii')


# With multiple text objects per figure (e.g., tick labels) we may end
# up reading the same tfm and vf files many times, so we implement a
# simple cache. TODO: is this worth making persistent?

@lru_cache()
def _fontfile(cls, suffix, texname):
filename = find_tex_file(texname + suffix)
Expand Down
4 changes: 0 additions & 4 deletions lib/matplotlib/texmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,13 @@
"""

import copy
import distutils.version
import glob
import hashlib
import logging
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import warnings

import numpy as np

Expand Down
121 changes: 55 additions & 66 deletions lib/matplotlib/textpath.py