diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 4386b584..6172c128 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1,5 +1,6 @@ # pylint: disable=C0103, C0114 from __future__ import annotations +from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, Any, @@ -188,7 +189,7 @@ def interpolate( # ------------ -class BaseObject: +class BaseObject(Generic[BaseObjectType]): r"""Provide common base functionality to objects. This class is intended to serve as a foundation for other classes, supplying @@ -994,7 +995,7 @@ def _clear(self) -> None: del self[key] -class TransformationMixin: +class TransformationMixin(ABC): """Provide objects transformation-related functionality.""" # --------------- @@ -1252,8 +1253,16 @@ def _skewBy( t = transform.Identity.skew(x=x, y=y) self.transformBy(tuple(t), origin=origin, **kwargs) + # ---------------- + # Abstract members + # ---------------- -class InterpolationMixin: + @abstractmethod + def raiseNotImplementedError(self): + pass + + +class InterpolationMixin(ABC): """Provide objects with interpolation-related functionality. :cvar compatibilityReporterClass: A class used for reporting interpolation @@ -1307,8 +1316,16 @@ def _isCompatible(self, other: Any, reporter: Any) -> None: """ self.raiseNotImplementedError() + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass -class SelectionMixin: + +class SelectionMixin(ABC): """Provide objects with selection-related functionality.""" # ------------- @@ -1382,19 +1399,25 @@ def _set_selected(self, value: bool) -> None: # Sub-Objects # ----------- @classmethod - def _getSelectedSubObjects(cls, subObjects: CollectionType[Any]) -> Tuple[Any]: + def _getSelectedSubObjects(cls, subObjects: Any) -> Tuple[Any]: selected = tuple(obj for obj in subObjects if obj.selected) return selected @classmethod - def _setSelectedSubObjects( - cls, subObjects: CollectionType[Any], selected: CollectionType[Any] - ) -> None: + def _setSelectedSubObjects(cls, subObjects: Any, selected: Any) -> None: for obj in subObjects: obj.selected = obj in selected + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + -class PointPositionMixin: +class PointPositionMixin(ABC): """Provide objects with the ability to determine point position. This class adds a `position` attribute as a :class:`dyanmicProperty`, for @@ -1460,8 +1483,56 @@ def _set_position(self, value: PairCollectionType[IntFloatType]) -> None: dY = y - pY self.moveBy((dX, dY)) + # ---------------- + # Abstract members + # ---------------- + + x: dynamicProperty = dynamicProperty("base_x") + + @abstractmethod + def _get_base_x(self) -> IntFloatType: + pass + + @abstractmethod + def _set_base_x(self, value: IntFloatType) -> None: + pass + + @abstractmethod + def _get_x(self) -> IntFloatType: + pass + + @abstractmethod + def _set_x(self, value: IntFloatType) -> None: + pass + + y: dynamicProperty = dynamicProperty("base_y") + + @abstractmethod + def _get_base_y(self) -> IntFloatType: + pass + + @abstractmethod + def _set_base_y(self, value: IntFloatType) -> None: + pass + + @abstractmethod + def _get_y(self) -> IntFloatType: + pass + + @abstractmethod + def _set_y(self, value: IntFloatType) -> None: + pass + + @abstractmethod + def moveBy(self, value): + pass + + @abstractmethod + def raiseNotImplementedError(self): + pass -class IdentifierMixin: + +class IdentifierMixin(ABC): """Provide objects with a unique identifier.""" # identifier @@ -1552,6 +1623,14 @@ def _setIdentifier(self, value: str) -> None: """ pass + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + def reference(obj: Callable[[], Any]) -> Callable[[], Any]: """ diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index d55ab19d..8d513f5f 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -1,5 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, cast, Any, Iterator, List, Optional, Tuple, Union +from typing import ( + TYPE_CHECKING, + cast, + Any, + Iterator, + List, + Optional, + Tuple, + TypeVar, + Union, +) from fontParts.base.errors import FontPartsError from fontParts.base.base import ( @@ -32,6 +42,7 @@ from fontParts.base.layer import BaseLayer from fontParts.base.font import BaseFont +BaseContourType = TypeVar("BaseContourType", bound="BaseContour") PointCollectionType = CollectionType[PairCollectionType[IntFloatType]] @@ -65,7 +76,7 @@ def _reprContents(self) -> List[str]: contents += self.glyph._reprContents() return contents - def copyData(self, source: BaseContour) -> None: + def copyData(self, source: BaseContourType) -> None: """Copy data from another contour instance. This will copy the contents of the following attributes from `source` diff --git a/Lib/fontParts/base/font.py b/Lib/fontParts/base/font.py index 620d1513..37f67ca8 100644 --- a/Lib/fontParts/base/font.py +++ b/Lib/fontParts/base/font.py @@ -204,7 +204,7 @@ def _get_base_path(self) -> Optional[str]: path = normalizers.normalizeFilePath(path) return path - def _get_path(self, **kwargs: Any) -> Optional[str]: + def _get_path(self, **kwargs: Any) -> Optional[str]: # type: ignore[return] r"""Get the path to the native font file. This method is the environment implementation @@ -574,7 +574,7 @@ def _get_base_info(self) -> BaseInfo: info.font = self return info - def _get_info(self) -> BaseInfo: + def _get_info(self) -> BaseInfo: # type: ignore[return] """Get the native font's info object. This is the environment implementation of :attr:`BaseFont.info`. @@ -613,7 +613,7 @@ def _get_base_groups(self) -> BaseGroups: groups.font = self return groups - def _get_groups(self) -> BaseGroups: + def _get_groups(self) -> BaseGroups: # type: ignore[return] """Get the native font's groups object. This is the environment implementation @@ -653,7 +653,7 @@ def _get_base_kerning(self) -> BaseKerning: kerning.font = self return kerning - def _get_kerning(self) -> BaseKerning: + def _get_kerning(self) -> BaseKerning: # type: ignore[return] """Get the native font's kerning object. This is the environment implementation @@ -752,7 +752,7 @@ def _get_base_features(self) -> BaseFeatures: features.font = self return features - def _get_features(self) -> BaseFeatures: + def _get_features(self) -> BaseFeatures: # type: ignore[return] """Get the native font's features object. This is the environment implementation of @@ -792,7 +792,7 @@ def _get_base_lib(self) -> BaseLib: lib.font = self return lib - def _get_lib(self) -> BaseLib: + def _get_lib(self) -> BaseLib: # type: ignore[return] """Get the native font's lib object. This is the environment implementation of :attr:`BaseFont.lib`. @@ -837,7 +837,7 @@ def _get_base_tempLib(self) -> BaseLib: lib.font = self return lib - def _get_tempLib(self) -> BaseLib: + def _get_tempLib(self) -> BaseLib: # type: ignore[return] """Get the native font's temporary lib object. This is the environment implementation @@ -883,7 +883,7 @@ def _get_base_layers(self) -> Tuple[BaseLayer, ...]: self._setFontInLayer(layer) return tuple(layers) - def _get_layers(self, **kwargs: Any) -> Tuple[BaseLayer, ...]: + def _get_layers(self, **kwargs: Any) -> Tuple[BaseLayer, ...]: # type: ignore[return] r"""Get the native font's layer objects. This is the environment implementation of @@ -932,7 +932,7 @@ def _set_base_layerOrder(self, value: CollectionType[str]) -> None: value = normalizers.normalizeLayerOrder(value, self) self._set_layerOrder(value) - def _get_layerOrder(self, **kwargs: Any) -> Tuple[str, ...]: + def _get_layerOrder(self, **kwargs: Any) -> Tuple[str, ...]: # type: ignore[return] r"""Get the order of the layers in the native font. This is the environment implementation of the @@ -1006,7 +1006,7 @@ def _set_base_defaultLayerName(self, value: str) -> None: value = normalizers.normalizeDefaultLayerName(value, self) self._set_defaultLayerName(value) - def _get_defaultLayerName(self) -> str: + def _get_defaultLayerName(self) -> str: # type: ignore[return] """Get the name of the native font's default layer. This is the environment implementation of @@ -1181,7 +1181,7 @@ def newLayer( self._setFontInLayer(layer) return layer - def _newLayer( + def _newLayer( # type: ignore[return] self, name: str, color: Optional[QuadrupleCollectionType[IntFloatType]], @@ -1577,7 +1577,7 @@ def _set_base_glyphOrder(self, value: CollectionType[str]) -> None: value = normalizers.normalizeGlyphOrder(value) self._set_glyphOrder(value) - def _get_glyphOrder(self) -> Tuple[str, ...]: + def _get_glyphOrder(self) -> Tuple[str, ...]: # type: ignore[return] r"""Get the order of the glyphs in the native font. This is the environment implementation of the @@ -1732,7 +1732,7 @@ def _get_guidelines(self) -> Tuple[BaseGuideline, ...]: def _len__guidelines(self) -> int: return self._lenGuidelines() - def _lenGuidelines(self, **kwargs: Any) -> int: + def _lenGuidelines(self, **kwargs: Any) -> int: # type: ignore[return] r"""Return the number of font-level guidelines in the native font. :param \**kwargs: Additional keyword arguments. @@ -1754,7 +1754,7 @@ def _getitem__guidelines(self, index: int) -> BaseGuideline: self._setFontInGuideline(guideline) return guideline - def _getGuideline(self, index: int, **kwargs: Any) -> BaseGuideline: + def _getGuideline(self, index: int, **kwargs: Any) -> BaseGuideline: # type: ignore[return] r"""Return the guideline at the given index. :param index: The index of the guideline. @@ -1846,7 +1846,7 @@ def appendGuideline( newGuideline.font = self return newGuideline - def _appendGuideline( + def _appendGuideline( # type: ignore[return] self, position: Optional[PairCollectionType[IntFloatType]], angle: Optional[float], diff --git a/Lib/fontParts/base/image.py b/Lib/fontParts/base/image.py index 21afa275..5509b826 100644 --- a/Lib/fontParts/base/image.py +++ b/Lib/fontParts/base/image.py @@ -2,7 +2,6 @@ from fontParts.base.base import ( BaseObject, TransformationMixin, - PointPositionMixin, SelectionMixin, dynamicProperty, reference, @@ -15,7 +14,6 @@ class BaseImage( BaseObject, TransformationMixin, - PointPositionMixin, SelectionMixin, DeprecatedImage, RemovedImage, diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index 93703ef5..6b008105 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1,6 +1,15 @@ # pylint: disable=C0103, C0302, C0114, W0613 from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Optional, Tuple +from abc import ABC, abstractmethod +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + Optional, + Tuple, +) import collections from fontParts.base.base import ( @@ -29,7 +38,7 @@ from fontParts.base.lib import BaseLib -class _BaseGlyphVendor(BaseObject, SelectionMixin): +class _BaseGlyphVendor(BaseObject, SelectionMixin, ABC): """Provide common glyph interaction. This class provides common glyph interaction code to the @@ -573,6 +582,28 @@ def _set_selectedGlyphNames(self, value: CollectionType[str]) -> None: has_key: Callable[[_BaseGlyphVendor, str], bool] = __contains__ + # ---------------- + # Abstract Members + # ---------------- + + defaultLayer: dynamicProperty = dynamicProperty("base_defaultLayer") + + @abstractmethod + def _get_base_defaultLayer(self) -> BaseLayer: + pass + + @abstractmethod + def _set_base_defaultLayer(self, layer: BaseLayer) -> None: + pass + + @abstractmethod + def _get_defaultLayer(self) -> BaseLayer: + pass + + @abstractmethod + def _set_defaultLayer(self, value: BaseLayer) -> None: + pass + class BaseLayer(_BaseGlyphVendor, InterpolationMixin, DeprecatedLayer, RemovedLayer): """Represent the basis for a layer object. @@ -1224,3 +1255,21 @@ def _getCharacterMapping(self) -> CharacterMappingType: for code in glyph.unicodes: mapping[code].append(glyph.name) return {k: tuple(v) for k, v in mapping.items()} + + # ------------------------- + # Abstract Member Overrides + # ------------------------- + + defaultLayer: dynamicProperty = dynamicProperty("base_defaultLayer") + + def _get_base_defaultLayer(self) -> BaseLayer: + raise NotImplementedError("BaseLayer does not implement this method.") + + def _set_base_defaultLayer(self, layer: BaseLayer) -> None: + raise NotImplementedError("BaseLayer does not implement this method.") + + def _get_defaultLayer(self) -> BaseLayer: + raise NotImplementedError("BaseLayer does not implement this method.") + + def _set_defaultLayer(self, value: BaseLayer) -> None: + raise NotImplementedError("BaseLayer does not implement this method.")