| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | from fontTools.qu2cu import quadratic_to_curves |
| | from fontTools.pens.filterPen import ContourFilterPen |
| | from fontTools.pens.reverseContourPen import ReverseContourPen |
| | import math |
| |
|
| |
|
| | class Qu2CuPen(ContourFilterPen): |
| | """A filter pen to convert quadratic bezier splines to cubic curves |
| | 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 cubic segments. |
| | """ |
| |
|
| | def __init__( |
| | self, |
| | other_pen, |
| | max_err, |
| | all_cubic=False, |
| | reverse_direction=False, |
| | stats=None, |
| | ): |
| | if reverse_direction: |
| | other_pen = ReverseContourPen(other_pen) |
| | super().__init__(other_pen) |
| | self.all_cubic = all_cubic |
| | self.max_err = max_err |
| | self.stats = stats |
| |
|
| | def _quadratics_to_curve(self, q): |
| | curves = quadratic_to_curves(q, self.max_err, all_cubic=self.all_cubic) |
| | if self.stats is not None: |
| | for curve in curves: |
| | n = str(len(curve) - 2) |
| | self.stats[n] = self.stats.get(n, 0) + 1 |
| | for curve in curves: |
| | if len(curve) == 4: |
| | yield ("curveTo", curve[1:]) |
| | else: |
| | yield ("qCurveTo", curve[1:]) |
| |
|
| | def filterContour(self, contour): |
| | quadratics = [] |
| | currentPt = None |
| | newContour = [] |
| | for op, args in contour: |
| | if op == "qCurveTo" and ( |
| | self.all_cubic or (len(args) > 2 and args[-1] is not None) |
| | ): |
| | if args[-1] is None: |
| | raise NotImplementedError( |
| | "oncurve-less contours with all_cubic not implemented" |
| | ) |
| | quadratics.append((currentPt,) + args) |
| | else: |
| | if quadratics: |
| | newContour.extend(self._quadratics_to_curve(quadratics)) |
| | quadratics = [] |
| | newContour.append((op, args)) |
| | currentPt = args[-1] if args else None |
| | if quadratics: |
| | newContour.extend(self._quadratics_to_curve(quadratics)) |
| |
|
| | if not self.all_cubic: |
| | |
| | contour = newContour |
| | newContour = [] |
| | for op, args in contour: |
| | if op == "qCurveTo" and newContour and newContour[-1][0] == "qCurveTo": |
| | pt0 = newContour[-1][1][-2] |
| | pt1 = newContour[-1][1][-1] |
| | pt2 = args[0] |
| | if ( |
| | pt1 is not None |
| | and math.isclose(pt2[0] - pt1[0], pt1[0] - pt0[0]) |
| | and math.isclose(pt2[1] - pt1[1], pt1[1] - pt0[1]) |
| | ): |
| | newArgs = newContour[-1][1][:-1] + args |
| | newContour[-1] = (op, newArgs) |
| | continue |
| |
|
| | newContour.append((op, args)) |
| |
|
| | return newContour |
| |
|