1"""
2Common functionality between the PDF and PS backends.
3"""
4
5import functools
6
7import matplotlib as mpl
8from matplotlib import _api
9from .. import font_manager, ft2font
10from ..afm import AFM
11from ..backend_bases import RendererBase
12
13
14@functools.lru_cache(50)
15def _cached_get_afm_from_fname(fname):
16    with open(fname, "rb") as fh:
17        return AFM(fh)
18
19
20class CharacterTracker:
21    """
22    Helper for font subsetting by the pdf and ps backends.
23
24    Maintains a mapping of font paths to the set of character codepoints that
25    are being used from that font.
26    """
27
28    def __init__(self):
29        self.used = {}
30
31    @_api.deprecated("3.3")
32    @property
33    def used_characters(self):
34        d = {}
35        for fname, chars in self.used.items():
36            realpath, stat_key = mpl.cbook.get_realpath_and_stat(fname)
37            d[stat_key] = (realpath, chars)
38        return d
39
40    def track(self, font, s):
41        """Record that string *s* is being typeset using font *font*."""
42        if isinstance(font, str):
43            # Unused, can be removed after removal of track_characters.
44            fname = font
45        else:
46            fname = font.fname
47        self.used.setdefault(fname, set()).update(map(ord, s))
48
49    # Not public, can be removed when pdf/ps merge_used_characters is removed.
50    def merge(self, other):
51        """Update self with a font path to character codepoints."""
52        for fname, charset in other.items():
53            self.used.setdefault(fname, set()).update(charset)
54
55
56class RendererPDFPSBase(RendererBase):
57    # The following attributes must be defined by the subclasses:
58    # - _afm_font_dir
59    # - _use_afm_rc_name
60
61    def __init__(self, width, height):
62        super().__init__()
63        self.width = width
64        self.height = height
65
66    def flipy(self):
67        # docstring inherited
68        return False  # y increases from bottom to top.
69
70    def option_scale_image(self):
71        # docstring inherited
72        return True  # PDF and PS support arbitrary image scaling.
73
74    def option_image_nocomposite(self):
75        # docstring inherited
76        # Decide whether to composite image based on rcParam value.
77        return not mpl.rcParams["image.composite_image"]
78
79    def get_canvas_width_height(self):
80        # docstring inherited
81        return self.width * 72.0, self.height * 72.0
82
83    def get_text_width_height_descent(self, s, prop, ismath):
84        # docstring inherited
85        if ismath == "TeX":
86            texmanager = self.get_texmanager()
87            fontsize = prop.get_size_in_points()
88            w, h, d = texmanager.get_text_width_height_descent(
89                s, fontsize, renderer=self)
90            return w, h, d
91        elif ismath:
92            # Circular import.
93            from matplotlib.backends.backend_ps import RendererPS
94            parse = self._text2path.mathtext_parser.parse(
95                s, 72, prop,
96                _force_standard_ps_fonts=(isinstance(self, RendererPS)
97                                          and mpl.rcParams["ps.useafm"]))
98            return parse.width, parse.height, parse.depth
99        elif mpl.rcParams[self._use_afm_rc_name]:
100            font = self._get_font_afm(prop)
101            l, b, w, h, d = font.get_str_bbox_and_descent(s)
102            scale = prop.get_size_in_points() / 1000
103            w *= scale
104            h *= scale
105            d *= scale
106            return w, h, d
107        else:
108            font = self._get_font_ttf(prop)
109            font.set_text(s, 0.0, flags=ft2font.LOAD_NO_HINTING)
110            w, h = font.get_width_height()
111            d = font.get_descent()
112            scale = 1 / 64
113            w *= scale
114            h *= scale
115            d *= scale
116            return w, h, d
117
118    def _get_font_afm(self, prop):
119        fname = font_manager.findfont(
120            prop, fontext="afm", directory=self._afm_font_dir)
121        return _cached_get_afm_from_fname(fname)
122
123    def _get_font_ttf(self, prop):
124        fname = font_manager.findfont(prop)
125        font = font_manager.get_font(fname)
126        font.clear()
127        font.set_size(prop.get_size_in_points(), 72)
128        return font
129