classes.c10_font

  1from typing import TYPE_CHECKING, Optional
  2import drawBot
  3import colorama
  4from loguru import logger
  5from icecream import ic
  6
  7from lib import fonts as libFonts
  8
  9# Avoid circular import issues, hide from runtime, but keep for type checking
 10if TYPE_CHECKING:
 11    from .c11_family import KFamily
 12
 13
 14class KFont:
 15    """
 16    Represents a single font and provides methods for font analysis and manipulation.
 17
 18    Usually grouped into `classes.c11_family.KFamily`.
 19    """
 20
 21    fullName: str
 22    """Full name of font: `Stabil Grotesk 400 Regular`"""
 23    familyName: str
 24    """Family name of font: `Stabil Grotesk`"""
 25    styleName: str
 26    """Style name of font: `Regular`"""
 27    shortName: str
 28    """Short name of font: `400 Regular`"""
 29    styleNumber: str | None
 30    """Style number of font: `400`"""
 31
 32    def __init__(self, path: str, family: Optional["KFamily"] = None):
 33        """
 34        Initialize a KFont instance.
 35
 36        Args:
 37            path: Path to the font file.
 38            family: Family this font belongs to (optional). See `classes.c11_family.KFamily`.
 39        """
 40        self.path = path
 41        """File system path to the font file"""
 42
 43        if family:
 44            self.family = family
 45
 46        names = libFonts.parseNameObject(path, separate=True)
 47        for [name, value] in names._asdict().items():
 48            setattr(self, name, value)
 49
 50    def __str__(self) -> str:
 51        """Return a string representation of the KFont instance."""
 52        return f"{colorama.Fore.BLACK}{colorama.Back.LIGHTGREEN_EX}KFont{colorama.Style.RESET_ALL} {self.fullName}"
 53
 54    @property
 55    def fontType(self):
 56        """
 57        Get the font type based on the style number.
 58
 59        Returns:
 60            The font type as determined by `lib.fonts.getFontType`.
 61        """
 62        try:
 63            return libFonts.getFontType(self.styleNumber)
 64        except Exception as e:
 65            logger.warning(f"[KFont] Error getting font type: {e}")
 66            return None
 67
 68    @property
 69    def version(self) -> str:
 70        """Returns the version of the font as a string."""
 71        return libFonts.getFontVersion(self.path)
 72
 73    def isType(self, criteria: libFonts.FontType) -> bool:
 74        """
 75        Check if the font matches given `lib.fonts.FontType` criteria:
 76
 77        ```
 78        upright \\d0
 79        italic \\d5
 80        display [^4-6]
 81        text [4-6]
 82        thin [1-3]
 83        thick [7-9]
 84        ```
 85
 86        Args:
 87            criteria: The font type to check (e.g., 'upright', 'italic', etc.).
 88
 89        Returns:
 90            True if the font matches the criteria, False otherwise.
 91        """
 92        return criteria in self.fontType
 93
 94    def getSibling(self) -> Optional["KFont"]:
 95        """
 96        Get the sibling font with a related style number.
 97
 98        Example:
 99            `400` => `450`
100
101        Returns:
102            The sibling KFont instance if found, otherwise None.
103        """
104        prefix = self.styleNumber[0]
105        suffix = "5" if self.isType("upright") else "0"
106        # Adjust to correct length: 40 => 45, 400 => 450
107        adjusted = (prefix + suffix).ljust(len(self.styleNumber), "0")
108        siblingNumber = int(adjusted)
109        try:
110            return self.family.get(criteria=siblingNumber)
111        except Exception as e:
112            logger.warning("[KFont] Error getting sibling: {}", e)
113            return None
114
115    def getWeightClass(self, numeric=False) -> str | int:
116        """
117        Get the weight class of the font.
118
119        Example:
120            `400` => `4`
121
122        Args:
123            numeric: If True, return as integer; as string otherwise.
124
125        Returns:
126            The weight class as string or integer.
127        """
128        numberString = self.styleNumber[0]
129        return int(numberString) if numeric else numberString
130
131    def activate(self) -> "KFont":
132        """
133        Activate this font for use in DrawBot.
134
135        Returns:
136            The KFont instance (self).
137        """
138        logger.trace("Activating KFont: {}", self.fullName)
139        drawBot.font(self.path)
140        return self
141
142    def hasCharacter(self, character: str) -> bool:
143        """
144        Check if the font contains a specific glyph.
145
146        Args:
147            glyph: The glyph name to check (e.g., 'A', 'space', 'uniE000').
148        """
149        with drawBot.savedState():
150            self.activate()
151            return drawBot.fontContainsCharacters(character)
152
153    def supportsFeature(self, featureCode: libFonts.FeatureId) -> bool:
154        """
155        Check if the font supports a specific OpenType feature.
156
157        Args:
158            featureCode: The OpenType feature code to check (e.g., 'ss01', 'liga').
159        """
160        return featureCode in drawBot.listOpenTypeFeatures(self.path)
class KFont:
 15class KFont:
 16    """
 17    Represents a single font and provides methods for font analysis and manipulation.
 18
 19    Usually grouped into `classes.c11_family.KFamily`.
 20    """
 21
 22    fullName: str
 23    """Full name of font: `Stabil Grotesk 400 Regular`"""
 24    familyName: str
 25    """Family name of font: `Stabil Grotesk`"""
 26    styleName: str
 27    """Style name of font: `Regular`"""
 28    shortName: str
 29    """Short name of font: `400 Regular`"""
 30    styleNumber: str | None
 31    """Style number of font: `400`"""
 32
 33    def __init__(self, path: str, family: Optional["KFamily"] = None):
 34        """
 35        Initialize a KFont instance.
 36
 37        Args:
 38            path: Path to the font file.
 39            family: Family this font belongs to (optional). See `classes.c11_family.KFamily`.
 40        """
 41        self.path = path
 42        """File system path to the font file"""
 43
 44        if family:
 45            self.family = family
 46
 47        names = libFonts.parseNameObject(path, separate=True)
 48        for [name, value] in names._asdict().items():
 49            setattr(self, name, value)
 50
 51    def __str__(self) -> str:
 52        """Return a string representation of the KFont instance."""
 53        return f"{colorama.Fore.BLACK}{colorama.Back.LIGHTGREEN_EX}KFont{colorama.Style.RESET_ALL} {self.fullName}"
 54
 55    @property
 56    def fontType(self):
 57        """
 58        Get the font type based on the style number.
 59
 60        Returns:
 61            The font type as determined by `lib.fonts.getFontType`.
 62        """
 63        try:
 64            return libFonts.getFontType(self.styleNumber)
 65        except Exception as e:
 66            logger.warning(f"[KFont] Error getting font type: {e}")
 67            return None
 68
 69    @property
 70    def version(self) -> str:
 71        """Returns the version of the font as a string."""
 72        return libFonts.getFontVersion(self.path)
 73
 74    def isType(self, criteria: libFonts.FontType) -> bool:
 75        """
 76        Check if the font matches given `lib.fonts.FontType` criteria:
 77
 78        ```
 79        upright \\d0
 80        italic \\d5
 81        display [^4-6]
 82        text [4-6]
 83        thin [1-3]
 84        thick [7-9]
 85        ```
 86
 87        Args:
 88            criteria: The font type to check (e.g., 'upright', 'italic', etc.).
 89
 90        Returns:
 91            True if the font matches the criteria, False otherwise.
 92        """
 93        return criteria in self.fontType
 94
 95    def getSibling(self) -> Optional["KFont"]:
 96        """
 97        Get the sibling font with a related style number.
 98
 99        Example:
100            `400` => `450`
101
102        Returns:
103            The sibling KFont instance if found, otherwise None.
104        """
105        prefix = self.styleNumber[0]
106        suffix = "5" if self.isType("upright") else "0"
107        # Adjust to correct length: 40 => 45, 400 => 450
108        adjusted = (prefix + suffix).ljust(len(self.styleNumber), "0")
109        siblingNumber = int(adjusted)
110        try:
111            return self.family.get(criteria=siblingNumber)
112        except Exception as e:
113            logger.warning("[KFont] Error getting sibling: {}", e)
114            return None
115
116    def getWeightClass(self, numeric=False) -> str | int:
117        """
118        Get the weight class of the font.
119
120        Example:
121            `400` => `4`
122
123        Args:
124            numeric: If True, return as integer; as string otherwise.
125
126        Returns:
127            The weight class as string or integer.
128        """
129        numberString = self.styleNumber[0]
130        return int(numberString) if numeric else numberString
131
132    def activate(self) -> "KFont":
133        """
134        Activate this font for use in DrawBot.
135
136        Returns:
137            The KFont instance (self).
138        """
139        logger.trace("Activating KFont: {}", self.fullName)
140        drawBot.font(self.path)
141        return self
142
143    def hasCharacter(self, character: str) -> bool:
144        """
145        Check if the font contains a specific glyph.
146
147        Args:
148            glyph: The glyph name to check (e.g., 'A', 'space', 'uniE000').
149        """
150        with drawBot.savedState():
151            self.activate()
152            return drawBot.fontContainsCharacters(character)
153
154    def supportsFeature(self, featureCode: libFonts.FeatureId) -> bool:
155        """
156        Check if the font supports a specific OpenType feature.
157
158        Args:
159            featureCode: The OpenType feature code to check (e.g., 'ss01', 'liga').
160        """
161        return featureCode in drawBot.listOpenTypeFeatures(self.path)

