[varLib.interpolatable] Support CFF2 input font
Fixes https://github.com/fonttools/fonttools/issues/3666
This commit is contained in:
parent
885d7c1ecb
commit
b5373bf5d2
@ -752,39 +752,41 @@ 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
|
||||||
|
|
||||||
|
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"]
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gather all glyphs at their "master" locations
|
||||||
|
ttGlyphSets = {}
|
||||||
|
glyphsets = defaultdict(dict)
|
||||||
|
|
||||||
if "gvar" in font:
|
if "gvar" in font:
|
||||||
|
|
||||||
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"]
|
|
||||||
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"]
|
gvar = font["gvar"]
|
||||||
glyf = font["glyf"]
|
glyf = font["glyf"]
|
||||||
# Gather all glyphs at their "master" locations
|
|
||||||
ttGlyphSets = {}
|
|
||||||
glyphsets = defaultdict(dict)
|
|
||||||
|
|
||||||
if glyphs is None:
|
if glyphs is None:
|
||||||
glyphs = sorted(gvar.variations.keys())
|
glyphs = sorted(gvar.variations.keys())
|
||||||
@ -806,30 +808,84 @@ def main(args=None):
|
|||||||
glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf
|
glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf
|
||||||
)
|
)
|
||||||
|
|
||||||
names = ["''"]
|
elif "CFF2" in font:
|
||||||
fonts = [font.getGlyphSet()]
|
fvarAxes = font["fvar"].axes
|
||||||
locations = [{}]
|
cff2 = font["CFF2"].cff.topDictIndex[0]
|
||||||
axis_triples = {a: (-1, 0, +1) for a in sorted(axisMapping.keys())}
|
charstrings = cff2.CharStrings
|
||||||
for locTuple in sorted(glyphsets.keys(), key=lambda v: (len(v), v)):
|
|
||||||
name = (
|
if glyphs is None:
|
||||||
"'"
|
glyphs = sorted(charstrings.keys())
|
||||||
+ " ".join(
|
for glyphname in glyphs:
|
||||||
"%s=%s"
|
cs = charstrings[glyphname]
|
||||||
% (
|
private = cs.private
|
||||||
k,
|
|
||||||
floatToFixedToStr(
|
# Extract vsindex for the glyph
|
||||||
piecewiseLinearMap(v, axisMapping[k]), 14
|
vsindices = {getattr(private, "vsindex", 0)}
|
||||||
),
|
vsindex = getattr(private, "vsindex", 0)
|
||||||
)
|
last_op = 0
|
||||||
for k, v in locTuple
|
# 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 = ["''"]
|
||||||
|
fonts = [font.getGlyphSet()]
|
||||||
|
locations = [{}]
|
||||||
|
axis_triples = {a: (-1, 0, +1) for a in sorted(axisMapping.keys())}
|
||||||
|
for locTuple in sorted(glyphsets.keys(), key=lambda v: (len(v), v)):
|
||||||
|
name = (
|
||||||
|
"'"
|
||||||
|
+ " ".join(
|
||||||
|
"%s=%s"
|
||||||
|
% (
|
||||||
|
k,
|
||||||
|
floatToFixedToStr(
|
||||||
|
piecewiseLinearMap(v, axisMapping[k]), 14
|
||||||
|
),
|
||||||
)
|
)
|
||||||
+ "'"
|
for k, v in locTuple
|
||||||
)
|
)
|
||||||
if normalized:
|
+ "'"
|
||||||
name += " (normalized)"
|
)
|
||||||
names.append(name)
|
if normalized:
|
||||||
fonts.append(glyphsets[locTuple])
|
name += " (normalized)"
|
||||||
locations.append(dict(locTuple))
|
names.append(name)
|
||||||
|
fonts.append(glyphsets[locTuple])
|
||||||
|
locations.append(dict(locTuple))
|
||||||
|
|
||||||
args.ignore_missing = True
|
args.ignore_missing = True
|
||||||
args.inputs = []
|
args.inputs = []
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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-")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user