Reformat with black

This commit is contained in:
Simon Cozens 2020-11-20 10:02:46 +00:00
parent 129e2413a0
commit 08e2a6cdc1

View File

@ -13,209 +13,260 @@ import itertools
class PerContourPen(BasePen): class PerContourPen(BasePen):
def __init__(self, Pen, glyphset=None): def __init__(self, Pen, glyphset=None):
BasePen.__init__(self, glyphset) BasePen.__init__(self, glyphset)
self._glyphset = glyphset self._glyphset = glyphset
self._Pen = Pen self._Pen = Pen
self._pen = None self._pen = None
self.value = [] self.value = []
def _moveTo(self, p0):
self._newItem() def _moveTo(self, p0):
self._pen.moveTo(p0) self._newItem()
def _lineTo(self, p1): self._pen.moveTo(p0)
self._pen.lineTo(p1)
def _qCurveToOne(self, p1, p2): def _lineTo(self, p1):
self._pen.qCurveTo(p1, p2) self._pen.lineTo(p1)
def _curveToOne(self, p1, p2, p3):
self._pen.curveTo(p1, p2, p3) def _qCurveToOne(self, p1, p2):
def _closePath(self): self._pen.qCurveTo(p1, p2)
self._pen.closePath()
self._pen = None def _curveToOne(self, p1, p2, p3):
def _endPath(self): self._pen.curveTo(p1, p2, p3)
self._pen.endPath()
self._pen = None def _closePath(self):
self._pen.closePath()
self._pen = None
def _endPath(self):
self._pen.endPath()
self._pen = None
def _newItem(self):
self._pen = pen = self._Pen()
self.value.append(pen)
def _newItem(self):
self._pen = pen = self._Pen()
self.value.append(pen)
class PerContourOrComponentPen(PerContourPen): class PerContourOrComponentPen(PerContourPen):
def addComponent(self, glyphName, transformation):
def addComponent(self, glyphName, transformation): self._newItem()
self._newItem() self.value[-1].addComponent(glyphName, transformation)
self.value[-1].addComponent(glyphName, transformation)
def _vdiff(v0, v1): def _vdiff(v0, v1):
return tuple(b-a for a,b in zip(v0,v1)) return tuple(b - a for a, b in zip(v0, v1))
def _vlen(vec): def _vlen(vec):
v = 0 v = 0
for x in vec: for x in vec:
v += x*x v += x * x
return v return v
def _matching_cost(G, matching): def _matching_cost(G, matching):
return sum(G[i][j] for i,j in enumerate(matching)) return sum(G[i][j] for i, j in enumerate(matching))
def min_cost_perfect_bipartite_matching(G): def min_cost_perfect_bipartite_matching(G):
n = len(G) n = len(G)
try: try:
from scipy.optimize import linear_sum_assignment from scipy.optimize import linear_sum_assignment
rows, cols = linear_sum_assignment(G)
assert (rows == list(range(n))).all()
return list(cols), _matching_cost(G, cols)
except ImportError:
pass
try: rows, cols = linear_sum_assignment(G)
from munkres import Munkres assert (rows == list(range(n))).all()
cols = [None] * n return list(cols), _matching_cost(G, cols)
for row,col in Munkres().compute(G): except ImportError:
cols[row] = col pass
return cols, _matching_cost(G, cols)
except ImportError:
pass
if n > 6: try:
raise Exception("Install Python module 'munkres' or 'scipy >= 0.17.0'") from munkres import Munkres
# Otherwise just brute-force cols = [None] * n
permutations = itertools.permutations(range(n)) for row, col in Munkres().compute(G):
best = list(next(permutations)) cols[row] = col
best_cost = _matching_cost(G, best) return cols, _matching_cost(G, cols)
for p in permutations: except ImportError:
cost = _matching_cost(G, p) pass
if cost < best_cost:
best, best_cost = list(p), cost if n > 6:
return best, best_cost raise Exception("Install Python module 'munkres' or 'scipy >= 0.17.0'")
# Otherwise just brute-force
permutations = itertools.permutations(range(n))
best = list(next(permutations))
best_cost = _matching_cost(G, best)
for p in permutations:
cost = _matching_cost(G, p)
if cost < best_cost:
best, best_cost = list(p), cost
return best, best_cost
def test(glyphsets, glyphs=None, names=None): def test(glyphsets, glyphs=None, names=None):
if names is None: if names is None:
names = glyphsets names = glyphsets
if glyphs is None: if glyphs is None:
glyphs = glyphsets[0].keys() glyphs = glyphsets[0].keys()
hist = [] hist = []
for glyph_name in glyphs: for glyph_name in glyphs:
#print() # print()
#print(glyph_name) # print(glyph_name)
try: try:
allVectors = [] allVectors = []
allNodeTypes = [] allNodeTypes = []
for glyphset,name in zip(glyphsets, names): for glyphset, name in zip(glyphsets, names):
#print('.', end='') # print('.', end='')
if glyph_name not in glyphset: if glyph_name not in glyphset:
print('%s: Glyph cannot be interpolated - does not exist in master %s' % (glyph_name, name)) print(
continue "%s: Glyph cannot be interpolated - does not exist in master %s"
glyph = glyphset[glyph_name] % (glyph_name, name)
)
continue
glyph = glyphset[glyph_name]
perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset) perContourPen = PerContourOrComponentPen(
glyph.draw(perContourPen) RecordingPen, glyphset=glyphset
contourPens = perContourPen.value )
del perContourPen glyph.draw(perContourPen)
contourPens = perContourPen.value
del perContourPen
contourVectors = [] contourVectors = []
nodeTypes = [] nodeTypes = []
allNodeTypes.append(nodeTypes) allNodeTypes.append(nodeTypes)
allVectors.append(contourVectors) allVectors.append(contourVectors)
for contour in contourPens: for contour in contourPens:
nodeTypes.append(tuple(instruction[0] for instruction in contour.value)) nodeTypes.append(
stats = StatisticsPen(glyphset=glyphset) tuple(instruction[0] for instruction in contour.value)
try: )
contour.replay(stats) stats = StatisticsPen(glyphset=glyphset)
except NotImplementedError as e: try:
print('%s: Glyph cannot be interpolated - open path!' % glyph_name) contour.replay(stats)
break except NotImplementedError as e:
size = abs(stats.area) ** .5 * .5 print(
vector = ( "%s: Glyph cannot be interpolated - open path!" % glyph_name
int(size), )
int(stats.meanX), break
int(stats.meanY), size = abs(stats.area) ** 0.5 * 0.5
int(stats.stddevX * 2), vector = (
int(stats.stddevY * 2), int(size),
int(stats.correlation * size), int(stats.meanX),
) int(stats.meanY),
contourVectors.append(vector) int(stats.stddevX * 2),
#print(vector) int(stats.stddevY * 2),
int(stats.correlation * size),
)
contourVectors.append(vector)
# print(vector)
# Check each master against the next one in the list. # Check each master against the next one in the list.
for i, (m0, m1) in enumerate(zip(allNodeTypes[:-1], allNodeTypes[1:])): for i, (m0, m1) in enumerate(zip(allNodeTypes[:-1], allNodeTypes[1:])):
if len(m0) != len(m1): if len(m0) != len(m1):
print('%s: %s+%s: Glyphs not compatible (wrong number of paths %i+%i)!!!!!' % (glyph_name, names[i], names[i+1], len(m0), len(m1))) print(
if m0 == m1: "%s: %s+%s: Glyphs not compatible (wrong number of paths %i+%i)!!!!!"
continue % (glyph_name, names[i], names[i + 1], len(m0), len(m1))
for pathIx, (nodes1, nodes2) in enumerate(zip(m0, m1)): )
if nodes1 == nodes2: if m0 == m1:
continue continue
print('%s: %s+%s: Glyphs not compatible at path %i!!!!!' % (glyph_name, names[i], names[i+1], pathIx)) for pathIx, (nodes1, nodes2) in enumerate(zip(m0, m1)):
if len(nodes1) != len(nodes2): if nodes1 == nodes2:
print("%s has %i nodes, %s has %i nodes" % (names[i], len(nodes1), names[i+1], len(nodes2))) continue
continue print(
for nodeIx, (n1, n2) in enumerate(zip(nodes1, nodes2)): "%s: %s+%s: Glyphs not compatible at path %i!!!!!"
if n1 != n2: % (glyph_name, names[i], names[i + 1], pathIx)
print("At node %i, %s has %s, %s has %s" % (nodeIx, names[i], n1, names[i+1], n2)) )
continue if len(nodes1) != len(nodes2):
print(
"%s has %i nodes, %s has %i nodes"
% (names[i], len(nodes1), names[i + 1], len(nodes2))
)
continue
for nodeIx, (n1, n2) in enumerate(zip(nodes1, nodes2)):
if n1 != n2:
print(
"At node %i, %s has %s, %s has %s"
% (nodeIx, names[i], n1, names[i + 1], n2)
)
continue
for i,(m0,m1) in enumerate(zip(allVectors[:-1],allVectors[1:])): for i, (m0, m1) in enumerate(zip(allVectors[:-1], allVectors[1:])):
if len(m0) != len(m1): if len(m0) != len(m1):
print('%s: %s+%s: Glyphs not compatible!!!!!' % (glyph_name, names[i], names[i+1])) print(
continue "%s: %s+%s: Glyphs not compatible!!!!!"
if not m0: % (glyph_name, names[i], names[i + 1])
continue )
costs = [[_vlen(_vdiff(v0,v1)) for v1 in m1] for v0 in m0] continue
matching, matching_cost = min_cost_perfect_bipartite_matching(costs) if not m0:
if matching != list(range(len(m0))): continue
print('%s: %s+%s: Glyph has wrong contour/component order: %s' % (glyph_name, names[i], names[i+1], matching)) #, m0, m1) costs = [[_vlen(_vdiff(v0, v1)) for v1 in m1] for v0 in m0]
break matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
upem = 2048 if matching != list(range(len(m0))):
item_cost = round((matching_cost / len(m0) / len(m0[0])) ** .5 / upem * 100) print(
hist.append(item_cost) "%s: %s+%s: Glyph has wrong contour/component order: %s"
threshold = 7 % (glyph_name, names[i], names[i + 1], matching)
if item_cost >= threshold: ) # , m0, m1)
print('%s: %s+%s: Glyph has very high cost: %d%%' % (glyph_name, names[i], names[i+1], item_cost)) break
upem = 2048
item_cost = round(
(matching_cost / len(m0) / len(m0[0])) ** 0.5 / upem * 100
)
hist.append(item_cost)
threshold = 7
if item_cost >= threshold:
print(
"%s: %s+%s: Glyph has very high cost: %d%%"
% (glyph_name, names[i], names[i + 1], item_cost)
)
except ValueError as e:
print("%s: %s: math error %s; skipping glyph." % (glyph_name, name, e))
print(contour.value)
# raise
# for x in hist:
# print(x)
except ValueError as e:
print('%s: %s: math error %s; skipping glyph.' % (glyph_name, name, e))
print(contour.value)
#raise
#for x in hist:
# print(x)
def main(args=None): def main(args=None):
"""Test for interpolatability issues between fonts""" """Test for interpolatability issues between fonts"""
import argparse import argparse
parser = argparse.ArgumentParser(
"fonttools varLib.interpolatable",
description=main.__doc__,
)
parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
help="Input TTF/UFO files")
args = parser.parse_args(args) parser = argparse.ArgumentParser(
glyphs = None "fonttools varLib.interpolatable",
#glyphs = ['uni08DB', 'uniFD76'] description=main.__doc__,
#glyphs = ['uni08DE', 'uni0034'] )
#glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina'] parser.add_argument(
"inputs", metavar="FILE", type=str, nargs="+", help="Input TTF/UFO files"
)
from os.path import basename args = parser.parse_args(args)
names = [basename(filename).rsplit('.', 1)[0] for filename in args.inputs] glyphs = None
# glyphs = ['uni08DB', 'uniFD76']
# glyphs = ['uni08DE', 'uni0034']
# glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']
fonts = [] from os.path import basename
for filename in args.inputs:
if filename.endswith(".ufo"):
from fontTools.ufoLib import UFOReader
fonts.append(UFOReader(filename))
else:
from fontTools.ttLib import TTFont
fonts.append(TTFont(filename))
glyphsets = [font.getGlyphSet() for font in fonts] names = [basename(filename).rsplit(".", 1)[0] for filename in args.inputs]
test(glyphsets, glyphs=glyphs, names=names)
if __name__ == '__main__': fonts = []
import sys for filename in args.inputs:
main() if filename.endswith(".ufo"):
from fontTools.ufoLib import UFOReader
fonts.append(UFOReader(filename))
else:
from fontTools.ttLib import TTFont
fonts.append(TTFont(filename))
glyphsets = [font.getGlyphSet() for font in fonts]
test(glyphsets, glyphs=glyphs, names=names)
if __name__ == "__main__":
import sys
main()