diff --git a/android/tests_backend/fonts.py b/android/tests_backend/fonts.py index 63014f7875..600444add0 100644 --- a/android/tests_backend/fonts.py +++ b/android/tests_backend/fonts.py @@ -59,6 +59,7 @@ def reflect_font_methods(): class FontMixin: supports_custom_fonts = True + supports_custom_variable_fonts = True def assert_font_options(self, weight=NORMAL, style=NORMAL, variant=NORMAL): assert (BOLD if self.typeface.isBold() else NORMAL) == weight diff --git a/changes/1837.feature.rst b/changes/1837.feature.rst index 896050da51..e9b4941620 100644 --- a/changes/1837.feature.rst +++ b/changes/1837.feature.rst @@ -1 +1 @@ -Support for custom font loading was added to the GTK backend. +Support for custom font loading was added to the GTK, Cocoa and iOS backends. diff --git a/cocoa/setup.py b/cocoa/setup.py index 2f26f8f9d9..f9c2736edd 100644 --- a/cocoa/setup.py +++ b/cocoa/setup.py @@ -6,6 +6,7 @@ setup( version=version, install_requires=[ + "fonttools >= 4.42.1, < 5.0.0", "rubicon-objc >= 0.4.5rc1, < 0.5.0", f"toga-core == {version}", ], diff --git a/cocoa/src/toga_cocoa/fonts.py b/cocoa/src/toga_cocoa/fonts.py index 9c60302f90..a8a902ab64 100644 --- a/cocoa/src/toga_cocoa/fonts.py +++ b/cocoa/src/toga_cocoa/fonts.py @@ -1,5 +1,7 @@ from pathlib import Path +from fontTools.ttLib import TTFont + from toga.fonts import ( _REGISTERED_FONT_CACHE, BOLD, @@ -14,46 +16,80 @@ SMALL_CAPS, SYSTEM, SYSTEM_DEFAULT_FONT_SIZE, - SYSTEM_DEFAULT_FONTS, ) from toga_cocoa.libs import ( + NSURL, NSFont, NSFontManager, NSFontMask, ) +from toga_cocoa.libs.core_text import core_text, kCTFontManagerScopeProcess _FONT_CACHE = {} +_CUSTOM_FONT_NAMES = {} class Font: def __init__(self, interface): self.interface = interface try: - font = _FONT_CACHE[self.interface] + attributed_font = _FONT_CACHE[self.interface] except KeyError: + font_family = self.interface.family font_key = self.interface._registered_font_key( - self.interface.family, + family=font_family, weight=self.interface.weight, style=self.interface.style, variant=self.interface.variant, ) + try: - font_path = _REGISTERED_FONT_CACHE[font_key] + # Built in fonts have known names; no need to interrogate a file. + custom_font_name = { + SYSTEM: None, # No font name required + MESSAGE: None, # No font name required + SERIF: "Times-Roman", + SANS_SERIF: "Helvetica", + CURSIVE: "Apple Chancery", + FANTASY: "Papyrus", + MONOSPACE: "Courier New", + }[font_family] except KeyError: - # Not a pre-registered font - if self.interface.family not in SYSTEM_DEFAULT_FONTS: + try: + font_path = _REGISTERED_FONT_CACHE[font_key] + except KeyError: + # The requested font has not been registered print( f"Unknown font '{self.interface}'; " "using system font as a fallback" ) - else: - if Path(font_path).is_file(): - # TODO: Load font file - self.interface.factory.not_implemented("Custom font loading") - # if corrupted font file: - # raise ValueError(f"Unable to load font file {font_path}") + font_family = SYSTEM + custom_font_name = None else: - raise ValueError(f"Font file {font_path} could not be found") + # We have a path for a font file. + try: + # A font *file* an only be registered once under Cocoa. + custom_font_name = _CUSTOM_FONT_NAMES[font_path] + except KeyError: + if Path(font_path).is_file(): + font_url = NSURL.fileURLWithPath(font_path) + success = core_text.CTFontManagerRegisterFontsForURL( + font_url, kCTFontManagerScopeProcess, None + ) + if success: + ttfont = TTFont(font_path) + custom_font_name = ttfont["name"].getBestFullName() + # Preserve the Postscript font name contained in the + # font file. + _CUSTOM_FONT_NAMES[font_path] = custom_font_name + else: + raise ValueError( + f"Unable to load font file {font_path}" + ) + else: + raise ValueError( + f"Font file {font_path} could not be found" + ) if self.interface.size == SYSTEM_DEFAULT_FONT_SIZE: font_size = NSFont.systemFontSize @@ -63,28 +99,13 @@ def __init__(self, interface): # (https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html). font_size = self.interface.size * 96 / 72 - if self.interface.family == SYSTEM: + # Construct the NSFont + if font_family == SYSTEM: font = NSFont.systemFontOfSize(font_size) - elif self.interface.family == MESSAGE: + elif font_family == MESSAGE: font = NSFont.messageFontOfSize(font_size) else: - family = { - SERIF: "Times-Roman", - SANS_SERIF: "Helvetica", - CURSIVE: "Apple Chancery", - FANTASY: "Papyrus", - MONOSPACE: "Courier New", - }.get(self.interface.family, self.interface.family) - - font = NSFont.fontWithName(family, size=font_size) - - if font is None: - print( - "Unable to load font: {}pt {}".format( - self.interface.size, family - ) - ) - font = NSFont.systemFontOfSize(font_size) + font = NSFont.fontWithName(custom_font_name, size=font_size) # Convert the base font definition into a font with all the desired traits. attributes_mask = 0 @@ -97,12 +118,12 @@ def __init__(self, interface): attributes_mask |= NSFontMask.SmallCaps.value if attributes_mask: - # If there is no font with the requested traits, this returns the original - # font unchanged. - font = NSFontManager.sharedFontManager.convertFont( + attributed_font = NSFontManager.sharedFontManager.convertFont( font, toHaveTrait=attributes_mask ) + else: + attributed_font = font - _FONT_CACHE[self.interface] = font.retain() + _FONT_CACHE[self.interface] = attributed_font.retain() - self.native = font + self.native = attributed_font diff --git a/cocoa/src/toga_cocoa/libs/core_text.py b/cocoa/src/toga_cocoa/libs/core_text.py index b19cf09a31..a109cbf175 100644 --- a/cocoa/src/toga_cocoa/libs/core_text.py +++ b/cocoa/src/toga_cocoa/libs/core_text.py @@ -1,106 +1,23 @@ ########################################################################## # System/Library/Frameworks/CoreText.framework ########################################################################## -from ctypes import POINTER, c_bool, c_double, c_uint32, c_void_p, cdll, util - -from rubicon.objc import CFIndex, CGFloat, CGGlyph, CGRect, CGSize, UniChar +from ctypes import c_bool, c_uint32, c_void_p, cdll, util ###################################################################### core_text = cdll.LoadLibrary(util.find_library("CoreText")) ###################################################################### -###################################################################### -# CTFontDescriptor.h - -CTFontOrientation = c_uint32 - -###################################################################### -# CTFontTraits.h - -CTFontSymbolicTraits = c_uint32 - ###################################################################### # CTFont.h -core_text.CTFontGetBoundingRectsForGlyphs.restype = CGRect -core_text.CTFontGetBoundingRectsForGlyphs.argtypes = [ - c_void_p, - CTFontOrientation, - POINTER(CGGlyph), - POINTER(CGRect), - CFIndex, -] - -core_text.CTFontGetAdvancesForGlyphs.restype = c_double -core_text.CTFontGetAdvancesForGlyphs.argtypes = [ - c_void_p, - CTFontOrientation, - POINTER(CGGlyph), - POINTER(CGSize), - CFIndex, -] - -core_text.CTFontGetAscent.restype = CGFloat -core_text.CTFontGetAscent.argtypes = [c_void_p] - -core_text.CTFontGetDescent.restype = CGFloat -core_text.CTFontGetDescent.argtypes = [c_void_p] - -core_text.CTFontGetSymbolicTraits.restype = CTFontSymbolicTraits -core_text.CTFontGetSymbolicTraits.argtypes = [c_void_p] - -core_text.CTFontGetGlyphsForCharacters.restype = c_bool -core_text.CTFontGetGlyphsForCharacters.argtypes = [ - c_void_p, - POINTER(UniChar), - POINTER(CGGlyph), - CFIndex, -] - -core_text.CTFontCreateWithGraphicsFont.restype = c_void_p -core_text.CTFontCreateWithGraphicsFont.argtypes = [ - c_void_p, - CGFloat, - c_void_p, - c_void_p, -] - -core_text.CTFontCopyFamilyName.restype = c_void_p -core_text.CTFontCopyFamilyName.argtypes = [c_void_p] - -core_text.CTFontCopyFullName.restype = c_void_p -core_text.CTFontCopyFullName.argtypes = [c_void_p] - -core_text.CTFontCreateWithFontDescriptor.restype = c_void_p -core_text.CTFontCreateWithFontDescriptor.argtypes = [c_void_p, CGFloat, c_void_p] - -core_text.CTFontDescriptorCreateWithAttributes.restype = c_void_p -core_text.CTFontDescriptorCreateWithAttributes.argtypes = [c_void_p] - -###################################################################### -# CTFontDescriptor.h - -kCTFontFamilyNameAttribute = c_void_p.in_dll(core_text, "kCTFontFamilyNameAttribute") -kCTFontTraitsAttribute = c_void_p.in_dll(core_text, "kCTFontTraitsAttribute") - -###################################################################### -# CTFontTraits.h - -kCTFontSymbolicTrait = c_void_p.in_dll(core_text, "kCTFontSymbolicTrait") -kCTFontWeightTrait = c_void_p.in_dll(core_text, "kCTFontWeightTrait") - -kCTFontItalicTrait = 1 << 0 -kCTFontBoldTrait = 1 << 1 - -###################################################################### -# CTLine.h - -core_text.CTLineCreateWithAttributedString.restype = c_void_p -core_text.CTLineCreateWithAttributedString.argtypes = [c_void_p] -core_text.CTLineDraw.restype = None -core_text.CTLineDraw.argtypes = [c_void_p, c_void_p] +core_text.CTFontManagerRegisterFontsForURL.restype = c_bool +core_text.CTFontManagerRegisterFontsForURL.argtypes = [c_void_p, c_uint32, c_void_p] ###################################################################### -# CTStringAttributes.h +# CTFontManagerScope.h -kCTFontAttributeName = c_void_p.in_dll(core_text, "kCTFontAttributeName") +kCTFontManagerScopeNone = 0 +kCTFontManagerScopeProcess = 1 +kCTFontManagerScopePersistent = 2 +kCTFontManagerScopeSession = 3 +kCTFontManagerScopeUser = 2 diff --git a/cocoa/tests_backend/fonts.py b/cocoa/tests_backend/fonts.py index afb232b40e..ec98b782a2 100644 --- a/cocoa/tests_backend/fonts.py +++ b/cocoa/tests_backend/fonts.py @@ -17,7 +17,8 @@ class FontMixin: - supports_custom_fonts = False + supports_custom_fonts = True + supports_custom_variable_fonts = False def assert_font_options(self, weight=NORMAL, style=NORMAL, variant=NORMAL): # Cocoa's FANTASY (Papyrus) and CURSIVE (Apple Chancery) system @@ -52,11 +53,18 @@ def assert_font_size(self, expected): def assert_font_family(self, expected): assert str(self.font.familyName) == { + # System and Message fonts use internal names + SYSTEM: ".AppleSystemUIFont", + MESSAGE: ".AppleSystemUIFont", + # Known fonts use pre-registered names CURSIVE: "Apple Chancery", FANTASY: "Papyrus", MONOSPACE: "Courier New", SANS_SERIF: "Helvetica", SERIF: "Times", - SYSTEM: ".AppleSystemUIFont", - MESSAGE: ".AppleSystemUIFont", + # Most other fonts we can just use the family name; + # however, the Font Awesome font has a different + # internal Postscript name, which *doesn't* include + # the "solid" weight component. + "Font Awesome 5 Free Solid": "Font Awesome 5 Free", }.get(expected, expected) diff --git a/docs/reference/api/resources/fonts.rst b/docs/reference/api/resources/fonts.rst index fd8171c22f..86bd1b35ff 100644 --- a/docs/reference/api/resources/fonts.rst +++ b/docs/reference/api/resources/fonts.rst @@ -71,7 +71,9 @@ properties to the ones used for widget styling:: Notes ----- -* macOS and iOS do not currently support registering user fonts. +* iOS and macOS do not support the use of variant font files (that is, fonts that + contain the details of multiple weights/variants in a single file). Variant font + files can be registered; however, only the "normal" variant will be used. * Android and Windows do not support the oblique font style. If an oblique font is specified, Toga will attempt to use an italic style of the same font. diff --git a/docs/reference/data/widgets_by_platform.csv b/docs/reference/data/widgets_by_platform.csv index 290faeee70..923ffbd4e2 100644 --- a/docs/reference/data/widgets_by_platform.csv +++ b/docs/reference/data/widgets_by_platform.csv @@ -28,7 +28,7 @@ ScrollContainer,Layout Widget,:class:`~toga.ScrollContainer`,A container that ca SplitContainer,Layout Widget,:class:`~toga.SplitContainer`,A container that divides an area into two panels with a movable border,|y|,|y|,|y|,,,, OptionContainer,Layout Widget,:class:`~toga.OptionContainer`,A container that can display multiple labeled tabs of content,|y|,|y|,|y|,,,, App Paths,Resource,:class:`~toga.paths.Paths`,A mechanism for obtaining platform-appropriate filesystem locations for an application.,|y|,|y|,|y|,|y|,|y|,,|b| -Font,Resource,:class:`~toga.Font`,A text font,|b|,|y|,|y|,|b|,|y|,, +Font,Resource,:class:`~toga.Font`,A text font,|y|,|y|,|y|,|y|,|y|,, Command,Resource,:class:`~toga.Command`,Command,|b|,|b|,|b|,,|b|,, Group,Resource,:class:`~toga.Group`,Command group,|b|,|b|,|b|,|b|,|b|,, Icon,Resource,:class:`~toga.Icon`,"A small, square image, used to provide easily identifiable visual context to a widget.",|y|,|y|,|y|,|y|,|y|,,|b| diff --git a/gtk/tests_backend/fonts.py b/gtk/tests_backend/fonts.py index 59c7ece09c..d09da437f6 100644 --- a/gtk/tests_backend/fonts.py +++ b/gtk/tests_backend/fonts.py @@ -11,6 +11,7 @@ class FontMixin: supports_custom_fonts = True + supports_custom_variable_fonts = True def assert_font_family(self, expected): assert self.font.get_family().split(",")[0] == expected diff --git a/iOS/setup.py b/iOS/setup.py index 2f26f8f9d9..f9c2736edd 100644 --- a/iOS/setup.py +++ b/iOS/setup.py @@ -6,6 +6,7 @@ setup( version=version, install_requires=[ + "fonttools >= 4.42.1, < 5.0.0", "rubicon-objc >= 0.4.5rc1, < 0.5.0", f"toga-core == {version}", ], diff --git a/iOS/src/toga_iOS/fonts.py b/iOS/src/toga_iOS/fonts.py index 3a93806bcb..e34dfa830d 100644 --- a/iOS/src/toga_iOS/fonts.py +++ b/iOS/src/toga_iOS/fonts.py @@ -1,5 +1,7 @@ from pathlib import Path +from fontTools.ttLib import TTFont + from toga.fonts import ( _REGISTERED_FONT_CACHE, BOLD, @@ -13,46 +15,80 @@ SERIF, SYSTEM, SYSTEM_DEFAULT_FONT_SIZE, - SYSTEM_DEFAULT_FONTS, ) from toga_iOS.libs import ( + NSURL, UIFont, UIFontDescriptorTraitBold, UIFontDescriptorTraitItalic, ) +from toga_iOS.libs.core_text import core_text, kCTFontManagerScopeProcess _FONT_CACHE = {} +_CUSTOM_FONT_NAMES = {} class Font: def __init__(self, interface): self.interface = interface try: - font = _FONT_CACHE[self.interface] + attributed_font = _FONT_CACHE[self.interface] except KeyError: + font_family = self.interface.family font_key = self.interface._registered_font_key( - self.interface.family, + family=font_family, weight=self.interface.weight, style=self.interface.style, variant=self.interface.variant, ) + try: - font_path = _REGISTERED_FONT_CACHE[font_key] + # Built in fonts have known names; no need to interrogate a file. + custom_font_name = { + SYSTEM: None, # No font name required + MESSAGE: None, # No font name required + SERIF: "Times-Roman", + SANS_SERIF: "Helvetica", + CURSIVE: "Snell Roundhand", + FANTASY: "Papyrus", + MONOSPACE: "Courier New", + }[font_family] except KeyError: - # Not a pre-registered font - if self.interface.family not in SYSTEM_DEFAULT_FONTS: + try: + font_path = _REGISTERED_FONT_CACHE[font_key] + except KeyError: + # The requested font has not been registered print( f"Unknown font '{self.interface}'; " "using system font as a fallback" ) - else: - if Path(font_path).is_file(): - # TODO: Load font file - self.interface.factory.not_implemented("Custom font loading") - # if corrupted font file: - # raise ValueError(f"Unable to load font file {font_path}") + font_family = SYSTEM + custom_font_name = None else: - raise ValueError(f"Font file {font_path} could not be found") + # We have a path for a font file. + try: + # A font *file* an only be registered once under Cocoa. + custom_font_name = _CUSTOM_FONT_NAMES[font_path] + except KeyError: + if Path(font_path).is_file(): + font_url = NSURL.fileURLWithPath(font_path) + success = core_text.CTFontManagerRegisterFontsForURL( + font_url, kCTFontManagerScopeProcess, None + ) + if success: + ttfont = TTFont(font_path) + custom_font_name = ttfont["name"].getBestFullName() + # Preserve the Postscript font name contained in the + # font file. + _CUSTOM_FONT_NAMES[font_path] = custom_font_name + else: + raise ValueError( + f"Unable to load font file {font_path}" + ) + else: + raise ValueError( + f"Font file {font_path} could not be found" + ) if self.interface.size == SYSTEM_DEFAULT_FONT_SIZE: size = UIFont.labelFontSize @@ -62,23 +98,12 @@ def __init__(self, interface): # (https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html). size = self.interface.size * 96 / 72 - if self.interface.family == SYSTEM: - base_font = UIFont.systemFontOfSize(size) - elif self.interface.family == MESSAGE: - base_font = UIFont.systemFontOfSize(size) + if font_family == SYSTEM: + font = UIFont.systemFontOfSize(size) + elif font_family == MESSAGE: + font = UIFont.systemFontOfSize(size) else: - family = { - SERIF: "Times-Roman", - SANS_SERIF: "Helvetica", - CURSIVE: "Snell Roundhand", - FANTASY: "Papyrus", - MONOSPACE: "Courier New", - }.get(self.interface.family, self.interface.family) - - base_font = UIFont.fontWithName(family, size=size) - if base_font is None: - print(f"Unable to load font: {size}pt {family}") - base_font = UIFont.systemFontOfSize(size) + font = UIFont.fontWithName(custom_font_name, size=size) # Convert the base font definition into a font with all the desired traits. traits = 0 @@ -88,18 +113,16 @@ def __init__(self, interface): traits |= UIFontDescriptorTraitItalic if traits: - # If there is no font with the requested traits, this returns the original - # font unchanged. - font = UIFont.fontWithDescriptor( - base_font.fontDescriptor.fontDescriptorWithSymbolicTraits(traits), + # If there is no font with the requested traits, this returns None. + attributed_font = UIFont.fontWithDescriptor( + font.fontDescriptor.fontDescriptorWithSymbolicTraits(traits), size=size, ) - # If the traits conversion failed, fall back to the default font. - if font is None: - font = base_font + if attributed_font is None: + attributed_font = font else: - font = base_font + attributed_font = font - _FONT_CACHE[self.interface] = font.retain() + _FONT_CACHE[self.interface] = attributed_font.retain() - self.native = font + self.native = attributed_font diff --git a/iOS/src/toga_iOS/libs/core_text.py b/iOS/src/toga_iOS/libs/core_text.py new file mode 100644 index 0000000000..a109cbf175 --- /dev/null +++ b/iOS/src/toga_iOS/libs/core_text.py @@ -0,0 +1,23 @@ +########################################################################## +# System/Library/Frameworks/CoreText.framework +########################################################################## +from ctypes import c_bool, c_uint32, c_void_p, cdll, util + +###################################################################### +core_text = cdll.LoadLibrary(util.find_library("CoreText")) +###################################################################### + +###################################################################### +# CTFont.h + +core_text.CTFontManagerRegisterFontsForURL.restype = c_bool +core_text.CTFontManagerRegisterFontsForURL.argtypes = [c_void_p, c_uint32, c_void_p] + +###################################################################### +# CTFontManagerScope.h + +kCTFontManagerScopeNone = 0 +kCTFontManagerScopeProcess = 1 +kCTFontManagerScopePersistent = 2 +kCTFontManagerScopeSession = 3 +kCTFontManagerScopeUser = 2 diff --git a/iOS/tests_backend/fonts.py b/iOS/tests_backend/fonts.py index e13f1017ec..b3b38fb1fe 100644 --- a/iOS/tests_backend/fonts.py +++ b/iOS/tests_backend/fonts.py @@ -20,7 +20,8 @@ class FontMixin: - supports_custom_fonts = False + supports_custom_fonts = True + supports_custom_variable_fonts = False def assert_font_options(self, weight=NORMAL, style=NORMAL, variant=NORMAL): # Cocoa's FANTASY (Papyrus) and CURSIVE (Snell Roundhand) system @@ -55,11 +56,18 @@ def assert_font_size(self, expected): def assert_font_family(self, expected): assert str(self.font.familyName) == { + # System and Message fonts use internal names + SYSTEM: ".AppleSystemUIFont", + MESSAGE: ".AppleSystemUIFont", + # Known fonts use pre-registered names CURSIVE: "Snell Roundhand", FANTASY: "Papyrus", MONOSPACE: "Courier New", SANS_SERIF: "Helvetica", SERIF: "Times New Roman", - SYSTEM: ".AppleSystemUIFont", - MESSAGE: ".AppleSystemUIFont", + # Most other fonts we can just use the family name; + # however, the Font Awesome font has a different + # internal Postscript name, which *doesn't* include + # the "solid" weight component. + "Font Awesome 5 Free Solid": "Font Awesome 5 Free", }.get(expected, expected) diff --git a/testbed/src/testbed/resources/canvas/write_text-iOS.png b/testbed/src/testbed/resources/canvas/write_text-iOS.png index bbabc836f7..149d8b12ba 100644 Binary files a/testbed/src/testbed/resources/canvas/write_text-iOS.png and b/testbed/src/testbed/resources/canvas/write_text-iOS.png differ diff --git a/testbed/src/testbed/resources/canvas/write_text-macOS.png b/testbed/src/testbed/resources/canvas/write_text-macOS.png index a8253778cc..4dcefb453c 100644 Binary files a/testbed/src/testbed/resources/canvas/write_text-macOS.png and b/testbed/src/testbed/resources/canvas/write_text-macOS.png differ diff --git a/testbed/tests/test_fonts.py b/testbed/tests/test_fonts.py index 6eb33763f1..c271089b62 100644 --- a/testbed/tests/test_fonts.py +++ b/testbed/tests/test_fonts.py @@ -74,25 +74,29 @@ async def test_font_options(widget: toga.Label, font_probe): @pytest.mark.parametrize( - "font_family,font_path,font_kwargs", + "font_family,font_path,font_kwargs,variable_font_test", [ # OpenType font with weight property ( "Font Awesome 5 Free Solid", "resources/fonts/Font Awesome 5 Free-Solid-900.otf", {"weight": BOLD}, + False, ), - # TrueType font, no options - ("Endor", "resources/fonts/ENDOR___.ttf", {}), + # TrueType font supporting multiple styles, no options + ("Endor", "resources/fonts/ENDOR___.ttf", {}, False), + # TrueType font supporting multiple styles, with options + ("Endor", "resources/fonts/ENDOR___.ttf", {"weight": BOLD}, True), # Font with weight property - ("Roboto", "resources/fonts/Roboto-Bold.ttf", {"weight": BOLD}), + ("Roboto", "resources/fonts/Roboto-Bold.ttf", {"weight": BOLD}, False), # Font with style property - ("Roboto", "resources/fonts/Roboto-Italic.ttf", {"style": ITALIC}), + ("Roboto", "resources/fonts/Roboto-Italic.ttf", {"style": ITALIC}, False), # Font with multiple properties ( "Roboto", "resources/fonts/Roboto-BoldItalic.ttf", {"weight": BOLD, "style": ITALIC}, + False, ), ], ) @@ -103,6 +107,7 @@ async def test_font_file_loaded( font_family: str, font_path: str, font_kwargs, + variable_font_test: bool, capsys: pytest.CaptureFixture[str], ): """Custom fonts can be loaded and used.""" @@ -125,7 +130,10 @@ async def test_font_file_loaded( # Check that font properties are updated font_probe.assert_font_family(font_family) - font_probe.assert_font_options(**font_kwargs) + # Only check the font options if this is a non-variable font test, or the backend + # supports variable fonts + if not variable_font_test or font_probe.supports_custom_variable_fonts: + font_probe.assert_font_options(**font_kwargs) # Setting the font to "Roboto something" involves setting the font to # "Roboto" as an intermediate step. However, we haven't registered "Roboto diff --git a/winforms/tests_backend/fonts.py b/winforms/tests_backend/fonts.py index a883d77735..9018b846bc 100644 --- a/winforms/tests_backend/fonts.py +++ b/winforms/tests_backend/fonts.py @@ -19,6 +19,7 @@ class FontMixin: supports_custom_fonts = True + supports_custom_variable_fonts = True def assert_font_options(self, weight=NORMAL, style=NORMAL, variant=NORMAL): assert BOLD if self.font.Bold else NORMAL == weight