_backend_pdf_ps.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """
  2. Common functionality between the PDF and PS backends.
  3. """
  4. from io import BytesIO
  5. import functools
  6. import logging
  7. from fontTools import subset
  8. import matplotlib as mpl
  9. from .. import font_manager, ft2font
  10. from .._afm import AFM
  11. from ..backend_bases import RendererBase
  12. @functools.lru_cache(50)
  13. def _cached_get_afm_from_fname(fname):
  14. with open(fname, "rb") as fh:
  15. return AFM(fh)
  16. def get_glyphs_subset(fontfile, characters):
  17. """
  18. Subset a TTF font
  19. Reads the named fontfile and restricts the font to the characters.
  20. Parameters
  21. ----------
  22. fontfile : str
  23. Path to the font file
  24. characters : str
  25. Continuous set of characters to include in subset
  26. Returns
  27. -------
  28. fontTools.ttLib.ttFont.TTFont
  29. An open font object representing the subset, which needs to
  30. be closed by the caller.
  31. """
  32. options = subset.Options(glyph_names=True, recommended_glyphs=True)
  33. # Prevent subsetting extra tables.
  34. options.drop_tables += [
  35. 'FFTM', # FontForge Timestamp.
  36. 'PfEd', # FontForge personal table.
  37. 'BDF', # X11 BDF header.
  38. 'meta', # Metadata stores design/supported languages (meaningless for subsets).
  39. 'MERG', # Merge Table.
  40. 'TSIV', # Microsoft Visual TrueType extension.
  41. 'Zapf', # Information about the individual glyphs in the font.
  42. 'bdat', # The bitmap data table.
  43. 'bloc', # The bitmap location table.
  44. 'cidg', # CID to Glyph ID table (Apple Advanced Typography).
  45. 'fdsc', # The font descriptors table.
  46. 'feat', # Feature name table (Apple Advanced Typography).
  47. 'fmtx', # The Font Metrics Table.
  48. 'fond', # Data-fork font information (Apple Advanced Typography).
  49. 'just', # The justification table (Apple Advanced Typography).
  50. 'kerx', # An extended kerning table (Apple Advanced Typography).
  51. 'ltag', # Language Tag.
  52. 'morx', # Extended Glyph Metamorphosis Table.
  53. 'trak', # Tracking table.
  54. 'xref', # The cross-reference table (some Apple font tooling information).
  55. ]
  56. # if fontfile is a ttc, specify font number
  57. if fontfile.endswith(".ttc"):
  58. options.font_number = 0
  59. font = subset.load_font(fontfile, options)
  60. subsetter = subset.Subsetter(options=options)
  61. subsetter.populate(text=characters)
  62. subsetter.subset(font)
  63. return font
  64. def font_as_file(font):
  65. """
  66. Convert a TTFont object into a file-like object.
  67. Parameters
  68. ----------
  69. font : fontTools.ttLib.ttFont.TTFont
  70. A font object
  71. Returns
  72. -------
  73. BytesIO
  74. A file object with the font saved into it
  75. """
  76. fh = BytesIO()
  77. font.save(fh, reorderTables=False)
  78. return fh
  79. class CharacterTracker:
  80. """
  81. Helper for font subsetting by the pdf and ps backends.
  82. Maintains a mapping of font paths to the set of character codepoints that
  83. are being used from that font.
  84. """
  85. def __init__(self):
  86. self.used = {}
  87. def track(self, font, s):
  88. """Record that string *s* is being typeset using font *font*."""
  89. char_to_font = font._get_fontmap(s)
  90. for _c, _f in char_to_font.items():
  91. self.used.setdefault(_f.fname, set()).add(ord(_c))
  92. def track_glyph(self, font, glyph):
  93. """Record that codepoint *glyph* is being typeset using font *font*."""
  94. self.used.setdefault(font.fname, set()).add(glyph)
  95. class RendererPDFPSBase(RendererBase):
  96. # The following attributes must be defined by the subclasses:
  97. # - _afm_font_dir
  98. # - _use_afm_rc_name
  99. def __init__(self, width, height):
  100. super().__init__()
  101. self.width = width
  102. self.height = height
  103. def flipy(self):
  104. # docstring inherited
  105. return False # y increases from bottom to top.
  106. def option_scale_image(self):
  107. # docstring inherited
  108. return True # PDF and PS support arbitrary image scaling.
  109. def option_image_nocomposite(self):
  110. # docstring inherited
  111. # Decide whether to composite image based on rcParam value.
  112. return not mpl.rcParams["image.composite_image"]
  113. def get_canvas_width_height(self):
  114. # docstring inherited
  115. return self.width * 72.0, self.height * 72.0
  116. def get_text_width_height_descent(self, s, prop, ismath):
  117. # docstring inherited
  118. if ismath == "TeX":
  119. return super().get_text_width_height_descent(s, prop, ismath)
  120. elif ismath:
  121. parse = self._text2path.mathtext_parser.parse(s, 72, prop)
  122. return parse.width, parse.height, parse.depth
  123. elif mpl.rcParams[self._use_afm_rc_name]:
  124. font = self._get_font_afm(prop)
  125. l, b, w, h, d = font.get_str_bbox_and_descent(s)
  126. scale = prop.get_size_in_points() / 1000
  127. w *= scale
  128. h *= scale
  129. d *= scale
  130. return w, h, d
  131. else:
  132. font = self._get_font_ttf(prop)
  133. font.set_text(s, 0.0, flags=ft2font.LoadFlags.NO_HINTING)
  134. w, h = font.get_width_height()
  135. d = font.get_descent()
  136. scale = 1 / 64
  137. w *= scale
  138. h *= scale
  139. d *= scale
  140. return w, h, d
  141. def _get_font_afm(self, prop):
  142. fname = font_manager.findfont(
  143. prop, fontext="afm", directory=self._afm_font_dir)
  144. return _cached_get_afm_from_fname(fname)
  145. def _get_font_ttf(self, prop):
  146. fnames = font_manager.fontManager._find_fonts_by_props(prop)
  147. try:
  148. font = font_manager.get_font(fnames)
  149. font.clear()
  150. font.set_size(prop.get_size_in_points(), 72)
  151. return font
  152. except RuntimeError:
  153. logging.getLogger(__name__).warning(
  154. "The PostScript/PDF backend does not currently "
  155. "support the selected font (%s).", fnames)
  156. raise