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

System font fallback #560

Merged
merged 4 commits into from
Apr 6, 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
9 changes: 7 additions & 2 deletions docs/source/localization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,23 @@ Default supported languages

Right now the list of languages support by default by Pygame GUI elements and windows is:

- Arabic
- German
- English
- Spanish
- French
- Georgian
- Hebrew
- Indonesian
- Italian
- Japanese
- Korean
- Polish
- Portuguese
- Russian
- Simplified Chinese
- Polish
- Ukrainian
- Vietnamese
- Simplified Chinese

Though many of the translations may be imperfect as they were largely not handled by native speakers (yet). If you
would like to improve an existing translation or add a new one the current default translations are
Expand Down
41 changes: 41 additions & 0 deletions docs/source/pygame_gui.core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Subpackages

pygame_gui.core.drawable_shapes
pygame_gui.core.interfaces
pygame_gui.core.text

Submodules
----------
Expand All @@ -28,6 +29,38 @@ pygame\_gui.core.colour\_parser module
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.gui\_font\_pygame module
--------------------------------------

.. automodule:: pygame_gui.core.gui_font_pygame
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.layered\_gui\_group module
--------------------------------------

.. automodule:: pygame_gui.core.layered_gui_group
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.object\_id module
--------------------------------------

.. automodule:: pygame_gui.core.object_id
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.resource\_loaders module
--------------------------------------

.. automodule:: pygame_gui.core.resource_loaders
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.surface\_cache module
--------------------------------------

Expand All @@ -44,6 +77,14 @@ pygame\_gui.core.ui\_appearance\_theme module
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.ui\_container module
---------------------------------------------

.. automodule:: pygame_gui.core.ui_container
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.core.ui\_element module
-----------------------------------

Expand Down
40 changes: 40 additions & 0 deletions docs/source/pygame_gui.elements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ pygame\_gui.elements package
Submodules
----------

pygame\_gui.elements.ui\_2d\_slider module
--------------------------------------

.. automodule:: pygame_gui.elements.ui_2d_slider
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_auto\_resizing\_container module
--------------------------------------

.. automodule:: pygame_gui.elements.auto_resizing_container
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_button module
--------------------------------------

Expand All @@ -20,6 +36,14 @@ pygame\_gui.elements.ui\_drop\_down\_menu module
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_horizontal\_scroll\_bar module
--------------------------------------------------

.. automodule:: pygame_gui.elements.ui_horizontal_scroll_bar
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_horizontal\_slider module
--------------------------------------------------

Expand Down Expand Up @@ -68,6 +92,14 @@ pygame\_gui.elements.ui\_screen\_space\_health\_bar module
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_scrolling\_container module
-----------------------------------------------

.. automodule:: pygame_gui.elements.ui_scrolling_container
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_selection\_list module
-----------------------------------------------

Expand All @@ -92,6 +124,14 @@ pygame\_gui.elements.ui\_text\_box module
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_text\_entry\_box module
-------------------------------------------------

.. automodule:: pygame_gui.elements.ui_text_entry_box
:members:
:no-undoc-members:
:show-inheritance:

pygame\_gui.elements.ui\_text\_entry\_line module
-------------------------------------------------

Expand Down
61 changes: 52 additions & 9 deletions pygame_gui/core/ui_font_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pygame
from pygame_gui.core.interfaces.gui_font_interface import IGUIFontInterface
from pygame.font import match_font

from pygame_gui.core.interfaces.font_dictionary_interface import IUIFontDictionaryInterface
from pygame_gui.core.resource_loaders import IResourceLoader
Expand Down Expand Up @@ -236,12 +237,12 @@ def find_font(self, font_size: int, font_name: str,
bold: bool = False, italic: bool = False, antialiased: bool = True,
script: str = 'Latn', direction: int = pygame.DIRECTION_LTR) -> IGUIFontInterface:
"""
Find a loaded font from the font dictionary. Will load a font if it does not already exist
Find a loaded font from the font dictionary. Will load a font if it does not already exist,
and we have paths to the needed files, however it will issue a warning after doing so
because dynamic file loading is normally a bad idea as you will get frame rate hitches
while the running program waits for the font to load.

Instead it's best to preload all your needed files at another time in your program when
Instead, it's best to preload all your needed files at another time in your program when
you have more control over the user experience.

:param font_size: The size of the font to find.
Expand All @@ -263,11 +264,11 @@ def find_font_resource(self, font_size: int, font_name: str,
script: str = 'Latn', direction: int = pygame.DIRECTION_LTR) -> FontResource:
"""
Find a loaded font resource from the font dictionary. Will load a font if it does not
already exist and we have paths to the needed files, however it will issue a warning
already exist, and we have paths to the needed files, however it will issue a warning
after doing so because dynamic file loading is normally a bad idea as you will get frame
rate hitches while the running program waits for the font to load.

Instead it's best to preload all your needed files at another time in your program when
Instead, it's best to preload all your needed files at another time in your program when
you have more control over the user experience.

:param font_size: The size of the font to find.
Expand Down Expand Up @@ -316,7 +317,47 @@ def find_font_resource(self, font_size: int, font_name: str,
script=script, direction=direction)
return self.loaded_fonts[font_id]
else:
return self.loaded_fonts[self.default_font.idx]
if self._load_system_font(font_id, font_size, font_name, bold,
italic, antialiased, script, direction,
force_immediate_load=True):
return self.loaded_fonts[font_id]
else:
return self.loaded_fonts[self.default_font.idx]

