diff --git a/Lib/fontParts/base/anchor.py b/Lib/fontParts/base/anchor.py index f02a75a2..0704102a 100644 --- a/Lib/fontParts/base/anchor.py +++ b/Lib/fontParts/base/anchor.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, Any, Callable, Optional, Union, List, Tuple + from fontTools.misc import transform from fontParts.base import normalizers from fontParts.base.base import ( @@ -13,6 +16,17 @@ from fontParts.base.compatibility import AnchorCompatibilityReporter from fontParts.base.color import Color from fontParts.base.deprecated import DeprecatedAnchor, RemovedAnchor +from fontParts.base.annotations import ( + QuadrupleType, + QuadrupleCollectionType, + SextupleCollectionType, + IntFloatType, +) + +if TYPE_CHECKING: + from fontParts.base.font import BaseFont + from fontParts.base.layer import BaseLayer + from fontParts.base.glyph import BaseGlyph class BaseAnchor( @@ -25,15 +39,16 @@ class BaseAnchor( SelectionMixin, IdentifierMixin, ): - """ - An anchor object. This object is almost always - created with :meth:`BaseGlyph.appendAnchor`. + """Represent the basis for an anchor object. + + This object is almost always created with :meth:`BaseGlyph.appendAnchor`. An orphan anchor can be created like this:: >>> anchor = RAnchor() + """ - def _reprContents(self): + def _reprContents(self) -> List[str]: contents = [ f"({self.x}, {self.y})", ] @@ -47,7 +62,7 @@ def _reprContents(self): # Copy # ---- - copyAttributes = ("x", "y", "name", "color") + copyAttributes: Tuple[str, ...] = ("x", "y", "name", "color") # ------- # Parents @@ -55,16 +70,34 @@ def _reprContents(self): # Glyph - _glyph = None + _glyph: Optional[Callable[[], BaseGlyph]] = None + + glyph: dynamicProperty = dynamicProperty( + "glyph", + """Get or set the anchor's parent glyph object. + + The value must be a :class:`BaseGlyph` instance or :obj:`None`. - glyph = dynamicProperty("glyph", "The anchor's parent :class:`BaseGlyph`.") + :return: The :class:`BaseGlyph` instance containing the anchor + or :obj:`None`. + :raises AssertionError: If attempting to set the glyph when it + has already been set. - def _get_glyph(self): + Example:: + + >>> glyph = anchor.glyph + + """, + ) + + 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 anchor already set") if glyph is not None: @@ -73,18 +106,46 @@ def _set_glyph(self, glyph): # Layer - layer = dynamicProperty("layer", "The anchor's parent :class:`BaseLayer`.") + layer: dynamicProperty = dynamicProperty( + "layer", + """Get the anchor's parent layer object. + + This property is read-only. + + :return: The :class:`BaseLayer` instance containing the anchor + or :obj:`None`. - def _get_layer(self): + Example:: + + >>> layer = anchor.layer + + """, + ) + + def _get_layer(self) -> Optional[BaseLayer]: if self._glyph is None: return None return self.glyph.layer # Font - font = dynamicProperty("font", "The anchor's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty( + "font", + """Get the anchor's parent font object. + + This property is read-only. + + :return: The :class:`BaseFont` instance containing the anchor + or :obj:`None`. + + Example:: - def _get_font(self): + >>> font = anchor.font + + """, + ) + + def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: return None return self.glyph.font @@ -97,85 +158,131 @@ def _get_font(self): x = dynamicProperty( "base_x", - """ - The x coordinate of the anchor. - It must be an :ref:`type-int-float`. :: + """Get or set the anchor's x-coordinate. + + The value must be an :class:`int` or a :class:`float`. + + :return: An :class:`int` or a :class:`float` representing the + x-coordinate of the anchor. + + Example:: >>> anchor.x 100 >>> anchor.x = 101 + """, ) - def _get_base_x(self): + def _get_base_x(self) -> IntFloatType: value = self._get_x() value = normalizers.normalizeX(value) return value - def _set_base_x(self, value): + def _set_base_x(self, value: IntFloatType) -> None: value = normalizers.normalizeX(value) self._set_x(value) - def _get_x(self): - """ - This is the environment implementation of - :attr:`BaseAnchor.x`. This must return an - :ref:`type-int-float`. + def _get_x(self) -> IntFloatType: + """Get the native anchor's x-coordinate. + + This is the environment implementation of the :attr:`BaseAnchor.x` property + getter. + + :return: An :class:`int` or a :class:`float` representing the + x-coordinate of the anchor. The value will be normalized + with :func:`normalizers.normalizeX`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_x(self, value): - """ - This is the environment implementation of - :attr:`BaseAnchor.x`. **value** will be - an :ref:`type-int-float`. + def _set_x(self, value: IntFloatType) -> None: + """Set the native anchor's x-coordinate. + + This is the environment implementation of the :attr:`BaseAnchor.x` property + setter. + + :param value: The x-coordinate to set as an :class:`int` or a :class:`float`. + The value will have been normalized with :func:`normalizers.normalizeX`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() # y - y = dynamicProperty( + y: dynamicProperty = dynamicProperty( "base_y", - """ - The y coordinate of the anchor. - It must be an :ref:`type-int-float`. :: + """Get or set the anchor's y-coordinate. + + The value must be an :class:`int` or a :class:`float`. + + :return: An :class:`int` or a :class:`float` representing the + y-coordinate of the anchor. + + Example:: >>> anchor.y 100 >>> anchor.y = 101 + """, ) - def _get_base_y(self): + def _get_base_y(self) -> IntFloatType: value = self._get_y() value = normalizers.normalizeY(value) return value - def _set_base_y(self, value): + def _set_base_y(self, value: IntFloatType) -> None: value = normalizers.normalizeY(value) self._set_y(value) - def _get_y(self): - """ - This is the environment implementation of - :attr:`BaseAnchor.y`. This must return an - :ref:`type-int-float`. + def _get_y(self) -> IntFloatType: + """Get the native anchor's y-coordinate. + + This is the environment implementation of the :attr:`BaseAnchor.y` property + getter. + + :return: An :class:`int` or a :class:`float` representing the + x-coordinate of the anchor. The value will be normalized + with :func:`normalizers.normalizeY`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_y(self, value): - """ - This is the environment implementation of - :attr:`BaseAnchor.y`. **value** will be - an :ref:`type-int-float`. + def _set_y(self, value: IntFloatType) -> None: + """Set the native anchor's y-coordinate. + + This is the environment implementation of the :attr:`BaseAnchor.y` property + setter. + + :param value: The y-coordinate to set as an :class:`int` or a :class:`float`. + The value will have been normalized with :func:`normalizers.normalizeY`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() @@ -185,29 +292,42 @@ def _set_y(self, value): # index - index = dynamicProperty( + index: dynamicProperty = dynamicProperty( "base_index", - """ - The index of the anchor within the ordered - list of the parent glyph's anchors. This - attribute is read only. :: + """Get the anchor's index. + + This property is read-only. - >>> anchor.index + :return: An :class:`int` representing the index of the anchor within + the ordered list of the parent glyph's anchors. + + Example:: + + >>> guideline.index 0 + """, ) - def _get_base_index(self): + def _get_base_index(self) -> Optional[int]: value = self._get_index() value = normalizers.normalizeIndex(value) return value - def _get_index(self): - """ - Get the anchor's index. - This must return an ``int``. + def _get_index(self) -> Optional[int]: + """Get the native anchor's index. + + This is the environment implementation of the :attr:`BaseAnchor.index` + property getter. + + :return: An :class:`int` representing the index of the anchor within + the ordered list of the parent glyph's anchors. The value will be + normalized with :func:`normalizers.normalizeIndex`. + + .. note:: + + Subclasses may override this method. - Subclasses may override this method. """ glyph = self.glyph if glyph is None: @@ -216,98 +336,138 @@ def _get_index(self): # name - name = dynamicProperty( + name: dynamicProperty = dynamicProperty( "base_name", - """ - The name of the anchor. This will be a - :ref:`type-string` or ``None``. + """Get or set the anchor's name. + + The value must be a :class:`str` or :obj: `None`. + + :return: A :class:`str` representing the name of the anchor, or :obj:`None`. >>> anchor.name 'my anchor' >>> anchor.name = None + """, ) - def _get_base_name(self): + def _get_base_name(self) -> Optional[str]: value = self._get_name() - value = normalizers.normalizeAnchorName(value) + if value is not None: + value = normalizers.normalizeAnchorName(value) return value - def _set_base_name(self, value): - value = normalizers.normalizeAnchorName(value) + def _set_base_name(self, value: Optional[str]) -> None: + if value is not None: + value = normalizers.normalizeAnchorName(value) self._set_name(value) - def _get_name(self): - """ - This is the environment implementation of - :attr:`BaseAnchor.name`. This must return a - :ref:`type-string` or ``None``. The returned - value will be normalized with - :func:`normalizers.normalizeAnchorName`. + def _get_name(self) -> Optional[str]: + """Get the native anchor's name. + + This is the environment implementation of the :attr:`BaseAnchor.name` + property getter. + + :return: A :class:`str` representing the name of the anchor, + or :obj:`None`. The value will have been normalized + with :func:`normalizers.normalizeAnchorName`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_name(self, value): - """ - This is the environment implementation of - :attr:`BaseAnchor.name`. **value** will be - a :ref:`type-string` or ``None``. It will - have been normalized with - :func:`normalizers.normalizeAnchorName`. + def _set_name(self, value: Optional[str]) -> None: + """Set the native anchor's name. + + This is the environment implementation of the :attr:`BaseAnchor.name` + property setter. + + :param value: The name to set as a :class:`str` or :obj:`None`. The + value will have been normalized + with :func:`normalizers.normalizeAnchorName`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() # color - color = dynamicProperty( + color: dynamicProperty = dynamicProperty( "base_color", - """ - The anchor's color. This will be a - :ref:`type-color` or ``None``. :: + """Get or set the anchor's color. + + The value must be a :ref:`type-color` or :obj:`None`. + + :return: A :ref:`type-color` representing the color of the anchor, + or :obj:`None`. + + Example:: >>> anchor.color None >>> anchor.color = (1, 0, 0, 0.5) + """, ) - 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: QuadrupleCollectionType[IntFloatType]) -> None: if value is not None: value = normalizers.normalizeColor(value) self._set_color(value) - def _get_color(self): - """ - This is the environment implementation of - :attr:`BaseAnchor.color`. This must return - a :ref:`type-color` or ``None``. The - returned value will be normalized with - :func:`normalizers.normalizeColor`. + def _get_color(self) -> Optional[QuadrupleCollectionType[IntFloatType]]: + """Get the native anchor's color. + + This is the environment implementation of the :attr:`BaseAnchor.color` + property getter. + + :return: A :ref:`type-color` representing the color of the anchor, + or :obj:`None`. The value will be normalized + with :func:`normalizers.normalizeColor`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_color(self, value): - """ - This is the environment implementation of - :attr:`BaseAnchor.color`. **value** will - be a :ref:`type-color` or ``None``. - It will have been normalized with - :func:`normalizers.normalizeColor`. + def _set_color(self, value: Optional[QuadrupleType[float]]) -> None: + """Set the native anchor's color. + + Description + + This is the environment implementation of the :attr:`BaseAnchor.color` + property setter. + + :param value: The :ref:`type-color` to set for the anchor or :obj:`None`. + The value will have been normalized with :func:`normalizers.normalizeColor`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. - Subclasses must override this method. """ self.raiseNotImplementedError() @@ -315,16 +475,21 @@ def _set_color(self, value): # Transformation # -------------- - def _transformBy(self, matrix, **kwargs): - """ - This is the environment implementation of - :meth:`BaseAnchor.transformBy`. + def _transformBy( + self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any + ) -> None: + r"""Transform the native anchor according to the given matrix. + + This is the environment implementation of :meth:`BaseAnchor.transformBy`. - **matrix** will be a :ref:`type-transformation`. - that has been normalized with - :func:`normalizers.normalizeTransformationMatrix`. + :param matrix: The :ref:`type-transformation` to apply. The value will + be normalized with :func:`normalizers.normalizeTransformationMatrix`. + :param \**kwargs: Additional keyword arguments. + + .. note:: + + Subclasses may override this method. - Subclasses may override this method. """ t = transform.Transform(*matrix) x, y = t.transformPoint((self.x, self.y)) @@ -337,9 +502,19 @@ def _transformBy(self, matrix, **kwargs): compatibilityReporterClass = AnchorCompatibilityReporter - def isCompatible(self, other): - """ - Evaluate interpolation compatibility with **other**. :: + def isCompatible( + self, other: BaseAnchor, cls=None + ) -> Tuple[bool, AnchorCompatibilityReporter]: + """Evaluate interpolation compatibility with another anchor. + + :param other: The other :class:`BaseAnchor` instance to check + compatibility with. + :return: A :class:`tuple` where the first element is a :class:`bool` + indicating compatibility, and the second element is + a :class:`fontParts.base.compatibility.AnchorCompatibilityReporter` + instance. + + Example:: >>> compatible, report = self.isCompatible(otherAnchor) >>> compatible @@ -348,18 +523,24 @@ def isCompatible(self, other): [Warning] Anchor: "left" + "right" [Warning] Anchor: "left" has name left | "right" has name right - This will return a ``bool`` indicating if the anchor is - compatible for interpolation with **other** and a - :ref:`type-string` of compatibility notes. """ return super(BaseAnchor, self).isCompatible(other, BaseAnchor) - def _isCompatible(self, other, reporter): - """ - This is the environment implementation of - :meth:`BaseAnchor.isCompatible`. + def _isCompatible( + self, other: BaseAnchor, reporter: AnchorCompatibilityReporter + ) -> None: + """Evaluate interpolation compatibility with another native anchor. + + This is the environment implementation of :meth:`BaseAnchor.isCompatible`. + + :param other: The other :class:`BaseAnchor` instance to check + compatibility with. + :param reporter: An object used to report compatibility issues. + + .. note:: + + Subclasses may override this method. - Subclasses may override this method. """ anchor1 = self anchor2 = other @@ -372,25 +553,30 @@ def _isCompatible(self, other, reporter): # Normalization # ------------- - def round(self): - """ - Round the anchor's coordinate. + def round(self) -> None: + """Round the anchor's coordinate. + + This applies to: - >>> anchor.round() + - :attr:`x` + - :attr:`y - This applies to the following: + Example::` + + >>> guideline.round() - * x - * y """ self._round() - def _round(self): - """ - This is the environment implementation of - :meth:`BaseAnchor.round`. + def _round(self) -> None: + """Round the native anchor's coordinate. + + This is the environment implementation of :meth:`BaseGuideline.round`. + + .. note:: + + Subclasses may override this method. - Subclasses may override this method. """ self.x = normalizers.normalizeVisualRounding(self.x) self.y = normalizers.normalizeVisualRounding(self.y)