From 786656a8466f2edb6e5740796a4bf2e2f5aab551 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Fri, 6 Dec 2024 08:47:26 +0100 Subject: [PATCH 01/14] Redefined variables for type security. --- Lib/fontParts/base/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 0b9252cd..4ca0c300 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -7,9 +7,9 @@ class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): from fontTools.ufoLib import fontInfoAttributesVersion3 - copyAttributes = set(fontInfoAttributesVersion3) - copyAttributes.remove("guidelines") - copyAttributes = tuple(copyAttributes) + fontInfoAttributes = set(fontInfoAttributesVersion3) + fontInfoAttributes.remove("guidelines") + copyAttributes = tuple(fontInfoAttributes) def _reprContents(self): contents = [] From 52842803039affefdabf6e3f6b8ebbf2b0cea469 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Sun, 8 Dec 2024 00:51:20 +0100 Subject: [PATCH 02/14] Add type annotations. --- Lib/fontParts/base/info.py | 134 +++++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 4ca0c300..0960569d 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -1,17 +1,45 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, Any, Callable, Generic, List, Optional, Tuple, Type, Union + +from fontTools.ufoLib import fontInfoAttributesVersion3 +from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute +from fontMath import MathInfo +from fontMath.mathFunctions import setRoundIntegerFunction + from fontParts.base.base import BaseObject, dynamicProperty, interpolate, reference from fontParts.base import normalizers from fontParts.base.errors import FontPartsError from fontParts.base.deprecated import DeprecatedInfo, RemovedInfo +from fontParts.base.annotations import ( + CharacterMappingType, + CollectionType, + QuadrupleCollectionType, + TransformationType, + ReverseComponentMappingType, + IntFloatType, +) + +if TYPE_CHECKING: + from fontParts.base.font import BaseFont + from fontParts.base.glyph import BaseGlyph + from fontParts.base.lib import BaseLib + + +# Notes +# What are the available types for the `value` parameter in `_validateFontInfoAttributeValue`? +# Where are the magic methods in this class defined? `mypy` throws this error: info.py:107: error: "__hasattr__" undefined in superclass [misc] +# What value types is `BaseInfo.__setattr__` supposed to return? `mypy` throws this error: info.py:113: error: "_getAttr" of "BaseInfo" does not return a value (it only ever returns None) [func-returns-value] +# Isn't `_fromMathInfo` supposed to return anything? It's missing a return statement, yet its called in the return of the public equivalent. class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): - from fontTools.ufoLib import fontInfoAttributesVersion3 + """Represent the basis for an info object.""" fontInfoAttributes = set(fontInfoAttributesVersion3) fontInfoAttributes.remove("guidelines") copyAttributes = tuple(fontInfoAttributes) - def _reprContents(self): + def _reprContents(self) -> List[str]: contents = [] if self.font is not None: contents.append("for font") @@ -24,16 +52,32 @@ def _reprContents(self): # Font - _font = None + _font: Optional[Callable[[], BaseFont]] = None + + font: dynamicProperty = dynamicProperty( + "font", + """Get or set the info's parent font object. + + The value must be a :class:`BaseFont` instance or :obj:`None`. + + :return: The :class:`BaseFont` instance containing the info + or :obj:`None`. + :raises AssertionError: If attempting to set the font when it + has already been set. + + Example:: - font = dynamicProperty("font", "The info's parent font.") + >>> font = info.font - def _get_font(self): + """, + ) + + def _get_font(self) -> Optional[BaseFont]: if self._font is None: return None return self._font() - def _set_font(self, font): + def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: if self._font is not None and self._font != font: raise AssertionError("font for info already set and is not same as font") if font is not None: @@ -45,9 +89,7 @@ def _set_font(self, font): # ---------- @staticmethod - def _validateFontInfoAttributeValue(attr, value): - from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute - + def _validateFontInfoAttributeValue(attr: str, value: Any): valid = validateFontInfoVersion3ValueForAttribute(attr, value) if not valid: raise ValueError(f"Invalid value {value} for attribute '{attr}'.") @@ -59,18 +101,14 @@ def _validateFontInfoAttributeValue(attr, value): # has - def __hasattr__(self, attr): - from fontTools.ufoLib import fontInfoAttributesVersion3 - + def __hasattr__(self, attr: str) -> bool: if attr in fontInfoAttributesVersion3: return True return super(BaseInfo, self).__hasattr__(attr) # get - def __getattribute__(self, attr): - from fontTools.ufoLib import fontInfoAttributesVersion3 - + def __getattribute__(self, attr: str) -> None: if attr != "guidelines" and attr in fontInfoAttributesVersion3: value = self._getAttr(attr) if value is not None: @@ -78,7 +116,7 @@ def __getattribute__(self, attr): return value return super(BaseInfo, self).__getattribute__(attr) - def _getAttr(self, attr): + def _getAttr(self, attr: str) -> None: """ Subclasses may override this method. @@ -86,25 +124,23 @@ def _getAttr(self, attr): it must implement '_get_attributeName' methods for all Info methods. """ - meth = f"_get_{attr}" - if not hasattr(self, meth): + methodName = f"_get_{attr}" + if not hasattr(self, methodName): raise AttributeError(f"No getter for attribute '{attr}'.") - meth = getattr(self, meth) - value = meth() + method = getattr(self, methodName) + value = method() return value # set - def __setattr__(self, attr, value): - from fontTools.ufoLib import fontInfoAttributesVersion3 - + def __setattr__(self, attr: str, value: Any) -> None: if attr != "guidelines" and attr in fontInfoAttributesVersion3: if value is not None: value = self._validateFontInfoAttributeValue(attr, value) return self._setAttr(attr, value) return super(BaseInfo, self).__setattr__(attr, value) - def _setAttr(self, attr, value): + def _setAttr(self, attr: str, value: Any) -> None: """ Subclasses may override this method. @@ -112,17 +148,17 @@ def _setAttr(self, attr, value): it must implement '_set_attributeName' methods for all Info methods. """ - meth = f"_set_{attr}" - if not hasattr(self, meth): + methodName = f"_set_{attr}" + if not hasattr(self, methodName): raise AttributeError(f"No setter for attribute '{attr}'.") - meth = getattr(self, meth) - meth(value) + method = getattr(self, methodName) + method(value) # ------------- # Normalization # ------------- - def round(self): + def round(self) -> None: """ Round the following attributes to integers: @@ -177,12 +213,10 @@ def round(self): """ self._round() - def _round(self, **kwargs): + def _round(self, **kwargs: Any) -> None: """ Subclasses may override this method. """ - from fontMath.mathFunctions import setRoundIntegerFunction - setRoundIntegerFunction(normalizers.normalizeVisualRounding) mathInfo = self._toMathInfo(guidelines=False) @@ -193,14 +227,14 @@ def _round(self, **kwargs): # Updating # -------- - def update(self, other): + def update(self, other: BaseInfo) -> None: """ Update this object with the values from **otherInfo**. """ self._update(other) - def _update(self, other): + def _update(self, other: BaseInfo) -> None: """ Subclasses may override this method. """ @@ -216,7 +250,7 @@ def _update(self, other): # Interpolation # ------------- - def toMathInfo(self, guidelines=True): + def toMathInfo(self, guidelines=True) -> MathInfo: """ Returns the info as an object that follows the `MathGlyph protocol `_. @@ -225,7 +259,7 @@ def toMathInfo(self, guidelines=True): """ return self._toMathInfo(guidelines=guidelines) - def fromMathInfo(self, mathInfo, guidelines=True): + def fromMathInfo(self, mathInfo, guidelines=True) -> BaseInfo: """ Replaces the contents of this info object with the contents of ``mathInfo``. @@ -236,11 +270,10 @@ def fromMathInfo(self, mathInfo, guidelines=True): """ return self._fromMathInfo(mathInfo, guidelines=guidelines) - def _toMathInfo(self, guidelines=True): + def _toMathInfo(self, guidelines=True) -> MathInfo: """ Subclasses may override this method. """ - import fontMath # A little trickery is needed here because MathInfo # handles font level guidelines. Those are not in this @@ -258,11 +291,11 @@ def _toMathInfo(self, guidelines=True): color=guideline.color, ) self.guidelines.append(d) - info = fontMath.MathInfo(self) + info = MathInfo(self) del self.guidelines return info - def _fromMathInfo(self, mathInfo, guidelines=True): + def _fromMathInfo(self, mathInfo, guidelines=True) -> None: """ Subclasses may override this method. """ @@ -278,7 +311,14 @@ def _fromMathInfo(self, mathInfo, guidelines=True): # XXX identifier is lost ) - def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): + def interpolate( + self, + factor: TransformationType, + minInfo: BaseInfo, + maxInfo: BaseInfo, + round: bool = True, + suppressError: bool = True + ) -> None: """ Interpolate all pairs between minInfo and maxInfo. The interpolation occurs on a 0 to 1.0 range where minInfo @@ -310,11 +350,17 @@ def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): factor, minInfo, maxInfo, round=round, suppressError=suppressError ) - def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): + def _interpolate( + self, + factor: TransformationType, + minInfo: BaseInfo, + maxInfo: BaseInfo, + round: bool = True, + suppressError: bool = True + ) -> None: """ Subclasses may override this method. """ - from fontMath.mathFunctions import setRoundIntegerFunction setRoundIntegerFunction(normalizers.normalizeVisualRounding) @@ -326,5 +372,5 @@ def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True) f"Info from font '{minInfo.font.name}' and font '{maxInfo.font.name}' could not be interpolated." ) if round: - result = result.round() + result = result.round() # type: ignore[func-returns-value] self._fromMathInfo(result) From 3b78b9e9012e4fc4d58c63506ed8c7605d9c6c41 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Sat, 7 Dec 2024 23:51:45 +0000 Subject: [PATCH 03/14] Format fixes by ruff --- Lib/fontParts/base/info.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 0960569d..e9be1ee8 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -1,5 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Generic, List, Optional, Tuple, Type, Union +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Generic, + List, + Optional, + Tuple, + Type, + Union, +) from fontTools.ufoLib import fontInfoAttributesVersion3 from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute @@ -32,6 +42,7 @@ # What value types is `BaseInfo.__setattr__` supposed to return? `mypy` throws this error: info.py:113: error: "_getAttr" of "BaseInfo" does not return a value (it only ever returns None) [func-returns-value] # Isn't `_fromMathInfo` supposed to return anything? It's missing a return statement, yet its called in the return of the public equivalent. + class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object.""" @@ -77,7 +88,9 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: + def _set_font( + self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] + ) -> None: if self._font is not None and self._font != font: raise AssertionError("font for info already set and is not same as font") if font is not None: @@ -317,7 +330,7 @@ def interpolate( minInfo: BaseInfo, maxInfo: BaseInfo, round: bool = True, - suppressError: bool = True + suppressError: bool = True, ) -> None: """ Interpolate all pairs between minInfo and maxInfo. @@ -356,7 +369,7 @@ def _interpolate( minInfo: BaseInfo, maxInfo: BaseInfo, round: bool = True, - suppressError: bool = True + suppressError: bool = True, ) -> None: """ Subclasses may override this method. From 64365af1ad5ceddc3012b832322388ae268c8af1 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Sun, 8 Dec 2024 01:14:13 +0100 Subject: [PATCH 04/14] Delete notes. --- Lib/fontParts/base/info.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 0960569d..fb349e69 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -27,11 +27,6 @@ # Notes -# What are the available types for the `value` parameter in `_validateFontInfoAttributeValue`? -# Where are the magic methods in this class defined? `mypy` throws this error: info.py:107: error: "__hasattr__" undefined in superclass [misc] -# What value types is `BaseInfo.__setattr__` supposed to return? `mypy` throws this error: info.py:113: error: "_getAttr" of "BaseInfo" does not return a value (it only ever returns None) [func-returns-value] -# Isn't `_fromMathInfo` supposed to return anything? It's missing a return statement, yet its called in the return of the public equivalent. - class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object.""" From 524abfa4af16dd17670dfd924fde0e74e60055ea Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Sun, 8 Dec 2024 01:24:14 +0100 Subject: [PATCH 05/14] Remove unused imports. --- Lib/fontParts/base/info.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 02b532c0..5a7ea754 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -3,11 +3,8 @@ TYPE_CHECKING, Any, Callable, - Generic, List, Optional, - Tuple, - Type, Union, ) @@ -21,23 +18,15 @@ from fontParts.base.errors import FontPartsError from fontParts.base.deprecated import DeprecatedInfo, RemovedInfo from fontParts.base.annotations import ( - CharacterMappingType, - CollectionType, - QuadrupleCollectionType, TransformationType, - ReverseComponentMappingType, - IntFloatType, ) if TYPE_CHECKING: from fontParts.base.font import BaseFont - from fontParts.base.glyph import BaseGlyph - from fontParts.base.lib import BaseLib # Notes - class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object.""" From 4a126494895a155e33cdbce6daa505f39fb04284 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Mon, 24 Feb 2025 15:26:49 +0100 Subject: [PATCH 06/14] Add type annotations to methods in BaseKerning class --- Lib/fontParts/base/kerning.py | 97 ++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index e85bedf3..271f6e7e 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -1,6 +1,19 @@ +# pylint: disable=C0103, C0114 +from __future__ import annotations +from typing import TYPE_CHECKING, Dict, Iterator, List, Optional +from collections.abc import MutableMapping + from fontParts.base.base import BaseDict, dynamicProperty, interpolate, reference from fontParts.base import normalizers from fontParts.base.deprecated import DeprecatedKerning, RemovedKerning +from fontParts.base.annotations import ( + IntFloatType, PairType, PairCollectionType, TransformationType + ) +if TYPE_CHECKING: + from fontParts.base.font import BaseFont + from fontParts.base.base import BaseItems + from fontParts.base.base import BaseKeys + from fontParts.base.base import BaseValues class BaseKerning(BaseDict, DeprecatedKerning, RemovedKerning): @@ -23,7 +36,7 @@ class BaseKerning(BaseDict, DeprecatedKerning, RemovedKerning): keyNormalizer = normalizers.normalizeKerningKey valueNormalizer = normalizers.normalizeKerningValue - def _reprContents(self): + def _reprContents(self) -> List[str]: contents = [] if self.font is not None: contents.append("for font") @@ -36,16 +49,16 @@ def _reprContents(self): # Font - _font = None + _font: Optional[BaseFont] = None - font = dynamicProperty("font", "The Kerning's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty("font", "The Kerning's parent :class:`BaseFont`.") - def _get_font(self): + def _get_font(self) -> Optional[BaseFont]: if self._font is None: return None return self._font() - def _set_font(self, font): + def _set_font(self, font: Optional[BaseFont]) -> None: if self._font is not None and self._font() != font: raise AssertionError("font for kerning already set and is not same as font") if font is not None: @@ -56,7 +69,7 @@ def _set_font(self, font): # Transformation # -------------- - def scaleBy(self, factor): + def scaleBy(self, factor: TransformationType) -> None: """ Scales all kerning values by **factor**. **factor** will be an :ref:`type-int-float`, ``tuple`` or ``list``. The first value of the @@ -68,7 +81,7 @@ def scaleBy(self, factor): factor = normalizers.normalizeTransformationScale(factor) self._scale(factor) - def _scale(self, factor): + def _scale(self, factor: PairType[float]) -> None: """ This is the environment implementation of :meth:`BaseKerning.scaleBy`. **factor** will be a ``tuple``. @@ -84,7 +97,7 @@ def _scale(self, factor): # Normalization # ------------- - def round(self, multiple=1): + def round(self, multiple: int = 1) -> None: """ Rounds the kerning values to increments of **multiple**, which will be an ``int``. @@ -97,7 +110,7 @@ def round(self, multiple=1): ) self._round(multiple) - def _round(self, multiple=1): + def _round(self, multiple: int = 1) -> None: """ This is the environment implementation of :meth:`BaseKerning.round`. **multiple** will be an ``int``. @@ -116,8 +129,13 @@ def _round(self, multiple=1): # ------------- def interpolate( - self, factor, minKerning, maxKerning, round=True, suppressError=True - ): + self, + factor: TransformationType, + minKerning: BaseKerning, + maxKerning: BaseKerning, + round: bool = True, + suppressError: bool = True + ) -> None: """ Interpolates all pairs between two :class:`BaseKerning` objects: @@ -155,8 +173,13 @@ def interpolate( ) def _interpolate( - self, factor, minKerning, maxKerning, round=True, suppressError=True - ): + self, + factor: PairType[float], + minKerning: BaseKerning, + maxKerning: BaseKerning, + round: bool = True, + suppressError: bool = True + ) -> None: """ This is the environment implementation of :meth:`BaseKerning.interpolate`. @@ -193,7 +216,11 @@ def _interpolate( result.extractKerning(self.font) @staticmethod - def _testKerningGroupCompatibility(minKerning, maxKerning, suppressError=False): + def _testKerningGroupCompatibility( + minKerning: BaseKerning, + maxKerning: BaseKerning, + suppressError: bool = False + ) -> bool: minGroups = minKerning.font.groups maxGroups = maxKerning.font.groups match = True @@ -221,7 +248,7 @@ def _testKerningGroupCompatibility(minKerning, maxKerning, suppressError=False): # RoboFab Compatibility # --------------------- - def remove(self, pair): + def remove(self, pair: PairType[str]) -> None: """ Removes a pair from the Kerning. **pair** will be a ``tuple`` of two :ref:`type-string`\s. @@ -230,7 +257,7 @@ def remove(self, pair): """ del self[pair] - def asDict(self, returnIntegers=True): + def asDict(self, returnIntegers: bool = True) -> Dict[str, int]: """ Return the Kerning as a ``dict``. @@ -245,7 +272,7 @@ def asDict(self, returnIntegers=True): # Inherited Functions # ------------------- - def __contains__(self, pair): + def __contains__(self, pair: PairType[str]) -> bool: """ Tests to see if a pair is in the Kerning. **pair** will be a ``tuple`` of two :ref:`type-string`\s. @@ -258,7 +285,7 @@ def __contains__(self, pair): """ return super(BaseKerning, self).__contains__(pair) - def __delitem__(self, pair): + def __delitem__(self, pair: PairType[str]) -> None: """ Removes **pair** from the Kerning. **pair** is a ``tuple`` of two :ref:`type-string`\s.:: @@ -267,7 +294,7 @@ def __delitem__(self, pair): """ super(BaseKerning, self).__delitem__(pair) - def __getitem__(self, pair): + def __getitem__(self, pair: PairType[str]) -> float: """ Returns the kerning value of the pair. **pair** is a ``tuple`` of two :ref:`type-string`\s. @@ -287,7 +314,7 @@ def __getitem__(self, pair): """ return super(BaseKerning, self).__getitem__(pair) - def __iter__(self): + def __iter__(self) -> Iterator[PairType[str]]: """ Iterates through the Kerning, giving the pair for each iteration. The order that the Kerning will iterate though is not fixed nor is it ordered.:: @@ -300,7 +327,7 @@ def __iter__(self): """ return super(BaseKerning, self).__iter__() - def __len__(self): + def __len__(self) -> int: """ Returns the number of pairs in Kerning as an ``int``.:: @@ -309,7 +336,7 @@ def __len__(self): """ return super(BaseKerning, self).__len__() - def __setitem__(self, pair, value): + def __setitem__(self, pair, value: IntFloatType) -> None: """ Sets the **pair** to the list of **value**. **pair** is the pair as a ``tuple`` of two :ref:`type-string`\s and **value** @@ -321,7 +348,7 @@ def __setitem__(self, pair, value): """ super(BaseKerning, self).__setitem__(pair, value) - def clear(self): + def clear(self) -> None: """ Removes all information from Kerning, resetting the Kerning to an empty dictionary. :: @@ -330,7 +357,9 @@ def clear(self): """ super(BaseKerning, self).clear() - def get(self, pair, default=None): + def get(self, + pair: PairType[str], + default: Optional[IntFloatType] = None) -> Optional[IntFloatType]: """ Returns the value for the kerning pair. **pair** is a ``tuple`` of two :ref:`type-string`\s, and the returned @@ -350,7 +379,9 @@ def get(self, pair, default=None): """ return super(BaseKerning, self).get(pair, default) - def find(self, pair, default=None): + def find(self, + pair: PairCollectionType[str], + default: Optional[IntFloatType] = None) -> Optional[IntFloatType]: """ Returns the value for the kerning pair - even if the pair only exists implicitly (one or both sides may be members of a kerning group). @@ -368,7 +399,9 @@ def find(self, pair, default=None): value = normalizers.normalizeKerningValue(value) return value - def _find(self, pair, default=None): + def _find(self, + pair: PairType[str], + default: Optional[IntFloatType] = None) -> Optional[IntFloatType]: """ This is the environment implementation of :attr:`BaseKerning.find`. This must return an @@ -380,7 +413,7 @@ def _find(self, pair, default=None): groups = font.groups return lookupKerningValue(pair, self, groups, fallback=default) - def items(self): + def items(self) -> BaseItems[PairType[str], IntFloatType]: """ Returns a list of ``tuple``\s of each pair and value. Pairs are a ``tuple`` of two :ref:`type-string`\s and values are :ref:`type-int-float`. @@ -392,7 +425,7 @@ def items(self): """ return super(BaseKerning, self).items() - def keys(self): + def keys(self)-> BaseKeys[PairType[str]]: """ Returns a ``list`` of all the pairs in Kerning. This list will be unordered.:: @@ -402,7 +435,9 @@ def keys(self): """ return super(BaseKerning, self).keys() - def pop(self, pair, default=None): + def pop(self, + pair: PairType[str], + default: Optional[IntFloatType] = None)-> Optional[IntFloatType]: """ Removes the **pair** from the Kerning and returns the value as an ``int``. If no pair is found, **default** is returned. **pair** is a @@ -417,7 +452,7 @@ def pop(self, pair, default=None): """ return super(BaseKerning, self).pop(pair, default) - def update(self, otherKerning): + def update(self, otherKerning: MutableMapping[PairType[str], IntFloatType]) -> None: """ Updates the Kerning based on **otherKerning**. **otherKerning** is a ``dict`` of kerning information. If a pair from **otherKerning** is in Kerning, the pair @@ -429,7 +464,7 @@ def update(self, otherKerning): """ super(BaseKerning, self).update(otherKerning) - def values(self): + def values(self)-> BaseValues[IntFloatType]: """ Returns a ``list`` of each pair's values, the values will be :ref:`type-int-float`\s. From f89e9dc1be3e81acb40fa575e179c021db47bdcb Mon Sep 17 00:00:00 2001 From: knutnergaard <15194233+knutnergaard@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:27:58 +0000 Subject: [PATCH 07/14] Format fixes by ruff --- Lib/fontParts/base/kerning.py | 48 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index 271f6e7e..eda3a73a 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -7,8 +7,12 @@ from fontParts.base import normalizers from fontParts.base.deprecated import DeprecatedKerning, RemovedKerning from fontParts.base.annotations import ( - IntFloatType, PairType, PairCollectionType, TransformationType - ) + IntFloatType, + PairType, + PairCollectionType, + TransformationType, +) + if TYPE_CHECKING: from fontParts.base.font import BaseFont from fontParts.base.base import BaseItems @@ -51,7 +55,9 @@ def _reprContents(self) -> List[str]: _font: Optional[BaseFont] = None - font: dynamicProperty = dynamicProperty("font", "The Kerning's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty( + "font", "The Kerning's parent :class:`BaseFont`." + ) def _get_font(self) -> Optional[BaseFont]: if self._font is None: @@ -134,7 +140,7 @@ def interpolate( minKerning: BaseKerning, maxKerning: BaseKerning, round: bool = True, - suppressError: bool = True + suppressError: bool = True, ) -> None: """ Interpolates all pairs between two :class:`BaseKerning` objects: @@ -178,7 +184,7 @@ def _interpolate( minKerning: BaseKerning, maxKerning: BaseKerning, round: bool = True, - suppressError: bool = True + suppressError: bool = True, ) -> None: """ This is the environment implementation of :meth:`BaseKerning.interpolate`. @@ -217,9 +223,7 @@ def _interpolate( @staticmethod def _testKerningGroupCompatibility( - minKerning: BaseKerning, - maxKerning: BaseKerning, - suppressError: bool = False + minKerning: BaseKerning, maxKerning: BaseKerning, suppressError: bool = False ) -> bool: minGroups = minKerning.font.groups maxGroups = maxKerning.font.groups @@ -357,9 +361,9 @@ def clear(self) -> None: """ super(BaseKerning, self).clear() - def get(self, - pair: PairType[str], - default: Optional[IntFloatType] = None) -> Optional[IntFloatType]: + def get( + self, pair: PairType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: """ Returns the value for the kerning pair. **pair** is a ``tuple`` of two :ref:`type-string`\s, and the returned @@ -379,9 +383,9 @@ def get(self, """ return super(BaseKerning, self).get(pair, default) - def find(self, - pair: PairCollectionType[str], - default: Optional[IntFloatType] = None) -> Optional[IntFloatType]: + def find( + self, pair: PairCollectionType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: """ Returns the value for the kerning pair - even if the pair only exists implicitly (one or both sides may be members of a kerning group). @@ -399,9 +403,9 @@ def find(self, value = normalizers.normalizeKerningValue(value) return value - def _find(self, - pair: PairType[str], - default: Optional[IntFloatType] = None) -> Optional[IntFloatType]: + def _find( + self, pair: PairType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: """ This is the environment implementation of :attr:`BaseKerning.find`. This must return an @@ -425,7 +429,7 @@ def items(self) -> BaseItems[PairType[str], IntFloatType]: """ return super(BaseKerning, self).items() - def keys(self)-> BaseKeys[PairType[str]]: + def keys(self) -> BaseKeys[PairType[str]]: """ Returns a ``list`` of all the pairs in Kerning. This list will be unordered.:: @@ -435,9 +439,9 @@ def keys(self)-> BaseKeys[PairType[str]]: """ return super(BaseKerning, self).keys() - def pop(self, - pair: PairType[str], - default: Optional[IntFloatType] = None)-> Optional[IntFloatType]: + def pop( + self, pair: PairType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: """ Removes the **pair** from the Kerning and returns the value as an ``int``. If no pair is found, **default** is returned. **pair** is a @@ -464,7 +468,7 @@ def update(self, otherKerning: MutableMapping[PairType[str], IntFloatType]) -> N """ super(BaseKerning, self).update(otherKerning) - def values(self)-> BaseValues[IntFloatType]: + def values(self) -> BaseValues[IntFloatType]: """ Returns a ``list`` of each pair's values, the values will be :ref:`type-int-float`\s. From 4254cb21d7631795ab12332ea861d127be3423c9 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Mon, 24 Feb 2025 18:29:16 +0100 Subject: [PATCH 08/14] Revert info.py to v1 --- Lib/fontParts/base/info.py | 131 +++++++++++++------------------------ 1 file changed, 44 insertions(+), 87 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 5a7ea754..4ca0c300 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -1,40 +1,17 @@ -from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - Callable, - List, - Optional, - Union, -) - -from fontTools.ufoLib import fontInfoAttributesVersion3 -from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute -from fontMath import MathInfo -from fontMath.mathFunctions import setRoundIntegerFunction - from fontParts.base.base import BaseObject, dynamicProperty, interpolate, reference from fontParts.base import normalizers from fontParts.base.errors import FontPartsError from fontParts.base.deprecated import DeprecatedInfo, RemovedInfo -from fontParts.base.annotations import ( - TransformationType, -) - -if TYPE_CHECKING: - from fontParts.base.font import BaseFont -# Notes - class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): - """Represent the basis for an info object.""" + from fontTools.ufoLib import fontInfoAttributesVersion3 fontInfoAttributes = set(fontInfoAttributesVersion3) fontInfoAttributes.remove("guidelines") copyAttributes = tuple(fontInfoAttributes) - def _reprContents(self) -> List[str]: + def _reprContents(self): contents = [] if self.font is not None: contents.append("for font") @@ -47,34 +24,16 @@ def _reprContents(self) -> List[str]: # Font - _font: Optional[Callable[[], BaseFont]] = None - - font: dynamicProperty = dynamicProperty( - "font", - """Get or set the info's parent font object. - - The value must be a :class:`BaseFont` instance or :obj:`None`. - - :return: The :class:`BaseFont` instance containing the info - or :obj:`None`. - :raises AssertionError: If attempting to set the font when it - has already been set. - - Example:: - - >>> font = info.font + _font = None - """, - ) + font = dynamicProperty("font", "The info's parent font.") - def _get_font(self) -> Optional[BaseFont]: + def _get_font(self): if self._font is None: return None return self._font() - def _set_font( - self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] - ) -> None: + def _set_font(self, font): if self._font is not None and self._font != font: raise AssertionError("font for info already set and is not same as font") if font is not None: @@ -86,7 +45,9 @@ def _set_font( # ---------- @staticmethod - def _validateFontInfoAttributeValue(attr: str, value: Any): + def _validateFontInfoAttributeValue(attr, value): + from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute + valid = validateFontInfoVersion3ValueForAttribute(attr, value) if not valid: raise ValueError(f"Invalid value {value} for attribute '{attr}'.") @@ -98,14 +59,18 @@ def _validateFontInfoAttributeValue(attr: str, value: Any): # has - def __hasattr__(self, attr: str) -> bool: + def __hasattr__(self, attr): + from fontTools.ufoLib import fontInfoAttributesVersion3 + if attr in fontInfoAttributesVersion3: return True return super(BaseInfo, self).__hasattr__(attr) # get - def __getattribute__(self, attr: str) -> None: + def __getattribute__(self, attr): + from fontTools.ufoLib import fontInfoAttributesVersion3 + if attr != "guidelines" and attr in fontInfoAttributesVersion3: value = self._getAttr(attr) if value is not None: @@ -113,7 +78,7 @@ def __getattribute__(self, attr: str) -> None: return value return super(BaseInfo, self).__getattribute__(attr) - def _getAttr(self, attr: str) -> None: + def _getAttr(self, attr): """ Subclasses may override this method. @@ -121,23 +86,25 @@ def _getAttr(self, attr: str) -> None: it must implement '_get_attributeName' methods for all Info methods. """ - methodName = f"_get_{attr}" - if not hasattr(self, methodName): + meth = f"_get_{attr}" + if not hasattr(self, meth): raise AttributeError(f"No getter for attribute '{attr}'.") - method = getattr(self, methodName) - value = method() + meth = getattr(self, meth) + value = meth() return value # set - def __setattr__(self, attr: str, value: Any) -> None: + def __setattr__(self, attr, value): + from fontTools.ufoLib import fontInfoAttributesVersion3 + if attr != "guidelines" and attr in fontInfoAttributesVersion3: if value is not None: value = self._validateFontInfoAttributeValue(attr, value) return self._setAttr(attr, value) return super(BaseInfo, self).__setattr__(attr, value) - def _setAttr(self, attr: str, value: Any) -> None: + def _setAttr(self, attr, value): """ Subclasses may override this method. @@ -145,17 +112,17 @@ def _setAttr(self, attr: str, value: Any) -> None: it must implement '_set_attributeName' methods for all Info methods. """ - methodName = f"_set_{attr}" - if not hasattr(self, methodName): + meth = f"_set_{attr}" + if not hasattr(self, meth): raise AttributeError(f"No setter for attribute '{attr}'.") - method = getattr(self, methodName) - method(value) + meth = getattr(self, meth) + meth(value) # ------------- # Normalization # ------------- - def round(self) -> None: + def round(self): """ Round the following attributes to integers: @@ -210,10 +177,12 @@ def round(self) -> None: """ self._round() - def _round(self, **kwargs: Any) -> None: + def _round(self, **kwargs): """ Subclasses may override this method. """ + from fontMath.mathFunctions import setRoundIntegerFunction + setRoundIntegerFunction(normalizers.normalizeVisualRounding) mathInfo = self._toMathInfo(guidelines=False) @@ -224,14 +193,14 @@ def _round(self, **kwargs: Any) -> None: # Updating # -------- - def update(self, other: BaseInfo) -> None: + def update(self, other): """ Update this object with the values from **otherInfo**. """ self._update(other) - def _update(self, other: BaseInfo) -> None: + def _update(self, other): """ Subclasses may override this method. """ @@ -247,7 +216,7 @@ def _update(self, other: BaseInfo) -> None: # Interpolation # ------------- - def toMathInfo(self, guidelines=True) -> MathInfo: + def toMathInfo(self, guidelines=True): """ Returns the info as an object that follows the `MathGlyph protocol `_. @@ -256,7 +225,7 @@ def toMathInfo(self, guidelines=True) -> MathInfo: """ return self._toMathInfo(guidelines=guidelines) - def fromMathInfo(self, mathInfo, guidelines=True) -> BaseInfo: + def fromMathInfo(self, mathInfo, guidelines=True): """ Replaces the contents of this info object with the contents of ``mathInfo``. @@ -267,10 +236,11 @@ def fromMathInfo(self, mathInfo, guidelines=True) -> BaseInfo: """ return self._fromMathInfo(mathInfo, guidelines=guidelines) - def _toMathInfo(self, guidelines=True) -> MathInfo: + def _toMathInfo(self, guidelines=True): """ Subclasses may override this method. """ + import fontMath # A little trickery is needed here because MathInfo # handles font level guidelines. Those are not in this @@ -288,11 +258,11 @@ def _toMathInfo(self, guidelines=True) -> MathInfo: color=guideline.color, ) self.guidelines.append(d) - info = MathInfo(self) + info = fontMath.MathInfo(self) del self.guidelines return info - def _fromMathInfo(self, mathInfo, guidelines=True) -> None: + def _fromMathInfo(self, mathInfo, guidelines=True): """ Subclasses may override this method. """ @@ -308,14 +278,7 @@ def _fromMathInfo(self, mathInfo, guidelines=True) -> None: # XXX identifier is lost ) - def interpolate( - self, - factor: TransformationType, - minInfo: BaseInfo, - maxInfo: BaseInfo, - round: bool = True, - suppressError: bool = True, - ) -> None: + def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Interpolate all pairs between minInfo and maxInfo. The interpolation occurs on a 0 to 1.0 range where minInfo @@ -347,17 +310,11 @@ def interpolate( factor, minInfo, maxInfo, round=round, suppressError=suppressError ) - def _interpolate( - self, - factor: TransformationType, - minInfo: BaseInfo, - maxInfo: BaseInfo, - round: bool = True, - suppressError: bool = True, - ) -> None: + def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Subclasses may override this method. """ + from fontMath.mathFunctions import setRoundIntegerFunction setRoundIntegerFunction(normalizers.normalizeVisualRounding) @@ -369,5 +326,5 @@ def _interpolate( f"Info from font '{minInfo.font.name}' and font '{maxInfo.font.name}' could not be interpolated." ) if round: - result = result.round() # type: ignore[func-returns-value] + result = result.round() self._fromMathInfo(result) From b2ef079d974d3d32f985232192eb5b7dceb2fb56 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Tue, 25 Feb 2025 03:37:24 +0100 Subject: [PATCH 09/14] Fix `mypy` errors.` --- Lib/fontParts/base/kerning.py | 39 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index eda3a73a..bd99b358 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -1,8 +1,11 @@ # pylint: disable=C0103, C0114 from __future__ import annotations -from typing import TYPE_CHECKING, Dict, Iterator, List, Optional +from typing import TYPE_CHECKING, Callable, Dict, Iterator, List, Optional, Union from collections.abc import MutableMapping +from fontMath import MathKerning +from fontMath.mathFunctions import setRoundIntegerFunction + from fontParts.base.base import BaseDict, dynamicProperty, interpolate, reference from fontParts.base import normalizers from fontParts.base.deprecated import DeprecatedKerning, RemovedKerning @@ -53,7 +56,7 @@ def _reprContents(self) -> List[str]: # Font - _font: Optional[BaseFont] = None + _font: Optional[Callable[[], BaseFont]] = None font: dynamicProperty = dynamicProperty( "font", "The Kerning's parent :class:`BaseFont`." @@ -64,7 +67,7 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[BaseFont]) -> None: + def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: if self._font is not None and self._font() != font: raise AssertionError("font for kerning already set and is not same as font") if font is not None: @@ -94,9 +97,9 @@ def _scale(self, factor: PairType[float]) -> None: Subclasses may override this method. """ - factor = factor[0] + horizontalFactor = factor[0] for k, v in self.items(): - v *= factor + v *= horizontalFactor self[k] = v # ------------- @@ -137,8 +140,9 @@ def _round(self, multiple: int = 1) -> None: def interpolate( self, factor: TransformationType, - minKerning: BaseKerning, - maxKerning: BaseKerning, + minKerning: MathKerning, + maxKerning: MathKerning, + round: bool = True, suppressError: bool = True, ) -> None: @@ -199,9 +203,6 @@ def _interpolate( Subclasses may override this method. """ - import fontMath - from fontMath.mathFunctions import setRoundIntegerFunction - setRoundIntegerFunction(normalizers.normalizeVisualRounding) kerningGroupCompatibility = self._testKerningGroupCompatibility( minKerning, maxKerning, suppressError=suppressError @@ -209,13 +210,13 @@ def _interpolate( if not kerningGroupCompatibility: self.clear() else: - minKerning = fontMath.MathKerning( + minMathKerning = MathKerning( kerning=minKerning, groups=minKerning.font.groups ) - maxKerning = fontMath.MathKerning( + maxMathKerning = MathKerning( kerning=maxKerning, groups=maxKerning.font.groups ) - result = interpolate(minKerning, maxKerning, factor) + result = interpolate(minMathKerning, maxMathKerning, factor) if round: result.round() self.clear() @@ -261,16 +262,16 @@ def remove(self, pair: PairType[str]) -> None: """ del self[pair] - def asDict(self, returnIntegers: bool = True) -> Dict[str, int]: + def asDict(self, returnIntegers: bool = True) -> Dict[PairType[str], IntFloatType]: """ Return the Kerning as a ``dict``. This is a backwards compatibility method. """ - d = {} - for k, v in self.items(): - d[k] = v if not returnIntegers else normalizers.normalizeVisualRounding(v) - return d + return { + k: (v if not returnIntegers else normalizers.normalizeVisualRounding(v)) + for k, v in self.items() + } # ------------------- # Inherited Functions @@ -399,7 +400,7 @@ def find( """ pair = normalizers.normalizeKerningKey(pair) value = self._find(pair, default) - if value != default: + if value and value != default: value = normalizers.normalizeKerningValue(value) return value From adce67bb80f6bf349f16c8f443aeff59608258cc Mon Sep 17 00:00:00 2001 From: knutnergaard <15194233+knutnergaard@users.noreply.github.com> Date: Tue, 25 Feb 2025 02:38:49 +0000 Subject: [PATCH 10/14] Format fixes by ruff --- Lib/fontParts/base/kerning.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index bd99b358..d5b6165c 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -67,7 +67,9 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: + def _set_font( + self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] + ) -> None: if self._font is not None and self._font() != font: raise AssertionError("font for kerning already set and is not same as font") if font is not None: @@ -142,7 +144,6 @@ def interpolate( factor: TransformationType, minKerning: MathKerning, maxKerning: MathKerning, - round: bool = True, suppressError: bool = True, ) -> None: From ece545269eab5617596aaf432169d05a0ff0c58e Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Tue, 25 Feb 2025 12:48:02 +0100 Subject: [PATCH 11/14] Add and improve documentation and make other minor edits. --- Lib/fontParts/base/kerning.py | 454 ++++++++++++++++++++++------------ 1 file changed, 301 insertions(+), 153 deletions(-) diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index d5b6165c..bc08765f 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -5,6 +5,7 @@ from fontMath import MathKerning from fontMath.mathFunctions import setRoundIntegerFunction +from fontTools.ufoLib.kerning import lookupKerningValue from fontParts.base.base import BaseDict, dynamicProperty, interpolate, reference from fontParts.base import normalizers @@ -24,24 +25,31 @@ class BaseKerning(BaseDict, DeprecatedKerning, RemovedKerning): - """ - A Kerning object. This object normally created as part of a - :class:`BaseFont`. An orphan Kerning object can be created - like this:: + """Represent the basis for a kerning object. - >>> groups = RKerning() + This object behaves like a Python :class:`dict` object. Most of the + dictionary functionality comes from :class:`BaseDict`. Consult that + object's documentation for the required environment implementation + details. + + :cvar keyNormalizer: A function to normalize the key of the dictionary. + Defaults to :func:`normalizers.normalizeKerningKey`. + :cvar valueNormalizer: A function to normalize the key of the dictionary. + Defaults to :func:`normalizers.normalizeKerningValue`. + + This object is normally created as part of a :class:`BaseFont`. + An orphan :class:`BaseKerning` object instance can be created like this:: - This object behaves like a Python dictionary. Most of the - dictionary functionality comes from :class:`BaseDict`, look at - that object for the required environment implementation details. + >>> groups = RKerning() - Kerning uses :func:`normalizers.normalizeKerningKey` to normalize the - key of the ``dict``, and :func:`normalizers.normalizeKerningValue` - to normalize the the value of the ``dict``. """ - keyNormalizer = normalizers.normalizeKerningKey - valueNormalizer = normalizers.normalizeKerningValue + keyNormalizer: Callable[[PairCollectionType[str]], PairType[str]] = ( + normalizers.normalizeKerningKey + ) + valueNormalizer: Callable[[IntFloatType], IntFloatType] = ( + normalizers.normalizeKerningValue + ) def _reprContents(self) -> List[str]: contents = [] @@ -59,7 +67,21 @@ def _reprContents(self) -> List[str]: _font: Optional[Callable[[], BaseFont]] = None font: dynamicProperty = dynamicProperty( - "font", "The Kerning's parent :class:`BaseFont`." + "font", + """Get or set the kerning's parent font object. + + The value must be a :class:`BaseFont` instance or :obj:`None`. + + :return: The :class:`BaseFont` instance containing the kerning + or :obj:`None`. + :raises AssertionError: If attempting to set the font when it has already been + set and is not the same as the provided font. + + Example:: + + >>> font = kerning.font + + """, ) def _get_font(self) -> Optional[BaseFont]: @@ -81,23 +103,36 @@ def _set_font( # -------------- def scaleBy(self, factor: TransformationType) -> None: - """ - Scales all kerning values by **factor**. **factor** will be an - :ref:`type-int-float`, ``tuple`` or ``list``. The first value of the - **factor** will be used to scale the kerning values. + """Scale all kerning values by the specified factor. + + :param factor: The factor by which to scale the kerning. The value may be a + single :class:`int` or :class:`float` or a :class:`tuple` or :class`list` + of two :class:`int` or :class:`float` values representing the factors + ``(x, y)``. In the latter case, the first value is used to scale the + kerning values. + + Example:: >>> myKerning.scaleBy(2) >>> myKerning.scaleBy((2, 3)) + """ factor = normalizers.normalizeTransformationScale(factor) self._scale(factor) def _scale(self, factor: PairType[float]) -> None: - """ + """Scale all native kerning values by the specified factor. + This is the environment implementation of :meth:`BaseKerning.scaleBy`. - **factor** will be a ``tuple``. - Subclasses may override this method. + :param factor: The factor by which to scale the kerning as a :class:`tuple` of + two :class:`int` or :class:`float` values representing the factors + ``(x, y)``. The first value is used to scale the kerning values. + + .. note:: + + Subclasses may override this method. + """ horizontalFactor = factor[0] for k, v in self.items(): @@ -109,11 +144,15 @@ def _scale(self, factor: PairType[float]) -> None: # ------------- def round(self, multiple: int = 1) -> None: - """ - Rounds the kerning values to increments of **multiple**, - which will be an ``int``. + """Round the kerning values to the specified increments. + + :param multiple: The increment to which the kerning values should be rounded + as an :class:`int`. Defaults to ``1``. + + Example:: + + >>> myKerning.round(2) - The default behavior is to round to increments of 1. """ if not isinstance(multiple, int): raise TypeError( @@ -121,12 +160,18 @@ def round(self, multiple: int = 1) -> None: ) self._round(multiple) - def _round(self, multiple: int = 1) -> None: - """ - This is the environment implementation of - :meth:`BaseKerning.round`. **multiple** will be an ``int``. + def _round(self, multiple: int) -> None: + """Round the native kerning values to the specified increments. + + This is the environment implementation of :meth:`BaseKerning.round`. + + :param multiple: The increment to which the kerning values should be rounded + as an :class:`int`. + + .. note:: + + Subclasses may override this method. - Subclasses may override this method. """ for pair, value in self.items(): value = ( @@ -147,26 +192,30 @@ def interpolate( round: bool = True, suppressError: bool = True, ) -> None: - """ - Interpolates all pairs between two :class:`BaseKerning` objects: + """Interpolate all kerning pairs in the font. + + The kerning data will be replaced by the interpolated kerning. + + :param factor: The interpolation value as a single :class:`int` + or :class:`float` or a :class:`list` or :class:`tuple` of + two :class:`int` or :class:`float` values representing the + factors ``(x, y)``. + :param minKerning: The :class:`BaseKerning` instance corresponding to the + 0.0 position in the interpolation. + :param maxKerning: The :class:`BaseKerning` instance corresponding to the + 1.0 position in the interpolation. + :param round: A :class:`bool` indicating whether the result should + be rounded to integers. Defaults to :obj:`True`. + :param suppressError: A :class:`bool` indicating whether to ignore + incompatible data or raise an error when such + incompatibilities are found. Defaults to :obj:`True`. + :raises TypeError: If `minGlyph` or `maxGlyph` are not instances + of :class:`BaseKerning`. + + Example:: >>> myKerning.interpolate(kerningOne, kerningTwo) - **minKerning** and **maxKerning**. The interpolation occurs on a - 0 to 1.0 range where **minKerning** is located at 0 and - **maxKerning** is located at 1.0. The kerning data is replaced by - the interpolated kerning. - - * **factor** is the interpolation value. It may be less than 0 - and greater than 1.0. It may be an :ref:`type-int-float`, - ``tuple`` or ``list``. If it is a ``tuple`` or ``list``, - the first number indicates the x factor and the second number - indicates the y factor. - * **round** is a ``bool`` indicating if the result should be rounded to - ``int``\s. The default behavior is to round interpolated kerning. - * **suppressError** is a ``bool`` indicating if incompatible data should - be ignored or if an error should be raised when such incompatibilities - are found. The default behavior is to ignore incompatible data. """ factor = normalizers.normalizeInterpolationFactor(factor) if not isinstance(minKerning, BaseKerning): @@ -188,21 +237,31 @@ def _interpolate( factor: PairType[float], minKerning: BaseKerning, maxKerning: BaseKerning, - round: bool = True, - suppressError: bool = True, + round: bool, + suppressError: bool, ) -> None: - """ - This is the environment implementation of :meth:`BaseKerning.interpolate`. + """Interpolate all kerning pairs in the native font. + + The kerning data will be replaced by the interpolated kerning. + + :param factor: The interpolation value as a single :class:`int` + or :class:`float` or a :class:`list` or :class:`tuple` of + two :class:`int` or :class:`float` values representing the + factors ``(x, y)``. + :param minKerning: The :class:`BaseKerning` subclass instance corresponding + to the 0.0 position in the interpolation. + :param maxKerning: The :class:`BaseKerning` subclass instance corresponding + to the 1.0 position in the interpolation. + :param round: A :class:`bool` indicating whether the result should + be rounded to integers. + :param suppressError: A :class:`bool` indicating whether to ignore + incompatible data or raise an error when such + incompatibilities are found. - * **factor** will be an :ref:`type-int-float`, ``tuple`` or ``list``. - * **minKerning** will be a :class:`BaseKerning` object. - * **maxKerning** will be a :class:`BaseKerning` object. - * **round** will be a ``bool`` indicating if the interpolated kerning - should be rounded. - * **suppressError** will be a ``bool`` indicating if incompatible data - should be ignored. + .. note:: + + Subclasses may override this method. - Subclasses may override this method. """ setRoundIntegerFunction(normalizers.normalizeVisualRounding) kerningGroupCompatibility = self._testKerningGroupCompatibility( @@ -255,19 +314,34 @@ def _testKerningGroupCompatibility( # --------------------- def remove(self, pair: PairType[str]) -> None: - """ - Removes a pair from the Kerning. **pair** will - be a ``tuple`` of two :ref:`type-string`\s. + """Remove the specified pair from the Kerning. + + :param pair: The pair to remove as a :class:`tuple` of two :class:`str` values. + + .. note:: + + This is a backwards compatibility method. + + Example:: + + >>> myKerning.remove(("A", "V")) - This is a backwards compatibility method. """ del self[pair] def asDict(self, returnIntegers: bool = True) -> Dict[PairType[str], IntFloatType]: - """ - Return the Kerning as a ``dict``. + """Return the kerning as a dictionary. + + :return A :class:`dict` reflecting the contents of the kerning. + + .. note:: + + This is a backwards compatibility method. + + Example:: + + >>> font.lib.asDict() - This is a backwards compatibility method. """ return { k: (v if not returnIntegers else normalizers.normalizeVisualRounding(v)) @@ -279,125 +353,164 @@ def asDict(self, returnIntegers: bool = True) -> Dict[PairType[str], IntFloatTyp # ------------------- def __contains__(self, pair: PairType[str]) -> bool: - """ - Tests to see if a pair is in the Kerning. - **pair** will be a ``tuple`` of two :ref:`type-string`\s. + """Check if the given pair exists in the kerning. - This returns a ``bool`` indicating if the **pair** - is in the Kerning. :: + :param pair: The kerning pair to check for existence as a :class:`tuple` of + two :class:`str` values. + :return: :obj:`True` if the `groupName` exists in the groups, :obj:`False` + otherwise. + + Example:: >>> ("A", "V") in font.kerning True + """ return super(BaseKerning, self).__contains__(pair) def __delitem__(self, pair: PairType[str]) -> None: - """ - Removes **pair** from the Kerning. **pair** is a ``tuple`` of two - :ref:`type-string`\s.:: + """Remove the given pair from the kerning. + + :param pair: The pair to remove as a :class:`tuple` of two :class:`str` values. + + Example:: >>> del font.kerning[("A","V")] + """ super(BaseKerning, self).__delitem__(pair) - def __getitem__(self, pair: PairType[str]) -> float: - """ - Returns the kerning value of the pair. **pair** is a ``tuple`` of - two :ref:`type-string`\s. + def __getitem__(self, pair: PairType[str]) -> IntFloatType: + """Get the value associated with the given kerning pair. + + :param pair: The pair to remove as a :class:`tuple` of two :class:`str` values. + :return: The kerning value as an :class:`int` or :class:`float`. - The returned value will be a :ref:`type-int-float`.:: + Example:: >>> font.kerning[("A", "V")] -15 - It is important to understand that any changes to the returned value - will not be reflected in the Kerning object. If one wants to make a change to - the value, one should do the following:: + .. note:: + + Any changes to the returned kerning value will not be reflected in + it's :class:`BaseKerning` instance. To make changes to this value, + do the following:: + + >>> value = font.kerning[("A", "V")] + >>> value += 10 + >>> font.kerning[("A", "V")] = value - >>> value = font.kerning[("A", "V")] - >>> value += 10 - >>> font.kerning[("A", "V")] = value """ return super(BaseKerning, self).__getitem__(pair) def __iter__(self) -> Iterator[PairType[str]]: - """ - Iterates through the Kerning, giving the pair for each iteration. The order that - the Kerning will iterate though is not fixed nor is it ordered.:: + """Return an iterator over the pairs in the kerning. + + The iteration order is not fixed. + + :return: An :class:`Iterator` over :class:`tuple` instances containing + two :class:`str` values. + + Example:: >>> for pair in font.kerning: >>> print pair ("A", "Y") ("A", "V") ("A", "W") + """ return super(BaseKerning, self).__iter__() def __len__(self) -> int: - """ - Returns the number of pairs in Kerning as an ``int``.:: + """Return the number of pairs in the kerning. + + :return: An :class:`int` representing the number of pairs in the kerning. + + Example:: >>> len(font.kerning) 5 + """ return super(BaseKerning, self).__len__() - def __setitem__(self, pair, value: IntFloatType) -> None: - """ - Sets the **pair** to the list of **value**. **pair** is the - pair as a ``tuple`` of two :ref:`type-string`\s and **value** + def __setitem__(self, pair: PairType[str], value: IntFloatType) -> None: + """Set the value for the given kerning pair. - is a :ref:`type-int-float`. + :param pair: The pair to set as a :class:`tuple` of two :class:`str` values. + :param value: The value to set as an :class:`int` or :class:`float`. + + Example:: >>> font.kerning[("A", "V")] = -20 >>> font.kerning[("A", "W")] = -10.5 + """ super(BaseKerning, self).__setitem__(pair, value) def clear(self) -> None: - """ - Removes all information from Kerning, - resetting the Kerning to an empty dictionary. :: + """Remove all information from kerning. + + This will reset the :class:`BaseKerning` instance to an empty dictionary. + + Example:: >>> font.kerning.clear() + """ super(BaseKerning, self).clear() def get( self, pair: PairType[str], default: Optional[IntFloatType] = None ) -> Optional[IntFloatType]: - """ - Returns the value for the kerning pair. - **pair** is a ``tuple`` of two :ref:`type-string`\s, and the returned - values will either be :ref:`type-int-float` or ``None`` - if no pair was found. :: + """Get the value for the given kerning pair. - >>> font.kerning[("A", "V")] + If the given `pair` is not found, The specified `default` will be returned. + + :param pair: The pair to get as a :class:`tuple` of two :class:`str` values. + :param default: The optional default value to return if the `pair` is not found. + :return: A :class:`tuple` of two :class:`str` values representing the value for + the given `pair`, or the `default` value if the `pair` is not found. + + Example:: + + >>> font.kerning.get(("A", "V")) -25 - It is important to understand that any changes to the returned value - will not be reflected in the Kerning object. If one wants to make a change to - the value, one should do the following:: + .. note:: + + Any changes to the returned kerning value will not be reflected in + it's :class:`BaseKerning` instance. To make changes to this value, + do the following:: + + >>> value = font.kerning[("A", "V")] + >>> value += 10 + >>> font.kerning[("A", "V")] = value - >>> value = font.kerning[("A", "V")] - >>> value += 10 - >>> font.kerning[("A", "V")] = value """ return super(BaseKerning, self).get(pair, default) def find( self, pair: PairCollectionType[str], default: Optional[IntFloatType] = None ) -> Optional[IntFloatType]: - """ - Returns the value for the kerning pair - even if the pair only exists - implicitly (one or both sides may be members of a kerning group). + """Get the value for the given explicit or implicit kerning pair. - **pair** is a ``tuple`` of two :ref:`type-string`\s, and the returned - values will either be :ref:`type-int-float` or ``None`` - if no pair was found. :: + This method will return the value for the given `pair`, even if it only exists + implicitly (one or both sides may be members of a kerning group). If the `pair` + is not found, the specified `default` will be returned. - >>> font.kerning[("A", "V")] + :param pair: The pair to get as a :class:`tuple` of two :class:`str` values. + :param default: The optional default value to return if the `pair` is not found. + :return: A :class:`tuple` of two :class:`str` values representing the value for + the given `pair`, or the `default` value if the `pair` is not found. + + Example:: + + >>> font.kerning.find(("A", "V")) -25 + """ pair = normalizers.normalizeKerningKey(pair) value = self._find(pair, default) @@ -408,76 +521,111 @@ def find( def _find( self, pair: PairType[str], default: Optional[IntFloatType] = None ) -> Optional[IntFloatType]: + """Get the value for the given explicit or implicit native kerning pair. + + This is the environment implementation of :attr:`BaseKerning.find`. + + :param pair: The pair to get as a :class:`tuple` of two :class:`str` values. + :param default: The optional default value to return if the `pair` is not found. + :return: A :class:`tuple` of two :class:`str` values representing the value for + the given `pair`, or the `default` value if the `pair` is not found. + + .. note:: + + Subclasses may override this method. + """ - This is the environment implementation of - :attr:`BaseKerning.find`. This must return an - :ref:`type-int-float` or `default`. - """ - from fontTools.ufoLib.kerning import lookupKerningValue font = self.font groups = font.groups return lookupKerningValue(pair, self, groups, fallback=default) def items(self) -> BaseItems[PairType[str], IntFloatType]: - """ - Returns a list of ``tuple``\s of each pair and value. Pairs are a - ``tuple`` of two :ref:`type-string`\s and values are :ref:`type-int-float`. + """Return the kerning's items. + + Each item is represented as a :class:`tuple` of key-value pairs, where: + - `key` is a :class:`tuple` of two :class:`str` values. + - `value` is an :class:`int` or a :class:`float`. - The initial list will be unordered. + :return: A :ref:`type-view` of the kerning's ``(key, value)`` pairs. + + Example:: >>> font.kerning.items() - [(("A", "V"), -30), (("A", "W"), -10)] + BaseKerning_items([(("A", "V"), -30), (("A", "W"), -10)]) + """ return super(BaseKerning, self).items() def keys(self) -> BaseKeys[PairType[str]]: - """ - Returns a ``list`` of all the pairs in Kerning. This list will be - unordered.:: + """Return the kering's pairs (keys). + + :return: A :ref:`type-view` of the kerning's pairs as :class: `tuple` instances + of two :class:`str` values. + + Example:: >>> font.kerning.keys() - [("A", "Y"), ("A", "V"), ("A", "W")] + BaseKerning_keys([("A", "Y"), ("A", "V"), ("A", "W")]) + """ return super(BaseKerning, self).keys() + def values(self) -> BaseValues[IntFloatType]: + """Return the kerning's values. + + :return: A :ref:`type-view` of :class:`int` or :class:`float` values. + + Example:: + + >>> font.kerning.items() + BaseKerning_values([-20, -15, 5, 3.5]) + + """ + return super(BaseKerning, self).values() + def pop( self, pair: PairType[str], default: Optional[IntFloatType] = None ) -> Optional[IntFloatType]: - """ - Removes the **pair** from the Kerning and returns the value as an ``int``. - If no pair is found, **default** is returned. **pair** is a - ``tuple`` of two :ref:`type-string`\s. This must return either + """Remove the specified kerning pair and return its associated value. - **default** or a :ref:`type-int-float`. + If the `pair` does not exist, the `default` value is returned. + + :param pair: The pair to remove as a :class:`tuple` of two :class:`str` values. + :param default: The optional default value to return if the `pair` is not + found`. The value must be an :class:`int`, a :class:`float` or :obj:`None`. + Defaults to :obj:`None`. + :return: The value for the given `pair` as an :class:`int` or :class:`float`, + or the `default` value if the `pair` is not found. + + Example:: >>> font.kerning.pop(("A", "V")) -20 >>> font.kerning.pop(("A", "W")) -10.5 + """ return super(BaseKerning, self).pop(pair, default) def update(self, otherKerning: MutableMapping[PairType[str], IntFloatType]) -> None: - """ - Updates the Kerning based on **otherKerning**. **otherKerning** is a ``dict`` of - kerning information. If a pair from **otherKerning** is in Kerning, the pair - value will be replaced by the value from **otherKerning**. If a pair - from **otherKerning** is not in the Kerning, it is added to the pairs. If Kerning - contains a pair that is not in **otherKerning**, it is not changed. + """Update the current kerning with key-value pairs from another. - >>> font.kerning.update(newKerning) - """ - super(BaseKerning, self).update(otherKerning) + For each pair in `otherKerning`: + - If the pair exists in the current kerning, its value is replaced with + the value from `otherKerning`. + - If the pair does not exist in the current kerning, it is added. - def values(self) -> BaseValues[IntFloatType]: - """ - Returns a ``list`` of each pair's values, the values will be - :ref:`type-int-float`\s. + Pairs that exist in the current kerning but are not in `otherLib` remain + unchanged. - The list will be unordered. + :param otherKerning: A :class:`MutableMapping` of key-value pairs to update the + current lib with. Keys must be a :class:`tuple` of two :class:`str` values. + Values must be an :class:`int` or a :class:`float`. + + Example:: + + >>> font.kerning.update(newKerning) - >>> font.kerning.items() - [-20, -15, 5, 3.5] """ - return super(BaseKerning, self).values() + super(BaseKerning, self).update(otherKerning) From 5aaefea24263fce1cdc21c16c27f0690c535b451 Mon Sep 17 00:00:00 2001 From: knutnergaard <15194233+knutnergaard@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:50:06 +0000 Subject: [PATCH 12/14] Format fixes by ruff --- Lib/fontParts/base/kerning.py | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index bc08765f..3c92c297 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -37,7 +37,7 @@ class BaseKerning(BaseDict, DeprecatedKerning, RemovedKerning): :cvar valueNormalizer: A function to normalize the key of the dictionary. Defaults to :func:`normalizers.normalizeKerningValue`. - This object is normally created as part of a :class:`BaseFont`. + This object is normally created as part of a :class:`BaseFont`. An orphan :class:`BaseKerning` object instance can be created like this:: >>> groups = RKerning() @@ -105,9 +105,9 @@ def _set_font( def scaleBy(self, factor: TransformationType) -> None: """Scale all kerning values by the specified factor. - :param factor: The factor by which to scale the kerning. The value may be a - single :class:`int` or :class:`float` or a :class:`tuple` or :class`list` - of two :class:`int` or :class:`float` values representing the factors + :param factor: The factor by which to scale the kerning. The value may be a + single :class:`int` or :class:`float` or a :class:`tuple` or :class`list` + of two :class:`int` or :class:`float` values representing the factors ``(x, y)``. In the latter case, the first value is used to scale the kerning values. @@ -125,8 +125,8 @@ def _scale(self, factor: PairType[float]) -> None: This is the environment implementation of :meth:`BaseKerning.scaleBy`. - :param factor: The factor by which to scale the kerning as a :class:`tuple` of - two :class:`int` or :class:`float` values representing the factors + :param factor: The factor by which to scale the kerning as a :class:`tuple` of + two :class:`int` or :class:`float` values representing the factors ``(x, y)``. The first value is used to scale the kerning values. .. note:: @@ -146,7 +146,7 @@ def _scale(self, factor: PairType[float]) -> None: def round(self, multiple: int = 1) -> None: """Round the kerning values to the specified increments. - :param multiple: The increment to which the kerning values should be rounded + :param multiple: The increment to which the kerning values should be rounded as an :class:`int`. Defaults to ``1``. Example:: @@ -165,7 +165,7 @@ def _round(self, multiple: int) -> None: This is the environment implementation of :meth:`BaseKerning.round`. - :param multiple: The increment to which the kerning values should be rounded + :param multiple: The increment to which the kerning values should be rounded as an :class:`int`. .. note:: @@ -197,8 +197,8 @@ def interpolate( The kerning data will be replaced by the interpolated kerning. :param factor: The interpolation value as a single :class:`int` - or :class:`float` or a :class:`list` or :class:`tuple` of - two :class:`int` or :class:`float` values representing the + or :class:`float` or a :class:`list` or :class:`tuple` of + two :class:`int` or :class:`float` values representing the factors ``(x, y)``. :param minKerning: The :class:`BaseKerning` instance corresponding to the 0.0 position in the interpolation. @@ -245,12 +245,12 @@ def _interpolate( The kerning data will be replaced by the interpolated kerning. :param factor: The interpolation value as a single :class:`int` - or :class:`float` or a :class:`list` or :class:`tuple` of - two :class:`int` or :class:`float` values representing the + or :class:`float` or a :class:`list` or :class:`tuple` of + two :class:`int` or :class:`float` values representing the factors ``(x, y)``. - :param minKerning: The :class:`BaseKerning` subclass instance corresponding + :param minKerning: The :class:`BaseKerning` subclass instance corresponding to the 0.0 position in the interpolation. - :param maxKerning: The :class:`BaseKerning` subclass instance corresponding + :param maxKerning: The :class:`BaseKerning` subclass instance corresponding to the 1.0 position in the interpolation. :param round: A :class:`bool` indicating whether the result should be rounded to integers. @@ -355,9 +355,9 @@ def asDict(self, returnIntegers: bool = True) -> Dict[PairType[str], IntFloatTyp def __contains__(self, pair: PairType[str]) -> bool: """Check if the given pair exists in the kerning. - :param pair: The kerning pair to check for existence as a :class:`tuple` of + :param pair: The kerning pair to check for existence as a :class:`tuple` of two :class:`str` values. - :return: :obj:`True` if the `groupName` exists in the groups, :obj:`False` + :return: :obj:`True` if the `groupName` exists in the groups, :obj:`False` otherwise. Example:: @@ -471,7 +471,7 @@ def get( :param pair: The pair to get as a :class:`tuple` of two :class:`str` values. :param default: The optional default value to return if the `pair` is not found. - :return: A :class:`tuple` of two :class:`str` values representing the value for + :return: A :class:`tuple` of two :class:`str` values representing the value for the given `pair`, or the `default` value if the `pair` is not found. Example:: @@ -497,8 +497,8 @@ def find( ) -> Optional[IntFloatType]: """Get the value for the given explicit or implicit kerning pair. - This method will return the value for the given `pair`, even if it only exists - implicitly (one or both sides may be members of a kerning group). If the `pair` + This method will return the value for the given `pair`, even if it only exists + implicitly (one or both sides may be members of a kerning group). If the `pair` is not found, the specified `default` will be returned. :param pair: The pair to get as a :class:`tuple` of two :class:`str` values. @@ -560,7 +560,7 @@ def items(self) -> BaseItems[PairType[str], IntFloatType]: def keys(self) -> BaseKeys[PairType[str]]: """Return the kering's pairs (keys). - :return: A :ref:`type-view` of the kerning's pairs as :class: `tuple` instances + :return: A :ref:`type-view` of the kerning's pairs as :class: `tuple` instances of two :class:`str` values. Example:: @@ -592,7 +592,7 @@ def pop( If the `pair` does not exist, the `default` value is returned. :param pair: The pair to remove as a :class:`tuple` of two :class:`str` values. - :param default: The optional default value to return if the `pair` is not + :param default: The optional default value to return if the `pair` is not found`. The value must be an :class:`int`, a :class:`float` or :obj:`None`. Defaults to :obj:`None`. :return: The value for the given `pair` as an :class:`int` or :class:`float`, @@ -616,11 +616,11 @@ def update(self, otherKerning: MutableMapping[PairType[str], IntFloatType]) -> N the value from `otherKerning`. - If the pair does not exist in the current kerning, it is added. - Pairs that exist in the current kerning but are not in `otherLib` remain + Pairs that exist in the current kerning but are not in `otherLib` remain unchanged. - :param otherKerning: A :class:`MutableMapping` of key-value pairs to update the - current lib with. Keys must be a :class:`tuple` of two :class:`str` values. + :param otherKerning: A :class:`MutableMapping` of key-value pairs to update the + current lib with. Keys must be a :class:`tuple` of two :class:`str` values. Values must be an :class:`int` or a :class:`float`. Example:: From 6aae3fdb437037bf59fbb5e34ce59f589ebe6799 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Wed, 26 Feb 2025 00:19:13 +0100 Subject: [PATCH 13/14] git --- Lib/fontParts/base/image.py | 105 +++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/Lib/fontParts/base/image.py b/Lib/fontParts/base/image.py index 5509b826..cfd69f42 100644 --- a/Lib/fontParts/base/image.py +++ b/Lib/fontParts/base/image.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, Any, Callable, Optional, Union + from fontTools.misc import transform from fontParts.base.base import ( BaseObject, @@ -9,6 +12,21 @@ from fontParts.base import normalizers from fontParts.base.color import Color from fontParts.base.deprecated import DeprecatedImage, RemovedImage +from fontParts.base.annotations import ( + PairType, + PairCollectionType, + QuadrupleType, + QuadrupleCollectionType, + SextupleType, + SextupleCollectionType, + IntFloatType, + TransformationType +) + +if TYPE_CHECKING: + from fontParts.base.glyph import BaseGlyph + from fontParts.base.layer import BaseLayer + from fontParts.base.font import BaseFont class BaseImage( @@ -20,7 +38,7 @@ class BaseImage( ): copyAttributes = ("transformation", "color", "data") - def _reprContents(self): + def _reprContents(self) -> list[str]: contents = [ f"offset='({self.offset[0]}, {self.offset[1]})'", ] @@ -31,7 +49,7 @@ def _reprContents(self): contents += self.glyph._reprContents() return contents - def __bool__(self): + def __bool__(self) -> bool: if self.data is None: return False elif len(self.data) == 0: @@ -47,16 +65,19 @@ def __bool__(self): # Glyph - _glyph = None + _glyph: Optional[Callable[[], BaseGlyph]] = None glyph = dynamicProperty("glyph", "The image's parent :class:`BaseGlyph`.") - def _get_glyph(self): + def _get_glyph(self) -> Optional[BaseGlyph]: if self._glyph is None: return None return self._glyph() - def _set_glyph(self, glyph): + def _set_glyph( + self, + glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]] + ) -> None: if self._glyph is not None: raise AssertionError("glyph for image already set") if glyph is not None: @@ -65,18 +86,18 @@ def _set_glyph(self, glyph): # Layer - layer = dynamicProperty("layer", "The image's parent :class:`BaseLayer`.") + layer: dynamicProperty = dynamicProperty("layer", "The image's parent :class:`BaseLayer`.") - def _get_layer(self): + def _get_layer(self) -> Optional[BaseLayer]: if self._glyph is None: return None return self.glyph.layer # Font - font = dynamicProperty("font", "The image's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty("font", "The image's parent :class:`BaseFont`.") - def _get_font(self): + def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: return None return self.glyph.font @@ -87,7 +108,7 @@ def _get_font(self): # Transformation - transformation = dynamicProperty( + transformation: dynamicProperty = dynamicProperty( "base_transformation", """ The image's :ref:`type-transformation`. @@ -100,28 +121,31 @@ def _get_font(self): """, ) - def _get_base_transformation(self): + def _get_base_transformation(self) -> SextupleType[float]: value = self._get_transformation() value = normalizers.normalizeTransformationMatrix(value) return value - def _set_base_transformation(self, value): + def _set_base_transformation( + self, + value: SextupleCollectionType[IntFloatType] + ) -> None: value = normalizers.normalizeTransformationMatrix(value) self._set_transformation(value) - def _get_transformation(self): + def _get_transformation(self) -> SextupleCollectionType[IntFloatType]: """ Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_transformation(self, value): + def _set_transformation(self, value: SextupleCollectionType[IntFloatType]) -> None: """ Subclasses must override this method. """ self.raiseNotImplementedError() - offset = dynamicProperty( + offset: dynamicProperty = dynamicProperty( "base_offset", """ The image's offset. This is a shortcut to the offset @@ -135,23 +159,23 @@ def _set_transformation(self, value): """, ) - def _get_base_offset(self): + def _get_base_offset(self) -> PairType[IntFloatType]: value = self._get_offset() value = normalizers.normalizeTransformationOffset(value) return value - def _set_base_offset(self, value): + def _set_base_offset(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeTransformationOffset(value) self._set_offset(value) - def _get_offset(self): + def _get_offset(self) -> PairCollectionType[IntFloatType]: """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation return (ox, oy) - def _set_offset(self, value): + def _set_offset(self, value: PairType[IntFloatType]) -> None: """ Subclasses may override this method. """ @@ -159,7 +183,7 @@ def _set_offset(self, value): ox, oy = value self.transformation = (sx, sxy, syx, sy, ox, oy) - scale = dynamicProperty( + scale: dynamicProperty = dynamicProperty( "base_scale", """ The image's scale. This is a shortcut to the scale @@ -173,23 +197,23 @@ def _set_offset(self, value): """, ) - def _get_base_scale(self): + def _get_base_scale(self) -> PairType[float]: value = self._get_scale() value = normalizers.normalizeTransformationScale(value) return value - def _set_base_scale(self, value): + def _set_base_scale(self, value: TransformationType) -> None: value = normalizers.normalizeTransformationScale(value) self._set_scale(value) - def _get_scale(self): + def _get_scale(self) -> TransformationType: """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation return (sx, sy) - def _set_scale(self, value): + def _set_scale(self, value: PairType[float]) -> None: """ Subclasses may override this method. """ @@ -199,7 +223,7 @@ def _set_scale(self, value): # Color - color = dynamicProperty( + color: dynamicProperty = dynamicProperty( "base_color", """ The image's color. This will be a @@ -211,19 +235,22 @@ def _set_scale(self, value): """, ) - def _get_base_color(self): + def _get_base_color(self) -> Optional[Color]: value = self._get_color() if value is not None: value = normalizers.normalizeColor(value) value = Color(value) return value - def _set_base_color(self, value): + def _set_base_color( + self, + value: Optional[QuadrupleCollectionType[IntFloatType]] + ) -> None: if value is not None: value = normalizers.normalizeColor(value) self._set_color(value) - def _get_color(self): + def _get_color(self) -> Optional[QuadrupleCollectionType[IntFloatType]]: """ Return the color value as a color tuple or None. @@ -231,7 +258,7 @@ def _get_color(self): """ self.raiseNotImplementedError() - def _set_color(self, value): + def _set_color(self, value: Optional[QuadrupleType[float]]) -> None: """ value will be a color tuple or None. @@ -241,7 +268,7 @@ def _set_color(self, value): # Data - data = dynamicProperty( + data: dynamicProperty = dynamicProperty( "data", """ The image's raw byte data. The possible @@ -249,13 +276,13 @@ def _set_color(self, value): """, ) - def _get_base_data(self): + def _get_base_data(self) -> bytes: return self._get_data() - def _set_base_data(self, value): + def _set_base_data(self, value: bytes) -> None: self._set_data(value) - def _get_data(self): + def _get_data(self) -> bytes: """ This must return raw byte data. @@ -263,7 +290,7 @@ def _get_data(self): """ self.raiseNotImplementedError() - def _set_data(self, value): + def _set_data(self, value: bytes) -> None: """ value will be raw byte data. @@ -275,7 +302,11 @@ def _set_data(self, value): # Transformation # -------------- - def _transformBy(self, matrix, **kwargs): + def _transformBy( + self, + matrix: SextupleCollectionType[IntFloatType], + **kwargs: Any + ) -> None: """ Subclasses may override this method. """ @@ -287,13 +318,13 @@ def _transformBy(self, matrix, **kwargs): # Normalization # ------------- - def round(self): + def round(self) -> None: """ Round offset coordinates. """ self._round() - def _round(self): + def _round(self) -> None: """ Subclasses may override this method. """ From 0f125bd380f09c48cd4bc9f7d29f17f2d3756029 Mon Sep 17 00:00:00 2001 From: knutnergaard <15194233+knutnergaard@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:19:59 +0000 Subject: [PATCH 14/14] Format fixes by ruff --- Lib/fontParts/base/image.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Lib/fontParts/base/image.py b/Lib/fontParts/base/image.py index cfd69f42..9f694545 100644 --- a/Lib/fontParts/base/image.py +++ b/Lib/fontParts/base/image.py @@ -20,7 +20,7 @@ SextupleType, SextupleCollectionType, IntFloatType, - TransformationType + TransformationType, ) if TYPE_CHECKING: @@ -75,8 +75,7 @@ def _get_glyph(self) -> Optional[BaseGlyph]: return self._glyph() def _set_glyph( - self, - glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]] + self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]] ) -> None: if self._glyph is not None: raise AssertionError("glyph for image already set") @@ -86,7 +85,9 @@ def _set_glyph( # Layer - layer: dynamicProperty = dynamicProperty("layer", "The image's parent :class:`BaseLayer`.") + layer: dynamicProperty = dynamicProperty( + "layer", "The image's parent :class:`BaseLayer`." + ) def _get_layer(self) -> Optional[BaseLayer]: if self._glyph is None: @@ -95,7 +96,9 @@ def _get_layer(self) -> Optional[BaseLayer]: # Font - font: dynamicProperty = dynamicProperty("font", "The image's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty( + "font", "The image's parent :class:`BaseFont`." + ) def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: @@ -127,8 +130,7 @@ def _get_base_transformation(self) -> SextupleType[float]: return value def _set_base_transformation( - self, - value: SextupleCollectionType[IntFloatType] + self, value: SextupleCollectionType[IntFloatType] ) -> None: value = normalizers.normalizeTransformationMatrix(value) self._set_transformation(value) @@ -243,8 +245,7 @@ def _get_base_color(self) -> Optional[Color]: return value def _set_base_color( - self, - value: Optional[QuadrupleCollectionType[IntFloatType]] + self, value: Optional[QuadrupleCollectionType[IntFloatType]] ) -> None: if value is not None: value = normalizers.normalizeColor(value) @@ -303,9 +304,7 @@ def _set_data(self, value: bytes) -> None: # -------------- def _transformBy( - self, - matrix: SextupleCollectionType[IntFloatType], - **kwargs: Any + self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any ) -> None: """ Subclasses may override this method.