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