Exemple #1
0
class BezierPenTool(object):
    """
    The Pen Tool used to Draw Bezier Curves inside a class:`~editfonts.widgets.glyph_box`.

    Current Status
    ~~~~~~~~~~~~~~~~~
    This tool is not complete and can only be used to draw contours with lines
    to activate the Bezier Pen Tool right click on the drawing area on the editor page and
    click any number of times to add a point to a new contour
    We can close those contours by clicking inside the halo around the starting point of the contour

    To be Implemented
    ~~~~~~~~~~~~~~~~~
    Add the Functionality to make Bezier Curves
    This can be done by monitoring the **drag event** on the Drawing Area inside a **GlyphBox**
    to implement the above features the following open source code file(s) may be usefull
    + .. drawingarea.py : https://github.com/GNOME/pygobject/blob/master/demos/gtk-demo/demos/drawingarea.py
    """
    def __init__(self, editorBox):
        self.editor = editorBox
        self.set_active(True)

    def set_active(self, state):
        """Activate/Deactivate the Bezier Pen Tool"""
        if state is True:
            self.is_active = True
            self.contour = Contour()
            self.contour.dirty = True
            self.editor.add_contour(self.contour)
            self.connect_editor()
        else:
            self.contour.dirty = False
            self.is_active = False
            self.disconnect_editor()

        globals.TOOL_ACTIVE['BezierPenTool'] = state

    def get_active(self):
        """
        Get the state of the Bezier Pen Tool
        True ~ is active
        False ~ is inactive
        """
        return self.is_active

    def disconnect_editor(self):
        """Disable Drawing with the Bezier Pen Tool"""
        self.editor.disconnect(self.handle_press)
        # self.editor.disconnect(self.handle_release)

    def connect_editor(self):
        """Enable Drawing with the Bezier Pen Tool"""
        # add all the events here which the pen tool needs to listen to
        self.handle_press = self.editor.connect("button-press-event",
                                                self._on_point_press)
        # self.handle_release = self.editor.connect("button-release-event",
        #                                           self._on_point_release)

    def _on_point_press(self, widget, event):
        """Enable Drawing with the Bezier Pen Tool"""
        if event.type == Gdk.EventType.BUTTON_PRESS\
                and event.button == 1:
            # print "Clicked on: (" + str(event.x) + ", " + str(event.y) + ")"
            # add point at the click locations
            # print self.get_active()
            if self.get_active():
                self.add_point(event.x, event.y)
            else:
                # print "This shouldn't be happening"
                pass

    def _on_point_release(self, widget, event):
        if event.type == Gdk.EventType.BUTTON_RELEASE\
                and event.button == 1:
            # print "Released on: (" + str(event.x) + ", " + str(event.y) + ")"
            # add point at the click locations
            """
            if self.get_active():
                self.add_point(event.x, event.y)
            else:
                print "This shouldn't be happening"
            """

    def add_point(self, x, y):
        """Add a Point to the currently active contour"""
        # print "Yahoo"
        point = Point((globals.invX(x), globals.invY(y)))
        # print "{" + str(point.x) + "," + str(point.y) + "}"
        # point.x = globals.invX(x)
        # point.y = globals.invY(y)

        if len(self.contour[:]) == 0:
            # add the point to the contour
            point.segmentType = u'move'
            point.smooth = False

            self.contour.appendPoint(point)

        elif self._check_close_contour(point):
            # convert the first point to a line type
            # print "the contour should be closed now"

            # close the contour
            self.contour[0].segmentType = u'line'

            # deactivate the bezier pen tool
            self.set_active(False)

            # No need for this
            # self.contour[0].segmentType = u'line'

        else:
            # add the point to the contour
            point.segmentType = u'line'
            point.smooth = False

            self.contour.appendPoint(point)

        self.editor.update_control_points()

    # check if the click is inside a zone defined by the
    # ZONE_R of the first point of the contour
    def _check_close_contour(self, point):
        """
        Check if the last click was within a certain distance **globals.ZONE_R** around
        the starting point of the contour
        """
        if len(self.contour[:]) == 0:
            return False

        t = distance(self.contour[0].x, self.contour[0].y, point.x, point.y)
        if t <= globals.ZONE_R:
            return True
        return False
