diff options
author | Elliott Hughes <enh@google.com> | 2022-09-01 01:05:19 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-09-01 01:05:19 +0000 |
commit | e47f00b7523ba5d28bc3654ef83125d8e7dc0e90 (patch) | |
tree | e2eb0b9f93f0a8cb6705199d2f738f35cba28e7f /Lib/fontTools/misc | |
parent | 312bed341d812d31e893eb9ed2b4bc0dd4da7fff (diff) | |
parent | 14ad209ddd0825427bd5f75052aa55f764b98891 (diff) | |
download | fonttools-e47f00b7523ba5d28bc3654ef83125d8e7dc0e90.tar.gz |
Upgrade fonttools to 4.37.1 am: ae8de171b8 am: ae308ea7c8 am: 14ad209ddd
Original change: https://android-review.googlesource.com/c/platform/external/fonttools/+/2193410
Change-Id: I6c8759cf0dc486db368dc2e7d569ddb3af37f018
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'Lib/fontTools/misc')
-rw-r--r-- | Lib/fontTools/misc/cliTools.py | 9 | ||||
-rw-r--r-- | Lib/fontTools/misc/symfont.py | 63 | ||||
-rw-r--r-- | Lib/fontTools/misc/treeTools.py | 45 | ||||
-rw-r--r-- | Lib/fontTools/misc/visitor.py | 143 |
4 files changed, 243 insertions, 17 deletions
diff --git a/Lib/fontTools/misc/cliTools.py b/Lib/fontTools/misc/cliTools.py index e8c17677..e7dadf98 100644 --- a/Lib/fontTools/misc/cliTools.py +++ b/Lib/fontTools/misc/cliTools.py @@ -6,7 +6,7 @@ import re numberAddedRE = re.compile(r"#\d+$") -def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False): +def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False, suffix=""): """Generates a suitable file name for writing output. Often tools will want to take a file, do some kind of transformation to it, @@ -14,6 +14,7 @@ def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False): output file, through one or more of the following steps: - changing the output directory + - appending suffix before file extension - replacing the file extension - suffixing the filename with a number (``#1``, ``#2``, etc.) to avoid overwriting an existing file. @@ -21,6 +22,8 @@ def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False): Args: input: Name of input file. outputDir: Optionally, a new directory to write the file into. + suffix: Optionally, a string suffix is appended to file name before + the extension. extension: Optionally, a replacement for the current file extension. overWrite: Overwriting an existing file is permitted if true; if false and the proposed filename exists, a new name will be generated by @@ -36,11 +39,11 @@ def makeOutputFileName(input, outputDir=None, extension=None, overWrite=False): fileName = numberAddedRE.split(fileName)[0] if extension is None: extension = os.path.splitext(input)[1] - output = os.path.join(dirName, fileName + extension) + output = os.path.join(dirName, fileName + suffix + extension) n = 1 if not overWrite: while os.path.exists(output): output = os.path.join( - dirName, fileName + "#" + repr(n) + extension) + dirName, fileName + suffix + "#" + repr(n) + extension) n += 1 return output diff --git a/Lib/fontTools/misc/symfont.py b/Lib/fontTools/misc/symfont.py index a1a87300..3ff2b5df 100644 --- a/Lib/fontTools/misc/symfont.py +++ b/Lib/fontTools/misc/symfont.py @@ -108,16 +108,34 @@ MomentYYPen = partial(GreenPen, func=y*y) MomentXYPen = partial(GreenPen, func=x*y) -def printGreenPen(penName, funcs, file=sys.stdout): +def printGreenPen(penName, funcs, file=sys.stdout, docstring=None): + + if docstring is not None: + print('"""%s"""' % docstring) print( -'''from fontTools.pens.basePen import BasePen +'''from fontTools.pens.basePen import BasePen, OpenContourError +try: + import cython +except ImportError: + # if cython not installed, use mock module with no-op decorators and types + from fontTools.misc import cython + +if cython.compiled: + # Yep, I'm compiled. + COMPILED = True +else: + # Just a lowly interpreted script. + COMPILED = False + + +__all__ = ["%s"] class %s(BasePen): def __init__(self, glyphset=None): BasePen.__init__(self, glyphset) -'''%penName, file=file) +'''% (penName, penName), file=file) for name,f in funcs: print(' self.%s = 0' % name, file=file) print(''' @@ -133,41 +151,58 @@ class %s(BasePen): p0 = self._getCurrentPoint() if p0 != self.__startPoint: # Green theorem is not defined on open contours. - raise NotImplementedError + raise OpenContourError( + "Green theorem is not defined on open contours." + ) ''', end='', file=file) for n in (1, 2, 3): + + subs = {P[i][j]: [X, Y][j][i] for i in range(n+1) for j in range(2)} + greens = [green(f, BezierCurve[n]) for name,f in funcs] + greens = [sp.gcd_terms(f.collect(sum(P,()))) for f in greens] # Optimize + greens = [f.subs(subs) for f in greens] # Convert to p to x/y + defs, exprs = sp.cse(greens, + optimizations='basic', + symbols=(sp.Symbol('r%d'%i) for i in count())) + + print() + for name,value in defs: + print(' @cython.locals(%s=cython.double)' % name, file=file) if n == 1: - print(''' + print('''\ + @cython.locals(x0=cython.double, y0=cython.double) + @cython.locals(x1=cython.double, y1=cython.double) def _lineTo(self, p1): x0,y0 = self._getCurrentPoint() x1,y1 = p1 ''', file=file) elif n == 2: - print(''' + print('''\ + @cython.locals(x0=cython.double, y0=cython.double) + @cython.locals(x1=cython.double, y1=cython.double) + @cython.locals(x2=cython.double, y2=cython.double) def _qCurveToOne(self, p1, p2): x0,y0 = self._getCurrentPoint() x1,y1 = p1 x2,y2 = p2 ''', file=file) elif n == 3: - print(''' + print('''\ + @cython.locals(x0=cython.double, y0=cython.double) + @cython.locals(x1=cython.double, y1=cython.double) + @cython.locals(x2=cython.double, y2=cython.double) + @cython.locals(x3=cython.double, y3=cython.double) def _curveToOne(self, p1, p2, p3): x0,y0 = self._getCurrentPoint() x1,y1 = p1 x2,y2 = p2 x3,y3 = p3 ''', file=file) - subs = {P[i][j]: [X, Y][j][i] for i in range(n+1) for j in range(2)} - greens = [green(f, BezierCurve[n]) for name,f in funcs] - greens = [sp.gcd_terms(f.collect(sum(P,()))) for f in greens] # Optimize - greens = [f.subs(subs) for f in greens] # Convert to p to x/y - defs, exprs = sp.cse(greens, - optimizations='basic', - symbols=(sp.Symbol('r%d'%i) for i in count())) for name,value in defs: print(' %s = %s' % (name, value), file=file) + print(file=file) for name,value in zip([f[0] for f in funcs], exprs): print(' self.%s += %s' % (name, value), file=file) diff --git a/Lib/fontTools/misc/treeTools.py b/Lib/fontTools/misc/treeTools.py new file mode 100644 index 00000000..24e10ba5 --- /dev/null +++ b/Lib/fontTools/misc/treeTools.py @@ -0,0 +1,45 @@ +"""Generic tools for working with trees.""" + +from math import ceil, log + + +def build_n_ary_tree(leaves, n): + """Build N-ary tree from sequence of leaf nodes. + + Return a list of lists where each non-leaf node is a list containing + max n nodes. + """ + if not leaves: + return [] + + assert n > 1 + + depth = ceil(log(len(leaves), n)) + + if depth <= 1: + return list(leaves) + + # Fully populate complete subtrees of root until we have enough leaves left + root = [] + unassigned = None + full_step = n ** (depth - 1) + for i in range(0, len(leaves), full_step): + subtree = leaves[i : i + full_step] + if len(subtree) < full_step: + unassigned = subtree + break + while len(subtree) > n: + subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)] + root.append(subtree) + + if unassigned: + # Recurse to fill the last subtree, which is the only partially populated one + subtree = build_n_ary_tree(unassigned, n) + if len(subtree) <= n - len(root): + # replace last subtree with its children if they can still fit + root.extend(subtree) + else: + root.append(subtree) + assert len(root) <= n + + return root diff --git a/Lib/fontTools/misc/visitor.py b/Lib/fontTools/misc/visitor.py new file mode 100644 index 00000000..3d28135f --- /dev/null +++ b/Lib/fontTools/misc/visitor.py @@ -0,0 +1,143 @@ +"""Generic visitor pattern implementation for Python objects.""" + +import enum + + +class Visitor(object): + + defaultStop = False + + @classmethod + def _register(celf, clazzes_attrs): + assert celf != Visitor, "Subclass Visitor instead." + if "_visitors" not in celf.__dict__: + celf._visitors = {} + + def wrapper(method): + assert method.__name__ == "visit" + for clazzes, attrs in clazzes_attrs: + if type(clazzes) != tuple: + clazzes = (clazzes,) + if type(attrs) == str: + attrs = (attrs,) + for clazz in clazzes: + _visitors = celf._visitors.setdefault(clazz, {}) + for attr in attrs: + assert attr not in _visitors, ( + "Oops, class '%s' has visitor function for '%s' defined already." + % (clazz.__name__, attr) + ) + _visitors[attr] = method + return None + + return wrapper + + @classmethod + def register(celf, clazzes): + if type(clazzes) != tuple: + clazzes = (clazzes,) + return celf._register([(clazzes, (None,))]) + + @classmethod + def register_attr(celf, clazzes, attrs): + clazzes_attrs = [] + if type(clazzes) != tuple: + clazzes = (clazzes,) + if type(attrs) == str: + attrs = (attrs,) + for clazz in clazzes: + clazzes_attrs.append((clazz, attrs)) + return celf._register(clazzes_attrs) + + @classmethod + def register_attrs(celf, clazzes_attrs): + return celf._register(clazzes_attrs) + + @classmethod + def _visitorsFor(celf, thing, _default={}): + typ = type(thing) + + for celf in celf.mro(): + + _visitors = getattr(celf, "_visitors", None) + if _visitors is None: + break + + m = celf._visitors.get(typ, None) + if m is not None: + return m + + return _default + + def visitObject(self, obj, *args, **kwargs): + """Called to visit an object. This function loops over all non-private + attributes of the objects and calls any user-registered (via + @register_attr() or @register_attrs()) visit() functions. + + If there is no user-registered visit function, of if there is and it + returns True, or it returns None (or doesn't return anything) and + visitor.defaultStop is False (default), then the visitor will proceed + to call self.visitAttr()""" + + keys = sorted(vars(obj).keys()) + _visitors = self._visitorsFor(obj) + defaultVisitor = _visitors.get("*", None) + for key in keys: + if key[0] == "_": + continue + value = getattr(obj, key) + visitorFunc = _visitors.get(key, defaultVisitor) + if visitorFunc is not None: + ret = visitorFunc(self, obj, key, value, *args, **kwargs) + if ret == False or (ret is None and self.defaultStop): + continue + self.visitAttr(obj, key, value, *args, **kwargs) + + def visitAttr(self, obj, attr, value, *args, **kwargs): + """Called to visit an attribute of an object.""" + self.visit(value, *args, **kwargs) + + def visitList(self, obj, *args, **kwargs): + """Called to visit any value that is a list.""" + for value in obj: + self.visit(value, *args, **kwargs) + + def visitDict(self, obj, *args, **kwargs): + """Called to visit any value that is a dictionary.""" + for value in obj.values(): + self.visit(value, *args, **kwargs) + + def visitLeaf(self, obj, *args, **kwargs): + """Called to visit any value that is not an object, list, + or dictionary.""" + pass + + def visit(self, obj, *args, **kwargs): + """This is the main entry to the visitor. The visitor will visit object + obj. + + The visitor will first determine if there is a registered (via + @register()) visit function for the type of object. If there is, it + will be called, and (visitor, obj, *args, **kwargs) will be passed to + the user visit function. + + If there is no user-registered visit function, of if there is and it + returns True, or it returns None (or doesn't return anything) and + visitor.defaultStop is False (default), then the visitor will proceed + to dispatch to one of self.visitObject(), self.visitList(), + self.visitDict(), or self.visitLeaf() (any of which can be overriden in + a subclass).""" + + visitorFunc = self._visitorsFor(obj).get(None, None) + if visitorFunc is not None: + ret = visitorFunc(self, obj, *args, **kwargs) + if ret == False or (ret is None and self.defaultStop): + return + if hasattr(obj, "__dict__") and not isinstance(obj, enum.Enum): + self.visitObject(obj, *args, **kwargs) + elif isinstance(obj, list): + self.visitList(obj, *args, **kwargs) + elif isinstance(obj, dict): + self.visitDict(obj, *args, **kwargs) + else: + self.visitLeaf(obj, *args, **kwargs) |