Merge pull request #2718 from fonttools/visitor
Add fontTools.misc.visitor, fontTools.ttLib.ttVisitor, ttLib.scaleUpem, Snippets/print-json.py
This commit is contained in:
commit
376caff386
143
Lib/fontTools/misc/visitor.py
Normal file
143
Lib/fontTools/misc/visitor.py
Normal file
@ -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)
|
219
Lib/fontTools/ttLib/scaleUpem.py
Normal file
219
Lib/fontTools/ttLib/scaleUpem.py
Normal file
@ -0,0 +1,219 @@
|
||||
"""Change the units-per-EM of a font.
|
||||
|
||||
Currently does not support CFF fonts. AAT, Graphite tables are not supported
|
||||
either."""
|
||||
|
||||
|
||||
from fontTools.ttLib.ttVisitor import TTVisitor
|
||||
import fontTools.ttLib as ttLib
|
||||
import fontTools.ttLib.tables.otBase as otBase
|
||||
import fontTools.ttLib.tables.otTables as otTables
|
||||
from fontTools.misc.fixedTools import otRound
|
||||
|
||||
|
||||
__all__ = ["scale_upem", "ScalerVisitor"]
|
||||
|
||||
|
||||
class ScalerVisitor(TTVisitor):
|
||||
def __init__(self, scaleFactor):
|
||||
self.scaleFactor = scaleFactor
|
||||
|
||||
def scale(self, v):
|
||||
return otRound(v * self.scaleFactor)
|
||||
|
||||
|
||||
@ScalerVisitor.register_attrs(
|
||||
(
|
||||
(ttLib.getTableClass("head"), ("unitsPerEm", "xMin", "yMin", "xMax", "yMax")),
|
||||
(
|
||||
ttLib.getTableClass("hhea"),
|
||||
(
|
||||
"ascent",
|
||||
"descent",
|
||||
"lineGap",
|
||||
"advanceWidthMax",
|
||||
"minLeftSideBearing",
|
||||
"minRightSideBearing",
|
||||
"xMaxExtent",
|
||||
"caretOffset",
|
||||
),
|
||||
),
|
||||
(
|
||||
ttLib.getTableClass("vhea"),
|
||||
(
|
||||
"ascent",
|
||||
"descent",
|
||||
"lineGap",
|
||||
"advanceHeightMax",
|
||||
"minTopSideBearing",
|
||||
"minBottomSideBearing",
|
||||
"yMaxExtent",
|
||||
"caretOffset",
|
||||
),
|
||||
),
|
||||
(
|
||||
ttLib.getTableClass("OS/2"),
|
||||
(
|
||||
"xAvgCharWidth",
|
||||
"ySubscriptXSize",
|
||||
"ySubscriptYSize",
|
||||
"ySubscriptXOffset",
|
||||
"ySubscriptYOffset",
|
||||
"ySuperscriptXSize",
|
||||
"ySuperscriptYSize",
|
||||
"ySuperscriptXOffset",
|
||||
"ySuperscriptYOffset",
|
||||
"yStrikeoutSize",
|
||||
"yStrikeoutPosition",
|
||||
"sTypoAscender",
|
||||
"sTypoDescender",
|
||||
"sTypoLineGap",
|
||||
"usWinAscent",
|
||||
"usWinDescent",
|
||||
"sxHeight",
|
||||
"sCapHeight",
|
||||
),
|
||||
),
|
||||
(otTables.ValueRecord, ("XAdvance", "YAdvance", "XPlacement", "YPlacement")),
|
||||
(otTables.Anchor, ("XCoordinate", "YCoordinate")),
|
||||
(otTables.CaretValue, ("Coordinate")),
|
||||
(otTables.BaseCoord, ("Coordinate")),
|
||||
(otTables.ClipBox, ("xMin", "yMin", "xMax", "yMax")),
|
||||
)
|
||||
)
|
||||
def visit(visitor, obj, attr, value):
|
||||
setattr(obj, attr, visitor.scale(value))
|
||||
|
||||
|
||||
@ScalerVisitor.register_attr(
|
||||
(ttLib.getTableClass("hmtx"), ttLib.getTableClass("vmtx")), "metrics"
|
||||
)
|
||||
def visit(visitor, obj, attr, metrics):
|
||||
for g in metrics:
|
||||
advance, lsb = metrics[g]
|
||||
metrics[g] = visitor.scale(advance), visitor.scale(lsb)
|
||||
|
||||
|
||||
@ScalerVisitor.register_attr(ttLib.getTableClass("glyf"), "glyphs")
|
||||
def visit(visitor, obj, attr, glyphs):
|
||||
for g in glyphs.values():
|
||||
if g.isComposite():
|
||||
for component in g.components:
|
||||
component.x = visitor.scale(component.x)
|
||||
component.y = visitor.scale(component.y)
|
||||
else:
|
||||
for attr in ("xMin", "xMax", "yMin", "yMax"):
|
||||
v = getattr(g, attr, None)
|
||||
if v is not None:
|
||||
setattr(g, attr, visitor.scale(v))
|
||||
|
||||
glyf = visitor.font["glyf"]
|
||||
coordinates = g.getCoordinates(glyf)[0]
|
||||
for i, (x, y) in enumerate(coordinates):
|
||||
coordinates[i] = visitor.scale(x), visitor.scale(y)
|
||||
|
||||
|
||||
@ScalerVisitor.register_attr(ttLib.getTableClass("gvar"), "variations")
|
||||
def visit(visitor, obj, attr, variations):
|
||||
for varlist in variations.values():
|
||||
for var in varlist:
|
||||
coordinates = var.coordinates
|
||||
for i, xy in enumerate(coordinates):
|
||||
if xy is None:
|
||||
continue
|
||||
coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])
|
||||
|
||||
|
||||
@ScalerVisitor.register_attr(ttLib.getTableClass("kern"), "kernTables")
|
||||
def visit(visitor, obj, attr, kernTables):
|
||||
for table in kernTables:
|
||||
kernTable = table.kernTable
|
||||
for k in kernTable.keys():
|
||||
kernTable[k] = visitor.scale(kernTable[k])
|
||||
|
||||
|
||||
# ItemVariationStore
|
||||
|
||||
|
||||
@ScalerVisitor.register(otTables.VarData)
|
||||
def visit(visitor, varData):
|
||||
for item in varData.Item:
|
||||
for i, v in enumerate(item):
|
||||
item[i] = visitor.scale(v)
|
||||
|
||||
|
||||
# COLRv1
|
||||
|
||||
|
||||
@ScalerVisitor.register(otTables.BaseGlyphPaintRecord)
|
||||
def visit(visitor, record):
|
||||
oldPaint = record.Paint
|
||||
|
||||
transform = otTables.Affine2x3()
|
||||
transform.populateDefaults()
|
||||
transform.xy = transform.yx = transform.dx = transform.dy = 0
|
||||
transform.xx = transform.yy = visitor.scaleFactor
|
||||
|
||||
scale = otTables.Paint()
|
||||
scale.Format = 12 # PaintTransform
|
||||
scale.Transform = transform
|
||||
scale.Paint = oldPaint
|
||||
record.Paint = scale
|
||||
return True
|
||||
|
||||
|
||||
@ScalerVisitor.register(otTables.Paint)
|
||||
def visit(visitor, paint):
|
||||
if paint.Format != 10: # PaintGlyph
|
||||
return True
|
||||
|
||||
newPaint = otTables.Paint()
|
||||
newPaint.Format = paint.Format
|
||||
newPaint.Paint = paint.Paint
|
||||
newPaint.Glyph = paint.Glyph
|
||||
del paint.Paint
|
||||
del paint.Glyph
|
||||
|
||||
transform = otTables.Affine2x3()
|
||||
transform.xy = transform.yx = transform.dx = transform.dy = 0
|
||||
transform.xx = transform.yy = 1 / visitor.scaleFactor
|
||||
|
||||
paint.Format = 12 # PaintTransform
|
||||
paint.Transform = transform
|
||||
paint.Paint = newPaint
|
||||
|
||||
visitor.visit(newPaint.Paint)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def scale_upem(font, new_upem):
|
||||
"""Change the units-per-EM of font to the new value."""
|
||||
upem = font["head"].unitsPerEm
|
||||
visitor = ScalerVisitor(new_upem / upem)
|
||||
visitor.visit(font)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from fontTools.ttLib import TTFont
|
||||
import sys
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("usage: fonttools ttLib.scaleUpem font new-upem")
|
||||
sys.exit()
|
||||
|
||||
font = TTFont(sys.argv[1])
|
||||
new_upem = int(sys.argv[2])
|
||||
|
||||
if "CFF " in font or "CFF2" in font:
|
||||
print(
|
||||
"fonttools ttLib.scaleUpem: CFF/CFF2 fonts are not supported.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
scale_upem(font, new_upem)
|
||||
|
||||
print("Writing out.ttf")
|
||||
font.save("out.ttf")
|
@ -398,12 +398,17 @@ class BitmapGlyph(object):
|
||||
# Allow lazy decompile.
|
||||
if attr[:2] == '__':
|
||||
raise AttributeError(attr)
|
||||
if not hasattr(self, "data"):
|
||||
if attr == "data":
|
||||
raise AttributeError(attr)
|
||||
self.decompile()
|
||||
del self.data
|
||||
return getattr(self, attr)
|
||||
|
||||
def ensureDecompiled(self, recurse=False):
|
||||
if hasattr(self, "data"):
|
||||
self.decompile()
|
||||
del self.data
|
||||
|
||||
# Not a fan of this but it is needed for safer safety checking.
|
||||
def getFormat(self):
|
||||
return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix):])
|
||||
|
@ -338,11 +338,15 @@ class EblcIndexSubTable(object):
|
||||
# Allow lazy decompile.
|
||||
if attr[:2] == '__':
|
||||
raise AttributeError(attr)
|
||||
if not hasattr(self, "data"):
|
||||
if attr == "data":
|
||||
raise AttributeError(attr)
|
||||
self.decompile()
|
||||
return getattr(self, attr)
|
||||
|
||||
def ensureDecompiled(self, recurse=False):
|
||||
if hasattr(self, "data"):
|
||||
self.decompile()
|
||||
|
||||
# This method just takes care of the indexSubHeader. Implementing subclasses
|
||||
# should call it to compile the indexSubHeader and then continue compiling
|
||||
# the remainder of their unique format.
|
||||
|
@ -164,7 +164,9 @@ class table__c_m_a_p(DefaultTable.DefaultTable):
|
||||
if ttFont.lazy is False: # Be lazy for None and True
|
||||
self.ensureDecompiled()
|
||||
|
||||
def ensureDecompiled(self):
|
||||
def ensureDecompiled(self, recurse=False):
|
||||
# The recurse argument is unused, but part of the signature of
|
||||
# ensureDecompiled across the library.
|
||||
for st in self.tables:
|
||||
st.ensureDecompiled()
|
||||
|
||||
@ -241,7 +243,9 @@ class CmapSubtable(object):
|
||||
self.platEncID = None #: The encoding ID of this subtable (interpretation depends on ``platformID``)
|
||||
self.language = None #: The language ID of this subtable (Macintosh platform only)
|
||||
|
||||
def ensureDecompiled(self):
|
||||
def ensureDecompiled(self, recurse=False):
|
||||
# The recurse argument is unused, but part of the signature of
|
||||
# ensureDecompiled across the library.
|
||||
if self.data is None:
|
||||
return
|
||||
self.decompile(None, None) # use saved data.
|
||||
|
@ -112,7 +112,9 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
||||
if ttFont.lazy is False: # Be lazy for None and True
|
||||
self.ensureDecompiled()
|
||||
|
||||
def ensureDecompiled(self):
|
||||
def ensureDecompiled(self, recurse=False):
|
||||
# The recurse argument is unused, but part of the signature of
|
||||
# ensureDecompiled across the library.
|
||||
for glyph in self.glyphs.values():
|
||||
glyph.expand(self)
|
||||
|
||||
|
@ -386,12 +386,14 @@ class TTFont(object):
|
||||
keys = sortedTagList(keys)
|
||||
return ["GlyphOrder"] + keys
|
||||
|
||||
def ensureDecompiled(self):
|
||||
def ensureDecompiled(self, recurse=None):
|
||||
"""Decompile all the tables, even if a TTFont was opened in 'lazy' mode."""
|
||||
for tag in self.keys():
|
||||
table = self[tag]
|
||||
if self.lazy is not False and hasattr(table, "ensureDecompiled"):
|
||||
table.ensureDecompiled()
|
||||
if recurse is None:
|
||||
recurse = self.lazy is not False
|
||||
if recurse and hasattr(table, "ensureDecompiled"):
|
||||
table.ensureDecompiled(recurse=recurse)
|
||||
self.lazy = False
|
||||
|
||||
def __len__(self):
|
||||
|
32
Lib/fontTools/ttLib/ttVisitor.py
Normal file
32
Lib/fontTools/ttLib/ttVisitor.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""Specialization of fontTools.misc.visitor to work with TTFont."""
|
||||
|
||||
from fontTools.misc.visitor import Visitor
|
||||
from fontTools.ttLib import TTFont
|
||||
|
||||
|
||||
class TTVisitor(Visitor):
|
||||
def visitAttr(self, obj, attr, value, *args, **kwargs):
|
||||
if isinstance(value, TTFont):
|
||||
return False
|
||||
super().visitAttr(obj, attr, value, *args, **kwargs)
|
||||
|
||||
def visit(self, obj, *args, **kwargs):
|
||||
if hasattr(obj, "ensureDecompiled"):
|
||||
obj.ensureDecompiled(recurse=False)
|
||||
super().visit(obj, *args, **kwargs)
|
||||
|
||||
|
||||
@TTVisitor.register(TTFont)
|
||||
def visit(visitor, font, *args, **kwargs):
|
||||
# Some objects have links back to TTFont; even though we
|
||||
# have a check in visitAttr to stop them from recursing
|
||||
# onto TTFont, sometimes they still do, for example when
|
||||
# someone overrides visitAttr.
|
||||
if hasattr(visitor, "font"):
|
||||
return False
|
||||
|
||||
visitor.font = font
|
||||
for tag in font.keys():
|
||||
visitor.visit(font[tag], *args, **kwargs)
|
||||
del visitor.font
|
||||
return False
|
153
Snippets/print-json.py
Normal file
153
Snippets/print-json.py
Normal file
@ -0,0 +1,153 @@
|
||||
import fontTools.ttLib as ttLib
|
||||
from fontTools.ttLib.ttVisitor import TTVisitor
|
||||
from fontTools.misc.textTools import Tag
|
||||
from array import array
|
||||
|
||||
|
||||
class JsonVisitor(TTVisitor):
|
||||
def _open(self, s):
|
||||
print(s, file=self.file)
|
||||
self._indent += self.indent
|
||||
self.comma = False
|
||||
|
||||
def _close(self, s):
|
||||
self._indent = self._indent[: -len(self.indent)]
|
||||
print("\n%s%s" % (self._indent, s), end="", file=self.file)
|
||||
self.comma = True
|
||||
|
||||
def __init__(self, file, indent=" "):
|
||||
self.file = file
|
||||
self.indent = indent
|
||||
self._indent = ""
|
||||
|
||||
def visitObject(self, obj):
|
||||
self._open("{")
|
||||
super().visitObject(obj)
|
||||
if self.comma:
|
||||
print(",", end="", file=self.file)
|
||||
print(
|
||||
'\n%s"type": "%s"' % (self._indent, obj.__class__.__name__),
|
||||
end="",
|
||||
file=self.file,
|
||||
)
|
||||
self._close("}")
|
||||
|
||||
def visitAttr(self, obj, attr, value):
|
||||
if self.comma:
|
||||
print(",", file=self.file)
|
||||
print('%s"%s": ' % (self._indent, attr), end="", file=self.file)
|
||||
self.visit(value)
|
||||
self.comma = True
|
||||
|
||||
def visitList(self, obj, *args, **kwargs):
|
||||
self._open("[")
|
||||
comma = False
|
||||
for value in obj:
|
||||
if comma:
|
||||
print(",", end="", file=self.file)
|
||||
print(file=self.file)
|
||||
print(self._indent, end="", file=self.file)
|
||||
self.visit(value, *args, **kwargs)
|
||||
comma = True
|
||||
self._close("]")
|
||||
|
||||
def visitDict(self, obj, *args, **kwargs):
|
||||
self._open("{")
|
||||
comma = False
|
||||
for key, value in obj.items():
|
||||
if comma:
|
||||
print(",", end="", file=self.file)
|
||||
print(file=self.file)
|
||||
print('%s"%s": ' % (self._indent, key), end="", file=self.file)
|
||||
self.visit(value, *args, **kwargs)
|
||||
comma = True
|
||||
self._close("}")
|
||||
|
||||
def visitLeaf(self, obj):
|
||||
if isinstance(obj, tuple):
|
||||
obj = list(obj)
|
||||
elif isinstance(obj, bytes):
|
||||
obj = list(obj)
|
||||
|
||||
if obj is None:
|
||||
s = "null"
|
||||
elif obj is True:
|
||||
s = "true"
|
||||
elif obj is False:
|
||||
s = "false"
|
||||
else:
|
||||
s = repr(obj)
|
||||
|
||||
if s[0] == "'":
|
||||
s = '"' + s[1:-1] + '"'
|
||||
|
||||
print("%s" % s, end="", file=self.file)
|
||||
|
||||
|
||||
@JsonVisitor.register(ttLib.TTFont)
|
||||
def visit(self, font):
|
||||
if hasattr(visitor, "font"):
|
||||
print("{}", end="", file=self.file)
|
||||
return False
|
||||
visitor.font = font
|
||||
|
||||
self._open("{")
|
||||
for tag in font.keys():
|
||||
if self.comma:
|
||||
print(",", file=self.file)
|
||||
print('\n%s"%s": ' % (self._indent, tag), end="", file=self.file)
|
||||
visitor.visit(font[tag])
|
||||
self._close("}")
|
||||
|
||||
del visitor.font
|
||||
return False
|
||||
|
||||
|
||||
@JsonVisitor.register(ttLib.GlyphOrder)
|
||||
def visit(self, obj):
|
||||
self.visitList(self.font.getGlyphOrder())
|
||||
return False
|
||||
|
||||
|
||||
@JsonVisitor.register_attr(ttLib.getTableClass("glyf"), "glyphOrder")
|
||||
def visit(visitor, obj, attr, value):
|
||||
return False
|
||||
|
||||
|
||||
@JsonVisitor.register(ttLib.getTableModule("glyf").GlyphCoordinates)
|
||||
def visit(self, obj):
|
||||
self.visitList(obj)
|
||||
return False
|
||||
|
||||
|
||||
@JsonVisitor.register(Tag)
|
||||
def visit(self, obj):
|
||||
print('"%s"' % str(obj), end="", file=self.file)
|
||||
return False
|
||||
|
||||
|
||||
@JsonVisitor.register(array)
|
||||
def visit(self, obj):
|
||||
self.visitList(obj)
|
||||
return False
|
||||
|
||||
|
||||
@JsonVisitor.register(bytearray)
|
||||
def visit(self, obj):
|
||||
self.visitList(obj)
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from fontTools.ttLib import TTFont
|
||||
import sys
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: print-json.py font")
|
||||
sys.exit()
|
||||
|
||||
font = TTFont(sys.argv[1])
|
||||
|
||||
visitor = JsonVisitor(sys.stdout)
|
||||
visitor.visit(font)
|
72
Tests/misc/visitor_test.py
Normal file
72
Tests/misc/visitor_test.py
Normal file
@ -0,0 +1,72 @@
|
||||
from fontTools.misc.visitor import Visitor
|
||||
import enum
|
||||
import pytest
|
||||
|
||||
|
||||
class E(enum.Enum):
|
||||
E1 = 1
|
||||
E2 = 2
|
||||
E3 = 3
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
self.b = [2, 3]
|
||||
self.c = {4: 5, 6: 7}
|
||||
self._d = 8
|
||||
self.e = E.E2
|
||||
self.f = 10
|
||||
|
||||
|
||||
class B:
|
||||
def __init__(self):
|
||||
self.a = A()
|
||||
|
||||
|
||||
class TestVisitor(Visitor):
|
||||
def __init__(self):
|
||||
self.value = []
|
||||
|
||||
def _add(self, s):
|
||||
self.value.append(s)
|
||||
|
||||
def visitLeaf(self, obj):
|
||||
self._add(obj)
|
||||
super().visitLeaf(obj)
|
||||
|
||||
|
||||
@TestVisitor.register(A)
|
||||
def visit(self, obj):
|
||||
self._add("A")
|
||||
|
||||
|
||||
@TestVisitor.register_attrs([(A, "e")])
|
||||
def visit(self, obj, attr, value):
|
||||
self._add(attr)
|
||||
self._add(value)
|
||||
return False
|
||||
|
||||
|
||||
@TestVisitor.register(B)
|
||||
def visit(self, obj):
|
||||
self._add("B")
|
||||
self.visitObject(obj)
|
||||
return False
|
||||
|
||||
|
||||
@TestVisitor.register_attr(B, "a")
|
||||
def visit(self, obj, attr, value):
|
||||
self._add("B a")
|
||||
|
||||
|
||||
class VisitorTest(object):
|
||||
def test_visitor(self):
|
||||
b = B()
|
||||
visitor = TestVisitor()
|
||||
visitor.visit(b)
|
||||
assert visitor.value == ["B", "B a", "A", 1, 2, 3, 5, 7, "e", E.E2, 10]
|
||||
|
||||
visitor.value = []
|
||||
visitor.defaultStop = True
|
||||
visitor.visit(b)
|
||||
assert visitor.value == ["B", "B a"]
|
39
Tests/ttLib/ttVisitor_test.py
Normal file
39
Tests/ttLib/ttVisitor_test.py
Normal file
@ -0,0 +1,39 @@
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.ttLib.ttVisitor import TTVisitor
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
||||
class TestVisitor(TTVisitor):
|
||||
def __init__(self):
|
||||
self.value = []
|
||||
self.depth = 0
|
||||
|
||||
def _add(self, s):
|
||||
self.value.append(s)
|
||||
|
||||
def visit(self, obj, target_depth):
|
||||
if self.depth == target_depth:
|
||||
self._add(obj)
|
||||
self.depth += 1
|
||||
super().visit(obj, target_depth)
|
||||
self.depth -= 1
|
||||
|
||||
|
||||
class TTVisitorTest(object):
|
||||
|
||||
@staticmethod
|
||||
def getpath(testfile):
|
||||
path = os.path.dirname(__file__)
|
||||
return os.path.join(path, "data", testfile)
|
||||
|
||||
def test_ttvisitor(self):
|
||||
|
||||
font = TTFont(self.getpath("TestVGID-Regular.otf"))
|
||||
visitor = TestVisitor()
|
||||
|
||||
# Count number of objects at depth 1:
|
||||
# That is, number of font tables, including GlyphOrder.
|
||||
visitor.visit(font, 1)
|
||||
|
||||
assert len(visitor.value) == 14
|
Loading…
x
Reference in New Issue
Block a user