Check for descriptor types with hasattr() to allow custom classes that don't inherit the default descriptors

This commit is contained in:
Jany Belluz 2022-06-01 12:00:52 +01:00
parent 42e4d66184
commit ec4bcf54c9
3 changed files with 44 additions and 17 deletions

View File

@ -8,7 +8,7 @@ import os
import posixpath import posixpath
from io import BytesIO, StringIO from io import BytesIO, StringIO
from textwrap import indent 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 etree as ET
from fontTools.misc import plistlib from fontTools.misc import plistlib
@ -1352,7 +1352,7 @@ class BaseDocWriter(object):
minVersion = self.documentObject.formatTuple minVersion = self.documentObject.formatTuple
if ( if (
any( any(
isinstance(axis, DiscreteAxisDescriptor) or hasattr(axis, 'values') or
axis.axisOrdering is not None or axis.axisOrdering is not None or
axis.axisLabels axis.axisLabels
for axis in self.documentObject.axes for axis in self.documentObject.axes
@ -1445,10 +1445,10 @@ class BaseDocWriter(object):
for label in axisObject.axisLabels: for label in axisObject.axisLabels:
self._addAxisLabel(labelsElement, label) self._addAxisLabel(labelsElement, label)
axisElement.append(labelsElement) axisElement.append(labelsElement)
if isinstance(axisObject, AxisDescriptor): if hasattr(axisObject, "minimum"):
axisElement.attrib['minimum'] = self.intOrFloat(axisObject.minimum) axisElement.attrib['minimum'] = self.intOrFloat(axisObject.minimum)
axisElement.attrib['maximum'] = self.intOrFloat(axisObject.maximum) 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['values'] = " ".join(self.intOrFloat(v) for v in axisObject.values)
axisElement.attrib['default'] = self.intOrFloat(axisObject.default) axisElement.attrib['default'] = self.intOrFloat(axisObject.default)
if axisObject.hidden: if axisObject.hidden:
@ -1682,14 +1682,19 @@ class BaseDocWriter(object):
for subset in vf.axisSubsets: for subset in vf.axisSubsets:
subsetElement = ET.Element('axis-subset') subsetElement = ET.Element('axis-subset')
subsetElement.attrib['name'] = subset.name 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: if subset.userMinimum != -math.inf:
subsetElement.attrib['userminimum'] = self.intOrFloat(subset.userMinimum) subsetElement.attrib['userminimum'] = self.intOrFloat(subset.userMinimum)
if subset.userMaximum != math.inf: if subset.userMaximum != math.inf:
subsetElement.attrib['usermaximum'] = self.intOrFloat(subset.userMaximum) subsetElement.attrib['usermaximum'] = self.intOrFloat(subset.userMaximum)
if subset.userDefault is not None: if subset.userDefault is not None:
subsetElement.attrib['userdefault'] = self.intOrFloat(subset.userDefault) 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) subsetElement.attrib['uservalue'] = self.intOrFloat(subset.userValue)
subsetsElement.append(subsetElement) subsetsElement.append(subsetElement)
vfElement.append(subsetsElement) vfElement.append(subsetsElement)
@ -2904,8 +2909,12 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
discreteAxes = [] discreteAxes = []
rangeAxisSubsets: List[Union[RangeAxisSubsetDescriptor, ValueAxisSubsetDescriptor]] = [] rangeAxisSubsets: List[Union[RangeAxisSubsetDescriptor, ValueAxisSubsetDescriptor]] = []
for axis in self.axes: for axis in self.axes:
if isinstance(axis, DiscreteAxisDescriptor): if hasattr(axis, "values"):
discreteAxes.append(axis) # 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: else:
rangeAxisSubsets.append(RangeAxisSubsetDescriptor(name=axis.name)) rangeAxisSubsets.append(RangeAxisSubsetDescriptor(name=axis.name))
valueCombinations = itertools.product(*[axis.values for axis in discreteAxes]) valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])

View File

@ -7,7 +7,7 @@ from __future__ import annotations
import itertools import itertools
import logging import logging
import math 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 ( from fontTools.designspaceLib import (
AxisDescriptor, AxisDescriptor,
@ -21,9 +21,9 @@ from fontTools.designspaceLib import (
) )
from fontTools.designspaceLib.statNames import StatNames, getStatNames from fontTools.designspaceLib.statNames import StatNames, getStatNames
from fontTools.designspaceLib.types import ( from fontTools.designspaceLib.types import (
ConditionSet,
Range, Range,
Region, Region,
ConditionSet,
getVFUserRegion, getVFUserRegion,
locationInRegion, locationInRegion,
regionInRegion, regionInRegion,
@ -87,11 +87,18 @@ def splitInterpolable(
discreteAxes = [] discreteAxes = []
interpolableUserRegion: Region = {} interpolableUserRegion: Region = {}
for axis in doc.axes: 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) discreteAxes.append(axis)
else: else:
axis = cast(AxisDescriptor, axis)
interpolableUserRegion[axis.name] = Range( 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]) valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])
for values in valueCombinations: for values in valueCombinations:
@ -191,7 +198,11 @@ def _extractSubSpace(
for axis in doc.axes: for axis in doc.axes:
range = userRegion[axis.name] 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( subDoc.addAxis(
AxisDescriptor( AxisDescriptor(
# Same info # Same info

View File

@ -1,14 +1,15 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional, Union, cast
from fontTools.designspaceLib import ( from fontTools.designspaceLib import (
AxisDescriptor,
DesignSpaceDocument, DesignSpaceDocument,
DesignSpaceDocumentError, DesignSpaceDocumentError,
DiscreteAxisDescriptor,
RangeAxisSubsetDescriptor, RangeAxisSubsetDescriptor,
SimpleLocationDict, SimpleLocationDict,
ValueAxisSubsetDescriptor,
VariableFontDescriptor, VariableFontDescriptor,
) )
@ -117,18 +118,24 @@ def getVFUserRegion(doc: DesignSpaceDocument, vf: VariableFontDescriptor) -> Reg
raise DesignSpaceDocumentError( raise DesignSpaceDocumentError(
f"Cannot find axis named '{axisSubset.name}' for variable font '{vf.name}'." f"Cannot find axis named '{axisSubset.name}' for variable font '{vf.name}'."
) )
if isinstance(axisSubset, RangeAxisSubsetDescriptor): if hasattr(axisSubset, "userMinimum"):
if isinstance(axis, DiscreteAxisDescriptor): # 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( raise DesignSpaceDocumentError(
f"Cannot select a range over '{axis.name}' for variable font '{vf.name}' " f"Cannot select a range over '{axis.name}' for variable font '{vf.name}' "
"because it's a discrete axis, use only 'userValue' instead." "because it's a discrete axis, use only 'userValue' instead."
) )
axis = cast(AxisDescriptor, axis)
vfUserRegion[axis.name] = Range( vfUserRegion[axis.name] = Range(
max(axisSubset.userMinimum, axis.minimum), max(axisSubset.userMinimum, axis.minimum),
min(axisSubset.userMaximum, axis.maximum), min(axisSubset.userMaximum, axis.maximum),
axisSubset.userDefault or axis.default, axisSubset.userDefault or axis.default,
) )
else: else:
axisSubset = cast(ValueAxisSubsetDescriptor, axisSubset)
vfUserRegion[axis.name] = axisSubset.userValue vfUserRegion[axis.name] = axisSubset.userValue
# Any axis not mentioned explicitly has a single location = default value # Any axis not mentioned explicitly has a single location = default value
for axis in doc.axes: for axis in doc.axes: