Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotations and doc style to font package. #1093

Merged
merged 13 commits into from Apr 17, 2024
6 changes: 6 additions & 0 deletions doc/modules/font.rst
Expand Up @@ -4,3 +4,9 @@ pyglet.font
.. automodule:: pyglet.font
:members:
:undoc-members:

pyglet.font.user
================
.. automodule:: pyglet.font.user
:members:
:undoc-members:
95 changes: 54 additions & 41 deletions pyglet/font/__init__.py
Expand Up @@ -15,29 +15,33 @@
See the :mod:`pyglet.font.base` module for documentation on the base classes used
by this package.
"""
from __future__ import annotations

import os
import sys
import weakref
from typing import Union, BinaryIO, Optional, Iterable
from typing import TYPE_CHECKING, BinaryIO, Iterable

import pyglet
from pyglet.font.user import UserDefinedFontBase
from pyglet import gl
from pyglet.font.user import UserDefinedFontBase

if TYPE_CHECKING:
from pyglet.font.base import Font

def _get_system_font_class():

def _get_system_font_class() -> type[Font]:
"""Get the appropriate class for the system being used.

Pyglet relies on OS dependent font systems for loading fonts and glyph creation.
"""
if pyglet.compat_platform == 'darwin':
if pyglet.compat_platform == "darwin":
from pyglet.font.quartz import QuartzFont
_font_class = QuartzFont

elif pyglet.compat_platform in ('win32', 'cygwin'):
elif pyglet.compat_platform in ("win32", "cygwin"):
from pyglet.libs.win32.constants import WINDOWS_7_OR_GREATER
if WINDOWS_7_OR_GREATER and not pyglet.options['win32_gdi_font']:
if WINDOWS_7_OR_GREATER and not pyglet.options["win32_gdi_font"]:
from pyglet.font.directwrite import Win32DirectWriteFont
_font_class = Win32DirectWriteFont
else:
Expand All @@ -50,22 +54,26 @@ def _get_system_font_class():
return _font_class


def add_user_font(font: UserDefinedFontBase):
def add_user_font(font: UserDefinedFontBase) -> None:
"""Add a custom font created by the user.

A strong reference needs to be applied to the font object,
otherwise pyglet may not find the font later.

:Parameters:
`font` : `~pyglet.font.user.UserDefinedFont`
Args:
font:
A font class instance defined by user.

Raises:
Exception: If font provided is not derived from :py:class:`~pyglet.font.user.UserDefinedFontBase`.
"""
if not isinstance(font, UserDefinedFontBase):
raise Exception("Font is not must be created fromm the UserDefinedFontBase.")
msg = "Font is not must be created fromm the UserDefinedFontBase."
raise Exception(msg)

# Locate or create font cache
shared_object_space = gl.current_context.object_space
if not hasattr(shared_object_space, 'pyglet_font_font_cache'):
if not hasattr(shared_object_space, "pyglet_font_font_cache"):
shared_object_space.pyglet_font_font_cache = weakref.WeakValueDictionary()
shared_object_space.pyglet_font_font_hold = []
# Match a tuple to specific name to reduce lookups.
Expand All @@ -76,9 +84,11 @@ def add_user_font(font: UserDefinedFontBase):
# Look for font name in font cache
descriptor = (font.name, font.size, font.bold, font.italic, font.stretch, font.dpi)
if descriptor in font_cache:
raise Exception(f"A font with parameters {descriptor} has already been created.")
msg = f"A font with parameters {descriptor} has already been created."
raise Exception(msg)
if _system_font_class.have_font(font.name):
raise Exception(f"Font name '{font.name}' already exists within the system fonts.")
msg = f"Font name '{font.name}' already exists within the system fonts."
raise Exception(msg)

if font.name not in _user_fonts:
_user_fonts.append(font.name)
Expand All @@ -91,33 +101,36 @@ def add_user_font(font: UserDefinedFontBase):


def have_font(name: str) -> bool:
"""Check if specified system font name is available."""
"""Check if specified font name is available in the system database or user font database."""
return name in _user_fonts or _system_font_class.have_font(name)


def load(name: Optional[Union[str, Iterable[str]]] = None, size: Optional[float] = None, bold: bool = False,
italic: bool = False, stretch: bool = False, dpi: Optional[float] = None):
def load(name: str | Iterable[str] | None = None, size: float | None = None, bold: bool | str = False,
italic: bool | str = False, stretch: bool | str = False, dpi: float | None = None) -> Font:
"""Load a font for rendering.

