1*e1fe3e4aSElliott Hughesimport os 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import CapturingLogHandler 4*e1fe3e4aSElliott Hughesfrom fontTools.cu2qu.ufo import ( 5*e1fe3e4aSElliott Hughes fonts_to_quadratic, 6*e1fe3e4aSElliott Hughes font_to_quadratic, 7*e1fe3e4aSElliott Hughes glyphs_to_quadratic, 8*e1fe3e4aSElliott Hughes glyph_to_quadratic, 9*e1fe3e4aSElliott Hughes logger, 10*e1fe3e4aSElliott Hughes CURVE_TYPE_LIB_KEY, 11*e1fe3e4aSElliott Hughes) 12*e1fe3e4aSElliott Hughesfrom fontTools.cu2qu.errors import ( 13*e1fe3e4aSElliott Hughes IncompatibleSegmentNumberError, 14*e1fe3e4aSElliott Hughes IncompatibleSegmentTypesError, 15*e1fe3e4aSElliott Hughes IncompatibleFontsError, 16*e1fe3e4aSElliott Hughes) 17*e1fe3e4aSElliott Hughes 18*e1fe3e4aSElliott Hughesimport pytest 19*e1fe3e4aSElliott Hughes 20*e1fe3e4aSElliott Hughes 21*e1fe3e4aSElliott HughesufoLib2 = pytest.importorskip("ufoLib2") 22*e1fe3e4aSElliott Hughes 23*e1fe3e4aSElliott HughesDATADIR = os.path.join(os.path.dirname(__file__), "data") 24*e1fe3e4aSElliott Hughes 25*e1fe3e4aSElliott HughesTEST_UFOS = [ 26*e1fe3e4aSElliott Hughes os.path.join(DATADIR, "RobotoSubset-Regular.ufo"), 27*e1fe3e4aSElliott Hughes os.path.join(DATADIR, "RobotoSubset-Bold.ufo"), 28*e1fe3e4aSElliott Hughes] 29*e1fe3e4aSElliott Hughes 30*e1fe3e4aSElliott Hughes 31*e1fe3e4aSElliott Hughes@pytest.fixture 32*e1fe3e4aSElliott Hughesdef fonts(): 33*e1fe3e4aSElliott Hughes return [ufoLib2.Font.open(ufo) for ufo in TEST_UFOS] 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughes 36*e1fe3e4aSElliott Hughesclass FontsToQuadraticTest(object): 37*e1fe3e4aSElliott Hughes def test_modified(self, fonts): 38*e1fe3e4aSElliott Hughes modified = fonts_to_quadratic(fonts) 39*e1fe3e4aSElliott Hughes assert modified 40*e1fe3e4aSElliott Hughes 41*e1fe3e4aSElliott Hughes def test_stats(self, fonts): 42*e1fe3e4aSElliott Hughes stats = {} 43*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, stats=stats) 44*e1fe3e4aSElliott Hughes assert stats == {"1": 1, "2": 79, "3": 130, "4": 2} 45*e1fe3e4aSElliott Hughes 46*e1fe3e4aSElliott Hughes def test_dump_stats(self, fonts): 47*e1fe3e4aSElliott Hughes with CapturingLogHandler(logger, "INFO") as captor: 48*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, dump_stats=True) 49*e1fe3e4aSElliott Hughes assert captor.assertRegex("New spline lengths:") 50*e1fe3e4aSElliott Hughes 51*e1fe3e4aSElliott Hughes def test_remember_curve_type_quadratic(self, fonts): 52*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, remember_curve_type=True) 53*e1fe3e4aSElliott Hughes assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "quadratic" 54*e1fe3e4aSElliott Hughes with CapturingLogHandler(logger, "INFO") as captor: 55*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, remember_curve_type=True) 56*e1fe3e4aSElliott Hughes assert captor.assertRegex("already converted") 57*e1fe3e4aSElliott Hughes 58*e1fe3e4aSElliott Hughes def test_remember_curve_type_mixed(self, fonts): 59*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, remember_curve_type=True, all_quadratic=False) 60*e1fe3e4aSElliott Hughes assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "mixed" 61*e1fe3e4aSElliott Hughes with CapturingLogHandler(logger, "INFO") as captor: 62*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, remember_curve_type=True) 63*e1fe3e4aSElliott Hughes assert captor.assertRegex("already converted") 64*e1fe3e4aSElliott Hughes 65*e1fe3e4aSElliott Hughes def test_no_remember_curve_type(self, fonts): 66*e1fe3e4aSElliott Hughes assert CURVE_TYPE_LIB_KEY not in fonts[0].lib 67*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, remember_curve_type=False) 68*e1fe3e4aSElliott Hughes assert CURVE_TYPE_LIB_KEY not in fonts[0].lib 69*e1fe3e4aSElliott Hughes 70*e1fe3e4aSElliott Hughes def test_different_glyphsets(self, fonts): 71*e1fe3e4aSElliott Hughes del fonts[0]["a"] 72*e1fe3e4aSElliott Hughes assert "a" not in fonts[0] 73*e1fe3e4aSElliott Hughes assert "a" in fonts[1] 74*e1fe3e4aSElliott Hughes assert fonts_to_quadratic(fonts) 75*e1fe3e4aSElliott Hughes 76*e1fe3e4aSElliott Hughes def test_max_err_em_float(self, fonts): 77*e1fe3e4aSElliott Hughes stats = {} 78*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, max_err_em=0.002, stats=stats) 79*e1fe3e4aSElliott Hughes assert stats == {"1": 5, "2": 193, "3": 14} 80*e1fe3e4aSElliott Hughes 81*e1fe3e4aSElliott Hughes def test_max_err_em_list(self, fonts): 82*e1fe3e4aSElliott Hughes stats = {} 83*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, max_err_em=[0.002, 0.002], stats=stats) 84*e1fe3e4aSElliott Hughes assert stats == {"1": 5, "2": 193, "3": 14} 85*e1fe3e4aSElliott Hughes 86*e1fe3e4aSElliott Hughes def test_max_err_float(self, fonts): 87*e1fe3e4aSElliott Hughes stats = {} 88*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, max_err=4.096, stats=stats) 89*e1fe3e4aSElliott Hughes assert stats == {"1": 5, "2": 193, "3": 14} 90*e1fe3e4aSElliott Hughes 91*e1fe3e4aSElliott Hughes def test_max_err_list(self, fonts): 92*e1fe3e4aSElliott Hughes stats = {} 93*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, max_err=[4.096, 4.096], stats=stats) 94*e1fe3e4aSElliott Hughes assert stats == {"1": 5, "2": 193, "3": 14} 95*e1fe3e4aSElliott Hughes 96*e1fe3e4aSElliott Hughes def test_both_max_err_and_max_err_em(self, fonts): 97*e1fe3e4aSElliott Hughes with pytest.raises(TypeError, match="Only one .* can be specified"): 98*e1fe3e4aSElliott Hughes fonts_to_quadratic(fonts, max_err=1.000, max_err_em=0.001) 99*e1fe3e4aSElliott Hughes 100*e1fe3e4aSElliott Hughes def test_single_font(self, fonts): 101*e1fe3e4aSElliott Hughes assert font_to_quadratic(fonts[0], max_err_em=0.002, reverse_direction=True) 102*e1fe3e4aSElliott Hughes assert font_to_quadratic( 103*e1fe3e4aSElliott Hughes fonts[1], max_err_em=0.002, reverse_direction=True, all_quadratic=False 104*e1fe3e4aSElliott Hughes ) 105*e1fe3e4aSElliott Hughes 106*e1fe3e4aSElliott Hughes 107*e1fe3e4aSElliott Hughesclass GlyphsToQuadraticTest(object): 108*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 109*e1fe3e4aSElliott Hughes ["glyph", "expected"], 110*e1fe3e4aSElliott Hughes [("A", False), ("a", True)], # contains no curves, it is not modified 111*e1fe3e4aSElliott Hughes ids=["lines-only", "has-curves"], 112*e1fe3e4aSElliott Hughes ) 113*e1fe3e4aSElliott Hughes def test_modified(self, fonts, glyph, expected): 114*e1fe3e4aSElliott Hughes glyphs = [f[glyph] for f in fonts] 115*e1fe3e4aSElliott Hughes assert glyphs_to_quadratic(glyphs) == expected 116*e1fe3e4aSElliott Hughes 117*e1fe3e4aSElliott Hughes def test_stats(self, fonts): 118*e1fe3e4aSElliott Hughes stats = {} 119*e1fe3e4aSElliott Hughes glyphs_to_quadratic([f["a"] for f in fonts], stats=stats) 120*e1fe3e4aSElliott Hughes assert stats == {"2": 1, "3": 7, "4": 3, "5": 1} 121*e1fe3e4aSElliott Hughes 122*e1fe3e4aSElliott Hughes def test_max_err_float(self, fonts): 123*e1fe3e4aSElliott Hughes glyphs = [f["a"] for f in fonts] 124*e1fe3e4aSElliott Hughes stats = {} 125*e1fe3e4aSElliott Hughes glyphs_to_quadratic(glyphs, max_err=4.096, stats=stats) 126*e1fe3e4aSElliott Hughes assert stats == {"2": 11, "3": 1} 127*e1fe3e4aSElliott Hughes 128*e1fe3e4aSElliott Hughes def test_max_err_list(self, fonts): 129*e1fe3e4aSElliott Hughes glyphs = [f["a"] for f in fonts] 130*e1fe3e4aSElliott Hughes stats = {} 131*e1fe3e4aSElliott Hughes glyphs_to_quadratic(glyphs, max_err=[4.096, 4.096], stats=stats) 132*e1fe3e4aSElliott Hughes assert stats == {"2": 11, "3": 1} 133*e1fe3e4aSElliott Hughes 134*e1fe3e4aSElliott Hughes def test_reverse_direction(self, fonts): 135*e1fe3e4aSElliott Hughes glyphs = [f["A"] for f in fonts] 136*e1fe3e4aSElliott Hughes assert glyphs_to_quadratic(glyphs, reverse_direction=True) 137*e1fe3e4aSElliott Hughes 138*e1fe3e4aSElliott Hughes def test_single_glyph(self, fonts): 139*e1fe3e4aSElliott Hughes assert glyph_to_quadratic(fonts[0]["a"], max_err=4.096, reverse_direction=True) 140*e1fe3e4aSElliott Hughes 141*e1fe3e4aSElliott Hughes @pytest.mark.parametrize( 142*e1fe3e4aSElliott Hughes ["outlines", "exception", "message"], 143*e1fe3e4aSElliott Hughes [ 144*e1fe3e4aSElliott Hughes [ 145*e1fe3e4aSElliott Hughes [ 146*e1fe3e4aSElliott Hughes [ 147*e1fe3e4aSElliott Hughes ("moveTo", ((0, 0),)), 148*e1fe3e4aSElliott Hughes ("curveTo", ((1, 1), (2, 2), (3, 3))), 149*e1fe3e4aSElliott Hughes ("curveTo", ((4, 4), (5, 5), (6, 6))), 150*e1fe3e4aSElliott Hughes ("closePath", ()), 151*e1fe3e4aSElliott Hughes ], 152*e1fe3e4aSElliott Hughes [ 153*e1fe3e4aSElliott Hughes ("moveTo", ((7, 7),)), 154*e1fe3e4aSElliott Hughes ("curveTo", ((8, 8), (9, 9), (10, 10))), 155*e1fe3e4aSElliott Hughes ("closePath", ()), 156*e1fe3e4aSElliott Hughes ], 157*e1fe3e4aSElliott Hughes ], 158*e1fe3e4aSElliott Hughes IncompatibleSegmentNumberError, 159*e1fe3e4aSElliott Hughes "have different number of segments", 160*e1fe3e4aSElliott Hughes ], 161*e1fe3e4aSElliott Hughes [ 162*e1fe3e4aSElliott Hughes [ 163*e1fe3e4aSElliott Hughes [ 164*e1fe3e4aSElliott Hughes ("moveTo", ((0, 0),)), 165*e1fe3e4aSElliott Hughes ("curveTo", ((1, 1), (2, 2), (3, 3))), 166*e1fe3e4aSElliott Hughes ("closePath", ()), 167*e1fe3e4aSElliott Hughes ], 168*e1fe3e4aSElliott Hughes [ 169*e1fe3e4aSElliott Hughes ("moveTo", ((4, 4),)), 170*e1fe3e4aSElliott Hughes ("lineTo", ((5, 5),)), 171*e1fe3e4aSElliott Hughes ("closePath", ()), 172*e1fe3e4aSElliott Hughes ], 173*e1fe3e4aSElliott Hughes ], 174*e1fe3e4aSElliott Hughes IncompatibleSegmentTypesError, 175*e1fe3e4aSElliott Hughes "have incompatible segment types", 176*e1fe3e4aSElliott Hughes ], 177*e1fe3e4aSElliott Hughes ], 178*e1fe3e4aSElliott Hughes ids=[ 179*e1fe3e4aSElliott Hughes "unequal-length", 180*e1fe3e4aSElliott Hughes "different-segment-types", 181*e1fe3e4aSElliott Hughes ], 182*e1fe3e4aSElliott Hughes ) 183*e1fe3e4aSElliott Hughes def test_incompatible_glyphs(self, outlines, exception, message): 184*e1fe3e4aSElliott Hughes glyphs = [] 185*e1fe3e4aSElliott Hughes for i, outline in enumerate(outlines): 186*e1fe3e4aSElliott Hughes glyph = ufoLib2.objects.Glyph("glyph%d" % i) 187*e1fe3e4aSElliott Hughes pen = glyph.getPen() 188*e1fe3e4aSElliott Hughes for operator, args in outline: 189*e1fe3e4aSElliott Hughes getattr(pen, operator)(*args) 190*e1fe3e4aSElliott Hughes glyphs.append(glyph) 191*e1fe3e4aSElliott Hughes with pytest.raises(exception) as excinfo: 192*e1fe3e4aSElliott Hughes glyphs_to_quadratic(glyphs) 193*e1fe3e4aSElliott Hughes assert excinfo.match(message) 194*e1fe3e4aSElliott Hughes 195*e1fe3e4aSElliott Hughes def test_incompatible_fonts(self): 196*e1fe3e4aSElliott Hughes font1 = ufoLib2.Font() 197*e1fe3e4aSElliott Hughes font1.info.unitsPerEm = 1000 198*e1fe3e4aSElliott Hughes glyph1 = font1.newGlyph("a") 199*e1fe3e4aSElliott Hughes pen1 = glyph1.getPen() 200*e1fe3e4aSElliott Hughes for operator, args in [ 201*e1fe3e4aSElliott Hughes ("moveTo", ((0, 0),)), 202*e1fe3e4aSElliott Hughes ("lineTo", ((1, 1),)), 203*e1fe3e4aSElliott Hughes ("endPath", ()), 204*e1fe3e4aSElliott Hughes ]: 205*e1fe3e4aSElliott Hughes getattr(pen1, operator)(*args) 206*e1fe3e4aSElliott Hughes 207*e1fe3e4aSElliott Hughes font2 = ufoLib2.Font() 208*e1fe3e4aSElliott Hughes font2.info.unitsPerEm = 1000 209*e1fe3e4aSElliott Hughes glyph2 = font2.newGlyph("a") 210*e1fe3e4aSElliott Hughes pen2 = glyph2.getPen() 211*e1fe3e4aSElliott Hughes for operator, args in [ 212*e1fe3e4aSElliott Hughes ("moveTo", ((0, 0),)), 213*e1fe3e4aSElliott Hughes ("curveTo", ((1, 1), (2, 2), (3, 3))), 214*e1fe3e4aSElliott Hughes ("endPath", ()), 215*e1fe3e4aSElliott Hughes ]: 216*e1fe3e4aSElliott Hughes getattr(pen2, operator)(*args) 217*e1fe3e4aSElliott Hughes 218*e1fe3e4aSElliott Hughes with pytest.raises(IncompatibleFontsError) as excinfo: 219*e1fe3e4aSElliott Hughes fonts_to_quadratic([font1, font2]) 220*e1fe3e4aSElliott Hughes assert excinfo.match("fonts contains incompatible glyphs: 'a'") 221*e1fe3e4aSElliott Hughes 222*e1fe3e4aSElliott Hughes assert hasattr(excinfo.value, "glyph_errors") 223*e1fe3e4aSElliott Hughes error = excinfo.value.glyph_errors["a"] 224*e1fe3e4aSElliott Hughes assert isinstance(error, IncompatibleSegmentTypesError) 225*e1fe3e4aSElliott Hughes assert error.segments == {1: ["line", "curve"]} 226*e1fe3e4aSElliott Hughes 227*e1fe3e4aSElliott Hughes def test_already_quadratic(self): 228*e1fe3e4aSElliott Hughes glyph = ufoLib2.objects.Glyph() 229*e1fe3e4aSElliott Hughes pen = glyph.getPen() 230*e1fe3e4aSElliott Hughes pen.moveTo((0, 0)) 231*e1fe3e4aSElliott Hughes pen.qCurveTo((1, 1), (2, 2)) 232*e1fe3e4aSElliott Hughes pen.closePath() 233*e1fe3e4aSElliott Hughes assert not glyph_to_quadratic(glyph) 234*e1fe3e4aSElliott Hughes 235*e1fe3e4aSElliott Hughes def test_open_paths(self): 236*e1fe3e4aSElliott Hughes glyph = ufoLib2.objects.Glyph() 237*e1fe3e4aSElliott Hughes pen = glyph.getPen() 238*e1fe3e4aSElliott Hughes pen.moveTo((0, 0)) 239*e1fe3e4aSElliott Hughes pen.lineTo((1, 1)) 240*e1fe3e4aSElliott Hughes pen.curveTo((2, 2), (3, 3), (4, 4)) 241*e1fe3e4aSElliott Hughes pen.endPath() 242*e1fe3e4aSElliott Hughes assert glyph_to_quadratic(glyph) 243*e1fe3e4aSElliott Hughes # open contour is still open 244*e1fe3e4aSElliott Hughes assert glyph[-1][0].segmentType == "move" 245*e1fe3e4aSElliott Hughes 246*e1fe3e4aSElliott Hughes def test_ignore_components(self): 247*e1fe3e4aSElliott Hughes glyph = ufoLib2.objects.Glyph() 248*e1fe3e4aSElliott Hughes pen = glyph.getPen() 249*e1fe3e4aSElliott Hughes pen.addComponent("a", (1, 0, 0, 1, 0, 0)) 250*e1fe3e4aSElliott Hughes pen.moveTo((0, 0)) 251*e1fe3e4aSElliott Hughes pen.curveTo((1, 1), (2, 2), (3, 3)) 252*e1fe3e4aSElliott Hughes pen.closePath() 253*e1fe3e4aSElliott Hughes assert glyph_to_quadratic(glyph) 254*e1fe3e4aSElliott Hughes assert len(glyph.components) == 1 255*e1fe3e4aSElliott Hughes 256*e1fe3e4aSElliott Hughes def test_overlapping_start_end_points(self): 257*e1fe3e4aSElliott Hughes # https://github.com/googlefonts/fontmake/issues/572 258*e1fe3e4aSElliott Hughes glyph1 = ufoLib2.objects.Glyph() 259*e1fe3e4aSElliott Hughes pen = glyph1.getPointPen() 260*e1fe3e4aSElliott Hughes pen.beginPath() 261*e1fe3e4aSElliott Hughes pen.addPoint((0, 651), segmentType="line") 262*e1fe3e4aSElliott Hughes pen.addPoint((0, 101), segmentType="line") 263*e1fe3e4aSElliott Hughes pen.addPoint((0, 101), segmentType="line") 264*e1fe3e4aSElliott Hughes pen.addPoint((0, 651), segmentType="line") 265*e1fe3e4aSElliott Hughes pen.endPath() 266*e1fe3e4aSElliott Hughes 267*e1fe3e4aSElliott Hughes glyph2 = ufoLib2.objects.Glyph() 268*e1fe3e4aSElliott Hughes pen = glyph2.getPointPen() 269*e1fe3e4aSElliott Hughes pen.beginPath() 270*e1fe3e4aSElliott Hughes pen.addPoint((1, 651), segmentType="line") 271*e1fe3e4aSElliott Hughes pen.addPoint((2, 101), segmentType="line") 272*e1fe3e4aSElliott Hughes pen.addPoint((3, 101), segmentType="line") 273*e1fe3e4aSElliott Hughes pen.addPoint((4, 651), segmentType="line") 274*e1fe3e4aSElliott Hughes pen.endPath() 275*e1fe3e4aSElliott Hughes 276*e1fe3e4aSElliott Hughes glyphs = [glyph1, glyph2] 277*e1fe3e4aSElliott Hughes 278*e1fe3e4aSElliott Hughes assert glyphs_to_quadratic(glyphs, reverse_direction=True) 279*e1fe3e4aSElliott Hughes 280*e1fe3e4aSElliott Hughes assert [[(p.x, p.y) for p in glyph[0]] for glyph in glyphs] == [ 281*e1fe3e4aSElliott Hughes [ 282*e1fe3e4aSElliott Hughes (0, 651), 283*e1fe3e4aSElliott Hughes (0, 651), 284*e1fe3e4aSElliott Hughes (0, 101), 285*e1fe3e4aSElliott Hughes (0, 101), 286*e1fe3e4aSElliott Hughes ], 287*e1fe3e4aSElliott Hughes [(1, 651), (4, 651), (3, 101), (2, 101)], 288*e1fe3e4aSElliott Hughes ] 289