diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-02-21 09:21:11 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-02-21 09:29:49 +0300 |
commit | 7732db443bee366753d1b056727860be4b833df0 (patch) | |
tree | 5d69ae560f055f7b9ff27ebee26440dc19eb6e86 /contrib | |
parent | 705f460d33000009c49a47b51913a7f29d2d6c46 (diff) | |
download | ydb-7732db443bee366753d1b056727860be4b833df0.tar.gz |
Intermediate changes
Diffstat (limited to 'contrib')
23 files changed, 310 insertions, 156 deletions
diff --git a/contrib/python/appnope/py3/.dist-info/METADATA b/contrib/python/appnope/py3/.dist-info/METADATA index 9c7b757060..76ef35d8e7 100644 --- a/contrib/python/appnope/py3/.dist-info/METADATA +++ b/contrib/python/appnope/py3/.dist-info/METADATA @@ -1,20 +1,15 @@ Metadata-Version: 2.1 Name: appnope -Version: 0.1.3 +Version: 0.1.4 Summary: Disable App Nap on macOS >= 10.9 Home-page: http://github.com/minrk/appnope Author: Min Ragan-Kelley Author-email: benjaminrk@gmail.com License: BSD -Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: MacOS :: MacOS X -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.2 -Classifier: Programming Language :: Python :: 3.3 +Requires-Python: >=3.6 Description-Content-Type: text/markdown License-File: LICENSE @@ -48,5 +43,3 @@ It uses ctypes to wrap a `[NSProcessInfo beginActivityWithOptions]` call to disa To install: pip install appnope - - diff --git a/contrib/python/appnope/py3/appnope/__init__.py b/contrib/python/appnope/py3/appnope/__init__.py index bcf87f4917..c447f24185 100644 --- a/contrib/python/appnope/py3/appnope/__init__.py +++ b/contrib/python/appnope/py3/appnope/__init__.py @@ -1,13 +1,15 @@ -__version__ = '0.1.3' +__version__ = "0.1.4" import re import sys import platform + def _v(version_s): - return tuple(int(s) for s in re.findall("\d+", version_s)) + return tuple(int(s) for s in re.findall(r"\d+", version_s)) + if sys.platform != "darwin" or _v(platform.mac_ver()[0]) < _v("10.9"): - from ._dummy import * + from ._dummy import * # noqa else: - from ._nope import * + from ._nope import * # noqa diff --git a/contrib/python/appnope/py3/appnope/_dummy.py b/contrib/python/appnope/py3/appnope/_dummy.py index a55ec5bfcd..a4ee06f89e 100644 --- a/contrib/python/appnope/py3/appnope/_dummy.py +++ b/contrib/python/appnope/py3/appnope/_dummy.py @@ -1,30 +1,32 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2013 Min RK # # Distributed under the terms of the 2-clause BSD License. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- from contextlib import contextmanager + def beginActivityWithOptions(options, reason=""): return + def endActivity(activity): return + def nope(): return + def nap(): return @contextmanager -def nope_scope( - options=0, - reason="Because Reasons" - ): +def nope_scope(options=0, reason="Because Reasons"): yield + def napping_allowed(): - return True
\ No newline at end of file + return True diff --git a/contrib/python/appnope/py3/appnope/_nope.py b/contrib/python/appnope/py3/appnope/_nope.py index d83e826797..10d1c056f4 100644 --- a/contrib/python/appnope/py3/appnope/_nope.py +++ b/contrib/python/appnope/py3/appnope/_nope.py @@ -1,16 +1,16 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2013 Min RK # # Distributed under the terms of the 2-clause BSD License. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- from contextlib import contextmanager import ctypes import ctypes.util -objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) -_ = ctypes.cdll.LoadLibrary(ctypes.util.find_library('Foundation')) +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) +_ = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Foundation")) void_p = ctypes.c_void_p ull = ctypes.c_uint64 @@ -22,74 +22,82 @@ objc.objc_msgSend.argtypes = [void_p, void_p] msg = objc.objc_msgSend + def _utf8(s): """ensure utf8 bytes""" if not isinstance(s, bytes): - s = s.encode('utf8') + s = s.encode("utf8") return s + def n(name): """create a selector name (for methods)""" return objc.sel_registerName(_utf8(name)) + def C(classname): """get an ObjC Class by name""" ret = objc.objc_getClass(_utf8(classname)) assert ret is not None, "Couldn't find Class %s" % classname return ret + # constants from Foundation -NSActivityIdleDisplaySleepDisabled = (1 << 40) -NSActivityIdleSystemSleepDisabled = (1 << 20) -NSActivitySuddenTerminationDisabled = (1 << 14) -NSActivityAutomaticTerminationDisabled = (1 << 15) -NSActivityUserInitiated = (0x00FFFFFF | NSActivityIdleSystemSleepDisabled) -NSActivityUserInitiatedAllowingIdleSystemSleep = (NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled) -NSActivityBackground = 0x000000FF -NSActivityLatencyCritical = 0xFF00000000 +NSActivityIdleDisplaySleepDisabled = 1 << 40 +NSActivityIdleSystemSleepDisabled = 1 << 20 +NSActivitySuddenTerminationDisabled = 1 << 14 +NSActivityAutomaticTerminationDisabled = 1 << 15 +NSActivityUserInitiated = 0x00FFFFFF | NSActivityIdleSystemSleepDisabled +NSActivityUserInitiatedAllowingIdleSystemSleep = ( + NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled +) +NSActivityBackground = 0x000000FF +NSActivityLatencyCritical = 0xFF00000000 + def beginActivityWithOptions(options, reason=""): """Wrapper for: - - [ [ NSProcessInfo processInfo] + + [ [ NSProcessInfo processInfo] beginActivityWithOptions: (uint64)options reason: (str)reason ] """ - NSProcessInfo = C('NSProcessInfo') - NSString = C('NSString') - + NSProcessInfo = C("NSProcessInfo") + NSString = C("NSString") + objc.objc_msgSend.argtypes = [void_p, void_p, void_p] reason = msg(NSString, n("stringWithUTF8String:"), _utf8(reason)) objc.objc_msgSend.argtypes = [void_p, void_p] - info = msg(NSProcessInfo, n('processInfo')) + info = msg(NSProcessInfo, n("processInfo")) objc.objc_msgSend.argtypes = [void_p, void_p, ull, void_p] - activity = msg(info, - n('beginActivityWithOptions:reason:'), - ull(options), - void_p(reason) + activity = msg( + info, n("beginActivityWithOptions:reason:"), ull(options), void_p(reason) ) return activity + def endActivity(activity): """end a process activity assertion""" - NSProcessInfo = C('NSProcessInfo') + NSProcessInfo = C("NSProcessInfo") objc.objc_msgSend.argtypes = [void_p, void_p] - info = msg(NSProcessInfo, n('processInfo')) + info = msg(NSProcessInfo, n("processInfo")) objc.objc_msgSend.argtypes = [void_p, void_p, void_p] msg(info, n("endActivity:"), void_p(activity)) + _theactivity = None + def nope(): """disable App Nap by setting NSActivityUserInitiatedAllowingIdleSystemSleep""" global _theactivity _theactivity = beginActivityWithOptions( - NSActivityUserInitiatedAllowingIdleSystemSleep, - "Because Reasons" + NSActivityUserInitiatedAllowingIdleSystemSleep, "Because Reasons" ) + def nap(): """end the caffeinated state started by `nope`""" global _theactivity @@ -97,17 +105,18 @@ def nap(): endActivity(_theactivity) _theactivity = None + def napping_allowed(): """is napping allowed?""" return _theactivity is None + @contextmanager def nope_scope( - options=NSActivityUserInitiatedAllowingIdleSystemSleep, - reason="Because Reasons" - ): + options=NSActivityUserInitiatedAllowingIdleSystemSleep, reason="Because Reasons" +): """context manager for beginActivityWithOptions. - + Within this context, App Nap will be disabled. """ activity = beginActivityWithOptions(options, reason) @@ -116,6 +125,7 @@ def nope_scope( finally: endActivity(activity) + __all__ = [ "NSActivityIdleDisplaySleepDisabled", "NSActivityIdleSystemSleepDisabled", diff --git a/contrib/python/appnope/py3/ya.make b/contrib/python/appnope/py3/ya.make index bacfa9a91b..a1901177d1 100644 --- a/contrib/python/appnope/py3/ya.make +++ b/contrib/python/appnope/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(0.1.3) +VERSION(0.1.4) LICENSE(BSD-2-Clause) diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA index f9e01c388f..b335b936d7 100644 --- a/contrib/python/fonttools/.dist-info/METADATA +++ b/contrib/python/fonttools/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: fonttools -Version: 4.47.2 +Version: 4.48.1 Summary: Tools to manipulate font files Home-page: http://github.com/fonttools/fonttools Author: Just van Rossum @@ -31,7 +31,7 @@ Requires-Python: >=3.8 License-File: LICENSE Provides-Extra: all Requires-Dist: fs <3,>=2.2.0 ; extra == 'all' -Requires-Dist: lxml <5,>=4.0 ; extra == 'all' +Requires-Dist: lxml >=4.0 ; extra == 'all' Requires-Dist: zopfli >=0.1.4 ; extra == 'all' Requires-Dist: lz4 >=1.7.4.2 ; extra == 'all' Requires-Dist: pycairo ; extra == 'all' @@ -52,7 +52,7 @@ Requires-Dist: pycairo ; extra == 'interpolatable' Requires-Dist: scipy ; (platform_python_implementation != "PyPy") and extra == 'interpolatable' Requires-Dist: munkres ; (platform_python_implementation == "PyPy") and extra == 'interpolatable' Provides-Extra: lxml -Requires-Dist: lxml <5,>=4.0 ; extra == 'lxml' +Requires-Dist: lxml >=4.0 ; extra == 'lxml' Provides-Extra: pathops Requires-Dist: skia-pathops >=0.5.0 ; extra == 'pathops' Provides-Extra: plot @@ -118,7 +118,7 @@ Python 3 `venv <https://docs.python.org/3/library/venv.html>`__ module. # create new virtual environment called e.g. 'fonttools-venv', or anything you like python -m virtualenv fonttools-venv - # source the `activate` shell script to enter the environment (Un*x); to exit, just type `deactivate` + # source the `activate` shell script to enter the environment (Unix-like); to exit, just type `deactivate` . fonttools-venv/bin/activate # to activate the virtual environment in Windows `cmd.exe`, do @@ -375,6 +375,32 @@ Have fun! Changelog ~~~~~~~~~ +4.48.1 (released 2024-02-06) +---------------------------- + +- Fixed uploading wheels to PyPI, no code changes since v4.48.0. + +4.48.0 (released 2024-02-06) +---------------------------- + +- [varLib] Do not log when there are no OTL tables to be merged. +- [setup.py] Do not restrict lxml<5 any more, tests pass just fine with lxml>=5. +- [feaLib] Remove glyph and class names length restrictions in FEA (#3424). +- [roundingPens] Added ``transformRoundFunc`` parameter to the rounding pens to allow + for custom rounding of the components' transforms (#3426). +- [feaLib] Keep declaration order of ligature components within a ligature set, instead + of sorting by glyph name (#3429). +- [feaLib] Fixed ordering of alternates in ``aalt`` lookups, following the declaration + order of feature references within the ``aalt`` feature block (#3430). +- [varLib.instancer] Fixed a bug in the instancer's IUP optimization (#3432). +- [sbix] Support sbix glyphs with new graphicType "flip" (#3433). +- [svgPathPen] Added ``--glyphs`` option to dump the SVG paths for the named glyphs + in the font (0572f78). +- [designspaceLib] Added "description" attribute to ``<mappings>`` and ``<mapping>`` + elements, and allow multiple ``<mappings>`` elements to group ``<mapping>`` elements + that are logically related (#3435, #3437). +- [otlLib] Correctly choose the most compact GSUB contextual lookup format (#3439). + 4.47.2 (released 2024-01-11) ---------------------------- diff --git a/contrib/python/fonttools/README.rst b/contrib/python/fonttools/README.rst index d84282fc76..2274fbdc69 100644 --- a/contrib/python/fonttools/README.rst +++ b/contrib/python/fonttools/README.rst @@ -44,7 +44,7 @@ Python 3 `venv <https://docs.python.org/3/library/venv.html>`__ module. # create new virtual environment called e.g. 'fonttools-venv', or anything you like python -m virtualenv fonttools-venv - # source the `activate` shell script to enter the environment (Un*x); to exit, just type `deactivate` + # source the `activate` shell script to enter the environment (Unix-like); to exit, just type `deactivate` . fonttools-venv/bin/activate # to activate the virtual environment in Windows `cmd.exe`, do diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py index 7410d3c7fe..feb093929e 100644 --- a/contrib/python/fonttools/fontTools/__init__.py +++ b/contrib/python/fonttools/fontTools/__init__.py @@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger log = logging.getLogger(__name__) -version = __version__ = "4.47.2" +version = __version__ = "4.48.1" __all__ = ["version", "log", "configLogger"] diff --git a/contrib/python/fonttools/fontTools/designspaceLib/__init__.py b/contrib/python/fonttools/fontTools/designspaceLib/__init__.py index 69d4912c09..d6789f5f61 100644 --- a/contrib/python/fonttools/fontTools/designspaceLib/__init__.py +++ b/contrib/python/fonttools/fontTools/designspaceLib/__init__.py @@ -476,7 +476,14 @@ class AxisMappingDescriptor(SimpleDescriptor): _attrs = ["inputLocation", "outputLocation"] - def __init__(self, *, inputLocation=None, outputLocation=None): + def __init__( + self, + *, + inputLocation=None, + outputLocation=None, + description=None, + groupDescription=None, + ): self.inputLocation: SimpleLocationDict = inputLocation or {} """dict. Axis values for the input of the mapping, in design space coordinates. @@ -491,6 +498,20 @@ class AxisMappingDescriptor(SimpleDescriptor): .. versionadded:: 5.1 """ + self.description = description + """string. A description of the mapping. + + varLib. + + .. versionadded:: 5.2 + """ + self.groupDescription = groupDescription + """string. A description of the group of mappings. + + varLib. + + .. versionadded:: 5.2 + """ class InstanceDescriptor(SimpleDescriptor): @@ -1421,10 +1442,19 @@ class BaseDocWriter(object): self._addAxis(axisObject) if self.documentObject.axisMappings: - mappingsElement = ET.Element("mappings") - self.root.findall(".axes")[0].append(mappingsElement) + mappingsElement = None + lastGroup = object() for mappingObject in self.documentObject.axisMappings: + if getattr(mappingObject, "groupDescription", None) != lastGroup: + if mappingsElement is not None: + self.root.findall(".axes")[0].append(mappingsElement) + lastGroup = getattr(mappingObject, "groupDescription", None) + mappingsElement = ET.Element("mappings") + if lastGroup is not None: + mappingsElement.attrib["description"] = lastGroup self._addAxisMapping(mappingsElement, mappingObject) + if mappingsElement is not None: + self.root.findall(".axes")[0].append(mappingsElement) if self.documentObject.locationLabels: labelsElement = ET.Element("labels") @@ -1586,6 +1616,8 @@ class BaseDocWriter(object): def _addAxisMapping(self, mappingsElement, mappingObject): mappingElement = ET.Element("mapping") + if getattr(mappingObject, "description", None) is not None: + mappingElement.attrib["description"] = mappingObject.description for what in ("inputLocation", "outputLocation"): whatObject = getattr(mappingObject, what, None) if whatObject is None: @@ -2081,10 +2113,11 @@ class BaseDocReader(LogMixin): self.documentObject.axes.append(axisObject) self.axisDefaults[axisObject.name] = axisObject.default - mappingsElement = self.root.find(".axes/mappings") self.documentObject.axisMappings = [] - if mappingsElement is not None: + for mappingsElement in self.root.findall(".axes/mappings"): + groupDescription = mappingsElement.attrib.get("description") for mappingElement in mappingsElement.findall("mapping"): + description = mappingElement.attrib.get("description") inputElement = mappingElement.find("input") outputElement = mappingElement.find("output") inputLoc = {} @@ -2098,7 +2131,10 @@ class BaseDocReader(LogMixin): value = float(dimElement.attrib["xvalue"]) outputLoc[name] = value axisMappingObject = self.axisMappingDescriptorClass( - inputLocation=inputLoc, outputLocation=outputLoc + inputLocation=inputLoc, + outputLocation=outputLoc, + description=description, + groupDescription=groupDescription, ) self.documentObject.axisMappings.append(axisMappingObject) @@ -3279,3 +3315,23 @@ class DesignSpaceDocument(LogMixin, AsDictMixin): finally: for source, font in zip(self.sources, fonts): source.font = font + + +def main(args=None): + """Roundtrip .designspace file through the DesignSpaceDocument class""" + + if args is None: + import sys + + args = sys.argv[1:] + + from argparse import ArgumentParser + + parser = ArgumentParser(prog="designspaceLib", description=main.__doc__) + parser.add_argument("input") + parser.add_argument("output") + + options = parser.parse_args(args) + + ds = DesignSpaceDocument.fromfile(options.input) + ds.write(options.output) diff --git a/contrib/python/fonttools/fontTools/designspaceLib/__main__.py b/contrib/python/fonttools/fontTools/designspaceLib/__main__.py new file mode 100644 index 0000000000..8f5e44ea9e --- /dev/null +++ b/contrib/python/fonttools/fontTools/designspaceLib/__main__.py @@ -0,0 +1,6 @@ +import sys +from fontTools.designspaceLib import main + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/contrib/python/fonttools/fontTools/feaLib/builder.py b/contrib/python/fonttools/fontTools/feaLib/builder.py index 36eed95148..7921a3f179 100644 --- a/contrib/python/fonttools/fontTools/feaLib/builder.py +++ b/contrib/python/fonttools/fontTools/feaLib/builder.py @@ -285,7 +285,11 @@ class Builder(object): def build_feature_aalt_(self): if not self.aalt_features_ and not self.aalt_alternates_: return - alternates = {g: set(a) for g, a in self.aalt_alternates_.items()} + # > alternate glyphs will be sorted in the order that the source features + # > are named in the aalt definition, not the order of the feature definitions + # > in the file. Alternates defined explicitly ... will precede all others. + # https://github.com/fonttools/fonttools/issues/836 + alternates = {g: list(a) for g, a in self.aalt_alternates_.items()} for location, name in self.aalt_features_ + [(None, "aalt")]: feature = [ (script, lang, feature, lookups) @@ -302,17 +306,14 @@ class Builder(object): lookuplist = [lookuplist] for lookup in lookuplist: for glyph, alts in lookup.getAlternateGlyphs().items(): - alternates.setdefault(glyph, set()).update(alts) + alts_for_glyph = alternates.setdefault(glyph, []) + alts_for_glyph.extend( + g for g in alts if g not in alts_for_glyph + ) single = { - glyph: list(repl)[0] for glyph, repl in alternates.items() if len(repl) == 1 - } - # TODO: Figure out the glyph alternate ordering used by makeotf. - # https://github.com/fonttools/fonttools/issues/836 - multi = { - glyph: sorted(repl, key=self.font.getGlyphID) - for glyph, repl in alternates.items() - if len(repl) > 1 + glyph: repl[0] for glyph, repl in alternates.items() if len(repl) == 1 } + multi = {glyph: repl for glyph, repl in alternates.items() if len(repl) > 1} if not single and not multi: return self.features_ = { @@ -1249,8 +1250,9 @@ class Builder(object): def add_single_subst(self, location, prefix, suffix, mapping, forceChain): if self.cur_feature_name_ == "aalt": for from_glyph, to_glyph in mapping.items(): - alts = self.aalt_alternates_.setdefault(from_glyph, set()) - alts.add(to_glyph) + alts = self.aalt_alternates_.setdefault(from_glyph, []) + if to_glyph not in alts: + alts.append(to_glyph) return if prefix or suffix or forceChain: self.add_single_subst_chained_(location, prefix, suffix, mapping) @@ -1303,8 +1305,8 @@ class Builder(object): # GSUB 3 def add_alternate_subst(self, location, prefix, glyph, suffix, replacement): if self.cur_feature_name_ == "aalt": - alts = self.aalt_alternates_.setdefault(glyph, set()) - alts.update(replacement) + alts = self.aalt_alternates_.setdefault(glyph, []) + alts.extend(g for g in replacement if g not in alts) return if prefix or suffix: chain = self.get_lookup_(location, ChainContextSubstBuilder) @@ -1338,7 +1340,7 @@ class Builder(object): # substitutions to be specified on target sequences that contain # glyph classes, the implementation software will enumerate # all specific glyph sequences if glyph classes are detected" - for g in sorted(itertools.product(*glyphs)): + for g in itertools.product(*glyphs): lookup.ligatures[g] = replacement # GSUB 5/6 diff --git a/contrib/python/fonttools/fontTools/feaLib/lexer.py b/contrib/python/fonttools/fontTools/feaLib/lexer.py index e0ae0aefee..5867f70b38 100644 --- a/contrib/python/fonttools/fontTools/feaLib/lexer.py +++ b/contrib/python/fonttools/fontTools/feaLib/lexer.py @@ -111,10 +111,6 @@ class Lexer(object): glyphclass = text[start + 1 : self.pos_] if len(glyphclass) < 1: raise FeatureLibError("Expected glyph class name", location) - if len(glyphclass) > 63: - raise FeatureLibError( - "Glyph class names must not be longer than 63 characters", location - ) if not Lexer.RE_GLYPHCLASS.match(glyphclass): raise FeatureLibError( "Glyph class names must consist of letters, digits, " diff --git a/contrib/python/fonttools/fontTools/feaLib/parser.py b/contrib/python/fonttools/fontTools/feaLib/parser.py index 8ffdf644c3..8cbe79592b 100644 --- a/contrib/python/fonttools/fontTools/feaLib/parser.py +++ b/contrib/python/fonttools/fontTools/feaLib/parser.py @@ -2071,13 +2071,7 @@ class Parser(object): def expect_glyph_(self): self.advance_lexer_() if self.cur_token_type_ is Lexer.NAME: - self.cur_token_ = self.cur_token_.lstrip("\\") - if len(self.cur_token_) > 63: - raise FeatureLibError( - "Glyph names must not be longer than 63 characters", - self.cur_token_location_, - ) - return self.cur_token_ + return self.cur_token_.lstrip("\\") elif self.cur_token_type_ is Lexer.CID: return "cid%05d" % self.cur_token_ raise FeatureLibError("Expected a glyph name or CID", self.cur_token_location_) diff --git a/contrib/python/fonttools/fontTools/otlLib/builder.py b/contrib/python/fonttools/fontTools/otlLib/builder.py index 4b457f4d9f..c8b14fc666 100644 --- a/contrib/python/fonttools/fontTools/otlLib/builder.py +++ b/contrib/python/fonttools/fontTools/otlLib/builder.py @@ -6,6 +6,7 @@ from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables.otBase import ( ValueRecord, valueRecordFormatDict, + OTLOffsetOverflowError, OTTableWriter, CountReference, ) @@ -350,16 +351,14 @@ class ChainContextualBuilder(LookupBuilder): return [x for x in ruleset if len(x.rules) > 0] def getCompiledSize_(self, subtables): - size = 0 - for st in subtables: - w = OTTableWriter() - w["LookupType"] = CountReference( - {"LookupType": st.LookupType}, "LookupType" - ) - # We need to make a copy here because compiling - # modifies the subtable (finalizing formats etc.) - copy.deepcopy(st).compile(w, self.font) - size += len(w.getAllData()) + if not subtables: + return 0 + # We need to make a copy here because compiling + # modifies the subtable (finalizing formats etc.) + table = self.buildLookup_(copy.deepcopy(subtables)) + w = OTTableWriter() + table.compile(w, self.font) + size = len(w.getAllData()) return size def build(self): @@ -410,22 +409,23 @@ class ChainContextualBuilder(LookupBuilder): if not ruleset.hasAnyGlyphClasses: candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)] + candidates_by_size = [] for i in [1, 2, 3]: if candidates[i]: try: - self.getCompiledSize_(candidates[i]) - except Exception as e: + size = self.getCompiledSize_(candidates[i]) + except OTLOffsetOverflowError as e: log.warning( "Contextual format %i at %s overflowed (%s)" % (i, str(self.location), e) ) - candidates[i] = None + else: + candidates_by_size.append((size, candidates[i])) - candidates = [x for x in candidates if x is not None] - if not candidates: + if not candidates_by_size: raise OpenTypeLibError("All candidates overflowed", self.location) - winner = min(candidates, key=self.getCompiledSize_) + _min_size, winner = min(candidates_by_size, key=lambda x: x[0]) subtables.extend(winner) # If we are not chaining, lookup type will be automatically fixed by @@ -774,7 +774,10 @@ class ChainContextSubstBuilder(ChainContextualBuilder): if lookup is not None: alts = lookup.getAlternateGlyphs() for glyph, replacements in alts.items(): - result.setdefault(glyph, set()).update(replacements) + alts_for_glyph = result.setdefault(glyph, []) + alts_for_glyph.extend( + g for g in replacements if g not in alts_for_glyph + ) return result def find_chainable_single_subst(self, mapping): @@ -1238,7 +1241,7 @@ class SingleSubstBuilder(LookupBuilder): return self.buildLookup_(subtables) def getAlternateGlyphs(self): - return {glyph: set([repl]) for glyph, repl in self.mapping.items()} + return {glyph: [repl] for glyph, repl in self.mapping.items()} def add_subtable_break(self, location): self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ @@ -1567,19 +1570,6 @@ def buildAlternateSubstSubtable(mapping): return self -def _getLigatureKey(components): - # Computes a key for ordering ligatures in a GSUB Type-4 lookup. - - # When building the OpenType lookup, we need to make sure that - # the longest sequence of components is listed first, so we - # use the negative length as the primary key for sorting. - # To make buildLigatureSubstSubtable() deterministic, we use the - # component sequence as the secondary key. - - # For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l). - return (-len(components), components) - - def buildLigatureSubstSubtable(mapping): """Builds a ligature substitution (GSUB4) subtable. @@ -1613,7 +1603,7 @@ def buildLigatureSubstSubtable(mapping): # with fontTools >= 3.1: # self.ligatures = dict(mapping) self.ligatures = {} - for components in sorted(mapping.keys(), key=_getLigatureKey): + for components in sorted(mapping.keys(), key=self._getLigatureSortKey): ligature = ot.Ligature() ligature.Component = components[1:] ligature.CompCount = len(ligature.Component) + 1 diff --git a/contrib/python/fonttools/fontTools/pens/hashPointPen.py b/contrib/python/fonttools/fontTools/pens/hashPointPen.py index b82468ec9c..f15dcabbfd 100644 --- a/contrib/python/fonttools/fontTools/pens/hashPointPen.py +++ b/contrib/python/fonttools/fontTools/pens/hashPointPen.py @@ -31,6 +31,20 @@ class HashPointPen(AbstractPointPen): > # The hash values are identical, the outline has not changed. > # Compile the hinting code ... > pass + + If you want to compare a glyph from a source format which supports floating point + coordinates and transformations against a glyph from a format which has restrictions + on the precision of floats, e.g. UFO vs. TTF, you must use an appropriate rounding + function to make the values comparable. For TTF fonts with composites, this + construct can be used to make the transform values conform to F2Dot14: + + > ttf_hash_pen = HashPointPen(ttf_glyph_width, ttFont.getGlyphSet()) + > ttf_round_pen = RoundingPointPen(ttf_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14)) + > ufo_hash_pen = HashPointPen(ufo_glyph.width, ufo) + > ttf_glyph.drawPoints(ttf_round_pen, ttFont["glyf"]) + > ufo_round_pen = RoundingPointPen(ufo_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14)) + > ufo_glyph.drawPoints(ufo_round_pen) + > assert ttf_hash_pen.hash == ufo_hash_pen.hash """ def __init__(self, glyphWidth=0, glyphSet=None): diff --git a/contrib/python/fonttools/fontTools/pens/roundingPen.py b/contrib/python/fonttools/fontTools/pens/roundingPen.py index 2a7c476c36..176bcc7a55 100644 --- a/contrib/python/fonttools/fontTools/pens/roundingPen.py +++ b/contrib/python/fonttools/fontTools/pens/roundingPen.py @@ -1,4 +1,4 @@ -from fontTools.misc.roundTools import otRound +from fontTools.misc.roundTools import noRound, otRound from fontTools.misc.transform import Transform from fontTools.pens.filterPen import FilterPen, FilterPointPen @@ -8,7 +8,9 @@ __all__ = ["RoundingPen", "RoundingPointPen"] class RoundingPen(FilterPen): """ - Filter pen that rounds point coordinates and component XY offsets to integer. + Filter pen that rounds point coordinates and component XY offsets to integer. For + rounding the component transform values, a separate round function can be passed to + the pen. >>> from fontTools.pens.recordingPen import RecordingPen >>> recpen = RecordingPen() @@ -28,9 +30,10 @@ class RoundingPen(FilterPen): True """ - def __init__(self, outPen, roundFunc=otRound): + def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound): super().__init__(outPen) self.roundFunc = roundFunc + self.transformRoundFunc = transformRoundFunc def moveTo(self, pt): self._outPen.moveTo((self.roundFunc(pt[0]), self.roundFunc(pt[1]))) @@ -49,12 +52,16 @@ class RoundingPen(FilterPen): ) def addComponent(self, glyphName, transformation): + xx, xy, yx, yy, dx, dy = transformation self._outPen.addComponent( glyphName, Transform( - *transformation[:4], - self.roundFunc(transformation[4]), - self.roundFunc(transformation[5]), + self.transformRoundFunc(xx), + self.transformRoundFunc(xy), + self.transformRoundFunc(yx), + self.transformRoundFunc(yy), + self.roundFunc(dx), + self.roundFunc(dy), ), ) @@ -62,6 +69,8 @@ class RoundingPen(FilterPen): class RoundingPointPen(FilterPointPen): """ Filter point pen that rounds point coordinates and component XY offsets to integer. + For rounding the component scale values, a separate round function can be passed to + the pen. >>> from fontTools.pens.recordingPen import RecordingPointPen >>> recpen = RecordingPointPen() @@ -87,26 +96,35 @@ class RoundingPointPen(FilterPointPen): True """ - def __init__(self, outPen, roundFunc=otRound): + def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound): super().__init__(outPen) self.roundFunc = roundFunc + self.transformRoundFunc = transformRoundFunc - def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): + def addPoint( + self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs + ): self._outPen.addPoint( (self.roundFunc(pt[0]), self.roundFunc(pt[1])), segmentType=segmentType, smooth=smooth, name=name, + identifier=identifier, **kwargs, ) - def addComponent(self, baseGlyphName, transformation, **kwargs): + def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): + xx, xy, yx, yy, dx, dy = transformation self._outPen.addComponent( - baseGlyphName, - Transform( - *transformation[:4], - self.roundFunc(transformation[4]), - self.roundFunc(transformation[5]), + baseGlyphName=baseGlyphName, + transformation=Transform( + self.transformRoundFunc(xx), + self.transformRoundFunc(xy), + self.transformRoundFunc(yx), + self.transformRoundFunc(yy), + self.roundFunc(dx), + self.roundFunc(dy), ), + identifier=identifier, **kwargs, ) diff --git a/contrib/python/fonttools/fontTools/pens/svgPathPen.py b/contrib/python/fonttools/fontTools/pens/svgPathPen.py index 53b3683f2d..29d41a8029 100644 --- a/contrib/python/fonttools/fontTools/pens/svgPathPen.py +++ b/contrib/python/fonttools/fontTools/pens/svgPathPen.py @@ -220,13 +220,19 @@ def main(args=None): "fonttools pens.svgPathPen", description="Generate SVG from text" ) parser.add_argument("font", metavar="font.ttf", help="Font file.") - parser.add_argument("text", metavar="text", help="Text string.") + parser.add_argument("text", metavar="text", nargs="?", help="Text string.") parser.add_argument( "-y", metavar="<number>", help="Face index into a collection to open. Zero based.", ) parser.add_argument( + "--glyphs", + metavar="whitespace-separated list of glyph names", + type=str, + help="Glyphs to show. Exclusive with text option", + ) + parser.add_argument( "--variations", metavar="AXIS=LOC", default="", @@ -241,6 +247,7 @@ def main(args=None): font = TTFont(options.font, fontNumber=fontNumber) text = options.text + glyphs = options.glyphs location = {} for tag_v in options.variations.split(): @@ -255,10 +262,17 @@ def main(args=None): glyphset = font.getGlyphSet(location=location) cmap = font["cmap"].getBestCmap() + if glyphs is not None and text is not None: + raise ValueError("Options --glyphs and --text are exclusive") + + if glyphs is None: + glyphs = " ".join(cmap[ord(u)] for u in text) + + glyphs = glyphs.split() + s = "" width = 0 - for u in text: - g = cmap[ord(u)] + for g in glyphs: glyph = glyphset[g] pen = SVGPathPen(glyphset) diff --git a/contrib/python/fonttools/fontTools/subset/__init__.py b/contrib/python/fonttools/fontTools/subset/__init__.py index 9b1758435c..250a07ef1a 100644 --- a/contrib/python/fonttools/fontTools/subset/__init__.py +++ b/contrib/python/fonttools/fontTools/subset/__init__.py @@ -3733,6 +3733,3 @@ __all__ = [ "parse_unicodes", "main", ] - -if __name__ == "__main__": - sys.exit(main()) diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/otTables.py b/contrib/python/fonttools/fontTools/ttLib/tables/otTables.py index 262f8d4187..3505f42337 100644 --- a/contrib/python/fonttools/fontTools/ttLib/tables/otTables.py +++ b/contrib/python/fonttools/fontTools/ttLib/tables/otTables.py @@ -1123,6 +1123,35 @@ class LigatureSubst(FormatSwitchingBaseTable): self.ligatures = ligatures del self.Format # Don't need this anymore + @staticmethod + def _getLigatureSortKey(components): + # Computes a key for ordering ligatures in a GSUB Type-4 lookup. + + # When building the OpenType lookup, we need to make sure that + # the longest sequence of components is listed first, so we + # use the negative length as the key for sorting. + # Note, we no longer need to worry about deterministic order because the + # ligature mapping `dict` remembers the insertion order, and this in + # turn depends on the order in which the ligatures are written in the FEA. + # Since python sort algorithm is stable, the ligatures of equal length + # will keep the relative order in which they appear in the feature file. + # For example, given the following ligatures (all starting with 'f' and + # thus belonging to the same LigatureSet): + # + # feature liga { + # sub f i by f_i; + # sub f f f by f_f_f; + # sub f f by f_f; + # sub f f i by f_f_i; + # } liga; + # + # this should sort to: f_f_f, f_f_i, f_i, f_f + # This is also what fea-rs does, see: + # https://github.com/adobe-type-tools/afdko/issues/1727 + # https://github.com/fonttools/fonttools/issues/3428 + # https://github.com/googlefonts/fontc/pull/680 + return -len(components) + def preWrite(self, font): self.Format = 1 ligatures = getattr(self, "ligatures", None) @@ -1135,13 +1164,11 @@ class LigatureSubst(FormatSwitchingBaseTable): # ligatures is map from components-sequence to lig-glyph newLigatures = dict() - for comps, lig in sorted( - ligatures.items(), key=lambda item: (-len(item[0]), item[0]) - ): + for comps in sorted(ligatures.keys(), key=self._getLigatureSortKey): ligature = Ligature() ligature.Component = comps[1:] ligature.CompCount = len(comps) - ligature.LigGlyph = lig + ligature.LigGlyph = ligatures[comps] newLigatures.setdefault(comps[0], []).append(ligature) ligatures = newLigatures diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/sbixGlyph.py b/contrib/python/fonttools/fontTools/ttLib/tables/sbixGlyph.py index fd687a1880..b744a2a3bc 100644 --- a/contrib/python/fonttools/fontTools/ttLib/tables/sbixGlyph.py +++ b/contrib/python/fonttools/fontTools/ttLib/tables/sbixGlyph.py @@ -54,6 +54,10 @@ class Glyph(object): # pad with spaces self.graphicType += " "[: (4 - len(self.graphicType))] + def is_reference_type(self): + """Returns True if this glyph is a reference to another glyph's image data.""" + return self.graphicType == "dupe" or self.graphicType == "flip" + def decompile(self, ttFont): self.glyphName = ttFont.getGlyphName(self.gid) if self.rawdata is None: @@ -71,7 +75,7 @@ class Glyph(object): sbixGlyphHeaderFormat, self.rawdata[:sbixGlyphHeaderFormatSize], self ) - if self.graphicType == "dupe": + if self.is_reference_type(): # this glyph is a reference to another glyph's image data (gid,) = struct.unpack(">H", self.rawdata[sbixGlyphHeaderFormatSize:]) self.referenceGlyphName = ttFont.getGlyphName(gid) @@ -94,7 +98,7 @@ class Glyph(object): rawdata = b"" else: rawdata = sstruct.pack(sbixGlyphHeaderFormat, self) - if self.graphicType == "dupe": + if self.is_reference_type(): rawdata += struct.pack(">H", ttFont.getGlyphID(self.referenceGlyphName)) else: assert self.imageData is not None @@ -117,8 +121,8 @@ class Glyph(object): originOffsetY=self.originOffsetY, ) xmlWriter.newline() - if self.graphicType == "dupe": - # graphicType == "dupe" is a reference to another glyph id. + if self.is_reference_type(): + # this glyph is a reference to another glyph id. xmlWriter.simpletag("ref", glyphname=self.referenceGlyphName) else: xmlWriter.begintag("hexdata") @@ -131,7 +135,7 @@ class Glyph(object): def fromXML(self, name, attrs, content, ttFont): if name == "ref": - # glyph is a "dupe", i.e. a reference to another glyph's image data. + # this glyph i.e. a reference to another glyph's image data. # in this case imageData contains the glyph id of the reference glyph # get glyph id from glyphname glyphname = safeEval("'''" + attrs["glyphname"] + "'''") diff --git a/contrib/python/fonttools/fontTools/varLib/__init__.py b/contrib/python/fonttools/fontTools/varLib/__init__.py index 46834f6433..b9af6dacbf 100644 --- a/contrib/python/fonttools/fontTools/varLib/__init__.py +++ b/contrib/python/fonttools/fontTools/varLib/__init__.py @@ -216,8 +216,6 @@ def _add_avar(font, axes, mappings, axisTags): if mappings: interesting = True - hiddenAxes = [axis for axis in axes.values() if axis.hidden] - inputLocations = [ { axes[name].tag: models.normalizeValue(v, vals_triples[axes[name].tag]) @@ -752,10 +750,14 @@ def _add_BASE(font, masterModel, master_ttfs, axisTags): def _merge_OTL(font, model, master_fonts, axisTags): + otl_tags = ["GSUB", "GDEF", "GPOS"] + if not any(tag in font for tag in otl_tags): + return + log.info("Merging OpenType Layout tables") merger = VariationMerger(model, axisTags, font) - merger.mergeTables(font, master_fonts, ["GSUB", "GDEF", "GPOS"]) + merger.mergeTables(font, master_fonts, otl_tags) store = merger.store_builder.finish() if not store: return diff --git a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py index d1cde0df7a..df55f0414d 100644 --- a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py +++ b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py @@ -614,7 +614,7 @@ def _instantiateGvarGlyph( if optimize: isComposite = glyf[glyphname].isComposite() for var in tupleVarStore: - var.optimize(coordinates, endPts, isComposite) + var.optimize(coordinates, endPts, isComposite=isComposite) def instantiateGvarGlyph(varfont, glyphname, axisLimits, optimize=True): diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make index 4d380b556c..35b91fddb1 100644 --- a/contrib/python/fonttools/ya.make +++ b/contrib/python/fonttools/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(4.47.2) +VERSION(4.48.1) LICENSE(MIT) @@ -41,6 +41,7 @@ PY_SRCS( fontTools/cu2qu/errors.py fontTools/cu2qu/ufo.py fontTools/designspaceLib/__init__.py + fontTools/designspaceLib/__main__.py fontTools/designspaceLib/split.py fontTools/designspaceLib/statNames.py fontTools/designspaceLib/types.py |