diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 39039cf73a..6ea4132b3d 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -6,6 +6,9 @@ strToFixedToFloat as str2fl, ) 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 . import DefaultTable from . import otTables @@ -74,9 +77,10 @@ def compile(self, ttFont): def decompile(self, data, ttFont): super().decompile(data, ttFont) - assert self.table.Version >= 0x00010000 self.majorVersion = self.table.Version >> 16 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] for axis in axisTags: self.segments[axis] = {} @@ -136,3 +140,48 @@ def fromXML(self, name, attrs, content, ttFont): segment[fromValue] = toValue else: 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 diff --git a/Lib/fontTools/ttLib/tables/_f_v_a_r.py b/Lib/fontTools/ttLib/tables/_f_v_a_r.py index 062a9aa429..a3bdacd4cc 100644 --- a/Lib/fontTools/ttLib/tables/_f_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_f_v_a_r.py @@ -110,6 +110,9 @@ def fromXML(self, name, attrs, content, ttFont): instance.fromXML(name, attrs, content, ttFont) self.instances.append(instance) + def getAxes(self): + return {a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in self.axes} + class Axis(object): def __init__(self): diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 3505f42337..879610a1ed 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -703,6 +703,9 @@ def fromXML(self, name, attrs, content, font): assert inner <= 0xFFFF 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): def populateDefaults(self, propagator=None): @@ -755,6 +758,9 @@ def fromXML(self, name, attrs, content, font): assert inner <= 0xFFFF mapping[glyph] = (outer << 16) | inner + def __getitem__(self, glyphName): + return self.mapping.get(glyphName, NO_VARIATION_INDEX) + class VarRegionList(BaseTable): def preWrite(self, font): diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index 52e048b5f1..578f6328f1 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -781,26 +781,15 @@ def normalizeLocation(self, location): 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: raise TTLibError("Not a variable font") - axes = { - a.axisTag: (a.minValue, a.defaultValue, a.maxValue) - for a in self["fvar"].axes - } + axes = self["fvar"].getAxes() location = normalizeLocation(location, axes) if "avar" in self: - avar = self["avar"] - 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 + location = self["avar"].renormalizeLocation(location, self) return location def getBestCmap( diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py index 5fc12e04c9..e212ecf8d1 100644 --- a/Lib/fontTools/varLib/interpolatable.py +++ b/Lib/fontTools/varLib/interpolatable.py @@ -749,22 +749,27 @@ def main(args=None): if "gvar" in font: # Is variable font - axisMapping = {} fvar = font["fvar"] + axisMapping = {} for axis in fvar.axes: axisMapping[axis.axisTag] = { -1: axis.minValue, 0: axis.defaultValue, 1: axis.maxValue, } + normalized = False if "avar" in font: avar = font["avar"] - for axisTag, segments in avar.segments.items(): - fvarMapping = axisMapping[axisTag].copy() - for location, value in segments.items(): - axisMapping[axisTag][value] = piecewiseLinearMap( - location, fvarMapping - ) + if getattr(avar.table, "VarStore", None): + axisMapping = {tag: {-1: -1, 0: 0, 1: 1} for tag in axisMapping} + normalized = True + else: + 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"] glyf = font["glyf"] @@ -811,6 +816,8 @@ def main(args=None): ) + "'" ) + if normalized: + name += " (normalized)" names.append(name) fonts.append(glyphsets[locTuple]) locations.append(dict(locTuple)) diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py index dbe07b3a5b..2c41bdf642 100644 --- a/Tests/ttLib/tables/_a_v_a_r_test.py +++ b/Tests/ttLib/tables/_a_v_a_r_test.py @@ -2,6 +2,7 @@ from fontTools.misc.textTools import deHexStr from fontTools.misc.xmlWriter import XMLWriter from fontTools.misc.fixedTools import floatToFixed as fl2fi +from fontTools.pens.statisticsPen import StatisticsPen from fontTools.ttLib import TTFont, TTLibError import fontTools.ttLib.tables.otTables as otTables from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r @@ -9,9 +10,12 @@ import fontTools.varLib.models as models import fontTools.varLib.varStore as varStore from io import BytesIO +import os import unittest +DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data") + TEST_DATA = deHexStr( "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 " @@ -100,7 +104,7 @@ def xml_lines(writer): class Avar2Test(unittest.TestCase): - def test(self): + def test_roundtrip(self): axisTags = ["wght", "wdth"] fvar = table__f_v_a_r() for tag in axisTags: @@ -173,6 +177,28 @@ def test(self): assert avar.table.VarStore.VarRegionList.RegionAxisCount == 2 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__": import sys diff --git a/Tests/ttLib/tables/data/Amstelvar-avar2.subset.ttf b/Tests/ttLib/tables/data/Amstelvar-avar2.subset.ttf new file mode 100644 index 0000000000..1361048713 Binary files /dev/null and b/Tests/ttLib/tables/data/Amstelvar-avar2.subset.ttf differ