classes.c11_family
1# Delay importing KFont until after KFamily to avoid circular import issues 2from __future__ import annotations 3import colorama 4import random 5from typing import TypeAlias, Literal 6from icecream import ic 7 8from lib import helpers, fonts as libFonts 9 10# Actually uses KFont, otherwise typing.TYPE_CHECKING would suffice to avoid circular import 11from .c10_font import KFont 12 13FontFormat: TypeAlias = Literal["plain", "rich"] 14"""Output format for fonts: `plain` for file paths, `rich` for KFont instances.""" 15 16FontOrder: TypeAlias = Literal["ascending", "descending"] 17"""Used to sort fonts in `KFamily.serve`.""" 18 19 20class KFamily: 21 """ 22 Represents a family of fonts and provides methods for querying and manipulating font collections. 23 24 See `classes.c10_font.KFont`. 25 """ 26 27 styles: list[str] 28 """List of plain font paths""" 29 30 fonts: list[KFont] 31 """List of KFonts""" 32 33 def __init__(self, fontFolder: str): 34 """ 35 Initialize a KFamily instance. 36 37 Args: 38 fontFolder: File system path to the folder containing font files. 39 """ 40 self.styles = libFonts.getFonts(fontFolder) 41 self.fonts = [KFont(path, self) for path in self.styles] 42 43 def __str__(self) -> str: 44 """ 45 Return a string representation of the KFamily instance. 46 """ 47 return f"{colorama.Fore.BLACK}{colorama.Back.LIGHTCYAN_EX}KFamily{colorama.Style.RESET_ALL} {', '.join([font.styleName for font in self.fonts])} {colorama.Style.DIM}({self.familyCount} {self.familyNoun})" 48 49 @property 50 def familyName(self) -> str: 51 """Returns the family name as a string.""" 52 font = self.fonts[0] 53 return font.familyName 54 55 @property 56 def familyCount(self) -> int: 57 """Returns the number of fonts in the family.""" 58 return len(self.fonts) 59 60 @property 61 def familyNoun(self) -> str: 62 """Returns 'font' or 'fonts' depending on the count.""" 63 return "fonts" if self.familyCount > 1 else "font" 64 65 @property 66 def scalar(self) -> int: 67 """ 68 Returns the length of `classes.c10_font.KFont.styleNumber`. 69 70 Example: 71 FontName-`200`Light => `3` 72 """ 73 return len(self.fonts[0].styleNumber) 74 75 def serve(self, format: FontFormat = "rich", order: FontOrder = "ascending"): 76 """ 77 Serve the fonts in the specified format and order. 78 79 Args: 80 format: Output format, see `FontFormat`. 81 order: Order of fonts, either 'ascending' or 'descending'. 82 83 Returns: 84 List of fonts in the specified format and order. 85 """ 86 items = self.fonts if format == "rich" else self.fonts 87 return self._reverse(items) if order == "descending" else items 88 89 def get( 90 self, 91 criteria: list[int] | list[libFonts.FontType], 92 format: FontFormat = "rich", 93 reversed: bool = False, 94 strategy: helpers.Strategy = "pick", 95 ) -> KFont | list[KFont]: 96 """ 97 Get fonts matching the given criteria. 98 99 Args: 100 criteria: List of weight numbers or font types to match. See `lib.fonts.FontType`. 101 format: Output format, see `FontFormat`. 102 reversed: Whether to reverse the result order. 103 strategy: Strategy for filtering fonts. 104 105 Example: 106 - `[50, 70]` => get 2 fonts 107 - `["upright", "display"]` => get intersection of types 108 109 Returns: 110 A single KFont or a list of KFonts matching the criteria. 111 """ 112 criteria = helpers.coerceList(criteria, strict=True) 113 result = [] 114 115 # Numeric argument: get individually and merge together 116 # Otherwise create intersection of criteria 117 isAllNumeric = all([isinstance(criterion, int) for criterion in criteria]) 118 119 for criterion in criteria: 120 # (1, 4) => get 100, 200, 300, 400 121 if isinstance(criterion, tuple): 122 matches = [ 123 font.path 124 for font in self.fonts 125 if helpers.isInRange(*criterion, font.getWeightClass(numeric=True)) 126 ] 127 elif isinstance(criterion, int): 128 if len(str(criterion)) > self.scalar: 129 # Scale down: 5000 => 500 130 scaled = str(criterion)[: self.scalar] 131 else: 132 # Scale up: 55 => 550 133 scaled = f"{criterion:0<{self.scalar}}" 134 matches = [libFonts.findFontByNumber(self.styles, int(scaled))] 135 else: 136 matches = libFonts.filterFonts( 137 self.styles, criterion, strategy=strategy 138 ) 139 140 result.append(matches) 141 142 # [40, 50] => flatten together 143 # ["upright", "thick"] => intersect 144 if isAllNumeric: 145 result = helpers.flatten(result) 146 else: 147 result = helpers.intersect(result) 148 149 if reversed: 150 result = self._reverse(result) 151 152 # KFont or plain font path 153 if format == "plain": 154 result = self._toPlainFonts(result) 155 else: 156 result = self._toRichFonts(result) 157 158 # Return list only for multiple fonts 159 return result[0] if helpers.isListOfOne(result) else result 160 161 def getRandom( 162 self, 163 criteria: libFonts.FontType | list[libFonts.FontType], 164 count=1, 165 strategy: helpers.Strategy = "pick", 166 ) -> KFont | list[KFont]: 167 """ 168 Get a random font or fonts matching the given criteria. 169 170 Args: 171 criteria: Font type or list of font types to match. 172 count: Number of random fonts to return. 173 strategy: Strategy for filtering fonts. 174 175 Returns: 176 A single KFont or a list of KFonts. 177 """ 178 result = self.get(criteria=criteria, strategy=strategy) 179 # -> str 180 if count == 1: 181 return random.choice(result) 182 # -> str[] 183 else: 184 return random.sample(result, min(len(result), count)) 185 186 def getFonts(self, **args) -> list[KFont]: 187 """ 188 Shorthand for get() to return `KFont` via rich `FontFormat`. 189 190 Args: 191 **args: Arguments passed to get(). 192 193 Returns: 194 List of KFont instances. 195 """ 196 return self.get(**args, format="rich") 197 198 def getWeightPair( 199 self, weightNumber: int, format: FontFormat = "rich" 200 ) -> list[KFont]: 201 """ 202 Get a pair of fonts for a given weight number. 203 204 Example: 205 `4` => `[40, 45]` 206 207 Args: 208 weightNumber: The weight number to match. 209 format: Output format, either 'plain' or 'rich'. 210 211 Returns: 212 List of KFonts or font paths for the weight pair. 213 """ 214 result = [ 215 font 216 for font in self.fonts 217 if font.getWeightClass(numeric=True) == weightNumber 218 ] 219 220 return result if format == "rich" else self._toPlainFonts(result) 221 222 def getChunks( 223 self, 224 mode: Literal["weight", "slope"], 225 subset: str | list = None, 226 reversed=False, 227 ) -> list[KFont]: 228 """ 229 Get fonts grouped by weight or slope. 230 231 Example: 232 - `weight` => [(100, 150), (200, 250), ...] 233 - `slope` => [(100, 200, ...), (150, 250, ...)] 234 235 Args: 236 mode: Grouping mode, either 'weight' or 'slope'. 237 subset: Optional subset via `KFamily.get`. 238 reversed: Whether to reverse the order within each group. 239 240 Returns: 241 List of lists of KFonts grouped by the specified mode. 242 """ 243 if mode == "slope": 244 criteria = ["upright", "italic"] 245 else: 246 criteria = self._getWeightClasses() 247 248 def _meetsCriterion(font: KFont, criterion): 249 if mode == "slope": 250 return font.isType(criterion) 251 else: 252 return font.getWeightClass() == criterion 253 254 def _filterForCriterion(criterion): 255 filtered = [font for font in fontList if _meetsCriterion(font, criterion)] 256 257 if reversed: 258 filtered.reverse() # Plain list[str] 259 260 return filtered 261 262 fontList = self.get(subset) if subset else self.fonts 263 264 return helpers.removeFalsy([_filterForCriterion(c) for c in criteria]) 265 266 def getRange(self, *args, size: int = 3): 267 """ 268 Get a range of evenly spaced fonts matching the given criteria. 269 270 Args: 271 *args: Criteria for selecting fonts. See `KFamily.get()`. 272 size: Number of fonts to return. 273 274 Example: 275 `'upright', 3` => `[10, 50, 90]` 276 277 Returns: 278 List of KFonts spaced evenly across the selection. 279 """ 280 result = self.get(*args) 281 return helpers.spaceEvenly(result, size=size) 282 283 def getExtremes(self, *args) -> list[KFont]: 284 """ 285 Get the extreme fonts (e.g., lightest and boldest) matching the criteria. 286 287 Args: 288 *args: Criteria for selecting fonts. See `KFamily.get()`. 289 290 Example: 291 - `'upright'` => `[10, 80]` 292 - `'text'` => `[40, 60]` 293 294 Returns: 295 List of two KFonts representing the extremes. 296 """ 297 return self.getRange(*args, size=2) 298 299 def activate(self): 300 """ 301 Activate the first font in the family for use in DrawBot. Useful before `omitMissing()`. 302 303 Returns: 304 The KFamily instance (self). 305 """ 306 self.fonts[0].activate() 307 return self 308 309 # Internals 310 def _reverse(self, items: list) -> list: 311 """ 312 Reverse the order of the given items, grouping by slope if applicable. 313 314 Args: 315 items: List of fonts or font paths. 316 317 Returns: 318 List of fonts or font paths in reversed order. 319 """ 320 arePlainFonts = all([isinstance(item, str) for item in items]) 321 322 # Pass in plain format to lib.fonts 323 if not arePlainFonts: 324 items = self._toPlainFonts(items) 325 326 fontsUpright, fontsItalic = libFonts.groupFontsBySlope(items, reverseOrder=1) 327 328 if fontsItalic: 329 items = helpers.flatten(list(zip(fontsUpright, fontsItalic))) 330 else: 331 items = fontsUpright 332 333 # Convert back to original format 334 if not arePlainFonts: 335 items = self._toRichFonts(items) 336 337 return items 338 339 def _getWeightClasses(self): 340 """ 341 Get all unique weight classes in the family. 342 343 Example: 344 `[1, 2, 3, 4, ... ]` 345 346 Returns: 347 Sorted list of unique weight classes. 348 """ 349 return sorted(set([item.getWeightClass() for item in self.fonts])) 350 351 def _toPlainFonts(self, fonts: list[KFont]) -> list[str]: 352 """ 353 Convert a list of KFont instances to a list of font paths. 354 355 Args: 356 fonts: List of KFont instances. 357 358 Returns: 359 List of font file paths as strings. 360 """ 361 return [font.path if isinstance(font, KFont) else font for font in fonts] 362 363 def _toRichFonts(self, fonts: list[str]) -> list[KFont]: 364 """ 365 Convert a list of font paths to KFont instances. 366 367 Args: 368 fonts: List of font file paths as strings. 369 370 Returns: 371 List of KFont instances. 372 """ 373 return [KFont(font, self) if isinstance(font, str) else font for font in fonts]
Output format for fonts: plain for file paths, rich for KFont instances.
Used to sort fonts in KFamily.serve.
21class KFamily: 22 """ 23 Represents a family of fonts and provides methods for querying and manipulating font collections. 24 25 See `classes.c10_font.KFont`. 26 """ 27 28 styles: list[str] 29 """List of plain font paths""" 30 31 fonts: list[KFont] 32 """List of KFonts""" 33 34 def __init__(self, fontFolder: str): 35 """ 36 Initialize a KFamily instance. 37 38 Args: 39 fontFolder: File system path to the folder containing font files. 40 """ 41 self.styles = libFonts.getFonts(fontFolder) 42 self.fonts = [KFont(path, self) for path in self.styles] 43 44 def __str__(self) -> str: 45 """ 46 Return a string representation of the KFamily instance. 47 """ 48 return f"{colorama.Fore.BLACK}{colorama.Back.LIGHTCYAN_EX}KFamily{colorama.Style.RESET_ALL} {', '.join([font.styleName for font in self.fonts])} {colorama.Style.DIM}({self.familyCount} {self.familyNoun})" 49 50 @property 51 def familyName(self) -> str: 52 """Returns the family name as a string.""" 53 font = self.fonts[0] 54 return font.familyName 55 56 @property 57 def familyCount(self) -> int: 58 """Returns the number of fonts in the family.""" 59 return len(self.fonts) 60 61 @property 62 def familyNoun(self) -> str: 63 """Returns 'font' or 'fonts' depending on the count.""" 64 return "fonts" if self.familyCount > 1 else "font" 65 66 @property 67 def scalar(self) -> int: 68 """ 69 Returns the length of `classes.c10_font.KFont.styleNumber`. 70 71 Example: 72 FontName-`200`Light => `3` 73 """ 74 return len(self.fonts[0].styleNumber) 75 76 def serve(self, format: FontFormat = "rich", order: FontOrder = "ascending"): 77 """ 78 Serve the fonts in the specified format and order. 79 80 Args: 81 format: Output format, see `FontFormat`. 82 order: Order of fonts, either 'ascending' or 'descending'. 83 84 Returns: 85 List of fonts in the specified format and order. 86 """ 87 items = self.fonts if format == "rich" else self.fonts 88 return self._reverse(items) if order == "descending" else items 89 90 def get( 91 self, 92 criteria: list[int] | list[libFonts.FontType], 93 format: FontFormat = "rich", 94 reversed: bool = False, 95 strategy: helpers.Strategy = "pick", 96 ) -> KFont | list[KFont]: 97 """ 98 Get fonts matching the given criteria. 99 100 Args: 101 criteria: List of weight numbers or font types to match. See `lib.fonts.FontType`. 102 format: Output format, see `FontFormat`. 103 reversed: Whether to reverse the result order. 104 strategy: Strategy for filtering fonts. 105 106 Example: 107 - `[50, 70]` => get 2 fonts 108 - `["upright", "display"]` => get intersection of types 109 110 Returns: 111 A single KFont or a list of KFonts matching the criteria. 112 """ 113 criteria = helpers.coerceList(criteria, strict=True) 114 result = [] 115 116 # Numeric argument: get individually and merge together 117 # Otherwise create intersection of criteria 118 isAllNumeric = all([isinstance(criterion, int) for criterion in criteria]) 119 120 for criterion in criteria: 121 # (1, 4) => get 100, 200, 300, 400 122 if isinstance(criterion, tuple): 123 matches = [ 124 font.path 125 for font in self.fonts 126 if helpers.isInRange(*criterion, font.getWeightClass(numeric=True)) 127 ] 128 elif isinstance(criterion, int): 129 if len(str(criterion)) > self.scalar: 130 # Scale down: 5000 => 500 131 scaled = str(criterion)[: self.scalar] 132 else: 133 # Scale up: 55 => 550 134 scaled = f"{criterion:0<{self.scalar}}" 135 matches = [libFonts.findFontByNumber(self.styles, int(scaled))] 136 else: 137 matches = libFonts.filterFonts( 138 self.styles, criterion, strategy=strategy 139 ) 140 141 result.append(matches) 142 143 # [40, 50] => flatten together 144 # ["upright", "thick"] => intersect 145 if isAllNumeric: 146 result = helpers.flatten(result) 147 else: 148 result = helpers.intersect(result) 149 150 if reversed: 151 result = self._reverse(result) 152 153 # KFont or plain font path 154 if format == "plain": 155 result = self._toPlainFonts(result) 156 else: 157 result = self._toRichFonts(result) 158 159 # Return list only for multiple fonts 160 return result[0] if helpers.isListOfOne(result) else result 161 162 def getRandom( 163 self, 164 criteria: libFonts.FontType | list[libFonts.FontType], 165 count=1, 166 strategy: helpers.Strategy = "pick", 167 ) -> KFont | list[KFont]: 168 """ 169 Get a random font or fonts matching the given criteria. 170 171 Args: 172 criteria: Font type or list of font types to match. 173 count: Number of random fonts to return. 174 strategy: Strategy for filtering fonts. 175 176 Returns: 177 A single KFont or a list of KFonts. 178 """ 179 result = self.get(criteria=criteria, strategy=strategy) 180 # -> str 181 if count == 1: 182 return random.choice(result) 183 # -> str[] 184 else: 185 return random.sample(result, min(len(result), count)) 186 187 def getFonts(self, **args) -> list[KFont]: 188 """ 189 Shorthand for get() to return `KFont` via rich `FontFormat`. 190 191 Args: 192 **args: Arguments passed to get(). 193 194 Returns: 195 List of KFont instances. 196 """ 197 return self.get(**args, format="rich") 198 199 def getWeightPair( 200 self, weightNumber: int, format: FontFormat = "rich" 201 ) -> list[KFont]: 202 """ 203 Get a pair of fonts for a given weight number. 204 205 Example: 206 `4` => `[40, 45]` 207 208 Args: 209 weightNumber: The weight number to match. 210 format: Output format, either 'plain' or 'rich'. 211 212 Returns: 213 List of KFonts or font paths for the weight pair. 214 """ 215 result = [ 216 font 217 for font in self.fonts 218 if font.getWeightClass(numeric=True) == weightNumber 219 ] 220 221 return result if format == "rich" else self._toPlainFonts(result) 222 223 def getChunks( 224 self, 225 mode: Literal["weight", "slope"], 226 subset: str | list = None, 227 reversed=False, 228 ) -> list[KFont]: 229 """ 230 Get fonts grouped by weight or slope. 231 232 Example: 233 - `weight` => [(100, 150), (200, 250), ...] 234 - `slope` => [(100, 200, ...), (150, 250, ...)] 235 236 Args: 237 mode: Grouping mode, either 'weight' or 'slope'. 238 subset: Optional subset via `KFamily.get`. 239 reversed: Whether to reverse the order within each group. 240 241 Returns: 242 List of lists of KFonts grouped by the specified mode. 243 """ 244 if mode == "slope": 245 criteria = ["upright", "italic"] 246 else: 247 criteria = self._getWeightClasses() 248 249 def _meetsCriterion(font: KFont, criterion): 250 if mode == "slope": 251 return font.isType(criterion) 252 else: 253 return font.getWeightClass() == criterion 254 255 def _filterForCriterion(criterion): 256 filtered = [font for font in fontList if _meetsCriterion(font, criterion)] 257 258 if reversed: 259 filtered.reverse() # Plain list[str] 260 261 return filtered 262 263 fontList = self.get(subset) if subset else self.fonts 264 265 return helpers.removeFalsy([_filterForCriterion(c) for c in criteria]) 266 267 def getRange(self, *args, size: int = 3): 268 """ 269 Get a range of evenly spaced fonts matching the given criteria. 270 271 Args: 272 *args: Criteria for selecting fonts. See `KFamily.get()`. 273 size: Number of fonts to return. 274 275 Example: 276 `'upright', 3` => `[10, 50, 90]` 277 278 Returns: 279 List of KFonts spaced evenly across the selection. 280 """ 281 result = self.get(*args) 282 return helpers.spaceEvenly(result, size=size) 283 284 def getExtremes(self, *args) -> list[KFont]: 285 """ 286 Get the extreme fonts (e.g., lightest and boldest) matching the criteria. 287 288 Args: 289 *args: Criteria for selecting fonts. See `KFamily.get()`. 290 291 Example: 292 - `'upright'` => `[10, 80]` 293 - `'text'` => `[40, 60]` 294 295 Returns: 296 List of two KFonts representing the extremes. 297 """ 298 return self.getRange(*args, size=2) 299 300 def activate(self): 301 """ 302 Activate the first font in the family for use in DrawBot. Useful before `omitMissing()`. 303 304 Returns: 305 The KFamily instance (self). 306 """ 307 self.fonts[0].activate() 308 return self 309 310 # Internals 311 def _reverse(self, items: list) -> list: 312 """ 313 Reverse the order of the given items, grouping by slope if applicable. 314 315 Args: 316 items: List of fonts or font paths. 317 318 Returns: 319 List of fonts or font paths in reversed order. 320 """ 321 arePlainFonts = all([isinstance(item, str) for item in items]) 322 323 # Pass in plain format to lib.fonts 324 if not arePlainFonts: 325 items = self._toPlainFonts(items) 326 327 fontsUpright, fontsItalic = libFonts.groupFontsBySlope(items, reverseOrder=1) 328 329 if fontsItalic: 330 items = helpers.flatten(list(zip(fontsUpright, fontsItalic))) 331 else: 332 items = fontsUpright 333 334 # Convert back to original format 335 if not arePlainFonts: 336 items = self._toRichFonts(items) 337 338 return items 339 340 def _getWeightClasses(self): 341 """ 342 Get all unique weight classes in the family. 343 344 Example: 345 `[1, 2, 3, 4, ... ]` 346 347 Returns: 348 Sorted list of unique weight classes. 349 """ 350 return sorted(set([item.getWeightClass() for item in self.fonts])) 351 352 def _toPlainFonts(self, fonts: list[KFont]) -> list[str]: 353 """ 354 Convert a list of KFont instances to a list of font paths. 355 356 Args: 357 fonts: List of KFont instances. 358 359 Returns: 360 List of font file paths as strings. 361 """ 362 return [font.path if isinstance(font, KFont) else font for font in fonts] 363 364 def _toRichFonts(self, fonts: list[str]) -> list[KFont]: 365 """ 366 Convert a list of font paths to KFont instances. 367 368 Args: 369 fonts: List of font file paths as strings. 370 371 Returns: 372 List of KFont instances. 373 """ 374 return [KFont(font, self) if isinstance(font, str) else font for font in fonts]
Represents a family of fonts and provides methods for querying and manipulating font collections.
34 def __init__(self, fontFolder: str): 35 """ 36 Initialize a KFamily instance. 37 38 Args: 39 fontFolder: File system path to the folder containing font files. 40 """ 41 self.styles = libFonts.getFonts(fontFolder) 42 self.fonts = [KFont(path, self) for path in self.styles]
Initialize a KFamily instance.
Arguments:
- fontFolder: File system path to the folder containing font files.
50 @property 51 def familyName(self) -> str: 52 """Returns the family name as a string.""" 53 font = self.fonts[0] 54 return font.familyName
Returns the family name as a string.
56 @property 57 def familyCount(self) -> int: 58 """Returns the number of fonts in the family.""" 59 return len(self.fonts)
Returns the number of fonts in the family.
61 @property 62 def familyNoun(self) -> str: 63 """Returns 'font' or 'fonts' depending on the count.""" 64 return "fonts" if self.familyCount > 1 else "font"
Returns 'font' or 'fonts' depending on the count.
76 def serve(self, format: FontFormat = "rich", order: FontOrder = "ascending"): 77 """ 78 Serve the fonts in the specified format and order. 79 80 Args: 81 format: Output format, see `FontFormat`. 82 order: Order of fonts, either 'ascending' or 'descending'. 83 84 Returns: 85 List of fonts in the specified format and order. 86 """ 87 items = self.fonts if format == "rich" else self.fonts 88 return self._reverse(items) if order == "descending" else items
Serve the fonts in the specified format and order.
Arguments:
- format: Output format, see
FontFormat. - order: Order of fonts, either 'ascending' or 'descending'.
Returns:
List of fonts in the specified format and order.
90 def get( 91 self, 92 criteria: list[int] | list[libFonts.FontType], 93 format: FontFormat = "rich", 94 reversed: bool = False, 95 strategy: helpers.Strategy = "pick", 96 ) -> KFont | list[KFont]: 97 """ 98 Get fonts matching the given criteria. 99 100 Args: 101 criteria: List of weight numbers or font types to match. See `lib.fonts.FontType`. 102 format: Output format, see `FontFormat`. 103 reversed: Whether to reverse the result order. 104 strategy: Strategy for filtering fonts. 105 106 Example: 107 - `[50, 70]` => get 2 fonts 108 - `["upright", "display"]` => get intersection of types 109 110 Returns: 111 A single KFont or a list of KFonts matching the criteria. 112 """ 113 criteria = helpers.coerceList(criteria, strict=True) 114 result = [] 115 116 # Numeric argument: get individually and merge together 117 # Otherwise create intersection of criteria 118 isAllNumeric = all([isinstance(criterion, int) for criterion in criteria]) 119 120 for criterion in criteria: 121 # (1, 4) => get 100, 200, 300, 400 122 if isinstance(criterion, tuple): 123 matches = [ 124 font.path 125 for font in self.fonts 126 if helpers.isInRange(*criterion, font.getWeightClass(numeric=True)) 127 ] 128 elif isinstance(criterion, int): 129 if len(str(criterion)) > self.scalar: 130 # Scale down: 5000 => 500 131 scaled = str(criterion)[: self.scalar] 132 else: 133 # Scale up: 55 => 550 134 scaled = f"{criterion:0<{self.scalar}}" 135 matches = [libFonts.findFontByNumber(self.styles, int(scaled))] 136 else: 137 matches = libFonts.filterFonts( 138 self.styles, criterion, strategy=strategy 139 ) 140 141 result.append(matches) 142 143 # [40, 50] => flatten together 144 # ["upright", "thick"] => intersect 145 if isAllNumeric: 146 result = helpers.flatten(result) 147 else: 148 result = helpers.intersect(result) 149 150 if reversed: 151 result = self._reverse(result) 152 153 # KFont or plain font path 154 if format == "plain": 155 result = self._toPlainFonts(result) 156 else: 157 result = self._toRichFonts(result) 158 159 # Return list only for multiple fonts 160 return result[0] if helpers.isListOfOne(result) else result
Get fonts matching the given criteria.
Arguments:
- criteria: List of weight numbers or font types to match. See
lib.fonts.FontType. - format: Output format, see
FontFormat. - reversed: Whether to reverse the result order.
- strategy: Strategy for filtering fonts.
Example:
[50, 70]=> get 2 fonts["upright", "display"]=> get intersection of types
Returns:
A single KFont or a list of KFonts matching the criteria.
162 def getRandom( 163 self, 164 criteria: libFonts.FontType | list[libFonts.FontType], 165 count=1, 166 strategy: helpers.Strategy = "pick", 167 ) -> KFont | list[KFont]: 168 """ 169 Get a random font or fonts matching the given criteria. 170 171 Args: 172 criteria: Font type or list of font types to match. 173 count: Number of random fonts to return. 174 strategy: Strategy for filtering fonts. 175 176 Returns: 177 A single KFont or a list of KFonts. 178 """ 179 result = self.get(criteria=criteria, strategy=strategy) 180 # -> str 181 if count == 1: 182 return random.choice(result) 183 # -> str[] 184 else: 185 return random.sample(result, min(len(result), count))
Get a random font or fonts matching the given criteria.
Arguments:
- criteria: Font type or list of font types to match.
- count: Number of random fonts to return.
- strategy: Strategy for filtering fonts.
Returns:
A single KFont or a list of KFonts.
187 def getFonts(self, **args) -> list[KFont]: 188 """ 189 Shorthand for get() to return `KFont` via rich `FontFormat`. 190 191 Args: 192 **args: Arguments passed to get(). 193 194 Returns: 195 List of KFont instances. 196 """ 197 return self.get(**args, format="rich")
Shorthand for get() to return KFont via rich FontFormat.
Arguments:
- **args: Arguments passed to get().
Returns:
List of KFont instances.
199 def getWeightPair( 200 self, weightNumber: int, format: FontFormat = "rich" 201 ) -> list[KFont]: 202 """ 203 Get a pair of fonts for a given weight number. 204 205 Example: 206 `4` => `[40, 45]` 207 208 Args: 209 weightNumber: The weight number to match. 210 format: Output format, either 'plain' or 'rich'. 211 212 Returns: 213 List of KFonts or font paths for the weight pair. 214 """ 215 result = [ 216 font 217 for font in self.fonts 218 if font.getWeightClass(numeric=True) == weightNumber 219 ] 220 221 return result if format == "rich" else self._toPlainFonts(result)
Get a pair of fonts for a given weight number.
Example:
4=>[40, 45]
Arguments:
- weightNumber: The weight number to match.
- format: Output format, either 'plain' or 'rich'.
Returns:
List of KFonts or font paths for the weight pair.
223 def getChunks( 224 self, 225 mode: Literal["weight", "slope"], 226 subset: str | list = None, 227 reversed=False, 228 ) -> list[KFont]: 229 """ 230 Get fonts grouped by weight or slope. 231 232 Example: 233 - `weight` => [(100, 150), (200, 250), ...] 234 - `slope` => [(100, 200, ...), (150, 250, ...)] 235 236 Args: 237 mode: Grouping mode, either 'weight' or 'slope'. 238 subset: Optional subset via `KFamily.get`. 239 reversed: Whether to reverse the order within each group. 240 241 Returns: 242 List of lists of KFonts grouped by the specified mode. 243 """ 244 if mode == "slope": 245 criteria = ["upright", "italic"] 246 else: 247 criteria = self._getWeightClasses() 248 249 def _meetsCriterion(font: KFont, criterion): 250 if mode == "slope": 251 return font.isType(criterion) 252 else: 253 return font.getWeightClass() == criterion 254 255 def _filterForCriterion(criterion): 256 filtered = [font for font in fontList if _meetsCriterion(font, criterion)] 257 258 if reversed: 259 filtered.reverse() # Plain list[str] 260 261 return filtered 262 263 fontList = self.get(subset) if subset else self.fonts 264 265 return helpers.removeFalsy([_filterForCriterion(c) for c in criteria])
Get fonts grouped by weight or slope.
Example:
weight=> [(100, 150), (200, 250), ...]slope=> [(100, 200, ...), (150, 250, ...)]
Arguments:
- mode: Grouping mode, either 'weight' or 'slope'.
- subset: Optional subset via
KFamily.get. - reversed: Whether to reverse the order within each group.
Returns:
List of lists of KFonts grouped by the specified mode.
267 def getRange(self, *args, size: int = 3): 268 """ 269 Get a range of evenly spaced fonts matching the given criteria. 270 271 Args: 272 *args: Criteria for selecting fonts. See `KFamily.get()`. 273 size: Number of fonts to return. 274 275 Example: 276 `'upright', 3` => `[10, 50, 90]` 277 278 Returns: 279 List of KFonts spaced evenly across the selection. 280 """ 281 result = self.get(*args) 282 return helpers.spaceEvenly(result, size=size)
Get a range of evenly spaced fonts matching the given criteria.
Arguments:
- *args: Criteria for selecting fonts. See
KFamily.get(). - size: Number of fonts to return.
Example:
'upright', 3=>[10, 50, 90]
Returns:
List of KFonts spaced evenly across the selection.
284 def getExtremes(self, *args) -> list[KFont]: 285 """ 286 Get the extreme fonts (e.g., lightest and boldest) matching the criteria. 287 288 Args: 289 *args: Criteria for selecting fonts. See `KFamily.get()`. 290 291 Example: 292 - `'upright'` => `[10, 80]` 293 - `'text'` => `[40, 60]` 294 295 Returns: 296 List of two KFonts representing the extremes. 297 """ 298 return self.getRange(*args, size=2)
Get the extreme fonts (e.g., lightest and boldest) matching the criteria.
Arguments:
- *args: Criteria for selecting fonts. See
KFamily.get().
Example:
'upright'=>[10, 80]'text'=>[40, 60]
Returns:
List of two KFonts representing the extremes.
300 def activate(self): 301 """ 302 Activate the first font in the family for use in DrawBot. Useful before `omitMissing()`. 303 304 Returns: 305 The KFamily instance (self). 306 """ 307 self.fonts[0].activate() 308 return self
Activate the first font in the family for use in DrawBot. Useful before omitMissing().
Returns:
The KFamily instance (self).