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

switch to pygame.font for rendering text #511

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 5 additions & 24 deletions pygame_gui/core/gui_font_freetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def __init__(self, file: Optional[FileArg], size: Union[int, float],
self.__internal_font: Font = Font(file, size, resolution=72)
self.__internal_font.pad = True
self.__internal_font.origin = True
self.__internal_font.kerning = True

self.__underline = False
self.__underline_adjustment = 0.0
Expand Down Expand Up @@ -50,17 +51,10 @@ def underline_adjustment(self, value: float):
def get_point_size(self):
return self.point_size

def get_font(self) -> Font:
return self.__internal_font

def get_text_height(self, text: str) -> int:
pass

def get_text_width(self, text: str) -> int:
pass

def get_rect(self, text: str) -> Rect:
return self.__internal_font.get_rect(text)
supposed_rect = self.__internal_font.get_rect(text)
text_surface, text_rect = self.__internal_font.render(text, pygame.Color("white"))
return pygame.Rect(supposed_rect.topleft, text_surface.get_size())

def get_metrics(self, text: str):
return self.__internal_font.get_metrics(text)
Expand Down Expand Up @@ -88,18 +82,5 @@ def get_padding_height(self):
# that doesn't drop below the base line (no y's, g's, p's etc)
# but also don't want it to flicker on and off. Base-line
# centering is the default for chunks on a single style row.
padding_state = self.__internal_font.pad

self.__internal_font.pad = False
no_pad_origin = self.__internal_font.get_rect('A').y

self.__internal_font.pad = True
pad_origin = self.__internal_font.get_rect('A').y

self.__internal_font.pad = padding_state
return pad_origin - no_pad_origin

def remove_font_pad_and_origin(self):
self.__internal_font.pad = False
self.__internal_font.origin = False

return -self.__internal_font.get_sized_descender(self.point_size)
98 changes: 98 additions & 0 deletions pygame_gui/core/gui_font_pygame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import pygame

from pygame_gui.core.interfaces.gui_font_interface import IGUIFontInterface
from pygame.font import Font
from typing import Union, IO, Optional, Dict, Tuple
from os import PathLike
from pygame import Color, Surface, Rect

AnyPath = Union[str, bytes, PathLike]
FileArg = Union[AnyPath, IO]


class GUIFontPygame(IGUIFontInterface):

def __init__(self, file: Optional[FileArg], size: Union[int, float],
force_style: bool = False, style: Optional[Dict[str, bool]] = None):
self.__internal_font: Font = Font(file, size) # no resolution option for pygame font?

self.__internal_font.set_point_size(size)
self.pad = True
self.origin = True
self.__underline = False
self.__underline_adjustment = 0.0

self.point_size = size
self.antialiased = True

if style is not None:
self.antialiased = style['antialiased']

if force_style:
self.__internal_font.bold = style['bold']
self.__internal_font.italic = style['italic']

Check warning on line 33 in pygame_gui/core/gui_font_pygame.py

View check run for this annotation

Codecov / codecov/patch

pygame_gui/core/gui_font_pygame.py#L32-L33

Added lines #L32 - L33 were not covered by tests

def size(self, text: str):
return self.__internal_font.size(text)

Check warning on line 36 in pygame_gui/core/gui_font_pygame.py

View check run for this annotation

Codecov / codecov/patch

pygame_gui/core/gui_font_pygame.py#L36

Added line #L36 was not covered by tests

@property
def underline(self) -> bool:
return self.__internal_font.underline

@underline.setter
def underline(self, value: bool):
self.__internal_font.underline = value

@property
def underline_adjustment(self) -> float:
# underline adjustment is missing in pygame.font. Would need to be added to SDL ttf
return self.__underline_adjustment

@underline_adjustment.setter
def underline_adjustment(self, value: float):
self.__underline_adjustment = value

def get_point_size(self):
return self.point_size

def get_rect(self, text: str) -> Rect:
# only way to get accurate font layout data with kerning is to render it ourselves it seems
text_surface = self.__internal_font.render(text, self.antialiased, pygame.Color("white"))
return pygame.Rect((0, self.__internal_font.get_ascent()), text_surface.get_size())

def get_metrics(self, text: str):
# this may need to be broken down further in the wrapper
return self.__internal_font.metrics(text)

def render_premul(self, text: str, text_color: Color) -> Surface:
text_surface = self.__internal_font.render(text, self.antialiased, text_color)
text_surface = text_surface.convert_alpha()
if text_surface.get_width() > 0 and text_surface.get_height() > 0:
text_surface = text_surface.premul_alpha()
return text_surface

def render_premul_to(self, text: str, text_colour: Color,
surf_size: Tuple[int, int], surf_position: Tuple[int, int]) -> Surface:
text_surface = pygame.Surface(surf_size, depth=32, flags=pygame.SRCALPHA)
text_surface.fill((0, 0, 0, 0))
temp_surf = self.__internal_font.render(text, self.antialiased, text_colour)
temp_surf = temp_surf.convert_alpha()
if temp_surf.get_width() > 0 and temp_surf.get_height() > 0:
temp_surf = temp_surf.premul_alpha()
text_surface.blit(temp_surf, (surf_position[0], surf_position[1]-self.__internal_font.get_ascent()),
special_flags=pygame.BLEND_PREMULTIPLIED)
return text_surface

def get_padding_height(self):
# 'font padding' this determines the amount of padding that
# font.pad adds to the top of text excluding
# any padding added to make glyphs even - this is useful
# for 'base-line centering' when we want to center text
# that doesn't drop below the base line (no y's, g's, p's etc)
# but also don't want it to flicker on and off. Base-line
# centering is the default for chunks on a single style row.

descender = self.__internal_font.get_descent()
return -descender + 1


22 changes: 0 additions & 22 deletions pygame_gui/core/interfaces/gui_font_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,6 @@ def render_premul_to(self, text: str, text_colour: Color, surf_size: Tuple[int,
:return:
"""

@abstractmethod
def get_text_width(self, text: str) -> int:
"""
Returns the width of text drawn by this font in pixels
:param text:
:return: The pixel width of the text.
"""

@abstractmethod
def get_text_height(self, text: str) -> int:
"""
Returns the height of text drawn by this font in pixels
:param text:
:return: The pixel height of the text.
"""

@abstractmethod
def get_rect(self, text: str) -> Rect:
"""
Expand Down Expand Up @@ -103,9 +87,3 @@ def underline_adjustment(self, value: float):
:return:
"""

@abstractmethod
def remove_font_pad_and_origin(self):
"""

:return:
"""
29 changes: 22 additions & 7 deletions pygame_gui/core/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from pygame_gui.core.interfaces import IUIManagerInterface, IGUIFontInterface
from pygame_gui.core.gui_font_freetype import GUIFontFreetype
from pygame_gui.core.gui_font_pygame import GUIFontPygame


__default_manager = None # type: Optional[IUIManagerInterface]
Expand Down Expand Up @@ -380,7 +381,9 @@
self.style = style
self.location = location[0]
self.force_style = location[1]
self.loaded_font = None # type: Union[GUIFontFreetype, None]
self.loaded_font = None # type: Union[IGUIFontInterface, None]

self.font_type_to_use = "pygame"

def load(self):
"""
Expand All @@ -392,25 +395,37 @@
error = None
if isinstance(self.location, PackageResource):
try:
self.loaded_font = GUIFontFreetype(
io.BytesIO((resources.files(self.location.package) /
self.location.resource).read_bytes()),
self.size, self.force_style, self.style)
if self.font_type_to_use == "freetype":
self.loaded_font = GUIFontFreetype(

Check warning on line 399 in pygame_gui/core/utility.py

View check run for this annotation

Codecov / codecov/patch

pygame_gui/core/utility.py#L399

Added line #L399 was not covered by tests
io.BytesIO((resources.files(self.location.package) /
self.location.resource).read_bytes()),
self.size, self.force_style, self.style)
elif self.font_type_to_use == "pygame":
self.loaded_font = GUIFontPygame(
io.BytesIO((resources.files(self.location.package) /
self.location.resource).read_bytes()),
self.size, self.force_style, self.style)
except (pygame.error, FileNotFoundError, OSError):
error = FileNotFoundError('Unable to load resource with path: ' +
str(self.location))

elif isinstance(self.location, str):
try:
self.loaded_font = GUIFontFreetype(self.location, self.size, self.force_style, self.style)
if self.font_type_to_use == "freetype":
self.loaded_font = GUIFontFreetype(self.location, self.size, self.force_style, self.style)

Check warning on line 415 in pygame_gui/core/utility.py

View check run for this annotation

Codecov / codecov/patch

pygame_gui/core/utility.py#L415

Added line #L415 was not covered by tests
elif self.font_type_to_use == "pygame":
self.loaded_font = GUIFontPygame(self.location, self.size, self.force_style, self.style)
except (pygame.error, FileNotFoundError, OSError):
error = FileNotFoundError('Unable to load resource with path: ' +
str(self.location))

elif isinstance(self.location, bytes):
try:
file_obj = io.BytesIO(base64.standard_b64decode(self.location))
self.loaded_font = GUIFontFreetype(file_obj, self.size, self.force_style, self.style)
if self.font_type_to_use == "freetype":
self.loaded_font = GUIFontFreetype(file_obj, self.size, self.force_style, self.style)
elif self.font_type_to_use == "pygame":
self.loaded_font = GUIFontPygame(file_obj, self.size, self.force_style, self.style)

Check warning on line 428 in pygame_gui/core/utility.py

View check run for this annotation

Codecov / codecov/patch

pygame_gui/core/utility.py#L425-L428

Added lines #L425 - L428 were not covered by tests
except (pygame.error, FileNotFoundError, OSError):
error = FileNotFoundError('Unable to load resource with path: ' +
str(self.location))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def test_apply_colour_to_surface(self, _init_pygame, default_ui_manager: UIManag
after_application_colour = test_surface_2.get_at((30, 0))
assert after_application_colour == pygame.Color(255, 255, 255, 255)

def test_rebuild_images_and_text(self, _init_pygame, default_ui_manager: UIManager):
def test_rebuild_images_and_text(self, _init_pygame, _display_surface_return_none, default_ui_manager: UIManager):
shape = DrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
theming_parameters={'text': 'doop doop',
'font': default_ui_manager.get_theme().get_font([]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


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

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

def test_full_rebuild_on_size_change_large(self, _init_pygame, default_ui_manager: UIManager):
def test_full_rebuild_on_size_change_large(self, _init_pygame, _display_surface_return_none,
default_ui_manager: UIManager):
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 25, 25),
theming_parameters={'text': 'test',
'font': default_ui_manager.ui_theme.get_font([]),
Expand All @@ -51,7 +53,8 @@ def test_full_rebuild_on_size_change_large(self, _init_pygame, default_ui_manage
states=['normal'], manager=default_ui_manager)
shape.full_rebuild_on_size_change()

def test_full_rebuild_on_size_change_large_shadow(self, _init_pygame, default_ui_manager: UIManager):
def test_full_rebuild_on_size_change_large_shadow(self, _init_pygame, _display_surface_return_none,
default_ui_manager: UIManager):
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 2, 2),
theming_parameters={'text': 'test',
'font': default_ui_manager.ui_theme.get_font([]),
Expand All @@ -66,7 +69,8 @@ def test_full_rebuild_on_size_change_large_shadow(self, _init_pygame, default_ui
states=['normal'], manager=default_ui_manager)
shape.full_rebuild_on_size_change()

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

def test_set_position(self, _init_pygame, default_ui_manager: UIManager):
def test_set_position(self, _init_pygame, _display_surface_return_none,
default_ui_manager: UIManager):
shape = EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
theming_parameters={'text': 'test',
'font': default_ui_manager.ui_theme.get_font([]),
Expand All @@ -96,7 +101,8 @@ def test_set_position(self, _init_pygame, default_ui_manager: UIManager):
states=['normal'], manager=default_ui_manager)
shape.set_position((50, 50))

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

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

def test_creation_with_gradients(self, _init_pygame, default_ui_manager: UIManager):
def test_creation_with_gradients(self, _init_pygame, _display_surface_return_none,
default_ui_manager: UIManager):
EllipseDrawableShape(containing_rect=pygame.Rect(0, 0, 100, 100),
theming_parameters={'text': 'test',
'font': default_ui_manager.get_theme().get_font([]),
Expand Down
Loading
Loading