1*e1fe3e4aSElliott Hughesfrom .interpolatableHelpers import * 2*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont 3*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.ttGlyphSet import LerpGlyphSet 4*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import ( 5*e1fe3e4aSElliott Hughes RecordingPen, 6*e1fe3e4aSElliott Hughes DecomposingRecordingPen, 7*e1fe3e4aSElliott Hughes RecordingPointPen, 8*e1fe3e4aSElliott Hughes) 9*e1fe3e4aSElliott Hughesfrom fontTools.pens.boundsPen import ControlBoundsPen 10*e1fe3e4aSElliott Hughesfrom fontTools.pens.cairoPen import CairoPen 11*e1fe3e4aSElliott Hughesfrom fontTools.pens.pointPen import ( 12*e1fe3e4aSElliott Hughes SegmentToPointPen, 13*e1fe3e4aSElliott Hughes PointToSegmentPen, 14*e1fe3e4aSElliott Hughes ReverseContourPointPen, 15*e1fe3e4aSElliott Hughes) 16*e1fe3e4aSElliott Hughesfrom fontTools.varLib.interpolatableHelpers import ( 17*e1fe3e4aSElliott Hughes PerContourOrComponentPen, 18*e1fe3e4aSElliott Hughes SimpleRecordingPointPen, 19*e1fe3e4aSElliott Hughes) 20*e1fe3e4aSElliott Hughesfrom itertools import cycle 21*e1fe3e4aSElliott Hughesfrom functools import wraps 22*e1fe3e4aSElliott Hughesfrom io import BytesIO 23*e1fe3e4aSElliott Hughesimport cairo 24*e1fe3e4aSElliott Hughesimport math 25*e1fe3e4aSElliott Hughesimport os 26*e1fe3e4aSElliott Hughesimport logging 27*e1fe3e4aSElliott Hughes 28*e1fe3e4aSElliott Hugheslog = logging.getLogger("fontTools.varLib.interpolatable") 29*e1fe3e4aSElliott Hughes 30*e1fe3e4aSElliott Hughes 31*e1fe3e4aSElliott Hughesclass OverridingDict(dict): 32*e1fe3e4aSElliott Hughes def __init__(self, parent_dict): 33*e1fe3e4aSElliott Hughes self.parent_dict = parent_dict 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughes def __missing__(self, key): 36*e1fe3e4aSElliott Hughes return self.parent_dict[key] 37*e1fe3e4aSElliott Hughes 38*e1fe3e4aSElliott Hughes 39*e1fe3e4aSElliott Hughesclass InterpolatablePlot: 40*e1fe3e4aSElliott Hughes width = 8.5 * 72 41*e1fe3e4aSElliott Hughes height = 11 * 72 42*e1fe3e4aSElliott Hughes pad = 0.1 * 72 43*e1fe3e4aSElliott Hughes title_font_size = 24 44*e1fe3e4aSElliott Hughes font_size = 16 45*e1fe3e4aSElliott Hughes page_number = 1 46*e1fe3e4aSElliott Hughes head_color = (0.3, 0.3, 0.3) 47*e1fe3e4aSElliott Hughes label_color = (0.2, 0.2, 0.2) 48*e1fe3e4aSElliott Hughes border_color = (0.9, 0.9, 0.9) 49*e1fe3e4aSElliott Hughes border_width = 0.5 50*e1fe3e4aSElliott Hughes fill_color = (0.8, 0.8, 0.8) 51*e1fe3e4aSElliott Hughes stroke_color = (0.1, 0.1, 0.1) 52*e1fe3e4aSElliott Hughes stroke_width = 1 53*e1fe3e4aSElliott Hughes oncurve_node_color = (0, 0.8, 0, 0.7) 54*e1fe3e4aSElliott Hughes oncurve_node_diameter = 6 55*e1fe3e4aSElliott Hughes offcurve_node_color = (0, 0.5, 0, 0.7) 56*e1fe3e4aSElliott Hughes offcurve_node_diameter = 4 57*e1fe3e4aSElliott Hughes handle_color = (0, 0.5, 0, 0.7) 58*e1fe3e4aSElliott Hughes handle_width = 0.5 59*e1fe3e4aSElliott Hughes corrected_start_point_color = (0, 0.9, 0, 0.7) 60*e1fe3e4aSElliott Hughes corrected_start_point_size = 7 61*e1fe3e4aSElliott Hughes wrong_start_point_color = (1, 0, 0, 0.7) 62*e1fe3e4aSElliott Hughes start_point_color = (0, 0, 1, 0.7) 63*e1fe3e4aSElliott Hughes start_arrow_length = 9 64*e1fe3e4aSElliott Hughes kink_point_size = 7 65*e1fe3e4aSElliott Hughes kink_point_color = (1, 0, 1, 0.7) 66*e1fe3e4aSElliott Hughes kink_circle_size = 15 67*e1fe3e4aSElliott Hughes kink_circle_stroke_width = 1 68*e1fe3e4aSElliott Hughes kink_circle_color = (1, 0, 1, 0.7) 69*e1fe3e4aSElliott Hughes contour_colors = ((1, 0, 0), (0, 0, 1), (0, 1, 0), (1, 1, 0), (1, 0, 1), (0, 1, 1)) 70*e1fe3e4aSElliott Hughes contour_alpha = 0.5 71*e1fe3e4aSElliott Hughes weight_issue_contour_color = (0, 0, 0, 0.4) 72*e1fe3e4aSElliott Hughes no_issues_label = "Your font's good! Have a cupcake..." 73*e1fe3e4aSElliott Hughes no_issues_label_color = (0, 0.5, 0) 74*e1fe3e4aSElliott Hughes cupcake_color = (0.3, 0, 0.3) 75*e1fe3e4aSElliott Hughes cupcake = r""" 76*e1fe3e4aSElliott Hughes ,@. 77*e1fe3e4aSElliott Hughes ,@.@@,. 78*e1fe3e4aSElliott Hughes ,@@,.@@@. @.@@@,. 79*e1fe3e4aSElliott Hughes ,@@. @@@. @@. @@,. 80*e1fe3e4aSElliott Hughes ,@@@.@,.@. @. @@@@,.@.@@,. 81*e1fe3e4aSElliott Hughes ,@@.@. @@.@@. @,. .@' @' @@, 82*e1fe3e4aSElliott Hughes ,@@. @. .@@.@@@. @@' @, 83*e1fe3e4aSElliott Hughes,@. @@. @, 84*e1fe3e4aSElliott Hughes@. @,@@,. , .@@, 85*e1fe3e4aSElliott Hughes@,. .@,@@,. .@@,. , .@@, @, @, 86*e1fe3e4aSElliott Hughes@. .@. @ @@,. , @ 87*e1fe3e4aSElliott Hughes @,.@@. @,. @@,. @. @,. @' 88*e1fe3e4aSElliott Hughes @@||@,. @'@,. @@,. @@ @,. @'@@, @' 89*e1fe3e4aSElliott Hughes \\@@@@' @,. @'@@@@' @@,. @@@' //@@@' 90*e1fe3e4aSElliott Hughes |||||||| @@,. @@' ||||||| |@@@|@|| || 91*e1fe3e4aSElliott Hughes \\\\\\\ ||@@@|| ||||||| ||||||| // 92*e1fe3e4aSElliott Hughes ||||||| |||||| |||||| |||||| || 93*e1fe3e4aSElliott Hughes \\\\\\ |||||| |||||| |||||| // 94*e1fe3e4aSElliott Hughes |||||| ||||| ||||| ||||| || 95*e1fe3e4aSElliott Hughes \\\\\ ||||| ||||| ||||| // 96*e1fe3e4aSElliott Hughes ||||| |||| ||||| |||| || 97*e1fe3e4aSElliott Hughes \\\\ |||| |||| |||| // 98*e1fe3e4aSElliott Hughes |||||||||||||||||||||||| 99*e1fe3e4aSElliott Hughes""" 100*e1fe3e4aSElliott Hughes emoticon_color = (0, 0.3, 0.3) 101*e1fe3e4aSElliott Hughes shrug = r"""\_(")_/""" 102*e1fe3e4aSElliott Hughes underweight = r""" 103*e1fe3e4aSElliott Hughes o 104*e1fe3e4aSElliott Hughes/|\ 105*e1fe3e4aSElliott Hughes/ \ 106*e1fe3e4aSElliott Hughes""" 107*e1fe3e4aSElliott Hughes overweight = r""" 108*e1fe3e4aSElliott Hughes o 109*e1fe3e4aSElliott Hughes/O\ 110*e1fe3e4aSElliott Hughes/ \ 111*e1fe3e4aSElliott Hughes""" 112*e1fe3e4aSElliott Hughes yay = r""" \o/ """ 113*e1fe3e4aSElliott Hughes 114*e1fe3e4aSElliott Hughes def __init__(self, out, glyphsets, names=None, **kwargs): 115*e1fe3e4aSElliott Hughes self.out = out 116*e1fe3e4aSElliott Hughes self.glyphsets = glyphsets 117*e1fe3e4aSElliott Hughes self.names = names or [repr(g) for g in glyphsets] 118*e1fe3e4aSElliott Hughes self.toc = {} 119*e1fe3e4aSElliott Hughes 120*e1fe3e4aSElliott Hughes for k, v in kwargs.items(): 121*e1fe3e4aSElliott Hughes if not hasattr(self, k): 122*e1fe3e4aSElliott Hughes raise TypeError("Unknown keyword argument: %s" % k) 123*e1fe3e4aSElliott Hughes setattr(self, k, v) 124*e1fe3e4aSElliott Hughes 125*e1fe3e4aSElliott Hughes self.panel_width = self.width / 2 - self.pad * 3 126*e1fe3e4aSElliott Hughes self.panel_height = ( 127*e1fe3e4aSElliott Hughes self.height / 2 - self.pad * 6 - self.font_size * 2 - self.title_font_size 128*e1fe3e4aSElliott Hughes ) 129*e1fe3e4aSElliott Hughes 130*e1fe3e4aSElliott Hughes def __enter__(self): 131*e1fe3e4aSElliott Hughes return self 132*e1fe3e4aSElliott Hughes 133*e1fe3e4aSElliott Hughes def __exit__(self, type, value, traceback): 134*e1fe3e4aSElliott Hughes pass 135*e1fe3e4aSElliott Hughes 136*e1fe3e4aSElliott Hughes def show_page(self): 137*e1fe3e4aSElliott Hughes self.page_number += 1 138*e1fe3e4aSElliott Hughes 139*e1fe3e4aSElliott Hughes def add_title_page( 140*e1fe3e4aSElliott Hughes self, files, *, show_tolerance=True, tolerance=None, kinkiness=None 141*e1fe3e4aSElliott Hughes ): 142*e1fe3e4aSElliott Hughes pad = self.pad 143*e1fe3e4aSElliott Hughes width = self.width - 3 * self.pad 144*e1fe3e4aSElliott Hughes height = self.height - 2 * self.pad 145*e1fe3e4aSElliott Hughes x = y = pad 146*e1fe3e4aSElliott Hughes 147*e1fe3e4aSElliott Hughes self.draw_label( 148*e1fe3e4aSElliott Hughes "Problem report for:", 149*e1fe3e4aSElliott Hughes x=x, 150*e1fe3e4aSElliott Hughes y=y, 151*e1fe3e4aSElliott Hughes bold=True, 152*e1fe3e4aSElliott Hughes width=width, 153*e1fe3e4aSElliott Hughes font_size=self.title_font_size, 154*e1fe3e4aSElliott Hughes ) 155*e1fe3e4aSElliott Hughes y += self.title_font_size 156*e1fe3e4aSElliott Hughes 157*e1fe3e4aSElliott Hughes import hashlib 158*e1fe3e4aSElliott Hughes 159*e1fe3e4aSElliott Hughes for file in files: 160*e1fe3e4aSElliott Hughes base_file = os.path.basename(file) 161*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 162*e1fe3e4aSElliott Hughes self.draw_label(base_file, x=x, y=y, bold=True, width=width) 163*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 164*e1fe3e4aSElliott Hughes 165*e1fe3e4aSElliott Hughes try: 166*e1fe3e4aSElliott Hughes h = hashlib.sha1(open(file, "rb").read()).hexdigest() 167*e1fe3e4aSElliott Hughes self.draw_label("sha1: %s" % h, x=x + pad, y=y, width=width) 168*e1fe3e4aSElliott Hughes y += self.font_size 169*e1fe3e4aSElliott Hughes except IsADirectoryError: 170*e1fe3e4aSElliott Hughes pass 171*e1fe3e4aSElliott Hughes 172*e1fe3e4aSElliott Hughes if file.endswith(".ttf"): 173*e1fe3e4aSElliott Hughes ttFont = TTFont(file) 174*e1fe3e4aSElliott Hughes name = ttFont["name"] if "name" in ttFont else None 175*e1fe3e4aSElliott Hughes if name: 176*e1fe3e4aSElliott Hughes for what, nameIDs in ( 177*e1fe3e4aSElliott Hughes ("Family name", (21, 16, 1)), 178*e1fe3e4aSElliott Hughes ("Version", (5,)), 179*e1fe3e4aSElliott Hughes ): 180*e1fe3e4aSElliott Hughes n = name.getFirstDebugName(nameIDs) 181*e1fe3e4aSElliott Hughes if n is None: 182*e1fe3e4aSElliott Hughes continue 183*e1fe3e4aSElliott Hughes self.draw_label( 184*e1fe3e4aSElliott Hughes "%s: %s" % (what, n), x=x + pad, y=y, width=width 185*e1fe3e4aSElliott Hughes ) 186*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 187*e1fe3e4aSElliott Hughes elif file.endswith((".glyphs", ".glyphspackage")): 188*e1fe3e4aSElliott Hughes from glyphsLib import GSFont 189*e1fe3e4aSElliott Hughes 190*e1fe3e4aSElliott Hughes f = GSFont(file) 191*e1fe3e4aSElliott Hughes for what, field in ( 192*e1fe3e4aSElliott Hughes ("Family name", "familyName"), 193*e1fe3e4aSElliott Hughes ("VersionMajor", "versionMajor"), 194*e1fe3e4aSElliott Hughes ("VersionMinor", "_versionMinor"), 195*e1fe3e4aSElliott Hughes ): 196*e1fe3e4aSElliott Hughes self.draw_label( 197*e1fe3e4aSElliott Hughes "%s: %s" % (what, getattr(f, field)), 198*e1fe3e4aSElliott Hughes x=x + pad, 199*e1fe3e4aSElliott Hughes y=y, 200*e1fe3e4aSElliott Hughes width=width, 201*e1fe3e4aSElliott Hughes ) 202*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 203*e1fe3e4aSElliott Hughes 204*e1fe3e4aSElliott Hughes self.draw_legend( 205*e1fe3e4aSElliott Hughes show_tolerance=show_tolerance, tolerance=tolerance, kinkiness=kinkiness 206*e1fe3e4aSElliott Hughes ) 207*e1fe3e4aSElliott Hughes self.show_page() 208*e1fe3e4aSElliott Hughes 209*e1fe3e4aSElliott Hughes def draw_legend(self, *, show_tolerance=True, tolerance=None, kinkiness=None): 210*e1fe3e4aSElliott Hughes cr = cairo.Context(self.surface) 211*e1fe3e4aSElliott Hughes 212*e1fe3e4aSElliott Hughes x = self.pad 213*e1fe3e4aSElliott Hughes y = self.height - self.pad - self.font_size * 2 214*e1fe3e4aSElliott Hughes width = self.width - 2 * self.pad 215*e1fe3e4aSElliott Hughes 216*e1fe3e4aSElliott Hughes xx = x + self.pad * 2 217*e1fe3e4aSElliott Hughes xxx = x + self.pad * 4 218*e1fe3e4aSElliott Hughes 219*e1fe3e4aSElliott Hughes if show_tolerance: 220*e1fe3e4aSElliott Hughes self.draw_label( 221*e1fe3e4aSElliott Hughes "Tolerance: badness; closer to zero the worse", x=xxx, y=y, width=width 222*e1fe3e4aSElliott Hughes ) 223*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 224*e1fe3e4aSElliott Hughes 225*e1fe3e4aSElliott Hughes self.draw_label("Underweight contours", x=xxx, y=y, width=width) 226*e1fe3e4aSElliott Hughes cr.rectangle(xx - self.pad * 0.7, y, 1.5 * self.pad, self.font_size) 227*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.fill_color) 228*e1fe3e4aSElliott Hughes cr.fill_preserve() 229*e1fe3e4aSElliott Hughes if self.stroke_color: 230*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.stroke_color) 231*e1fe3e4aSElliott Hughes cr.set_line_width(self.stroke_width) 232*e1fe3e4aSElliott Hughes cr.stroke_preserve() 233*e1fe3e4aSElliott Hughes cr.set_source_rgba(*self.weight_issue_contour_color) 234*e1fe3e4aSElliott Hughes cr.fill() 235*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 236*e1fe3e4aSElliott Hughes 237*e1fe3e4aSElliott Hughes self.draw_label( 238*e1fe3e4aSElliott Hughes "Colored contours: contours with the wrong order", x=xxx, y=y, width=width 239*e1fe3e4aSElliott Hughes ) 240*e1fe3e4aSElliott Hughes cr.rectangle(xx - self.pad * 0.7, y, 1.5 * self.pad, self.font_size) 241*e1fe3e4aSElliott Hughes if self.fill_color: 242*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.fill_color) 243*e1fe3e4aSElliott Hughes cr.fill_preserve() 244*e1fe3e4aSElliott Hughes if self.stroke_color: 245*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.stroke_color) 246*e1fe3e4aSElliott Hughes cr.set_line_width(self.stroke_width) 247*e1fe3e4aSElliott Hughes cr.stroke_preserve() 248*e1fe3e4aSElliott Hughes cr.set_source_rgba(*self.contour_colors[0], self.contour_alpha) 249*e1fe3e4aSElliott Hughes cr.fill() 250*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 251*e1fe3e4aSElliott Hughes 252*e1fe3e4aSElliott Hughes self.draw_label("Kink artifact", x=xxx, y=y, width=width) 253*e1fe3e4aSElliott Hughes self.draw_circle( 254*e1fe3e4aSElliott Hughes cr, 255*e1fe3e4aSElliott Hughes x=xx, 256*e1fe3e4aSElliott Hughes y=y + self.font_size * 0.5, 257*e1fe3e4aSElliott Hughes diameter=self.kink_circle_size, 258*e1fe3e4aSElliott Hughes stroke_width=self.kink_circle_stroke_width, 259*e1fe3e4aSElliott Hughes color=self.kink_circle_color, 260*e1fe3e4aSElliott Hughes ) 261*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 262*e1fe3e4aSElliott Hughes 263*e1fe3e4aSElliott Hughes self.draw_label("Point causing kink in the contour", x=xxx, y=y, width=width) 264*e1fe3e4aSElliott Hughes self.draw_dot( 265*e1fe3e4aSElliott Hughes cr, 266*e1fe3e4aSElliott Hughes x=xx, 267*e1fe3e4aSElliott Hughes y=y + self.font_size * 0.5, 268*e1fe3e4aSElliott Hughes diameter=self.kink_point_size, 269*e1fe3e4aSElliott Hughes color=self.kink_point_color, 270*e1fe3e4aSElliott Hughes ) 271*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 272*e1fe3e4aSElliott Hughes 273*e1fe3e4aSElliott Hughes self.draw_label("Suggested new contour start point", x=xxx, y=y, width=width) 274*e1fe3e4aSElliott Hughes self.draw_dot( 275*e1fe3e4aSElliott Hughes cr, 276*e1fe3e4aSElliott Hughes x=xx, 277*e1fe3e4aSElliott Hughes y=y + self.font_size * 0.5, 278*e1fe3e4aSElliott Hughes diameter=self.corrected_start_point_size, 279*e1fe3e4aSElliott Hughes color=self.corrected_start_point_color, 280*e1fe3e4aSElliott Hughes ) 281*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 282*e1fe3e4aSElliott Hughes 283*e1fe3e4aSElliott Hughes self.draw_label( 284*e1fe3e4aSElliott Hughes "Contour start point in contours with wrong direction", 285*e1fe3e4aSElliott Hughes x=xxx, 286*e1fe3e4aSElliott Hughes y=y, 287*e1fe3e4aSElliott Hughes width=width, 288*e1fe3e4aSElliott Hughes ) 289*e1fe3e4aSElliott Hughes self.draw_arrow( 290*e1fe3e4aSElliott Hughes cr, 291*e1fe3e4aSElliott Hughes x=xx - self.start_arrow_length * 0.3, 292*e1fe3e4aSElliott Hughes y=y + self.font_size * 0.5, 293*e1fe3e4aSElliott Hughes color=self.wrong_start_point_color, 294*e1fe3e4aSElliott Hughes ) 295*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 296*e1fe3e4aSElliott Hughes 297*e1fe3e4aSElliott Hughes self.draw_label( 298*e1fe3e4aSElliott Hughes "Contour start point when the first two points overlap", 299*e1fe3e4aSElliott Hughes x=xxx, 300*e1fe3e4aSElliott Hughes y=y, 301*e1fe3e4aSElliott Hughes width=width, 302*e1fe3e4aSElliott Hughes ) 303*e1fe3e4aSElliott Hughes self.draw_dot( 304*e1fe3e4aSElliott Hughes cr, 305*e1fe3e4aSElliott Hughes x=xx, 306*e1fe3e4aSElliott Hughes y=y + self.font_size * 0.5, 307*e1fe3e4aSElliott Hughes diameter=self.corrected_start_point_size, 308*e1fe3e4aSElliott Hughes color=self.start_point_color, 309*e1fe3e4aSElliott Hughes ) 310*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 311*e1fe3e4aSElliott Hughes 312*e1fe3e4aSElliott Hughes self.draw_label("Contour start point and direction", x=xxx, y=y, width=width) 313*e1fe3e4aSElliott Hughes self.draw_arrow( 314*e1fe3e4aSElliott Hughes cr, 315*e1fe3e4aSElliott Hughes x=xx - self.start_arrow_length * 0.3, 316*e1fe3e4aSElliott Hughes y=y + self.font_size * 0.5, 317*e1fe3e4aSElliott Hughes color=self.start_point_color, 318*e1fe3e4aSElliott Hughes ) 319*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 320*e1fe3e4aSElliott Hughes 321*e1fe3e4aSElliott Hughes self.draw_label("Legend:", x=x, y=y, width=width, bold=True) 322*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 323*e1fe3e4aSElliott Hughes 324*e1fe3e4aSElliott Hughes if kinkiness is not None: 325*e1fe3e4aSElliott Hughes self.draw_label( 326*e1fe3e4aSElliott Hughes "Kink-reporting aggressiveness: %g" % kinkiness, 327*e1fe3e4aSElliott Hughes x=xxx, 328*e1fe3e4aSElliott Hughes y=y, 329*e1fe3e4aSElliott Hughes width=width, 330*e1fe3e4aSElliott Hughes ) 331*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 332*e1fe3e4aSElliott Hughes 333*e1fe3e4aSElliott Hughes if tolerance is not None: 334*e1fe3e4aSElliott Hughes self.draw_label( 335*e1fe3e4aSElliott Hughes "Error tolerance: %g" % tolerance, 336*e1fe3e4aSElliott Hughes x=xxx, 337*e1fe3e4aSElliott Hughes y=y, 338*e1fe3e4aSElliott Hughes width=width, 339*e1fe3e4aSElliott Hughes ) 340*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 341*e1fe3e4aSElliott Hughes 342*e1fe3e4aSElliott Hughes self.draw_label("Parameters:", x=x, y=y, width=width, bold=True) 343*e1fe3e4aSElliott Hughes y -= self.pad + self.font_size 344*e1fe3e4aSElliott Hughes 345*e1fe3e4aSElliott Hughes def add_summary(self, problems): 346*e1fe3e4aSElliott Hughes pad = self.pad 347*e1fe3e4aSElliott Hughes width = self.width - 3 * self.pad 348*e1fe3e4aSElliott Hughes height = self.height - 2 * self.pad 349*e1fe3e4aSElliott Hughes x = y = pad 350*e1fe3e4aSElliott Hughes 351*e1fe3e4aSElliott Hughes self.draw_label( 352*e1fe3e4aSElliott Hughes "Summary of problems", 353*e1fe3e4aSElliott Hughes x=x, 354*e1fe3e4aSElliott Hughes y=y, 355*e1fe3e4aSElliott Hughes bold=True, 356*e1fe3e4aSElliott Hughes width=width, 357*e1fe3e4aSElliott Hughes font_size=self.title_font_size, 358*e1fe3e4aSElliott Hughes ) 359*e1fe3e4aSElliott Hughes y += self.title_font_size 360*e1fe3e4aSElliott Hughes 361*e1fe3e4aSElliott Hughes glyphs_per_problem = defaultdict(set) 362*e1fe3e4aSElliott Hughes for glyphname, problems in sorted(problems.items()): 363*e1fe3e4aSElliott Hughes for problem in problems: 364*e1fe3e4aSElliott Hughes glyphs_per_problem[problem["type"]].add(glyphname) 365*e1fe3e4aSElliott Hughes 366*e1fe3e4aSElliott Hughes if "nothing" in glyphs_per_problem: 367*e1fe3e4aSElliott Hughes del glyphs_per_problem["nothing"] 368*e1fe3e4aSElliott Hughes 369*e1fe3e4aSElliott Hughes for problem_type in sorted( 370*e1fe3e4aSElliott Hughes glyphs_per_problem, key=lambda x: InterpolatableProblem.severity[x] 371*e1fe3e4aSElliott Hughes ): 372*e1fe3e4aSElliott Hughes y += self.font_size 373*e1fe3e4aSElliott Hughes self.draw_label( 374*e1fe3e4aSElliott Hughes "%s: %d" % (problem_type, len(glyphs_per_problem[problem_type])), 375*e1fe3e4aSElliott Hughes x=x, 376*e1fe3e4aSElliott Hughes y=y, 377*e1fe3e4aSElliott Hughes width=width, 378*e1fe3e4aSElliott Hughes bold=True, 379*e1fe3e4aSElliott Hughes ) 380*e1fe3e4aSElliott Hughes y += self.font_size 381*e1fe3e4aSElliott Hughes 382*e1fe3e4aSElliott Hughes for glyphname in sorted(glyphs_per_problem[problem_type]): 383*e1fe3e4aSElliott Hughes if y + self.font_size > height: 384*e1fe3e4aSElliott Hughes self.show_page() 385*e1fe3e4aSElliott Hughes y = self.font_size + pad 386*e1fe3e4aSElliott Hughes self.draw_label(glyphname, x=x + 2 * pad, y=y, width=width - 2 * pad) 387*e1fe3e4aSElliott Hughes y += self.font_size 388*e1fe3e4aSElliott Hughes 389*e1fe3e4aSElliott Hughes self.show_page() 390*e1fe3e4aSElliott Hughes 391*e1fe3e4aSElliott Hughes def _add_listing(self, title, items): 392*e1fe3e4aSElliott Hughes pad = self.pad 393*e1fe3e4aSElliott Hughes width = self.width - 2 * self.pad 394*e1fe3e4aSElliott Hughes height = self.height - 2 * self.pad 395*e1fe3e4aSElliott Hughes x = y = pad 396*e1fe3e4aSElliott Hughes 397*e1fe3e4aSElliott Hughes self.draw_label( 398*e1fe3e4aSElliott Hughes title, x=x, y=y, bold=True, width=width, font_size=self.title_font_size 399*e1fe3e4aSElliott Hughes ) 400*e1fe3e4aSElliott Hughes y += self.title_font_size + self.pad 401*e1fe3e4aSElliott Hughes 402*e1fe3e4aSElliott Hughes last_glyphname = None 403*e1fe3e4aSElliott Hughes for page_no, (glyphname, problems) in items: 404*e1fe3e4aSElliott Hughes if glyphname == last_glyphname: 405*e1fe3e4aSElliott Hughes continue 406*e1fe3e4aSElliott Hughes last_glyphname = glyphname 407*e1fe3e4aSElliott Hughes if y + self.font_size > height: 408*e1fe3e4aSElliott Hughes self.show_page() 409*e1fe3e4aSElliott Hughes y = self.font_size + pad 410*e1fe3e4aSElliott Hughes self.draw_label(glyphname, x=x + 5 * pad, y=y, width=width - 2 * pad) 411*e1fe3e4aSElliott Hughes self.draw_label(str(page_no), x=x, y=y, width=4 * pad, align=1) 412*e1fe3e4aSElliott Hughes y += self.font_size 413*e1fe3e4aSElliott Hughes 414*e1fe3e4aSElliott Hughes self.show_page() 415*e1fe3e4aSElliott Hughes 416*e1fe3e4aSElliott Hughes def add_table_of_contents(self): 417*e1fe3e4aSElliott Hughes self._add_listing("Table of contents", sorted(self.toc.items())) 418*e1fe3e4aSElliott Hughes 419*e1fe3e4aSElliott Hughes def add_index(self): 420*e1fe3e4aSElliott Hughes self._add_listing("Index", sorted(self.toc.items(), key=lambda x: x[1][0])) 421*e1fe3e4aSElliott Hughes 422*e1fe3e4aSElliott Hughes def add_problems(self, problems, *, show_tolerance=True, show_page_number=True): 423*e1fe3e4aSElliott Hughes for glyph, glyph_problems in problems.items(): 424*e1fe3e4aSElliott Hughes last_masters = None 425*e1fe3e4aSElliott Hughes current_glyph_problems = [] 426*e1fe3e4aSElliott Hughes for p in glyph_problems: 427*e1fe3e4aSElliott Hughes masters = ( 428*e1fe3e4aSElliott Hughes p["master_idx"] 429*e1fe3e4aSElliott Hughes if "master_idx" in p 430*e1fe3e4aSElliott Hughes else (p["master_1_idx"], p["master_2_idx"]) 431*e1fe3e4aSElliott Hughes ) 432*e1fe3e4aSElliott Hughes if masters == last_masters: 433*e1fe3e4aSElliott Hughes current_glyph_problems.append(p) 434*e1fe3e4aSElliott Hughes continue 435*e1fe3e4aSElliott Hughes # Flush 436*e1fe3e4aSElliott Hughes if current_glyph_problems: 437*e1fe3e4aSElliott Hughes self.add_problem( 438*e1fe3e4aSElliott Hughes glyph, 439*e1fe3e4aSElliott Hughes current_glyph_problems, 440*e1fe3e4aSElliott Hughes show_tolerance=show_tolerance, 441*e1fe3e4aSElliott Hughes show_page_number=show_page_number, 442*e1fe3e4aSElliott Hughes ) 443*e1fe3e4aSElliott Hughes self.show_page() 444*e1fe3e4aSElliott Hughes current_glyph_problems = [] 445*e1fe3e4aSElliott Hughes last_masters = masters 446*e1fe3e4aSElliott Hughes current_glyph_problems.append(p) 447*e1fe3e4aSElliott Hughes if current_glyph_problems: 448*e1fe3e4aSElliott Hughes self.add_problem( 449*e1fe3e4aSElliott Hughes glyph, 450*e1fe3e4aSElliott Hughes current_glyph_problems, 451*e1fe3e4aSElliott Hughes show_tolerance=show_tolerance, 452*e1fe3e4aSElliott Hughes show_page_number=show_page_number, 453*e1fe3e4aSElliott Hughes ) 454*e1fe3e4aSElliott Hughes self.show_page() 455*e1fe3e4aSElliott Hughes 456*e1fe3e4aSElliott Hughes def add_problem( 457*e1fe3e4aSElliott Hughes self, glyphname, problems, *, show_tolerance=True, show_page_number=True 458*e1fe3e4aSElliott Hughes ): 459*e1fe3e4aSElliott Hughes if type(problems) not in (list, tuple): 460*e1fe3e4aSElliott Hughes problems = [problems] 461*e1fe3e4aSElliott Hughes 462*e1fe3e4aSElliott Hughes self.toc[self.page_number] = (glyphname, problems) 463*e1fe3e4aSElliott Hughes 464*e1fe3e4aSElliott Hughes problem_type = problems[0]["type"] 465*e1fe3e4aSElliott Hughes problem_types = set(problem["type"] for problem in problems) 466*e1fe3e4aSElliott Hughes if not all(pt == problem_type for pt in problem_types): 467*e1fe3e4aSElliott Hughes problem_type = ", ".join(sorted({problem["type"] for problem in problems})) 468*e1fe3e4aSElliott Hughes 469*e1fe3e4aSElliott Hughes log.info("Drawing %s: %s", glyphname, problem_type) 470*e1fe3e4aSElliott Hughes 471*e1fe3e4aSElliott Hughes master_keys = ( 472*e1fe3e4aSElliott Hughes ("master_idx",) 473*e1fe3e4aSElliott Hughes if "master_idx" in problems[0] 474*e1fe3e4aSElliott Hughes else ("master_1_idx", "master_2_idx") 475*e1fe3e4aSElliott Hughes ) 476*e1fe3e4aSElliott Hughes master_indices = [problems[0][k] for k in master_keys] 477*e1fe3e4aSElliott Hughes 478*e1fe3e4aSElliott Hughes if problem_type == InterpolatableProblem.MISSING: 479*e1fe3e4aSElliott Hughes sample_glyph = next( 480*e1fe3e4aSElliott Hughes i for i, m in enumerate(self.glyphsets) if m[glyphname] is not None 481*e1fe3e4aSElliott Hughes ) 482*e1fe3e4aSElliott Hughes master_indices.insert(0, sample_glyph) 483*e1fe3e4aSElliott Hughes 484*e1fe3e4aSElliott Hughes x = self.pad 485*e1fe3e4aSElliott Hughes y = self.pad 486*e1fe3e4aSElliott Hughes 487*e1fe3e4aSElliott Hughes self.draw_label( 488*e1fe3e4aSElliott Hughes "Glyph name: " + glyphname, 489*e1fe3e4aSElliott Hughes x=x, 490*e1fe3e4aSElliott Hughes y=y, 491*e1fe3e4aSElliott Hughes color=self.head_color, 492*e1fe3e4aSElliott Hughes align=0, 493*e1fe3e4aSElliott Hughes bold=True, 494*e1fe3e4aSElliott Hughes font_size=self.title_font_size, 495*e1fe3e4aSElliott Hughes ) 496*e1fe3e4aSElliott Hughes tolerance = min(p.get("tolerance", 1) for p in problems) 497*e1fe3e4aSElliott Hughes if tolerance < 1 and show_tolerance: 498*e1fe3e4aSElliott Hughes self.draw_label( 499*e1fe3e4aSElliott Hughes "tolerance: %.2f" % tolerance, 500*e1fe3e4aSElliott Hughes x=x, 501*e1fe3e4aSElliott Hughes y=y, 502*e1fe3e4aSElliott Hughes width=self.width - 2 * self.pad, 503*e1fe3e4aSElliott Hughes align=1, 504*e1fe3e4aSElliott Hughes bold=True, 505*e1fe3e4aSElliott Hughes ) 506*e1fe3e4aSElliott Hughes y += self.title_font_size + self.pad 507*e1fe3e4aSElliott Hughes self.draw_label( 508*e1fe3e4aSElliott Hughes "Problems: " + problem_type, 509*e1fe3e4aSElliott Hughes x=x, 510*e1fe3e4aSElliott Hughes y=y, 511*e1fe3e4aSElliott Hughes width=self.width - 2 * self.pad, 512*e1fe3e4aSElliott Hughes color=self.head_color, 513*e1fe3e4aSElliott Hughes bold=True, 514*e1fe3e4aSElliott Hughes ) 515*e1fe3e4aSElliott Hughes y += self.font_size + self.pad * 2 516*e1fe3e4aSElliott Hughes 517*e1fe3e4aSElliott Hughes scales = [] 518*e1fe3e4aSElliott Hughes for which, master_idx in enumerate(master_indices): 519*e1fe3e4aSElliott Hughes glyphset = self.glyphsets[master_idx] 520*e1fe3e4aSElliott Hughes name = self.names[master_idx] 521*e1fe3e4aSElliott Hughes 522*e1fe3e4aSElliott Hughes self.draw_label( 523*e1fe3e4aSElliott Hughes name, 524*e1fe3e4aSElliott Hughes x=x, 525*e1fe3e4aSElliott Hughes y=y, 526*e1fe3e4aSElliott Hughes color=self.label_color, 527*e1fe3e4aSElliott Hughes width=self.panel_width, 528*e1fe3e4aSElliott Hughes align=0.5, 529*e1fe3e4aSElliott Hughes ) 530*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 531*e1fe3e4aSElliott Hughes 532*e1fe3e4aSElliott Hughes if glyphset[glyphname] is not None: 533*e1fe3e4aSElliott Hughes scales.append( 534*e1fe3e4aSElliott Hughes self.draw_glyph(glyphset, glyphname, problems, which, x=x, y=y) 535*e1fe3e4aSElliott Hughes ) 536*e1fe3e4aSElliott Hughes else: 537*e1fe3e4aSElliott Hughes self.draw_emoticon(self.shrug, x=x, y=y) 538*e1fe3e4aSElliott Hughes y += self.panel_height + self.font_size + self.pad 539*e1fe3e4aSElliott Hughes 540*e1fe3e4aSElliott Hughes if any( 541*e1fe3e4aSElliott Hughes pt 542*e1fe3e4aSElliott Hughes in ( 543*e1fe3e4aSElliott Hughes InterpolatableProblem.NOTHING, 544*e1fe3e4aSElliott Hughes InterpolatableProblem.WRONG_START_POINT, 545*e1fe3e4aSElliott Hughes InterpolatableProblem.CONTOUR_ORDER, 546*e1fe3e4aSElliott Hughes InterpolatableProblem.KINK, 547*e1fe3e4aSElliott Hughes InterpolatableProblem.UNDERWEIGHT, 548*e1fe3e4aSElliott Hughes InterpolatableProblem.OVERWEIGHT, 549*e1fe3e4aSElliott Hughes ) 550*e1fe3e4aSElliott Hughes for pt in problem_types 551*e1fe3e4aSElliott Hughes ): 552*e1fe3e4aSElliott Hughes x = self.pad + self.panel_width + self.pad 553*e1fe3e4aSElliott Hughes y = self.pad 554*e1fe3e4aSElliott Hughes y += self.title_font_size + self.pad * 2 555*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 556*e1fe3e4aSElliott Hughes 557*e1fe3e4aSElliott Hughes glyphset1 = self.glyphsets[master_indices[0]] 558*e1fe3e4aSElliott Hughes glyphset2 = self.glyphsets[master_indices[1]] 559*e1fe3e4aSElliott Hughes 560*e1fe3e4aSElliott Hughes # Draw the mid-way of the two masters 561*e1fe3e4aSElliott Hughes 562*e1fe3e4aSElliott Hughes self.draw_label( 563*e1fe3e4aSElliott Hughes "midway interpolation", 564*e1fe3e4aSElliott Hughes x=x, 565*e1fe3e4aSElliott Hughes y=y, 566*e1fe3e4aSElliott Hughes color=self.head_color, 567*e1fe3e4aSElliott Hughes width=self.panel_width, 568*e1fe3e4aSElliott Hughes align=0.5, 569*e1fe3e4aSElliott Hughes ) 570*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 571*e1fe3e4aSElliott Hughes 572*e1fe3e4aSElliott Hughes midway_glyphset = LerpGlyphSet(glyphset1, glyphset2) 573*e1fe3e4aSElliott Hughes self.draw_glyph( 574*e1fe3e4aSElliott Hughes midway_glyphset, 575*e1fe3e4aSElliott Hughes glyphname, 576*e1fe3e4aSElliott Hughes [{"type": "midway"}] 577*e1fe3e4aSElliott Hughes + [ 578*e1fe3e4aSElliott Hughes p 579*e1fe3e4aSElliott Hughes for p in problems 580*e1fe3e4aSElliott Hughes if p["type"] 581*e1fe3e4aSElliott Hughes in ( 582*e1fe3e4aSElliott Hughes InterpolatableProblem.KINK, 583*e1fe3e4aSElliott Hughes InterpolatableProblem.UNDERWEIGHT, 584*e1fe3e4aSElliott Hughes InterpolatableProblem.OVERWEIGHT, 585*e1fe3e4aSElliott Hughes ) 586*e1fe3e4aSElliott Hughes ], 587*e1fe3e4aSElliott Hughes None, 588*e1fe3e4aSElliott Hughes x=x, 589*e1fe3e4aSElliott Hughes y=y, 590*e1fe3e4aSElliott Hughes scale=min(scales), 591*e1fe3e4aSElliott Hughes ) 592*e1fe3e4aSElliott Hughes 593*e1fe3e4aSElliott Hughes y += self.panel_height + self.font_size + self.pad 594*e1fe3e4aSElliott Hughes 595*e1fe3e4aSElliott Hughes if any( 596*e1fe3e4aSElliott Hughes pt 597*e1fe3e4aSElliott Hughes in ( 598*e1fe3e4aSElliott Hughes InterpolatableProblem.WRONG_START_POINT, 599*e1fe3e4aSElliott Hughes InterpolatableProblem.CONTOUR_ORDER, 600*e1fe3e4aSElliott Hughes InterpolatableProblem.KINK, 601*e1fe3e4aSElliott Hughes ) 602*e1fe3e4aSElliott Hughes for pt in problem_types 603*e1fe3e4aSElliott Hughes ): 604*e1fe3e4aSElliott Hughes # Draw the proposed fix 605*e1fe3e4aSElliott Hughes 606*e1fe3e4aSElliott Hughes self.draw_label( 607*e1fe3e4aSElliott Hughes "proposed fix", 608*e1fe3e4aSElliott Hughes x=x, 609*e1fe3e4aSElliott Hughes y=y, 610*e1fe3e4aSElliott Hughes color=self.head_color, 611*e1fe3e4aSElliott Hughes width=self.panel_width, 612*e1fe3e4aSElliott Hughes align=0.5, 613*e1fe3e4aSElliott Hughes ) 614*e1fe3e4aSElliott Hughes y += self.font_size + self.pad 615*e1fe3e4aSElliott Hughes 616*e1fe3e4aSElliott Hughes overriding1 = OverridingDict(glyphset1) 617*e1fe3e4aSElliott Hughes overriding2 = OverridingDict(glyphset2) 618*e1fe3e4aSElliott Hughes perContourPen1 = PerContourOrComponentPen( 619*e1fe3e4aSElliott Hughes RecordingPen, glyphset=overriding1 620*e1fe3e4aSElliott Hughes ) 621*e1fe3e4aSElliott Hughes perContourPen2 = PerContourOrComponentPen( 622*e1fe3e4aSElliott Hughes RecordingPen, glyphset=overriding2 623*e1fe3e4aSElliott Hughes ) 624*e1fe3e4aSElliott Hughes glyphset1[glyphname].draw(perContourPen1) 625*e1fe3e4aSElliott Hughes glyphset2[glyphname].draw(perContourPen2) 626*e1fe3e4aSElliott Hughes 627*e1fe3e4aSElliott Hughes for problem in problems: 628*e1fe3e4aSElliott Hughes if problem["type"] == InterpolatableProblem.CONTOUR_ORDER: 629*e1fe3e4aSElliott Hughes fixed_contours = [ 630*e1fe3e4aSElliott Hughes perContourPen2.value[i] for i in problems[0]["value_2"] 631*e1fe3e4aSElliott Hughes ] 632*e1fe3e4aSElliott Hughes perContourPen2.value = fixed_contours 633*e1fe3e4aSElliott Hughes 634*e1fe3e4aSElliott Hughes for problem in problems: 635*e1fe3e4aSElliott Hughes if problem["type"] == InterpolatableProblem.WRONG_START_POINT: 636*e1fe3e4aSElliott Hughes # Save the wrong contours 637*e1fe3e4aSElliott Hughes wrongContour1 = perContourPen1.value[problem["contour"]] 638*e1fe3e4aSElliott Hughes wrongContour2 = perContourPen2.value[problem["contour"]] 639*e1fe3e4aSElliott Hughes 640*e1fe3e4aSElliott Hughes # Convert the wrong contours to point pens 641*e1fe3e4aSElliott Hughes points1 = RecordingPointPen() 642*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points1, False) 643*e1fe3e4aSElliott Hughes wrongContour1.replay(converter) 644*e1fe3e4aSElliott Hughes points2 = RecordingPointPen() 645*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points2, False) 646*e1fe3e4aSElliott Hughes wrongContour2.replay(converter) 647*e1fe3e4aSElliott Hughes 648*e1fe3e4aSElliott Hughes proposed_start = problem["value_2"] 649*e1fe3e4aSElliott Hughes 650*e1fe3e4aSElliott Hughes # See if we need reversing; fragile but worth a try 651*e1fe3e4aSElliott Hughes if problem["reversed"]: 652*e1fe3e4aSElliott Hughes new_points2 = RecordingPointPen() 653*e1fe3e4aSElliott Hughes reversedPen = ReverseContourPointPen(new_points2) 654*e1fe3e4aSElliott Hughes points2.replay(reversedPen) 655*e1fe3e4aSElliott Hughes points2 = new_points2 656*e1fe3e4aSElliott Hughes proposed_start = len(points2.value) - 2 - proposed_start 657*e1fe3e4aSElliott Hughes 658*e1fe3e4aSElliott Hughes # Rotate points2 so that the first point is the same as in points1 659*e1fe3e4aSElliott Hughes beginPath = points2.value[:1] 660*e1fe3e4aSElliott Hughes endPath = points2.value[-1:] 661*e1fe3e4aSElliott Hughes pts = points2.value[1:-1] 662*e1fe3e4aSElliott Hughes pts = pts[proposed_start:] + pts[:proposed_start] 663*e1fe3e4aSElliott Hughes points2.value = beginPath + pts + endPath 664*e1fe3e4aSElliott Hughes 665*e1fe3e4aSElliott Hughes # Convert the point pens back to segment pens 666*e1fe3e4aSElliott Hughes segment1 = RecordingPen() 667*e1fe3e4aSElliott Hughes converter = PointToSegmentPen(segment1, True) 668*e1fe3e4aSElliott Hughes points1.replay(converter) 669*e1fe3e4aSElliott Hughes segment2 = RecordingPen() 670*e1fe3e4aSElliott Hughes converter = PointToSegmentPen(segment2, True) 671*e1fe3e4aSElliott Hughes points2.replay(converter) 672*e1fe3e4aSElliott Hughes 673*e1fe3e4aSElliott Hughes # Replace the wrong contours 674*e1fe3e4aSElliott Hughes wrongContour1.value = segment1.value 675*e1fe3e4aSElliott Hughes wrongContour2.value = segment2.value 676*e1fe3e4aSElliott Hughes perContourPen1.value[problem["contour"]] = wrongContour1 677*e1fe3e4aSElliott Hughes perContourPen2.value[problem["contour"]] = wrongContour2 678*e1fe3e4aSElliott Hughes 679*e1fe3e4aSElliott Hughes for problem in problems: 680*e1fe3e4aSElliott Hughes # If we have a kink, try to fix it. 681*e1fe3e4aSElliott Hughes if problem["type"] == InterpolatableProblem.KINK: 682*e1fe3e4aSElliott Hughes # Save the wrong contours 683*e1fe3e4aSElliott Hughes wrongContour1 = perContourPen1.value[problem["contour"]] 684*e1fe3e4aSElliott Hughes wrongContour2 = perContourPen2.value[problem["contour"]] 685*e1fe3e4aSElliott Hughes 686*e1fe3e4aSElliott Hughes # Convert the wrong contours to point pens 687*e1fe3e4aSElliott Hughes points1 = RecordingPointPen() 688*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points1, False) 689*e1fe3e4aSElliott Hughes wrongContour1.replay(converter) 690*e1fe3e4aSElliott Hughes points2 = RecordingPointPen() 691*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points2, False) 692*e1fe3e4aSElliott Hughes wrongContour2.replay(converter) 693*e1fe3e4aSElliott Hughes 694*e1fe3e4aSElliott Hughes i = problem["value"] 695*e1fe3e4aSElliott Hughes 696*e1fe3e4aSElliott Hughes # Position points to be around the same ratio 697*e1fe3e4aSElliott Hughes # beginPath / endPath dance 698*e1fe3e4aSElliott Hughes j = i + 1 699*e1fe3e4aSElliott Hughes pt0 = points1.value[j][1][0] 700*e1fe3e4aSElliott Hughes pt1 = points2.value[j][1][0] 701*e1fe3e4aSElliott Hughes j_prev = (i - 1) % (len(points1.value) - 2) + 1 702*e1fe3e4aSElliott Hughes pt0_prev = points1.value[j_prev][1][0] 703*e1fe3e4aSElliott Hughes pt1_prev = points2.value[j_prev][1][0] 704*e1fe3e4aSElliott Hughes j_next = (i + 1) % (len(points1.value) - 2) + 1 705*e1fe3e4aSElliott Hughes pt0_next = points1.value[j_next][1][0] 706*e1fe3e4aSElliott Hughes pt1_next = points2.value[j_next][1][0] 707*e1fe3e4aSElliott Hughes 708*e1fe3e4aSElliott Hughes pt0 = complex(*pt0) 709*e1fe3e4aSElliott Hughes pt1 = complex(*pt1) 710*e1fe3e4aSElliott Hughes pt0_prev = complex(*pt0_prev) 711*e1fe3e4aSElliott Hughes pt1_prev = complex(*pt1_prev) 712*e1fe3e4aSElliott Hughes pt0_next = complex(*pt0_next) 713*e1fe3e4aSElliott Hughes pt1_next = complex(*pt1_next) 714*e1fe3e4aSElliott Hughes 715*e1fe3e4aSElliott Hughes # Find the ratio of the distance between the points 716*e1fe3e4aSElliott Hughes r0 = abs(pt0 - pt0_prev) / abs(pt0_next - pt0_prev) 717*e1fe3e4aSElliott Hughes r1 = abs(pt1 - pt1_prev) / abs(pt1_next - pt1_prev) 718*e1fe3e4aSElliott Hughes r_mid = (r0 + r1) / 2 719*e1fe3e4aSElliott Hughes 720*e1fe3e4aSElliott Hughes pt0 = pt0_prev + r_mid * (pt0_next - pt0_prev) 721*e1fe3e4aSElliott Hughes pt1 = pt1_prev + r_mid * (pt1_next - pt1_prev) 722*e1fe3e4aSElliott Hughes 723*e1fe3e4aSElliott Hughes points1.value[j] = ( 724*e1fe3e4aSElliott Hughes points1.value[j][0], 725*e1fe3e4aSElliott Hughes (((pt0.real, pt0.imag),) + points1.value[j][1][1:]), 726*e1fe3e4aSElliott Hughes points1.value[j][2], 727*e1fe3e4aSElliott Hughes ) 728*e1fe3e4aSElliott Hughes points2.value[j] = ( 729*e1fe3e4aSElliott Hughes points2.value[j][0], 730*e1fe3e4aSElliott Hughes (((pt1.real, pt1.imag),) + points2.value[j][1][1:]), 731*e1fe3e4aSElliott Hughes points2.value[j][2], 732*e1fe3e4aSElliott Hughes ) 733*e1fe3e4aSElliott Hughes 734*e1fe3e4aSElliott Hughes # Convert the point pens back to segment pens 735*e1fe3e4aSElliott Hughes segment1 = RecordingPen() 736*e1fe3e4aSElliott Hughes converter = PointToSegmentPen(segment1, True) 737*e1fe3e4aSElliott Hughes points1.replay(converter) 738*e1fe3e4aSElliott Hughes segment2 = RecordingPen() 739*e1fe3e4aSElliott Hughes converter = PointToSegmentPen(segment2, True) 740*e1fe3e4aSElliott Hughes points2.replay(converter) 741*e1fe3e4aSElliott Hughes 742*e1fe3e4aSElliott Hughes # Replace the wrong contours 743*e1fe3e4aSElliott Hughes wrongContour1.value = segment1.value 744*e1fe3e4aSElliott Hughes wrongContour2.value = segment2.value 745*e1fe3e4aSElliott Hughes 746*e1fe3e4aSElliott Hughes # Assemble 747*e1fe3e4aSElliott Hughes fixed1 = RecordingPen() 748*e1fe3e4aSElliott Hughes fixed2 = RecordingPen() 749*e1fe3e4aSElliott Hughes for contour in perContourPen1.value: 750*e1fe3e4aSElliott Hughes fixed1.value.extend(contour.value) 751*e1fe3e4aSElliott Hughes for contour in perContourPen2.value: 752*e1fe3e4aSElliott Hughes fixed2.value.extend(contour.value) 753*e1fe3e4aSElliott Hughes fixed1.draw = fixed1.replay 754*e1fe3e4aSElliott Hughes fixed2.draw = fixed2.replay 755*e1fe3e4aSElliott Hughes 756*e1fe3e4aSElliott Hughes overriding1[glyphname] = fixed1 757*e1fe3e4aSElliott Hughes overriding2[glyphname] = fixed2 758*e1fe3e4aSElliott Hughes 759*e1fe3e4aSElliott Hughes try: 760*e1fe3e4aSElliott Hughes midway_glyphset = LerpGlyphSet(overriding1, overriding2) 761*e1fe3e4aSElliott Hughes self.draw_glyph( 762*e1fe3e4aSElliott Hughes midway_glyphset, 763*e1fe3e4aSElliott Hughes glyphname, 764*e1fe3e4aSElliott Hughes {"type": "fixed"}, 765*e1fe3e4aSElliott Hughes None, 766*e1fe3e4aSElliott Hughes x=x, 767*e1fe3e4aSElliott Hughes y=y, 768*e1fe3e4aSElliott Hughes scale=min(scales), 769*e1fe3e4aSElliott Hughes ) 770*e1fe3e4aSElliott Hughes except ValueError: 771*e1fe3e4aSElliott Hughes self.draw_emoticon(self.shrug, x=x, y=y) 772*e1fe3e4aSElliott Hughes y += self.panel_height + self.pad 773*e1fe3e4aSElliott Hughes 774*e1fe3e4aSElliott Hughes else: 775*e1fe3e4aSElliott Hughes emoticon = self.shrug 776*e1fe3e4aSElliott Hughes if InterpolatableProblem.UNDERWEIGHT in problem_types: 777*e1fe3e4aSElliott Hughes emoticon = self.underweight 778*e1fe3e4aSElliott Hughes elif InterpolatableProblem.OVERWEIGHT in problem_types: 779*e1fe3e4aSElliott Hughes emoticon = self.overweight 780*e1fe3e4aSElliott Hughes elif InterpolatableProblem.NOTHING in problem_types: 781*e1fe3e4aSElliott Hughes emoticon = self.yay 782*e1fe3e4aSElliott Hughes self.draw_emoticon(emoticon, x=x, y=y) 783*e1fe3e4aSElliott Hughes 784*e1fe3e4aSElliott Hughes if show_page_number: 785*e1fe3e4aSElliott Hughes self.draw_label( 786*e1fe3e4aSElliott Hughes str(self.page_number), 787*e1fe3e4aSElliott Hughes x=0, 788*e1fe3e4aSElliott Hughes y=self.height - self.font_size - self.pad, 789*e1fe3e4aSElliott Hughes width=self.width, 790*e1fe3e4aSElliott Hughes color=self.head_color, 791*e1fe3e4aSElliott Hughes align=0.5, 792*e1fe3e4aSElliott Hughes ) 793*e1fe3e4aSElliott Hughes 794*e1fe3e4aSElliott Hughes def draw_label( 795*e1fe3e4aSElliott Hughes self, 796*e1fe3e4aSElliott Hughes label, 797*e1fe3e4aSElliott Hughes *, 798*e1fe3e4aSElliott Hughes x=0, 799*e1fe3e4aSElliott Hughes y=0, 800*e1fe3e4aSElliott Hughes color=(0, 0, 0), 801*e1fe3e4aSElliott Hughes align=0, 802*e1fe3e4aSElliott Hughes bold=False, 803*e1fe3e4aSElliott Hughes width=None, 804*e1fe3e4aSElliott Hughes height=None, 805*e1fe3e4aSElliott Hughes font_size=None, 806*e1fe3e4aSElliott Hughes ): 807*e1fe3e4aSElliott Hughes if width is None: 808*e1fe3e4aSElliott Hughes width = self.width 809*e1fe3e4aSElliott Hughes if height is None: 810*e1fe3e4aSElliott Hughes height = self.height 811*e1fe3e4aSElliott Hughes if font_size is None: 812*e1fe3e4aSElliott Hughes font_size = self.font_size 813*e1fe3e4aSElliott Hughes cr = cairo.Context(self.surface) 814*e1fe3e4aSElliott Hughes cr.select_font_face( 815*e1fe3e4aSElliott Hughes "@cairo:", 816*e1fe3e4aSElliott Hughes cairo.FONT_SLANT_NORMAL, 817*e1fe3e4aSElliott Hughes cairo.FONT_WEIGHT_BOLD if bold else cairo.FONT_WEIGHT_NORMAL, 818*e1fe3e4aSElliott Hughes ) 819*e1fe3e4aSElliott Hughes cr.set_font_size(font_size) 820*e1fe3e4aSElliott Hughes font_extents = cr.font_extents() 821*e1fe3e4aSElliott Hughes font_size = font_size * font_size / font_extents[2] 822*e1fe3e4aSElliott Hughes cr.set_font_size(font_size) 823*e1fe3e4aSElliott Hughes font_extents = cr.font_extents() 824*e1fe3e4aSElliott Hughes 825*e1fe3e4aSElliott Hughes cr.set_source_rgb(*color) 826*e1fe3e4aSElliott Hughes 827*e1fe3e4aSElliott Hughes extents = cr.text_extents(label) 828*e1fe3e4aSElliott Hughes if extents.width > width: 829*e1fe3e4aSElliott Hughes # Shrink 830*e1fe3e4aSElliott Hughes font_size *= width / extents.width 831*e1fe3e4aSElliott Hughes cr.set_font_size(font_size) 832*e1fe3e4aSElliott Hughes font_extents = cr.font_extents() 833*e1fe3e4aSElliott Hughes extents = cr.text_extents(label) 834*e1fe3e4aSElliott Hughes 835*e1fe3e4aSElliott Hughes # Center 836*e1fe3e4aSElliott Hughes label_x = x + (width - extents.width) * align 837*e1fe3e4aSElliott Hughes label_y = y + font_extents[0] 838*e1fe3e4aSElliott Hughes cr.move_to(label_x, label_y) 839*e1fe3e4aSElliott Hughes cr.show_text(label) 840*e1fe3e4aSElliott Hughes 841*e1fe3e4aSElliott Hughes def draw_glyph(self, glyphset, glyphname, problems, which, *, x=0, y=0, scale=None): 842*e1fe3e4aSElliott Hughes if type(problems) not in (list, tuple): 843*e1fe3e4aSElliott Hughes problems = [problems] 844*e1fe3e4aSElliott Hughes 845*e1fe3e4aSElliott Hughes midway = any(problem["type"] == "midway" for problem in problems) 846*e1fe3e4aSElliott Hughes problem_type = problems[0]["type"] 847*e1fe3e4aSElliott Hughes problem_types = set(problem["type"] for problem in problems) 848*e1fe3e4aSElliott Hughes if not all(pt == problem_type for pt in problem_types): 849*e1fe3e4aSElliott Hughes problem_type = "mixed" 850*e1fe3e4aSElliott Hughes glyph = glyphset[glyphname] 851*e1fe3e4aSElliott Hughes 852*e1fe3e4aSElliott Hughes recording = RecordingPen() 853*e1fe3e4aSElliott Hughes glyph.draw(recording) 854*e1fe3e4aSElliott Hughes decomposedRecording = DecomposingRecordingPen(glyphset) 855*e1fe3e4aSElliott Hughes glyph.draw(decomposedRecording) 856*e1fe3e4aSElliott Hughes 857*e1fe3e4aSElliott Hughes boundsPen = ControlBoundsPen(glyphset) 858*e1fe3e4aSElliott Hughes decomposedRecording.replay(boundsPen) 859*e1fe3e4aSElliott Hughes bounds = boundsPen.bounds 860*e1fe3e4aSElliott Hughes if bounds is None: 861*e1fe3e4aSElliott Hughes bounds = (0, 0, 0, 0) 862*e1fe3e4aSElliott Hughes 863*e1fe3e4aSElliott Hughes glyph_width = bounds[2] - bounds[0] 864*e1fe3e4aSElliott Hughes glyph_height = bounds[3] - bounds[1] 865*e1fe3e4aSElliott Hughes 866*e1fe3e4aSElliott Hughes if glyph_width: 867*e1fe3e4aSElliott Hughes if scale is None: 868*e1fe3e4aSElliott Hughes scale = self.panel_width / glyph_width 869*e1fe3e4aSElliott Hughes else: 870*e1fe3e4aSElliott Hughes scale = min(scale, self.panel_height / glyph_height) 871*e1fe3e4aSElliott Hughes if glyph_height: 872*e1fe3e4aSElliott Hughes if scale is None: 873*e1fe3e4aSElliott Hughes scale = self.panel_height / glyph_height 874*e1fe3e4aSElliott Hughes else: 875*e1fe3e4aSElliott Hughes scale = min(scale, self.panel_height / glyph_height) 876*e1fe3e4aSElliott Hughes if scale is None: 877*e1fe3e4aSElliott Hughes scale = 1 878*e1fe3e4aSElliott Hughes 879*e1fe3e4aSElliott Hughes cr = cairo.Context(self.surface) 880*e1fe3e4aSElliott Hughes cr.translate(x, y) 881*e1fe3e4aSElliott Hughes # Center 882*e1fe3e4aSElliott Hughes cr.translate( 883*e1fe3e4aSElliott Hughes (self.panel_width - glyph_width * scale) / 2, 884*e1fe3e4aSElliott Hughes (self.panel_height - glyph_height * scale) / 2, 885*e1fe3e4aSElliott Hughes ) 886*e1fe3e4aSElliott Hughes cr.scale(scale, -scale) 887*e1fe3e4aSElliott Hughes cr.translate(-bounds[0], -bounds[3]) 888*e1fe3e4aSElliott Hughes 889*e1fe3e4aSElliott Hughes if self.border_color: 890*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.border_color) 891*e1fe3e4aSElliott Hughes cr.rectangle(bounds[0], bounds[1], glyph_width, glyph_height) 892*e1fe3e4aSElliott Hughes cr.set_line_width(self.border_width / scale) 893*e1fe3e4aSElliott Hughes cr.stroke() 894*e1fe3e4aSElliott Hughes 895*e1fe3e4aSElliott Hughes if self.fill_color or self.stroke_color: 896*e1fe3e4aSElliott Hughes pen = CairoPen(glyphset, cr) 897*e1fe3e4aSElliott Hughes decomposedRecording.replay(pen) 898*e1fe3e4aSElliott Hughes 899*e1fe3e4aSElliott Hughes if self.fill_color and problem_type != InterpolatableProblem.OPEN_PATH: 900*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.fill_color) 901*e1fe3e4aSElliott Hughes cr.fill_preserve() 902*e1fe3e4aSElliott Hughes 903*e1fe3e4aSElliott Hughes if self.stroke_color: 904*e1fe3e4aSElliott Hughes cr.set_source_rgb(*self.stroke_color) 905*e1fe3e4aSElliott Hughes cr.set_line_width(self.stroke_width / scale) 906*e1fe3e4aSElliott Hughes cr.stroke_preserve() 907*e1fe3e4aSElliott Hughes 908*e1fe3e4aSElliott Hughes cr.new_path() 909*e1fe3e4aSElliott Hughes 910*e1fe3e4aSElliott Hughes if ( 911*e1fe3e4aSElliott Hughes InterpolatableProblem.UNDERWEIGHT in problem_types 912*e1fe3e4aSElliott Hughes or InterpolatableProblem.OVERWEIGHT in problem_types 913*e1fe3e4aSElliott Hughes ): 914*e1fe3e4aSElliott Hughes perContourPen = PerContourOrComponentPen(RecordingPen, glyphset=glyphset) 915*e1fe3e4aSElliott Hughes recording.replay(perContourPen) 916*e1fe3e4aSElliott Hughes for problem in problems: 917*e1fe3e4aSElliott Hughes if problem["type"] in ( 918*e1fe3e4aSElliott Hughes InterpolatableProblem.UNDERWEIGHT, 919*e1fe3e4aSElliott Hughes InterpolatableProblem.OVERWEIGHT, 920*e1fe3e4aSElliott Hughes ): 921*e1fe3e4aSElliott Hughes contour = perContourPen.value[problem["contour"]] 922*e1fe3e4aSElliott Hughes contour.replay(CairoPen(glyphset, cr)) 923*e1fe3e4aSElliott Hughes cr.set_source_rgba(*self.weight_issue_contour_color) 924*e1fe3e4aSElliott Hughes cr.fill() 925*e1fe3e4aSElliott Hughes 926*e1fe3e4aSElliott Hughes if any( 927*e1fe3e4aSElliott Hughes t in problem_types 928*e1fe3e4aSElliott Hughes for t in { 929*e1fe3e4aSElliott Hughes InterpolatableProblem.NOTHING, 930*e1fe3e4aSElliott Hughes InterpolatableProblem.NODE_COUNT, 931*e1fe3e4aSElliott Hughes InterpolatableProblem.NODE_INCOMPATIBILITY, 932*e1fe3e4aSElliott Hughes } 933*e1fe3e4aSElliott Hughes ): 934*e1fe3e4aSElliott Hughes cr.set_line_cap(cairo.LINE_CAP_ROUND) 935*e1fe3e4aSElliott Hughes 936*e1fe3e4aSElliott Hughes # Oncurve nodes 937*e1fe3e4aSElliott Hughes for segment, args in decomposedRecording.value: 938*e1fe3e4aSElliott Hughes if not args: 939*e1fe3e4aSElliott Hughes continue 940*e1fe3e4aSElliott Hughes x, y = args[-1] 941*e1fe3e4aSElliott Hughes cr.move_to(x, y) 942*e1fe3e4aSElliott Hughes cr.line_to(x, y) 943*e1fe3e4aSElliott Hughes cr.set_source_rgba(*self.oncurve_node_color) 944*e1fe3e4aSElliott Hughes cr.set_line_width(self.oncurve_node_diameter / scale) 945*e1fe3e4aSElliott Hughes cr.stroke() 946*e1fe3e4aSElliott Hughes 947*e1fe3e4aSElliott Hughes # Offcurve nodes 948*e1fe3e4aSElliott Hughes for segment, args in decomposedRecording.value: 949*e1fe3e4aSElliott Hughes if not args: 950*e1fe3e4aSElliott Hughes continue 951*e1fe3e4aSElliott Hughes for x, y in args[:-1]: 952*e1fe3e4aSElliott Hughes cr.move_to(x, y) 953*e1fe3e4aSElliott Hughes cr.line_to(x, y) 954*e1fe3e4aSElliott Hughes cr.set_source_rgba(*self.offcurve_node_color) 955*e1fe3e4aSElliott Hughes cr.set_line_width(self.offcurve_node_diameter / scale) 956*e1fe3e4aSElliott Hughes cr.stroke() 957*e1fe3e4aSElliott Hughes 958*e1fe3e4aSElliott Hughes # Handles 959*e1fe3e4aSElliott Hughes for segment, args in decomposedRecording.value: 960*e1fe3e4aSElliott Hughes if not args: 961*e1fe3e4aSElliott Hughes pass 962*e1fe3e4aSElliott Hughes elif segment in ("moveTo", "lineTo"): 963*e1fe3e4aSElliott Hughes cr.move_to(*args[0]) 964*e1fe3e4aSElliott Hughes elif segment == "qCurveTo": 965*e1fe3e4aSElliott Hughes for x, y in args: 966*e1fe3e4aSElliott Hughes cr.line_to(x, y) 967*e1fe3e4aSElliott Hughes cr.new_sub_path() 968*e1fe3e4aSElliott Hughes cr.move_to(*args[-1]) 969*e1fe3e4aSElliott Hughes elif segment == "curveTo": 970*e1fe3e4aSElliott Hughes cr.line_to(*args[0]) 971*e1fe3e4aSElliott Hughes cr.new_sub_path() 972*e1fe3e4aSElliott Hughes cr.move_to(*args[1]) 973*e1fe3e4aSElliott Hughes cr.line_to(*args[2]) 974*e1fe3e4aSElliott Hughes cr.new_sub_path() 975*e1fe3e4aSElliott Hughes cr.move_to(*args[-1]) 976*e1fe3e4aSElliott Hughes else: 977*e1fe3e4aSElliott Hughes continue 978*e1fe3e4aSElliott Hughes 979*e1fe3e4aSElliott Hughes cr.set_source_rgba(*self.handle_color) 980*e1fe3e4aSElliott Hughes cr.set_line_width(self.handle_width / scale) 981*e1fe3e4aSElliott Hughes cr.stroke() 982*e1fe3e4aSElliott Hughes 983*e1fe3e4aSElliott Hughes matching = None 984*e1fe3e4aSElliott Hughes for problem in problems: 985*e1fe3e4aSElliott Hughes if problem["type"] == InterpolatableProblem.CONTOUR_ORDER: 986*e1fe3e4aSElliott Hughes matching = problem["value_2"] 987*e1fe3e4aSElliott Hughes colors = cycle(self.contour_colors) 988*e1fe3e4aSElliott Hughes perContourPen = PerContourOrComponentPen( 989*e1fe3e4aSElliott Hughes RecordingPen, glyphset=glyphset 990*e1fe3e4aSElliott Hughes ) 991*e1fe3e4aSElliott Hughes recording.replay(perContourPen) 992*e1fe3e4aSElliott Hughes for i, contour in enumerate(perContourPen.value): 993*e1fe3e4aSElliott Hughes if matching[i] == i: 994*e1fe3e4aSElliott Hughes continue 995*e1fe3e4aSElliott Hughes color = next(colors) 996*e1fe3e4aSElliott Hughes contour.replay(CairoPen(glyphset, cr)) 997*e1fe3e4aSElliott Hughes cr.set_source_rgba(*color, self.contour_alpha) 998*e1fe3e4aSElliott Hughes cr.fill() 999*e1fe3e4aSElliott Hughes 1000*e1fe3e4aSElliott Hughes for problem in problems: 1001*e1fe3e4aSElliott Hughes if problem["type"] in ( 1002*e1fe3e4aSElliott Hughes InterpolatableProblem.NOTHING, 1003*e1fe3e4aSElliott Hughes InterpolatableProblem.WRONG_START_POINT, 1004*e1fe3e4aSElliott Hughes ): 1005*e1fe3e4aSElliott Hughes idx = problem.get("contour") 1006*e1fe3e4aSElliott Hughes 1007*e1fe3e4aSElliott Hughes # Draw suggested point 1008*e1fe3e4aSElliott Hughes if idx is not None and which == 1 and "value_2" in problem: 1009*e1fe3e4aSElliott Hughes perContourPen = PerContourOrComponentPen( 1010*e1fe3e4aSElliott Hughes RecordingPen, glyphset=glyphset 1011*e1fe3e4aSElliott Hughes ) 1012*e1fe3e4aSElliott Hughes decomposedRecording.replay(perContourPen) 1013*e1fe3e4aSElliott Hughes points = SimpleRecordingPointPen() 1014*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points, False) 1015*e1fe3e4aSElliott Hughes perContourPen.value[ 1016*e1fe3e4aSElliott Hughes idx if matching is None else matching[idx] 1017*e1fe3e4aSElliott Hughes ].replay(converter) 1018*e1fe3e4aSElliott Hughes targetPoint = points.value[problem["value_2"]][0] 1019*e1fe3e4aSElliott Hughes cr.save() 1020*e1fe3e4aSElliott Hughes cr.translate(*targetPoint) 1021*e1fe3e4aSElliott Hughes cr.scale(1 / scale, 1 / scale) 1022*e1fe3e4aSElliott Hughes self.draw_dot( 1023*e1fe3e4aSElliott Hughes cr, 1024*e1fe3e4aSElliott Hughes diameter=self.corrected_start_point_size, 1025*e1fe3e4aSElliott Hughes color=self.corrected_start_point_color, 1026*e1fe3e4aSElliott Hughes ) 1027*e1fe3e4aSElliott Hughes cr.restore() 1028*e1fe3e4aSElliott Hughes 1029*e1fe3e4aSElliott Hughes # Draw start-point arrow 1030*e1fe3e4aSElliott Hughes if which == 0 or not problem.get("reversed"): 1031*e1fe3e4aSElliott Hughes color = self.start_point_color 1032*e1fe3e4aSElliott Hughes else: 1033*e1fe3e4aSElliott Hughes color = self.wrong_start_point_color 1034*e1fe3e4aSElliott Hughes first_pt = None 1035*e1fe3e4aSElliott Hughes i = 0 1036*e1fe3e4aSElliott Hughes cr.save() 1037*e1fe3e4aSElliott Hughes for segment, args in decomposedRecording.value: 1038*e1fe3e4aSElliott Hughes if segment == "moveTo": 1039*e1fe3e4aSElliott Hughes first_pt = args[0] 1040*e1fe3e4aSElliott Hughes continue 1041*e1fe3e4aSElliott Hughes if first_pt is None: 1042*e1fe3e4aSElliott Hughes continue 1043*e1fe3e4aSElliott Hughes if segment == "closePath": 1044*e1fe3e4aSElliott Hughes second_pt = first_pt 1045*e1fe3e4aSElliott Hughes else: 1046*e1fe3e4aSElliott Hughes second_pt = args[0] 1047*e1fe3e4aSElliott Hughes 1048*e1fe3e4aSElliott Hughes if idx is None or i == idx: 1049*e1fe3e4aSElliott Hughes cr.save() 1050*e1fe3e4aSElliott Hughes first_pt = complex(*first_pt) 1051*e1fe3e4aSElliott Hughes second_pt = complex(*second_pt) 1052*e1fe3e4aSElliott Hughes length = abs(second_pt - first_pt) 1053*e1fe3e4aSElliott Hughes cr.translate(first_pt.real, first_pt.imag) 1054*e1fe3e4aSElliott Hughes if length: 1055*e1fe3e4aSElliott Hughes # Draw arrowhead 1056*e1fe3e4aSElliott Hughes cr.rotate( 1057*e1fe3e4aSElliott Hughes math.atan2( 1058*e1fe3e4aSElliott Hughes second_pt.imag - first_pt.imag, 1059*e1fe3e4aSElliott Hughes second_pt.real - first_pt.real, 1060*e1fe3e4aSElliott Hughes ) 1061*e1fe3e4aSElliott Hughes ) 1062*e1fe3e4aSElliott Hughes cr.scale(1 / scale, 1 / scale) 1063*e1fe3e4aSElliott Hughes self.draw_arrow(cr, color=color) 1064*e1fe3e4aSElliott Hughes else: 1065*e1fe3e4aSElliott Hughes # Draw circle 1066*e1fe3e4aSElliott Hughes cr.scale(1 / scale, 1 / scale) 1067*e1fe3e4aSElliott Hughes self.draw_dot( 1068*e1fe3e4aSElliott Hughes cr, 1069*e1fe3e4aSElliott Hughes diameter=self.corrected_start_point_size, 1070*e1fe3e4aSElliott Hughes color=color, 1071*e1fe3e4aSElliott Hughes ) 1072*e1fe3e4aSElliott Hughes cr.restore() 1073*e1fe3e4aSElliott Hughes 1074*e1fe3e4aSElliott Hughes if idx is not None: 1075*e1fe3e4aSElliott Hughes break 1076*e1fe3e4aSElliott Hughes 1077*e1fe3e4aSElliott Hughes first_pt = None 1078*e1fe3e4aSElliott Hughes i += 1 1079*e1fe3e4aSElliott Hughes 1080*e1fe3e4aSElliott Hughes cr.restore() 1081*e1fe3e4aSElliott Hughes 1082*e1fe3e4aSElliott Hughes if problem["type"] == InterpolatableProblem.KINK: 1083*e1fe3e4aSElliott Hughes idx = problem.get("contour") 1084*e1fe3e4aSElliott Hughes perContourPen = PerContourOrComponentPen( 1085*e1fe3e4aSElliott Hughes RecordingPen, glyphset=glyphset 1086*e1fe3e4aSElliott Hughes ) 1087*e1fe3e4aSElliott Hughes decomposedRecording.replay(perContourPen) 1088*e1fe3e4aSElliott Hughes points = SimpleRecordingPointPen() 1089*e1fe3e4aSElliott Hughes converter = SegmentToPointPen(points, False) 1090*e1fe3e4aSElliott Hughes perContourPen.value[idx if matching is None else matching[idx]].replay( 1091*e1fe3e4aSElliott Hughes converter 1092*e1fe3e4aSElliott Hughes ) 1093*e1fe3e4aSElliott Hughes 1094*e1fe3e4aSElliott Hughes targetPoint = points.value[problem["value"]][0] 1095*e1fe3e4aSElliott Hughes cr.save() 1096*e1fe3e4aSElliott Hughes cr.translate(*targetPoint) 1097*e1fe3e4aSElliott Hughes cr.scale(1 / scale, 1 / scale) 1098*e1fe3e4aSElliott Hughes if midway: 1099*e1fe3e4aSElliott Hughes self.draw_circle( 1100*e1fe3e4aSElliott Hughes cr, 1101*e1fe3e4aSElliott Hughes diameter=self.kink_circle_size, 1102*e1fe3e4aSElliott Hughes stroke_width=self.kink_circle_stroke_width, 1103*e1fe3e4aSElliott Hughes color=self.kink_circle_color, 1104*e1fe3e4aSElliott Hughes ) 1105*e1fe3e4aSElliott Hughes else: 1106*e1fe3e4aSElliott Hughes self.draw_dot( 1107*e1fe3e4aSElliott Hughes cr, 1108*e1fe3e4aSElliott Hughes diameter=self.kink_point_size, 1109*e1fe3e4aSElliott Hughes color=self.kink_point_color, 1110*e1fe3e4aSElliott Hughes ) 1111*e1fe3e4aSElliott Hughes cr.restore() 1112*e1fe3e4aSElliott Hughes 1113*e1fe3e4aSElliott Hughes return scale 1114*e1fe3e4aSElliott Hughes 1115*e1fe3e4aSElliott Hughes def draw_dot(self, cr, *, x=0, y=0, color=(0, 0, 0), diameter=10): 1116*e1fe3e4aSElliott Hughes cr.save() 1117*e1fe3e4aSElliott Hughes cr.set_line_width(diameter) 1118*e1fe3e4aSElliott Hughes cr.set_line_cap(cairo.LINE_CAP_ROUND) 1119*e1fe3e4aSElliott Hughes cr.move_to(x, y) 1120*e1fe3e4aSElliott Hughes cr.line_to(x, y) 1121*e1fe3e4aSElliott Hughes if len(color) == 3: 1122*e1fe3e4aSElliott Hughes color = color + (1,) 1123*e1fe3e4aSElliott Hughes cr.set_source_rgba(*color) 1124*e1fe3e4aSElliott Hughes cr.stroke() 1125*e1fe3e4aSElliott Hughes cr.restore() 1126*e1fe3e4aSElliott Hughes 1127*e1fe3e4aSElliott Hughes def draw_circle( 1128*e1fe3e4aSElliott Hughes self, cr, *, x=0, y=0, color=(0, 0, 0), diameter=10, stroke_width=1 1129*e1fe3e4aSElliott Hughes ): 1130*e1fe3e4aSElliott Hughes cr.save() 1131*e1fe3e4aSElliott Hughes cr.set_line_width(stroke_width) 1132*e1fe3e4aSElliott Hughes cr.set_line_cap(cairo.LINE_CAP_SQUARE) 1133*e1fe3e4aSElliott Hughes cr.arc(x, y, diameter / 2, 0, 2 * math.pi) 1134*e1fe3e4aSElliott Hughes if len(color) == 3: 1135*e1fe3e4aSElliott Hughes color = color + (1,) 1136*e1fe3e4aSElliott Hughes cr.set_source_rgba(*color) 1137*e1fe3e4aSElliott Hughes cr.stroke() 1138*e1fe3e4aSElliott Hughes cr.restore() 1139*e1fe3e4aSElliott Hughes 1140*e1fe3e4aSElliott Hughes def draw_arrow(self, cr, *, x=0, y=0, color=(0, 0, 0)): 1141*e1fe3e4aSElliott Hughes cr.save() 1142*e1fe3e4aSElliott Hughes if len(color) == 3: 1143*e1fe3e4aSElliott Hughes color = color + (1,) 1144*e1fe3e4aSElliott Hughes cr.set_source_rgba(*color) 1145*e1fe3e4aSElliott Hughes cr.translate(self.start_arrow_length + x, y) 1146*e1fe3e4aSElliott Hughes cr.move_to(0, 0) 1147*e1fe3e4aSElliott Hughes cr.line_to( 1148*e1fe3e4aSElliott Hughes -self.start_arrow_length, 1149*e1fe3e4aSElliott Hughes -self.start_arrow_length * 0.4, 1150*e1fe3e4aSElliott Hughes ) 1151*e1fe3e4aSElliott Hughes cr.line_to( 1152*e1fe3e4aSElliott Hughes -self.start_arrow_length, 1153*e1fe3e4aSElliott Hughes self.start_arrow_length * 0.4, 1154*e1fe3e4aSElliott Hughes ) 1155*e1fe3e4aSElliott Hughes cr.close_path() 1156*e1fe3e4aSElliott Hughes cr.fill() 1157*e1fe3e4aSElliott Hughes cr.restore() 1158*e1fe3e4aSElliott Hughes 1159*e1fe3e4aSElliott Hughes def draw_text(self, text, *, x=0, y=0, color=(0, 0, 0), width=None, height=None): 1160*e1fe3e4aSElliott Hughes if width is None: 1161*e1fe3e4aSElliott Hughes width = self.width 1162*e1fe3e4aSElliott Hughes if height is None: 1163*e1fe3e4aSElliott Hughes height = self.height 1164*e1fe3e4aSElliott Hughes 1165*e1fe3e4aSElliott Hughes text = text.splitlines() 1166*e1fe3e4aSElliott Hughes cr = cairo.Context(self.surface) 1167*e1fe3e4aSElliott Hughes cr.set_source_rgb(*color) 1168*e1fe3e4aSElliott Hughes cr.set_font_size(self.font_size) 1169*e1fe3e4aSElliott Hughes cr.select_font_face( 1170*e1fe3e4aSElliott Hughes "@cairo:monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL 1171*e1fe3e4aSElliott Hughes ) 1172*e1fe3e4aSElliott Hughes text_width = 0 1173*e1fe3e4aSElliott Hughes text_height = 0 1174*e1fe3e4aSElliott Hughes font_extents = cr.font_extents() 1175*e1fe3e4aSElliott Hughes font_font_size = font_extents[2] 1176*e1fe3e4aSElliott Hughes font_ascent = font_extents[0] 1177*e1fe3e4aSElliott Hughes for line in text: 1178*e1fe3e4aSElliott Hughes extents = cr.text_extents(line) 1179*e1fe3e4aSElliott Hughes text_width = max(text_width, extents.x_advance) 1180*e1fe3e4aSElliott Hughes text_height += font_font_size 1181*e1fe3e4aSElliott Hughes if not text_width: 1182*e1fe3e4aSElliott Hughes return 1183*e1fe3e4aSElliott Hughes cr.translate(x, y) 1184*e1fe3e4aSElliott Hughes scale = min(width / text_width, height / text_height) 1185*e1fe3e4aSElliott Hughes # center 1186*e1fe3e4aSElliott Hughes cr.translate( 1187*e1fe3e4aSElliott Hughes (width - text_width * scale) / 2, (height - text_height * scale) / 2 1188*e1fe3e4aSElliott Hughes ) 1189*e1fe3e4aSElliott Hughes cr.scale(scale, scale) 1190*e1fe3e4aSElliott Hughes 1191*e1fe3e4aSElliott Hughes cr.translate(0, font_ascent) 1192*e1fe3e4aSElliott Hughes for line in text: 1193*e1fe3e4aSElliott Hughes cr.move_to(0, 0) 1194*e1fe3e4aSElliott Hughes cr.show_text(line) 1195*e1fe3e4aSElliott Hughes cr.translate(0, font_font_size) 1196*e1fe3e4aSElliott Hughes 1197*e1fe3e4aSElliott Hughes def draw_cupcake(self): 1198*e1fe3e4aSElliott Hughes self.draw_label( 1199*e1fe3e4aSElliott Hughes self.no_issues_label, 1200*e1fe3e4aSElliott Hughes x=self.pad, 1201*e1fe3e4aSElliott Hughes y=self.pad, 1202*e1fe3e4aSElliott Hughes color=self.no_issues_label_color, 1203*e1fe3e4aSElliott Hughes width=self.width - 2 * self.pad, 1204*e1fe3e4aSElliott Hughes align=0.5, 1205*e1fe3e4aSElliott Hughes bold=True, 1206*e1fe3e4aSElliott Hughes font_size=self.title_font_size, 1207*e1fe3e4aSElliott Hughes ) 1208*e1fe3e4aSElliott Hughes 1209*e1fe3e4aSElliott Hughes self.draw_text( 1210*e1fe3e4aSElliott Hughes self.cupcake, 1211*e1fe3e4aSElliott Hughes x=self.pad, 1212*e1fe3e4aSElliott Hughes y=self.pad + self.font_size, 1213*e1fe3e4aSElliott Hughes width=self.width - 2 * self.pad, 1214*e1fe3e4aSElliott Hughes height=self.height - 2 * self.pad - self.font_size, 1215*e1fe3e4aSElliott Hughes color=self.cupcake_color, 1216*e1fe3e4aSElliott Hughes ) 1217*e1fe3e4aSElliott Hughes 1218*e1fe3e4aSElliott Hughes def draw_emoticon(self, emoticon, x=0, y=0): 1219*e1fe3e4aSElliott Hughes self.draw_text( 1220*e1fe3e4aSElliott Hughes emoticon, 1221*e1fe3e4aSElliott Hughes x=x, 1222*e1fe3e4aSElliott Hughes y=y, 1223*e1fe3e4aSElliott Hughes color=self.emoticon_color, 1224*e1fe3e4aSElliott Hughes width=self.panel_width, 1225*e1fe3e4aSElliott Hughes height=self.panel_height, 1226*e1fe3e4aSElliott Hughes ) 1227*e1fe3e4aSElliott Hughes 1228*e1fe3e4aSElliott Hughes 1229*e1fe3e4aSElliott Hughesclass InterpolatablePostscriptLike(InterpolatablePlot): 1230*e1fe3e4aSElliott Hughes def __exit__(self, type, value, traceback): 1231*e1fe3e4aSElliott Hughes self.surface.finish() 1232*e1fe3e4aSElliott Hughes 1233*e1fe3e4aSElliott Hughes def show_page(self): 1234*e1fe3e4aSElliott Hughes super().show_page() 1235*e1fe3e4aSElliott Hughes self.surface.show_page() 1236*e1fe3e4aSElliott Hughes 1237*e1fe3e4aSElliott Hughes 1238*e1fe3e4aSElliott Hughesclass InterpolatablePS(InterpolatablePostscriptLike): 1239*e1fe3e4aSElliott Hughes def __enter__(self): 1240*e1fe3e4aSElliott Hughes self.surface = cairo.PSSurface(self.out, self.width, self.height) 1241*e1fe3e4aSElliott Hughes return self 1242*e1fe3e4aSElliott Hughes 1243*e1fe3e4aSElliott Hughes 1244*e1fe3e4aSElliott Hughesclass InterpolatablePDF(InterpolatablePostscriptLike): 1245*e1fe3e4aSElliott Hughes def __enter__(self): 1246*e1fe3e4aSElliott Hughes self.surface = cairo.PDFSurface(self.out, self.width, self.height) 1247*e1fe3e4aSElliott Hughes self.surface.set_metadata( 1248*e1fe3e4aSElliott Hughes cairo.PDF_METADATA_CREATOR, "fonttools varLib.interpolatable" 1249*e1fe3e4aSElliott Hughes ) 1250*e1fe3e4aSElliott Hughes self.surface.set_metadata(cairo.PDF_METADATA_CREATE_DATE, "") 1251*e1fe3e4aSElliott Hughes return self 1252*e1fe3e4aSElliott Hughes 1253*e1fe3e4aSElliott Hughes 1254*e1fe3e4aSElliott Hughesclass InterpolatableSVG(InterpolatablePlot): 1255*e1fe3e4aSElliott Hughes def __enter__(self): 1256*e1fe3e4aSElliott Hughes self.sink = BytesIO() 1257*e1fe3e4aSElliott Hughes self.surface = cairo.SVGSurface(self.sink, self.width, self.height) 1258*e1fe3e4aSElliott Hughes return self 1259*e1fe3e4aSElliott Hughes 1260*e1fe3e4aSElliott Hughes def __exit__(self, type, value, traceback): 1261*e1fe3e4aSElliott Hughes if self.surface is not None: 1262*e1fe3e4aSElliott Hughes self.show_page() 1263*e1fe3e4aSElliott Hughes 1264*e1fe3e4aSElliott Hughes def show_page(self): 1265*e1fe3e4aSElliott Hughes super().show_page() 1266*e1fe3e4aSElliott Hughes self.surface.finish() 1267*e1fe3e4aSElliott Hughes self.out.append(self.sink.getvalue()) 1268*e1fe3e4aSElliott Hughes self.sink = BytesIO() 1269*e1fe3e4aSElliott Hughes self.surface = cairo.SVGSurface(self.sink, self.width, self.height) 1270