Coverage for topdownengine/font.py: 53%
55 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 14:20 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 14:20 +0000
1import pygame as pg
3class Font:
4 """A class built on top of pygame.Font that allows for caching of different sizes, prebuilt word wrap, and other features.
6 Attributes:
7 path (str): The path to load from. If there is no font at that path, it will load a system font of that name.
8 """
10 def __init__(self, path: str):
11 """Initialize the Font object.
13 Args:
14 path (str): The path to load from. If there is no font at that path, it will load a system font of that name.
15 """
17 self.path = path
18 self._sizes = {}
20 def _get_size(self, size: int) -> pg.Font:
21 if not size in self._sizes:
22 try:
23 # Attempt to load it as a custom font file
24 self._sizes[size] = pg.font.Font(self.path, size)
25 except (FileNotFoundError, pg.error):
26 # Try to get it as a system font if it doesn't exist
27 # If it's not a system font, pygame-ce uses a default font.
28 self._sizes[size] = pg.font.SysFont(self.path, size)
30 return self._sizes[size]
32 def get_max_size_for_text_in_rect(self, text: str, target_rect: pg.Rect):
33 """Finds the largest font size where the text fits inside a target_rect. Uses the height as the maximum logical starting size.
35 Args:
36 text (str): The text to scale.
37 target_rect (pygame.Rect): The Rect object to scale to.
39 Returns:
40 int: The largest possible font size where the text fits in the target_rect.
41 """
42 start_size = max(2, target_rect.height)
44 for size in range(start_size, 1, -1):
45 rendered = self._render(size, text, "black")
46 text_w, text_h = rendered.get_bounding_rect().size
48 if text_w <= target_rect.width and text_h <= target_rect.height:
49 return size
51 return 2
53 def wrap(self, line: str, size: int, max_width: int) -> list[str]:
54 """Break a single string into multiple lines based on width.
56 Args:
57 line (str): The string to wrap.
58 size (int): The fontsize to use for calculations.
59 max_width (int): The maximum width for each wrapped line.
61 Returns:
62 list[str]: The list of wrapped lines.
63 """
64 lines = []
65 current_line = ""
66 fnt = self._get_size(size)
68 # Split by spaces
69 words = line.split(" ")
71 for word in words:
72 if not word:
73 continue
75 # Determine the line to test size with
76 test_line = f"{current_line} {word}" if current_line else word
78 # If it fits, add it to the current line
79 if fnt.size(test_line)[0] <= max_width:
80 current_line = test_line
81 else:
82 # The current line is full, so append it and make a new line.
83 if current_line:
84 lines.append(current_line)
85 current_line = ""
87 # Check if the word itself is wider than max_width
88 if fnt.size(word)[0] > max_width:
89 # Split by character for oversized words
90 temp_word = ""
91 for char in word:
92 if fnt.size(temp_word + char)[0] <= max_width:
93 temp_word += char
94 else:
95 lines.append(temp_word)
96 temp_word = char
97 current_line = temp_word
98 else:
99 current_line = word
101 # Add the final leftover line
102 if current_line:
103 lines.append(current_line)
105 return lines
107 def _render(self, size: int, text: str, color: pg.typing.ColorLike):
108 return self._get_size(size).render(text, True, color)
110 def draw_text(self, text: str, x: int, y: int, size: int, surface: pg.Surface, color: pg.typing.ColorLike, align: str="center") -> None:
111 """Draws text to a surface.
113 Args:
114 text (str): The text to render to the surface.
115 x (int): The x-position.
116 y (int): The y-position.
117 size (int): The font size to use.
118 surface (pygame.Surface): The surface to render to.
119 color (pygame.typing.ColorLike): The color to use.
120 align (str): The alignment to use. Defaults to "center".
122 Raises:
123 ValueError: If an invalid align argument is passed into the method.
124 """
125 surf = self._render(size, text, color)
127 try:
128 rect = surf.get_rect(**{align: (x, y)})
129 except AttributeError:
130 raise ValueError(f"Invalid align value of {align}")
132 surface.blit(surf, rect)