Merge pull request #3473 from fonttools/avar2-modules

[avar2] Implement avar2 support in ttGlyphSet.
This commit is contained in:
Behdad Esfahbod 2024-04-06 07:59:22 -05:00 committed by GitHub
commit 705acc994f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 103 additions and 23 deletions

View File

@ -6,6 +6,9 @@ from fontTools.misc.fixedTools import (
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 @@ class table__a_v_a_r(BaseTTXConverter):
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 @@ class table__a_v_a_r(BaseTTXConverter):
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

View File

@ -110,6 +110,9 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
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):

View File

@ -703,6 +703,9 @@ class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")):
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 @@ class VarIdxMap(BaseTable):
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):

View File

@ -781,26 +781,15 @@ class TTFont(object):
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(

View File

@ -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))

View File

@ -2,6 +2,7 @@ from fontTools.misc.testTools import parseXML
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 @@ 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 "
@ -100,7 +104,7 @@ class AxisVariationTableTest(unittest.TestCase):
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 @@ class Avar2Test(unittest.TestCase):
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

Binary file not shown.