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:
commit
e782c6e9d2
@ -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:
|
||||||
|
@ -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]
|
||||||
|
36
Tests/varLib/data/TestVVAR.designspace
Normal file
36
Tests/varLib/data/TestVVAR.designspace
Normal 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>
|
BIN
Tests/varLib/data/master_vvar_cff2/TestVVAR.0.otf
Normal file
BIN
Tests/varLib/data/master_vvar_cff2/TestVVAR.0.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_vvar_cff2/TestVVAR.1.otf
Normal file
BIN
Tests/varLib/data/master_vvar_cff2/TestVVAR.1.otf
Normal file
Binary file not shown.
59
Tests/varLib/data/test_results/TestVVAR.ttx
Normal file
59
Tests/varLib/data/test_results/TestVVAR.ttx
Normal 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>
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user