1# Copyright 2016 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import sys 16import unittest 17 18from fontTools.pens.qu2cuPen import Qu2CuPen 19from fontTools.pens.recordingPen import RecordingPen 20from textwrap import dedent 21import pytest 22 23try: 24 from .utils import CUBIC_GLYPHS, QUAD_GLYPHS 25 from .utils import DummyGlyph 26 from .utils import DummyPen 27except ImportError as e: 28 pytest.skip(str(e), allow_module_level=True) 29 30MAX_ERR = 1.0 31 32 33class _TestPenMixin(object): 34 """Collection of tests that are shared by both the SegmentPen and the 35 PointPen test cases, plus some helper methods. 36 Note: We currently don't have a PointPen. 37 """ 38 39 maxDiff = None 40 41 def diff(self, expected, actual): 42 import difflib 43 44 expected = str(self.Glyph(expected)).splitlines(True) 45 actual = str(self.Glyph(actual)).splitlines(True) 46 diff = difflib.unified_diff( 47 expected, actual, fromfile="expected", tofile="actual" 48 ) 49 return "".join(diff) 50 51 def convert_glyph(self, glyph, **kwargs): 52 # draw source glyph onto a new glyph using a Cu2Qu pen and return it 53 converted = self.Glyph() 54 pen = getattr(converted, self.pen_getter_name)() 55 cubicpen = self.Qu2CuPen(pen, MAX_ERR, all_cubic=True, **kwargs) 56 getattr(glyph, self.draw_method_name)(cubicpen) 57 return converted 58 59 def expect_glyph(self, source, expected): 60 converted = self.convert_glyph(source) 61 self.assertNotEqual(converted, source) 62 if not converted.approx(expected): 63 print(self.diff(expected, converted)) 64 self.fail("converted glyph is different from expected") 65 66 def test_convert_simple_glyph(self): 67 self.expect_glyph(QUAD_GLYPHS["a"], CUBIC_GLYPHS["a"]) 68 self.expect_glyph(QUAD_GLYPHS["A"], CUBIC_GLYPHS["A"]) 69 70 def test_convert_composite_glyph(self): 71 source = CUBIC_GLYPHS["Aacute"] 72 converted = self.convert_glyph(source) 73 # components don't change after quadratic conversion 74 self.assertEqual(converted, source) 75 76 def test_reverse_direction(self): 77 for name in ("a", "A", "Eacute"): 78 source = QUAD_GLYPHS[name] 79 normal_glyph = self.convert_glyph(source) 80 reversed_glyph = self.convert_glyph(source, reverse_direction=True) 81 82 # the number of commands is the same, just their order is iverted 83 self.assertTrue(len(normal_glyph.outline), len(reversed_glyph.outline)) 84 self.assertNotEqual(normal_glyph, reversed_glyph) 85 86 def test_stats(self): 87 stats = {} 88 for name in QUAD_GLYPHS.keys(): 89 source = QUAD_GLYPHS[name] 90 self.convert_glyph(source, stats=stats) 91 92 self.assertTrue(stats) 93 self.assertTrue("2" in stats) 94 self.assertEqual(type(stats["2"]), int) 95 96 def test_addComponent(self): 97 pen = self.Pen() 98 cubicpen = self.Qu2CuPen(pen, MAX_ERR) 99 cubicpen.addComponent("a", (1, 2, 3, 4, 5.0, 6.0)) 100 101 # components are passed through without changes 102 self.assertEqual( 103 str(pen).splitlines(), 104 [ 105 "pen.addComponent('a', (1, 2, 3, 4, 5.0, 6.0))", 106 ], 107 ) 108 109 110class TestQu2CuPen(unittest.TestCase, _TestPenMixin): 111 def __init__(self, *args, **kwargs): 112 super(TestQu2CuPen, self).__init__(*args, **kwargs) 113 self.Glyph = DummyGlyph 114 self.Pen = DummyPen 115 self.Qu2CuPen = Qu2CuPen 116 self.pen_getter_name = "getPen" 117 self.draw_method_name = "draw" 118 119 def test_qCurveTo_1_point(self): 120 pen = DummyPen() 121 cubicpen = Qu2CuPen(pen, MAX_ERR) 122 cubicpen.moveTo((0, 0)) 123 cubicpen.qCurveTo((1, 1)) 124 cubicpen.closePath() 125 126 self.assertEqual( 127 str(pen).splitlines(), 128 [ 129 "pen.moveTo((0, 0))", 130 "pen.qCurveTo((1, 1))", 131 "pen.closePath()", 132 ], 133 ) 134 135 def test_qCurveTo_2_points(self): 136 pen = DummyPen() 137 cubicpen = Qu2CuPen(pen, MAX_ERR) 138 cubicpen.moveTo((0, 0)) 139 cubicpen.qCurveTo((1, 1), (2, 2)) 140 cubicpen.closePath() 141 142 self.assertEqual( 143 str(pen).splitlines(), 144 [ 145 "pen.moveTo((0, 0))", 146 "pen.qCurveTo((1, 1), (2, 2))", 147 "pen.closePath()", 148 ], 149 ) 150 151 def test_qCurveTo_3_points_no_conversion(self): 152 pen = DummyPen() 153 cubicpen = Qu2CuPen(pen, MAX_ERR) 154 cubicpen.moveTo((0, 0)) 155 cubicpen.qCurveTo((0, 3), (1, 3), (1, 0)) 156 cubicpen.closePath() 157 158 self.assertEqual( 159 str(pen).splitlines(), 160 [ 161 "pen.moveTo((0, 0))", 162 "pen.qCurveTo((0, 3), (1, 3), (1, 0))", 163 "pen.closePath()", 164 ], 165 ) 166 167 def test_qCurveTo_no_oncurve_points(self): 168 pen = DummyPen() 169 cubicpen = Qu2CuPen(pen, MAX_ERR) 170 cubicpen.qCurveTo((0, 0), (1, 0), (1, 1), (0, 1), None) 171 cubicpen.closePath() 172 173 self.assertEqual( 174 str(pen).splitlines(), 175 ["pen.qCurveTo((0, 0), (1, 0), (1, 1), (0, 1), None)", "pen.closePath()"], 176 ) 177 178 def test_curveTo_1_point(self): 179 pen = DummyPen() 180 cubicpen = Qu2CuPen(pen, MAX_ERR) 181 cubicpen.moveTo((0, 0)) 182 cubicpen.curveTo((1, 1)) 183 cubicpen.closePath() 184 185 self.assertEqual( 186 str(pen).splitlines(), 187 [ 188 "pen.moveTo((0, 0))", 189 "pen.curveTo((1, 1))", 190 "pen.closePath()", 191 ], 192 ) 193 194 def test_curveTo_2_points(self): 195 pen = DummyPen() 196 cubicpen = Qu2CuPen(pen, MAX_ERR) 197 cubicpen.moveTo((0, 0)) 198 cubicpen.curveTo((1, 1), (2, 2)) 199 cubicpen.closePath() 200 201 self.assertEqual( 202 str(pen).splitlines(), 203 [ 204 "pen.moveTo((0, 0))", 205 "pen.curveTo((1, 1), (2, 2))", 206 "pen.closePath()", 207 ], 208 ) 209 210 def test_curveTo_3_points(self): 211 pen = DummyPen() 212 cubicpen = Qu2CuPen(pen, MAX_ERR) 213 cubicpen.moveTo((0, 0)) 214 cubicpen.curveTo((1, 1), (2, 2), (3, 3)) 215 cubicpen.closePath() 216 217 self.assertEqual( 218 str(pen).splitlines(), 219 [ 220 "pen.moveTo((0, 0))", 221 "pen.curveTo((1, 1), (2, 2), (3, 3))", 222 "pen.closePath()", 223 ], 224 ) 225 226 def test_all_cubic(self): 227 inPen = RecordingPen() 228 inPen.value = [ 229 ("moveTo", ((1204, 347),)), 230 ("qCurveTo", ((1255, 347), (1323, 433), (1323, 467))), 231 ("qCurveTo", ((1323, 478), (1310, 492), (1302, 492))), 232 ("qCurveTo", ((1295, 492), (1289, 484))), 233 ("lineTo", ((1272, 461),)), 234 ("qCurveTo", ((1256, 439), (1221, 416), (1200, 416))), 235 ("qCurveTo", ((1181, 416), (1141, 440), (1141, 462))), 236 ("qCurveTo", ((1141, 484), (1190, 565), (1190, 594))), 237 ("qCurveTo", ((1190, 607), (1181, 634), (1168, 634))), 238 ("qCurveTo", ((1149, 634), (1146, 583), (1081, 496), (1081, 463))), 239 ("qCurveTo", ((1081, 417), (1164, 347), (1204, 347))), 240 ("closePath", ()), 241 ] 242 243 outPen = RecordingPen() 244 q2cPen = Qu2CuPen(outPen, 1.0, all_cubic=True) 245 inPen.replay(q2cPen) 246 247 print(outPen.value) 248 249 assert not any(typ == "qCurveTo" for typ, _ in outPen.value) 250 251 252if __name__ == "__main__": 253 unittest.main() 254