diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py index 400e960e4..31ec7c6bd 100644 --- a/Lib/fontTools/designspaceLib/__init__.py +++ b/Lib/fontTools/designspaceLib/__init__.py @@ -8,7 +8,7 @@ import os import posixpath from io import BytesIO, StringIO from textwrap import indent -from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union +from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union, cast from fontTools.misc import etree as ET from fontTools.misc import plistlib @@ -1352,7 +1352,7 @@ class BaseDocWriter(object): minVersion = self.documentObject.formatTuple if ( any( - isinstance(axis, DiscreteAxisDescriptor) or + hasattr(axis, 'values') or axis.axisOrdering is not None or axis.axisLabels for axis in self.documentObject.axes @@ -1445,10 +1445,10 @@ class BaseDocWriter(object): for label in axisObject.axisLabels: self._addAxisLabel(labelsElement, label) axisElement.append(labelsElement) - if isinstance(axisObject, AxisDescriptor): + if hasattr(axisObject, "minimum"): axisElement.attrib['minimum'] = self.intOrFloat(axisObject.minimum) axisElement.attrib['maximum'] = self.intOrFloat(axisObject.maximum) - elif isinstance(axisObject, DiscreteAxisDescriptor): + elif hasattr(axisObject, "values"): axisElement.attrib['values'] = " ".join(self.intOrFloat(v) for v in axisObject.values) axisElement.attrib['default'] = self.intOrFloat(axisObject.default) if axisObject.hidden: @@ -1682,14 +1682,19 @@ class BaseDocWriter(object): for subset in vf.axisSubsets: subsetElement = ET.Element('axis-subset') subsetElement.attrib['name'] = subset.name - if isinstance(subset, RangeAxisSubsetDescriptor): + # Mypy doesn't support narrowing union types via hasattr() + # https://mypy.readthedocs.io/en/stable/type_narrowing.html + # TODO(Python 3.10): use TypeGuard + if hasattr(subset, "userMinimum"): + subset = cast(RangeAxisSubsetDescriptor, subset) if subset.userMinimum != -math.inf: subsetElement.attrib['userminimum'] = self.intOrFloat(subset.userMinimum) if subset.userMaximum != math.inf: subsetElement.attrib['usermaximum'] = self.intOrFloat(subset.userMaximum) if subset.userDefault is not None: subsetElement.attrib['userdefault'] = self.intOrFloat(subset.userDefault) - elif isinstance(subset, ValueAxisSubsetDescriptor): + elif hasattr(subset, "userValue"): + subset = cast(ValueAxisSubsetDescriptor, subset) subsetElement.attrib['uservalue'] = self.intOrFloat(subset.userValue) subsetsElement.append(subsetElement) vfElement.append(subsetsElement) @@ -2904,8 +2909,12 @@ class DesignSpaceDocument(LogMixin, AsDictMixin): discreteAxes = [] rangeAxisSubsets: List[Union[RangeAxisSubsetDescriptor, ValueAxisSubsetDescriptor]] = [] for axis in self.axes: - if isinstance(axis, DiscreteAxisDescriptor): - discreteAxes.append(axis) + if hasattr(axis, "values"): + # Mypy doesn't support narrowing union types via hasattr() + # TODO(Python 3.10): use TypeGuard + # https://mypy.readthedocs.io/en/stable/type_narrowing.html + axis = cast(DiscreteAxisDescriptor, axis) + discreteAxes.append(axis) # type: ignore else: rangeAxisSubsets.append(RangeAxisSubsetDescriptor(name=axis.name)) valueCombinations = itertools.product(*[axis.values for axis in discreteAxes]) diff --git a/Lib/fontTools/designspaceLib/split.py b/Lib/fontTools/designspaceLib/split.py index 2a09418ce..408de70a1 100644 --- a/Lib/fontTools/designspaceLib/split.py +++ b/Lib/fontTools/designspaceLib/split.py @@ -7,7 +7,7 @@ from __future__ import annotations import itertools import logging import math -from typing import Any, Callable, Dict, Iterator, List, Tuple +from typing import Any, Callable, Dict, Iterator, List, Tuple, cast from fontTools.designspaceLib import ( AxisDescriptor, @@ -21,9 +21,9 @@ from fontTools.designspaceLib import ( ) from fontTools.designspaceLib.statNames import StatNames, getStatNames from fontTools.designspaceLib.types import ( + ConditionSet, Range, Region, - ConditionSet, getVFUserRegion, locationInRegion, regionInRegion, @@ -87,11 +87,18 @@ def splitInterpolable( discreteAxes = [] interpolableUserRegion: Region = {} for axis in doc.axes: - if isinstance(axis, DiscreteAxisDescriptor): + if hasattr(axis, "values"): + # Mypy doesn't support narrowing union types via hasattr() + # TODO(Python 3.10): use TypeGuard + # https://mypy.readthedocs.io/en/stable/type_narrowing.html + axis = cast(DiscreteAxisDescriptor, axis) discreteAxes.append(axis) else: + axis = cast(AxisDescriptor, axis) interpolableUserRegion[axis.name] = Range( - axis.minimum, axis.maximum, axis.default + axis.minimum, + axis.maximum, + axis.default, ) valueCombinations = itertools.product(*[axis.values for axis in discreteAxes]) for values in valueCombinations: @@ -191,7 +198,11 @@ def _extractSubSpace( for axis in doc.axes: range = userRegion[axis.name] - if isinstance(range, Range) and isinstance(axis, AxisDescriptor): + if isinstance(range, Range) and hasattr(axis, "minimum"): + # Mypy doesn't support narrowing union types via hasattr() + # TODO(Python 3.10): use TypeGuard + # https://mypy.readthedocs.io/en/stable/type_narrowing.html + axis = cast(AxisDescriptor, axis) subDoc.addAxis( AxisDescriptor( # Same info diff --git a/Lib/fontTools/designspaceLib/types.py b/Lib/fontTools/designspaceLib/types.py index cc3285ec7..80ba9d6d7 100644 --- a/Lib/fontTools/designspaceLib/types.py +++ b/Lib/fontTools/designspaceLib/types.py @@ -1,14 +1,15 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Union, cast from fontTools.designspaceLib import ( + AxisDescriptor, DesignSpaceDocument, DesignSpaceDocumentError, - DiscreteAxisDescriptor, RangeAxisSubsetDescriptor, SimpleLocationDict, + ValueAxisSubsetDescriptor, VariableFontDescriptor, ) @@ -117,18 +118,24 @@ def getVFUserRegion(doc: DesignSpaceDocument, vf: VariableFontDescriptor) -> Reg raise DesignSpaceDocumentError( f"Cannot find axis named '{axisSubset.name}' for variable font '{vf.name}'." ) - if isinstance(axisSubset, RangeAxisSubsetDescriptor): - if isinstance(axis, DiscreteAxisDescriptor): + if hasattr(axisSubset, "userMinimum"): + # Mypy doesn't support narrowing union types via hasattr() + # TODO(Python 3.10): use TypeGuard + # https://mypy.readthedocs.io/en/stable/type_narrowing.html + axisSubset = cast(RangeAxisSubsetDescriptor, axisSubset) + if not hasattr(axis, "minimum"): raise DesignSpaceDocumentError( f"Cannot select a range over '{axis.name}' for variable font '{vf.name}' " "because it's a discrete axis, use only 'userValue' instead." ) + axis = cast(AxisDescriptor, axis) vfUserRegion[axis.name] = Range( max(axisSubset.userMinimum, axis.minimum), min(axisSubset.userMaximum, axis.maximum), axisSubset.userDefault or axis.default, ) else: + axisSubset = cast(ValueAxisSubsetDescriptor, axisSubset) vfUserRegion[axis.name] = axisSubset.userValue # Any axis not mentioned explicitly has a single location = default value for axis in doc.axes: