diff --git a/Lib/fontParts/base/annotations.py b/Lib/fontParts/base/annotations.py index 30afc84e..32b447d4 100644 --- a/Lib/fontParts/base/annotations.py +++ b/Lib/fontParts/base/annotations.py @@ -1,47 +1,51 @@ # pylint: disable=C0103, C0114 - from __future__ import annotations from typing import Dict, List, Protocol, Tuple, TypeVar, Union from fontTools.pens.basePen import AbstractPen from fontTools.pens.pointPen import AbstractPointPen -# ------------ -# Type Aliases -# ------------ - -# Builtins - +# Generic T = TypeVar('T') + +PairType = Tuple[T, T] +QuadrupleType = Tuple[T, T, T, T] +QuintupleType = Tuple[T, T, T, T, T] +SextupleType = Tuple[T, T, T, T, T, T] CollectionType = Union[List[T], Tuple[T, ...]] -IntFloatType = Union[int, float] +PairCollectionType = Union[List[T], PairType[T]] +QuadrupleCollectionType = Union[List[T], QuadrupleType[T]] +SextupleCollectionType = Union[List[T], SextupleType[T]] -# FontTools +# Builtins +IntFloatType = Union[int, float] +# Pens PenType = AbstractPen PointPenType = AbstractPointPen -# FontParts - -BoundsType = Tuple[IntFloatType, IntFloatType, IntFloatType, IntFloatType] +# Mapping CharacterMappingType = Dict[int, Tuple[str, ...]] -ColorType = Tuple[IntFloatType, IntFloatType, IntFloatType, IntFloatType] -CoordinateType = Tuple[IntFloatType, IntFloatType] -FactorType = Union[IntFloatType, Tuple[IntFloatType, IntFloatType]] -InterpolatableType = TypeVar('InterpolatableType', bound='Interpolatable') -KerningKeyType = Tuple[str, str] -KerningDictType = Dict[KerningKeyType, IntFloatType] ReverseComponentMappingType = Dict[str, Tuple[str, ...]] -ScaleType = Tuple[IntFloatType, IntFloatType] -TransformationMatrixType = Tuple[ - IntFloatType, IntFloatType, IntFloatType, - IntFloatType, IntFloatType, IntFloatType -] + +# Kerning +KerningDictType = Dict[PairType[str], PairType[str]] + +# Transformation +TransformationType = Union[IntFloatType, List[IntFloatType], PairType[IntFloatType]] + +# Interpolation +InterpolatableType = TypeVar('InterpolatableType', bound='Interpolatable') class Interpolatable(Protocol): """Represent a protocol for interpolatable types.""" - def __add__(self, other: InterpolatableType) -> InterpolatableType: ... - def __sub__(self, other: InterpolatableType) -> InterpolatableType: ... - def __mul__(self, other: FactorType) -> InterpolatableType: ... + def __add__(self, other: InterpolatableType) -> InterpolatableType: + ... + + def __sub__(self, other: InterpolatableType) -> InterpolatableType: + ... + + def __mul__(self, other: TransformationType) -> InterpolatableType: + ... diff --git a/Lib/fontParts/base/bPoint.py b/Lib/fontParts/base/bPoint.py index 772306ce..9d93dee3 100644 --- a/Lib/fontParts/base/bPoint.py +++ b/Lib/fontParts/base/bPoint.py @@ -14,8 +14,9 @@ from fontParts.base import normalizers from fontParts.base.deprecated import DeprecatedBPoint, RemovedBPoint from fontParts.base.annotations import ( - CoordinateType, - TransformationMatrixType + PairType, + PairCollectionType, + SextupleCollectionType ) if TYPE_CHECKING: from fontParts.base.contour import BaseContour @@ -241,16 +242,16 @@ def _get_font(self) -> Optional[BaseFont]: """ ) - def _get_base_anchor(self) -> CoordinateType: + def _get_base_anchor(self) -> PairType[IntFloatType]: value = self._get_anchor() value = normalizers.normalizeCoordinateTuple(value) return value - def _set_base_anchor(self, value: CoordinateType) -> None: + def _set_base_anchor(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeCoordinateTuple(value) self._set_anchor(value) - def _get_anchor(self) -> CoordinateType: + def _get_anchor(self) -> PairType[IntFloatType]: """Get the the bPoint's anchor point. This is the environment implementation of the :attr:`BaseBPoint.anchor` @@ -268,7 +269,7 @@ def _get_anchor(self) -> CoordinateType: point = self._point return (point.x, point.y) - def _set_anchor(self, value: CoordinateType) -> None: + def _set_anchor(self, value: PairCollectionType[IntFloatType]) -> None: """Set the the bPoint's anchor point. This is the environment implementation of the :attr:`BaseBPoint.anchor` @@ -303,16 +304,16 @@ def _set_anchor(self, value: CoordinateType) -> None: """ ) - def _get_base_bcpIn(self) -> CoordinateType: + def _get_base_bcpIn(self) -> PairType[IntFloatType]: value = self._get_bcpIn() value = normalizers.normalizeCoordinateTuple(value) return value - def _set_base_bcpIn(self, value: CoordinateType) -> None: + def _set_base_bcpIn(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeCoordinateTuple(value) self._set_bcpIn(value) - def _get_bcpIn(self) -> CoordinateType: + def _get_bcpIn(self) -> PairType[IntFloatType]: """Get the bPoint's incoming off-curve. This is the environment implementation of the :attr:`BaseBPoint.bcpIn` @@ -336,7 +337,7 @@ def _get_bcpIn(self) -> CoordinateType: x = y = 0 return (x, y) - def _set_bcpIn(self, value: CoordinateType) -> None: + def _set_bcpIn(self, value: PairCollectionType[IntFloatType]) -> None: """Set the bPoint's incoming off-curve. This is the environment implementation of the :attr:`BaseBPoint.bcpIn` @@ -390,16 +391,16 @@ def _set_bcpIn(self, value: CoordinateType) -> None: """ ) - def _get_base_bcpOut(self) -> CoordinateType: + def _get_base_bcpOut(self) -> PairType[IntFloatType]: value = self._get_bcpOut() value = normalizers.normalizeCoordinateTuple(value) return value - def _set_base_bcpOut(self, value: CoordinateType) -> None: + def _set_base_bcpOut(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeCoordinateTuple(value) self._set_bcpOut(value) - def _get_bcpOut(self) -> CoordinateType: + def _get_bcpOut(self) -> PairType[IntFloatType]: """Get the bPoint's outgoing off-curve. This is the environment implementation of the :attr:`BaseBPoint.bcpOut` @@ -423,7 +424,7 @@ def _get_bcpOut(self) -> CoordinateType: x = y = 0 return (x, y) - def _set_bcpOut(self, value: CoordinateType) -> None: + def _set_bcpOut(self, value: PairCollectionType[IntFloatType]) -> None: """Set the bPoint's outgoing off-curve. This is the environment implementation of the :attr:`BaseBPoint.bcpOut` @@ -612,7 +613,9 @@ def _get_index(self) -> Optional[int]: # Transformation # -------------- - def _transformBy(self, matrix: TransformationMatrixType, **kwargs: Any) -> None: + def _transformBy(self, + matrix: SextupleCollectionType[IntFloatType], + **kwargs: Any) -> None: r"""Transform the native bPoint. This is the environment implementation of :meth:`BaseBPoint.transformBy`. @@ -666,7 +669,8 @@ def round(self) -> None: normalizers.normalizeVisualRounding(y)) -def relativeBCPIn(anchor: CoordinateType, BCPIn: CoordinateType) -> CoordinateType: +def relativeBCPIn(anchor: PairCollectionType[IntFloatType], + BCPIn: PairCollectionType[IntFloatType]) -> PairType[IntFloatType]: """convert absolute incoming bcp value to a relative value. :param anchor: The anchor reference point from which to measure the relative @@ -679,7 +683,8 @@ def relativeBCPIn(anchor: CoordinateType, BCPIn: CoordinateType) -> CoordinateTy return (BCPIn[0] - anchor[0], BCPIn[1] - anchor[1]) -def absoluteBCPIn(anchor: CoordinateType, BCPIn: CoordinateType) -> CoordinateType: +def absoluteBCPIn(anchor: PairCollectionType[IntFloatType], + BCPIn: PairCollectionType[IntFloatType]) -> PairType[IntFloatType]: """convert relative incoming bcp value to an absolute value. :param anchor: The anchor reference point from which the relative BCP value @@ -692,7 +697,8 @@ def absoluteBCPIn(anchor: CoordinateType, BCPIn: CoordinateType) -> CoordinateTy return (BCPIn[0] + anchor[0], BCPIn[1] + anchor[1]) -def relativeBCPOut(anchor: CoordinateType, BCPOut: CoordinateType) -> CoordinateType: +def relativeBCPOut(anchor: PairCollectionType[IntFloatType], + BCPOut: PairCollectionType[IntFloatType]) -> PairType[IntFloatType]: """convert absolute outgoing bcp value to a relative value. :param anchor: The anchor reference point from which to measure the relative @@ -705,7 +711,8 @@ def relativeBCPOut(anchor: CoordinateType, BCPOut: CoordinateType) -> Coordinate return (BCPOut[0] - anchor[0], BCPOut[1] - anchor[1]) -def absoluteBCPOut(anchor: CoordinateType, BCPOut: CoordinateType) -> CoordinateType: +def absoluteBCPOut(anchor: PairCollectionType[IntFloatType], + BCPOut: PairCollectionType[IntFloatType]) -> PairType[IntFloatType]: """convert relative outgoing bcp value to an absolute value. :param anchor: The anchor reference point from which the relative BCP value diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index ff91cdf2..a0fbdd1a 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -11,12 +11,11 @@ from fontParts.base import normalizers from fontParts.base.annotations import ( CollectionType, - CoordinateType, - FactorType, + PairCollectionType, + SextupleCollectionType, IntFloatType, + TransformationType, InterpolatableType, - ScaleType, - TransformationMatrixType ) BaseObjectType = TypeVar('BaseObjectType', bound='BaseObject') @@ -134,7 +133,7 @@ def __set__(self, obj: Any, value: Any) -> None: def interpolate(minValue: InterpolatableType, maxValue: InterpolatableType, - factor: FactorType) -> InterpolatableType: + factor: TransformationType) -> InterpolatableType: """Interpolate between two number-like objects. This method performs linear interpolation, calculating a value that is @@ -807,8 +806,8 @@ class TransformationMixin: # --------------- def transformBy(self, - matrix: TransformationMatrixType, - origin: Optional[CoordinateType] = None) -> None: + matrix: SextupleCollectionType[IntFloatType], + origin: Optional[PairCollectionType[IntFloatType]] = None) -> None: """Transform the object according to the given matrix. :param matrix: The :ref:`type-transformation` to apply. @@ -836,7 +835,7 @@ def transformBy(self, self._transformBy(matrix) def _transformBy(self, - matrix: TransformationMatrixType, + matrix: SextupleCollectionType[IntFloatType], **kwargs: Any) -> None: r"""Transform the native object according to the given matrix. @@ -856,7 +855,7 @@ def _transformBy(self, """ self.raiseNotImplementedError() - def moveBy(self, value: CoordinateType) -> None: + def moveBy(self, value: PairCollectionType[IntFloatType]) -> None: """Move the object according to the given coordinates. :param value: The x and y values to move the object by as @@ -870,7 +869,7 @@ def moveBy(self, value: CoordinateType) -> None: value = normalizers.normalizeTransformationOffset(value) self._moveBy(value) - def _moveBy(self, value: CoordinateType, **kwargs: Any) -> None: + def _moveBy(self, value: PairCollectionType[IntFloatType], **kwargs: Any) -> None: r"""Move the native object according to the given coordinates. This is the environment implementation of :meth:`BaseObject.moveBy`. @@ -890,12 +889,14 @@ def _moveBy(self, value: CoordinateType, **kwargs: Any) -> None: self.transformBy(tuple(t), **kwargs) def scaleBy(self, - value: ScaleType, - origin: Optional[CoordinateType] = None) -> None: + value: TransformationType, + origin: Optional[PairCollectionType[IntFloatType]] = None) -> None: """Scale the object according to the given values. - :param value: The x and y values to scale the glyph by as - a :class:`tuple` of two :class:`int` or :class:`float` values. + :param value: The value to scale the glyph by as a single :class:`int` + or :class:`float`, or a :class:`tuple` or :class:`list` of + two :class:`int` or :class:`float` values representing the values + ``(x, y)``. :param origin: The optional point at which the scale should originate as a :ref:`type-coordinate`. Defaults to :obj:`None`, representing an origin of ``(0, 0)``. @@ -913,16 +914,17 @@ def scaleBy(self, self._scaleBy(value, origin=origin) def _scaleBy(self, - value: ScaleType, - origin: Optional[CoordinateType], + value: TransformationType, + origin: Optional[PairCollectionType[IntFloatType]], **kwargs: Any) -> None: r"""Scale the native object according to the given values. This is the environment implementation of :meth:`BaseObject.scaleBy`. - :param value: The x and y values to scale the glyph by as - a :class:`tuple` of two :class:`int` or :class:`float` values. The - value will have been normalized + :param value: The value to scale the glyph by as a single :class:`int` + or :class:`float`, or a :class:`tuple` or :class:`list` of + two :class:`int` or :class:`float` values representing the values + ``(x, y)``. The value will have been normalized with :func:`normalizeTransformationScale`. :param origin: The point at which the scale should originate as a :ref:`type-coordinate` or :obj:`None`. The value will have been @@ -940,7 +942,7 @@ def _scaleBy(self, def rotateBy(self, value: IntFloatType, - origin: Optional[CoordinateType] = None) -> None: + origin: Optional[PairCollectionType[IntFloatType]] = None) -> None: """Rotate the object by the specified value. :param value: The angle at which to rotate the object as an :class:`int` @@ -963,7 +965,7 @@ def rotateBy(self, def _rotateBy(self, value: IntFloatType, - origin: Optional[CoordinateType], + origin: Optional[PairCollectionType[IntFloatType]], **kwargs: Any) -> None: r"""Rotate the native object by the specified value. @@ -987,8 +989,8 @@ def _rotateBy(self, self.transformBy(tuple(t), origin=origin, **kwargs) def skewBy(self, - value: FactorType, - origin: Optional[CoordinateType] = None) -> None: + value: TransformationType, + origin: Optional[PairCollectionType[IntFloatType]] = None) -> None: """Skew the object by the given value. :param value: The value by which to skew the object as either a @@ -1012,8 +1014,8 @@ def skewBy(self, self._skewBy(value, origin=origin) def _skewBy(self, - value: FactorType, - origin: Optional[CoordinateType], + value: TransformationType, + origin: Optional[PairCollectionType[IntFloatType]], **kwargs: Any) -> None: r"""Skew the native object by the given value. @@ -1200,16 +1202,16 @@ class PointPositionMixin: """ ) - def _get_base_position(self) -> CoordinateType: + def _get_base_position(self) -> PairType[IntFloatType]: value = self._get_position() value = normalizers.normalizeCoordinateTuple(value) return value - def _set_base_position(self, value: CoordinateType) -> None: + def _set_base_position(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeCoordinateTuple(value) self._set_position(value) - def _get_position(self) -> CoordinateType: + def _get_position(self) -> PairType[IntFloatType]: """Get the point position of the object. This is the environment implementation of @@ -1226,7 +1228,7 @@ def _get_position(self) -> CoordinateType: """ return (self.x, self.y) - def _set_position(self, value: CoordinateType) -> None: + def _set_position(self, value: PairCollectionType[IntFloatType]) -> None: """Set the point position of the object. This is the environment implementation of diff --git a/Lib/fontParts/base/font.py b/Lib/fontParts/base/font.py index fc5e5c29..d9e02008 100644 --- a/Lib/fontParts/base/font.py +++ b/Lib/fontParts/base/font.py @@ -14,9 +14,9 @@ from fontParts.base.annotations import ( CharacterMappingType, CollectionType, - ColorType, - CoordinateType, - FactorType, + QuadrupleCollectionType, + PairCollectionType, + TransformationType, KerningDictType, ReverseComponentMappingType ) @@ -1137,7 +1137,8 @@ def _getLayer(self, name: str, **kwargs: Any) -> BaseLayer: def newLayer(self, name: str, - color: Optional[ColorType] = None) -> BaseLayer: + color: Optional[QuadrupleCollectionType[IntFloatType]] = None + ) -> BaseLayer: """Create a new layer in the font. :param name: The name of the new layer to create. @@ -1164,7 +1165,7 @@ def newLayer(self, def _newLayer(self, name: str, - color: Optional[ColorType], + color: Optional[QuadrupleCollectionType[IntFloatType]], **kwargs: Any) -> BaseLayer: r"""Create a new layer in the native font. @@ -1718,7 +1719,7 @@ def _get_guidelines(self) -> Tuple[BaseGuideline, ...]: """ return tuple(self._getitem__guidelines(i) - for i in range(self._len__guidelines())) + for i in range(self._len__guidelines())) def _len__guidelines(self) -> int: return self._lenGuidelines() @@ -1769,10 +1770,10 @@ def _getGuidelineIndex(self, guideline: BaseGuideline) -> int: raise FontPartsError("The guideline could not be found.") def appendGuideline(self, - position: Optional[CoordinateType] = None, + position: Optional[PairCollectionType[IntFloatType]] = None, angle: Optional[float] = None, name: Optional[str] = None, - color: Optional[ColorType] = None, + color: Optional[QuadrupleCollectionType[IntFloatType]] = None, guideline: Optional[BaseGuideline] = None ) -> BaseGuideline: """Append a new guideline to the font. @@ -1836,10 +1837,10 @@ def appendGuideline(self, return newGuideline def _appendGuideline(self, - position: Optional[CoordinateType], + position: Optional[PairCollectionType[IntFloatType]], angle: Optional[float], name: Optional[str], - color: Optional[ColorType], + color: Optional[QuadrupleCollectionType[IntFloatType]], guideline: Optional[BaseGuideline], **kwargs) -> BaseGuideline: r"""Append a new guideline to the native font. @@ -1935,7 +1936,7 @@ def _clearGuidelines(self) -> None: # ------------- def interpolate(self, - factor: FactorType, + factor: TransformationType, minFont: BaseFont, maxFont: BaseFont, round: bool = True, @@ -1985,7 +1986,7 @@ def interpolate(self, round=round, suppressError=suppressError) def _interpolate(self, - factor: FactorType, + factor: TransformationType, minFont: BaseFont, maxFont: BaseFont, round: bool, @@ -2233,7 +2234,7 @@ def _getCharacterMapping(self) -> CharacterMappingType: def _get_base_selectedLayers(self) -> Tuple[BaseLayer, ...]: selected = tuple(normalizers.normalizeLayer(layer) for - layer in self._get_selectedLayers()) + layer in self._get_selectedLayers()) return selected def _get_selectedLayers(self) -> Tuple[BaseLayer, ...]: @@ -2297,7 +2298,7 @@ def _set_selectedLayers(self, value: List[BaseLayer]) -> None: def _get_base_selectedLayerNames(self) -> Tuple[str, ...]: selected = tuple(normalizers.normalizeLayerName(name) for - name in self._get_selectedLayerNames()) + name in self._get_selectedLayerNames()) return selected def _get_selectedLayerNames(self) -> Tuple[str, ...]: @@ -2368,7 +2369,7 @@ def _set_selectedLayerNames(self, value: List[str]) -> None: def _get_base_selectedGuidelines(self) -> Tuple[BaseGuideline, ...]: selected = tuple(normalizers.normalizeGuideline(guideline) for - guideline in self._get_selectedGuidelines()) + guideline in self._get_selectedGuidelines()) return selected def _get_selectedGuidelines(self) -> Tuple[BaseGuideline, ...]: diff --git a/Lib/fontParts/base/glyph.py b/Lib/fontParts/base/glyph.py index e2fc01c3..9b68e35d 100644 --- a/Lib/fontParts/base/glyph.py +++ b/Lib/fontParts/base/glyph.py @@ -27,16 +27,15 @@ from fontParts.base.color import Color from fontParts.base.deprecated import DeprecatedGlyph, RemovedGlyph from fontParts.base.annotations import ( - BoundsType, + PairType, CollectionType, - CoordinateType, - ColorType, - FactorType, + PairCollectionType, + QuadrupleCollectionType, + SextupleCollectionType, IntFloatType, + TransformationType, PenType, - PointPenType, - TransformationMatrixType, - ScaleType + PointPenType ) if TYPE_CHECKING: from fontParts.base.font import BaseFont @@ -1016,7 +1015,7 @@ def _clear(self, def appendGlyph(self, other: BaseGlyph, - offset: Optional[CoordinateType] = None) -> None: + offset: Optional[PairCollectionType[IntFloatType]] = None) -> None: """Append data from `other` to new objects in the glyph. This will append: @@ -1045,7 +1044,7 @@ def appendGlyph(self, def _appendGlyph(self, other: BaseGlyph, - offset: CoordinateType) -> None: + offset: PairCollectionType[IntFloatType]) -> None: """Append data from `other` to new objects in the native glyph. This is the environment implementation of :meth:`BaseGlyph.appendGlyph`. @@ -1207,7 +1206,7 @@ def _getContourIndex(self, contour: BaseContour) -> int: def appendContour(self, contour: BaseContour, - offset: Optional[CoordinateType] = None) -> BaseContour: + offset: Optional[PairCollectionType[IntFloatType]] = None) -> BaseContour: """Append the given contour's data to the glyph. :param contour: The :class:`BaseContour` instace containing the source @@ -1231,7 +1230,7 @@ def appendContour(self, def _appendContour(self, contour: BaseContour, - offset: CoordinateType, + offset: PairCollectionType[IntFloatType], **kwargs: Any) -> BaseContour: r"""Append the given contour's data to the native glyph. @@ -1439,8 +1438,8 @@ def _getComponentIndex(self, component: BaseComponent) -> int: def appendComponent(self, baseGlyph: Optional[str] = None, - offset: Optional[CoordinateType] = None, - scale: Optional[ScaleType] = None, + offset: Optional[PairCollectionType[IntFloatType]] = None, + scale: Optional[TransformationType] = None, component: Optional[BaseComponent] = None ) -> BaseComponent: """Append a component to the glyph. @@ -1514,7 +1513,7 @@ def appendComponent(self, def _appendComponent(self, baseGlyph: str, - transformation: Optional[TransformationMatrixType], + transformation: Optional[SextupleCollectionType[IntFloatType]], identifier: Optional[str], **kwargs: Any) -> BaseComponent: r"""Append a component to the native glyph. @@ -1708,8 +1707,8 @@ def _getAnchorIndex(self, anchor: BaseAnchor) -> int: def appendAnchor(self, name: Optional[str] = None, - position: Optional[CoordinateType] = None, - color: Optional[ColorType] = None, + position: Optional[PairCollectionType[IntFloatType]] = None, + color: Optional[QuadrupleCollectionType[IntFloatType]] = None, anchor: Optional[BaseAnchor] = None) -> BaseAnchor: """Append an anchor to the glyph. @@ -1761,8 +1760,8 @@ def appendAnchor(self, def _appendAnchor(self, # type: ignore[return] name: str, - position: Optional[CoordinateType], - color: Optional[ColorType], + position: Optional[PairCollectionType[IntFloatType]], + color: Optional[QuadrupleCollectionType[IntFloatType]], identifier: Optional[str], **kwargs: Any) -> BaseAnchor: r"""Append an anchor to the native glyph. @@ -1941,10 +1940,10 @@ def _getGuidelineIndex(self, guideline: BaseGuideline) -> int: raise FontPartsError("The guideline could not be found.") def appendGuideline(self, - position: Optional[CoordinateType] = None, + position: Optional[PairCollectionType[IntFloatType]] = None, angle: Optional[IntFloatType] = None, name: Optional[str] = None, - color: Optional[ColorType] = None, + color: Optional[QuadrupleCollectionType[IntFloatType]] = None, guideline: Optional[BaseGuideline] = None ) -> BaseGuideline: """Append a guideline to the glyph. @@ -2004,10 +2003,10 @@ def appendGuideline(self, return newGuideline def _appendGuideline(self, # type: ignore[return] - position: CoordinateType, + position: PairCollectionType[IntFloatType], angle: IntFloatType, name: Optional[str], - color: Optional[ColorType], + color: Optional[QuadrupleCollectionType[IntFloatType]], identifier: Optional[str], **kwargs: Any) -> BaseGuideline: r"""Append a guideline to the native glyph. @@ -2273,7 +2272,7 @@ def _autoContourOrder(self, **kwargs: Any) -> None: # -------------- def _transformBy(self, - matrix: TransformationMatrixType, + matrix: SextupleCollectionType[IntFloatType], **kwargs: Any) -> None: r"""Transform the glyph according to the given matrix. @@ -2295,8 +2294,8 @@ def _transformBy(self, guideline.transformBy(matrix) def scaleBy(self, - value: ScaleType, - origin: Optional[CoordinateType] = None, + value: TransformationType, + origin: Optional[PairCollectionType[IntFloatType]] = None, width: bool = False, height: bool = False) -> None: """Scale the glyph according to the given values. @@ -2530,7 +2529,7 @@ def _fromMathGlyph(self, copied.note = mathGlyph.note return copied - def __mul__(self, factor: FactorType) -> BaseGlyph: + def __mul__(self, factor: TransformationType) -> BaseGlyph: """Multiply the current glyph by a given factor. :param factor: The factor by which to multiply the glyph as a @@ -2553,7 +2552,7 @@ def __mul__(self, factor: FactorType) -> BaseGlyph: __rmul__ = __mul__ - def __truediv__(self, factor: FactorType) -> BaseGlyph: + def __truediv__(self, factor: TransformationType) -> BaseGlyph: """Divide the current glyph by a given factor. :param factor: The factor by which to divide the glyph as a @@ -2619,7 +2618,7 @@ def __sub__(self, other: BaseGlyph) -> BaseGlyph: return copied def interpolate(self, - factor: FactorType, + factor: TransformationType, minGlyph: BaseGlyph, maxGlyph: BaseGlyph, round: bool = True, @@ -2869,7 +2868,7 @@ def _isCompatible(self, # Data Queries # ------------ - def pointInside(self, point: CoordinateType) -> bool: + def pointInside(self, point: PairCollectionType[IntFloatType]) -> bool: """Check if `point` lies inside the filled area of the glyph. :param point: The point to check as a :ref:`type-coordinate`. @@ -2885,7 +2884,7 @@ def pointInside(self, point: CoordinateType) -> bool: point = normalizers.normalizeCoordinateTuple(point) return self._pointInside(point) - def _pointInside(self, point: CoordinateType) -> bool: + def _pointInside(self, point: PairCollectionType[IntFloatType]) -> bool: """Check if `point` lies inside the filled area of the native glyph. This is the environment implementation of :meth:`BaseGlyph.pointInside`. @@ -2922,13 +2921,13 @@ def _pointInside(self, point: CoordinateType) -> bool: """ ) - def _get_base_bounds(self) -> Optional[BoundsType]: + def _get_base_bounds(self) -> Optional[PairType[IntFloatType]]: value = self._get_bounds() if value is not None: value = normalizers.normalizeBoundingBox(value) return value - def _get_bounds(self) -> Optional[BoundsType]: + def _get_bounds(self) -> Optional[PairType[IntFloatType]]: """Get the bounds of the native glyph. This is the environment implementation of the :attr:`BaseGlyph.bounds` @@ -3189,9 +3188,9 @@ def _get_image(self) -> BaseImage: # type: ignore[return] def addImage(self, path: Optional[str] = None, data: Optional[bytes] = None, - scale: Optional[ScaleType] = None, - position: Optional[CoordinateType] = None, - color: Optional[ColorType] = None) -> BaseImage: + scale: Optional[TransformationType] = None, + position: Optional[PairCollectionType[IntFloatType]] = None, + color: Optional[QuadrupleCollectionType[IntFloatType]] = None) -> BaseImage: """Set the image in the glyph. The image data may be provided as either the `path` to an image file or @@ -3260,8 +3259,8 @@ def addImage(self, def _addImage(self, # type: ignore[return] data: bytes, - transformation: Optional[TransformationMatrixType], - color: Optional[ColorType]) -> BaseImage: + transformation: Optional[SextupleCollectionType[IntFloatType]], + color: Optional[QuadrupleCollectionType[IntFloatType]]) -> BaseImage: """Set the image in the native glyph. Each environment may have different possible @@ -3326,19 +3325,19 @@ def _clearImage(self, **kwargs: Any) -> None: """ ) - def _get_base_markColor(self) -> Optional[ColorType]: + def _get_base_markColor(self) -> Optional[QuadrupleCollectionType[IntFloatType]]: value = self._get_markColor() if value is None: return None normalizedValue = normalizers.normalizeColor(value) return Color(normalizedValue) - def _set_base_markColor(self, value: Optional[ColorType]) -> None: + def _set_base_markColor(self, value: Optional[QuadrupleCollectionType[IntFloatType]]) -> None: if value is not None: value = normalizers.normalizeColor(value) self._set_markColor(value) - def _get_markColor(self) -> Optional[ColorType]: # type: ignore[return] + def _get_markColor(self) -> Optional[QuadrupleCollectionType[IntFloatType]]: # type: ignore[return] """Get the glyph's mark color. This is the environment implementation of @@ -3356,7 +3355,7 @@ def _get_markColor(self) -> Optional[ColorType]: # type: ignore[return] """ self.raiseNotImplementedError() - def _set_markColor(self, value: Optional[ColorType]) -> None: + def _set_markColor(self, value: Optional[QuadrupleCollectionType[IntFloatType]]) -> None: """Set the glyph's mark color. This is the environment implementation of diff --git a/Lib/fontParts/base/normalizers.py b/Lib/fontParts/base/normalizers.py index 4105b556..b3cce30c 100644 --- a/Lib/fontParts/base/normalizers.py +++ b/Lib/fontParts/base/normalizers.py @@ -1,51 +1,87 @@ # -*- coding: utf8 -*- +from __future__ import annotations +from typing import TYPE_CHECKING, Any, Optional, Tuple, Type, Union from collections import Counter from fontTools.misc.fixedTools import otRound +from fontParts.base.annotations import ( + T, + PairType, + QuadrupleType, + SextupleType, + CollectionType, + PairCollectionType, + QuadrupleCollectionType, + SextupleCollectionType, + IntFloatType, + TransformationType, +) + +if TYPE_CHECKING: + from fontParts.base.layer import BaseLayer + from fontParts.base.glyph import BaseGlyph + from fontParts.base.contour import BaseContour + from fontParts.base.point import BasePoint + from fontParts.base.segment import BaseSegment + from fontParts.base.bPoint import BaseBPoint + from fontParts.base.component import BaseComponent + from fontParts.base.anchor import BaseAnchor + from fontParts.base.guideline import BaseGuideline + # ---- # Font # ---- -def normalizeFileFormatVersion(value): - """ - Normalizes a font's file format version. +def normalizeFileFormatVersion(value: int) -> int: + """Normalize a font's file format version. + + :param value: The file format verison of the font as an :class:`int`. + :return: An :class:`int` representing the normalized file format version. + :raises TypeError: If `value` is not of type :class:`int`. - * **value** must be a :ref:`type-int`. - * Returned value will be a ``int``. """ if not isinstance(value, int): - raise TypeError("File format versions must be instances of " - ":ref:`type-int`, not %s." + raise TypeError("Expected file formmat verison 'value' to be of type int, not %s." % type(value).__name__) return value -def normalizeFileStructure(value): - """ - Normalizes a font's file structure. +def normalizeFileStructure(value: str) -> str: + """Normalize a font's file structure. + + :param value: The file structure to normalize as a :class:`str`. + :return: A :class:`str` representing the normalized file structure. + :raises TypeError: If `value` is not a :class:`str`. - * **value** must be a :ref:`type-string`. - * Returned value will be a ``string``. """ allowedFileStructures = ["zip", "package"] if value not in allowedFileStructures: - raise TypeError("File Structure must be %s, not %s" % (", ".join(allowedFileStructures), value)) + raise TypeError("File Structure must be %s, not %s" + % (", ".join(allowedFileStructures), value)) return value -def normalizeLayerOrder(value, font): - """ - Normalizes layer order. - - ** **value** must be a ``tuple`` or ``list``. - * **value** items must normalize as layer names with - :func:`normalizeLayerName`. - * **value** must contain layers that exist in **font**. - * **value** must not contain duplicate layers. - * Returned ``tuple`` will be unencoded ``unicode`` strings - for each layer name. +def normalizeLayerOrder(value: CollectionType[str], font) -> Tuple[str, ...]: + """Normalize layer order. + + :param value: The layer order to normalize as a :class:`list` + or :class:`tuple` of layer names. Each item in `value`: + - must normalize with :func:`normalizeLayerName`. + - must correspond to an existing layer name in the font. + - must be unique (no duplicates are allowed). + :param font: The :class:`BaseFont` (subclass) instance to which the + normalization applies. + :return: A :class:`tuple` of layer names in their normalized order. + :raises TypeError: + - If `value` is not a :class:`list` or :class:`tuple. + - If any `value` item is not a :class:`str`. + :raises ValueError: + - If `value` contains duplicate values. + - If any `value` item does not exist in `font`. + - If any `value` item is an empty :class:`str`. + """ if not isinstance(value, (tuple, list)): raise TypeError("Layer order must be a list, not %s." @@ -64,14 +100,20 @@ def normalizeLayerOrder(value, font): return tuple(value) -def normalizeDefaultLayerName(value, font): - """ - Normalizes default layer name. +def normalizeDefaultLayerName(value: str, font) -> str: + """Normalize a default layer name. + + :param value: The layer name to normalize as a :class:`str`. The value: + - must normalize as layer name with :func:`normalizeLayerName`. + - must be a layer in `font`. + :param font: The :class:`BaseFont` (subclass) instance to which the + normalization applies. + :return: A :class:`str` representing the normalized default layer name. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: + - If `value` does not exist in `font`. + - If `value` is an empty :class:`str`. - * **value** must normalize as layer name with - :func:`normalizeLayerName`. - * **value** must be a layer in **font**. - * Returned value will be an unencoded ``unicode`` string. """ value = normalizeLayerName(value) if value not in font.layerOrder: @@ -79,15 +121,21 @@ def normalizeDefaultLayerName(value, font): return str(value) -def normalizeGlyphOrder(value): - """ - Normalizes glyph order. +def normalizeGlyphOrder(value: CollectionType[str]) -> Tuple[str, ...]: + """Normalize glyph order. + + :param value: The glyph order to normalize as a :class:`list` + or :class:`tuple` of glyph names. Each item in `value`: + - must normalize as glyph names with :func:`normalizeGlyphName`. + - must be unique (no duplicates are allowed). + :return: A :class:`tuple` of glyph names in their normalized order. + :raises TypeError: + - If `value` is not a :class:`list` or :class:`tuple. + - If any `value` item is not a :class:`str`. + :raises ValueError: + - If `value` contains duplicate values. + - If any `value` item is an empty :class:`str`. - ** **value** must be a ``tuple`` or ``list``. - * **value** items must normalize as glyph names with - :func:`normalizeGlyphName`. - * **value** must not repeat glyph names. - * Returned value will be a ``tuple`` of unencoded ``unicode`` strings. """ if not isinstance(value, (tuple, list)): raise TypeError("Glyph order must be a list, not %s." @@ -105,17 +153,26 @@ def normalizeGlyphOrder(value): # Kerning # ------- +def normalizeKerningKey(value: PairCollectionType[str]) -> PairType[str]: + """Normalize a kerning key. + + :param value: The kerning key to normalize as a :class:`tuple` + or :class:`list` containing two items representing the left and right + kerning key groups. Each item in `value`: + - must be a :class:`str`. + - must be at least one character long. + :return: The normalized kerning key as a :class:`tuple` of :class:`str` items. + :raises TypeError: + - If `value` is not a :class:`list` or :class:`tuple. + - If any `value` item is not a :class:`str`. + :raises ValueError: + - If `value` does not a contain a pair of items. + - If any `value` item is an empty :class:`str`. + - If the left kerning key group starts with 'public.' but does not + start with 'public.kern1.'. + - If the right kerning key group starts with 'public.' but does not + start with 'public.kern2.'. -def normalizeKerningKey(value): - """ - Normalizes kerning key. - - * **value** must be a ``tuple`` or ``list``. - * **value** must contain only two members. - * **value** items must be :ref:`type-string`. - * **value** items must be at least one character long. - * Returned value will be a two member ``tuple`` of unencoded - ``unicode`` strings. """ if not isinstance(value, (tuple, list)): raise TypeError("Kerning key must be a tuple instance, not %s." @@ -140,30 +197,33 @@ def normalizeKerningKey(value): return tuple(value) -def normalizeKerningValue(value): - """ - Normalizes kerning value. +def normalizeKerningValue(value: IntFloatType) -> IntFloatType: + """Normalize a kerning value. + + :param value: The kerning value to normalize as an :class:`int` or :class:`float`. + :return: An :class:`int` or :class:`flaot` representing the normalized + kerning value. + raises TypeError: If `value` is not an :class:`int` or a :class:`float`. - * **value** must be an :ref:`type-int-float`. - * Returned value is the same type as input value. """ if not isinstance(value, (int, float)): raise TypeError("Kerning value must be a int or a float, not %s." % type(value).__name__) return value - # ------ # Groups # ------ -def normalizeGroupKey(value): - """ - Normalizes group key. - * **value** must be a :ref:`type-string`. - * **value** must be least one character long. - * Returned value will be an unencoded ``unicode`` string. +def normalizeGroupKey(value: str) -> str: + """Normalize a group key. + + :param value: The group key to normalize as a non-empty :class:`str`. + :return: A :class:`str` representing the normalized group key. + :raises TyypeError: If `value` is not a :class:`str`. + raises ValueError: If `value` is an empty :class:`str`. + """ if not isinstance(value, str): raise TypeError("Group key must be a string, not %s." @@ -173,14 +233,17 @@ def normalizeGroupKey(value): return value -def normalizeGroupValue(value): - """ - Normalizes group value. +def normalizeGroupValue(value: CollectionType[str]) -> Tuple[str, ...]: + """Normalize a group value. + + :param value: The group value to normalize as a :class:`list` + or :class:`tuple` of :class:`str` items representing the names of the + glyphs in the group. All `value` items must + normalize :func:`normalizeGlyphName`. + :return: A :class:`tuple` of :class:`str` items representing the normalized + gorup value. + :raises TyypeError: If `value` is not a :class:`list` or :class:`tuple`. - * **value** must be a ``list``. - * **value** items must normalize as glyph names with - :func:`normalizeGlyphName`. - * Returned value will be a ``tuple`` of unencoded ``unicode`` strings. """ if not isinstance(value, (tuple, list)): raise TypeError("Group value must be a list, not %s." @@ -193,12 +256,13 @@ def normalizeGroupValue(value): # Features # -------- -def normalizeFeatureText(value): - """ - Normalizes feature text. +def normalizeFeatureText(value: str) -> str: + """Normalize feature text. + + :param value: The feature text to normalize as a :class:`str`. + :return: A :class:`str` representing the normalized feature text. + :raises TypeError: If `value` is not a :class:`str`. - * **value** must be a :ref:`type-string`. - * Returned value will be an unencoded ``unicode`` string. """ if not isinstance(value, str): raise TypeError("Feature text must be a string, not %s." @@ -210,13 +274,14 @@ def normalizeFeatureText(value): # Lib # --- -def normalizeLibKey(value): - """ - Normalizes lib key. +def normalizeLibKey(value: str) -> str: + """Normalize a lib key. + + :param value: The lib key to normalize as a non-empty :class:`str`. + :return: A :class:`str` representing the noramlized lib key. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is an empty :class:`str`. - * **value** must be a :ref:`type-string`. - * **value** must be at least one character long. - * Returned value will be an unencoded ``unicode`` string. """ if not isinstance(value, str): raise TypeError("Lib key must be a string, not %s." @@ -226,12 +291,17 @@ def normalizeLibKey(value): return value -def normalizeLibValue(value): - """ - Normalizes lib value. +def normalizeLibValue(value: Any) -> Any: + """Normalize a lib value. + + If `value` is a collection (:class:`list`, :class:`tuple`, or :class:`dict`), + its elements will be normalized recursively. + + :param value: The lib value to normalize. The value (or any item) must not + be :obj:`None`. + :return: The normalized lib value, of the same type as `value`. + :raises ValueError: If `value` or any of it's items is :obj:`None`. - * **value** must not be ``None``. - * Returned value is the same type as the input value. """ if value is None: raise ValueError("Lib value must not be None.") @@ -249,24 +319,26 @@ def normalizeLibValue(value): # Layer # ----- -def normalizeLayer(value): - """ - Normalizes layer. +def normalizeLayer(value: BaseLayer) -> BaseLayer: + """Normalize a layer. + + :param value: The layer to normalize as an instance of :class:`BaseLayer`. + :return: The normalized :class:`BaseLayer`instance. + :raises TypeError: If `value` is not an instance of :class:`BaseLayer`. - * **value** must be a instance of :class:`BaseLayer` - * Returned value is the same type as the input value. """ from fontParts.base.layer import BaseLayer return normalizeInternalObjectType(value, BaseLayer, "Layer") -def normalizeLayerName(value): - """ - Normalizes layer name. +def normalizeLayerName(value: str) -> str: + """Normalize a layer name. + + :param value: The layer name to normalize as a non-empty :class:`str`. + :return: A :class:`str` representing the normalized layer name. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is an empty :class:`str`. - * **value** must be a :ref:`type-string`. - * **value** must be at least one character long. - * Returned value will be an unencoded ``unicode`` string. """ if not isinstance(value, str): raise TypeError("Layer names must be strings, not %s." @@ -280,42 +352,49 @@ def normalizeLayerName(value): # Glyph # ----- -def normalizeGlyph(value): - """ - Normalizes glyph. +def normalizeGlyph(value: BaseGlyph) -> BaseGlyph: + """Normalize a glyph. + + :param value: The glyph to normalize as an instance of :class:`BaseGlyph`. + :return: The normalized :class:`BaseGlyph` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseGlyph`. - * **value** must be a instance of :class:`BaseGlyph` - * Returned value is the same type as the input value. """ from fontParts.base.glyph import BaseGlyph return normalizeInternalObjectType(value, BaseGlyph, "Glyph") -def normalizeGlyphName(value): - """ - Normalizes glyph name. +def normalizeGlyphName(value: str) -> str: + """Normalize a glyph's name. + + :param value: The glyph name to normalize as a non-empty :class:`str`. + :return: A :class:`str` representing the normalized glyph name. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is an empty string. - * **value** must be a :ref:`type-string`. - * **value** must be at least one character long. - * Returned value will be an unencoded ``unicode`` string. """ if not isinstance(value, str): - raise TypeError("Glyph names must be strings, not %s." - % type(value).__name__) + raise TypeError("Glyph names must be strings, not %s." % type(value).__name__) if len(value) < 1: raise ValueError("Glyph names must be at least one character long.") return value -def normalizeGlyphUnicodes(value): - """ - Normalizes glyph unicodes. +def normalizeGlyphUnicodes(value: CollectionType[int]) -> Tuple[int, ...]: + """Normalize a glyph's unicodes. + + :param value: The glyph unicodes to normalize as a :class:`list` + or :class:`tuple` of Unicode values. Each item in `value`: + - must normalize as glyph unicodes with :func:`normalizeGlyphUnicode`. + - must be unique (no duplicates are allowed). + :return: A :class:`tuple of :class:`int` values representing the noramlized + glyph unicodes. + :raises TypeError: If `value` is not a :class:`list` or :class:`tuple`. + :raises ValueError: + - If `value` contains duplicate values. + - If any `value` item is not a valid hexadelimal value. + - If any `value` item is not within the unicode range. - * **value** must be a ``list``. - * **value** items must normalize as glyph unicodes with - :func:`normalizeGlyphUnicode`. - * **value** must not repeat unicode values. - * Returned value will be a ``tuple`` of ints. """ if not isinstance(value, (tuple, list)): raise TypeError("Glyph unicodes must be a list, not %s." @@ -327,16 +406,20 @@ def normalizeGlyphUnicodes(value): return tuple(values) -def normalizeGlyphUnicode(value): - """ - Normalizes glyph unicode. +def normalizeGlyphUnicode(value: Union[int, str]) -> int: + """Normalize a glyph's unicode. + + :param value: The glyph Unicode value to normalize as an :class:`int` or a + hexadecimal :class:`str`. The value must be within the unicode range. + :return: An :class:`int` representing the noramlized unicode value. + :raises TypeError: If `value` is not and :class:`int` or a :class:`str`. + :raises ValueError: + - If `value` is not a valid hexadelimal value. + - If `value` is not within the unicode range. - * **value** must be an int or hex (represented as a string). - * **value** must be in a unicode range. - * Returned value will be an ``int``. """ if not isinstance(value, (int, str)) or isinstance(value, bool): - raise TypeError("Glyph unicode must be a int or hex string, not %s." + raise TypeError("Glyph unicode must be an int or hex string, not %s." % type(value).__name__) if isinstance(value, str): try: @@ -348,12 +431,13 @@ def normalizeGlyphUnicode(value): return value -def normalizeGlyphWidth(value): - """ - Normalizes glyph width. +def normalizeGlyphWidth(value: IntFloatType) -> IntFloatType: + """Normalize a glyph's width. + + :param value: The glyph width to normalize as an :class:`int` or :class:`float`. + :return: The normalized glyph width, of the same type as `value`. + :raises TypeError: if `value` is not an :class:`int` or a :class:`float`. - * **value** must be a :ref:`type-int-float`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)): raise TypeError("Glyph width must be an :ref:`type-int-float`, not %s." @@ -361,12 +445,14 @@ def normalizeGlyphWidth(value): return value -def normalizeGlyphLeftMargin(value): - """ - Normalizes glyph left margin. +def normalizeGlyphLeftMargin(value: Optional[IntFloatType]) -> Optional[IntFloatType]: + """Normalize a glyph's left margin. + + :param value: The glyph left margin to normalize as + an :class:`int`, :class:`float`, or :obj:`None`. + :return: The normalized glyph left margin, of the same type as `value`. + :raises TypeError: if `value` is not an :class:`int` or a :class:`float`. - * **value** must be a :ref:`type-int-float` or `None`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)) and value is not None: raise TypeError("Glyph left margin must be an :ref:`type-int-float`, " @@ -374,12 +460,14 @@ def normalizeGlyphLeftMargin(value): return value -def normalizeGlyphRightMargin(value): - """ - Normalizes glyph right margin. +def normalizeGlyphRightMargin(value: Optional[IntFloatType]) -> Optional[IntFloatType]: + """Normalize a glyph's right margin. + + :param value: The glyph right margin to normalize as + an :class:`int`, :class:`float`, or :obj:`None`. + :return: The normalized glyph right margin, of the same type as `value`. + :raises TypeError: if `value` is not an :class:`int` or a :class:`float`. - * **value** must be a :ref:`type-int-float` or `None`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)) and value is not None: raise TypeError("Glyph right margin must be an :ref:`type-int-float`, " @@ -387,12 +475,13 @@ def normalizeGlyphRightMargin(value): return value -def normalizeGlyphHeight(value): - """ - Normalizes glyph height. +def normalizeGlyphHeight(value: IntFloatType) -> IntFloatType: + """Normalize a glyph's height. + + :param value: The glyph height to normalize as an :class:`int` or :class:`float`. + :return: The normalized glyph height, of the same type as `value`. + :raises TypeError: if `value` is not an :class:`int` or a :class:`float`. - * **value** must be a :ref:`type-int-float`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)): raise TypeError("Glyph height must be an :ref:`type-int-float`, not " @@ -400,12 +489,14 @@ def normalizeGlyphHeight(value): return value -def normalizeGlyphBottomMargin(value): - """ - Normalizes glyph bottom margin. +def normalizeGlyphBottomMargin(value: Optional[IntFloatType]) -> Optional[IntFloatType]: + """Normalize a glyph's bottom margin. + + :param value: The glyph bottom margin to normalize as + an :class:`int`, :class:`float`, or :obj:`None`. + :return: The normalized glyph bottom margin, of the same type as `value`. + :raises TypeError: if `value` is not an :class:`int` or a :class:`float`. - * **value** must be a :ref:`type-int-float` or `None`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)) and value is not None: raise TypeError("Glyph bottom margin must be an " @@ -414,25 +505,31 @@ def normalizeGlyphBottomMargin(value): return value -def normalizeGlyphTopMargin(value): - """ - Normalizes glyph top margin. +def normalizeGlyphTopMargin(value: Optional[IntFloatType]) -> Optional[IntFloatType]: + """Normalize a glyph's top margin. + + :param value: The glyph top margin to normalize as + an :class:`int`, :class:`float`, or :obj:`None`. + :return: The normalized glyph top margin, of the same type as `value`. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. - * **value** must be a :ref:`type-int-float` or `None`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)) and value is not None: - raise TypeError("Glyph top margin must be an :ref:`type-int-float`, " - "not %s." % type(value).__name__) + raise TypeError("Glyph top margin must be an " + ":ref:`type-int-float`, not %s." + % type(value).__name__) return value -def normalizeGlyphFormatVersion(value): - """ - Normalizes glyph format version for saving to XML string. +def normalizeGlyphFormatVersion(value: IntFloatType) -> int: + """Normalize a glyph's format version for saving to XML string. + + :param value: The format version to normalize as + an :class:`int` or a :class:`float` equal to either ``1`` or ``2``. + :return: An :class:`int` representing the normalized glyph format version. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. + :raises ValueError: If `value` is not equal to ``1`` or ``2``. - * **value** must be a :ref:`type-int-float` of either 1 or 2. - * Returned value will be an int. """ if not isinstance(value, (int, float)): raise TypeError("Glyph Format Version must be an " @@ -449,12 +546,13 @@ def normalizeGlyphFormatVersion(value): # ------- -def normalizeContour(value): - """ - Normalizes contour. +def normalizeContour(value: BaseContour) -> BaseContour: + """Normalize a contour. + + :param value: The contour to normalize as an instance of :class:`BaseContour`. + :return: The normalized :class:`BaseContour` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseContour`. - * **value** must be a instance of :class:`BaseContour` - * Returned value is the same type as the input value. """ from fontParts.base.contour import BaseContour return normalizeInternalObjectType(value, BaseContour, "Contour") @@ -464,26 +562,26 @@ def normalizeContour(value): # Point # ----- -def normalizePointType(value): - """ - Normalizes point type. - - * **value** must be an string. - * **value** must be one of the following: - - +----------+ - | move | - +----------+ - | line | - +----------+ - | offcurve | - +----------+ - | curve | - +----------+ - | qcurve | - +----------+ - - * Returned value will be an unencoded ``unicode`` string. +def normalizePointType(value: str) -> str: + """Normalize a point type. + + :param value: The point type to normalize as a :class:`str` containing one + of the following values: + + +----------------+---------------------------------------------------+ + | Value | Description | + +----------------+---------------------------------------------------+ + | ``'move'`` | The first point in an open contour. | + | ``'line'`` | A straight line from the previous point. | + | ``'offcurve'`` | A control point in a curve or qcurve. | + | ``'curve'`` | A cubic Bézier curve from the previous point. | + | ``'qcurve'`` | A quadratic Bézier curve from the previous point. | + +----------------+---------------------------------------------------+ + + :return: A :class:`str` representing the normalized point type. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is not one of the allowed types. + """ allowedTypes = ['move', 'line', 'offcurve', 'curve', 'qcurve'] if not isinstance(value, str): @@ -495,13 +593,14 @@ def normalizePointType(value): return value -def normalizePointName(value): - """ - Normalizes point name. +def normalizePointName(value: str) -> str: + """Normalize a point name. + + :param value: The point name to normalize as a non-empty :class:`str`. + :return: A :class:`str` representing the normalized point name. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is an empty :class:`str`. - * **value** must be a :ref:`type-string`. - * **value** must be at least one character long. - * Returned value will be an unencoded ``unicode`` string. """ if not isinstance(value, str): raise TypeError("Point names must be strings, not %s." @@ -511,12 +610,13 @@ def normalizePointName(value): return value -def normalizePoint(value): - """ - Normalizes point. +def normalizePoint(value: BasePoint) -> BasePoint: + """Normalize a point. + + :param value: The contour to normalize as an instance of :class:`BasePoint`. + :return: The normalized :class:`BasePoint` instance. + :raises TypeError: If `value` is not an instance of :class:`BasePoint`. - * **value** must be a instance of :class:`BasePoint` - * Returned value is the same type as the input value. """ from fontParts.base.point import BasePoint return normalizeInternalObjectType(value, BasePoint, "Point") @@ -526,35 +626,37 @@ def normalizePoint(value): # ------- -def normalizeSegment(value): - """ - Normalizes segment. +def normalizeSegment(value: BaseSegment) -> BaseSegment: + """Normalize a segment. + + :param value: The contour to normalize as an instance of :class:`BaseSegment`. + :return: The normalized :class:`BaseSegment` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseSegment`. - * **value** must be a instance of :class:`BaseSegment` - * Returned value is the same type as the input value. """ from fontParts.base.segment import BaseSegment return normalizeInternalObjectType(value, BaseSegment, "Segment") -def normalizeSegmentType(value): - """ - Normalizes segment type. - - * **value** must be a :ref:`type-string`. - * **value** must be one of the following: - - +--------+ - | move | - +--------+ - | line | - +--------+ - | curve | - +--------+ - | qcurve | - +--------+ - - * Returned value will be an unencoded ``unicode`` string. +def normalizeSegmentType(value: str) -> str: + """Normalize a segment type. + + :param value: The segment type to normalize as a :class:`str` containing one + of the following values: + + +--------------+-------------------------------------------------+ + | Value | Description | + +--------------+-------------------------------------------------+ + | ``'move'`` | The start of an open contour. | + | ``'line'`` | A straight segment between two on-curve points. | + | ``'curve'`` | A cubic Bézier curve segment. | + | ``'qcurve'`` | A quadratic Bézier curve segment. | + +--------------+-------------------------------------------------+ + + :return: A :class:`str` representing the normalized segment type. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is not one of the allowed types. + """ allowedTypes = ['move', 'line', 'curve', 'qcurve'] if not isinstance(value, str): @@ -570,31 +672,35 @@ def normalizeSegmentType(value): # BPoint # ------ -def normalizeBPoint(value): - """ - Normalizes bPoint. +def normalizeBPoint(value: BaseBPoint) -> BaseBPoint: + """Normalize a bPoint. + + :param value: The contour to normalize as an instance of :class:`BaseBPoint`. + :return: The normalized :class:`BaseBPoint` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseBPoint`. - * **value** must be a instance of :class:`BaseBPoint` - * Returned value is the same type as the input value. """ from fontParts.base.bPoint import BaseBPoint return normalizeInternalObjectType(value, BaseBPoint, "bPoint") -def normalizeBPointType(value): - """ - Normalizes bPoint type. +def normalizeBPointType(value: str) -> str: + """Normalize a bPoint type. - * **value** must be an string. - * **value** must be one of the following: + :param value: The bPoint type to normalize as a :class:`str` containing one + of the following values: - +--------+ - | corner | - +--------+ - | curve | - +--------+ + +--------------+---------------------------------------------------------+ + | Value | Description | + +--------------+---------------------------------------------------------+ + | ``'curve'`` | A point where bcpIn and bcpOut are smooth (linked). | + | ``'corner'`` | A point where bcpIn and bcpOut are not smooth (linked). | + +--------------+---------------------------------------------------------+ + + :return: A :class:`str` representing the normalized bPoint type. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is not one of the allowed types. - * Returned value will be an unencoded ``unicode`` string. """ allowedTypes = ['corner', 'curve'] if not isinstance(value, str): @@ -610,25 +716,26 @@ def normalizeBPointType(value): # Component # --------- -def normalizeComponent(value): - """ - Normalizes component. +def normalizeComponent(value: BaseComponent) -> BaseComponent: + """Normalize a component. + + :param value: The contour to normalize as an instance of :class:`BaseComponent`. + :return: The normalized :class:`BaseComponent` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseComponent`. - * **value** must be a instance of :class:`BaseComponent` - * Returned value is the same type as the input value. """ from fontParts.base.component import BaseComponent return normalizeInternalObjectType(value, BaseComponent, "Component") -def normalizeComponentScale(value): - """ - Normalizes component scale. +def normalizeComponentScale(value: PairCollectionType[IntFloatType]) -> PairType[float]: + """Normalize a component scale. + + :param value: The component scale to noramlize as a :class:`list` + or :class:`tuple` of two :class:`int` or :class:`float` values. + :return: A :class:`tuple` of two :class:`float` values representing the + normalized component scale. - * **value** must be a `tuple`` or ``list``. - * **value** must have exactly two items. - These items must be instances of :ref:`type-int-float`. - * Returned value is a ``tuple`` of two ``float``\s. """ if not isinstance(value, (list, tuple)): raise TypeError("Component scale must be a tuple " @@ -650,24 +757,27 @@ def normalizeComponentScale(value): # Anchor # ------ -def normalizeAnchor(value): - """ - Normalizes anchor. +def normalizeAnchor(value: BaseAnchor) -> BaseAnchor: + """Normalize an anchor. + + :param value: The contour to normalize as an instance of :class:`BaseAnchor`. + :return: The normalized :class:`BaseAnchor` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseAnchor`. - * **value** must be a instance of :class:`BaseAnchor` - * Returned value is the same type as the input value. """ from fontParts.base.anchor import BaseAnchor return normalizeInternalObjectType(value, BaseAnchor, "Anchor") -def normalizeAnchorName(value): - """ - Normalizes anchor name. +def normalizeAnchorName(value: str) -> str: + """Normalize an anchor name. + + :param value: The anchor name to normalize as a non-empty :class:`str`, + or :obj:`None`. + :return: A :class:`str` representing the normalized anchor name, or :obj:`None`. + :raises TypeError: If `value` is not a :class:`str` or :obj:`None`. + :raises ValueError: If `value` is an empty :class:`str`. - * **value** must be a :ref:`type-string` or ``None``. - * **value** must be at least one character long if :ref:`type-string`. - * Returned value will be an unencoded ``unicode`` string or ``None``. """ if value is None: return None @@ -684,24 +794,26 @@ def normalizeAnchorName(value): # Guideline # --------- -def normalizeGuideline(value): - """ - Normalizes guideline. +def normalizeGuideline(value: BaseGuideline) -> BaseGuideline: + """Normalize a guideline. + + :param value: The contour to normalize as an instance of :class:`BaseGuideline`. + :return: The normalized :class:`BaseGuideline` instance. + :raises TypeError: If `value` is not an instance of :class:`BaseGuideline`. - * **value** must be a instance of :class:`BaseGuideline` - * Returned value is the same type as the input value. """ from fontParts.base.guideline import BaseGuideline return normalizeInternalObjectType(value, BaseGuideline, "Guideline") -def normalizeGuidelineName(value): - """ - Normalizes guideline name. +def normalizeGuidelineName(value: str) -> str: + """Normalize a guideline name. + + :param value: The guideline name to normalize as a non-empty :class:`str` + :return: A :class:`str` representing the normalized guideline name. + :raises TypeError: If `value` is not a :class:`str`. + :raises ValueError: If `value` is an empty :class:`str`. - * **value** must be a :ref:`type-string`. - * **value** must be at least one character long. - * Returned value will be an unencoded ``unicode`` string. """ if not isinstance(value, str): raise TypeError("Guideline names must be strings, not %s." @@ -716,12 +828,15 @@ def normalizeGuidelineName(value): # Generic # ------- -def normalizeInternalObjectType(value, cls, name): - """ - Normalizes an internal object type. +def normalizeInternalObjectType(value: object, cls: Type[T], name: str) -> T: + """Normalize an internal object type. + + :param value: The object instance to normalize. + :param cls: The class against which to check the type of `value`. + :param name: The name of the variable being checked (for error messages). + :return: The normalized object, of the same type as `value`. + :raise TypeError: If `value` is not an instance of `cls`. - * **value** must be a instance of **cls**. - * Returned value is the same type as the input value. """ if not isinstance(value, cls): raise TypeError("%s must be a %s instance, not %s." @@ -729,12 +844,14 @@ def normalizeInternalObjectType(value, cls, name): return value -def normalizeBoolean(value): - """ - Normalizes a boolean. +def normalizeBoolean(value: int) -> bool: + """Normalize a boolean. + + :param value: The boolean to normalize as an :class:`int` of ``0`` or + ``1``, or a :class:`bool`. + :return: A :class:`bool` representing the normalized value. + :raise ValueError: If `value` is not a valid :class:`bool` or :class:`int`. - * **value** must be an ``int`` with value of 0 or 1, or a ``bool``. - * Returned value will be a boolean. """ if isinstance(value, int) and value in (0, 1): value = bool(value) @@ -746,12 +863,13 @@ def normalizeBoolean(value): # Identification -def normalizeIndex(value): - """ - Normalizes index. +def normalizeIndex(value: Optional[int]) -> Optional[int]: + """Normalize an index. + + :param value: The index to normalize as an :class:`int`, or :obj:`None`. + :return: The normalized index, of the same type as `value`. + :raise ValueError: If `value` is not an :class:`int` or :obj:`None`. - * **value** must be an ``int`` or ``None``. - * Returned value is the same type as the input value. """ if value is not None: if not isinstance(value, int): @@ -760,14 +878,20 @@ def normalizeIndex(value): return value -def normalizeIdentifier(value): - """ - Normalizes identifier. +def normalizeIdentifier(value: Optional[str]) -> Optional[str]: + """Normalize an identifier. + + :param value: The identifier to normalize as a non-empty :class:`str`, + or :obj:`None`. The value: + - must not be longer than 100 characters. + - must not contain a character out of the range ``0x20`` - ``0x7E``. + :return: The normalized identifier, of the same type as `value`. + :raises TypeError: If `value` is not a :class:`str` or :obj:`None`. + :raises ValueError: + - If `value` is an empty :class:`str`. + - If `value` is longer than 100 characters. + - If `value` contains a character outside the range ``0x20`` - ``0x7E``. - * **value** must be an :ref:`type-string` or `None`. - * **value** must not be longer than 100 characters. - * **value** must not contain a character out the range of 0x20 - 0x7E. - * Returned value is an unencoded ``unicode`` string. """ if value is None: return value @@ -783,50 +907,54 @@ def normalizeIdentifier(value): v = ord(c) if v < 0x20 or v > 0x7E: raise ValueError("The identifier string ('%s') contains a " - "character out size of the range 0x20 - 0x7E." + "character outside of the range 0x20 - 0x7E." % value) return value # Coordinates -def normalizeX(value): - """ - Normalizes x coordinate. +def normalizeX(value: IntFloatType) -> IntFloatType: + """Normalize an x-coordinate. + + :param value: The x-coordinate to normalize as an :class:`int` or :class:`float`. + :return: The normalized glyph top margin, of the same type as `value`. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. - * **value** must be an :ref:`type-int-float`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)): - raise TypeError("X coordinates must be instances of " + raise TypeError("X-coordinates must be instances of " ":ref:`type-int-float`, not %s." % type(value).__name__) return value -def normalizeY(value): - """ - Normalizes y coordinate. +def normalizeY(value: IntFloatType) -> IntFloatType: + """Normalize a y-coordinate. + + :param value: The y-coordinate to normalize as an :class:`int` or :class:`float`. + :return: The normalized glyph top margin, of the same type as `value`. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. - * **value** must be an :ref:`type-int-float`. - * Returned value is the same type as the input value. """ if not isinstance(value, (int, float)): - raise TypeError("Y coordinates must be instances of " + raise TypeError("Y-coordinates must be instances of " ":ref:`type-int-float`, not %s." % type(value).__name__) return value -def normalizeCoordinateTuple(value): - """ - Normalizes coordinate tuple. +def normalizeCoordinateTuple(value: PairCollectionType[IntFloatType] + ) -> PairType[IntFloatType]: + """Normalize a coordinate tuple. + + :param value: The coordinate tuple to noramlize as a :class:`list` + or :class:`tuple` of two :class:`int` or :class:`float` values. + :return: :return: A :class:`tuple` of two values of the same type as the + items in `value`, representing the normalized coordinates. + :raises TypeError: If `value` is not a :class:`list` or :class:`tuple`. + :raises ValueError: If `value` does not contain exactly two items. - * **value** must be a ``tuple`` or ``list``. - * **value** must have exactly two items. - * **value** items must be an :ref:`type-int-float`. - * Returned value is a ``tuple`` of two values of the same type as - the input values. """ if not isinstance(value, (tuple, list)): raise TypeError("Coordinates must be tuple instances, not %s." @@ -840,21 +968,31 @@ def normalizeCoordinateTuple(value): return (x, y) -def normalizeBoundingBox(value): - """ - Normalizes bounding box. +def normalizeBoundingBox(value: QuadrupleCollectionType[IntFloatType] + ) -> QuadrupleType[float]: + """Normalize a bounding box. + + :param value: The bounding box to normalize as a :class:`list` + or :class:`tuple` of four :class:`int` or :class:`float` values + representing the coordinates (in order) xMin, yMin, xMax, and yMax. The + xMin and yMin values must be less than or equal to the corresponding xMax, + yMax values. + :return: A :class:`tuple` of four :class:`float` values representing the + normalized bounding box. + :raises TypeError: + - If `value` is not a :class:`tuple` or :class:`list`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: + - If `value` does not contain exactly four items. + - If xMin is greater than xMax. + - If yMin is greater than yMax. - * **value** must be an ``tuple`` or ``list``. - * **value** must have exactly four items. - * **value** items must be :ref:`type-int-float`. - * xMin and yMin must be less than or equal to the corresponding xMax, yMax. - * Returned value will be a tuple of four ``float``. """ if not isinstance(value, (tuple, list)): - raise TypeError("Bounding box be tuple instances, not %s." + raise TypeError("Bounding box must be tuple instances, not %s." % type(value).__name__) if len(value) != 4: - raise ValueError("Bounding box be tuples containing four items, not " + raise ValueError("Bounding box must be tuples containing four items, not " "%d." % len(value)) for v in value: if not isinstance(v, (int, float)): @@ -870,11 +1008,14 @@ def normalizeBoundingBox(value): return tuple(float(v) for v in value) -def normalizeArea(value): - """ - Normalizes area. +def normalizeArea(value: IntFloatType) -> float: + """Normalize an area. + + :param value: The area to normalize as a positive :class:`int` or :class:`float`. + :return: A positive :class:`float` representing the normalized area. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. + :raises ValueError: If `value` is not a positive value. - * **value** must be a positive :ref:`type-int-float`. """ if not isinstance(value, (int, float)): raise TypeError("Area must be an instance of :ref:`type-int-float`, " @@ -885,14 +1026,17 @@ def normalizeArea(value): return float(value) -def normalizeRotationAngle(value): - """ - Normalizes an angle. +def normalizeRotationAngle(value: IntFloatType) -> float: + """Normalize an angle. + + :param value: The angle to normalize as an :class:`int` or a :class:`float` + between ``-360 and ``360``. If the value is negative, it is normalized by + adding it to ``360``. + :return: A :class:`float` between ``0.0`` and ``360.0`` representing the + normalized rotation angle. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. + :raises ValueError: If `value` is not between ``-360 and ``360``. - * Value must be a :ref:`type-int-float`. - * Value must be between -360 and 360. - * If the value is negative, it is normalized by adding it to 360 - * Returned value is a ``float`` between 0 and 360. """ if not isinstance(value, (int, float)): raise TypeError("Angle must be instances of " @@ -907,14 +1051,23 @@ def normalizeRotationAngle(value): # Color -def normalizeColor(value): - """ - Normalizes :ref:`type-color`. +def normalizeColor(value: QuadrupleCollectionType[IntFloatType] + ) -> QuadrupleType[float]: + """Normalize a color. + + :param value: The color to normalize as a :class:`list` or :class:`tuple` + of four :class:`int` or :class:`float` values between ``0`` and ``1``, + corresponding to the red, green, blue, and alpha (RGBA) channels, in that + order. + :return: A :class:`tuple` of four :class:`float` values representing the + noramlized color. + :raises TypeError: + - If `value` is not a :class:`list` or :class:`tuple`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: + - If `value` does not contain exactly four items. + - If any `value` item is not between ``0`` and ``1``. - * **value** must be an ``tuple`` or ``list``. - * **value** must have exactly four items. - * **value** color components must be between 0 and 1. - * Returned value is a ``tuple`` containing four ``float`` values. """ from fontParts.base.color import Color if not isinstance(value, (tuple, list, Color)): @@ -935,12 +1088,13 @@ def normalizeColor(value): # Note -def normalizeGlyphNote(value): - """ - Normalizes Glyph Note. +def normalizeGlyphNote(value: str) -> str: + """Normalize a glyph note. + + :param value: The glyph note to normalize as a :class:`str`. + :return: A :class:`str` representing the noramlized glyph note. + :raises TypeError if `value` is not a :class:`str`. - * **value** must be a :ref:`type-string`. - * Returned value is an unencoded ``unicode`` string """ if not isinstance(value, str): raise TypeError("Note must be a string, not %s." @@ -950,12 +1104,13 @@ def normalizeGlyphNote(value): # File Path -def normalizeFilePath(value): - """ - Normalizes file path. +def normalizeFilePath(value: str) -> str: + """Normalize a file path. + + :param value: The file path to normalize as a :class:`str`. + :return: A :class:`str` representing the noramlized file path. + :raises TypeError if `value` is not a :class:`str`. - * **value** must be a :ref:`type-string`. - * Returned value is an unencoded ``unicode`` string """ if not isinstance(value, str): raise TypeError("File paths must be strings, not %s." @@ -965,29 +1120,37 @@ def normalizeFilePath(value): # Interpolation -def normalizeInterpolationFactor(value): - """ - Normalizes interpolation factor. +def normalizeInterpolationFactor(value: TransformationType) -> PairType[float]: + """Normalize an interpolation factor. + + :param value: The interpolation factor to normalize as a single :class:`int` + or :class:`float`, or a :class:`tuple` or :class:`list` of + two :class:`int` or :class:`float` values. + :return: A :class:`tuple` of two :class:`float` values representing the + normalized interpolation factor. + :raises TypeError: + - If `value` is not an :class:`int`, :class:`float`, :class:`tuple`, + or :class:`list`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: + - If `value` is a :class:`tuple` or :class:`list` and does not contain + exactly two items. - * **value** must be an :ref:`type-int-float`, ``tuple`` or ``list``. - * If **value** is a ``tuple`` or ``list``, it must have exactly two items. - These items must be instances of :ref:`type-int-float`. - * Returned value is a ``tuple`` of two ``float``. """ if not isinstance(value, (int, float, list, tuple)): - raise TypeError("Interpolation factor must be an int, float, or tuple " - "instances, not %s." % type(value).__name__) + raise TypeError("Interpolation factor must be an int, float, tuple, " + "or list, not %s." % type(value).__name__) if isinstance(value, (int, float)): value = (float(value), float(value)) else: - if not len(value) == 2: + if len(value) != 2: raise ValueError("Interpolation factor tuple must contain two " - "values, not %d." % len(value)) + "items, not %d." % len(value)) for v in value: if not isinstance(v, (int, float)): - raise TypeError("Interpolation factor tuple values must be an " - ":ref:`type-int-float`, not %s." - % type(value).__name__) + raise TypeError("Interpolation factor tuple values must be " + "instances of int or float, not %s." + % type(v).__name__) value = tuple(float(v) for v in value) return value @@ -996,14 +1159,19 @@ def normalizeInterpolationFactor(value): # Transformations # --------------- -def normalizeTransformationMatrix(value): - """ - Normalizes transformation matrix. +def normalizeTransformationMatrix(value: SextupleCollectionType[IntFloatType] + ) -> SextupleType[float]: + """Normalize a transformation matrix. + + :param value: The transformation matrix to normalize as a :class:`list` + or :class:`tuple` of six :class:`int` or :class:`float` values. + :return: A :class:`tuple` of six :class:`float` values representing the + normalized transformation matrix. + :raises TypeError: + - If `value` is not a :class:`tuple` or :class:`list`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: If `value` does not contain exactly six items. - * **value** must be an ``tuple`` or ``list``. - * **value** must have exactly six items. Each of these - items must be an instance of :ref:`type-int-float`. - * Returned value is a ``tuple`` of six ``float``. """ if not isinstance(value, (tuple, list)): raise TypeError("Transformation matrices must be tuple instances, " @@ -1019,28 +1187,44 @@ def normalizeTransformationMatrix(value): return tuple(float(v) for v in value) -def normalizeTransformationOffset(value): - """ - Normalizes transformation offset. +def normalizeTransformationOffset(value: PairCollectionType[IntFloatType] + ) -> PairType[IntFloatType]: + """Normalize a transformation offset. + + :param value: The transformation offset to normalize as a :class:`list` + or :class:`tuple` of two :class:`int` or :class:`float` values. + :return: A :class:`tuple` of two :class:`float` values representing the + normalized transformation offset. + :raises TypeError: + - If `value` is not a :class:`tuple`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: If `value` does not contain exactly two items. - * **value** must be an ``tuple``. - * **value** must have exactly two items. Each item - must be an instance of :ref:`type-int-float`. - * Returned value is a ``tuple`` of two ``float``. """ return normalizeCoordinateTuple(value) -def normalizeTransformationSkewAngle(value): - """ - Normalizes transformation skew angle. - - * **value** must be an :ref:`type-int-float`, ``tuple`` or ``list``. - * If **value** is a ``tuple`` or ``list``, it must have exactly two items. - These items must be instances of :ref:`type-int-float`. - * **value** items must be between -360 and 360. - * If the value is negative, it is normalized by adding it to 360 - * Returned value is a ``tuple`` of two ``float`` between 0 and 360. +def normalizeTransformationSkewAngle(value: TransformationType) -> PairType[float]: + """Normalize a transformation skew angle. + + :param value: The skew angle to normalize as a single :class:`int` + or :class:`float`, or a :class:`tuple` or :class:`list` of + two :class:`int` or :class:`float` values. Each value must be + between ``-360 and ``360``. If the value is negative, it is normalized by + adding it to ``360``. + :return: A :class:`tuple` of two :class:`float` values between ``0.0`` and + ``360.0`` representing the normalized skew angle. + :raises TypeError: + - If `value` is not an :class:`int`, :class:`float`, :class:`tuple`, + or :class:`list`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: + - If `value` is a :class:`tuple` or :class:`list` and does not contain + exactly two items. + :raises ValueError: + - If `value` does not contain exactly two items. + - If any `value` item is not between ``-360`` and ``360``. + """ if not isinstance(value, (int, float, list, tuple)): raise TypeError("Transformation skew angle must be an int, float, or " @@ -1064,14 +1248,22 @@ def normalizeTransformationSkewAngle(value): return tuple(float(v + 360) if v < 0 else float(v) for v in value) -def normalizeTransformationScale(value): - """ - Normalizes transformation scale. +def normalizeTransformationScale(value: TransformationType) -> PairType[float]: + """Normalize a transformation scale. + + :param value: The scale to normalize as a single :class:`int` + or :class:`float`, or a :class:`tuple` or :class:`list` of + two :class:`int` or :class:`float` values. + :return: A :class:`tuple` of two :class:`float` values representing the + normalized scale. + :raises TypeError: + - If `value` is not an :class:`int`, :class:`float`, :class:`tuple`, + or :class:`list`. + - If any `value` item is not an :class:`int` or a :class:`float`. + :raises ValueError: + - If `value` is a :class:`tuple` or :class:`list` and does not contain + exactly two items. - * **value** must be an :ref:`type-int-float`, ``tuple`` or ``list``. - * If **value** is a ``tuple`` or ``list``, it must have exactly two items. - These items must be instances of :ref:`type-int-float`. - * Returned value is a ``tuple`` of two ``float``\s. """ if not isinstance(value, (int, float, list, tuple)): raise TypeError("Transformation scale must be an int, float, or tuple " @@ -1091,17 +1283,19 @@ def normalizeTransformationScale(value): return value -def normalizeVisualRounding(value): - """ - Normalizes rounding. - Python 3 uses banker’s rounding, meaning anything that is at 0.5 - will go to the even number. This isn't always ideal for point - coordinates, so instead round to the higher number. +def normalizeVisualRounding(value: IntFloatType) -> int: + """Normalize rounding. - * **value** must be an :ref:`type-int-float` - * Returned value is a ``int`` - """ + Python 3 uses banker’s rounding, meaning anything that is at 0.5 will round + to the nearest even number. This isn't always ideal for point coordinates, + so instead, this function rounds to the higher number + with :func:`fontTools.misc.roundTools.otRound`. + :param value: The value to round as an :class:`int` or a :class:`float`. + :return: An :class:`int` representing the normalized rounding. + :raises TypeError: If `value` is not an :class:`int` or a :class:`float`. + + """ if not isinstance(value, (int, float)): raise TypeError("Value to round must be an int or float, not %s." % type(value).__name__) diff --git a/Lib/fontParts/base/point.py b/Lib/fontParts/base/point.py index 18149aeb..33c65f60 100644 --- a/Lib/fontParts/base/point.py +++ b/Lib/fontParts/base/point.py @@ -14,8 +14,9 @@ from fontParts.base import normalizers from fontParts.base.deprecated import DeprecatedPoint, RemovedPoint from fontParts.base.annotations import( - IntFloatType, - TransformationMatrixType + QuintupleType, + SextupleCollectionType, + IntFloatType ) if TYPE_CHECKING: @@ -45,7 +46,7 @@ class BasePoint(BaseObject, """ - copyAttributes: Tuple[str, str, str, str, str] = ( + copyAttributes: QuintupleType[str] = ( "type", "smooth", "x", @@ -568,7 +569,9 @@ def _set_name(self, value: str) -> None: # Transformation # -------------- - def _transformBy(self, matrix: TransformationMatrixType, **kwargs: Any) -> None: + def _transformBy(self, + matrix: SextupleCollectionType[IntFloatType], + **kwargs: Any) -> None: r"""Transform the native point. This is the environment implementation of :meth:`BasePoint.transformBy`.