Add support for building VVAR table from vmtx and VORG tables.

Add test case.
This commit is contained in:
ReadRoberts 2019-03-21 10:06:47 -07:00
parent d2c462a0fb
commit ecf738b964
6 changed files with 223 additions and 26 deletions

View File

@ -463,46 +463,134 @@ def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
var = TupleVariation(support, delta)
cvar.variations.append(var)
def _add_HVAR(font, masterModel, master_ttfs, axisTags):
hvar_fields = { 'table_tag': 'HVAR',
'metrics_tag': 'hmtx',
'sb1': 'LsbMap',
'sb2': 'RsbMap',
'mapping_name': 'AdvWidthMap',
}
vvar_fields = { 'table_tag': 'VVAR',
'metrics_tag': 'vmtx',
'bearing1': 'TsbMap',
'bearing2': 'BsbMap',
'mapping': 'AdvHeightMap',
}
MetricsFields = namedtuple('MetricsFields',
['table_tag', 'metrics_tag', 'sb1', 'sb2', 'adv_mapping', 'vorig_mapping'])
log.info("Generating HVAR")
hvar_fields = MetricsFields(table_tag='HVAR', metrics_tag='hmtx', sb1='LsbMap',
sb2='RsbMap', adv_mapping='AdvWidthMap', vorig_mapping=None)
vvar_fields = MetricsFields(table_tag='VVAR', metrics_tag='vmtx', sb1='TsbMap',
sb2='BsbMap', adv_mapping='AdvHeightMap', vorig_mapping='VOrgMap')
def _add_HVAR(font, masterModel, master_ttfs, axisTags):
_add_VHVAR(font, masterModel, master_ttfs, axisTags, hvar_fields)
def _add_VVAR(font, masterModel, master_ttfs, axisTags):
_add_VHVAR(font, masterModel, master_ttfs, axisTags, vvar_fields)
def _add_VHVAR(font, masterModel, master_ttfs, axisTags, table_fields):
table_tag = table_fields.table_tag
assert table_tag not in font
log.info("Generating " + table_tag)
VHVAR = newTable(table_tag)
table_class = getattr(ot, table_tag)
vhvar = VHVAR.table = table_class()
vhvar.Version = 0x00010000
glyphOrder = font.getGlyphOrder()
hAdvanceDeltasAndSupports = {}
metricses = [m["hmtx"].metrics for m in master_ttfs]
for glyph in glyphOrder:
hAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in metricses]
hAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(hAdvances)
# Build list of source font advance widths for each glyph
metrics_tag = table_fields.metrics_tag
adv_metricses = [m[metrics_tag].metrics for m in master_ttfs]
singleModel = models.allEqual(id(v[1]) for v in hAdvanceDeltasAndSupports.values())
# Build list of source font vertical origin coords for each glyph
if table_tag == 'VVAR' and 'VORG' in master_ttfs[0]:
v_orig_metricses = [m['VORG'].VOriginRecords for m in master_ttfs]
default_y_origs = [m['VORG'].defaultVertOriginY for m in master_ttfs]
v_orig_metricses = list(zip(v_orig_metricses, default_y_origs))
else:
v_orig_metricses = None
metricsStore, advanceMapping, vOrigMapping = _get_advance_metrics(font,
masterModel, master_ttfs, axisTags, glyphOrder, adv_metricses,
v_orig_metricses)
vhvar.VarStore = metricsStore
if advanceMapping is None:
setattr(vhvar, table_fields.adv_mapping, None)
else:
setattr(vhvar, table_fields.adv_mapping, advanceMapping)
if vOrigMapping is not None:
setattr(vhvar, table_fields.vorig_mapping, vOrigMapping)
setattr(vhvar, table_fields.sb1, None)
setattr(vhvar, table_fields.sb2, None)
font[table_tag] = VHVAR
return
def _get_advance_metrics(font, masterModel, master_ttfs,
axisTags, glyphOrder, adv_metricses, v_orig_metricses=None):
vhAdvanceDeltasAndSupports = {}
vOrigDeltasAndSupports = {}
for glyph in glyphOrder:
vhAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in adv_metricses]
vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances)
singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values())
if v_orig_metricses:
singleModel = False
for glyph in glyphOrder:
# We need to supply a vOrigs tuple with non-None default values
# for each glyph. v_orig_metricses contains values only for those
# glyphs which have a non-default vOrig.
vOrigs = [metrics[glyph] if glyph in metrics else defaultVOrig
for metrics, defaultVOrig in v_orig_metricses]
print(glyph, vOrigs)
vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs)
directStore = None
if singleModel:
# Build direct mapping
supports = next(iter(hAdvanceDeltasAndSupports.values()))[1][1:]
supports = next(iter(vhAdvanceDeltasAndSupports.values()))[1][1:]
varTupleList = builder.buildVarRegionList(supports, axisTags)
varTupleIndexes = list(range(len(supports)))
varData = builder.buildVarData(varTupleIndexes, [], optimize=False)
for glyphName in glyphOrder:
varData.addItem(hAdvanceDeltasAndSupports[glyphName][0])
varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0])
varData.optimize()
directStore = builder.buildVarStore(varTupleList, [varData])
# Build optimized indirect mapping
storeBuilder = varStore.OnlineVarStoreBuilder(axisTags)
mapping = {}
adv_mapping = {}
for glyphName in glyphOrder:
deltas,supports = hAdvanceDeltasAndSupports[glyphName]
deltas, supports = vhAdvanceDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports)
mapping[glyphName] = storeBuilder.storeDeltas(deltas)
adv_mapping[glyphName] = storeBuilder.storeDeltas(deltas)
if v_orig_metricses:
v_orig_mapping_i = {}
for glyphName in glyphOrder:
deltas, supports = vOrigDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports)
v_orig_mapping_i[glyphName] = storeBuilder.storeDeltas(deltas)
indirectStore = storeBuilder.finish()
mapping2 = indirectStore.optimize()
mapping = [mapping2[mapping[g]] for g in glyphOrder]
advanceMapping = builder.buildVarIdxMap(mapping, glyphOrder)
adv_mapping = [mapping2[adv_mapping[g]] for g in glyphOrder]
advanceMapping = builder.buildVarIdxMap(adv_mapping, glyphOrder)
if v_orig_metricses:
v_orig_mapping_i = [mapping2[v_orig_mapping_i[g]] for g in glyphOrder]
use_direct = False
vOrigMapping = None
if directStore:
# Compile both, see which is more compact
@ -517,18 +605,16 @@ def _add_HVAR(font, masterModel, master_ttfs, axisTags):
use_direct = directSize < indirectSize
# Done; put it all together.
assert "HVAR" not in font
HVAR = font["HVAR"] = newTable('HVAR')
hvar = HVAR.table = ot.HVAR()
hvar.Version = 0x00010000
hvar.LsbMap = hvar.RsbMap = None
if use_direct:
hvar.VarStore = directStore
hvar.AdvWidthMap = None
metricsStore = directStore
advanceMapping = None
else:
hvar.VarStore = indirectStore
hvar.AdvWidthMap = advanceMapping
metricsStore = indirectStore
if v_orig_metricses:
vOrigMapping = builder.buildVarIdxMap(v_orig_mapping_i, glyphOrder)
return metricsStore, advanceMapping, vOrigMapping
def _add_MVAR(font, masterModel, master_ttfs, axisTags):
@ -840,6 +926,8 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
_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:

