From dfc01b7be339d9517202ef43424a7d26fdd9767f Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 11:46:30 +0100 Subject: [PATCH 1/6] Add type annotation and correct errors. --- Lib/fontParts/base/component.py | 133 ++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index 755e28ae..90af01d3 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, cast, Any, Iterator, List, Optional, Tuple, Union + from fontTools.misc import transform from fontParts.base import normalizers from fontParts.base.errors import FontPartsError @@ -13,6 +16,24 @@ ) from fontParts.base.compatibility import ComponentCompatibilityReporter from fontParts.base.deprecated import DeprecatedComponent, RemovedComponent +from fontParts.base.annotations import ( + PairType, + PairCollectionType, + QuadrupleType, + SextupleType, + SextupleCollectionType, + IntFloatType, + PenType, + PointPenType, +) + +if TYPE_CHECKING: + from fontParts.base.point import BasePoint + from fontParts.base.bPoint import BaseBPoint + from fontParts.base.segment import BaseSegment + from fontParts.base.glyph import BaseGlyph + from fontParts.base.layer import BaseLayer + from fontParts.base.font import BaseFont class BaseComponent( @@ -24,9 +45,9 @@ class BaseComponent( DeprecatedComponent, RemovedComponent, ): - copyAttributes = ("baseGlyph", "transformation") + copyAttributes: Tuple[str, str] = ("baseGlyph", "transformation") - def _reprContents(self): + def _reprContents(self) -> List[str]: contents = [ f"baseGlyph='{self.baseGlyph}'", f"offset='({self.offset[0]}, {self.offset[1]})'", @@ -44,14 +65,14 @@ def _reprContents(self): _glyph = None - glyph = dynamicProperty("glyph", "The component's parent glyph.") + glyph: dynamicProperty = dynamicProperty("glyph", "The component's parent glyph.") - 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[BaseGlyph]) -> None: if self._glyph is not None: raise AssertionError("glyph for component already set") if glyph is not None: @@ -60,18 +81,18 @@ def _set_glyph(self, glyph): # Layer - layer = dynamicProperty("layer", "The component's parent layer.") + layer: dynamicProperty = dynamicProperty("layer", "The component's parent layer.") - 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 component's parent font.") + font: dynamicProperty = dynamicProperty("font", "The component's parent font.") - def _get_font(self): + def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: return None return self.glyph.font @@ -82,31 +103,29 @@ def _get_font(self): # baseGlyph - baseGlyph = dynamicProperty( + baseGlyph: dynamicProperty = dynamicProperty( "base_baseGlyph", "The name of the glyph the component references." ) - def _get_base_baseGlyph(self): + def _get_base_baseGlyph(self) -> Optional[str]: value = self._get_baseGlyph() # if the component does not belong to a layer, # it is allowed to have None as its baseGlyph - if value is None and self.layer is None: - pass - else: - value = normalizers.normalizeGlyphName(value) - return value + if value is None or self.layer is None: + return value + return normalizers.normalizeGlyphName(value) - def _set_base_baseGlyph(self, value): + def _set_base_baseGlyph(self, value: str) -> None: value = normalizers.normalizeGlyphName(value) self._set_baseGlyph(value) - def _get_baseGlyph(self): + def _get_baseGlyph(self) -> Optional[str]: """ Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_baseGlyph(self, value): + def _set_baseGlyph(self, value: str) -> None: """ Subclasses must override this method. """ @@ -114,26 +133,26 @@ def _set_baseGlyph(self, value): # transformation - transformation = dynamicProperty( + transformation: dynamicProperty = dynamicProperty( "base_transformation", "The component's transformation matrix." ) - def _get_base_transformation(self): + def _get_base_transformation(self) -> SextupleType[float]: value = self._get_transformation() value = normalizers.normalizeTransformationMatrix(value) return value - def _set_base_transformation(self, value): + def _set_base_transformation(self, value: SextupleCollectionType[IntFloatType]) -> None: value = normalizers.normalizeTransformationMatrix(value) self._set_transformation(value) - def _get_transformation(self): + def _get_transformation(self) -> SextupleType[float]: """ Subclasses must override this method. """ self.raiseNotImplementedError() - def _set_transformation(self, value): + def _set_transformation(self, value: SextupleCollectionType[IntFloatType]) -> None: """ Subclasses must override this method. """ @@ -141,25 +160,25 @@ def _set_transformation(self, value): # offset - offset = dynamicProperty("base_offset", "The component's offset.") + offset: dynamicProperty = dynamicProperty("base_offset", "The component's offset.") - def _get_base_offset(self): + def _get_base_offset(self) -> PairType[IntFloatType]: value = self._get_offset() value = normalizers.normalizeTransformationOffset(value) return value - def _set_base_offset(self, value): + def _set_base_offset(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeTransformationOffset(value) self._set_offset(value) - def _get_offset(self): + def _get_offset(self) -> PairType[IntFloatType]: """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation return ox, oy - def _set_offset(self, value): + def _set_offset(self, value: PairCollectionType[IntFloatType]) -> None: """ Subclasses may override this method. """ @@ -169,25 +188,25 @@ def _set_offset(self, value): # scale - scale = dynamicProperty("base_scale", "The component's scale.") + scale: dynamicProperty = dynamicProperty("base_scale", "The component's scale.") - def _get_base_scale(self): + def _get_base_scale(self) -> PairType[float]: value = self._get_scale() value = normalizers.normalizeComponentScale(value) return value - def _set_base_scale(self, value): + def _set_base_scale(self, value: PairCollectionType[IntFloatType]) -> None: value = normalizers.normalizeComponentScale(value) self._set_scale(value) - def _get_scale(self): + def _get_scale(self) -> PairType[float]: """ Subclasses may override this method. """ sx, sxy, syx, sy, ox, oy = self.transformation return sx, sy - def _set_scale(self, value): + def _set_scale(self, value: PairCollectionType[IntFloatType]) -> None: """ Subclasses may override this method. """ @@ -201,7 +220,7 @@ def _set_scale(self, value): # index - index = dynamicProperty( + index: dynamicProperty = dynamicProperty( "base_index", ( "The index of the component within the " @@ -209,7 +228,7 @@ def _set_scale(self, value): ), ) - def _get_base_index(self): + def _get_base_index(self) -> Optional[int]: glyph = self.glyph if glyph is None: return None @@ -217,11 +236,13 @@ def _get_base_index(self): value = normalizers.normalizeIndex(value) return value - def _set_base_index(self, value): + def _set_base_index(self, value: int) -> None: glyph = self.glyph if glyph is None: raise FontPartsError("The component does not belong to a glyph.") value = normalizers.normalizeIndex(value) + if value is None: + raise ValueError("Value cannot be None.") componentCount = len(glyph.components) if value < 0: value = -(value % componentCount) @@ -229,14 +250,14 @@ def _set_base_index(self, value): value = componentCount self._set_index(value) - def _get_index(self): + def _get_index(self) -> Optional[int]: """ Subclasses may override this method. """ glyph = self.glyph return glyph.components.index(self) - def _set_index(self, value): + def _set_index(self, value: int) -> None: """ Subclasses must override this method. """ @@ -246,13 +267,13 @@ def _set_index(self, value): # Pens # ---- - def draw(self, pen): + def draw(self, pen: PenType) -> None: """ Draw the component with the given Pen. """ self._draw(pen) - def _draw(self, pen, **kwargs): + def _draw(self, pen: PenType, **kwargs: Any) -> None: """ Subclasses may override this method. """ @@ -261,13 +282,13 @@ def _draw(self, pen, **kwargs): adapter = PointToSegmentPen(pen) self.drawPoints(adapter) - def drawPoints(self, pen): + def drawPoints(self, pen: PointPenType) -> None: """ Draw the contour with the given PointPen. """ self._drawPoints(pen) - def _drawPoints(self, pen, **kwargs): + def _drawPoints(self, pen: PointPenType, **kwargs: Any) -> None: """ Subclasses may override this method. """ @@ -289,7 +310,7 @@ def _drawPoints(self, pen, **kwargs): # Transformation # -------------- - def _transformBy(self, matrix, **kwargs): + def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any) -> None: """ Subclasses may override this method. """ @@ -301,13 +322,13 @@ def _transformBy(self, matrix, **kwargs): # Normalization # ------------- - def round(self): + def round(self) -> None: """ Round offset coordinates. """ self._round() - def _round(self): + def _round(self) -> None: """ Subclasses may override this method. """ @@ -316,7 +337,7 @@ def _round(self): y = normalizers.normalizeVisualRounding(y) self.offset = (x, y) - def decompose(self): + def decompose(self) -> None: """ Decompose the component. """ @@ -325,7 +346,7 @@ def decompose(self): raise FontPartsError("The component does not belong to a glyph.") self._decompose() - def _decompose(self): + def _decompose(self) -> None: """ Subclasses must override this method. """ @@ -337,7 +358,7 @@ def _decompose(self): compatibilityReporterClass = ComponentCompatibilityReporter - def isCompatible(self, other): + def isCompatible(self, other: BaseComponent) -> Tuple[bool, ComponentCompatibilityReporter]: """ Evaluate interpolation compatibility with **other**. :: @@ -354,7 +375,7 @@ def isCompatible(self, other): """ return super(BaseComponent, self).isCompatible(other, BaseComponent) - def _isCompatible(self, other, reporter): + def _isCompatible(self, other: BaseComponent, reporter: ComponentCompatibilityReporter) -> None: """ This is the environment implementation of :meth:`BaseComponent.isCompatible`. @@ -364,7 +385,7 @@ def _isCompatible(self, other, reporter): component1 = self component2 = other # base glyphs - if component1.baseName != component2.baseName: + if component1.baseGlyph != component2.baseGlyph: reporter.baseDifference = True reporter.warning = True @@ -372,7 +393,7 @@ def _isCompatible(self, other, reporter): # Data Queries # ------------ - def pointInside(self, point): + def pointInside(self, point: PairCollectionType[IntFloatType]) -> bool: """ Determine if point is in the black or white of the component. @@ -381,7 +402,7 @@ def pointInside(self, point): point = normalizers.normalizeCoordinateTuple(point) return self._pointInside(point) - def _pointInside(self, point): + def _pointInside(self, point: PairCollectionType[IntFloatType]) -> bool: """ Subclasses may override this method. """ @@ -391,18 +412,18 @@ def _pointInside(self, point): self.draw(pen) return pen.getResult() - bounds = dynamicProperty( + bounds: dynamicProperty = dynamicProperty( "base_bounds", ("The bounds of the component: " "(xMin, yMin, xMax, yMax) or None."), ) - def _get_base_bounds(self): + def _get_base_bounds(self) -> QuadrupleType[float]: value = self._get_bounds() if value is not None: value = normalizers.normalizeBoundingBox(value) return value - def _get_bounds(self): + def _get_bounds(self) -> QuadrupleType[float]: """ Subclasses may override this method. """ From c8d1a8ec6b5775d7873a54c14aadc83075a89ae4 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 5 Dec 2024 10:47:10 +0000 Subject: [PATCH 2/6] Format fixes by ruff --- Lib/fontParts/base/component.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index 90af01d3..bbec6a1c 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -142,7 +142,9 @@ def _get_base_transformation(self) -> SextupleType[float]: value = normalizers.normalizeTransformationMatrix(value) return value - def _set_base_transformation(self, value: SextupleCollectionType[IntFloatType]) -> None: + def _set_base_transformation( + self, value: SextupleCollectionType[IntFloatType] + ) -> None: value = normalizers.normalizeTransformationMatrix(value) self._set_transformation(value) @@ -310,7 +312,9 @@ def _drawPoints(self, pen: PointPenType, **kwargs: Any) -> None: # Transformation # -------------- - def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any) -> None: + def _transformBy( + self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any + ) -> None: """ Subclasses may override this method. """ @@ -358,7 +362,9 @@ def _decompose(self) -> None: compatibilityReporterClass = ComponentCompatibilityReporter - def isCompatible(self, other: BaseComponent) -> Tuple[bool, ComponentCompatibilityReporter]: + def isCompatible( + self, other: BaseComponent + ) -> Tuple[bool, ComponentCompatibilityReporter]: """ Evaluate interpolation compatibility with **other**. :: @@ -375,7 +381,9 @@ def isCompatible(self, other: BaseComponent) -> Tuple[bool, ComponentCompatibili """ return super(BaseComponent, self).isCompatible(other, BaseComponent) - def _isCompatible(self, other: BaseComponent, reporter: ComponentCompatibilityReporter) -> None: + def _isCompatible( + self, other: BaseComponent, reporter: ComponentCompatibilityReporter + ) -> None: """ This is the environment implementation of :meth:`BaseComponent.isCompatible`. From fd426e3661b5a1849a6c5dbaae9a35f0f1176538 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 11:51:01 +0100 Subject: [PATCH 3/6] Delete unused imports. --- Lib/fontParts/base/component.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index 90af01d3..12a83fa4 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, cast, Any, Iterator, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, List, Optional, Tuple from fontTools.misc import transform from fontParts.base import normalizers @@ -8,7 +8,6 @@ BaseObject, TransformationMixin, InterpolationMixin, - PointPositionMixin, SelectionMixin, IdentifierMixin, dynamicProperty, @@ -28,9 +27,6 @@ ) if TYPE_CHECKING: - from fontParts.base.point import BasePoint - from fontParts.base.bPoint import BaseBPoint - from fontParts.base.segment import BaseSegment from fontParts.base.glyph import BaseGlyph from fontParts.base.layer import BaseLayer from fontParts.base.font import BaseFont From 40abe55c0914adf1fd4597103699bcfbaf696a74 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 20:15:57 +0100 Subject: [PATCH 4/6] Add/improve documentation and make other slight changes. --- Lib/fontParts/base/component.py | 466 +++++++++++++++++++++++++++----- 1 file changed, 393 insertions(+), 73 deletions(-) diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index dad33ebc..5e5ee063 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING, Any, List, Optional, Tuple from fontTools.misc import transform +from fontTools.pens.pointInsidePen import PointInsidePen +from fontTools.pens.boundsPen import BoundsPen from fontParts.base import normalizers from fontParts.base.errors import FontPartsError from fontParts.base.base import ( @@ -41,6 +43,12 @@ class BaseComponent( DeprecatedComponent, RemovedComponent, ): + """Represent the basis for a component object. + + This object provides a reference to another glyph, allowing it to be + inserted as part of an outline. + + """ copyAttributes: Tuple[str, str] = ("baseGlyph", "transformation") def _reprContents(self) -> List[str]: @@ -61,7 +69,23 @@ def _reprContents(self) -> List[str]: _glyph = None - glyph: dynamicProperty = dynamicProperty("glyph", "The component's parent glyph.") + glyph: dynamicProperty = dynamicProperty( + "glyph", + """Get or set the component's parent glyph object. + + The value must be a :class:`BaseGlyph` instance or :obj:`None`. + + :return: The :class:`BaseGlyph` instance containing the component + or :obj:`None`. + :raises AssertionError: If attempting to set the glyph when it + has already been set. + + Example:: + + >>> glyph = component.glyph + + """, + ) def _get_glyph(self) -> Optional[BaseGlyph]: if self._glyph is None: @@ -77,7 +101,21 @@ def _set_glyph(self, glyph: Optional[BaseGlyph]) -> None: # Layer - layer: dynamicProperty = dynamicProperty("layer", "The component's parent layer.") + layer: dynamicProperty = dynamicProperty( + "layer", + """Get the component's parent layer object. + + This property is read-only. + + :return: The :class:`BaseLayer` instance containing the component + or :obj:`None`. + + Example:: + + >>> layer = component.layer + + """, + ) def _get_layer(self) -> Optional[BaseLayer]: if self._glyph is None: @@ -86,7 +124,21 @@ def _get_layer(self) -> Optional[BaseLayer]: # Font - font: dynamicProperty = dynamicProperty("font", "The component's parent font.") + font: dynamicProperty = dynamicProperty( + "font", + """Get the component's parent font object. + + This property is read-only. + + :return: The :class:`BaseFont` instance containing the component + or :obj:`None`. + + Example:: + + >>> font = component.font + + """, + ) def _get_font(self) -> Optional[BaseFont]: if self._glyph is None: @@ -100,15 +152,26 @@ def _get_font(self) -> Optional[BaseFont]: # baseGlyph baseGlyph: dynamicProperty = dynamicProperty( - "base_baseGlyph", "The name of the glyph the component references." + "base_baseGlyph", + """Get or set the name of the glyph referenced by the component. + + The value must be a :class:`str`. + + :return: A :class:`str` representing the name of the base glyph, + or :obj:`None` if the component does not belong to a layer. + :raise ValueError: If value is None when the component is part of a layer. + + """, ) def _get_base_baseGlyph(self) -> Optional[str]: value = self._get_baseGlyph() # if the component does not belong to a layer, # it is allowed to have None as its baseGlyph - if value is None or self.layer is None: + if value is None and self.layer is None: return value + if value is None: + raise ValueError(f"Value cannot be None when layer is '{self.layer}'.") return normalizers.normalizeGlyphName(value) def _set_base_baseGlyph(self, value: str) -> None: @@ -116,21 +179,55 @@ def _set_base_baseGlyph(self, value: str) -> None: self._set_baseGlyph(value) def _get_baseGlyph(self) -> Optional[str]: - """ - Subclasses must override this method. + """Get the name of the glyph referenced by the native component. + + This is the environment implementation of the :attr:`BaseComponent.baseGlyph` + property getter. + + :return: A :class:`str` representing the name of the base glyph, + or :obj:`None` if the component does not belong to a layer. The value + will be normalized with :func:`normalizers.normalizeGlyphName`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. + """ self.raiseNotImplementedError() def _set_baseGlyph(self, value: str) -> None: - """ - Subclasses must override this method. + """Set the name of the glyph referenced by the native component. + + This is the environment implementation of the :attr:`BaseComponent.baseGlyph` + property setter. + + :param value: The name of the glyph to set as a :class:`str`. The value + will have been normalized + with :func:`normalizers.normalizeGlyphName`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. + """ self.raiseNotImplementedError() # transformation transformation: dynamicProperty = dynamicProperty( - "base_transformation", "The component's transformation matrix." + "base_transformation", + """Get or set the component's transformation matrix. + + The value must be a :ref:`type-transformation`. + + :return: A :ref:`type-transformation` value representing the + transformation matrix of the component. + + """ ) def _get_base_transformation(self) -> SextupleType[float]: @@ -145,20 +242,54 @@ def _set_base_transformation( self._set_transformation(value) def _get_transformation(self) -> SextupleType[float]: - """ - Subclasses must override this method. + """Get the native component's transformation matrix. + + This is the environment implementation of the + :attr:`BaseComponent.transformation` property getter. + + :return: A :ref:`type-transformation` value representing the + transformation matrix of the component. The value will be + normalized with :func:`normalizers.normalizeTransformationMatrix`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. + """ self.raiseNotImplementedError() def _set_transformation(self, value: SextupleCollectionType[IntFloatType]) -> None: - """ - Subclasses must override this method. + """Set the native component's transformation matrix. + + This is the environment implementation of the + :attr:`BaseComponent.transformation` property setter. + + :param value: The :ref:`type-transformation` to set. The value will have + been normalized with :func:`normalizers.normalizeTransformationMatrix`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. + """ self.raiseNotImplementedError() # offset - offset: dynamicProperty = dynamicProperty("base_offset", "The component's offset.") + offset: dynamicProperty = dynamicProperty( + "base_offset", + """Get or set the component's offset. + + The value must be a :ref:`type-coordinate.` + + :return: A :ref:`type-coordinate.` representing the offset of the component. + + """ + ) def _get_base_offset(self) -> PairType[IntFloatType]: value = self._get_offset() @@ -170,15 +301,36 @@ def _set_base_offset(self, value: PairCollectionType[IntFloatType]) -> None: self._set_offset(value) def _get_offset(self) -> PairType[IntFloatType]: - """ - Subclasses may override this method. + """Get the native component's offset. + + This is the environment implementation of the :attr:`BaseComponent.offset` + property getter. + + :return: A :ref:`type-coordinate.` representing the offset of the component. + The value will be normalized + with :func:`normalizers.normalizeTransformationOffset`. + + .. note:: + + Subclasses may override this method. + """ sx, sxy, syx, sy, ox, oy = self.transformation return ox, oy def _set_offset(self, value: PairCollectionType[IntFloatType]) -> None: - """ - Subclasses may override this method. + """Set the native component's offset. + + This is the environment implementation of the :attr:`BaseComponent.offset` + property setter. + + :param value: The offset to set as a :ref:`type-coordinate.`. The value will + have been normalized with :func:`normalizers.normalizeTransformationOffset`. + + .. note:: + + Subclasses may override this method. + """ sx, sxy, syx, sy, ox, oy = self.transformation ox, oy = value @@ -186,7 +338,18 @@ def _set_offset(self, value: PairCollectionType[IntFloatType]) -> None: # scale - scale: dynamicProperty = dynamicProperty("base_scale", "The component's scale.") + scale: dynamicProperty = dynamicProperty( + "base_scale", + """Get or set the component's scale. + + The value must be a :class:`list` or :class:`tuple` of two :class:`int` + or :class:`float` items representing the ``(x, y)`` scale of the component. + + :return: A :class:`tuple` of two :class:`float` items representing the + ``(x, y)`` scale of the component. + + """ + ) def _get_base_scale(self) -> PairType[float]: value = self._get_scale() @@ -198,15 +361,38 @@ def _set_base_scale(self, value: PairCollectionType[IntFloatType]) -> None: self._set_scale(value) def _get_scale(self) -> PairType[float]: - """ - Subclasses may override this method. + """Get the native component's scale. + + This is the environment implementation of the :attr:`BaseComponent.scale` + property getter. + + :return: A :class:`tuple` of two :class:`float` items representing the + ``(x, y)`` scale of the component. The value will have been normalized + with :func:`normalizers.normalizeComponentScale`. + + .. note:: + + Subclasses may override this method. + """ sx, sxy, syx, sy, ox, oy = self.transformation return sx, sy def _set_scale(self, value: PairCollectionType[IntFloatType]) -> None: - """ - Subclasses may override this method. + """Set the native component's scale. + + This is the environment implementation of the :attr:`BaseComponent.scale` + property setter. + + :param value: The scale to set as a :class:`list` or :class:`tuple` + of :class:`int` or :class:`float` items representing the ``(x, y)`` + scale of the component. The value will have been normalized + with :func:`normalizers.normalizeComponentScale`. + + .. note:: + + Subclasses may override this method. + """ sx, sxy, syx, sy, ox, oy = self.transformation sx, sy = value @@ -220,10 +406,17 @@ def _set_scale(self, value: PairCollectionType[IntFloatType]) -> None: index: dynamicProperty = dynamicProperty( "base_index", - ( - "The index of the component within the " - "ordered list of the parent glyph's components." - ), + """Get or set the index of the contour. + + The value must be an :class:`int` + + :return: An :class:`int` representing the index of the component within + the ordered list of parent glyph's components, or :obj:`None` if the + component does not belong to a glyph. + :raise FontPartsError: If attempting to set the index while the + component does not belong to a glyph. + + """ ) def _get_base_index(self) -> Optional[int]: @@ -240,7 +433,7 @@ def _set_base_index(self, value: int) -> None: raise FontPartsError("The component does not belong to a glyph.") value = normalizers.normalizeIndex(value) if value is None: - raise ValueError("Value cannot be None.") + return componentCount = len(glyph.components) if value < 0: value = -(value % componentCount) @@ -249,15 +442,39 @@ def _set_base_index(self, value: int) -> None: self._set_index(value) def _get_index(self) -> Optional[int]: - """ - Subclasses may override this method. + """Get the index of the native contour. + + This is the environment implementation of the :attr:`BaseComponent.index` + property getter. + + :return: An :class:`int` representing the index of the component within + the ordered list of parent glyph's components, or :obj:`None` if the + component does not belong to a glyph. The value will be normalized + with :func:`normalizers.normalizeIndex`. + + .. note:: + + Subclasses may override this method. + """ glyph = self.glyph return glyph.components.index(self) def _set_index(self, value: int) -> None: - """ - Subclasses must override this method. + """Set the index of the native contour. + + This is the environment implementation of the :attr:`BaseComponent.index` + property setter. + + :param value: The index to set as an :class:`int`. The value will have been + normalized with :func:`normalizers.normalizeIndex`. + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. + """ self.raiseNotImplementedError() @@ -266,14 +483,27 @@ def _set_index(self, value: int) -> None: # ---- def draw(self, pen: PenType) -> None: - """ - Draw the component with the given Pen. + """Draw the component with the given pen. + + :param pen: The :class:`fontTools.pens.basePen.AbstractPen` with which + to draw the componnet. + """ self._draw(pen) def _draw(self, pen: PenType, **kwargs: Any) -> None: - """ - Subclasses may override this method. + r"""Draw the native component with the given pen. + + This is the environment implementation of :meth:`BaseComponent.draw`. + + :param pen: The :class:`fontTools.pens.basePen.AbstractPen` with which + to draw the componnet. + :param \**kwargs: Additional keyword arguments. + + .. note:: + + Subclasses may override this method. + """ from fontTools.ufoLib.pointPen import PointToSegmentPen @@ -281,14 +511,27 @@ def _draw(self, pen: PenType, **kwargs: Any) -> None: self.drawPoints(adapter) def drawPoints(self, pen: PointPenType) -> None: - """ - Draw the contour with the given PointPen. + """Draw the component with the given point pen. + + :param pen: The :class:`fontTools.pens.pointPen.AbstractPointPen` with + which to draw the componnet. + """ self._drawPoints(pen) def _drawPoints(self, pen: PointPenType, **kwargs: Any) -> None: - """ - Subclasses may override this method. + r"""Draw the native component with the given point pen. + + This is the environment implementation of :meth:`BaseComponent.draw`. + + :param pen: The :class:`fontTools.pens.pointPen.AbstractPointPen` with + which to draw the componnet. + :param \**kwargs: Additional keyword arguments. + + .. note:: + + Subclasses may override this method. + """ # The try: ... except TypeError: ... # handles backwards compatibility with @@ -311,8 +554,18 @@ def _drawPoints(self, pen: PointPenType, **kwargs: Any) -> None: def _transformBy( self, matrix: SextupleCollectionType[IntFloatType], **kwargs: Any ) -> None: - """ - Subclasses may override this method. + r"""Transform the component according to the given matrix. + + This is the environment implementation of :meth:`BaseComponent.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. + """ t = transform.Transform(*matrix) transformation = t.transform(self.transformation) @@ -323,14 +576,22 @@ def _transformBy( # ------------- def round(self) -> None: - """ - Round offset coordinates. + """Round the compnent's offset coordinates. + + This applies to :attr:`offset` + """ self._round() def _round(self) -> None: - """ - Subclasses may override this method. + """Round the native compnent's offset coordinates. + + This is the environment implementation of :meth:`BaseComponent.round`. + + .. note:: + + Subclasses may override this method. + """ x, y = self.offset x = normalizers.normalizeVisualRounding(x) @@ -338,17 +599,24 @@ def _round(self) -> None: self.offset = (x, y) def decompose(self) -> None: - """ - Decompose the component. - """ + """Decompose the component.""" glyph = self.glyph if glyph is None: raise FontPartsError("The component does not belong to a glyph.") self._decompose() def _decompose(self) -> None: - """ - Subclasses must override this method. + """Decompose the native component. + + This is the environment implementation of :meth:`BaseComponent.decompose`. + + :raises NotImplementedError: If the method has not been overridden by a + subclass. + + .. important:: + + Subclasses must override this method. + """ self.raiseNotImplementedError() @@ -361,8 +629,16 @@ def _decompose(self) -> None: def isCompatible( self, other: BaseComponent ) -> Tuple[bool, ComponentCompatibilityReporter]: - """ - Evaluate interpolation compatibility with **other**. :: + """Evaluate interpolation compatibility with another component. + + :param other: The other :class:`BaseComponent` 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.ComponentCompatibilityReporter` + instance. + + Example:: >>> compatible, report = self.isCompatible(otherComponent) >>> compatible @@ -371,20 +647,24 @@ def isCompatible( [Warning] Component: "A" + "B" [Warning] Component: "A" has name A | "B" has name B - This will return a ``bool`` indicating if the component is - compatible for interpolation with **other** and a - :ref:`type-string` of compatibility notes. """ return super(BaseComponent, self).isCompatible(other, BaseComponent) def _isCompatible( self, other: BaseComponent, reporter: ComponentCompatibilityReporter ) -> None: - """ - This is the environment implementation of - :meth:`BaseComponent.isCompatible`. + """Evaluate interpolation compatibility with another native component. + + This is the environment implementation of :meth:`BaseComponent.isCompatible`. + + :param other: The other :class:`BaseComponent` 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. """ component1 = self component2 = other @@ -398,27 +678,56 @@ def _isCompatible( # ------------ def pointInside(self, point: PairCollectionType[IntFloatType]) -> bool: - """ - Determine if point is in the black or white of the component. + """Check if `point` lies inside the filled area of the component. + + :param point: The point to check as a :ref:`type-coordinate`. + :return: :obj:`True` if `point` is inside the filled area of the + glyph, :obj:`False` otherwise. + + Example:: + + >>> glyph.pointInside((40, 65)) + True - point must be an (x, y) tuple. """ point = normalizers.normalizeCoordinateTuple(point) return self._pointInside(point) def _pointInside(self, point: PairCollectionType[IntFloatType]) -> bool: - """ - Subclasses may override this method. - """ - from fontTools.pens.pointInsidePen import PointInsidePen + """Check if `point` lies inside the filled area of the native component. + + This is the environment implementation of :meth:`BaseComponent.pointInside`. + + :param point: The point to check as a :ref:`type-coordinate`. The value will + have been normalized with :func:`normalizers.normalizeCoordinateTuple`. + :return: :class:`bool`. + .. note:: + + Subclasses may override this method. + + """ pen = PointInsidePen(glyphSet=self.layer, testPoint=point, evenOdd=False) self.draw(pen) return pen.getResult() bounds: dynamicProperty = dynamicProperty( "base_bounds", - ("The bounds of the component: " "(xMin, yMin, xMax, yMax) or None."), + """Get the bounds of the component. + + This property is read-only. + + :return: A :class:`tuple` of four :class:`int` or :class:`float` values + in the form ``(x minimum, y minimum, x maximum, y maximum)`` + representing the bounds of the component, or :obj:`None` if the + component is empty. + + Example:: + + >>> component.bounds + (10, 30, 765, 643) + + """, ) def _get_base_bounds(self) -> QuadrupleType[float]: @@ -428,11 +737,22 @@ def _get_base_bounds(self) -> QuadrupleType[float]: return value def _get_bounds(self) -> QuadrupleType[float]: - """ - Subclasses may override this method. - """ - from fontTools.pens.boundsPen import BoundsPen + """Get the bounds of the component. + This is the environment implementation of the :attr:`BaseComponent.bounds` + property getter. + + :return: A :class:`tuple` of four :class:`int` or :class:`float` values + in the form ``(x minimum, y minimum, x maximum, y maximum)`` + representing the bounds of the component, or :obj:`None` if the + component is empty. The value will be normalized + with :func:`normalizers.normalizeBoundingBox`. + + .. note:: + + Subclasses may override this method. + + """ pen = BoundsPen(self.layer) self.draw(pen) return pen.bounds From 8f8179501a43a47f0287a7e648e7c727e2e0e271 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 20:46:28 +0100 Subject: [PATCH 5/6] Resolve `mypy` errors. --- Lib/fontParts/base/base.py | 3 +-- Lib/fontParts/base/component.py | 2 +- Lib/fontParts/base/contour.py | 2 +- Lib/fontParts/base/font.py | 2 +- Lib/fontParts/base/glyph.py | 2 +- Lib/fontParts/base/layer.py | 2 +- Lib/fontParts/base/segment.py | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 6172c128..a0b6da74 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -241,8 +241,7 @@ def __repr__(self) -> str: contentString = "" return f"<{self.__class__.__name__}{contentString} at {id(self)}>" - @classmethod - def _reprContents(cls) -> List[str]: + def _reprContents(self) -> List[str]: """Provide a list of strings for inclusion in :meth:`BaseObject.__repr__. :return: A :class:`list` of :class:`str` items. diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index 5e5ee063..763b0604 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -627,7 +627,7 @@ def _decompose(self) -> None: compatibilityReporterClass = ComponentCompatibilityReporter def isCompatible( - self, other: BaseComponent + self, other: BaseComponent, cls=None ) -> Tuple[bool, ComponentCompatibilityReporter]: """Evaluate interpolation compatibility with another component. diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index 8d513f5f..041bea4a 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -488,7 +488,7 @@ def _transformBy( compatibilityReporterClass = ContourCompatibilityReporter def isCompatible( - self, other: BaseContour + self, other: BaseContour, cls=None ) -> Tuple[bool, ContourCompatibilityReporter]: """Evaluate interpolation compatibility with another contour. diff --git a/Lib/fontParts/base/font.py b/Lib/fontParts/base/font.py index 37f67ca8..0899ef0a 100644 --- a/Lib/fontParts/base/font.py +++ b/Lib/fontParts/base/font.py @@ -2058,7 +2058,7 @@ def _interpolate( compatibilityReporterClass = FontCompatibilityReporter - def isCompatible(self, other: BaseFont) -> Tuple[bool, FontCompatibilityReporter]: + def isCompatible(self, other: BaseFont, cls=None) -> Tuple[bool, FontCompatibilityReporter]: """Evaluate interpolation compatibility with another font. This method will return a :class:`bool` indicating if the font is diff --git a/Lib/fontParts/base/glyph.py b/Lib/fontParts/base/glyph.py index 13b02a7a..f5530694 100644 --- a/Lib/fontParts/base/glyph.py +++ b/Lib/fontParts/base/glyph.py @@ -2795,7 +2795,7 @@ def _checkPairs( reporter.warning = True reporterObject.append(compatibility) - def isCompatible(self, other: BaseGlyph) -> Tuple[bool, GlyphCompatibilityReporter]: + def isCompatible(self, other: BaseGlyph, cls=None) -> Tuple[bool, GlyphCompatibilityReporter]: """Evaluate interpolation compatibility with another glyph. :param other: The other :class:`BaseGlyph` instance to check diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index 6b008105..a4059e27 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1106,7 +1106,7 @@ def _interpolate( compatibilityReporterClass = LayerCompatibilityReporter - def isCompatible(self, other: BaseLayer) -> Tuple[bool, LayerCompatibilityReporter]: + def isCompatible(self, other: BaseLayer, cls=None) -> Tuple[bool, LayerCompatibilityReporter]: """Evaluate interpolation compatibility with another layer. :param other: The other :class:`BaseLayer` instance to check diff --git a/Lib/fontParts/base/segment.py b/Lib/fontParts/base/segment.py index d0267513..5737c581 100644 --- a/Lib/fontParts/base/segment.py +++ b/Lib/fontParts/base/segment.py @@ -614,7 +614,7 @@ def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: A compatibilityReporterClass = SegmentCompatibilityReporter def isCompatible( - self, other: BaseSegment + self, other: BaseSegment, cls=None ) -> Tuple[bool, SegmentCompatibilityReporter]: """Evaluate interpolation compatibility with another segment. From 778a2323a7252193e5446c6b698d8f4cfba4a85d Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 5 Dec 2024 19:47:35 +0000 Subject: [PATCH 6/6] Format fixes by ruff --- Lib/fontParts/base/component.py | 27 ++++++++++++++------------- Lib/fontParts/base/font.py | 4 +++- Lib/fontParts/base/glyph.py | 4 +++- Lib/fontParts/base/layer.py | 4 +++- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index 763b0604..14f584bf 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -49,6 +49,7 @@ class BaseComponent( inserted as part of an outline. """ + copyAttributes: Tuple[str, str] = ("baseGlyph", "transformation") def _reprContents(self) -> List[str]: @@ -227,7 +228,7 @@ def _set_baseGlyph(self, value: str) -> None: :return: A :ref:`type-transformation` value representing the transformation matrix of the component. - """ + """, ) def _get_base_transformation(self) -> SextupleType[float]: @@ -288,7 +289,7 @@ def _set_transformation(self, value: SextupleCollectionType[IntFloatType]) -> No :return: A :ref:`type-coordinate.` representing the offset of the component. - """ + """, ) def _get_base_offset(self) -> PairType[IntFloatType]: @@ -348,7 +349,7 @@ def _set_offset(self, value: PairCollectionType[IntFloatType]) -> None: :return: A :class:`tuple` of two :class:`float` items representing the ``(x, y)`` scale of the component. - """ + """, ) def _get_base_scale(self) -> PairType[float]: @@ -416,7 +417,7 @@ def _set_scale(self, value: PairCollectionType[IntFloatType]) -> None: :raise FontPartsError: If attempting to set the index while the component does not belong to a glyph. - """ + """, ) def _get_base_index(self) -> Optional[int]: @@ -739,18 +740,18 @@ def _get_base_bounds(self) -> QuadrupleType[float]: def _get_bounds(self) -> QuadrupleType[float]: """Get the bounds of the component. - This is the environment implementation of the :attr:`BaseComponent.bounds` - property getter. + This is the environment implementation of the :attr:`BaseComponent.bounds` + property getter. - :return: A :class:`tuple` of four :class:`int` or :class:`float` values - in the form ``(x minimum, y minimum, x maximum, y maximum)`` - representing the bounds of the component, or :obj:`None` if the - component is empty. The value will be normalized - with :func:`normalizers.normalizeBoundingBox`. + :return: A :class:`tuple` of four :class:`int` or :class:`float` values + in the form ``(x minimum, y minimum, x maximum, y maximum)`` + representing the bounds of the component, or :obj:`None` if the + component is empty. The value will be normalized + with :func:`normalizers.normalizeBoundingBox`. - .. note:: + .. note:: - Subclasses may override this method. + Subclasses may override this method. """ pen = BoundsPen(self.layer) diff --git a/Lib/fontParts/base/font.py b/Lib/fontParts/base/font.py index 0899ef0a..75002d68 100644 --- a/Lib/fontParts/base/font.py +++ b/Lib/fontParts/base/font.py @@ -2058,7 +2058,9 @@ def _interpolate( compatibilityReporterClass = FontCompatibilityReporter - def isCompatible(self, other: BaseFont, cls=None) -> Tuple[bool, FontCompatibilityReporter]: + def isCompatible( + self, other: BaseFont, cls=None + ) -> Tuple[bool, FontCompatibilityReporter]: """Evaluate interpolation compatibility with another font. This method will return a :class:`bool` indicating if the font is diff --git a/Lib/fontParts/base/glyph.py b/Lib/fontParts/base/glyph.py index f5530694..dc6c9a81 100644 --- a/Lib/fontParts/base/glyph.py +++ b/Lib/fontParts/base/glyph.py @@ -2795,7 +2795,9 @@ def _checkPairs( reporter.warning = True reporterObject.append(compatibility) - def isCompatible(self, other: BaseGlyph, cls=None) -> Tuple[bool, GlyphCompatibilityReporter]: + def isCompatible( + self, other: BaseGlyph, cls=None + ) -> Tuple[bool, GlyphCompatibilityReporter]: """Evaluate interpolation compatibility with another glyph. :param other: The other :class:`BaseGlyph` instance to check diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index a4059e27..081e81eb 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1106,7 +1106,9 @@ def _interpolate( compatibilityReporterClass = LayerCompatibilityReporter - def isCompatible(self, other: BaseLayer, cls=None) -> Tuple[bool, LayerCompatibilityReporter]: + def isCompatible( + self, other: BaseLayer, cls=None + ) -> Tuple[bool, LayerCompatibilityReporter]: """Evaluate interpolation compatibility with another layer. :param other: The other :class:`BaseLayer` instance to check