# Copyright 2014, Sandia Corporation. Under the terms of Contract
# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain
# rights in this software.
"""Font management and font metrics.
"""
from __future__ import absolute_import
import custom_inherit
import reportlab.pdfbase.pdfmetrics
import six
import toyplot.units
[docs]@six.add_metaclass(custom_inherit.DocInheritMeta(style="numpy_napoleon"))
class Font(object):
"""Abstract interface for objects that return information about a specific combination of typeface and size."""
@property
def ascent(self):
"""Font ascent (maximum height above the baseline).
Returns
-------
ascent: :class:`number<numbers.Number>`
ascent of the font in CSS pixels.
"""
raise NotImplementedError() # pragma: no cover
@property
def descent(self):
"""Font descent (maximum height below the baseline).
Returns
-------
descent: :class:`number<numbers.Number>`
descent of the font in CSS pixels.
"""
raise NotImplementedError() # pragma: no cover
[docs] def width(self, string):
"""Return the width of a string if rendered using the font.
Parameters
----------
string: str, required
The text to be measured.
Returns
-------
width: :class:`number<numbers.Number>`
Width of the string in CSS pixels, if rendered using the given font and
font size.
"""
raise NotImplementedError() # pragma: no cover
[docs]@six.add_metaclass(custom_inherit.DocInheritMeta(style="numpy_napoleon"))
class Library(object):
"""Abstract interface for objects that manage a collection of fonts."""
[docs] def font(self, style):
"""Lookup a font using CSS style information and return a corresponding Font object.
Parameters
----------
style: dict containing CSS style information
Returns
-------
font: instance of :class:`toyplot.font.Font`
"""
raise NotImplementedError() # pragma: no cover
[docs]class ReportlabFont(Font):
"""Use Reportlab to access the metrics for a font.
Parameters
----------
font_family: str
PDF font family to use for measurement.
font_size: number
Font size for the measurement. Defaults to CSS pixel units, and
supports all toyplot :ref:`units`.
"""
def __init__(self, family, size):
self._family = family
self._size = toyplot.units.convert(size, target="pt", default="px")
ascent, descent = reportlab.pdfbase.pdfmetrics.getAscentDescent(self._family, self._size)
self._ascent = toyplot.units.convert(ascent, target="px", default="pt")
self._descent = toyplot.units.convert(descent, target="px", default="pt")
def __repr__(self): # pragma: no cover
return "<toyplot.font.ReportlabFont family=%r size=%r ascent=%r descent=%r>" % (self._family, self._size, self.ascent, self.descent)
@property
def ascent(self):
return self._ascent
@property
def descent(self):
return self._descent
[docs] def width(self, string):
width = reportlab.pdfbase.pdfmetrics.stringWidth(string, self._family, self._size)
width = toyplot.units.convert(width, target="px", default="pt")
return width
[docs]class ReportlabLibrary(Library):
"""Use Reportlab to provide information about standard PDF fonts."""
def __init__(self):
self._cache = dict()
[docs] def font(self, style):
"""
Returns
-------
font: instance of :class:`toyplot.font.ReportlabFont`
"""
size = style["font-size"]
bold = True if style.get("font-weight", "") == "bold" else False
italic = True if style.get("font-style", "") == "italic" else False
for font_family in style["font-family"].split(","):
font_family = font_family.lower()
if font_family not in ReportlabLibrary.font._substitutions:
continue
font_family = ReportlabLibrary.font._substitutions[font_family]
key = (font_family, bold, italic, size)
if key not in self._cache:
family = ReportlabLibrary.font._font_table[(font_family, bold, italic)]
self._cache[key] = ReportlabFont(family, size)
return self._cache[key]
raise ValueError("Unknown font family: %s" % style) # pragma: no cover
font._substitutions = {
"courier": "courier",
"helvetica": "helvetica",
"monospace": "courier",
"sans-serif": "helvetica",
"serif": "times",
"times": "times",
}
font._font_table = {
("courier", False, False): "Courier",
("courier", True, False): "Courier-Bold",
("courier", False, True): "Courier-Oblique",
("courier", True, True): "Courier-BoldOblique",
("helvetica", False, False): "Helvetica",
("helvetica", True, False): "Helvetica-Bold",
("helvetica", False, True): "Helvetica-Oblique",
("helvetica", True, True): "Helvetica-BoldOblique",
("times", False, False): "Times-Roman",
("times", True, False): "Times-Bold",
("times", False, True): "Times-Italic",
("times", True, True): "Times-BoldItalic",
}