1*e1fe3e4aSElliott Hughes""" 2*e1fe3e4aSElliott HughesTool to find wrong contour order between different masters, and 3*e1fe3e4aSElliott Hughesother interpolatability (or lack thereof) issues. 4*e1fe3e4aSElliott Hughes 5*e1fe3e4aSElliott HughesCall as: 6*e1fe3e4aSElliott Hughes$ fonttools varLib.interpolatable font1 font2 ... 7*e1fe3e4aSElliott Hughes""" 8*e1fe3e4aSElliott Hughes 9*e1fe3e4aSElliott Hughesfrom .interpolatableHelpers import * 10*e1fe3e4aSElliott Hughesfrom .interpolatableTestContourOrder import test_contour_order 11*e1fe3e4aSElliott Hughesfrom .interpolatableTestStartingPoint import test_starting_point 12*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import ( 13*e1fe3e4aSElliott Hughes RecordingPen, 14*e1fe3e4aSElliott Hughes DecomposingRecordingPen, 15*e1fe3e4aSElliott Hughes lerpRecordings, 16*e1fe3e4aSElliott Hughes) 17*e1fe3e4aSElliott Hughesfrom fontTools.pens.transformPen import TransformPen 18*e1fe3e4aSElliott Hughesfrom fontTools.pens.statisticsPen import StatisticsPen, StatisticsControlPen 19*e1fe3e4aSElliott Hughesfrom fontTools.pens.momentsPen import OpenContourError 20*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import piecewiseLinearMap, normalizeLocation 21*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import floatToFixedToStr 22*e1fe3e4aSElliott Hughesfrom fontTools.misc.transform import Transform 23*e1fe3e4aSElliott Hughesfrom collections import defaultdict 24*e1fe3e4aSElliott Hughesfrom types import SimpleNamespace 25*e1fe3e4aSElliott Hughesfrom functools import wraps 26*e1fe3e4aSElliott Hughesfrom pprint import pformat 27*e1fe3e4aSElliott Hughesfrom math import sqrt, atan2, pi 28*e1fe3e4aSElliott Hughesimport logging 29*e1fe3e4aSElliott Hughesimport os 30*e1fe3e4aSElliott Hughes 31*e1fe3e4aSElliott Hugheslog = logging.getLogger("fontTools.varLib.interpolatable") 32*e1fe3e4aSElliott Hughes 33*e1fe3e4aSElliott HughesDEFAULT_TOLERANCE = 0.95 34*e1fe3e4aSElliott HughesDEFAULT_KINKINESS = 0.5 35*e1fe3e4aSElliott HughesDEFAULT_KINKINESS_LENGTH = 0.002 # ratio of UPEM 36*e1fe3e4aSElliott HughesDEFAULT_UPEM = 1000 37*e1fe3e4aSElliott Hughes 38*e1fe3e4aSElliott Hughes 39*e1fe3e4aSElliott Hughesclass Glyph: 40*e1fe3e4aSElliott Hughes ITEMS = ( 41*e1fe3e4aSElliott Hughes "recordings", 42*e1fe3e4aSElliott Hughes "greenStats", 43*e1fe3e4aSElliott Hughes "controlStats", 44*e1fe3e4aSElliott Hughes "greenVectors", 45*e1fe3e4aSElliott Hughes "controlVectors", 46*e1fe3e4aSElliott Hughes "nodeTypes", 47*e1fe3e4aSElliott Hughes "isomorphisms", 48*e1fe3e4aSElliott Hughes "points", 49*e1fe3e4aSElliott Hughes "openContours", 50*e1fe3e4aSElliott Hughes ) 51*e1fe3e4aSElliott Hughes 52*e1fe3e4aSElliott Hughes def __init__(self, glyphname, glyphset): 53*e1fe3e4aSElliott Hughes self.name = glyphname 54*e1fe3e4aSElliott Hughes for item in self.ITEMS: 55*e1fe3e4aSElliott Hughes setattr(self, item, []) 56*e1fe3e4aSElliott Hughes self._populate(glyphset) 57*e1fe3e4aSElliott Hughes 58*e1fe3e4aSElliott Hughes def _fill_in(self, ix): 59*e1fe3e4aSElliott Hughes for item in self.ITEMS: 60*e1fe3e4aSElliott Hughes if len(getattr(self, item)) == ix: 61*e1fe3e4aSElliott Hughes getattr(self, item).append(None) 62*e1fe3e4aSElliott Hughes 63*e1fe3e4aSElliott Hughes def _populate(self, glyphset): 64*e1fe3e4aSElliott Hughes glyph = glyphset[self.name] 65*e1fe3e4aSElliott Hughes self.doesnt_exist = glyph is None 66*e1fe3e4aSElliott Hughes if self.doesnt_exist: 67*e1fe3e4aSElliott Hughes return 68*e1fe3e4aSElliott Hughes 69*e1fe3e4aSElliott Hughes perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset) 70*e1fe3e4aSElliott Hughes try: 71*e1fe3e4aSElliott Hughes glyph.draw(perContourPen, outputImpliedClosingLine=True) 72*e1fe3e4aSElliott Hughes except TypeError: 73*e1fe3e4aSElliott Hughes glyph.draw(perContourPen) 74*e1fe3e4aSElliott Hughes self.recordings = perContourPen.value 75*e1fe3e4aSElliott Hughes del perContourPen 76*e1fe3e4aSElliott Hughes 77*e1fe3e4aSElliott Hughes for ix, contour in enumerate(self.recordings): 78*e1fe3e4aSElliott Hughes nodeTypes = [op for op, arg in contour.value] 79*e1fe3e4aSElliott Hughes self.nodeTypes.append(nodeTypes) 80*e1fe3e4aSElliott Hughes 81*e1fe3e4aSElliott Hughes greenStats = StatisticsPen(glyphset=glyphset) 82*e1fe3e4aSElliott Hughes controlStats = StatisticsControlPen(glyphset=glyphset) 83*e1fe3e4aSElliott Hughes try: 84*e1fe3e4aSElliott Hughes contour.replay(greenStats) 85*e1fe3e4aSElliott Hughes contour.replay(controlStats) 86*e1fe3e4aSElliott Hughes self.openContours.append(False) 87*e1fe3e4aSElliott Hughes except OpenContourError as e: 88*e1fe3e4aSElliott Hughes self.openContours.append(True) 89*e1fe3e4aSElliott Hughes self._fill_in(ix) 90*e1fe3e4aSElliott Hughes continue 91*e1fe3e4aSElliott Hughes self.greenStats.append(greenStats) 92*e1fe3e4aSElliott Hughes self.controlStats.append(controlStats) 93*e1fe3e4aSElliott Hughes self.greenVectors.append(contour_vector_from_stats(greenStats)) 94*e1fe3e4aSElliott Hughes self.controlVectors.append(contour_vector_from_stats(controlStats)) 95*e1fe3e4aSElliott Hughes 96*e1fe3e4aSElliott Hughes # Check starting point 97*e1fe3e4aSElliott Hughes if nodeTypes[0] == "addComponent": 98*e1fe3e4aSElliott Hughes self._fill_in(ix) 99*e1fe3e4aSElliott Hughes continue 100*e1fe3e4aSElliott Hughes 101*e1fe3e4aSElliott Hughes assert nodeTypes[0] == "moveTo" 102*e1fe3e4aSElliott Hughes assert nodeTypes[-1] in ("closePath", "endPath") 103*e1fe3e4aSElliott Hughes points = SimpleRecordingPointPen() 104*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points, False) 105*e1fe3e4aSElliott Hughes contour.replay(converter) 106*e1fe3e4aSElliott Hughes # points.value is a list of pt,bool where bool is true if on-curve and false if off-curve; 107*e1fe3e4aSElliott Hughes # now check all rotations and mirror-rotations of the contour and build list of isomorphic 108*e1fe3e4aSElliott Hughes # possible starting points. 109*e1fe3e4aSElliott Hughes self.points.append(points.value) 110*e1fe3e4aSElliott Hughes 111*e1fe3e4aSElliott Hughes isomorphisms = [] 112*e1fe3e4aSElliott Hughes self.isomorphisms.append(isomorphisms) 113*e1fe3e4aSElliott Hughes 114*e1fe3e4aSElliott Hughes # Add rotations 115*e1fe3e4aSElliott Hughes add_isomorphisms(points.value, isomorphisms, False) 116*e1fe3e4aSElliott Hughes # Add mirrored rotations 117*e1fe3e4aSElliott Hughes add_isomorphisms(points.value, isomorphisms, True) 118*e1fe3e4aSElliott Hughes 119*e1fe3e4aSElliott Hughes def draw(self, pen, countor_idx=None): 120*e1fe3e4aSElliott Hughes if countor_idx is None: 121*e1fe3e4aSElliott Hughes for contour in self.recordings: 122*e1fe3e4aSElliott Hughes contour.draw(pen) 123*e1fe3e4aSElliott Hughes else: 124*e1fe3e4aSElliott Hughes self.recordings[countor_idx].draw(pen) 125*e1fe3e4aSElliott Hughes 126*e1fe3e4aSElliott Hughes 127*e1fe3e4aSElliott Hughesdef test_gen( 128*e1fe3e4aSElliott Hughes glyphsets, 129*e1fe3e4aSElliott Hughes glyphs=None, 130*e1fe3e4aSElliott Hughes names=None, 131*e1fe3e4aSElliott Hughes ignore_missing=False, 132*e1fe3e4aSElliott Hughes *, 133*e1fe3e4aSElliott Hughes locations=None, 134*e1fe3e4aSElliott Hughes tolerance=DEFAULT_TOLERANCE, 135*e1fe3e4aSElliott Hughes kinkiness=DEFAULT_KINKINESS, 136*e1fe3e4aSElliott Hughes upem=DEFAULT_UPEM, 137*e1fe3e4aSElliott Hughes show_all=False, 138*e1fe3e4aSElliott Hughes): 139*e1fe3e4aSElliott Hughes if tolerance >= 10: 140*e1fe3e4aSElliott Hughes tolerance *= 0.01 141*e1fe3e4aSElliott Hughes assert 0 <= tolerance <= 1 142*e1fe3e4aSElliott Hughes if kinkiness >= 10: 143*e1fe3e4aSElliott Hughes kinkiness *= 0.01 144*e1fe3e4aSElliott Hughes assert 0 <= kinkiness 145*e1fe3e4aSElliott Hughes 146*e1fe3e4aSElliott Hughes names = names or [repr(g) for g in glyphsets] 147*e1fe3e4aSElliott Hughes 148*e1fe3e4aSElliott Hughes if glyphs is None: 149*e1fe3e4aSElliott Hughes # `glyphs = glyphsets[0].keys()` is faster, certainly, but doesn't allow for sparse TTFs/OTFs given out of order 150*e1fe3e4aSElliott Hughes # ... risks the sparse master being the first one, and only processing a subset of the glyphs 151*e1fe3e4aSElliott Hughes glyphs = {g for glyphset in glyphsets for g in glyphset.keys()} 152*e1fe3e4aSElliott Hughes 153*e1fe3e4aSElliott Hughes parents, order = find_parents_and_order(glyphsets, locations) 154*e1fe3e4aSElliott Hughes 155*e1fe3e4aSElliott Hughes def grand_parent(i, glyphname): 156*e1fe3e4aSElliott Hughes if i is None: 157*e1fe3e4aSElliott Hughes return None 158*e1fe3e4aSElliott Hughes i = parents[i] 159*e1fe3e4aSElliott Hughes if i is None: 160*e1fe3e4aSElliott Hughes return None 161*e1fe3e4aSElliott Hughes while parents[i] is not None and glyphsets[i][glyphname] is None: 162*e1fe3e4aSElliott Hughes i = parents[i] 163*e1fe3e4aSElliott Hughes return i 164*e1fe3e4aSElliott Hughes 165*e1fe3e4aSElliott Hughes for glyph_name in glyphs: 166*e1fe3e4aSElliott Hughes log.info("Testing glyph %s", glyph_name) 167*e1fe3e4aSElliott Hughes allGlyphs = [Glyph(glyph_name, glyphset) for glyphset in glyphsets] 168*e1fe3e4aSElliott Hughes if len([1 for glyph in allGlyphs if glyph is not None]) <= 1: 169*e1fe3e4aSElliott Hughes continue 170*e1fe3e4aSElliott Hughes for master_idx, (glyph, glyphset, name) in enumerate( 171*e1fe3e4aSElliott Hughes zip(allGlyphs, glyphsets, names) 172*e1fe3e4aSElliott Hughes ): 173*e1fe3e4aSElliott Hughes if glyph.doesnt_exist: 174*e1fe3e4aSElliott Hughes if not ignore_missing: 175*e1fe3e4aSElliott Hughes yield ( 176*e1fe3e4aSElliott Hughes glyph_name, 177*e1fe3e4aSElliott Hughes { 178*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.MISSING, 179*e1fe3e4aSElliott Hughes "master": name, 180*e1fe3e4aSElliott Hughes "master_idx": master_idx, 181*e1fe3e4aSElliott Hughes }, 182*e1fe3e4aSElliott Hughes ) 183*e1fe3e4aSElliott Hughes continue 184*e1fe3e4aSElliott Hughes 185*e1fe3e4aSElliott Hughes has_open = False 186*e1fe3e4aSElliott Hughes for ix, open in enumerate(glyph.openContours): 187*e1fe3e4aSElliott Hughes if not open: 188*e1fe3e4aSElliott Hughes continue 189*e1fe3e4aSElliott Hughes has_open = True 190*e1fe3e4aSElliott Hughes yield ( 191*e1fe3e4aSElliott Hughes glyph_name, 192*e1fe3e4aSElliott Hughes { 193*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.OPEN_PATH, 194*e1fe3e4aSElliott Hughes "master": name, 195*e1fe3e4aSElliott Hughes "master_idx": master_idx, 196*e1fe3e4aSElliott Hughes "contour": ix, 197*e1fe3e4aSElliott Hughes }, 198*e1fe3e4aSElliott Hughes ) 199*e1fe3e4aSElliott Hughes if has_open: 200*e1fe3e4aSElliott Hughes continue 201*e1fe3e4aSElliott Hughes 202*e1fe3e4aSElliott Hughes matchings = [None] * len(glyphsets) 203*e1fe3e4aSElliott Hughes 204*e1fe3e4aSElliott Hughes for m1idx in order: 205*e1fe3e4aSElliott Hughes glyph1 = allGlyphs[m1idx] 206*e1fe3e4aSElliott Hughes if glyph1 is None or not glyph1.nodeTypes: 207*e1fe3e4aSElliott Hughes continue 208*e1fe3e4aSElliott Hughes m0idx = grand_parent(m1idx, glyph_name) 209*e1fe3e4aSElliott Hughes if m0idx is None: 210*e1fe3e4aSElliott Hughes continue 211*e1fe3e4aSElliott Hughes glyph0 = allGlyphs[m0idx] 212*e1fe3e4aSElliott Hughes if glyph0 is None or not glyph0.nodeTypes: 213*e1fe3e4aSElliott Hughes continue 214*e1fe3e4aSElliott Hughes 215*e1fe3e4aSElliott Hughes # 216*e1fe3e4aSElliott Hughes # Basic compatibility checks 217*e1fe3e4aSElliott Hughes # 218*e1fe3e4aSElliott Hughes 219*e1fe3e4aSElliott Hughes m1 = glyph0.nodeTypes 220*e1fe3e4aSElliott Hughes m0 = glyph1.nodeTypes 221*e1fe3e4aSElliott Hughes if len(m0) != len(m1): 222*e1fe3e4aSElliott Hughes yield ( 223*e1fe3e4aSElliott Hughes glyph_name, 224*e1fe3e4aSElliott Hughes { 225*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.PATH_COUNT, 226*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 227*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 228*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 229*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 230*e1fe3e4aSElliott Hughes "value_1": len(m0), 231*e1fe3e4aSElliott Hughes "value_2": len(m1), 232*e1fe3e4aSElliott Hughes }, 233*e1fe3e4aSElliott Hughes ) 234*e1fe3e4aSElliott Hughes continue 235*e1fe3e4aSElliott Hughes 236*e1fe3e4aSElliott Hughes if m0 != m1: 237*e1fe3e4aSElliott Hughes for pathIx, (nodes1, nodes2) in enumerate(zip(m0, m1)): 238*e1fe3e4aSElliott Hughes if nodes1 == nodes2: 239*e1fe3e4aSElliott Hughes continue 240*e1fe3e4aSElliott Hughes if len(nodes1) != len(nodes2): 241*e1fe3e4aSElliott Hughes yield ( 242*e1fe3e4aSElliott Hughes glyph_name, 243*e1fe3e4aSElliott Hughes { 244*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.NODE_COUNT, 245*e1fe3e4aSElliott Hughes "path": pathIx, 246*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 247*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 248*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 249*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 250*e1fe3e4aSElliott Hughes "value_1": len(nodes1), 251*e1fe3e4aSElliott Hughes "value_2": len(nodes2), 252*e1fe3e4aSElliott Hughes }, 253*e1fe3e4aSElliott Hughes ) 254*e1fe3e4aSElliott Hughes continue 255*e1fe3e4aSElliott Hughes for nodeIx, (n1, n2) in enumerate(zip(nodes1, nodes2)): 256*e1fe3e4aSElliott Hughes if n1 != n2: 257*e1fe3e4aSElliott Hughes yield ( 258*e1fe3e4aSElliott Hughes glyph_name, 259*e1fe3e4aSElliott Hughes { 260*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.NODE_INCOMPATIBILITY, 261*e1fe3e4aSElliott Hughes "path": pathIx, 262*e1fe3e4aSElliott Hughes "node": nodeIx, 263*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 264*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 265*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 266*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 267*e1fe3e4aSElliott Hughes "value_1": n1, 268*e1fe3e4aSElliott Hughes "value_2": n2, 269*e1fe3e4aSElliott Hughes }, 270*e1fe3e4aSElliott Hughes ) 271*e1fe3e4aSElliott Hughes continue 272*e1fe3e4aSElliott Hughes 273*e1fe3e4aSElliott Hughes # 274*e1fe3e4aSElliott Hughes # InterpolatableProblem.CONTOUR_ORDER check 275*e1fe3e4aSElliott Hughes # 276*e1fe3e4aSElliott Hughes 277*e1fe3e4aSElliott Hughes this_tolerance, matching = test_contour_order(glyph0, glyph1) 278*e1fe3e4aSElliott Hughes if this_tolerance < tolerance: 279*e1fe3e4aSElliott Hughes yield ( 280*e1fe3e4aSElliott Hughes glyph_name, 281*e1fe3e4aSElliott Hughes { 282*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.CONTOUR_ORDER, 283*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 284*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 285*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 286*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 287*e1fe3e4aSElliott Hughes "value_1": list(range(len(matching))), 288*e1fe3e4aSElliott Hughes "value_2": matching, 289*e1fe3e4aSElliott Hughes "tolerance": this_tolerance, 290*e1fe3e4aSElliott Hughes }, 291*e1fe3e4aSElliott Hughes ) 292*e1fe3e4aSElliott Hughes matchings[m1idx] = matching 293*e1fe3e4aSElliott Hughes 294*e1fe3e4aSElliott Hughes # 295*e1fe3e4aSElliott Hughes # wrong-start-point / weight check 296*e1fe3e4aSElliott Hughes # 297*e1fe3e4aSElliott Hughes 298*e1fe3e4aSElliott Hughes m0Isomorphisms = glyph0.isomorphisms 299*e1fe3e4aSElliott Hughes m1Isomorphisms = glyph1.isomorphisms 300*e1fe3e4aSElliott Hughes m0Vectors = glyph0.greenVectors 301*e1fe3e4aSElliott Hughes m1Vectors = glyph1.greenVectors 302*e1fe3e4aSElliott Hughes recording0 = glyph0.recordings 303*e1fe3e4aSElliott Hughes recording1 = glyph1.recordings 304*e1fe3e4aSElliott Hughes 305*e1fe3e4aSElliott Hughes # If contour-order is wrong, adjust it 306*e1fe3e4aSElliott Hughes matching = matchings[m1idx] 307*e1fe3e4aSElliott Hughes if ( 308*e1fe3e4aSElliott Hughes matching is not None and m1Isomorphisms 309*e1fe3e4aSElliott Hughes ): # m1 is empty for composite glyphs 310*e1fe3e4aSElliott Hughes m1Isomorphisms = [m1Isomorphisms[i] for i in matching] 311*e1fe3e4aSElliott Hughes m1Vectors = [m1Vectors[i] for i in matching] 312*e1fe3e4aSElliott Hughes recording1 = [recording1[i] for i in matching] 313*e1fe3e4aSElliott Hughes 314*e1fe3e4aSElliott Hughes midRecording = [] 315*e1fe3e4aSElliott Hughes for c0, c1 in zip(recording0, recording1): 316*e1fe3e4aSElliott Hughes try: 317*e1fe3e4aSElliott Hughes r = RecordingPen() 318*e1fe3e4aSElliott Hughes r.value = list(lerpRecordings(c0.value, c1.value)) 319*e1fe3e4aSElliott Hughes midRecording.append(r) 320*e1fe3e4aSElliott Hughes except ValueError: 321*e1fe3e4aSElliott Hughes # Mismatch because of the reordering above 322*e1fe3e4aSElliott Hughes midRecording.append(None) 323*e1fe3e4aSElliott Hughes 324*e1fe3e4aSElliott Hughes for ix, (contour0, contour1) in enumerate( 325*e1fe3e4aSElliott Hughes zip(m0Isomorphisms, m1Isomorphisms) 326*e1fe3e4aSElliott Hughes ): 327*e1fe3e4aSElliott Hughes if ( 328*e1fe3e4aSElliott Hughes contour0 is None 329*e1fe3e4aSElliott Hughes or contour1 is None 330*e1fe3e4aSElliott Hughes or len(contour0) == 0 331*e1fe3e4aSElliott Hughes or len(contour0) != len(contour1) 332*e1fe3e4aSElliott Hughes ): 333*e1fe3e4aSElliott Hughes # We already reported this; or nothing to do; or not compatible 334*e1fe3e4aSElliott Hughes # after reordering above. 335*e1fe3e4aSElliott Hughes continue 336*e1fe3e4aSElliott Hughes 337*e1fe3e4aSElliott Hughes this_tolerance, proposed_point, reverse = test_starting_point( 338*e1fe3e4aSElliott Hughes glyph0, glyph1, ix, tolerance, matching 339*e1fe3e4aSElliott Hughes ) 340*e1fe3e4aSElliott Hughes 341*e1fe3e4aSElliott Hughes if this_tolerance < tolerance: 342*e1fe3e4aSElliott Hughes yield ( 343*e1fe3e4aSElliott Hughes glyph_name, 344*e1fe3e4aSElliott Hughes { 345*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.WRONG_START_POINT, 346*e1fe3e4aSElliott Hughes "contour": ix, 347*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 348*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 349*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 350*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 351*e1fe3e4aSElliott Hughes "value_1": 0, 352*e1fe3e4aSElliott Hughes "value_2": proposed_point, 353*e1fe3e4aSElliott Hughes "reversed": reverse, 354*e1fe3e4aSElliott Hughes "tolerance": this_tolerance, 355*e1fe3e4aSElliott Hughes }, 356*e1fe3e4aSElliott Hughes ) 357*e1fe3e4aSElliott Hughes 358*e1fe3e4aSElliott Hughes # Weight check. 359*e1fe3e4aSElliott Hughes # 360*e1fe3e4aSElliott Hughes # If contour could be mid-interpolated, and the two 361*e1fe3e4aSElliott Hughes # contours have the same area sign, proceeed. 362*e1fe3e4aSElliott Hughes # 363*e1fe3e4aSElliott Hughes # The sign difference can happen if it's a weirdo 364*e1fe3e4aSElliott Hughes # self-intersecting contour; ignore it. 365*e1fe3e4aSElliott Hughes contour = midRecording[ix] 366*e1fe3e4aSElliott Hughes 367*e1fe3e4aSElliott Hughes if contour and (m0Vectors[ix][0] < 0) == (m1Vectors[ix][0] < 0): 368*e1fe3e4aSElliott Hughes midStats = StatisticsPen(glyphset=None) 369*e1fe3e4aSElliott Hughes contour.replay(midStats) 370*e1fe3e4aSElliott Hughes 371*e1fe3e4aSElliott Hughes midVector = contour_vector_from_stats(midStats) 372*e1fe3e4aSElliott Hughes 373*e1fe3e4aSElliott Hughes m0Vec = m0Vectors[ix] 374*e1fe3e4aSElliott Hughes m1Vec = m1Vectors[ix] 375*e1fe3e4aSElliott Hughes size0 = m0Vec[0] * m0Vec[0] 376*e1fe3e4aSElliott Hughes size1 = m1Vec[0] * m1Vec[0] 377*e1fe3e4aSElliott Hughes midSize = midVector[0] * midVector[0] 378*e1fe3e4aSElliott Hughes 379*e1fe3e4aSElliott Hughes for overweight, problem_type in enumerate( 380*e1fe3e4aSElliott Hughes ( 381*e1fe3e4aSElliott Hughes InterpolatableProblem.UNDERWEIGHT, 382*e1fe3e4aSElliott Hughes InterpolatableProblem.OVERWEIGHT, 383*e1fe3e4aSElliott Hughes ) 384*e1fe3e4aSElliott Hughes ): 385*e1fe3e4aSElliott Hughes if overweight: 386*e1fe3e4aSElliott Hughes expectedSize = max(size0, size1) 387*e1fe3e4aSElliott Hughes continue 388*e1fe3e4aSElliott Hughes else: 389*e1fe3e4aSElliott Hughes expectedSize = sqrt(size0 * size1) 390*e1fe3e4aSElliott Hughes 391*e1fe3e4aSElliott Hughes log.debug( 392*e1fe3e4aSElliott Hughes "%s: actual size %g; threshold size %g, master sizes: %g, %g", 393*e1fe3e4aSElliott Hughes problem_type, 394*e1fe3e4aSElliott Hughes midSize, 395*e1fe3e4aSElliott Hughes expectedSize, 396*e1fe3e4aSElliott Hughes size0, 397*e1fe3e4aSElliott Hughes size1, 398*e1fe3e4aSElliott Hughes ) 399*e1fe3e4aSElliott Hughes 400*e1fe3e4aSElliott Hughes if ( 401*e1fe3e4aSElliott Hughes not overweight and expectedSize * tolerance > midSize + 1e-5 402*e1fe3e4aSElliott Hughes ) or (overweight and 1e-5 + expectedSize / tolerance < midSize): 403*e1fe3e4aSElliott Hughes try: 404*e1fe3e4aSElliott Hughes if overweight: 405*e1fe3e4aSElliott Hughes this_tolerance = expectedSize / midSize 406*e1fe3e4aSElliott Hughes else: 407*e1fe3e4aSElliott Hughes this_tolerance = midSize / expectedSize 408*e1fe3e4aSElliott Hughes except ZeroDivisionError: 409*e1fe3e4aSElliott Hughes this_tolerance = 0 410*e1fe3e4aSElliott Hughes log.debug("tolerance %g", this_tolerance) 411*e1fe3e4aSElliott Hughes yield ( 412*e1fe3e4aSElliott Hughes glyph_name, 413*e1fe3e4aSElliott Hughes { 414*e1fe3e4aSElliott Hughes "type": problem_type, 415*e1fe3e4aSElliott Hughes "contour": ix, 416*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 417*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 418*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 419*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 420*e1fe3e4aSElliott Hughes "tolerance": this_tolerance, 421*e1fe3e4aSElliott Hughes }, 422*e1fe3e4aSElliott Hughes ) 423*e1fe3e4aSElliott Hughes 424*e1fe3e4aSElliott Hughes # 425*e1fe3e4aSElliott Hughes # "kink" detector 426*e1fe3e4aSElliott Hughes # 427*e1fe3e4aSElliott Hughes m0 = glyph0.points 428*e1fe3e4aSElliott Hughes m1 = glyph1.points 429*e1fe3e4aSElliott Hughes 430*e1fe3e4aSElliott Hughes # If contour-order is wrong, adjust it 431*e1fe3e4aSElliott Hughes if matchings[m1idx] is not None and m1: # m1 is empty for composite glyphs 432*e1fe3e4aSElliott Hughes m1 = [m1[i] for i in matchings[m1idx]] 433*e1fe3e4aSElliott Hughes 434*e1fe3e4aSElliott Hughes t = 0.1 # ~sin(radian(6)) for tolerance 0.95 435*e1fe3e4aSElliott Hughes deviation_threshold = ( 436*e1fe3e4aSElliott Hughes upem * DEFAULT_KINKINESS_LENGTH * DEFAULT_KINKINESS / kinkiness 437*e1fe3e4aSElliott Hughes ) 438*e1fe3e4aSElliott Hughes 439*e1fe3e4aSElliott Hughes for ix, (contour0, contour1) in enumerate(zip(m0, m1)): 440*e1fe3e4aSElliott Hughes if ( 441*e1fe3e4aSElliott Hughes contour0 is None 442*e1fe3e4aSElliott Hughes or contour1 is None 443*e1fe3e4aSElliott Hughes or len(contour0) == 0 444*e1fe3e4aSElliott Hughes or len(contour0) != len(contour1) 445*e1fe3e4aSElliott Hughes ): 446*e1fe3e4aSElliott Hughes # We already reported this; or nothing to do; or not compatible 447*e1fe3e4aSElliott Hughes # after reordering above. 448*e1fe3e4aSElliott Hughes continue 449*e1fe3e4aSElliott Hughes 450*e1fe3e4aSElliott Hughes # Walk the contour, keeping track of three consecutive points, with 451*e1fe3e4aSElliott Hughes # middle one being an on-curve. If the three are co-linear then 452*e1fe3e4aSElliott Hughes # check for kinky-ness. 453*e1fe3e4aSElliott Hughes for i in range(len(contour0)): 454*e1fe3e4aSElliott Hughes pt0 = contour0[i] 455*e1fe3e4aSElliott Hughes pt1 = contour1[i] 456*e1fe3e4aSElliott Hughes if not pt0[1] or not pt1[1]: 457*e1fe3e4aSElliott Hughes # Skip off-curves 458*e1fe3e4aSElliott Hughes continue 459*e1fe3e4aSElliott Hughes pt0_prev = contour0[i - 1] 460*e1fe3e4aSElliott Hughes pt1_prev = contour1[i - 1] 461*e1fe3e4aSElliott Hughes pt0_next = contour0[(i + 1) % len(contour0)] 462*e1fe3e4aSElliott Hughes pt1_next = contour1[(i + 1) % len(contour1)] 463*e1fe3e4aSElliott Hughes 464*e1fe3e4aSElliott Hughes if pt0_prev[1] and pt1_prev[1]: 465*e1fe3e4aSElliott Hughes # At least one off-curve is required 466*e1fe3e4aSElliott Hughes continue 467*e1fe3e4aSElliott Hughes if pt0_prev[1] and pt1_prev[1]: 468*e1fe3e4aSElliott Hughes # At least one off-curve is required 469*e1fe3e4aSElliott Hughes continue 470*e1fe3e4aSElliott Hughes 471*e1fe3e4aSElliott Hughes pt0 = complex(*pt0[0]) 472*e1fe3e4aSElliott Hughes pt1 = complex(*pt1[0]) 473*e1fe3e4aSElliott Hughes pt0_prev = complex(*pt0_prev[0]) 474*e1fe3e4aSElliott Hughes pt1_prev = complex(*pt1_prev[0]) 475*e1fe3e4aSElliott Hughes pt0_next = complex(*pt0_next[0]) 476*e1fe3e4aSElliott Hughes pt1_next = complex(*pt1_next[0]) 477*e1fe3e4aSElliott Hughes 478*e1fe3e4aSElliott Hughes # We have three consecutive points. Check whether 479*e1fe3e4aSElliott Hughes # they are colinear. 480*e1fe3e4aSElliott Hughes d0_prev = pt0 - pt0_prev 481*e1fe3e4aSElliott Hughes d0_next = pt0_next - pt0 482*e1fe3e4aSElliott Hughes d1_prev = pt1 - pt1_prev 483*e1fe3e4aSElliott Hughes d1_next = pt1_next - pt1 484*e1fe3e4aSElliott Hughes 485*e1fe3e4aSElliott Hughes sin0 = d0_prev.real * d0_next.imag - d0_prev.imag * d0_next.real 486*e1fe3e4aSElliott Hughes sin1 = d1_prev.real * d1_next.imag - d1_prev.imag * d1_next.real 487*e1fe3e4aSElliott Hughes try: 488*e1fe3e4aSElliott Hughes sin0 /= abs(d0_prev) * abs(d0_next) 489*e1fe3e4aSElliott Hughes sin1 /= abs(d1_prev) * abs(d1_next) 490*e1fe3e4aSElliott Hughes except ZeroDivisionError: 491*e1fe3e4aSElliott Hughes continue 492*e1fe3e4aSElliott Hughes 493*e1fe3e4aSElliott Hughes if abs(sin0) > t or abs(sin1) > t: 494*e1fe3e4aSElliott Hughes # Not colinear / not smooth. 495*e1fe3e4aSElliott Hughes continue 496*e1fe3e4aSElliott Hughes 497*e1fe3e4aSElliott Hughes # Check the mid-point is actually, well, in the middle. 498*e1fe3e4aSElliott Hughes dot0 = d0_prev.real * d0_next.real + d0_prev.imag * d0_next.imag 499*e1fe3e4aSElliott Hughes dot1 = d1_prev.real * d1_next.real + d1_prev.imag * d1_next.imag 500*e1fe3e4aSElliott Hughes if dot0 < 0 or dot1 < 0: 501*e1fe3e4aSElliott Hughes # Sharp corner. 502*e1fe3e4aSElliott Hughes continue 503*e1fe3e4aSElliott Hughes 504*e1fe3e4aSElliott Hughes # Fine, if handle ratios are similar... 505*e1fe3e4aSElliott Hughes r0 = abs(d0_prev) / (abs(d0_prev) + abs(d0_next)) 506*e1fe3e4aSElliott Hughes r1 = abs(d1_prev) / (abs(d1_prev) + abs(d1_next)) 507*e1fe3e4aSElliott Hughes r_diff = abs(r0 - r1) 508*e1fe3e4aSElliott Hughes if abs(r_diff) < t: 509*e1fe3e4aSElliott Hughes # Smooth enough. 510*e1fe3e4aSElliott Hughes continue 511*e1fe3e4aSElliott Hughes 512*e1fe3e4aSElliott Hughes mid = (pt0 + pt1) / 2 513*e1fe3e4aSElliott Hughes mid_prev = (pt0_prev + pt1_prev) / 2 514*e1fe3e4aSElliott Hughes mid_next = (pt0_next + pt1_next) / 2 515*e1fe3e4aSElliott Hughes 516*e1fe3e4aSElliott Hughes mid_d0 = mid - mid_prev 517*e1fe3e4aSElliott Hughes mid_d1 = mid_next - mid 518*e1fe3e4aSElliott Hughes 519*e1fe3e4aSElliott Hughes sin_mid = mid_d0.real * mid_d1.imag - mid_d0.imag * mid_d1.real 520*e1fe3e4aSElliott Hughes try: 521*e1fe3e4aSElliott Hughes sin_mid /= abs(mid_d0) * abs(mid_d1) 522*e1fe3e4aSElliott Hughes except ZeroDivisionError: 523*e1fe3e4aSElliott Hughes continue 524*e1fe3e4aSElliott Hughes 525*e1fe3e4aSElliott Hughes # ...or if the angles are similar. 526*e1fe3e4aSElliott Hughes if abs(sin_mid) * (tolerance * kinkiness) <= t: 527*e1fe3e4aSElliott Hughes # Smooth enough. 528*e1fe3e4aSElliott Hughes continue 529*e1fe3e4aSElliott Hughes 530*e1fe3e4aSElliott Hughes # How visible is the kink? 531*e1fe3e4aSElliott Hughes 532*e1fe3e4aSElliott Hughes cross = sin_mid * abs(mid_d0) * abs(mid_d1) 533*e1fe3e4aSElliott Hughes arc_len = abs(mid_d0 + mid_d1) 534*e1fe3e4aSElliott Hughes deviation = abs(cross / arc_len) 535*e1fe3e4aSElliott Hughes if deviation < deviation_threshold: 536*e1fe3e4aSElliott Hughes continue 537*e1fe3e4aSElliott Hughes deviation_ratio = deviation / arc_len 538*e1fe3e4aSElliott Hughes if deviation_ratio > t: 539*e1fe3e4aSElliott Hughes continue 540*e1fe3e4aSElliott Hughes 541*e1fe3e4aSElliott Hughes this_tolerance = t / (abs(sin_mid) * kinkiness) 542*e1fe3e4aSElliott Hughes 543*e1fe3e4aSElliott Hughes log.debug( 544*e1fe3e4aSElliott Hughes "kink: deviation %g; deviation_ratio %g; sin_mid %g; r_diff %g", 545*e1fe3e4aSElliott Hughes deviation, 546*e1fe3e4aSElliott Hughes deviation_ratio, 547*e1fe3e4aSElliott Hughes sin_mid, 548*e1fe3e4aSElliott Hughes r_diff, 549*e1fe3e4aSElliott Hughes ) 550*e1fe3e4aSElliott Hughes log.debug("tolerance %g", this_tolerance) 551*e1fe3e4aSElliott Hughes yield ( 552*e1fe3e4aSElliott Hughes glyph_name, 553*e1fe3e4aSElliott Hughes { 554*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.KINK, 555*e1fe3e4aSElliott Hughes "contour": ix, 556*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 557*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 558*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 559*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 560*e1fe3e4aSElliott Hughes "value": i, 561*e1fe3e4aSElliott Hughes "tolerance": this_tolerance, 562*e1fe3e4aSElliott Hughes }, 563*e1fe3e4aSElliott Hughes ) 564*e1fe3e4aSElliott Hughes 565*e1fe3e4aSElliott Hughes # 566*e1fe3e4aSElliott Hughes # --show-all 567*e1fe3e4aSElliott Hughes # 568*e1fe3e4aSElliott Hughes 569*e1fe3e4aSElliott Hughes if show_all: 570*e1fe3e4aSElliott Hughes yield ( 571*e1fe3e4aSElliott Hughes glyph_name, 572*e1fe3e4aSElliott Hughes { 573*e1fe3e4aSElliott Hughes "type": InterpolatableProblem.NOTHING, 574*e1fe3e4aSElliott Hughes "master_1": names[m0idx], 575*e1fe3e4aSElliott Hughes "master_2": names[m1idx], 576*e1fe3e4aSElliott Hughes "master_1_idx": m0idx, 577*e1fe3e4aSElliott Hughes "master_2_idx": m1idx, 578*e1fe3e4aSElliott Hughes }, 579*e1fe3e4aSElliott Hughes ) 580*e1fe3e4aSElliott Hughes 581*e1fe3e4aSElliott Hughes 582*e1fe3e4aSElliott Hughes@wraps(test_gen) 583*e1fe3e4aSElliott Hughesdef test(*args, **kwargs): 584*e1fe3e4aSElliott Hughes problems = defaultdict(list) 585*e1fe3e4aSElliott Hughes for glyphname, problem in test_gen(*args, **kwargs): 586*e1fe3e4aSElliott Hughes problems[glyphname].append(problem) 587*e1fe3e4aSElliott Hughes return problems 588*e1fe3e4aSElliott Hughes 589*e1fe3e4aSElliott Hughes 590*e1fe3e4aSElliott Hughesdef recursivelyAddGlyph(glyphname, glyphset, ttGlyphSet, glyf): 591*e1fe3e4aSElliott Hughes if glyphname in glyphset: 592*e1fe3e4aSElliott Hughes return 593*e1fe3e4aSElliott Hughes glyphset[glyphname] = ttGlyphSet[glyphname] 594*e1fe3e4aSElliott Hughes 595*e1fe3e4aSElliott Hughes for component in getattr(glyf[glyphname], "components", []): 596*e1fe3e4aSElliott Hughes recursivelyAddGlyph(component.glyphName, glyphset, ttGlyphSet, glyf) 597*e1fe3e4aSElliott Hughes 598*e1fe3e4aSElliott Hughes 599*e1fe3e4aSElliott Hughesdef ensure_parent_dir(path): 600*e1fe3e4aSElliott Hughes dirname = os.path.dirname(path) 601*e1fe3e4aSElliott Hughes if dirname: 602*e1fe3e4aSElliott Hughes os.makedirs(dirname, exist_ok=True) 603*e1fe3e4aSElliott Hughes return path 604*e1fe3e4aSElliott Hughes 605*e1fe3e4aSElliott Hughes 606*e1fe3e4aSElliott Hughesdef main(args=None): 607*e1fe3e4aSElliott Hughes """Test for interpolatability issues between fonts""" 608*e1fe3e4aSElliott Hughes import argparse 609*e1fe3e4aSElliott Hughes import sys 610*e1fe3e4aSElliott Hughes 611*e1fe3e4aSElliott Hughes parser = argparse.ArgumentParser( 612*e1fe3e4aSElliott Hughes "fonttools varLib.interpolatable", 613*e1fe3e4aSElliott Hughes description=main.__doc__, 614*e1fe3e4aSElliott Hughes ) 615*e1fe3e4aSElliott Hughes parser.add_argument( 616*e1fe3e4aSElliott Hughes "--glyphs", 617*e1fe3e4aSElliott Hughes action="store", 618*e1fe3e4aSElliott Hughes help="Space-separate name of glyphs to check", 619*e1fe3e4aSElliott Hughes ) 620*e1fe3e4aSElliott Hughes parser.add_argument( 621*e1fe3e4aSElliott Hughes "--show-all", 622*e1fe3e4aSElliott Hughes action="store_true", 623*e1fe3e4aSElliott Hughes help="Show all glyph pairs, even if no problems are found", 624*e1fe3e4aSElliott Hughes ) 625*e1fe3e4aSElliott Hughes parser.add_argument( 626*e1fe3e4aSElliott Hughes "--tolerance", 627*e1fe3e4aSElliott Hughes action="store", 628*e1fe3e4aSElliott Hughes type=float, 629*e1fe3e4aSElliott Hughes help="Error tolerance. Between 0 and 1. Default %s" % DEFAULT_TOLERANCE, 630*e1fe3e4aSElliott Hughes ) 631*e1fe3e4aSElliott Hughes parser.add_argument( 632*e1fe3e4aSElliott Hughes "--kinkiness", 633*e1fe3e4aSElliott Hughes action="store", 634*e1fe3e4aSElliott Hughes type=float, 635*e1fe3e4aSElliott Hughes help="How aggressively report kinks. Default %s" % DEFAULT_KINKINESS, 636*e1fe3e4aSElliott Hughes ) 637*e1fe3e4aSElliott Hughes parser.add_argument( 638*e1fe3e4aSElliott Hughes "--json", 639*e1fe3e4aSElliott Hughes action="store_true", 640*e1fe3e4aSElliott Hughes help="Output report in JSON format", 641*e1fe3e4aSElliott Hughes ) 642*e1fe3e4aSElliott Hughes parser.add_argument( 643*e1fe3e4aSElliott Hughes "--pdf", 644*e1fe3e4aSElliott Hughes action="store", 645*e1fe3e4aSElliott Hughes help="Output report in PDF format", 646*e1fe3e4aSElliott Hughes ) 647*e1fe3e4aSElliott Hughes parser.add_argument( 648*e1fe3e4aSElliott Hughes "--ps", 649*e1fe3e4aSElliott Hughes action="store", 650*e1fe3e4aSElliott Hughes help="Output report in PostScript format", 651*e1fe3e4aSElliott Hughes ) 652*e1fe3e4aSElliott Hughes parser.add_argument( 653*e1fe3e4aSElliott Hughes "--html", 654*e1fe3e4aSElliott Hughes action="store", 655*e1fe3e4aSElliott Hughes help="Output report in HTML format", 656*e1fe3e4aSElliott Hughes ) 657*e1fe3e4aSElliott Hughes parser.add_argument( 658*e1fe3e4aSElliott Hughes "--quiet", 659*e1fe3e4aSElliott Hughes action="store_true", 660*e1fe3e4aSElliott Hughes help="Only exit with code 1 or 0, no output", 661*e1fe3e4aSElliott Hughes ) 662*e1fe3e4aSElliott Hughes parser.add_argument( 663*e1fe3e4aSElliott Hughes "--output", 664*e1fe3e4aSElliott Hughes action="store", 665*e1fe3e4aSElliott Hughes help="Output file for the problem report; Default: stdout", 666*e1fe3e4aSElliott Hughes ) 667*e1fe3e4aSElliott Hughes parser.add_argument( 668*e1fe3e4aSElliott Hughes "--ignore-missing", 669*e1fe3e4aSElliott Hughes action="store_true", 670*e1fe3e4aSElliott Hughes help="Will not report glyphs missing from sparse masters as errors", 671*e1fe3e4aSElliott Hughes ) 672*e1fe3e4aSElliott Hughes parser.add_argument( 673*e1fe3e4aSElliott Hughes "inputs", 674*e1fe3e4aSElliott Hughes metavar="FILE", 675*e1fe3e4aSElliott Hughes type=str, 676*e1fe3e4aSElliott Hughes nargs="+", 677*e1fe3e4aSElliott Hughes help="Input a single variable font / DesignSpace / Glyphs file, or multiple TTF/UFO files", 678*e1fe3e4aSElliott Hughes ) 679*e1fe3e4aSElliott Hughes parser.add_argument( 680*e1fe3e4aSElliott Hughes "--name", 681*e1fe3e4aSElliott Hughes metavar="NAME", 682*e1fe3e4aSElliott Hughes type=str, 683*e1fe3e4aSElliott Hughes action="append", 684*e1fe3e4aSElliott Hughes help="Name of the master to use in the report. If not provided, all are used.", 685*e1fe3e4aSElliott Hughes ) 686*e1fe3e4aSElliott Hughes parser.add_argument("-v", "--verbose", action="store_true", help="Run verbosely.") 687*e1fe3e4aSElliott Hughes parser.add_argument("--debug", action="store_true", help="Run with debug output.") 688*e1fe3e4aSElliott Hughes 689*e1fe3e4aSElliott Hughes args = parser.parse_args(args) 690*e1fe3e4aSElliott Hughes 691*e1fe3e4aSElliott Hughes from fontTools import configLogger 692*e1fe3e4aSElliott Hughes 693*e1fe3e4aSElliott Hughes configLogger(level=("INFO" if args.verbose else "ERROR")) 694*e1fe3e4aSElliott Hughes if args.debug: 695*e1fe3e4aSElliott Hughes configLogger(level="DEBUG") 696*e1fe3e4aSElliott Hughes 697*e1fe3e4aSElliott Hughes glyphs = args.glyphs.split() if args.glyphs else None 698*e1fe3e4aSElliott Hughes 699*e1fe3e4aSElliott Hughes from os.path import basename 700*e1fe3e4aSElliott Hughes 701*e1fe3e4aSElliott Hughes fonts = [] 702*e1fe3e4aSElliott Hughes names = [] 703*e1fe3e4aSElliott Hughes locations = [] 704*e1fe3e4aSElliott Hughes upem = DEFAULT_UPEM 705*e1fe3e4aSElliott Hughes 706*e1fe3e4aSElliott Hughes original_args_inputs = tuple(args.inputs) 707*e1fe3e4aSElliott Hughes 708*e1fe3e4aSElliott Hughes if len(args.inputs) == 1: 709*e1fe3e4aSElliott Hughes designspace = None 710*e1fe3e4aSElliott Hughes if args.inputs[0].endswith(".designspace"): 711*e1fe3e4aSElliott Hughes from fontTools.designspaceLib import DesignSpaceDocument 712*e1fe3e4aSElliott Hughes 713*e1fe3e4aSElliott Hughes designspace = DesignSpaceDocument.fromfile(args.inputs[0]) 714*e1fe3e4aSElliott Hughes args.inputs = [master.path for master in designspace.sources] 715*e1fe3e4aSElliott Hughes locations = [master.location for master in designspace.sources] 716*e1fe3e4aSElliott Hughes axis_triples = { 717*e1fe3e4aSElliott Hughes a.name: (a.minimum, a.default, a.maximum) for a in designspace.axes 718*e1fe3e4aSElliott Hughes } 719*e1fe3e4aSElliott Hughes axis_mappings = {a.name: a.map for a in designspace.axes} 720*e1fe3e4aSElliott Hughes axis_triples = { 721*e1fe3e4aSElliott Hughes k: tuple(piecewiseLinearMap(v, dict(axis_mappings[k])) for v in vv) 722*e1fe3e4aSElliott Hughes for k, vv in axis_triples.items() 723*e1fe3e4aSElliott Hughes } 724*e1fe3e4aSElliott Hughes 725*e1fe3e4aSElliott Hughes elif args.inputs[0].endswith((".glyphs", ".glyphspackage")): 726*e1fe3e4aSElliott Hughes from glyphsLib import GSFont, to_designspace 727*e1fe3e4aSElliott Hughes 728*e1fe3e4aSElliott Hughes gsfont = GSFont(args.inputs[0]) 729*e1fe3e4aSElliott Hughes upem = gsfont.upm 730*e1fe3e4aSElliott Hughes designspace = to_designspace(gsfont) 731*e1fe3e4aSElliott Hughes fonts = [source.font for source in designspace.sources] 732*e1fe3e4aSElliott Hughes names = ["%s-%s" % (f.info.familyName, f.info.styleName) for f in fonts] 733*e1fe3e4aSElliott Hughes args.inputs = [] 734*e1fe3e4aSElliott Hughes locations = [master.location for master in designspace.sources] 735*e1fe3e4aSElliott Hughes axis_triples = { 736*e1fe3e4aSElliott Hughes a.name: (a.minimum, a.default, a.maximum) for a in designspace.axes 737*e1fe3e4aSElliott Hughes } 738*e1fe3e4aSElliott Hughes axis_mappings = {a.name: a.map for a in designspace.axes} 739*e1fe3e4aSElliott Hughes axis_triples = { 740*e1fe3e4aSElliott Hughes k: tuple(piecewiseLinearMap(v, dict(axis_mappings[k])) for v in vv) 741*e1fe3e4aSElliott Hughes for k, vv in axis_triples.items() 742*e1fe3e4aSElliott Hughes } 743*e1fe3e4aSElliott Hughes 744*e1fe3e4aSElliott Hughes elif args.inputs[0].endswith(".ttf"): 745*e1fe3e4aSElliott Hughes from fontTools.ttLib import TTFont 746*e1fe3e4aSElliott Hughes 747*e1fe3e4aSElliott Hughes font = TTFont(args.inputs[0]) 748*e1fe3e4aSElliott Hughes upem = font["head"].unitsPerEm 749*e1fe3e4aSElliott Hughes if "gvar" in font: 750*e1fe3e4aSElliott Hughes # Is variable font 751*e1fe3e4aSElliott Hughes 752*e1fe3e4aSElliott Hughes axisMapping = {} 753*e1fe3e4aSElliott Hughes fvar = font["fvar"] 754*e1fe3e4aSElliott Hughes for axis in fvar.axes: 755*e1fe3e4aSElliott Hughes axisMapping[axis.axisTag] = { 756*e1fe3e4aSElliott Hughes -1: axis.minValue, 757*e1fe3e4aSElliott Hughes 0: axis.defaultValue, 758*e1fe3e4aSElliott Hughes 1: axis.maxValue, 759*e1fe3e4aSElliott Hughes } 760*e1fe3e4aSElliott Hughes if "avar" in font: 761*e1fe3e4aSElliott Hughes avar = font["avar"] 762*e1fe3e4aSElliott Hughes for axisTag, segments in avar.segments.items(): 763*e1fe3e4aSElliott Hughes fvarMapping = axisMapping[axisTag].copy() 764*e1fe3e4aSElliott Hughes for location, value in segments.items(): 765*e1fe3e4aSElliott Hughes axisMapping[axisTag][value] = piecewiseLinearMap( 766*e1fe3e4aSElliott Hughes location, fvarMapping 767*e1fe3e4aSElliott Hughes ) 768*e1fe3e4aSElliott Hughes 769*e1fe3e4aSElliott Hughes gvar = font["gvar"] 770*e1fe3e4aSElliott Hughes glyf = font["glyf"] 771*e1fe3e4aSElliott Hughes # Gather all glyphs at their "master" locations 772*e1fe3e4aSElliott Hughes ttGlyphSets = {} 773*e1fe3e4aSElliott Hughes glyphsets = defaultdict(dict) 774*e1fe3e4aSElliott Hughes 775*e1fe3e4aSElliott Hughes if glyphs is None: 776*e1fe3e4aSElliott Hughes glyphs = sorted(gvar.variations.keys()) 777*e1fe3e4aSElliott Hughes for glyphname in glyphs: 778*e1fe3e4aSElliott Hughes for var in gvar.variations[glyphname]: 779*e1fe3e4aSElliott Hughes locDict = {} 780*e1fe3e4aSElliott Hughes loc = [] 781*e1fe3e4aSElliott Hughes for tag, val in sorted(var.axes.items()): 782*e1fe3e4aSElliott Hughes locDict[tag] = val[1] 783*e1fe3e4aSElliott Hughes loc.append((tag, val[1])) 784*e1fe3e4aSElliott Hughes 785*e1fe3e4aSElliott Hughes locTuple = tuple(loc) 786*e1fe3e4aSElliott Hughes if locTuple not in ttGlyphSets: 787*e1fe3e4aSElliott Hughes ttGlyphSets[locTuple] = font.getGlyphSet( 788*e1fe3e4aSElliott Hughes location=locDict, normalized=True, recalcBounds=False 789*e1fe3e4aSElliott Hughes ) 790*e1fe3e4aSElliott Hughes 791*e1fe3e4aSElliott Hughes recursivelyAddGlyph( 792*e1fe3e4aSElliott Hughes glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf 793*e1fe3e4aSElliott Hughes ) 794*e1fe3e4aSElliott Hughes 795*e1fe3e4aSElliott Hughes names = ["''"] 796*e1fe3e4aSElliott Hughes fonts = [font.getGlyphSet()] 797*e1fe3e4aSElliott Hughes locations = [{}] 798*e1fe3e4aSElliott Hughes axis_triples = {a: (-1, 0, +1) for a in sorted(axisMapping.keys())} 799*e1fe3e4aSElliott Hughes for locTuple in sorted(glyphsets.keys(), key=lambda v: (len(v), v)): 800*e1fe3e4aSElliott Hughes name = ( 801*e1fe3e4aSElliott Hughes "'" 802*e1fe3e4aSElliott Hughes + " ".join( 803*e1fe3e4aSElliott Hughes "%s=%s" 804*e1fe3e4aSElliott Hughes % ( 805*e1fe3e4aSElliott Hughes k, 806*e1fe3e4aSElliott Hughes floatToFixedToStr( 807*e1fe3e4aSElliott Hughes piecewiseLinearMap(v, axisMapping[k]), 14 808*e1fe3e4aSElliott Hughes ), 809*e1fe3e4aSElliott Hughes ) 810*e1fe3e4aSElliott Hughes for k, v in locTuple 811*e1fe3e4aSElliott Hughes ) 812*e1fe3e4aSElliott Hughes + "'" 813*e1fe3e4aSElliott Hughes ) 814*e1fe3e4aSElliott Hughes names.append(name) 815*e1fe3e4aSElliott Hughes fonts.append(glyphsets[locTuple]) 816*e1fe3e4aSElliott Hughes locations.append(dict(locTuple)) 817*e1fe3e4aSElliott Hughes args.ignore_missing = True 818*e1fe3e4aSElliott Hughes args.inputs = [] 819*e1fe3e4aSElliott Hughes 820*e1fe3e4aSElliott Hughes if not locations: 821*e1fe3e4aSElliott Hughes locations = [{} for _ in fonts] 822*e1fe3e4aSElliott Hughes 823*e1fe3e4aSElliott Hughes for filename in args.inputs: 824*e1fe3e4aSElliott Hughes if filename.endswith(".ufo"): 825*e1fe3e4aSElliott Hughes from fontTools.ufoLib import UFOReader 826*e1fe3e4aSElliott Hughes 827*e1fe3e4aSElliott Hughes font = UFOReader(filename) 828*e1fe3e4aSElliott Hughes info = SimpleNamespace() 829*e1fe3e4aSElliott Hughes font.readInfo(info) 830*e1fe3e4aSElliott Hughes upem = info.unitsPerEm 831*e1fe3e4aSElliott Hughes fonts.append(font) 832*e1fe3e4aSElliott Hughes else: 833*e1fe3e4aSElliott Hughes from fontTools.ttLib import TTFont 834*e1fe3e4aSElliott Hughes 835*e1fe3e4aSElliott Hughes font = TTFont(filename) 836*e1fe3e4aSElliott Hughes upem = font["head"].unitsPerEm 837*e1fe3e4aSElliott Hughes fonts.append(font) 838*e1fe3e4aSElliott Hughes 839*e1fe3e4aSElliott Hughes names.append(basename(filename).rsplit(".", 1)[0]) 840*e1fe3e4aSElliott Hughes 841*e1fe3e4aSElliott Hughes glyphsets = [] 842*e1fe3e4aSElliott Hughes for font in fonts: 843*e1fe3e4aSElliott Hughes if hasattr(font, "getGlyphSet"): 844*e1fe3e4aSElliott Hughes glyphset = font.getGlyphSet() 845*e1fe3e4aSElliott Hughes else: 846*e1fe3e4aSElliott Hughes glyphset = font 847*e1fe3e4aSElliott Hughes glyphsets.append({k: glyphset[k] for k in glyphset.keys()}) 848*e1fe3e4aSElliott Hughes 849*e1fe3e4aSElliott Hughes if args.name: 850*e1fe3e4aSElliott Hughes accepted_names = set(args.name) 851*e1fe3e4aSElliott Hughes glyphsets = [ 852*e1fe3e4aSElliott Hughes glyphset 853*e1fe3e4aSElliott Hughes for name, glyphset in zip(names, glyphsets) 854*e1fe3e4aSElliott Hughes if name in accepted_names 855*e1fe3e4aSElliott Hughes ] 856*e1fe3e4aSElliott Hughes locations = [ 857*e1fe3e4aSElliott Hughes location 858*e1fe3e4aSElliott Hughes for name, location in zip(names, locations) 859*e1fe3e4aSElliott Hughes if name in accepted_names 860*e1fe3e4aSElliott Hughes ] 861*e1fe3e4aSElliott Hughes names = [name for name in names if name in accepted_names] 862*e1fe3e4aSElliott Hughes 863*e1fe3e4aSElliott Hughes if not glyphs: 864*e1fe3e4aSElliott Hughes glyphs = sorted(set([gn for glyphset in glyphsets for gn in glyphset.keys()])) 865*e1fe3e4aSElliott Hughes 866*e1fe3e4aSElliott Hughes glyphsSet = set(glyphs) 867*e1fe3e4aSElliott Hughes for glyphset in glyphsets: 868*e1fe3e4aSElliott Hughes glyphSetGlyphNames = set(glyphset.keys()) 869*e1fe3e4aSElliott Hughes diff = glyphsSet - glyphSetGlyphNames 870*e1fe3e4aSElliott Hughes if diff: 871*e1fe3e4aSElliott Hughes for gn in diff: 872*e1fe3e4aSElliott Hughes glyphset[gn] = None 873*e1fe3e4aSElliott Hughes 874*e1fe3e4aSElliott Hughes # Normalize locations 875*e1fe3e4aSElliott Hughes locations = [normalizeLocation(loc, axis_triples) for loc in locations] 876*e1fe3e4aSElliott Hughes tolerance = args.tolerance or DEFAULT_TOLERANCE 877*e1fe3e4aSElliott Hughes kinkiness = args.kinkiness if args.kinkiness is not None else DEFAULT_KINKINESS 878*e1fe3e4aSElliott Hughes 879*e1fe3e4aSElliott Hughes try: 880*e1fe3e4aSElliott Hughes log.info("Running on %d glyphsets", len(glyphsets)) 881*e1fe3e4aSElliott Hughes log.info("Locations: %s", pformat(locations)) 882*e1fe3e4aSElliott Hughes problems_gen = test_gen( 883*e1fe3e4aSElliott Hughes glyphsets, 884*e1fe3e4aSElliott Hughes glyphs=glyphs, 885*e1fe3e4aSElliott Hughes names=names, 886*e1fe3e4aSElliott Hughes locations=locations, 887*e1fe3e4aSElliott Hughes upem=upem, 888*e1fe3e4aSElliott Hughes ignore_missing=args.ignore_missing, 889*e1fe3e4aSElliott Hughes tolerance=tolerance, 890*e1fe3e4aSElliott Hughes kinkiness=kinkiness, 891*e1fe3e4aSElliott Hughes show_all=args.show_all, 892*e1fe3e4aSElliott Hughes ) 893*e1fe3e4aSElliott Hughes problems = defaultdict(list) 894*e1fe3e4aSElliott Hughes 895*e1fe3e4aSElliott Hughes f = ( 896*e1fe3e4aSElliott Hughes sys.stdout 897*e1fe3e4aSElliott Hughes if args.output is None 898*e1fe3e4aSElliott Hughes else open(ensure_parent_dir(args.output), "w") 899*e1fe3e4aSElliott Hughes ) 900*e1fe3e4aSElliott Hughes 901*e1fe3e4aSElliott Hughes if not args.quiet: 902*e1fe3e4aSElliott Hughes if args.json: 903*e1fe3e4aSElliott Hughes import json 904*e1fe3e4aSElliott Hughes 905*e1fe3e4aSElliott Hughes for glyphname, problem in problems_gen: 906*e1fe3e4aSElliott Hughes problems[glyphname].append(problem) 907*e1fe3e4aSElliott Hughes 908*e1fe3e4aSElliott Hughes print(json.dumps(problems), file=f) 909*e1fe3e4aSElliott Hughes else: 910*e1fe3e4aSElliott Hughes last_glyphname = None 911*e1fe3e4aSElliott Hughes for glyphname, p in problems_gen: 912*e1fe3e4aSElliott Hughes problems[glyphname].append(p) 913*e1fe3e4aSElliott Hughes 914*e1fe3e4aSElliott Hughes if glyphname != last_glyphname: 915*e1fe3e4aSElliott Hughes print(f"Glyph {glyphname} was not compatible:", file=f) 916*e1fe3e4aSElliott Hughes last_glyphname = glyphname 917*e1fe3e4aSElliott Hughes last_master_idxs = None 918*e1fe3e4aSElliott Hughes 919*e1fe3e4aSElliott Hughes master_idxs = ( 920*e1fe3e4aSElliott Hughes (p["master_idx"]) 921*e1fe3e4aSElliott Hughes if "master_idx" in p 922*e1fe3e4aSElliott Hughes else (p["master_1_idx"], p["master_2_idx"]) 923*e1fe3e4aSElliott Hughes ) 924*e1fe3e4aSElliott Hughes if master_idxs != last_master_idxs: 925*e1fe3e4aSElliott Hughes master_names = ( 926*e1fe3e4aSElliott Hughes (p["master"]) 927*e1fe3e4aSElliott Hughes if "master" in p 928*e1fe3e4aSElliott Hughes else (p["master_1"], p["master_2"]) 929*e1fe3e4aSElliott Hughes ) 930*e1fe3e4aSElliott Hughes print(f" Masters: %s:" % ", ".join(master_names), file=f) 931*e1fe3e4aSElliott Hughes last_master_idxs = master_idxs 932*e1fe3e4aSElliott Hughes 933*e1fe3e4aSElliott Hughes if p["type"] == InterpolatableProblem.MISSING: 934*e1fe3e4aSElliott Hughes print( 935*e1fe3e4aSElliott Hughes " Glyph was missing in master %s" % p["master"], file=f 936*e1fe3e4aSElliott Hughes ) 937*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.OPEN_PATH: 938*e1fe3e4aSElliott Hughes print( 939*e1fe3e4aSElliott Hughes " Glyph has an open path in master %s" % p["master"], 940*e1fe3e4aSElliott Hughes file=f, 941*e1fe3e4aSElliott Hughes ) 942*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.PATH_COUNT: 943*e1fe3e4aSElliott Hughes print( 944*e1fe3e4aSElliott Hughes " Path count differs: %i in %s, %i in %s" 945*e1fe3e4aSElliott Hughes % ( 946*e1fe3e4aSElliott Hughes p["value_1"], 947*e1fe3e4aSElliott Hughes p["master_1"], 948*e1fe3e4aSElliott Hughes p["value_2"], 949*e1fe3e4aSElliott Hughes p["master_2"], 950*e1fe3e4aSElliott Hughes ), 951*e1fe3e4aSElliott Hughes file=f, 952*e1fe3e4aSElliott Hughes ) 953*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.NODE_COUNT: 954*e1fe3e4aSElliott Hughes print( 955*e1fe3e4aSElliott Hughes " Node count differs in path %i: %i in %s, %i in %s" 956*e1fe3e4aSElliott Hughes % ( 957*e1fe3e4aSElliott Hughes p["path"], 958*e1fe3e4aSElliott Hughes p["value_1"], 959*e1fe3e4aSElliott Hughes p["master_1"], 960*e1fe3e4aSElliott Hughes p["value_2"], 961*e1fe3e4aSElliott Hughes p["master_2"], 962*e1fe3e4aSElliott Hughes ), 963*e1fe3e4aSElliott Hughes file=f, 964*e1fe3e4aSElliott Hughes ) 965*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.NODE_INCOMPATIBILITY: 966*e1fe3e4aSElliott Hughes print( 967*e1fe3e4aSElliott Hughes " Node %o incompatible in path %i: %s in %s, %s in %s" 968*e1fe3e4aSElliott Hughes % ( 969*e1fe3e4aSElliott Hughes p["node"], 970*e1fe3e4aSElliott Hughes p["path"], 971*e1fe3e4aSElliott Hughes p["value_1"], 972*e1fe3e4aSElliott Hughes p["master_1"], 973*e1fe3e4aSElliott Hughes p["value_2"], 974*e1fe3e4aSElliott Hughes p["master_2"], 975*e1fe3e4aSElliott Hughes ), 976*e1fe3e4aSElliott Hughes file=f, 977*e1fe3e4aSElliott Hughes ) 978*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.CONTOUR_ORDER: 979*e1fe3e4aSElliott Hughes print( 980*e1fe3e4aSElliott Hughes " Contour order differs: %s in %s, %s in %s" 981*e1fe3e4aSElliott Hughes % ( 982*e1fe3e4aSElliott Hughes p["value_1"], 983*e1fe3e4aSElliott Hughes p["master_1"], 984*e1fe3e4aSElliott Hughes p["value_2"], 985*e1fe3e4aSElliott Hughes p["master_2"], 986*e1fe3e4aSElliott Hughes ), 987*e1fe3e4aSElliott Hughes file=f, 988*e1fe3e4aSElliott Hughes ) 989*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.WRONG_START_POINT: 990*e1fe3e4aSElliott Hughes print( 991*e1fe3e4aSElliott Hughes " Contour %d start point differs: %s in %s, %s in %s; reversed: %s" 992*e1fe3e4aSElliott Hughes % ( 993*e1fe3e4aSElliott Hughes p["contour"], 994*e1fe3e4aSElliott Hughes p["value_1"], 995*e1fe3e4aSElliott Hughes p["master_1"], 996*e1fe3e4aSElliott Hughes p["value_2"], 997*e1fe3e4aSElliott Hughes p["master_2"], 998*e1fe3e4aSElliott Hughes p["reversed"], 999*e1fe3e4aSElliott Hughes ), 1000*e1fe3e4aSElliott Hughes file=f, 1001*e1fe3e4aSElliott Hughes ) 1002*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.UNDERWEIGHT: 1003*e1fe3e4aSElliott Hughes print( 1004*e1fe3e4aSElliott Hughes " Contour %d interpolation is underweight: %s, %s" 1005*e1fe3e4aSElliott Hughes % ( 1006*e1fe3e4aSElliott Hughes p["contour"], 1007*e1fe3e4aSElliott Hughes p["master_1"], 1008*e1fe3e4aSElliott Hughes p["master_2"], 1009*e1fe3e4aSElliott Hughes ), 1010*e1fe3e4aSElliott Hughes file=f, 1011*e1fe3e4aSElliott Hughes ) 1012*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.OVERWEIGHT: 1013*e1fe3e4aSElliott Hughes print( 1014*e1fe3e4aSElliott Hughes " Contour %d interpolation is overweight: %s, %s" 1015*e1fe3e4aSElliott Hughes % ( 1016*e1fe3e4aSElliott Hughes p["contour"], 1017*e1fe3e4aSElliott Hughes p["master_1"], 1018*e1fe3e4aSElliott Hughes p["master_2"], 1019*e1fe3e4aSElliott Hughes ), 1020*e1fe3e4aSElliott Hughes file=f, 1021*e1fe3e4aSElliott Hughes ) 1022*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.KINK: 1023*e1fe3e4aSElliott Hughes print( 1024*e1fe3e4aSElliott Hughes " Contour %d has a kink at %s: %s, %s" 1025*e1fe3e4aSElliott Hughes % ( 1026*e1fe3e4aSElliott Hughes p["contour"], 1027*e1fe3e4aSElliott Hughes p["value"], 1028*e1fe3e4aSElliott Hughes p["master_1"], 1029*e1fe3e4aSElliott Hughes p["master_2"], 1030*e1fe3e4aSElliott Hughes ), 1031*e1fe3e4aSElliott Hughes file=f, 1032*e1fe3e4aSElliott Hughes ) 1033*e1fe3e4aSElliott Hughes elif p["type"] == InterpolatableProblem.NOTHING: 1034*e1fe3e4aSElliott Hughes print( 1035*e1fe3e4aSElliott Hughes " Showing %s and %s" 1036*e1fe3e4aSElliott Hughes % ( 1037*e1fe3e4aSElliott Hughes p["master_1"], 1038*e1fe3e4aSElliott Hughes p["master_2"], 1039*e1fe3e4aSElliott Hughes ), 1040*e1fe3e4aSElliott Hughes file=f, 1041*e1fe3e4aSElliott Hughes ) 1042*e1fe3e4aSElliott Hughes else: 1043*e1fe3e4aSElliott Hughes for glyphname, problem in problems_gen: 1044*e1fe3e4aSElliott Hughes problems[glyphname].append(problem) 1045*e1fe3e4aSElliott Hughes 1046*e1fe3e4aSElliott Hughes problems = sort_problems(problems) 1047*e1fe3e4aSElliott Hughes 1048*e1fe3e4aSElliott Hughes for p in "ps", "pdf": 1049*e1fe3e4aSElliott Hughes arg = getattr(args, p) 1050*e1fe3e4aSElliott Hughes if arg is None: 1051*e1fe3e4aSElliott Hughes continue 1052*e1fe3e4aSElliott Hughes log.info("Writing %s to %s", p.upper(), arg) 1053*e1fe3e4aSElliott Hughes from .interpolatablePlot import InterpolatablePS, InterpolatablePDF 1054*e1fe3e4aSElliott Hughes 1055*e1fe3e4aSElliott Hughes PlotterClass = InterpolatablePS if p == "ps" else InterpolatablePDF 1056*e1fe3e4aSElliott Hughes 1057*e1fe3e4aSElliott Hughes with PlotterClass( 1058*e1fe3e4aSElliott Hughes ensure_parent_dir(arg), glyphsets=glyphsets, names=names 1059*e1fe3e4aSElliott Hughes ) as doc: 1060*e1fe3e4aSElliott Hughes doc.add_title_page( 1061*e1fe3e4aSElliott Hughes original_args_inputs, tolerance=tolerance, kinkiness=kinkiness 1062*e1fe3e4aSElliott Hughes ) 1063*e1fe3e4aSElliott Hughes if problems: 1064*e1fe3e4aSElliott Hughes doc.add_summary(problems) 1065*e1fe3e4aSElliott Hughes doc.add_problems(problems) 1066*e1fe3e4aSElliott Hughes if not problems and not args.quiet: 1067*e1fe3e4aSElliott Hughes doc.draw_cupcake() 1068*e1fe3e4aSElliott Hughes if problems: 1069*e1fe3e4aSElliott Hughes doc.add_index() 1070*e1fe3e4aSElliott Hughes doc.add_table_of_contents() 1071*e1fe3e4aSElliott Hughes 1072*e1fe3e4aSElliott Hughes if args.html: 1073*e1fe3e4aSElliott Hughes log.info("Writing HTML to %s", args.html) 1074*e1fe3e4aSElliott Hughes from .interpolatablePlot import InterpolatableSVG 1075*e1fe3e4aSElliott Hughes 1076*e1fe3e4aSElliott Hughes svgs = [] 1077*e1fe3e4aSElliott Hughes glyph_starts = {} 1078*e1fe3e4aSElliott Hughes with InterpolatableSVG(svgs, glyphsets=glyphsets, names=names) as svg: 1079*e1fe3e4aSElliott Hughes svg.add_title_page( 1080*e1fe3e4aSElliott Hughes original_args_inputs, 1081*e1fe3e4aSElliott Hughes show_tolerance=False, 1082*e1fe3e4aSElliott Hughes tolerance=tolerance, 1083*e1fe3e4aSElliott Hughes kinkiness=kinkiness, 1084*e1fe3e4aSElliott Hughes ) 1085*e1fe3e4aSElliott Hughes for glyph, glyph_problems in problems.items(): 1086*e1fe3e4aSElliott Hughes glyph_starts[len(svgs)] = glyph 1087*e1fe3e4aSElliott Hughes svg.add_problems( 1088*e1fe3e4aSElliott Hughes {glyph: glyph_problems}, 1089*e1fe3e4aSElliott Hughes show_tolerance=False, 1090*e1fe3e4aSElliott Hughes show_page_number=False, 1091*e1fe3e4aSElliott Hughes ) 1092*e1fe3e4aSElliott Hughes if not problems and not args.quiet: 1093*e1fe3e4aSElliott Hughes svg.draw_cupcake() 1094*e1fe3e4aSElliott Hughes 1095*e1fe3e4aSElliott Hughes import base64 1096*e1fe3e4aSElliott Hughes 1097*e1fe3e4aSElliott Hughes with open(ensure_parent_dir(args.html), "wb") as f: 1098*e1fe3e4aSElliott Hughes f.write(b"<!DOCTYPE html>\n") 1099*e1fe3e4aSElliott Hughes f.write( 1100*e1fe3e4aSElliott Hughes b'<html><body align="center" style="font-family: sans-serif; text-color: #222">\n' 1101*e1fe3e4aSElliott Hughes ) 1102*e1fe3e4aSElliott Hughes f.write(b"<title>fonttools varLib.interpolatable report</title>\n") 1103*e1fe3e4aSElliott Hughes for i, svg in enumerate(svgs): 1104*e1fe3e4aSElliott Hughes if i in glyph_starts: 1105*e1fe3e4aSElliott Hughes f.write(f"<h1>Glyph {glyph_starts[i]}</h1>\n".encode("utf-8")) 1106*e1fe3e4aSElliott Hughes f.write("<img src='data:image/svg+xml;base64,".encode("utf-8")) 1107*e1fe3e4aSElliott Hughes f.write(base64.b64encode(svg)) 1108*e1fe3e4aSElliott Hughes f.write(b"' />\n") 1109*e1fe3e4aSElliott Hughes f.write(b"<hr>\n") 1110*e1fe3e4aSElliott Hughes f.write(b"</body></html>\n") 1111*e1fe3e4aSElliott Hughes 1112*e1fe3e4aSElliott Hughes except Exception as e: 1113*e1fe3e4aSElliott Hughes e.args += original_args_inputs 1114*e1fe3e4aSElliott Hughes log.error(e) 1115*e1fe3e4aSElliott Hughes raise 1116*e1fe3e4aSElliott Hughes 1117*e1fe3e4aSElliott Hughes if problems: 1118*e1fe3e4aSElliott Hughes return problems 1119*e1fe3e4aSElliott Hughes 1120*e1fe3e4aSElliott Hughes 1121*e1fe3e4aSElliott Hughesif __name__ == "__main__": 1122*e1fe3e4aSElliott Hughes import sys 1123*e1fe3e4aSElliott Hughes 1124*e1fe3e4aSElliott Hughes problems = main() 1125*e1fe3e4aSElliott Hughes sys.exit(int(bool(problems))) 1126