[varLib.interpolatable] Support CFF2 input font

Fixes https://github.com/fonttools/fonttools/issues/3666
This commit is contained in:
Behdad Esfahbod 2024-10-21 23:39:08 -06:00
parent 885d7c1ecb
commit b5373bf5d2
3 changed files with 1676 additions and 48 deletions

View File

@ -752,11 +752,11 @@ def main(args=None):
elif args.inputs[0].endswith(".ttf") or args.inputs[0].endswith(".otf"): elif args.inputs[0].endswith(".ttf") or args.inputs[0].endswith(".otf"):
from fontTools.ttLib import TTFont from fontTools.ttLib import TTFont
# Is variable font? # Is variable font?
font = TTFont(args.inputs[0]) font = TTFont(args.inputs[0])
upem = font["head"].unitsPerEm upem = font["head"].unitsPerEm
if "gvar" in font:
fvar = font["fvar"] fvar = font["fvar"]
axisMapping = {} axisMapping = {}
@ -780,12 +780,14 @@ def main(args=None):
location, fvarMapping location, fvarMapping
) )
gvar = font["gvar"]
glyf = font["glyf"]
# Gather all glyphs at their "master" locations # Gather all glyphs at their "master" locations
ttGlyphSets = {} ttGlyphSets = {}
glyphsets = defaultdict(dict) glyphsets = defaultdict(dict)
if "gvar" in font:
gvar = font["gvar"]
glyf = font["glyf"]
if glyphs is None: if glyphs is None:
glyphs = sorted(gvar.variations.keys()) glyphs = sorted(gvar.variations.keys())
for glyphname in glyphs: for glyphname in glyphs:
@ -806,6 +808,60 @@ def main(args=None):
glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf
) )
elif "CFF2" in font:
fvarAxes = font["fvar"].axes
cff2 = font["CFF2"].cff.topDictIndex[0]
charstrings = cff2.CharStrings
if glyphs is None:
glyphs = sorted(charstrings.keys())
for glyphname in glyphs:
cs = charstrings[glyphname]
private = cs.private
# Extract vsindex for the glyph
vsindices = {getattr(private, "vsindex", 0)}
vsindex = getattr(private, "vsindex", 0)
last_op = 0
# The spec says vsindex can only appear once and must be the first
# operator in the charstring, but we support multiple.
# https://github.com/harfbuzz/boring-expansion-spec/issues/158
for op in enumerate(cs.program):
if op == "blend":
vsindices.add(vsindex)
elif op == "vsindex":
assert isinstance(last_op, int)
vsindex = last_op
last_op = op
if not hasattr(private, "vstore"):
continue
varStore = private.vstore.otVarStore
for vsindex in vsindices:
varData = varStore.VarData[vsindex]
for regionIndex in varData.VarRegionIndex:
region = varStore.VarRegionList.Region[regionIndex]
locDict = {}
loc = []
for axisIndex, axis in enumerate(region.VarRegionAxis):
tag = fvarAxes[axisIndex].axisTag
val = axis.PeakCoord
locDict[tag] = val
loc.append((tag, val))
locTuple = tuple(loc)
if locTuple not in ttGlyphSets:
ttGlyphSets[locTuple] = font.getGlyphSet(
location=locDict,
normalized=True,
recalcBounds=False,
)
glyphset = glyphsets[locTuple]
glyphset[glyphname] = ttGlyphSets[locTuple][glyphname]
names = ["''"] names = ["''"]
fonts = [font.getGlyphSet()] fonts = [font.getGlyphSet()]
locations = [{}] locations = [{}]

File diff suppressed because it is too large Load Diff

View File

@ -94,6 +94,33 @@ class InterpolatableTest(unittest.TestCase):
otf_paths = self.get_file_list(self.tempdir, suffix) otf_paths = self.get_file_list(self.tempdir, suffix)
self.assertIsNone(interpolatable_main(otf_paths)) self.assertIsNone(interpolatable_main(otf_paths))
def test_interpolatable_cff2(self):
suffix = ".otf"
ttx_dir = self.get_test_input("variable_ttx_interpolatable_cff2")
ttx_path = os.path.abspath(os.path.join(ttx_dir, "interpolatable-test.ttx"))
self.temp_dir()
self.compile_font(ttx_path, suffix, self.tempdir)
otf_path = self.get_file_list(self.tempdir, suffix)[0]
problems = interpolatable_main([otf_path])
print(problems)
self.assertEqual(
problems["uni0408"],
[
{
"type": "underweight",
"contour": 0,
"master_1": "'wght=200.0 opsz=20.0'",
"master_2": "'wght=200.0 opsz=60.0'",
"master_1_idx": 2,
"master_2_idx": 3,
"tolerance": 0.9184032411892079,
},
],
)
def test_interpolatable_ufo(self): def test_interpolatable_ufo(self):
ttx_dir = self.get_test_input("master_ufo") ttx_dir = self.get_test_input("master_ufo")
ufo_paths = self.get_file_list(ttx_dir, ".ufo", "TestFamily2-") ufo_paths = self.get_file_list(ttx_dir, ".ufo", "TestFamily2-")