Пример #1
0
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
Пример #2
0
 def test_load(self):
     self.assertEqual(designspace.load(_getpath("VarLibTest.designspace")),
                      ([{
                          'filename': 'VarLibTest-Light.ufo',
                          'groups': {
                              'copy': True
                          },
                          'info': {
                              'copy': True
                          },
                          'lib': {
                              'copy': True
                          },
                          'location': {
                              'weight': 0.0
                          },
                          'name': 'master_1'
                      }, {
                          'filename': 'VarLibTest-Bold.ufo',
                          'location': {
                              'weight': 1.0
                          },
                          'name': 'master_2'
                      }], [{
                          'filename': 'instance/VarLibTest-Medium.ufo',
                          'location': {
                              'weight': 0.5
                          },
                          'familyname': 'VarLibTest',
                          'stylename': 'Medium',
                          'info': {},
                          'kerning': {}
                      }]))
Пример #3
0
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(font, model, loc)

	print("Building variations tables")
	merge_tables(font, merger, master_fonts, axes, base_idx, ['GPOS'])
	return font
Пример #4
0
 def _set_path(self, path):
     self._path = path
     if path is not None:
         self._ds = designspace.load(path)
         self._axes = self._ds['axes']
         self._sources = self._ds['sources']
         self._instances = self._ds.get('instances', [])
     else:
         self._ds = None
         self._axes = []
         self._sources = []
         self._instances = []
Пример #5
0
    def test_load(self):
        self.maxDiff = None
        self.assertEqual(
            designspace.load(_getpath("Designspace.designspace")),

                {'sources':
                  [{'location': {'weight': 0.0},
                    'groups': {'copy': True},
                    'filename': 'DesignspaceTest-Light.ufo',
                    'info': {'copy': True},
                    'name': 'master_1',
                    'lib': {'copy': True}},
                   {'location': {'weight': 1.0},
                    'name': 'master_2',
                    'filename': 'DesignspaceTest-Bold.ufo'}],

                 'instances':
                  [{'location': {'weight': 0.5},
                    'familyname': 'DesignspaceTest',
                    'filename': 'instance/DesignspaceTest-Medium.ufo',
                    'kerning': {},
                    'info': {},
                    'stylename': 'Medium'}],

                 'axes':
                  [{'name': 'weight',
                    'map': [{'input': 0.0, 'output': 10.0},
                            {'input': 401.0, 'output': 66.0},
                            {'input': 1000.0, 'output': 990.0}],
                    'tag': 'wght',
                    'maximum': 1000.0,
                    'minimum': 0.0,
                    'default': 0.0},
                   {'maximum': 1000.0,
                    'default': 250.0,
                    'minimum': 0.0,
                    'name': 'width',
                    'tag': 'wdth'},
                   {'name': 'contrast',
                    'tag': 'cntr',
                    'maximum': 100.0,
                    'minimum': 0.0,
                    'default': 0.0,
                    'labelname': {'de': 'Kontrast', 'en': 'Contrast'}}]
                }
        )
Пример #6
0
 def test_load(self):
     self.assertEqual(
         designspace.load(_getpath("VarLibTest.designspace")),
             ([{'filename': 'VarLibTest-Light.ufo',
                'groups': {'copy': True},
                'info': {'copy': True},
                'lib': {'copy': True},
                'location': {'weight': 0.0},
                'name': 'master_1'},
               {'filename': 'VarLibTest-Bold.ufo',
                'location': {'weight': 1.0},
                'name': 'master_2'}],
              [{'filename': 'instance/VarLibTest-Medium.ufo',
                'location': {'weight': 0.5},
                'familyname': 'VarLibTest',
                'stylename': 'Medium',
                'info': {},
                'kerning': {}}])
     )
Пример #7
0
def load_designspace(designspace_filename):

	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):

		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)

	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))


	# 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 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():
			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]" % (axis_name, v, obj_name, axis.minimum, axis.maximum)


	# Normalize master locations

	normalized_master_locs = [o['location'] for o in masters]
	log.info("Internal master locations:\n%s", pformat(normalized_master_locs))

	# TODO This mapping should ideally be moved closer to logic in _add_fvar/avar
	internal_axis_supports = {}
	for axis in axes.values():
		triple = (axis.minimum, axis.default, axis.maximum)
		internal_axis_supports[axis.name] = [axis.map_forward(v) for v in triple]
	log.info("Internal axis supports:\n%s", pformat(internal_axis_supports))

	normalized_master_locs = [models.normalizeLocation(m, internal_axis_supports) for m in normalized_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):
		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)

	return axes, internal_axis_supports, base_idx, normalized_master_locs, masters, instances
Пример #8
0
 def test_load2(self):
     self.assertEqual(
         designspace.load(_getpath("Designspace2.designspace")),
                 {'sources': [], 'instances': [{}]})
Пример #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
Пример #10
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."

	log.info("Index of base master: %s", base_idx)

	log.info("Building variable font")
	log.info("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)
		if default == lower == upper:
			continue
		axes[tag] = (lower, default, upper)
	log.info("Axes:\n%s", pformat(axes))

	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, instances, axis_map)


	# Normalize master locations
	master_locs = [models.normalizeLocation(m, axes) for m in master_locs]

	log.info("Normalized master locations:\n%s", pformat(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]

	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
Пример #11
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
Пример #12
0
def load_designspace(designspace_filename):

	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):

		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)

	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
	log.info("Axes:\n%s", pformat(axes))


	# 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 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():
			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]" % (axis_name, v, obj_name, axis.minimum, axis.maximum)


	# Normalize master locations

	normalized_master_locs = [o['location'] for o in masters]
	log.info("Internal master locations:\n%s", pformat(normalized_master_locs))

	# TODO This mapping should ideally be moved closer to logic in _add_fvar_avar
	internal_axis_supports = {}
	for axis in axes.values():
		triple = (axis.minimum, axis.default, axis.maximum)
		internal_axis_supports[axis.name] = [axis.map_forward(v) for v in triple]
	log.info("Internal axis supports:\n%s", pformat(internal_axis_supports))

	normalized_master_locs = [models.normalizeLocation(m, internal_axis_supports) for m in normalized_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):
		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)

	return axes, internal_axis_supports, base_idx, normalized_master_locs, masters, instances
Пример #13
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
Пример #14
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