diff --git a/src/fontra/client/core/classes.json b/src/fontra/client/core/classes.json index ef5436b18..5e465627c 100644 --- a/src/fontra/client/core/classes.json +++ b/src/fontra/client/core/classes.json @@ -18,6 +18,10 @@ "axes": { "type": "list", "subtype": "GlobalAxis" + }, + "sources": { + "type": "list", + "subtype": "GlobalSource" } }, "VariableGlyph": { @@ -66,6 +70,10 @@ "type": "dict", "subtype": "float" }, + "locationBase": { + "type": "str", + "optional": true + }, "inactive": { "type": "bool" }, @@ -102,6 +110,14 @@ "verticalOrigin": { "type": "float", "optional": true + }, + "anchors": { + "type": "list", + "subtype": "Anchor" + }, + "guidelines": { + "type": "list", + "subtype": "Guideline" } }, "PackedPath": { @@ -167,6 +183,40 @@ "type": "float" } }, + "Anchor": { + "name": { + "type": "str" + }, + "x": { + "type": "float" + }, + "y": { + "type": "float" + }, + "customData": { + "type": "dict", + "subtype": "Any" + } + }, + "Guideline": { + "name": { + "type": "str", + "optional": true + }, + "x": { + "type": "float" + }, + "y": { + "type": "float" + }, + "angle": { + "type": "float" + }, + "customData": { + "type": "dict", + "subtype": "Any" + } + }, "GlobalAxis": { "name": { "type": "str" @@ -193,5 +243,35 @@ "hidden": { "type": "bool" } + }, + "GlobalSource": { + "name": { + "type": "str" + }, + "location": { + "type": "dict", + "subtype": "float" + }, + "verticalMetrics": { + "type": "dict", + "subtype": "GlobalMetric" + }, + "guidelines": { + "type": "list", + "subtype": "Guideline" + }, + "customData": { + "type": "dict", + "subtype": "Any" + } + }, + "GlobalMetric": { + "value": { + "type": "float" + }, + "customData": { + "type": "dict", + "subtype": "Any" + } } } diff --git a/src/fontra/core/classes.py b/src/fontra/core/classes.py index 89cad5320..4e86393f6 100644 --- a/src/fontra/core/classes.py +++ b/src/fontra/core/classes.py @@ -10,45 +10,87 @@ from .path import PackedPath, Path, Point, PointType -Location = dict[str, float] -CustomData = dict[str, Any] + +@dataclass(kw_only=True) +class Font: + unitsPerEm: int = 1000 + glyphs: dict[str, VariableGlyph] = field(default_factory=dict) + glyphMap: dict[str, list[int]] = field(default_factory=dict) + customData: CustomData = field(default_factory=dict) + axes: list[Union[GlobalAxis, GlobalDiscreteAxis]] = field(default_factory=list) + sources: list[GlobalSource] = field(default_factory=list) + + def _trackAssignedAttributeNames(self): + # see fonthandler.py + self._assignedAttributeNames = set() + + def __setattr__(self, attrName, value): + if hasattr(self, "_assignedAttributeNames"): + self._assignedAttributeNames.add(attrName) + super().__setattr__(attrName, value) @dataclass(kw_only=True) -class Component: +class GlobalSource: name: str - transformation: DecomposedTransform = field(default_factory=DecomposedTransform) - location: Location = field(default_factory=Location) + location: Location = field(default_factory=dict) + verticalMetrics: dict[str, GlobalMetric] = field(default_factory=dict) + guidelines: list[Union[Guideline, HorizontalGuideline, VerticalGuideline]] = field( + default_factory=list + ) + customData: CustomData = field(default_factory=dict) @dataclass(kw_only=True) -class StaticGlyph: - path: Union[PackedPath, Path] = field(default_factory=PackedPath) - components: list[Component] = field(default_factory=list) - xAdvance: Optional[float] = None - yAdvance: Optional[float] = None - verticalOrigin: Optional[float] = None +class GlobalMetric: + value: float + customData: CustomData = field(default_factory=dict) - def convertToPackedPaths(self): - return replace(self, path=self.path.asPackedPath()) - def convertToPaths(self): - return replace(self, path=self.path.asPath()) +@dataclass(kw_only=True) +class Guideline: + name: Optional[str] + x: float + y: float + angle: float + customData: CustomData = field(default_factory=dict) @dataclass(kw_only=True) -class Source: - name: str - layerName: str - location: Location = field(default_factory=Location) - inactive: bool = False - customData: CustomData = field(default_factory=CustomData) +class HorizontalGuideline: + name: Optional[str] + y: float + customData: CustomData = field(default_factory=dict) @dataclass(kw_only=True) -class Layer: - glyph: StaticGlyph - customData: CustomData = field(default_factory=CustomData) +class VerticalGuideline: + name: str | None + x: float + customData: CustomData = field(default_factory=dict) + + +@dataclass(kw_only=True) +class GlobalAxis: + name: str # this identifies the axis + label: str # a user friendly label + tag: str # the opentype 4-char tag + minValue: float + defaultValue: float + maxValue: float + mapping: list[list[float]] = field(default_factory=list) + hidden: bool = False + + +@dataclass(kw_only=True) +class GlobalDiscreteAxis: + name: str # this identifies the axis + label: str # a user friendly label + tag: str # the opentype 4-char tag + values: list[float] + defaultValue: float + mapping: list[list[float]] = field(default_factory=list) + hidden: bool = False @dataclass(kw_only=True) @@ -65,7 +107,7 @@ class VariableGlyph: axes: list[LocalAxis] = field(default_factory=list) sources: list[Source] = field(default_factory=list) layers: dict[str, Layer] = field(default_factory=dict) - customData: CustomData = field(default_factory=CustomData) + customData: CustomData = field(default_factory=dict) def convertToPackedPaths(self): return _convertToPathType(self, True) @@ -74,11 +116,59 @@ def convertToPaths(self): return _convertToPathType(self, False) -def _hasAnyPathType(varGlyph, pathType): - return any( - isinstance(layer.glyph.path, pathType) for layer in varGlyph.layers.values() +@dataclass(kw_only=True) +class Source: + name: str + layerName: str + location: Location = field(default_factory=dict) + locationBase: Optional[str] = None + inactive: bool = False + customData: CustomData = field(default_factory=dict) + + +@dataclass(kw_only=True) +class Layer: + glyph: StaticGlyph + customData: CustomData = field(default_factory=dict) + + +@dataclass(kw_only=True) +class StaticGlyph: + path: Union[PackedPath, Path] = field(default_factory=PackedPath) + components: list[Component] = field(default_factory=list) + xAdvance: Optional[float] = None + yAdvance: Optional[float] = None + verticalOrigin: Optional[float] = None + anchors: list[Anchor] = field(default_factory=list) + guidelines: list[Union[Guideline, HorizontalGuideline, VerticalGuideline]] = field( + default_factory=list ) + def convertToPackedPaths(self): + return replace(self, path=self.path.asPackedPath()) + + def convertToPaths(self): + return replace(self, path=self.path.asPath()) + + +@dataclass(kw_only=True) +class Component: + name: str + transformation: DecomposedTransform = field(default_factory=DecomposedTransform) + location: Location = field(default_factory=dict) + + +@dataclass(kw_only=True) +class Anchor: + name: str + x: float + y: float + customData: CustomData = field(default_factory=dict) + + +Location = dict[str, float] +CustomData = dict[str, Any] + def _convertToPathType(varGlyph, packedPath): if not _hasAnyPathType(varGlyph, Path if packedPath else PackedPath): @@ -97,49 +187,10 @@ def _convertToPathType(varGlyph, packedPath): ) -@dataclass(kw_only=True) -class GlobalAxis: - name: str # this identifies the axis - label: str # a user friendly label - tag: str # the opentype 4-char tag - minValue: float - defaultValue: float - maxValue: float - mapping: list[list[float]] = field(default_factory=list) - hidden: bool = False - - -@dataclass(kw_only=True) -class GlobalDiscreteAxis: - name: str # this identifies the axis - label: str # a user friendly label - tag: str # the opentype 4-char tag - values: list[float] - defaultValue: float - mapping: list[list[float]] = field(default_factory=list) - hidden: bool = False - - -GlyphSet = dict[str, VariableGlyph] -GlyphMap = dict[str, list[int]] - - -@dataclass(kw_only=True) -class Font: - unitsPerEm: int = 1000 - glyphs: GlyphSet = field(default_factory=GlyphSet) - glyphMap: GlyphMap = field(default_factory=GlyphMap) - customData: CustomData = field(default_factory=CustomData) - axes: list[Union[GlobalAxis, GlobalDiscreteAxis]] = field(default_factory=list) - - def _trackAssignedAttributeNames(self): - # see fonthandler.py - self._assignedAttributeNames = set() - - def __setattr__(self, attrName, value): - if hasattr(self, "_assignedAttributeNames"): - self._assignedAttributeNames.add(attrName) - super().__setattr__(attrName, value) +def _hasAnyPathType(varGlyph, pathType): + return any( + isinstance(layer.glyph.path, pathType) for layer in varGlyph.layers.values() + ) def makeSchema(*classes, schema=None): diff --git a/test-common/fonts/MutatorSans.fontra/font-data.json b/test-common/fonts/MutatorSans.fontra/font-data.json index 80631efbd..34f511d6a 100644 --- a/test-common/fonts/MutatorSans.fontra/font-data.json +++ b/test-common/fonts/MutatorSans.fontra/font-data.json @@ -43,5 +43,6 @@ "mapping": [], "hidden": false } -] +], +"sources": [] }