Reformat with black
This commit is contained in:
parent
129e2413a0
commit
08e2a6cdc1
@ -13,209 +13,260 @@ import itertools
|
||||
|
||||
|
||||
class PerContourPen(BasePen):
|
||||
def __init__(self, Pen, glyphset=None):
|
||||
BasePen.__init__(self, glyphset)
|
||||
self._glyphset = glyphset
|
||||
self._Pen = Pen
|
||||
self._pen = None
|
||||
self.value = []
|
||||
def _moveTo(self, p0):
|
||||
self._newItem()
|
||||
self._pen.moveTo(p0)
|
||||
def _lineTo(self, p1):
|
||||
self._pen.lineTo(p1)
|
||||
def _qCurveToOne(self, p1, p2):
|
||||
self._pen.qCurveTo(p1, p2)
|
||||
def _curveToOne(self, p1, p2, p3):
|
||||
self._pen.curveTo(p1, p2, p3)
|
||||
def _closePath(self):
|
||||
self._pen.closePath()
|
||||
self._pen = None
|
||||
def _endPath(self):
|
||||
self._pen.endPath()
|
||||
self._pen = None
|
||||
def __init__(self, Pen, glyphset=None):
|
||||
BasePen.__init__(self, glyphset)
|
||||
self._glyphset = glyphset
|
||||
self._Pen = Pen
|
||||
self._pen = None
|
||||
self.value = []
|
||||
|
||||
def _moveTo(self, p0):
|
||||
self._newItem()
|
||||
self._pen.moveTo(p0)
|
||||
|
||||
def _lineTo(self, p1):
|
||||
self._pen.lineTo(p1)
|
||||
|
||||
def _qCurveToOne(self, p1, p2):
|
||||
self._pen.qCurveTo(p1, p2)
|
||||
|
||||
def _curveToOne(self, p1, p2, p3):
|
||||
self._pen.curveTo(p1, p2, p3)
|
||||
|
||||
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):
|
||||
|
||||
def addComponent(self, glyphName, transformation):
|
||||
self._newItem()
|
||||
self.value[-1].addComponent(glyphName, transformation)
|
||||
def addComponent(self, glyphName, transformation):
|
||||
self._newItem()
|
||||
self.value[-1].addComponent(glyphName, transformation)
|
||||
|
||||
|
||||
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):
|
||||
v = 0
|
||||
for x in vec:
|
||||
v += x*x
|
||||
return v
|
||||
v = 0
|
||||
for x in vec:
|
||||
v += x * x
|
||||
return v
|
||||
|
||||
|
||||
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):
|
||||
n = len(G)
|
||||
try:
|
||||
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
|
||||
n = len(G)
|
||||
try:
|
||||
from scipy.optimize import linear_sum_assignment
|
||||
|
||||
try:
|
||||
from munkres import Munkres
|
||||
cols = [None] * n
|
||||
for row,col in Munkres().compute(G):
|
||||
cols[row] = col
|
||||
return cols, _matching_cost(G, cols)
|
||||
except ImportError:
|
||||
pass
|
||||
rows, cols = linear_sum_assignment(G)
|
||||
assert (rows == list(range(n))).all()
|
||||
return list(cols), _matching_cost(G, cols)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if n > 6:
|
||||
raise Exception("Install Python module 'munkres' or 'scipy >= 0.17.0'")
|
||||
try:
|
||||
from munkres import Munkres
|
||||
|
||||
# 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
|
||||
cols = [None] * n
|
||||
for row, col in Munkres().compute(G):
|
||||
cols[row] = col
|
||||
return cols, _matching_cost(G, cols)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if n > 6:
|
||||
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):
|
||||
|
||||
if names is None:
|
||||
names = glyphsets
|
||||
if glyphs is None:
|
||||
glyphs = glyphsets[0].keys()
|
||||
if names is None:
|
||||
names = glyphsets
|
||||
if glyphs is None:
|
||||
glyphs = glyphsets[0].keys()
|
||||
|
||||
hist = []
|
||||
for glyph_name in glyphs:
|
||||
#print()
|
||||
#print(glyph_name)
|
||||
hist = []
|
||||
for glyph_name in glyphs:
|
||||
# print()
|
||||
# print(glyph_name)
|
||||
|
||||
try:
|
||||
allVectors = []
|
||||
allNodeTypes = []
|
||||
for glyphset,name in zip(glyphsets, names):
|
||||
#print('.', end='')
|
||||
if glyph_name not in glyphset:
|
||||
print('%s: Glyph cannot be interpolated - does not exist in master %s' % (glyph_name, name))
|
||||
continue
|
||||
glyph = glyphset[glyph_name]
|
||||
try:
|
||||
allVectors = []
|
||||
allNodeTypes = []
|
||||
for glyphset, name in zip(glyphsets, names):
|
||||
# print('.', end='')
|
||||
if glyph_name not in glyphset:
|
||||
print(
|
||||
"%s: Glyph cannot be interpolated - does not exist in master %s"
|
||||
% (glyph_name, name)
|
||||
)
|
||||
continue
|
||||
glyph = glyphset[glyph_name]
|
||||
|
||||
perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset)
|
||||
glyph.draw(perContourPen)
|
||||
contourPens = perContourPen.value
|
||||
del perContourPen
|
||||
perContourPen = PerContourOrComponentPen(
|
||||
RecordingPen, glyphset=glyphset
|
||||
)
|
||||
glyph.draw(perContourPen)
|
||||
contourPens = perContourPen.value
|
||||
del perContourPen
|
||||
|
||||
contourVectors = []
|
||||
nodeTypes = []
|
||||
allNodeTypes.append(nodeTypes)
|
||||
allVectors.append(contourVectors)
|
||||
for contour in contourPens:
|
||||
nodeTypes.append(tuple(instruction[0] for instruction in contour.value))
|
||||
stats = StatisticsPen(glyphset=glyphset)
|
||||
try:
|
||||
contour.replay(stats)
|
||||
except NotImplementedError as e:
|
||||
print('%s: Glyph cannot be interpolated - open path!' % glyph_name)
|
||||
break
|
||||
size = abs(stats.area) ** .5 * .5
|
||||
vector = (
|
||||
int(size),
|
||||
int(stats.meanX),
|
||||
int(stats.meanY),
|
||||
int(stats.stddevX * 2),
|
||||
int(stats.stddevY * 2),
|
||||
int(stats.correlation * size),
|
||||
)
|
||||
contourVectors.append(vector)
|
||||
#print(vector)
|
||||
contourVectors = []
|
||||
nodeTypes = []
|
||||
allNodeTypes.append(nodeTypes)
|
||||
allVectors.append(contourVectors)
|
||||
for contour in contourPens:
|
||||
nodeTypes.append(
|
||||
tuple(instruction[0] for instruction in contour.value)
|
||||
)
|
||||
stats = StatisticsPen(glyphset=glyphset)
|
||||
try:
|
||||
contour.replay(stats)
|
||||
except NotImplementedError as e:
|
||||
print(
|
||||
"%s: Glyph cannot be interpolated - open path!" % glyph_name
|
||||
)
|
||||
break
|
||||
size = abs(stats.area) ** 0.5 * 0.5
|
||||
vector = (
|
||||
int(size),
|
||||
int(stats.meanX),
|
||||
int(stats.meanY),
|
||||
int(stats.stddevX * 2),
|
||||
int(stats.stddevY * 2),
|
||||
int(stats.correlation * size),
|
||||
)
|
||||
contourVectors.append(vector)
|
||||
# print(vector)
|
||||
|
||||
# Check each master against the next one in the list.
|
||||
for i, (m0, m1) in enumerate(zip(allNodeTypes[:-1], allNodeTypes[1:])):
|
||||
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)))
|
||||
if m0 == m1:
|
||||
continue
|
||||
for pathIx, (nodes1, nodes2) in enumerate(zip(m0, m1)):
|
||||
if nodes1 == nodes2:
|
||||
continue
|
||||
print('%s: %s+%s: Glyphs not compatible at path %i!!!!!' % (glyph_name, names[i], names[i+1], pathIx))
|
||||
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
|
||||
# Check each master against the next one in the list.
|
||||
for i, (m0, m1) in enumerate(zip(allNodeTypes[:-1], allNodeTypes[1:])):
|
||||
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))
|
||||
)
|
||||
if m0 == m1:
|
||||
continue
|
||||
for pathIx, (nodes1, nodes2) in enumerate(zip(m0, m1)):
|
||||
if nodes1 == nodes2:
|
||||
continue
|
||||
print(
|
||||
"%s: %s+%s: Glyphs not compatible at path %i!!!!!"
|
||||
% (glyph_name, names[i], names[i + 1], pathIx)
|
||||
)
|
||||
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:])):
|
||||
if len(m0) != len(m1):
|
||||
print('%s: %s+%s: Glyphs not compatible!!!!!' % (glyph_name, names[i], names[i+1]))
|
||||
continue
|
||||
if not m0:
|
||||
continue
|
||||
costs = [[_vlen(_vdiff(v0,v1)) for v1 in m1] for v0 in m0]
|
||||
matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
|
||||
if matching != list(range(len(m0))):
|
||||
print('%s: %s+%s: Glyph has wrong contour/component order: %s' % (glyph_name, names[i], names[i+1], matching)) #, m0, m1)
|
||||
break
|
||||
upem = 2048
|
||||
item_cost = round((matching_cost / len(m0) / len(m0[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))
|
||||
for i, (m0, m1) in enumerate(zip(allVectors[:-1], allVectors[1:])):
|
||||
if len(m0) != len(m1):
|
||||
print(
|
||||
"%s: %s+%s: Glyphs not compatible!!!!!"
|
||||
% (glyph_name, names[i], names[i + 1])
|
||||
)
|
||||
continue
|
||||
if not m0:
|
||||
continue
|
||||
costs = [[_vlen(_vdiff(v0, v1)) for v1 in m1] for v0 in m0]
|
||||
matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
|
||||
if matching != list(range(len(m0))):
|
||||
print(
|
||||
"%s: %s+%s: Glyph has wrong contour/component order: %s"
|
||||
% (glyph_name, names[i], names[i + 1], matching)
|
||||
) # , m0, m1)
|
||||
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):
|
||||
"""Test for interpolatability issues between fonts"""
|
||||
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")
|
||||
"""Test for interpolatability issues between fonts"""
|
||||
import argparse
|
||||
|
||||
args = parser.parse_args(args)
|
||||
glyphs = None
|
||||
#glyphs = ['uni08DB', 'uniFD76']
|
||||
#glyphs = ['uni08DE', 'uni0034']
|
||||
#glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']
|
||||
parser = argparse.ArgumentParser(
|
||||
"fonttools varLib.interpolatable",
|
||||
description=main.__doc__,
|
||||
)
|
||||
parser.add_argument(
|
||||
"inputs", metavar="FILE", type=str, nargs="+", help="Input TTF/UFO files"
|
||||
)
|
||||
|
||||
from os.path import basename
|
||||
names = [basename(filename).rsplit('.', 1)[0] for filename in args.inputs]
|
||||
args = parser.parse_args(args)
|
||||
glyphs = None
|
||||
# glyphs = ['uni08DB', 'uniFD76']
|
||||
# glyphs = ['uni08DE', 'uni0034']
|
||||
# glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina']
|
||||
|
||||
fonts = []
|
||||
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))
|
||||
from os.path import basename
|
||||
|
||||
glyphsets = [font.getGlyphSet() for font in fonts]
|
||||
test(glyphsets, glyphs=glyphs, names=names)
|
||||
names = [basename(filename).rsplit(".", 1)[0] for filename in args.inputs]
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
main()
|
||||
fonts = []
|
||||
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]
|
||||
test(glyphsets, glyphs=glyphs, names=names)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user