Represents a single font and provides methods for font analysis and manipulation.

Usually grouped into classes.c11_family.KFamily.

KFont(path: str, family: Optional[classes.c11_family.KFamily] = None)
33    def __init__(self, path: str, family: Optional["KFamily"] = None):
34        """
35        Initialize a KFont instance.
36
37        Args:
38            path: Path to the font file.
39            family: Family this font belongs to (optional). See `classes.c11_family.KFamily`.
40        """
41        self.path = path
42        """File system path to the font file"""
43
44        if family:
45            self.family = family
46
47        names = libFonts.parseNameObject(path, separate=True)
48        for [name, value] in names._asdict().items():
49            setattr(self, name, value)

Initialize a KFont instance.

Arguments:
fullName: str

Full name of font: Stabil Grotesk 400 Regular

familyName: str

Family name of font: Stabil Grotesk

styleName: str

Style name of font: Regular

shortName: str

Short name of font: 400 Regular

styleNumber: str | None

Style number of font: 400

path

File system path to the font file

fontType
55    @property
56    def fontType(self):
57        """
58        Get the font type based on the style number.
59
60        Returns:
61            The font type as determined by `lib.fonts.getFontType`.
62        """
63        try:
64            return libFonts.getFontType(self.styleNumber)
65        except Exception as e:
66            logger.warning(f"[KFont] Error getting font type: {e}")
67            return None

