Merge remote-tracking branch 'origin/master' into partial-instancer
This commit is contained in:
commit
1726a4a1fc
@ -5,6 +5,6 @@ from fontTools.misc.loggingTools import configLogger
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
version = __version__ = "3.43.1.dev0"
|
version = __version__ = "3.43.2.dev0"
|
||||||
|
|
||||||
__all__ = ["version", "log", "configLogger"]
|
__all__ = ["version", "log", "configLogger"]
|
||||||
|
@ -174,7 +174,7 @@ class WOFF2Writer(SFNTWriter):
|
|||||||
self.file = file
|
self.file = file
|
||||||
self.numTables = numTables
|
self.numTables = numTables
|
||||||
self.sfntVersion = Tag(sfntVersion)
|
self.sfntVersion = Tag(sfntVersion)
|
||||||
self.flavorData = flavorData or WOFF2FlavorData()
|
self.flavorData = WOFF2FlavorData(data=flavorData)
|
||||||
|
|
||||||
self.directoryFormat = woff2DirectoryFormat
|
self.directoryFormat = woff2DirectoryFormat
|
||||||
self.directorySize = woff2DirectorySize
|
self.directorySize = woff2DirectorySize
|
||||||
@ -1128,24 +1128,37 @@ class WOFF2FlavorData(WOFFFlavorData):
|
|||||||
|
|
||||||
Flavor = 'woff2'
|
Flavor = 'woff2'
|
||||||
|
|
||||||
def __init__(self, reader=None, transformedTables=None):
|
def __init__(self, reader=None, data=None, transformedTables=None):
|
||||||
"""Data class that holds the WOFF2 header major/minor version, any
|
"""Data class that holds the WOFF2 header major/minor version, any
|
||||||
metadata or private data (as bytes strings), and the set of
|
metadata or private data (as bytes strings), and the set of
|
||||||
table tags that have transformations applied (if reader is not None),
|
table tags that have transformations applied (if reader is not None),
|
||||||
or will have once the WOFF2 font is compiled.
|
or will have once the WOFF2 font is compiled.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader: an SFNTReader (or subclass) object to read flavor data from.
|
||||||
|
data: another WOFFFlavorData object to initialise data from.
|
||||||
|
transformedTables: set of strings containing table tags to be transformed.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError if the brotli module is not installed.
|
||||||
|
|
||||||
|
NOTE: The 'reader' argument, on the one hand, and the 'data' and
|
||||||
|
'transformedTables' arguments, on the other hand, are mutually exclusive.
|
||||||
"""
|
"""
|
||||||
if not haveBrotli:
|
if not haveBrotli:
|
||||||
raise ImportError("No module named brotli")
|
raise ImportError("No module named brotli")
|
||||||
|
|
||||||
if reader is not None and transformedTables is not None:
|
if reader is not None:
|
||||||
|
if data is not None:
|
||||||
|
raise TypeError(
|
||||||
|
"'reader' and 'data' arguments are mutually exclusive"
|
||||||
|
)
|
||||||
|
if transformedTables is not None:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"'reader' and 'transformedTables' arguments are mutually exclusive"
|
"'reader' and 'transformedTables' arguments are mutually exclusive"
|
||||||
)
|
)
|
||||||
|
|
||||||
if transformedTables is None:
|
if transformedTables is not None and (
|
||||||
transformedTables = woff2TransformedTableTags
|
|
||||||
else:
|
|
||||||
if (
|
|
||||||
"glyf" in transformedTables and "loca" not in transformedTables
|
"glyf" in transformedTables and "loca" not in transformedTables
|
||||||
or "loca" in transformedTables and "glyf" not in transformedTables
|
or "loca" in transformedTables and "glyf" not in transformedTables
|
||||||
):
|
):
|
||||||
@ -1164,19 +1177,29 @@ class WOFF2FlavorData(WOFFFlavorData):
|
|||||||
reader.file.seek(reader.metaOffset)
|
reader.file.seek(reader.metaOffset)
|
||||||
rawData = reader.file.read(reader.metaLength)
|
rawData = reader.file.read(reader.metaLength)
|
||||||
assert len(rawData) == reader.metaLength
|
assert len(rawData) == reader.metaLength
|
||||||
data = brotli.decompress(rawData)
|
metaData = brotli.decompress(rawData)
|
||||||
assert len(data) == reader.metaOrigLength
|
assert len(metaData) == reader.metaOrigLength
|
||||||
self.metaData = data
|
self.metaData = metaData
|
||||||
if reader.privLength:
|
if reader.privLength:
|
||||||
reader.file.seek(reader.privOffset)
|
reader.file.seek(reader.privOffset)
|
||||||
data = reader.file.read(reader.privLength)
|
privData = reader.file.read(reader.privLength)
|
||||||
assert len(data) == reader.privLength
|
assert len(privData) == reader.privLength
|
||||||
self.privData = data
|
self.privData = privData
|
||||||
transformedTables = [
|
transformedTables = [
|
||||||
tag
|
tag
|
||||||
for tag, entry in reader.tables.items()
|
for tag, entry in reader.tables.items()
|
||||||
if entry.transformed
|
if entry.transformed
|
||||||
]
|
]
|
||||||
|
elif data:
|
||||||
|
self.majorVersion = data.majorVersion
|
||||||
|
self.majorVersion = data.minorVersion
|
||||||
|
self.metaData = data.metaData
|
||||||
|
self.privData = data.privData
|
||||||
|
if transformedTables is None and hasattr(data, "transformedTables"):
|
||||||
|
transformedTables = data.transformedTables
|
||||||
|
|
||||||
|
if transformedTables is None:
|
||||||
|
transformedTables = woff2TransformedTableTags
|
||||||
|
|
||||||
self.transformedTables = set(transformedTables)
|
self.transformedTables = set(transformedTables)
|
||||||
|
|
||||||
@ -1354,7 +1377,9 @@ def compress(input_file, output_file, transform_tables=None):
|
|||||||
font.flavor = "woff2"
|
font.flavor = "woff2"
|
||||||
|
|
||||||
if transform_tables is not None:
|
if transform_tables is not None:
|
||||||
font.flavorData = WOFF2FlavorData(transformedTables=transform_tables)
|
font.flavorData = WOFF2FlavorData(
|
||||||
|
data=font.flavorData, transformedTables=transform_tables
|
||||||
|
)
|
||||||
|
|
||||||
font.save(output_file, reorderTables=False)
|
font.save(output_file, reorderTables=False)
|
||||||
|
|
||||||
|
@ -767,6 +767,45 @@ def load_designspace(designspace):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 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):
|
def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
|
||||||
"""
|
"""
|
||||||
Build variation font from a designspace file.
|
Build variation font from a designspace file.
|
||||||
@ -840,6 +879,10 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
|
|||||||
post.extraNames = []
|
post.extraNames = []
|
||||||
post.mapping = {}
|
post.mapping = {}
|
||||||
|
|
||||||
|
set_default_weight_width_slant(
|
||||||
|
vf, location={axis.axisTag: axis.defaultValue for axis in vf["fvar"].axes}
|
||||||
|
)
|
||||||
|
|
||||||
for tag in exclude:
|
for tag in exclude:
|
||||||
if tag in vf:
|
if tag in vf:
|
||||||
del vf[tag]
|
del vf[tag]
|
||||||
|
6
NEWS.rst
6
NEWS.rst
@ -1,3 +1,9 @@
|
|||||||
|
3.43.1 (released 2019-06-19)
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
- [subset] Fixed regression when passing ``--flavor=woff2`` option with an input font
|
||||||
|
that was already compressed as WOFF 1.0 (#1650).
|
||||||
|
|
||||||
3.43.0 (released 2019-06-18)
|
3.43.0 (released 2019-06-18)
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
BIN
Tests/subset/data/Lobster.subset.otf
Normal file
BIN
Tests/subset/data/Lobster.subset.otf
Normal file
Binary file not shown.
@ -678,6 +678,48 @@ class SubsetTest(unittest.TestCase):
|
|||||||
subsetfont = TTFont(subsetpath)
|
subsetfont = TTFont(subsetpath)
|
||||||
self.expect_ttx(subsetfont, self.getpath("expect_HVVAR_retain_gids.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
|
self.expect_ttx(subsetfont, self.getpath("expect_HVVAR_retain_gids.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
|
||||||
|
|
||||||
|
def test_subset_flavor(self):
|
||||||
|
_, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
|
||||||
|
font = TTFont(fontpath)
|
||||||
|
|
||||||
|
woff_path = self.temp_path(".woff")
|
||||||
|
subset.main(
|
||||||
|
[
|
||||||
|
fontpath,
|
||||||
|
"*",
|
||||||
|
"--flavor=woff",
|
||||||
|
"--output-file=%s" % woff_path,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
woff = TTFont(woff_path)
|
||||||
|
|
||||||
|
self.assertEqual(woff.flavor, "woff")
|
||||||
|
|
||||||
|
woff2_path = self.temp_path(".woff2")
|
||||||
|
subset.main(
|
||||||
|
[
|
||||||
|
woff_path,
|
||||||
|
"*",
|
||||||
|
"--flavor=woff2",
|
||||||
|
"--output-file=%s" % woff2_path,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
woff2 = TTFont(woff2_path)
|
||||||
|
|
||||||
|
self.assertEqual(woff2.flavor, "woff2")
|
||||||
|
|
||||||
|
ttf_path = self.temp_path(".ttf")
|
||||||
|
subset.main(
|
||||||
|
[
|
||||||
|
woff2_path,
|
||||||
|
"*",
|
||||||
|
"--output-file=%s" % ttf_path,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
ttf = TTFont(ttf_path)
|
||||||
|
|
||||||
|
self.assertEqual(ttf.flavor, None)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(unittest.main())
|
sys.exit(unittest.main())
|
||||||
|
@ -386,15 +386,18 @@ class WOFF2FlavorDataTest(unittest.TestCase):
|
|||||||
self.assertEqual(flavorData.minorVersion, 1)
|
self.assertEqual(flavorData.minorVersion, 1)
|
||||||
|
|
||||||
def test_mutually_exclusive_args(self):
|
def test_mutually_exclusive_args(self):
|
||||||
|
msg = "arguments are mutually exclusive"
|
||||||
reader = DummyReader(self.file)
|
reader = DummyReader(self.file)
|
||||||
with self.assertRaisesRegex(TypeError, "arguments are mutually exclusive"):
|
with self.assertRaisesRegex(TypeError, msg):
|
||||||
WOFF2FlavorData(reader, transformedTables={"hmtx"})
|
WOFF2FlavorData(reader, transformedTables={"hmtx"})
|
||||||
|
with self.assertRaisesRegex(TypeError, msg):
|
||||||
|
WOFF2FlavorData(reader, data=WOFF2FlavorData())
|
||||||
|
|
||||||
def test_transformTables_default(self):
|
def test_transformedTables_default(self):
|
||||||
flavorData = WOFF2FlavorData()
|
flavorData = WOFF2FlavorData()
|
||||||
self.assertEqual(flavorData.transformedTables, set(woff2TransformedTableTags))
|
self.assertEqual(flavorData.transformedTables, set(woff2TransformedTableTags))
|
||||||
|
|
||||||
def test_transformTables_invalid(self):
|
def test_transformedTables_invalid(self):
|
||||||
msg = r"'glyf' and 'loca' must be transformed \(or not\) together"
|
msg = r"'glyf' and 'loca' must be transformed \(or not\) together"
|
||||||
|
|
||||||
with self.assertRaisesRegex(ValueError, msg):
|
with self.assertRaisesRegex(ValueError, msg):
|
||||||
@ -1258,6 +1261,24 @@ class MainTest(object):
|
|||||||
|
|
||||||
assert (tmpdir / "TestOTF-Regular.woff2").check(file=True)
|
assert (tmpdir / "TestOTF-Regular.woff2").check(file=True)
|
||||||
|
|
||||||
|
def test_recompress_woff2_keeps_flavorData(self, tmpdir):
|
||||||
|
woff2_font = ttLib.TTFont(BytesIO(TT_WOFF2.getvalue()))
|
||||||
|
woff2_font.flavorData.privData = b"FOOBAR"
|
||||||
|
woff2_file = tmpdir / "TestTTF-Regular.woff2"
|
||||||
|
woff2_font.save(str(woff2_file))
|
||||||
|
|
||||||
|
assert woff2_font.flavorData.transformedTables == {"glyf", "loca"}
|
||||||
|
|
||||||
|
woff2.main(["compress", "--hmtx-transform", str(woff2_file)])
|
||||||
|
|
||||||
|
output_file = tmpdir / "TestTTF-Regular#1.woff2"
|
||||||
|
assert output_file.check(file=True)
|
||||||
|
|
||||||
|
new_woff2_font = ttLib.TTFont(str(output_file))
|
||||||
|
|
||||||
|
assert new_woff2_font.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
|
||||||
|
assert new_woff2_font.flavorData.privData == b"FOOBAR"
|
||||||
|
|
||||||
def test_decompress_ttf(self, tmpdir):
|
def test_decompress_ttf(self, tmpdir):
|
||||||
input_file = tmpdir / "TestTTF-Regular.woff2"
|
input_file = tmpdir / "TestTTF-Regular.woff2"
|
||||||
input_file.write_binary(TT_WOFF2.getvalue())
|
input_file.write_binary(TT_WOFF2.getvalue())
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
will be recalculated by the compiler -->
|
will be recalculated by the compiler -->
|
||||||
<version value="4"/>
|
<version value="4"/>
|
||||||
<xAvgCharWidth value="506"/>
|
<xAvgCharWidth value="506"/>
|
||||||
<usWeightClass value="400"/>
|
<usWeightClass value="368"/>
|
||||||
<usWidthClass value="5"/>
|
<usWidthClass value="5"/>
|
||||||
<fsType value="00000000 00000100"/>
|
<fsType value="00000000 00000100"/>
|
||||||
<ySubscriptXSize value="650"/>
|
<ySubscriptXSize value="650"/>
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
will be recalculated by the compiler -->
|
will be recalculated by the compiler -->
|
||||||
<version value="4"/>
|
<version value="4"/>
|
||||||
<xAvgCharWidth value="580"/>
|
<xAvgCharWidth value="580"/>
|
||||||
<usWeightClass value="400"/>
|
<usWeightClass value="350"/>
|
||||||
<usWidthClass value="5"/>
|
<usWidthClass value="5"/>
|
||||||
<fsType value="00000000 00000100"/>
|
<fsType value="00000000 00000100"/>
|
||||||
<ySubscriptXSize value="650"/>
|
<ySubscriptXSize value="650"/>
|
||||||
|
@ -4,6 +4,7 @@ from fontTools.ttLib import TTFont, newTable
|
|||||||
from fontTools.varLib import build
|
from fontTools.varLib import build
|
||||||
from fontTools.varLib.mutator import instantiateVariableFont
|
from fontTools.varLib.mutator import instantiateVariableFont
|
||||||
from fontTools.varLib import main as varLib_main, load_masters
|
from fontTools.varLib import main as varLib_main, load_masters
|
||||||
|
from fontTools.varLib import set_default_weight_width_slant
|
||||||
from fontTools.designspaceLib import (
|
from fontTools.designspaceLib import (
|
||||||
DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor,
|
DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor,
|
||||||
)
|
)
|
||||||
@ -661,5 +662,81 @@ def _extract_flat_kerning(font, pairpos_table):
|
|||||||
return extracted_kerning
|
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__":
|
if __name__ == "__main__":
|
||||||
sys.exit(unittest.main())
|
sys.exit(unittest.main())
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 3.43.1.dev0
|
current_version = 3.43.2.dev0
|
||||||
commit = True
|
commit = True
|
||||||
tag = False
|
tag = False
|
||||||
tag_name = {new_version}
|
tag_name = {new_version}
|
||||||
|
2
setup.py
2
setup.py
@ -352,7 +352,7 @@ def find_data_files(manpath="share/man"):
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="fonttools",
|
name="fonttools",
|
||||||
version="3.43.1.dev0",
|
version="3.43.2.dev0",
|
||||||
description="Tools to manipulate font files",
|
description="Tools to manipulate font files",
|
||||||
author="Just van Rossum",
|
author="Just van Rossum",
|
||||||
author_email="just@letterror.com",
|
author_email="just@letterror.com",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user