Merge remote-tracking branch 'origin/master' into partial-instancer

This commit is contained in:
Cosimo Lupo 2019-06-20 15:05:34 +01:00
commit 1726a4a1fc
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F
12 changed files with 239 additions and 25 deletions

View File

@ -5,6 +5,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
version = __version__ = "3.43.1.dev0"
version = __version__ = "3.43.2.dev0"
__all__ = ["version", "log", "configLogger"]

View File

@ -174,7 +174,7 @@ class WOFF2Writer(SFNTWriter):
self.file = file
self.numTables = numTables
self.sfntVersion = Tag(sfntVersion)
self.flavorData = flavorData or WOFF2FlavorData()
self.flavorData = WOFF2FlavorData(data=flavorData)
self.directoryFormat = woff2DirectoryFormat
self.directorySize = woff2DirectorySize
@ -1128,24 +1128,37 @@ class WOFF2FlavorData(WOFFFlavorData):
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
metadata or private data (as bytes strings), and the set of
table tags that have transformations applied (if reader is not None),
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:
raise ImportError("No module named brotli")
if reader is not None and transformedTables is not None:
raise TypeError(
"'reader' and 'transformedTables' arguments are mutually exclusive"
)
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(
"'reader' and 'transformedTables' arguments are mutually exclusive"
)
if transformedTables is None:
transformedTables = woff2TransformedTableTags
else:
if (
if transformedTables is not None and (
"glyf" in transformedTables and "loca" not in transformedTables
or "loca" in transformedTables and "glyf" not in transformedTables
):
@ -1164,19 +1177,29 @@ class WOFF2FlavorData(WOFFFlavorData):
reader.file.seek(reader.metaOffset)
rawData = reader.file.read(reader.metaLength)
assert len(rawData) == reader.metaLength
data = brotli.decompress(rawData)
assert len(data) == reader.metaOrigLength
self.metaData = data
metaData = brotli.decompress(rawData)
assert len(metaData) == reader.metaOrigLength
self.metaData = metaData
if reader.privLength:
reader.file.seek(reader.privOffset)
data = reader.file.read(reader.privLength)
assert len(data) == reader.privLength
self.privData = data
privData = reader.file.read(reader.privLength)
assert len(privData) == reader.privLength
self.privData = privData
transformedTables = [
tag
for tag, entry in reader.tables.items()
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)
@ -1354,7 +1377,9 @@ def compress(input_file, output_file, transform_tables=None):
font.flavor = "woff2"
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)

View File

@ -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):
"""
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.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]

View File

@ -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)
----------------------------

Binary file not shown.

View File

@ -678,6 +678,48 @@ class SubsetTest(unittest.TestCase):
subsetfont = TTFont(subsetpath)
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__":
sys.exit(unittest.main())

View File

@ -386,15 +386,18 @@ class WOFF2FlavorDataTest(unittest.TestCase):
self.assertEqual(flavorData.minorVersion, 1)
def test_mutually_exclusive_args(self):
msg = "arguments are mutually exclusive"
reader = DummyReader(self.file)
with self.assertRaisesRegex(TypeError, "arguments are mutually exclusive"):
with self.assertRaisesRegex(TypeError, msg):
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()
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"
with self.assertRaisesRegex(ValueError, msg):
@ -1258,6 +1261,24 @@ class MainTest(object):
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):
input_file = tmpdir / "TestTTF-Regular.woff2"
input_file.write_binary(TT_WOFF2.getvalue())

View File

@ -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"/>

View File

@ -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"/>

View File

@ -4,6 +4,7 @@ from fontTools.ttLib import TTFont, newTable
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 @@ def _extract_flat_kerning(font, pairpos_table):
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())

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.43.1.dev0
current_version = 3.43.2.dev0
commit = True
tag = False
tag_name = {new_version}

View File

@ -352,7 +352,7 @@ def find_data_files(manpath="share/man"):
setup(
name="fonttools",
version="3.43.1.dev0",
version="3.43.2.dev0",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="just@letterror.com",