Get the font type based on the style number.

Returns:

The font type as determined by lib.fonts.getFontType.

version: str
69    @property
70    def version(self) -> str:
71        """Returns the version of the font as a string."""
72        return libFonts.getFontVersion(self.path)

Returns the version of the font as a string.

def isType( self, criteria: Literal['upright', 'italic', 'display', 'text', 'thin', 'thick', 'any']) -> bool:
74    def isType(self, criteria: libFonts.FontType) -> bool:
75        """
76        Check if the font matches given `lib.fonts.FontType` criteria:
77
78        ```
79        upright \\d0
80        italic \\d5
81        display [^4-6]
82        text [4-6]
83        thin [1-3]
84        thick [7-9]
85        ```
86
87        Args:
88            criteria: The font type to check (e.g., 'upright', 'italic', etc.).
89
90        Returns:
91            True if the font matches the criteria, False otherwise.
92        """
93        return criteria in self.fontType

Check if the font matches given lib.fonts.FontType criteria:

upright \d0
italic \d5
display [^4-6]
text [4-6]
thin [1-3]
thick [7-9]
Arguments:
  • criteria: The font type to check (e.g., 'upright', 'italic', etc.).
Returns:

True if the font matches the criteria, False otherwise.

def getSibling(self) -> Optional[KFont]:
 95    def getSibling(self) -> Optional["KFont"]:
 96        """
 97        Get the sibling font with a related style number.
 98
 99        Example:
100            `400` => `450`
101
102        Returns:
103            The sibling KFont instance if found, otherwise None.
104        """
105        prefix = self.styleNumber[0]
106        suffix = "5" if self.isType("upright") else "0"
107        # Adjust to correct length: 40 => 45, 400 => 450
108        adjusted = (prefix + suffix).ljust(len(self.styleNumber), "0")
109        siblingNumber = int(adjusted)
110        try:
111            return self.family.get(criteria=siblingNumber)
112        except Exception as e:
113            logger.warning("[KFont] Error getting sibling: {}", e)
114            return None

