Skip to content

Commit

Permalink
Add annotations and doc style to font package. (#1093)
Browse files Browse the repository at this point in the history
* Update __init__ font.

* Update font base.

* Update font base and DirectWrite.

* Freetype annotations.

* DirectWrite Annotations

* Add Quartz Annoations

* Add typings for TTF. Probably all wrong as nothing can be inspected to do table creation inside a function and data being decoded from data.

* Add ClassVar

* Add User defined font to font rst.
Add some documentation for adding and creating a UserDefinedFont.

* Update typing for win32 font module.

* Keep quote style to preservation level.

Add per file ignores for some of the platform and non-public specific modules.

* Fix ctypes imports coming from _ctypes.
  • Loading branch information
caffeinepills committed Apr 17, 2024
1 parent b056ea0 commit 22dba5d
Show file tree
Hide file tree
Showing 12 changed files with 1,907 additions and 1,608 deletions.
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")

0 comments on commit 22dba5d

Please sign in to comment.