diff --git a/Lib/fontParts/base/image.py b/Lib/fontParts/base/image.py index 5509b826..9f694545 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,18 @@ 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 +85,22 @@ 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 +111,7 @@ def _get_font(self): # Transformation - transformation = dynamicProperty( + transformation: dynamicProperty = dynamicProperty( "base_transformation", """ The image's :ref:`type-transformation`. @@ -100,28 +124,30 @@ 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 +161,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 +185,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 +199,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 +225,7 @@ def _set_scale(self, value): # Color - color = dynamicProperty( + color: dynamicProperty = dynamicProperty( "base_color", """ The image's color. This will be a @@ -211,19 +237,21 @@ 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 +259,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 +269,7 @@ def _set_color(self, value): # Data - data = dynamicProperty( + data: dynamicProperty = dynamicProperty( "data", """ The image's raw byte data. The possible @@ -249,13 +277,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 +291,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 +303,9 @@ 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 +317,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. """ diff --git a/Lib/fontParts/base/kerning.py b/Lib/fontParts/base/kerning.py index e85bedf3..3c92c297 100644 --- a/Lib/fontParts/base/kerning.py +++ b/Lib/fontParts/base/kerning.py @@ -1,29 +1,57 @@ +# pylint: disable=C0103, C0114 +from __future__ import annotations +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 fontTools.ufoLib.kerning import lookupKerningValue + 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): - """ - 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 behaves like a Python dictionary. Most of the - dictionary functionality comes from :class:`BaseDict`, look at - that object for the required environment implementation details. + This object is normally created as part of a :class:`BaseFont`. + An orphan :class:`BaseKerning` object instance can be created like this:: + + >>> 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): + def _reprContents(self) -> List[str]: contents = [] if self.font is not None: contents.append("for font") @@ -36,16 +64,34 @@ def _reprContents(self): # Font - _font = None + _font: Optional[Callable[[], BaseFont]] = None - font = dynamicProperty("font", "The Kerning's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty( + "font", + """Get or set the kerning's parent font object. - def _get_font(self): + 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]: 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 kerning already set and is not same as font") if font is not None: @@ -56,40 +102,57 @@ def _set_font(self, font): # Transformation # -------------- - def scaleBy(self, factor): - """ - 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. + 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 + ``(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): - """ + 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. + """ - factor = factor[0] + horizontalFactor = factor[0] for k, v in self.items(): - v *= factor + v *= horizontalFactor self[k] = v # ------------- # Normalization # ------------- - def round(self, multiple=1): - """ - Rounds the kerning values to increments of **multiple**, - which will be an ``int``. + 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 + 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( @@ -97,12 +160,18 @@ def round(self, multiple=1): ) self._round(multiple) - def _round(self, multiple=1): - """ - 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 = ( @@ -116,28 +185,37 @@ def _round(self, multiple=1): # ------------- def interpolate( - self, factor, minKerning, maxKerning, round=True, suppressError=True - ): - """ - Interpolates all pairs between two :class:`BaseKerning` objects: + self, + factor: TransformationType, + minKerning: MathKerning, + maxKerning: MathKerning, + round: bool = True, + suppressError: bool = True, + ) -> None: + """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): @@ -155,24 +233,36 @@ def interpolate( ) def _interpolate( - self, factor, minKerning, maxKerning, round=True, suppressError=True - ): - """ - This is the environment implementation of :meth:`BaseKerning.interpolate`. + self, + factor: PairType[float], + minKerning: BaseKerning, + maxKerning: BaseKerning, + round: bool, + suppressError: bool, + ) -> None: + """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. + + .. note:: + + Subclasses may override this method. - * **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. - - Subclasses may override this method. """ - import fontMath - from fontMath.mathFunctions import setRoundIntegerFunction - setRoundIntegerFunction(normalizers.normalizeVisualRounding) kerningGroupCompatibility = self._testKerningGroupCompatibility( minKerning, maxKerning, suppressError=suppressError @@ -180,20 +270,22 @@ 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() 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,222 +313,319 @@ def _testKerningGroupCompatibility(minKerning, maxKerning, suppressError=False): # RoboFab Compatibility # --------------------- - def remove(self, pair): - """ - Removes a pair from the Kerning. **pair** will - be a ``tuple`` of two :ref:`type-string`\s. + def remove(self, pair: PairType[str]) -> None: + """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=True): - """ - Return the Kerning as a ``dict``. + def asDict(self, returnIntegers: bool = True) -> Dict[PairType[str], IntFloatType]: + """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. """ - 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 # ------------------- - def __contains__(self, pair): - """ - Tests to see if a pair is in the Kerning. - **pair** will be a ``tuple`` of two :ref:`type-string`\s. + 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 + two :class:`str` values. + :return: :obj:`True` if the `groupName` exists in the groups, :obj:`False` + otherwise. - This returns a ``bool`` indicating if the **pair** - is in the Kerning. :: + Example:: >>> ("A", "V") in font.kerning True + """ return super(BaseKerning, self).__contains__(pair) - def __delitem__(self, pair): - """ - Removes **pair** from the Kerning. **pair** is a ``tuple`` of two - :ref:`type-string`\s.:: + def __delitem__(self, pair: PairType[str]) -> None: + """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): - """ - 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. - The returned value will be a :ref:`type-int-float`.:: + :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`. + + 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): - """ - 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.:: + def __iter__(self) -> Iterator[PairType[str]]: + """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): - """ - Returns the number of pairs in Kerning as an ``int``.:: + def __len__(self) -> 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): - """ - 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): - """ - Removes all information from Kerning, - resetting the Kerning to an empty dictionary. :: + def clear(self) -> None: + """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, default=None): - """ - 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. :: + def get( + self, pair: PairType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: + """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, default=None): - """ - 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). + def find( + self, pair: PairCollectionType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: + """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) - if value != default: + if value and value != default: value = normalizers.normalizeKerningValue(value) return value - def _find(self, pair, default=None): - """ - This is the environment implementation of - :attr:`BaseKerning.find`. This must return an - :ref:`type-int-float` or `default`. + 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. + """ - from fontTools.ufoLib.kerning import lookupKerningValue font = self.font groups = font.groups return lookupKerningValue(pair, self, groups, fallback=default) - def items(self): - """ - 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`. + def items(self) -> BaseItems[PairType[str], IntFloatType]: + """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`. + + :return: A :ref:`type-view` of the kerning's ``(key, value)`` pairs. - The initial list will be unordered. + 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): - """ - Returns a ``list`` of all the pairs in Kerning. This list will be - unordered.:: + 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 + 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 pop(self, pair, default=None): + 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]) + """ - 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 + return super(BaseKerning, self).values() - **default** or a :ref:`type-int-float`. + def pop( + self, pair: PairType[str], default: Optional[IntFloatType] = None + ) -> Optional[IntFloatType]: + """Remove the specified kerning pair and return its associated value. + + 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): - """ - 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. + def update(self, otherKerning: MutableMapping[PairType[str], IntFloatType]) -> None: + """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): - """ - 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)