"""A font abstraction used to render text."""
from typing import Literal
import sdl2, sdl2.sdlttf, sdl2.ext
from importlib.resources import files
import ctypes
from .. import Color
[docs]class Font:
"""
This is the font object that is used to render text.
Args:
font: The font to use. Can also be a path to a font file. Defaults to Roboto.
Included fonts are "Comfortaa", "Fredoka", "Merriweather", "Roboto", "SourceCodePro", "Mozart"
size: The size of the font in pixels. Defaults to 16.
styles: The styles to apply to the font. Set multiple styles by | them together. Defaults to 0.
These are the values you can use: Font.BOLD, Font.ITALIC, Font.UNDERLINE, Font.STRIKETHROUGH.
color: The color of the font. Defaults to Color(0, 0, 0).
"""
_text_fonts = {
"Comfortaa": "Comfortaa-Regular.ttf",
"Fredoka": "Fredoka-Regular.ttf",
"Merriweather": "Merriweather-Regular.ttf",
"Roboto": "Roboto-Regular.ttf",
"SourceCodePro": "SourceCodePro-Regular.ttf",
"Mozart": "Mozart-Regular.ttf",
}
BOLD = sdl2.sdlttf.TTF_STYLE_BOLD
ITALIC = sdl2.sdlttf.TTF_STYLE_ITALIC
UNDERLINE = sdl2.sdlttf.TTF_STYLE_UNDERLINE
STRIKETHROUGH = sdl2.sdlttf.TTF_STYLE_STRIKETHROUGH
def __init__(
self,
font: str | Literal["Comfortaa", "Fredoka", "Merriweather", "Roboto", "SourceCodePro", "Mozart"] = "Roboto",
size: int = 16,
styles: int = 0,
color: Color = Color(0, 0, 0),
):
self._size = size
self._styles = styles
self._color = color
if font in Font._text_fonts:
self._font_path = str(files("rubato.static.fonts").joinpath(Font._text_fonts[font]))
else:
self._font_path = font
try:
self._font = sdl2.ext.FontTTF(self._font_path, str(self._size) + "px", self._color.to_tuple())
except ValueError as e:
raise FileNotFoundError(f"Font {font} cannot be found.") from e
self.apply_styles()
@property
def size(self) -> int:
"""The size of the text in points."""
return self._size
@size.setter
def size(self, new: int):
self._size = new
sdl2.sdlttf.TTF_SetFontSize(self._font.get_ttf_font(), new)
@property
def color(self) -> Color:
"""The color of the text."""
return self._color
@color.setter
def color(self, new: Color):
self._color = new
self._font = sdl2.ext.FontTTF(self._font_path, self._size, self._color.to_tuple())
self.apply_styles()
def _generate(self, text: str, align: str, width: int | float = 0) -> sdl2.SDL_Surface:
"""
Generate a surface containing the text.
Args:
text: The text to render.
align: The alignment to use.
width: The maximum width to use. Defaults to -1.
rot: The rotation of the text in degrees. Defaults to 0.
Raises:
ValueError: The width is too small for the text.
ValueError: The size of the text is too large for the font.
Returns:
The surface containing the text.
"""
try:
return self._font.render_text(text, width=None if width <= 0 else round(width), align=align)
except RuntimeError as e:
raise ValueError(f"The width {width} is too small for the text.") from e
except OSError as e:
raise ValueError(f"The size {self._size} is too big for the text.") from e
[docs] def size_text(self, text: str) -> tuple[int, int]:
"""
Calculated the dimensions of a string of text using a given font.
Args:
text: The string of text to calculate dimensions for.
Returns:
The dimensions of the string.
"""
text_w, text_h = ctypes.c_int(0), ctypes.c_int(0)
sdl2.sdlttf.TTF_SizeText(self._font.get_ttf_font(), text.encode(), ctypes.byref(text_w), ctypes.byref(text_h))
return (text_w.value, text_h.value)
[docs] def add_style(self, *styles: int):
"""
Adds a style to the font.
Style can be one of the following: Font.BOLD, Font.ITALIC, Font.UNDERLINE, Font.STRIKETHROUGH.
Args:
style: The style to add.
"""
# For developer: now that style is an bit map, 0000, each bit represents a style
for style in styles:
if 0 <= style <= 15:
self._styles |= style
self.apply_styles()
else:
raise ValueError("Style is not valid.")
[docs] def remove_style(self, *styles: int):
"""
Removes a style from the font.
Style can be one of the following: Font.BOLD, Font.ITALIC, Font.UNDERLINE, Font.STRIKETHROUGH.
Args:
style: The style to remove.
"""
for style in styles:
if 0 <= style <= 15:
self._styles &= ~style
self.apply_styles()
else:
raise ValueError("Style is not valid.")
[docs] def apply_styles(self):
"""Applies the styles to the font."""
sdl2.sdlttf.TTF_SetFontStyle(self._font.get_ttf_font(), self._styles)
[docs] def clone(self):
"""
Clones the font.
"""
return Font(self._font_path, self._size, self._styles, self._color.clone())
def __del__(self):
self._font.close()