aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/pens/qu2cuPen.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/pens/qu2cuPen.py')
-rw-r--r--Lib/fontTools/pens/qu2cuPen.py105
1 files changed, 105 insertions, 0 deletions
diff --git a/Lib/fontTools/pens/qu2cuPen.py b/Lib/fontTools/pens/qu2cuPen.py
new file mode 100644
index 00000000..7e400f98
--- /dev/null
+++ b/Lib/fontTools/pens/qu2cuPen.py
@@ -0,0 +1,105 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+# Copyright 2023 Behdad Esfahbod. 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 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:
+ # Add back implicit oncurve points
+ 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