Merge pull request #864 from miguelsousa/pr857-followup
Followup to PR #857
This commit is contained in:
commit
ae58d3872c
@ -29,7 +29,7 @@ from fontTools.ttLib.tables._g_v_a_r import TupleVariation
|
||||
from fontTools.ttLib.tables import otTables as ot
|
||||
from fontTools.varLib import builder, designspace, models
|
||||
from fontTools.varLib.merger import VariationMerger
|
||||
import collections
|
||||
from collections import OrderedDict
|
||||
import warnings
|
||||
import os.path
|
||||
import logging
|
||||
@ -42,7 +42,6 @@ log = logging.getLogger("fontTools.varLib")
|
||||
#
|
||||
|
||||
# Move to fvar table proper?
|
||||
# TODO how to provide axis order?
|
||||
def _add_fvar(font, axes, instances, axis_map):
|
||||
"""
|
||||
Add 'fvar' table to font.
|
||||
@ -53,16 +52,14 @@ def _add_fvar(font, axes, instances, axis_map):
|
||||
instances is list of dictionary objects with 'location', 'stylename',
|
||||
and possibly 'postscriptfontname' entries.
|
||||
|
||||
axisMap is dictionary mapping axis-id to (axis-tag, axis-name).
|
||||
axis_map is dictionary mapping axis-id to (axis-tag, axis-name).
|
||||
"""
|
||||
|
||||
assert "fvar" not in font
|
||||
font['fvar'] = fvar = newTable('fvar')
|
||||
nameTable = font['name']
|
||||
|
||||
for iden in axis_map.keys():
|
||||
if not iden in axes:
|
||||
continue
|
||||
for iden in sorted(axes.keys(), key=lambda i: axis_map.keys().index(i)):
|
||||
axis = Axis()
|
||||
axis.axisTag = Tag(axis_map[iden][0])
|
||||
axis.minValue, axis.defaultValue, axis.maxValue = axes[iden]
|
||||
@ -267,7 +264,11 @@ def build(designspace_filename, master_finder=lambda s:s, axisMap=None):
|
||||
(axis-tag, axis-name).
|
||||
"""
|
||||
|
||||
masters, instances, axisMapDS = designspace.load(designspace_filename)
|
||||
ds = designspace.load(designspace_filename)
|
||||
axes = ds['axes']
|
||||
masters = ds['masters']
|
||||
instances = ds['instances']
|
||||
|
||||
base_idx = None
|
||||
for i,m in enumerate(masters):
|
||||
if 'info' in m and m['info']['copy']:
|
||||
@ -283,32 +284,62 @@ def build(designspace_filename, master_finder=lambda s:s, axisMap=None):
|
||||
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:
|
||||
axis_map = designspace.standard_axis_map.copy()
|
||||
# a dictionary mapping axis-id to (axis-tag, axis-name) was provided
|
||||
axis_map = standard_axis_map.copy()
|
||||
axis_map.update(axisMap)
|
||||
elif axisMapDS:
|
||||
axis_map = axisMapDS
|
||||
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 = designspace.standard_axis_map
|
||||
|
||||
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_tags = set(master_locs[0].keys())
|
||||
assert all(axis_tags == set(m.keys()) for m in master_locs)
|
||||
axis_names = set(master_locs[0].keys())
|
||||
assert all(axis_names == 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))
|
||||
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))
|
||||
|
||||
@ -318,17 +349,17 @@ def build(designspace_filename, master_finder=lambda s:s, axisMap=None):
|
||||
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)
|
||||
fvar = _add_fvar(gx, axes_dict, instances, axis_map)
|
||||
|
||||
|
||||
# Normalize master locations
|
||||
master_locs = [models.normalizeLocation(m, axes) for m in master_locs]
|
||||
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
|
||||
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]
|
||||
|
@ -9,13 +9,7 @@ except ImportError:
|
||||
|
||||
__all__ = ['load', 'loads']
|
||||
|
||||
standard_axis_map = collections.OrderedDict(
|
||||
[['weight', ('wght', 'Weight')],
|
||||
['width', ('wdth', 'Width')],
|
||||
['slant', ('slnt', 'Slant')],
|
||||
['optical', ('opsz', 'Optical Size')],
|
||||
['custom',('xxxx', 'Custom')]]
|
||||
)
|
||||
namespaces = {'xml': '{http://www.w3.org/XML/1998/namespace}'}
|
||||
|
||||
def _xmlParseLocation(et):
|
||||
loc = {}
|
||||
@ -40,37 +34,61 @@ def _loadItem(et):
|
||||
item[elt.tag] = value
|
||||
return item
|
||||
|
||||
def _xmlParseAxisOrMap(elt):
|
||||
dic = {}
|
||||
for name in elt.attrib:
|
||||
if name in ['name', 'tag']:
|
||||
dic[name] = elt.attrib[name]
|
||||
else:
|
||||
dic[name] = float(elt.attrib[name])
|
||||
return dic
|
||||
|
||||
def _loadAxis(et):
|
||||
item = dict(_xmlParseAxisOrMap(et))
|
||||
maps = []
|
||||
labelnames = {}
|
||||
for elt in et:
|
||||
assert elt.tag in ['labelname', 'map']
|
||||
if elt.tag == 'labelname':
|
||||
lang = elt.attrib["{0}lang".format(namespaces['xml'])]
|
||||
labelnames[lang] = elt.text
|
||||
elif elt.tag == 'map':
|
||||
maps.append(_xmlParseAxisOrMap(elt))
|
||||
if labelnames:
|
||||
item['labelname'] = labelnames
|
||||
if maps:
|
||||
item['map'] = maps
|
||||
return item
|
||||
|
||||
def _load(et):
|
||||
designspace = {}
|
||||
ds = et.getroot()
|
||||
|
||||
axisMap = collections.OrderedDict()
|
||||
axesET = ds.find('axes')
|
||||
if axesET:
|
||||
axisList = axesET.findall('axis')
|
||||
for axisET in axisList:
|
||||
axisName = axisET.attrib["name"]
|
||||
labelET = axisET.find('labelname')
|
||||
if (None == labelET):
|
||||
# If the designpsace file axes is a std axes, the label name may be omitted.
|
||||
tag, label = standard_axis_map[axisName]
|
||||
else:
|
||||
label = labelET.text
|
||||
tag = axisET.attrib["tag"]
|
||||
axisMap[axisName] = (tag, label)
|
||||
|
||||
axes = []
|
||||
ds_axes = ds.find('axes')
|
||||
if ds_axes:
|
||||
for et in ds_axes:
|
||||
axes.append(_loadAxis(et))
|
||||
designspace['axes'] = axes
|
||||
|
||||
masters = []
|
||||
for et in ds.find('sources'):
|
||||
masters.append(_loadItem(et))
|
||||
designspace['masters'] = masters
|
||||
|
||||
instances = []
|
||||
for et in ds.find('instances'):
|
||||
instances.append(_loadItem(et))
|
||||
designspace['instances'] = instances
|
||||
|
||||
return masters, instances, axisMap
|
||||
return designspace
|
||||
|
||||
def load(filename):
|
||||
"""Load designspace from a file name or object. Returns two items:
|
||||
list of masters (aka sources) and list of instances."""
|
||||
"""Load designspace from a file name or object.
|
||||
Returns a dictionary containing three items:
|
||||
- list of axes
|
||||
- list of masters (aka sources)
|
||||
- list of instances"""
|
||||
return _load(ET.parse(filename))
|
||||
|
||||
def loads(string):
|
||||
|
@ -12,7 +12,11 @@ import os.path
|
||||
|
||||
def interpolate_layout(designspace_filename, loc, finder):
|
||||
|
||||
masters, instances, axisMap = designspace.load(designspace_filename)
|
||||
ds = designspace.load(designspace_filename)
|
||||
axes = ds['axes']
|
||||
masters = ds['masters']
|
||||
instances = ds['instances']
|
||||
|
||||
base_idx = None
|
||||
for i,m in enumerate(masters):
|
||||
if 'info' in m and m['info']['copy']:
|
||||
@ -34,26 +38,37 @@ def interpolate_layout(designspace_filename, loc, finder):
|
||||
|
||||
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)
|
||||
axis_names = set(master_locs[0].keys())
|
||||
assert all(axis_names == 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)
|
||||
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)
|
||||
pprint(axes_dict)
|
||||
|
||||
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]
|
||||
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:")
|
||||
@ -66,7 +81,7 @@ def interpolate_layout(designspace_filename, loc, finder):
|
||||
merger = InstancerMerger(font, model, loc)
|
||||
|
||||
print("Building variations tables")
|
||||
merger.mergeTables(font, master_fonts, axes, base_idx, ['GPOS'])
|
||||
merger.mergeTables(font, master_fonts, axes_dict, base_idx, ['GPOS'])
|
||||
return font
|
||||
|
||||
|
||||
|
3
NEWS.rst
3
NEWS.rst
@ -1,3 +1,6 @@
|
||||
- [varLib] designspace.load() now returns a dictionary, instead of a tuple,
|
||||
and supports <axes> element (#864)
|
||||
|
||||
3.7.2 (released 2017-02-17)
|
||||
---------------------------
|
||||
|
||||
|
@ -1,24 +1,36 @@
|
||||
<?xml version="1.0"?>
|
||||
<designspace format="3">
|
||||
<axes>
|
||||
<axis default="0.0" maximum="1000.0" minimum="0.0" name="weight" tag="wght">
|
||||
<map input="0.0" output="10.0" />
|
||||
<map input="401.0" output="66.0" />
|
||||
<map input="1000.0" output="990.0" />
|
||||
</axis>
|
||||
<axis default="250.0" maximum="1000.0" minimum="0.0" name="width" tag="wdth" />
|
||||
<axis default="0.0" maximum="100.0" minimum="0.0" name="contrast" tag="cntr">
|
||||
<labelname xml:lang="en">Contrast</labelname>
|
||||
<labelname xml:lang="de">Kontrast</labelname>
|
||||
</axis>
|
||||
</axes>
|
||||
<sources>
|
||||
<source filename="VarLibTest-Light.ufo" name="master_1">
|
||||
<lib copy="1"/>
|
||||
<groups copy="1"/>
|
||||
<info copy="1"/>
|
||||
<location>
|
||||
<dimension name="weight" xvalue="0.000000"/>
|
||||
<dimension name="weight" xvalue="0.0"/>
|
||||
</location>
|
||||
</source>
|
||||
<source filename="VarLibTest-Bold.ufo" name="master_2">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="1.000000"/>
|
||||
<dimension name="weight" xvalue="1.0"/>
|
||||
</location>
|
||||
</source>
|
||||
</sources>
|
||||
<instances>
|
||||
<instance familyname="VarLibTest" filename="instance/VarLibTest-Medium.ufo" stylename="Medium">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="0.500000"/>
|
||||
<dimension name="weight" xvalue="0.5"/>
|
||||
</location>
|
||||
<info/>
|
||||
<kerning/>
|
||||
|
@ -9,21 +9,47 @@ class DesignspaceTest(unittest.TestCase):
|
||||
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': {}}])
|
||||
|
||||
{'instances':
|
||||
[{'info': {},
|
||||
'familyname': 'VarLibTest',
|
||||
'filename': 'instance/VarLibTest-Medium.ufo',
|
||||
'kerning': {},
|
||||
'location': {'weight': 0.5},
|
||||
'stylename': 'Medium'}],
|
||||
|
||||
'masters':
|
||||
[{'info': {'copy': True},
|
||||
'name': 'master_1',
|
||||
'lib': {'copy': True},
|
||||
'filename': 'VarLibTest-Light.ufo',
|
||||
'location': {'weight': 0.0},
|
||||
'groups': {'copy': True}},
|
||||
{'location': {'weight': 1.0},
|
||||
'name': 'master_2',
|
||||
'filename': 'VarLibTest-Bold.ufo'}],
|
||||
|
||||
'axes':
|
||||
[{'map': [{'input': 0.0, 'output': 10.0},
|
||||
{'input': 401.0, 'output': 66.0},
|
||||
{'input': 1000.0, 'output': 990.0}],
|
||||
'name': 'weight',
|
||||
'default': 0.0,
|
||||
'tag': 'wght',
|
||||
'maximum': 1000.0,
|
||||
'minimum': 0.0},
|
||||
{'default': 250.0,
|
||||
'minimum': 0.0,
|
||||
'tag': 'wdth',
|
||||
'maximum': 1000.0,
|
||||
'name': 'width'},
|
||||
{'name': 'contrast',
|
||||
'default': 0.0,
|
||||
'tag': 'cntr',
|
||||
'maximum': 100.0,
|
||||
'minimum': 0.0,
|
||||
'labelname': {'de': 'Kontrast', 'en': 'Contrast'}}]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user