class ContourTest(unittest.TestCase):

    def __init__(self, methodName):
        unittest.TestCase.__init__(self, methodName)

    def setUp(self):
        self.font = Font()
        self.glyph = self.font.newGlyph("A")
        self.contour = Contour()

    def tearDown(self):
        del self.contour
        del self.glyph
        del self.font

    def test_getParent(self):
        self.assertIsNone(self.contour.getParent())
        self.contour = Contour(self.glyph)
        self.assertEqual(self.contour.getParent(), self.glyph)

    def test_font(self):
        self.assertIsNone(self.contour.font)
        self.contour = Contour(self.glyph)
        self.assertEqual(self.contour.font, self.font)
        with self.assertRaises(AttributeError):
            self.contour.font = "foo"

    def test_layerSet(self):
        self.assertIsNone(self.contour.layerSet)
        self.contour = Contour(self.glyph)
        self.assertIsNotNone(self.contour.layerSet)
        self.assertEqual(self.contour.layerSet, self.glyph.layerSet)
        with self.assertRaises(AttributeError):
            self.contour.layerSet = "foo"

    def test_layer(self):
        self.assertIsNone(self.contour.layer)
        self.contour = Contour(self.glyph)
        self.assertIsNotNone(self.contour.layer)
        self.assertEqual(self.contour.layer, self.glyph.layer)
        with self.assertRaises(AttributeError):
            self.contour.layer = "foo"

    def test_glyph(self):
        self.assertIsNone(self.contour.glyph)
        self.contour = Contour(self.glyph)
        self.assertEqual(self.contour.glyph, self.glyph)
        glyph = Glyph()
        self.contour = Contour()
        self.contour.glyph = glyph
        self.assertEqual(self.contour.glyph, glyph)
        with self.assertRaises(AssertionError):
            self.contour.glyph = self.glyph

    def test_list_behavior(self):
        font = Font(getTestFontPath())
        glyph = font["A"]
        contour = glyph[0]
        with self.assertRaises(IndexError):
            contour[len(contour) + 1]
        self.assertEqual(contour[len(contour):len(contour)], [])
        self.assertEqual(contour[len(contour) + 1:len(contour) + 2], [])
        self.assertNotEqual(contour[0:len(contour) + 1], [])
        self.assertEqual([(point.x, point.y) for point in contour[0:]],
                         [(point.x, point.y)
                          for point in contour[0:len(contour) + 1]])
        self.assertEqual([(point.x, point.y) for point in contour[0:]],
                         [(0, 0), (700, 0), (700, 700), (0, 700)])
        self.assertEqual([(point.x, point.y) for point in contour],
                         [(0, 0), (700, 0), (700, 700), (0, 700)])

    def test_onCurvePoints(self):
        font = Font(getTestFontPath())
        glyph = font["A"]
        contour = glyph[0]
        self.assertEqual(len(contour.onCurvePoints), 4)
        self.assertEqual([(point.x, point.y)
                          for point in contour.onCurvePoints],
                         [(0, 0), (700, 0), (700, 700), (0, 700)])
        glyph = font["B"]
        contour = glyph[0]
        self.assertEqual(len(contour.onCurvePoints), 4)
        self.assertEqual([(point.x, point.y)
                          for point in contour.onCurvePoints],
                         [(0, 350), (350, 0), (700, 350), (350, 700)])

    def test_appendPoint(self):
        pointA = Point((0, 5))
        self.assertFalse(self.contour.dirty)
        self.contour.appendPoint(pointA)
        self.assertTrue(self.contour.dirty)
        self.assertEqual([(point.x, point.y)
                          for point in self.contour],
                         [(0, 5)])
        pointB = Point((6, 7))
        self.contour.appendPoint(pointB)
        self.assertEqual([(point.x, point.y)
                          for point in self.contour],
                         [(0, 5), (6, 7)])

    def test_insertPoint(self):
        pointA = Point((0, 5))
        pointB = Point((6, 7))
        pointC = Point((8, 9))
        self.assertFalse(self.contour.dirty)
        self.contour.insertPoint(0, pointA)
        self.assertTrue(self.contour.dirty)
        self.assertEqual([(point.x, point.y)
                          for point in self.contour],
                         [(0, 5)])
        self.contour.insertPoint(0, pointB)
        self.assertEqual([(point.x, point.y)
                          for point in self.contour],
                         [(6, 7), (0, 5)])
        self.contour.insertPoint(1, pointC)
        self.assertEqual([(point.x, point.y)
                          for point in self.contour],
                         [(6, 7), (8, 9), (0, 5)])

    def test_removePoint(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertFalse(contour.dirty)
        contour.removePoint(contour[1])
        self.assertTrue(contour.dirty)
        self.assertEqual([(point.x, point.y)
                          for point in contour],
                         [(0, 0), (700, 700), (0, 700)])
        contour.removePoint(contour[0])
        self.assertEqual([(point.x, point.y)
                          for point in contour],
                         [(700, 700), (0, 700)])
        contour.removePoint(contour[1])
        self.assertEqual([(point.x, point.y)
                          for point in contour],
                         [(700, 700)])

    def test_setStartPoint(self):
        font = Font(getTestFontPath())
        contour = font["B"][0]
        start = [(point.segmentType, point.x, point.y) for point in contour]
        contour.setStartPoint(6)
        self.assertTrue(contour.dirty)
        contour.setStartPoint(6)
        end = [(point.segmentType, point.x, point.y) for point in contour]
        self.assertEqual(start, end)
        contour = font["A"][0]
        start = [(point.segmentType, point.x, point.y) for point in contour]
        contour.setStartPoint(2)
        contour.setStartPoint(2)
        end = [(point.segmentType, point.x, point.y) for point in contour]
        self.assertEqual(start, end)
        contour = font["B"][0]
        start = [(point.segmentType, point.x, point.y) for point in contour]
        contour.setStartPoint(3)
        contour.setStartPoint(9)
        end = [(point.segmentType, point.x, point.y) for point in contour]
        self.assertEqual(start, end)

    def test_len(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertEqual(len(contour), 4)
        contour = font["B"][0]
        self.assertEqual(len(contour), 12)

    def test_iter(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertEqual([(point.x, point.y) for point in contour],
                         [(0, 0), (700, 0), (700, 700), (0, 700)])

    def test_index(self):
        font = Font(getTestFontPath())
        contour = font["B"][0]
        self.assertEqual(contour.index(contour[2]), 2)

    def test_reverse(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        contour.reverse()
        self.assertEqual([(point.x, point.y) for point in contour._points],
                         [(0, 0), (0, 700), (700, 700), (700, 0)])
        contour.reverse()
        self.assertEqual([(point.x, point.y) for point in contour._points],
                         [(0, 0), (700, 0), (700, 700), (0, 700)])

    def test_segments(self):
        font = Font(getTestFontPath())
        glyph = font["A"]
        contour = glyph[0]
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(700, 0, "line")], [(700, 700, "line")],
             [(0, 700, "line")], [(0, 0, "line")]])
        glyph = font["B"]
        contour = glyph[0]
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(0, 157, None), (157, 0, None), (350, 0, "curve")],
             [(543, 0, None), (700, 157, None), (700, 350, "curve")],
             [(700, 543, None), (543, 700, None), (350, 700, "curve")],
             [(157, 700, None), (0, 543, None), (0, 350, "curve")]])

    def test_removeSegment_lines(self):
        font = Font(getTestFontPath())
        glyph = font["A"]
        contour = glyph[0]
        contour.removeSegment(len(contour.segments) - 2)
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(700, 0, "line")], [(700, 700, "line")],
             [(0, 0, "line")]])
        contour.removeSegment(len(contour.segments) - 1)
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(700, 700, "line")], [(700, 0, "line")]])

    def test_removeSegment_first_segment(self):
        font = Font(getTestFontPath())
        glyph = font["A"]
        contour = glyph[0]
        contour.removeSegment(0)
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(700, 700, "line")], [(0, 700, "line")],
             [(0, 0, "line")]])

    def test_removeSegment_last_segment(self):
        font = Font(getTestFontPath())
        glyph = font["A"]
        contour = glyph[0]
        contour.removeSegment(len(contour.segments) - 1)
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(700, 700, "line")], [(0, 700, "line")],
             [(700, 0, "line")]])

    def test_removeSegment_curves(self):
        font = Font(getTestFontPath())
        glyph = font["B"]
        contour = glyph[0]
        contour.removeSegment(len(contour.segments) - 2)
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(0, 157, None), (157, 0, None), (350, 0, "curve")],
             [(543, 0, None), (700, 157, None), (700, 350, "curve")],
             [(157, 700, None), (0, 543, None), (0, 350, "curve")]])

    def test_removeSegment_curves_preserveCurve(self):
        font = Font(getTestFontPath())
        glyph = font["B"]
        contour = glyph[0]
        contour.removeSegment(len(contour.segments) - 2, preserveCurve=True)
        self.assertEqual(
            [simpleSegment(segment) for segment in contour.segments],
            [[(0, 157, None), (157, 0, None), (350, 0, "curve")],
             [(543, 0, None), (700, 157, None), (700, 350, "curve")],
             [(700.0, 736.0, None), (0.0, 736.0, None), (0, 350, "curve")]])

    def test_clockwise_get(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertFalse(contour.clockwise)
        contour = font["A"][1]
        self.assertTrue(contour.clockwise)
        contour._clockwiseCache = None
        contour.clockwise = False
        self.assertFalse(contour.clockwise)
        contour._clockwiseCache = None
        contour.clockwise = True
        self.assertTrue(contour.clockwise)

    def test_clockwise_set(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        contour.clockwise = False
        self.assertFalse(contour.clockwise)
        contour._clockwiseCache = None
        contour.clockwise = True
        self.assertTrue(contour.clockwise)

    def test_open(self):
        font = Font(getTestFontPath("TestOpenContour.ufo"))
        glyph = font["A"]
        self.assertTrue(glyph[0].open)
        self.assertFalse(glyph[1].open)
        self.assertTrue(glyph[2].open)
        self.assertFalse(glyph[3].open)
        contour = Contour()
        self.assertTrue(contour.open)

    def test_bounds(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertEqual(contour.bounds, (0, 0, 700, 700))

    def test_controlPointBounds(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertEqual(contour.controlPointBounds, (0, 0, 700, 700))

    def test_move(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        contour.move((100, 100))
        self.assertEqual(contour.bounds, (100, 100, 800, 800))
        self.assertTrue(contour.dirty)

    def test_pointInside(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertTrue(contour.pointInside((100, 100)))
        self.assertFalse(contour.pointInside((0, 0)))
        self.assertFalse(contour.pointInside((-100, -100)))

    def test_positionForProspectivePointInsertionAtSegmentAndT(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        self.assertEqual(
            contour.positionForProspectivePointInsertionAtSegmentAndT(0, .5),
            ((350.0, 0.0), False))
        contour = font["B"][0]
        self.assertEqual(
            contour.positionForProspectivePointInsertionAtSegmentAndT(0, .5),
            ((102.625, 102.625), True))
        contour = font["B"][1]
        self.assertEqual(
            contour.positionForProspectivePointInsertionAtSegmentAndT(0, .5),
            ((226.125, 473.5), True))

    def test_splitAndInsertPointAtSegmentAndT(self):
        font = Font(getTestFontPath())
        contour = font["A"][0]
        contour.splitAndInsertPointAtSegmentAndT(0, .5)
        self.assertEqual(
            [(point.x, point.y, point.segmentType) for point in contour],
            [(0, 0, "line"), (350.0, 0.0, "line"), (700, 0, "line"),
             (700, 700, "line"), (0, 700, "line")])
        contour = font["B"][0]
        contour.splitAndInsertPointAtSegmentAndT(0, .5)
        self.assertEqual(
            [(point.x, point.y, point.segmentType) for point in contour],
            [(0, 350, "curve"), (0.0, 253.5, None), (39.25, 166.0, None),
             (102.625, 102.625, "curve"), (166.0, 39.25, None),
             (253.5, 0.0, None), (350, 0, "curve"), (543, 0, None),
             (700, 157, None), (700, 350, "curve"), (700, 543, None),
             (543, 700, None), (350, 700, "curve"), (157, 700, None),
             (0, 543, None)])

    def test_identifier(self):
        glyph = Glyph()
        contour = Contour()
        glyph.appendContour(contour)
        contour.identifier = "contour 1"
        self.assertEqual(contour.identifier, "contour 1")
        self.assertEqual(sorted(glyph.identifiers), ["contour 1"])
        contour = Contour()
        glyph.appendContour(contour)
        with self.assertRaises(AssertionError):
            contour.identifier = "contour 1"
        contour.identifier = "contour 2"
        self.assertEqual(sorted(glyph.identifiers), ["contour 1", "contour 2"])
        contour.identifier = "not contour 2 anymore"
        self.assertEqual(contour.identifier, "contour 2")
        self.assertEqual(sorted(glyph.identifiers), ["contour 1", "contour 2"])
        contour.identifier = None
        self.assertEqual(contour.identifier, "contour 2")
        self.assertEqual(sorted(glyph.identifiers), ["contour 1", "contour 2"])
    
    def test_correct_direction_same_area(self):
        glyph = Glyph()
        pen = glyph.getPointPen()
        pen.beginPath()
        pen.addPoint((0, 0), segmentType="line")
        pen.addPoint((0, 50), segmentType="line")
        pen.addPoint((50, 50), segmentType="line")
        pen.endPath()
        pen.beginPath()
        pen.addPoint((50, 50), segmentType="line")
        pen.addPoint((50, 100), segmentType="line")
        pen.addPoint((100, 100), segmentType="line")
        pen.endPath()
        try:
            glyph.correctContourDirection()
        except Exception as e:
            self.fail("glyph.correctContourDirection() raised unexpected exception: "
                      + str(e))
class BezierPenTool(object):
    """
    The Pen Tool used to Draw Bezier Curves inside a class:`~editfonts.widgets.glyph_box`.

    Current Status
    ~~~~~~~~~~~~~~~~~
    This tool is not complete and can only be used to draw contours with lines
    to activate the Bezier Pen Tool right click on the drawing area on the editor page and
    click any number of times to add a point to a new contour
    We can close those contours by clicking inside the halo around the starting point of the contour

    To be Implemented
    ~~~~~~~~~~~~~~~~~
    Add the Functionality to make Bezier Curves
    This can be done by monitoring the **drag event** on the Drawing Area inside a **GlyphBox**
    to implement the above features the following open source code file(s) may be usefull
    + .. drawingarea.py : https://github.com/GNOME/pygobject/blob/master/demos/gtk-demo/demos/drawingarea.py
    """

    def __init__(self, editorBox):
        self.editor = editorBox
        self.set_active(True)

    def set_active(self, state):
        """Activate/Deactivate the Bezier Pen Tool"""
        if state is True:
            self.is_active = True
            self.contour = Contour()
            self.contour.dirty = True
            self.editor.add_contour(self.contour)
            self.connect_editor()
        else:
            self.contour.dirty = False
            self.is_active = False
            self.disconnect_editor()

        globals.TOOL_ACTIVE['BezierPenTool'] = state

    def get_active(self):
        """
        Get the state of the Bezier Pen Tool
        True ~ is active
        False ~ is inactive
        """
        return self.is_active

    def disconnect_editor(self):
        """Disable Drawing with the Bezier Pen Tool"""
        self.editor.disconnect(self.handle_press)
        # self.editor.disconnect(self.handle_release)

    def connect_editor(self):
        """Enable Drawing with the Bezier Pen Tool"""
        # add all the events here which the pen tool needs to listen to
        self.handle_press = self.editor.connect("button-press-event",
                                                self._on_point_press)
        # self.handle_release = self.editor.connect("button-release-event",
        #                                           self._on_point_release)

    def _on_point_press(self, widget, event):
        """Enable Drawing with the Bezier Pen Tool"""
        if event.type == Gdk.EventType.BUTTON_PRESS\
                and event.button == 1:
            # print "Clicked on: (" + str(event.x) + ", " + str(event.y) + ")"
            # add point at the click locations
            # print self.get_active()
            if self.get_active():
                self.add_point(event.x, event.y)
            else:
                # print "This shouldn't be happening"
                pass

    def _on_point_release(self, widget, event):
        if event.type == Gdk.EventType.BUTTON_RELEASE\
                and event.button == 1:
            # print "Released on: (" + str(event.x) + ", " + str(event.y) + ")"
            # add point at the click locations
            """
            if self.get_active():
                self.add_point(event.x, event.y)
            else:
                print "This shouldn't be happening"
            """

    def add_point(self, x, y):
        """Add a Point to the currently active contour"""
        # print "Yahoo"
        point = Point((globals.invX(x), globals.invY(y)))
        # print "{" + str(point.x) + "," + str(point.y) + "}"
        # point.x = globals.invX(x)
        # point.y = globals.invY(y)

        if len(self.contour[:]) == 0:
            # add the point to the contour
            point.segmentType = u'move'
            point.smooth = False

            self.contour.appendPoint(point)

        elif self._check_close_contour(point):
            # convert the first point to a line type
            # print "the contour should be closed now"

            # close the contour
            self.contour[0].segmentType = u'line'

            # deactivate the bezier pen tool
            self.set_active(False)

            # No need for this
            # self.contour[0].segmentType = u'line'

        else:
            # add the point to the contour
            point.segmentType = u'line'
            point.smooth = False

            self.contour.appendPoint(point)

        self.editor.update_control_points()

    # check if the click is inside a zone defined by the
    # ZONE_R of the first point of the contour
    def _check_close_contour(self, point):
        """
        Check if the last click was within a certain distance **globals.ZONE_R** around
        the starting point of the contour
        """
        if len(self.contour[:]) == 0:
            return False

        t = distance(self.contour[0].x, self.contour[0].y, point.x, point.y)
        if t <= globals.ZONE_R:
            return True
        return False
Exemple #4
0
def create_contour(contour_path):
    contour = Contour()
    for point in map(create_point, contour_path):
        contour.appendPoint(point)
    return contour