Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart component speedup #829

Merged
merged 5 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions Lib/glyphsLib/builder/smart_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@

from enum import IntEnum

# We're going to use pickle/unpickle to copy the node objects because
# it's considerably faster than copy.deepcopy
import pickle

from fontTools.varLib.models import VariationModel, normalizeValue
from fontTools.varLib.models import VariationModel, normalizeValue, VariationModelError
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates

from glyphsLib.classes import GSLayer
Expand Down Expand Up @@ -61,10 +57,20 @@ def normalized_location(layer, base_layer):
return loc


def variation_model(glyph, smart_layers):
def variation_model(glyph, smart_layers, layer):
master_locations = [normalized_location(l, smart_layers[0]) for l in smart_layers]
axis_order = [ax.name for ax in glyph.smartComponentAxes]
return VariationModel(master_locations, axisOrder=axis_order, extrapolate=True)
try:
model = VariationModel(master_locations, axisOrder=axis_order, extrapolate=True)
except VariationModelError as e:
locations = "Locations were:\n"
for smart_layer, master_location in zip(smart_layers, master_locations):
locations += f" {smart_layer.name} = {master_location}\n"
raise ValueError(
"Could not generate smart component model for %s used in %s.\n%s"
% (glyph.name, layer, locations)
) from e
return model


# Two slightly horrible functions for turning a GSLayer into a
Expand Down Expand Up @@ -103,7 +109,8 @@ def to_ufo_smart_component(self, layer, component, pen):
"Could not find any masters for the smart component %s used in %s"
% (root.name, layer.name)
)
model = variation_model(root, masters)

model = variation_model(root, masters, layer)

# Determine the normalized location of the interpolant within the
# mini-designspace, remembering that we have to work out where the
Expand All @@ -120,12 +127,17 @@ def to_ufo_smart_component(self, layer, component, pen):
for name, value in component.smartComponentValues.items()
}
coordinates = [get_coordinates(l) for l in masters]
new_coords = model.interpolateFromMasters(normalized_location, coordinates)
try:
new_coords = model.interpolateFromMasters(normalized_location, coordinates)
except Exception as e:
raise ValueError(
"Could not interpolate smart component %s used in %s" % (root.name, layer)
) from e

# Decompose by creating a new layer, copying its shapes and applying
# the new coordinates
new_layer = GSLayer()
new_layer._shapes = pickle.loads(pickle.dumps(masters[0]._shapes))
new_layer._shapes = [shape.clone() for shape in masters[0]._shapes]
set_coordinates(new_layer, new_coords)

# Don't forget that the GSComponent might also be transformed, so
Expand Down
18 changes: 18 additions & 0 deletions Lib/glyphsLib/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1893,6 +1893,14 @@ def __init__(
if name is not None:
self.name = name

def clone(self):
"""Clones the node (does not clone attributes)"""
return GSNode(
position=(self._position.x, self._position.y),
type=self.type,
smooth=self.smooth,
)

def __repr__(self):
content = self.type
if self.smooth:
Expand Down Expand Up @@ -2130,6 +2138,13 @@ def __init__(self):
self._nodes = []
self.attributes = {}

def clone(self):
"""Clones the path (Does not clone attributes)"""
cloned = GSPath()
cloned.closed = self.closed
cloned.nodes = [node.clone() for node in self.nodes]
return cloned

@property
def parent(self):
return self._parent
Expand Down Expand Up @@ -2498,6 +2513,9 @@ def __init__(self, glyph="", offset=(0, 0), scale=(1, 1), transform=None):
else:
self.transform = transform

def clone(self):
return GSComponent(self.name, transform=copy.deepcopy(self.transform))

def __repr__(self):
return '<GSComponent "{}" x={:.1f} y={:.1f}>'.format(
self.name, self.transform[4], self.transform[5]
Expand Down