From dfc01b7be339d9517202ef43424a7d26fdd9767f Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 11:46:30 +0100 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 From b30b8836dc6e3d8e42ae6cd537021f36e16e4dfc Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 23:36:44 +0100 Subject: [PATCH 07/10] =?UTF-8?q?Resp=C3=B8ve=20`mypy`=20errors=20related?= =?UTF-8?q?=20to=20`base.reference`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/fontParts/base/bPoint.py | 8 ++++---- Lib/fontParts/base/base.py | 2 +- Lib/fontParts/base/component.py | 11 ++++++----- Lib/fontParts/base/contour.py | 5 +++-- Lib/fontParts/base/groups.py | 5 +++-- Lib/fontParts/base/layer.py | 5 +++-- Lib/fontParts/base/lib.py | 9 +++++---- Lib/fontParts/base/point.py | 6 +++--- Lib/fontParts/base/segment.py | 6 +++--- 9 files changed, 31 insertions(+), 26 deletions(-) diff --git a/Lib/fontParts/base/bPoint.py b/Lib/fontParts/base/bPoint.py index 0fa173e5..20e05005 100644 --- a/Lib/fontParts/base/bPoint.py +++ b/Lib/fontParts/base/bPoint.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union from fontTools.misc import transform from fontParts.base.base import ( @@ -59,7 +59,7 @@ def __eq__(self, other): # this class should not be used in hashable # collections since it is dynamically generated. - __hash__ = None + __hash__ = None # type: ignore[assignment] # ------- # Parents @@ -126,7 +126,7 @@ def _get_base_nextSegment(self) -> Optional[BaseSegment]: # Contour - _contour: Optional[BaseContour] = None + _contour: Optional[Callable[[], BaseContour]] = None contour = dynamicProperty( "contour", @@ -151,7 +151,7 @@ def _get_contour(self) -> Optional[BaseContour]: return None return self._contour() - def _set_contour(self, contour: Optional[BaseContour]) -> None: + def _set_contour(self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]]) -> None: if self._contour is not None: raise AssertionError("contour for bPoint already set") if contour is not None: diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index a0b6da74..e355e166 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1631,7 +1631,7 @@ def raiseNotImplementedError(self): pass -def reference(obj: Callable[[], Any]) -> Callable[[], Any]: +def reference(obj: Any) -> Callable[[], Any]: """ This code returns a simple function that returns the given object. This is a backwards compatibility function that is under review (see issue #749). diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index 763b0604..0e5f791b 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, Any, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union from fontTools.misc import transform from fontTools.pens.pointInsidePen import PointInsidePen @@ -67,7 +67,7 @@ def _reprContents(self) -> List[str]: # Glyph - _glyph = None + _glyph: Optional[Callable[[], BaseGlyph]] = None glyph: dynamicProperty = dynamicProperty( "glyph", @@ -92,7 +92,7 @@ def _get_glyph(self) -> Optional[BaseGlyph]: return None return self._glyph() - def _set_glyph(self, glyph: Optional[BaseGlyph]) -> None: + def _set_glyph(self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]]) -> None: if self._glyph is not None: raise AssertionError("glyph for component already set") if glyph is not None: @@ -431,9 +431,10 @@ 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: + index = normalizers.normalizeIndex(value) + if index is None: return + value = index componentCount = len(glyph.components) if value < 0: value = -(value % componentCount) diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index 041bea4a..2533baa5 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -3,6 +3,7 @@ TYPE_CHECKING, cast, Any, + Callable, Iterator, List, Optional, @@ -105,7 +106,7 @@ def copyData(self, source: BaseContourType) -> None: # Glyph - _glyph = None + _glyph: Optional[Callable[[], BaseGlyph]] = None glyph: dynamicProperty = dynamicProperty( "glyph", @@ -130,7 +131,7 @@ def _get_glyph(self) -> Optional[BaseGlyph]: return None return self._glyph() - def _set_glyph(self, glyph: Optional[BaseGlyph]) -> None: + def _set_glyph(self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]]) -> None: if self._glyph is not None: raise AssertionError("glyph for contour already set") if glyph is not None: diff --git a/Lib/fontParts/base/groups.py b/Lib/fontParts/base/groups.py index d35e7590..798f7dca 100644 --- a/Lib/fontParts/base/groups.py +++ b/Lib/fontParts/base/groups.py @@ -7,6 +7,7 @@ List, Optional, Tuple, + Union, ) from collections.abc import MutableMapping @@ -63,7 +64,7 @@ def _reprContents(self) -> List[str]: # Font - _font = None + _font: Optional[Callable[[], BaseFont]] = None font: dynamicProperty = dynamicProperty( "font", @@ -89,7 +90,7 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[BaseFont]) -> None: + def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: if self._font is not None and self._font != font: raise AssertionError("font for groups already set and is not same as font") if font is not None: diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index a4059e27..ad60e970 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -9,6 +9,7 @@ List, Optional, Tuple, + Union, ) import collections @@ -673,7 +674,7 @@ def copyData(self, source: BaseLayer) -> None: # Font - _font = None + _font: Optional[Callable[[], BaseFont]] = None font: dynamicProperty = dynamicProperty( "font", @@ -698,7 +699,7 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[BaseFont]) -> None: + def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: if self._font is not None: raise AssertionError("font for layer already set") if font is not None: diff --git a/Lib/fontParts/base/lib.py b/Lib/fontParts/base/lib.py index 9f4efbb7..69727566 100644 --- a/Lib/fontParts/base/lib.py +++ b/Lib/fontParts/base/lib.py @@ -6,6 +6,7 @@ Iterator, List, Optional, + Union, ) from collections.abc import MutableMapping @@ -64,7 +65,7 @@ def _reprContents(self) -> List[str]: # Glyph - _glyph: Optional[BaseGlyph] = None + _glyph: Optional[Callable[[], BaseGlyph]] = None glyph: dynamicProperty = dynamicProperty( "glyph", @@ -91,7 +92,7 @@ def _get_glyph(self) -> Optional[BaseGlyph]: return None return self._glyph() - def _set_glyph(self, glyph: Optional[BaseGlyph]) -> None: + def _set_glyph(self, glyph: Optional[Union[BaseGlyph, Callable[[], BaseGlyph]]]) -> None: if self._font is not None: raise AssertionError("font for lib already set") if self._glyph is not None and self._glyph() != glyph: @@ -102,7 +103,7 @@ def _set_glyph(self, glyph: Optional[BaseGlyph]) -> None: # Font - _font: Optional[BaseFont] = None + _font: Optional[Callable[[], BaseFont]] = None font: dynamicProperty = dynamicProperty( "font", @@ -131,7 +132,7 @@ def _get_font(self) -> Optional[BaseFont]: return self.glyph.font return None - def _set_font(self, font: Optional[BaseFont]) -> None: + def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: if self._font is not None and self._font() != font: raise AssertionError("font for lib already set and is not same as font") if self._glyph is not None: diff --git a/Lib/fontParts/base/point.py b/Lib/fontParts/base/point.py index b32a2558..5724ca1b 100644 --- a/Lib/fontParts/base/point.py +++ b/Lib/fontParts/base/point.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Union, Tuple +from typing import TYPE_CHECKING, Any, Callable, Optional, Union, Tuple from fontTools.misc import transform from fontParts.base.base import ( @@ -66,7 +66,7 @@ def _reprContents(self) -> list[str]: # Contour - _contour: Optional[BaseContour] = None + _contour: Optional[Callable[[], BaseContour]] = None contour: dynamicProperty = dynamicProperty( "contour", @@ -91,7 +91,7 @@ def _get_contour(self) -> Optional[BaseContour]: return None return self._contour() - def _set_contour(self, contour: Optional[BaseContour]) -> None: + def _set_contour(self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]]) -> None: if self._contour is not None: raise AssertionError("contour for point already set") if contour is not None: diff --git a/Lib/fontParts/base/segment.py b/Lib/fontParts/base/segment.py index 5737c581..5a12202b 100644 --- a/Lib/fontParts/base/segment.py +++ b/Lib/fontParts/base/segment.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Generator, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Callable, Generator, List, Optional, Tuple, Union from fontParts.base.errors import FontPartsError from fontParts.base.base import ( @@ -61,7 +61,7 @@ def _reprContents(self) -> List[str]: # Contour - _contour: Optional[BaseContour] = None + _contour: Optional[Callable[[], BaseContour]] = None contour: dynamicProperty = dynamicProperty( "contour", @@ -86,7 +86,7 @@ def _get_contour(self) -> Optional[BaseContour]: return None return self._contour() - def _set_contour(self, contour: Optional[BaseContour]) -> None: + def _set_contour(self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]]) -> None: if self._contour is not None: raise AssertionError("contour for segment already set") if contour is not None: From fde1ca208f664f33b16c8d92976a757ea1b85d7c Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 5 Dec 2024 22:37:32 +0000 Subject: [PATCH 08/10] Format fixes by ruff --- Lib/fontParts/base/bPoint.py | 4 +++- Lib/fontParts/base/component.py | 4 +++- Lib/fontParts/base/contour.py | 4 +++- Lib/fontParts/base/groups.py | 4 +++- Lib/fontParts/base/layer.py | 4 +++- Lib/fontParts/base/lib.py | 8 ++++++-- Lib/fontParts/base/point.py | 4 +++- Lib/fontParts/base/segment.py | 4 +++- 8 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Lib/fontParts/base/bPoint.py b/Lib/fontParts/base/bPoint.py index 20e05005..525a3448 100644 --- a/Lib/fontParts/base/bPoint.py +++ b/Lib/fontParts/base/bPoint.py @@ -151,7 +151,9 @@ def _get_contour(self) -> Optional[BaseContour]: return None return self._contour() - def _set_contour(self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]]) -> None: + def _set_contour( + self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]] + ) -> None: if self._contour is not None: raise AssertionError("contour for bPoint already set") if contour is not None: diff --git a/Lib/fontParts/base/component.py b/Lib/fontParts/base/component.py index f054e2ed..f8f1d6d3 100644 --- a/Lib/fontParts/base/component.py +++ b/Lib/fontParts/base/component.py @@ -93,7 +93,9 @@ def _get_glyph(self) -> Optional[BaseGlyph]: 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 component already set") if glyph is not None: diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index 2533baa5..1f11c503 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -131,7 +131,9 @@ def _get_glyph(self) -> Optional[BaseGlyph]: 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 contour already set") if glyph is not None: diff --git a/Lib/fontParts/base/groups.py b/Lib/fontParts/base/groups.py index 798f7dca..1ad554ff 100644 --- a/Lib/fontParts/base/groups.py +++ b/Lib/fontParts/base/groups.py @@ -90,7 +90,9 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: + def _set_font( + self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] + ) -> None: if self._font is not None and self._font != font: raise AssertionError("font for groups already set and is not same as font") if font is not None: diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index 8c365a0f..480bc146 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -699,7 +699,9 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: + def _set_font( + self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] + ) -> None: if self._font is not None: raise AssertionError("font for layer already set") if font is not None: diff --git a/Lib/fontParts/base/lib.py b/Lib/fontParts/base/lib.py index 69727566..7f27652d 100644 --- a/Lib/fontParts/base/lib.py +++ b/Lib/fontParts/base/lib.py @@ -92,7 +92,9 @@ def _get_glyph(self) -> Optional[BaseGlyph]: 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._font is not None: raise AssertionError("font for lib already set") if self._glyph is not None and self._glyph() != glyph: @@ -132,7 +134,9 @@ def _get_font(self) -> Optional[BaseFont]: return self.glyph.font return None - def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: + def _set_font( + self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] + ) -> None: if self._font is not None and self._font() != font: raise AssertionError("font for lib already set and is not same as font") if self._glyph is not None: diff --git a/Lib/fontParts/base/point.py b/Lib/fontParts/base/point.py index 5724ca1b..e6e35699 100644 --- a/Lib/fontParts/base/point.py +++ b/Lib/fontParts/base/point.py @@ -91,7 +91,9 @@ def _get_contour(self) -> Optional[BaseContour]: return None return self._contour() - def _set_contour(self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]]) -> None: + def _set_contour( + self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]] + ) -> None: if self._contour is not None: raise AssertionError("contour for point already set") if contour is not None: diff --git a/Lib/fontParts/base/segment.py b/Lib/fontParts/base/segment.py index 5a12202b..3cdbaaa7 100644 --- a/Lib/fontParts/base/segment.py +++ b/Lib/fontParts/base/segment.py @@ -86,7 +86,9 @@ def _get_contour(self) -> Optional[BaseContour]: return None return self._contour() - def _set_contour(self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]]) -> None: + def _set_contour( + self, contour: Optional[Union[BaseContour, Callable[[], BaseContour]]] + ) -> None: if self._contour is not None: raise AssertionError("contour for segment already set") if contour is not None: From cb0d9472cd49c2dde3ef6edd69c4b4803cf0d447 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Fri, 6 Dec 2024 00:19:30 +0100 Subject: [PATCH 09/10] Add annotation of `self` in `Interpolatable` protocol. --- Lib/fontParts/base/annotations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/fontParts/base/annotations.py b/Lib/fontParts/base/annotations.py index 11908339..836fb12d 100644 --- a/Lib/fontParts/base/annotations.py +++ b/Lib/fontParts/base/annotations.py @@ -54,8 +54,8 @@ class Interpolatable(Protocol): """Represent a protocol for interpolatable types.""" - def __add__(self, other: InterpolatableType) -> InterpolatableType: ... + def __add__(self: InterpolatableType, other: InterpolatableType) -> InterpolatableType: ... - def __sub__(self, other: InterpolatableType) -> InterpolatableType: ... + def __sub__(self: InterpolatableType, other: InterpolatableType) -> InterpolatableType: ... - def __mul__(self, other: TransformationType) -> InterpolatableType: ... + def __mul__(self: InterpolatableType, other: TransformationType) -> InterpolatableType: ... From a43d5882fff85b80bab381ceb48ae16b2a7051ce Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 5 Dec 2024 23:19:52 +0000 Subject: [PATCH 10/10] Format fixes by ruff --- Lib/fontParts/base/annotations.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Lib/fontParts/base/annotations.py b/Lib/fontParts/base/annotations.py index 836fb12d..a02df1ee 100644 --- a/Lib/fontParts/base/annotations.py +++ b/Lib/fontParts/base/annotations.py @@ -54,8 +54,14 @@ class Interpolatable(Protocol): """Represent a protocol for interpolatable types.""" - def __add__(self: InterpolatableType, other: InterpolatableType) -> InterpolatableType: ... + def __add__( + self: InterpolatableType, other: InterpolatableType + ) -> InterpolatableType: ... - def __sub__(self: InterpolatableType, other: InterpolatableType) -> InterpolatableType: ... + def __sub__( + self: InterpolatableType, other: InterpolatableType + ) -> InterpolatableType: ... - def __mul__(self: InterpolatableType, other: TransformationType) -> InterpolatableType: ... + def __mul__( + self: InterpolatableType, other: TransformationType + ) -> InterpolatableType: ...