| # Copyright 2016 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| from . import CUBIC_GLYPHS |
| from fontTools.pens.pointPen import PointToSegmentPen, SegmentToPointPen |
| from math import isclose |
| import unittest |
| |
| |
| class BaseDummyPen(object): |
| """Base class for pens that record the commands they are called with.""" |
| |
| def __init__(self, *args, **kwargs): |
| self.commands = [] |
| |
| def __str__(self): |
| """Return the pen commands as a string of python code.""" |
| return _repr_pen_commands(self.commands) |
| |
| def addComponent(self, glyphName, transformation, **kwargs): |
| self.commands.append(('addComponent', (glyphName, transformation), kwargs)) |
| |
| |
| class DummyPen(BaseDummyPen): |
| """A SegmentPen that records the commands it's called with.""" |
| |
| def moveTo(self, pt): |
| self.commands.append(('moveTo', (pt,), {})) |
| |
| def lineTo(self, pt): |
| self.commands.append(('lineTo', (pt,), {})) |
| |
| def curveTo(self, *points): |
| self.commands.append(('curveTo', points, {})) |
| |
| def qCurveTo(self, *points): |
| self.commands.append(('qCurveTo', points, {})) |
| |
| def closePath(self): |
| self.commands.append(('closePath', tuple(), {})) |
| |
| def endPath(self): |
| self.commands.append(('endPath', tuple(), {})) |
| |
| |
| class DummyPointPen(BaseDummyPen): |
| """A PointPen that records the commands it's called with.""" |
| |
| def beginPath(self, **kwargs): |
| self.commands.append(('beginPath', tuple(), kwargs)) |
| |
| def endPath(self): |
| self.commands.append(('endPath', tuple(), {})) |
| |
| def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): |
| kwargs['segmentType'] = str(segmentType) if segmentType else None |
| kwargs['smooth'] = smooth |
| kwargs['name'] = name |
| self.commands.append(('addPoint', (pt,), kwargs)) |
| |
| |
| class DummyGlyph(object): |
| """Provides a minimal interface for storing a glyph's outline data in a |
| SegmentPen-oriented way. The glyph's outline consists in the list of |
| SegmentPen commands required to draw it. |
| """ |
| |
| # the SegmentPen class used to draw on this glyph type |
| DrawingPen = DummyPen |
| |
| def __init__(self, glyph=None): |
| """If another glyph (i.e. any object having a 'draw' method) is given, |
| its outline data is copied to self. |
| """ |
| self._pen = self.DrawingPen() |
| self.outline = self._pen.commands |
| if glyph: |
| self.appendGlyph(glyph) |
| |
| def appendGlyph(self, glyph): |
| """Copy another glyph's outline onto self.""" |
| glyph.draw(self._pen) |
| |
| def getPen(self): |
| """Return the SegmentPen that can 'draw' on this glyph.""" |
| return self._pen |
| |
| def getPointPen(self): |
| """Return a PointPen adapter that can 'draw' on this glyph.""" |
| return PointToSegmentPen(self._pen) |
| |
| def draw(self, pen): |
| """Use another SegmentPen to replay the glyph's outline commands.""" |
| if self.outline: |
| for cmd, args, kwargs in self.outline: |
| getattr(pen, cmd)(*args, **kwargs) |
| |
| def drawPoints(self, pointPen): |
| """Use another PointPen to replay the glyph's outline commands, |
| indirectly through an adapter. |
| """ |
| pen = SegmentToPointPen(pointPen) |
| self.draw(pen) |
| |
| def __eq__(self, other): |
| """Return True if 'other' glyph's outline is the same as self.""" |
| if hasattr(other, 'outline'): |
| return self.outline == other.outline |
| elif hasattr(other, 'draw'): |
| return self.outline == self.__class__(other).outline |
| return NotImplemented |
| |
| def __ne__(self, other): |
| """Return True if 'other' glyph's outline is different from self.""" |
| return not (self == other) |
| |
| def approx(self, other, rel_tol=1e-12): |
| if hasattr(other, 'outline'): |
| outline2 == other.outline |
| elif hasattr(other, 'draw'): |
| outline2 = self.__class__(other).outline |
| else: |
| raise TypeError(type(other).__name__) |
| outline1 = self.outline |
| if len(outline1) != len(outline2): |
| return False |
| for (cmd1, arg1, kwd1), (cmd2, arg2, kwd2) in zip(outline1, outline2): |
| if cmd1 != cmd2: |
| return False |
| if kwd1 != kwd2: |
| return False |
| if arg1: |
| if isinstance(arg1[0], tuple): |
| if not arg2 or not isinstance(arg2[0], tuple): |
| return False |
| for (x1, y1), (x2, y2) in zip(arg1, arg2): |
| if ( |
| not isclose(x1, x2, rel_tol=rel_tol) or |
| not isclose(y1, y2, rel_tol=rel_tol) |
| ): |
| return False |
| elif arg1 != arg2: |
| return False |
| elif arg2: |
| return False |
| return True |
| |
| def __str__(self): |
| """Return commands making up the glyph's outline as a string.""" |
| return str(self._pen) |
| |
| |
| class DummyPointGlyph(DummyGlyph): |
| """Provides a minimal interface for storing a glyph's outline data in a |
| PointPen-oriented way. The glyph's outline consists in the list of |
| PointPen commands required to draw it. |
| """ |
| |
| # the PointPen class used to draw on this glyph type |
| DrawingPen = DummyPointPen |
| |
| def appendGlyph(self, glyph): |
| """Copy another glyph's outline onto self.""" |
| glyph.drawPoints(self._pen) |
| |
| def getPen(self): |
| """Return a SegmentPen adapter that can 'draw' on this glyph.""" |
| return SegmentToPointPen(self._pen) |
| |
| def getPointPen(self): |
| """Return the PointPen that can 'draw' on this glyph.""" |
| return self._pen |
| |
| def draw(self, pen): |
| """Use another SegmentPen to replay the glyph's outline commands, |
| indirectly through an adapter. |
| """ |
| pointPen = PointToSegmentPen(pen) |
| self.drawPoints(pointPen) |
| |
| def drawPoints(self, pointPen): |
| """Use another PointPen to replay the glyph's outline commands.""" |
| if self.outline: |
| for cmd, args, kwargs in self.outline: |
| getattr(pointPen, cmd)(*args, **kwargs) |
| |
| |
| def _repr_pen_commands(commands): |
| """ |
| >>> print(_repr_pen_commands([ |
| ... ('moveTo', tuple(), {}), |
| ... ('lineTo', ((1.0, 0.1),), {}), |
| ... ('curveTo', ((1.0, 0.1), (2.0, 0.2), (3.0, 0.3)), {}) |
| ... ])) |
| pen.moveTo() |
| pen.lineTo((1, 0.1)) |
| pen.curveTo((1, 0.1), (2, 0.2), (3, 0.3)) |
| |
| >>> print(_repr_pen_commands([ |
| ... ('beginPath', tuple(), {}), |
| ... ('addPoint', ((1.0, 0.1),), |
| ... {"segmentType":"line", "smooth":True, "name":"test", "z":1}), |
| ... ])) |
| pen.beginPath() |
| pen.addPoint((1, 0.1), name='test', segmentType='line', smooth=True, z=1) |
| |
| >>> print(_repr_pen_commands([ |
| ... ('addComponent', ('A', (1, 0, 0, 1, 0, 0)), {}) |
| ... ])) |
| pen.addComponent('A', (1, 0, 0, 1, 0, 0)) |
| """ |
| s = [] |
| for cmd, args, kwargs in commands: |
| if args: |
| if isinstance(args[0], tuple): |
| # cast float to int if there're no digits after decimal point, |
| # and round floats to 12 decimal digits (more than enough) |
| args = [ |
| tuple((int(v) if int(v) == v else round(v, 12)) for v in pt) |
| for pt in args |
| ] |
| args = ", ".join(repr(a) for a in args) |
| if kwargs: |
| kwargs = ", ".join("%s=%r" % (k, v) |
| for k, v in sorted(kwargs.items())) |
| if args and kwargs: |
| s.append("pen.%s(%s, %s)" % (cmd, args, kwargs)) |
| elif args: |
| s.append("pen.%s(%s)" % (cmd, args)) |
| elif kwargs: |
| s.append("pen.%s(%s)" % (cmd, kwargs)) |
| else: |
| s.append("pen.%s()" % cmd) |
| return "\n".join(s) |
| |
| |
| class TestDummyGlyph(unittest.TestCase): |
| |
| def test_equal(self): |
| # verify that the copy and the copy of the copy are equal to |
| # the source glyph's outline, as well as to each other |
| source = CUBIC_GLYPHS['a'] |
| copy = DummyGlyph(source) |
| copy2 = DummyGlyph(copy) |
| self.assertEqual(source, copy) |
| self.assertEqual(source, copy2) |
| self.assertEqual(copy, copy2) |
| # assert equality doesn't hold any more after modification |
| copy.outline.pop() |
| self.assertNotEqual(source, copy) |
| self.assertNotEqual(copy, copy2) |
| |
| |
| class TestDummyPointGlyph(unittest.TestCase): |
| |
| def test_equal(self): |
| # same as above but using the PointPen protocol |
| source = CUBIC_GLYPHS['a'] |
| copy = DummyPointGlyph(source) |
| copy2 = DummyPointGlyph(copy) |
| self.assertEqual(source, copy) |
| self.assertEqual(source, copy2) |
| self.assertEqual(copy, copy2) |
| copy.outline.pop() |
| self.assertNotEqual(source, copy) |
| self.assertNotEqual(copy, copy2) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |