123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- # 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
|