Source code for campy.graphics.gfont

"""Friendly fonts.

A :class:`GFont` provides a convenient way to define fonts.

To get the current platform's default font::

    font = GFont.default()

To get a font from a text description in the form FAMILY-STYLE or
FAMILY-STYLE-MODIFIERS::

    font1 = GFont.parse('Helvetica-12')
    font2 = GFont.parse('Times New Roman-24-bold')
    font3 = GFont.parse('Verdana-11-bold-italic')

For finer grained control, use the constructor directly.

    font1 = GFont.parse('Helvetica', 12)
    font2 = GFont.parse('Times New Roman', 24, weight=True)
    font3 = GFont.parse('Verdana', 11, weight=True, slant=True)

To access a system font by name, such as the macOS alert header::

    font = GFont.system('systemAlertHeaderFont')

Note that a given system font may not be accessible on all platforms.

Once you have a :class:`GFont`, you can access its family, size, weight, and
slant. You can also access font metrics directly, such as the font's ascent,
the font's descent, the font's line spacing, and whether the font is
fixed-width. These are properties that apply to the font as a whole, and not
to any particular text rendered in this font.

To get the width of a particular string, use :func:`GFont.measure`::

    font = GFont.default()
    width = font.measure('Hello World!')
    print(width)  # => 77
"""
import campy.private.platform as _platform

import logging

# Module-level logger.
logger = logging.getLogger(__name__)


[docs]class GFont: # TODO(sredmond): Support underline and overstrike. # TODO(sredmond): Think harder about which of these should be required and # which should be optional. def __init__(self, family, size, weight=False, slant=False): self._family = family self._size = size self._weight = weight self._slant = slant metrics = _platform.Platform().gfont_get_font_metrics(self) self._ascent = metrics['ascent'] self._descent = metrics['ascent'] self._linespace = metrics['ascent'] # TODO(sredmond): Push the actual value of 1 back down into the platform abstraction. self._fixed = metrics['fixed'] == '1'
[docs] @classmethod def parse(cls, description): """Parse a description into a font. A description can be either FAMILY-SIZE or FAMILY-SIZE-MODIFIERS, where FAMILY is the name of a font family available on the current system, SIZE is a positive integer representing the font size (in points, not in pixels), and MODIFIERS is either the string "bold", "bold-italic", or "italic". Examples of valid font descriptions include:: Helvetica-12 Dialog-13 Courier-18 Times New Roman-24-bold Times-16-italic Verdana-11-bold-italic This method returns None if no font could be parsed. """ # TODO(sredmond): Redesign the allowed description scheme to handle a # broader set of descriptions. Notably, fonts with dashes in their # names are currently impossible to specify. I don't know of any fonts # with that property, but it's still an unnecessary restriction. pieces = description.split('-') if len(pieces) < 2 or len(pieces) > 4: logger.warning('Unable to parse font description {!r} into an appropriate number of pieces.'.format(description)) return family = pieces[0] size = int(pieces[1]) weight = 'bold' in pieces[2:] slant = 'italic' in pieces[2:] return cls(family, size, weight, slant)
[docs] @classmethod def system(cls, font_name): logger.info('Loading a font from a system name may not be portable.') attributes = _platform.Platform().gfont_attributes_from_system_name(font_name) return cls(family=attributes['family'], size=attributes['size'], weight=bool(attributes['weight']), slant=bool(attributes['slant']))
# TODO(sredmond): Also support X Font Descriptors, which is something like: # -*-family-weight-slant-*--*-size-*-*-*-*-charset
[docs] @classmethod def default(cls): attributes = _platform.Platform().gfont_default_attributes() return cls(family=attributes['family'], size=attributes['size'], weight=attributes['weight'] != 'normal', slant=attributes['slant'] != 'roman')
@property def family(self): return self._family @property def size(self): return self._size @property def weight(self): return self._weight @property def slant(self): return self._slant @property def ascent(self): return self._ascent @property def descent(self): return self._descent @property def linespace(self): return self._linespace @property def fixed(self): return self._fixed
[docs] def measure(self, text): return _platform.Platform().gfont_measure_text_width(self, text)
def __str__(self): """Implement `str(self)`.""" out = '{family}-{size}'.format(family=self.family, size=self.size) if self.weight: out += '-bold' if self.slant: out += '-italic' return out def __repr__(self): """Implement `repr(self)`.""" return 'GFont(family={family}, size={size}, weight={weight}, slant={slant})'.format( family=self.family, size=self.size, weight='bold' if self.weight else 'normal', slant='italic' if self.slant else 'roman' )