Merge pull request #1551 from fonttools/VVAR-support

[varLib] Add support for building VVAR table from vmtx and VORG tables.
This commit is contained in:
Read Roberts 2019-04-02 09:38:22 -07:00 committed by GitHub
commit e782c6e9d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 211 additions and 34 deletions

View File

@ -463,46 +463,120 @@ def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
var = TupleVariation(support, delta) var = TupleVariation(support, delta)
cvar.variations.append(var) 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() glyphOrder = font.getGlyphOrder()
hAdvanceDeltasAndSupports = {} # Build list of source font advance widths for each glyph
metricses = [m["hmtx"].metrics for m in master_ttfs] metricsTag = tableFields.metricsTag
for glyph in glyphOrder: advMetricses = [m[metricsTag].metrics for m in master_ttfs]
hAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in metricses]
hAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(hAdvances)
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 directStore = None
if singleModel: if singleModel:
# Build direct mapping # Build direct mapping
supports = next(iter(vhAdvanceDeltasAndSupports.values()))[1][1:]
supports = next(iter(hAdvanceDeltasAndSupports.values()))[1][1:]
varTupleList = builder.buildVarRegionList(supports, axisTags) varTupleList = builder.buildVarRegionList(supports, axisTags)
varTupleIndexes = list(range(len(supports))) varTupleIndexes = list(range(len(supports)))
varData = builder.buildVarData(varTupleIndexes, [], optimize=False) varData = builder.buildVarData(varTupleIndexes, [], optimize=False)
for glyphName in glyphOrder: for glyphName in glyphOrder:
varData.addItem(hAdvanceDeltasAndSupports[glyphName][0]) varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0])
varData.optimize() varData.optimize()
directStore = builder.buildVarStore(varTupleList, [varData]) directStore = builder.buildVarStore(varTupleList, [varData])
# Build optimized indirect mapping # Build optimized indirect mapping
storeBuilder = varStore.OnlineVarStoreBuilder(axisTags) storeBuilder = varStore.OnlineVarStoreBuilder(axisTags)
mapping = {} advMapping = {}
for glyphName in glyphOrder: for glyphName in glyphOrder:
deltas,supports = hAdvanceDeltasAndSupports[glyphName] deltas, supports = vhAdvanceDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports) 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() indirectStore = storeBuilder.finish()
mapping2 = indirectStore.optimize() mapping2 = indirectStore.optimize()
mapping = [mapping2[mapping[g]] for g in glyphOrder] advMapping = [mapping2[advMapping[g]] for g in glyphOrder]
advanceMapping = builder.buildVarIdxMap(mapping, 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: if directStore:
# Compile both, see which is more compact # Compile both, see which is more compact
@ -515,20 +589,17 @@ def _add_HVAR(font, masterModel, master_ttfs, axisTags):
advanceMapping.compile(writer, font) advanceMapping.compile(writer, font)
indirectSize = len(writer.getAllData()) indirectSize = len(writer.getAllData())
use_direct = directSize < indirectSize useDirect = directSize < indirectSize
# Done; put it all together. if useDirect:
assert "HVAR" not in font metricsStore = directStore
HVAR = font["HVAR"] = newTable('HVAR') advanceMapping = None
hvar = HVAR.table = ot.HVAR()
hvar.Version = 0x00010000
hvar.LsbMap = hvar.RsbMap = None
if use_direct:
hvar.VarStore = directStore
hvar.AdvWidthMap = None
else: else:
hvar.VarStore = indirectStore metricsStore = indirectStore
hvar.AdvWidthMap = advanceMapping if vOrigMetricses:
vOrigMapping = builder.buildVarIdxMap(vOrigMap, glyphOrder)
return metricsStore, advanceMapping, vOrigMapping
def _add_MVAR(font, masterModel, master_ttfs, axisTags): def _add_MVAR(font, masterModel, master_ttfs, axisTags):
@ -840,6 +911,8 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
_add_MVAR(vf, model, master_fonts, axisTags) _add_MVAR(vf, model, master_fonts, axisTags)
if 'HVAR' not in exclude: if 'HVAR' not in exclude:
_add_HVAR(vf, model, master_fonts, axisTags) _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: if 'GDEF' not in exclude or 'GPOS' not in exclude:
_merge_OTL(vf, model, master_fonts, axisTags) _merge_OTL(vf, model, master_fonts, axisTags)
if 'gvar' not in exclude and 'glyf' in vf: if 'gvar' not in exclude and 'glyf' in vf:

View File

@ -469,11 +469,6 @@ class CFF2CharStringMergePen(T2CharStringPen):
arg[0] = round_func(arg[0]) arg[0] = round_func(arg[0])
program.append(arg[0]) program.append(arg[0])
for arg in blendlist: 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) deltas = var_model.getDeltas(arg)
if round_func: if round_func:
deltas = [round_func(delta) for delta in deltas] deltas = [round_func(delta) for delta in deltas]

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] 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) 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(): def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument() ds = DesignSpaceDocument()