def interpolate_layout(designspace_filename, loc, finder): masters, instances = designspace.load(designspace_filename) 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; Add <info> element to one of the masters in the .designspace document." from pprint import pprint print("Index of base master:", base_idx) print("Building GX") print("Loading TTF masters") basedir = os.path.dirname(designspace_filename) master_ttfs = [ finder(os.path.join(basedir, m['filename'])) for m in masters ] master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] #font = master_fonts[base_idx] font = TTFont(master_ttfs[base_idx]) master_locs = [o['location'] for o in masters] axis_tags = set(master_locs[0].keys()) assert all(axis_tags == set(m.keys()) for m in master_locs) # Set up axes axes = {} for tag in axis_tags: default = master_locs[base_idx][tag] lower = min(m[tag] for m in master_locs) upper = max(m[tag] for m in master_locs) axes[tag] = (lower, default, upper) print("Axes:") pprint(axes) print("Location:", loc) print("Master locations:") pprint(master_locs) # Normalize locations loc = models.normalizeLocation(loc, axes) master_locs = [models.normalizeLocation(m, axes) for m in master_locs] print("Normalized location:", loc) print("Normalized master locations:") pprint(master_locs) # Assume single-model for now. model = models.VariationModel(master_locs) assert 0 == model.mapping[base_idx] merger = InstancerMerger(model, loc) print("Building variations tables") merge_tables(font, merger, master_fonts, axes, base_idx, ['GPOS']) return font
def build(designspace_filename, master_finder=lambda s: s, exclude=[]): """ Build variation font from a designspace file. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). """ axes, internal_axis_supports, base_idx, normalized_master_locs, masters, instances = 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_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] # Reload base font as target font vf = TTFont(master_ttfs[base_idx]) # TODO append masters as named-instances as well; needs .designspace change. fvar = _add_fvar(vf, axes, instances) if 'STAT' not in exclude: _add_stat(vf, axes) if 'avar' not in exclude: _add_avar(vf, axes) del instances # 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 # 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] log.info("Building variations tables") if 'MVAR' not in exclude: _add_MVAR(vf, model, master_fonts, axisTags) if 'HVAR' not in exclude: _add_HVAR(vf, model, master_fonts, axisTags) if 'GDEF' not in exclude or 'GPOS' not in exclude: _merge_OTL(vf, model, master_fonts, axisTags) if 'gvar' not in exclude and 'glyf' in vf: _add_gvar(vf, model, master_fonts) if 'cvar' not in exclude: _merge_TTHinting(vf, model, master_fonts) for tag in exclude: if tag in vf: del vf[tag] return vf, model, master_ttfs
def interpolate_layout(designspace_filename, loc, master_finder=lambda s: s, mapped=False): """ Interpolate GPOS from a designspace file and location. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). If mapped is False (default), then location is mapped using the map element of the axes in designspace file. If mapped is True, it is assumed that location is in designspace's internal space and no mapping is performed. """ axes, internal_axis_supports, base_idx, normalized_master_locs, masters, instances = load_designspace( designspace_filename) log.info("Building interpolated 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_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] #font = master_fonts[base_idx] font = TTFont(master_ttfs[base_idx]) log.info("Location: %s", pformat(loc)) if not mapped: loc = {name: axes[name].map_forward(v) for name, v in loc.items()} log.info("Internal location: %s", pformat(loc)) loc = models.normalizeLocation(loc, internal_axis_supports) log.info("Normalized location: %s", pformat(loc)) # Assume single-model for now. model = models.VariationModel(normalized_master_locs) assert 0 == model.mapping[base_idx] merger = InstancerMerger(font, model, loc) log.info("Building interpolated tables") merger.mergeTables(font, master_fonts, ['GPOS']) return font
def interpolate_layout(designspace, loc, master_finder=lambda s: s, mapped=False): """ Interpolate GPOS from a designspace file and location. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). If mapped is False (default), then location is mapped using the map element of the axes in designspace file. If mapped is True, it is assumed that location is in designspace's internal space and no mapping is performed. """ if hasattr(designspace, "sources"): # Assume a DesignspaceDocument pass else: # Assume a file path from fontTools.designspaceLib import DesignSpaceDocument designspace = DesignSpaceDocument.fromfile(designspace) ds = load_designspace(designspace) log.info("Building interpolated font") log.info("Loading master fonts") master_fonts = load_masters(designspace, master_finder) font = deepcopy(master_fonts[ds.base_idx]) log.info("Location: %s", pformat(loc)) if not mapped: loc = {name: ds.axes[name].map_forward(v) for name, v in loc.items()} log.info("Internal location: %s", pformat(loc)) loc = models.normalizeLocation(loc, ds.internal_axis_supports) log.info("Normalized location: %s", pformat(loc)) # Assume single-model for now. model = models.VariationModel(ds.normalized_master_locs) assert 0 == model.mapping[ds.base_idx] merger = InstancerMerger(font, model, loc) log.info("Building interpolated tables") # TODO GSUB/GDEF merger.mergeTables(font, master_fonts, ['GPOS']) return font
def _make_model(self): masters = self.designspace.sources internal_master_locs = [o.location for o in masters] self.internal_axis_supports = {} for axis in self.designspace.axes: triple = (axis.minimum, axis.default, axis.maximum) self.internal_axis_supports[axis.name] = [ axis.map_forward(v) for v in triple ] normalized_master_locs = [ models.normalizeLocation(m, self.internal_axis_supports) for m in internal_master_locs ] axis_tags = [axis.name for axis in self.designspace.axes] self.variation_model = models.VariationModel(normalized_master_locs, axisOrder=axis_tags)
def compile_instructions(font, ship=True, keep_cvar=False): if "glyf" not in font: raise VTTLibError("Missing 'glyf' table; not a TrueType font") if "TSI1" not in font: raise VTTLibError("The font contains no 'TSI1' table") if keep_cvar and "cvar" not in font: raise VTTLibError( "The keep_cvar parameter is set, but the cvar table is missing from the font" ) control_program = get_extra_assembly(font, "cvt") set_cvt_table(font, control_program) if "TSIC" in font and not keep_cvar: # Compile TSIC tsic = font["TSIC"].table cvts = font["cvt "].values # Gather the full set of cvts (not just those in TSIC) for all locations cvt_sets = [Vector(cvts)] for record in tsic.Record: cvt_local = { i: v for i, v in zip(record.CVTArray, record.CVTValueArray) } cvt_set = [ cvt_local[i] if i in cvt_local else cvt for i, cvt in enumerate(cvts) ] cvt_sets.append(Vector(cvt_set)) # Compute variations locs = [{axis_tag: 0.0 for axis_tag in tsic.AxisArray}] # default instance locs += [{ axis_tag: val for axis_tag, val in zip(tsic.AxisArray, loc.Axis) } for loc in tsic.RecordLocations] model = models.VariationModel(locs) variations = [] deltas, supports = model.getDeltasAndSupports(cvt_sets) for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): delta = [otRound(d) for d in delta] if all(abs(v) <= 0.5 for v in delta): continue # Remove any deltas that weren't specified in TSIC at this location # TODO: Just replace optimizaton with getting rid of 0 deltas? tsic_rec_index = model.reverseMapping[ i + 1] - 1 # Map back to TSIC records delta = [ d if j in tsic.Record[tsic_rec_index].CVTArray and d != 0 else None for j, d in enumerate(delta) ] var = TupleVariation(support, delta) variations.append(var) if variations: cvar = font["cvar"] = newTable("cvar") cvar.version = 1 cvar.variations = variations for tag in ("prep", "fpgm"): if tag not in font: font[tag] = newTable(tag) data = get_extra_assembly(font, tag) font[tag].program = make_program(data, tag) glyph_order = font.getGlyphOrder() glyf_table = font["glyf"] for glyph_name in glyph_order: try: data = get_glyph_assembly(font, glyph_name) except KeyError: continue program, components = make_glyph_program(data, glyph_name) if program or components: glyph = glyf_table[glyph_name] if components: if not glyph.isComposite(): log.warning( "Glyph '%s' contains components in VTT assembly but " "not in glyf table; drop assembly and skip " "compilation" % glyph_name) set_glyph_assembly(font, glyph_name, "") else: check_composite_info(glyph_name, glyph, components, glyph_order) set_components_flags(glyph, components) if program: glyph.program = program if ship: for tag in ("TSI%s" % i for i in (0, 1, 2, 3, 5, "C")): if tag in font: del font[tag]
def build(designspace, master_finder=lambda s: s, exclude=[], optimize=True): """ Build variation font from a designspace file. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). """ if hasattr(designspace, "sources"): # Assume a DesignspaceDocument pass else: # Assume a file path designspace = DesignSpaceDocument.fromfile(designspace) ds = load_designspace(designspace) log.info("Building variable font") log.info("Loading master fonts") master_fonts = load_masters(designspace, master_finder) # TODO: 'master_ttfs' is unused except for return value, remove later master_ttfs = [] for master in master_fonts: try: master_ttfs.append(master.reader.file.name) except AttributeError: master_ttfs.append(None) # in-memory fonts have no path # Copy the base master to work from it vf = deepcopy(master_fonts[ds.base_idx]) # TODO append masters as named-instances as well; needs .designspace change. fvar = _add_fvar(vf, ds.axes, ds.instances) if 'STAT' not in exclude: _add_stat(vf, ds.axes) if 'avar' not in exclude: _add_avar(vf, ds.axes) # Map from axis names to axis tags... 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[ds.base_idx] log.info("Building variations tables") if 'BASE' not in exclude and 'BASE' in vf: _add_BASE(vf, model, master_fonts, axisTags) if 'MVAR' not in exclude: _add_MVAR(vf, model, master_fonts, axisTags) if 'HVAR' not in exclude: _add_HVAR(vf, model, master_fonts, axisTags) if 'VVAR' not in exclude and 'vmtx' in vf: _add_VVAR(vf, model, master_fonts, axisTags) if 'GDEF' not in exclude or 'GPOS' not in exclude: _merge_OTL(vf, model, master_fonts, axisTags) if 'gvar' not in exclude and 'glyf' in vf: _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: featureTag = ds.lib.get(FEAVAR_FEATURETAG_LIB_KEY, "rclt" if ds.rulesProcessingLast else "rvrn") _add_GSUB_feature_variations(vf, ds.axes, ds.internal_axis_supports, ds.rules, featureTag) if 'CFF2' not in exclude and ('CFF ' in vf or 'CFF2' in vf): _add_CFF2(vf, model, master_fonts) if "post" in vf: # set 'post' to format 2 to keep the glyph names dropped from CFF2 post = vf["post"] if post.formatType != 2.0: post.formatType = 2.0 post.extraNames = [] post.mapping = {} set_default_weight_width_slant( vf, location={axis.axisTag: axis.defaultValue for axis in vf["fvar"].axes}) for tag in exclude: if tag in vf: del vf[tag] # TODO: Only return vf for 4.0+, the rest is unused. return vf, model, master_ttfs
def build(designspace, master_finder=lambda s: s, exclude=[], optimize=True): """ Build variation font from a designspace file. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). """ if hasattr(designspace, "sources"): # Assume a DesignspaceDocument pass else: # Assume a file path designspace = DesignSpaceDocument.fromfile(designspace) ds = load_designspace(designspace) log.info("Building variable font") log.info("Loading master fonts") master_fonts = load_masters(designspace, master_finder) # TODO: 'master_ttfs' is unused except for return value, remove later master_ttfs = [] for master in master_fonts: try: master_ttfs.append(master.reader.file.name) except AttributeError: master_ttfs.append(None) # in-memory fonts have no path # Copy the base master to work from it vf = deepcopy(master_fonts[ds.base_idx]) # TODO append masters as named-instances as well; needs .designspace change. fvar = _add_fvar(vf, ds.axes, ds.instances) if 'STAT' not in exclude: _add_stat(vf, ds.axes) if 'avar' not in exclude: _add_avar(vf, ds.axes) # Map from axis names to axis tags... 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[ds.base_idx] log.info("Building variations tables") if 'MVAR' not in exclude: _add_MVAR(vf, model, master_fonts, axisTags) if 'HVAR' not in exclude: _add_HVAR(vf, model, master_fonts, axisTags) if 'GDEF' not in exclude or 'GPOS' not in exclude: _merge_OTL(vf, model, master_fonts, axisTags) if 'gvar' not in exclude and 'glyf' in vf: _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) if 'CFF2' not in exclude and 'CFF ' in vf: _add_CFF2(vf, model, master_fonts) for tag in exclude: if tag in vf: del vf[tag] # TODO: Only return vf for 4.0+, the rest is unused. return vf, model, master_ttfs
def build(designspace_filename, master_finder=lambda s: s, axisMap=None): """ Build variation font from a designspace file. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). If axisMap is set, it should be dictionary mapping axis-id to (axis-tag, axis-name). """ masters, instances = designspace.load(designspace_filename) 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; Add <info> element to one of the masters in the .designspace document." from pprint import pprint print("Index of base master:", base_idx) print("Building GX") print("Loading TTF masters") basedir = os.path.dirname(designspace_filename) master_ttfs = [ master_finder(os.path.join(basedir, m['filename'])) for m in masters ] master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] standard_axis_map = { 'weight': ('wght', 'Weight'), 'width': ('wdth', 'Width'), 'slant': ('slnt', 'Slant'), 'optical': ('opsz', 'Optical Size'), 'custom': ('xxxx', 'Custom'), } axis_map = standard_axis_map if axisMap: axis_map = axis_map.copy() axis_map.update(axisMap) # TODO: For weight & width, use OS/2 values and setup 'avar' mapping. master_locs = [o['location'] for o in masters] axis_tags = set(master_locs[0].keys()) assert all(axis_tags == set(m.keys()) for m in master_locs) # Set up axes axes = {} for tag in axis_tags: default = master_locs[base_idx][tag] lower = min(m[tag] for m in master_locs) upper = max(m[tag] for m in master_locs) axes[tag] = (lower, default, upper) print("Axes:") pprint(axes) print("Master locations:") pprint(master_locs) # We can use the base font straight, but it's faster to load it again since # then we won't be recompiling the existing ('glyf', 'hmtx', ...) tables. #gx = master_fonts[base_idx] gx = TTFont(master_ttfs[base_idx]) # TODO append masters as named-instances as well; needs .designspace change. fvar = _add_fvar(gx, axes, instances, axis_map) # Normalize master locations master_locs = [models.normalizeLocation(m, axes) for m in master_locs] print("Normalized master locations:") pprint(master_locs) # TODO Clean this up. del instances del axes master_locs = [{axis_map[k][0]: v for k, v in loc.items()} for loc in master_locs] #instance_locs = [{axis_map[k][0]:v for k,v in loc.items()} for loc in instance_locs] axisTags = [axis.axisTag for axis in fvar.axes] # Assume single-model for now. model = models.VariationModel(master_locs) assert 0 == model.mapping[base_idx] print("Building variations tables") if 'glyf' in gx: _add_gvar(gx, model, master_fonts) _add_HVAR(gx, model, master_fonts, axisTags) _merge_OTL(gx, model, master_fonts, axisTags, base_idx) return gx, model, master_ttfs
def build(designspace_filename, master_finder=lambda s: s): """ Build variation font from a designspace file. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). """ ds = designspace.load(designspace_filename) axes = ds.get('axes') masters = ds.get('sources') if not masters: raise VarLibError("no sources found in .designspace") instances = ds.get('instances', []) standard_axis_map = OrderedDict([ ('weight', ('wght', { 'en': 'Weight' })), ('width', ('wdth', { 'en': 'Width' })), ('slant', ('slnt', { 'en': 'Slant' })), ('optical', ('opsz', { 'en': 'Optical Size' })), ]) # Setup axes class DesignspaceAxis(object): @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) 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', 'labelname', 'minimum', 'default', 'maximum', 'map' ]: assert item in axis_dict, 'Axis does not have "%s"' % item 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 # 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'] for name in loc.keys(): assert name in axes, "Location axis '%s' unknown for '%s'." % ( name, obj_name) for axis_name, axis in axes.items(): if axis_name not in loc: loc[axis_name] = axis.default else: 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]" % ( name, v, obj_name, axis.minimum, axis.maximum) # Normalize master locations master_locs = [o['location'] for o in masters] log.info("Internal master locations:\n%s", pformat(master_locs)) # TODO This mapping should ideally be moved closer to logic in _add_fvar_avar axis_supports = {} for axis in axes.values(): triple = (axis.minimum, axis.default, axis.maximum) axis_supports[axis.name] = [axis.map_forward(v) for v in triple] log.info("Internal axis supports:\n%s", pformat(axis_supports)) master_locs = [ models.normalizeLocation(m, axis_supports) for m in master_locs ] log.info("Normalized master locations:\n%s", pformat(master_locs)) del axis_supports # Find base master base_idx = None for i, m in enumerate(master_locs): if all(v == 0 for v in m.values()): assert base_idx is None base_idx = i assert base_idx is not None, "Base master not found; no master at default location?" log.info("Index of base master: %s", base_idx) 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_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] # Reload base font as target font vf = TTFont(master_ttfs[base_idx]) # TODO append masters as named-instances as well; needs .designspace change. fvar, avar = _add_fvar_avar(vf, axes, instances) del instances # Map from axis names to axis tags... master_locs = [{axes[k].tag: v for k, v in loc.items()} for loc in master_locs] #del axes # From here on, we use fvar axes only axisTags = [axis.axisTag for axis in fvar.axes] # Assume single-model for now. model = models.VariationModel(master_locs) assert 0 == model.mapping[base_idx] log.info("Building variations tables") _add_MVAR(vf, model, master_fonts, axisTags) if 'glyf' in vf: _add_gvar(vf, model, master_fonts) _add_HVAR(vf, model, master_fonts, axisTags) _merge_OTL(vf, model, master_fonts, axisTags, base_idx) return vf, model, master_ttfs
def build(designspace_filename, master_finder=lambda s: s, axisMap=None): """ Build variation font from a designspace file. If master_finder is set, it should be a callable that takes master filename as found in designspace file and map it to master font binary as to be opened (eg. .ttf or .otf). If axisMap is set, it should be an ordered dictionary mapping axis-id to (axis-tag, axis-name). """ ds = designspace.load(designspace_filename) axes = ds['axes'] if 'axes' in ds else [] if 'sources' not in ds or not ds['sources']: raise VarLibError("no 'sources' defined in .designspace") masters = ds['sources'] instances = ds['instances'] if 'instances' in ds else [] 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; Add <info> element to one of the masters in the .designspace document." log.info("Index of base master: %s", base_idx) 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_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] standard_axis_map = OrderedDict([('weight', ('wght', 'Weight')), ('width', ('wdth', 'Width')), ('slant', ('slnt', 'Slant')), ('optical', ('opsz', 'Optical Size')), ('custom', ('xxxx', 'Custom'))]) if axisMap: # a dictionary mapping axis-id to (axis-tag, axis-name) was provided axis_map = standard_axis_map.copy() axis_map.update(axisMap) elif axes: # the designspace file loaded had an <axes> element. # honor the order of the axes axis_map = OrderedDict() for axis in axes: axis_name = axis['name'] if axis_name in standard_axis_map: axis_map[axis_name] = standard_axis_map[axis_name] else: tag = axis['tag'] assert axis['labelname']['en'] label = axis['labelname']['en'] axis_map[axis_name] = (tag, label) else: axis_map = standard_axis_map # TODO: For weight & width, use OS/2 values and setup 'avar' mapping. master_locs = [o['location'] for o in masters] axis_names = set(master_locs[0].keys()) assert all(axis_names == set(m.keys()) for m in master_locs) # Set up axes axes_dict = {} if axes: # the designspace file loaded had an <axes> element for axis in axes: default = axis['default'] lower = axis['minimum'] upper = axis['maximum'] name = axis['name'] axes_dict[name] = (lower, default, upper) else: for name in axis_names: default = master_locs[base_idx][name] lower = min(m[name] for m in master_locs) upper = max(m[name] for m in master_locs) if default == lower == upper: continue axes_dict[name] = (lower, default, upper) log.info("Axes:\n%s", pformat(axes_dict)) assert all(name in axis_map for name in axes_dict.keys()) log.info("Master locations:\n%s", pformat(master_locs)) # We can use the base font straight, but it's faster to load it again since # then we won't be recompiling the existing ('glyf', 'hmtx', ...) tables. #gx = master_fonts[base_idx] gx = TTFont(master_ttfs[base_idx]) # TODO append masters as named-instances as well; needs .designspace change. fvar = _add_fvar(gx, axes_dict, instances, axis_map) # Normalize master locations master_locs = [models.normalizeLocation(m, axes_dict) for m in master_locs] log.info("Normalized master locations:\n%s", pformat(master_locs)) # TODO Clean this up. del instances del axes_dict master_locs = [{axis_map[k][0]: v for k, v in loc.items()} for loc in master_locs] #instance_locs = [{axis_map[k][0]:v for k,v in loc.items()} for loc in instance_locs] axisTags = [axis.axisTag for axis in fvar.axes] # Assume single-model for now. model = models.VariationModel(master_locs) assert 0 == model.mapping[base_idx] log.info("Building variations tables") if 'glyf' in gx: _add_gvar(gx, model, master_fonts) _add_HVAR(gx, model, master_fonts, axisTags) _merge_OTL(gx, model, master_fonts, axisTags, base_idx) return gx, model, master_ttfs
def interpolate_layout(designspace_filename, loc, finder): ds = designspace.load(designspace_filename) axes = ds['axes'] if 'axes' in ds else [] if 'sources' not in ds or not ds['sources']: raise VarLibError("no 'sources' defined in .designspace") masters = ds['sources'] 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; Add <info> element to one of the masters in the .designspace document." from pprint import pprint print("Index of base master:", base_idx) print("Building variable font") print("Loading master fonts") basedir = os.path.dirname(designspace_filename) master_ttfs = [finder(os.path.join(basedir, m['filename'])) for m in masters] master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] #font = master_fonts[base_idx] font = TTFont(master_ttfs[base_idx]) master_locs = [o['location'] for o in masters] axis_names = set(master_locs[0].keys()) assert all(axis_names == set(m.keys()) for m in master_locs) # Set up axes axes_dict = {} if axes: # the designspace file loaded had an <axes> element for axis in axes: default = axis['default'] lower = axis['minimum'] upper = axis['maximum'] name = axis['name'] axes_dict[name] = (lower, default, upper) else: for tag in axis_names: default = master_locs[base_idx][tag] lower = min(m[tag] for m in master_locs) upper = max(m[tag] for m in master_locs) if default == lower == upper: continue axes_dict[tag] = (lower, default, upper) print("Axes:") pprint(axes_dict) print("Location:", loc) print("Master locations:") pprint(master_locs) # Normalize locations loc = models.normalizeLocation(loc, axes_dict) master_locs = [models.normalizeLocation(m, axes_dict) for m in master_locs] print("Normalized location:", loc) print("Normalized master locations:") pprint(master_locs) # Assume single-model for now. model = models.VariationModel(master_locs) assert 0 == model.mapping[base_idx] merger = InstancerMerger(font, model, loc) print("Building variations tables") merger.mergeTables(font, master_fonts, axes_dict, base_idx, ['GPOS']) return font