Merge pull request #3473 from fonttools/avar2-modules
[avar2] Implement avar2 support in ttGlyphSet.
This commit is contained in:
commit
705acc994f
@ -6,6 +6,9 @@ from fontTools.misc.fixedTools import (
|
|||||||
strToFixedToFloat as str2fl,
|
strToFixedToFloat as str2fl,
|
||||||
)
|
)
|
||||||
from fontTools.misc.textTools import bytesjoin, safeEval
|
from fontTools.misc.textTools import bytesjoin, safeEval
|
||||||
|
from fontTools.misc.roundTools import otRound
|
||||||
|
from fontTools.varLib.models import piecewiseLinearMap
|
||||||
|
from fontTools.varLib.varStore import VarStoreInstancer, NO_VARIATION_INDEX
|
||||||
from fontTools.ttLib import TTLibError
|
from fontTools.ttLib import TTLibError
|
||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
from . import otTables
|
from . import otTables
|
||||||
@ -74,9 +77,10 @@ class table__a_v_a_r(BaseTTXConverter):
|
|||||||
|
|
||||||
def decompile(self, data, ttFont):
|
def decompile(self, data, ttFont):
|
||||||
super().decompile(data, ttFont)
|
super().decompile(data, ttFont)
|
||||||
assert self.table.Version >= 0x00010000
|
|
||||||
self.majorVersion = self.table.Version >> 16
|
self.majorVersion = self.table.Version >> 16
|
||||||
self.minorVersion = self.table.Version & 0xFFFF
|
self.minorVersion = self.table.Version & 0xFFFF
|
||||||
|
if self.majorVersion not in (1, 2):
|
||||||
|
raise NotImplementedError("Unknown avar table version")
|
||||||
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
|
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
|
||||||
for axis in axisTags:
|
for axis in axisTags:
|
||||||
self.segments[axis] = {}
|
self.segments[axis] = {}
|
||||||
@ -136,3 +140,48 @@ class table__a_v_a_r(BaseTTXConverter):
|
|||||||
segment[fromValue] = toValue
|
segment[fromValue] = toValue
|
||||||
else:
|
else:
|
||||||
super().fromXML(name, attrs, content, ttFont)
|
super().fromXML(name, attrs, content, ttFont)
|
||||||
|
|
||||||
|
def renormalizeLocation(self, location, font):
|
||||||
|
|
||||||
|
if self.majorVersion not in (1, 2):
|
||||||
|
raise NotImplementedError("Unknown avar table version")
|
||||||
|
|
||||||
|
avarSegments = self.segments
|
||||||
|
mappedLocation = {}
|
||||||
|
for axisTag, value in location.items():
|
||||||
|
avarMapping = avarSegments.get(axisTag, None)
|
||||||
|
if avarMapping is not None:
|
||||||
|
value = piecewiseLinearMap(value, avarMapping)
|
||||||
|
mappedLocation[axisTag] = value
|
||||||
|
|
||||||
|
if self.majorVersion < 2:
|
||||||
|
return mappedLocation
|
||||||
|
|
||||||
|
# Version 2
|
||||||
|
|
||||||
|
varIdxMap = self.table.VarIdxMap
|
||||||
|
varStore = self.table.VarStore
|
||||||
|
axes = font["fvar"].axes
|
||||||
|
if varStore is not None:
|
||||||
|
instancer = VarStoreInstancer(varStore, axes, mappedLocation)
|
||||||
|
|
||||||
|
coords = list(fl2fi(mappedLocation.get(axis.axisTag, 0), 14) for axis in axes)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for varIdx, v in enumerate(coords):
|
||||||
|
|
||||||
|
if varIdxMap is not None:
|
||||||
|
varIdx = varIdxMap[varIdx]
|
||||||
|
|
||||||
|
if varStore is not None:
|
||||||
|
delta = instancer[varIdx]
|
||||||
|
v += otRound(delta)
|
||||||
|
v = min(max(v, -(1 << 14)), +(1 << 14))
|
||||||
|
|
||||||
|
out.append(v)
|
||||||
|
|
||||||
|
mappedLocation = {
|
||||||
|
axis.axisTag: fi2fl(v, 14) for v, axis in zip(out, axes) if v != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappedLocation
|
||||||
|
@ -110,6 +110,9 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
|
|||||||
instance.fromXML(name, attrs, content, ttFont)
|
instance.fromXML(name, attrs, content, ttFont)
|
||||||
self.instances.append(instance)
|
self.instances.append(instance)
|
||||||
|
|
||||||
|
def getAxes(self):
|
||||||
|
return {a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in self.axes}
|
||||||
|
|
||||||
|
|
||||||
class Axis(object):
|
class Axis(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -703,6 +703,9 @@ class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
|
|||||||
assert inner <= 0xFFFF
|
assert inner <= 0xFFFF
|
||||||
mapping.insert(index, (outer << 16) | inner)
|
mapping.insert(index, (outer << 16) | inner)
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
return self.mapping[i] if i < len(self.mapping) else NO_VARIATION_INDEX
|
||||||
|
|
||||||
|
|
||||||
class VarIdxMap(BaseTable):
|
class VarIdxMap(BaseTable):
|
||||||
def populateDefaults(self, propagator=None):
|
def populateDefaults(self, propagator=None):
|
||||||
@ -755,6 +758,9 @@ class VarIdxMap(BaseTable):
|
|||||||
assert inner <= 0xFFFF
|
assert inner <= 0xFFFF
|
||||||
mapping[glyph] = (outer << 16) | inner
|
mapping[glyph] = (outer << 16) | inner
|
||||||
|
|
||||||
|
def __getitem__(self, glyphName):
|
||||||
|
return self.mapping.get(glyphName, NO_VARIATION_INDEX)
|
||||||
|
|
||||||
|
|
||||||
class VarRegionList(BaseTable):
|
class VarRegionList(BaseTable):
|
||||||
def preWrite(self, font):
|
def preWrite(self, font):
|
||||||
|
@ -781,26 +781,15 @@ class TTFont(object):
|
|||||||
|
|
||||||
Raises ``TTLibError`` if the font is not a variable font.
|
Raises ``TTLibError`` if the font is not a variable font.
|
||||||
"""
|
"""
|
||||||
from fontTools.varLib.models import normalizeLocation, piecewiseLinearMap
|
from fontTools.varLib.models import normalizeLocation
|
||||||
|
|
||||||
if "fvar" not in self:
|
if "fvar" not in self:
|
||||||
raise TTLibError("Not a variable font")
|
raise TTLibError("Not a variable font")
|
||||||
|
|
||||||
axes = {
|
axes = self["fvar"].getAxes()
|
||||||
a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
|
|
||||||
for a in self["fvar"].axes
|
|
||||||
}
|
|
||||||
location = normalizeLocation(location, axes)
|
location = normalizeLocation(location, axes)
|
||||||
if "avar" in self:
|
if "avar" in self:
|
||||||
avar = self["avar"]
|
location = self["avar"].renormalizeLocation(location, self)
|
||||||
avarSegments = avar.segments
|
|
||||||
mappedLocation = {}
|
|
||||||
for axisTag, value in location.items():
|
|
||||||
avarMapping = avarSegments.get(axisTag, None)
|
|
||||||
if avarMapping is not None:
|
|
||||||
value = piecewiseLinearMap(value, avarMapping)
|
|
||||||
mappedLocation[axisTag] = value
|
|
||||||
location = mappedLocation
|
|
||||||
return location
|
return location
|
||||||
|
|
||||||
def getBestCmap(
|
def getBestCmap(
|
||||||
|
@ -749,22 +749,27 @@ def main(args=None):
|
|||||||
if "gvar" in font:
|
if "gvar" in font:
|
||||||
# Is variable font
|
# Is variable font
|
||||||
|
|
||||||
axisMapping = {}
|
|
||||||
fvar = font["fvar"]
|
fvar = font["fvar"]
|
||||||
|
axisMapping = {}
|
||||||
for axis in fvar.axes:
|
for axis in fvar.axes:
|
||||||
axisMapping[axis.axisTag] = {
|
axisMapping[axis.axisTag] = {
|
||||||
-1: axis.minValue,
|
-1: axis.minValue,
|
||||||
0: axis.defaultValue,
|
0: axis.defaultValue,
|
||||||
1: axis.maxValue,
|
1: axis.maxValue,
|
||||||
}
|
}
|
||||||
|
normalized = False
|
||||||
if "avar" in font:
|
if "avar" in font:
|
||||||
avar = font["avar"]
|
avar = font["avar"]
|
||||||
for axisTag, segments in avar.segments.items():
|
if getattr(avar.table, "VarStore", None):
|
||||||
fvarMapping = axisMapping[axisTag].copy()
|
axisMapping = {tag: {-1: -1, 0: 0, 1: 1} for tag in axisMapping}
|
||||||
for location, value in segments.items():
|
normalized = True
|
||||||
axisMapping[axisTag][value] = piecewiseLinearMap(
|
else:
|
||||||
location, fvarMapping
|
for axisTag, segments in avar.segments.items():
|
||||||
)
|
fvarMapping = axisMapping[axisTag].copy()
|
||||||
|
for location, value in segments.items():
|
||||||
|
axisMapping[axisTag][value] = piecewiseLinearMap(
|
||||||
|
location, fvarMapping
|
||||||
|
)
|
||||||
|
|
||||||
gvar = font["gvar"]
|
gvar = font["gvar"]
|
||||||
glyf = font["glyf"]
|
glyf = font["glyf"]
|
||||||
@ -811,6 +816,8 @@ def main(args=None):
|
|||||||
)
|
)
|
||||||
+ "'"
|
+ "'"
|
||||||
)
|
)
|
||||||
|
if normalized:
|
||||||
|
name += " (normalized)"
|
||||||
names.append(name)
|
names.append(name)
|
||||||
fonts.append(glyphsets[locTuple])
|
fonts.append(glyphsets[locTuple])
|
||||||
locations.append(dict(locTuple))
|
locations.append(dict(locTuple))
|
||||||
|
@ -2,6 +2,7 @@ from fontTools.misc.testTools import parseXML
|
|||||||
from fontTools.misc.textTools import deHexStr
|
from fontTools.misc.textTools import deHexStr
|
||||||
from fontTools.misc.xmlWriter import XMLWriter
|
from fontTools.misc.xmlWriter import XMLWriter
|
||||||
from fontTools.misc.fixedTools import floatToFixed as fl2fi
|
from fontTools.misc.fixedTools import floatToFixed as fl2fi
|
||||||
|
from fontTools.pens.statisticsPen import StatisticsPen
|
||||||
from fontTools.ttLib import TTFont, TTLibError
|
from fontTools.ttLib import TTFont, TTLibError
|
||||||
import fontTools.ttLib.tables.otTables as otTables
|
import fontTools.ttLib.tables.otTables as otTables
|
||||||
from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r
|
from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r
|
||||||
@ -9,9 +10,12 @@ from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis
|
|||||||
import fontTools.varLib.models as models
|
import fontTools.varLib.models as models
|
||||||
import fontTools.varLib.varStore as varStore
|
import fontTools.varLib.varStore as varStore
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
|
||||||
|
|
||||||
TEST_DATA = deHexStr(
|
TEST_DATA = deHexStr(
|
||||||
"00 01 00 00 00 00 00 02 "
|
"00 01 00 00 00 00 00 02 "
|
||||||
"00 04 C0 00 C0 00 00 00 00 00 13 33 33 33 40 00 40 00 "
|
"00 04 C0 00 C0 00 00 00 00 00 13 33 33 33 40 00 40 00 "
|
||||||
@ -100,7 +104,7 @@ class AxisVariationTableTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class Avar2Test(unittest.TestCase):
|
class Avar2Test(unittest.TestCase):
|
||||||
def test(self):
|
def test_roundtrip(self):
|
||||||
axisTags = ["wght", "wdth"]
|
axisTags = ["wght", "wdth"]
|
||||||
fvar = table__f_v_a_r()
|
fvar = table__f_v_a_r()
|
||||||
for tag in axisTags:
|
for tag in axisTags:
|
||||||
@ -173,6 +177,28 @@ class Avar2Test(unittest.TestCase):
|
|||||||
assert avar.table.VarStore.VarRegionList.RegionAxisCount == 2
|
assert avar.table.VarStore.VarRegionList.RegionAxisCount == 2
|
||||||
assert avar.table.VarStore.VarRegionList.RegionCount == 1
|
assert avar.table.VarStore.VarRegionList.RegionCount == 1
|
||||||
|
|
||||||
|
def test_ttGlyphSet(self):
|
||||||
|
ttf = os.path.join(DATA_DIR, "Amstelvar-avar2.subset.ttf")
|
||||||
|
font = TTFont(ttf)
|
||||||
|
|
||||||
|
regular = font.getGlyphSet()
|
||||||
|
black = font.getGlyphSet(location={"wght": 900})
|
||||||
|
expanded = font.getGlyphSet(location={"wdth": 125})
|
||||||
|
|
||||||
|
regularStats = StatisticsPen()
|
||||||
|
blackStats = StatisticsPen()
|
||||||
|
expandedStats = StatisticsPen()
|
||||||
|
|
||||||
|
for glyph in "Test":
|
||||||
|
regular[glyph].draw(regularStats)
|
||||||
|
black[glyph].draw(blackStats)
|
||||||
|
expanded[glyph].draw(expandedStats)
|
||||||
|
|
||||||
|
assert abs(regularStats.area) < abs(blackStats.area)
|
||||||
|
assert abs(expandedStats.area) < abs(blackStats.area)
|
||||||
|
|
||||||
|
assert regularStats.meanX < expandedStats.meanX
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
|
BIN
Tests/ttLib/tables/data/Amstelvar-avar2.subset.ttf
Normal file
BIN
Tests/ttLib/tables/data/Amstelvar-avar2.subset.ttf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user