Repository: fonttools/fonttools

Issue:
More errors in merging OTF sparse masters
Test case attached.

    fonttools varLib SourceCodeVariable-Roman.designspace

```
Merging OpenType Layout tables
Traceback (most recent call last):
  File "fontTools/varLib/cff.py", line 205, in merge_PrivateDicts
    values = [pd.rawDict[key] for pd in pds]
  File "fontTools/varLib/cff.py", line 205, in <listcomp>
    values = [pd.rawDict[key] for pd in pds]
KeyError: 'FamilyBlues'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "fonttools", line 10, in <module>
    sys.exit(main())
  File "fontTools/__main__.py", line 29, in main
    runpy.run_module(mod, run_name='__main__')
  File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 208, in run_module
    return _run_code(code, {}, init_globals, run_name, mod_spec)
  File "/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "fontTools/varLib/__main__.py", line 7, in <module>
    sys.exit(main())
  File "fontTools/varLib/__init__.py", line 1075, in main
    optimize=options.optimize
  File "fontTools/varLib/__init__.py", line 924, in build
    _add_CFF2(vf, model, master_fonts)
  File "fontTools/varLib/__init__.py", line 767, in _add_CFF2
    merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder)
  File "fontTools/varLib/cff.py", line 312, in merge_region_fonts
    merge_PrivateDicts(top_dicts, cvData.vsindex_dict, model, fd_map)
  File "fontTools/varLib/cff.py", line 209, in merge_PrivateDicts
    b"Warning: {key} in default font Private dict is "
AttributeError: 'bytes' object has no attribute 'format'
```

