Skip to content

Commit

Permalink
Fix the groups + other WIP stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
belluzj committed Dec 15, 2017
1 parent 3733b3d commit 8e4781a
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 96 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ htmlcov

# Autosaved files
*~

# Files generated by tests
actual*
expected*
36 changes: 18 additions & 18 deletions Lib/glyphsLib/builder/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ def masters(self):
"""
if self._ufos:
return self._ufos.values()
kerning_groups = {}

# Store set of actually existing master (layer) ids. This helps with
# catching dangling layer data that Glyphs may ignore, e.g. when
Expand All @@ -134,7 +133,6 @@ def masters(self):
self.to_ufo_font_attributes(self.family_name)

for glyph in self.font.glyphs:
self.to_ufo_glyph_groups(kerning_groups, glyph)
glyph_name = glyph.name

for layer in glyph.layers.values():
Expand All @@ -153,8 +151,10 @@ def masters(self):
ufo_glyph = ufo.newGlyph(glyph_name)
self.to_ufo_glyph(ufo_glyph, layer, glyph)
ufo_layer = ufo.layers.defaultLayer
ufo_layer.lib[GLYPHS_PREFIX + 'layerOrderInGlyph.' +
glyph.name] = self._layer_order_in_glyph(layer)
if self.minimize_glyphs_diffs:
ufo_layer.lib[GLYPHS_PREFIX + 'layerOrderInGlyph.' +
glyph.name] = self._layer_order_in_glyph(
layer)

for master_id, glyph_name, layer_name, layer \
in supplementary_layer_data:
Expand All @@ -181,22 +181,22 @@ def masters(self):
else:
ufo_layer = ufo_font.layers[layer_name]
# TODO: (jany) move as much as possible into layers.py
ufo_layer.lib[GLYPHS_PREFIX + 'layerId'] = layer.layerId
ufo_layer.lib[GLYPHS_PREFIX + 'layerOrderInGlyph.' +
glyph_name] = self._layer_order_in_glyph(layer)
if self.minimize_glyphs_diffs:
ufo_layer.lib[GLYPHS_PREFIX + 'layerId'] = layer.layerId
ufo_layer.lib[GLYPHS_PREFIX + 'layerOrderInGlyph.' +
glyph_name] = self._layer_order_in_glyph(layer)
ufo_glyph = ufo_layer.newGlyph(glyph_name)
self.to_ufo_glyph(ufo_glyph, layer, layer.parent)

for ufo in self._ufos.values():
if self.propagate_anchors:
self.to_ufo_propagate_font_anchors(ufo)
self.to_ufo_features(ufo) # This depends on the glyphOrder key
self.to_ufo_kerning_groups(ufo, kerning_groups)
for layer in ufo.layers:
self.to_ufo_layer_lib(layer)

for master_id, kerning in self.font.kerning.items():
self.to_ufo_kerning(self._ufos[master_id], kerning)
self.to_ufo_groups()
self.to_ufo_kerning()

return self._ufos.values()

Expand Down Expand Up @@ -261,11 +261,11 @@ def instance_data(self):
from .features import to_ufo_features
from .font import to_ufo_font_attributes
from .glyph import to_ufo_glyph, to_ufo_glyph_background
from .groups import to_ufo_groups
from .guidelines import to_ufo_guidelines
from .hints import to_ufo_hints
from .instances import to_designspace_instances
from .kerning import (to_ufo_kerning, to_ufo_glyph_groups,
to_ufo_kerning_groups)
from .kerning import to_ufo_kerning
from .masters import to_ufo_master_attributes
from .names import to_ufo_names
from .paths import to_ufo_paths
Expand Down Expand Up @@ -348,22 +348,22 @@ def font(self):
self.to_glyphs_ordered_masters()

self._font = self.glyphs_module.GSFont()
self._ufos = OrderedDict() # Same as in UFOBuilder
for index, ufo in enumerate(self.ufos):
kerning_groups = self.to_glyphs_kerning_groups(ufo)

master = self.glyphs_module.GSFontMaster()
self.to_glyphs_font_attributes(ufo, master,
is_initial=(index == 0))
self.to_glyphs_master_attributes(ufo, master)
self._font.masters.insert(len(self._font.masters), master)
self._ufos[master.id] = ufo

for layer in ufo.layers:
self.to_glyphs_layer_lib(layer)
for glyph in layer:
self.to_glyphs_glyph(glyph, layer, master)
self.to_glyphs_glyph_groups(kerning_groups, glyph)

self.to_glyphs_kerning(ufo, master)
self.to_glyphs_groups()
self.to_glyphs_kerning()

# Now that all GSGlyph are built, restore the glyph order
for first_ufo in self.ufos:
Expand Down Expand Up @@ -437,11 +437,11 @@ def _fake_designspace(self, ufos):
from .features import to_glyphs_features
from .font import to_glyphs_font_attributes, to_glyphs_ordered_masters
from .glyph import to_glyphs_glyph
from .groups import to_glyphs_groups
from .guidelines import to_glyphs_guidelines
from .hints import to_glyphs_hints
from .instances import to_glyphs_instances
from .kerning import (to_glyphs_glyph_groups, to_glyphs_kerning_groups,
to_glyphs_kerning)
from .kerning import to_glyphs_kerning
from .layers import to_glyphs_layer, to_glyphs_layer_order
from .masters import to_glyphs_master_attributes
from .names import to_glyphs_family_names, to_glyphs_master_names
Expand Down
11 changes: 11 additions & 0 deletions Lib/glyphsLib/builder/custom_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,17 @@ def register(handler):
for glyphs_name, ufo_name in GLYPHS_UFO_CUSTOM_PARAMS:
register(ParamHandler(glyphs_name, ufo_name, glyphs_long_name=ufo_name))

GLYPHS_UFO_CUSTOM_PARAMS_NO_SHORT_NAME = (
'openTypeHheaCaretSlopeRun',
'openTypeVheaCaretSlopeRun',
'openTypeHheaCaretSlopeRise',
'openTypeVheaCaretSlopeRise',
'openTypeHheaCaretOffset',
'openTypeVheaCaretOffset',
)
for name in GLYPHS_UFO_CUSTOM_PARAMS_NO_SHORT_NAME:
register(ParamHandler(name, name))

# convert code page numbers to OS/2 ulCodePageRange bits
register(ParamHandler(
glyphs_name='codePageRanges',
Expand Down
14 changes: 7 additions & 7 deletions Lib/glyphsLib/builder/glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
PUBLIC_PREFIX)

SCRIPT_LIB_KEY = GLYPHLIB_PREFIX + 'script'
ORIGINAL_WIDTH_KEY = GLYPHLIB_PREFIX + 'originalWidth'


def to_ufo_glyph(self, ufo_glyph, layer, glyph):
Expand Down Expand Up @@ -70,8 +71,7 @@ def to_ufo_glyph(self, ufo_glyph, layer, glyph):
ufo_glyph.font.lib[postscriptNamesKey] = dict()
ufo_glyph.font.lib[postscriptNamesKey][ufo_glyph.name] = production_name

for key in ['leftMetricsKey', 'rightMetricsKey', 'widthMetricsKey',
'leftKerningGroup', 'rightKerningGroup']:
for key in ['leftMetricsKey', 'rightMetricsKey', 'widthMetricsKey']:
value = getattr(layer, key, None)
if value:
ufo_glyph.lib[GLYPHLIB_PREFIX + 'layer.' + key] = value
Expand Down Expand Up @@ -102,7 +102,8 @@ def to_ufo_glyph(self, ufo_glyph, layer, glyph):
elif category == 'Mark' and subCategory == 'Nonspacing' and width > 0:
# zero the width of Nonspacing Marks like Glyphs.app does on export
# TODO: check for customParameter DisableAllAutomaticBehaviour
ufo_glyph.lib[GLYPHLIB_PREFIX + 'originalWidth'] = width
# FIXME: (jany) also don't do that when rt UFO -> glyphs -> UFO
ufo_glyph.lib[ORIGINAL_WIDTH_KEY] = width
ufo_glyph.width = 0
else:
ufo_glyph.width = width
Expand Down Expand Up @@ -176,8 +177,7 @@ def to_glyphs_glyph(self, ufo_glyph, ufo_layer, master):

layer = self.to_glyphs_layer(ufo_layer, glyph, master)

for key in ['leftMetricsKey', 'rightMetricsKey', 'widthMetricsKey',
'leftKerningGroup', 'rightKerningGroup']:
for key in ['leftMetricsKey', 'rightMetricsKey', 'widthMetricsKey']:
for prefix, object in (('glyph.', glyph), ('layer.', layer)):
full_key = GLYPHLIB_PREFIX + prefix + key
if full_key in ufo_glyph.lib:
Expand All @@ -203,8 +203,8 @@ def to_glyphs_glyph(self, ufo_glyph, ufo_layer, master):
layer.width = ufo_glyph.width
if category == 'Mark' and sub_category == 'Nonspacing' and layer.width == 0:
# Restore originalWidth
if GLYPHLIB_PREFIX + 'originalWidth' in ufo_glyph.lib:
layer.width = ufo_glyph.lib[GLYPHLIB_PREFIX + 'originalWidth']
if ORIGINAL_WIDTH_KEY in ufo_glyph.lib:
layer.width = ufo_glyph.lib[ORIGINAL_WIDTH_KEY]
# TODO: check for customParameter DisableAllAutomaticBehaviour?

self.to_glyphs_background_image(ufo_glyph, layer)
Expand Down
190 changes: 190 additions & 0 deletions Lib/glyphsLib/builder/groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import (print_function, division, absolute_import,
unicode_literals)

from collections import defaultdict
import os
import re

from glyphsLib import classes
from .constants import GLYPHLIB_PREFIX

UFO_ORIGINAL_KERNING_GROUPS_KEY = GLYPHLIB_PREFIX + 'originalKerningGroups'
UFO_GROUPS_NOT_IN_FEATURE_KEY = GLYPHLIB_PREFIX + 'groupsNotInFeature'
UFO_KERN_GROUP_PATTERN = re.compile('^public\\.kern([12])\\.(.*)$')


def to_ufo_groups(self):
# Build groups once and then apply to all UFOs.
groups = defaultdict(list)

# Classes usually go to the feature file, unless we have our custom flag
group_names = None
if UFO_GROUPS_NOT_IN_FEATURE_KEY in self.font.userData.keys():
group_names = set(self.font.userData[UFO_GROUPS_NOT_IN_FEATURE_KEY])
if group_names:
for gsclass in self.font.classes.values():
if gsclass.name in group_names:
if gsclass.code:
groups[gsclass.name] = gsclass.code.split(' ')
else:
# Empty group: using split like above would produce ['']
groups[gsclass.name] = []

# Rebuild kerning groups from `left/rightKerningGroup`s
# Use the original list of kerning groups as a base, to recover
# - the original ordering
# - the kerning groups of glyphs that were not in the font (which can be
# stored in a UFO but not by Glyphs)
recovered = set()
orig_groups = self.font.userData.get(UFO_ORIGINAL_KERNING_GROUPS_KEY)
if orig_groups:
for group, glyphs in orig_groups.items():
if not glyphs:
# Restore empty group
groups[group] = []
for glyph_name in glyphs:
# Check that the original value is still valid
match = UFO_KERN_GROUP_PATTERN.match(group)
side = match.group(1)
group_name = match.group(2)
glyph = self.font.glyphs[glyph_name]
if not glyph or getattr(
glyph, _glyph_kerning_attr(glyph, side)) == group_name:
# The original grouping is still valid
groups[group].append(glyph_name)
# Remember not to add this glyph again later
# Thus the original position in the list is preserved
recovered.add((glyph_name, int(side)))

# Read modified grouping values
for glyph in self.font.glyphs.values():
for side in 1, 2:
if (glyph.name, side) not in recovered:
attr = _glyph_kerning_attr(glyph, side)
group = getattr(glyph, attr)
if group:
group = 'public.kern%s.%s' % (side, group)
groups[group].append(glyph.name)

# Update all UFOs with the same info
for ufo in self._ufos.values():
for name, glyphs in groups.items():
# Shallow copy to prevent unexpected object sharing
ufo.groups[name] = glyphs[:]


def to_glyphs_groups(self):
# Build the GSClasses from the groups of the first UFO.
groups = []
for ufo in self.ufos:
for name, glyphs in ufo.groups.items():
if _is_kerning_group(name):
_to_glyphs_kerning_group(self, name, glyphs)
else:
gsclass = classes.GSClass(name, " ".join(glyphs))
self.font.classes.append(gsclass)
groups.append(name)
if self.minimize_ufo_diffs:
self.font.userData[UFO_GROUPS_NOT_IN_FEATURE_KEY] = groups
break

# Check that other UFOs are identical and print a warning if not.
for index, ufo in enumerate(self.ufos):
if index == 0:
reference_ufo = ufo
else:
_assert_groups_are_identical(self, reference_ufo, ufo)


def _is_kerning_group(name):
return (name.startswith('public.kern1.') or
name.startswith('public.kern2.'))


def _to_glyphs_kerning_group(self, name, glyphs):
if self.minimize_ufo_diffs:
# Preserve ordering when going from UFO group
# to left/rightKerningGroup disseminated in GSGlyphs
# back to UFO group.
if not self.font.userData.get(UFO_ORIGINAL_KERNING_GROUPS_KEY):
self.font.userData[UFO_ORIGINAL_KERNING_GROUPS_KEY] = {}
self.font.userData[UFO_ORIGINAL_KERNING_GROUPS_KEY][name] = glyphs

match = UFO_KERN_GROUP_PATTERN.match(name)
side = match.group(1)
group_name = match.group(2)
for glyph_name in glyphs:
glyph = self.font.glyphs[glyph_name]
if glyph:
setattr(glyph, _glyph_kerning_attr(glyph, side), group_name)


def _glyph_kerning_attr(glyph, side):
"""Return leftKerningGroup or rightKerningGroup depending on the UFO
group's side (1 or 2) and the glyph's direction (LTR or RTL).
"""
side = int(side)
if _is_ltr(glyph):
if side == 1:
return 'rightKerningGroup'
else:
return 'leftKerningGroup'
else:
# RTL
if side == 1:
return 'leftKerningGroup'
else:
return 'rightKerningGroup'


def _is_ltr(glyph):
# TODO: (jany) have a real implem?
# The following one is just to make my simple test pass
if glyph.name.endswith('-hb'):
return False
return True


def _assert_groups_are_identical(self, reference_ufo, ufo):
first_time = [True] # Using a mutable as a non-local for closure below

def _warn(message, *args):
if first_time:
self.logger.warn('Using UFO `%s` as a reference for groups:',
_ufo_logging_ref(reference_ufo))
first_time.clear()
self.logger.warn(' ' + message, *args)

# Check for inconsistencies
for group, glyphs in ufo.groups.items():
if group not in reference_ufo.groups:
_warn("group `%s` from `%s` will be lost because it's not "
"defined in the reference UFO", group, _ufo_logging_ref(ufo))
reference_glyphs = reference_ufo.groups[group]
if glyphs != reference_glyphs:
_warn("group `%s` from `%s` will not be stored accurately because "
"it is different from the reference UFO", group,
_ufo_logging_ref(ufo))
_warn(" reference = %s", ' '.join(glyphs))
_warn(" current = %s", ' '.join(reference_glyphs))


def _ufo_logging_ref(ufo):
"""Return a string that can identify this UFO in logs."""
if ufo.path:
return os.path.basename(ufo.path)
return ufo.info.styleName
Loading

0 comments on commit 8e4781a

Please sign in to comment.