def __init__(self, other_point_pen, max_err, reverse_direction=False, stats=None): BasePointToSegmentPen.__init__(self) if reverse_direction: self.pen = ReverseContourPointPen(other_point_pen) else: self.pen = other_point_pen self.max_err = max_err self.stats = stats
def drawPoints(self, pointPen): if self.drawInner: reversePen = ReverseContourPointPen(pointPen) self.innerGlyph.drawPoints(CleanPointPen(reversePen)) if self.drawOuter: self.outerGlyph.drawPoints(CleanPointPen(pointPen)) if self.drawOriginal: if self.drawOuter: pointPen = ReverseContourPointPen(pointPen) self.originalGlyph.drawPoints(CleanPointPen(pointPen)) for glyphName, transform in self.components: pointPen.addComponent(glyphName, transform)
def reverse(self): """ Reverse the direction of the contour. It's important to note that the actual points stored in this object will be completely repalced by new points. This will post *Contour.WindingDirectionChanged*, *Contour.PointsChanged* and *Contour.Changed* notifications. """ from ufoLib.pointPen import ReverseContourPointPen oldDirection = self.clockwise # put the current points in another contour otherContour = self.__class__(glyph=None, pointClass=self.pointClass) # draw the points in this contour through # the reversing pen. reversePen = ReverseContourPointPen(otherContour) self.drawPoints(reversePen) # clear the points in this contour self._clear(postNotification=False) # set the points back into this contour self._points = otherContour._points # post a notification self.postNotification("Contour.WindingDirectionChanged", data=dict(oldValue=oldDirection, newValue=self.clockwise)) self.postNotification("Contour.PointsChanged") self.dirty = True
def test_reverse_point_pen(contour, expected): try: from ufoLib.pointPen import (ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen) except ImportError: pytest.skip("ufoLib not installed") recpen = RecordingPen() pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine=True) revpen = ReverseContourPointPen(pt2seg) seg2pt = SegmentToPointPen(revpen) for operator, operands in contour: getattr(seg2pt, operator)(*operands) # for closed contours that have a lineTo following the moveTo, # and whose points don't overlap, our current implementation diverges # from the ReverseContourPointPen as wrapped by ufoLib's pen converters. # In the latter case, an extra lineTo is added because of # outputImpliedClosingLine=True. This is redundant but not incorrect, # as the number of points is the same in both. if (contour and contour[-1][0] == "closePath" and contour[1][0] == "lineTo" and contour[1][1] != contour[0][1]): expected = expected[:-1] + [("lineTo", contour[0][1])] + expected[-1:] assert recpen.value == expected
def test_reverse_point_pen(contour, expected): try: from ufoLib.pointPen import (ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen) except ImportError: pytest.skip("ufoLib not installed") recpen = RecordingPen() pt2seg = PointToSegmentPen(recpen) revpen = ReverseContourPointPen(pt2seg) seg2pt = SegmentToPointPen(revpen) for operator, operands in contour: getattr(seg2pt, operator)(*operands) assert recpen.value == expected
def convert(glyph, maxDistance, minLength, useArcLength): originalNumPoints = 0 nbPoints = 0 conts = [] for contour in glyph: conts.append([]) cmds = conts[-1] p0 = getFirstOnPoint(contour) nseg = len(contour) for s in range(nseg): seg = contour[s] if seg.type == 'line': originalNumPoints += 1 p1 = seg.points[0] cmds.append((lineto, p1)) nbPoints += 1 p0 = p1 elif seg.type == 'qcurve': #print("Should not have quadratic segment in here. Skipping.",) p0 = seg.points[-1] elif seg.type == 'curve': originalNumPoints += 3 p1, p2, p3 = seg.points pt0 = Point(p0.x, p0.y) pt1 = Point(p1.x, p1.y) pt2 = Point(p2.x, p2.y) pt3 = Point(p3.x, p3.y) qsegs = [] inputCubic = (pt0, pt1, pt2, pt3) for cubic in splitCubicOnInflection(inputCubic, minLength): qsegs = qsegs + adaptiveSmoothCubicSplit( cubic, maxDistance, minLength, useArcLength) nbQSegMinusOne = len(qsegs) - 1 smooth = True for i, qseg in enumerate(qsegs): # We have to split the quad segment because Robofont does not (seem to) support # ON-OFF-ON quadratic bezier curves. If ever Robofont can handle this, # then it would suffice to write something like: # (a0, a1, a2) = qseg # cmds.append((curveto, (a1, a2))) if i == nbQSegMinusOne: smooth = seg.smooth q1, q2 = qseg cmds.append((curveto, (q1[1], q2[1], q2[2], smooth))) #ql, qr = splitQuadratic(0.5, qseg) #cmds.append((curveto, (ql[1], qr[1], qr[2], smooth))) nbPoints += 3 p0 = p3 else: #print("Unknown segment type: "+seg.type+". Skipping.",) p0 = seg.points[-1] glyph.clearContours() glyph.preferredSegmentStyle = 'qcurve' pen = ReverseContourPointPen(glyph.getPointPen()) for cmds in conts: if cmds == []: continue pen.beginPath() for action, args in cmds: action(pen, args) pen.endPath() # Now, we make sure that each contour starts with a ON control point for contour in glyph: contour.setStartSegment(0) glyph.changed() return originalNumPoints, nbPoints
def __init__(self, other_pen): adapter_point_pen = PointToSegmentPen(other_pen) reverse_point_pen = ReverseContourPointPen(adapter_point_pen) SegmentToPointPen.__init__(self, reverse_point_pen)
class Cu2QuPointPen(BasePointToSegmentPen): """ A filter pen to convert cubic bezier curves to quadratic b-splines using the RoboFab PointPen protocol. other_point_pen: another PointPen used to draw the transformed outline. max_err: maximum approximation error in font units. reverse_direction: reverse the winding direction of all contours. stats: a dictionary counting the point numbers of quadratic segments. """ def __init__(self, other_point_pen, max_err, reverse_direction=False, stats=None): BasePointToSegmentPen.__init__(self) if reverse_direction: self.pen = ReverseContourPointPen(other_point_pen) else: self.pen = other_point_pen self.max_err = max_err self.stats = stats def _flushContour(self, segments): assert len(segments) >= 1 closed = segments[0][0] != "move" new_segments = [] prev_points = segments[-1][1] prev_on_curve = prev_points[-1][0] for segment_type, points in segments: if segment_type == 'curve': for sub_points in self._split_super_bezier_segments(points): on_curve, smooth, name, kwargs = sub_points[-1] bcp1, bcp2 = sub_points[0][0], sub_points[1][0] cubic = [prev_on_curve, bcp1, bcp2, on_curve] quad = curve_to_quadratic(cubic, self.max_err) if self.stats is not None: n = str(len(quad)) self.stats[n] = self.stats.get(n, 0) + 1 new_points = [(pt, False, None, {}) for pt in quad[1:-1]] new_points.append((on_curve, smooth, name, kwargs)) new_segments.append(["qcurve", new_points]) prev_on_curve = sub_points[-1][0] else: new_segments.append([segment_type, points]) prev_on_curve = points[-1][0] if closed: # the BasePointToSegmentPen.endPath method that calls _flushContour # rotates the point list of closed contours so that they end with # the first on-curve point. We restore the original starting point. new_segments = new_segments[-1:] + new_segments[:-1] self._drawPoints(new_segments) def _split_super_bezier_segments(self, points): sub_segments = [] # n is the number of control points n = len(points) - 1 if n == 2: # a simple bezier curve segment sub_segments.append(points) elif n > 2: # a "super" bezier; decompose it on_curve, smooth, name, kwargs = points[-1] num_sub_segments = n - 1 for i, sub_points in enumerate( decomposeSuperBezierSegment([pt for pt, _, _, _ in points])): new_segment = [] for point in sub_points[:-1]: new_segment.append((point, False, None, {})) if i == (num_sub_segments - 1): # the last on-curve keeps its original attributes new_segment.append((on_curve, smooth, name, kwargs)) else: # on-curves of sub-segments are always "smooth" new_segment.append((sub_points[-1], True, None, {})) sub_segments.append(new_segment) else: raise AssertionError("expected 2 control points, found: %d" % n) return sub_segments def _drawPoints(self, segments): pen = self.pen pen.beginPath() last_offcurves = [] for i, (segment_type, points) in enumerate(segments): if segment_type in ("move", "line"): assert len(points) == 1, ( "illegal line segment point count: %d" % len(points)) pt, smooth, name, kwargs = points[0] pen.addPoint(pt, segment_type, smooth, name, **kwargs) elif segment_type == "qcurve": assert len(points) >= 2, ( "illegal qcurve segment point count: %d" % len(points)) offcurves = points[:-1] if offcurves: if i == 0: # any off-curve points preceding the first on-curve # will be appended at the end of the contour last_offcurves = offcurves else: for (pt, smooth, name, kwargs) in offcurves: pen.addPoint(pt, None, smooth, name, **kwargs) pt, smooth, name, kwargs = points[-1] pen.addPoint(pt, segment_type, smooth, name, **kwargs) else: # 'curve' segments must have been converted to 'qcurve' by now raise AssertionError("unexpected segment type: %r" % segment_type) for (pt, smooth, name, kwargs) in last_offcurves: pen.addPoint(pt, None, smooth, name, **kwargs) pen.endPath() def addComponent(self, baseGlyphName, transformation): assert self.currentPath is None self.pen.addComponent(baseGlyphName, transformation)
def drawShapeWithRectInGlyph(self, shape, rect, glyph): # draw the shape into the glyph # tell the glyph something is going to happen (undo is going to be prepared) glyph.prepareUndo("Drawing Shapes") # get the pen to draw with pen = glyph.getPointPen() if glyph.preferredSegmentType == "qcurve" and not self.shouldReverse: pen = ReverseContourPointPen(pen) elif self.shouldReverse: pen = ReverseContourPointPen(pen) x, y, w, h = rect # draw with the pen a rect in the glyph if shape == "rect": pen.beginPath() pen.addPoint(_roundPoint(x, y), "line") pen.addPoint(_roundPoint(x + w, y), "line") pen.addPoint(_roundPoint(x + w, y + h), "line") pen.addPoint(_roundPoint(x, y + h), "line") pen.endPath() # draw with the pen an oval in the glyph elif shape == "oval": hw = w / 2. hh = h / 2. r = .55 segmentType = glyph.preferredSegmentType if glyph.preferredSegmentType == "qcurve": r = .42 pen.beginPath() pen.addPoint(_roundPoint(x + hw, y), segmentType, True) pen.addPoint(_roundPoint(x + hw + hw * r, y)) pen.addPoint(_roundPoint(x + w, y + hh - hh * r)) pen.addPoint(_roundPoint(x + w, y + hh), segmentType, True) pen.addPoint(_roundPoint(x + w, y + hh + hh * r)) pen.addPoint(_roundPoint(x + hw + hw * r, y + h)) pen.addPoint(_roundPoint(x + hw, y + h), segmentType, True) pen.addPoint(_roundPoint(x + hw - hw * r, y + h)) pen.addPoint(_roundPoint(x, y + hh + hh * r)) pen.addPoint(_roundPoint(x, y + hh), segmentType, True) pen.addPoint(_roundPoint(x, y + hh - hh * r)) pen.addPoint(_roundPoint(x + hw - hw * r, y)) pen.endPath() # tell the glyph you are done with your actions so it can handle the undo properly glyph.performUndo() glyph.changed()
def convert(glyph, maxDistance, minLength, useArcLength): originalNumPoints = 0 nbPoints = 0 conts = [] for contour in glyph: conts.append([]) cmds = conts[-1] p0 = getFirstOnPoint(contour) nseg = len(contour) for s in range(nseg): seg = contour[s] if seg.type == 'line': originalNumPoints += 1 p1 = seg.points[0] cmds.append((lineto, p1)) nbPoints += 1 p0 = p1 elif seg.type == 'qcurve': #print("Should not have quadratic segment in here. Skipping.",) p0 = seg.points[-1] elif seg.type == 'curve': originalNumPoints += 3 p1, p2, p3 = seg.points pt0 = Point(p0.x, p0.y) pt1 = Point(p1.x, p1.y) pt2 = Point(p2.x, p2.y) pt3 = Point(p3.x, p3.y) qsegs = [] inputCubic = (pt0, pt1, pt2, pt3) for cubic in splitCubicOnInflection(inputCubic, minLength): qsegs = qsegs + adaptiveSmoothCubicSplit(cubic, maxDistance, minLength, useArcLength) nbQSegMinusOne = len(qsegs) - 1 smooth = True for i, qseg in enumerate(qsegs): # We have to split the quad segment because Robofont does not (seem to) support # ON-OFF-ON quadratic bezier curves. If ever Robofont can handle this, # then it would suffice to write something like: # (a0, a1, a2) = qseg # cmds.append((curveto, (a1, a2))) if i == nbQSegMinusOne: smooth = seg.smooth q1, q2 = qseg cmds.append((curveto, (q1[1], q2[1], q2[2], smooth))) #ql, qr = splitQuadratic(0.5, qseg) #cmds.append((curveto, (ql[1], qr[1], qr[2], smooth))) nbPoints += 3 p0 = p3 else: #print("Unknown segment type: "+seg.type+". Skipping.",) p0 = seg.points[-1] glyph.clearContours() glyph.preferredSegmentStyle = 'qcurve' pen = ReverseContourPointPen(glyph.getPointPen()) for cmds in conts: if cmds == []: continue pen.beginPath() for action, args in cmds: action(pen, args) pen.endPath() # Now, we make sure that each contour starts with a ON control point for contour in glyph: contour.setStartSegment(0) glyph.changed() return originalNumPoints, nbPoints