diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 0543ee375..cceabcb6e 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -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: diff --git a/Tests/varLib/data/TestVVAR.designspace b/Tests/varLib/data/TestVVAR.designspace new file mode 100644 index 000000000..162f7385c --- /dev/null +++ b/Tests/varLib/data/TestVVAR.designspace @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 000000000..9749ab5b0 Binary files /dev/null and b/Tests/varLib/data/master_vvar_cff2/TestVVAR.0.otf 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 000000000..2ea0e9f07 Binary files /dev/null and b/Tests/varLib/data/master_vvar_cff2/TestVVAR.1.otf differ diff --git a/Tests/varLib/data/test_results/TestVVAR.ttx b/Tests/varLib/data/test_results/TestVVAR.ttx new file mode 100644 index 000000000..48ca40858 --- /dev/null +++ b/Tests/varLib/data/test_results/TestVVAR.ttx @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py index 32e7ab5e2..c7e6a65d9 100644 --- a/Tests/varLib/varLib_test.py +++ b/Tests/varLib/varLib_test.py @@ -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()