def _set_segments(glyph, segments, reverse_direction): """Draw segments as extracted by GetSegmentsPen back to a glyph.""" glyph.clearContours() pen = glyph.getPen() if reverse_direction: pen = ReverseContourPen(pen) for tag, args in segments: if tag == 'move': pen.moveTo(*args) elif tag == 'line': pen.lineTo(*args) elif tag == 'curve': pen.curveTo(*args[1:]) elif tag == 'qcurve': pen.qCurveTo(*args[1:]) elif tag == 'close': pen.closePath() elif tag == 'end': pen.endPath() else: raise AssertionError('Unhandled segment type "%s"' % tag)
class Cu2QuPen(AbstractPen): """ A filter pen to convert cubic bezier curves to quadratic b-splines using the FontTools SegmentPen protocol. Args: other_pen: another SegmentPen used to draw the transformed outline. max_err: maximum approximation error in font units. For optimal results, if you know the UPEM of the font, we recommend setting this to a value equal, or close to UPEM / 1000. reverse_direction: flip the contours' direction but keep starting point. stats: a dictionary counting the point numbers of quadratic segments. ignore_single_points: don't emit contours containing only a single point NOTE: The "ignore_single_points" argument is deprecated since v1.3.0, which dropped Robofab subpport. It's no longer needed to special-case UFO2-style anchors (aka "named points") when using ufoLib >= 2.0, as these are no longer drawn onto pens as single-point contours, but are handled separately as anchors. """ def __init__(self, other_pen, max_err, reverse_direction=False, stats=None, ignore_single_points=False): if reverse_direction: self.pen = ReverseContourPen(other_pen) else: self.pen = other_pen self.max_err = max_err self.stats = stats if ignore_single_points: import warnings warnings.warn( "ignore_single_points is deprecated and " "will be removed in future versions", UserWarning, stacklevel=2) self.ignore_single_points = ignore_single_points self.start_pt = None self.current_pt = None def _check_contour_is_open(self): if self.current_pt is None: raise AssertionError("moveTo is required") def _check_contour_is_closed(self): if self.current_pt is not None: raise AssertionError("closePath or endPath is required") def _add_moveTo(self): if self.start_pt is not None: self.pen.moveTo(self.start_pt) self.start_pt = None def moveTo(self, pt): self._check_contour_is_closed() self.start_pt = self.current_pt = pt if not self.ignore_single_points: self._add_moveTo() def lineTo(self, pt): self._check_contour_is_open() self._add_moveTo() self.pen.lineTo(pt) self.current_pt = pt def qCurveTo(self, *points): self._check_contour_is_open() n = len(points) if n == 1: self.lineTo(points[0]) elif n > 1: self._add_moveTo() self.pen.qCurveTo(*points) self.current_pt = points[-1] else: raise AssertionError("illegal qcurve segment point count: %d" % n) def _curve_to_quadratic(self, pt1, pt2, pt3): curve = (self.current_pt, pt1, pt2, pt3) quadratic = curve_to_quadratic(curve, self.max_err) if self.stats is not None: n = str(len(quadratic) - 2) self.stats[n] = self.stats.get(n, 0) + 1 self.qCurveTo(*quadratic[1:]) def curveTo(self, *points): self._check_contour_is_open() n = len(points) if n == 3: # this is the most common case, so we special-case it self._curve_to_quadratic(*points) elif n > 3: for segment in decomposeSuperBezierSegment(points): self._curve_to_quadratic(*segment) elif n == 2: self.qCurveTo(*points) elif n == 1: self.lineTo(points[0]) else: raise AssertionError("illegal curve segment point count: %d" % n) def closePath(self): self._check_contour_is_open() if self.start_pt is None: # if 'start_pt' is _not_ None, we are ignoring single-point paths self.pen.closePath() self.current_pt = self.start_pt = None def endPath(self): self._check_contour_is_open() if self.start_pt is None: self.pen.endPath() self.current_pt = self.start_pt = None def addComponent(self, glyphName, transformation): self._check_contour_is_closed() self.pen.addComponent(glyphName, transformation)