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
Esempio n. 2
0
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
Esempio n. 3
0
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)
Esempio n. 6
0
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]
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
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
Esempio n. 10
0
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
Esempio n. 11
0
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
Esempio n. 12
0
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