Skip to content

Commit 681f121

Browse files
committed
switch to pygame.font for rendering text
should enable some arabic font rendering eventually with a bit more hacking
1 parent 02e73cc commit 681f121

14 files changed

+321
-121
lines changed

pygame_gui/core/gui_font_freetype.py

+5-24
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def __init__(self, file: Optional[FileArg], size: Union[int, float],
1717
self.__internal_font: Font = Font(file, size, resolution=72)
1818
self.__internal_font.pad = True
1919
self.__internal_font.origin = True
20+
self.__internal_font.kerning = True
2021

2122
self.__underline = False
2223
self.__underline_adjustment = 0.0
@@ -50,17 +51,10 @@ def underline_adjustment(self, value: float):
5051
def get_point_size(self):
5152
return self.point_size
5253

53-
def get_font(self) -> Font:
54-
return self.__internal_font
55-
56-
def get_text_height(self, text: str) -> int:
57-
pass
58-
59-
def get_text_width(self, text: str) -> int:
60-
pass
61-
6254
def get_rect(self, text: str) -> Rect:
63-
return self.__internal_font.get_rect(text)
55+
supposed_rect = self.__internal_font.get_rect(text)
56+
text_surface, text_rect = self.__internal_font.render(text, pygame.Color("white"))
57+
return pygame.Rect(supposed_rect.topleft, text_surface.get_size())
6458

6559
def get_metrics(self, text: str):
6660
return self.__internal_font.get_metrics(text)
@@ -88,18 +82,5 @@ def get_padding_height(self):
8882
# that doesn't drop below the base line (no y's, g's, p's etc)
8983
# but also don't want it to flicker on and off. Base-line
9084
# centering is the default for chunks on a single style row.
91-
padding_state = self.__internal_font.pad
92-
93-
self.__internal_font.pad = False
94-
no_pad_origin = self.__internal_font.get_rect('A').y
95-
96-
self.__internal_font.pad = True
97-
pad_origin = self.__internal_font.get_rect('A').y
98-
99-
self.__internal_font.pad = padding_state
100-
return pad_origin - no_pad_origin
101-
102-
def remove_font_pad_and_origin(self):
103-
self.__internal_font.pad = False
104-
self.__internal_font.origin = False
10585

86+
return -self.__internal_font.get_sized_descender(self.point_size)

pygame_gui/core/gui_font_pygame.py

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import pygame
2+
3+
from pygame_gui.core.interfaces.gui_font_interface import IGUIFontInterface
4+
from pygame.font import Font
5+
from typing import Union, IO, Optional, Dict, Tuple
6+
from os import PathLike
7+
from pygame import Color, Surface, Rect
8+
9+
AnyPath = Union[str, bytes, PathLike]
10+
FileArg = Union[AnyPath, IO]
11+
12+
13+
class GUIFontPygame(IGUIFontInterface):
14+
15+
def __init__(self, file: Optional[FileArg], size: Union[int, float],
16+
force_style: bool = False, style: Optional[Dict[str, bool]] = None):
17+
self.__internal_font: Font = Font(file, size) # no resolution option for pygame font?
18+
19+
self.__internal_font.set_point_size(size)
20+
self.pad = True
21+
self.origin = True
22+
self.__underline = False
23+
self.__underline_adjustment = 0.0
24+
25+
self.point_size = size
26+
self.antialiased = True
27+
28+
if style is not None:
29+
self.antialiased = style['antialiased']
30+
31+
if force_style:
32+
self.__internal_font.bold = style['bold']
33+
self.__internal_font.italic = style['italic']
34+
35+
def size(self, text: str):
36+
return self.__internal_font.size(text)
37+
38+
@property
39+
def underline(self) -> bool:
40+
return self.__internal_font.underline
41+
42+
@underline.setter
43+
def underline(self, value: bool):
44+
self.__internal_font.underline = value
45+
46+
@property
47+
def underline_adjustment(self) -> float:
48+
# underline adjustment is missing in pygame.font. Would need to be added to SDL ttf
49+
return self.__underline_adjustment
50+
51+
@underline_adjustment.setter
52+
def underline_adjustment(self, value: float):
53+
self.__underline_adjustment = value
54+
55+
def get_point_size(self):
56+
return self.point_size
57+
58+
def get_rect(self, text: str) -> Rect:
59+
# only way to get accurate font layout data with kerning is to render it ourselves it seems
60+
text_surface = self.__internal_font.render(text, self.antialiased, pygame.Color("white"))
61+
return pygame.Rect((0, self.__internal_font.get_ascent()), text_surface.get_size())
62+
63+
def get_metrics(self, text: str):
64+
# this may need to be broken down further in the wrapper
65+
return self.__internal_font.metrics(text)
66+
67+
def render_premul(self, text: str, text_color: Color) -> Surface:
68+
text_surface = self.__internal_font.render(text, self.antialiased, text_color)
69+
text_surface = text_surface.convert_alpha()
70+
if text_surface.get_width() > 0 and text_surface.get_height() > 0:
71+
text_surface = text_surface.premul_alpha()
72+
return text_surface
73+
74+
def render_premul_to(self, text: str, text_colour: Color,
75+
surf_size: Tuple[int, int], surf_position: Tuple[int, int]) -> Surface:
76+
text_surface = pygame.Surface(surf_size, depth=32, flags=pygame.SRCALPHA)
77+
text_surface.fill((0, 0, 0, 0))
78+
temp_surf = self.__internal_font.render(text, self.antialiased, text_colour)
79+
temp_surf = temp_surf.convert_alpha()
80+
if temp_surf.get_width() > 0 and temp_surf.get_height() > 0:
81+
temp_surf = temp_surf.premul_alpha()
82+
text_surface.blit(temp_surf, (surf_position[0], surf_position[1]-self.__internal_font.get_ascent()),
83+
special_flags=pygame.BLEND_PREMULTIPLIED)
84+
return text_surface
85+
86+
def get_padding_height(self):
87+
# 'font padding' this determines the amount of padding that
88+
# font.pad adds to the top of text excluding
89+
# any padding added to make glyphs even - this is useful
90+
# for 'base-line centering' when we want to center text
91+
# that doesn't drop below the base line (no y's, g's, p's etc)
92+
# but also don't want it to flicker on and off. Base-line
93+
# centering is the default for chunks on a single style row.
94+
95+
descender = self.__internal_font.get_descent()
96+
return -descender + 1
97+
98+

pygame_gui/core/interfaces/gui_font_interface.py

-22
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,6 @@ def render_premul_to(self, text: str, text_colour: Color, surf_size: Tuple[int,
2525
:return:
2626
"""
2727

28-
@abstractmethod
29-
def get_text_width(self, text: str) -> int:
30-
"""
31-
Returns the width of text drawn by this font in pixels
32-
:param text:
33-
:return: The pixel width of the text.
34-
"""
35-
36-
@abstractmethod
37-
def get_text_height(self, text: str) -> int:
38-
"""
39-
Returns the height of text drawn by this font in pixels
40-
:param text:
41-
:return: The pixel height of the text.
42-
"""
43-
4428
@abstractmethod
4529
def get_rect(self, text: str) -> Rect:
4630
"""
@@ -103,9 +87,3 @@ def underline_adjustment(self, value: float):
10387
:return:
10488
"""
10589

106-
@abstractmethod
107-
def remove_font_pad_and_origin(self):
108-
"""
109-
110-
:return:
111-
"""

pygame_gui/core/utility.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from pygame_gui.core.interfaces import IUIManagerInterface, IGUIFontInterface
2525
from pygame_gui.core.gui_font_freetype import GUIFontFreetype
26+
from pygame_gui.core.gui_font_pygame import GUIFontPygame
2627

2728

2829
__default_manager = None # type: Optional[IUIManagerInterface]
@@ -380,7 +381,9 @@ def __init__(self,
380381
self.style = style
381382
self.location = location[0]
382383
self.force_style = location[1]
383-
self.loaded_font = None # type: Union[GUIFontFreetype, None]
384+
self.loaded_font = None # type: Union[IGUIFontInterface, None]
385+
386+
self.font_type_to_use = "pygame"
384387

385388
def load(self):
386389
"""
@@ -392,25 +395,37 @@ def load(self):
392395
error = None
393396
if isinstance(self.location, PackageResource):
394397
try:
395-
self.loaded_font = GUIFontFreetype(
396-
io.BytesIO((resources.files(self.location.package) /
397-
self.location.resource).read_bytes()),
398-
self.size, self.force_style, self.style)
398+
if self.font_type_to_use == "freetype":
399+
self.loaded_font = GUIFontFreetype(
400+
io.BytesIO((resources.files(self.location.package) /
401+
self.location.resource).read_bytes()),
402+
self.size, self.force_style, self.style)
403+
elif self.font_type_to_use == "pygame":
404+
self.loaded_font = GUIFontPygame(
405+
io.BytesIO((resources.files(self.location.package) /
406+
self.location.resource).read_bytes()),
407+
self.size, self.force_style, self.style)
399408
except (pygame.error, FileNotFoundError, OSError):
400409
error = FileNotFoundError('Unable to load resource with path: ' +
401410
str(self.location))
402411

403412
elif isinstance(self.location, str):
404413
try:
405-
self.loaded_font = GUIFontFreetype(self.location, self.size, self.force_style, self.style)
414+
if self.font_type_to_use == "freetype":
415+
self.loaded_font = GUIFontFreetype(self.location, self.size, self.force_style, self.style)
416+
elif self.font_type_to_use == "pygame":
417+
self.loaded_font = GUIFontPygame(self.location, self.size, self.force_style, self.style)
406418
except (pygame.error, FileNotFoundError, OSError):
407419
error = FileNotFoundError('Unable to load resource with path: ' +
408420
str(self.location))
409421

410422
elif isinstance(self.location, bytes):
411423
try:
412424
file_obj = io.BytesIO(base64.standard_b64decode(self.location))
413-
self.loaded_font = GUIFontFreetype(file_obj, self.size, self.force_style, self.style)
425+
if self.font_type_to_use == "freetype":
426+
self.loaded_font = GUIFontFreetype(file_obj, self.size, self.force_style, self.style)
427+
elif self.font_type_to_use == "pygame":
428+
self.loaded_font = GUIFontPygame(file_obj, self.size, self.force_style, self.style)
414429
except (pygame.error, FileNotFoundError, OSError):
415430
error = FileNotFoundError('Unable to load resource with path: ' +
416431
str(self.location))

tests/test_core/test_drawable_shapes/test_drawable_shape.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def test_apply_colour_to_surface(self, _init_pygame, default_ui_manager: UIManag
185185
after_application_colour = test_surface_2.get_at((30, 0))
186186
assert after_application_colour == pygame.Color(255, 255, 255, 255)
187187

188-
def test_rebuild_images_and_text(self, _init_pygame, default_ui_manager: UIManager):
188+
def test_rebuild_images_and_text(self, _init_pygame, _display_surface_return_none, default_ui_manager: UIManager):
189189
shape = DrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
190190
theming_parameters={'text': 'doop doop',
191191
'font': default_ui_manager.get_theme().get_font([]),

tests/test_core/test_drawable_shapes/test_ellipse_drawable_shape.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class TestEllipseDrawableShape:
10-
def test_creation(self, _init_pygame, default_ui_manager: UIManager):
10+
def test_creation(self, _init_pygame, _display_surface_return_none, default_ui_manager: UIManager):
1111
EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
1212
theming_parameters={'text': 'test',
1313
'font': default_ui_manager.get_theme().get_font([]),
@@ -21,7 +21,8 @@ def test_creation(self, _init_pygame, default_ui_manager: UIManager):
2121
'text_vert_alignment': 'center'},
2222
states=['normal'], manager=default_ui_manager)
2323

24-
def test_full_rebuild_on_size_change_negative_values(self, _init_pygame, default_ui_manager: UIManager):
24+
def test_full_rebuild_on_size_change_negative_values(self, _init_pygame, _display_surface_return_none,
25+
default_ui_manager: UIManager):
2526
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
2627
theming_parameters={'text': 'test',
2728
'font': default_ui_manager.ui_theme.get_font([]),
@@ -36,7 +37,8 @@ def test_full_rebuild_on_size_change_negative_values(self, _init_pygame, default
3637
states=['normal'], manager=default_ui_manager)
3738
shape.full_rebuild_on_size_change()
3839

39-
def test_full_rebuild_on_size_change_large(self, _init_pygame, default_ui_manager: UIManager):
40+
def test_full_rebuild_on_size_change_large(self, _init_pygame, _display_surface_return_none,
41+
default_ui_manager: UIManager):
4042
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 25, 25),
4143
theming_parameters={'text': 'test',
4244
'font': default_ui_manager.ui_theme.get_font([]),
@@ -51,7 +53,8 @@ def test_full_rebuild_on_size_change_large(self, _init_pygame, default_ui_manage
5153
states=['normal'], manager=default_ui_manager)
5254
shape.full_rebuild_on_size_change()
5355

54-
def test_full_rebuild_on_size_change_large_shadow(self, _init_pygame, default_ui_manager: UIManager):
56+
def test_full_rebuild_on_size_change_large_shadow(self, _init_pygame, _display_surface_return_none,
57+
default_ui_manager: UIManager):
5558
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 2, 2),
5659
theming_parameters={'text': 'test',
5760
'font': default_ui_manager.ui_theme.get_font([]),
@@ -66,7 +69,8 @@ def test_full_rebuild_on_size_change_large_shadow(self, _init_pygame, default_ui
6669
states=['normal'], manager=default_ui_manager)
6770
shape.full_rebuild_on_size_change()
6871

69-
def test_collide_point(self, _init_pygame, default_ui_manager: UIManager):
72+
def test_collide_point(self, _init_pygame, _display_surface_return_none,
73+
default_ui_manager: UIManager):
7074
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
7175
theming_parameters={'text': 'test',
7276
'font': default_ui_manager.ui_theme.get_font([]),
@@ -81,7 +85,8 @@ def test_collide_point(self, _init_pygame, default_ui_manager: UIManager):
8185
states=['normal'], manager=default_ui_manager)
8286
assert shape.collide_point((50, 50)) is True
8387

84-
def test_set_position(self, _init_pygame, default_ui_manager: UIManager):
88+
def test_set_position(self, _init_pygame, _display_surface_return_none,
89+
default_ui_manager: UIManager):
8590
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
8691
theming_parameters={'text': 'test',
8792
'font': default_ui_manager.ui_theme.get_font([]),
@@ -96,7 +101,8 @@ def test_set_position(self, _init_pygame, default_ui_manager: UIManager):
96101
states=['normal'], manager=default_ui_manager)
97102
shape.set_position((50, 50))
98103

99-
def test_set_dimensions(self, _init_pygame, default_ui_manager: UIManager):
104+
def test_set_dimensions(self, _init_pygame, _display_surface_return_none,
105+
default_ui_manager: UIManager):
100106
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
101107
theming_parameters={'text': 'test',
102108
'font': default_ui_manager.ui_theme.get_font([]),
@@ -113,7 +119,8 @@ def test_set_dimensions(self, _init_pygame, default_ui_manager: UIManager):
113119

114120
assert not shape.set_dimensions(shape.containing_rect.size)
115121

116-
def test_creation_with_gradients(self, _init_pygame, default_ui_manager: UIManager):
122+
def test_creation_with_gradients(self, _init_pygame, _display_surface_return_none,
123+
default_ui_manager: UIManager):
117124
EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
118125
theming_parameters={'text': 'test',
119126
'font': default_ui_manager.get_theme().get_font([]),

0 commit comments

Comments
 (0)