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
@dataclass
class DFontProps:
 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.

DFontProps( fontSize: int = 24, leading: float = 1.25, letterSpacing: int = 0, fontData: classes.c10_font.KFont | list[classes.c10_font.KFont] = None, align: str = None, openType: Union[Literal['calt', 'case', 'dlig', 'frac', 'liga', 'kern', 'lnum', 'onum', 'ordn', 'pnum', 'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'ss06', 'ss07', 'ss08', 'ss09', 'ss10', 'subs', 'sups', 'titl', 'tnum'], list[Literal['calt', 'case', 'dlig', 'frac', 'liga', 'kern', 'lnum', 'onum', 'ordn', 'pnum', 'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'ss06', 'ss07', 'ss08', 'ss09', 'ss10', 'subs', 'sups', 'titl', 'tnum']]] = None)
fontSize: int = 24

The font size in points.

leading: float = 1.25

The leading (line spacing) multiplier.

letterSpacing: int = 0

Relative letter spacing unit, behaves similar to Adobe.

The font data or list of font data.

align: str = None

Text alignment option.

openType: Union[Literal['calt', 'case', 'dlig', 'frac', 'liga', 'kern', 'lnum', 'onum', 'ordn', 'pnum', 'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'ss06', 'ss07', 'ss08', 'ss09', 'ss10', 'subs', 'sups', 'titl', 'tnum'], list[Literal['calt', 'case', 'dlig', 'frac', 'liga', 'kern', 'lnum', 'onum', 'ordn', 'pnum', 'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'ss06', 'ss07', 'ss08', 'ss09', 'ss10', 'subs', 'sups', 'titl', 'tnum']]] = None

OpenType font features.

lineHeight
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.

blockHeight: float
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.

tracking
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.

fontPath
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.

isSingleFont: bool
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.

openTypeFeatures
 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.

def getArgs(self, clean=False, pick: list[str] = None) -> dict:
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.

def calc(self, clean=True) -> dict:
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().

def activateFont(self):
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.

def apply(self):
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.

def describe(self, format: Literal['string', 'list'] = 'string'):
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.

def updateFont( self, value: classes.c10_font.KFont) -> DFontProps:
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)

def updateFontSize(self, value: int) -> DFontProps:
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)