datatypes.data_fontprops
1from typing import List, Literal 2from drawBot import _drawBotDrawingTool as drawBot 3from dataclasses import dataclass, asdict 4from functools import cached_property 5 6from lib import helpers, fonts 7from classes import KFont 8 9# TODO: Add RGB/CMYK fill property 10 11 12# NamedTuple doesn’t allow custom init or internal properties 13# Dataclass to allow cached_property 14@dataclass 15class DFontProps: 16 """Data structure for font properties used in DrawBot.""" 17 18 fontSize: int = 24 19 """The font size in points.""" 20 21 leading: float = 1.25 22 """The leading (line spacing) multiplier.""" 23 24 letterSpacing: int = 0 25 """Relative letter spacing unit, behaves similar to Adobe.""" 26 27 fontData: KFont | list[KFont] = None 28 """The font data or list of font data.""" 29 30 align: str = None 31 """Text alignment option.""" 32 33 openType: fonts.FontFeature | list[fonts.FontFeature] = None 34 """OpenType font features.""" 35 36 @property 37 def lineHeight(self): 38 """The calculated line height. 39 40 Returns: 41 The product of fontSize and leading, or None if fontSize undefined. 42 """ 43 try: 44 return self.fontSize * self.leading 45 except Exception: 46 return 47 48 @cached_property 49 def blockHeight(self) -> float: 50 """The height of one line of text.""" 51 with drawBot.savedState(): 52 self.apply() 53 _, h = drawBot.textSize("H") 54 return h 55 56 @property 57 def tracking(self): 58 """The absolute tracking value, scales with fontSize. 59 60 Returns: 61 The calculated tracking value, or None if calculation fails. 62 """ 63 try: 64 return self.letterSpacing * (self.fontSize / 1000) 65 except Exception: 66 return 67 68 @property 69 def fontPath(self): 70 """The file path of the font. 71 72 Returns: 73 The path to the font file, or the fontData if path is unavailable. 74 """ 75 try: 76 return self.fontData.path 77 except Exception: 78 return self.fontData 79 80 @property 81 def isSingleFont(self) -> bool: 82 """Whether fontData is a single font. 83 84 Returns: 85 True if fontData is a string or KFont instance, False otherwise. 86 """ 87 return isinstance(self.fontData, (str, KFont)) 88 89 @property 90 def openTypeFeatures(self): 91 """OpenType features as a dictionary. 92 93 Returns: 94 Dictionary of OpenType features set to True, or None if unavailable. 95 """ 96 try: 97 return {key: True for key in helpers.coerceList(self.openType)} 98 except Exception: 99 pass 100 101 def getArgs(self, clean=False, pick: list[str] = None) -> dict: 102 """ 103 Get properties as a dictionary. 104 105 Args: 106 clean: If True, remove falsy properties. 107 pick: List of property keys to include. 108 109 Returns: 110 Dictionary of font properties. 111 """ 112 asDict = asdict(self) 113 asDict["lineHeight"] = self.lineHeight 114 asDict["tracking"] = self.tracking 115 asDict["openTypeFeatures"] = self.openTypeFeatures 116 117 if self.align: 118 asDict["align"] = self.align 119 120 if self.isSingleFont: 121 asDict["font"] = self.fontPath 122 123 if pick: 124 asDict = helpers.pick(asDict, pick) 125 126 if clean: 127 return helpers.omitBy(asDict) 128 else: 129 return asDict 130 131 def calc(self, clean=True) -> dict: 132 """ 133 Calculate keyword arguments for FormattedString(). 134 135 Args: 136 clean: If True, remove falsy properties. 137 138 Returns: 139 Dictionary of keyword arguments for FormattedString(). 140 """ 141 142 return self.getArgs( 143 clean=clean, 144 pick=[ 145 "font", 146 "fontSize", 147 "lineHeight", 148 "tracking", 149 "align", 150 "openTypeFeatures", 151 ], 152 ) 153 154 def activateFont(self): 155 """ 156 Set the font for DrawBot. 157 158 Returns: 159 The current instance. 160 """ 161 if self.isSingleFont: 162 drawBot.font(self.fontPath) 163 return self 164 165 def apply(self): 166 """ 167 Set DrawBot properties for the current font settings. 168 169 Returns: 170 The current instance. 171 """ 172 drawBot.fontSize(self.fontSize) 173 drawBot.lineHeight(self.lineHeight) 174 drawBot.tracking(self.tracking) 175 176 self.activateFont() 177 178 if self.openTypeFeatures: 179 drawBot.openTypeFeatures(**self.openTypeFeatures) 180 else: 181 drawBot.openTypeFeatures(resetFeatures=True) 182 183 return self 184 185 def describe(self, format: Literal["string", "list"] = "string"): 186 """ 187 Return human-friendly font properties. 188 189 Args: 190 format: Return as 'str' or 'list[str]'. 191 192 Returns: 193 Human-readable font properties. 194 """ 195 196 props: List[float | int] = [self.fontSize] 197 lh, t = self.lineHeight, self.letterSpacing 198 199 if lh and lh != self.fontSize: 200 props.append(lh) 201 if t: 202 props.append(t) 203 204 rounded = [str(round(p)) for p in props] 205 return rounded if format == "list" else " ".join(rounded) 206 207 def updateFont(self, value: KFont) -> "DFontProps": 208 """ 209 Update font and return self. 210 211 Args: 212 value: The new font. 213 214 Example: 215 `props.updateFont(newFont)` 216 """ 217 self.fontData = value 218 return self 219 220 def updateFontSize(self, value: int) -> "DFontProps": 221 """ 222 Update fontSize and return self. 223 224 Args: 225 value: The new font size. 226 227 Example: 228 `props.updateFontSize(12)` 229 """ 230 self.fontSize = value 231 return self
15@dataclass 16class DFontProps: 17 """Data structure for font properties used in DrawBot.""" 18 19 fontSize: int = 24 20 """The font size in points.""" 21 22 leading: float = 1.25 23 """The leading (line spacing) multiplier.""" 24 25 letterSpacing: int = 0 26 """Relative letter spacing unit, behaves similar to Adobe.""" 27 28 fontData: KFont | list[KFont] = None 29 """The font data or list of font data.""" 30 31 align: str = None 32 """Text alignment option.""" 33 34 openType: fonts.FontFeature | list[fonts.FontFeature] = None 35 """OpenType font features.""" 36 37 @property 38 def lineHeight(self): 39 """The calculated line height. 40 41 Returns: 42 The product of fontSize and leading, or None if fontSize undefined. 43 """ 44 try: 45 return self.fontSize * self.leading 46 except Exception: 47 return 48 49 @cached_property 50 def blockHeight(self) -> float: 51 """The height of one line of text.""" 52 with drawBot.savedState(): 53 self.apply() 54 _, h = drawBot.textSize("H") 55 return h 56 57 @property 58 def tracking(self): 59 """The absolute tracking value, scales with fontSize. 60 61 Returns: 62 The calculated tracking value, or None if calculation fails. 63 """ 64 try: 65 return self.letterSpacing * (self.fontSize / 1000) 66 except Exception: 67 return 68 69 @property 70 def fontPath(self): 71 """The file path of the font. 72 73 Returns: 74 The path to the font file, or the fontData if path is unavailable. 75 """ 76 try: 77 return self.fontData.path 78 except Exception: 79 return self.fontData 80 81 @property 82 def isSingleFont(self) -> bool: 83 """Whether fontData is a single font. 84 85 Returns: 86 True if fontData is a string or KFont instance, False otherwise. 87 """ 88 return isinstance(self.fontData, (str, KFont)) 89 90 @property 91 def openTypeFeatures(self): 92 """OpenType features as a dictionary. 93 94 Returns: 95 Dictionary of OpenType features set to True, or None if unavailable. 96 """ 97 try: 98 return {key: True for key in helpers.coerceList(self.openType)} 99 except Exception: 100 pass 101 102 def getArgs(self, clean=False, pick: list[str] = None) -> dict: 103 """ 104 Get properties as a dictionary. 105 106 Args: 107 clean: If True, remove falsy properties. 108 pick: List of property keys to include. 109 110 Returns: 111 Dictionary of font properties. 112 """ 113 asDict = asdict(self) 114 asDict["lineHeight"] = self.lineHeight 115 asDict["tracking"] = self.tracking 116 asDict["openTypeFeatures"] = self.openTypeFeatures 117 118 if self.align: 119 asDict["align"] = self.align 120 121 if self.isSingleFont: 122 asDict["font"] = self.fontPath 123 124 if pick: 125 asDict = helpers.pick(asDict, pick) 126 127 if clean: 128 return helpers.omitBy(asDict) 129 else: 130 return asDict 131 132 def calc(self, clean=True) -> dict: 133 """ 134 Calculate keyword arguments for FormattedString(). 135 136 Args: 137 clean: If True, remove falsy properties. 138 139 Returns: 140 Dictionary of keyword arguments for FormattedString(). 141 """ 142 143 return self.getArgs( 144 clean=clean, 145 pick=[ 146 "font", 147 "fontSize", 148 "lineHeight", 149 "tracking", 150 "align", 151 "openTypeFeatures", 152 ], 153 ) 154 155 def activateFont(self): 156 """ 157 Set the font for DrawBot. 158 159 Returns: 160 The current instance. 161 """ 162 if self.isSingleFont: 163 drawBot.font(self.fontPath) 164 return self 165 166 def apply(self): 167 """ 168 Set DrawBot properties for the current font settings. 169 170 Returns: 171 The current instance. 172 """ 173 drawBot.fontSize(self.fontSize) 174 drawBot.lineHeight(self.lineHeight) 175 drawBot.tracking(self.tracking) 176 177 self.activateFont() 178 179 if self.openTypeFeatures: 180 drawBot.openTypeFeatures(**self.openTypeFeatures) 181 else: 182 drawBot.openTypeFeatures(resetFeatures=True) 183 184 return self 185 186 def describe(self, format: Literal["string", "list"] = "string"): 187 """ 188 Return human-friendly font properties. 189 190 Args: 191 format: Return as 'str' or 'list[str]'. 192 193 Returns: 194 Human-readable font properties. 195 """ 196 197 props: List[float | int] = [self.fontSize] 198 lh, t = self.lineHeight, self.letterSpacing 199 200 if lh and lh != self.fontSize: 201 props.append(lh) 202 if t: 203 props.append(t) 204 205 rounded = [str(round(p)) for p in props] 206 return rounded if format == "list" else " ".join(rounded) 207 208 def updateFont(self, value: KFont) -> "DFontProps": 209 """ 210 Update font and return self. 211 212 Args: 213 value: The new font. 214 215 Example: 216 `props.updateFont(newFont)` 217 """ 218 self.fontData = value 219 return self 220 221 def updateFontSize(self, value: int) -> "DFontProps": 222 """ 223 Update fontSize and return self. 224 225 Args: 226 value: The new font size. 227 228 Example: 229 `props.updateFontSize(12)` 230 """ 231 self.fontSize = value 232 return self
Data structure for font properties used in DrawBot.
OpenType font features.
37 @property 38 def lineHeight(self): 39 """The calculated line height. 40 41 Returns: 42 The product of fontSize and leading, or None if fontSize undefined. 43 """ 44 try: 45 return self.fontSize * self.leading 46 except Exception: 47 return
The calculated line height.
Returns:
The product of fontSize and leading, or None if fontSize undefined.
49 @cached_property 50 def blockHeight(self) -> float: 51 """The height of one line of text.""" 52 with drawBot.savedState(): 53 self.apply() 54 _, h = drawBot.textSize("H") 55 return h
The height of one line of text.
57 @property 58 def tracking(self): 59 """The absolute tracking value, scales with fontSize. 60 61 Returns: 62 The calculated tracking value, or None if calculation fails. 63 """ 64 try: 65 return self.letterSpacing * (self.fontSize / 1000) 66 except Exception: 67 return
The absolute tracking value, scales with fontSize.
Returns:
The calculated tracking value, or None if calculation fails.
69 @property 70 def fontPath(self): 71 """The file path of the font. 72 73 Returns: 74 The path to the font file, or the fontData if path is unavailable. 75 """ 76 try: 77 return self.fontData.path 78 except Exception: 79 return self.fontData
The file path of the font.
Returns:
The path to the font file, or the fontData if path is unavailable.
81 @property 82 def isSingleFont(self) -> bool: 83 """Whether fontData is a single font. 84 85 Returns: 86 True if fontData is a string or KFont instance, False otherwise. 87 """ 88 return isinstance(self.fontData, (str, KFont))
Whether fontData is a single font.
Returns:
True if fontData is a string or KFont instance, False otherwise.
90 @property 91 def openTypeFeatures(self): 92 """OpenType features as a dictionary. 93 94 Returns: 95 Dictionary of OpenType features set to True, or None if unavailable. 96 """ 97 try: 98 return {key: True for key in helpers.coerceList(self.openType)} 99 except Exception: 100 pass
OpenType features as a dictionary.
Returns:
Dictionary of OpenType features set to True, or None if unavailable.
102 def getArgs(self, clean=False, pick: list[str] = None) -> dict: 103 """ 104 Get properties as a dictionary. 105 106 Args: 107 clean: If True, remove falsy properties. 108 pick: List of property keys to include. 109 110 Returns: 111 Dictionary of font properties. 112 """ 113 asDict = asdict(self) 114 asDict["lineHeight"] = self.lineHeight 115 asDict["tracking"] = self.tracking 116 asDict["openTypeFeatures"] = self.openTypeFeatures 117 118 if self.align: 119 asDict["align"] = self.align 120 121 if self.isSingleFont: 122 asDict["font"] = self.fontPath 123 124 if pick: 125 asDict = helpers.pick(asDict, pick) 126 127 if clean: 128 return helpers.omitBy(asDict) 129 else: 130 return asDict
Get properties as a dictionary.
Arguments:
- clean: If True, remove falsy properties.
- pick: List of property keys to include.
Returns:
Dictionary of font properties.
132 def calc(self, clean=True) -> dict: 133 """ 134 Calculate keyword arguments for FormattedString(). 135 136 Args: 137 clean: If True, remove falsy properties. 138 139 Returns: 140 Dictionary of keyword arguments for FormattedString(). 141 """ 142 143 return self.getArgs( 144 clean=clean, 145 pick=[ 146 "font", 147 "fontSize", 148 "lineHeight", 149 "tracking", 150 "align", 151 "openTypeFeatures", 152 ], 153 )
Calculate keyword arguments for FormattedString().
Arguments:
- clean: If True, remove falsy properties.
Returns:
Dictionary of keyword arguments for FormattedString().
155 def activateFont(self): 156 """ 157 Set the font for DrawBot. 158 159 Returns: 160 The current instance. 161 """ 162 if self.isSingleFont: 163 drawBot.font(self.fontPath) 164 return self
Set the font for DrawBot.
Returns:
The current instance.
166 def apply(self): 167 """ 168 Set DrawBot properties for the current font settings. 169 170 Returns: 171 The current instance. 172 """ 173 drawBot.fontSize(self.fontSize) 174 drawBot.lineHeight(self.lineHeight) 175 drawBot.tracking(self.tracking) 176 177 self.activateFont() 178 179 if self.openTypeFeatures: 180 drawBot.openTypeFeatures(**self.openTypeFeatures) 181 else: 182 drawBot.openTypeFeatures(resetFeatures=True) 183 184 return self
Set DrawBot properties for the current font settings.
Returns:
The current instance.
186 def describe(self, format: Literal["string", "list"] = "string"): 187 """ 188 Return human-friendly font properties. 189 190 Args: 191 format: Return as 'str' or 'list[str]'. 192 193 Returns: 194 Human-readable font properties. 195 """ 196 197 props: List[float | int] = [self.fontSize] 198 lh, t = self.lineHeight, self.letterSpacing 199 200 if lh and lh != self.fontSize: 201 props.append(lh) 202 if t: 203 props.append(t) 204 205 rounded = [str(round(p)) for p in props] 206 return rounded if format == "list" else " ".join(rounded)
Return human-friendly font properties.
Arguments:
- format: Return as 'str' or 'list[str]'.
Returns:
Human-readable font properties.
208 def updateFont(self, value: KFont) -> "DFontProps": 209 """ 210 Update font and return self. 211 212 Args: 213 value: The new font. 214 215 Example: 216 `props.updateFont(newFont)` 217 """ 218 self.fontData = value 219 return self
Update font and return self.
Arguments:
- value: The new font.
Example:
props.updateFont(newFont)
221 def updateFontSize(self, value: int) -> "DFontProps": 222 """ 223 Update fontSize and return self. 224 225 Args: 226 value: The new font size. 227 228 Example: 229 `props.updateFontSize(12)` 230 """ 231 self.fontSize = value 232 return self
Update fontSize and return self.
Arguments:
- value: The new font size.
Example:
props.updateFontSize(12)