2023-07-23 09:10:47 -06:00
|
|
|
from fontTools.ttLib import newTable
|
2023-07-22 21:18:52 -06:00
|
|
|
from fontTools.pens.areaPen import AreaPen
|
2023-07-23 12:09:02 -06:00
|
|
|
from fontTools.varLib.models import piecewiseLinearMap, normalizeValue
|
2023-07-22 21:18:52 -06:00
|
|
|
from fontTools.misc.cliTools import makeOutputFileName
|
2023-07-23 09:29:42 -06:00
|
|
|
import math
|
|
|
|
import logging
|
|
|
|
from pprint import pformat
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
__all__ = [
|
|
|
|
"planWeightAxis",
|
|
|
|
"planWidthAxis",
|
|
|
|
"planAxis",
|
|
|
|
"measureBlackness",
|
|
|
|
"measureWidth",
|
|
|
|
"makeDesignspaceSnippet",
|
|
|
|
"addEmptyAvar",
|
|
|
|
"main",
|
|
|
|
]
|
|
|
|
|
2023-07-23 09:29:42 -06:00
|
|
|
log = logging.getLogger("fontTools.varLib.avarPlanner")
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-22 22:06:07 -06:00
|
|
|
WEIGHTS = [
|
|
|
|
50,
|
|
|
|
100,
|
|
|
|
150,
|
|
|
|
200,
|
|
|
|
250,
|
|
|
|
300,
|
|
|
|
350,
|
|
|
|
400,
|
|
|
|
450,
|
|
|
|
500,
|
|
|
|
550,
|
|
|
|
600,
|
|
|
|
650,
|
|
|
|
700,
|
|
|
|
750,
|
|
|
|
800,
|
|
|
|
850,
|
|
|
|
900,
|
|
|
|
950,
|
|
|
|
]
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
WIDTHS = [
|
|
|
|
50.0,
|
|
|
|
62.5,
|
|
|
|
75.0,
|
|
|
|
87.5,
|
|
|
|
100.0,
|
|
|
|
112.5,
|
|
|
|
125.0,
|
|
|
|
137.5,
|
|
|
|
150.0,
|
|
|
|
162.5,
|
|
|
|
175.0,
|
|
|
|
187.5,
|
|
|
|
200.0,
|
|
|
|
]
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
SAMPLES = 8
|
2023-07-23 10:23:39 -06:00
|
|
|
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
def measureBlackness(glyphset, glyphs=None):
|
2023-07-23 10:38:33 -06:00
|
|
|
if isinstance(glyphs, dict):
|
|
|
|
frequencies = glyphs
|
|
|
|
else:
|
|
|
|
frequencies = {g: 1 for g in glyphs}
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
wght_sum = wdth_sum = 0
|
2023-07-23 10:38:33 -06:00
|
|
|
for glyph_name in glyphs:
|
2023-07-22 22:04:18 -06:00
|
|
|
if frequencies is not None:
|
|
|
|
frequency = frequencies.get(glyph_name, 0)
|
|
|
|
if frequency == 0:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
frequency = 1
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
glyph = glyphset[glyph_name]
|
|
|
|
|
|
|
|
pen = AreaPen(glyphset=glyphset)
|
|
|
|
glyph.draw(pen)
|
|
|
|
|
2023-07-22 22:04:18 -06:00
|
|
|
wght_sum += abs(pen.value) * glyph.width * frequency
|
|
|
|
wdth_sum += glyph.width * frequency
|
2023-07-22 21:18:52 -06:00
|
|
|
|
|
|
|
return wght_sum / wdth_sum
|
|
|
|
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
def measureWidth(glyphset, glyphs=None):
|
|
|
|
if isinstance(glyphs, dict):
|
|
|
|
frequencies = glyphs
|
|
|
|
else:
|
|
|
|
frequencies = {g: 1 for g in glyphs}
|
|
|
|
|
|
|
|
wdth_sum = 0
|
|
|
|
freq_sum = 0
|
|
|
|
for glyph_name in glyphs:
|
|
|
|
if frequencies is not None:
|
|
|
|
frequency = frequencies.get(glyph_name, 0)
|
|
|
|
if frequency == 0:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
frequency = 1
|
|
|
|
|
|
|
|
glyph = glyphset[glyph_name]
|
|
|
|
|
|
|
|
pen = AreaPen(glyphset=glyphset)
|
|
|
|
glyph.draw(pen)
|
|
|
|
|
|
|
|
wdth_sum += glyph.width * frequency
|
|
|
|
freq_sum += frequency
|
|
|
|
|
|
|
|
return wdth_sum / freq_sum
|
|
|
|
|
|
|
|
|
|
|
|
def planAxis(
|
|
|
|
axisTag,
|
|
|
|
measureFunc,
|
2023-07-23 11:16:25 -06:00
|
|
|
glyphSetFunc,
|
|
|
|
minValue,
|
|
|
|
defaultValue,
|
|
|
|
maxValue,
|
2023-07-23 16:24:01 -06:00
|
|
|
values=None,
|
2023-07-23 11:16:25 -06:00
|
|
|
samples=None,
|
|
|
|
glyphs=None,
|
2023-07-23 13:48:06 -06:00
|
|
|
designUnits=None,
|
2023-07-23 12:09:02 -06:00
|
|
|
pins=None,
|
2023-07-22 22:06:07 -06:00
|
|
|
):
|
2023-07-23 10:27:19 -06:00
|
|
|
if samples is None:
|
|
|
|
samples = SAMPLES
|
2023-07-23 10:38:33 -06:00
|
|
|
if glyphs is None:
|
2023-07-23 11:16:25 -06:00
|
|
|
glyphs = glyphSetFunc({}).keys()
|
2023-07-23 12:09:02 -06:00
|
|
|
if pins is None:
|
|
|
|
pins = {}
|
|
|
|
else:
|
|
|
|
pins = pins.copy()
|
2023-07-23 10:27:19 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
log.info("Value min %g / default %g / max %g", minValue, defaultValue, maxValue)
|
2023-07-23 13:48:06 -06:00
|
|
|
triple = (minValue, defaultValue, maxValue)
|
|
|
|
|
|
|
|
if designUnits is not None:
|
2023-07-23 16:24:01 -06:00
|
|
|
log.info("Value design-units min %g / default %g / max %g", *designUnits)
|
2023-07-23 13:48:06 -06:00
|
|
|
else:
|
|
|
|
designUnits = triple
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 11:16:25 -06:00
|
|
|
# if "avar" in font:
|
2023-07-23 16:24:01 -06:00
|
|
|
# log.debug("Checking that font doesn't have axis mapping already.")
|
|
|
|
# existingMapping = font["avar"].segments[axisTag]
|
2023-07-23 11:16:25 -06:00
|
|
|
# if existingMapping and existingMapping != {-1: -1, 0: 0, +1: +1}:
|
2023-07-23 16:24:01 -06:00
|
|
|
# log.error("Font already has a `avar` value mapping. Remove it.")
|
2023-07-23 09:45:58 -06:00
|
|
|
|
2023-07-23 12:09:02 -06:00
|
|
|
if pins:
|
|
|
|
log.info("Pins %s", sorted(pins.items()))
|
2023-07-23 13:48:06 -06:00
|
|
|
pins.update(
|
|
|
|
{
|
|
|
|
minValue: designUnits[0],
|
|
|
|
defaultValue: designUnits[1],
|
|
|
|
maxValue: designUnits[2],
|
|
|
|
}
|
|
|
|
)
|
2023-07-23 12:09:02 -06:00
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
out = {}
|
|
|
|
outNormalized = {}
|
|
|
|
|
2023-07-23 11:16:25 -06:00
|
|
|
upem = 1 # font["head"].unitsPerEm
|
2023-07-23 14:55:24 -06:00
|
|
|
axisBlackness = {}
|
2023-07-23 16:24:01 -06:00
|
|
|
for value in sorted({minValue, defaultValue, maxValue} | set(pins.values())):
|
|
|
|
glyphset = glyphSetFunc(location={axisTag: value})
|
2023-07-23 13:48:06 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
designValue = piecewiseLinearMap(value, pins)
|
2023-07-23 13:48:06 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
axisBlackness[designValue] = measureFunc(glyphset, glyphs) / (upem * upem)
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
log.debug("Calculated average value:\n%s", pformat(axisBlackness))
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 12:09:02 -06:00
|
|
|
for (rangeMin, targetMin), (rangeMax, targetMax) in zip(
|
|
|
|
list(sorted(pins.items()))[:-1],
|
|
|
|
list(sorted(pins.items()))[1:],
|
|
|
|
):
|
2023-07-23 16:24:01 -06:00
|
|
|
targetValues = {w for w in values if rangeMin < w < rangeMax}
|
|
|
|
if not targetValues:
|
2023-07-22 21:18:52 -06:00
|
|
|
continue
|
|
|
|
|
2023-07-23 12:09:02 -06:00
|
|
|
normalizedMin = normalizeValue(rangeMin, triple)
|
|
|
|
normalizedMax = normalizeValue(rangeMax, triple)
|
2023-07-23 13:48:06 -06:00
|
|
|
normalizedTargetMin = normalizeValue(targetMin, designUnits)
|
|
|
|
normalizedTargetMax = normalizeValue(targetMax, designUnits)
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
log.info("Planning target values %s.", sorted(targetValues))
|
2023-07-23 10:27:19 -06:00
|
|
|
log.info("Sampling %u points in range %g,%g.", samples, rangeMin, rangeMax)
|
2023-07-23 16:24:01 -06:00
|
|
|
valueBlackness = axisBlackness.copy()
|
2023-07-23 10:27:19 -06:00
|
|
|
for sample in range(1, samples + 1):
|
2023-07-23 16:24:01 -06:00
|
|
|
value = rangeMin + (rangeMax - rangeMin) * sample / (samples + 1)
|
|
|
|
log.info("Sampling value %g.", value)
|
|
|
|
glyphset = glyphSetFunc(location={axisTag: value})
|
|
|
|
designValue = piecewiseLinearMap(value, pins)
|
|
|
|
valueBlackness[designValue] = measureFunc(glyphset, glyphs) / (upem * upem)
|
|
|
|
log.debug("Sampled average value:\n%s", pformat(valueBlackness))
|
|
|
|
|
|
|
|
blacknessValue = {}
|
|
|
|
for value in sorted(valueBlackness):
|
|
|
|
blacknessValue[valueBlackness[value]] = value
|
|
|
|
|
|
|
|
logMin = math.log(valueBlackness[targetMin])
|
|
|
|
logMax = math.log(valueBlackness[targetMax])
|
2023-07-23 12:09:02 -06:00
|
|
|
out[rangeMin] = targetMin
|
|
|
|
outNormalized[normalizedMin] = normalizedTargetMin
|
2023-07-23 16:24:01 -06:00
|
|
|
for value in sorted(targetValues):
|
|
|
|
t = (value - rangeMin) / (rangeMax - rangeMin)
|
2023-07-23 09:29:42 -06:00
|
|
|
targetBlackness = math.exp(logMin + t * (logMax - logMin))
|
2023-07-23 16:24:01 -06:00
|
|
|
targetValue = piecewiseLinearMap(targetBlackness, blacknessValue)
|
|
|
|
log.info("Planned mapping value %g to %g." % (value, targetValue))
|
|
|
|
out[value] = targetValue
|
2023-07-23 12:09:02 -06:00
|
|
|
outNormalized[
|
|
|
|
normalizedMin + t * (normalizedMax - normalizedMin)
|
2023-07-23 16:24:01 -06:00
|
|
|
] = normalizedTargetMin + (targetValue - targetMin) / (
|
2023-07-23 12:09:02 -06:00
|
|
|
targetMax - targetMin
|
|
|
|
) * (
|
|
|
|
normalizedTargetMax - normalizedTargetMin
|
|
|
|
)
|
|
|
|
out[rangeMax] = targetMax
|
|
|
|
outNormalized[normalizedMax] = normalizedTargetMax
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
log.info("Planned mapping for the `%s` axis:\n%s", axisTag, pformat(out))
|
|
|
|
log.info(
|
|
|
|
"Planned normalized mapping for the `%s` axis:\n%s",
|
|
|
|
axisTag,
|
|
|
|
pformat(outNormalized),
|
|
|
|
)
|
|
|
|
|
|
|
|
if all(abs(k - v) < 0.02 for k, v in outNormalized.items()):
|
|
|
|
log.info("Detected identity mapping for the `%s` axis. Dropping.", axisTag)
|
|
|
|
out = {}
|
|
|
|
outNormalized = {}
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
return out, outNormalized
|
|
|
|
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
def planWeightAxis(
|
|
|
|
glyphSetFunc,
|
|
|
|
minValue,
|
|
|
|
defaultValue,
|
|
|
|
maxValue,
|
|
|
|
weights=None,
|
|
|
|
samples=None,
|
|
|
|
glyphs=None,
|
|
|
|
designUnits=None,
|
|
|
|
pins=None,
|
|
|
|
):
|
|
|
|
if weights is None:
|
|
|
|
weights = WEIGHTS
|
|
|
|
|
|
|
|
return planAxis(
|
|
|
|
"wght",
|
|
|
|
measureBlackness,
|
|
|
|
glyphSetFunc,
|
|
|
|
minValue,
|
|
|
|
defaultValue,
|
|
|
|
maxValue,
|
|
|
|
values=weights,
|
|
|
|
samples=samples,
|
|
|
|
glyphs=glyphs,
|
|
|
|
designUnits=designUnits,
|
|
|
|
pins=pins,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def planWidthAxis(
|
|
|
|
glyphSetFunc,
|
|
|
|
minValue,
|
|
|
|
defaultValue,
|
|
|
|
maxValue,
|
|
|
|
widths=None,
|
|
|
|
samples=None,
|
|
|
|
glyphs=None,
|
|
|
|
designUnits=None,
|
|
|
|
pins=None,
|
|
|
|
):
|
|
|
|
if widths is None:
|
|
|
|
widths = WIDTHS
|
|
|
|
|
|
|
|
return planAxis(
|
|
|
|
"wdth",
|
|
|
|
measureWidth,
|
|
|
|
glyphSetFunc,
|
|
|
|
minValue,
|
|
|
|
defaultValue,
|
|
|
|
maxValue,
|
|
|
|
values=widths,
|
|
|
|
samples=samples,
|
|
|
|
glyphs=glyphs,
|
|
|
|
designUnits=designUnits,
|
|
|
|
pins=pins,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def makeDesignspaceSnippet(axisTag, axisName, axisLimit, mapping):
|
|
|
|
designspaceSnippet = (
|
|
|
|
' <axis tag="%s" name="%s" minimum="%g" default="%g" maximum="%g"'
|
|
|
|
% ((axisTag, axisName) + axisLimit)
|
|
|
|
)
|
|
|
|
if mapping:
|
|
|
|
designspaceSnippet += ">\n"
|
|
|
|
else:
|
|
|
|
designspaceSnippet += "/>"
|
|
|
|
|
|
|
|
for key, value in mapping.items():
|
|
|
|
designspaceSnippet += ' <map input="%g" output="%g"/>\n' % (key, value)
|
|
|
|
|
|
|
|
if mapping:
|
|
|
|
designspaceSnippet += " </axis>"
|
|
|
|
|
|
|
|
return designspaceSnippet
|
|
|
|
|
|
|
|
|
2023-07-23 10:19:35 -06:00
|
|
|
def addEmptyAvar(font):
|
|
|
|
font["avar"] = newTable("avar")
|
|
|
|
for axis in fvar.axes:
|
|
|
|
font["avar"].segments[axis.axisTag] = {}
|
|
|
|
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
def main(args=None):
|
2023-07-23 09:29:42 -06:00
|
|
|
from fontTools import configLogger
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
if args is None:
|
|
|
|
import sys
|
|
|
|
|
|
|
|
args = sys.argv[1:]
|
|
|
|
|
|
|
|
from fontTools.ttLib import TTFont
|
|
|
|
import argparse
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
"fonttools varLib.avarPlanner",
|
|
|
|
description="Plan `avar` table for variable font",
|
|
|
|
)
|
|
|
|
parser.add_argument("font", metavar="font.ttf", help="Font file.")
|
2023-07-23 11:36:01 -06:00
|
|
|
parser.add_argument(
|
|
|
|
"-o",
|
|
|
|
"--output-file",
|
|
|
|
type=str,
|
|
|
|
help="Output font file name.",
|
|
|
|
)
|
2023-07-23 11:16:25 -06:00
|
|
|
parser.add_argument(
|
2023-07-23 16:24:01 -06:00
|
|
|
"--weights", type=str, help="Space-separate list of weights to generate."
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--widths", type=str, help="Space-separate list of widths to generate."
|
2023-07-23 11:16:25 -06:00
|
|
|
)
|
2023-07-23 10:27:19 -06:00
|
|
|
parser.add_argument("-s", "--samples", type=int, help="Number of samples.")
|
2023-07-23 11:16:25 -06:00
|
|
|
parser.add_argument(
|
|
|
|
"-g",
|
|
|
|
"--glyphs",
|
|
|
|
type=str,
|
|
|
|
help="Space-separate list of glyphs to use for sampling.",
|
|
|
|
)
|
2023-07-23 13:48:06 -06:00
|
|
|
parser.add_argument(
|
2023-07-23 15:47:03 -06:00
|
|
|
"--weight-design-units",
|
2023-07-23 13:48:06 -06:00
|
|
|
type=str,
|
2023-07-23 15:47:03 -06:00
|
|
|
help="min:default:max in design units for the `wght` axis.",
|
2023-07-23 13:48:06 -06:00
|
|
|
)
|
2023-07-23 12:09:02 -06:00
|
|
|
parser.add_argument(
|
2023-07-23 15:47:03 -06:00
|
|
|
"--width-design-units",
|
2023-07-23 12:09:02 -06:00
|
|
|
type=str,
|
2023-07-23 15:47:03 -06:00
|
|
|
help="min:default:max in design units for the `wdth` axis.",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--weight-pins",
|
|
|
|
type=str,
|
|
|
|
help="Space-separate list of before:after pins. for the `wght` axis.",
|
2023-07-23 12:09:02 -06:00
|
|
|
)
|
2023-07-23 16:24:01 -06:00
|
|
|
parser.add_argument(
|
|
|
|
"--width-pins",
|
|
|
|
type=str,
|
|
|
|
help="Space-separate list of before:after pins. for the `wdth` axis.",
|
|
|
|
)
|
2023-07-23 09:41:29 -06:00
|
|
|
parser.add_argument(
|
|
|
|
"-p", "--plot", action="store_true", help="Plot the resulting mapping."
|
|
|
|
)
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-23 09:29:42 -06:00
|
|
|
logging_group = parser.add_mutually_exclusive_group(required=False)
|
|
|
|
logging_group.add_argument(
|
|
|
|
"-v", "--verbose", action="store_true", help="Run more verbosely."
|
|
|
|
)
|
|
|
|
logging_group.add_argument(
|
|
|
|
"-q", "--quiet", action="store_true", help="Turn verbosity off."
|
|
|
|
)
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
options = parser.parse_args(args)
|
|
|
|
|
2023-07-23 09:29:42 -06:00
|
|
|
configLogger(
|
|
|
|
level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
|
|
|
|
)
|
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
font = TTFont(options.font)
|
2023-07-23 10:04:28 -06:00
|
|
|
if not "fvar" in font:
|
|
|
|
log.error("Not a variable font.")
|
|
|
|
sys.exit(1)
|
2023-07-22 21:18:52 -06:00
|
|
|
fvar = font["fvar"]
|
2023-07-23 15:47:03 -06:00
|
|
|
wghtAxis = wdthAxis = None
|
2023-07-22 21:18:52 -06:00
|
|
|
for axis in fvar.axes:
|
|
|
|
if axis.axisTag == "wght":
|
|
|
|
wghtAxis = axis
|
2023-07-23 15:47:03 -06:00
|
|
|
elif axis.axisTag == "wdth":
|
|
|
|
wdthAxis = axis
|
2023-07-22 21:18:52 -06:00
|
|
|
|
2023-07-22 23:07:48 -06:00
|
|
|
if "avar" in font:
|
|
|
|
existingMapping = font["avar"].segments["wght"]
|
2023-07-23 10:04:28 -06:00
|
|
|
if wghtAxis:
|
|
|
|
font["avar"].segments["wght"] = {}
|
2023-07-22 23:07:48 -06:00
|
|
|
else:
|
|
|
|
existingMapping = None
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
if options.glyphs is not None:
|
|
|
|
glyphs = options.glyphs.split()
|
|
|
|
if ":" in options.glyphs:
|
|
|
|
glyphs = {}
|
|
|
|
for g in options.glyphs.split():
|
|
|
|
if ":" in g:
|
|
|
|
glyph, frequency = g.split(":")
|
|
|
|
glyphs[glyph] = float(frequency)
|
|
|
|
else:
|
|
|
|
glyphs[g] = 1.0
|
|
|
|
else:
|
|
|
|
glyphs = None
|
|
|
|
|
|
|
|
if wdthAxis:
|
|
|
|
log.info("Planning width axis.")
|
|
|
|
|
|
|
|
if options.widths is not None:
|
|
|
|
widths = [float(w) for w in options.widths.split()]
|
|
|
|
else:
|
|
|
|
widths = None
|
|
|
|
|
|
|
|
if options.width_design_units is not None:
|
|
|
|
designUnits = [float(d) for d in options.width_design_units.split(":")]
|
|
|
|
else:
|
|
|
|
designUnits = None
|
|
|
|
|
|
|
|
if options.width_pins is not None:
|
|
|
|
pins = {}
|
|
|
|
for pin in options.width_pins.split():
|
|
|
|
before, after = pin.split(":")
|
|
|
|
pins[float(before)] = float(after)
|
|
|
|
else:
|
|
|
|
pins = None
|
|
|
|
|
|
|
|
widthMapping, widthMappingNormalized = planWidthAxis(
|
|
|
|
font.getGlyphSet,
|
|
|
|
wdthAxis.minValue,
|
|
|
|
wdthAxis.defaultValue,
|
|
|
|
wdthAxis.maxValue,
|
|
|
|
widths=widths,
|
|
|
|
samples=options.samples,
|
|
|
|
glyphs=glyphs,
|
|
|
|
designUnits=designUnits,
|
|
|
|
pins=pins,
|
|
|
|
)
|
|
|
|
|
|
|
|
if options.plot:
|
|
|
|
from matplotlib import pyplot
|
|
|
|
|
|
|
|
pyplot.plot(
|
|
|
|
sorted(widthMappingNormalized),
|
|
|
|
[widthMappingNormalized[k] for k in sorted(widthMappingNormalized)],
|
|
|
|
)
|
|
|
|
pyplot.show()
|
|
|
|
|
|
|
|
if existingMapping is not None:
|
|
|
|
log.info("Existing width mapping:\n%s", pformat(existingMapping))
|
|
|
|
|
2023-07-23 10:04:28 -06:00
|
|
|
if wghtAxis:
|
2023-07-23 16:24:01 -06:00
|
|
|
log.info("Planning weight axis.")
|
|
|
|
|
2023-07-23 10:29:59 -06:00
|
|
|
if options.weights is not None:
|
2023-07-23 11:12:26 -06:00
|
|
|
weights = [float(w) for w in options.weights.split()]
|
2023-07-23 10:29:59 -06:00
|
|
|
else:
|
2023-07-23 16:24:01 -06:00
|
|
|
weights = None
|
2023-07-23 10:38:33 -06:00
|
|
|
|
2023-07-23 15:47:03 -06:00
|
|
|
if options.weight_design_units is not None:
|
|
|
|
designUnits = [float(d) for d in options.weight_design_units.split(":")]
|
2023-07-23 13:48:06 -06:00
|
|
|
else:
|
|
|
|
designUnits = None
|
|
|
|
|
2023-07-23 15:47:03 -06:00
|
|
|
if options.weight_pins is not None:
|
2023-07-23 12:09:02 -06:00
|
|
|
pins = {}
|
2023-07-23 15:47:03 -06:00
|
|
|
for pin in options.weight_pins.split():
|
2023-07-23 12:09:02 -06:00
|
|
|
before, after = pin.split(":")
|
|
|
|
pins[float(before)] = float(after)
|
|
|
|
else:
|
|
|
|
pins = None
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
weightMapping, weightMappingNormalized = planWeightAxis(
|
2023-07-23 11:16:25 -06:00
|
|
|
font.getGlyphSet,
|
2023-07-23 10:27:19 -06:00
|
|
|
wghtAxis.minValue,
|
|
|
|
wghtAxis.defaultValue,
|
|
|
|
wghtAxis.maxValue,
|
2023-07-23 10:29:59 -06:00
|
|
|
weights=weights,
|
2023-07-23 10:27:19 -06:00
|
|
|
samples=options.samples,
|
2023-07-23 10:38:33 -06:00
|
|
|
glyphs=glyphs,
|
2023-07-23 13:48:06 -06:00
|
|
|
designUnits=designUnits,
|
2023-07-23 12:09:02 -06:00
|
|
|
pins=pins,
|
2023-07-23 10:04:28 -06:00
|
|
|
)
|
2023-07-22 23:07:48 -06:00
|
|
|
|
2023-07-23 10:04:28 -06:00
|
|
|
if options.plot:
|
|
|
|
from matplotlib import pyplot
|
2023-07-23 09:41:29 -06:00
|
|
|
|
2023-07-23 10:04:28 -06:00
|
|
|
pyplot.plot(
|
2023-07-23 16:24:01 -06:00
|
|
|
sorted(weightMappingNormalized),
|
|
|
|
[weightMappingNormalized[k] for k in sorted(weightMappingNormalized)],
|
2023-07-23 10:04:28 -06:00
|
|
|
)
|
|
|
|
pyplot.show()
|
2023-07-23 09:41:29 -06:00
|
|
|
|
2023-07-23 10:04:28 -06:00
|
|
|
if existingMapping is not None:
|
|
|
|
log.info("Existing weight mapping:\n%s", pformat(existingMapping))
|
2023-07-22 23:07:48 -06:00
|
|
|
|
|
|
|
if "avar" not in font:
|
2023-07-23 10:19:35 -06:00
|
|
|
addEmptyAvar(font)
|
2023-07-23 10:06:51 -06:00
|
|
|
|
2023-07-22 21:18:52 -06:00
|
|
|
avar = font["avar"]
|
|
|
|
|
2023-07-23 16:24:01 -06:00
|
|
|
if wdthAxis:
|
|
|
|
avar.segments["wdth"] = widthMappingNormalized
|
|
|
|
designspaceSnippet = makeDesignspaceSnippet(
|
|
|
|
"wdth",
|
|
|
|
"Width",
|
|
|
|
(wdthAxis.minValue, wdthAxis.defaultValue, wdthAxis.maxValue),
|
|
|
|
widthMapping,
|
|
|
|
)
|
|
|
|
log.info("Width axis designspace snippet:")
|
|
|
|
print(designspaceSnippet)
|
|
|
|
|
|
|
|
if wghtAxis:
|
|
|
|
avar.segments["wght"] = weightMappingNormalized
|
|
|
|
designspaceSnippet = makeDesignspaceSnippet(
|
|
|
|
"wght",
|
|
|
|
"Weight",
|
|
|
|
(wghtAxis.minValue, wghtAxis.defaultValue, wghtAxis.maxValue),
|
|
|
|
weightMapping,
|
2023-07-23 13:08:40 -06:00
|
|
|
)
|
|
|
|
log.info("Weight axis designspace snippet:")
|
|
|
|
print(designspaceSnippet)
|
2023-07-23 09:50:32 -06:00
|
|
|
|
2023-07-23 11:31:14 -06:00
|
|
|
if options.output_file is None:
|
|
|
|
outfile = makeOutputFileName(options.font, overWrite=True, suffix=".avar")
|
|
|
|
else:
|
|
|
|
outfile = options.output_file
|
2023-07-23 11:36:01 -06:00
|
|
|
if outfile:
|
|
|
|
log.info("Saving %s", outfile)
|
|
|
|
font.save(outfile)
|
2023-07-22 21:18:52 -06:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
|
|
|
|
|
|
|
sys.exit(main())
|