diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py index c2dd8463d..dece1501c 100644 --- a/Lib/fontTools/__init__.py +++ b/Lib/fontTools/__init__.py @@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger log = logging.getLogger(__name__) -version = __version__ = "4.24.3.dev0" +version = __version__ = "4.24.5.dev0" __all__ = ["version", "log", "configLogger"] diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index 06d5cde54..0e748124a 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -358,13 +358,13 @@ class OTTableWriter(object): tables, extTables, done = extTables, None, {} # add Coverage table if it is sorted last. - sortCoverageLast = 0 + sortCoverageLast = False if hasattr(self, "sortCoverageLast"): # Find coverage table for i in range(numItems): item = self.items[i] - if hasattr(item, "name") and (item.name == "Coverage"): - sortCoverageLast = 1 + if getattr(item, 'name', None) == "Coverage": + sortCoverageLast = True break if id(item) not in done: item._gatherTables(tables, extTables, done) @@ -377,7 +377,7 @@ class OTTableWriter(object): if not hasattr(item, "getData"): continue - if sortCoverageLast and (i==1) and item.name == 'Coverage': + if sortCoverageLast and (i==1) and getattr(item, 'name', None) == 'Coverage': # we've already 'gathered' it above continue diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index b62227cc0..4c8ef04d1 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -1503,7 +1503,6 @@ def fixLookupOverFlows(ttf, overflowRecord): def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord): ok = 1 - newSubTable.Format = oldSubTable.Format oldMapping = sorted(oldSubTable.mapping.items()) oldLen = len(oldMapping) @@ -1529,7 +1528,6 @@ def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord): def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): ok = 1 - newSubTable.Format = oldSubTable.Format if hasattr(oldSubTable, 'sortCoverageLast'): newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast @@ -1559,7 +1557,6 @@ def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): ok = 1 - newSubTable.Format = oldSubTable.Format oldLigs = sorted(oldSubTable.ligatures.items()) oldLen = len(oldLigs) diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py index 77d2ea7ef..888b52c26 100644 --- a/Lib/fontTools/varLib/merger.py +++ b/Lib/fontTools/varLib/merger.py @@ -993,7 +993,7 @@ def merge(merger, self, lst): varidx = (dev.StartSize << 16) + dev.EndSize delta = otRound(instancer[varidx]) - setattr(self, name, getattr(self, name) + delta) + setattr(self, name, getattr(self, name, 0) + delta) # diff --git a/NEWS.rst b/NEWS.rst index 02ce5cf39..fd3b9907c 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,18 @@ +4.24.4 (released 2021-05-25) +---------------------------- + +- [subset/instancer] Fixed ``AttributeError`` when instantiating a VF that + contains GPOS ValueRecords with ``Device`` tables but without the respective + non-Device values (e.g. ``XAdvDevice`` without ``XAdvance``). When not + explicitly set, the latter are assumed to be 0 (#2323). + +4.24.3 (released 2021-05-20) +---------------------------- + +- [otTables] Fixed ``AttributeError`` in methods that split LigatureSubst, + MultipleSubst and AlternateSubst subtables when an offset overflow occurs. + The ``Format`` attribute was removed in v4.22.0 (#2319). + 4.24.2 (released 2021-05-20) ---------------------------- diff --git a/README.rst b/README.rst index 97d23e4bf..78f422ef2 100644 --- a/README.rst +++ b/README.rst @@ -205,6 +205,36 @@ are required to unlock the extra features named "ufo", etc. * `reportlab `__: Python toolkit for generating PDFs and graphics. +How to make a new release +~~~~~~~~~~~~~~~~~~~~~~~~~ + +1) Update ``NEWS.rst`` with all the changes since the last release. Write a + changelog entry for each PR, with one or two short sentences summarizing it, + as well as links to the PR and relevant issues addressed by the PR. +2) Use semantic versioning to decide whether the new release will be a 'major', + 'minor' or 'patch' release. It's usually one of the latter two, depending on + whether new backward compatible APIs were added, or simply some bugs were fixed. +3) Run ``python setup.py release`` command from the tip of the ``main`` branch. + By default this bumps the third or 'patch' digit only, unless you pass ``--major`` + or ``--minor`` to bump respectively the first or second digit. + This bumps the package version string, extracts the changes since the latest + version from ``NEWS.rst``, and uses that text to create an annotated git tag + (or a signed git tag if you pass the ``--sign`` option and your git and Github + account are configured for `signing commits `__ + using a GPG key). + It also commits an additional version bump which opens the main branch for + the subsequent developmental cycle +4) Push both the tag and commit to the upstream repository, by running the command + ``git push --follow-tags``. +5) Let the CI build the wheel and source distribution packages and verify both + get uploaded to the Python Package Index (PyPI). +6) [Optional] Go to fonttools `Github Releases `__ + page and create a new release, copy-pasting the content of the git tag + message. This way, the release notes are nicely formatted as markdown, and + users watching the repo will get an email notification. One day we shall + automate that too. + + Acknowledgements ~~~~~~~~~~~~~~~~ diff --git a/Tests/ttLib/tables/otTables_test.py b/Tests/ttLib/tables/otTables_test.py index 9202aa557..b1697761b 100644 --- a/Tests/ttLib/tables/otTables_test.py +++ b/Tests/ttLib/tables/otTables_test.py @@ -512,8 +512,11 @@ class InsertionMorphActionTest(unittest.TestCase): for name, attrs, content in parseXML(self.MORPH_ACTION_XML): a.fromXML(name, attrs, content, self.font) writer = OTTableWriter() - a.compile(writer, self.font, - actionIndex={('B', 'C'): 9, ('B', 'A', 'D'): 7}) + a.compile( + writer, + self.font, + actionIndex={('B', 'C'): 9, ('B', 'A', 'D'): 7}, + ) self.assertEqual(hexStr(writer.getAllData()), "1234fc4300090007") def testCompileActions_empty(self): @@ -551,13 +554,11 @@ class SplitMultipleSubstTest: from fontTools.ttLib.tables.otBase import OverflowErrorRecord oldSubTable = buildMultipleSubstSubtable({'e': 1, 'a': 2, 'b': 3, 'c': 4, 'd': 5}) - oldSubTable.Format = 1 newSubTable = otTables.MultipleSubst() ok = otTables.splitMultipleSubst(oldSubTable, newSubTable, OverflowErrorRecord((None, None, None, itemName, itemRecord))) assert ok - assert oldSubTable.Format == newSubTable.Format return oldSubTable.mapping, newSubTable.mapping def test_Coverage(self): @@ -577,113 +578,113 @@ class SplitMultipleSubstTest: def test_splitMarkBasePos(): - from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable + from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable - marks = { - "acutecomb": (0, buildAnchor(0, 600)), - "gravecomb": (0, buildAnchor(0, 590)), - "cedillacomb": (1, buildAnchor(0, 0)), - } - bases = { - "a": { - 0: buildAnchor(350, 500), - 1: None, - }, - "c": { - 0: buildAnchor(300, 700), - 1: buildAnchor(300, 0), - }, - } - glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"] - glyphMap = {g: i for i, g in enumerate(glyphOrder)} + marks = { + "acutecomb": (0, buildAnchor(0, 600)), + "gravecomb": (0, buildAnchor(0, 590)), + "cedillacomb": (1, buildAnchor(0, 0)), + } + bases = { + "a": { + 0: buildAnchor(350, 500), + 1: None, + }, + "c": { + 0: buildAnchor(300, 700), + 1: buildAnchor(300, 0), + }, + } + glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"] + glyphMap = {g: i for i, g in enumerate(glyphOrder)} - oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap) - newSubTable = otTables.MarkBasePos() + oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap) + newSubTable = otTables.MarkBasePos() - ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None) + ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None) - assert ok + assert ok - assert getXML(oldSubTable.toXML) == [ - '', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '', - ] + assert getXML(oldSubTable.toXML) == [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ] - assert getXML(newSubTable.toXML) == [ - '', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '', - ] + assert getXML(newSubTable.toXML) == [ + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ] if __name__ == "__main__": diff --git a/Tests/varLib/instancer/data/PartialInstancerTest4-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest4-VF.ttx new file mode 100644 index 000000000..8d445b019 --- /dev/null +++ b/Tests/varLib/instancer/data/PartialInstancerTest4-VF.ttx @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + Regular + + + Bold + + + New Font + + + Regular + + + 1.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 1.000 + + + NewFont-Regular + + + Weight + + + Regular + + + Bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 400.0 + 400.0 + 700.0 + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/instancer/instancer_test.py b/Tests/varLib/instancer/instancer_test.py index cb7e8547a..90048c181 100644 --- a/Tests/varLib/instancer/instancer_test.py +++ b/Tests/varLib/instancer/instancer_test.py @@ -936,6 +936,30 @@ class InstantiateOTLTest(object): assert not hasattr(valueRec1, "XAdvDevice") assert valueRec1.XAdvance == v2 + def test_GPOS_ValueRecord_XAdvDevice_wtihout_XAdvance(self): + # Test VF contains a PairPos adjustment in which the default instance + # has no XAdvance but there are deltas in XAdvDevice (VariationIndex). + vf = ttLib.TTFont() + vf.importXML(os.path.join(TESTDATA, "PartialInstancerTest4-VF.ttx")) + pairPos = vf["GPOS"].table.LookupList.Lookup[0].SubTable[0] + assert pairPos.ValueFormat1 == 0x40 + valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 + assert not hasattr(valueRec1, "XAdvance") + assert valueRec1.XAdvDevice.DeltaFormat == 0x8000 + outer = valueRec1.XAdvDevice.StartSize + inner = valueRec1.XAdvDevice.EndSize + assert vf["GDEF"].table.VarStore.VarData[outer].Item[inner] == [-50] + + # check that MutatorMerger for ValueRecord doesn't raise AttributeError + # when XAdvDevice is present but there's no corresponding XAdvance. + instancer.instantiateOTL(vf, {"wght": 0.5}) + + pairPos = vf["GPOS"].table.LookupList.Lookup[0].SubTable[0] + assert pairPos.ValueFormat1 == 0x4 + valueRec1 = pairPos.PairSet[0].PairValueRecord[0].Value1 + assert not hasattr(valueRec1, "XAdvDevice") + assert valueRec1.XAdvance == -25 + class InstantiateAvarTest(object): @pytest.mark.parametrize("location", [{"wght": 0.0}, {"wdth": 0.0}]) diff --git a/setup.cfg b/setup.cfg index 08b1a3836..784ba7139 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 4.24.3.dev0 +current_version = 4.24.5.dev0 commit = True tag = False tag_name = {new_version} diff --git a/setup.py b/setup.py index 5a183c368..d90ca14a8 100755 --- a/setup.py +++ b/setup.py @@ -441,7 +441,7 @@ if ext_modules: setup_params = dict( name="fonttools", - version="4.24.3.dev0", + version="4.24.5.dev0", description="Tools to manipulate font files", author="Just van Rossum", author_email="just@letterror.com",