Get the sibling font with a related style number.

Example:

400 => 450

Returns:

The sibling KFont instance if found, otherwise None.

def getWeightClass(self, numeric=False) -> str | int:
116    def getWeightClass(self, numeric=False) -> str | int:
117        """
118        Get the weight class of the font.
119
120        Example:
121            `400` => `4`
122
123        Args:
124            numeric: If True, return as integer; as string otherwise.
125
126        Returns:
127            The weight class as string or integer.
128        """
129        numberString = self.styleNumber[0]
130        return int(numberString) if numeric else numberString

Get the weight class of the font.

Example:

400 => 4

Arguments:
  • numeric: If True, return as integer; as string otherwise.
Returns:

The weight class as string or integer.

def activate(self) -> KFont:
132    def activate(self) -> "KFont":
133        """
134        Activate this font for use in DrawBot.
135
136        Returns:
137            The KFont instance (self).
138        """
139        logger.trace("Activating KFont: {}", self.fullName)
140        drawBot.font(self.path)
141        return self

Activate this font for use in DrawBot.

Returns:

The KFont instance (self).

def hasCharacter(self, character: str) -> bool:
143    def hasCharacter(self, character: str) -> bool:
144        """
145        Check if the font contains a specific glyph.
146
147        Args:
148            glyph: The glyph name to check (e.g., 'A', 'space', 'uniE000').
149        """
150        with drawBot.savedState():
151            self.activate()
152            return drawBot.fontContainsCharacters(character)

Check if the font contains a specific glyph.

Arguments:
  • glyph: The glyph name to check (e.g., 'A', 'space', 'uniE000').
def supportsFeature( self, featureCode: Literal['liga', 'dlig', 'calt', 'locl', 'titl', 'case', 'pnum', 'lnum', 'onum', 'tnum', 'zero', 'subs', 'sups', 'numr', 'dnom', 'frac', 'ordn', 'kern', 'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'ss06', 'ss07', 'ss08', 'ss09', 'ss10']) -> bool:
154    def supportsFeature(self, featureCode: libFonts.FeatureId) -> bool:
155        """
156        Check if the font supports a specific OpenType feature.
157
158        Args:
159            featureCode: The OpenType feature code to check (e.g., 'ss01', 'liga').
160        """
161        return featureCode in drawBot.listOpenTypeFeatures(self.path)

Check if the font supports a specific OpenType feature.

Arguments:
  • featureCode: The OpenType feature code to check (e.g., 'ss01', 'liga').