:Parameters:
`name` : str, or list of str
Args:
name:
Font family, for example, "Times New Roman". If a list of names
is provided, the first one matching a known font is used. If no
font can be matched to the name(s), a default font is used. In
pyglet 1.1, the name may be omitted.
`size` : float
font can be matched to the name(s), a default font is used. The default font
will be platform dependent.
size:
Size of the font, in points. The returned font may be an exact
match or the closest available.
`bold` : bool
bold:
If True, a bold variant is returned, if one exists for the given
family and size.
`italic` : bool
If True, an italic variant is returned, if one exists for the given
family and size.
`dpi` : float
family and size. For some Font renderers, bold is the weight of the font, and a string
can be provided specifying the weight. For example, "semibold" or "light".
italic:
If True, an italic variant is returned, if one exists for the given family and size. For some Font
renderers, italics may have an "oblique" variation which can be specified as a string.
stretch:
If True, a stretch variant is returned, if one exists for the given family and size. Currently only
supported by Windows through the ``DirectWrite`` font renderer. For example, "condensed" or "expanded".
dpi: float
The assumed resolution of the display device, for the purposes of
determining the pixel size of the font. Defaults to 96.
:rtype: `Font`
"""
# Arbitrary default size
if size is None:
Expand All @@ -127,7 +140,7 @@ def load(name: Optional[Union[str, Iterable[str]]] = None, size: Optional[float]

# Locate or create font cache
shared_object_space = gl.current_context.object_space
if not hasattr(shared_object_space, 'pyglet_font_font_cache'):
if not hasattr(shared_object_space, "pyglet_font_font_cache"):
shared_object_space.pyglet_font_font_cache = weakref.WeakValueDictionary()
shared_object_space.pyglet_font_font_hold = []
# Match a tuple to specific name to reduce lookups.
Expand Down Expand Up @@ -162,7 +175,7 @@ def load(name: Optional[Union[str, Iterable[str]]] = None, size: Optional[float]
# Not in cache, create from scratch
font = _system_font_class(name, size, bold=bold, italic=italic, stretch=stretch, dpi=dpi)
# Save parameters for new-style layout classes to recover
# TODO: add properties to the Font classes, so these can be queried:
# TODO: add properties to the base Font so completion is proper:
font.size = size
font.bold = bold
font.italic = italic
Expand All @@ -178,12 +191,12 @@ def load(name: Optional[Union[str, Iterable[str]]] = None, size: Optional[float]
return font


if not getattr(sys, 'is_pyglet_doc_run', False):
if not getattr(sys, "is_pyglet_doc_run", False):
_system_font_class = _get_system_font_class()
_user_fonts = []


def add_file(font: Union[str, BinaryIO]):
def add_file(font: str | BinaryIO) -> None:
"""Add a font to pyglet's search path.

In order to load a font that is not installed on the system, you must
Expand All @@ -195,32 +208,32 @@ def add_file(font: Union[str, BinaryIO]):
you should pass the face name (not the file name) to :meth::py:func:`pyglet.font.load` or any
other place where you normally specify a font.

:Parameters:
`font` : str or file-like object
Args:
font:
Filename or file-like object to load fonts from.

"""
if isinstance(font, str):
font = open(font, 'rb')
if hasattr(font, 'read'):
font = open(font, "rb") # noqa: SIM115
if hasattr(font, "read"):
font = font.read()
_system_font_class.add_font_data(font)


def add_directory(directory):
def add_directory(directory: str) -> None:
"""Add a directory of fonts to pyglet's search path.

This function simply calls :meth:`pyglet.font.add_file` for each file with a ``.ttf``
extension in the given directory. Subdirectories are not searched.

:Parameters:
`dir` : str
Args:
directory:
Directory that contains font files.

"""
for file in os.listdir(directory):
if file[-4:].lower() == '.ttf':
if file[-4:].lower() == ".ttf":
add_file(os.path.join(directory, file))


__all__ = ('add_file', 'add_directory', 'add_user_font', 'load', 'have_font')
__all__ = ("add_file", "add_directory", "add_user_font", "load", "have_font")