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
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])

View File

@ -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

View File

@ -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: