Merge pull request #1551 from fonttools/VVAR-support

[varLib] Add support for building VVAR table from vmtx and VORG tables.
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 0543ee3..38ee201 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -463,46 +463,120 @@
 		var = TupleVariation(support, delta)
 		cvar.variations.append(var)
 
-def _add_HVAR(font, masterModel, master_ttfs, axisTags):
+MetricsFields = namedtuple('MetricsFields',
+	['tableTag', 'metricsTag', 'sb1', 'sb2', 'advMapping', 'vOrigMapping'])
 
-	log.info("Generating HVAR")
+hvarFields = MetricsFields(tableTag='HVAR', metricsTag='hmtx', sb1='LsbMap',
+	sb2='RsbMap', advMapping='AdvWidthMap', vOrigMapping=None)
+
+vvarFields = MetricsFields(tableTag='VVAR', metricsTag='vmtx', sb1='TsbMap',
+	sb2='BsbMap', advMapping='AdvHeightMap', vOrigMapping='VOrgMap')
+
+def _add_HVAR(font, masterModel, master_ttfs, axisTags):
+	_add_VHVAR(font, masterModel, master_ttfs, axisTags, hvarFields)
+
+def _add_VVAR(font, masterModel, master_ttfs, axisTags):
+	_add_VHVAR(font, masterModel, master_ttfs, axisTags, vvarFields)
+
+def _add_VHVAR(font, masterModel, master_ttfs, axisTags, tableFields):
+
+	tableTag = tableFields.tableTag
+	assert tableTag not in font
+	log.info("Generating " + tableTag)
+	VHVAR = newTable(tableTag)
+	tableClass = getattr(ot, tableTag)
+	vhvar = VHVAR.table = tableClass()
+	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
+	metricsTag = tableFields.metricsTag
+	advMetricses = [m[metricsTag].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 tableTag == 'VVAR' and 'VORG' in master_ttfs[0]:
+		vOrigMetricses = [m['VORG'].VOriginRecords for m in master_ttfs]
+		defaultYOrigs = [m['VORG'].defaultVertOriginY for m in master_ttfs]
+		vOrigMetricses = list(zip(vOrigMetricses, defaultYOrigs))
+	else:
+		vOrigMetricses = None
+
+	metricsStore, advanceMapping, vOrigMapping = _get_advance_metrics(font,
+		masterModel, master_ttfs, axisTags, glyphOrder, advMetricses,
+		vOrigMetricses)
+
+	vhvar.VarStore = metricsStore
+	if advanceMapping is None:
+		setattr(vhvar, tableFields.advMapping, None)
+	else:
+		setattr(vhvar, tableFields.advMapping, advanceMapping)
+	if vOrigMapping is not None:
+		setattr(vhvar, tableFields.vOrigMapping, vOrigMapping)
+	setattr(vhvar, tableFields.sb1, None)
+	setattr(vhvar, tableFields.sb2, None)
+
+	font[tableTag] = VHVAR
+	return
+
+def _get_advance_metrics(font, masterModel, master_ttfs,
+		axisTags, glyphOrder, advMetricses, vOrigMetricses=None):
+
+	vhAdvanceDeltasAndSupports = {}
+	vOrigDeltasAndSupports = {}
+	for glyph in glyphOrder:
+		vhAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in advMetricses]
+		vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances)
+
+	singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values())
+
+	if vOrigMetricses:
+		singleModel = False
+		for glyph in glyphOrder:
+			# We need to supply a vOrigs tuple with non-None default values
+			# for each glyph. vOrigMetricses 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 vOrigMetricses]
+			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 = {}
+	advMapping = {}
 	for glyphName in glyphOrder:
-		deltas,supports = hAdvanceDeltasAndSupports[glyphName]
+		deltas, supports = vhAdvanceDeltasAndSupports[glyphName]
 		storeBuilder.setSupports(supports)
-		mapping[glyphName] = storeBuilder.storeDeltas(deltas)
+		advMapping[glyphName] = storeBuilder.storeDeltas(deltas)
+
+	if vOrigMetricses:
+		vOrigMap = {}
+		for glyphName in glyphOrder:
+			deltas, supports = vOrigDeltasAndSupports[glyphName]
+			storeBuilder.setSupports(supports)
+			vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas)
+
 	indirectStore = storeBuilder.finish()
 	mapping2 = indirectStore.optimize()
-	mapping = [mapping2[mapping[g]] for g in glyphOrder]
-	advanceMapping = builder.buildVarIdxMap(mapping, glyphOrder)
+	advMapping = [mapping2[advMapping[g]] for g in glyphOrder]
+	advanceMapping = builder.buildVarIdxMap(advMapping, glyphOrder)
 
-	use_direct = False
+	if vOrigMetricses:
+		vOrigMap = [mapping2[vOrigMap[g]] for g in glyphOrder]
+
+	useDirect = False
+	vOrigMapping = None
 	if directStore:
 		# Compile both, see which is more compact
 
@@ -515,20 +589,17 @@
 		advanceMapping.compile(writer, font)
 		indirectSize = len(writer.getAllData())
 
-		use_direct = directSize < indirectSize
+		useDirect = 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
+	if useDirect:
+		metricsStore = directStore
+		advanceMapping = None
 	else:
-		hvar.VarStore = indirectStore
-		hvar.AdvWidthMap = advanceMapping
+		metricsStore = indirectStore
+		if vOrigMetricses:
+			vOrigMapping = builder.buildVarIdxMap(vOrigMap, glyphOrder)
+
+	return metricsStore, advanceMapping, vOrigMapping
 
 def _add_MVAR(font, masterModel, master_ttfs, axisTags):
 
@@ -840,6 +911,8 @@
 		_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:
diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py
index a000dd4..b6febe2 100644
--- a/Lib/fontTools/varLib/cff.py
+++ b/Lib/fontTools/varLib/cff.py
@@ -469,11 +469,6 @@
 							arg[0] = round_func(arg[0])
 						program.append(arg[0])
 					for arg in blendlist:
-						# for each coordinate tuple, append the region deltas
-						if len(arg) != 3:
-							print(arg)
-							import pdb
-							pdb.set_trace()
 						deltas = var_model.getDeltas(arg)
 						if round_func:
 							deltas = [round_func(delta) for delta in deltas]
diff --git a/Tests/varLib/data/TestVVAR.designspace b/Tests/varLib/data/TestVVAR.designspace
new file mode 100644
index 0000000..162f738
--- /dev/null
+++ b/Tests/varLib/data/TestVVAR.designspace
@@ -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>
diff --git a/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.otf b/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.otf
new file mode 100644
index 0000000..9749ab5
--- /dev/null
+++ b/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.otf
Binary files differ
diff --git a/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.otf b/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.otf
new file mode 100644
index 0000000..2ea0e9f
--- /dev/null
+++ b/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.otf
Binary files differ
diff --git a/Tests/varLib/data/test_results/TestVVAR.ttx b/Tests/varLib/data/test_results/TestVVAR.ttx
new file mode 100644
index 0000000..48ca408
--- /dev/null
+++ b/Tests/varLib/data/test_results/TestVVAR.ttx
@@ -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>
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index 32e7ab5..c7e6a65 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -442,6 +442,20 @@
         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()