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, Sequence, get_args 6from icecream import ic 7from loguru import logger 8 9from lib import helpers, fonts as libFonts 10from datatypes.data_fontlist import DFontList 11 12# Actually uses KFont, otherwise typing.TYPE_CHECKING would suffice to avoid circular import 13from .c10_font import KFont 14 15FontOrder: TypeAlias = Literal["ascending", "descending"] 16"""Used to sort fonts in `KFamily.serve`.""" 17 18 19class KFamily: 20 """ 21 Represents a family of fonts and provides methods for querying and manipulating font collections. 22 23 See `classes.c10_font.KFont`. 24 """ 25 26 styles: list[str] 27 """List of plain font paths""" 28 29 fonts: DFontList 30 """List-like collection of KFonts (`datatypes.data_fontlist.DFontList`).""" 31 32 def __init__(self, fontFolder: str): 33 """ 34 Initialize a KFamily instance. 35 36 Args: 37 fontFolder: File system path to the folder containing font files. 38 """ 39 self.styles = libFonts.getFonts(fontFolder) 40 self.fonts = DFontList([KFont(path, self) for path in self.styles]) 41 42 def __str__(self) -> str: 43 """ 44 Return a string representation of the KFamily instance. 45 """ 46 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})" 47 48 @property 49 def familyName(self) -> str: 50 """Returns the family name as a string.""" 51 return self.fonts[0].familyName 52 53 @property 54 def familyAbbreviation(self) -> str: 55 """Returns the family abbreviation, formed by the first letter of each word in the family name. 56 57 Example: 58 `Stabil Grotesk` => `SG` 59 """ 60 return "".join([word[0] for word in self.familyName.split(" ")]) 61 62 @property 63 def familyCount(self) -> int: 64 """Returns the number of fonts in the family.""" 65 return len(self.fonts) 66 67 @property 68 def familyNoun(self) -> str: 69 """Returns 'font' or 'fonts' depending on the count.""" 70 return "fonts" if self.familyCount > 1 else "font" 71 72 @property 73 def version(self) -> str: 74 """Returns the version of the family, derived from the first font.""" 75 return libFonts.getFontVersion(self.fonts[0].path) 76 77 @property 78 def scalar(self) -> int: 79 """ 80 Returns the length of `classes.c10_font.KFont.styleNumber`. 81 82 Example: 83 FontName-`200`Light => `3` 84 """ 85 return len(self.fonts[0].styleNumber) 86 87 def get( 88 self, 89 criteria: list[int | str | tuple[int, int]] | int | str | tuple[int, int], 90 strategy: helpers.Strategy = "pick", 91 unwrapSingle: bool = True, 92 ) -> KFont | DFontList: 93 """ 94 Get fonts matching the given criteria. 95 96 Args: 97 criteria: Style number(s), style name(s), weight-class range tuple(s), 98 or `lib.fonts.FontType` criterion/criteria. 99 strategy: Strategy for filtering fonts. 100 unwrapSingle: Whether to return a single KFont if only one match is found. 101 102 Example: 103 - `[50, 70]` => get 2 fonts 104 - `["upright", "display"]` => get intersection of types 105 106 Returns: 107 A single KFont or a list of KFonts matching the criteria. 108 """ 109 110 if isinstance(criteria, list): 111 logger.warning( 112 "Using `KFamily.get()` with multiple criteria is deprecated. Use `findFonts()` for naming criteria and `filterFonts()` for genre criteria instead." 113 ) 114 115 criteria = helpers.coerceList(criteria, strict=True) 116 # Numeric criteria are resolved position-by-position. 117 # Mixed/text criteria are combined as an intersection. 118 isAllNumeric = all([isinstance(criterion, int) for criterion in criteria]) 119 if isAllNumeric: 120 result = self.findFonts(criteria) 121 else: 122 result = self.filterFonts(criteria, strategy=strategy) 123 124 result = DFontList([item for item in result if item is not None]) 125 126 # Return list only for multiple fonts 127 if unwrapSingle and isinstance(result, (list, DFontList)) and len(result) == 1: 128 return result[0] 129 130 return result 131 132 def findFonts( 133 self, 134 criteria: list[int | str], 135 ) -> DFontList[KFont]: 136 """ 137 Resolve criteria one-by-one to fonts, preserving input length and order. 138 139 Accepts style numbers (int or numeric string) and style names. 140 141 Args: 142 criteria: List of style numbers or style names. 143 144 Returns: 145 List of resolved fonts in the same order and length as criteria. 146 """ 147 criteria = helpers.coerceList(criteria, strict=True) 148 resolved = [self._findFontPath(criterion) for criterion in criteria] 149 150 return DFontList([KFont(path, self) for path in resolved if path is not None]) 151 152 def filterFonts( 153 self, 154 criteria: list[int | str | tuple[int, int]] | libFonts.FontType, 155 strategy: helpers.Strategy = "pick", 156 ) -> DFontList[KFont]: 157 """ 158 Combine criteria and return fonts that satisfy their intersection. 159 160 Args: 161 criteria: List of criteria to combine. Supports style numbers, 162 style names, style-number ranges `(start, end)` by weight class, 163 and `lib.fonts.FontType` values. 164 strategy: Filtering strategy for `lib.fonts.FontType` criteria. 165 166 Returns: 167 Intersected list of matching fonts. 168 """ 169 criteria = helpers.coerceList(criteria, strict=True) 170 allMatches = [ 171 self._getMatchesForCriterion(criterion, strategy) for criterion in criteria 172 ] 173 result = helpers.intersect(allMatches) 174 175 return DFontList(self._toRichFonts(result)) 176 177 def getRandom( 178 self, 179 criteria: libFonts.FontType | list[libFonts.FontType], 180 count=1, 181 strategy: helpers.Strategy = "pick", 182 ) -> KFont | DFontList: 183 """ 184 Get a random font or fonts matching the given criteria. 185 186 Args: 187 criteria: Font type or list of font types to match. 188 count: Number of random fonts to return. 189 strategy: Strategy for filtering fonts. 190 191 Returns: 192 A single KFont or a list of KFonts. 193 """ 194 result = self.filterFonts(criteria=criteria, strategy=strategy) 195 # -> str 196 if count == 1: 197 return random.choice(result) 198 # -> str[] 199 else: 200 return DFontList(random.sample(result, min(len(result), count))) 201 202 def getWeightPair(self, weightNumber: int) -> DFontList: 203 """ 204 Get a pair of fonts for a given weight number. 205 206 Example: 207 `4` => `[40, 45]` 208 209 Args: 210 weightNumber: The weight number to match. 211 212 Returns: 213 List of KFonts 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 DFontList(result) 222 223 def getRange(self, *args, size: int = 3): 224 """ 225 Get a range of evenly spaced fonts matching the given criteria. 226 227 Args: 228 *args: Criteria for selecting fonts. See `KFamily.get()`. 229 size: Number of fonts to return. 230 231 Example: 232 `'upright', 3` => `[10, 50, 90]` 233 234 Returns: 235 List of KFonts spaced evenly across the selection. 236 """ 237 result = self.get(*args) 238 if isinstance(result, KFont): 239 return DFontList([result]) 240 return DFontList(helpers.spaceEvenly(result, size=size)) 241 242 def getExtremes(self, *args) -> DFontList: 243 """ 244 Get the extreme fonts (e.g., lightest and boldest) matching the criteria. 245 246 Args: 247 *args: Criteria for selecting fonts. See `KFamily.get()`. 248 249 Example: 250 - `'upright'` => `[10, 80]` 251 - `'text'` => `[40, 60]` 252 253 Returns: 254 List of two KFonts representing the extremes. 255 """ 256 return self.getRange(*args, size=2) 257 258 def activate(self) -> "KFamily": 259 """ 260 Activate the first font in the family for use in DrawBot. Useful before `omitMissing()`. 261 262 Returns: 263 The KFamily instance (self). 264 """ 265 self.fonts[0].activate() 266 return self 267 268 # Internals 269 def _getWeightClasses(self): 270 """ 271 Get all unique weight classes in the family. 272 273 Example: 274 `[1, 2, 3, 4, ... ]` 275 276 Returns: 277 Sorted list of unique weight classes. 278 """ 279 return sorted(set([item.getWeightClass() for item in self.fonts])) 280 281 def _toRichFonts(self, fonts: list[str]) -> list[KFont]: 282 """Convert a list of font paths to KFont instances.""" 283 return [KFont(font, self) if isinstance(font, str) else font for font in fonts] 284 285 def _normalizeStyleNumber(self, criterion: int) -> int: 286 """ 287 Normalize style numbers to family scalar length. 288 289 Examples: 290 `5000` => `500` 291 `55` => `550` for scalar 3 292 """ 293 if len(str(criterion)) > self.scalar: 294 # Scale down: 5000 => 500 295 scaled = str(criterion)[: self.scalar] 296 else: 297 # Scale up: 55 => 550 298 scaled = f"{criterion:0<{self.scalar}}" 299 return int(scaled) 300 301 def _findFontPath(self, criterion: int | str) -> str | None: 302 """Resolve a single style number or style name to a font path.""" 303 if isinstance(criterion, int): 304 return libFonts.findFontByNumber( 305 self.styles, self._normalizeStyleNumber(criterion) 306 ) 307 308 if isinstance(criterion, str) and criterion.strip().isnumeric(): 309 numeric = int(criterion.strip()) 310 return libFonts.findFontByNumber( 311 self.styles, self._normalizeStyleNumber(numeric) 312 ) 313 314 # Match by style name (case-insensitive) 315 if isinstance(criterion, str): 316 target = criterion.casefold().strip() 317 for font in self.fonts: 318 if font.styleName.casefold() == target: 319 return font.path 320 321 logger.warning( 322 f"[KFamily._findFontPath] Could not resolve criterion: {criterion!r}" 323 ) 324 return None 325 326 def _getMatchesForCriterion( 327 self, 328 criterion: int | str | tuple[int, int], 329 strategy: helpers.Strategy, 330 ) -> list[str]: 331 """Resolve one criterion to a list of matching plain font paths.""" 332 if isinstance(criterion, tuple): 333 return [ 334 font.path 335 for font in self.fonts 336 if helpers.isInRange(*criterion, font.getWeightClass(numeric=True)) 337 ] 338 339 # ! Prefer matching by FontType if criterion is a valid FontType value 340 if isinstance(criterion, str) and criterion.casefold().strip() in get_args( 341 libFonts.FontType 342 ): 343 return libFonts.filterFonts(self.styles, criterion, strategy=strategy) 344 345 resolved = self._findFontPath(criterion) 346 if resolved: 347 return [resolved] 348 349 return libFonts.filterFonts(self.styles, criterion, strategy=strategy)
Used to sort fonts in KFamily.serve.
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: DFontList 31 """List-like collection of KFonts (`datatypes.data_fontlist.DFontList`).""" 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 = DFontList([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 return self.fonts[0].familyName 53 54 @property 55 def familyAbbreviation(self) -> str: 56 """Returns the family abbreviation, formed by the first letter of each word in the family name. 57 58 Example: 59 `Stabil Grotesk` => `SG` 60 """ 61 return "".join([word[0] for word in self.familyName.split(" ")]) 62 63 @property 64 def familyCount(self) -> int: 65 """Returns the number of fonts in the family.""" 66 return len(self.fonts) 67 68 @property 69 def familyNoun(self) -> str: 70 """Returns 'font' or 'fonts' depending on the count.""" 71 return "fonts" if self.familyCount > 1 else "font" 72 73 @property 74 def version(self) -> str: 75 """Returns the version of the family, derived from the first font.""" 76 return libFonts.getFontVersion(self.fonts[0].path) 77 78 @property 79 def scalar(self) -> int: 80 """ 81 Returns the length of `classes.c10_font.KFont.styleNumber`. 82 83 Example: 84 FontName-`200`Light => `3` 85 """ 86 return len(self.fonts[0].styleNumber) 87 88 def get( 89 self, 90 criteria: list[int | str | tuple[int, int]] | int | str | tuple[int, int], 91 strategy: helpers.Strategy = "pick", 92 unwrapSingle: bool = True, 93 ) -> KFont | DFontList: 94 """ 95 Get fonts matching the given criteria. 96 97 Args: 98 criteria: Style number(s), style name(s), weight-class range tuple(s), 99 or `lib.fonts.FontType` criterion/criteria. 100 strategy: Strategy for filtering fonts. 101 unwrapSingle: Whether to return a single KFont if only one match is found. 102 103 Example: 104 - `[50, 70]` => get 2 fonts 105 - `["upright", "display"]` => get intersection of types 106 107 Returns: 108 A single KFont or a list of KFonts matching the criteria. 109 """ 110 111 if isinstance(criteria, list): 112 logger.warning( 113 "Using `KFamily.get()` with multiple criteria is deprecated. Use `findFonts()` for naming criteria and `filterFonts()` for genre criteria instead." 114 ) 115 116 criteria = helpers.coerceList(criteria, strict=True) 117 # Numeric criteria are resolved position-by-position. 118 # Mixed/text criteria are combined as an intersection. 119 isAllNumeric = all([isinstance(criterion, int) for criterion in criteria]) 120 if isAllNumeric: 121 result = self.findFonts(criteria) 122 else: 123 result = self.filterFonts(criteria, strategy=strategy) 124 125 result = DFontList([item for item in result if item is not None]) 126 127 # Return list only for multiple fonts 128 if unwrapSingle and isinstance(result, (list, DFontList)) and len(result) == 1: 129 return result[0] 130 131 return result 132 133 def findFonts( 134 self, 135 criteria: list[int | str], 136 ) -> DFontList[KFont]: 137 """ 138 Resolve criteria one-by-one to fonts, preserving input length and order. 139 140 Accepts style numbers (int or numeric string) and style names. 141 142 Args: 143 criteria: List of style numbers or style names. 144 145 Returns: 146 List of resolved fonts in the same order and length as criteria. 147 """ 148 criteria = helpers.coerceList(criteria, strict=True) 149 resolved = [self._findFontPath(criterion) for criterion in criteria] 150 151 return DFontList([KFont(path, self) for path in resolved if path is not None]) 152 153 def filterFonts( 154 self, 155 criteria: list[int | str | tuple[int, int]] | libFonts.FontType, 156 strategy: helpers.Strategy = "pick", 157 ) -> DFontList[KFont]: 158 """ 159 Combine criteria and return fonts that satisfy their intersection. 160 161 Args: 162 criteria: List of criteria to combine. Supports style numbers, 163 style names, style-number ranges `(start, end)` by weight class, 164 and `lib.fonts.FontType` values. 165 strategy: Filtering strategy for `lib.fonts.FontType` criteria. 166 167 Returns: 168 Intersected list of matching fonts. 169 """ 170 criteria = helpers.coerceList(criteria, strict=True) 171 allMatches = [ 172 self._getMatchesForCriterion(criterion, strategy) for criterion in criteria 173 ] 174 result = helpers.intersect(allMatches) 175 176 return DFontList(self._toRichFonts(result)) 177 178 def getRandom( 179 self, 180 criteria: libFonts.FontType | list[libFonts.FontType], 181 count=1, 182 strategy: helpers.Strategy = "pick", 183 ) -> KFont | DFontList: 184 """ 185 Get a random font or fonts matching the given criteria. 186 187 Args: 188 criteria: Font type or list of font types to match. 189 count: Number of random fonts to return. 190 strategy: Strategy for filtering fonts. 191 192 Returns: 193 A single KFont or a list of KFonts. 194 """ 195 result = self.filterFonts(criteria=criteria, strategy=strategy) 196 # -> str 197 if count == 1: 198 return random.choice(result) 199 # -> str[] 200 else: 201 return DFontList(random.sample(result, min(len(result), count))) 202 203 def getWeightPair(self, weightNumber: int) -> DFontList: 204 """ 205 Get a pair of fonts for a given weight number. 206 207 Example: 208 `4` => `[40, 45]` 209 210 Args: 211 weightNumber: The weight number to match. 212 213 Returns: 214 List of KFonts for the weight pair. 215 """ 216 result = [ 217 font 218 for font in self.fonts 219 if font.getWeightClass(numeric=True) == weightNumber 220 ] 221 222 return DFontList(result) 223 224 def getRange(self, *args, size: int = 3): 225 """ 226 Get a range of evenly spaced fonts matching the given criteria. 227 228 Args: 229 *args: Criteria for selecting fonts. See `KFamily.get()`. 230 size: Number of fonts to return. 231 232 Example: 233 `'upright', 3` => `[10, 50, 90]` 234 235 Returns: 236 List of KFonts spaced evenly across the selection. 237 """ 238 result = self.get(*args) 239 if isinstance(result, KFont): 240 return DFontList([result]) 241 return DFontList(helpers.spaceEvenly(result, size=size)) 242 243 def getExtremes(self, *args) -> DFontList: 244 """ 245 Get the extreme fonts (e.g., lightest and boldest) matching the criteria. 246 247 Args: 248 *args: Criteria for selecting fonts. See `KFamily.get()`. 249 250 Example: 251 - `'upright'` => `[10, 80]` 252 - `'text'` => `[40, 60]` 253 254 Returns: 255 List of two KFonts representing the extremes. 256 """ 257 return self.getRange(*args, size=2) 258 259 def activate(self) -> "KFamily": 260 """ 261 Activate the first font in the family for use in DrawBot. Useful before `omitMissing()`. 262 263 Returns: 264 The KFamily instance (self). 265 """ 266 self.fonts[0].activate() 267 return self 268 269 # Internals 270 def _getWeightClasses(self): 271 """ 272 Get all unique weight classes in the family. 273 274 Example: 275 `[1, 2, 3, 4, ... ]` 276 277 Returns: 278 Sorted list of unique weight classes. 279 """ 280 return sorted(set([item.getWeightClass() for item in self.fonts])) 281 282 def _toRichFonts(self, fonts: list[str]) -> list[KFont]: 283 """Convert a list of font paths to KFont instances.""" 284 return [KFont(font, self) if isinstance(font, str) else font for font in fonts] 285 286 def _normalizeStyleNumber(self, criterion: int) -> int: 287 """ 288 Normalize style numbers to family scalar length. 289 290 Examples: 291 `5000` => `500` 292 `55` => `550` for scalar 3 293 """ 294 if len(str(criterion)) > self.scalar: 295 # Scale down: 5000 => 500 296 scaled = str(criterion)[: self.scalar] 297 else: 298 # Scale up: 55 => 550 299 scaled = f"{criterion:0<{self.scalar}}" 300 return int(scaled) 301 302 def _findFontPath(self, criterion: int | str) -> str | None: 303 """Resolve a single style number or style name to a font path.""" 304 if isinstance(criterion, int): 305 return libFonts.findFontByNumber( 306 self.styles, self._normalizeStyleNumber(criterion) 307 ) 308 309 if isinstance(criterion, str) and criterion.strip().isnumeric(): 310 numeric = int(criterion.strip()) 311 return libFonts.findFontByNumber( 312 self.styles, self._normalizeStyleNumber(numeric) 313 ) 314 315 # Match by style name (case-insensitive) 316 if isinstance(criterion, str): 317 target = criterion.casefold().strip() 318 for font in self.fonts: 319 if font.styleName.casefold() == target: 320 return font.path 321 322 logger.warning( 323 f"[KFamily._findFontPath] Could not resolve criterion: {criterion!r}" 324 ) 325 return None 326 327 def _getMatchesForCriterion( 328 self, 329 criterion: int | str | tuple[int, int], 330 strategy: helpers.Strategy, 331 ) -> list[str]: 332 """Resolve one criterion to a list of matching plain font paths.""" 333 if isinstance(criterion, tuple): 334 return [ 335 font.path 336 for font in self.fonts 337 if helpers.isInRange(*criterion, font.getWeightClass(numeric=True)) 338 ] 339 340 # ! Prefer matching by FontType if criterion is a valid FontType value 341 if isinstance(criterion, str) and criterion.casefold().strip() in get_args( 342 libFonts.FontType 343 ): 344 return libFonts.filterFonts(self.styles, criterion, strategy=strategy) 345 346 resolved = self._findFontPath(criterion) 347 if resolved: 348 return [resolved] 349 350 return libFonts.filterFonts(self.styles, criterion, strategy=strategy)
Represents a family of fonts and provides methods for querying and manipulating font collections.
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 = DFontList([KFont(path, self) for path in self.styles])
Initialize a KFamily instance.
Arguments:
- fontFolder: File system path to the folder containing font files.
List-like collection of KFonts (datatypes.data_fontlist.DFontList).
49 @property 50 def familyName(self) -> str: 51 """Returns the family name as a string.""" 52 return self.fonts[0].familyName
Returns the family name as a string.
54 @property 55 def familyAbbreviation(self) -> str: 56 """Returns the family abbreviation, formed by the first letter of each word in the family name. 57 58 Example: 59 `Stabil Grotesk` => `SG` 60 """ 61 return "".join([word[0] for word in self.familyName.split(" ")])
Returns the family abbreviation, formed by the first letter of each word in the family name.
Example:
Stabil Grotesk=>SG
63 @property 64 def familyCount(self) -> int: 65 """Returns the number of fonts in the family.""" 66 return len(self.fonts)
Returns the number of fonts in the family.
68 @property 69 def familyNoun(self) -> str: 70 """Returns 'font' or 'fonts' depending on the count.""" 71 return "fonts" if self.familyCount > 1 else "font"
Returns 'font' or 'fonts' depending on the count.
73 @property 74 def version(self) -> str: 75 """Returns the version of the family, derived from the first font.""" 76 return libFonts.getFontVersion(self.fonts[0].path)
Returns the version of the family, derived from the first font.
88 def get( 89 self, 90 criteria: list[int | str | tuple[int, int]] | int | str | tuple[int, int], 91 strategy: helpers.Strategy = "pick", 92 unwrapSingle: bool = True, 93 ) -> KFont | DFontList: 94 """ 95 Get fonts matching the given criteria. 96 97 Args: 98 criteria: Style number(s), style name(s), weight-class range tuple(s), 99 or `lib.fonts.FontType` criterion/criteria. 100 strategy: Strategy for filtering fonts. 101 unwrapSingle: Whether to return a single KFont if only one match is found. 102 103 Example: 104 - `[50, 70]` => get 2 fonts 105 - `["upright", "display"]` => get intersection of types 106 107 Returns: 108 A single KFont or a list of KFonts matching the criteria. 109 """ 110 111 if isinstance(criteria, list): 112 logger.warning( 113 "Using `KFamily.get()` with multiple criteria is deprecated. Use `findFonts()` for naming criteria and `filterFonts()` for genre criteria instead." 114 ) 115 116 criteria = helpers.coerceList(criteria, strict=True) 117 # Numeric criteria are resolved position-by-position. 118 # Mixed/text criteria are combined as an intersection. 119 isAllNumeric = all([isinstance(criterion, int) for criterion in criteria]) 120 if isAllNumeric: 121 result = self.findFonts(criteria) 122 else: 123 result = self.filterFonts(criteria, strategy=strategy) 124 125 result = DFontList([item for item in result if item is not None]) 126 127 # Return list only for multiple fonts 128 if unwrapSingle and isinstance(result, (list, DFontList)) and len(result) == 1: 129 return result[0] 130 131 return result
Get fonts matching the given criteria.
Arguments:
- criteria: Style number(s), style name(s), weight-class range tuple(s),
or
lib.fonts.FontTypecriterion/criteria. - strategy: Strategy for filtering fonts.
- unwrapSingle: Whether to return a single KFont if only one match is found.
Example:
[50, 70]=> get 2 fonts["upright", "display"]=> get intersection of types
Returns:
A single KFont or a list of KFonts matching the criteria.
133 def findFonts( 134 self, 135 criteria: list[int | str], 136 ) -> DFontList[KFont]: 137 """ 138 Resolve criteria one-by-one to fonts, preserving input length and order. 139 140 Accepts style numbers (int or numeric string) and style names. 141 142 Args: 143 criteria: List of style numbers or style names. 144 145 Returns: 146 List of resolved fonts in the same order and length as criteria. 147 """ 148 criteria = helpers.coerceList(criteria, strict=True) 149 resolved = [self._findFontPath(criterion) for criterion in criteria] 150 151 return DFontList([KFont(path, self) for path in resolved if path is not None])
Resolve criteria one-by-one to fonts, preserving input length and order.
Accepts style numbers (int or numeric string) and style names.
Arguments:
- criteria: List of style numbers or style names.
Returns:
List of resolved fonts in the same order and length as criteria.
153 def filterFonts( 154 self, 155 criteria: list[int | str | tuple[int, int]] | libFonts.FontType, 156 strategy: helpers.Strategy = "pick", 157 ) -> DFontList[KFont]: 158 """ 159 Combine criteria and return fonts that satisfy their intersection. 160 161 Args: 162 criteria: List of criteria to combine. Supports style numbers, 163 style names, style-number ranges `(start, end)` by weight class, 164 and `lib.fonts.FontType` values. 165 strategy: Filtering strategy for `lib.fonts.FontType` criteria. 166 167 Returns: 168 Intersected list of matching fonts. 169 """ 170 criteria = helpers.coerceList(criteria, strict=True) 171 allMatches = [ 172 self._getMatchesForCriterion(criterion, strategy) for criterion in criteria 173 ] 174 result = helpers.intersect(allMatches) 175 176 return DFontList(self._toRichFonts(result))
Combine criteria and return fonts that satisfy their intersection.
Arguments:
- criteria: List of criteria to combine. Supports style numbers,
style names, style-number ranges
(start, end)by weight class, andlib.fonts.FontTypevalues. - strategy: Filtering strategy for
lib.fonts.FontTypecriteria.
Returns:
Intersected list of matching fonts.
178 def getRandom( 179 self, 180 criteria: libFonts.FontType | list[libFonts.FontType], 181 count=1, 182 strategy: helpers.Strategy = "pick", 183 ) -> KFont | DFontList: 184 """ 185 Get a random font or fonts matching the given criteria. 186 187 Args: 188 criteria: Font type or list of font types to match. 189 count: Number of random fonts to return. 190 strategy: Strategy for filtering fonts. 191 192 Returns: 193 A single KFont or a list of KFonts. 194 """ 195 result = self.filterFonts(criteria=criteria, strategy=strategy) 196 # -> str 197 if count == 1: 198 return random.choice(result) 199 # -> str[] 200 else: 201 return DFontList(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.
203 def getWeightPair(self, weightNumber: int) -> DFontList: 204 """ 205 Get a pair of fonts for a given weight number. 206 207 Example: 208 `4` => `[40, 45]` 209 210 Args: 211 weightNumber: The weight number to match. 212 213 Returns: 214 List of KFonts for the weight pair. 215 """ 216 result = [ 217 font 218 for font in self.fonts 219 if font.getWeightClass(numeric=True) == weightNumber 220 ] 221 222 return DFontList(result)
Get a pair of fonts for a given weight number.
Example:
4=>[40, 45]
Arguments:
- weightNumber: The weight number to match.
Returns:
List of KFonts for the weight pair.
224 def getRange(self, *args, size: int = 3): 225 """ 226 Get a range of evenly spaced fonts matching the given criteria. 227 228 Args: 229 *args: Criteria for selecting fonts. See `KFamily.get()`. 230 size: Number of fonts to return. 231 232 Example: 233 `'upright', 3` => `[10, 50, 90]` 234 235 Returns: 236 List of KFonts spaced evenly across the selection. 237 """ 238 result = self.get(*args) 239 if isinstance(result, KFont): 240 return DFontList([result]) 241 return DFontList(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.
243 def getExtremes(self, *args) -> DFontList: 244 """ 245 Get the extreme fonts (e.g., lightest and boldest) matching the criteria. 246 247 Args: 248 *args: Criteria for selecting fonts. See `KFamily.get()`. 249 250 Example: 251 - `'upright'` => `[10, 80]` 252 - `'text'` => `[40, 60]` 253 254 Returns: 255 List of two KFonts representing the extremes. 256 """ 257 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.
259 def activate(self) -> "KFamily": 260 """ 261 Activate the first font in the family for use in DrawBot. Useful before `omitMissing()`. 262 263 Returns: 264 The KFamily instance (self). 265 """ 266 self.fonts[0].activate() 267 return self
Activate the first font in the family for use in DrawBot. Useful before omitMissing().
Returns:
The KFamily instance (self).