xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/interpolatablePlot.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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