From 6f88b12c1e2756ed8cf804faa39f96e4b5a696af Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Wed, 26 Feb 2025 13:55:13 +0100 Subject: [PATCH 1/3] Annotate and document `anchor.py`. --- Lib/fontParts/base/anchor.py | 408 +++++++++++++++++++++++------------ 1 file changed, 271 insertions(+), 137 deletions(-) diff --git a/Lib/fontParts/base/anchor.py b/Lib/fontParts/base/anchor.py index f02a75a2..29ba710d 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,16 @@ def _reprContents(self): # Glyph - _glyph = None + _glyph: Optional[Callable[[], BaseGlyph]] = None - glyph = dynamicProperty("glyph", "The anchor's parent :class:`BaseGlyph`.") + glyph: dynamicProperty = dynamicProperty("glyph", "The anchor's parent :class:`BaseGlyph`.") - def _get_glyph(self): + def _get_glyph(self) -> Optional[BaseGlyph]: if self._glyph is None: return None return self._glyph() - def _set_glyph(self, glyph): + def _set_glyph(self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]]) -> None: if self._glyph is not None: raise AssertionError("glyph for anchor already set") if glyph is not None: @@ -73,18 +88,18 @@ def _set_glyph(self, glyph): # Layer - layer = dynamicProperty("layer", "The anchor's parent :class:`BaseLayer`.") + layer: dynamicProperty = dynamicProperty("layer", "The anchor's parent :class:`BaseLayer`.") - def _get_layer(self): + def _get_layer(self) -> Optional[BaseLayer]: if self._glyph is None: return None return self.glyph.layer # Font - font = dynamicProperty("font", "The anchor's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty("font", "The anchor's parent :class:`BaseFont`.") - def _get_font(self): + def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: return None return self.glyph.font @@ -97,85 +112,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 +246,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. - >>> anchor.index + This property is read-only. + + :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 +290,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 +429,19 @@ 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. - **matrix** will be a :ref:`type-transformation`. - that has been normalized with - :func:`normalizers.normalizeTransformationMatrix`. + This is the environment implementation of :meth:`BaseAnchor.transformBy`. + + :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 +454,17 @@ 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 +473,22 @@ 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 +501,30 @@ def _isCompatible(self, other, reporter): # Normalization # ------------- - def round(self): - """ - Round the anchor's coordinate. + def round(self) -> None: + """Round the anchor's coordinate. - >>> anchor.round() + This applies to: - This applies to the following: + - :attr:`x` + - :attr:`y + + 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) From cc0f0ecbc8badb327c76bfc62bd25575f50d1525 Mon Sep 17 00:00:00 2001 From: knutnergaard <15194233+knutnergaard@users.noreply.github.com> Date: Wed, 26 Feb 2025 12:56:58 +0000 Subject: [PATCH 2/3] Format fixes by ruff --- Lib/fontParts/base/anchor.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Lib/fontParts/base/anchor.py b/Lib/fontParts/base/anchor.py index 29ba710d..6e4fd85c 100644 --- a/Lib/fontParts/base/anchor.py +++ b/Lib/fontParts/base/anchor.py @@ -39,9 +39,9 @@ class BaseAnchor( SelectionMixin, IdentifierMixin, ): - """Represent the basis for an anchor object. + """Represent the basis for an anchor object. - This object is almost always created with :meth:`BaseGlyph.appendAnchor`. + This object is almost always created with :meth:`BaseGlyph.appendAnchor`. An orphan anchor can be created like this:: >>> anchor = RAnchor() @@ -72,14 +72,18 @@ def _reprContents(self) -> List[str]: _glyph: Optional[Callable[[], BaseGlyph]] = None - glyph: dynamicProperty = dynamicProperty("glyph", "The anchor's parent :class:`BaseGlyph`.") + glyph: dynamicProperty = dynamicProperty( + "glyph", "The anchor's parent :class:`BaseGlyph`." + ) def _get_glyph(self) -> Optional[BaseGlyph]: if self._glyph is None: return None return self._glyph() - def _set_glyph(self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]]) -> None: + 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: @@ -88,7 +92,9 @@ def _set_glyph(self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]]) # Layer - layer: dynamicProperty = dynamicProperty("layer", "The anchor's parent :class:`BaseLayer`.") + layer: dynamicProperty = dynamicProperty( + "layer", "The anchor's parent :class:`BaseLayer`." + ) def _get_layer(self) -> Optional[BaseLayer]: if self._glyph is None: @@ -97,7 +103,9 @@ def _get_layer(self) -> Optional[BaseLayer]: # Font - font: dynamicProperty = dynamicProperty("font", "The anchor's parent :class:`BaseFont`.") + font: dynamicProperty = dynamicProperty( + "font", "The anchor's parent :class:`BaseFont`." + ) def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: @@ -429,7 +437,9 @@ def _set_color(self, value: Optional[QuadrupleType[float]]) -> None: # Transformation # -------------- - def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any) -> None: + 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`. @@ -454,7 +464,9 @@ def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: A compatibilityReporterClass = AnchorCompatibilityReporter - def isCompatible(self, other: BaseAnchor, cls=None) -> Tuple[bool, AnchorCompatibilityReporter]: + 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 @@ -476,7 +488,9 @@ def isCompatible(self, other: BaseAnchor, cls=None) -> Tuple[bool, AnchorCompati """ return super(BaseAnchor, self).isCompatible(other, BaseAnchor) - def _isCompatible(self, other: BaseAnchor, reporter: AnchorCompatibilityReporter) -> None: + def _isCompatible( + self, other: BaseAnchor, reporter: AnchorCompatibilityReporter + ) -> None: """Evaluate interpolation compatibility with another native anchor. This is the environment implementation of :meth:`BaseAnchor.isCompatible`. From 174ac9d1c98415372a4fc6eb138190d737510109 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Wed, 26 Feb 2025 15:05:07 +0100 Subject: [PATCH 3/3] Add documentation for parents. --- Lib/fontParts/base/anchor.py | 44 +++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/Lib/fontParts/base/anchor.py b/Lib/fontParts/base/anchor.py index 6e4fd85c..0704102a 100644 --- a/Lib/fontParts/base/anchor.py +++ b/Lib/fontParts/base/anchor.py @@ -73,7 +73,21 @@ def _reprContents(self) -> List[str]: _glyph: Optional[Callable[[], BaseGlyph]] = None glyph: dynamicProperty = dynamicProperty( - "glyph", "The anchor's parent :class:`BaseGlyph`." + "glyph", + """Get or set the anchor's parent glyph object. + + The value must be a :class:`BaseGlyph` instance or :obj:`None`. + + :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. + + Example:: + + >>> glyph = anchor.glyph + + """, ) def _get_glyph(self) -> Optional[BaseGlyph]: @@ -93,7 +107,19 @@ def _set_glyph( # Layer layer: dynamicProperty = dynamicProperty( - "layer", "The anchor's parent :class:`BaseLayer`." + "layer", + """Get the anchor's parent layer object. + + This property is read-only. + + :return: The :class:`BaseLayer` instance containing the anchor + or :obj:`None`. + + Example:: + + >>> layer = anchor.layer + + """, ) def _get_layer(self) -> Optional[BaseLayer]: @@ -104,7 +130,19 @@ def _get_layer(self) -> Optional[BaseLayer]: # Font font: dynamicProperty = dynamicProperty( - "font", "The anchor's parent :class:`BaseFont`." + "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:: + + >>> font = anchor.font + + """, ) def _get_font(self) -> Optional[BaseFont]: