From 150d4fc195a19f383ce4380b87beb483b3938554 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 2 Aug 2024 14:10:52 -0600 Subject: [PATCH] [varLib.avar] Sketch of code to reconstruct mappings from binary https://github.com/Lorp/fencer/issues/25 --- Lib/fontTools/varLib/avar.py | 106 ++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/varLib/avar.py b/Lib/fontTools/varLib/avar.py index 60f0d7e70..541bc5b83 100644 --- a/Lib/fontTools/varLib/avar.py +++ b/Lib/fontTools/varLib/avar.py @@ -1,10 +1,104 @@ from fontTools.varLib import _add_avar, load_designspace +from fontTools.varLib.varStore import VarStoreInstancer +from fontTools.misc.fixedTools import fixedToFloat as fi2fl from fontTools.misc.cliTools import makeOutputFileName +from itertools import product import logging log = logging.getLogger("fontTools.varLib.avar") +def _denormalize(v, axis): + return axis.defaultValue + v * ( + (axis.maxValue if v >= 0 else axis.minValue) - axis.defaultValue + ) + + +def mappings_from_avar(font, denormalize=True): + fvarAxes = font["fvar"].axes + axisMap = {a.axisTag: a for a in fvarAxes} + axisTags = [a.axisTag for a in fvarAxes] + axisIndexes = {a.axisTag: i for i, a in enumerate(fvarAxes)} + if "avar" not in font: + return {}, {} + avar = font["avar"] + axisMaps = { + tag: seg + for tag, seg in avar.segments.items() + if seg and seg != {-1: -1, 0: 0, 1: 1} + } + mappings = [] + + if getattr(avar, "majorVersion", 1) == 2: + varStore = avar.table.VarStore + regions = varStore.VarRegionList.Region + inputLocations = set() + for varData in varStore.VarData: + regionIndices = varData.VarRegionIndex + for regionIndex in regionIndices: + peakLocation = {} + corners = [] + region = regions[regionIndex] + for axisIndex, axis in enumerate(region.VarRegionAxis): + if axis.PeakCoord == 0: + continue + axisTag = axisTags[axisIndex] + peakLocation[axisTag] = axis.PeakCoord + corner = [] + if axis.StartCoord != 0: + corner.append((axisTag, axis.StartCoord)) + if axis.EndCoord != 0: + corner.append((axisTag, axis.EndCoord)) + corners.append(corner) + corners = set(product(*corners)) + inputLocations.update(corners) + + inputLocations = [ + dict(t) + for t in sorted( + inputLocations, + key=lambda t: (len(t), tuple(axisIndexes[tag] for tag, _ in t)), + ) + ] + + varIdxMap = avar.table.VarIdxMap + instancer = VarStoreInstancer(varStore, fvarAxes) + for location in inputLocations: + instancer.setLocation(location) + outputLocation = {} + for axisIndex, axisTag in enumerate(axisTags): + varIdx = axisIndex + if varIdxMap is not None: + varIdx = varIdxMap[varIdx] + delta = instancer[varIdx] + if delta != 0: + v = location.get(axisTag, 0) + v = v + fi2fl(delta, 14) + v = max(-1, min(1, v)) + outputLocation[axisTag] = v + mappings.append((location, outputLocation)) + # Filter out empty mappings + mappings = [io for io in mappings if io[1]] + + if denormalize: + for tag, seg in axisMaps.items(): + if tag not in axisMap: + raise ValueError(f"Unknown axis tag {tag}") + denorm = lambda v: _denormalize(v, axisMap[tag]) + axisMaps[tag] = {denorm(k): denorm(v) for k, v in seg.items()} + + for i, (inputLoc, outputLoc) in enumerate(mappings): + inputLoc = { + tag: _denormalize(val, axisMap[tag]) for tag, val in inputLoc.items() + } + outputLoc = { + tag: _denormalize(val, axisMap[tag]) for tag, val in outputLoc.items() + } + mappings[i] = (inputLoc, outputLoc) + + return axisMaps, mappings + + def main(args=None): """Add `avar` table from designspace file to variable font.""" @@ -24,7 +118,11 @@ def main(args=None): ) parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.") parser.add_argument( - "designspace", metavar="family.designspace", help="Designspace file." + "designspace", + metavar="family.designspace", + help="Designspace file.", + nargs="?", + default=None, ) parser.add_argument( "-o", @@ -45,6 +143,12 @@ def main(args=None): log.error("Not a variable font.") return 1 + if options.designspace is None: + from pprint import pprint + + pprint(mappings_from_avar(font)) + return + axisTags = [a.axisTag for a in font["fvar"].axes] ds = load_designspace(options.designspace)