aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/misc
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2022-09-01 01:05:19 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-09-01 01:05:19 +0000
commite47f00b7523ba5d28bc3654ef83125d8e7dc0e90 (patch)
treee2eb0b9f93f0a8cb6705199d2f738f35cba28e7f /Lib/fontTools/misc
parent312bed341d812d31e893eb9ed2b4bc0dd4da7fff (diff)
parent14ad209ddd0825427bd5f75052aa55f764b98891 (diff)
downloadfonttools-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.py9
-rw-r--r--Lib/fontTools/misc/symfont.py63
-rw-r--r--Lib/fontTools/misc/treeTools.py45
-rw-r--r--Lib/fontTools/misc/visitor.py143
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)