Merge pull request #1652 from anthrotype/varlib-set-default-OS2-classes
varLib: add set_default_weight_width_slant
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 891e92c..66b193b 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -857,6 +857,45 @@
)
+# https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass
+WDTH_VALUE_TO_OS2_WIDTH_CLASS = {
+ 50: 1,
+ 62.5: 2,
+ 75: 3,
+ 87.5: 4,
+ 100: 5,
+ 112.5: 6,
+ 125: 7,
+ 150: 8,
+ 200: 9,
+}
+
+
+def set_default_weight_width_slant(font, location):
+ if "OS/2" in font:
+ if "wght" in location:
+ weight_class = otRound(max(1, min(location["wght"], 1000)))
+ if font["OS/2"].usWeightClass != weight_class:
+ log.info("Setting OS/2.usWidthClass = %s", weight_class)
+ font["OS/2"].usWeightClass = weight_class
+
+ if "wdth" in location:
+ # map 'wdth' axis (50..200) to OS/2.usWidthClass (1..9), rounding to closest
+ widthValue = min(max(location["wdth"], 50), 200)
+ widthClass = otRound(
+ models.piecewiseLinearMap(widthValue, WDTH_VALUE_TO_OS2_WIDTH_CLASS)
+ )
+ if font["OS/2"].usWidthClass != widthClass:
+ log.info("Setting OS/2.usWidthClass = %s", widthClass)
+ font["OS/2"].usWidthClass = widthClass
+
+ if "slnt" in location and "post" in font:
+ italicAngle = max(-90, min(location["slnt"], 90))
+ if font["post"].italicAngle != italicAngle:
+ log.info("Setting post.italicAngle = %s", italicAngle)
+ font["post"].italicAngle = italicAngle
+
+
def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
"""
Build variation font from a designspace file.
@@ -930,6 +969,10 @@
post.extraNames = []
post.mapping = {}
+ set_default_weight_width_slant(
+ vf, location={axis.axisTag: axis.defaultValue for axis in vf["fvar"].axes}
+ )
+
for tag in exclude:
if tag in vf:
del vf[tag]
diff --git a/Tests/varLib/data/test_results/BuildMain.ttx b/Tests/varLib/data/test_results/BuildMain.ttx
index 7150a57..7e5d956 100644
--- a/Tests/varLib/data/test_results/BuildMain.ttx
+++ b/Tests/varLib/data/test_results/BuildMain.ttx
@@ -55,7 +55,7 @@
will be recalculated by the compiler -->
<version value="4"/>
<xAvgCharWidth value="506"/>
- <usWeightClass value="400"/>
+ <usWeightClass value="368"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="650"/>
diff --git a/Tests/varLib/data/test_results/SparseMasters.ttx b/Tests/varLib/data/test_results/SparseMasters.ttx
index 06e58c9..c2aa335 100644
--- a/Tests/varLib/data/test_results/SparseMasters.ttx
+++ b/Tests/varLib/data/test_results/SparseMasters.ttx
@@ -55,7 +55,7 @@
will be recalculated by the compiler -->
<version value="4"/>
<xAvgCharWidth value="580"/>
- <usWeightClass value="400"/>
+ <usWeightClass value="350"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="650"/>
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index e29befb..ec05d56 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -4,6 +4,7 @@
from fontTools.varLib import build
from fontTools.varLib.mutator import instantiateVariableFont
from fontTools.varLib import main as varLib_main, load_masters
+from fontTools.varLib import set_default_weight_width_slant
from fontTools.designspaceLib import (
DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor,
)
@@ -661,5 +662,81 @@
return extracted_kerning
+@pytest.fixture
+def ttFont():
+ f = TTFont()
+ f["OS/2"] = newTable("OS/2")
+ f["OS/2"].usWeightClass = 400
+ f["OS/2"].usWidthClass = 100
+ f["post"] = newTable("post")
+ f["post"].italicAngle = 0
+ return f
+
+
+class SetDefaultWeightWidthSlantTest(object):
+ @pytest.mark.parametrize(
+ "location, expected",
+ [
+ ({"wght": 0}, 1),
+ ({"wght": 1}, 1),
+ ({"wght": 100}, 100),
+ ({"wght": 1000}, 1000),
+ ({"wght": 1001}, 1000),
+ ],
+ )
+ def test_wght(self, ttFont, location, expected):
+ set_default_weight_width_slant(ttFont, location)
+
+ assert ttFont["OS/2"].usWeightClass == expected
+
+ @pytest.mark.parametrize(
+ "location, expected",
+ [
+ ({"wdth": 0}, 1),
+ ({"wdth": 56}, 1),
+ ({"wdth": 57}, 2),
+ ({"wdth": 62.5}, 2),
+ ({"wdth": 75}, 3),
+ ({"wdth": 87.5}, 4),
+ ({"wdth": 100}, 5),
+ ({"wdth": 112.5}, 6),
+ ({"wdth": 125}, 7),
+ ({"wdth": 150}, 8),
+ ({"wdth": 200}, 9),
+ ({"wdth": 201}, 9),
+ ({"wdth": 1000}, 9),
+ ],
+ )
+ def test_wdth(self, ttFont, location, expected):
+ set_default_weight_width_slant(ttFont, location)
+
+ assert ttFont["OS/2"].usWidthClass == expected
+
+ @pytest.mark.parametrize(
+ "location, expected",
+ [
+ ({"slnt": -91}, -90),
+ ({"slnt": -90}, -90),
+ ({"slnt": 0}, 0),
+ ({"slnt": 11.5}, 11.5),
+ ({"slnt": 90}, 90),
+ ({"slnt": 91}, 90),
+ ],
+ )
+ def test_slnt(self, ttFont, location, expected):
+ set_default_weight_width_slant(ttFont, location)
+
+ assert ttFont["post"].italicAngle == expected
+
+ def test_all(self, ttFont):
+ set_default_weight_width_slant(
+ ttFont, {"wght": 500, "wdth": 150, "slnt": -12.0}
+ )
+
+ assert ttFont["OS/2"].usWeightClass == 500
+ assert ttFont["OS/2"].usWidthClass == 8
+ assert ttFont["post"].italicAngle == -12.0
+
+
if __name__ == "__main__":
sys.exit(unittest.main())