[cantarell-fonts/housekeeping: 2/3] Update bundled instantiator
- From: Nikolaus Waxweiler <nwaxweiler src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [cantarell-fonts/housekeeping: 2/3] Update bundled instantiator
- Date: Sat, 13 Jun 2020 21:03:49 +0000 (UTC)
commit af9aa377b739389da043bb22ca8c4959d668b83e
Author: Nikolaus Waxweiler <madigens gmail com>
Date: Sat Jun 13 22:03:13 2020 +0100
Update bundled instantiator
scripts/instantiator.py | 113 +++++++++++++++++++++++++++++++++++++++---------
1 file changed, 93 insertions(+), 20 deletions(-)
---
diff --git a/scripts/instantiator.py b/scripts/instantiator.py
index 360e3041..d533073a 100644
--- a/scripts/instantiator.py
+++ b/scripts/instantiator.py
@@ -181,21 +181,36 @@ class Instantiator:
):
"""Instantiates a new data class from a Designspace object."""
if designspace.default is None:
- raise InstantiatorError(
- "Can't generate UFOs from this designspace: no default font."
- )
+ raise InstantiatorError(_error_msg_no_default(designspace))
if any(anisotropic(instance.location) for instance in designspace.instances):
raise InstantiatorError(
"The Designspace contains anisotropic instance locations, which are "
- "not supported by varLib."
+ "not supported by varLib. Look for and remove all 'yvalue=\"...\"' or "
+ "use MutatorMath instead."
)
designspace.loadSourceFonts(ufoLib2.Font.open)
- glyph_names: Set[str] = set()
+ # The default font (default layer) determines which glyphs are interpolated,
+ # because the math behind varLib and MutatorMath uses the default font as the
+ # point of reference for all data.
+ default_font = designspace.default.font
+ glyph_names: Set[str] = set(default_font.keys())
+
for source in designspace.sources:
- glyph_names.update(source.font.keys())
+ other_names = set(source.font.keys())
+ diff_names = other_names - glyph_names
+ if diff_names:
+ logger.warning(
+ "The source %s (%s) contains glyphs that are missing from the "
+ "default source, which will be ignored: %s. If this is unintended, "
+ "check that these glyphs have the exact same name as the "
+ "corresponding glyphs in the default source.",
+ source.name,
+ source.filename,
+ ", ".join(sorted(diff_names)),
+ )
# Construct Variators
axis_bounds: AxisBounds = {} # Design space!
@@ -213,17 +228,31 @@ class Instantiator:
special_axes[axis.tag] = axis
masters_info = collect_info_masters(designspace, axis_bounds)
- info_mutator = Variator.from_masters(masters_info, axis_order)
+ try:
+ info_mutator = Variator.from_masters(masters_info, axis_order)
+ except varLib.errors.VarLibError as e:
+ raise InstantiatorError(
+ f"Cannot set up fontinfo for interpolation: {e}'"
+ ) from e
masters_kerning = collect_kerning_masters(designspace, axis_bounds)
- kerning_mutator = Variator.from_masters(masters_kerning, axis_order)
+ try:
+ kerning_mutator = Variator.from_masters(masters_kerning, axis_order)
+ except varLib.errors.VarLibError as e:
+ raise InstantiatorError(
+ f"Cannot set up kerning for interpolation: {e}'"
+ ) from e
- default_font = designspace.findDefault().font
glyph_mutators: Dict[str, Variator] = {}
glyph_name_to_unicodes: Dict[str, List[int]] = {}
for glyph_name in glyph_names:
items = collect_glyph_masters(designspace, glyph_name, axis_bounds)
- glyph_mutators[glyph_name] = Variator.from_masters(items, axis_order)
+ try:
+ glyph_mutators[glyph_name] = Variator.from_masters(items, axis_order)
+ except varLib.errors.VarLibError as e:
+ raise InstantiatorError(
+ f"Cannot set up glyph '{glyph_name}' for interpolation: {e}'"
+ ) from e
glyph_name_to_unicodes[glyph_name] = default_font[glyph_name].unicodes
# Construct defaults to copy over
@@ -325,7 +354,11 @@ class Instantiator:
# whatever reason (usually outline incompatibility)...
if glyph_name not in self.skip_export_glyphs:
raise InstantiatorError(
- f"Failed to generate instance of glyph '{glyph_name}'."
+ f"Failed to generate instance of glyph '{glyph_name}': "
+ f"{str(e)}. (Note: the most common cause for an error here is "
+ "that the glyph outlines are not point-for-point compatible or "
+ "have the same starting point or are in the same order in all "
+ "masters.)"
) from e
# ...except if the glyph is in public.skipExportGlyphs and would
@@ -417,6 +450,28 @@ class Instantiator:
)
+def _error_msg_no_default(designspace: designspaceLib.DesignSpaceDocument) -> str:
+ if any(axis.map for axis in designspace.axes):
+ bonus_msg = (
+ "For axes with a mapping, the 'default' values should have an "
+ "'input=\"...\"' map value, where the corresponding 'output=\"...\"' "
+ "value then points to the master source."
+ )
+ else:
+ bonus_msg = ""
+
+ default_location = ", ".join(
+ f"{k}: {v}" for k, v in designspace.newDefaultLocation().items()
+ )
+
+ return (
+ "Can't generate UFOs from this Designspace because there is no default "
+ f"master source at location '{default_location}'. Check that all 'default' "
+ "values of all axes together point to a single actual master source. "
+ f"{bonus_msg}"
+ )
+
+
def location_to_key(location: Location) -> LocationKey:
"""Converts a Location into a sorted tuple so it can be used as a dict
key."""
@@ -452,20 +507,31 @@ def collect_kerning_masters(
designspace: designspaceLib.DesignSpaceDocument, axis_bounds: AxisBounds
) -> List[Tuple[Location, FontMathObject]]:
"""Return master kerning objects wrapped by MathKerning."""
+
+ # Always take the groups from the default source. This also avoids fontMath
+ # making a union of all groups it is given.
+ groups = designspace.default.font.groups
+
locations_and_masters = []
for source in designspace.sources:
if source.layerName is not None:
continue # No kerning in source layers.
+ # If a source has groups, they should match the default's.
+ if source.font.groups and source.font.groups != groups:
+ logger.warning(
+ "The source %s (%s) contains different groups than the default source. "
+ "The default source's groups will be used for the instances.",
+ source.name,
+ source.filename,
+ )
+
# This assumes that groups of all sources are the same.
normalized_location = varLib.models.normalizeLocation(
source.location, axis_bounds
)
locations_and_masters.append(
- (
- normalized_location,
- fontMath.MathKerning(source.font.kerning, source.font.groups),
- )
+ (normalized_location, fontMath.MathKerning(source.font.kerning, groups))
)
return locations_and_masters
@@ -582,17 +648,24 @@ def swap_glyph_names(font: ufoLib2.Font, name_old: str, name_new: str):
glyph_old.drawPoints(p)
glyph_swap.width = glyph_old.width
- glyph_old.clear()
+ glyph_old.clearContours()
+ glyph_old.clearComponents()
p = glyph_old.getPointPen()
glyph_new.drawPoints(p)
glyph_old.width = glyph_new.width
- glyph_new.clear()
+ glyph_new.clearContours()
+ glyph_new.clearComponents()
p = glyph_new.getPointPen()
glyph_swap.drawPoints(p)
glyph_new.width = glyph_swap.width
- # 2. Remap components.
+ # 2. Swap anchors.
+ glyph_swap.anchors = glyph_old.anchors
+ glyph_old.anchors = glyph_new.anchors
+ glyph_new.anchors = glyph_swap.anchors
+
+ # 3. Remap components.
for g in font:
for c in g.components:
if c.baseGlyph == name_old:
@@ -600,7 +673,7 @@ def swap_glyph_names(font: ufoLib2.Font, name_old: str, name_new: str):
elif c.baseGlyph == name_new:
c.baseGlyph = name_old
- # 3. Swap literal names in kerning.
+ # 4. Swap literal names in kerning.
kerning_new = {}
for first, second in font.kerning.keys():
value = font.kerning[(first, second)]
@@ -615,7 +688,7 @@ def swap_glyph_names(font: ufoLib2.Font, name_old: str, name_new: str):
kerning_new[(first, second)] = value
font.kerning = kerning_new
- # 4. Swap names in groups.
+ # 5. Swap names in groups.
for group_name, group_members in font.groups.items():
group_members_new = []
for name in group_members:
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]