def _load_system_font(self, font_id: str,font_size: int, font_name: str,
bold: bool = False, italic: bool = False, antialiased: bool = True,
script: str = 'Latn', direction: int = pygame.DIRECTION_LTR,
force_immediate_load: bool = False) -> bool:
# let's try and use the SystemFonts
found_system_no_style_font_path = match_font(font_name, bold=False, italic=False)
if bold or italic:
found_system_font_path = match_font(font_name, bold, italic)
else:
found_system_font_path = found_system_no_style_font_path
if found_system_no_style_font_path is not None and found_system_font_path is not None:
if bold and italic and found_system_font_path is not None:
self.add_font_path(font_name, font_path=found_system_no_style_font_path,
bold_italic_path=found_system_font_path)
elif bold and not italic and found_system_font_path is not None:
self.add_font_path(font_name, font_path=found_system_no_style_font_path,
bold_path=found_system_font_path)
elif not bold and italic and found_system_font_path is not None:
self.add_font_path(font_name, font_path=found_system_no_style_font_path,
italic_path=found_system_font_path)
else:
self.add_font_path(font_name, font_path=found_system_no_style_font_path)

self._load_single_font_style((found_system_no_style_font_path, False),
font_id, font_size,
font_style={'bold': True,
'italic': True,
'antialiased': antialiased,
'script': script,
'direction': direction},
force_immediate_load=force_immediate_load
)
return True
return False

def get_default_font(self) -> IGUIFontInterface:
"""
Expand Down Expand Up @@ -368,7 +409,7 @@ def preload_font(self, font_size: int, font_name: str,
"""
Lets us load a font at a particular size and style before we use it. While you can get
away with relying on dynamic font loading during development, it is better to eventually
pre-load all your font data at a controlled time, which is where this method comes in.
preload all your font data at a controlled time, which is where this method comes in.

:param font_size: The size of the font to load.
:param font_name: The name of the font to load.
Expand Down Expand Up @@ -434,7 +475,9 @@ def preload_font(self, font_size: int, font_name: str,
'direction': direction},
force_immediate_load=force_immediate_load)
else:
warnings.warn('Trying to pre-load font id:' + font_id + ' with no paths set')
if not self._load_system_font(font_id, font_size, font_name, bold,
italic, antialiased, script, direction, force_immediate_load):
warnings.warn('Trying to pre-load font id:' + font_id + ' with no paths set & its not a system font')

def _load_single_font_style(self,
font_loc: Tuple[Union[str, PackageResource, bytes], bool],
Expand All @@ -446,7 +489,7 @@ def _load_single_font_style(self,
Load a single font file with a given style.

:param font_loc: Path to the font file.
:param font_id: id for the font in the loaded fonts dictionary.
:param font_id: id for the font in the loaded 'fonts' dictionary.
:param font_size: pygame font size.
:param font_style: style dictionary (italic, bold, antialiased, all, some or none)

Expand Down Expand Up @@ -515,7 +558,7 @@ def print_unused_loaded_fonts(self):

def convert_html_to_point_size(self, html_size: float) -> int:
"""
Takes in a HTML style font size and converts it into a point font size.
Takes in an HTML style font size and converts it into a point font size.

:param html_size: Size in HTML style.

Expand Down
27 changes: 25 additions & 2 deletions tests/test_core/test_ui_font_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,18 @@ def test_find_font_unloaded_style_bold_italic(self, _init_pygame, _display_surfa
def test_find_font_unloaded(self, _init_pygame, _display_surface_return_none):
font_dictionary = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')

font_dictionary.find_font(font_size=20, font_name='arial')
# try genuine (for match font) & nonsense
font_dictionary.find_font(font_size=20, font_name='sans')
font_dictionary_2 = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
font_dictionary_2.find_font(font_size=20, font_name='sans', bold=True, italic=True)

font_dictionary_3 = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
font_dictionary_3.find_font(font_size=20, font_name='sans', bold=True, italic=False)

font_dictionary_4 = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
font_dictionary_4.find_font(font_size=20, font_name='sans', bold=False, italic=True)

font_dictionary.find_font(font_size=20, font_name='nonsense_adhawdw')

def test_create_font_id(self, _init_pygame, _display_surface_return_none):
font_dictionary = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
Expand All @@ -85,8 +96,20 @@ def test_preload_already_loaded(self, _init_pygame, _display_surface_return_none

def test_preload_no_paths(self, _init_pygame, _display_surface_return_none):
font_dictionary = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
# test probably universally valid system font
font_dictionary.preload_font(font_name='sans', font_size=14)

font_dictionary_2 = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
font_dictionary_2.preload_font(font_size=20, font_name='sans', bold=True, italic=True)

font_dictionary_3 = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
font_dictionary_3.preload_font(font_size=20, font_name='sans', bold=True, italic=False)

font_dictionary_4 = UIFontDictionary(BlockingThreadedResourceLoader(), locale='en')
font_dictionary_4.preload_font(font_size=20, font_name='sans', bold=False, italic=True)

with pytest.warns(UserWarning, match="Trying to pre-load font id"):
font_dictionary.preload_font(font_name='arial', font_size=14)
font_dictionary.preload_font(font_name='nonsense_adhawdw', font_size=14)

def test_preload_bad_paths(self, _init_pygame, _display_surface_return_none):
loader = BlockingThreadedResourceLoader()
Expand Down
Loading