2929
3030from base64 import b64encode
3131from collections import namedtuple
32+ from collections .abc import Iterable
3233import copy
3334import dataclasses
3435from functools import lru_cache
36+ import functools
3537from io import BytesIO
3638import json
3739import logging
@@ -536,6 +538,56 @@ def afmFontProperty(fontpath, font):
536538 return FontEntry (fontpath , name , style , variant , weight , stretch , size )
537539
538540
541+ def _cleanup_fontproperties_init (init_method ):
542+ """
543+ A decorator to limit the call signature to single a positional argument
544+ or alternatively only keyword arguments.
545+
546+ We still accept but deprecate all other call signatures.
547+
548+ When the deprecation expires we can switch the signature to::
549+
550+ __init__(self, pattern=None, /, *, family=None, style=None, ...)
551+
552+ plus a runtime check that pattern is not used alongside with the
553+ keyword arguments. This results eventually in the two possible
554+ call signatures::
555+
556+ FontProperties(pattern)
557+ FontProperties(family=..., size=..., ...)
558+
559+ """
560+ @functools .wraps (init_method )
561+ def wrapper (self , * args , ** kwargs ):
562+ # multiple args with at least some positional ones
563+ if len (args ) > 1 or len (args ) == 1 and kwargs :
564+ # Note: Both cases were previously handled as individual properties.
565+ # Therefore, we do not mention the case of font properties here.
566+ _api .warn_deprecated (
567+ "3.10" ,
568+ message = "Passing individual properties to FontProperties() "
569+ "positionally is deprecated. Please pass all properties "
570+ "via keyword arguments."
571+ )
572+ # single non-string arg -> clearly a family not a pattern
573+ if (len (args ) == 1 and not kwargs
574+ and isinstance (args [0 ], Iterable ) and not isinstance (args [0 ], str )):
575+ # Case font-family list passed as single argument
576+ _api .warn_deprecated (
577+ "3.10" ,
578+ message = "Passing family as positional argument to FontProperties() "
579+ "is deprecated. Please pass family names as keyword"
580+ "argument."
581+ )
582+ # Note on single string arg:
583+ # This has been interpreted as pattern so far. We are already raising if a
584+ # non-pattern compatible family string was given. Therefore, we do not need
585+ # to warn for this case.
586+ return init_method (self , * args , ** kwargs )
587+
588+ return wrapper
589+
590+
539591class FontProperties :
540592 """
541593 A class for storing and manipulating font properties.
@@ -585,9 +637,14 @@ class FontProperties:
585637 approach allows all text sizes to be made larger or smaller based
586638 on the font manager's default font size.
587639
588- This class will also accept a fontconfig_ pattern_, if it is the only
589- argument provided. This support does not depend on fontconfig; we are
590- merely borrowing its pattern syntax for use here.
640+ This class accepts a single positional string as fontconfig_ pattern_,
641+ or alternatively individual properties as keyword arguments::
642+
643+ FontProperties(pattern)
644+ FontProperties(*, family=None, style=None, variant=None, ...)
645+
646+ This support does not depend on fontconfig; we are merely borrowing its
647+ pattern syntax for use here.
591648
592649 .. _fontconfig: https://www.freedesktop.org/wiki/Software/fontconfig/
593650 .. _pattern:
@@ -599,6 +656,7 @@ class FontProperties:
599656 fontconfig.
600657 """
601658
659+ @_cleanup_fontproperties_init
602660 def __init__ (self , family = None , style = None , variant = None , weight = None ,
603661 stretch = None , size = None ,
604662 fname = None , # if set, it's a hardcoded filename to use
0 commit comments