Skip to content

Commit

Permalink
Merge pull request #1195 from googlefonts/decompose-components
Browse files Browse the repository at this point in the history
Implement decompose-components action
  • Loading branch information
justvanrossum authored Mar 11, 2024
2 parents bb3a255 + 484a214 commit bfc1b7f
Show file tree
Hide file tree
Showing 47 changed files with 49,161 additions and 5 deletions.
11 changes: 8 additions & 3 deletions src/fontra/core/instancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
from .classes import (
Component,
GlobalAxis,
GlobalDiscreteAxis,
Layer,
LocalAxis,
Source,
StaticGlyph,
VariableGlyph,
)
from .path import InterpolationError, PackedPath, joinPaths
from .protocols import ReadableFontBackend


class LocationCoordinateSystem(Enum):
Expand All @@ -31,11 +33,11 @@ class LocationCoordinateSystem(Enum):

@dataclass
class FontInstancer:
backend: Any
backend: ReadableFontBackend

def __post_init__(self) -> None:
self.glyphInstancers: dict[str, GlyphInstancer] = {}
self.globalAxes: list[GlobalAxis] | None = None
self.globalAxes: list[GlobalAxis | GlobalDiscreteAxis] | None = None

async def getGlyphInstancer(
self, glyphName: str, addToCache: bool = False
Expand All @@ -45,6 +47,7 @@ async def getGlyphInstancer(
if self.globalAxes is None:
self.globalAxes = await self.backend.getGlobalAxes()
glyph = await self.backend.getGlyph(glyphName)
assert glyph is not None, glyphName
glyph = await self._ensureComponentLocationCompatibility(glyph)
glyphInstancer = GlyphInstancer(glyph, self)
if addToCache:
Expand Down Expand Up @@ -194,7 +197,7 @@ def instantiate(
)

@cached_property
def globalAxes(self) -> list[GlobalAxis]:
def globalAxes(self) -> list[GlobalAxis | GlobalDiscreteAxis]:
assert self.fontInstancer.globalAxes is not None
return self.fontInstancer.globalAxes

Expand All @@ -204,6 +207,8 @@ def componentTypes(self) -> list[bool]:
variable (has a non-empty location) and False if it is a "classic"
component.
"""
# TODO: also return True for components that vary their non-translate
# transformation fields
numComponents = len(self.activeLayerGlyphs[0].components)
return [
any(
Expand Down
48 changes: 47 additions & 1 deletion src/fontra/workflow/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from ..backends import getFileSystemBackend, newFileSystemBackend
from ..backends.copy import copyFont
from ..core.classes import GlobalAxis, GlobalDiscreteAxis, VariableGlyph
from ..core.instancer import FontInstancer, LocationCoordinateSystem
from ..core.path import PackedPathPointPen
from ..core.protocols import ReadableFontBackend

# All actions should use this logger, regardless of where they are defined
Expand Down Expand Up @@ -347,7 +349,7 @@ def _renameAxis(axis, axes):
@dataclass(kw_only=True)
class DropInactiveSourcesAction(BaseFilterAction):
async def processGlyph(self, glyph: VariableGlyph) -> VariableGlyph:
usedSources = [source for source in glyph.sources if not source.inactive]
usedSources = getActiveSources(glyph.sources)
usedLayerNames = {source.layerName for source in usedSources}
usedLayers = {
layerName: layer
Expand Down Expand Up @@ -471,3 +473,47 @@ async def processGlobalAxes(self, axes) -> list[GlobalAxis | GlobalDiscreteAxis]

async def processGlyph(self, glyph: VariableGlyph) -> VariableGlyph:
return _remapSourceLocations(glyph, self._axisValueMapFunctions)


@registerActionClass("decompose-components")
@dataclass(kw_only=True)
class DecomposeComponentsAction(BaseFilterAction):
onlyVariableComponents: bool = False

@cached_property
def fontInstancer(self):
return FontInstancer(self.validatedInput)

async def getGlyph(self, glyphName: str) -> VariableGlyph:
instancer = await self.fontInstancer.getGlyphInstancer(glyphName)
glyph = instancer.glyph

if not instancer.componentTypes or (
self.onlyVariableComponents and not any(instancer.componentTypes)
):
return glyph

newLayers = {}
for source in instancer.activeSources:
pen = PackedPathPointPen()
await instancer.drawPoints(
pen,
source.location,
coordSystem=LocationCoordinateSystem.SOURCE,
flattenComponents=True,
flattenVarComponents=True,
)

layer = glyph.layers[source.layerName]
newLayers[source.layerName] = replace(
layer,
glyph=replace(layer.glyph, path=pen.getPath(), components=[]),
)

glyph = replace(glyph, layers=newLayers)

return glyph


def getActiveSources(sources):
return [source for source in sources if not source.inactive]
43 changes: 43 additions & 0 deletions test-py/data/workflow/input-components.fontra/font-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"unitsPerEm": 1000,
"customData": {},
"axes": [
{
"name": "width",
"label": "width",
"tag": "wdth",
"minValue": 0,
"defaultValue": 0,
"maxValue": 1000
},
{
"name": "weight",
"label": "weight",
"tag": "wght",
"minValue": 100,
"defaultValue": 100,
"maxValue": 900,
"mapping": [
[
100,
150
],
[
900,
850
]
]
},
{
"name": "italic",
"label": "italic",
"tag": "ital",
"values": [
0,
1
],
"defaultValue": 0
}
],
"sources": []
}
5 changes: 5 additions & 0 deletions test-py/data/workflow/input-components.fontra/glyph-info.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
glyph name;code points
A;U+0041,U+0061
Adieresis;U+00C4,U+00E4
dieresis;U+00A8
dot;U+27D1
Loading

0 comments on commit bfc1b7f

Please sign in to comment.