In the variable font tutorial there is a beta note stating that instances should ideally line up with their associated weight class values, but this does not happen with the current implementation without importing your instances as masters.
I am wondering if support for the ‘avar’ table will be implemented in the future to solve this problem, effectively warping/mapping the design space so that the regular weight can actually sit at 400 on a weight axis, bold at 700, etc.?
I have had success with manually creating an ‘avar’ table and inserting it into my font once converted to .ttx to get weights to line up with their weight class values, but I am curious if this table will be supported when exporting from Glyphs App in the future.
Sure thing, I started with this as a reference for the xml structure of the table:
My workflow for this currently:
Set the weightClass values as custom parameters on the extreme masters so that the font exports with the correct scale min and max
Export the variable font and convert to ttx
Paste the sample ‘avar’ table before the ‘fvar’ table
I ran a script in the macro panel to calculate the values based on my current project (but I’m trying to figure out one to generate the table for any project with a single axis):
font = Glyphs.font
instanceIndex = 0.0
for instance in font.instances:
if instance.active == True:
# scaled fvar weight based on a Min of 5 and a max of 254, difference = 249
scaledWeight = ((instance.weightValue - 5.0) / 249) * 900
# toValue is the normalized value on the original scale (based on scaled weight)
toValue = (scaledWeight / 900)
# css weight starting at 0 for Hairline and Thin at 100
cssWeight = (instanceIndex * 100)
# fromValue is the normalized value on the post-mapping scale (100, 200, 300, etc.)
fromValue = (cssWeight / 900)
# print values
print "Original weight = %f" % instance.weightValue
print "Scaled Weight = %f" % scaledWeight
print "CSS weight = %f" % cssWeight
print "avar to value = %f" % toValue
print "avar from value = %f" % fromValue
print "\n"
instanceIndex = instanceIndex + 1
I then copied and pasted the to and from values to my ‘avar’ table in the ttx
Lastly I changed the instance values in the ‘fvar’ table to their intended weightClass. These numbers correspond to the post-avar-mapped scale and their original values at this point are useless
This is the way my from and to values are calculated:
If the instance is lighter than or equal to your default
to value is ((instanceWeight - axisMin) / (default - axisMin)) -1
from value is ((cssWeight - axisMin) / (defaultCss - axisMin)) -1
If the instance is heavier than or equal to the default:
to value is ((instanceWeight - default) / (axisMax - default))
from value is ((cssWeight - defaultCss) / (axisMax - defaultCss))
It’s worth noting that the normalized scale goes from -1 to 0 to 1 but these two segments are not necessarily equal. 0 is the default on the scale, so if your lightest master is the default then your map should be:
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
# intermediate instance mappings go here
<mapping from="1.0" to="1.0"/>
You’re welcome! I ended up rounding mine to the fourth decimal place because the is what I have seen elsewhere; the spec says these values are stored as “F2DOT14” but I am not entirely sure what that means
The examples there show decimals with six digits after the decimal point. I am guessing this is the necessary number of digits to cover the full range of fractions from 0/16384 to 16383/16384 whereas four or five digits may result in multiple decimal values equating to the same fraction?
Asking because when inputing decimal values into ttx I would want them to convert properly and accurately
Apologies, my avar to and from equations I had detailed were incorrect. They were correct for a specific project with the axis minimum at 0 but upon revisiting this for another project I realized the calculations were not applicable at large.
I’ve edited my previous post so that they are now correct:
This is the way my from and to values are calculated:
If the instance is lighter than or equal to your default
to value is ((instanceWeight - axisMin) / (default - axisMin)) -1
from value is ((cssWeight - axisMin) / (defaultCss - axisMin)) -1
If the instance is heavier than or equal to the default:
to value is ((instanceWeight - default) / (axisMax - default))
from value is ((cssWeight - defaultCss) / (axisMax - defaultCss))
Additionally I created a macro that is generally applicable to other weight-axis-only projects based on the following assumptions:
Font has a custom parameter for “Variation Font Origin”
Masters are ordered thinnest to boldest
The extreme masters have the custom parameter “Axes Location”
Instances are ordered thinnest to boldest
Instances are named conventionally
font = Glyphs.font
cssMin = float(font.masters[0].customParameters['Axis Location'][0]['Location'])
cssMax = float(font.masters[-1].customParameters['Axis Location'][0]['Location'])
cssDefault = font.masters[font.customParameters['Variation Font Origin']].customParameters['Axis Location'][0]['Location']
weightMin = font.instances[0].weightValue
weightMax = font.instances[-1].weightValue
weightDefault = font.masters[font.customParameters['Variation Font Origin']].weightValue
# scales weight based on min and max declared in master custom parameters
def convertWeight(inputWeight):
outputWeight = (((inputWeight - weightMin) / (weightMax-weightMin)) * (cssMax - cssMin)) + cssMin
return outputWeight
for instance in font.instances:
if instance.active == True:
scaledWeight = convertWeight(instance.weightValue)
# css weight based on canonical naming
if instance.name == "Thin":
cssWeight = 100
elif instance.name == "ExtraLight" or instance.name == "UltraLight":
cssWeight = 200
elif instance.name == "Light":
cssWeight = 300
elif instance.name == "Regular":
cssWeight = 400
elif instance.name == "Medium":
cssWeight = 500
elif instance.name == "SemiBold":
cssWeight = 600
elif instance.name == "Bold":
cssWeight = 700
elif instance.name == "ExtraBold" or instance.name == "UltraBold":
cssWeight = 800
elif instance.name == "Black" or instance.name == "Heavy":
cssWeight = 900
elif instance.name == "Hairline":
cssWeight = 0
else:
cssWeight = 1000
# toValue is the normalized value on the original scale (based on scaled weight)
# fromValue is the normalized value on the post-mapping scale (100, 200, 300, etc.)
if instance.weightValue <= weightDefault and weightMin != weightDefault:
toValue = ((scaledWeight - cssMin) / (convertWeight(weightDefault) - cssMin)) - 1
fromValue = ((cssWeight - cssMin) / (cssDefault - cssMin)) - 1
elif instance.weightValue >= weightDefault:
toValue = (scaledWeight - convertWeight(weightDefault)) / (cssMax - convertWeight(weightDefault))
fromValue = (cssWeight - cssDefault) / (cssMax - cssDefault)
# print values
print instance.name
print "Original weight = %f" % instance.weightValue
print "Scaled Weight = %f" % scaledWeight
print "CSS weight = %f" % cssWeight
print "avar to value = %f" % toValue
print "avar from value = %f" % fromValue
print "\n"
Hi, I have written two scripts (with my very limited knowledge) which tackle this. MakeWebVariable.py creates the AVAR table and converts all values (including the values in intermediate layers) into USWeightClass values. WriteAVAR.py is the basic version of that and only writes an AVAR table with the normal (stem) weight values already set in the file.
Let me know if this is what you’re looking for. Please also let me know if something is horribly wrong with them