1 from __future__ import print_function, division, absolute_import 2 from fontTools.misc.py23 import * 3 from fontTools.misc.loggingTools import CapturingLogHandler 4 import unittest 5 6 from fontTools.pens.basePen import AbstractPen 7 from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen, \ 8 SegmentToPointPen, GuessSmoothPointPen, ReverseContourPointPen 9 10 11 class _TestSegmentPen(AbstractPen): 12 13 def __init__(self): 14 self._commands = [] 15 16 def __repr__(self): 17 return " ".join(self._commands) 18 19 def moveTo(self, pt): 20 self._commands.append("%s %s moveto" % (pt[0], pt[1])) 21 22 def lineTo(self, pt): 23 self._commands.append("%s %s lineto" % (pt[0], pt[1])) 24 25 def curveTo(self, *pts): 26 pts = ["%s %s" % pt for pt in pts] 27 self._commands.append("%s curveto" % " ".join(pts)) 28 29 def qCurveTo(self, *pts): 30 pts = ["%s %s" % pt if pt is not None else "None" for pt in pts] 31 self._commands.append("%s qcurveto" % " ".join(pts)) 32 33 def closePath(self): 34 self._commands.append("closepath") 35 36 def endPath(self): 37 self._commands.append("endpath") 38 39 def addComponent(self, glyphName, transformation): 40 self._commands.append("'%s' %s addcomponent" % (glyphName, transformation)) 41 42 43 def _reprKwargs(kwargs): 44 items = [] 45 for key in sorted(kwargs): 46 value = kwargs[key] 47 if isinstance(value, basestring): 48 items.append("%s='%s'" % (key, value)) 49 else: 50 items.append("%s=%s" % (key, value)) 51 return items 52 53 54 class _TestPointPen(AbstractPointPen): 55 56 def __init__(self): 57 self._commands = [] 58 59 def __repr__(self): 60 return " ".join(self._commands) 61 62 def beginPath(self, identifier=None, **kwargs): 63 items = [] 64 if identifier is not None: 65 items.append("identifier='%s'" % identifier) 66 items.extend(_reprKwargs(kwargs)) 67 self._commands.append("beginPath(%s)" % ", ".join(items)) 68 69 def addPoint(self, pt, segmentType=None, smooth=False, name=None, 70 identifier=None, **kwargs): 71 items = ["%s" % (pt,)] 72 if segmentType is not None: 73 items.append("segmentType='%s'" % segmentType) 74 if smooth: 75 items.append("smooth=True") 76 if name is not None: 77 items.append("name='%s'" % name) 78 if identifier is not None: 79 items.append("identifier='%s'" % identifier) 80 items.extend(_reprKwargs(kwargs)) 81 self._commands.append("addPoint(%s)" % ", ".join(items)) 82 83 def endPath(self): 84 self._commands.append("endPath()") 85 86 def addComponent(self, glyphName, transform, identifier=None, **kwargs): 87 items = ["'%s'" % glyphName, "%s" % transform] 88 if identifier is not None: 89 items.append("identifier='%s'" % identifier) 90 items.extend(_reprKwargs(kwargs)) 91 self._commands.append("addComponent(%s)" % ", ".join(items)) 92 93 94 class PointToSegmentPenTest(unittest.TestCase): 95 96 def test_open(self): 97 pen = _TestSegmentPen() 98 ppen = PointToSegmentPen(pen) 99 ppen.beginPath() 100 ppen.addPoint((10, 10), "move") 101 ppen.addPoint((10, 20), "line") 102 ppen.endPath() 103 self.assertEqual("10 10 moveto 10 20 lineto endpath", repr(pen)) 104 105 def test_closed(self): 106 pen = _TestSegmentPen() 107 ppen = PointToSegmentPen(pen) 108 ppen.beginPath() 109 ppen.addPoint((10, 10), "line") 110 ppen.addPoint((10, 20), "line") 111 ppen.addPoint((20, 20), "line") 112 ppen.endPath() 113 self.assertEqual("10 10 moveto 10 20 lineto 20 20 lineto closepath", repr(pen)) 114 115 def test_cubic(self): 116 pen = _TestSegmentPen() 117 ppen = PointToSegmentPen(pen) 118 ppen.beginPath() 119 ppen.addPoint((10, 10), "line") 120 ppen.addPoint((10, 20)) 121 ppen.addPoint((20, 20)) 122 ppen.addPoint((20, 40), "curve") 123 ppen.endPath() 124 self.assertEqual("10 10 moveto 10 20 20 20 20 40 curveto closepath", repr(pen)) 125 126 def test_quad(self): 127 pen = _TestSegmentPen() 128 ppen = PointToSegmentPen(pen) 129 ppen.beginPath(identifier='foo') 130 ppen.addPoint((10, 10), "line") 131 ppen.addPoint((10, 40)) 132 ppen.addPoint((40, 40)) 133 ppen.addPoint((10, 40), "qcurve") 134 ppen.endPath() 135 self.assertEqual("10 10 moveto 10 40 40 40 10 40 qcurveto closepath", repr(pen)) 136 137 def test_quad_onlyOffCurvePoints(self): 138 pen = _TestSegmentPen() 139 ppen = PointToSegmentPen(pen) 140 ppen.beginPath() 141 ppen.addPoint((10, 10)) 142 ppen.addPoint((10, 40)) 143 ppen.addPoint((40, 40)) 144 ppen.endPath() 145 self.assertEqual("10 10 10 40 40 40 None qcurveto closepath", repr(pen)) 146 147 def test_roundTrip1(self): 148 tpen = _TestPointPen() 149 ppen = PointToSegmentPen(SegmentToPointPen(tpen)) 150 ppen.beginPath() 151 ppen.addPoint((10, 10), "line") 152 ppen.addPoint((10, 20)) 153 ppen.addPoint((20, 20)) 154 ppen.addPoint((20, 40), "curve") 155 ppen.endPath() 156 self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') addPoint((10, 20)) " 157 "addPoint((20, 20)) addPoint((20, 40), segmentType='curve') endPath()", 158 repr(tpen)) 159 160 161 class TestSegmentToPointPen(unittest.TestCase): 162 163 def test_move(self): 164 tpen = _TestPointPen() 165 pen = SegmentToPointPen(tpen) 166 pen.moveTo((10, 10)) 167 pen.endPath() 168 self.assertEqual("beginPath() addPoint((10, 10), segmentType='move') endPath()", 169 repr(tpen)) 170 171 def test_poly(self): 172 tpen = _TestPointPen() 173 pen = SegmentToPointPen(tpen) 174 pen.moveTo((10, 10)) 175 pen.lineTo((10, 20)) 176 pen.lineTo((20, 20)) 177 pen.closePath() 178 self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') " 179 "addPoint((10, 20), segmentType='line') " 180 "addPoint((20, 20), segmentType='line') endPath()", 181 repr(tpen)) 182 183 def test_cubic(self): 184 tpen = _TestPointPen() 185 pen = SegmentToPointPen(tpen) 186 pen.moveTo((10, 10)) 187 pen.curveTo((10, 20), (20, 20), (20, 10)) 188 pen.closePath() 189 self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') " 190 "addPoint((10, 20)) addPoint((20, 20)) addPoint((20, 10), " 191 "segmentType='curve') endPath()", repr(tpen)) 192 193 def test_quad(self): 194 tpen = _TestPointPen() 195 pen = SegmentToPointPen(tpen) 196 pen.moveTo((10, 10)) 197 pen.qCurveTo((10, 20), (20, 20), (20, 10)) 198 pen.closePath() 199 self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') " 200 "addPoint((10, 20)) addPoint((20, 20)) " 201 "addPoint((20, 10), segmentType=qcurve) endPath()", 202 repr(tpen)) 203 204 def test_quad(self): 205 tpen = _TestPointPen() 206 pen = SegmentToPointPen(tpen) 207 pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None) 208 pen.closePath() 209 self.assertEqual("beginPath() addPoint((10, 20)) addPoint((20, 20)) " 210 "addPoint((20, 10)) addPoint((10, 10)) endPath()", 211 repr(tpen)) 212 213 def test_roundTrip1(self): 214 spen = _TestSegmentPen() 215 pen = SegmentToPointPen(PointToSegmentPen(spen)) 216 pen.moveTo((10, 10)) 217 pen.lineTo((10, 20)) 218 pen.lineTo((20, 20)) 219 pen.closePath() 220 self.assertEqual("10 10 moveto 10 20 lineto 20 20 lineto closepath", repr(spen)) 221 222 def test_roundTrip2(self): 223 spen = _TestSegmentPen() 224 pen = SegmentToPointPen(PointToSegmentPen(spen)) 225 pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None) 226 pen.closePath() 227 pen.addComponent('base', [1, 0, 0, 1, 0, 0]) 228 self.assertEqual("10 20 20 20 20 10 10 10 None qcurveto closepath " 229 "'base' [1, 0, 0, 1, 0, 0] addcomponent", 230 repr(spen)) 231 232 233 class TestGuessSmoothPointPen(unittest.TestCase): 234 235 def test_guessSmooth_exact(self): 236 tpen = _TestPointPen() 237 pen = GuessSmoothPointPen(tpen) 238 pen.beginPath(identifier="foo") 239 pen.addPoint((0, 100), segmentType="curve") 240 pen.addPoint((0, 200)) 241 pen.addPoint((400, 200), identifier='bar') 242 pen.addPoint((400, 100), segmentType="curve") 243 pen.addPoint((400, 0)) 244 pen.addPoint((0, 0)) 245 pen.endPath() 246 self.assertEqual("beginPath(identifier='foo') " 247 "addPoint((0, 100), segmentType='curve', smooth=True) " 248 "addPoint((0, 200)) addPoint((400, 200), identifier='bar') " 249 "addPoint((400, 100), segmentType='curve', smooth=True) " 250 "addPoint((400, 0)) addPoint((0, 0)) endPath()", 251 repr(tpen)) 252 253 def test_guessSmooth_almost(self): 254 tpen = _TestPointPen() 255 pen = GuessSmoothPointPen(tpen) 256 pen.beginPath() 257 pen.addPoint((0, 100), segmentType="curve") 258 pen.addPoint((1, 200)) 259 pen.addPoint((395, 200)) 260 pen.addPoint((400, 100), segmentType="curve") 261 pen.addPoint((400, 0)) 262 pen.addPoint((0, 0)) 263 pen.endPath() 264 self.assertEqual("beginPath() addPoint((0, 100), segmentType='curve', smooth=True) " 265 "addPoint((1, 200)) addPoint((395, 200)) " 266 "addPoint((400, 100), segmentType='curve', smooth=True) " 267 "addPoint((400, 0)) addPoint((0, 0)) endPath()", 268 repr(tpen)) 269 270 def test_guessSmooth_tangent(self): 271 tpen = _TestPointPen() 272 pen = GuessSmoothPointPen(tpen) 273 pen.beginPath() 274 pen.addPoint((0, 0), segmentType="move") 275 pen.addPoint((0, 100), segmentType="line") 276 pen.addPoint((3, 200)) 277 pen.addPoint((300, 200)) 278 pen.addPoint((400, 200), segmentType="curve") 279 pen.endPath() 280 self.assertEqual("beginPath() addPoint((0, 0), segmentType='move') " 281 "addPoint((0, 100), segmentType='line', smooth=True) " 282 "addPoint((3, 200)) addPoint((300, 200)) " 283 "addPoint((400, 200), segmentType='curve') endPath()", 284 repr(tpen)) 285 286 class TestReverseContourPointPen(unittest.TestCase): 287 288 def test_singlePoint(self): 289 tpen = _TestPointPen() 290 pen = ReverseContourPointPen(tpen) 291 pen.beginPath() 292 pen.addPoint((0, 0), segmentType="move") 293 pen.endPath() 294 self.assertEqual("beginPath() " 295 "addPoint((0, 0), segmentType='move') " 296 "endPath()", 297 repr(tpen)) 298 299 def test_line(self): 300 tpen = _TestPointPen() 301 pen = ReverseContourPointPen(tpen) 302 pen.beginPath() 303 pen.addPoint((0, 0), segmentType="move") 304 pen.addPoint((0, 100), segmentType="line") 305 pen.endPath() 306 self.assertEqual("beginPath() " 307 "addPoint((0, 100), segmentType='move') " 308 "addPoint((0, 0), segmentType='line') " 309 "endPath()", 310 repr(tpen)) 311 312 def test_triangle(self): 313 tpen = _TestPointPen() 314 pen = ReverseContourPointPen(tpen) 315 pen.beginPath() 316 pen.addPoint((0, 0), segmentType="line") 317 pen.addPoint((0, 100), segmentType="line") 318 pen.addPoint((100, 100), segmentType="line") 319 pen.endPath() 320 self.assertEqual("beginPath() " 321 "addPoint((0, 0), segmentType='line') " 322 "addPoint((100, 100), segmentType='line') " 323 "addPoint((0, 100), segmentType='line') " 324 "endPath()", 325 repr(tpen)) 326 327 def test_cubicOpen(self): 328 tpen = _TestPointPen() 329 pen = ReverseContourPointPen(tpen) 330 pen.beginPath() 331 pen.addPoint((0, 0), segmentType="move") 332 pen.addPoint((0, 100)) 333 pen.addPoint((100, 200)) 334 pen.addPoint((200, 200), segmentType="curve") 335 pen.endPath() 336 self.assertEqual("beginPath() " 337 "addPoint((200, 200), segmentType='move') " 338 "addPoint((100, 200)) " 339 "addPoint((0, 100)) " 340 "addPoint((0, 0), segmentType='curve') " 341 "endPath()", 342 repr(tpen)) 343 344 def test_quadOpen(self): 345 tpen = _TestPointPen() 346 pen = ReverseContourPointPen(tpen) 347 pen.beginPath() 348 pen.addPoint((0, 0), segmentType="move") 349 pen.addPoint((0, 100)) 350 pen.addPoint((100, 200)) 351 pen.addPoint((200, 200), segmentType="qcurve") 352 pen.endPath() 353 self.assertEqual("beginPath() " 354 "addPoint((200, 200), segmentType='move') " 355 "addPoint((100, 200)) " 356 "addPoint((0, 100)) " 357 "addPoint((0, 0), segmentType='qcurve') " 358 "endPath()", 359 repr(tpen)) 360 361 def test_cubicClosed(self): 362 tpen = _TestPointPen() 363 pen = ReverseContourPointPen(tpen) 364 pen.beginPath() 365 pen.addPoint((0, 0), segmentType="line") 366 pen.addPoint((0, 100)) 367 pen.addPoint((100, 200)) 368 pen.addPoint((200, 200), segmentType="curve") 369 pen.endPath() 370 self.assertEqual("beginPath() " 371 "addPoint((0, 0), segmentType='curve') " 372 "addPoint((200, 200), segmentType='line') " 373 "addPoint((100, 200)) " 374 "addPoint((0, 100)) " 375 "endPath()", 376 repr(tpen)) 377 378 def test_quadClosedOffCurveStart(self): 379 tpen = _TestPointPen() 380 pen = ReverseContourPointPen(tpen) 381 pen.beginPath() 382 pen.addPoint((100, 200)) 383 pen.addPoint((200, 200), segmentType="qcurve") 384 pen.addPoint((0, 0), segmentType="line") 385 pen.addPoint((0, 100)) 386 pen.endPath() 387 self.assertEqual("beginPath() " 388 "addPoint((100, 200)) " 389 "addPoint((0, 100)) " 390 "addPoint((0, 0), segmentType='qcurve') " 391 "addPoint((200, 200), segmentType='line') " 392 "endPath()", 393 repr(tpen)) 394 395 def test_quadNoOnCurve(self): 396 tpen = _TestPointPen() 397 pen = ReverseContourPointPen(tpen) 398 pen.beginPath(identifier='bar') 399 pen.addPoint((0, 0)) 400 pen.addPoint((0, 100), identifier='foo', arbitrary='foo') 401 pen.addPoint((100, 200), arbitrary=123) 402 pen.addPoint((200, 200)) 403 pen.endPath() 404 pen.addComponent("base", [1, 0, 0, 1, 0, 0], identifier='foo') 405 self.assertEqual("beginPath(identifier='bar') " 406 "addPoint((0, 0)) " 407 "addPoint((200, 200)) " 408 "addPoint((100, 200), arbitrary=123) " 409 "addPoint((0, 100), identifier='foo', arbitrary='foo') " 410 "endPath() " 411 "addComponent('base', [1, 0, 0, 1, 0, 0], identifier='foo')", 412 repr(tpen)) 413