""" Tool to find wrong contour order between different masters, and other interpolatability (or lack thereof) issues. Call as: $ fonttools varLib.interpolatable font1 font2 ... """ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from fontTools.pens.basePen import AbstractPen, BasePen from fontTools.pens.recordingPen import RecordingPen from fontTools.pens.statisticsPen import StatisticsPen 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 _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 _vdiff(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 def _matching_cost(G, 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 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 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() hist = [] for glyph_name in glyphs: #print() #print(glyph_name) try: allVectors = [] for glyphset,name in zip(glyphsets, names): #print('.', end='') glyph = glyphset[glyph_name] perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset) glyph.draw(perContourPen) contourPens = perContourPen.value del perContourPen contourVectors = [] allVectors.append(contourVectors) for contour in contourPens: stats = StatisticsPen(glyphset=glyphset) contour.replay(stats) 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) # Check each master against the next one in the list. 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)) 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): filenames = args glyphs = None #glyphs = ['uni08DB', 'uniFD76'] #glyphs = ['uni08DE', 'uni0034'] #glyphs = ['uni08DE', 'uni0034', 'uni0751', 'uni0753', 'uni0754', 'uni08A4', 'uni08A4.fina', 'uni08A5.fina'] from os.path import basename names = [basename(filename).rsplit('.', 1)[0] for filename in filenames] from fontTools.ttLib import TTFont fonts = [TTFont(filename) for filename in filenames] glyphsets = [font.getGlyphSet() for font in fonts] test(glyphsets, glyphs=glyphs, names=names) if __name__ == '__main__': import sys main(sys.argv[1:])