Skip to content

Commit

Permalink
Merge pull request #3473 from fonttools/avar2-modules
Browse files Browse the repository at this point in the history
[avar2] Implement avar2 support in ttGlyphSet.
  • Loading branch information
behdad committed Apr 6, 2024
2 parents fbe4765 + ed092f2 commit 705acc9
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 23 deletions.
51 changes: 50 additions & 1 deletion Lib/fontTools/ttLib/tables/_a_v_a_r.py
Expand Up @@ -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
Expand Down Expand Up @@ -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] = {}
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions Lib/fontTools/ttLib/tables/_f_v_a_r.py
Expand Up @@ -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):
Expand Down
6 changes: 6 additions & 0 deletions Lib/fontTools/ttLib/tables/otTables.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
17 changes: 3 additions & 14 deletions Lib/fontTools/ttLib/ttFont.py
Expand Up @@ -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(
Expand Down
21 changes: 14 additions & 7 deletions Lib/fontTools/varLib/interpolatable.py
Expand Up @@ -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"]
Expand Down Expand Up @@ -811,6 +816,8 @@ def main(args=None):
)
+ "'"
)
if normalized:
name += " (normalized)"
names.append(name)
fonts.append(glyphsets[locTuple])
locations.append(dict(locTuple))
Expand Down
28 changes: 27 additions & 1 deletion Tests/ttLib/tables/_a_v_a_r_test.py
Expand Up @@ -2,16 +2,20 @@
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
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis
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 "
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
Binary file not shown.

0 comments on commit 705acc9

Please sign in to comment.