[SourceCode_sparse2.zip](https://github.com/fonttools/fonttools/files/3308015/SourceCode_sparse2.zip)



Discussion:
@readroberts the source file of the master OTF without `FamilyBlues` [has those values set](https://github.com/adobe-fonts/source-code-pro/blob/master/Roman/Masters/master_1/SourceCode_Regular.ufo/fontinfo.plist#L34-L48); when the font is built the values are rightly left out because they match the `BlueValues`.
So there's no simple workaround to this issue, and I expect other projects to run into the same problem.

Resolving patch:
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -23,8 +23,7 @@
 from fontTools.misc.py23 import *
 from fontTools.misc.fixedTools import otRound
 from fontTools.misc.arrayTools import Vector
-from fontTools.ttLib import TTFont, newTable, TTLibError
-from fontTools.ttLib.tables._n_a_m_e import NameRecord
+from fontTools.ttLib import TTFont, newTable
 from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
 from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
 from fontTools.ttLib.tables.ttProgram import Program
@@ -36,7 +35,7 @@
 from fontTools.varLib.mvar import MVAR_ENTRIES
 from fontTools.varLib.iup import iup_delta_optimize
 from fontTools.varLib.featureVars import addFeatureVariations
-from fontTools.designspaceLib import DesignSpaceDocument, AxisDescriptor
+from fontTools.designspaceLib import DesignSpaceDocument
 from collections import OrderedDict, namedtuple
 import os.path
 import logging
@@ -1107,9 +1106,6 @@ def main(args=None):
 
 	designspace_filename = options.designspace
 	finder = MasterFinder(options.master_finder)
-	outfile = options.outfile
-	if outfile is None:
-		outfile = os.path.splitext(designspace_filename)[0] + '-VF.ttf'
 
 	vf, _, _ = build(
 		designspace_filename,
@@ -1118,6 +1114,11 @@ def main(args=None):
 		optimize=options.optimize
 	)
 
+	outfile = options.outfile
+	if outfile is None:
+		ext = "otf" if vf.sfntVersion == "OTTO" else "ttf"
+		outfile = os.path.splitext(designspace_filename)[0] + '-VF.' + ext
+
 	log.info("Saving variation font %s", outfile)
 	vf.save(outfile)
 
diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py
old mode 100755
new mode 100644
--- a/Lib/fontTools/varLib/cff.py
+++ b/Lib/fontTools/varLib/cff.py
@@ -131,7 +131,7 @@ def __init__(self, key, value, values):
 
 
 def conv_to_int(num):
-	if num % 1 == 0:
+	if isinstance(num, float) and num.is_integer():
 		return int(num)
 	return num
 
@@ -198,17 +198,17 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
 			pds.append(pd)
 		num_masters = len(pds)
 		for key, value in private_dict.rawDict.items():
+			dataList = []
 			if key not in pd_blend_fields:
 				continue
 			if isinstance(value, list):
 				try:
 					values = [pd.rawDict[key] for pd in pds]
 				except KeyError:
-					del private_dict.rawDict[key]
 					print(
-						b"Warning: {key} in default font Private dict is "
-						b"missing from another font, and was "
-						b"discarded.".format(key=key))
+						"Warning: {key} in default font Private dict is "
+						"missing from another font, and was "
+						"discarded.".format(key=key))
 					continue
 				try:
 					values = zip(*values)
@@ -227,7 +227,6 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
 				and is converted finally to:
 				OtherBlues = [[-217, 17.0, 46.0], [-205, 0.0, 0.0]]
 				"""
-				dataList = []
 				prev_val_list = [0] * num_masters
 				any_points_differ = False
 				for val_list in values:
@@ -237,8 +236,6 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
 						any_points_differ = True
 					prev_val_list = val_list
 					deltas = sub_model.getDeltas(rel_list)
-					# Convert numbers with no decimal part to an int.
-					deltas = [conv_to_int(delta) for delta in deltas]
 					# For PrivateDict BlueValues, the default font
 					# values are absolute, not relative to the prior value.
 					deltas[0] = val_list[0]
@@ -253,6 +250,18 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
 					dataList = sub_model.getDeltas(values)
 				else:
 					dataList = values[0]
+
+			# Convert numbers with no decimal part to an int
+			if isinstance(dataList, list):
+				for i, item in enumerate(dataList):
+					if isinstance(item, list):
+						for j, jtem in enumerate(item):
+							dataList[i][j] = conv_to_int(jtem)
+					else:
+						dataList[i] = conv_to_int(item)
+			else:
+				dataList = conv_to_int(dataList)
+
 			private_dict.rawDict[key] = dataList
 
 
@@ -270,7 +279,7 @@ def getfd_map(varFont, fonts_list):
 	num_regions = len(region_fonts)
 	topDict = default_font['CFF '].cff.topDictIndex[0]
 	if not hasattr(topDict, 'FDSelect'):
-		# All glyphs reference only one FontDict. 
+		# All glyphs reference only one FontDict.
 		# Map the FD index for regions to index 0.
 		fd_map[0] = {ri:0 for ri in range(num_regions)}
 		return fd_map
@@ -390,9 +399,9 @@ def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel):
 		# as we know it doesn't exist yet.
 		if vsindex != 0:
 			new_cs.program[:0] = [vsindex, 'vsindex']
-			
+
 	# If there is no variation in any of the charstrings, then vsindex_dict
-	# never gets built. This could still be needed if there is variation 
+	# never gets built. This could still be needed if there is variation
 	# in the PrivatDict, so we will build the default data for vsindex = 0.
 	if not vsindex_dict:
 		key = (True,) * num_masters


Test patch:
diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.ttx b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.ttx
--- a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.ttx
+++ b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.ttx
@@ -374,6 +374,7 @@
           <FontName value="MasterSet_Kanji-w0.00-Generic"/>
           <Private>
             <BlueValues value="-250 -250 1100 1100"/>
+            <FamilyBlues value="-250 -240 1100 1110"/>
             <BlueScale value="0.039625"/>
             <BlueShift value="7"/>
             <BlueFuzz value="0"/>
diff --git a/Tests/varLib/data/test_results/BuildTestCFF2.ttx b/Tests/varLib/data/test_results/BuildTestCFF2.ttx
--- a/Tests/varLib/data/test_results/BuildTestCFF2.ttx
+++ b/Tests/varLib/data/test_results/BuildTestCFF2.ttx
@@ -86,10 +86,10 @@
             <BlueShift value="7"/>
             <BlueFuzz value="0"/>
             <StdHW>
-                <blend value="67 -39.0 67.0"/>
+                <blend value="67 -39 67"/>
             </StdHW>
             <StdVW>
-                <blend value="85 -51.0 87.0"/>
+                <blend value="85 -51 87"/>
             </StdVW>
           </Private>
         </FontDict>
diff --git a/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx b/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
--- a/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
+++ b/Tests/varLib/data/test_results/TestNonMarkingCFF2.ttx
@@ -31,10 +31,10 @@
             <BlueShift value="7"/>
             <BlueFuzz value="0"/>
             <StdHW>
-                <blend value="28 39.0"/>
+                <blend value="28 39"/>
             </StdHW>
             <StdVW>
-                <blend value="34 51.0"/>
+                <blend value="34 51"/>
             </StdVW>
           </Private>
         </FontDict>
diff --git a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
--- a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
+++ b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
@@ -132,6 +132,7 @@
         <FontDict index="0">
           <Private>
             <BlueValues value="-250 -250 1100 1100"/>
+            <FamilyBlues value="-250 -240 1100 1110"/>
             <BlueScale value="0.039625"/>
             <BlueShift value="7"/>
             <BlueFuzz value="0"/>