diff options
Diffstat (limited to 'Lib/fontTools/varLib/__init__.py')
-rw-r--r-- | Lib/fontTools/varLib/__init__.py | 218 |
1 files changed, 98 insertions, 120 deletions
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 5f0f9bd2..437324e0 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -31,11 +31,13 @@ from fontTools.ttLib.tables.ttProgram import Program from fontTools.ttLib.tables.TupleVariation import TupleVariation from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables.otBase import OTTableWriter -from fontTools.varLib import builder, designspace, models, varStore +from fontTools.varLib import builder, models, varStore from fontTools.varLib.merger import VariationMerger, _all_equal from fontTools.varLib.mvar import MVAR_ENTRIES from fontTools.varLib.iup import iup_delta_optimize -from collections import OrderedDict +from fontTools.varLib.featureVars import addFeatureVariations +from fontTools.designspaceLib import DesignSpaceDocument, AxisDescriptor +from collections import OrderedDict, namedtuple import os.path import logging from pprint import pformat @@ -73,7 +75,7 @@ def _add_fvar(font, axes, instances): axis.axisTag = Tag(a.tag) # TODO Skip axes that have no variation. axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum - axis.axisNameID = nameTable.addName(tounicode(a.labelname['en'])) + axis.axisNameID = nameTable.addName(tounicode(a.labelNames['en'])) # TODO: # Replace previous line with the following when the following issues are resolved: # https://github.com/fonttools/fonttools/issues/930 @@ -82,9 +84,9 @@ def _add_fvar(font, axes, instances): fvar.axes.append(axis) for instance in instances: - coordinates = instance['location'] - name = tounicode(instance['stylename']) - psname = instance.get('postscriptfontname') + coordinates = instance.location + name = tounicode(instance.styleName) + psname = instance.postScriptFontName inst = NamedInstance() inst.subfamilyNameID = nameTable.addName(name) @@ -104,7 +106,7 @@ def _add_avar(font, axes): """ Add 'avar' table to font. - axes is an ordered dictionary of DesignspaceAxis objects. + axes is an ordered dictionary of AxisDescriptor objects. """ assert axes @@ -127,7 +129,7 @@ def _add_avar(font, axes): if not axis.map: continue - items = sorted(axis.map.items()) + items = sorted(axis.map) keys = [item[0] for item in items] vals = [item[1] for item in items] @@ -566,52 +568,59 @@ def _merge_OTL(font, model, master_fonts, axisTags): font['GPOS'].table.remap_device_varidxes(varidx_map) +def _add_GSUB_feature_variations(font, axes, internal_axis_supports, rules): + + def normalize(name, value): + return models.normalizeLocation( + {name: value}, internal_axis_supports + )[name] + + log.info("Generating GSUB FeatureVariations") + + axis_tags = {name: axis.tag for name, axis in axes.items()} + + conditional_subs = [] + for rule in rules: + + region = [] + for conditions in rule.conditionSets: + space = {} + for condition in conditions: + axis_name = condition["name"] + minimum = normalize(axis_name, condition["minimum"]) + maximum = normalize(axis_name, condition["maximum"]) + tag = axis_tags[axis_name] + space[tag] = (minimum, maximum) + region.append(space) + + subs = {k: v for k, v in rule.subs} + + conditional_subs.append((region, subs)) + + addFeatureVariations(font, conditional_subs) + -# Pretty much all of this file should be redesigned and moved inot submodules... -# Such a mess right now, but kludging along... -class _DesignspaceAxis(object): - - def __repr__(self): - return repr(self.__dict__) - - @staticmethod - def _map(v, map): - keys = map.keys() - if not keys: - return v - if v in keys: - return map[v] - k = min(keys) - if v < k: - return v + map[k] - k - k = max(keys) - if v > k: - return v + map[k] - k - # Interpolate - a = max(k for k in keys if k < v) - b = min(k for k in keys if k > v) - va = map[a] - vb = map[b] - return va + (vb - va) * (v - a) / (b - a) - - def map_forward(self, v): - if self.map is None: return v - return self._map(v, self.map) - - def map_backward(self, v): - if self.map is None: return v - map = {v:k for k,v in self.map.items()} - return self._map(v, map) +_DesignSpaceData = namedtuple( + "_DesignSpaceData", + [ + "axes", + "internal_axis_supports", + "base_idx", + "normalized_master_locs", + "masters", + "instances", + "rules", + ], +) def load_designspace(designspace_filename): - ds = designspace.load(designspace_filename) - axes = ds.get('axes') - masters = ds.get('sources') + ds = DesignSpaceDocument.fromfile(designspace_filename) + masters = ds.sources if not masters: raise VarLibError("no sources found in .designspace") - instances = ds.get('instances', []) + instances = ds.instances standard_axis_map = OrderedDict([ ('weight', ('wght', {'en':'Weight'})), @@ -620,70 +629,31 @@ def load_designspace(designspace_filename): ('optical', ('opsz', {'en':'Optical Size'})), ]) - # Setup axes - axis_objects = OrderedDict() - if axes is not None: - for axis_dict in axes: - axis_name = axis_dict.get('name') - if not axis_name: - axis_name = axis_dict['name'] = axis_dict['tag'] - if 'map' not in axis_dict: - axis_dict['map'] = None - else: - axis_dict['map'] = {m['input']:m['output'] for m in axis_dict['map']} - - if axis_name in standard_axis_map: - if 'tag' not in axis_dict: - axis_dict['tag'] = standard_axis_map[axis_name][0] - if 'labelname' not in axis_dict: - axis_dict['labelname'] = standard_axis_map[axis_name][1].copy() - - axis = _DesignspaceAxis() - for item in ['name', 'tag', 'minimum', 'default', 'maximum', 'map']: - assert item in axis_dict, 'Axis does not have "%s"' % item - if 'labelname' not in axis_dict: - axis_dict['labelname'] = {'en': axis_name} - axis.__dict__ = axis_dict - axis_objects[axis_name] = axis - else: - # No <axes> element. Guess things... - base_idx = None - for i,m in enumerate(masters): - if 'info' in m and m['info']['copy']: - assert base_idx is None - base_idx = i - assert base_idx is not None, "Cannot find 'base' master; Either add <axes> element to .designspace document, or add <info> element to one of the sources in the .designspace document." - - master_locs = [o['location'] for o in masters] - base_loc = master_locs[base_idx] - axis_names = set(base_loc.keys()) - assert all(name in standard_axis_map for name in axis_names), "Non-standard axis found and there exist no <axes> element." - - for name,(tag,labelname) in standard_axis_map.items(): - if name not in axis_names: - continue - - axis = _DesignspaceAxis() - axis.name = name - axis.tag = tag - axis.labelname = labelname.copy() - axis.default = base_loc[name] - axis.minimum = min(m[name] for m in master_locs if name in m) - axis.maximum = max(m[name] for m in master_locs if name in m) - axis.map = None - # TODO Fill in weight / width mapping from OS/2 table? Need loading fonts... - axis_objects[name] = axis - del base_idx, base_loc, axis_names, master_locs - axes = axis_objects - del axis_objects - log.info("Axes:\n%s", pformat(axes)) + axes = OrderedDict() + for axis in ds.axes: + axis_name = axis.name + if not axis_name: + assert axis.tag is not None + axis_name = axis.name = axis.tag + + if axis_name in standard_axis_map: + if axis.tag is None: + axis.tag = standard_axis_map[axis_name][0] + if not axis.labelNames: + axis.labelNames.update(standard_axis_map[axis_name][1]) + else: + assert axis.tag is not None + if not axis.labelNames: + axis.labelNames["en"] = axis_name + axes[axis_name] = axis + log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()])) # Check all master and instance locations are valid and fill in defaults for obj in masters+instances: - obj_name = obj.get('name', obj.get('stylename', '')) - loc = obj['location'] + obj_name = obj.name or obj.styleName or '' + loc = obj.location for axis_name in loc.keys(): assert axis_name in axes, "Location axis '%s' unknown for '%s'." % (axis_name, obj_name) for axis_name,axis in axes.items(): @@ -693,10 +663,9 @@ def load_designspace(designspace_filename): v = axis.map_backward(loc[axis_name]) assert axis.minimum <= v <= axis.maximum, "Location for axis '%s' (mapped to %s) out of range for '%s' [%s..%s]" % (axis_name, v, obj_name, axis.minimum, axis.maximum) - # Normalize master locations - internal_master_locs = [o['location'] for o in masters] + internal_master_locs = [o.location for o in masters] log.info("Internal master locations:\n%s", pformat(internal_master_locs)) # TODO This mapping should ideally be moved closer to logic in _add_fvar/avar @@ -709,7 +678,6 @@ def load_designspace(designspace_filename): normalized_master_locs = [models.normalizeLocation(m, internal_axis_supports) for m in internal_master_locs] log.info("Normalized master locations:\n%s", pformat(normalized_master_locs)) - # Find base master base_idx = None for i,m in enumerate(normalized_master_locs): @@ -719,7 +687,15 @@ def load_designspace(designspace_filename): assert base_idx is not None, "Base master not found; no master at default location?" log.info("Index of base master: %s", base_idx) - return axes, internal_axis_supports, base_idx, normalized_master_locs, masters, instances + return _DesignSpaceData( + axes, + internal_axis_supports, + base_idx, + normalized_master_locs, + masters, + instances, + ds.rules, + ) def build(designspace_filename, master_finder=lambda s:s, exclude=[], optimize=True): @@ -731,33 +707,33 @@ def build(designspace_filename, master_finder=lambda s:s, exclude=[], optimize=T binary as to be opened (eg. .ttf or .otf). """ - axes, internal_axis_supports, base_idx, normalized_master_locs, masters, instances = load_designspace(designspace_filename) + ds = load_designspace(designspace_filename) log.info("Building variable font") log.info("Loading master fonts") basedir = os.path.dirname(designspace_filename) - master_ttfs = [master_finder(os.path.join(basedir, m['filename'])) for m in masters] + master_ttfs = [master_finder(os.path.join(basedir, m.filename)) for m in ds.masters] master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] # Reload base font as target font - vf = TTFont(master_ttfs[base_idx]) + vf = TTFont(master_ttfs[ds.base_idx]) # TODO append masters as named-instances as well; needs .designspace change. - fvar = _add_fvar(vf, axes, instances) + fvar = _add_fvar(vf, ds.axes, ds.instances) if 'STAT' not in exclude: - _add_stat(vf, axes) + _add_stat(vf, ds.axes) if 'avar' not in exclude: - _add_avar(vf, axes) - del instances + _add_avar(vf, ds.axes) # Map from axis names to axis tags... - normalized_master_locs = [{axes[k].tag:v for k,v in loc.items()} for loc in normalized_master_locs] - #del axes + normalized_master_locs = [ + {ds.axes[k].tag: v for k,v in loc.items()} for loc in ds.normalized_master_locs + ] # From here on, we use fvar axes only axisTags = [axis.axisTag for axis in fvar.axes] # Assume single-model for now. model = models.VariationModel(normalized_master_locs, axisOrder=axisTags) - assert 0 == model.mapping[base_idx] + assert 0 == model.mapping[ds.base_idx] log.info("Building variations tables") if 'MVAR' not in exclude: @@ -770,6 +746,8 @@ def build(designspace_filename, master_finder=lambda s:s, exclude=[], optimize=T _add_gvar(vf, model, master_fonts, optimize=optimize) if 'cvar' not in exclude and 'glyf' in vf: _merge_TTHinting(vf, model, master_fonts) + if 'GSUB' not in exclude and ds.rules: + _add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules) for tag in exclude: if tag in vf: |