View File

@ -0,0 +1,36 @@
<?xml version='1.0' encoding='utf-8'?>
<designspace format="3">
<axes>
<axis default="0" maximum="1000" minimum="0" name="weight" tag="wght" />
</axes>
<sources>
<source filename="master_vvar_cff2/TestVVAR.0.ufo" stylename="w0.00">
<info copy="1" />
<location>
<dimension name="weight" xvalue="0.00" />
</location>
</source>
<source filename="master_vvar_cff2/TestVVAR.1.ufo" stylename="w1000.00">
<location>
<dimension name="weight" xvalue="1000.00" />
</location>
</source>
</sources>
<instances>
<instance familyname="TestVVAR" filename="instances/TestVVAR-ExtraLight.otf" postscriptfontname="TestVVAR-ExtraLight" stylename="ExtraLight">
<location>
<dimension name="weight" xvalue="0" />
</location>
<kerning />
<info />
</instance>
<instance familyname="TestVVAR" filename="instances/TestVVAR-Heavy.otf" postscriptfontname="TestVVAR-Heavy" stylename="Heavy">
<location>
<dimension name="weight" xvalue="1000" />
</location>
<kerning />
<info />
</instance>
</instances>
</designspace>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="OTTO" ttLibVersion="3.39">
<VVAR>
<Version value="0x00010000"/>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=1 -->
<!-- RegionCount=1 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="0.0"/>
<PeakCoord value="1.0"/>
<EndCoord value="1.0"/>
</VarRegionAxis>
</Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=1 -->
<NumShorts value="0"/>
<!-- VarRegionCount=0 -->
<Item index="0" value="[]"/>
</VarData>
</VarStore>
<AdvHeightMap>
<Map glyph=".notdef" outer="0" inner="0"/>
<Map glyph="glyph00007" outer="0" inner="0"/>
<Map glyph="glyph00008" outer="0" inner="0"/>
<Map glyph="glyph00009" outer="0" inner="0"/>
<Map glyph="glyph00010" outer="0" inner="0"/>
<Map glyph="glyph00011" outer="0" inner="0"/>
<Map glyph="glyph00012" outer="0" inner="0"/>
<Map glyph="uni3042" outer="0" inner="0"/>
<Map glyph="uni56FD" outer="0" inner="0"/>
<Map glyph="uni6280" outer="0" inner="0"/>
<Map glyph="uniFF20" outer="0" inner="0"/>
<Map glyph="uniFF21" outer="0" inner="0"/>
<Map glyph="uniFF41" outer="0" inner="0"/>
</AdvHeightMap>
<VOrgMap>
<Map glyph=".notdef" outer="0" inner="0"/>
<Map glyph="glyph00007" outer="0" inner="0"/>
<Map glyph="glyph00008" outer="0" inner="0"/>
<Map glyph="glyph00009" outer="0" inner="0"/>
<Map glyph="glyph00010" outer="0" inner="0"/>
<Map glyph="glyph00011" outer="0" inner="0"/>
<Map glyph="glyph00012" outer="0" inner="0"/>
<Map glyph="uni3042" outer="0" inner="0"/>
<Map glyph="uni56FD" outer="0" inner="0"/>
<Map glyph="uni6280" outer="0" inner="0"/>
<Map glyph="uniFF20" outer="0" inner="0"/>
<Map glyph="uniFF21" outer="0" inner="0"/>
<Map glyph="uniFF41" outer="0" inner="0"/>
</VOrgMap>
</VVAR>
</ttFont>

View File

@ -442,6 +442,20 @@ class BuildTest(unittest.TestCase):
mvar_tags = [vr.ValueTag for vr in varfont["MVAR"].table.ValueRecord]
assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES)
def test_varlib_build_VVAR_CFF2(self):
ds_path = self.get_test_input('TestVVAR.designspace')
suffix = '.otf'
expected_ttx_name = 'TestVVAR'
tables = ["VVAR"]
finder = lambda s: s.replace('.ufo', suffix)
varfont, model, _ = build(ds_path, finder)
varfont = reload_font(varfont)
expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
self.expect_ttx(varfont, expected_ttx_path, tables)
self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument()