146 lines
4.3 KiB
Python
146 lines
4.3 KiB
Python
"""
|
|
Common functionality between the PDF and PS backends.
|
|
"""
|
|
|
|
from io import BytesIO
|
|
import functools
|
|
|
|
from fontTools import subset
|
|
|
|
import matplotlib as mpl
|
|
from .. import font_manager, ft2font
|
|
from .._afm import AFM
|
|
from ..backend_bases import RendererBase
|
|
|
|
|
|
@functools.lru_cache(50)
|
|
def _cached_get_afm_from_fname(fname):
|
|
with open(fname, "rb") as fh:
|
|
return AFM(fh)
|
|
|
|
|
|
def get_glyphs_subset(fontfile, characters):
|
|
"""
|
|
Subset a TTF font
|
|
|
|
Reads the named fontfile and restricts the font to the characters.
|
|
Returns a serialization of the subset font as file-like object.
|
|
|
|
Parameters
|
|
----------
|
|
fontfile : str
|
|
Path to the font file
|
|
characters : str
|
|
Continuous set of characters to include in subset
|
|
"""
|
|
|
|
options = subset.Options(glyph_names=True, recommended_glyphs=True)
|
|
|
|
# Prevent subsetting extra tables.
|
|
options.drop_tables += [
|
|
'FFTM', # FontForge Timestamp.
|
|
'PfEd', # FontForge personal table.
|
|
'BDF', # X11 BDF header.
|
|
'meta', # Metadata stores design/supported languages (meaningless for subsets).
|
|
]
|
|
|
|
# if fontfile is a ttc, specify font number
|
|
if fontfile.endswith(".ttc"):
|
|
options.font_number = 0
|
|
|
|
with subset.load_font(fontfile, options) as font:
|
|
subsetter = subset.Subsetter(options=options)
|
|
subsetter.populate(text=characters)
|
|
subsetter.subset(font)
|
|
fh = BytesIO()
|
|
font.save(fh, reorderTables=False)
|
|
return fh
|
|
|
|
|
|
class CharacterTracker:
|
|
"""
|
|
Helper for font subsetting by the pdf and ps backends.
|
|
|
|
Maintains a mapping of font paths to the set of character codepoints that
|
|
are being used from that font.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.used = {}
|
|
|
|
def track(self, font, s):
|
|
"""Record that string *s* is being typeset using font *font*."""
|
|
char_to_font = font._get_fontmap(s)
|
|
for _c, _f in char_to_font.items():
|
|
self.used.setdefault(_f.fname, set()).add(ord(_c))
|
|
|
|
def track_glyph(self, font, glyph):
|
|
"""Record that codepoint *glyph* is being typeset using font *font*."""
|
|
self.used.setdefault(font.fname, set()).add(glyph)
|
|
|
|
|
|
class RendererPDFPSBase(RendererBase):
|
|
# The following attributes must be defined by the subclasses:
|
|
# - _afm_font_dir
|
|
# - _use_afm_rc_name
|
|
|
|
def __init__(self, width, height):
|
|
super().__init__()
|
|
self.width = width
|
|
self.height = height
|
|
|
|
def flipy(self):
|
|
# docstring inherited
|
|
return False # y increases from bottom to top.
|
|
|
|
def option_scale_image(self):
|
|
# docstring inherited
|
|
return True # PDF and PS support arbitrary image scaling.
|
|
|
|
def option_image_nocomposite(self):
|
|
# docstring inherited
|
|
# Decide whether to composite image based on rcParam value.
|
|
return not mpl.rcParams["image.composite_image"]
|
|
|
|
def get_canvas_width_height(self):
|
|
# docstring inherited
|
|
return self.width * 72.0, self.height * 72.0
|
|
|
|
def get_text_width_height_descent(self, s, prop, ismath):
|
|
# docstring inherited
|
|
if ismath == "TeX":
|
|
return super().get_text_width_height_descent(s, prop, ismath)
|
|
elif ismath:
|
|
parse = self._text2path.mathtext_parser.parse(s, 72, prop)
|
|
return parse.width, parse.height, parse.depth
|
|
elif mpl.rcParams[self._use_afm_rc_name]:
|
|
font = self._get_font_afm(prop)
|
|
l, b, w, h, d = font.get_str_bbox_and_descent(s)
|
|
scale = prop.get_size_in_points() / 1000
|
|
w *= scale
|
|
h *= scale
|
|
d *= scale
|
|
return w, h, d
|
|
else:
|
|
font = self._get_font_ttf(prop)
|
|
font.set_text(s, 0.0, flags=ft2font.LOAD_NO_HINTING)
|
|
w, h = font.get_width_height()
|
|
d = font.get_descent()
|
|
scale = 1 / 64
|
|
w *= scale
|
|
h *= scale
|
|
d *= scale
|
|
return w, h, d
|
|
|
|
def _get_font_afm(self, prop):
|
|
fname = font_manager.findfont(
|
|
prop, fontext="afm", directory=self._afm_font_dir)
|
|
return _cached_get_afm_from_fname(fname)
|
|
|
|
def _get_font_ttf(self, prop):
|
|
fnames = font_manager.fontManager._find_fonts_by_props(prop)
|
|
font = font_manager.get_font(fnames)
|
|
font.clear()
|
|
font.set_size(prop.get_size_in_points(), 72)
|
|
return font
|